Add global knowledge base with RAG search

- KB module: fastembed (AllMiniLML6V2) for CPU embedding, SQLite for
  vector storage with brute-force cosine similarity search
- Chunking by ## headings, embeddings stored as BLOB in kb_chunks table
- API: GET/PUT /api/kb for full-text read/write with auto re-indexing
- Agent tools: kb_search (top-5 semantic search) and kb_read (full text)
  available in both planning and execution phases
- Frontend: Settings menu in sidebar footer, KB editor as independent
  view with markdown textarea and save button
- Also: extract shared db_err/ApiResult to api/mod.rs, add context
  management design doc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 08:15:50 +00:00
parent 1aa81896b5
commit d9d3bc340c
19 changed files with 2283 additions and 53 deletions

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue'
import type { Project } from '../types'
defineProps<{
@@ -10,14 +11,22 @@ const emit = defineEmits<{
select: [id: string]
create: []
delete: [id: string]
openKb: []
}>()
const showSettings = ref(false)
function onDelete(e: Event, id: string) {
e.stopPropagation()
if (confirm('确定删除这个项目?')) {
emit('delete', id)
}
}
function onOpenKb() {
showSettings.value = false
emit('openKb')
}
</script>
<template>
@@ -41,6 +50,14 @@ function onDelete(e: Event, id: string) {
<span class="project-time">{{ new Date(project.updated_at).toLocaleDateString() }}</span>
</div>
</nav>
<div class="sidebar-footer">
<div class="settings-wrapper">
<button class="btn-settings" @click="showSettings = !showSettings">Settings</button>
<div v-if="showSettings" class="settings-menu">
<button class="settings-item" @click="onOpenKb">Knowledge Base</button>
</div>
</div>
</div>
</aside>
</template>
@@ -153,4 +170,59 @@ function onDelete(e: Event, id: string) {
font-size: 11px;
color: var(--text-secondary);
}
.sidebar-footer {
padding: 12px 16px;
border-top: 1px solid var(--border);
}
.settings-wrapper {
position: relative;
}
.btn-settings {
width: 100%;
padding: 8px;
background: transparent;
color: var(--text-secondary);
border: none;
font-size: 13px;
cursor: pointer;
text-align: left;
border-radius: 6px;
}
.btn-settings:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.settings-menu {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
margin-bottom: 4px;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.settings-item {
width: 100%;
padding: 8px 12px;
background: none;
border: none;
color: var(--text-primary);
font-size: 13px;
text-align: left;
cursor: pointer;
border-radius: 6px;
}
.settings-item:hover {
background: var(--bg-tertiary);
}
</style>