Files
zclaw_openfang/desktop/src/components/ui/EmojiPicker.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

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