From be9a49092347176338f4b3288a49e55767275139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E9=91=AB=E5=8D=87?= Date: Tue, 25 Aug 2026 16:57:42 +0800 Subject: [PATCH] fix: harden wiki delivery and leaderboard performance --- frontend-web/src/core/llmwiki/source-links.ts | 19 ++++-- .../src/strategy-components/api/llmwiki.ts | 43 ++++++++++--- .../llmwiki/WeKnoraWikiManagementPage.tsx | 40 +++++++++++- .../llmwiki/WeKnoraWikiPageView.tsx | 11 ++-- .../llmwiki/WikiProtectedImage.tsx | 21 +++---- offline-backend-20260512/backend/CLAUDE.md | 27 +++++++- offline-backend-20260512/backend/README.md | 31 +++++++++- .../app/gateway/routers/admin_users.py | 38 ++++++++---- .../backend/app/gateway/routers/llmwiki.py | 56 ++++++++++++++++- .../app/gateway/routers/llmwiki_index.py | 36 +++++++++++ .../backend/app/gateway/weknora_embed.py | 20 ++++++ .../integrations/weknora/local_index/sync.py | 7 +++ .../deerflow/persistence/llm_metrics/model.py | 1 + .../persistence/llmwiki_index/base.py | 13 ++++ .../persistence/llmwiki_index/memory.py | 42 +++++++++++++ .../deerflow/persistence/llmwiki_index/sql.py | 62 +++++++++++++++++++ .../versions/20260821_01_fixed_questions.py | 8 +-- ...20260821_02_fixed_question_stream_speed.py | 8 +-- .../20260823_01_llmwiki_local_wiki_index.py | 4 +- ...erge_report_structure_and_llmwiki_heads.py | 34 ++++++++++ .../harness/deerflow/persistence/run/model.py | 2 + .../persistence/tool_metrics/model.py | 2 + .../scripts/sql/leaderboard_indexes_mysql.sql | 23 +++++-- .../test_admin_leaderboard_query_shape.py | 43 +++++++++++++ .../test_llmwiki_local_index_repository.py | 16 +++++ .../backend/tests/test_llmwiki_wiki_sync.py | 10 ++- .../backend/tests/test_migration_graph.py | 17 +++++ .../tests/test_public_llmwiki_embed.py | 50 +++++++++++++++ 28 files changed, 616 insertions(+), 68 deletions(-) create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/migrations/versions/20260825_01_merge_report_structure_and_llmwiki_heads.py create mode 100644 offline-backend-20260512/backend/tests/test_admin_leaderboard_query_shape.py create mode 100644 offline-backend-20260512/backend/tests/test_migration_graph.py diff --git a/frontend-web/src/core/llmwiki/source-links.ts b/frontend-web/src/core/llmwiki/source-links.ts index 60f7f677..10bc99ee 100644 --- a/frontend-web/src/core/llmwiki/source-links.ts +++ b/frontend-web/src/core/llmwiki/source-links.ts @@ -27,6 +27,16 @@ export type KnowledgeSourceLike = { slug?: string | null; }; +/** Build a HashRouter URL without dropping a reverse-proxy path such as /web/. */ +export function buildAppHashUrl(path: string): string { + const route = path.startsWith("/") ? path : `/${path}`; + if (typeof window !== "undefined") { + return `${window.location.pathname || "/"}#${route}`; + } + const base = import.meta.env.BASE_URL || "/"; + return `${base}#${route}`; +} + export function parseWeKnoraPreviewUrl(url?: string | null): WeKnoraSourceTarget | null { if (!url?.startsWith("/weknora-source-preview?")) { return null; @@ -92,7 +102,7 @@ export function buildWeKnoraSourceIframeUrl(target: WeKnoraSourceTarget): string if (target.documentId) params.set("documentId", target.documentId); if (target.wikiSlug) params.set("wikiSlug", target.wikiSlug); const query = params.toString(); - return `/#/page/workspace/knowledge/${encodeURIComponent(target.knowledgeBaseId)}${query ? `?${query}` : ""}`; + return buildAppHashUrl(`/page/workspace/knowledge/${encodeURIComponent(target.knowledgeBaseId)}${query ? `?${query}` : ""}`); } /** @@ -103,7 +113,7 @@ export function buildWeKnoraWikiPageUrl( knowledgeBaseId: string, wikiSlug: string, ): string { - return `/#${buildWeKnoraWikiPagePath(knowledgeBaseId, wikiSlug)}`; + return buildAppHashUrl(buildWeKnoraWikiPagePath(knowledgeBaseId, wikiSlug)); } export function buildWeKnoraWikiPagePath( @@ -129,7 +139,7 @@ export function buildPublicKnowledgeWikiUrl( knowledgeBaseId: string, wikiSlug?: string, ): string { - return `/#${buildPublicKnowledgeWikiPath(knowledgeBaseId, wikiSlug)}`; + return buildAppHashUrl(buildPublicKnowledgeWikiPath(knowledgeBaseId, wikiSlug)); } export function buildLegacyKnowledgeSourceUrl(source: KnowledgeSourceLike): string { @@ -160,6 +170,7 @@ export function normalizeMalformedWeKnoraHashRoute(): boolean { const { pathname, search, hash } = window.location; if (!pathname.startsWith("/page/workspace/knowledge/")) return false; if (hash && !hash.startsWith("#/")) return false; - window.location.replace(`/#${pathname}${search}`); + const base = import.meta.env.BASE_URL || "/"; + window.location.replace(`${base}#${pathname}${search}`); return true; } diff --git a/frontend-web/src/strategy-components/api/llmwiki.ts b/frontend-web/src/strategy-components/api/llmwiki.ts index 5b8f16c9..40f0fd38 100644 --- a/frontend-web/src/strategy-components/api/llmwiki.ts +++ b/frontend-web/src/strategy-components/api/llmwiki.ts @@ -179,6 +179,28 @@ export interface LlmWikiWikiIndexStatus { last_error?: string | null; last_started_at?: string | null; last_completed_at?: string | null; + progress?: { + total_pages: number; + processed_pages: number; + vectorized_pages: number; + failed_pages: number; + vector_count: number; + completion_percent: number; + }; + pages?: Array<{ + id: string; + slug: string; + title: string; + page_type: string; + status: string; + index_status: string; + vector_count: number; + vectorized: boolean; + processed_in_current_sync: boolean; + indexed_at?: string | null; + updated_at?: string | null; + last_error?: string | null; + }>; } export interface LlmWikiSourceContext { @@ -444,21 +466,22 @@ export async function getLlmWikiDocumentPreview( return response.blob(); } -export async function getLlmWikiProtectedFile( +export async function getLlmWikiProtectedFileUrl( knowledgeBaseId: string, filePath: string, publicAccess = false, -): Promise { - const base = publicAccess ? PUBLIC_API() : API(); +): Promise { const params = new URLSearchParams({ file_path: filePath }); - const response = await apiFetch( - `${base}/knowledge-bases/${encodeURIComponent(knowledgeBaseId)}/files?${params.toString()}`, - ); - if (!response.ok) { - await readJson(response, "读取 Wiki 图片"); - throw new Error("读取 Wiki 图片失败"); + if (publicAccess) { + return `${PUBLIC_API()}/knowledge-bases/${encodeURIComponent(knowledgeBaseId)}/files?${params.toString()}`; } - return response.blob(); + const access = await readJson<{ token: string; expires_in: number }>( + await apiFetch( + `${API()}/knowledge-bases/${encodeURIComponent(knowledgeBaseId)}/files/access-url?${params.toString()}`, + ), + "读取 Wiki 图片", + ); + return `${PUBLIC_API()}/files/${encodeURIComponent(access.token)}`; } export async function getLlmWikiGraph(knowledgeBaseId: string): Promise { diff --git a/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraWikiManagementPage.tsx b/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraWikiManagementPage.tsx index 3eddeb72..ec06241b 100644 --- a/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraWikiManagementPage.tsx +++ b/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraWikiManagementPage.tsx @@ -6,6 +6,7 @@ import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; +import { Progress } from "@/components/ui/progress"; import { Textarea } from "@/components/ui/textarea"; import { MarkdownCodeEditor } from "@/components/wiki/MarkdownCodeEditor"; import { @@ -331,6 +332,10 @@ export function WeKnoraWikiManagementPage() { () => moveTargetOptions(pagesList), [pagesList], ); + const pageIndexBySlug = useMemo( + () => new Map((indexStatus.data?.pages ?? []).map((item) => [item.slug, item])), + [indexStatus.data?.pages], + ); const currentMoveTarget = moveTargetValue(draft.parentSlug, draft.categoryPathText, draft.folderId); useEffect(() => { @@ -387,6 +392,17 @@ export function WeKnoraWikiManagementPage() { }); }; + const renderVectorBadge = (page: WikiPageListItem) => { + const item = pageIndexBySlug.get(page.slug); + if (item?.last_error || item?.index_status === "failed" || item?.index_status === "stale") { + return 失败; + } + if (item?.vectorized) { + return 已向量化 · {item.vector_count} 段; + } + return 未向量化; + }; + const renderWikiNode = (node: WikiTreeNode, depth = 0) => { const hasChildren = node.children.length > 0; const isExpanded = query.trim() ? true : expandedDirectories.has(node.key); @@ -432,7 +448,8 @@ export function WeKnoraWikiManagementPage() { >
- {page.title || page.slug} + {page.title || page.slug} + {renderVectorBadge(page)}
{page.slug}
@@ -460,6 +477,7 @@ export function WeKnoraWikiManagementPage() {
{page.title || page.slug} + {renderVectorBadge(page)}
{page.slug}
{page.aliases?.length ? ( @@ -634,6 +652,26 @@ export function WeKnoraWikiManagementPage() {