From b51b768c299020574c75072f5708225a0be68b55 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Thu, 6 Aug 2026 23:15:06 +0800 Subject: [PATCH] feat(frontend): add template library and extract create-assistant page Introduce a split-view template library for creating assistants from presets, share selection cards between create and template flows, and move the choose step out of AssistantPage into CreateAssistantPage. Co-authored-by: Cursor --- frontend/src/app/assistants/new/page.tsx | 4 +- .../app/assistants/templates/[id]/page.tsx | 10 + .../src/app/assistants/templates/page.tsx | 10 + .../src/components/pages/AssistantPage.tsx | 228 +--------- .../components/pages/CreateAssistantPage.tsx | 240 ++++++++++ .../components/pages/TemplateLibraryPage.tsx | 423 ++++++++++++++++++ .../components/ui/selection-option-card.tsx | 76 ++++ frontend/src/data/assistant-templates.ts | 299 +++++++++++++ 8 files changed, 1077 insertions(+), 213 deletions(-) create mode 100644 frontend/src/app/assistants/templates/[id]/page.tsx create mode 100644 frontend/src/app/assistants/templates/page.tsx create mode 100644 frontend/src/components/pages/CreateAssistantPage.tsx create mode 100644 frontend/src/components/pages/TemplateLibraryPage.tsx create mode 100644 frontend/src/components/ui/selection-option-card.tsx create mode 100644 frontend/src/data/assistant-templates.ts diff --git a/frontend/src/app/assistants/new/page.tsx b/frontend/src/app/assistants/new/page.tsx index 5c03fe3..a4cdd7b 100644 --- a/frontend/src/app/assistants/new/page.tsx +++ b/frontend/src/app/assistants/new/page.tsx @@ -1,5 +1,5 @@ -import { AssistantPage } from "@/components/pages/AssistantPage"; +import { CreateAssistantPage } from "@/components/pages/CreateAssistantPage"; export default function Page() { - return ; + return ; } diff --git a/frontend/src/app/assistants/templates/[id]/page.tsx b/frontend/src/app/assistants/templates/[id]/page.tsx new file mode 100644 index 0000000..bdfb4a3 --- /dev/null +++ b/frontend/src/app/assistants/templates/[id]/page.tsx @@ -0,0 +1,10 @@ +import { redirect } from "next/navigation"; + +type PageProps = { + params: Promise<{ id: string }>; +}; + +export default async function Page({ params }: PageProps) { + const { id } = await params; + redirect(`/assistants/templates?template=${encodeURIComponent(id)}`); +} diff --git a/frontend/src/app/assistants/templates/page.tsx b/frontend/src/app/assistants/templates/page.tsx new file mode 100644 index 0000000..2e3c04e --- /dev/null +++ b/frontend/src/app/assistants/templates/page.tsx @@ -0,0 +1,10 @@ +import { TemplateLibraryPage } from "@/components/pages/TemplateLibraryPage"; + +type PageProps = { + searchParams: Promise<{ template?: string }>; +}; + +export default async function Page({ searchParams }: PageProps) { + const { template } = await searchParams; + return ; +} diff --git a/frontend/src/components/pages/AssistantPage.tsx b/frontend/src/components/pages/AssistantPage.tsx index ea9cb88..f1c34ff 100644 --- a/frontend/src/components/pages/AssistantPage.tsx +++ b/frontend/src/components/pages/AssistantPage.tsx @@ -3,19 +3,16 @@ import { Boxes, Brain, - Check, Copy, MessageSquareText, MoreHorizontal, Pencil, Plus, - Rocket, Sparkles, Trash2, - Workflow, - ChevronLeft, ChevronUp, ChevronDown, + LayoutTemplate, Save, Terminal, Loader2, @@ -24,7 +21,6 @@ import { import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { TurnConfigEditor } from "@/components/turn-config-editor"; -import { Input } from "@/components/ui/input"; import { DropdownMenu, DropdownMenuContent, @@ -111,7 +107,6 @@ type OpenCodeForm = { }; type AssistantType = "提示词" | "工作流" | "Dify" | "FastGPT" | "OpenCode"; -type BuildMethod = "提示词" | "工作流" | "智能体平台" | "OpenCode"; const assistantTypes: AssistantType[] = [ "提示词", @@ -137,14 +132,6 @@ const typeToLabel: Record = { fastgpt: "FastGPT", opencode: "OpenCode", }; -const typeFromBuildMethod: Record< - Exclude, - ApiAssistantType -> = { - 提示词: "prompt", - 工作流: "workflow", - OpenCode: "opencode", -}; // 后端 type → 编辑器视图(工作流暂为占位页) const typeToView = { @@ -155,13 +142,12 @@ const typeToView = { workflow: "workflow", } as const; -type View = "list" | "choose" | "loading" | (typeof typeToView)[ApiAssistantType]; +type View = "list" | "loading" | (typeof typeToView)[ApiAssistantType]; // 路由驱动的页面模式: -// /assistants → list | /assistants/new → choose(引导,确认即建库) | /assistants/[id] → edit +// /assistants → list | /assistants/[id] → edit export type AssistantPageProps = | { mode: "list" } - | { mode: "choose" } | { mode: "edit"; assistantId: string }; // 各类型的空白表单模板(新建用) @@ -229,47 +215,6 @@ function formatTimestamp(iso?: string | null): string { )}:${pad(d.getMinutes())}`; } -type AssistantTypeOption = { - type: BuildMethod; - label: string; - description: string; - icon: React.ReactNode; - /** 提示词、工作流、智能体平台已落地;OpenCode 暂时显示即将上线 */ - available: boolean; -}; - -const assistantTypeOptions: AssistantTypeOption[] = [ - { - type: "提示词", - label: "使用提示词构建", - description: "通过提示词、模型与语音快速搭建对话助手,适合大多数场景。", - icon: , - available: true, - }, - { - type: "工作流", - label: "使用工作流构建", - description: "用可视化编排串联多个节点,适合多步骤、带分支的复杂流程。", - icon: , - available: true, - }, - { - type: "智能体平台", - label: "从智能体平台创建", - description: - "接入 Dify、FastGPT 等智能体平台,复用平台中的提示词、知识库和工作流配置。", - icon: , - available: true, - }, - { - type: "OpenCode", - label: "使用 OpenCode 构建", - description: "对接 OpenCode 服务,通过提示词驱动代码助手并支持实时语音对话。", - icon: , - available: false, - }, -]; - type AssistantListItem = { id: string; name: string; @@ -313,7 +258,6 @@ export function AssistantPage(props: AssistantPageProps) { const [tools, setTools] = useState([]); // 视图由路由模式决定;仅编辑模式需要先 loading,等拿到助手类型后切换 const [view, setView] = useState(() => { - if (props.mode === "choose") return "choose"; if (props.mode === "edit") return "loading"; return "list"; }); @@ -321,13 +265,6 @@ export function AssistantPage(props: AssistantPageProps) { const [typeFilter, setTypeFilter] = useState("全部"); const [sortOrder, setSortOrder] = useState("newest"); const [currentPage, setCurrentPage] = useState(1); - // choose 步骤的草稿:名称与已选类型,确认后直接建库并进入编辑页 - // (工作流占位页也用它展示名称与类型) - const [draftName, setDraftName] = useState(""); - const [draftType, setDraftType] = useState(null); - // 引导页:创建请求进行中 / 创建失败 - const [creating, setCreating] = useState(false); - const [createError, setCreateError] = useState(null); // 已保存基线(当前类型表单的 JSON);与表单不一致时保存按钮才可点击 const [savedSnapshot, setSavedSnapshot] = useState(null); @@ -499,33 +436,6 @@ export function AssistantPage(props: AssistantPageProps) { router.push(`/assistants/${assistant.id}`); } - // 引导页确认:直接创建到数据库拿到 id,再进入该助手的编辑页 - // 智能体平台默认先建 dify,具体平台在编辑页再选 - async function confirmType() { - 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); - } - } - // 复制助手:服务端整行复制(含真 key,密钥不经浏览器) async function handleDuplicate(assistant: AssistantListItem) { try { @@ -1014,10 +924,20 @@ export function AssistantPage(props: AssistantPageProps) { title="助手列表" description="管理已有的视频助手,支持提示词、工作流、Dify 和 FastGPT 类型。" action={ - +
+ + +
} > @@ -1191,120 +1111,6 @@ export function AssistantPage(props: AssistantPageProps) { ); } - if (view === "choose") { - return ( - router.push("/assistants")} - > - - 返回列表 - - } - > - - - - -
-
构建方式
- -
- {assistantTypeOptions.map((option) => { - const selected = draftType === option.type; - - return ( - - ); - })} -
-
- -
- {createError && ( - {createError} - )} - - -
-
- ); - } - if (view === "workflow") { return ( , + 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: , + available: true, + }, + { + type: "工作流", + label: "使用工作流构建", + description: "用可视化编排串联多个节点,适合多步骤、带分支的复杂流程。", + icon: , + available: true, + }, + { + type: "智能体平台", + label: "从智能体平台创建", + description: + "接入 Dify、FastGPT 等智能体平台,复用平台中的提示词、知识库和工作流配置。", + icon: , + available: true, + }, + { + type: "OpenCode", + label: "使用 OpenCode 构建", + description: "对接 OpenCode 服务,通过提示词驱动代码助手并支持实时语音对话。", + icon: , + available: false, + }, +]; + +function defaultKnowledgeRetrievalConfig(): KnowledgeRetrievalConfig { + return { + mode: "automatic", + topN: 5, + scoreThreshold: 0, + }; +} + +function baseUpsert(over: Partial): 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, + modelResourceIds: {}, + knowledgeBaseId: null, + knowledgeRetrievalConfig: defaultKnowledgeRetrievalConfig(), + toolIds: [], + prompt: "", + dynamicVariableDefinitions: {}, + apiUrl: "", + apiKey: "", + appId: "", + graph: {}, + ...over, + }; +} + +export function CreateAssistantPage() { + const router = useRouter(); + const [draftName, setDraftName] = useState(""); + const [draftType, setDraftType] = useState(null); + const [creating, setCreating] = useState(false); + const [createError, setCreateError] = useState(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 ( + router.push("/assistants")} + > + + 返回列表 + + } + > + + + + +
+
构建方式
+ +
+ {assistantTypeOptions.map((option) => ( + setDraftType(option.type)} + unselectedTrailing={ + !option.available ? ( + + 即将上线 + + ) : undefined + } + /> + ))} +
+
+ +
+ {createError && ( + {createError} + )} + + +
+
+ ); +} diff --git a/frontend/src/components/pages/TemplateLibraryPage.tsx b/frontend/src/components/pages/TemplateLibraryPage.tsx new file mode 100644 index 0000000..7d3ed3e --- /dev/null +++ b/frontend/src/components/pages/TemplateLibraryPage.tsx @@ -0,0 +1,423 @@ +"use client"; + +import { useMemo, useState } from "react"; +import { useRouter } from "next/navigation"; +import { + Bot, + ChevronLeft, + LayoutTemplate, + Loader2, + MessageSquareText, + Rocket, + Sparkles, + Workflow, +} from "lucide-react"; + +import { SectionCard } from "@/components/editor/section-card"; +import { ListPageSection } from "@/components/layout/list-page-layout"; +import { TopbarPortal } from "@/components/layout/topbar-portal"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { FilterPills } from "@/components/ui/filter-pills"; +import { Input } from "@/components/ui/input"; +import { SearchInput } from "@/components/ui/search-input"; +import { + SelectionOptionCard, + selectionOptionGridThreeColumnClassName, +} from "@/components/ui/selection-option-card"; +import { + assistantTemplates, + type AssistantTemplate, + type TemplateBuildMethod, +} from "@/data/assistant-templates"; +import { + assistantsApi, + type AssistantType as ApiAssistantType, + type AssistantUpsert, + type KnowledgeRetrievalConfig, +} from "@/lib/api"; +import { defaultTurnConfig } from "@/lib/turn-config"; + +function defaultKnowledgeRetrievalConfig(): KnowledgeRetrievalConfig { + return { + mode: "automatic", + topN: 5, + scoreThreshold: 0, + }; +} + +const TEMPLATE_TYPE_FILTERS = ["全部", "Prompt", "Workflow"] as const; + +type TemplateTypeFilter = (typeof TEMPLATE_TYPE_FILTERS)[number]; + +function templateTypeLabel(buildMethod: TemplateBuildMethod): "Prompt" | "Workflow" { + return buildMethod === "工作流" ? "Workflow" : "Prompt"; +} + +function matchesTypeFilter( + buildMethod: TemplateBuildMethod, + filter: TemplateTypeFilter, +): boolean { + if (filter === "全部") return true; + return templateTypeLabel(buildMethod) === filter; +} + +const buildMethodIcon: Record< + TemplateBuildMethod, + React.ComponentType<{ size?: number; className?: string }> +> = { + 提示词: MessageSquareText, + 工作流: Workflow, +}; + +function templateAssistantType( + buildMethod: TemplateBuildMethod, +): ApiAssistantType { + return buildMethod === "工作流" ? "workflow" : "prompt"; +} + +function baseUpsertFromTemplate( + name: string, + template: AssistantTemplate, +): AssistantUpsert { + return { + name: name.trim(), + type: templateAssistantType(template.buildMethod), + runtimeMode: "pipeline", + greeting: template.greeting, + enableInterrupt: true, + turnConfig: defaultTurnConfig(), + startup: { + executionMode: "sequential", + openingMode: "interruptible", + entryMode: "wait_user", + actions: [], + openingMessage: null, + }, + visionEnabled: false, + visionModelResourceId: null, + modelResourceIds: {}, + knowledgeBaseId: null, + knowledgeRetrievalConfig: defaultKnowledgeRetrievalConfig(), + toolIds: [], + prompt: template.prompt, + dynamicVariableDefinitions: {}, + apiUrl: "", + apiKey: "", + appId: "", + graph: {}, + }; +} + +function TemplatePromptPreview({ template }: { template: AssistantTemplate }) { + const BuildIcon = buildMethodIcon[template.buildMethod]; + + return ( +
+ } + title="提示词" + description="描述助手的角色、能力和回答要求" + > +

+ {template.prompt} +

+
+ + } + title="开场白" + description="助手与用户首次对话时的开场语" + > +

+ {template.greeting} +

+
+ + } + title="模版说明" + description="构建方式、标签与能力亮点" + > +
+ + + {templateTypeLabel(template.buildMethod)} + + {template.tags.map((tag) => ( + + {tag} + + ))} +
+

+ {template.description} +

+
    + {template.highlights.map((item) => ( +
  • + + {item} +
  • + ))} +
+
+
+ ); +} + +function TemplateWorkflowPreview() { + return ( +
+ ); +} + +function TemplatePreviewHeader({ + selectedTemplate, +}: { + selectedTemplate: AssistantTemplate | null; +}) { + const previewHint = selectedTemplate + ? selectedTemplate.buildMethod === "工作流" + ? "工作流模版的节点编排预览(占位画布)。" + : "查看模版预置的提示词、开场白与说明。" + : "从左侧选择模版后,在此预览预置配置。"; + + return ( +
+
配置预览
+

+ {previewHint} +

+
+ ); +} + +function TemplateEmptyPanel() { + return ( +
+ +
+ 请从左侧选择一个模版 +
+

+ 选中后,右侧将展示该模版的配置预览。 +

+
+ ); +} + +export type TemplateLibraryPageProps = { + initialTemplateId?: string; +}; + +export function TemplateLibraryPage({ + initialTemplateId, +}: TemplateLibraryPageProps = {}) { + const router = useRouter(); + const [assistantName, setAssistantName] = useState(""); + const [typeFilter, setTypeFilter] = useState("全部"); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedId, setSelectedId] = useState( + initialTemplateId ?? null, + ); + const [creating, setCreating] = useState(false); + const [createError, setCreateError] = useState(null); + + const filteredTemplates = useMemo(() => { + const keyword = searchQuery.trim().toLowerCase(); + return assistantTemplates.filter((template) => { + const matchesType = matchesTypeFilter(template.buildMethod, typeFilter); + const matchesSearch = + keyword === "" || + template.name.toLowerCase().includes(keyword) || + template.description.toLowerCase().includes(keyword) || + template.tags.some((tag) => tag.toLowerCase().includes(keyword)); + return matchesType && matchesSearch; + }); + }, [typeFilter, searchQuery]); + + const selectedTemplate = useMemo( + () => assistantTemplates.find((template) => template.id === selectedId) ?? null, + [selectedId], + ); + + const canCreate = Boolean( + assistantName.trim() && selectedTemplate && !creating, + ); + + async function handleCreateFromTemplate() { + if (!canCreate || !selectedTemplate) return; + + setCreating(true); + setCreateError(null); + try { + const created = await assistantsApi.create( + baseUpsertFromTemplate(assistantName, selectedTemplate), + ); + router.push(`/assistants/${created.id}`); + } catch (error) { + setCreateError(error instanceof Error ? error.message : "创建失败"); + setCreating(false); + } + } + + return ( + <> + +
+

模版库

+
+
+ +
+ + +
+ + {selectedTemplate?.buildMethod === "工作流" ? ( + + ) : ( +
+
+ {selectedTemplate ? ( + + ) : ( + + )} +
+
+ )} +
+
+ + ); +} diff --git a/frontend/src/components/ui/selection-option-card.tsx b/frontend/src/components/ui/selection-option-card.tsx new file mode 100644 index 0000000..e795d79 --- /dev/null +++ b/frontend/src/components/ui/selection-option-card.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { Check } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +/** Create-assistant page: 4 columns within max-w-[1180px] content */ +export const selectionOptionGridClassName = + "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"; + +/** Template library left panel: 3 columns within a half-width split */ +export const selectionOptionGridThreeColumnClassName = + "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"; + +export type SelectionOptionCardProps = { + selected: boolean; + icon: React.ReactNode; + title: string; + description: string; + onClick: () => void; + /** Top-right content when not selected. Omit to show nothing. */ + unselectedTrailing?: React.ReactNode; + descriptionClassName?: string; + className?: string; +}; + +export function SelectionOptionCard({ + selected, + icon, + title, + description, + onClick, + unselectedTrailing, + descriptionClassName, + className, +}: SelectionOptionCardProps) { + return ( + + ); +} diff --git a/frontend/src/data/assistant-templates.ts b/frontend/src/data/assistant-templates.ts new file mode 100644 index 0000000..5e412ce --- /dev/null +++ b/frontend/src/data/assistant-templates.ts @@ -0,0 +1,299 @@ +export type TemplateBuildMethod = "提示词" | "工作流"; + +export type TemplatePreviewMessage = { + role: "user" | "assistant"; + content: string; +}; + +export type AssistantTemplate = { + id: string; + name: string; + category: string; + description: string; + tags: string[]; + buildMethod: TemplateBuildMethod; + greeting: string; + prompt: string; + highlights: string[]; + previewMessages: TemplatePreviewMessage[]; +}; + +export const assistantTemplates: AssistantTemplate[] = [ + { + id: "tpl-customer-service", + name: "电商售后客服", + category: "客服", + description: + "处理退换货、物流查询与订单问题,语气耐心专业,适合接入商品知识库。", + tags: ["退换货", "订单查询", "Pipeline"], + buildMethod: "提示词", + greeting: "您好,我是您的专属售后助手,请问需要查询订单还是办理退换货?", + prompt: + "你是一名电商平台的售后客服助手。优先确认用户订单号,再依据平台政策解答退换货、物流与发票问题。回答简洁、步骤清晰,无法处理时引导用户转人工。", + highlights: [ + "支持订单号引导与常见问题分流", + "默认 Pipeline 模式,可挂载商品知识库", + "开场白可直接询问订单或退换货意图", + ], + previewMessages: [ + { role: "assistant", content: "您好,我是您的专属售后助手,请问需要查询订单还是办理退换货?" }, + { role: "user", content: "我昨天下的单还没发货,订单号是 20240806-8831。" }, + { + role: "assistant", + content: + "已查到订单 20240806-8831,当前状态为「仓库拣货中」,预计今晚 22:00 前发出。需要我帮您修改收货地址吗?", + }, + { role: "user", content: "不用改地址,能加急吗?" }, + { + role: "assistant", + content: + "可以为您标记加急出库,成功后物流单号会通过短信通知。还有其他问题我可以帮您处理吗?", + }, + ], + }, + { + id: "tpl-sales-consultant", + name: "B2B 销售顾问", + category: "销售", + description: + "面向企业客户的方案咨询助手,擅长需求挖掘、产品亮点介绍与预约演示。", + tags: ["需求挖掘", "方案介绍", "预约演示"], + buildMethod: "提示词", + greeting: "您好,我是产品顾问小云。可以先简单说说您团队目前遇到的业务场景吗?", + prompt: + "你是 B2B SaaS 产品的销售顾问。通过 2-3 个开放式问题了解客户规模、现有流程与痛点,再匹配产品能力。避免过度承诺,适时邀请预约产品演示。", + highlights: [ + "结构化需求提问,避免一上来报价", + "适合 Realtime 或 Pipeline 语音对话", + "可扩展 CRM 工具调用(Mock)", + ], + previewMessages: [ + { role: "assistant", content: "您好,我是产品顾问小云。可以先简单说说您团队目前遇到的业务场景吗?" }, + { role: "user", content: "我们客服团队大概 40 人,想降低重复咨询的处理时间。" }, + { + role: "assistant", + content: + "了解。请问重复咨询主要集中在哪些主题?例如物流、账号还是产品使用?另外目前是否有知识库或标准话术?", + }, + { role: "user", content: "主要是产品使用和售后政策,知识库比较零散。" }, + { + role: "assistant", + content: + "这类场景很适合用 AI 助手先做首轮解答并检索知识库。我可以安排 30 分钟演示,带您看同类客户的落地方式,您看本周四下午是否方便?", + }, + ], + }, + { + id: "tpl-interview-coach", + name: "面试模拟教练", + category: "培训", + description: + "模拟技术/产品面试场景,按 STAR 法则追问并给出反馈,适合培训与练习。", + tags: ["STAR 追问", "反馈点评", "角色扮演"], + buildMethod: "工作流", + greeting: "欢迎参加模拟面试。请先告诉我目标岗位,我们从自我介绍开始。", + prompt: + "你是一名严谨的面试官教练。每次只提一个问题,等待候选人回答后再追问细节。回答结束后给出 1 条优点和 1 条改进建议,保持鼓励但不空泛。", + highlights: [ + "工作流编排:开场 → 提问 → 点评 → 下一题", + "适合视频通话中的连续追问", + "可按岗位切换题库(Mock)", + ], + previewMessages: [ + { role: "assistant", content: "欢迎参加模拟面试。请先告诉我目标岗位,我们从自我介绍开始。" }, + { role: "user", content: "前端工程师,偏 React 和实时通信。" }, + { + role: "assistant", + content: + "好的。请用 90 秒介绍一个你负责过的实时通信项目,重点说明你的职责与遇到的最大技术挑战。", + }, + { role: "user", content: "做过 WebRTC 客服系统,我负责信令层和重连策略……" }, + { + role: "assistant", + content: + "回答结构清晰。建议补充量化结果,例如延迟或掉线率改善数据。下一个问题:如果 ICE 连接频繁失败,你会如何排查?", + }, + ], + }, + { + id: "tpl-health-triage", + name: "健康咨询分诊", + category: "医疗", + description: + "收集症状与基本信息,给出就医建议与注意事项,不替代医生诊断。", + tags: ["分诊引导", "合规提示", "预约转诊"], + buildMethod: "工作流", + greeting: "您好,我是健康咨询助手。我会帮您梳理症状,但无法替代医生诊断。请问哪里不舒服?", + prompt: + "你是医疗机构的预问诊分诊助手。依次了解主诉、持续时间、严重程度与既往史。给出就医 urgency 建议与注意事项,遇到危急描述立即建议拨打 120 或前往急诊。", + highlights: [ + "内置合规免责声明", + "工作流分支:轻症建议 / 建议门诊 / 紧急提醒", + "可对接预约 API(Mock)", + ], + previewMessages: [ + { + role: "assistant", + content: "您好,我是健康咨询助手。我会帮您梳理症状,但无法替代医生诊断。请问哪里不舒服?", + }, + { role: "user", content: "头痛两天了,还有点低烧。" }, + { + role: "assistant", + content: + "请问体温大概多少?头痛是持续性的还是阵发性的?有没有颈部僵硬、呕吐或意识模糊?", + }, + { role: "user", content: "37.8 度左右,一阵一阵的,没有其他那些症状。" }, + { + role: "assistant", + content: + "目前看不属于紧急危象。建议今日到社区门诊或互联网医院进一步评估,期间多休息、补水,若体温超过 38.5℃ 或症状加重请及时就医。", + }, + ], + }, + { + id: "tpl-hotel-concierge", + name: "酒店礼宾助手", + category: "通用", + description: + "解答入住、设施、周边推荐与客房服务请求,语气温暖,适合 hospitality 场景。", + tags: ["入住咨询", "客房服务", "周边推荐"], + buildMethod: "提示词", + greeting: "欢迎光临。我是礼宾助手,可以帮您查询入住信息、设施开放时间与周边推荐。", + prompt: + "你是高端酒店的礼宾助手。优先确认客人姓名或房间号(若涉及隐私可跳过),再回答设施、餐饮、交通与本地推荐。语气礼貌、简洁,主动提供下一步帮助。", + highlights: [ + "适合视频大堂或移动端呼叫", + "可挂载酒店 FAQ 知识库", + "支持中英文切换(Mock)", + ], + previewMessages: [ + { role: "assistant", content: "欢迎光临。我是礼宾助手,可以帮您查询入住信息、设施开放时间与周边推荐。" }, + { role: "user", content: "健身房几点开?早餐到几点?" }, + { + role: "assistant", + content: + "健身房每日 6:00–22:00 开放,位于 3 层。自助早餐 6:30–10:30,在一层「云间」餐厅。需要我帮您预约私教或打包外带早餐吗?", + }, + ], + }, + { + id: "tpl-onboarding-guide", + name: "新员工入职向导", + category: "培训", + description: + "解答公司制度、IT 账号、办公地点与首日流程,降低 HR 重复答疑压力。", + tags: ["入职流程", "IT 支持", "制度问答"], + buildMethod: "提示词", + greeting: "欢迎加入团队!我是入职向导,可以帮您了解首日安排、账号开通和常用制度。", + prompt: + "你是企业新员工入职向导。根据员工部门与入职日期,介绍报到流程、IT 账号、考勤与福利政策。不确定的信息引导联系 HR 或 IT 服务台,不要编造政策细节。", + highlights: [ + "可绑定员工手册知识库", + "支持动态变量 {{department}}、{{start_date}}", + "适合 Pipeline + 知识检索", + ], + previewMessages: [ + { role: "assistant", content: "欢迎加入团队!我是入职向导,可以帮您了解首日安排、账号开通和常用制度。" }, + { role: "user", content: "明天第一天上班,我需要带什么?几点到?" }, + { + role: "assistant", + content: + "请携带身份证原件与学历复印件,9:30 到总部 A 座 18 层 HR 前台报到。IT 会在报到后 1 小时内开通邮箱与 VPN,建议提前下载企业通讯 App。", + }, + ], + }, + { + id: "tpl-ticket-routing", + name: "工单智能分流", + category: "客服", + description: + "按问题类型、优先级与 SLA 将用户诉求路由到不同处理分支,适合复杂售后场景。", + tags: ["条件分支", "工单创建", "SLA"], + buildMethod: "工作流", + greeting: "您好,我是工单助手。请简单描述您遇到的问题,我会帮您归类并创建工单。", + prompt: + "你是工单分流助手。先识别问题类别(账号、支付、物流、产品故障),再判断紧急程度,引导用户补充必要信息并说明预计处理时效。", + highlights: [ + "多分支路由:咨询 / 投诉 / 紧急故障", + "可对接工单系统创建 API(Mock)", + "支持转人工节点", + ], + previewMessages: [], + }, + { + id: "tpl-insurance-claim", + name: "保险理赔引导", + category: "通用", + description: + "分步骤收集事故信息、材料清单与理赔进度查询,减少重复说明。", + tags: ["材料清单", "进度查询", "分步表单"], + buildMethod: "工作流", + greeting: "您好,我是理赔引导助手。请问您需要报案、补充材料,还是查询进度?", + prompt: + "你是保险理赔引导助手。根据用户意图进入不同流程:报案需收集时间地点与损失描述;补材料需核对清单;查进度需验证保单号后四位。", + highlights: [ + "报案 / 补材 / 查进度三条主路径", + "节点化收集结构化字段", + "合规话术与隐私字段脱敏", + ], + previewMessages: [], + }, + { + id: "tpl-course-sales", + name: "课程咨询转化", + category: "销售", + description: + "从课程介绍、试听预约到优惠说明的多节点销售流程,适合教培机构。", + tags: ["试听预约", "优惠说明", "线索留资"], + buildMethod: "工作流", + greeting: "你好,我是课程顾问。想了解哪门课程?我可以介绍大纲并帮你预约试听。", + prompt: + "你是教培机构课程顾问。先确认目标课程与用户基础,再介绍班型与价格区间,适时引导留资或预约试听,避免硬性推销。", + highlights: [ + "课程匹配 → 试听预约 → 优惠说明", + "支持留资节点写入 CRM(Mock)", + "可插入固定话术节点", + ], + previewMessages: [], + }, + { + id: "tpl-device-repair", + name: "设备故障报修", + category: "客服", + description: + "引导用户描述设备型号与故障现象,自动判断远程指导或上门维修路径。", + tags: ["故障诊断", "上门预约", "远程指导"], + buildMethod: "工作流", + greeting: "您好,我是设备报修助手。请告诉我设备型号和出现的故障现象。", + prompt: + "你是设备售后报修助手。收集型号、购买日期、故障代码或现象,判断可否远程排查;需上门时收集地址与可预约时间段。", + highlights: [ + "远程指导 / 上门维修双路径", + "故障现象结构化采集", + "可对接预约系统(Mock)", + ], + previewMessages: [], + }, + { + id: "tpl-compliance-review", + name: "合规话术审查", + category: "培训", + description: + "按合规检查清单逐条核对销售话术,输出风险点与改写建议。", + tags: ["合规检查", "话术改写", "审核流程"], + buildMethod: "工作流", + greeting: "请提交待审查的话术或录音转写内容,我会按合规清单逐条检查。", + prompt: + "你是金融销售合规审查助手。按禁止承诺收益、风险提示、适当性匹配等维度检查输入内容,逐条标注风险并给出合规改写建议。", + highlights: [ + "清单式多节点审查", + "输出风险等级与改写建议", + "适合质检与培训场景", + ], + previewMessages: [], + }, +]; + +export function getAssistantTemplate(id: string): AssistantTemplate | undefined { + return assistantTemplates.find((template) => template.id === id); +}