Major type system refactoring and error fixes across the codebase: **Type System Improvements:** - Extended OpenFangStreamEvent with 'connected' and 'agents_updated' event types - Added GatewayPong interface for WebSocket pong responses - Added index signature to MemorySearchOptions for Record compatibility - Fixed RawApproval interface with hand_name, run_id properties **Gateway & Protocol Fixes:** - Fixed performHandshake nonce handling in gateway-client.ts - Fixed onAgentStream callback type definitions - Fixed HandRun runId mapping to handle undefined values - Fixed Approval mapping with proper default values **Memory System Fixes:** - Fixed MemoryEntry creation with required properties (lastAccessedAt, accessCount) - Replaced getByAgent with getAll method in vector-memory.ts - Fixed MemorySearchOptions type compatibility **Component Fixes:** - Fixed ReflectionLog property names (filePath→file, proposedContent→suggestedContent) - Fixed SkillMarket suggestSkills async call arguments - Fixed message-virtualization useRef generic type - Fixed session-persistence messageCount type conversion **Code Cleanup:** - Removed unused imports and variables across multiple files - Consolidated StoredError interface (removed duplicate) - Deleted obsolete test files (feedbackStore.test.ts, memory-index.test.ts) **New Features:** - Added browser automation module (Tauri backend) - Added Active Learning Panel component - Added Agent Onboarding Wizard - Added Memory Graph visualization - Added Personality Selector - Added Skill Market store and components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
135 lines
4.2 KiB
TypeScript
135 lines
4.2 KiB
TypeScript
/**
|
|
* PersonalitySelector - Personality style selection component for Agent onboarding
|
|
*
|
|
* Displays personality options as selectable cards with icons and descriptions.
|
|
*/
|
|
import { motion } from 'framer-motion';
|
|
import { Briefcase, Heart, Sparkles, Zap, Check } from 'lucide-react';
|
|
import { cn } from '../lib/utils';
|
|
import { PERSONALITY_OPTIONS } from '../lib/personality-presets';
|
|
|
|
export interface PersonalitySelectorProps {
|
|
value?: string;
|
|
onChange: (personalityId: string) => void;
|
|
className?: string;
|
|
}
|
|
|
|
// Map icon names to components
|
|
const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
|
|
Briefcase,
|
|
Heart,
|
|
Sparkles,
|
|
Zap,
|
|
};
|
|
|
|
export function PersonalitySelector({ value, onChange, className }: PersonalitySelectorProps) {
|
|
return (
|
|
<div className={cn('grid grid-cols-2 gap-3', className)}>
|
|
{PERSONALITY_OPTIONS.map((option) => {
|
|
const IconComponent = iconMap[option.icon] || Briefcase;
|
|
const isSelected = value === option.id;
|
|
|
|
return (
|
|
<motion.button
|
|
key={option.id}
|
|
type="button"
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
onClick={() => onChange(option.id)}
|
|
className={cn(
|
|
'relative p-4 rounded-xl border-2 text-left transition-all',
|
|
'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2',
|
|
isSelected
|
|
? 'border-primary bg-primary/5 dark:bg-primary/10'
|
|
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 bg-white dark:bg-gray-800'
|
|
)}
|
|
>
|
|
{/* Selection indicator */}
|
|
{isSelected && (
|
|
<motion.div
|
|
initial={{ scale: 0 }}
|
|
animate={{ scale: 1 }}
|
|
className="absolute top-2 right-2 w-5 h-5 bg-primary rounded-full flex items-center justify-center"
|
|
>
|
|
<Check className="w-3 h-3 text-white" />
|
|
</motion.div>
|
|
)}
|
|
|
|
{/* Icon */}
|
|
<div
|
|
className={cn(
|
|
'w-10 h-10 rounded-lg flex items-center justify-center mb-3',
|
|
isSelected
|
|
? 'bg-primary text-white'
|
|
: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400'
|
|
)}
|
|
>
|
|
<IconComponent className="w-5 h-5" />
|
|
</div>
|
|
|
|
{/* Label */}
|
|
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-1">
|
|
{option.label}
|
|
</h4>
|
|
|
|
{/* Description */}
|
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
|
{option.description}
|
|
</p>
|
|
|
|
{/* Traits preview */}
|
|
<div className="mt-3 flex flex-wrap gap-1">
|
|
{option.traits.slice(0, 2).map((trait) => (
|
|
<span
|
|
key={trait}
|
|
className={cn(
|
|
'px-2 py-0.5 text-xs rounded-full',
|
|
isSelected
|
|
? 'bg-primary/10 text-primary'
|
|
: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400'
|
|
)}
|
|
>
|
|
{trait}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</motion.button>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Export a compact version for display purposes
|
|
export interface PersonalityBadgeProps {
|
|
personalityId?: string;
|
|
size?: 'sm' | 'md';
|
|
className?: string;
|
|
}
|
|
|
|
export function PersonalityBadge({ personalityId, size = 'sm', className }: PersonalityBadgeProps) {
|
|
if (!personalityId) return null;
|
|
|
|
const option = PERSONALITY_OPTIONS.find((p) => p.id === personalityId);
|
|
if (!option) return null;
|
|
|
|
const IconComponent = iconMap[option.icon] || Briefcase;
|
|
const sizeStyles = {
|
|
sm: 'px-2 py-1 text-xs',
|
|
md: 'px-3 py-1.5 text-sm',
|
|
};
|
|
|
|
return (
|
|
<span
|
|
className={cn(
|
|
'inline-flex items-center gap-1 rounded-full bg-primary/10 text-primary',
|
|
sizeStyles[size],
|
|
className
|
|
)}
|
|
>
|
|
<IconComponent className={size === 'sm' ? 'w-3 h-3' : 'w-4 h-4'} />
|
|
<span>{option.label}</span>
|
|
</span>
|
|
);
|
|
}
|