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>
104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
/**
|
|
* EmojiPicker - Emoji selection component for Agent onboarding
|
|
*
|
|
* Displays categorized emoji presets for users to choose from.
|
|
*/
|
|
import { useState } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { cn } from '../../lib/utils';
|
|
import { EMOJI_PRESETS, ALL_EMOJIS } from '../../lib/personality-presets';
|
|
|
|
type EmojiCategory = 'all' | 'animals' | 'objects' | 'expressions';
|
|
|
|
export interface EmojiPickerProps {
|
|
value?: string;
|
|
onChange: (emoji: string) => void;
|
|
className?: string;
|
|
}
|
|
|
|
const categoryLabels: Record<EmojiCategory, string> = {
|
|
all: '全部',
|
|
animals: '动物',
|
|
objects: '物体',
|
|
expressions: '表情',
|
|
};
|
|
|
|
export function EmojiPicker({ value, onChange, className }: EmojiPickerProps) {
|
|
const [activeCategory, setActiveCategory] = useState<EmojiCategory>('all');
|
|
|
|
const getEmojisForCategory = (category: EmojiCategory): string[] => {
|
|
if (category === 'all') {
|
|
return ALL_EMOJIS;
|
|
}
|
|
return EMOJI_PRESETS[category] || [];
|
|
};
|
|
|
|
const emojis = getEmojisForCategory(activeCategory);
|
|
|
|
return (
|
|
<div className={cn('space-y-3', className)}>
|
|
{/* Category Tabs */}
|
|
<div className="flex gap-1 p-1 bg-gray-100 dark:bg-gray-800 rounded-lg">
|
|
{(Object.keys(categoryLabels) as EmojiCategory[]).map((category) => (
|
|
<button
|
|
key={category}
|
|
type="button"
|
|
onClick={() => setActiveCategory(category)}
|
|
className={cn(
|
|
'flex-1 px-3 py-1.5 text-xs font-medium rounded-md transition-colors',
|
|
activeCategory === category
|
|
? 'bg-white dark:bg-gray-700 text-primary shadow-sm'
|
|
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'
|
|
)}
|
|
>
|
|
{categoryLabels[category]}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Emoji Grid */}
|
|
<motion.div
|
|
layout
|
|
className="grid grid-cols-8 gap-1"
|
|
>
|
|
<AnimatePresence mode="popLayout">
|
|
{emojis.map((emoji) => (
|
|
<motion.button
|
|
key={emoji}
|
|
type="button"
|
|
layout
|
|
initial={{ opacity: 0, scale: 0.8 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
exit={{ opacity: 0, scale: 0.8 }}
|
|
transition={{ duration: 0.15 }}
|
|
onClick={() => onChange(emoji)}
|
|
className={cn(
|
|
'w-9 h-9 flex items-center justify-center text-xl rounded-lg transition-all',
|
|
'hover:bg-gray-100 dark:hover:bg-gray-700',
|
|
'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1',
|
|
value === emoji && 'bg-primary/10 ring-2 ring-primary'
|
|
)}
|
|
>
|
|
{emoji}
|
|
</motion.button>
|
|
))}
|
|
</AnimatePresence>
|
|
</motion.div>
|
|
|
|
{/* Selected Preview */}
|
|
{value && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 5 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className="flex items-center gap-2 p-2 bg-primary/5 rounded-lg"
|
|
>
|
|
<span className="text-2xl">{value}</span>
|
|
<span className="text-sm text-gray-600 dark:text-gray-400">
|
|
已选择
|
|
</span>
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|