diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java b/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java index adcabd47..2278f6ea 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/controller/WikiController.java @@ -298,11 +298,13 @@ public class WikiController { // ==================== Wiki Pages ==================== @RequireWorkspaceRole("viewer") - @Operation(summary = "获取 Wiki 页面列表") + @Operation(summary = "获取 Wiki 页面列表(可按原始材料过滤)") @GetMapping("/knowledge-bases/{kbId}/pages") public R> listPages(@PathVariable Long kbId, + @RequestParam(required = false) Long rawId, @RequestHeader(value = "X-Workspace-Id", required = false) Long workspaceId) { verifyKBWorkspace(kbId, workspaceId); + if (rawId != null) return R.ok(pageService.listBySourceRawId(kbId, rawId)); return R.ok(pageService.listByKbId(kbId)); } diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiPageService.java b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiPageService.java index 616fdcb4..6b296f7a 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiPageService.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiPageService.java @@ -180,11 +180,22 @@ public class WikiPageService { } /** - * 创建新 Wiki 页面 + * Create a new wiki page (without explicit pageType) */ @Transactional public WikiPageEntity createPage(Long kbId, String slug, String title, String content, String summary, String sourceRawIds) { + return createPage(kbId, slug, title, content, summary, sourceRawIds, null); + } + + /** + * Create a new wiki page with explicit pageType classification. + * pageType is stored lowercase (concept / person / place / event / technology / + * organization / product / term / process / other). + */ + @Transactional + public WikiPageEntity createPage(Long kbId, String slug, String title, String content, + String summary, String sourceRawIds, String pageType) { WikiPageEntity entity = new WikiPageEntity(); entity.setKbId(kbId); entity.setSlug(slug); @@ -195,11 +206,28 @@ public class WikiPageService { entity.setSourceRawIds(sourceRawIds); entity.setVersion(1); entity.setLastUpdatedBy("ai"); + if (pageType != null && !pageType.isBlank()) { + entity.setPageType(pageType.toLowerCase()); + } pageMapper.insert(entity); evictSummaryCache(kbId); return entity; } + /** + * List pages derived from a specific raw material (for UI sidebar filtering). + * Uses a LIKE search on sourceRawIds JSON field — cheap and dialect-agnostic. + */ + public List listBySourceRawId(Long kbId, Long rawId) { + List pages = pageMapper.selectList( + new LambdaQueryWrapper() + .eq(WikiPageEntity::getKbId, kbId) + .like(WikiPageEntity::getSourceRawIds, rawId.toString()) + .orderByAsc(WikiPageEntity::getTitle)); + pages.forEach(p -> p.setContent(null)); + return pages; + } + /** * AI 更新页面内容(手动编辑的页面不覆盖内容,仅追加来源) */ diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java index 9eea8336..cc12d971 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java @@ -675,7 +675,7 @@ public class WikiProcessingService { // ─── Phase B-1: BatchCreate (RFC-047 P1) ─── // One LLM call for all N creates instead of N individual calls. // batchCreatePages handles sub-batching, liveIndex updates, and progress counting. - batchCreatePages(kb, raw, textContent, existingPagesIndex, createMetas, created, pc); + batchCreatePages(kb, raw, textContent, existingPagesIndex, createMetas, created, pc, documentMap); List> createFutures = new ArrayList<>(0); // kept for allOf join below // ─── 阶段 B-2:并行 merge ─── @@ -754,7 +754,7 @@ public class WikiProcessingService { private int batchCreatePages(WikiKnowledgeBaseEntity kb, WikiRawMaterialEntity raw, String chunkText, String existingPagesIndex, List createMetas, AtomicInteger created, - ProgressCounter pc) { + ProgressCounter pc, String documentMap) { if (createMetas.isEmpty()) return 0; Long kbId = kb.getId(); Long rawId = raw.getId(); @@ -779,8 +779,12 @@ public class WikiProcessingService { String batchSystem = PromptLoader.loadPrompt("wiki/batch-create-system"); String batchUserTemplate = PromptLoader.loadPrompt("wiki/batch-create-user"); + String docMapSection = (documentMap != null && !documentMap.isBlank()) + ? "## 文档全局概念地图(预分析结果,供页面内容生成参考)\n\n```json\n" + documentMap + "\n```\n" + : ""; String batchUser = batchUserTemplate .replace("{config}", configContent) + .replace("{document_map_section}", docMapSection) .replace("{existing_pages}", liveIndex.toString()) .replace("{pages_to_create}", metasJson.toString()) .replace("{raw_title}", raw.getTitle()) @@ -820,6 +824,7 @@ public class WikiProcessingService { String title = pageJson.path("title").asText(""); String content = pageJson.path("content").asText(""); String pageSummary = pageJson.path("summary").asText(""); + String pageType = pageJson.path("page_type").asText(""); if (content.isBlank()) { log.info("[Wiki] BatchCreate: blank content for slug='{}', retrying individually", slug); final String blankSlug = slug; @@ -869,7 +874,7 @@ public class WikiProcessingService { boolean wasCreated = false; boolean ok = false; try { - wasCreated = savePageContent(kb, raw, slug, title, content, pageSummary); + wasCreated = savePageContent(kb, raw, slug, title, content, pageSummary, pageType); if (wasCreated) { created.incrementAndGet(); totalCreated++; @@ -991,6 +996,12 @@ public class WikiProcessingService { */ private boolean savePageContent(WikiKnowledgeBaseEntity kb, WikiRawMaterialEntity raw, String slug, String title, String content, String pageSummary) { + return savePageContent(kb, raw, slug, title, content, pageSummary, null); + } + + private boolean savePageContent(WikiKnowledgeBaseEntity kb, WikiRawMaterialEntity raw, + String slug, String title, String content, String pageSummary, + String pageType) { Long kbId = kb.getId(); Long rawId = raw.getId(); @@ -1037,7 +1048,7 @@ public class WikiProcessingService { String sourceRawIds = "[" + rawId + "]"; try { - WikiPageEntity created = pageService.createPage(kbId, slug, title, content, pageSummary, sourceRawIds); + WikiPageEntity created = pageService.createPage(kbId, slug, title, content, pageSummary, sourceRawIds, pageType); pageService.mergeSourceLineage(created.getId(), rawId, raw.getTitle()); log.info("[Wiki] Phase B create page slug='{}' done (created)", slug); citationService.buildCitationsAsync(created.getId(), kbId); diff --git a/mateclaw-server/src/main/resources/prompts/wiki/batch-create-system.txt b/mateclaw-server/src/main/resources/prompts/wiki/batch-create-system.txt index ae4ed9e9..74bb3546 100644 --- a/mateclaw-server/src/main/resources/prompts/wiki/batch-create-system.txt +++ b/mateclaw-server/src/main/resources/prompts/wiki/batch-create-system.txt @@ -26,7 +26,7 @@ 每个页面输出一个 FILE 块,格式如下: ---FILE: {slug}--- -{"slug":"...","title":"...","summary":"...","content":"## 标题\n\n摘要...\n\n### 章节\n..."} +{"slug":"...","title":"...","summary":"...","page_type":"concept","content":"## 标题\n\n摘要...\n\n### 章节\n..."} ---END FILE--- 规则: @@ -40,3 +40,4 @@ - `title`:与 metadata 保持一致;如有更精确的描述可微调 - `content`:完整 markdown 正文 - `summary`:一段话简短摘要 +- `page_type`:页面类型,从以下值中选一个:concept / person / place / event / technology / organization / product / term / process / other diff --git a/mateclaw-server/src/main/resources/prompts/wiki/batch-create-user.txt b/mateclaw-server/src/main/resources/prompts/wiki/batch-create-user.txt index 73bd485b..6de57f8c 100644 --- a/mateclaw-server/src/main/resources/prompts/wiki/batch-create-user.txt +++ b/mateclaw-server/src/main/resources/prompts/wiki/batch-create-user.txt @@ -2,6 +2,8 @@ {config} +{document_map_section} + ## 已有 Wiki 页面索引(用于建立 [[链接]]) {existing_pages} diff --git a/mateclaw-ui/src/api/index.ts b/mateclaw-ui/src/api/index.ts index 9509d994..fec88161 100644 --- a/mateclaw-ui/src/api/index.ts +++ b/mateclaw-ui/src/api/index.ts @@ -432,7 +432,8 @@ export const wikiApi = { http.post(`/wiki/knowledge-bases/${kbId}/raw/${rawId}/reprocess`), // Wiki Pages - listPages: (kbId: number) => http.get(`/wiki/knowledge-bases/${kbId}/pages`), + listPages: (kbId: number, rawId?: number) => + http.get(`/wiki/knowledge-bases/${kbId}/pages`, rawId != null ? { params: { rawId } } : undefined), getPage: (kbId: number, slug: string) => http.get(`/wiki/knowledge-bases/${kbId}/pages/${encodeURIComponent(slug)}`), updatePage: (kbId: number, slug: string, content: string) => diff --git a/mateclaw-ui/src/components/chat/ModelSelector.vue b/mateclaw-ui/src/components/chat/ModelSelector.vue index 7d120f71..6d06136a 100644 --- a/mateclaw-ui/src/components/chat/ModelSelector.vue +++ b/mateclaw-ui/src/components/chat/ModelSelector.vue @@ -245,11 +245,11 @@ watch(open, async (isOpen) => { .model-dropdown-backdrop { position: fixed; inset: 0; - z-index: 2000; + z-index: 4000; } .model-dropdown { - z-index: 2001; + z-index: 4001; min-width: 280px; max-width: 360px; background: var(--mc-bg-elevated); diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index b5148abc..18f8c8b3 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -1299,7 +1299,15 @@ export default { explainRelation: 'Explain relation', }, configPanel: { + embeddingModel: 'Embedding Model', + embeddingModelHint: 'Semantic search model for this KB; leave empty for system default', modelStrategy: 'Model Strategy', + globalDefault: 'Global default', + selectModel: 'Select a model…', + otherProvider: 'Other', + searchModel: 'Search models…', + noModelMatch: 'No matching models', + addFallback: '+ Add fallback', stepModel: { route: 'Analysis / Routing', create_page: 'Page Creation', @@ -1319,6 +1327,34 @@ export default { noPages: 'No pages yet', noResults: 'No pages match your search', loadMore: 'Load {n} more', + filteredByRaw: 'Filtered by source material', + clearFilter: 'Clear filter', + pageTypes: { + concept: 'Concepts', + person: 'People', + place: 'Places', + event: 'Events', + technology: 'Technology', + organization: 'Organizations', + product: 'Products', + term: 'Terms', + process: 'Processes', + other: 'Other', + }, + graph: { + tab: 'Knowledge Graph', + nodes: 'nodes', + edges: 'edges', + orphans: 'orphans', + showOrphans: 'Show orphans', + allTypes: 'All types', + resetView: 'Reset view', + fullscreen: 'Fullscreen', + exitFullscreen: 'Exit fullscreen', + linksTo: 'Links to', + openPage: 'Open page', + empty: 'No graph data — process some raw materials first', + }, }, cronJobs: { kicker: 'Automation', diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index 0af5889c..bb5aae20 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -1309,7 +1309,15 @@ export default { explainRelation: '查看关联原因', }, configPanel: { + embeddingModel: '向量化模型', + embeddingModelHint: '语义搜索模型,留空使用系统默认', modelStrategy: '模型策略', + globalDefault: '跟随全局默认', + selectModel: '选择可用模型…', + otherProvider: '其他', + searchModel: '搜索模型…', + noModelMatch: '未找到匹配模型', + addFallback: '+ 添加备选', stepModel: { route: '分析/路由', create_page: '页面生成', @@ -1329,6 +1337,34 @@ export default { noPages: '暂无页面', noResults: '未找到匹配页面', loadMore: '加载更多 {n} 条', + filteredByRaw: '按原始材料过滤', + clearFilter: '清除过滤', + pageTypes: { + concept: '概念', + person: '人物', + place: '地点', + event: '事件', + technology: '技术', + organization: '组织', + product: '产品', + term: '术语', + process: '流程', + other: '其他', + }, + graph: { + tab: '知识图谱', + nodes: '节点', + edges: '连接', + orphans: '孤立节点', + showOrphans: '显示孤立节点', + allTypes: '全部类型', + resetView: '重置视图', + fullscreen: '全屏', + exitFullscreen: '退出全屏', + linksTo: '链接到', + openPage: '打开页面', + empty: '暂无图谱数据,请先处理原始材料', + }, }, cronJobs: { kicker: '自动执行', diff --git a/mateclaw-ui/src/stores/useWikiStore.ts b/mateclaw-ui/src/stores/useWikiStore.ts index a64926d1..f07d4112 100644 --- a/mateclaw-ui/src/stores/useWikiStore.ts +++ b/mateclaw-ui/src/stores/useWikiStore.ts @@ -58,6 +58,10 @@ export const useWikiStore = defineStore('wiki', () => { const currentPage = ref(null) const loading = ref(false) + // Raw material filter state + const selectedRawId = ref(null) + const totalPageCount = ref(0) + async function fetchKnowledgeBases() { loading.value = true try { @@ -98,9 +102,20 @@ export const useWikiStore = defineStore('wiki', () => { rawMaterials.value = res.data || [] } - async function fetchPages(kbId: number) { - const res: any = await wikiApi.listPages(kbId) + async function fetchPages(kbId: number, rawId?: number | null) { + const res: any = await wikiApi.listPages(kbId, rawId ?? undefined) pages.value = res.data || [] + if (!rawId) totalPageCount.value = pages.value.length + } + + async function filterPagesByRaw(kbId: number, rawId: number) { + selectedRawId.value = rawId + await fetchPages(kbId, rawId) + } + + async function clearRawFilter(kbId: number) { + selectedRawId.value = null + await fetchPages(kbId) } async function loadPage(kbId: number, slug: string) { @@ -150,12 +165,16 @@ export const useWikiStore = defineStore('wiki', () => { pages, currentPage, loading, + selectedRawId, + totalPageCount, fetchKnowledgeBases, selectKB, createKB, deleteKB, fetchRawMaterials, fetchPages, + filterPagesByRaw, + clearRawFilter, loadPage, addRawText, uploadRawFile, diff --git a/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue b/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue index d4f38e35..266d0491 100644 --- a/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue +++ b/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue @@ -55,7 +55,13 @@
{{ t('wiki.noRawMaterials') }}
-
+
{{ raw.title }} @@ -410,6 +416,16 @@ async function processAll() { setTimeout(() => { store.fetchRawMaterials(kbId) }, 5000) } +function toggleRawFilter(rawId: number) { + if (!store.currentKB) return + const kbId = store.currentKB.id + if (store.selectedRawId === rawId) { + store.clearRawFilter(kbId) + } else { + store.filterPagesByRaw(kbId, rawId) + } +} + async function handleScanDir() { if (!store.currentKB || !dirPath.value.trim()) return scanning.value = true @@ -476,8 +492,9 @@ async function handleScanDir() { .raw-list-title { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--mc-text-tertiary); margin-bottom: 4px; } .empty-hint { text-align: center; padding: 24px 0; font-size: 14px; color: var(--mc-text-tertiary); } -.raw-item { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px; background: linear-gradient(180deg, var(--mc-bg-elevated), var(--mc-bg-muted)); border: 1px solid var(--mc-border-light); border-radius: 14px; font-size: 13px; transition: border-color 0.15s, transform 0.15s; } +.raw-item { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px; background: linear-gradient(180deg, var(--mc-bg-elevated), var(--mc-bg-muted)); border: 1px solid var(--mc-border-light); border-radius: 14px; font-size: 13px; transition: border-color 0.15s, transform 0.15s; cursor: pointer; } .raw-item:hover { border-color: var(--mc-border); transform: translateY(-1px); } +.raw-item--active { border-color: var(--mc-primary) !important; background: var(--mc-primary-bg) !important; transform: translateY(-1px); } .raw-item-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .raw-item-info { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; } diff --git a/mateclaw-ui/src/views/Wiki/components/WikiConfig.vue b/mateclaw-ui/src/views/Wiki/components/WikiConfig.vue index f4c6bca2..9d634470 100644 --- a/mateclaw-ui/src/views/Wiki/components/WikiConfig.vue +++ b/mateclaw-ui/src/views/Wiki/components/WikiConfig.vue @@ -5,128 +5,162 @@

{{ t('wiki.configDesc') }}

- -
- -
- -
+
- -
- {{ t('wiki.configPanel.modelStrategy') }} -
-
- - + +
+
+ + + +
+
{{ t('wiki.configPanel.modelStrategy') }}
+
+ + + + +
-
-
- -
- - {{ chatModelOptions.find(m => String(m.id) === String(fId))?.name || fId }} - - - +
+ {{ activeStepCount }} / {{ stepKeys.length }}
+ + +
- -
- - - - -
- -
- - + +
+
+ + + +
+
处理规则
+
AI 消化原始材料时遵循的质量、格式和语言规则
+ +
+ {{ line }} + +{{ totalLines - 4 }} 行 +
+
点击配置 →
+
+ + + +
+
+ + +
+
+ + + +
+
{{ t('wiki.configPanel.searchPreview') }}
+
{{ t('wiki.configPanel.searchPreviewPlaceholder') }}
+
+ + + +
+
+ + + + + + +
diff --git a/mateclaw-ui/src/views/Wiki/components/WikiConfigModels.vue b/mateclaw-ui/src/views/Wiki/components/WikiConfigModels.vue new file mode 100644 index 00000000..40890958 --- /dev/null +++ b/mateclaw-ui/src/views/Wiki/components/WikiConfigModels.vue @@ -0,0 +1,291 @@ + + + + + diff --git a/mateclaw-ui/src/views/Wiki/components/WikiConfigRules.vue b/mateclaw-ui/src/views/Wiki/components/WikiConfigRules.vue new file mode 100644 index 00000000..c00325af --- /dev/null +++ b/mateclaw-ui/src/views/Wiki/components/WikiConfigRules.vue @@ -0,0 +1,473 @@ +