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 = { const nextConfig: NextConfig = {
/* config options here */ /* config options here */
allowedDevOrigins: ["127.0.0.1"],
}; };
export default nextConfig; export default nextConfig;

View File

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