diff --git a/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx b/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx index 917fc787..261c32ff 100644 --- a/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx +++ b/frontend-web/src/components/workspace/agents/agent-detail-panel.tsx @@ -15,6 +15,7 @@ import { SparklesIcon, SquarePenIcon, Trash2Icon, + UploadCloudIcon, UserRoundIcon, XIcon, } from "lucide-react"; @@ -96,6 +97,7 @@ interface AgentDraft { skills: string[]; published: boolean; featured: boolean; + templateName: string; } function draftFromAgent(agent: Agent): AgentDraft { @@ -107,6 +109,7 @@ function draftFromAgent(agent: Agent): AgentDraft { skills: agent.skills ?? [], published: agent.published, featured: agent.featured_order != null, + templateName: agent.template_name ?? "", }; } @@ -305,6 +308,8 @@ export function AgentDetailPanel({ if (draft.description !== (agent.description ?? "")) request.description = draft.description; if (draft.soul !== (agent.soul ?? "")) request.soul = draft.soul; + if (draft.templateName.trim() !== (agent.template_name ?? "")) + request.template_name = draft.templateName.trim(); if (nextPublished !== agent.published) request.published = nextPublished; if (JSON.stringify(draft.skills) !== JSON.stringify(agent.skills ?? [])) request.skills = draft.skills; @@ -546,9 +551,31 @@ export function AgentDetailPanel({ )} + {/* 入库模板名 — view mode shows a hint row when configured. */} + {!editing && agent.template_name?.trim() && ( +
入库模板名
+ + setDraft({ ...draft, templateName: e.currentTarget.value }) + } + placeholder="例如:临时策略库(留空表示不支持入库)" + className="h-8 text-sm" + /> ++ 配置后,对话生成的 Markdown 文件可一键发布到知识入库接口。 +
+公开发布
diff --git a/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx index 43839827..d3df711e 100644 --- a/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -11,8 +11,10 @@ import { PackageIcon, SaveIcon, SquareArrowOutUpRightIcon, + UploadCloudIcon, XIcon, } from "lucide-react"; +import { useParams } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { AnchorHTMLAttributes, HTMLAttributes, ReactNode } from "react"; import { toast } from "sonner"; @@ -35,6 +37,8 @@ import { } from "@/components/ui/select"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { CodeEditor } from "@/components/workspace/code-editor"; +import { publishArtifactToKnowledge } from "@/core/agents/api"; +import { useAgent } from "@/core/agents/hooks"; import { apiFetch } from "@/core/api"; import { downloadArtifactFile, @@ -86,6 +90,13 @@ function extractTextFromChildren(children: ReactNode): string { return ""; } +/** 取 md 标题用于入库:优先文中第一个标题行,否则退回文件名(去扩展名)。 */ +function extractMarkdownTitle(content: string, filepath: string): string { + const heading = /^\s{0,3}#{1,6}\s+(.+?)\s*#*\s*$/m.exec(content); + if (heading?.[1]) return heading[1].trim(); + return getFileName(filepath).replace(/\.(md|markdown)$/i, ""); +} + function findScrollableArtifactElement(root: HTMLElement | null) { if (!root) { return null; @@ -439,6 +450,50 @@ export function ArtifactFileDetail({ updateArtifact, ]); + // 发布入库:仅当当前路由是智能体会话(agents/:agent_id/chats/…)且该智能体 + // 配置了入库模板名(template_name)时,md 产物右上角才出现发布按钮。 + const { agent_id: routeAgentId } = useParams<{ agent_id?: string }>(); + const { agent: routeAgent } = useAgent(routeAgentId ?? null); + const ingestTemplateName = routeAgent?.template_name?.trim() ?? ""; + const canPublishToKnowledge = + !!routeAgentId && + !!ingestTemplateName && + language === "markdown" && + !isSkillFile && + !isMock; + const [isPublishingToKnowledge, setIsPublishingToKnowledge] = useState(false); + const handlePublishToKnowledge = useCallback(async () => { + if (!routeAgentId || isPublishingToKnowledge) { + return; + } + const source = (displayContent ?? "").trim(); + if (!source) { + toast.error("Markdown 内容为空,无法发布入库"); + return; + } + setIsPublishingToKnowledge(true); + try { + const result = await publishArtifactToKnowledge({ + agent_id: routeAgentId, + title: extractMarkdownTitle(source, filepath), + content: source, + }); + if (result.success) { + toast.success(`已发布入库:${result.node_name}`); + } else { + const last = result.attempts[result.attempts.length - 1]; + toast.error( + `发布入库失败:${last?.error ?? last?.response_text ?? "接口返回失败"}`, + ); + } + } catch (error) { + console.error("Failed to publish artifact to knowledge base:", error); + toast.error(error instanceof Error ? error.message : "发布入库失败"); + } finally { + setIsPublishingToKnowledge(false); + } + }, [displayContent, filepath, isPublishingToKnowledge, routeAgentId]); + const [isExportingWord, setIsExportingWord] = useState(false); const handleDownloadAsWord = useCallback(async () => { const source = (displayContent ?? "").trim(); @@ -591,6 +646,15 @@ export function ArtifactFileDetail({+ 配置后,该智能体对话中生成的 Markdown + 文件右上角会出现"发布入库"按钮,可一键发送到知识入库接口。 +
+