Refactor Sidebar and AssistantPage components to enhance type definitions and user interaction
Updated the Sidebar component to include an optional className property in the icon type definition for better styling flexibility. In the AssistantPage, introduced new FastGptForm and DifyForm types, updated the assistant type options to reflect their availability, and enhanced the state management for creating and editing assistants. Improved the user experience by refining the view handling logic and adding dedicated forms for FastGPT and Dify configurations.
This commit is contained in:
@@ -26,7 +26,7 @@ type SidebarProps = {
|
||||
const mainItems: Array<{
|
||||
key: NavKey;
|
||||
label: string;
|
||||
icon: React.ComponentType<{ size?: number }>;
|
||||
icon: React.ComponentType<{ size?: number; className?: string }>;
|
||||
}> = [
|
||||
{ key: "home", label: "首页", icon: Home },
|
||||
{ key: "test", label: "测试助手", icon: PlayCircle },
|
||||
@@ -35,7 +35,7 @@ const mainItems: Array<{
|
||||
const componentSubItems: Array<{
|
||||
key: NavKey;
|
||||
label: string;
|
||||
icon: React.ComponentType<{ size?: number }>;
|
||||
icon: React.ComponentType<{ size?: number; className?: string }>;
|
||||
}> = [
|
||||
{ key: "components-models", label: "模型资源", icon: Brain },
|
||||
{ key: "components-knowledge", label: "知识库", icon: Database },
|
||||
@@ -45,7 +45,7 @@ const componentSubItems: Array<{
|
||||
const monitorSubItems: Array<{
|
||||
key: NavKey;
|
||||
label: string;
|
||||
icon: React.ComponentType<{ size?: number }>;
|
||||
icon: React.ComponentType<{ size?: number; className?: string }>;
|
||||
}> = [
|
||||
{ key: "history", label: "历史记录", icon: Clock3 },
|
||||
{ key: "dashboard", label: "数据看板", icon: Database },
|
||||
@@ -294,7 +294,7 @@ function NavButton({
|
||||
}: {
|
||||
active: boolean;
|
||||
collapsed: boolean;
|
||||
icon: React.ComponentType<{ size?: number }>;
|
||||
icon: React.ComponentType<{ size?: number; className?: string }>;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
small?: boolean;
|
||||
|
||||
@@ -74,6 +74,25 @@ type AssistantForm = {
|
||||
enableInterrupt: boolean;
|
||||
};
|
||||
|
||||
type FastGptForm = {
|
||||
name: string;
|
||||
appId: string;
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
asr: string;
|
||||
voice: string;
|
||||
enableInterrupt: boolean;
|
||||
};
|
||||
|
||||
type DifyForm = {
|
||||
name: string;
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
asr: string;
|
||||
voice: string;
|
||||
enableInterrupt: boolean;
|
||||
};
|
||||
|
||||
type AssistantType = "提示词" | "工作流" | "Dify" | "FastGPT";
|
||||
|
||||
const assistantTypes: AssistantType[] = ["提示词", "工作流", "Dify", "FastGPT"];
|
||||
@@ -83,7 +102,7 @@ type AssistantTypeOption = {
|
||||
label: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
/** 提示词类型已落地,其余三种暂时显示占位页 */
|
||||
/** 提示词、Dify、FastGPT 类型已落地,工作流暂时显示占位页 */
|
||||
available: boolean;
|
||||
};
|
||||
|
||||
@@ -107,14 +126,14 @@ const assistantTypeOptions: AssistantTypeOption[] = [
|
||||
label: "使用 Dify 构建",
|
||||
description: "对接 Dify 应用,复用其编排能力与知识库配置。",
|
||||
icon: <Boxes size={20} />,
|
||||
available: false,
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
type: "FastGPT",
|
||||
label: "使用 FastGPT 构建",
|
||||
description: "对接 FastGPT 应用,复用其知识库问答与工作流能力。",
|
||||
icon: <Database size={20} />,
|
||||
available: false,
|
||||
available: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -220,9 +239,26 @@ export function AssistantPage() {
|
||||
knowledgeBase: "无",
|
||||
enableInterrupt: true,
|
||||
});
|
||||
const [view, setView] = useState<"list" | "choose" | "create" | "placeholder">(
|
||||
"list",
|
||||
);
|
||||
const [fastGptForm, setFastGptForm] = useState<FastGptForm>({
|
||||
name: "FastGPT 售后咨询助手",
|
||||
appId: "",
|
||||
apiUrl: "https://api.fastgpt.in/api/v1/chat/completions",
|
||||
apiKey: "",
|
||||
asr: "SenseVoice",
|
||||
voice: "晓宁",
|
||||
enableInterrupt: true,
|
||||
});
|
||||
const [difyForm, setDifyForm] = useState<DifyForm>({
|
||||
name: "Dify 知识库问答助手",
|
||||
apiUrl: "https://api.dify.ai/v1/chat-messages",
|
||||
apiKey: "",
|
||||
asr: "SenseVoice",
|
||||
voice: "晓宁",
|
||||
enableInterrupt: true,
|
||||
});
|
||||
const [view, setView] = useState<
|
||||
"list" | "choose" | "create" | "create-dify" | "create-fastgpt" | "placeholder"
|
||||
>("list");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [typeFilter, setTypeFilter] = useState<TypeFilter>("全部");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
@@ -238,6 +274,25 @@ export function AssistantPage() {
|
||||
setView("choose");
|
||||
}
|
||||
|
||||
// 编辑:根据助手类型进入对应的构建/编辑页,并把名称带入相应表单
|
||||
function handleEdit(assistant: AssistantListItem) {
|
||||
if (assistant.type === "提示词") {
|
||||
updateForm("name", assistant.name);
|
||||
setView("create");
|
||||
} else if (assistant.type === "FastGPT") {
|
||||
updateFastGptForm("name", assistant.name);
|
||||
setView("create-fastgpt");
|
||||
} else if (assistant.type === "Dify") {
|
||||
updateDifyForm("name", assistant.name);
|
||||
setView("create-dify");
|
||||
} else {
|
||||
// 工作流:暂时显示占位页
|
||||
setDraftName(assistant.name);
|
||||
setDraftType(assistant.type);
|
||||
setView("placeholder");
|
||||
}
|
||||
}
|
||||
|
||||
function confirmType() {
|
||||
if (!draftName.trim() || !draftType) {
|
||||
return;
|
||||
@@ -247,8 +302,16 @@ export function AssistantPage() {
|
||||
// 提示词类型:复用现有创建表单,并把已填的名称带过去
|
||||
updateForm("name", draftName.trim());
|
||||
setView("create");
|
||||
} else if (draftType === "FastGPT") {
|
||||
// FastGPT 类型:进入 FastGPT 构建表单,并把已填的名称带过去
|
||||
updateFastGptForm("name", draftName.trim());
|
||||
setView("create-fastgpt");
|
||||
} else if (draftType === "Dify") {
|
||||
// Dify 类型:进入 Dify 构建表单,并把已填的名称带过去
|
||||
updateDifyForm("name", draftName.trim());
|
||||
setView("create-dify");
|
||||
} else {
|
||||
// 工作流 / Dify / FastGPT:暂时显示占位页
|
||||
// 工作流:暂时显示占位页
|
||||
setView("placeholder");
|
||||
}
|
||||
}
|
||||
@@ -296,6 +359,26 @@ export function AssistantPage() {
|
||||
[key]: value,
|
||||
}));
|
||||
}
|
||||
|
||||
function updateFastGptForm<K extends keyof FastGptForm>(
|
||||
key: K,
|
||||
value: FastGptForm[K],
|
||||
) {
|
||||
setFastGptForm((prev) => ({
|
||||
...prev,
|
||||
[key]: value,
|
||||
}));
|
||||
}
|
||||
|
||||
function updateDifyForm<K extends keyof DifyForm>(
|
||||
key: K,
|
||||
value: DifyForm[K],
|
||||
) {
|
||||
setDifyForm((prev) => ({
|
||||
...prev,
|
||||
[key]: value,
|
||||
}));
|
||||
}
|
||||
if (view === "list") {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-[1440px] flex-col gap-8">
|
||||
@@ -400,7 +483,7 @@ export function AssistantPage() {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-1.5 border-hairline-strong text-xs text-muted-foreground hover:text-foreground"
|
||||
onClick={() => setView("create")}
|
||||
onClick={() => handleEdit(assistant)}
|
||||
>
|
||||
<Pencil size={14} />
|
||||
编辑
|
||||
@@ -680,6 +763,194 @@ export function AssistantPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (view === "create-dify") {
|
||||
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={difyForm.name}
|
||||
onChange={(value) => updateDifyForm("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={<Boxes size={18} />}
|
||||
title="Dify 应用配置"
|
||||
description="填写 API URL 与 API Key 以对接 Dify 应用。开场白、知识库、提示词等对话编排请在 Dify 平台配置,本页不重复设置。"
|
||||
>
|
||||
<InputField
|
||||
label="API URL"
|
||||
value={difyForm.apiUrl}
|
||||
onChange={(value) => updateDifyForm("apiUrl", value)}
|
||||
placeholder="https://api.dify.ai/v1/chat-messages"
|
||||
/>
|
||||
<InputField
|
||||
label="API Key"
|
||||
value={difyForm.apiKey}
|
||||
onChange={(value) => updateDifyForm("apiKey", value)}
|
||||
placeholder="请输入 Dify API Key"
|
||||
type="password"
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard
|
||||
icon={<Brain size={18} />}
|
||||
title="语音配置"
|
||||
description="配置本平台的语音识别与播报音色。大模型、知识库与开场白由 Dify 应用提供,请前往 Dify 平台配置。"
|
||||
>
|
||||
<SelectField
|
||||
label="语音识别"
|
||||
value={difyForm.asr}
|
||||
onChange={(value) => updateDifyForm("asr", value)}
|
||||
options={["SenseVoice", "Paraformer", "Whisper", "FunASR"]}
|
||||
/>
|
||||
<SelectField
|
||||
label="播报声音"
|
||||
value={difyForm.voice}
|
||||
onChange={(value) => updateDifyForm("voice", value)}
|
||||
options={["晓宁", "晓美", "晓宇", "晓晨"]}
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard
|
||||
icon={<Sparkles size={18} />}
|
||||
title="交互策略"
|
||||
description="设置实时视频对话时的交互体验"
|
||||
>
|
||||
<ToggleRow
|
||||
title="允许用户打断"
|
||||
description="用户说话时,助手可以停止当前播报并重新理解用户输入"
|
||||
checked={difyForm.enableInterrupt}
|
||||
onChange={(checked) =>
|
||||
updateDifyForm("enableInterrupt", checked)
|
||||
}
|
||||
/>
|
||||
</SectionCard>
|
||||
</div>
|
||||
|
||||
<DebugDrawer mode={debugMode} onModeChange={setDebugMode} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (view === "create-fastgpt") {
|
||||
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={fastGptForm.name}
|
||||
onChange={(value) => updateFastGptForm("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={<Database size={18} />}
|
||||
title="FastGPT 应用配置"
|
||||
description="填写 App ID、API URL 与 API Key 以对接 FastGPT 应用。开场白、知识库、提示词等对话编排请在 FastGPT 平台配置,本页不重复设置。"
|
||||
>
|
||||
<InputField
|
||||
label="App ID"
|
||||
value={fastGptForm.appId}
|
||||
onChange={(value) => updateFastGptForm("appId", value)}
|
||||
placeholder="请输入 FastGPT 应用 ID"
|
||||
/>
|
||||
<InputField
|
||||
label="API URL"
|
||||
value={fastGptForm.apiUrl}
|
||||
onChange={(value) => updateFastGptForm("apiUrl", value)}
|
||||
placeholder="https://api.fastgpt.in/api/v1/chat/completions"
|
||||
/>
|
||||
<InputField
|
||||
label="API Key"
|
||||
value={fastGptForm.apiKey}
|
||||
onChange={(value) => updateFastGptForm("apiKey", value)}
|
||||
placeholder="请输入 FastGPT API Key"
|
||||
type="password"
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard
|
||||
icon={<Brain size={18} />}
|
||||
title="语音配置"
|
||||
description="配置本平台的语音识别与播报音色。大模型、知识库与开场白由 FastGPT 应用提供,请前往 FastGPT 平台配置。"
|
||||
>
|
||||
<SelectField
|
||||
label="语音识别"
|
||||
value={fastGptForm.asr}
|
||||
onChange={(value) => updateFastGptForm("asr", value)}
|
||||
options={["SenseVoice", "Paraformer", "Whisper", "FunASR"]}
|
||||
/>
|
||||
<SelectField
|
||||
label="播报声音"
|
||||
value={fastGptForm.voice}
|
||||
onChange={(value) => updateFastGptForm("voice", value)}
|
||||
options={["晓宁", "晓美", "晓宇", "晓晨"]}
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard
|
||||
icon={<Sparkles size={18} />}
|
||||
title="交互策略"
|
||||
description="设置实时视频对话时的交互体验"
|
||||
>
|
||||
<ToggleRow
|
||||
title="允许用户打断"
|
||||
description="用户说话时,助手可以停止当前播报并重新理解用户输入"
|
||||
checked={fastGptForm.enableInterrupt}
|
||||
onChange={(checked) =>
|
||||
updateFastGptForm("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">
|
||||
@@ -1148,6 +1419,35 @@ function SectionCard({
|
||||
);
|
||||
}
|
||||
|
||||
function InputField({
|
||||
label,
|
||||
value,
|
||||
placeholder,
|
||||
type = "text",
|
||||
onChange,
|
||||
}: {
|
||||
label?: string;
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
type?: string;
|
||||
onChange: (value: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<label className="block">
|
||||
{label && (
|
||||
<div className="mb-2 text-sm font-medium text-foreground">{label}</div>
|
||||
)}
|
||||
<Input
|
||||
value={value}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
className="border-hairline-strong bg-background text-foreground placeholder:text-muted-soft"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
function TextAreaField({
|
||||
label,
|
||||
value,
|
||||
|
||||
Reference in New Issue
Block a user