Add runtime mode selection to AssistantPage and configure allowed development origins

Introduced a new feature in AssistantPage allowing users to select between 'pipeline' and 'realtime' runtime modes, enhancing the flexibility of the assistant's functionality. Updated the configuration to include 'allowedDevOrigins' for local development. This change improves user experience by providing clear options for runtime configurations and streamlining the development process.
This commit is contained in:
Xin Wang
2026-06-07 11:39:37 +08:00
parent d090b49001
commit 7b80852e03
2 changed files with 139 additions and 51 deletions

View File

@@ -2,6 +2,7 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
allowedDevOrigins: ["127.0.0.1"],
};
export default nextConfig;

View File

@@ -7,10 +7,10 @@ import {
Check,
Database,
MessageSquareText,
Mic,
MoreHorizontal,
Pencil,
Plus,
Rocket,
Search,
Sparkles,
Trash2,
@@ -48,10 +48,14 @@ import {
} from "@/components/ui/card";
import { useState } from "react";
type RuntimeMode = "pipeline" | "realtime";
type AssistantForm = {
name: string;
greeting: string;
prompt: string;
runtimeMode: RuntimeMode;
realtimeModel: string;
model: string;
asr: string;
voice: string;
@@ -190,6 +194,8 @@ export function AssistantPage() {
greeting: "您好,我是 AI 视频助手,请问有什么可以帮您?",
prompt:
"你是一名专业的政务视频咨询助手,负责为市民提供政策解读、办事指南和常见问题解答。\n\n请遵循以下原则\n1. 回答准确、简洁,使用通俗易懂的语言\n2. 不确定的信息应明确告知,不编造政策内容\n3. 涉及具体办事流程时,引导用户前往官方渠道核实",
runtimeMode: "pipeline",
realtimeModel: "gpt-realtime-2",
model: "DeepSeek-V3",
asr: "SenseVoice",
voice: "晓宁",
@@ -686,6 +692,66 @@ export function AssistantPage() {
</div>
<div className="space-y-5">
<SectionCard>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<button
type="button"
onClick={() => updateForm("runtimeMode", "pipeline")}
className={[
"rounded-2xl border p-5 text-left transition-colors",
form.runtimeMode === "pipeline"
? "border-primary bg-primary/5 ring-1 ring-primary"
: "border-hairline bg-canvas-soft hover:border-hairline-strong",
].join(" ")}
>
<div className="mb-3 flex items-center justify-between gap-3">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-surface-strong text-foreground">
<Boxes size={18} />
</div>
<div className="font-medium text-foreground">Pipeline </div>
</div>
{form.runtimeMode === "pipeline" && (
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground">
<Check size={14} />
</span>
)}
</div>
<p className="text-sm leading-6 text-muted-foreground">
ASRLLM TTS 线
</p>
</button>
<button
type="button"
onClick={() => updateForm("runtimeMode", "realtime")}
className={[
"rounded-2xl border p-5 text-left transition-colors",
form.runtimeMode === "realtime"
? "border-primary bg-primary/5 ring-1 ring-primary"
: "border-hairline bg-canvas-soft hover:border-hairline-strong",
].join(" ")}
>
<div className="mb-3 flex items-center justify-between gap-3">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-surface-strong text-foreground">
<MessageSquareText size={18} />
</div>
<div className="font-medium text-foreground">Realtime </div>
</div>
{form.runtimeMode === "realtime" && (
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground">
<Check size={14} />
</span>
)}
</div>
<p className="text-sm leading-6 text-muted-foreground">
使
</p>
</button>
</div>
</SectionCard>
<SectionCard
icon={<Bot size={18} />}
title="基础信息"
@@ -714,18 +780,51 @@ export function AssistantPage() {
/>
</SectionCard>
<SectionCard
icon={<Brain size={18} />}
title="模型配置"
description="选择大语言模型和知识库能力"
>
<SelectField
label="大语言模型"
value={form.model}
onChange={(value) => updateForm("model", value)}
options={["DeepSeek-V3", "Qwen-Max", "Kimi-K2", "Doubao-Pro", "GPT-4o"]}
/>
</SectionCard>
{form.runtimeMode === "realtime" && (
<SectionCard
icon={<Brain size={18} />}
title="Realtime 配置"
description="当前模式下 ASR 与 TTS 由 Realtime 模型内置完成"
>
<SelectField
label="Realtime 模型"
value={form.realtimeModel}
onChange={(value) => updateForm("realtimeModel", value)}
options={["gpt-realtime-2", "gpt-realtime", "gpt-4o-realtime-preview"]}
/>
</SectionCard>
)}
{form.runtimeMode === "pipeline" && (
<SectionCard
icon={<Brain size={18} />}
title="Pipeline 配置"
description="选择 ASR、LLM 和 TTS 组成级联语音管线"
>
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
<SelectField
label="大语言模型"
value={form.model}
onChange={(value) => updateForm("model", value)}
options={["DeepSeek-V3", "Qwen-Max", "Kimi-K2", "Doubao-Pro", "GPT-4o"]}
/>
<SelectField
label="语音识别"
value={form.asr}
onChange={(value) => updateForm("asr", value)}
options={["SenseVoice", "Paraformer", "Whisper", "FunASR"]}
/>
<SelectField
label="播报声音"
value={form.voice}
onChange={(value) => updateForm("voice", value)}
options={["晓宁", "晓美", "晓宇", "晓晨"]}
/>
</div>
</SectionCard>
)}
<SectionCard
icon={<Database size={18} />}
@@ -740,28 +839,6 @@ export function AssistantPage() {
/>
</SectionCard>
<SectionCard
icon={<Mic size={18} />}
title="语音配置"
description="配置语音识别模型和播报声音"
>
<div className="grid grid-cols-2 gap-4">
<SelectField
label="语音识别"
value={form.asr}
onChange={(value) => updateForm("asr", value)}
options={["SenseVoice", "Paraformer", "Whisper", "FunASR"]}
/>
<SelectField
label="播报声音"
value={form.voice}
onChange={(value) => updateForm("voice", value)}
options={["晓宁", "晓美", "晓宇", "晓晨"]}
/>
</div>
</SectionCard>
<SectionCard
icon={<Sparkles size={18} />}
title="交互策略"
@@ -785,29 +862,39 @@ function SectionCard({
description,
children,
}: {
icon: React.ReactNode;
title: string;
description: string;
icon?: React.ReactNode;
title?: string;
description?: string;
children: React.ReactNode;
}) {
const hasHeader = Boolean(title);
return (
<Card className="rounded-2xl border-hairline bg-card text-card-foreground shadow-sm">
<CardHeader>
<div className="flex items-start gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-surface-strong text-foreground">
{icon}
</div>
{hasHeader && (
<CardHeader>
<div className="flex items-start gap-3">
{icon && (
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-surface-strong text-foreground">
{icon}
</div>
)}
<div>
<CardTitle className="text-base font-medium">{title}</CardTitle>
<CardDescription className="mt-1 text-muted-foreground">
{description}
</CardDescription>
<div>
<CardTitle className="text-base font-medium">{title}</CardTitle>
{description && (
<CardDescription className="mt-1 text-muted-foreground">
{description}
</CardDescription>
)}
</div>
</div>
</div>
</CardHeader>
</CardHeader>
)}
<CardContent className="space-y-4">{children}</CardContent>
<CardContent className={hasHeader ? "space-y-4" : undefined}>
{children}
</CardContent>
</Card>
);
}