diff --git a/frontend/src/components/workspace/messages/message-list-item.tsx b/frontend/src/components/workspace/messages/message-list-item.tsx index 5da40c7d..8c612e34 100644 --- a/frontend/src/components/workspace/messages/message-list-item.tsx +++ b/frontend/src/components/workspace/messages/message-list-item.tsx @@ -1,7 +1,7 @@ import type { Message } from "@langchain/langgraph-sdk"; import { FileIcon, Loader2Icon } from "lucide-react"; import { useParams } from "next/navigation"; -import { memo, useMemo, type ImgHTMLAttributes } from "react"; +import { memo, useMemo, useState, type ImgHTMLAttributes } from "react"; import rehypeKatex from "rehype-katex"; import { Loader } from "@/components/ai-elements/loader"; @@ -18,6 +18,7 @@ import { } from "@/components/ai-elements/reasoning"; import { Task, TaskTrigger } from "@/components/ai-elements/task"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import { resolveArtifactURL } from "@/core/artifacts/utils"; import { useI18n } from "@/core/i18n/hooks"; import { @@ -28,6 +29,7 @@ import { type FileInMessage, } from "@/core/messages/utils"; import { useRehypeSplitWordsIntoSpans } from "@/core/rehype"; +import { materializeSkillYaml } from "@/core/skills"; import { humanMessagePlugins } from "@/core/streamdown"; import { cn } from "@/lib/utils"; @@ -262,6 +264,11 @@ function isImageFile(filename: string): boolean { return IMAGE_EXTENSIONS.includes(getFileExt(filename)); } +function isYamlFile(filename: string): boolean { + const ext = getFileExt(filename); + return ext === "yaml" || ext === "yml"; +} + /** * Format bytes to human-readable size string */ @@ -312,6 +319,11 @@ function RichFileCard({ const { t } = useI18n(); const isUploading = file.status === "uploading"; const isImage = isImageFile(file.filename); + const isYaml = isYamlFile(file.filename); + const [isMaterializing, setIsMaterializing] = useState(false); + const [materializeMessage, setMaterializeMessage] = useState( + null, + ); if (isUploading) { return ( @@ -344,6 +356,28 @@ function RichFileCard({ const fileUrl = resolveArtifactURL(file.path, threadId); + const handleMaterializeYaml = async () => { + if (!isYaml || isMaterializing) return; + setIsMaterializing(true); + setMaterializeMessage(null); + try { + const result = await materializeSkillYaml({ + thread_id: threadId, + path: file.path!, + target_dir: "/mnt/user-data/uploads/skill", + clear_target: true, + }); + setMaterializeMessage( + `已创建 ${result.created_files} 个文件 / ${result.created_directories} 个目录`, + ); + } catch (error) { + const message = error instanceof Error ? error.message : "解析失败"; + setMaterializeMessage(`失败: ${message}`); + } finally { + setIsMaterializing(false); + } + }; + if (isImage) { return ( + {isYaml && ( +
+ + {materializeMessage && ( + + {materializeMessage} + + )} +
+ )} ); } diff --git a/frontend/src/components/workspace/messages/message-list.tsx b/frontend/src/components/workspace/messages/message-list.tsx index c83194e2..a6604775 100644 --- a/frontend/src/components/workspace/messages/message-list.tsx +++ b/frontend/src/components/workspace/messages/message-list.tsx @@ -50,23 +50,23 @@ export function MessageList({ const rehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading); const updateSubtask = useUpdateSubtask(); const messages = messagesOverride ?? thread.messages; - if (thread.isThreadLoading && !suppressThreadLoading) { + if (thread.isThreadLoading && !suppressThreadLoading && messages.length === 0) { return ; } return ( - + {groupMessages(messages, (group) => { if (group.type === "human" || group.type === "assistant") { - return ( + return group.messages.map((msg) => ( - ); + )); } else if (group.type === "assistant:clarification") { const message = group.messages[0]; if (message && hasContent(message)) { @@ -172,9 +172,9 @@ export function MessageList({ {t.subtasks.executing(tasks.size)} , ); - const taskIds = message.tool_calls?.map( - (toolCall) => toolCall.id, - ); + const taskIds = message.tool_calls + ?.filter((toolCall) => toolCall.name === "task") + .map((toolCall) => toolCall.id); for (const taskId of taskIds ?? []) { results.push(