Feature: Implement knowledge base saving for chat messages

Adds functionality to save selected chat messages into an external knowledge database, including new dialog components, state management in App.jsx, updated history syncing, and necessary UI/UX improvements.
This commit is contained in:
2026-06-15 03:53:10 +02:00
parent d0e5c9300c
commit 4db6fde9be
5 changed files with 343 additions and 18 deletions

View File

@@ -54,6 +54,7 @@ function itemSyncMeta(item) {
function kindLabel(item) {
if (item?.kind === 'text') return 'Text'
if (item?.kind === 'website') return 'Website'
if (item?.kind === 'chat_message') return 'Chat Message'
return 'File'
}
@@ -69,7 +70,7 @@ async function expectJson(response) {
return data
}
export default function LibraryManager({ apiBase, library, jobs, onRefresh }) {
export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessage, onRefresh }) {
const [busy, setBusy] = useState(false)
const [errorMessage, setErrorMessage] = useState('')
const [toasts, setToasts] = useState([])
@@ -332,6 +333,8 @@ export default function LibraryManager({ apiBase, library, jobs, onRefresh }) {
item.path,
item.url,
item.kind,
item.source_session_title,
item.source_role,
item.metadata?.headline,
item.metadata?.summary,
...(item.metadata?.keywords || []),
@@ -437,7 +440,13 @@ export default function LibraryManager({ apiBase, library, jobs, onRefresh }) {
{filteredItems.map(item => {
const sync = itemSyncMeta(item)
const label = kindLabel(item)
const source = item.kind === 'website' ? item.url : item.kind === 'text' ? 'Written in Heimgeist' : item.path
const source = item.kind === 'website'
? item.url
: item.kind === 'text'
? 'Written in Heimgeist'
: item.kind === 'chat_message'
? `${item.source_role === 'user' ? 'User message' : 'Assistant message'} · ${item.source_session_title || 'Chat'}`
: item.path
const itemId = item.item_id || item.sha256 || item.rel
const isExpanded = expandedItemId === itemId
const preview = contentPreviews[itemId]
@@ -509,7 +518,7 @@ export default function LibraryManager({ apiBase, library, jobs, onRefresh }) {
{isExpanded && (
<div className="library-content-preview">
<div className="library-content-preview-label">
{item.kind === 'website' ? 'Saved website text' : item.kind === 'text' ? 'Stored text' : 'Extracted text used by RAG'}
{item.kind === 'website' ? 'Saved website text' : item.kind === 'chat_message' ? 'Stored chat knowledge' : item.kind === 'text' ? 'Stored text' : 'Extracted text used by RAG'}
</div>
{preview?.loading && <div className="muted-copy">Loading content</div>}
{preview?.error && <div className="form-error">{preview.error}</div>}
@@ -525,7 +534,8 @@ export default function LibraryManager({ apiBase, library, jobs, onRefresh }) {
<button className="button ghost" disabled={busy} onClick={() => toggleContentPreview(item)}>
{isExpanded ? 'Hide Content' : 'View Content'}
</button>
{item.kind === 'text' && <button className="button ghost" disabled={busy} onClick={() => editText(item)}>Edit</button>}
{(item.kind === 'text' || item.kind === 'chat_message') && <button className="button ghost" disabled={busy} onClick={() => editText(item)}>Edit</button>}
{item.kind === 'chat_message' && <button className="button ghost" onClick={() => onOpenChatMessage?.(item)}>Open Chat</button>}
{item.kind === 'website' && <button className="button ghost" onClick={() => desktopApi.openExternalLink(item.url)}>Open</button>}
{item.kind === 'website' && <button className="button ghost" disabled={busy} onClick={() => refreshWebsite(item)}>Refresh</button>}
{(!item.kind || item.kind === 'file') && <button className="button ghost" onClick={() => desktopApi.openPath(item.path)}>Open</button>}