feat: add prompt opening behavior modes
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user