From bee1e97cdbdd5b7d0c73e46824641141981920f7 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Wed, 5 Aug 2026 08:54:24 +0800 Subject: [PATCH] feat(frontend): compact theme selector, adaptive prompt editor layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Theme dropdown: 亮色/暗色/随系统 as one row of three icons - Prompt editor: sections centered (max-w-7xl) when debug drawer is closed, full-width aligned with tab edges when open - Debug drawer opens by default - Prompt textarea: 8 → 16 rows Co-Authored-By: Claude --- .../assistant-editor/prompt-editor.tsx | 739 +++++++++--------- .../src/components/layout/ThemeToggle.tsx | 10 +- 2 files changed, 382 insertions(+), 367 deletions(-) diff --git a/frontend/src/components/assistant-editor/prompt-editor.tsx b/frontend/src/components/assistant-editor/prompt-editor.tsx index a851d4e..ac87add 100644 --- a/frontend/src/components/assistant-editor/prompt-editor.tsx +++ b/frontend/src/components/assistant-editor/prompt-editor.tsx @@ -43,11 +43,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import type { - DynamicVariableDefinition, - StartupConfig, - Tool, -} from "@/lib/api"; +import type { DynamicVariableDefinition, StartupConfig, Tool } from "@/lib/api"; type ResourceOption = { value: string; label: string }; type PromptOpeningMode = StartupConfig["openingMode"]; @@ -153,7 +149,7 @@ export function PromptEditor({ const selectedAnchorRef = useRef(null); const [activeSection, setActiveSection] = useState("conversation"); - const [debugOpen, setDebugOpen] = useState(false); + const [debugOpen, setDebugOpen] = useState(true); useEffect(() => { const container = scrollContainerRef.current; @@ -245,13 +241,14 @@ export function PromptEditor({ updateForm("startup", { ...form.startup, openingMode: nextMode, - openingMessage: nextMode === "confirmation" - ? { - title: openingMessage?.title ?? "重要提示", - message: openingMessage?.message ?? "请确认已阅读以上信息。", - confirmLabel: openingMessage?.confirmLabel ?? "确认", - } - : null, + openingMessage: + nextMode === "confirmation" + ? { + title: openingMessage?.title ?? "重要提示", + message: openingMessage?.message ?? "请确认已阅读以上信息。", + confirmLabel: openingMessage?.confirmLabel ?? "确认", + } + : null, }); } @@ -322,377 +319,395 @@ export function PromptEditor({ aria-label="提示词配置分区" className="shrink-0 overflow-x-auto overscroll-none bg-background px-4 pt-3 sm:px-6 sm:pt-4 lg:px-8" > -
- {promptSections.map((section) => { - const active = activeSection === section.id; +
+
+ {promptSections.map((section) => { + const active = activeSection === section.id; - return ( - - ); - })} + return ( + + ); + })} +
-
{ - sectionRefs.current.conversation = element; - }} - className="scroll-mt-3 space-y-3" +
- } - title="提示词" - description="描述助手的角色、能力和回答要求" +
{ + sectionRefs.current.conversation = element; + }} + className="scroll-mt-3 space-y-3" > - updateForm("prompt", value)} - placeholder="请输入提示词,描述助手的角色、能力和回答要求" - rows={8} - /> - scrollToSection("variables")} - /> - - - } - title="开场白" - description="助手与用户首次对话时的开场语" - > - updateForm("greeting", value)} - placeholder="请输入助手开场白" - /> - scrollToSection("variables")} - /> - {form.runtimeMode === "pipeline" && ( -
-
-
- 开场白继续方式 -
- -

- {openingModeDescription} -

-
- - {openingMode === "confirmation" && openingMessage && ( -
- - - updateOpeningMessage({ message }) - } - placeholder="请输入需要用户确认的重要信息" - rows={4} - /> - -

- 确认前关闭语音、文字和图片输入;确认后立即停止剩余开场白并进入 Agent。 -

-
- )} - -
-
- Agent 进入行为 -
- -

- 该设置仅控制开场阶段正常完成后的 Agent 首轮行为。 -

-
-
- )} -
-
- -
{ - sectionRefs.current.models = element; - }} - className="scroll-mt-3 space-y-3" - > - } - title="模型与语音" - description={ - form.runtimeMode === "pipeline" - ? "选择运行方式,以及大语言模型、语音识别与语音合成资源" - : "选择运行方式;Realtime 模型内置语音识别与语音合成" - } - > - { - updateForm("runtimeMode", runtimeMode); - if ( - runtimeMode === "realtime" && - (form.startup.actions.length || - form.startup.openingMode !== "interruptible" || - form.startup.entryMode !== "wait_user" || - form.startup.openingMessage) - ) { - updateForm("startup", { - executionMode: "sequential", - openingMode: "interruptible", - entryMode: "wait_user", - actions: [], - openingMessage: null, - }); - } - if (runtimeMode === "realtime") { - updateForm( - "toolIds", - form.toolIds.filter( - (id) => - tools.find((tool) => tool.id === id)?.type !== - "system", - ), - ); - } - }} - /> - - {form.runtimeMode === "pipeline" ? ( - <> - - updateForm("asr", value)} - options={asrOptions} - noneLabel="无" - /> - updateForm("voice", value)} - options={ttsOptions} - noneLabel="无" - /> - - ) : ( - updateForm("realtimeModel", value)} - options={realtimeOptions} - noneLabel="无" - /> - )} - -
- -
{ - sectionRefs.current.capabilities = element; - }} - className="scroll-mt-3 space-y-3" - > - {form.runtimeMode === "pipeline" && ( - - updateForm("visionModelResourceId", value) - } - /> - )} - - {form.runtimeMode === "pipeline" && ( } - title="知识库配置" - description="选择助手回答时可检索的业务知识来源" + icon={} + title="提示词" + description="描述助手的角色、能力和回答要求" > -
- - 知识库选择 - - - updateForm("knowledgeRetrievalConfig", config) - } - /> -
- updateForm("knowledgeBase", value)} - options={kbOptions} - noneLabel="无" + updateForm("prompt", value)} + placeholder="请输入提示词,描述助手的角色、能力和回答要求" + rows={16} + /> + scrollToSection("variables")} />
- )} - } - title="工具" - description="配置该提示词助手可以调用的工具" - > - - tool.status === "active" && - (form.runtimeMode === "pipeline" || tool.type !== "system"), + } + title="开场白" + description="助手与用户首次对话时的开场语" + > + updateForm("greeting", value)} + placeholder="请输入助手开场白" + /> + scrollToSection("variables")} + /> + {form.runtimeMode === "pipeline" && ( +
+
+
+ 开场白继续方式 +
+ +

+ {openingModeDescription} +

+
+ + {openingMode === "confirmation" && openingMessage && ( +
+ + + updateOpeningMessage({ message }) + } + placeholder="请输入需要用户确认的重要信息" + rows={4} + /> + +

+ 确认前关闭语音、文字和图片输入;确认后立即停止剩余开场白并进入 + Agent。 +

+
+ )} + +
+
+ Agent 进入行为 +
+ +

+ 该设置仅控制开场阶段正常完成后的 Agent 首轮行为。 +

+
+
)} - selectedIds={form.toolIds} - onChange={(toolIds) => updateForm("toolIds", toolIds)} - /> -
-
+
+
-
{ - sectionRefs.current.interaction = element; - }} - className="scroll-mt-3 space-y-3" - > - } - title="交互策略" - description="设置实时视频对话时的交互体验" +
{ + sectionRefs.current.models = element; + }} + className="scroll-mt-3 space-y-3" > - {form.runtimeMode === "pipeline" ? ( - - updateForm("enableInterrupt", checked) - } - onConfigChange={(config) => - updateForm("turnConfig", config) + } + title="模型与语音" + description={ + form.runtimeMode === "pipeline" + ? "选择运行方式,以及大语言模型、语音识别与语音合成资源" + : "选择运行方式;Realtime 模型内置语音识别与语音合成" + } + > + { + updateForm("runtimeMode", runtimeMode); + if ( + runtimeMode === "realtime" && + (form.startup.actions.length || + form.startup.openingMode !== "interruptible" || + form.startup.entryMode !== "wait_user" || + form.startup.openingMessage) + ) { + updateForm("startup", { + executionMode: "sequential", + openingMode: "interruptible", + entryMode: "wait_user", + actions: [], + openingMessage: null, + }); + } + if (runtimeMode === "realtime") { + updateForm( + "toolIds", + form.toolIds.filter( + (id) => + tools.find((tool) => tool.id === id)?.type !== + "system", + ), + ); + } + }} + /> + + {form.runtimeMode === "pipeline" ? ( + <> + + updateForm("asr", value)} + options={asrOptions} + noneLabel="无" + /> + updateForm("voice", value)} + options={ttsOptions} + noneLabel="无" + /> + + ) : ( + updateForm("realtimeModel", value)} + options={realtimeOptions} + noneLabel="无" + /> + )} + +
+ +
{ + sectionRefs.current.capabilities = element; + }} + className="scroll-mt-3 space-y-3" + > + {form.runtimeMode === "pipeline" && ( + + updateForm("visionModelResourceId", value) } /> - ) : ( -

- 高级抢话与轮次检测当前仅支持 Pipeline 模式。 -

)} - -
-
{ - sectionRefs.current.variables = element; - }} - className="scroll-mt-3 space-y-3" - > - } - title="动态变量" - description="编辑提示词和开场白中引用的自定义变量" + {form.runtimeMode === "pipeline" && ( + } + title="知识库配置" + description="选择助手回答时可检索的业务知识来源" + > +
+ + 知识库选择 + + + updateForm("knowledgeRetrievalConfig", config) + } + /> +
+ updateForm("knowledgeBase", value)} + options={kbOptions} + noneLabel="无" + /> +
+ )} + + } + title="工具" + description="配置该提示词助手可以调用的工具" + > + + tool.status === "active" && + (form.runtimeMode === "pipeline" || + tool.type !== "system"), + )} + selectedIds={form.toolIds} + onChange={(toolIds) => updateForm("toolIds", toolIds)} + /> + +
+ +
{ + sectionRefs.current.interaction = element; + }} + className="scroll-mt-3 space-y-3" > - - updateForm( - "dynamicVariableDefinitions", - dynamicVariableDefinitions, - ) - } - /> - -
+ } + title="交互策略" + description="设置实时视频对话时的交互体验" + > + {form.runtimeMode === "pipeline" ? ( + + updateForm("enableInterrupt", checked) + } + onConfigChange={(config) => + updateForm("turnConfig", config) + } + /> + ) : ( +

+ 高级抢话与轮次检测当前仅支持 Pipeline 模式。 +

+ )} +
+
+ +
{ + sectionRefs.current.variables = element; + }} + className="scroll-mt-3 space-y-3" + > + } + title="动态变量" + description="编辑提示词和开场白中引用的自定义变量" + > + + updateForm( + "dynamicVariableDefinitions", + dynamicVariableDefinitions, + ) + } + /> + +
+
diff --git a/frontend/src/components/layout/ThemeToggle.tsx b/frontend/src/components/layout/ThemeToggle.tsx index ac31d27..577798b 100644 --- a/frontend/src/components/layout/ThemeToggle.tsx +++ b/frontend/src/components/layout/ThemeToggle.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react"; -import { Check, Laptop, Moon, Sun } from "lucide-react"; +import { Laptop, Moon, Sun } from "lucide-react"; export type ThemeMode = "light" | "dark" | "system"; @@ -60,7 +60,7 @@ export function ThemeModeSelector() {
{themeOptions.map((option) => { const Icon = option.icon; @@ -72,17 +72,17 @@ export function ThemeModeSelector() { type="button" role="radio" aria-checked={selected} + aria-label={option.label} + title={option.label} onClick={() => selectMode(option.value)} className={[ - "flex h-10 w-full items-center gap-2.5 rounded-xl px-3 text-sm font-normal transition-colors", + "flex h-9 items-center justify-center rounded-xl transition-colors", selected ? "bg-surface-strong text-foreground shadow-sm" : "text-muted-foreground hover:bg-surface-strong/60 hover:text-foreground", ].join(" ")} > - {option.label} - {selected && } ); })}