Implement assistant type selection and creation flow in AssistantPage

Added a new selection step for assistant types, allowing users to choose between different construction methods (提示词, 工作流, Dify, FastGPT) before creating an assistant. Enhanced the UI to display options with descriptions and icons, and updated the state management to handle the new view for selecting assistant types. This improves the user experience by guiding users through the assistant creation process.
This commit is contained in:
Xin Wang
2026-06-06 07:29:23 +08:00
parent 2676d61ccd
commit 30f520ba0b

View File

@@ -2,7 +2,11 @@
import {
Bot,
Boxes,
Brain,
Check,
Database,
MessageSquareText,
Mic,
MoreHorizontal,
Pencil,
@@ -11,6 +15,7 @@ import {
Search,
Sparkles,
Trash2,
Workflow,
} from "lucide-react";
import { Button } from "@/components/ui/button";
@@ -53,6 +58,46 @@ type AssistantForm = {
type AssistantType = "提示词" | "工作流" | "Dify" | "FastGPT";
type AssistantTypeOption = {
type: AssistantType;
label: string;
description: string;
icon: React.ReactNode;
/** 提示词类型已落地,其余三种暂时显示占位页 */
available: boolean;
};
const assistantTypeOptions: AssistantTypeOption[] = [
{
type: "提示词",
label: "使用提示词构建",
description: "通过提示词、模型与语音快速搭建对话助手,适合大多数场景。",
icon: <MessageSquareText size={20} />,
available: true,
},
{
type: "工作流",
label: "使用工作流构建",
description: "用可视化编排串联多个节点,适合多步骤、带分支的复杂流程。",
icon: <Workflow size={20} />,
available: false,
},
{
type: "Dify",
label: "使用 Dify 构建",
description: "对接 Dify 应用,复用其编排能力与知识库配置。",
icon: <Boxes size={20} />,
available: false,
},
{
type: "FastGPT",
label: "使用 FastGPT 构建",
description: "对接 FastGPT 应用,复用其知识库问答与工作流能力。",
icon: <Database size={20} />,
available: false,
},
];
type AssistantListItem = {
id: string;
name: string;
@@ -100,8 +145,34 @@ export function AssistantPage() {
knowledgeBase: "政务政策知识库",
enableInterrupt: true,
});
const [view, setView] = useState<"list" | "create">("list");
const [view, setView] = useState<"list" | "choose" | "create" | "placeholder">(
"list",
);
const [searchQuery, setSearchQuery] = useState("");
// choose 步骤的草稿:名称与已选类型,确认后才决定进入哪个构建页
const [draftName, setDraftName] = useState("");
const [draftType, setDraftType] = useState<AssistantType | null>(null);
function startCreate() {
setDraftName("");
setDraftType(null);
setView("choose");
}
function confirmType() {
if (!draftName.trim() || !draftType) {
return;
}
if (draftType === "提示词") {
// 提示词类型:复用现有创建表单,并把已填的名称带过去
updateForm("name", draftName.trim());
setView("create");
} else {
// 工作流 / Dify / FastGPT暂时显示占位页
setView("placeholder");
}
}
const filteredAssistants = mockAssistants.filter((assistant) => {
const keyword = searchQuery.trim().toLowerCase();
@@ -140,7 +211,7 @@ export function AssistantPage() {
<Button
size="lg"
className="w-full shrink-0 gap-2 sm:w-auto"
onClick={() => setView("create")}
onClick={startCreate}
>
<Plus size={16} />
@@ -272,6 +343,189 @@ export function AssistantPage() {
</div>
);
}
if (view === "choose") {
const selectedOption = assistantTypeOptions.find(
(option) => option.type === draftType,
);
return (
<div className="mx-auto flex w-full max-w-[920px] flex-col gap-8">
<div className="flex items-start justify-between gap-6">
<div>
<div className="caption-label text-muted-soft"></div>
<h1 className="font-display display-lg mt-3 text-ink"></h1>
<p className="mt-3 max-w-2xl text-[15px] leading-7 text-muted-foreground">
</p>
</div>
<Button
variant="outline"
size="lg"
className="shrink-0 border-hairline-strong text-muted-foreground hover:text-foreground"
onClick={() => setView("list")}
>
</Button>
</div>
<section className="rounded-2xl border border-hairline bg-card p-6 shadow-sm">
<label className="block">
<div className="mb-2 text-sm font-medium text-foreground">
</div>
<Input
value={draftName}
autoFocus
onChange={(event) => setDraftName(event.target.value)}
placeholder="请输入助手名称"
className="border-hairline-strong bg-background text-foreground placeholder:text-muted-soft"
/>
</label>
</section>
<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">
{assistantTypeOptions.map((option) => {
const selected = draftType === option.type;
return (
<button
key={option.type}
type="button"
onClick={() => setDraftType(option.type)}
className={`group relative flex flex-col gap-4 rounded-2xl border bg-card p-5 text-left transition-colors ${
selected
? "border-primary ring-1 ring-primary"
: "border-hairline hover:border-hairline-strong"
}`}
>
<div className="flex items-center justify-between">
<div className="flex h-11 w-11 items-center justify-center rounded-full bg-surface-strong text-foreground">
{option.icon}
</div>
{selected ? (
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary text-primary-foreground">
<Check size={14} />
</span>
) : (
!option.available && (
<Badge
variant="secondary"
className="h-6 bg-surface-strong px-3 text-xs text-muted-foreground"
>
线
</Badge>
)
)}
</div>
<div>
<div className="text-base font-medium text-foreground">
{option.label}
</div>
<p className="mt-1.5 text-sm leading-6 text-muted-foreground">
{option.description}
</p>
</div>
</button>
);
})}
</div>
</section>
<div className="flex items-center justify-end gap-3">
<Button
variant="outline"
size="lg"
className="border-hairline-strong text-muted-foreground hover:text-foreground"
onClick={() => setView("list")}
>
</Button>
<Button
size="lg"
className="gap-2"
disabled={!draftName.trim() || !draftType}
onClick={confirmType}
>
<Rocket size={16} />
{selectedOption && !selectedOption.available
? "下一步"
: "开始构建"}
</Button>
</div>
</div>
);
}
if (view === "placeholder") {
const option = assistantTypeOptions.find(
(item) => item.type === draftType,
);
return (
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-6">
<div className="flex items-start justify-between gap-6">
<div>
<div className="caption-label text-muted-soft">
{option?.label ?? "新建助手"}
</div>
<h1 className="font-display display-lg mt-3 text-ink">
{draftName.trim() || "创建助手"}
</h1>
<p className="mt-3 max-w-2xl text-[15px] leading-7 text-muted-foreground">
{draftType}
</p>
</div>
<Button
variant="outline"
size="lg"
className="shrink-0 border-hairline-strong text-muted-foreground hover:text-foreground"
onClick={() => setView("list")}
>
</Button>
</div>
<section className="relative overflow-hidden rounded-3xl border border-hairline bg-canvas-soft px-10 py-20 text-center">
<div
aria-hidden
className="pointer-events-none absolute -right-24 top-0 h-72 w-72 rounded-full opacity-50 blur-3xl"
style={{
backgroundImage:
"radial-gradient(circle, color-mix(in srgb, var(--gradient-sky) 50%, transparent), transparent 70%)",
}}
/>
<div
aria-hidden
className="pointer-events-none absolute -left-20 bottom-0 h-64 w-64 rounded-full opacity-45 blur-3xl"
style={{
backgroundImage:
"radial-gradient(circle, color-mix(in srgb, var(--gradient-lavender) 50%, transparent), transparent 70%)",
}}
/>
<div className="relative">
<div className="caption-label inline-flex rounded-full bg-surface-strong px-3 py-1 text-muted-foreground">
</div>
<p className="font-display display-sm mx-auto mt-5 max-w-md text-ink">
{draftType}
</p>
<p className="mx-auto mt-3 max-w-md text-sm leading-7 text-body">
</p>
</div>
</section>
</div>
);
}
return (
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-6">
<div className="flex items-start justify-between gap-6">