From d403008f9b8b2d69f379274e803ee9c4b41bf8d7 Mon Sep 17 00:00:00 2001 From: yuxuanhui Date: Thu, 10 Sep 2026 12:22:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=20Semi=20AI=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=8D=87=E7=BA=A7=E7=A0=94=E7=A9=B6=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E4=B8=8E=E7=A0=94=E7=A9=B6=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../semi-ai-workspace/issues/01-implement.md | 15 ++ .scratch/semi-ai-workspace/spec.md | 9 + frontend/src/App.tsx | 17 +- frontend/src/ai/AssistantDialogue.tsx | 115 ++++++++++ frontend/src/ai/ChatPanel.tsx | 188 ++++++---------- frontend/src/ai/ResearchInput.tsx | 211 ++++++++++++++++++ frontend/src/ai/ToolCard.tsx | 8 +- frontend/src/ai/style.css | 98 +++++++- frontend/src/ai/workspace.ts | 58 +++++ frontend/src/pages/DatasetPage.tsx | 41 +++- frontend/src/research/FeaturesPage.tsx | 69 +++--- frontend/src/research/ResearchList.tsx | 4 +- frontend/src/research/ResearchWorkspace.tsx | 78 +++++-- frontend/tests/ai.spec.ts | 60 ++++- frontend/tests/research-navigation.spec.ts | 4 +- frontend/tests/semi-ai.spec.ts | 173 ++++++++++++++ 16 files changed, 974 insertions(+), 174 deletions(-) create mode 100644 .scratch/semi-ai-workspace/issues/01-implement.md create mode 100644 .scratch/semi-ai-workspace/spec.md create mode 100644 frontend/src/ai/AssistantDialogue.tsx create mode 100644 frontend/src/ai/ResearchInput.tsx create mode 100644 frontend/tests/semi-ai.spec.ts diff --git a/.scratch/semi-ai-workspace/issues/01-implement.md b/.scratch/semi-ai-workspace/issues/01-implement.md new file mode 100644 index 0000000..707c736 --- /dev/null +++ b/.scratch/semi-ai-workspace/issues/01-implement.md @@ -0,0 +1,15 @@ +# 统一 AI 输入、对话与研究入口 +Type: task +Status: resolved + +按 ../spec.md 实现并验证。主代理负责代码修改与最终验证;只读子代理核对测试启动方式。 + +## Comments +- 2026-09-10:用户已授权本地迭代。开始前仅 AGENTS.md 有用户改动,保持不动。 + +## Answer +- 已完成 AIChatInput 共用输入、AIChatDialogue 消息外壳、真实上下文引用、提示预填与 Semi AI 按钮/图标/Token。数据目录、模板、变体、特征生成入口已接入。 +- 保留 SDK/后端协议、流式 Markdown、工具卡片、确认及停止流程;研究详情支持窄屏,假设长度符合后端 10,000 字符限制。 +- 验证:19 项相关 Playwright E2E 全通过(本地模拟服务);最终上下文摘要调整后 2 项链路复核通过。最终 pnpm build(含 tsc)及 git diff --check 通过。 +- 人工核看助手/生成页宽窄屏、深色截图;独立只读核验未发现业务边界问题。截图位于 output/playwright/。 +- 构建提示:第三方 lottie-web 的 eval 与主分包大小警告仍存在。未执行真实模型调用或平台模拟;未发布。 diff --git a/.scratch/semi-ai-workspace/spec.md b/.scratch/semi-ai-workspace/spec.md new file mode 100644 index 0000000..5bd765a --- /dev/null +++ b/.scratch/semi-ai-workspace/spec.md @@ -0,0 +1,9 @@ +# Semi AI 研究工作空间 + +将现有 AI 研究助手、数据目录、模板工坊、Alpha 变体、特征工程升级为 Semi 2.103.0 AI 组件体验。保留后端协议、固定输入、业务工具卡片和确认执行语义。 + +- 助手采用 AIChatInput / AIChatDialogue,复用流式 Markdown 和业务卡片;显示真实页面引用、按场景预填提示,保留会话与草稿行为。 +- 研究假设采用共用 AI 输入区,调用原有生成接口,保留假设和结构化编辑器。 +- AI 入口使用 colorful Button、Semi AI icons 与 AI tokens;普通业务状态仍保留状态语义。 +- 不启用未实现的文件上传、模型切换、历史消息修改或自动提交。 +- 验证类型、构建、助手/数据目录/研究页面的适用 E2E,并检查宽窄屏视觉。 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7bdf912..9350403 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,7 +11,11 @@ import { Tooltip, } from "@douyinfe/semi-ui-19"; import zhCN from "@douyinfe/semi-ui-19/lib/es/locale/source/zh_CN"; -import { IconSetting, IconComment, IconHistory } from "@douyinfe/semi-icons"; +import { + IconSetting, + IconAIFilledLevel3, + IconHistory, +} from "@douyinfe/semi-icons"; import { QuantFlowPage } from "./research/QuantFlowPage"; import { PipelinePage } from "./research/PipelinePage"; import { FeaturesPage } from "./research/FeaturesPage"; @@ -265,7 +269,7 @@ export default function App() { /> + ); + }, + }} + /> + ); +} diff --git a/frontend/src/ai/ChatPanel.tsx b/frontend/src/ai/ChatPanel.tsx index 6803961..eab640e 100644 --- a/frontend/src/ai/ChatPanel.tsx +++ b/frontend/src/ai/ChatPanel.tsx @@ -1,25 +1,16 @@ import { useChat } from "@ai-sdk/react"; -import { - lazy, - Suspense, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import { - Banner, - Button, - Select, - Spin, - Tag, - TextArea, -} from "@douyinfe/semi-ui-19"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Banner, Button, Select, Spin, Tag } from "@douyinfe/semi-ui-19"; import { api, post } from "../api"; import type { Job } from "../types"; -import { BusinessCard } from "./ToolCard"; -import { contextLabel } from "./workspace"; +import { AssistantDialogue } from "./AssistantDialogue"; +import { ResearchInput } from "./ResearchInput"; +import { + IconAIFilledLevel3, + IconPlus, + IconChevronRight, +} from "@douyinfe/semi-icons"; +import { contextLabel, contextReferences, researchPrompts } from "./workspace"; import { chatTransport } from "./transport"; import { runLabels } from "./types"; import type { @@ -33,12 +24,6 @@ import type { UIAction, } from "./types"; -const MessageMarkdown = lazy(() => - import("./MessageMarkdown").then((module) => ({ - default: module.MessageMarkdown, - })), -); - export function ChatPanel({ open, context, @@ -218,8 +203,10 @@ export function ChatPanel({ useEffect(() => { if (!open) return; previousFocus.current = document.activeElement as HTMLElement; - const textarea = input.current?.querySelector("textarea"); - if (textarea && !textarea.disabled) textarea.focus(); + const editor = input.current?.querySelector( + '[contenteditable="true"]', + ); + if (editor) editor.focus(); else root.current ?.querySelector('button[aria-label="收起研究助手"]') @@ -257,7 +244,16 @@ export function ChatPanel({ } } async function send() { - if (!text.trim() || !conversationId || activeRun || streaming) return; + if ( + !ready || + loading || + !text.trim() || + text.length > 20000 || + !conversationId || + activeRun || + streaming + ) + return; const message = text; setText(""); setFailure(""); @@ -302,6 +298,17 @@ export function ChatPanel({ className="ai-chat" id="research-assistant" aria-label="AI 研究助手" + onKeyDownCapture={(event) => { + // Semi consumes Escape in its editor even without a suggestion popup. + if ( + event.key === "Escape" && + (event.target as HTMLElement).closest('[contenteditable="true"]') + ) { + event.preventDefault(); + event.stopPropagation(); + onClose(); + } + }} onKeyDown={(event) => { if (event.key === "Escape") { event.stopPropagation(); @@ -310,7 +317,7 @@ export function ChatPanel({ if (event.key === "Tab" && window.innerWidth < 1440) { const elements = [ ...(root.current?.querySelectorAll( - 'button:not(:disabled), textarea:not(:disabled), input:not(:disabled), [tabindex="0"]', + 'button:not(:disabled), [contenteditable="true"], input:not(:disabled), [tabindex="0"]', ) ?? []), ].filter((el) => el.offsetParent !== null); if (event.shiftKey && document.activeElement === elements[0]) { @@ -350,10 +357,14 @@ export function ChatPanel({ }} />
-

AI 研究助手

+

+