248 lines
6.9 KiB
TypeScript
248 lines
6.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import {
|
|
Boxes,
|
|
ChevronLeft,
|
|
Loader2,
|
|
MessageSquareText,
|
|
Rocket,
|
|
Terminal,
|
|
Workflow,
|
|
} from "lucide-react";
|
|
|
|
import {
|
|
ListPageLayout,
|
|
ListPageSection,
|
|
} from "@/components/layout/list-page-layout";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import {
|
|
SelectionOptionCard,
|
|
selectionOptionGridClassName,
|
|
} from "@/components/ui/selection-option-card";
|
|
import {
|
|
assistantsApi,
|
|
type AssistantType as ApiAssistantType,
|
|
type AssistantUpsert,
|
|
type KnowledgeRetrievalConfig,
|
|
} from "@/lib/api";
|
|
import { defaultTurnConfig } from "@/lib/turn-config";
|
|
|
|
type BuildMethod = "提示词" | "工作流" | "智能体平台" | "OpenCode";
|
|
|
|
const typeFromBuildMethod: Record<
|
|
Exclude<BuildMethod, "智能体平台">,
|
|
ApiAssistantType
|
|
> = {
|
|
提示词: "prompt",
|
|
工作流: "workflow",
|
|
OpenCode: "opencode",
|
|
};
|
|
|
|
type AssistantTypeOption = {
|
|
type: BuildMethod;
|
|
label: string;
|
|
description: string;
|
|
icon: React.ReactNode;
|
|
/** 提示词、工作流、智能体平台已落地;OpenCode 暂时显示即将上线 */
|
|
available: boolean;
|
|
};
|
|
|
|
const assistantTypeOptions: AssistantTypeOption[] = [
|
|
{
|
|
type: "提示词",
|
|
label: "使用提示词构建",
|
|
description: "通过提示词、模型与语音快速搭建对话助手,适合大多数场景。",
|
|
icon: <MessageSquareText size={20} />,
|
|
available: true,
|
|
},
|
|
{
|
|
type: "工作流",
|
|
label: "使用工作流构建",
|
|
description: "用可视化编排串联多个节点,适合多步骤、带分支的复杂流程。",
|
|
icon: <Workflow size={20} />,
|
|
available: true,
|
|
},
|
|
{
|
|
type: "智能体平台",
|
|
label: "从智能体平台创建",
|
|
description:
|
|
"接入 Dify、FastGPT 等智能体平台,复用平台中的提示词、知识库和工作流配置。",
|
|
icon: <Boxes size={20} />,
|
|
available: true,
|
|
},
|
|
{
|
|
type: "OpenCode",
|
|
label: "使用 OpenCode 构建",
|
|
description: "对接 OpenCode 服务,通过提示词驱动代码助手并支持实时语音对话。",
|
|
icon: <Terminal size={20} />,
|
|
available: false,
|
|
},
|
|
];
|
|
|
|
function defaultKnowledgeRetrievalConfig(): KnowledgeRetrievalConfig {
|
|
return {
|
|
mode: "automatic",
|
|
topN: 5,
|
|
scoreThreshold: 0,
|
|
};
|
|
}
|
|
|
|
function baseUpsert(over: Partial<AssistantUpsert>): AssistantUpsert {
|
|
return {
|
|
name: "",
|
|
type: "prompt",
|
|
runtimeMode: "pipeline",
|
|
greeting: "",
|
|
enableInterrupt: true,
|
|
turnConfig: defaultTurnConfig(),
|
|
startup: {
|
|
executionMode: "sequential",
|
|
openingMode: "interruptible",
|
|
entryMode: "wait_user",
|
|
actions: [],
|
|
openingMessage: null,
|
|
},
|
|
visionEnabled: false,
|
|
visionModelResourceId: null,
|
|
visionPromptMode: "runtime",
|
|
visionPrompt: "",
|
|
modelResourceIds: {},
|
|
knowledgeBaseId: null,
|
|
knowledgeRetrievalConfig: defaultKnowledgeRetrievalConfig(),
|
|
toolIds: [],
|
|
prompt: "",
|
|
dynamicVariableDefinitions: {},
|
|
analysisConfig: {
|
|
enabled: false,
|
|
modelResourceId: "",
|
|
fields: [],
|
|
},
|
|
apiUrl: "",
|
|
apiKey: "",
|
|
appId: "",
|
|
graph: {},
|
|
...over,
|
|
};
|
|
}
|
|
|
|
export function CreateAssistantPage() {
|
|
const router = useRouter();
|
|
const [draftName, setDraftName] = useState("");
|
|
const [draftType, setDraftType] = useState<BuildMethod | null>(null);
|
|
const [creating, setCreating] = useState(false);
|
|
const [createError, setCreateError] = useState<string | null>(null);
|
|
|
|
async function handleCreate() {
|
|
if (!draftName.trim() || !draftType || creating) {
|
|
return;
|
|
}
|
|
|
|
setCreating(true);
|
|
setCreateError(null);
|
|
try {
|
|
const assistantType: ApiAssistantType =
|
|
draftType === "智能体平台"
|
|
? "dify"
|
|
: typeFromBuildMethod[draftType];
|
|
const created = await assistantsApi.create(
|
|
baseUpsert({
|
|
name: draftName.trim(),
|
|
type: assistantType,
|
|
}),
|
|
);
|
|
router.push(`/assistants/${created.id}`);
|
|
} catch (error) {
|
|
setCreateError(error instanceof Error ? error.message : "创建失败");
|
|
setCreating(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<ListPageLayout
|
|
title="创建助手"
|
|
className="max-w-[1180px]"
|
|
description="先为助手取个名字,再选择构建方式。确认后将立即创建助手并进入编辑页。"
|
|
action={
|
|
<Button
|
|
variant="outline"
|
|
className="w-full shrink-0 gap-2 border-hairline-strong text-muted-foreground hover:text-foreground sm:w-auto"
|
|
onClick={() => router.push("/assistants")}
|
|
>
|
|
<ChevronLeft size={16} />
|
|
返回列表
|
|
</Button>
|
|
}
|
|
>
|
|
<ListPageSection>
|
|
<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>
|
|
</ListPageSection>
|
|
|
|
<section className="flex flex-col gap-3">
|
|
<div className="text-sm font-medium text-foreground">构建方式</div>
|
|
|
|
<div className={selectionOptionGridClassName}>
|
|
{assistantTypeOptions.map((option) => (
|
|
<SelectionOptionCard
|
|
key={option.type}
|
|
selected={draftType === option.type}
|
|
icon={option.icon}
|
|
title={option.label}
|
|
description={option.description}
|
|
onClick={() => setDraftType(option.type)}
|
|
unselectedTrailing={
|
|
!option.available ? (
|
|
<Badge
|
|
variant="secondary"
|
|
className="h-6 bg-surface-strong px-3 text-xs text-muted-foreground"
|
|
>
|
|
即将上线
|
|
</Badge>
|
|
) : undefined
|
|
}
|
|
/>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<div className="flex items-center justify-end gap-3">
|
|
{createError && (
|
|
<span className="text-xs text-destructive">{createError}</span>
|
|
)}
|
|
<Button
|
|
variant="outline"
|
|
className="border-hairline-strong text-muted-foreground hover:text-foreground"
|
|
disabled={creating}
|
|
onClick={() => router.push("/assistants")}
|
|
>
|
|
取消
|
|
</Button>
|
|
<Button
|
|
className="gap-2"
|
|
disabled={!draftName.trim() || !draftType || creating}
|
|
onClick={() => void handleCreate()}
|
|
>
|
|
{creating ? (
|
|
<Loader2 size={16} className="animate-spin" />
|
|
) : (
|
|
<Rocket size={16} />
|
|
)}
|
|
创建助手
|
|
</Button>
|
|
</div>
|
|
</ListPageLayout>
|
|
);
|
|
}
|