技能展示适配持久化 + 参考文献收集与新对话流式修复

- 后端新增 skill_display_adapters 表,持久化每个技能的 display 配置与 sample
  payload;/api/skills 列表、详情、设置接口同步读写
- 新增 /api/skills/{name}/display-sample 端点,编辑器优先加载库中样例
- 前端展示适配面板改为从后端加载持久样例,无样例时提示"自动识别"
- 参考文献收集修复:仅在 bash/shell/python 等执行类工具上从参数推断技能
  名,避免 read_file / web_fetch 误匹配;引用列表纳入最新一条助手消息
- ChatBox 改用「最新 human → 最新 ai」窗口收集引用,并仅保留 citation
  模式
- 修复「新对话」点击后已发消息消失:useThreadStream 只在 threadId 切回
  空值时重置/中止 SSE,避免 onCreated 分配 id 时把刚开始的流打断;
  ChatPage 在 landing 状态隐藏 MessageList,移除 KeyedChatPage remount
- 技能详情对话框:所有者可查看源码与文件 tab(不再要求管理员)
- LocalSandbox:Windows PowerShell 5 路径下把 && 重写为 ;,并以
  UTF-8 解码 subprocess 输出,避免中文乱码
- knowledge-search-v2 mock 样例改为中文固定测试数据

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.7 authored and xuhd committed 2026-05-28 18:28:41 +08:00
1 parent 7adb93fb8e
commit 0256d527f0
16 files changed
+2722 -2134

No files matched your search

+19 -8
View File
@@ -231,14 +231,25 @@ export default function ChatPage() {
</header>
<main className="flex min-h-0 max-w-full grow flex-col">
<div className="flex size-full justify-center">
<MessageList
className={cn("size-full", !isLandingState && "pt-10")}
threadId={threadId}
thread={thread}
paddingBottom={messageListPaddingBottom}
tokenUsageEnabled={tokenUsageEnabled}
onClarificationSubmit={handleClarificationSubmit}
/>
{/*
Landing state covers two cases: (a) a brand-new chat that
hasn't sent anything yet, and (b) the user clicking 「新对话」
mid-stream. In (b) the underlying useStream's internal
stream.values takes one or two effect ticks to clear, so
rendering MessageList unconditionally would leak the previous
conversation under the welcome card. Skip rendering during
the landing state to guarantee a clean slate.
*/}
{!isLandingState && (
<MessageList
className={cn("size-full", "pt-10")}
threadId={threadId}
thread={thread}
paddingBottom={messageListPaddingBottom}
tokenUsageEnabled={tokenUsageEnabled}
onClarificationSubmit={handleClarificationSubmit}
/>
)}
</div>
<div className="absolute right-0 bottom-0 left-0 z-30 flex justify-center px-4">
<div
+4 -16
View File
@@ -1,5 +1,5 @@
import type { CSSProperties, ReactNode } from "react";
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
import { Navigate, Route, Routes } from "react-router-dom";
import { getStoredAuth } from "@/core/auth";
@@ -89,20 +89,6 @@ export function ChatRuntime({ children }: { children: ReactNode }) {
);
}
/**
* 让 ChatPage 在每次真正的 react-router navigation 上完整 remount。
*
* 背景:点「新对话」时即使路径不变,我们也会用 `navigate(..., { replace: true,
* state: { resetAt } })` 强制刷新 location.key —— 用它作 key 能保证 useStream、
* useThreadStream、optimistic state、refs 一次性全部丢弃,避免上一条流的
* `stream.values` 残留把欢迎页底下的旧消息拖出来(SDK 的 stream.clear() 需要
* 多次 effect tick 才会清干净,中间过渡帧用户能看见)。
*/
function KeyedChatPage({ render }: { render: () => ReactNode }) {
const key = useLocation().key;
return <ChatRuntime key={key}>{render()}</ChatRuntime>;
}
export default function WorkspaceRoutes({
pageSidebarCollapsed = false,
pageSidebarHidden = false,
@@ -137,7 +123,9 @@ export default function WorkspaceRoutes({
path="chats/:thread_id"
element={
<WorkspaceLayout {...layoutProps}>
<KeyedChatPage render={() => <ChatPage />} />
<ChatRuntime>
<ChatPage />
</ChatRuntime>
</WorkspaceLayout>
}
/>