diff --git a/frontend-web/src/components/workspace/skill-display-sources.tsx b/frontend-web/src/components/workspace/skill-display-sources.tsx index 0e60488c..8e9950e2 100644 --- a/frontend-web/src/components/workspace/skill-display-sources.tsx +++ b/frontend-web/src/components/workspace/skill-display-sources.tsx @@ -240,7 +240,18 @@ type WeKnoraSourceContext = { knowledge_base_name?: string; document_title?: string; document_file_name?: string; + display_mode?: "wiki" | "chunks"; focus_chunk_id: string; + wiki_page?: { + id?: string; + slug?: string; + title?: string; + page_type?: string; + status?: string; + summary?: string; + content?: string; + source_refs?: string[]; + } | null; chunks: Array<{ id: string; chunk_index?: number; @@ -312,6 +323,33 @@ function WeKnoraSourceContextPanel({ if (!context) { return null; } + const wikiPage = context.wiki_page; + const wikiContent = cleanReferenceText( + [wikiPage?.summary, wikiPage?.content].filter(Boolean).join("\n\n"), + [], + ); + if (wikiPage && wikiContent) { + return ( +
+
+ {context.knowledge_base_name || "WeKnora 知识库"} + · LLMWiki + {wikiPage.page_type && · {wikiPage.page_type}} +
+
+
+ + {wikiPage.title || wikiPage.slug || "LLMWiki 页面"} + + + 清洗页 + +
+
{wikiContent}
+
+
+ ); + } return (
diff --git a/frontend-web/src/core/config/index.ts b/frontend-web/src/core/config/index.ts index a33f0cfc..e3c3e006 100644 --- a/frontend-web/src/core/config/index.ts +++ b/frontend-web/src/core/config/index.ts @@ -13,19 +13,33 @@ export function buildIntranetSourceUrl(recUuid?: string | null): string { return `${INTRANET_SOURCE_URL_BASE}${encodeURIComponent(recUuid)}`; } -function getBaseOrigin() { +function getBaseOrigin() { if (typeof window !== "undefined") { return window.location.origin; } // Fallback for SSR - return "http://localhost:2026"; -} - -export function getBackendBaseURL() { - if (env.NEXT_PUBLIC_BACKEND_BASE_URL) { - return new URL(env.NEXT_PUBLIC_BACKEND_BASE_URL, getBaseOrigin()) - .toString() - .replace(/\/+$/, ""); + return "http://localhost:2026"; +} + +function alignLoopbackHostname(url: URL): URL { + if (typeof window === "undefined") return url; + const loopbackHosts = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]); + if ( + loopbackHosts.has(url.hostname) && + loopbackHosts.has(window.location.hostname) + ) { + url.hostname = window.location.hostname; + } + return url; +} + +export function getBackendBaseURL() { + if (env.NEXT_PUBLIC_BACKEND_BASE_URL) { + return alignLoopbackHostname( + new URL(env.NEXT_PUBLIC_BACKEND_BASE_URL, getBaseOrigin()), + ) + .toString() + .replace(/\/+$/, ""); } else { return ""; } diff --git a/frontend-web/src/strategy-components/api/llmwiki.ts b/frontend-web/src/strategy-components/api/llmwiki.ts index 8d870812..48846c38 100644 --- a/frontend-web/src/strategy-components/api/llmwiki.ts +++ b/frontend-web/src/strategy-components/api/llmwiki.ts @@ -1,6 +1,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiFetch } from "@/core/api/fetch-client"; +import { useLiveAccount } from "@/core/auth/use-live-account"; import { getBackendBaseURL } from "@/core/config"; const API = () => `${getBackendBaseURL()}/api/llmwiki`; @@ -52,6 +53,7 @@ export interface LlmWikiKnowledgeBase { created_at?: string | null; updated_at?: string | null; published_at?: string | null; + is_conversation_deposit?: boolean; } export interface LlmWikiDocument { @@ -152,6 +154,11 @@ export interface LlmWikiSearchResult { url: string; } +export interface LlmWikiWeKnoraEmbedSession { + frame_path: string; + expires_in: number; +} + async function readJson(response: Response, context: string): Promise { if (!response.ok) { const payload = await response.json().catch(() => null as unknown); @@ -206,6 +213,19 @@ export async function getLlmWikiKnowledgeBase( ); } +export async function createLlmWikiWeKnoraEmbedSession( + id: string, + tab: "documents" | "wiki" | "graph", +): Promise { + return readJson( + await apiFetch( + `${API()}/knowledge-bases/${encodeURIComponent(id)}/weknora/session?tab=${encodeURIComponent(tab)}`, + { method: "POST" }, + ), + "创建 WeKnora 详情会话", + ); +} + export async function createLlmWikiKnowledgeBase(input: { name: string; description: string; @@ -497,8 +517,8 @@ export async function testLlmWikiConnection( const runtimeKey = ["llmwiki", "runtime"] as const; const settingsKey = ["llmwiki", "settings"] as const; -const knowledgeBasesKey = (scope: LlmWikiScope) => - ["llmwiki", "knowledge-bases", scope] as const; +const knowledgeBasesKey = (userId: string | null, scope: LlmWikiScope) => + ["llmwiki", "knowledge-bases", userId ?? "anonymous", scope] as const; export function useLlmWikiRuntime() { return useQuery({ @@ -513,8 +533,9 @@ export function useLlmWikiKnowledgeBases( scope: LlmWikiScope, enabled = true, ) { + const { userId } = useLiveAccount(); return useQuery({ - queryKey: knowledgeBasesKey(scope), + queryKey: knowledgeBasesKey(userId, scope), queryFn: () => listLlmWikiKnowledgeBases(scope), enabled, refetchInterval: 15_000, @@ -525,8 +546,9 @@ export function useAgentLlmWikiKnowledgeBases( agentId: string | null | undefined, enabled = true, ) { + const { userId } = useLiveAccount(); return useQuery({ - queryKey: ["llmwiki", "agents", agentId, "knowledge-bases"], + queryKey: ["llmwiki", "agents", agentId, "knowledge-bases", userId ?? "anonymous"], queryFn: () => listAgentLlmWikiKnowledgeBases(agentId!), enabled: enabled && !!agentId, }); @@ -536,8 +558,9 @@ export function useLlmWikiKnowledgeBase( knowledgeBaseId: string | null, enabled = true, ) { + const { userId } = useLiveAccount(); return useQuery({ - queryKey: ["llmwiki", "knowledge-base", knowledgeBaseId], + queryKey: ["llmwiki", "knowledge-base", knowledgeBaseId, userId ?? "anonymous"], queryFn: () => getLlmWikiKnowledgeBase(knowledgeBaseId!), enabled: enabled && !!knowledgeBaseId, refetchInterval: 8_000, @@ -548,8 +571,9 @@ export function useLlmWikiDocuments( knowledgeBaseId: string | null, enabled = true, ) { + const { userId } = useLiveAccount(); return useQuery({ - queryKey: ["llmwiki", "documents", knowledgeBaseId], + queryKey: ["llmwiki", "documents", knowledgeBaseId, userId ?? "anonymous"], queryFn: () => listLlmWikiDocuments(knowledgeBaseId!), enabled: enabled && !!knowledgeBaseId, refetchInterval: 8_000, @@ -561,8 +585,9 @@ export function useLlmWikiDocument( documentId: string | null, enabled = true, ) { + const { userId } = useLiveAccount(); return useQuery({ - queryKey: ["llmwiki", "document", knowledgeBaseId, documentId], + queryKey: ["llmwiki", "document", knowledgeBaseId, documentId, userId ?? "anonymous"], queryFn: () => getLlmWikiDocument(knowledgeBaseId!, documentId!), enabled: enabled && !!knowledgeBaseId && !!documentId, }); @@ -575,16 +600,18 @@ export function useLlmWikiDocumentChunks( pageSize = 20, enabled = true, ) { + const { userId } = useLiveAccount(); return useQuery({ - queryKey: ["llmwiki", "document-chunks", knowledgeBaseId, documentId, page, pageSize], + queryKey: ["llmwiki", "document-chunks", knowledgeBaseId, documentId, page, pageSize, userId ?? "anonymous"], queryFn: () => listLlmWikiDocumentChunks(knowledgeBaseId!, documentId!, page, pageSize), enabled: enabled && !!knowledgeBaseId && !!documentId, }); } export function useLlmWikiGraph(knowledgeBaseId: string | null, enabled = true) { + const { userId } = useLiveAccount(); return useQuery({ - queryKey: ["llmwiki", "graph", knowledgeBaseId], + queryKey: ["llmwiki", "graph", knowledgeBaseId, userId ?? "anonymous"], queryFn: () => getLlmWikiGraph(knowledgeBaseId!), enabled: enabled && !!knowledgeBaseId, }); @@ -597,8 +624,9 @@ export function useLlmWikiWikiPages( query = "", enabled = true, ) { + const { userId } = useLiveAccount(); return useQuery({ - queryKey: ["llmwiki", "wiki-pages", knowledgeBaseId, page, pageSize, query], + queryKey: ["llmwiki", "wiki-pages", knowledgeBaseId, page, pageSize, query, userId ?? "anonymous"], queryFn: () => listLlmWikiWikiPages(knowledgeBaseId!, page, pageSize, query), enabled: enabled && !!knowledgeBaseId, refetchInterval: 10_000, @@ -610,16 +638,18 @@ export function useLlmWikiWikiPage( slug: string | null, enabled = true, ) { + const { userId } = useLiveAccount(); return useQuery({ - queryKey: ["llmwiki", "wiki-page", knowledgeBaseId, slug], + queryKey: ["llmwiki", "wiki-page", knowledgeBaseId, slug, userId ?? "anonymous"], queryFn: () => getLlmWikiWikiPage(knowledgeBaseId!, slug!), enabled: enabled && !!knowledgeBaseId && !!slug, }); } export function useLlmWikiSettings(enabled = true) { + const { userId } = useLiveAccount(); return useQuery({ - queryKey: settingsKey, + queryKey: [...settingsKey, userId ?? "anonymous"], queryFn: getLlmWikiSettings, enabled, }); diff --git a/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraKnowledgeDetailPage.tsx b/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraKnowledgeDetailPage.tsx index 6ff67b7d..a7484cfd 100644 --- a/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraKnowledgeDetailPage.tsx +++ b/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraKnowledgeDetailPage.tsx @@ -1,10 +1,13 @@ -import { AlertCircle, Loader2, RefreshCw } from "lucide-react"; -import { useEffect, useMemo, useRef } from "react"; +import { AlertCircle, Loader2, RefreshCw, Upload } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; import { Navigate, useNavigate, useParams } from "react-router-dom"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { getBackendBaseURL } from "@/core/config"; import { + createLlmWikiWeKnoraEmbedSession, + useLlmWikiActions, useLlmWikiKnowledgeBase, useLlmWikiRuntime, } from "@/strategy-components/api/llmwiki"; @@ -60,18 +63,46 @@ export function WeKnoraKnowledgeDetailPage() { const knowledgeBaseId = strategyKnowledgeBaseId ?? workspaceKnowledgeBaseId ?? ""; const navigate = useNavigate(); const frameRef = useRef(null); + const fileInputRef = useRef(null); + const [frameSrc, setFrameSrc] = useState(""); + const [frameError, setFrameError] = useState(null); + const [uploading, setUploading] = useState(false); const runtime = useLlmWikiRuntime(); const details = useLlmWikiKnowledgeBase(knowledgeBaseId || null, !!knowledgeBaseId); + const actions = useLlmWikiActions(); + const detailsReady = !!details.data; const preferredTab = details.data?.indexing_strategy?.wiki_enabled ? "wiki" : "documents"; - const frameSrc = useMemo(() => { - if (!knowledgeBaseId || runtime.data?.provider !== "weknora") return ""; - const base = getBackendBaseURL().replace(/\/+$/, ""); - const params = new URLSearchParams({ tab: preferredTab }); - return `${base}/api/llmwiki/knowledge-bases/${encodeURIComponent( - knowledgeBaseId, - )}/weknora/frame?${params.toString()}`; - }, [knowledgeBaseId, preferredTab, runtime.data?.provider]); + + useEffect(() => { + let cancelled = false; + setFrameSrc(""); + setFrameError(null); + if (!knowledgeBaseId || runtime.data?.provider !== "weknora" || !detailsReady) { + return () => { + cancelled = true; + }; + } + + void createLlmWikiWeKnoraEmbedSession(knowledgeBaseId, preferredTab) + .then((session) => { + if (cancelled) return; + const base = getBackendBaseURL().replace(/\/+$/, ""); + const path = session.frame_path.startsWith("/") + ? session.frame_path + : `/${session.frame_path}`; + setFrameSrc(`${base}${path}`); + }) + .catch((error: unknown) => { + if (cancelled) return; + const message = error instanceof Error ? error.message : "创建 WeKnora 详情会话失败"; + setFrameError(message); + }); + + return () => { + cancelled = true; + }; + }, [detailsReady, knowledgeBaseId, preferredTab, runtime.data?.provider]); useEffect(() => { if (!frameSrc) return; @@ -95,13 +126,30 @@ export function WeKnoraKnowledgeDetailPage() { return (
- 正在打开 WeKnora 详情页 + 加载中
); } if (runtime.data?.provider !== "weknora") return ; + if (frameError) { + return ( +
+
+ +
+
+
无法打开 WeKnora 详情
+

{frameError}

+
+ +
+ ); + } + if (details.error || !details.data) { return (
@@ -121,22 +169,80 @@ export function WeKnoraKnowledgeDetailPage() { ); } + const canUpload = details.data.can_write && !details.data.is_conversation_deposit; + return (
-