Refactor: Rename all database references to Knowledge across backend and frontend services, updating endpoints, workflows, and UI terminology.

This commit is contained in:
2026-06-16 23:37:33 +02:00
parent 65277acd5e
commit adb7ba7f8e
6 changed files with 57 additions and 76 deletions

View File

@@ -274,7 +274,7 @@ RESEARCH = graph(
)
BUILTIN_WORKFLOWS = [
{"slug": "input-output", "name": "Input -> Output", "description": "Normal Heimgeist chat with optional compatibility context.", "routing_description": "Choose only when no external tool is needed: conversational replies, writing, editing, reasoning, brainstorming, explanation, or stable knowledge the model can answer without retrieval. Do not choose when the standalone request needs facts outside the conversation/model context, source grounding, live or recently changed information, local database retrieval, attachment analysis, or knowledge-saving actions.", "routing_examples": ["Explain this concept", "Draft a reply", "Rewrite this paragraph", "What do you mean by that?"], "estimated_cost_class": "low", "required_capabilities": ["chat"], "graph": DIRECT},
{"slug": "input-output", "name": "Input -> Output", "description": "Normal Heimgeist chat with optional compatibility context.", "routing_description": "Choose only when no external tool is needed: conversational replies, writing, editing, reasoning, brainstorming, explanation, or stable knowledge the model can answer without retrieval. Do not choose when the standalone request needs facts outside the conversation/model context, source grounding, live local knowledge retrieval, attachment analysis, or knowledge-saving actions.", "routing_examples": ["Explain this concept", "Draft a reply", "Rewrite this paragraph", "What do you mean by that?"], "estimated_cost_class": "low", "required_capabilities": ["chat"], "graph": DIRECT},
{"slug": "chat-memory-answer", "name": "Chat Memory Answer", "description": "Search previous Heimgeist chats and answer from matching turns.", "routing_description": "Choose when the standalone request asks Heimgeist to use past chats, earlier conversations, remembered discussion context, previous decisions, or topics discussed before. This workflow searches previous chat turns before answering. Do not choose for general factual questions that merely resemble something discussed before; choose Web Answer, Knowledge Answer, or direct chat based on the requested evidence.", "routing_examples": ["What music did we talk about before?", "What did we decide earlier about packaging?", "Use our previous chat about Ollama settings"], "estimated_cost_class": "medium", "required_capabilities": ["chat_memory", "chat"], "graph": CHAT_MEMORY},
{"slug": "recent-chat-memory-answer", "name": "Recent Chat Memory Answer", "description": "Answer from the most recent previous Heimgeist chat.", "routing_description": "Choose when the standalone request specifically asks about the last, previous, latest, or most recent earlier Heimgeist chat or conversation. This workflow retrieves the most recent previous chat turn instead of doing a relevance search.", "routing_examples": ["What was the last conversation about?", "Worum ging es im letzten Gespräch?", "Continue from the previous chat"], "estimated_cost_class": "medium", "required_capabilities": ["chat_memory", "chat"], "graph": RECENT_CHAT_MEMORY},
{"slug": "knowledge-answer", "name": "Knowledge Answer", "description": "Answer from local knowledge.", "routing_description": "Choose when the standalone request asks about or should be grounded in Heimgeist's local Knowledge store, and current web evidence is not required. This workflow retrieves local knowledge before answering.", "routing_examples": ["What do my notes say about this?", "Summarize the saved knowledge entry"], "estimated_cost_class": "medium", "required_capabilities": ["rag"], "graph": KNOWLEDGE},
@@ -282,7 +282,7 @@ BUILTIN_WORKFLOWS = [
{"slug": "vision-answer", "name": "Vision Answer", "description": "Analyze attachments and answer with a vision model.", "routing_description": "Use when image or document attachments must be analyzed.", "routing_examples": ["What is shown in this image?"], "estimated_cost_class": "medium", "required_capabilities": ["vision"], "graph": VISION},
{"slug": "knowledge-web-answer", "name": "Knowledge + Web Answer", "description": "Combine local knowledge and current web evidence.", "routing_description": "Choose when both capabilities are needed: Heimgeist's local Knowledge store is relevant, and the standalone request also requires web evidence or updated external facts.", "routing_examples": ["Compare my notes with the latest information", "Check my saved source against current web evidence"], "estimated_cost_class": "high", "required_capabilities": ["rag", "web"], "graph": KNOWLEDGE_WEB},
{"slug": "remember-this", "name": "Remember This", "description": "Save a selected chat message to knowledge.", "routing_description": "Use only when the user explicitly asks to remember or save a chat message.", "routing_examples": ["Remember this answer"], "estimated_cost_class": "low", "required_capabilities": ["knowledge_write"], "graph": REMEMBER},
{"slug": "save-source", "name": "Save Source", "description": "Save a website snapshot to knowledge.", "routing_description": "Use when the user explicitly asks to save a URL as a knowledge source.", "routing_examples": ["Save this website to my database"], "estimated_cost_class": "low", "required_capabilities": ["web", "knowledge_write"], "graph": SAVE_SOURCE},
{"slug": "save-source", "name": "Save Source", "description": "Save a website snapshot to knowledge.", "routing_description": "Use when the user explicitly asks to save a URL as a knowledge source.", "routing_examples": ["Save this website to Knowledge"], "estimated_cost_class": "low", "required_capabilities": ["web", "knowledge_write"], "graph": SAVE_SOURCE},
{"slug": "research", "name": "Research", "description": "Bounded two-round evidence research with source validation.", "routing_description": "Choose only when the standalone request needs an investigation rather than a direct lookup: compare sources, resolve uncertainty, validate claims, synthesize several angles, or perform a second search if first-pass evidence is insufficient. Do not choose this solely because web evidence is needed, because the topic is important, or because information is current. Choose Web Answer for direct web-grounded questions.", "routing_examples": ["Research the competing explanations and cite sources", "Compare the strongest evidence for both claims", "Investigate this topic in depth"], "estimated_cost_class": "high", "required_capabilities": ["web", "chat"], "graph": RESEARCH},
]

View File

@@ -619,7 +619,7 @@ def _entry_dedupe_key(entry: Dict[str, Any]) -> Optional[tuple[str, str]]:
def _merge_entry_origin(entry: Dict[str, Any], source_library: Dict[str, Any]) -> None:
slug = str(source_library.get("slug") or "").strip()
name = str(source_library.get("name") or slug or "Imported database").strip()
name = str(source_library.get("name") or slug or "Imported collection").strip()
if not slug:
return
entry.setdefault("origin_library_slug", slug)
@@ -1326,7 +1326,7 @@ def _run_prepare_pipeline(slug: str, on_progress=None, **opts):
corpus_signature = payload.get("corpus_signature")
prepare_signature = payload.get("prepare_signature")
if not files or not source_signature or not corpus_signature or not prepare_signature:
raise RuntimeError("Add files before preparing this database.")
raise RuntimeError("Add files before preparing Knowledge.")
paths = _collect_library_paths(slug)
states = dict(payload.get("states") or {})
@@ -1337,7 +1337,7 @@ def _run_prepare_pipeline(slug: str, on_progress=None, **opts):
embed_model = opts.get("embed_model") or _default_embed_model() or pipeline.get("embed_model") or DEFAULT_EMBED_MODEL
if on_progress:
on_progress("prepare", 0.01, "Preparing database for chat...")
on_progress("prepare", 0.01, "Preparing Knowledge for chat...")
if not states.get("has_corpus"):
build_progress = _scaled_progress(on_progress, 0.02, 0.34, "Reading files") if on_progress else None
@@ -1643,9 +1643,9 @@ def purge_libraries():
]
if active_jobs:
active_slugs = sorted({str(job.get("slug") or "") for job in active_jobs if job.get("slug")})
detail = "Cannot purge databases while library sync jobs are still running."
detail = "Cannot purge Knowledge while sync jobs are still running."
if active_slugs:
detail = f"{detail} Active databases: {', '.join(active_slugs)}."
detail = f"{detail} Active jobs: {', '.join(active_slugs)}."
raise HTTPException(status_code=409, detail=detail)
removed: List[str] = []
@@ -1669,7 +1669,7 @@ def purge_libraries():
preview = "; ".join(failures[:3])
if len(failures) > 3:
preview = f"{preview}; ..."
raise HTTPException(status_code=500, detail=f"Failed to purge some databases. {preview}")
raise HTTPException(status_code=500, detail=f"Failed to purge some Knowledge data. {preview}")
return {
"ok": True,
@@ -1959,7 +1959,7 @@ async def create_library_website(slug: str, req: CreateWebsiteRequest):
None,
)
if duplicate:
raise HTTPException(status_code=409, detail="This video is already saved in the database.")
raise HTTPException(status_code=409, detail="This video is already saved in Knowledge.")
item_id = uuid.uuid4().hex
source_path = _managed_source_path(slug, item_id)
@@ -2027,7 +2027,7 @@ async def create_library_website(slug: str, req: CreateWebsiteRequest):
None,
)
if duplicate:
raise HTTPException(status_code=409, detail="This website is already saved in the database.")
raise HTTPException(status_code=409, detail="This website is already saved in Knowledge.")
item_id = uuid.uuid4().hex
source_path = _managed_source_path(slug, item_id)
@@ -2305,7 +2305,7 @@ async def prepare_library(slug: str):
data = read_library(slug)
payload = library_payload(data)
if not payload["states"].get("has_files"):
raise HTTPException(status_code=400, detail="Add files before preparing this database.")
raise HTTPException(status_code=400, detail="Add files before preparing Knowledge.")
lock = LIB_LOCKS.setdefault(slug, asyncio.Lock())
async with lock:
if _has_active_job(slug):

View File

@@ -1130,7 +1130,7 @@ export default function App() {
setKnowledgeSaveTarget(null)
await refreshLibraries()
await refreshLibraryJobs()
showKnowledgeSaveToast(result?.already_saved ? 'This message is already saved in that database.' : 'Message saved to knowledge.')
showKnowledgeSaveToast(result?.already_saved ? 'This message is already saved in Knowledge.' : 'Message saved to Knowledge.')
}
function openChatMessageSource(item) {
@@ -1431,7 +1431,7 @@ async function createNewChat() {
className={`sidebar-tab ${activeSidebarMode === 'dbs' ? 'active' : ''}`}
onClick={() => handleSidebarClick('dbs')}
>
DBs
Knowledge
</div>
<div
className={`sidebar-tab ${activeSidebarMode === 'workflows' ? 'active' : ''}`}

View File

@@ -560,7 +560,7 @@ export default function GeneralSettings({
const handlePurgeLibraries = async () => {
const confirmed = window.confirm(
'Delete all Heimgeist databases, staged files, and indexes from local storage? Chat history will be kept.'
'Delete Heimgeist Knowledge, staged files, and indexes from local storage? Chat history will be kept.'
);
if (!confirmed) {
return;
@@ -570,7 +570,7 @@ export default function GeneralSettings({
setLibraryPurgeStatus({ tone: 'neutral', message: '' });
try {
const response = await fetch(`${backendApiUrl}/libraries/purge`, {
const response = await fetch(`${backendApiUrl}/knowledge/purge`, {
method: 'POST',
});
const data = await response.json().catch(() => null);
@@ -583,8 +583,8 @@ export default function GeneralSettings({
setLibraryPurgeStatus({
tone: 'success',
message: count > 0
? `Removed ${count} database${count === 1 ? '' : 's'} from local storage.`
: 'No local databases were found to remove.',
? 'Removed local Knowledge data from storage.'
: 'No local Knowledge data was found to remove.',
});
if (onLibrariesPurged) {
@@ -593,7 +593,7 @@ export default function GeneralSettings({
} catch (error) {
setLibraryPurgeStatus({
tone: 'error',
message: `Database purge failed: ${error.message || String(error)}`,
message: `Knowledge purge failed: ${error.message || String(error)}`,
});
} finally {
setIsPurgingLibraries(false);
@@ -693,7 +693,7 @@ export default function GeneralSettings({
))}
</select>
<p className="setting-description">
Heimgeist uses this model for building or rebuilding local database embeddings.
Heimgeist uses this model for building or rebuilding local Knowledge embeddings.
</p>
</div>
<div className="setting-section">
@@ -917,7 +917,7 @@ export default function GeneralSettings({
</p>
</div>
<div className="setting-section danger-zone">
<h3>Purge Databases</h3>
<h3>Purge Knowledge</h3>
<div className="setting-control-row">
<button
type="button"
@@ -925,11 +925,11 @@ export default function GeneralSettings({
onClick={handlePurgeLibraries}
disabled={isPurgingLibraries || !backendApiUrl}
>
{isPurgingLibraries ? 'Purging...' : 'Delete All Databases'}
{isPurgingLibraries ? 'Purging...' : 'Delete Knowledge'}
</button>
</div>
<p className="setting-description">
Removes every local Heimgeist database, including staged files, corpora, and indexes. This is meant as a recovery action when the DB panel becomes unusable. Chat history stays intact.
Removes local Heimgeist Knowledge, including staged files, corpora, and indexes. This is meant as a recovery action when the Knowledge panel becomes unusable. Chat history stays intact.
</p>
{libraryPurgeStatus.message && (
<p className={`setting-status ${libraryPurgeStatus.tone}`}>

View File

@@ -40,7 +40,7 @@ function itemSyncMeta(item) {
status,
progress,
label: progress > 0 ? `Syncing ${Math.round(progress)}%` : 'Syncing',
detail: detail || 'Rebuilding the database search indexes.'
detail: detail || 'Rebuilding Knowledge search indexes.'
}
}
return {
@@ -145,7 +145,7 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
if (contentPreviews[itemId]) return
setContentPreviews(current => ({ ...current, [itemId]: { loading: true } }))
try {
const response = await fetch(`${apiBase}/libraries/${library.slug}/items/${itemId}`)
const response = await fetch(`${apiBase}/knowledge/items/${itemId}`)
const data = await expectJson(response)
setContentPreviews(current => ({ ...current, [itemId]: { data } }))
} catch (error) {
@@ -178,13 +178,13 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
if (!Array.isArray(paths) || paths.length === 0) return
try {
await runAction(async () => {
const response = await fetch(`${apiBase}/libraries/${library.slug}/files/register`, {
const response = await fetch(`${apiBase}/knowledge/files/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paths })
})
return expectJson(response)
}, 'Files added. Heimgeist is updating the database.')
}, 'Files added. Heimgeist is updating Knowledge.')
} catch {}
}
@@ -196,8 +196,8 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
const editing = Boolean(editingItemId)
const response = await fetch(
editing
? `${apiBase}/libraries/${library.slug}/texts/${editingItemId}`
: `${apiBase}/libraries/${library.slug}/texts`,
? `${apiBase}/knowledge/texts/${editingItemId}`
: `${apiBase}/knowledge/texts`,
{
method: editing ? 'PATCH' : 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -205,7 +205,7 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
}
)
return expectJson(response)
}, editingItemId ? 'Text updated.' : 'Text added to the database.')
}, editingItemId ? 'Text updated.' : 'Text added to Knowledge.')
if (editingItemId) {
setContentPreviews(current => {
const next = { ...current }
@@ -223,7 +223,7 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
setBusy(true)
setErrorMessage('')
try {
const response = await fetch(`${apiBase}/libraries/${library.slug}/items/${item.item_id}`)
const response = await fetch(`${apiBase}/knowledge/items/${item.item_id}`)
const data = await expectJson(response)
setEditingItemId(item.item_id)
setTextTitle(data.title || item.title || item.name || '')
@@ -242,7 +242,7 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
setWebsiteProcessing(true)
try {
const result = await runAction(async () => {
const response = await fetch(`${apiBase}/libraries/${library.slug}/websites`, {
const response = await fetch(`${apiBase}/knowledge/websites`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: websiteUrl, title: websiteTitle || null })
@@ -266,7 +266,7 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
if (!library || !item?.item_id) return
try {
const result = await runAction(async () => {
const response = await fetch(`${apiBase}/libraries/${library.slug}/websites/${item.item_id}/refresh`, {
const response = await fetch(`${apiBase}/knowledge/websites/${item.item_id}/refresh`, {
method: 'POST'
})
return expectJson(response)
@@ -286,7 +286,7 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
setWebsiteProcessing(true)
try {
const result = await runAction(async () => {
const response = await fetch(`${apiBase}/libraries/${library.slug}/videos/${item.item_id}/refresh`, {
const response = await fetch(`${apiBase}/knowledge/videos/${item.item_id}/refresh`, {
method: 'POST'
})
return expectJson(response)
@@ -308,7 +308,7 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
if (!library) return
try {
await runAction(async () => {
const response = await fetch(`${apiBase}/libraries/${library.slug}/files`, {
const response = await fetch(`${apiBase}/knowledge/files`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rel: item.rel })
@@ -322,7 +322,7 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
if (!library) return
try {
await runAction(async () => {
const response = await fetch(`${apiBase}/libraries/${library.slug}/files/enrichment`, {
const response = await fetch(`${apiBase}/knowledge/files/enrichment`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rel: item.rel, enabled })
@@ -336,9 +336,9 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
if (!library) return
try {
await runAction(async () => {
const response = await fetch(`${apiBase}/libraries/${library.slug}/jobs/prepare`, { method: 'POST' })
const response = await fetch(`${apiBase}/knowledge/jobs/prepare`, { method: 'POST' })
return expectJson(response)
}, 'Generating metadata for this database.')
}, 'Generating metadata for Knowledge.')
} catch {}
}
@@ -346,7 +346,7 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
if (!library) return
try {
await runAction(async () => {
const response = await fetch(`${apiBase}/libraries/${library.slug}/jobs/prepare`, { method: 'POST' })
const response = await fetch(`${apiBase}/knowledge/jobs/prepare`, { method: 'POST' })
return expectJson(response)
})
} catch {}
@@ -400,16 +400,16 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
return
}
if (!previousState.isSyncing && nextState.isSyncing) {
queueToast('Syncing this database. Heimgeist is rebuilding its search indexes.')
queueToast('Syncing Knowledge. Heimgeist is rebuilding its search indexes.')
} else if (previousState.isSyncing && !nextState.isSyncing) {
if (nextState.hasFailedItems) queueToast('Some content did not finish syncing.', 'warning')
else if (nextState.isReadyForChat) queueToast('Sync complete. This database is ready in chat.', 'success')
else if (nextState.isReadyForChat) queueToast('Sync complete. Knowledge is ready in chat.', 'success')
}
previousLibraryStateRef.current = nextState
}, [library?.slug, items.length, hasFailedItems, isReadyForChat, isSyncing])
if (!library) {
return <div className="placeholder-view"><p>Create a database, then add files, your own texts, or websites.</p></div>
return <div className="placeholder-view"><p>Add files, your own texts, or websites to Knowledge.</p></div>
}
return (
@@ -465,14 +465,14 @@ export default function LibraryManager({ apiBase, library, jobs, onOpenChatMessa
<div className="library-content-heading">
<div>
<h2>Contents</h2>
<p className="muted-copy">Files, texts, website snapshots, videos, and saved chat messages are searched together when this database is selected.</p>
<p className="muted-copy">Files, texts, website snapshots, videos, and saved chat messages are searched together through Knowledge workflows.</p>
</div>
<input
className="library-search"
value={search}
onChange={event => setSearch(event.target.value)}
placeholder="Search contents"
aria-label="Search database contents"
aria-label="Search Knowledge contents"
/>
</div>

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react'
import React, { useEffect, useState } from 'react'
import { splitThinkBlocks } from './chatText'
function cleanAssistantContent(content) {
@@ -46,18 +46,11 @@ async function expectJson(response) {
export default function SaveMessageToKnowledgeDialog({
apiBase,
defaultLibrarySlug,
libraries,
message,
onClose,
onSaved,
precedingUserMessage,
}) {
const initialLibrarySlug = useMemo(() => {
if (libraries.some(library => library.slug === defaultLibrarySlug)) return defaultLibrarySlug
return libraries[0]?.slug || ''
}, [defaultLibrarySlug, libraries])
const [librarySlug, setLibrarySlug] = useState(initialLibrarySlug)
const [title, setTitle] = useState(() => defaultTitle(message))
const [content, setContent] = useState(() => defaultKnowledgeContent(message, precedingUserMessage))
const [busy, setBusy] = useState(false)
@@ -73,11 +66,11 @@ export default function SaveMessageToKnowledgeDialog({
async function submit(event) {
event.preventDefault()
if (!librarySlug || !message?.message_id) return
if (!message?.message_id) return
setBusy(true)
setError('')
try {
const response = await fetch(`${apiBase}/libraries/${librarySlug}/chat-messages`, {
const response = await fetch(`${apiBase}/knowledge/chat-messages`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -103,39 +96,27 @@ export default function SaveMessageToKnowledgeDialog({
<div className="knowledge-save-header">
<div>
<strong id="knowledge-save-title">Save message to knowledge</strong>
<p>The saved snapshot becomes a normal RAG item in the selected database.</p>
<p>The saved snapshot becomes a normal item in Knowledge.</p>
</div>
<button type="button" className="icon-button" title="Close" aria-label="Close" onClick={onClose} disabled={busy}>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M18 6 6 18M6 6l12 12" /></svg>
</button>
</div>
{libraries.length === 0 ? (
<div className="form-error">Create a database before saving chat knowledge.</div>
) : (
<>
<label>
Database
<select value={librarySlug} onChange={event => setLibrarySlug(event.target.value)} disabled={busy}>
{libraries.map(library => <option key={library.slug} value={library.slug}>{library.name}</option>)}
</select>
</label>
<label>
Title
<input value={title} onChange={event => setTitle(event.target.value)} required autoFocus disabled={busy} />
</label>
<label>
Knowledge content
<textarea value={content} onChange={event => setContent(event.target.value)} rows={12} required disabled={busy} />
</label>
</>
)}
<label>
Title
<input value={title} onChange={event => setTitle(event.target.value)} required autoFocus disabled={busy} />
</label>
<label>
Knowledge content
<textarea value={content} onChange={event => setContent(event.target.value)} rows={12} required disabled={busy} />
</label>
{error && <div className="form-error">{error}</div>}
<div className="knowledge-save-actions">
<button type="button" className="button ghost" onClick={onClose} disabled={busy}>Cancel</button>
<button type="submit" className="button" disabled={busy || libraries.length === 0 || !librarySlug || !title.trim() || !content.trim()}>
{busy ? 'Saving...' : 'Save to Database'}
<button type="submit" className="button" disabled={busy || !title.trim() || !content.trim()}>
{busy ? 'Saving...' : 'Save to Knowledge'}
</button>
</div>
</form>