feat: add prompt opening behavior modes

This commit is contained in:
Xin Wang
2026-08-04 13:29:21 +08:00
parent 78bba090a2
commit 5bf5987fe4
11 changed files with 235 additions and 97 deletions

View File

@@ -26,7 +26,6 @@ import {
ResourceSelectField,
RuntimeModeSelector,
TextAreaField,
ToggleRow,
ToolPicker,
} from "@/components/assistant-editor/editor-controls";
import type { AssistantForm } from "@/components/assistant-editor/types";
@@ -35,9 +34,52 @@ import { VisionConfigSection } from "@/components/editor/vision-config-section";
import { TurnConfigEditor } from "@/components/turn-config-editor";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import type { DynamicVariableDefinition, Tool } from "@/lib/api";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import type {
DynamicVariableDefinition,
StartupConfig,
Tool,
} from "@/lib/api";
type ResourceOption = { value: string; label: string };
type PromptOpeningMode = StartupConfig["openingMode"];
type PromptEntryMode = StartupConfig["entryMode"];
const OPENING_MODE_OPTIONS: Array<{
value: PromptOpeningMode;
label: string;
description: string;
}> = [
{
value: "interruptible",
label: "可打断",
description: "用户说话、发文字或图片时立即结束开场白,并保留这次输入。",
},
{
value: "playback",
label: "播完继续",
description: "开场白播放期间关闭输入,实际播放完成后进入 Agent。",
},
{
value: "confirmation",
label: "需要弹窗确认继续",
description: "关闭对话输入并显示弹窗,用户确认后立即进入 Agent。",
},
];
const ENTRY_MODE_OPTIONS: Array<{
value: PromptEntryMode;
label: string;
}> = [
{ value: "wait_user", label: "等待下一轮用户输入" },
{ value: "generate", label: "进入后立即回复" },
];
const promptSections = [
{ id: "conversation", label: "对话内容" },
@@ -94,6 +136,10 @@ export function PromptEditor({
handlePromptModelChange,
}: PromptEditorProps) {
const openingMessage = form.startup.openingMessage;
const openingMode = form.startup.openingMode;
const openingModeDescription = OPENING_MODE_OPTIONS.find(
(option) => option.value === openingMode,
)?.description;
const scrollContainerRef = useRef<HTMLDivElement>(null);
const sectionRefs = useRef<Record<PromptSectionId, HTMLElement | null>>({
conversation: null,
@@ -192,15 +238,15 @@ export function PromptEditor({
});
}
function setOpeningMessage(enabled: boolean) {
function setOpeningMode(nextMode: PromptOpeningMode) {
updateForm("startup", {
...form.startup,
openingMessage: enabled
openingMode: nextMode,
openingMessage: nextMode === "confirmation"
? {
title: "重要提示",
message: "请确认已阅读以上信息。",
confirmLabel: "确认",
skipSpeechOnConfirm: true,
title: openingMessage?.title ?? "重要提示",
message: openingMessage?.message ?? "请确认已阅读以上信息。",
confirmLabel: openingMessage?.confirmLabel ?? "确认",
}
: null,
});
@@ -322,14 +368,33 @@ export function PromptEditor({
/>
{form.runtimeMode === "pipeline" && (
<div className="mt-4 space-y-3 border-t border-hairline pt-4">
<ToggleRow
title="开场确认弹窗"
description="与开场白同时显示,用户确认后按下方策略进入对话。"
checked={Boolean(openingMessage)}
onChange={setOpeningMessage}
/>
<div>
<div className="mb-1.5 text-sm font-medium text-foreground">
</div>
<Select
value={openingMode}
onValueChange={(value: PromptOpeningMode) =>
setOpeningMode(value)
}
>
<SelectTrigger className="w-full border-hairline-strong bg-background text-foreground">
<SelectValue />
</SelectTrigger>
<SelectContent>
{OPENING_MODE_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="mt-1.5 text-xs leading-5 text-muted-foreground">
{openingModeDescription}
</p>
</div>
{openingMessage && (
{openingMode === "confirmation" && openingMessage && (
<div className="space-y-3 rounded-xl border border-hairline bg-canvas-soft p-4">
<label className="block">
<span className="mb-1.5 block text-sm font-medium text-foreground">
@@ -368,33 +433,39 @@ export function PromptEditor({
className="border-hairline-strong bg-background"
/>
</label>
<ToggleRow
title="确认后跳过剩余开场白"
description="开启后立即停止尚未播完的开场白;关闭后等待播报完成再进入对话。"
checked={openingMessage.skipSpeechOnConfirm}
onChange={(skipSpeechOnConfirm) =>
updateOpeningMessage({ skipSpeechOnConfirm })
}
/>
<p className="text-xs leading-5 text-muted-foreground">
Agent
</p>
</div>
)}
<div className="border-t border-hairline pt-3">
<ToggleRow
title="进入后立即回复"
description="开启后由助手主动生成首句;关闭后等待用户下一轮输入。"
checked={form.startup.entryMode === "generate"}
onChange={(generate) =>
<div className="mb-1.5 text-sm font-medium text-foreground">
Agent
</div>
<Select
value={form.startup.entryMode}
onValueChange={(entryMode: PromptEntryMode) =>
updateForm("startup", {
...form.startup,
entryMode: generate ? "generate" : "wait_user",
entryMode,
})
}
/>
>
<SelectTrigger className="w-full border-hairline-strong bg-background text-foreground">
<SelectValue />
</SelectTrigger>
<SelectContent>
{ENTRY_MODE_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="mt-1.5 text-xs leading-5 text-muted-foreground">
Agent
</p>
</div>
</div>
)}
@@ -423,10 +494,13 @@ export function PromptEditor({
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,