diff --git a/backend/agent/workflows/builtins.py b/backend/agent/workflows/builtins.py index 0e04dfb..56fe559 100644 --- a/backend/agent/workflows/builtins.py +++ b/backend/agent/workflows/builtins.py @@ -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}, ] diff --git a/backend/local_rag.py b/backend/local_rag.py index 74528f6..5ca95be 100644 --- a/backend/local_rag.py +++ b/backend/local_rag.py @@ -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): diff --git a/src/App.jsx b/src/App.jsx index 074ab4c..91d77d3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -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
- Heimgeist uses this model for building or rebuilding local database embeddings. + Heimgeist uses this model for building or rebuilding local Knowledge embeddings.
- 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.
{libraryPurgeStatus.message && (diff --git a/src/LibraryManager.jsx b/src/LibraryManager.jsx index 92c2270..baa6f95 100644 --- a/src/LibraryManager.jsx +++ b/src/LibraryManager.jsx @@ -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
Create a database, then add files, your own texts, or websites.
Add files, your own texts, or websites to Knowledge.
Files, texts, website snapshots, videos, and saved chat messages are searched together when this database is selected.
+Files, texts, website snapshots, videos, and saved chat messages are searched together through Knowledge workflows.
The saved snapshot becomes a normal RAG item in the selected database.
+The saved snapshot becomes a normal item in Knowledge.