Some checks failed
CI / Rust Check (push) Has been cancelled
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
style: 统一代码格式和注释风格 docs: 更新多个功能文档的完整度和状态 feat(runtime): 添加路径验证工具支持 fix(pipeline): 改进条件判断和变量解析逻辑 test(types): 为ID类型添加全面测试用例 chore: 更新依赖项和Cargo.lock文件 perf(mcp): 优化MCP协议传输和错误处理
341 lines
11 KiB
TypeScript
341 lines
11 KiB
TypeScript
/**
|
|
* Workflow Recommendations Component
|
|
*
|
|
* Displays proactive workflow recommendations from the Adaptive Intelligence Mesh.
|
|
* Shows detected patterns and suggested workflows based on user behavior.
|
|
*/
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { useMeshStore } from '../store/meshStore';
|
|
import type { WorkflowRecommendation, BehaviorPattern, PatternTypeVariant } from '../lib/intelligence-client';
|
|
|
|
// === Main Component ===
|
|
|
|
export const WorkflowRecommendations: React.FC = () => {
|
|
const {
|
|
recommendations,
|
|
patterns,
|
|
isLoading,
|
|
error,
|
|
analyze,
|
|
acceptRecommendation,
|
|
dismissRecommendation,
|
|
} = useMeshStore();
|
|
|
|
const [selectedPattern, setSelectedPattern] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
// Initial analysis
|
|
analyze();
|
|
}, [analyze]);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center p-8">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" />
|
|
<span className="ml-3 text-gray-400">Analyzing patterns...</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-lg">
|
|
<p className="text-red-400 text-sm">{error}</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Recommendations Section */}
|
|
<section>
|
|
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
|
<span className="text-2xl">💡</span>
|
|
Recommended Workflows
|
|
{recommendations.length > 0 && (
|
|
<span className="ml-2 px-2 py-0.5 bg-blue-500/20 text-blue-400 text-xs rounded-full">
|
|
{recommendations.length}
|
|
</span>
|
|
)}
|
|
</h3>
|
|
|
|
<AnimatePresence mode="popLayout">
|
|
{recommendations.length === 0 ? (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
className="p-6 bg-gray-800/30 rounded-lg border border-gray-700/50 text-center"
|
|
>
|
|
<p className="text-gray-400">No recommendations available yet.</p>
|
|
<p className="text-gray-500 text-sm mt-2">
|
|
Continue using the app to build up behavior patterns.
|
|
</p>
|
|
</motion.div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{recommendations.map((rec) => (
|
|
<RecommendationCard
|
|
key={rec.id}
|
|
recommendation={rec}
|
|
onAccept={() => acceptRecommendation(rec.id)}
|
|
onDismiss={() => dismissRecommendation(rec.id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</AnimatePresence>
|
|
</section>
|
|
|
|
{/* Detected Patterns Section */}
|
|
<section>
|
|
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
|
<span className="text-2xl">📊</span>
|
|
Detected Patterns
|
|
{patterns.length > 0 && (
|
|
<span className="ml-2 px-2 py-0.5 bg-purple-500/20 text-purple-400 text-xs rounded-full">
|
|
{patterns.length}
|
|
</span>
|
|
)}
|
|
</h3>
|
|
|
|
{patterns.length === 0 ? (
|
|
<div className="p-6 bg-gray-800/30 rounded-lg border border-gray-700/50 text-center">
|
|
<p className="text-gray-400">No patterns detected yet.</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid gap-3">
|
|
{patterns.map((pattern) => (
|
|
<PatternCard
|
|
key={pattern.id}
|
|
pattern={pattern}
|
|
isSelected={selectedPattern === pattern.id}
|
|
onClick={() =>
|
|
setSelectedPattern(
|
|
selectedPattern === pattern.id ? null : pattern.id
|
|
)
|
|
}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</section>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// === Sub-Components ===
|
|
|
|
interface RecommendationCardProps {
|
|
recommendation: WorkflowRecommendation;
|
|
onAccept: () => void;
|
|
onDismiss: () => void;
|
|
}
|
|
|
|
const RecommendationCard: React.FC<RecommendationCardProps> = ({
|
|
recommendation,
|
|
onAccept,
|
|
onDismiss,
|
|
}) => {
|
|
const confidencePercent = Math.round(recommendation.confidence * 100);
|
|
|
|
const getConfidenceColor = (confidence: number) => {
|
|
if (confidence >= 0.8) return 'text-green-400';
|
|
if (confidence >= 0.6) return 'text-yellow-400';
|
|
return 'text-orange-400';
|
|
};
|
|
|
|
return (
|
|
<motion.div
|
|
layout
|
|
initial={{ opacity: 0, y: -10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, scale: 0.95 }}
|
|
className="p-4 bg-gray-800/50 rounded-lg border border-gray-700/50 hover:border-blue-500/30 transition-colors"
|
|
>
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<h4 className="text-white font-medium truncate">
|
|
{recommendation.pipeline_id}
|
|
</h4>
|
|
<span
|
|
className={`text-xs font-mono ${getConfidenceColor(
|
|
recommendation.confidence
|
|
)}`}
|
|
>
|
|
{confidencePercent}%
|
|
</span>
|
|
</div>
|
|
|
|
<p className="text-gray-400 text-sm mb-3">{recommendation.reason}</p>
|
|
|
|
{/* Suggested Inputs */}
|
|
{Object.keys(recommendation.suggested_inputs).length > 0 && (
|
|
<div className="mb-3">
|
|
<p className="text-xs text-gray-500 mb-1">Suggested inputs:</p>
|
|
<div className="flex flex-wrap gap-1">
|
|
{Object.entries(recommendation.suggested_inputs).map(
|
|
([key, value]) => (
|
|
<span
|
|
key={key}
|
|
className="px-2 py-0.5 bg-gray-700/50 text-gray-300 text-xs rounded"
|
|
>
|
|
{key}: {String(value).slice(0, 20)}
|
|
</span>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Matched Patterns */}
|
|
{recommendation.patterns_matched.length > 0 && (
|
|
<div className="text-xs text-gray-500">
|
|
Based on {recommendation.patterns_matched.length} pattern(s)
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="flex gap-2 shrink-0">
|
|
<button
|
|
onClick={onAccept}
|
|
className="px-3 py-1.5 bg-blue-500 hover:bg-blue-600 text-white text-sm rounded transition-colors"
|
|
>
|
|
Accept
|
|
</button>
|
|
<button
|
|
onClick={onDismiss}
|
|
className="px-3 py-1.5 bg-gray-700 hover:bg-gray-600 text-gray-300 text-sm rounded transition-colors"
|
|
>
|
|
Dismiss
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Confidence Bar */}
|
|
<div className="mt-3 h-1 bg-gray-700 rounded-full overflow-hidden">
|
|
<motion.div
|
|
initial={{ width: 0 }}
|
|
animate={{ width: `${confidencePercent}%` }}
|
|
className={`h-full ${
|
|
recommendation.confidence >= 0.8
|
|
? 'bg-green-500'
|
|
: recommendation.confidence >= 0.6
|
|
? 'bg-yellow-500'
|
|
: 'bg-orange-500'
|
|
}`}
|
|
/>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
interface PatternCardProps {
|
|
pattern: BehaviorPattern;
|
|
isSelected: boolean;
|
|
onClick: () => void;
|
|
}
|
|
|
|
const PatternCard: React.FC<PatternCardProps> = ({
|
|
pattern,
|
|
isSelected,
|
|
onClick,
|
|
}) => {
|
|
const getPatternTypeLabel = (type: PatternTypeVariant | string) => {
|
|
// Handle object format
|
|
const typeStr = typeof type === 'string' ? type : type.type;
|
|
|
|
switch (typeStr) {
|
|
case 'SkillCombination':
|
|
return { label: 'Skill Combo', icon: '⚡' };
|
|
case 'TemporalTrigger':
|
|
return { label: 'Time Trigger', icon: '⏰' };
|
|
case 'TaskPipelineMapping':
|
|
return { label: 'Task Mapping', icon: '🔄' };
|
|
case 'InputPattern':
|
|
return { label: 'Input Pattern', icon: '📝' };
|
|
default:
|
|
return { label: typeStr, icon: '📊' };
|
|
}
|
|
};
|
|
|
|
const { label, icon } = getPatternTypeLabel(pattern.pattern_type as PatternTypeVariant);
|
|
const confidencePercent = Math.round(pattern.confidence * 100);
|
|
|
|
return (
|
|
<motion.div
|
|
layout
|
|
onClick={onClick}
|
|
className={`p-3 rounded-lg border cursor-pointer transition-colors ${
|
|
isSelected
|
|
? 'bg-purple-500/10 border-purple-500/50'
|
|
: 'bg-gray-800/30 border-gray-700/50 hover:border-gray-600'
|
|
}`}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-lg">{icon}</span>
|
|
<span className="text-white font-medium">{label}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-xs text-gray-400">
|
|
{pattern.frequency}x used
|
|
</span>
|
|
<span
|
|
className={`text-xs font-mono ${
|
|
pattern.confidence >= 0.6
|
|
? 'text-green-400'
|
|
: 'text-yellow-400'
|
|
}`}
|
|
>
|
|
{confidencePercent}%
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<AnimatePresence>
|
|
{isSelected && (
|
|
<motion.div
|
|
initial={{ height: 0, opacity: 0 }}
|
|
animate={{ height: 'auto', opacity: 1 }}
|
|
exit={{ height: 0, opacity: 0 }}
|
|
className="mt-3 pt-3 border-t border-gray-700/50 overflow-hidden"
|
|
>
|
|
<div className="space-y-2 text-sm">
|
|
<div>
|
|
<span className="text-gray-500">ID:</span>{' '}
|
|
<span className="text-gray-300 font-mono text-xs">
|
|
{pattern.id}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-gray-500">First seen:</span>{' '}
|
|
<span className="text-gray-300">
|
|
{new Date(pattern.first_occurrence).toLocaleDateString()}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-gray-500">Last seen:</span>{' '}
|
|
<span className="text-gray-300">
|
|
{new Date(pattern.last_occurrence).toLocaleDateString()}
|
|
</span>
|
|
</div>
|
|
{pattern.context.intent && (
|
|
<div>
|
|
<span className="text-gray-500">Intent:</span>{' '}
|
|
<span className="text-gray-300">{pattern.context.intent}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
export default WorkflowRecommendations;
|