From a95ae458efa9c6f7692232df7b9a42a9bde2cb55 Mon Sep 17 00:00:00 2001 From: Soumyadas15 Date: Thu, 29 May 2025 15:16:30 +0530 Subject: [PATCH] chore(dev): add styling to custom agents --- backend/agent/api.py | 24 +- .../20250115000000_add_agent_styling.sql | 13 + .../agents/_components/agent-preview.tsx | 367 +++- .../_components/agent-tools-configuration.tsx | 2 +- .../agents/_components/agents-grid.tsx | 34 +- .../agents/_components/style-picker.tsx | 1722 +++++++++++++++++ .../agents/_utils/get-agent-style.ts | 2 +- .../(dashboard)/agents/new/[agentId]/page.tsx | 82 +- .../thread/chat-input/chat-input.tsx | 4 +- .../thread/content/ThreadContent.tsx | 10 +- 10 files changed, 2219 insertions(+), 41 deletions(-) create mode 100644 backend/supabase/migrations/20250115000000_add_agent_styling.sql create mode 100644 frontend/src/app/(dashboard)/agents/_components/style-picker.tsx diff --git a/backend/agent/api.py b/backend/agent/api.py index 91a643e7..184b43d2 100644 --- a/backend/agent/api.py +++ b/backend/agent/api.py @@ -51,6 +51,8 @@ class AgentCreateRequest(BaseModel): configured_mcps: Optional[List[Dict[str, Any]]] = [] agentpress_tools: Optional[Dict[str, Any]] = {} is_default: Optional[bool] = False + avatar: Optional[str] = None + avatar_color: Optional[str] = None class AgentUpdateRequest(BaseModel): name: Optional[str] = None @@ -59,6 +61,8 @@ class AgentUpdateRequest(BaseModel): configured_mcps: Optional[List[Dict[str, Any]]] = None agentpress_tools: Optional[Dict[str, Any]] = None is_default: Optional[bool] = None + avatar: Optional[str] = None + avatar_color: Optional[str] = None class AgentResponse(BaseModel): agent_id: str @@ -69,6 +73,8 @@ class AgentResponse(BaseModel): configured_mcps: List[Dict[str, Any]] agentpress_tools: Dict[str, Any] is_default: bool + avatar: Optional[str] + avatar_color: Optional[str] created_at: str updated_at: str @@ -528,6 +534,8 @@ async def get_thread_agent(thread_id: str, user_id: str = Depends(get_current_us configured_mcps=agent_data.get('configured_mcps', []), agentpress_tools=agent_data.get('agentpress_tools', {}), is_default=agent_data.get('is_default', False), + avatar=agent_data.get('avatar'), + avatar_color=agent_data.get('avatar_color'), created_at=agent_data['created_at'], updated_at=agent_data['updated_at'] ), @@ -1007,6 +1015,8 @@ async def get_agents(user_id: str = Depends(get_current_user_id_from_jwt)): configured_mcps=agent.get('configured_mcps', []), agentpress_tools=agent.get('agentpress_tools', {}), is_default=agent.get('is_default', False), + avatar=agent.get('avatar'), + avatar_color=agent.get('avatar_color'), created_at=agent['created_at'], updated_at=agent['updated_at'] )) @@ -1041,6 +1051,8 @@ async def get_agent(agent_id: str, user_id: str = Depends(get_current_user_id_fr configured_mcps=agent_data.get('configured_mcps', []), agentpress_tools=agent_data.get('agentpress_tools', {}), is_default=agent_data.get('is_default', False), + avatar=agent_data.get('avatar'), + avatar_color=agent_data.get('avatar_color'), created_at=agent_data['created_at'], updated_at=agent_data['updated_at'] ) @@ -1078,7 +1090,9 @@ async def create_agent( "system_prompt": agent_data.system_prompt, "configured_mcps": agent_data.configured_mcps or [], "agentpress_tools": agent_data.agentpress_tools or {}, - "is_default": agent_data.is_default or False + "is_default": agent_data.is_default or False, + "avatar": agent_data.avatar, + "avatar_color": agent_data.avatar_color } new_agent = await client.table('agents').insert(insert_data).execute() @@ -1098,6 +1112,8 @@ async def create_agent( configured_mcps=agent.get('configured_mcps', []), agentpress_tools=agent.get('agentpress_tools', {}), is_default=agent.get('is_default', False), + avatar=agent.get('avatar'), + avatar_color=agent.get('avatar_color'), created_at=agent['created_at'], updated_at=agent['updated_at'] ) @@ -1150,6 +1166,10 @@ async def update_agent( # If setting as default, unset other defaults first if agent_data.is_default: await client.table('agents').update({"is_default": False}).eq("account_id", user_id).eq("is_default", True).neq("agent_id", agent_id).execute() + if agent_data.avatar is not None: + update_data["avatar"] = agent_data.avatar + if agent_data.avatar_color is not None: + update_data["avatar_color"] = agent_data.avatar_color if not update_data: # No fields to update, return existing agent @@ -1180,6 +1200,8 @@ async def update_agent( configured_mcps=agent.get('configured_mcps', []), agentpress_tools=agent.get('agentpress_tools', {}), is_default=agent.get('is_default', False), + avatar=agent.get('avatar'), + avatar_color=agent.get('avatar_color'), created_at=agent['created_at'], updated_at=agent['updated_at'] ) diff --git a/backend/supabase/migrations/20250115000000_add_agent_styling.sql b/backend/supabase/migrations/20250115000000_add_agent_styling.sql new file mode 100644 index 00000000..0d586655 --- /dev/null +++ b/backend/supabase/migrations/20250115000000_add_agent_styling.sql @@ -0,0 +1,13 @@ +BEGIN; + +ALTER TABLE agents +ADD COLUMN IF NOT EXISTS avatar VARCHAR(10), +ADD COLUMN IF NOT EXISTS avatar_color VARCHAR(7); + +CREATE INDEX IF NOT EXISTS idx_agents_avatar ON agents(avatar); +CREATE INDEX IF NOT EXISTS idx_agents_avatar_color ON agents(avatar_color); + +COMMENT ON COLUMN agents.avatar IS 'Emoji character used as agent avatar (e.g., ๐Ÿค–)'; +COMMENT ON COLUMN agents.avatar_color IS 'Hex color code for agent avatar background (e.g., #3b82f6)'; + +COMMIT; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/agents/_components/agent-preview.tsx b/frontend/src/app/(dashboard)/agents/_components/agent-preview.tsx index aee82751..f1363274 100644 --- a/frontend/src/app/(dashboard)/agents/_components/agent-preview.tsx +++ b/frontend/src/app/(dashboard)/agents/_components/agent-preview.tsx @@ -1,8 +1,19 @@ -import React from 'react'; -import { Bot, Calendar, Settings, Sparkles, User } from 'lucide-react'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import React, { useState, useRef, useCallback, useEffect } from 'react'; import { Badge } from '@/components/ui/badge'; -import { DEFAULT_AGENTPRESS_TOOLS, getToolDisplayName } from '../_data/tools'; +import { cn } from '@/lib/utils'; +import { toast } from 'sonner'; +import { getAgentAvatar } from '../_utils/get-agent-style'; +import { + ChatInput, + ChatInputHandles +} from '@/components/thread/chat-input/chat-input'; +import { ThreadContent } from '@/components/thread/content/ThreadContent'; +import { UnifiedMessage } from '@/components/thread/types'; +import { useInitiateAgentWithInvalidation } from '@/hooks/react-query/dashboard/use-initiate-agent'; +import { useAgentStream } from '@/hooks/useAgentStream'; +import { useAddUserMessageMutation } from '@/hooks/react-query/threads/use-messages'; +import { useStartAgentMutation, useStopAgentMutation } from '@/hooks/react-query/threads/use-agent-run'; +import { BillingError } from '@/lib/api'; interface Agent { agent_id: string; @@ -21,15 +32,349 @@ interface AgentPreviewProps { } export const AgentPreview = ({ agent }: AgentPreviewProps) => { - const enabledTools = Object.entries(agent.agentpress_tools || {}) - .filter(([_, tool]) => tool.enabled) - .map(([toolName]) => toolName); + const [messages, setMessages] = useState([]); + const [inputValue, setInputValue] = useState(''); + const [threadId, setThreadId] = useState(null); + const [agentRunId, setAgentRunId] = useState(null); + const [agentStatus, setAgentStatus] = useState<'idle' | 'running' | 'connecting' | 'error'>('idle'); + const [isSubmitting, setIsSubmitting] = useState(false); + const [hasStartedConversation, setHasStartedConversation] = useState(false); + + const messagesEndRef = useRef(null); + const chatInputRef = useRef(null); + + const getAgentStyling = () => { + const agentData = agent as any; + if (agentData.avatar && agentData.avatar_color) { + return { + avatar: agentData.avatar, + color: agentData.avatar_color, + }; + } + return getAgentAvatar(agent.agent_id); + }; + + const { avatar, color } = getAgentStyling(); + + const initiateAgentMutation = useInitiateAgentWithInvalidation(); + const addUserMessageMutation = useAddUserMessageMutation(); + const startAgentMutation = useStartAgentMutation(); + const stopAgentMutation = useStopAgentMutation(); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const handleNewMessageFromStream = useCallback((message: UnifiedMessage) => { + console.log(`[PREVIEW STREAM] Received message: ID=${message.message_id}, Type=${message.type}`); + + setMessages((prev) => { + const messageExists = prev.some((m) => m.message_id === message.message_id); + if (messageExists) { + return prev.map((m) => m.message_id === message.message_id ? message : m); + } else { + return [...prev, message]; + } + }); + }, []); + + const handleStreamStatusChange = useCallback((hookStatus: string) => { + console.log(`[PREVIEW] Stream status changed: ${hookStatus}`); + switch (hookStatus) { + case 'idle': + case 'completed': + case 'stopped': + case 'agent_not_running': + case 'error': + case 'failed': + setAgentStatus('idle'); + setAgentRunId(null); + break; + case 'connecting': + setAgentStatus('connecting'); + break; + case 'streaming': + setAgentStatus('running'); + break; + } + }, []); + + const handleStreamError = useCallback((errorMessage: string) => { + console.error(`[PREVIEW] Stream error: ${errorMessage}`); + if (!errorMessage.toLowerCase().includes('not found') && + !errorMessage.toLowerCase().includes('agent run is not running')) { + toast.error(`Stream Error: ${errorMessage}`); + } + }, []); + + const handleStreamClose = useCallback(() => { + console.log(`[PREVIEW] Stream closed`); + }, []); + + const { + status: streamHookStatus, + textContent: streamingTextContent, + toolCall: streamingToolCall, + error: streamError, + agentRunId: currentHookRunId, + startStreaming, + stopStreaming, + } = useAgentStream( + { + onMessage: handleNewMessageFromStream, + onStatusChange: handleStreamStatusChange, + onError: handleStreamError, + onClose: handleStreamClose, + }, + threadId, + setMessages, + ); + + useEffect(() => { + if (agentRunId && agentRunId !== currentHookRunId && threadId) { + console.log(`[PREVIEW] Starting stream for agentRunId: ${agentRunId}, threadId: ${threadId}`); + startStreaming(agentRunId); + } + }, [agentRunId, startStreaming, currentHookRunId, threadId]); + + useEffect(() => { + console.log('[PREVIEW] State update:', { + threadId, + agentRunId, + currentHookRunId, + agentStatus, + streamHookStatus, + messagesCount: messages.length, + hasStartedConversation + }); + }, [threadId, agentRunId, currentHookRunId, agentStatus, streamHookStatus, messages.length, hasStartedConversation]); + + useEffect(() => { + if (streamingTextContent) { + scrollToBottom(); + } + }, [streamingTextContent]); + + const handleSubmitFirstMessage = async ( + message: string, + options?: { + model_name?: string; + enable_thinking?: boolean; + reasoning_effort?: string; + stream?: boolean; + enable_context_manager?: boolean; + }, + ) => { + if (!message.trim() && !chatInputRef.current?.getPendingFiles().length) return; + + setIsSubmitting(true); + setHasStartedConversation(true); + + try { + const files = chatInputRef.current?.getPendingFiles() || []; + + const formData = new FormData(); + formData.append('prompt', message); + formData.append('agent_id', agent.agent_id); + + files.forEach((file, index) => { + formData.append('files', file, file.name); + }); + + if (options?.model_name) formData.append('model_name', options.model_name); + formData.append('enable_thinking', String(options?.enable_thinking ?? false)); + formData.append('reasoning_effort', options?.reasoning_effort ?? 'low'); + formData.append('stream', String(options?.stream ?? true)); + formData.append('enable_context_manager', String(options?.enable_context_manager ?? false)); + + console.log('[PREVIEW] Initiating agent...'); + const result = await initiateAgentMutation.mutateAsync(formData); + console.log('[PREVIEW] Agent initiated:', result); + + if (result.thread_id) { + setThreadId(result.thread_id); + if (result.agent_run_id) { + console.log('[PREVIEW] Setting agent run ID:', result.agent_run_id); + setAgentRunId(result.agent_run_id); + } else { + console.log('[PREVIEW] No agent_run_id in result, starting agent manually...'); + try { + const agentResult = await startAgentMutation.mutateAsync({ + threadId: result.thread_id, + options + }); + console.log('[PREVIEW] Agent started manually:', agentResult); + setAgentRunId(agentResult.agent_run_id); + } catch (startError) { + console.error('[PREVIEW] Error starting agent manually:', startError); + toast.error('Failed to start agent'); + } + } + const userMessage: UnifiedMessage = { + message_id: `user-${Date.now()}`, + thread_id: result.thread_id, + type: 'user', + is_llm_message: false, + content: message, + metadata: '{}', + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + setMessages([userMessage]); + } + + chatInputRef.current?.clearPendingFiles(); + setInputValue(''); + } catch (error: any) { + console.error('[PREVIEW] Error during initiation:', error); + if (error instanceof BillingError) { + toast.error('Billing limit reached. Please upgrade your plan.'); + } else { + toast.error('Failed to start conversation'); + } + setHasStartedConversation(false); + } finally { + setIsSubmitting(false); + } + }; + + const handleSubmitMessage = useCallback( + async ( + message: string, + options?: { model_name?: string; enable_thinking?: boolean }, + ) => { + if (!message.trim() || !threadId) return; + setIsSubmitting(true); + + const optimisticUserMessage: UnifiedMessage = { + message_id: `temp-${Date.now()}`, + thread_id: threadId, + type: 'user', + is_llm_message: false, + content: message, + metadata: '{}', + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + + setMessages((prev) => [...prev, optimisticUserMessage]); + setInputValue(''); + + try { + const messagePromise = addUserMessageMutation.mutateAsync({ + threadId, + message + }); + + const agentPromise = startAgentMutation.mutateAsync({ + threadId, + options + }); + + const results = await Promise.allSettled([messagePromise, agentPromise]); + + if (results[0].status === 'rejected') { + throw new Error(`Failed to send message: ${results[0].reason?.message || results[0].reason}`); + } + + if (results[1].status === 'rejected') { + const error = results[1].reason; + if (error instanceof BillingError) { + toast.error('Billing limit reached. Please upgrade your plan.'); + setMessages(prev => prev.filter(m => m.message_id !== optimisticUserMessage.message_id)); + return; + } + throw new Error(`Failed to start agent: ${error?.message || error}`); + } + + const agentResult = results[1].value; + setAgentRunId(agentResult.agent_run_id); + + } catch (err) { + console.error('[PREVIEW] Error sending message:', err); + toast.error(err instanceof Error ? err.message : 'Operation failed'); + setMessages((prev) => prev.filter((m) => m.message_id !== optimisticUserMessage.message_id)); + } finally { + setIsSubmitting(false); + } + }, + [threadId, addUserMessageMutation, startAgentMutation], + ); + + const handleStopAgent = useCallback(async () => { + console.log('[PREVIEW] Stopping agent...'); + setAgentStatus('idle'); + await stopStreaming(); + + if (agentRunId) { + try { + await stopAgentMutation.mutateAsync(agentRunId); + } catch (error) { + console.error('[PREVIEW] Error stopping agent:', error); + } + } + }, [stopStreaming, agentRunId, stopAgentMutation]); + + const handleToolClick = useCallback((assistantMessageId: string | null, toolName: string) => { + console.log('[PREVIEW] Tool clicked:', toolName); + toast.info(`Tool: ${toolName} (Preview mode - tool details not available)`); + }, []); - const enabledMCPs = agent.configured_mcps || []; return ( -
- +
+
+
+ {avatar} +
+
+

{agent.name || 'Unnamed Agent'}

+ {agent.description && ( +

{agent.description}

+ )} +
+ Preview Mode +
+
+
+ {}} + streamHookStatus={streamHookStatus} + isPreviewMode={true} + /> +
+
+
+
+
+ +
+
); -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/agents/_components/agent-tools-configuration.tsx b/frontend/src/app/(dashboard)/agents/_components/agent-tools-configuration.tsx index 24c80ab7..6c5e4bf4 100644 --- a/frontend/src/app/(dashboard)/agents/_components/agent-tools-configuration.tsx +++ b/frontend/src/app/(dashboard)/agents/_components/agent-tools-configuration.tsx @@ -61,7 +61,7 @@ export const AgentToolsConfiguration = ({ tools, onToolsChange }: AgentToolsConf />
-
+
{getFilteredTools().map(([toolName, toolInfo]) => (
; agentpress_tools?: Record; avatar?: string; - color?: string; + avatar_color?: string; } interface AgentsGridProps { @@ -28,7 +28,17 @@ interface AgentsGridProps { } const AgentModal = ({ agent, isOpen, onClose, onCustomize, onChat }) => { - const { avatar, color } = getAgentAvatar(agent.agent_id); + const getAgentStyling = (agent: Agent) => { + if (agent.avatar && agent.avatar_color) { + return { + avatar: agent.avatar, + color: agent.avatar_color, + }; + } + return getAgentAvatar(agent.agent_id); + }; + + const { avatar, color } = getAgentStyling(agent); const truncateDescription = (text, maxLength = 120) => { if (!text || text.length <= maxLength) return text || 'Try out this agent'; @@ -40,9 +50,9 @@ const AgentModal = ({ agent, isOpen, onClose, onCustomize, onChat }) => { Agent actions
-
+
- {agent.avatar || avatar} + {avatar}
{agent.is_default && (
@@ -109,11 +119,21 @@ export const AgentsGrid = ({ setSelectedAgent(null); }; + const getAgentStyling = (agent: Agent) => { + if (agent.avatar && agent.avatar_color) { + return { + avatar: agent.avatar, + color: agent.avatar_color, + }; + } + return getAgentAvatar(agent.agent_id); + }; + return ( <>
{agents.map((agent) => { - const { avatar, color } = getAgentAvatar(agent.agent_id); + const { avatar, color } = getAgentStyling(agent); return (
handleAgentClick(agent)} > -
+
- {agent.avatar || avatar} + {avatar}
{agent.is_default && (
diff --git a/frontend/src/app/(dashboard)/agents/_components/style-picker.tsx b/frontend/src/app/(dashboard)/agents/_components/style-picker.tsx new file mode 100644 index 00000000..06dca53d --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/style-picker.tsx @@ -0,0 +1,1722 @@ +"use client" + +import { useState, useMemo } from "react" +import { Button } from "@/components/ui/button" +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Separator } from "@/components/ui/separator" +import { Input } from "@/components/ui/input" +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Palette, Search } from "lucide-react" +import React from "react" + +const EMOJI_CATEGORIES = { + smileys: { + name: "Smileys & People", + icon: "๐Ÿ˜€", + emojis: [ + "๐Ÿ˜€", + "๐Ÿ˜ƒ", + "๐Ÿ˜„", + "๐Ÿ˜", + "๐Ÿ˜†", + "๐Ÿ˜…", + "๐Ÿ˜‚", + "๐Ÿคฃ", + "๐Ÿ˜Š", + "๐Ÿ˜‡", + "๐Ÿ™‚", + "๐Ÿ™ƒ", + "๐Ÿ˜‰", + "๐Ÿ˜Œ", + "๐Ÿ˜", + "๐Ÿฅฐ", + "๐Ÿ˜˜", + "๐Ÿ˜—", + "๐Ÿ˜™", + "๐Ÿ˜š", + "๐Ÿ˜‹", + "๐Ÿ˜›", + "๐Ÿ˜", + "๐Ÿ˜œ", + "๐Ÿคช", + "๐Ÿคจ", + "๐Ÿง", + "๐Ÿค“", + "๐Ÿ˜Ž", + "๐Ÿคฉ", + "๐Ÿฅณ", + "๐Ÿ˜", + "๐Ÿ˜’", + "๐Ÿ˜ž", + "๐Ÿ˜”", + "๐Ÿ˜Ÿ", + "๐Ÿ˜•", + "๐Ÿ™", + "โ˜น๏ธ", + "๐Ÿ˜ฃ", + "๐Ÿ˜–", + "๐Ÿ˜ซ", + "๐Ÿ˜ฉ", + "๐Ÿฅบ", + "๐Ÿ˜ข", + "๐Ÿ˜ญ", + "๐Ÿ˜ค", + "๐Ÿ˜ ", + "๐Ÿ˜ก", + "๐Ÿคฌ", + "๐Ÿคฏ", + "๐Ÿ˜ณ", + "๐Ÿฅต", + "๐Ÿฅถ", + "๐Ÿ˜ฑ", + "๐Ÿ˜จ", + "๐Ÿ˜ฐ", + "๐Ÿ˜ฅ", + "๐Ÿ˜“", + "๐Ÿค—", + "๐Ÿค”", + "๐Ÿคญ", + "๐Ÿคซ", + "๐Ÿคฅ", + "๐Ÿ˜ถ", + "๐Ÿ˜", + "๐Ÿ˜‘", + "๐Ÿ˜ฌ", + "๐Ÿ™„", + "๐Ÿ˜ฏ", + "๐Ÿ˜ฆ", + "๐Ÿ˜ง", + "๐Ÿ˜ฎ", + "๐Ÿ˜ฒ", + "๐Ÿฅฑ", + "๐Ÿ˜ด", + "๐Ÿคค", + "๐Ÿ˜ช", + "๐Ÿ˜ต", + "๐Ÿค", + "๐Ÿฅด", + "๐Ÿคข", + "๐Ÿคฎ", + "๐Ÿคง", + "๐Ÿ˜ท", + "๐Ÿค’", + "๐Ÿค•", + "๐Ÿค‘", + "๐Ÿค ", + "๐Ÿ˜ˆ", + "๐Ÿ‘ฟ", + "๐Ÿ‘น", + "๐Ÿ‘บ", + "๐Ÿคก", + "๐Ÿ’ฉ", + "๐Ÿ‘ป", + "๐Ÿ’€", + "โ˜ ๏ธ", + "๐Ÿ‘ฝ", + "๐Ÿ‘พ", + "๐Ÿค–", + "๐ŸŽƒ", + "๐Ÿ˜บ", + "๐Ÿ˜ธ", + "๐Ÿ˜น", + "๐Ÿ˜ป", + "๐Ÿ˜ผ", + "๐Ÿ˜ฝ", + "๐Ÿ™€", + "๐Ÿ˜ฟ", + "๐Ÿ˜พ", + ], + }, + people: { + name: "People & Body", + icon: "๐Ÿ‘‹", + emojis: [ + "๐Ÿ‘‹", + "๐Ÿคš", + "๐Ÿ–๏ธ", + "โœ‹", + "๐Ÿ––", + "๐Ÿ‘Œ", + "๐ŸคŒ", + "๐Ÿค", + "โœŒ๏ธ", + "๐Ÿคž", + "๐ŸคŸ", + "๐Ÿค˜", + "๐Ÿค™", + "๐Ÿ‘ˆ", + "๐Ÿ‘‰", + "๐Ÿ‘†", + "๐Ÿ–•", + "๐Ÿ‘‡", + "โ˜๏ธ", + "๐Ÿ‘", + "๐Ÿ‘Ž", + "๐Ÿ‘Š", + "โœŠ", + "๐Ÿค›", + "๐Ÿคœ", + "๐Ÿ‘", + "๐Ÿ™Œ", + "๐Ÿ‘", + "๐Ÿคฒ", + "๐Ÿค", + "๐Ÿ™", + "โœ๏ธ", + "๐Ÿ’…", + "๐Ÿคณ", + "๐Ÿ’ช", + "๐Ÿฆพ", + "๐Ÿฆฟ", + "๐Ÿฆต", + "๐Ÿฆถ", + "๐Ÿ‘‚", + "๐Ÿฆป", + "๐Ÿ‘ƒ", + "๐Ÿง ", + "๐Ÿซ€", + "๐Ÿซ", + "๐Ÿฆท", + "๐Ÿฆด", + "๐Ÿ‘€", + "๐Ÿ‘๏ธ", + "๐Ÿ‘…", + "๐Ÿ‘„", + "๐Ÿ’‹", + "๐Ÿฉธ", + "๐Ÿ‘ถ", + "๐Ÿง’", + "๐Ÿ‘ฆ", + "๐Ÿ‘ง", + "๐Ÿง‘", + "๐Ÿ‘ฑ", + "๐Ÿ‘จ", + "๐Ÿง”", + "๐Ÿ‘จโ€๐Ÿฆฐ", + "๐Ÿ‘จโ€๐Ÿฆฑ", + "๐Ÿ‘จโ€๐Ÿฆณ", + "๐Ÿ‘จโ€๐Ÿฆฒ", + "๐Ÿ‘ฉ", + "๐Ÿ‘ฉโ€๐Ÿฆฐ", + "๐Ÿง‘โ€๐Ÿฆฐ", + "๐Ÿ‘ฉโ€๐Ÿฆฑ", + "๐Ÿง‘โ€๐Ÿฆฑ", + "๐Ÿ‘ฉโ€๐Ÿฆณ", + "๐Ÿง‘โ€๐Ÿฆณ", + "๐Ÿ‘ฉโ€๐Ÿฆฒ", + "๐Ÿง‘โ€๐Ÿฆฒ", + "๐Ÿ‘ฑโ€โ™€๏ธ", + "๐Ÿ‘ฑโ€โ™‚๏ธ", + "๐Ÿง“", + "๐Ÿ‘ด", + "๐Ÿ‘ต", + "๐Ÿ™", + "๐Ÿ™โ€โ™‚๏ธ", + "๐Ÿ™โ€โ™€๏ธ", + "๐Ÿ™Ž", + "๐Ÿ™Žโ€โ™‚๏ธ", + "๐Ÿ™Žโ€โ™€๏ธ", + "๐Ÿ™…", + "๐Ÿ™…โ€โ™‚๏ธ", + "๐Ÿ™…โ€โ™€๏ธ", + "๐Ÿ™†", + "๐Ÿ™†โ€โ™‚๏ธ", + "๐Ÿ™†โ€โ™€๏ธ", + "๐Ÿ’", + "๐Ÿ’โ€โ™‚๏ธ", + "๐Ÿ’โ€โ™€๏ธ", + "๐Ÿ™‹", + "๐Ÿ™‹โ€โ™‚๏ธ", + "๐Ÿ™‹โ€โ™€๏ธ", + "๐Ÿง", + "๐Ÿงโ€โ™‚๏ธ", + "๐Ÿงโ€โ™€๏ธ", + "๐Ÿ™‡", + "๐Ÿ™‡โ€โ™‚๏ธ", + "๐Ÿ™‡โ€โ™€๏ธ", + "๐Ÿคฆ", + "๐Ÿคฆโ€โ™‚๏ธ", + "๐Ÿคฆโ€โ™€๏ธ", + "๐Ÿคท", + "๐Ÿคทโ€โ™‚๏ธ", + "๐Ÿคทโ€โ™€๏ธ", + ], + }, + animals: { + name: "Animals & Nature", + icon: "๐Ÿถ", + emojis: [ + "๐Ÿถ", + "๐Ÿฑ", + "๐Ÿญ", + "๐Ÿน", + "๐Ÿฐ", + "๐ŸฆŠ", + "๐Ÿป", + "๐Ÿผ", + "๐Ÿปโ€โ„๏ธ", + "๐Ÿจ", + "๐Ÿฏ", + "๐Ÿฆ", + "๐Ÿฎ", + "๐Ÿท", + "๐Ÿฝ", + "๐Ÿธ", + "๐Ÿต", + "๐Ÿ™ˆ", + "๐Ÿ™‰", + "๐Ÿ™Š", + "๐Ÿ’", + "๐Ÿ”", + "๐Ÿง", + "๐Ÿฆ", + "๐Ÿค", + "๐Ÿฃ", + "๐Ÿฅ", + "๐Ÿฆ†", + "๐Ÿฆ…", + "๐Ÿฆ‰", + "๐Ÿฆ‡", + "๐Ÿบ", + "๐Ÿ—", + "๐Ÿด", + "๐Ÿฆ„", + "๐Ÿ", + "๐Ÿ›", + "๐Ÿฆ‹", + "๐ŸŒ", + "๐Ÿž", + "๐Ÿœ", + "๐ŸฆŸ", + "๐Ÿฆ—", + "๐Ÿ•ท๏ธ", + "๐Ÿ•ธ๏ธ", + "๐Ÿฆ‚", + "๐Ÿข", + "๐Ÿ", + "๐ŸฆŽ", + "๐Ÿฆ–", + "๐Ÿฆ•", + "๐Ÿ™", + "๐Ÿฆ‘", + "๐Ÿฆ", + "๐Ÿฆž", + "๐Ÿฆ€", + "๐Ÿก", + "๐Ÿ ", + "๐ŸŸ", + "๐Ÿฌ", + "๐Ÿณ", + "๐Ÿ‹", + "๐Ÿฆˆ", + "๐ŸŠ", + "๐Ÿ…", + "๐Ÿ†", + "๐Ÿฆ“", + "๐Ÿฆ", + "๐Ÿฆง", + "๐Ÿ˜", + "๐Ÿฆ›", + "๐Ÿฆ", + "๐Ÿช", + "๐Ÿซ", + "๐Ÿฆ’", + "๐Ÿฆ˜", + "๐Ÿƒ", + "๐Ÿ‚", + "๐Ÿ„", + "๐ŸŽ", + "๐Ÿ–", + "๐Ÿ", + "๐Ÿ‘", + "๐Ÿฆ™", + "๐Ÿ", + "๐ŸฆŒ", + "๐Ÿ•", + "๐Ÿฉ", + "๐Ÿฆฎ", + "๐Ÿ•โ€๐Ÿฆบ", + "๐Ÿˆ", + "๐Ÿˆโ€โฌ›", + "๐Ÿ“", + "๐Ÿฆƒ", + "๐Ÿฆš", + "๐Ÿฆœ", + "๐Ÿฆข", + "๐Ÿฆฉ", + "๐Ÿ•Š๏ธ", + "๐Ÿ‡", + "๐Ÿฆ", + "๐Ÿฆจ", + "๐Ÿฆก", + "๐Ÿฆฆ", + "๐Ÿฆฅ", + "๐Ÿ", + "๐Ÿ€", + "๐Ÿฟ๏ธ", + "๐Ÿฆ”", + ], + }, + food: { + name: "Food & Drink", + icon: "๐ŸŽ", + emojis: [ + "๐ŸŽ", + "๐Ÿ", + "๐ŸŠ", + "๐Ÿ‹", + "๐ŸŒ", + "๐Ÿ‰", + "๐Ÿ‡", + "๐Ÿ“", + "๐Ÿซ", + "๐Ÿˆ", + "๐Ÿ’", + "๐Ÿ‘", + "๐Ÿฅญ", + "๐Ÿ", + "๐Ÿฅฅ", + "๐Ÿฅ", + "๐Ÿ…", + "๐Ÿ†", + "๐Ÿฅ‘", + "๐Ÿฅฆ", + "๐Ÿฅฌ", + "๐Ÿฅ’", + "๐ŸŒถ๏ธ", + "๐Ÿซ‘", + "๐ŸŒฝ", + "๐Ÿฅ•", + "๐Ÿซ’", + "๐Ÿง„", + "๐Ÿง…", + "๐Ÿฅ”", + "๐Ÿ ", + "๐Ÿฅ", + "๐Ÿฅฏ", + "๐Ÿž", + "๐Ÿฅ–", + "๐Ÿฅจ", + "๐Ÿง€", + "๐Ÿฅš", + "๐Ÿณ", + "๐Ÿงˆ", + "๐Ÿฅž", + "๐Ÿง‡", + "๐Ÿฅ“", + "๐Ÿฅฉ", + "๐Ÿ—", + "๐Ÿ–", + "๐Ÿฆด", + "๐ŸŒญ", + "๐Ÿ”", + "๐ŸŸ", + "๐Ÿ•", + "๐Ÿซ“", + "๐Ÿฅช", + "๐Ÿฅ™", + "๐Ÿง†", + "๐ŸŒฎ", + "๐ŸŒฏ", + "๐Ÿซ”", + "๐Ÿฅ—", + "๐Ÿฅ˜", + "๐Ÿซ•", + "๐Ÿฅซ", + "๐Ÿ", + "๐Ÿœ", + "๐Ÿฒ", + "๐Ÿ›", + "๐Ÿฃ", + "๐Ÿฑ", + "๐ŸฅŸ", + "๐Ÿฆช", + "๐Ÿค", + "๐Ÿ™", + "๐Ÿš", + "๐Ÿ˜", + "๐Ÿฅ", + "๐Ÿฅ ", + "๐Ÿฅฎ", + "๐Ÿข", + "๐Ÿก", + "๐Ÿง", + "๐Ÿจ", + "๐Ÿฆ", + "๐Ÿฅง", + "๐Ÿง", + "๐Ÿฐ", + "๐ŸŽ‚", + "๐Ÿฎ", + "๐Ÿญ", + "๐Ÿฌ", + "๐Ÿซ", + "๐Ÿฟ", + "๐Ÿฉ", + "๐Ÿช", + "๐ŸŒฐ", + "๐Ÿฅœ", + "๐Ÿฏ", + "๐Ÿฅ›", + "๐Ÿผ", + "โ˜•", + "๐Ÿซ–", + "๐Ÿต", + "๐Ÿงƒ", + "๐Ÿฅค", + "๐Ÿง‹", + "๐Ÿถ", + "๐Ÿบ", + "๐Ÿป", + "๐Ÿฅ‚", + "๐Ÿท", + "๐Ÿฅƒ", + "๐Ÿธ", + "๐Ÿน", + "๐Ÿง‰", + "๐Ÿพ", + ], + }, + activities: { + name: "Activities", + icon: "โšฝ", + emojis: [ + "โšฝ", + "๐Ÿ€", + "๐Ÿˆ", + "โšพ", + "๐ŸฅŽ", + "๐ŸŽพ", + "๐Ÿ", + "๐Ÿ‰", + "๐Ÿฅ", + "๐ŸŽฑ", + "๐Ÿช€", + "๐Ÿ“", + "๐Ÿธ", + "๐Ÿ’", + "๐Ÿ‘", + "๐Ÿฅ", + "๐Ÿ", + "๐Ÿชƒ", + "๐Ÿฅ…", + "โ›ณ", + "๐Ÿช", + "๐Ÿน", + "๐ŸŽฃ", + "๐Ÿคฟ", + "๐ŸฅŠ", + "๐Ÿฅ‹", + "๐ŸŽฝ", + "๐Ÿ›น", + "๐Ÿ›ท", + "โ›ธ๏ธ", + "๐ŸฅŒ", + "๐ŸŽฟ", + "โ›ท๏ธ", + "๐Ÿ‚", + "๐Ÿช‚", + "๐Ÿ‹๏ธ", + "๐Ÿ‹๏ธโ€โ™‚๏ธ", + "๐Ÿ‹๏ธโ€โ™€๏ธ", + "๐Ÿคผ", + "๐Ÿคผโ€โ™‚๏ธ", + "๐Ÿคผโ€โ™€๏ธ", + "๐Ÿคธ", + "๐Ÿคธโ€โ™‚๏ธ", + "๐Ÿคธโ€โ™€๏ธ", + "โ›น๏ธ", + "โ›น๏ธโ€โ™‚๏ธ", + "โ›น๏ธโ€โ™€๏ธ", + "๐Ÿคบ", + "๐Ÿคพ", + "๐Ÿคพโ€โ™‚๏ธ", + "๐Ÿคพโ€โ™€๏ธ", + "๐ŸŒ๏ธ", + "๐ŸŒ๏ธโ€โ™‚๏ธ", + "๐ŸŒ๏ธโ€โ™€๏ธ", + "๐Ÿ‡", + "๐Ÿง˜", + "๐Ÿง˜โ€โ™‚๏ธ", + "๐Ÿง˜โ€โ™€๏ธ", + "๐Ÿ„", + "๐Ÿ„โ€โ™‚๏ธ", + "๐Ÿ„โ€โ™€๏ธ", + "๐ŸŠ", + "๐ŸŠโ€โ™‚๏ธ", + "๐ŸŠโ€โ™€๏ธ", + "๐Ÿคฝ", + "๐Ÿคฝโ€โ™‚๏ธ", + "๐Ÿคฝโ€โ™€๏ธ", + "๐Ÿšฃ", + "๐Ÿšฃโ€โ™‚๏ธ", + "๐Ÿšฃโ€โ™€๏ธ", + "๐Ÿง—", + "๐Ÿง—โ€โ™‚๏ธ", + "๐Ÿง—โ€โ™€๏ธ", + "๐Ÿšต", + "๐Ÿšตโ€โ™‚๏ธ", + "๐Ÿšตโ€โ™€๏ธ", + "๐Ÿšด", + "๐Ÿšดโ€โ™‚๏ธ", + "๐Ÿšดโ€โ™€๏ธ", + "๐Ÿ†", + "๐Ÿฅ‡", + "๐Ÿฅˆ", + "๐Ÿฅ‰", + "๐Ÿ…", + "๐ŸŽ–๏ธ", + "๐Ÿต๏ธ", + "๐ŸŽ—๏ธ", + "๐ŸŽซ", + "๐ŸŽŸ๏ธ", + "๐ŸŽช", + "๐Ÿคน", + "๐Ÿคนโ€โ™‚๏ธ", + "๐Ÿคนโ€โ™€๏ธ", + "๐ŸŽญ", + "๐Ÿฉฐ", + "๐ŸŽจ", + "๐ŸŽฌ", + "๐ŸŽค", + "๐ŸŽง", + "๐ŸŽผ", + "๐ŸŽต", + "๐ŸŽถ", + "๐Ÿฅ", + "๐Ÿช˜", + "๐ŸŽน", + "๐ŸŽท", + "๐ŸŽบ", + "๐Ÿช—", + "๐ŸŽธ", + "๐Ÿช•", + "๐ŸŽป", + "๐ŸŽฒ", + "โ™Ÿ๏ธ", + "๐ŸŽฏ", + "๐ŸŽณ", + "๐ŸŽฎ", + "๐ŸŽฐ", + "๐Ÿงฉ", + ], + }, + travel: { + name: "Travel & Places", + icon: "๐Ÿš—", + emojis: [ + "๐Ÿš—", + "๐Ÿš•", + "๐Ÿš™", + "๐ŸšŒ", + "๐ŸšŽ", + "๐ŸŽ๏ธ", + "๐Ÿš“", + "๐Ÿš‘", + "๐Ÿš’", + "๐Ÿš", + "๐Ÿ›ป", + "๐Ÿšš", + "๐Ÿš›", + "๐Ÿšœ", + "๐Ÿ๏ธ", + "๐Ÿ›ต", + "๐Ÿšฒ", + "๐Ÿ›ด", + "๐Ÿ›น", + "๐Ÿ›ผ", + "๐Ÿš", + "๐Ÿ›ธ", + "โœˆ๏ธ", + "๐Ÿ›ฉ๏ธ", + "๐Ÿ›ซ", + "๐Ÿ›ฌ", + "๐Ÿช‚", + "๐Ÿ’บ", + "๐Ÿš€", + "๐Ÿ›ฐ๏ธ", + "๐Ÿš‰", + "๐ŸšŠ", + "๐Ÿš", + "๐Ÿšž", + "๐Ÿš‹", + "๐Ÿšƒ", + "๐Ÿš‹", + "๐Ÿšž", + "๐Ÿš", + "๐Ÿš„", + "๐Ÿš…", + "๐Ÿšˆ", + "๐Ÿš‚", + "๐Ÿš†", + "๐Ÿš‡", + "๐ŸšŠ", + "๐Ÿš‰", + "โœˆ๏ธ", + "๐Ÿ›ซ", + "๐Ÿ›ฌ", + "๐Ÿ›ฉ๏ธ", + "๐Ÿ’บ", + "๐Ÿ›ฐ๏ธ", + "๐Ÿš€", + "๐Ÿ›ธ", + "๐Ÿš", + "๐Ÿ›ถ", + "โ›ต", + "๐Ÿšค", + "๐Ÿ›ฅ๏ธ", + "๐Ÿ›ณ๏ธ", + "โ›ด๏ธ", + "๐Ÿšข", + "โš“", + "โ›ฝ", + "๐Ÿšง", + "๐Ÿšจ", + "๐Ÿšฅ", + "๐Ÿšฆ", + "๐Ÿ›‘", + "๐Ÿš", + "๐Ÿ—บ๏ธ", + "๐Ÿ—ฟ", + "๐Ÿ—ฝ", + "๐Ÿ—ผ", + "๐Ÿฐ", + "๐Ÿฏ", + "๐ŸŸ๏ธ", + "๐ŸŽก", + "๐ŸŽข", + "๐ŸŽ ", + "โ›ฒ", + "โ›ฑ๏ธ", + "๐Ÿ–๏ธ", + "๐Ÿ๏ธ", + "๐Ÿœ๏ธ", + "๐ŸŒ‹", + "โ›ฐ๏ธ", + "๐Ÿ”๏ธ", + "๐Ÿ—ป", + "๐Ÿ•๏ธ", + "โ›บ", + "๐Ÿ›–", + "๐Ÿ ", + "๐Ÿก", + "๐Ÿ˜๏ธ", + "๐Ÿš๏ธ", + "๐Ÿ—๏ธ", + "๐Ÿญ", + "๐Ÿข", + "๐Ÿฌ", + "๐Ÿฃ", + "๐Ÿค", + "๐Ÿฅ", + "๐Ÿฆ", + "๐Ÿจ", + "๐Ÿช", + "๐Ÿซ", + "๐Ÿฉ", + "๐Ÿ’’", + "๐Ÿ›๏ธ", + "โ›ช", + "๐Ÿ•Œ", + "๐Ÿ›•", + "๐Ÿ•", + "๐Ÿ•‹", + "โ›ฉ๏ธ", + "๐Ÿ›ค๏ธ", + "๐Ÿ›ฃ๏ธ", + "๐Ÿ—พ", + "๐ŸŽ‘", + "๐Ÿž๏ธ", + "๐ŸŒ…", + "๐ŸŒ„", + "๐ŸŒ ", + "๐ŸŽ‡", + "๐ŸŽ†", + "๐ŸŒ‡", + "๐ŸŒ†", + "๐Ÿ™๏ธ", + "๐ŸŒƒ", + "๐ŸŒŒ", + "๐ŸŒ‰", + "๐ŸŒ", + ], + }, + objects: { + name: "Objects", + icon: "โŒš", + emojis: [ + "โŒš", + "๐Ÿ“ฑ", + "๐Ÿ“ฒ", + "๐Ÿ’ป", + "โŒจ๏ธ", + "๐Ÿ–ฅ๏ธ", + "๐Ÿ–จ๏ธ", + "๐Ÿ–ฑ๏ธ", + "๐Ÿ–ฒ๏ธ", + "๐Ÿ•น๏ธ", + "๐Ÿ—œ๏ธ", + "๐Ÿ’ฝ", + "๐Ÿ’พ", + "๐Ÿ’ฟ", + "๐Ÿ“€", + "๐Ÿ“ผ", + "๐Ÿ“ท", + "๐Ÿ“ธ", + "๐Ÿ“น", + "๐ŸŽฅ", + "๐Ÿ“ฝ๏ธ", + "๐ŸŽž๏ธ", + "๐Ÿ“ž", + "โ˜Ž๏ธ", + "๐Ÿ“Ÿ", + "๐Ÿ“ ", + "๐Ÿ“บ", + "๐Ÿ“ป", + "๐ŸŽ™๏ธ", + "๐ŸŽš๏ธ", + "๐ŸŽ›๏ธ", + "๐Ÿงญ", + "โฑ๏ธ", + "โฒ๏ธ", + "โฐ", + "๐Ÿ•ฐ๏ธ", + "โŒ›", + "โณ", + "๐Ÿ“ก", + "๐Ÿ”‹", + "๐Ÿ”Œ", + "๐Ÿ’ก", + "๐Ÿ”ฆ", + "๐Ÿ•ฏ๏ธ", + "๐Ÿช”", + "๐Ÿงฏ", + "๐Ÿ›ข๏ธ", + "๐Ÿ’ธ", + "๐Ÿ’ต", + "๐Ÿ’ด", + "๐Ÿ’ถ", + "๐Ÿ’ท", + "๐Ÿช™", + "๐Ÿ’ฐ", + "๐Ÿ’ณ", + "๐Ÿ’Ž", + "โš–๏ธ", + "๐Ÿชœ", + "๐Ÿงฐ", + "๐Ÿ”ง", + "๐Ÿ”จ", + "โš’๏ธ", + "๐Ÿ› ๏ธ", + "โ›๏ธ", + "๐Ÿช“", + "๐Ÿชš", + "๐Ÿ”ฉ", + "โš™๏ธ", + "๐Ÿชค", + "๐Ÿงฑ", + "โ›“๏ธ", + "๐Ÿงฒ", + "๐Ÿ”ซ", + "๐Ÿ’ฃ", + "๐Ÿงจ", + "๐Ÿช“", + "๐Ÿ”ช", + "๐Ÿ—ก๏ธ", + "โš”๏ธ", + "๐Ÿ›ก๏ธ", + "๐Ÿšฌ", + "โšฐ๏ธ", + "๐Ÿชฆ", + "โšฑ๏ธ", + "๐Ÿบ", + "๐Ÿ”ฎ", + "๐Ÿ“ฟ", + "๐Ÿงฟ", + "๐Ÿ’ˆ", + "โš—๏ธ", + "๐Ÿ”ญ", + "๐Ÿ”ฌ", + "๐Ÿ•ณ๏ธ", + "๐Ÿฉน", + "๐Ÿฉบ", + "๐Ÿ’Š", + "๐Ÿ’‰", + "๐Ÿฉธ", + "๐Ÿงฌ", + "๐Ÿฆ ", + "๐Ÿงซ", + "๐Ÿงช", + "๐ŸŒก๏ธ", + "๐Ÿงน", + "๐Ÿชฃ", + "๐Ÿงฝ", + "๐Ÿงด", + "๐Ÿ›Ž๏ธ", + "๐Ÿ”‘", + "๐Ÿ—๏ธ", + "๐Ÿšช", + "๐Ÿช‘", + "๐Ÿ›‹๏ธ", + "๐Ÿ›๏ธ", + "๐Ÿ›Œ", + "๐Ÿงธ", + "๐Ÿช†", + "๐Ÿ–ผ๏ธ", + "๐Ÿชž", + "๐ŸชŸ", + "๐Ÿ›๏ธ", + "๐Ÿ›’", + "๐ŸŽ", + "๐ŸŽˆ", + "๐ŸŽ", + "๐ŸŽ€", + "๐Ÿช„", + "๐Ÿช…", + "๐ŸŽŠ", + "๐ŸŽ‰", + "๐ŸŽŽ", + "๐Ÿฎ", + "๐ŸŽ", + "๐Ÿงง", + "โœ‰๏ธ", + "๐Ÿ“ฉ", + "๐Ÿ“จ", + "๐Ÿ“ง", + "๐Ÿ’Œ", + "๐Ÿ“ฅ", + "๐Ÿ“ค", + "๐Ÿ“ฆ", + "๐Ÿท๏ธ", + "๐Ÿชง", + "๐Ÿ“ช", + "๐Ÿ“ซ", + "๐Ÿ“ฌ", + "๐Ÿ“ญ", + "๐Ÿ“ฎ", + "๐Ÿ“ฏ", + "๐Ÿ“œ", + "๐Ÿ“ƒ", + "๐Ÿ“„", + "๐Ÿ“‘", + "๐Ÿงพ", + "๐Ÿ“Š", + "๐Ÿ“ˆ", + "๐Ÿ“‰", + "๐Ÿ—’๏ธ", + "๐Ÿ—“๏ธ", + "๐Ÿ“†", + "๐Ÿ“…", + "๐Ÿ—‘๏ธ", + "๐Ÿ“‡", + "๐Ÿ—ƒ๏ธ", + "๐Ÿ—ณ๏ธ", + "๐Ÿ—„๏ธ", + "๐Ÿ“‹", + "๐Ÿ“", + "๐Ÿ“‚", + "๐Ÿ—‚๏ธ", + "๐Ÿ—ž๏ธ", + "๐Ÿ“ฐ", + "๐Ÿ““", + "๐Ÿ“”", + "๐Ÿ“’", + "๐Ÿ“•", + "๐Ÿ“—", + "๐Ÿ“˜", + "๐Ÿ“™", + "๐Ÿ“š", + "๐Ÿ“–", + "๐Ÿ”–", + "๐Ÿงท", + "๐Ÿ”—", + "๐Ÿ“Ž", + "๐Ÿ–‡๏ธ", + "๐Ÿ“", + "๐Ÿ“", + "๐Ÿงฎ", + "๐Ÿ“Œ", + "๐Ÿ“", + "โœ‚๏ธ", + "๐Ÿ–Š๏ธ", + "๐Ÿ–‹๏ธ", + "โœ’๏ธ", + "๐Ÿ–Œ๏ธ", + "๐Ÿ–๏ธ", + "๐Ÿ“", + "โœ๏ธ", + "๐Ÿ”", + "๐Ÿ”Ž", + "๐Ÿ”", + "๐Ÿ”", + "๐Ÿ”’", + "๐Ÿ”“", + ], + }, + symbols: { + name: "Symbols", + icon: "โค๏ธ", + emojis: [ + "โค๏ธ", + "๐Ÿงก", + "๐Ÿ’›", + "๐Ÿ’š", + "๐Ÿ’™", + "๐Ÿ’œ", + "๐Ÿ–ค", + "๐Ÿค", + "๐ŸคŽ", + "๐Ÿ’”", + "โฃ๏ธ", + "๐Ÿ’•", + "๐Ÿ’ž", + "๐Ÿ’“", + "๐Ÿ’—", + "๐Ÿ’–", + "๐Ÿ’˜", + "๐Ÿ’", + "๐Ÿ’Ÿ", + "โ˜ฎ๏ธ", + "โœ๏ธ", + "โ˜ช๏ธ", + "๐Ÿ•‰๏ธ", + "โ˜ธ๏ธ", + "โœก๏ธ", + "๐Ÿ”ฏ", + "๐Ÿ•Ž", + "โ˜ฏ๏ธ", + "โ˜ฆ๏ธ", + "๐Ÿ›", + "โ›Ž", + "โ™ˆ", + "โ™‰", + "โ™Š", + "โ™‹", + "โ™Œ", + "โ™", + "โ™Ž", + "โ™", + "โ™", + "โ™‘", + "โ™’", + "โ™“", + "๐Ÿ†”", + "โš›๏ธ", + "๐Ÿ‰‘", + "โ˜ข๏ธ", + "โ˜ฃ๏ธ", + "๐Ÿ“ด", + "๐Ÿ“ณ", + "๐Ÿˆถ", + "๐Ÿˆš", + "๐Ÿˆธ", + "๐Ÿˆบ", + "๐Ÿˆท๏ธ", + "โœด๏ธ", + "๐Ÿ†š", + "๐Ÿ’ฎ", + "๐Ÿ‰", + "ใŠ™๏ธ", + "ใŠ—๏ธ", + "๐Ÿˆด", + "๐Ÿˆต", + "๐Ÿˆน", + "๐Ÿˆฒ", + "๐Ÿ…ฐ๏ธ", + "๐Ÿ…ฑ๏ธ", + "๐Ÿ†Ž", + "๐Ÿ†‘", + "๐Ÿ…พ๏ธ", + "๐Ÿ†˜", + "โŒ", + "โญ•", + "๐Ÿ›‘", + "โ›”", + "๐Ÿ“›", + "๐Ÿšซ", + "๐Ÿ’ฏ", + "๐Ÿ’ข", + "โ™จ๏ธ", + "๐Ÿšท", + "๐Ÿšฏ", + "๐Ÿšณ", + "๐Ÿšฑ", + "๐Ÿ”ž", + "๐Ÿ“ต", + "๐Ÿšญ", + "โ—", + "โ•", + "โ“", + "โ”", + "โ€ผ๏ธ", + "โ‰๏ธ", + "๐Ÿ”…", + "๐Ÿ”†", + "ใ€ฝ๏ธ", + "โš ๏ธ", + "๐Ÿšธ", + "๐Ÿ”ฑ", + "โšœ๏ธ", + "๐Ÿ”ฐ", + "โ™ป๏ธ", + "โœ…", + "๐Ÿˆฏ", + "๐Ÿ’น", + "โ‡๏ธ", + "โœณ๏ธ", + "โŽ", + "๐ŸŒ", + "๐Ÿ’ ", + "โ“‚๏ธ", + "๐ŸŒ€", + "๐Ÿ’ค", + "๐Ÿง", + "๐Ÿšพ", + "โ™ฟ", + "๐Ÿ…ฟ๏ธ", + "๐Ÿ›—", + "๐Ÿˆณ", + "๐Ÿˆ‚๏ธ", + "๐Ÿ›‚", + "๐Ÿ›ƒ", + "๐Ÿ›„", + "๐Ÿ›…", + "๐Ÿšน", + "๐Ÿšบ", + "๐Ÿšผ", + "โšง๏ธ", + "๐Ÿšป", + "๐Ÿšฎ", + "๐ŸŽฆ", + "๐Ÿ“ถ", + "๐Ÿˆ", + "๐Ÿ”ฃ", + "โ„น๏ธ", + "๐Ÿ”ค", + "๐Ÿ”ก", + "๐Ÿ” ", + "๐Ÿ†–", + "๐Ÿ†—", + "๐Ÿ†™", + "๐Ÿ†’", + "๐Ÿ†•", + "๐Ÿ†“", + "0๏ธโƒฃ", + "1๏ธโƒฃ", + "2๏ธโƒฃ", + "3๏ธโƒฃ", + "4๏ธโƒฃ", + "5๏ธโƒฃ", + "6๏ธโƒฃ", + "7๏ธโƒฃ", + "8๏ธโƒฃ", + "9๏ธโƒฃ", + "๐Ÿ”Ÿ", + "๐Ÿ”ข", + "#๏ธโƒฃ", + "*๏ธโƒฃ", + "โ๏ธ", + "โ–ถ๏ธ", + "โธ๏ธ", + "โฏ๏ธ", + "โน๏ธ", + "โบ๏ธ", + "โญ๏ธ", + "โฎ๏ธ", + "โฉ", + "โช", + "โซ", + "โฌ", + "โ—€๏ธ", + "๐Ÿ”ผ", + "๐Ÿ”ฝ", + "โžก๏ธ", + "โฌ…๏ธ", + "โฌ†๏ธ", + "โฌ‡๏ธ", + "โ†—๏ธ", + "โ†˜๏ธ", + "โ†™๏ธ", + "โ†–๏ธ", + "โ†•๏ธ", + "โ†”๏ธ", + "โ†ช๏ธ", + "โ†ฉ๏ธ", + "โคด๏ธ", + "โคต๏ธ", + "๐Ÿ”€", + "๐Ÿ”", + "๐Ÿ”‚", + "๐Ÿ”„", + "๐Ÿ”ƒ", + "๐ŸŽต", + "๐ŸŽถ", + "โž•", + "โž–", + "โž—", + "โœ–๏ธ", + "๐ŸŸฐ", + "โ™พ๏ธ", + "๐Ÿ’ฒ", + "๐Ÿ’ฑ", + "โ„ข๏ธ", + "ยฉ๏ธ", + "ยฎ๏ธ", + "ใ€ฐ๏ธ", + "โžฐ", + "โžฟ", + "๐Ÿ”š", + "๐Ÿ”™", + "๐Ÿ”›", + "๐Ÿ”", + "๐Ÿ”œ", + "โœ”๏ธ", + "โ˜‘๏ธ", + "๐Ÿ”˜", + "๐Ÿ”ด", + "๐ŸŸ ", + "๐ŸŸก", + "๐ŸŸข", + "๐Ÿ”ต", + "๐ŸŸฃ", + "โšซ", + "โšช", + "๐ŸŸค", + "๐Ÿ”บ", + "๐Ÿ”ป", + "๐Ÿ”ธ", + "๐Ÿ”น", + "๐Ÿ”ถ", + "๐Ÿ”ท", + "๐Ÿ”ณ", + "๐Ÿ”ฒ", + "โ–ช๏ธ", + "โ–ซ๏ธ", + "โ—พ", + "โ—ฝ", + "โ—ผ๏ธ", + "โ—ป๏ธ", + "๐ŸŸฅ", + "๐ŸŸง", + "๐ŸŸจ", + "๐ŸŸฉ", + "๐ŸŸฆ", + "๐ŸŸช", + "โฌ›", + "โฌœ", + "๐ŸŸซ", + "๐Ÿ”ˆ", + "๐Ÿ”‡", + "๐Ÿ”‰", + "๐Ÿ”Š", + "๐Ÿ””", + "๐Ÿ”•", + "๐Ÿ“ฃ", + "๐Ÿ“ข", + "๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ", + "๐Ÿ’ฌ", + "๐Ÿ’ญ", + "๐Ÿ—ฏ๏ธ", + "โ™ ๏ธ", + "โ™ฃ๏ธ", + "โ™ฅ๏ธ", + "โ™ฆ๏ธ", + "๐Ÿƒ", + "๐ŸŽด", + "๐Ÿ€„", + "๐Ÿ•", + "๐Ÿ•‘", + "๐Ÿ•’", + "๐Ÿ•“", + "๐Ÿ•”", + "๐Ÿ••", + "๐Ÿ•–", + "๐Ÿ•—", + "๐Ÿ•˜", + "๐Ÿ•™", + "๐Ÿ•š", + "๐Ÿ•›", + "๐Ÿ•œ", + "๐Ÿ•", + "๐Ÿ•ž", + "๐Ÿ•Ÿ", + "๐Ÿ• ", + "๐Ÿ•ก", + "๐Ÿ•ข", + "๐Ÿ•ฃ", + "๐Ÿ•ค", + "๐Ÿ•ฅ", + "๐Ÿ•ฆ", + "๐Ÿ•ง", + ], + }, + flags: { + name: "Flags", + icon: "๐Ÿ", + emojis: [ + "๐Ÿ", + "๐Ÿšฉ", + "๐ŸŽŒ", + "๐Ÿด", + "๐Ÿณ๏ธ", + "๐Ÿณ๏ธโ€๐ŸŒˆ", + "๐Ÿณ๏ธโ€โšง๏ธ", + "๐Ÿดโ€โ˜ ๏ธ", + "๐Ÿ‡ฆ๐Ÿ‡จ", + "๐Ÿ‡ฆ๐Ÿ‡ฉ", + "๐Ÿ‡ฆ๐Ÿ‡ช", + "๐Ÿ‡ฆ๐Ÿ‡ซ", + "๐Ÿ‡ฆ๐Ÿ‡ฌ", + "๐Ÿ‡ฆ๐Ÿ‡ฎ", + "๐Ÿ‡ฆ๐Ÿ‡ฑ", + "๐Ÿ‡ฆ๐Ÿ‡ฒ", + "๐Ÿ‡ฆ๐Ÿ‡ด", + "๐Ÿ‡ฆ๐Ÿ‡ถ", + "๐Ÿ‡ฆ๐Ÿ‡ท", + "๐Ÿ‡ฆ๐Ÿ‡ธ", + "๐Ÿ‡ฆ๐Ÿ‡น", + "๐Ÿ‡ฆ๐Ÿ‡บ", + "๐Ÿ‡ฆ๐Ÿ‡ผ", + "๐Ÿ‡ฆ๐Ÿ‡ฝ", + "๐Ÿ‡ฆ๐Ÿ‡ฟ", + "๐Ÿ‡ง๐Ÿ‡ฆ", + "๐Ÿ‡ง๐Ÿ‡ง", + "๐Ÿ‡ง๐Ÿ‡ฉ", + "๐Ÿ‡ง๐Ÿ‡ช", + "๐Ÿ‡ง๐Ÿ‡ซ", + "๐Ÿ‡ง๐Ÿ‡ฌ", + "๐Ÿ‡ง๐Ÿ‡ญ", + "๐Ÿ‡ง๐Ÿ‡ฎ", + "๐Ÿ‡ง๐Ÿ‡ฏ", + "๐Ÿ‡ง๐Ÿ‡ฑ", + "๐Ÿ‡ง๐Ÿ‡ฒ", + "๐Ÿ‡ง๐Ÿ‡ณ", + "๐Ÿ‡ง๐Ÿ‡ด", + "๐Ÿ‡ง๐Ÿ‡ถ", + "๐Ÿ‡ง๐Ÿ‡ท", + "๐Ÿ‡ง๐Ÿ‡ธ", + "๐Ÿ‡ง๐Ÿ‡น", + "๐Ÿ‡ง๐Ÿ‡ป", + "๐Ÿ‡ง๐Ÿ‡ผ", + "๐Ÿ‡ง๐Ÿ‡พ", + "๐Ÿ‡ง๐Ÿ‡ฟ", + "๐Ÿ‡จ๐Ÿ‡ฆ", + "๐Ÿ‡จ๐Ÿ‡จ", + "๐Ÿ‡จ๐Ÿ‡ฉ", + "๐Ÿ‡จ๐Ÿ‡ซ", + "๐Ÿ‡จ๐Ÿ‡ฌ", + "๐Ÿ‡จ๐Ÿ‡ญ", + "๐Ÿ‡จ๐Ÿ‡ฎ", + "๐Ÿ‡จ๐Ÿ‡ฐ", + "๐Ÿ‡จ๐Ÿ‡ฑ", + "๐Ÿ‡จ๐Ÿ‡ฒ", + "๐Ÿ‡จ๐Ÿ‡ณ", + "๐Ÿ‡จ๐Ÿ‡ด", + "๐Ÿ‡จ๐Ÿ‡ต", + "๐Ÿ‡จ๐Ÿ‡ท", + "๐Ÿ‡จ๐Ÿ‡บ", + "๐Ÿ‡จ๐Ÿ‡ป", + "๐Ÿ‡จ๐Ÿ‡ผ", + "๐Ÿ‡จ๐Ÿ‡ฝ", + "๐Ÿ‡จ๐Ÿ‡พ", + "๐Ÿ‡จ๐Ÿ‡ฟ", + "๐Ÿ‡ฉ๐Ÿ‡ช", + "๐Ÿ‡ฉ๐Ÿ‡ฌ", + "๐Ÿ‡ฉ๐Ÿ‡ฏ", + "๐Ÿ‡ฉ๐Ÿ‡ฐ", + "๐Ÿ‡ฉ๐Ÿ‡ฒ", + "๐Ÿ‡ฉ๐Ÿ‡ด", + "๐Ÿ‡ฉ๐Ÿ‡ฟ", + "๐Ÿ‡ช๐Ÿ‡ฆ", + "๐Ÿ‡ช๐Ÿ‡จ", + "๐Ÿ‡ช๐Ÿ‡ช", + "๐Ÿ‡ช๐Ÿ‡ฌ", + "๐Ÿ‡ช๐Ÿ‡ญ", + "๐Ÿ‡ช๐Ÿ‡ท", + "๐Ÿ‡ช๐Ÿ‡ธ", + "๐Ÿ‡ช๐Ÿ‡น", + "๐Ÿ‡ช๐Ÿ‡บ", + "๐Ÿ‡ซ๐Ÿ‡ฎ", + "๐Ÿ‡ซ๐Ÿ‡ฏ", + "๐Ÿ‡ซ๐Ÿ‡ฐ", + "๐Ÿ‡ซ๐Ÿ‡ฒ", + "๐Ÿ‡ซ๐Ÿ‡ด", + "๐Ÿ‡ซ๐Ÿ‡ท", + "๐Ÿ‡ฌ๐Ÿ‡ฆ", + "๐Ÿ‡ฌ๐Ÿ‡ง", + "๐Ÿ‡ฌ๐Ÿ‡ฉ", + "๐Ÿ‡ฌ๐Ÿ‡ช", + "๐Ÿ‡ฌ๐Ÿ‡ซ", + "๐Ÿ‡ฌ๐Ÿ‡ฌ", + "๐Ÿ‡ฌ๐Ÿ‡ญ", + "๐Ÿ‡ฌ๐Ÿ‡ฎ", + "๐Ÿ‡ฌ๐Ÿ‡ฑ", + "๐Ÿ‡ฌ๐Ÿ‡ฒ", + "๐Ÿ‡ฌ๐Ÿ‡ณ", + "๐Ÿ‡ฌ๐Ÿ‡ต", + "๐Ÿ‡ฌ๐Ÿ‡ถ", + "๐Ÿ‡ฌ๐Ÿ‡ท", + "๐Ÿ‡ฌ๐Ÿ‡ธ", + "๐Ÿ‡ฌ๐Ÿ‡น", + "๐Ÿ‡ฌ๐Ÿ‡บ", + "๐Ÿ‡ฌ๐Ÿ‡ผ", + "๐Ÿ‡ฌ๐Ÿ‡พ", + "๐Ÿ‡ญ๐Ÿ‡ฐ", + "๐Ÿ‡ญ๐Ÿ‡ฒ", + "๐Ÿ‡ญ๐Ÿ‡ณ", + "๐Ÿ‡ญ๐Ÿ‡ท", + "๐Ÿ‡ญ๐Ÿ‡น", + "๐Ÿ‡ญ๐Ÿ‡บ", + "๐Ÿ‡ฎ๐Ÿ‡จ", + "๐Ÿ‡ฎ๐Ÿ‡ฉ", + "๐Ÿ‡ฎ๐Ÿ‡ช", + "๐Ÿ‡ฎ๐Ÿ‡ฑ", + "๐Ÿ‡ฎ๐Ÿ‡ฒ", + "๐Ÿ‡ฎ๐Ÿ‡ณ", + "๐Ÿ‡ฎ๐Ÿ‡ด", + "๐Ÿ‡ฎ๐Ÿ‡ถ", + "๐Ÿ‡ฎ๐Ÿ‡ท", + "๐Ÿ‡ฎ๐Ÿ‡ธ", + "๐Ÿ‡ฎ๐Ÿ‡น", + "๐Ÿ‡ฏ๐Ÿ‡ช", + "๐Ÿ‡ฏ๐Ÿ‡ฒ", + "๐Ÿ‡ฏ๐Ÿ‡ด", + "๐Ÿ‡ฏ๐Ÿ‡ต", + "๐Ÿ‡ฐ๐Ÿ‡ช", + "๐Ÿ‡ฐ๐Ÿ‡ฌ", + "๐Ÿ‡ฐ๐Ÿ‡ญ", + "๐Ÿ‡ฐ๐Ÿ‡ฎ", + "๐Ÿ‡ฐ๐Ÿ‡ฒ", + "๐Ÿ‡ฐ๐Ÿ‡ณ", + "๐Ÿ‡ฐ๐Ÿ‡ต", + "๐Ÿ‡ฐ๐Ÿ‡ท", + "๐Ÿ‡ฐ๐Ÿ‡ผ", + "๐Ÿ‡ฐ๐Ÿ‡พ", + "๐Ÿ‡ฐ๐Ÿ‡ฟ", + "๐Ÿ‡ฑ๐Ÿ‡ฆ", + "๐Ÿ‡ฑ๐Ÿ‡ง", + "๐Ÿ‡ฑ๐Ÿ‡จ", + "๐Ÿ‡ฑ๐Ÿ‡ฎ", + "๐Ÿ‡ฑ๐Ÿ‡ฐ", + "๐Ÿ‡ฑ๐Ÿ‡ท", + "๐Ÿ‡ฑ๐Ÿ‡ธ", + "๐Ÿ‡ฑ๐Ÿ‡น", + "๐Ÿ‡ฑ๐Ÿ‡บ", + "๐Ÿ‡ฑ๐Ÿ‡ป", + "๐Ÿ‡ฑ๐Ÿ‡พ", + "๐Ÿ‡ฒ๐Ÿ‡ฆ", + "๐Ÿ‡ฒ๐Ÿ‡จ", + "๐Ÿ‡ฒ๐Ÿ‡ฉ", + "๐Ÿ‡ฒ๐Ÿ‡ช", + "๐Ÿ‡ฒ๐Ÿ‡ซ", + "๐Ÿ‡ฒ๐Ÿ‡ฌ", + "๐Ÿ‡ฒ๐Ÿ‡ญ", + "๐Ÿ‡ฒ๐Ÿ‡ฐ", + "๐Ÿ‡ฒ๐Ÿ‡ฑ", + "๐Ÿ‡ฒ๐Ÿ‡ฒ", + "๐Ÿ‡ฒ๐Ÿ‡ณ", + "๐Ÿ‡ฒ๐Ÿ‡ด", + "๐Ÿ‡ฒ๐Ÿ‡ต", + "๐Ÿ‡ฒ๐Ÿ‡ถ", + "๐Ÿ‡ฒ๐Ÿ‡ท", + "๐Ÿ‡ฒ๐Ÿ‡ธ", + "๐Ÿ‡ฒ๐Ÿ‡น", + "๐Ÿ‡ฒ๐Ÿ‡บ", + "๐Ÿ‡ฒ๐Ÿ‡ป", + "๐Ÿ‡ฒ๐Ÿ‡ผ", + "๐Ÿ‡ฒ๐Ÿ‡ฝ", + "๐Ÿ‡ฒ๐Ÿ‡พ", + "๐Ÿ‡ฒ๐Ÿ‡ฟ", + "๐Ÿ‡ณ๐Ÿ‡ฆ", + "๐Ÿ‡ณ๐Ÿ‡จ", + "๐Ÿ‡ณ๐Ÿ‡ช", + "๐Ÿ‡ณ๐Ÿ‡ซ", + "๐Ÿ‡ณ๐Ÿ‡ฌ", + "๐Ÿ‡ณ๐Ÿ‡ฎ", + "๐Ÿ‡ณ๐Ÿ‡ฑ", + "๐Ÿ‡ณ๐Ÿ‡ด", + "๐Ÿ‡ณ๐Ÿ‡ต", + "๐Ÿ‡ณ๐Ÿ‡ท", + "๐Ÿ‡ณ๐Ÿ‡บ", + "๐Ÿ‡ณ๐Ÿ‡ฟ", + "๐Ÿ‡ด๐Ÿ‡ฒ", + "๐Ÿ‡ต๐Ÿ‡ฆ", + "๐Ÿ‡ต๐Ÿ‡ช", + "๐Ÿ‡ต๐Ÿ‡ซ", + "๐Ÿ‡ต๐Ÿ‡ฌ", + "๐Ÿ‡ต๐Ÿ‡ญ", + "๐Ÿ‡ต๐Ÿ‡ฐ", + "๐Ÿ‡ต๐Ÿ‡ฑ", + "๐Ÿ‡ต๐Ÿ‡ฒ", + "๐Ÿ‡ต๐Ÿ‡ณ", + "๐Ÿ‡ต๐Ÿ‡ท", + "๐Ÿ‡ต๐Ÿ‡ธ", + "๐Ÿ‡ต๐Ÿ‡น", + "๐Ÿ‡ต๐Ÿ‡ผ", + "๐Ÿ‡ต๐Ÿ‡พ", + "๐Ÿ‡ถ๐Ÿ‡ฆ", + "๐Ÿ‡ท๐Ÿ‡ช", + "๐Ÿ‡ท๐Ÿ‡ด", + "๐Ÿ‡ท๐Ÿ‡ธ", + "๐Ÿ‡ท๐Ÿ‡บ", + "๐Ÿ‡ท๐Ÿ‡ผ", + "๐Ÿ‡ธ๐Ÿ‡ฆ", + "๐Ÿ‡ธ๐Ÿ‡ง", + "๐Ÿ‡ธ๐Ÿ‡จ", + "๐Ÿ‡ธ๐Ÿ‡ฉ", + "๐Ÿ‡ธ๐Ÿ‡ช", + "๐Ÿ‡ธ๐Ÿ‡ฌ", + "๐Ÿ‡ธ๐Ÿ‡ญ", + "๐Ÿ‡ธ๐Ÿ‡ฎ", + "๐Ÿ‡ธ๐Ÿ‡ฏ", + "๐Ÿ‡ธ๐Ÿ‡ฐ", + "๐Ÿ‡ธ๐Ÿ‡ฑ", + "๐Ÿ‡ธ๐Ÿ‡ฒ", + "๐Ÿ‡ธ๐Ÿ‡ณ", + "๐Ÿ‡ธ๐Ÿ‡ด", + "๐Ÿ‡ธ๐Ÿ‡ท", + "๐Ÿ‡ธ๐Ÿ‡ธ", + "๐Ÿ‡ธ๐Ÿ‡น", + "๐Ÿ‡ธ๐Ÿ‡ป", + "๐Ÿ‡ธ๐Ÿ‡ฝ", + "๐Ÿ‡ธ๐Ÿ‡พ", + "๐Ÿ‡ธ๐Ÿ‡ฟ", + "๐Ÿ‡น๐Ÿ‡ฆ", + "๐Ÿ‡น๐Ÿ‡จ", + "๐Ÿ‡น๐Ÿ‡ฉ", + "๐Ÿ‡น๐Ÿ‡ซ", + "๐Ÿ‡น๐Ÿ‡ฌ", + "๐Ÿ‡น๐Ÿ‡ญ", + "๐Ÿ‡น๐Ÿ‡ฏ", + "๐Ÿ‡น๐Ÿ‡ฐ", + "๐Ÿ‡น๐Ÿ‡ฑ", + "๐Ÿ‡น๐Ÿ‡ฒ", + "๐Ÿ‡น๐Ÿ‡ณ", + "๐Ÿ‡น๐Ÿ‡ด", + "๐Ÿ‡น๐Ÿ‡ท", + "๐Ÿ‡น๐Ÿ‡น", + "๐Ÿ‡น๐Ÿ‡ป", + "๐Ÿ‡น๐Ÿ‡ผ", + "๐Ÿ‡น๐Ÿ‡ฟ", + "๐Ÿ‡บ๐Ÿ‡ฆ", + "๐Ÿ‡บ๐Ÿ‡ฌ", + "๐Ÿ‡บ๐Ÿ‡ฒ", + "๐Ÿ‡บ๐Ÿ‡ณ", + "๐Ÿ‡บ๐Ÿ‡ธ", + "๐Ÿ‡บ๐Ÿ‡พ", + "๐Ÿ‡บ๐Ÿ‡ฟ", + "๐Ÿ‡ป๐Ÿ‡ฆ", + "๐Ÿ‡ป๐Ÿ‡จ", + "๐Ÿ‡ป๐Ÿ‡ช", + "๐Ÿ‡ป๐Ÿ‡ฌ", + "๐Ÿ‡ป๐Ÿ‡ฎ", + "๐Ÿ‡ป๐Ÿ‡ณ", + "๐Ÿ‡ป๐Ÿ‡บ", + "๐Ÿ‡ผ๐Ÿ‡ซ", + "๐Ÿ‡ผ๐Ÿ‡ธ", + "๐Ÿ‡ฝ๐Ÿ‡ฐ", + "๐Ÿ‡พ๐Ÿ‡ช", + "๐Ÿ‡พ๐Ÿ‡น", + "๐Ÿ‡ฟ๐Ÿ‡ฆ", + "๐Ÿ‡ฟ๐Ÿ‡ฒ", + "๐Ÿ‡ฟ๐Ÿ‡ผ", + "๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ", + "๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ", + "๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ", + ], + }, +} + +const COLOR_PALETTE = [ + "#ef4444", + "#f97316", + "#f59e0b", + "#eab308", + "#84cc16", + "#22c55e", + "#10b981", + "#14b8a6", + "#06b6d4", + "#0ea5e9", + "#3b82f6", + "#6366f1", + "#8b5cf6", + "#a855f7", + "#d946ef", + "#ec4899", + "#f43f5e", + "#64748b", + "#6b7280", + "#374151", + "#111827", + "#000000", + "#ffffff", + "#f8fafc", +] + +export const StylePicker = ({ + children, + agentId, + currentEmoji, + currentColor, + onStyleChange +}: { + children: React.ReactNode; + agentId: string; + currentEmoji?: string; + currentColor?: string; + onStyleChange: (emoji: string, color: string) => void; +}) => { + const [selectedColor, setSelectedColor] = useState(currentColor || "#3b82f6") + const [selectedEmoji, setSelectedEmoji] = useState(currentEmoji || "๐Ÿ˜€") + const [isOpen, setIsOpen] = useState(false) + const [searchTerm, setSearchTerm] = useState("") + const [activeCategory, setActiveCategory] = useState("smileys") + + // Update local state when props change + React.useEffect(() => { + if (currentColor) setSelectedColor(currentColor); + }, [currentColor]); + + React.useEffect(() => { + if (currentEmoji) setSelectedEmoji(currentEmoji); + }, [currentEmoji]); + + const filteredEmojis = useMemo(() => { + if (!searchTerm) return EMOJI_CATEGORIES[activeCategory as keyof typeof EMOJI_CATEGORIES].emojis + const allEmojis = Object.values(EMOJI_CATEGORIES).flatMap((category) => category.emojis) + return allEmojis.filter((emoji) => { + return emoji.includes(searchTerm) + }) + }, [searchTerm, activeCategory]) + + const handleSubmit = () => { + onStyleChange(selectedEmoji, selectedColor); + setIsOpen(false) + } + + const handleReset = () => { + // Reset to generated defaults based on agent ID + const avatars = ['๐Ÿค–', '๐ŸŽฏ', 'โšก', '๐Ÿš€', '๐Ÿ”ฎ', '๐ŸŽจ', '๐Ÿ“Š', '๐Ÿ”ง', '๐Ÿ’ก', '๐ŸŒŸ']; + const colors = ["#ef4444", "#f97316", "#f59e0b", "#eab308", "#84cc16", "#22c55e", "#10b981", "#14b8a6", "#06b6d4", "#0ea5e9", "#3b82f6", "#6366f1", "#8b5cf6", "#a855f7"]; + + const avatarIndex = agentId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) % avatars.length; + const colorIndex = agentId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) % colors.length; + + setSelectedEmoji(avatars[avatarIndex]); + setSelectedColor(colors[colorIndex]); + setSearchTerm("") + setActiveCategory("smileys") + } + + return ( + + + {children} + + + + +
+
+
+ Color +
+
+ {COLOR_PALETTE.map((color) => ( +
+
+ setSelectedColor(e.target.value)} + className="w-8 h-8 rounded border cursor-pointer" + /> + setSelectedColor(e.target.value)} + className="flex-1 px-2 py-1 text-sm border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="#000000" + /> +
+
+ + + +
+
+ {selectedEmoji} + Emoji +
+ {!searchTerm && ( + + + {Object.entries(EMOJI_CATEGORIES) + .slice(0, 4) + .map(([key, category]) => ( + + {category.icon} + + ))} + + + {Object.entries(EMOJI_CATEGORIES) + .slice(4, 8) + .map(([key, category]) => ( + + {category.icon} + + ))} + + + )} + +
+ {filteredEmojis.map((emoji, index) => ( + + ))} +
+
+ + {searchTerm && filteredEmojis.length === 0 && ( +
No emojis found for "{searchTerm}"
+ )} +
+ + + + + + + + + + ) +} diff --git a/frontend/src/app/(dashboard)/agents/_utils/get-agent-style.ts b/frontend/src/app/(dashboard)/agents/_utils/get-agent-style.ts index 90bd52f9..65374cbf 100644 --- a/frontend/src/app/(dashboard)/agents/_utils/get-agent-style.ts +++ b/frontend/src/app/(dashboard)/agents/_utils/get-agent-style.ts @@ -1,6 +1,6 @@ export const getAgentAvatar = (agentId: string) => { const avatars = ['๐Ÿค–', '๐ŸŽฏ', 'โšก', '๐Ÿš€', '๐Ÿ”ฎ', '๐ŸŽจ', '๐Ÿ“Š', '๐Ÿ”ง', '๐Ÿ’ก', '๐ŸŒŸ']; - const colors = ['bg-cyan-400', 'bg-green-400', 'bg-purple-400', 'bg-blue-400', 'bg-pink-400', 'bg-yellow-400', 'bg-red-400', 'bg-indigo-400']; + const colors = ['#06b6d4', '#22c55e', '#8b5cf6', '#3b82f6', '#ec4899', '#eab308', '#ef4444', '#6366f1']; const avatarIndex = agentId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) % avatars.length; const colorIndex = agentId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) % colors.length; return { diff --git a/frontend/src/app/(dashboard)/agents/new/[agentId]/page.tsx b/frontend/src/app/(dashboard)/agents/new/[agentId]/page.tsx index 82167b66..0451bdca 100644 --- a/frontend/src/app/(dashboard)/agents/new/[agentId]/page.tsx +++ b/frontend/src/app/(dashboard)/agents/new/[agentId]/page.tsx @@ -14,6 +14,22 @@ import { AgentPreview } from '../../_components/agent-preview'; import { cn } from '@/lib/utils'; import { getAgentAvatar } from '../../_utils/get-agent-style'; import { EditableText } from '@/components/ui/editable'; +import { StylePicker } from '../../_components/style-picker'; + +// Extended agent interface for styling +interface AgentWithStyling { + agent_id: string; + name?: string; + description?: string; + system_prompt?: string; + agentpress_tools?: Record; + configured_mcps?: Array<{ name: string; qualifiedName: string; config: any; enabledTools?: string[] }>; + is_default?: boolean; + avatar?: string; + avatar_color?: string; + created_at?: string; + updated_at?: string; +} type SaveStatus = 'idle' | 'saving' | 'saved' | 'error'; @@ -33,6 +49,8 @@ export default function AgentConfigurationPage() { agentpress_tools: {}, configured_mcps: [], is_default: false, + avatar: '', + avatar_color: '', }); const originalDataRef = useRef(null); @@ -42,13 +60,16 @@ export default function AgentConfigurationPage() { useEffect(() => { if (agent) { + const agentData = agent as any; // Safe casting for extended properties const initialData = { - name: agent.name || '', - description: agent.description || '', - system_prompt: agent.system_prompt || '', - agentpress_tools: agent.agentpress_tools || {}, - configured_mcps: agent.configured_mcps || [], - is_default: agent.is_default || false, + name: agentData.name || '', + description: agentData.description || '', + system_prompt: agentData.system_prompt || '', + agentpress_tools: agentData.agentpress_tools || {}, + configured_mcps: agentData.configured_mcps || [], + is_default: agentData.is_default || false, + avatar: agentData.avatar || '', + avatar_color: agentData.avatar_color || '', }; setFormData(initialData); originalDataRef.current = { ...initialData }; @@ -128,6 +149,29 @@ export default function AgentConfigurationPage() { } }, []); + const handleStyleChange = useCallback((emoji: string, color: string) => { + const newFormData = { + ...formData, + avatar: emoji, + avatar_color: color, + }; + setFormData(newFormData); + debouncedSave(newFormData); + }, [formData, debouncedSave]); + + // Get current style with fallback to generated defaults + const getCurrentStyle = useCallback(() => { + if (formData.avatar && formData.avatar_color) { + return { + avatar: formData.avatar, + color: formData.avatar_color, + }; + } + return getAgentAvatar(agentId); + }, [formData.avatar, formData.avatar_color, agentId]); + + const currentStyle = getCurrentStyle(); + const getSaveStatusBadge = () => { const showSaved = saveStatus === 'idle' && !hasDataChanged(formData, originalDataRef.current); switch (saveStatus) { @@ -212,9 +256,19 @@ export default function AgentConfigurationPage() {
-
- {avatar} -
+ +
+ {currentStyle.avatar} +
+
-
-
-
- -

Preview

-
- -
+
+
diff --git a/frontend/src/components/thread/chat-input/chat-input.tsx b/frontend/src/components/thread/chat-input/chat-input.tsx index bd1dd8d7..5d0914e9 100644 --- a/frontend/src/components/thread/chat-input/chat-input.tsx +++ b/frontend/src/components/thread/chat-input/chat-input.tsx @@ -43,6 +43,7 @@ export interface ChatInputProps { onAgentSelect?: (agentId: string | undefined) => void; agentName?: string; messages?: any[]; + bgColor?: string; } export interface UploadedFile { @@ -72,6 +73,7 @@ export const ChatInput = forwardRef( onAgentSelect, agentName, messages = [], + bgColor = 'bg-sidebar', }, ref, ) => { @@ -237,7 +239,7 @@ export const ChatInput = forwardRef( }} >
- + {onAgentSelect && (
= ({ @@ -272,7 +273,8 @@ export const ThreadContent: React.FC = ({ streamHookStatus = "idle", sandboxId, project, - debugMode = false + debugMode = false, + isPreviewMode = false, }) => { const messagesEndRef = useRef(null); const messagesContainerRef = useRef(null); @@ -284,6 +286,10 @@ export const ThreadContent: React.FC = ({ // React Query file preloader const { preloadFiles } = useFilePreloader(); + const containerClassName = isPreviewMode + ? "flex-1 overflow-y-auto scrollbar-thin scrollbar-track-secondary/0 scrollbar-thumb-primary/10 scrollbar-thumb-rounded-full hover:scrollbar-thumb-primary/10 px-6 py-4 pb-72" + : "flex-1 overflow-y-auto scrollbar-thin scrollbar-track-secondary/0 scrollbar-thumb-primary/10 scrollbar-thumb-rounded-full hover:scrollbar-thumb-primary/10 px-6 py-4 pb-72 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"; + // In playback mode, we use visibleMessages instead of messages const displayMessages = readOnly && visibleMessages ? visibleMessages : messages; @@ -339,7 +345,7 @@ export const ThreadContent: React.FC = ({