Enhance OpenCode form handling in AssistantPage
- Introduce a new model field in the OpenCode form to manage language model selection. - Refactor the form handling logic to improve data loading and error management for OpenCode assistants. - Update UI components to utilize ResourceSelectField for model and voice configuration, enhancing user experience. - Clear form fields when creating new OpenCode entries to ensure a fresh start for users.
This commit is contained in:
@@ -113,6 +113,7 @@ type OpenCodeForm = {
|
||||
prompt: string;
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
model: string;
|
||||
asr: string;
|
||||
voice: string;
|
||||
enableInterrupt: boolean;
|
||||
@@ -243,6 +244,7 @@ export function AssistantPage() {
|
||||
"你是一个代码助手的语音交互界面,请用简洁、口语化的方式回答用户关于代码与工程的问题。",
|
||||
apiUrl: "http://localhost:4096",
|
||||
apiKey: "",
|
||||
model: "",
|
||||
asr: "",
|
||||
voice: "",
|
||||
enableInterrupt: true,
|
||||
@@ -385,8 +387,15 @@ export function AssistantPage() {
|
||||
setListError(error instanceof Error ? error.message : "加载助手失败");
|
||||
}
|
||||
} else if (assistant.type === "OpenCode") {
|
||||
updateOpenCodeForm("name", assistant.name);
|
||||
setView("create-opencode");
|
||||
void loadResources();
|
||||
setSaveError(null);
|
||||
setEditingId(assistant.id);
|
||||
try {
|
||||
fillOpenCodeForm(await assistantsApi.get(assistant.id));
|
||||
setView("create-opencode");
|
||||
} catch (error) {
|
||||
setListError(error instanceof Error ? error.message : "加载助手失败");
|
||||
}
|
||||
} else {
|
||||
// 工作流:暂时显示占位页
|
||||
setDraftName(assistant.name);
|
||||
@@ -417,6 +426,7 @@ export function AssistantPage() {
|
||||
appId: "",
|
||||
apiUrl: "",
|
||||
apiKey: "",
|
||||
model: "",
|
||||
asr: "",
|
||||
voice: "",
|
||||
enableInterrupt: true,
|
||||
@@ -437,8 +447,19 @@ export function AssistantPage() {
|
||||
});
|
||||
setView("create-dify");
|
||||
} else if (draftType === "OpenCode") {
|
||||
// OpenCode 类型:进入 OpenCode 构建表单,并把已填的名称带过去
|
||||
updateOpenCodeForm("name", draftName.trim());
|
||||
// OpenCode 类型:新建,清空表单 + 带入名称
|
||||
void loadResources();
|
||||
setEditingId(null);
|
||||
setSaveError(null);
|
||||
setOpenCodeForm({
|
||||
name: draftName.trim(),
|
||||
prompt: "",
|
||||
apiUrl: "",
|
||||
apiKey: "",
|
||||
asr: "",
|
||||
voice: "",
|
||||
enableInterrupt: true,
|
||||
});
|
||||
setView("create-opencode");
|
||||
} else {
|
||||
// 工作流:暂时显示占位页
|
||||
@@ -529,6 +550,7 @@ export function AssistantPage() {
|
||||
apiUrl: a.apiUrl,
|
||||
// 编辑时不把打码占位符放入输入框;空值写回后端表示保留旧 key
|
||||
apiKey: "",
|
||||
model: a.llmCredentialId ?? "",
|
||||
asr: a.asrCredentialId ?? "",
|
||||
voice: a.ttsCredentialId ?? "",
|
||||
enableInterrupt: a.enableInterrupt,
|
||||
@@ -578,6 +600,36 @@ export function AssistantPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// ---- OpenCode ----
|
||||
function fillOpenCodeForm(a: Assistant) {
|
||||
setOpenCodeForm({
|
||||
name: a.name,
|
||||
prompt: a.prompt,
|
||||
apiUrl: a.apiUrl,
|
||||
// 编辑时不把打码占位符放入输入框;空值写回后端表示保留旧 key
|
||||
apiKey: "",
|
||||
asr: a.asrCredentialId ?? "",
|
||||
voice: a.ttsCredentialId ?? "",
|
||||
enableInterrupt: a.enableInterrupt,
|
||||
});
|
||||
}
|
||||
|
||||
function handleSaveOpenCode() {
|
||||
void save(
|
||||
baseUpsert({
|
||||
name: openCodeForm.name.trim(),
|
||||
type: "opencode",
|
||||
enableInterrupt: openCodeForm.enableInterrupt,
|
||||
llmCredentialId: openCodeForm.model || null,
|
||||
asrCredentialId: openCodeForm.asr || null,
|
||||
ttsCredentialId: openCodeForm.voice || null,
|
||||
prompt: openCodeForm.prompt,
|
||||
apiUrl: openCodeForm.apiUrl,
|
||||
apiKey: openCodeForm.apiKey,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const listItems: AssistantListItem[] = assistants.map((a) => ({
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
@@ -1300,8 +1352,21 @@ export function AssistantPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 gap-2">
|
||||
<Button className="gap-2">
|
||||
<Save size={16} />
|
||||
{saveError && (
|
||||
<span className="self-center text-xs text-destructive">
|
||||
{saveError}
|
||||
</span>
|
||||
)}
|
||||
<Button
|
||||
className="gap-2"
|
||||
disabled={saving || !openCodeForm.name.trim()}
|
||||
onClick={() => void handleSaveOpenCode()}
|
||||
>
|
||||
{saving ? (
|
||||
<Loader2 size={16} className="animate-spin" />
|
||||
) : (
|
||||
<Save size={16} />
|
||||
)}
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
@@ -1325,7 +1390,7 @@ export function AssistantPage() {
|
||||
value={openCodeForm.apiKey}
|
||||
onChange={(value) => updateOpenCodeForm("apiKey", value)}
|
||||
placeholder="请输入 OpenCode API Key"
|
||||
storedValueMask=""
|
||||
storedValueMask={storedApiKeyMask}
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
@@ -1344,20 +1409,29 @@ export function AssistantPage() {
|
||||
|
||||
<SectionCard
|
||||
icon={<Brain size={18} />}
|
||||
title="语音配置"
|
||||
description="配置本平台的语音识别与播报音色。"
|
||||
title="模型与语音配置"
|
||||
description="配置 OpenCode 使用的大语言模型、语音识别与语音合成资源。"
|
||||
>
|
||||
<SelectField
|
||||
<ResourceSelectField
|
||||
label="大语言模型"
|
||||
value={openCodeForm.model}
|
||||
onChange={(value) => updateOpenCodeForm("model", value)}
|
||||
options={credOptions("LLM")}
|
||||
noneLabel="无"
|
||||
/>
|
||||
<ResourceSelectField
|
||||
label="语音识别"
|
||||
value={openCodeForm.asr}
|
||||
onChange={(value) => updateOpenCodeForm("asr", value)}
|
||||
options={["SenseVoice", "Paraformer", "Whisper", "FunASR"]}
|
||||
options={credOptions("ASR")}
|
||||
noneLabel="无"
|
||||
/>
|
||||
<SelectField
|
||||
label="播报声音"
|
||||
<ResourceSelectField
|
||||
label="语音合成"
|
||||
value={openCodeForm.voice}
|
||||
onChange={(value) => updateOpenCodeForm("voice", value)}
|
||||
options={["晓宁", "晓美", "晓宇", "晓晨"]}
|
||||
options={credOptions("TTS")}
|
||||
noneLabel="无"
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
@@ -1997,40 +2071,6 @@ function TextAreaField({
|
||||
);
|
||||
}
|
||||
|
||||
function SelectField({
|
||||
label,
|
||||
value,
|
||||
options,
|
||||
onChange,
|
||||
}: {
|
||||
label?: string;
|
||||
value: string;
|
||||
options: string[];
|
||||
onChange: (value: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="block">
|
||||
{label && (
|
||||
<div className="mb-2 text-sm font-medium text-foreground">{label}</div>
|
||||
)}
|
||||
|
||||
<Select value={value} onValueChange={onChange}>
|
||||
<SelectTrigger className="w-full border-hairline-strong bg-background text-foreground">
|
||||
<SelectValue placeholder={label ? `请选择${label}` : "请选择"} />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent className="border-hairline bg-popover text-popover-foreground">
|
||||
{options.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Radix Select 不允许空字符串 value,用哨兵表示"未选/无"
|
||||
const NONE_VALUE = "__none__";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user