diff --git a/frontend-web/src/components/workspace/knowledge-product-nav.tsx b/frontend-web/src/components/workspace/knowledge-product-nav.tsx index 96af3aca..97b191cc 100644 --- a/frontend-web/src/components/workspace/knowledge-product-nav.tsx +++ b/frontend-web/src/components/workspace/knowledge-product-nav.tsx @@ -6,13 +6,22 @@ import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { getStoredAuth } from "@/core/auth"; +import { useLlmWikiRuntime } from "@/strategy-components/api/llmwiki"; import { loadKnowledgeBaseProductVersion, + loadOrdinaryKnowledgeLanding, + saveOrdinaryKnowledgeLanding, + type OrdinaryKnowledgeLanding, type KnowledgeBaseProductVersion, updateKnowledgeBaseProductVersion, } from "@/core/system-settings/api"; export const KNOWLEDGE_PRODUCT_KEY = ["system-settings", "knowledge-base-product-version"] as const; +const LANDING_KEY = ["system-settings", "ordinary-knowledge-landing"] as const; + +export function useOrdinaryKnowledgeLanding() { + return useQuery({ queryKey: LANDING_KEY, queryFn: loadOrdinaryKnowledgeLanding, staleTime: 0 }); +} export const KNOWLEDGE_NORMAL_VIEW_VALUE = "normal"; export const KNOWLEDGE_NORMAL_VIEW_SEARCH = `?view=${KNOWLEDGE_NORMAL_VIEW_VALUE}`; @@ -25,6 +34,14 @@ export function KnowledgeProductNav() { const navigate = useNavigate(); const client = useQueryClient(); const version = useKnowledgeBaseProductVersion(); + const landing = useOrdinaryKnowledgeLanding(); + const saveLanding = useMutation({ + mutationFn: saveOrdinaryKnowledgeLanding, + onSuccess: (mode) => { client.setQueryData(LANDING_KEY, mode); toast.success("普通知识库默认页面已更新"); }, + onError: (error) => toast.error(error.message), + }); + const runtime = useLlmWikiRuntime(); + const normalAvailable = runtime.data?.provider === "weknora" && Boolean(runtime.data.api_base_url?.trim()); const isAdmin = getStoredAuth()?.system_role === "admin"; const save = useMutation({ mutationFn: updateKnowledgeBaseProductVersion, @@ -36,15 +53,20 @@ export function KnowledgeProductNav() { const assistant = location.pathname.includes("/knowledge/assistant"); return ( -
- {isAdmin && ( -
+
+ 普通知识库默认页面 + 默认产品版本 { + const files = Array.from(event.target.files || []); + event.target.value = ""; + setTab("transfer"); + for (const file of files) { + if (file.size > 100 * 1024 * 1024) { toast.error(`${file.name} 超过 100 MB,请拆分后上传`); continue; } + try { + await actions.uploadDocument.mutateAsync({ file, assistantBaseId: baseId }); + toast.success(`${file.name} 已上传,后台正在解析、生成 Wiki 并向量化`); + } catch (error) { toast.error(`${file.name}:${(error as Error).message}`); } + } + }} /> 上次同步:{new Date(s.updated_at).toLocaleString()}

- {isAdmin && ( + {s.source_type === "file" &&

本地上传文档 · 处理进度及重试请查看导入记录

} + {isAdmin && s.source_type === "weknora" && ( + )}
))} diff --git a/frontend-web/src/pages/EnterpriseResearchPage.tsx b/frontend-web/src/pages/EnterpriseResearchPage.tsx index 1f5bdcdb..04de5d30 100644 --- a/frontend-web/src/pages/EnterpriseResearchPage.tsx +++ b/frontend-web/src/pages/EnterpriseResearchPage.tsx @@ -1,3 +1,4 @@ +import { userFacingKnowledgeMessage } from "@/lib/knowledge-message"; import { type FormEvent, useEffect, useMemo, useRef, useState } from "react"; import { AlertCircle, @@ -211,7 +212,7 @@ export default function EnterpriseResearchPage() { setTasks((current) => current.map((task) => task.id === taskId ? { ...task, active_report_job_id: null } : task)); } if (event.type === "completed") toast.success("企业研究报告已完成"); - if (event.type === "failed") toast.error(event.job.error_message || "报告生成失败"); + if (event.type === "failed") toast.error(userFacingKnowledgeMessage(event.job.error_message || "报告生成失败")); } }, }).catch((error: unknown) => { @@ -290,7 +291,7 @@ export default function EnterpriseResearchPage() { return; } if (!isWeKnoraReady) { - toast.error("知识服务尚未启用,请先在系统中配置 WeKnora"); + toast.error("知识服务尚未启用,请先在系统中配置知识服务"); return; } @@ -442,7 +443,7 @@ export default function EnterpriseResearchPage() {
阶段一:证据编排

建立一个可追溯的研究任务

-

页面独立于现有“深度研究”功能。任务、检索词与证据快照保存在 DeerFlow 后端;当前版本只使用已授权的 WeKnora 知识库,不会修改任何知识库或既有报告。

+

页面独立于现有“深度研究”功能。任务、检索词与证据快照保存在 DeerFlow 后端;当前版本只使用已授权的 普通知识库,不会修改任何知识库或既有报告。

@@ -609,12 +610,12 @@ function TaskOverview({
    {template.stages.map((stage, index) => { const complete = task.status !== "collecting" && (index < 2 || task.sources.length > 0); const current = task.status === "collecting" && index === 1; - return
  1. {complete ? : index + 1}

    {stage}

    {index === 0 ? "已固定研究主题、范围和知识库权限。" : index === 1 ? task.status === "collecting" ? "正在调用 WeKnora 检索并去重证据。" : `已执行 ${task.queries.length} 个检索方向。` : index === 2 ? `${task.sources.length} 条去重后的资料可在右侧逐条核验。` : "研究计划会固定选用资料、章节结构和写作约束。"}

  2. ; + return
  3. {complete ? : index + 1}

    {stage}

    {index === 0 ? "已固定研究主题、范围和知识库权限。" : index === 1 ? task.status === "collecting" ? "正在调用知识服务检索并去重证据。" : `已执行 ${task.queries.length} 个检索方向。` : index === 2 ? `${task.sources.length} 条去重后的资料可在右侧逐条核验。` : "研究计划会固定选用资料、章节结构和写作约束。"}

  4. ; })}
- 已执行检索词检索词由模板和你的研究范围生成,结果仅来自当前账号可见的 WeKnora 知识库。 -
{task.queries.map((query) => {query})}
{task.warning ?
{task.warning}
: null}
+ 已执行检索词检索词由模板和你的研究范围生成,结果仅来自当前账号可见的 普通知识库。 +
{task.queries.map((query) => {query})}
{task.warning ?
{userFacingKnowledgeMessage(task.warning)}
: null}
{canPlan ? 研究计划与选用资料计划和资料选择会保存到当前任务;后续报告只能使用这里确认过的证据。 diff --git a/frontend-web/src/pages/LlmWikiKnowledgePage.tsx b/frontend-web/src/pages/LlmWikiKnowledgePage.tsx index 8ab563a0..9f411d00 100644 --- a/frontend-web/src/pages/LlmWikiKnowledgePage.tsx +++ b/frontend-web/src/pages/LlmWikiKnowledgePage.tsx @@ -91,7 +91,7 @@ export default function LlmWikiKnowledgePage({ return ; } - if (runtime.isError || runtime.data?.provider !== "weknora") { + if (runtime.isError || runtime.data?.provider !== "weknora" || !runtime.data?.api_base_url?.trim()) { if (mode === "list" || mode === "detail") { return ; } diff --git a/frontend-web/src/strategy-components/api/assistant-knowledge.ts b/frontend-web/src/strategy-components/api/assistant-knowledge.ts index f3eb8af0..eba441b6 100644 --- a/frontend-web/src/strategy-components/api/assistant-knowledge.ts +++ b/frontend-web/src/strategy-components/api/assistant-knowledge.ts @@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiFetch } from "@/core/api/fetch-client"; import { getBackendBaseURL } from "@/core/config"; +import { userFacingKnowledgeMessage } from "@/lib/knowledge-message"; const BASE = () => `${getBackendBaseURL()}/api/assistant-knowledge`; @@ -113,7 +114,7 @@ async function readJson(response: Response): Promise { ? (data as { detail: unknown }).detail : `请求失败 (${response.status})`; throw new Error( - typeof detail === "string" ? detail : JSON.stringify(detail), + userFacingKnowledgeMessage(typeof detail === "string" ? detail : JSON.stringify(detail)), ); } return (await response.json()) as T; @@ -408,6 +409,17 @@ export async function uploadAssistantWeKnoraPackage(input: { ); } +export async function uploadAssistantDocument(input: { file: File; assistantBaseId?: string | null }) { + const body = new FormData(); + body.set("file", input.file); + if (input.assistantBaseId) body.set("assistant_base_id", input.assistantBaseId); + return normalizeImportJob(await readJson(await apiFetch(`${BASE()}/imports/files`, { method: "POST", body }))); +} + +export async function retryAssistantDocument(jobId: string) { + return readJson(await apiFetch(`${BASE()}/import-jobs/${encodeURIComponent(jobId)}/retry-file`, { method: "POST" })); +} + export async function deleteAssistantKnowledgeSource(sourceId: string) { const response = await apiFetch( `${BASE()}/sources/${encodeURIComponent(sourceId)}`, @@ -490,6 +502,8 @@ export function useAssistantKnowledgeActions() { mutationFn: uploadAssistantWeKnoraPackage, onSuccess: invalidate, }), + uploadDocument: useMutation({ mutationFn: uploadAssistantDocument, onSuccess: invalidate }), + retryDocument: useMutation({ mutationFn: retryAssistantDocument, onSuccess: invalidate }), reimportSource: useMutation({ mutationFn: reimportAssistantKnowledgeSource, onSuccess: invalidate, diff --git a/frontend-web/src/strategy-components/api/enterprise-research.ts b/frontend-web/src/strategy-components/api/enterprise-research.ts index 8a5dce55..85432aad 100644 --- a/frontend-web/src/strategy-components/api/enterprise-research.ts +++ b/frontend-web/src/strategy-components/api/enterprise-research.ts @@ -1,3 +1,4 @@ +import { userFacingKnowledgeMessage } from "@/lib/knowledge-message"; import { apiFetch } from "@/core/api/fetch-client"; import { getBackendBaseURL } from "@/core/config"; @@ -85,7 +86,7 @@ async function readJson(response: Response, context: string): Promise { const detail = payload && typeof payload === "object" && "detail" in payload ? String((payload as { detail: unknown }).detail) : `${context}失败(${response.status})`; - throw new Error(detail); + throw new Error(userFacingKnowledgeMessage(detail)); } return response.json() as Promise; } @@ -249,6 +250,6 @@ export async function deleteEnterpriseResearchTask(taskId: string): Promise `${getBackendBaseURL()}/api/llmwiki`; const PUBLIC_API = () => `${getBackendBaseURL()}/api/public/llmwiki`; @@ -366,10 +367,6 @@ export interface LlmWikiWikiPageUpdate { wiki_path?: string; } -function userFacingKnowledgeMessage(value: string): string { - return value.replace(/weknora/gi, "知识服务"); -} - async function readJson(response: Response, context: string): Promise { if (!response.ok) { const payload = await response.json().catch(() => null as unknown); diff --git a/frontend-web/src/strategy-components/api/skill-knowledge.ts b/frontend-web/src/strategy-components/api/skill-knowledge.ts index ae6a5ca3..201a66a2 100644 --- a/frontend-web/src/strategy-components/api/skill-knowledge.ts +++ b/frontend-web/src/strategy-components/api/skill-knowledge.ts @@ -1,3 +1,4 @@ +import { userFacingKnowledgeMessage } from "@/lib/knowledge-message"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiFetch } from "@/core/api/fetch-client"; @@ -102,7 +103,7 @@ async function readJson(response: Response): Promise { data && typeof data === "object" && "detail" in data ? JSON.stringify((data as { detail: unknown }).detail) : `请求失败 (${response.status})`; - throw new Error(detail.replace(/^"|"$/g, "")); + throw new Error(userFacingKnowledgeMessage(detail.replace(/^"|"$/g, ""))); } return (await response.json()) as T; } 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 64e4dcab..498b94b3 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 @@ -17,6 +17,7 @@ import { import { toast } from "sonner"; import { Button } from "@/components/ui/button"; +import { useOrdinaryKnowledgeLanding } from "@/components/workspace/knowledge-product-nav"; import { Dialog, DialogContent, @@ -139,6 +140,7 @@ export function WeKnoraKnowledgeDetailPage() { const [frameError, setFrameError] = useState(null); const isAdmin = getStoredAuth()?.system_role === "admin"; const runtime = useLlmWikiRuntime(); + const landing = useOrdinaryKnowledgeLanding(); const details = useLlmWikiKnowledgeBase( knowledgeBaseId || null, !!knowledgeBaseId, @@ -165,6 +167,8 @@ export function WeKnoraKnowledgeDetailPage() { searchParams.get("sourceChunkId") || searchParams.get("chunkId") || ""; const documentId = searchParams.get("documentId") || ""; const wikiSlug = searchParams.get("wikiSlug") || ""; + const defaultEntry = searchParams.get("view") !== "weknora" && !requestedTab && !wikiSlug && !sourceChunkId && !documentId; + const showNativeIndex = defaultEntry && (landing.data ?? "wiki") === "wiki" && !!detailsWikiEnabled; const nativeWikiPageUrl = knowledgeBaseId && wikiSlug && preferredTab === "wiki" ? buildWeKnoraWikiPagePath(knowledgeBaseId, wikiSlug) @@ -175,7 +179,7 @@ export function WeKnoraKnowledgeDetailPage() { setFrameSrc(""); setFrameError(null); if ( - nativeWikiPageUrl || + nativeWikiPageUrl || landing.isLoading || showNativeIndex || !knowledgeBaseId || runtime.data?.provider !== "weknora" || !detailsReady @@ -225,6 +229,8 @@ export function WeKnoraKnowledgeDetailPage() { documentId, knowledgeBaseId, nativeWikiPageUrl, + landing.isLoading, + showNativeIndex, preferredTab, runtime.data?.provider, sourceChunkId, @@ -253,7 +259,7 @@ export function WeKnoraKnowledgeDetailPage() { return ; } - if (runtime.isLoading || details.isLoading) { + if (runtime.isLoading || details.isLoading || landing.isLoading) { return (
@@ -262,6 +268,10 @@ export function WeKnoraKnowledgeDetailPage() { ); } + if (showNativeIndex) { + return ; + } + if (runtime.data?.provider !== "weknora") return ; @@ -313,7 +323,7 @@ export function WeKnoraKnowledgeDetailPage() { return (
- {canManageWiki && ( + {detailsWikiEnabled && (
{canEditCurrentWikiArticle && ( @@ -334,6 +344,7 @@ export function WeKnoraKnowledgeDetailPage() { )}
+ {isAdmin && ( + }
)} diff --git a/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraPackageTransfersPage.tsx b/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraPackageTransfersPage.tsx index dc11e341..1c9de6e5 100644 --- a/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraPackageTransfersPage.tsx +++ b/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraPackageTransfersPage.tsx @@ -1,3 +1,4 @@ +import { userFacingKnowledgeMessage } from "@/lib/knowledge-message"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Archive, @@ -180,7 +181,7 @@ export default function WeKnoraPackageTransfersPage() {
{job.error && (

- {job.error} + {userFacingKnowledgeMessage(job.error)}

)}
diff --git a/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraWikiPageView.tsx b/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraWikiPageView.tsx index 22f28e75..bf37b0fb 100644 --- a/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraWikiPageView.tsx +++ b/frontend-web/src/strategy-components/components/resource-management/llmwiki/WeKnoraWikiPageView.tsx @@ -153,11 +153,11 @@ export function WeKnoraWikiPageView({ const pages = publicEmbed ? publicPages : privatePages; const remotePagesList = useMemo(() => pages.data?.pages || [], [pages.data?.pages]); const explicitIndexPage = useMemo( - () => (publicEmbed ? remotePagesList.find(isWikiIndexPage) : undefined), + () => remotePagesList.find(isWikiIndexPage), [publicEmbed, remotePagesList], ); const generatedIndexPage = useMemo( - () => publicEmbed && remotePagesList.length > 0 && !explicitIndexPage + () => remotePagesList.length > 0 && !explicitIndexPage ? { id: "__generated_public_index__", slug: GENERATED_PUBLIC_INDEX_SLUG, @@ -181,14 +181,14 @@ export function WeKnoraWikiPageView({ [generatedIndexPage, remotePagesList], ); const landingPage = useMemo(() => selectDefaultWikiPage(pagesList), [pagesList]); - const wikiSlug = requestedWikiSlug || (publicEmbed ? landingPage?.slug || "" : ""); + const wikiSlug = requestedWikiSlug || landingPage?.slug || ""; const generatedIndexActive = generatedIndexPage?.slug === wikiSlug; const generatedIndexRequested = - publicEmbed && wikiSlug === GENERATED_PUBLIC_INDEX_SLUG && !explicitIndexPage; + wikiSlug === GENERATED_PUBLIC_INDEX_SLUG && !explicitIndexPage; const privatePage = useLlmWikiWikiPage( noteId || null, wikiSlug || null, - !!noteId && !!wikiSlug && !publicEmbed, + !!noteId && !!wikiSlug && !publicEmbed && !generatedIndexActive && !generatedIndexRequested, ); const publicPage = usePublicLlmWikiWikiPage( noteId || null, @@ -377,15 +377,15 @@ export function WeKnoraWikiPageView({ }; if (!wikiSlug) { - if (publicEmbed && pages.isLoading) { + if (pages.isLoading) { return (
- 正在加载公开 Wiki + 正在加载 Wiki 索引
); } - if (publicEmbed && pages.error) { + if (pages.error) { return (
@@ -405,9 +405,9 @@ export function WeKnoraWikiPageView({ } return (
- {publicEmbed ? "该知识库暂无可展示的 Wiki 页面" : "当前链接缺少 Wiki 页面标识"} + 该知识库暂无可展示的 Wiki 页面 {!publicEmbed && ( - + <> )}
); @@ -492,6 +492,7 @@ export function WeKnoraWikiPageView({
+ {globalBase && isDeposited && }