Add OpenCode assistant type and configuration form in AssistantPage
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
Waypoints,
|
||||
AudioLines,
|
||||
Square,
|
||||
Terminal,
|
||||
} from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -93,9 +94,25 @@ type DifyForm = {
|
||||
enableInterrupt: boolean;
|
||||
};
|
||||
|
||||
type AssistantType = "提示词" | "工作流" | "Dify" | "FastGPT";
|
||||
type OpenCodeForm = {
|
||||
name: string;
|
||||
prompt: string;
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
asr: string;
|
||||
voice: string;
|
||||
enableInterrupt: boolean;
|
||||
};
|
||||
|
||||
const assistantTypes: AssistantType[] = ["提示词", "工作流", "Dify", "FastGPT"];
|
||||
type AssistantType = "提示词" | "工作流" | "Dify" | "FastGPT" | "OpenCode";
|
||||
|
||||
const assistantTypes: AssistantType[] = [
|
||||
"提示词",
|
||||
"工作流",
|
||||
"Dify",
|
||||
"FastGPT",
|
||||
"OpenCode",
|
||||
];
|
||||
|
||||
type AssistantTypeOption = {
|
||||
type: AssistantType;
|
||||
@@ -135,6 +152,13 @@ const assistantTypeOptions: AssistantTypeOption[] = [
|
||||
icon: <Database size={20} />,
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
type: "OpenCode",
|
||||
label: "使用 OpenCode 构建",
|
||||
description: "对接 OpenCode 服务,通过提示词驱动代码助手并支持实时语音对话。",
|
||||
icon: <Terminal size={20} />,
|
||||
available: true,
|
||||
},
|
||||
];
|
||||
|
||||
type AssistantListItem = {
|
||||
@@ -217,6 +241,12 @@ const mockAssistants: AssistantListItem[] = [
|
||||
type: "FastGPT",
|
||||
updatedAt: "2026-04-28 19:34",
|
||||
},
|
||||
{
|
||||
id: "asst_013",
|
||||
name: "OpenCode 代码助手",
|
||||
type: "OpenCode",
|
||||
updatedAt: "2026-06-07 14:22",
|
||||
},
|
||||
];
|
||||
|
||||
type DebugMode = "text" | "voice";
|
||||
@@ -256,8 +286,24 @@ export function AssistantPage() {
|
||||
voice: "晓宁",
|
||||
enableInterrupt: true,
|
||||
});
|
||||
const [openCodeForm, setOpenCodeForm] = useState<OpenCodeForm>({
|
||||
name: "OpenCode 代码助手",
|
||||
prompt:
|
||||
"你是一个代码助手的语音交互界面,请用简洁、口语化的方式回答用户关于代码与工程的问题。",
|
||||
apiUrl: "http://localhost:4096",
|
||||
apiKey: "",
|
||||
asr: "SenseVoice",
|
||||
voice: "晓宁",
|
||||
enableInterrupt: true,
|
||||
});
|
||||
const [view, setView] = useState<
|
||||
"list" | "choose" | "create" | "create-dify" | "create-fastgpt" | "placeholder"
|
||||
| "list"
|
||||
| "choose"
|
||||
| "create"
|
||||
| "create-dify"
|
||||
| "create-fastgpt"
|
||||
| "create-opencode"
|
||||
| "placeholder"
|
||||
>("list");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [typeFilter, setTypeFilter] = useState<TypeFilter>("全部");
|
||||
@@ -285,6 +331,9 @@ export function AssistantPage() {
|
||||
} else if (assistant.type === "Dify") {
|
||||
updateDifyForm("name", assistant.name);
|
||||
setView("create-dify");
|
||||
} else if (assistant.type === "OpenCode") {
|
||||
updateOpenCodeForm("name", assistant.name);
|
||||
setView("create-opencode");
|
||||
} else {
|
||||
// 工作流:暂时显示占位页
|
||||
setDraftName(assistant.name);
|
||||
@@ -310,6 +359,10 @@ export function AssistantPage() {
|
||||
// Dify 类型:进入 Dify 构建表单,并把已填的名称带过去
|
||||
updateDifyForm("name", draftName.trim());
|
||||
setView("create-dify");
|
||||
} else if (draftType === "OpenCode") {
|
||||
// OpenCode 类型:进入 OpenCode 构建表单,并把已填的名称带过去
|
||||
updateOpenCodeForm("name", draftName.trim());
|
||||
setView("create-opencode");
|
||||
} else {
|
||||
// 工作流:暂时显示占位页
|
||||
setView("placeholder");
|
||||
@@ -379,6 +432,16 @@ export function AssistantPage() {
|
||||
[key]: value,
|
||||
}));
|
||||
}
|
||||
|
||||
function updateOpenCodeForm<K extends keyof OpenCodeForm>(
|
||||
key: K,
|
||||
value: OpenCodeForm[K],
|
||||
) {
|
||||
setOpenCodeForm((prev) => ({
|
||||
...prev,
|
||||
[key]: value,
|
||||
}));
|
||||
}
|
||||
if (view === "list") {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-[1440px] flex-col gap-8">
|
||||
@@ -593,7 +656,7 @@ export function AssistantPage() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-[920px] flex-col gap-8">
|
||||
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-8">
|
||||
<div className="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<h1 className="font-display display-lg text-ink">创建助手</h1>
|
||||
@@ -631,7 +694,7 @@ export function AssistantPage() {
|
||||
<section className="flex flex-col gap-4">
|
||||
<div className="text-sm font-medium text-foreground">构建方式</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{assistantTypeOptions.map((option) => {
|
||||
const selected = draftType === option.type;
|
||||
|
||||
@@ -951,6 +1014,110 @@ export function AssistantPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (view === "create-opencode") {
|
||||
return (
|
||||
<div className="-mt-6 flex h-full flex-col gap-4">
|
||||
<div className="flex shrink-0 items-center justify-between gap-6 border-b border-hairline pb-3 pt-1">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<EditableTitle
|
||||
value={openCodeForm.name}
|
||||
onChange={(value) => updateOpenCodeForm("name", value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="gap-2 border-hairline-strong text-muted-foreground hover:text-foreground"
|
||||
onClick={() => setView("list")}
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
返回
|
||||
</Button>
|
||||
|
||||
<Button className="gap-2">
|
||||
<Save size={16} />
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-0 flex-1 gap-6">
|
||||
<div className="min-w-0 flex-1 space-y-5 overflow-y-auto pr-1">
|
||||
<SectionCard
|
||||
icon={<Terminal size={18} />}
|
||||
title="OpenCode 服务配置"
|
||||
description="填写 OpenCode 服务地址与 API Key 以对接代码助手。"
|
||||
>
|
||||
<InputField
|
||||
label="OpenCode URL"
|
||||
value={openCodeForm.apiUrl}
|
||||
onChange={(value) => updateOpenCodeForm("apiUrl", value)}
|
||||
placeholder="http://localhost:4096"
|
||||
/>
|
||||
<InputField
|
||||
label="API Key"
|
||||
value={openCodeForm.apiKey}
|
||||
onChange={(value) => updateOpenCodeForm("apiKey", value)}
|
||||
placeholder="请输入 OpenCode API Key"
|
||||
type="password"
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard
|
||||
icon={<MessageSquareText size={18} />}
|
||||
title="提示词"
|
||||
description="描述助手的角色、能力和回答要求"
|
||||
>
|
||||
<TextAreaField
|
||||
value={openCodeForm.prompt}
|
||||
onChange={(value) => updateOpenCodeForm("prompt", value)}
|
||||
placeholder="请输入提示词,描述助手的角色、能力和回答要求"
|
||||
rows={8}
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard
|
||||
icon={<Brain size={18} />}
|
||||
title="语音配置"
|
||||
description="配置本平台的语音识别与播报音色。"
|
||||
>
|
||||
<SelectField
|
||||
label="语音识别"
|
||||
value={openCodeForm.asr}
|
||||
onChange={(value) => updateOpenCodeForm("asr", value)}
|
||||
options={["SenseVoice", "Paraformer", "Whisper", "FunASR"]}
|
||||
/>
|
||||
<SelectField
|
||||
label="播报声音"
|
||||
value={openCodeForm.voice}
|
||||
onChange={(value) => updateOpenCodeForm("voice", value)}
|
||||
options={["晓宁", "晓美", "晓宇", "晓晨"]}
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard
|
||||
icon={<Sparkles size={18} />}
|
||||
title="交互策略"
|
||||
description="设置实时视频对话时的交互体验"
|
||||
>
|
||||
<ToggleRow
|
||||
title="允许用户打断"
|
||||
description="用户说话时,助手可以停止当前播报并重新理解用户输入"
|
||||
checked={openCodeForm.enableInterrupt}
|
||||
onChange={(checked) =>
|
||||
updateOpenCodeForm("enableInterrupt", checked)
|
||||
}
|
||||
/>
|
||||
</SectionCard>
|
||||
</div>
|
||||
|
||||
<DebugDrawer mode={debugMode} onModeChange={setDebugMode} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="-mt-6 flex h-full flex-col gap-4">
|
||||
<div className="flex shrink-0 items-center justify-between gap-6 border-b border-hairline pb-3 pt-1">
|
||||
|
||||
Reference in New Issue
Block a user