refactor: 清理未使用代码并添加未来功能标记
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
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协议传输和错误处理
This commit is contained in:
340
desktop/src/components/WorkflowRecommendations.tsx
Normal file
340
desktop/src/components/WorkflowRecommendations.tsx
Normal file
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user