Deprecate explicit chat database selection components and hooks, standardizing workflow confirmation dialogs to reference 'Knowledge' instead of specific libraries.

This commit is contained in:
2026-06-16 23:38:30 +02:00
parent adb7ba7f8e
commit 124581744c
5 changed files with 2 additions and 228 deletions

View File

@@ -1,101 +0,0 @@
import { useEffect, useRef, useState } from 'react'
export default function ChatDatabasePicker({
activeSessionId,
chatLibrary,
chatLibrarySlug,
chatLibraryStatusSuffix,
isLibrarySyncing,
libraries,
setChatLibraryForSession,
}) {
const dbPickerRef = useRef(null)
const [isDbPickerOpen, setIsDbPickerOpen] = useState(false)
useEffect(() => {
if (!isDbPickerOpen) return
const onPointerDown = (event) => {
if (!dbPickerRef.current?.contains(event.target)) {
setIsDbPickerOpen(false)
}
}
document.addEventListener('mousedown', onPointerDown)
return () => document.removeEventListener('mousedown', onPointerDown)
}, [isDbPickerOpen])
useEffect(() => {
setIsDbPickerOpen(false)
}, [activeSessionId])
return (
<div className="footer-tool-group" ref={dbPickerRef}>
<button
type="button"
className={"db-picker-toggle" + (chatLibrary ? " active" : "")}
onClick={() => {
if (!activeSessionId) return
setIsDbPickerOpen(prev => !prev)
}}
title={chatLibrary ? `Database: ${chatLibrary.name}${chatLibraryStatusSuffix}` : 'Select database for this chat'}
aria-haspopup="menu"
aria-expanded={isDbPickerOpen}
disabled={!activeSessionId}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
aria-hidden="true">
<ellipse cx="12" cy="5" rx="8" ry="3"/>
<path d="M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5"/>
<path d="M4 11v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"/>
</svg>
</button>
{isDbPickerOpen && (
<div className="db-picker-menu" role="menu">
<button
type="button"
className={"db-picker-option" + (!chatLibrarySlug ? " selected" : "")}
onClick={() => {
setChatLibraryForSession(activeSessionId, null)
setIsDbPickerOpen(false)
}}
>
<span>No database</span>
{!chatLibrarySlug && <span className="db-picker-status">Selected</span>}
</button>
{libraries.length === 0 ? (
<div className="db-picker-empty">No databases yet.</div>
) : (
libraries.map(library => {
const selected = chatLibrarySlug === library.slug
const syncing = isLibrarySyncing(library.slug)
const status = !library.files?.length
? 'Empty'
: library.states?.is_indexed
? 'Ready'
: syncing
? 'Syncing'
: 'Needs sync'
return (
<button
key={library.slug}
type="button"
className={"db-picker-option" + (selected ? " selected" : "")}
onClick={() => {
setChatLibraryForSession(activeSessionId, library.slug)
setIsDbPickerOpen(false)
}}
>
<span>{library.name}</span>
<span className="db-picker-status">{selected ? 'Selected' : status}</span>
</button>
)
})
)}
</div>
)}
</div>
)
}

View File

@@ -70,7 +70,7 @@ return (
onChange={handleBackendUrlChange}
placeholder={`e.g., ${DEFAULT_BACKEND_API_URL}`}
/>
<p className="setting-description">Internal UI requests like chats, sessions, and databases go to this URL.</p>
<p className="setting-description">Internal UI requests like chats, sessions, and Knowledge go to this URL.</p>
</div>
<div className="setting-section">

View File

@@ -2,7 +2,6 @@ export const API_URL_KEY = 'backendApiUrl'
export const COLOR_SCHEME_KEY = 'colorScheme'
export const WEBSEARCH_URL_KEY = 'websearch.searxUrl'
export const WEBSEARCH_ENGINES_KEY = 'websearch.engines'
export const CHAT_LIBRARY_MAP_KEY = 'chat.libraryBySession'
export const CHAT_WORKFLOW_MAP_KEY = 'chat.workflowBySession'
export const DEFAULT_SEARX_URL = 'http://127.0.0.1:8888'
export const DEFAULT_BACKEND_API_URL = import.meta.env.VITE_API_URL ?? 'http://127.0.0.1:8000'

View File

@@ -1,124 +0,0 @@
import { useEffect, useMemo, useState } from 'react'
import { CHAT_LIBRARY_MAP_KEY } from './appConfig'
function libraryJobIsActive(job) {
return job?.status === 'queued' || job?.status === 'running'
}
function getLibraryStatusSuffix(library, hasActiveJob) {
if (!library) return ''
if (!library.files?.length) return ' (empty)'
if (library.states?.is_indexed) return ''
return hasActiveJob ? ' (syncing)' : ' (needs sync)'
}
function loadChatLibraryMap() {
try {
const raw = localStorage.getItem(CHAT_LIBRARY_MAP_KEY)
return raw ? JSON.parse(raw) : {}
} catch {
return {}
}
}
export function useChatLibrarySelection({ activeSessionId, libraries, libraryJobs }) {
const [chatLibraryBySession, setChatLibraryBySession] = useState(loadChatLibraryMap)
useEffect(() => {
try {
localStorage.setItem(CHAT_LIBRARY_MAP_KEY, JSON.stringify(chatLibraryBySession || {}))
} catch {}
}, [chatLibraryBySession])
useEffect(() => {
const validSlugs = new Set(libraries.map(library => library.slug))
setChatLibraryBySession(prev => {
let changed = false
const next = {}
for (const [sessionId, slug] of Object.entries(prev || {})) {
if (validSlugs.has(slug)) {
next[sessionId] = slug
} else {
changed = true
}
}
return changed ? next : prev
})
}, [libraries])
const chatLibrarySlug = activeSessionId ? (chatLibraryBySession[activeSessionId] || null) : null
const chatLibrary = useMemo(() => {
return libraries.find(lib => lib.slug === chatLibrarySlug) || null
}, [chatLibrarySlug, libraries])
const chatLibraryHasActiveJob = useMemo(() => {
if (!chatLibrarySlug) return false
return libraryJobs.some(job => job.slug === chatLibrarySlug && libraryJobIsActive(job))
}, [chatLibrarySlug, libraryJobs])
const chatLibraryStatusSuffix = useMemo(() => {
return getLibraryStatusSuffix(chatLibrary, chatLibraryHasActiveJob)
}, [chatLibrary, chatLibraryHasActiveJob])
function getChatLibrarySlugForSession(sessionId) {
if (!sessionId) return null
return chatLibraryBySession[sessionId] || null
}
function getChatLibraryForSession(sessionId) {
const slug = getChatLibrarySlugForSession(sessionId)
if (!slug) return null
return libraries.find(lib => lib.slug === slug) || null
}
function isLibrarySyncing(slug) {
if (!slug) return false
return libraryJobs.some(job => job.slug === slug && libraryJobIsActive(job))
}
function setChatLibraryForSession(sessionId, slug) {
if (!sessionId) return
setChatLibraryBySession(prev => {
const next = { ...(prev || {}) }
if (slug) {
next[sessionId] = slug
} else {
delete next[sessionId]
}
return next
})
}
function removeLibraryFromChatSelections(slug) {
if (!slug) return
setChatLibraryBySession(prev => {
let changed = false
const next = {}
for (const [sessionId, librarySlug] of Object.entries(prev || {})) {
if (librarySlug === slug) {
changed = true
continue
}
next[sessionId] = librarySlug
}
return changed ? next : prev
})
}
function clearChatLibrarySelections() {
setChatLibraryBySession({})
}
return {
chatLibrary,
chatLibraryBySession,
chatLibrarySlug,
chatLibraryStatusSuffix,
clearChatLibrarySelections,
getChatLibraryForSession,
isLibrarySyncing,
removeLibraryFromChatSelections,
setChatLibraryForSession,
}
}

View File

@@ -8,7 +8,7 @@ export default function WorkflowConfirmationDialog({ confirmation, onRespond })
<div className="workflow-confirmation-dialog">
<h3>Confirm workflow action</h3>
<p>This workflow wants to run <code>{confirmation.payload?.tool}</code>.</p>
{args.library_slug && <p><strong>Destination:</strong> {args.library_slug}</p>}
{args.library_slug && <p><strong>Destination:</strong> Knowledge</p>}
{args.title && <p><strong>Title:</strong> {args.title}</p>}
{args.message_id && <p><strong>Message:</strong> {args.message_id}</p>}
{args.url && <p><strong>URL:</strong> {args.url}</p>}