Files
zclaw_openfang/desktop/src/components/PersonalitySelector.tsx
iven f4efc823e2 refactor(types): comprehensive TypeScript type system improvements
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>
2026-03-17 08:05:07 +08:00

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>
);
}