feat(desktop): pipeline result preview + industry templates + onboarding auto-trigger
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Sprint 2: 产品体验打磨 + 行业模板 - Create PipelineResultPreview component with tab-based output switching - Connect workflow/hand messages to PresentationContainer in ChatArea - Add auto-trigger first Hand after onboarding (industry-specific queries) - Seed 3 industry agent templates (education, healthcare, design-shantou) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,171 @@
|
|||||||
|
-- Industry Agent Template Seed Data
|
||||||
|
-- Three target user groups: Education, Healthcare, Design (Shantou)
|
||||||
|
-- Each template maps to pipeline YAML templates in pipelines/ directory
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 1. Education — 教学助手
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO agent_templates (
|
||||||
|
id, name, description, category, source, emoji,
|
||||||
|
model, system_prompt, tools, capabilities, temperature, max_tokens,
|
||||||
|
visibility, status, current_version,
|
||||||
|
soul_content, scenarios, welcome_message, quick_commands,
|
||||||
|
personality, communication_style, source_id, version
|
||||||
|
) VALUES (
|
||||||
|
'edu-teacher-001',
|
||||||
|
'教学助手',
|
||||||
|
'专为教师和教培机构设计的 AI 助手,擅长课件生成、学情分析、测验出题和教案设计',
|
||||||
|
'education',
|
||||||
|
'builtin',
|
||||||
|
'📚',
|
||||||
|
NULL,
|
||||||
|
'你是一个专业的教学助手,帮助教师完成备课、课件制作、学情分析和测验出题等工作。你熟悉中国教育体系,了解各学科教学大纲和课程标准。回答要专业、准确、有教育洞察。',
|
||||||
|
'["web-search", "data-analysis", "chart-visualization", "deep-research", "classroom-generator", "translation"]',
|
||||||
|
'["quiz", "whiteboard", "slideshow", "speech", "collector"]',
|
||||||
|
0.7,
|
||||||
|
4096,
|
||||||
|
'public',
|
||||||
|
'active',
|
||||||
|
1,
|
||||||
|
'# 教学助手人格定义
|
||||||
|
|
||||||
|
## 身份
|
||||||
|
你是一位经验丰富的教学顾问和备课助手,熟悉中国 K-12 和高等教育的教学实践。
|
||||||
|
|
||||||
|
## 核心价值观
|
||||||
|
- 以学生为中心的教学理念
|
||||||
|
- 注重因材施教和差异化教学
|
||||||
|
- 鼓励互动式、探究式学习
|
||||||
|
|
||||||
|
## 行为准则
|
||||||
|
- 提供具体可执行的教学建议
|
||||||
|
- 内容符合课程标准要求
|
||||||
|
- 生成结构清晰的课件和测验',
|
||||||
|
'["课件生成", "学情分析", "教案设计", "教研辅助", "测验出题"]',
|
||||||
|
'你好!我是你的智能教学助手 📚
|
||||||
|
|
||||||
|
我可以帮你:
|
||||||
|
• 生成课件和幻灯片
|
||||||
|
• 分析学情数据
|
||||||
|
• 设计测验题目
|
||||||
|
• 编写教案
|
||||||
|
|
||||||
|
请告诉我你需要什么帮助?',
|
||||||
|
'[{"label":"生成课件","command":"帮我生成一个关于这个主题的课件,包含教学目标、知识点讲解和课堂活动"},{"label":"学情分析","command":"帮我分析这组学生的成绩数据,找出薄弱环节和改进建议"},{"label":"出测验题","command":"帮我出10道这个知识点的测验题,包含选择题、填空题和简答题"},{"label":"生成教案","command":"帮我设计一节45分钟的教案,包含导入、新授、练习和总结环节"}]',
|
||||||
|
'friendly',
|
||||||
|
'温暖、耐心、善于用易懂的语言解释复杂概念',
|
||||||
|
'education-teacher',
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 2. Healthcare — 医疗行政助手
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO agent_templates (
|
||||||
|
id, name, description, category, source, emoji,
|
||||||
|
model, system_prompt, tools, capabilities, temperature, max_tokens,
|
||||||
|
visibility, status, current_version,
|
||||||
|
soul_content, scenarios, welcome_message, quick_commands,
|
||||||
|
personality, communication_style, source_id, version
|
||||||
|
) VALUES (
|
||||||
|
'healthcare-admin-001',
|
||||||
|
'医疗行政助手',
|
||||||
|
'专为医院行政科室设计,擅长政策解读、数据报告、合规检查和会议纪要',
|
||||||
|
'healthcare',
|
||||||
|
'builtin',
|
||||||
|
'🏥',
|
||||||
|
NULL,
|
||||||
|
'你是一个专业的医疗行政管理助手,帮助医院行政人员处理政策文件解读、数据报告生成、合规检查和会议纪要等工作。你熟悉中国医疗体系和相关法规政策。回答要严谨、准确、有法律依据。',
|
||||||
|
'["web-search", "data-analysis", "deep-research", "consulting-analysis", "translation", "legal-compliance-checker"]',
|
||||||
|
'["researcher", "browser", "collector"]',
|
||||||
|
0.5,
|
||||||
|
4096,
|
||||||
|
'public',
|
||||||
|
'active',
|
||||||
|
1,
|
||||||
|
'# 医疗行政助手人格定义
|
||||||
|
|
||||||
|
## 身份
|
||||||
|
你是一位专业的医疗行政顾问,熟悉医院管理流程和医疗法规政策。
|
||||||
|
|
||||||
|
## 核心价值观
|
||||||
|
- 严格遵守医疗法规和政策要求
|
||||||
|
- 注重数据准确性和可追溯性
|
||||||
|
- 以合规和安全为第一优先级
|
||||||
|
|
||||||
|
## 行为准则
|
||||||
|
- 引用具体政策条款和文件编号
|
||||||
|
- 数据分析结果需标注来源和统计方法
|
||||||
|
- 涉及患者隐私的内容需提醒脱敏处理',
|
||||||
|
'["政策解读", "数据报告", "合规检查", "文献研究", "会议纪要"]',
|
||||||
|
'你好!我是你的医疗行政助手 🏥
|
||||||
|
|
||||||
|
我可以帮你:
|
||||||
|
• 解读最新医疗政策文件
|
||||||
|
• 生成科室数据报告
|
||||||
|
• 检查制度合规性
|
||||||
|
• 整理会议纪要
|
||||||
|
|
||||||
|
请告诉我你需要什么帮助?',
|
||||||
|
'[{"label":"政策解读","command":"帮我解读最新的医疗政策文件,提取关键要点和影响分析"},{"label":"数据报告","command":"帮我生成科室运营数据报告,包含趋势分析和改进建议"},{"label":"会议纪要","command":"帮我把会议录音/笔记整理成结构化纪要,包含决议和待办事项"},{"label":"合规检查","command":"帮我检查这项制度是否符合最新的医疗法规要求"}]',
|
||||||
|
'professional',
|
||||||
|
'专业、准确、注重细节,提供技术深度和可操作建议',
|
||||||
|
'healthcare-admin',
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 3. Design (Shantou) — 设计助手
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO agent_templates (
|
||||||
|
id, name, description, category, source, emoji,
|
||||||
|
model, system_prompt, tools, capabilities, temperature, max_tokens,
|
||||||
|
visibility, status, current_version,
|
||||||
|
soul_content, scenarios, welcome_message, quick_commands,
|
||||||
|
personality, communication_style, source_id, version
|
||||||
|
) VALUES (
|
||||||
|
'design-shantou-001',
|
||||||
|
'设计助手',
|
||||||
|
'专为汕头制衣、玩具设计行业打造,擅长趋势分析、竞品研究、客户沟通和方案设计',
|
||||||
|
'design-shantou',
|
||||||
|
'builtin',
|
||||||
|
'🎨',
|
||||||
|
NULL,
|
||||||
|
'你是一个专业的设计行业助手,专注于汕头特色产业(制衣、玩具)。你擅长趋势分析、竞品调研、设计灵感和客户沟通辅助。你了解潮汕地区的产业特点和市场需求。回答要富有创意、实用且有商业洞察。',
|
||||||
|
'["web-search", "data-analysis", "deep-research", "consulting-analysis", "content-creator"]',
|
||||||
|
'["browser", "collector", "researcher", "clip"]',
|
||||||
|
0.8,
|
||||||
|
4096,
|
||||||
|
'public',
|
||||||
|
'active',
|
||||||
|
1,
|
||||||
|
'# 设计助手人格定义
|
||||||
|
|
||||||
|
## 身份
|
||||||
|
你是一位创意设计顾问,专注于汕头制衣和玩具设计行业,了解全球设计趋势和本地市场需求。
|
||||||
|
|
||||||
|
## 核心价值观
|
||||||
|
- 设计要以市场需求为导向
|
||||||
|
- 鼓励创新但注重可行性
|
||||||
|
- 尊重原创,保护知识产权
|
||||||
|
|
||||||
|
## 行为准则
|
||||||
|
- 提供具体可落地的设计建议
|
||||||
|
- 趋势分析需引用权威来源
|
||||||
|
- 竞品分析要客观全面',
|
||||||
|
'["趋势分析", "竞品研究", "客户沟通", "供应链查询", "方案设计"]',
|
||||||
|
'你好!我是你的设计助手 🎨
|
||||||
|
|
||||||
|
我可以帮你:
|
||||||
|
• 分析行业设计趋势
|
||||||
|
• 调研竞品产品和策略
|
||||||
|
• 辅助客户沟通方案
|
||||||
|
• 整理供应链信息
|
||||||
|
|
||||||
|
请告诉我你需要什么帮助?',
|
||||||
|
'[{"label":"趋势分析","command":"帮我分析2026年制衣/玩具行业的流行设计趋势,包含色彩、材质和风格"},{"label":"竞品研究","command":"帮我调研这个产品的竞品,对比价格、设计和市场定位"},{"label":"客户沟通","command":"帮我准备客户提案,包含产品推荐和报价方案"},{"label":"供应链查询","command":"帮我收集和整理相关原材料/配件的供应商信息"}]',
|
||||||
|
'creative',
|
||||||
|
'富有创意、思维开放,鼓励探索新想法和解决方案',
|
||||||
|
'design-shantou',
|
||||||
|
1
|
||||||
|
);
|
||||||
@@ -357,6 +357,26 @@ function App() {
|
|||||||
time: '',
|
time: '',
|
||||||
});
|
});
|
||||||
setShowOnboarding(false);
|
setShowOnboarding(false);
|
||||||
|
|
||||||
|
// Auto-trigger first Hand based on industry template
|
||||||
|
const templateId = clone.source_template_id || '';
|
||||||
|
const industryQueries: Record<string, { hand: string; action: string; query: string }> = {
|
||||||
|
'edu-teacher-001': { hand: 'researcher', action: 'report', query: '帮我研究2026年教育数字化转型趋势,包括AI教学工具的最新进展' },
|
||||||
|
'healthcare-admin-001': { hand: 'collector', action: 'collect', query: '帮我采集最新的医疗政策文件摘要,重点关注基层医疗改革方向' },
|
||||||
|
'design-shantou-001': { hand: 'researcher', action: 'report', query: '帮我研究2026年服装设计流行趋势,包括色彩、面料和款式创新' },
|
||||||
|
};
|
||||||
|
const task = industryQueries[templateId];
|
||||||
|
if (task) {
|
||||||
|
// Delay slightly to let UI settle
|
||||||
|
setTimeout(() => {
|
||||||
|
useHandStore.getState().triggerHand(task.hand, {
|
||||||
|
action: task.action,
|
||||||
|
query: { query: task.query },
|
||||||
|
}).catch(() => {
|
||||||
|
// Non-critical — user can trigger manually
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理主视图切换
|
// 处理主视图切换
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import { ChatMode } from './ai/ChatMode';
|
|||||||
import { ModelSelector } from './ai/ModelSelector';
|
import { ModelSelector } from './ai/ModelSelector';
|
||||||
import { TaskProgress } from './ai/TaskProgress';
|
import { TaskProgress } from './ai/TaskProgress';
|
||||||
import { SuggestionChips } from './ai/SuggestionChips';
|
import { SuggestionChips } from './ai/SuggestionChips';
|
||||||
|
import { PipelineResultPreview } from './pipeline/PipelineResultPreview';
|
||||||
|
import { PresentationContainer } from './presentation/PresentationContainer';
|
||||||
// TokenMeter temporarily unused — using inline text counter instead
|
// TokenMeter temporarily unused — using inline text counter instead
|
||||||
|
|
||||||
// Default heights for virtualized messages
|
// Default heights for virtualized messages
|
||||||
@@ -665,6 +667,20 @@ function MessageBubble({ message, setInput }: { message: Message; setInput: (tex
|
|||||||
)
|
)
|
||||||
: '...'}
|
: '...'}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Pipeline / Hand result presentation */}
|
||||||
|
{!isUser && (message.role === 'workflow' || message.role === 'hand') && message.workflowResult && typeof message.workflowResult === 'object' && message.workflowResult !== null && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<PipelineResultPreview
|
||||||
|
outputs={message.workflowResult as Record<string, unknown>}
|
||||||
|
pipelineId={message.workflowId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isUser && message.role === 'hand' && message.handResult && typeof message.handResult === 'object' && message.handResult !== null && !message.workflowResult && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<PresentationContainer data={message.handResult} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{message.error && (
|
{message.error && (
|
||||||
<div className="flex items-center gap-2 mt-2">
|
<div className="flex items-center gap-2 mt-2">
|
||||||
<p className="text-xs text-red-500">{message.error}</p>
|
<p className="text-xs text-red-500">{message.error}</p>
|
||||||
|
|||||||
85
desktop/src/components/pipeline/PipelineResultPreview.tsx
Normal file
85
desktop/src/components/pipeline/PipelineResultPreview.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* PipelineResultPreview — Pipeline 输出结果预览
|
||||||
|
*
|
||||||
|
* 将 Pipeline outputs 的多个顶层键以 tab 切换展示,
|
||||||
|
* 每个 tab 内嵌 PresentationContainer 做智能渲染。
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useMemo } from 'react';
|
||||||
|
import { PresentationContainer } from '../presentation/PresentationContainer';
|
||||||
|
import { FileText, BarChart3, HelpCircle, Presentation, PenTool } from 'lucide-react';
|
||||||
|
|
||||||
|
interface PipelineResultPreviewProps {
|
||||||
|
/** Pipeline outputs 对象(多键 JSON) */
|
||||||
|
outputs: Record<string, unknown>;
|
||||||
|
/** Pipeline ID */
|
||||||
|
pipelineId?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const KEY_ICONS: Record<string, React.ReactNode> = {
|
||||||
|
summary: <FileText className="w-3.5 h-3.5" />,
|
||||||
|
report: <FileText className="w-3.5 h-3.5" />,
|
||||||
|
analysis: <BarChart3 className="w-3.5 h-3.5" />,
|
||||||
|
quiz: <HelpCircle className="w-3.5 h-3.5" />,
|
||||||
|
scenes: <Presentation className="w-3.5 h-3.5" />,
|
||||||
|
slides: <Presentation className="w-3.5 h-3.5" />,
|
||||||
|
plan: <PenTool className="w-3.5 h-3.5" />,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getKeyIcon(key: string): React.ReactNode {
|
||||||
|
for (const [pattern, icon] of Object.entries(KEY_ICONS)) {
|
||||||
|
if (key.toLowerCase().includes(pattern)) return icon;
|
||||||
|
}
|
||||||
|
return <FileText className="w-3.5 h-3.5" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PipelineResultPreview({ outputs, pipelineId, className = '' }: PipelineResultPreviewProps) {
|
||||||
|
const keys = useMemo(() => Object.keys(outputs), [outputs]);
|
||||||
|
const [activeKey, setActiveKey] = useState(keys[0]);
|
||||||
|
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="text-sm text-gray-400 italic p-4">
|
||||||
|
Pipeline 执行完成,无输出数据
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden ${className}`}>
|
||||||
|
{/* Tab bar */}
|
||||||
|
{keys.length > 1 && (
|
||||||
|
<div className="flex border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 overflow-x-auto">
|
||||||
|
{keys.map((key) => (
|
||||||
|
<button
|
||||||
|
key={key}
|
||||||
|
onClick={() => setActiveKey(key)}
|
||||||
|
className={`flex items-center gap-1.5 px-4 py-2.5 text-xs font-medium whitespace-nowrap transition-colors border-b-2 ${
|
||||||
|
activeKey === key
|
||||||
|
? 'border-emerald-500 text-emerald-700 dark:text-emerald-400 bg-white dark:bg-gray-800'
|
||||||
|
: 'border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{getKeyIcon(key)}
|
||||||
|
<span>{key}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="p-4 bg-white dark:bg-gray-900">
|
||||||
|
{outputs[activeKey] !== undefined && outputs[activeKey] !== null ? (
|
||||||
|
<PresentationContainer
|
||||||
|
data={outputs[activeKey]}
|
||||||
|
pipelineId={pipelineId}
|
||||||
|
allowSwitch={true}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-400">该部分无数据</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user