diff --git a/.github/assets/screenshot.png b/.github/assets/screenshot.png new file mode 100644 index 00000000..d3dfffd2 Binary files /dev/null and b/.github/assets/screenshot.png differ diff --git a/README.md b/README.md index 735a1e38..6f37a054 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Chat with your data, build pipelines, and manage analytics assetsโ€”all on your local machine.

- Pluto Duck Screenshot + Pluto Duck Screenshot

## ๐ŸŒŸ Product Vision diff --git a/backend/pluto_duck_backend/agent/core/deep/event_mapper.py b/backend/pluto_duck_backend/agent/core/deep/event_mapper.py index 4c73e505..d6761a91 100644 --- a/backend/pluto_duck_backend/agent/core/deep/event_mapper.py +++ b/backend/pluto_duck_backend/agent/core/deep/event_mapper.py @@ -43,7 +43,7 @@ def __init__( self._run_id = run_id self._conversation_id = conversation_id self._experiment_profile = experiment_profile or prompt_layout - self._tool_stack: list[str] = [] + self._tool_stack: list[dict[str, str]] = [] self._chunk_buffer: list[str] = [] self._chunk_tokens = 0 self._chunk_chars = 0 @@ -201,6 +201,19 @@ def _coerce_text(self, value: Any) -> str | None: # noqa: ANN401 return None return text + def _resolve_tool_call_id(self, serialized: dict[str, Any], kwargs: dict[str, Any]) -> str: + candidates = ( + kwargs.get("tool_call_id"), + kwargs.get("id"), + serialized.get("tool_call_id"), + serialized.get("call_id"), + ) + for candidate in candidates: + text = self._coerce_text(candidate) + if text is not None: + return text + return f"tool-{uuid4()}" + def _join_text_fragments(self, fragments: list[str]) -> str | None: normalized = [ text @@ -496,26 +509,33 @@ async def on_tool_start( **kwargs: Any, ) -> None: # noqa: ANN401 tool_name = serialized.get("name") or serialized.get("id") or "tool" - self._tool_stack.append(tool_name) + tool_call_id = self._resolve_tool_call_id(serialized, kwargs) + self._tool_stack.append({"name": tool_name, "tool_call_id": tool_call_id}) await self._emit( AgentEvent( type=EventType.TOOL, subtype=EventSubType.START, - content={"tool": tool_name, "input": input_str}, - metadata={"run_id": self._run_id}, + content={"tool": tool_name, "input": input_str, "tool_call_id": tool_call_id}, + metadata={"run_id": self._run_id, "tool_call_id": tool_call_id}, timestamp=self._ts(), ) ) async def on_tool_end(self, output: Any, **kwargs: Any) -> None: # noqa: ANN401 - # Pop the tool name from the stack to match with start event - tool_name = self._tool_stack.pop() if self._tool_stack else "tool" + # Keep the same tool_call_id between start/end so frontend correlation is stable. + if self._tool_stack: + tool_context = self._tool_stack.pop() + tool_name = tool_context["name"] + tool_call_id = tool_context["tool_call_id"] + else: + tool_name = "tool" + tool_call_id = self._coerce_text(kwargs.get("tool_call_id")) or f"tool-{uuid4()}" await self._emit( AgentEvent( type=EventType.TOOL, subtype=EventSubType.END, - content={"tool": tool_name, "output": self._json_safe(output)}, - metadata={"run_id": self._run_id}, + content={"tool": tool_name, "output": self._json_safe(output), "tool_call_id": tool_call_id}, + metadata={"run_id": self._run_id, "tool_call_id": tool_call_id}, timestamp=self._ts(), ) ) diff --git a/backend/pluto_duck_backend/app/services/chat/repository.py b/backend/pluto_duck_backend/app/services/chat/repository.py index d3e3df70..515eb062 100644 --- a/backend/pluto_duck_backend/app/services/chat/repository.py +++ b/backend/pluto_duck_backend/app/services/chat/repository.py @@ -1059,11 +1059,22 @@ def get_conversation_events(self, conversation_id: str, limit: int = 200) -> Lis conversation_run_id = self._as_non_empty_str(run_row[0] if run_row else None) rows = con.execute( """ + WITH latest_events AS ( + SELECT id, type, subtype, payload, metadata, timestamp + FROM agent_events + WHERE conversation_id = ? + ORDER BY + COALESCE(TRY_CAST(json_extract_string(metadata, '$.display_order') AS BIGINT), 0) DESC, + timestamp DESC, + id DESC + LIMIT ? + ) SELECT id, type, subtype, payload, metadata, timestamp - FROM agent_events - WHERE conversation_id = ? - ORDER BY timestamp ASC, id ASC - LIMIT ? + FROM latest_events + ORDER BY + COALESCE(TRY_CAST(json_extract_string(metadata, '$.display_order') AS BIGINT), 0) ASC, + timestamp ASC, + id ASC """, [conversation_id, limit], ).fetchall() diff --git a/backend/tests/agent/test_orchestrator_display_order.py b/backend/tests/agent/test_orchestrator_display_order.py index 100f9a52..ba78c7fe 100644 --- a/backend/tests/agent/test_orchestrator_display_order.py +++ b/backend/tests/agent/test_orchestrator_display_order.py @@ -181,6 +181,40 @@ async def test_hitl_events_get_unique_display_order() -> None: assert len(display_orders) >= 4 +@pytest.mark.asyncio +async def test_tool_start_end_emit_stable_tool_call_id() -> None: + """Tool start/end events must share tool_call_id for frontend correlation.""" + collected: List[Dict[str, Any]] = [] + callback_ref: list[PlutoDuckEventCallbackHandler | None] = [None] + + emit = _build_emit(collected=collected, callback_ref=callback_ref) + + handler = PlutoDuckEventCallbackHandler( + sink=EventSink(emit=emit), + run_id="run-tool-id", + display_order_start=1, + ) + callback_ref[0] = handler + + await handler.on_tool_start({"name": "save_analysis"}, '{"name":"a"}') + await handler.on_tool_end({"status": "success"}) + + tool_events = [event for event in collected if event["type"] == "tool"] + assert len(tool_events) == 2 + + start_event = tool_events[0] + end_event = tool_events[1] + + start_tool_call_id = start_event["metadata"].get("tool_call_id") + end_tool_call_id = end_event["metadata"].get("tool_call_id") + + assert isinstance(start_tool_call_id, str) + assert start_tool_call_id + assert start_event["content"]["tool_call_id"] == start_tool_call_id + assert end_tool_call_id == start_tool_call_id + assert end_event["content"]["tool_call_id"] == start_tool_call_id + + @pytest.mark.asyncio async def test_finally_events_get_display_order() -> None: """MESSAGE/FINAL and RUN/END events emitted via emit() get display_order assigned.""" diff --git a/backend/tests/services/test_chat_repository_event_contract.py b/backend/tests/services/test_chat_repository_event_contract.py index 7e31a599..78bc995d 100644 --- a/backend/tests/services/test_chat_repository_event_contract.py +++ b/backend/tests/services/test_chat_repository_event_contract.py @@ -99,3 +99,57 @@ def test_append_and_get_messages_preserve_display_order_contract(tmp_path) -> No assert messages[0]["content"]["display_order"] == 41 assert messages[1]["display_order"] == 42 assert messages[1]["content"]["display_order"] == 42 + + +def test_get_conversation_events_returns_latest_window(tmp_path) -> None: + warehouse = tmp_path / "warehouse.duckdb" + repo = ChatRepository(warehouse) + + conversation_id = str(uuid4()) + run_id = str(uuid4()) + repo.create_conversation(conversation_id, "hello", {}) + repo.set_active_run(conversation_id, run_id) + + for idx in range(220): + repo.log_event( + conversation_id, + { + "type": "reasoning", + "subtype": "chunk", + "content": {"phase": "llm_reasoning", "text": f"token-{idx}"}, + "metadata": {"run_id": run_id}, + "timestamp": "2025-01-01T00:00:00+00:00", + }, + ) + + repo.log_event( + conversation_id, + { + "type": "tool", + "subtype": "start", + "content": {"tool": "save_analysis", "input": "{}"}, + "metadata": {"run_id": run_id}, + "timestamp": "2025-01-01T00:00:00+00:00", + }, + ) + repo.log_event( + conversation_id, + { + "type": "tool", + "subtype": "end", + "content": { + "tool": "save_analysis", + "output": {"status": "success", "analysis_id": "asset-1"}, + }, + "metadata": {"run_id": run_id}, + "timestamp": "2025-01-01T00:00:00+00:00", + }, + ) + + events = repo.get_conversation_events(conversation_id, limit=200) + + assert len(events) == 200 + assert events[0]["sequence"] == 23 + assert events[-1]["sequence"] == 222 + assert any(event["type"] == "tool" and event["subtype"] == "start" for event in events) + assert any(event["type"] == "tool" and event["subtype"] == "end" for event in events) diff --git a/frontend/pluto_duck_frontend/app/page.tsx b/frontend/pluto_duck_frontend/app/page.tsx index 2a667d2f..0b37407b 100644 --- a/frontend/pluto_duck_frontend/app/page.tsx +++ b/frontend/pluto_duck_frontend/app/page.tsx @@ -24,6 +24,7 @@ import { DatasetList, ProfileCard } from '../components/sidebar'; import { DatasetDetailView } from '../components/datasets'; import { AssetListView } from '../components/assets'; import { ProjectSelector, CreateProjectModal } from '../components/projects'; +import { DisplayConfigModal } from '../components/editor/components/DisplayConfigModal'; import { useBoards } from '../hooks/useBoards'; import { useProjects } from '../hooks/useProjects'; import { useProjectState } from '../hooks/useProjectState'; @@ -48,6 +49,9 @@ import { useBackendStatus } from '../hooks/useBackendStatus'; type MainView = 'boards' | 'assets' | 'datasets'; type Dataset = FileAsset | CachedTable; type Locale = 'en' | 'ko'; +type PendingBoardAction = + | { type: 'markdown'; content: string } + | { type: 'asset-embed'; analysisId: string }; const SIDEBAR_COLLAPSED_KEY = 'pluto-duck-sidebar-collapsed'; const SELECTED_DATASET_ID_KEY = 'pluto_selected_dataset_id'; @@ -97,7 +101,8 @@ function WorkspacePageBody({ const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [chatPanelCollapsed, setChatPanelCollapsed] = useState(false); const [boardSelectorOpen, setBoardSelectorOpen] = useState(false); - const [pendingSendContent, setPendingSendContent] = useState(null); + const [pendingBoardAction, setPendingBoardAction] = useState(null); + const [chatEmbedFlow, setChatEmbedFlow] = useState<{ analysisId: string } | null>(null); const [sidebarTab, setSidebarTab] = useState<'boards' | 'datasets'>('boards'); const [sidebarDatasets, setSidebarDatasets] = useState<(FileAsset | CachedTable)[]>([]); const [selectedDataset, setSelectedDataset] = useState(null); @@ -608,11 +613,36 @@ function WorkspacePageBody({ boardsViewRef.current?.insertMarkdown(content); } else { // No board selected - show selector modal - setPendingSendContent(content); + setPendingBoardAction({ type: 'markdown', content }); setBoardSelectorOpen(true); } }, [activeBoard]); + const handleRequestAssetEmbed = useCallback((analysisId: string) => { + if (activeBoard) { + setChatEmbedFlow({ analysisId }); + return; + } + setPendingBoardAction({ type: 'asset-embed', analysisId }); + setBoardSelectorOpen(true); + }, [activeBoard]); + + const handleChatEmbedConfigSave = useCallback((config: AssetEmbedConfig) => { + if (!chatEmbedFlow) { + return; + } + if (!defaultProjectId) { + console.warn('No project selected.'); + return; + } + boardsViewRef.current?.insertAssetEmbed(chatEmbedFlow.analysisId, defaultProjectId, config); + setChatEmbedFlow(null); + }, [chatEmbedFlow, defaultProjectId]); + + const handleChatEmbedCancel = useCallback(() => { + setChatEmbedFlow(null); + }, []); + // Handle embedding asset from chat to board const handleEmbedAssetToBoard = useCallback((analysisId: string, config: AssetEmbedConfig) => { if (!activeBoard) { @@ -631,17 +661,26 @@ function WorkspacePageBody({ // Handle board selection from modal const handleBoardSelect = useCallback((boardId: string) => { const board = boards.find(b => b.id === boardId); - if (board) { - selectBoard(board); - // Wait for board to be selected and editor to mount, then insert content - if (pendingSendContent) { - setTimeout(() => { - boardsViewRef.current?.insertMarkdown(pendingSendContent); - setPendingSendContent(null); - }, 100); - } + if (!board) { + return; + } + selectBoard(board); + + if (!pendingBoardAction) { + return; } - }, [boards, selectBoard, pendingSendContent]); + + const action = pendingBoardAction; + setPendingBoardAction(null); + // Wait for board selection + editor mount before applying the pending action. + setTimeout(() => { + if (action.type === 'markdown') { + boardsViewRef.current?.insertMarkdown(action.content); + return; + } + setChatEmbedFlow({ analysisId: action.analysisId }); + }, 100); + }, [boards, selectBoard, pendingBoardAction]); useEffect(() => { const handleMouseMove = (e: MouseEvent) => { @@ -981,6 +1020,7 @@ function WorkspacePageBody({ savedTabs={currentProject?.settings?.ui_state?.chat?.open_tabs} savedActiveTabId={currentProject?.settings?.ui_state?.chat?.active_tab_id} onSendToBoard={handleSendToBoard} + onRequestAssetEmbed={handleRequestAssetEmbed} onEmbedAssetToBoard={handleEmbedAssetToBoard} /> @@ -1088,6 +1128,15 @@ function WorkspacePageBody({ boards={boards} onSelect={handleBoardSelect} /> + {chatEmbedFlow && defaultProjectId && ( + + )} ); } diff --git a/frontend/pluto_duck_frontend/components/chat/AssetEmbedTestButtons.tsx b/frontend/pluto_duck_frontend/components/chat/AssetEmbedTestButtons.tsx deleted file mode 100644 index a9bc6230..00000000 --- a/frontend/pluto_duck_frontend/components/chat/AssetEmbedTestButtons.tsx +++ /dev/null @@ -1,62 +0,0 @@ -'use client'; - -import { Button } from '@/components/ui/button'; -import type { AssetEmbedConfig } from '../editor/nodes/AssetEmbedNode'; - -interface AssetEmbedTestButtonsProps { - onEmbed: (analysisId: string, config: AssetEmbedConfig) => void; -} - -// Hardcoded test analysis ID -const TEST_ANALYSIS_ID = 'meta_ad_daily_timeseries_with_ma_anomalies'; - -// Test configurations -const TEST_CONFIGS: Array<{ label: string; config: AssetEmbedConfig }> = [ - { - label: 'Table', - config: { - displayType: 'table', - tableConfig: { rowsPerPage: 5 }, - }, - }, - { - label: 'Bar', - config: { - displayType: 'chart', - chartConfig: { - type: 'bar', - xColumn: 'date', - yColumn: 'spend', - }, - }, - }, - { - label: 'Line', - config: { - displayType: 'chart', - chartConfig: { - type: 'line', - xColumn: 'date', - yColumn: 'spend', - }, - }, - }, -]; - -export function AssetEmbedTestButtons({ onEmbed }: AssetEmbedTestButtonsProps) { - return ( -
- {TEST_CONFIGS.map(({ label, config }) => ( - - ))} -
- ); -} diff --git a/frontend/pluto_duck_frontend/components/chat/ChatPanel.tsx b/frontend/pluto_duck_frontend/components/chat/ChatPanel.tsx index 1cf25514..f3ca2d90 100644 --- a/frontend/pluto_duck_frontend/components/chat/ChatPanel.tsx +++ b/frontend/pluto_duck_frontend/components/chat/ChatPanel.tsx @@ -23,7 +23,6 @@ import { LoadingDots } from '../ai-elements/loading-dots'; import { MentionMenu } from './MentionMenu'; import { ChatOnboarding } from './ChatOnboarding'; import { RenderItem, type FeedbackType } from './renderers'; -import { AssetEmbedTestButtons } from './AssetEmbedTestButtons'; import { type MentionItem } from '../../hooks/useAssetMentions'; import type { ChatSessionSummary } from '../../lib/chatApi'; import type { ChatRenderItem, AssistantMessageItem, ReasoningItem } from '../../types/chatRenderItem'; @@ -64,6 +63,7 @@ interface ConversationMessagesProps { onEditUserMessage?: (messageId: string, content: string) => void; onFeedback?: (messageId: string, type: 'like' | 'dislike') => void; onSendToBoard?: (messageId: string, content: string) => void; + onRequestAssetEmbed?: (analysisId: string) => void; onApprovalDecision?: (approvalEventId: string, runId: string | null, decision: 'approved' | 'rejected') => void; } @@ -88,6 +88,7 @@ const ConversationMessages = memo(function ConversationMessages({ onEditUserMessage, onFeedback, onSendToBoard, + onRequestAssetEmbed, onApprovalDecision, }: ConversationMessagesProps) { const previousRenderItemsRef = useRef(renderItems); @@ -257,6 +258,7 @@ const ConversationMessages = memo(function ConversationMessages({ onEditUserMessage={onEditUserMessage} onFeedback={onFeedback} onSendToBoard={onSendToBoard} + onRequestAssetEmbed={onRequestAssetEmbed} onApprovalDecision={onApprovalDecision} /> @@ -294,6 +296,7 @@ interface ChatPanelProps { onEditUserMessage?: (messageId: string, content: string) => void; onFeedback?: (messageId: string, type: 'like' | 'dislike') => void; onSendToBoard?: (messageId: string, content: string) => void; + onRequestAssetEmbed?: (analysisId: string) => void; onApprovalDecision?: (approvalEventId: string, runId: string | null, decision: 'approved' | 'rejected') => void; onEmbedAssetToBoard?: (analysisId: string, config: AssetEmbedConfig) => void; feedbackMap?: Map; @@ -314,6 +317,7 @@ export function ChatPanel({ onEditUserMessage, onFeedback, onSendToBoard, + onRequestAssetEmbed, onApprovalDecision, onEmbedAssetToBoard, feedbackMap, @@ -417,6 +421,7 @@ export function ChatPanel({ onEditUserMessage={onEditUserMessage} onFeedback={onFeedback} onSendToBoard={onSendToBoard} + onRequestAssetEmbed={onRequestAssetEmbed} onApprovalDecision={onApprovalDecision} /> @@ -429,12 +434,6 @@ export function ChatPanel({ {/* Input area */}
- {/* Test buttons for Asset Embed (development only) */} - {process.env.NODE_ENV === 'development' && onEmbedAssetToBoard && ( -
- -
- )} ; savedActiveTabId?: string; onSendToBoard?: (messageId: string, content: string) => void; + onRequestAssetEmbed?: (analysisId: string) => void; onEmbedAssetToBoard?: (analysisId: string, config: AssetEmbedConfig) => void; } @@ -32,6 +33,7 @@ export function MultiTabChatPanel({ savedTabs, savedActiveTabId, onSendToBoard, + onRequestAssetEmbed, onEmbedAssetToBoard, }: MultiTabChatPanelProps) { const { @@ -142,6 +144,7 @@ export function MultiTabChatPanel({ onSubmit={handleSubmit} projectId={projectId || undefined} onSendToBoard={onSendToBoard} + onRequestAssetEmbed={onRequestAssetEmbed} onApprovalDecision={handleApprovalDecision} onEmbedAssetToBoard={onEmbedAssetToBoard} /> @@ -165,6 +168,7 @@ export function MultiTabChatPanel({ feedbackMap={feedbackMap} projectId={projectId || undefined} onSendToBoard={onSendToBoard} + onRequestAssetEmbed={onRequestAssetEmbed} onApprovalDecision={handleApprovalDecision} onEmbedAssetToBoard={onEmbedAssetToBoard} /> diff --git a/frontend/pluto_duck_frontend/components/chat/renderers/RenderItem.tsx b/frontend/pluto_duck_frontend/components/chat/renderers/RenderItem.tsx index c2a48dfa..5ab55d9d 100644 --- a/frontend/pluto_duck_frontend/components/chat/renderers/RenderItem.tsx +++ b/frontend/pluto_duck_frontend/components/chat/renderers/RenderItem.tsx @@ -19,6 +19,7 @@ export interface RenderItemProps { onRegenerate?: (messageId: string) => void; onFeedback?: (messageId: string, type: 'like' | 'dislike') => void; onSendToBoard?: (messageId: string, content: string) => void; + onRequestAssetEmbed?: (analysisId: string) => void; onApprovalDecision?: (approvalEventId: string, runId: string | null, decision: 'approved' | 'rejected') => void; } @@ -32,6 +33,7 @@ export const RenderItem = memo(function RenderItem({ onRegenerate, onFeedback, onSendToBoard, + onRequestAssetEmbed, onApprovalDecision, }: RenderItemProps) { switch (item.type) { @@ -53,10 +55,20 @@ export const RenderItem = memo(function RenderItem({ ); case 'tool': - return ; + return ( + + ); case 'tool-group': - return ; + return ( + + ); case 'assistant-message': return ( diff --git a/frontend/pluto_duck_frontend/components/chat/renderers/ToolGroupRenderer.tsx b/frontend/pluto_duck_frontend/components/chat/renderers/ToolGroupRenderer.tsx index 12cf8ab6..fb1bffa2 100644 --- a/frontend/pluto_duck_frontend/components/chat/renderers/ToolGroupRenderer.tsx +++ b/frontend/pluto_duck_frontend/components/chat/renderers/ToolGroupRenderer.tsx @@ -19,6 +19,7 @@ import { formatToolName } from './ToolRenderer'; import { buildToolDetailEntriesForChildren } from './toolDetailContent'; import { parseTodosFromToolPayload } from './toolTodoParser'; import { getToolTodoTextClass } from './toolTodoViewModel'; +import { extractEmbeddableAssetsFromToolGroup } from './toolAssetDetector'; function mapGroupStateToPhase(state: ToolGroupItem['state']): 'running' | 'complete' | 'error' { if (state === 'pending') { @@ -91,13 +92,35 @@ function renderTodoChildren(children: ToolItem[]) { export interface ToolGroupRendererProps { item: ToolGroupItem; + onRequestAssetEmbed?: (analysisId: string) => void; } export const ToolGroupRenderer = memo(function ToolGroupRenderer({ item, + onRequestAssetEmbed, }: ToolGroupRendererProps) { const isTodo = item.toolName === 'write_todos'; const displayName = isTodo ? 'Update Todos' : formatToolName(item.toolName); + // Check completed children individually โ€” start/end events may not be + // correlated (missing tool_call_id), so the group state can remain + // 'pending' even when some children already carry a completed output. + const embeddableAssets = + item.toolName === 'save_analysis' + ? extractEmbeddableAssetsFromToolGroup( + item.children.filter(c => c.state === 'completed') + ) + : []; + + // DEBUG: Remove after verifying Send to Board button + if (item.toolName === 'save_analysis') { + console.log('[SendToBoard GROUP DEBUG]', { + groupState: item.state, + childrenCount: item.children.length, + childrenStates: item.children.map(c => ({ state: c.state, hasOutput: c.output !== undefined })), + embeddableAssets, + hasCallback: !!onRequestAssetEmbed, + }); + } return ( + {embeddableAssets.length > 0 && ( +
+ {embeddableAssets.map(asset => ( + + ))} +
+ )}
); }); diff --git a/frontend/pluto_duck_frontend/components/chat/renderers/ToolRenderer.tsx b/frontend/pluto_duck_frontend/components/chat/renderers/ToolRenderer.tsx index 08adacdd..a963e83c 100644 --- a/frontend/pluto_duck_frontend/components/chat/renderers/ToolRenderer.tsx +++ b/frontend/pluto_duck_frontend/components/chat/renderers/ToolRenderer.tsx @@ -1,6 +1,7 @@ 'use client'; import { memo } from 'react'; +import { AnimatePresence, motion } from 'motion/react'; import { ChevronDownIcon } from 'lucide-react'; import { Tool, @@ -30,6 +31,7 @@ import { shouldDefaultOpenToolTodo, shouldShowToolTodoChevron, } from './toolTodoViewModel'; +import { extractEmbeddableAsset } from './toolAssetDetector'; /** * Convert snake_case or camelCase to Title Case @@ -160,11 +162,13 @@ function getToolUIState(state: ToolItem['state']): 'input-streaming' | 'output-a export interface ToolRendererProps { item: ToolItem; variant?: 'default' | 'inline'; + onRequestAssetEmbed?: (analysisId: string) => void; } export const ToolRenderer = memo(function ToolRenderer({ item, variant = 'default', + onRequestAssetEmbed, }: ToolRendererProps) { // 1) inline write_todos if (variant === 'inline' && item.toolName === 'write_todos') { @@ -261,6 +265,21 @@ export const ToolRenderer = memo(function ToolRenderer({ const keyParam = extractKeyParam(item.input); const preview = item.state !== 'pending' ? getFirstMeaningfulItem(item.output) : null; const detailRows = buildToolDetailRowsForChild(item); + const embeddableAsset = + item.state === 'completed' + ? extractEmbeddableAsset(item.toolName, item.output) + : null; + + // DEBUG: Remove after verifying Send to Board button + if (item.toolName === 'save_analysis') { + console.log('[SendToBoard DEBUG]', { + toolName: item.toolName, + state: item.state, + output: item.output, + embeddableAsset, + hasCallback: !!onRequestAssetEmbed, + }); + } return ( @@ -289,6 +308,26 @@ export const ToolRenderer = memo(function ToolRenderer({
)} + + {embeddableAsset && ( + + + + )} + ); }); diff --git a/frontend/pluto_duck_frontend/components/chat/renderers/__tests__/toolAssetDetector.test.ts b/frontend/pluto_duck_frontend/components/chat/renderers/__tests__/toolAssetDetector.test.ts new file mode 100644 index 00000000..b2e810d9 --- /dev/null +++ b/frontend/pluto_duck_frontend/components/chat/renderers/__tests__/toolAssetDetector.test.ts @@ -0,0 +1,132 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + extractEmbeddableAsset, + extractEmbeddableAssetsFromToolGroup, +} from '../toolAssetDetector.ts'; +import type { ToolItem } from '../../../../types/chatRenderItem.ts'; + +function buildToolChild( + overrides: Partial = {} +): ToolItem { + return { + id: 'tool-child', + runId: null, + seq: 1, + timestamp: '2026-01-01T00:00:00.000Z', + isStreaming: false, + type: 'tool', + toolName: 'save_analysis', + state: 'completed', + ...overrides, + }; +} + +test('extractEmbeddableAsset returns asset for successful save_analysis output', () => { + const asset = extractEmbeddableAsset('save_analysis', { + status: 'success', + analysis_id: 'analysis-1', + name: 'Revenue Insight', + }); + + assert.deepEqual(asset, { + analysisId: 'analysis-1', + name: 'Revenue Insight', + label: 'Revenue Insight', + }); +}); + +test('extractEmbeddableAsset handles ToolMessage content wrapper', () => { + const asset = extractEmbeddableAsset('save_analysis', { + content: { + status: 'success', + analysis_id: 'analysis-2', + }, + }); + + assert.deepEqual(asset, { + analysisId: 'analysis-2', + name: null, + label: 'analysis-2', + }); +}); + +test('extractEmbeddableAsset returns null for non-target tools or invalid states', () => { + assert.equal( + extractEmbeddableAsset('run_sql', { + status: 'success', + analysis_id: 'analysis-3', + }), + null, + ); + assert.equal( + extractEmbeddableAsset('run_analysis', { + status: 'success', + analysis_id: 'analysis-3', + }), + null, + ); + assert.equal( + extractEmbeddableAsset('save_analysis', { + status: 'error', + analysis_id: 'analysis-3', + }), + null, + ); + assert.equal( + extractEmbeddableAsset('save_analysis', { + status: 'success', + }), + null, + ); +}); + +test('extractEmbeddableAssetsFromToolGroup deduplicates analysis IDs', () => { + const assets = extractEmbeddableAssetsFromToolGroup([ + buildToolChild({ + id: 'tool-1', + output: { + status: 'success', + analysis_id: 'dup-analysis', + name: 'A', + }, + }), + buildToolChild({ + id: 'tool-2', + output: { + status: 'success', + analysis_id: 'dup-analysis', + name: 'B', + }, + }), + buildToolChild({ + id: 'tool-3', + toolName: 'run_sql', + output: { + status: 'success', + analysis_id: 'ignored', + }, + }), + buildToolChild({ + id: 'tool-4', + output: { + status: 'success', + analysis_id: 'analysis-4', + }, + }), + ]); + + assert.deepEqual(assets, [ + { + analysisId: 'dup-analysis', + name: 'A', + label: 'A', + }, + { + analysisId: 'analysis-4', + name: null, + label: 'analysis-4', + }, + ]); +}); diff --git a/frontend/pluto_duck_frontend/components/chat/renderers/toolAssetDetector.ts b/frontend/pluto_duck_frontend/components/chat/renderers/toolAssetDetector.ts new file mode 100644 index 00000000..c838a118 --- /dev/null +++ b/frontend/pluto_duck_frontend/components/chat/renderers/toolAssetDetector.ts @@ -0,0 +1,97 @@ +import type { ToolItem } from '../../../types/chatRenderItem'; +import { extractToolMessageContent } from './toolDetailContent.ts'; + +export interface EmbeddableAsset { + analysisId: string; + name: string | null; + label: string; +} + +type ToolOutputRecord = { + status?: unknown; + analysis_id?: unknown; + name?: unknown; +}; + +function asOutputRecord(output: unknown): ToolOutputRecord | null { + const content = extractToolMessageContent(output); + if (content == null) { + return null; + } + + if (typeof content === 'string') { + const trimmed = content.trim(); + if (!trimmed) { + return null; + } + try { + const parsed = JSON.parse(trimmed) as unknown; + if (parsed && typeof parsed === 'object') { + return parsed as ToolOutputRecord; + } + return null; + } catch { + return null; + } + } + + if (typeof content === 'object') { + return content as ToolOutputRecord; + } + + return null; +} + +export function extractEmbeddableAsset( + toolName: string, + output: unknown +): EmbeddableAsset | null { + if (toolName !== 'save_analysis') { + return null; + } + + const record = asOutputRecord(output); + if (!record) { + return null; + } + + if (record.status !== 'success') { + return null; + } + + if (typeof record.analysis_id !== 'string' || record.analysis_id.trim().length === 0) { + return null; + } + + const analysisId = record.analysis_id.trim(); + const name = typeof record.name === 'string' && record.name.trim().length > 0 + ? record.name.trim() + : null; + + return { + analysisId, + name, + label: name ?? analysisId, + }; +} + +export function extractEmbeddableAssetsFromToolGroup( + children: ToolItem[] +): EmbeddableAsset[] { + const seen = new Set(); + const assets: EmbeddableAsset[] = []; + + children.forEach(child => { + const asset = extractEmbeddableAsset(child.toolName, child.output); + if (!asset) { + return; + } + if (seen.has(asset.analysisId)) { + return; + } + seen.add(asset.analysisId); + assets.push(asset); + }); + + return assets; +} diff --git a/frontend/pluto_duck_frontend/package.json b/frontend/pluto_duck_frontend/package.json index 4ad48977..f0c592fe 100644 --- a/frontend/pluto_duck_frontend/package.json +++ b/frontend/pluto_duck_frontend/package.json @@ -14,6 +14,9 @@ "dependencies": { "@dnd-kit/core": "^6.1.0", "@dnd-kit/utilities": "^3.2.2", + "@json-render/core": "^0.9.1", + "@json-render/react": "^0.9.1", + "@json-render/shadcn": "^0.9.1", "@lexical/code": "^0.18.0", "@lexical/link": "^0.18.0", "@lexical/list": "^0.18.0", @@ -56,7 +59,8 @@ "streamdown": "^1.4.0", "tailwind-merge": "^3.3.1", "tokenlens": "^1.3.1", - "use-stick-to-bottom": "^1.1.1" + "use-stick-to-bottom": "^1.1.1", + "zod": "^4.3.6" }, "devDependencies": { "@tailwindcss/typography": "^0.5.19", diff --git a/frontend/pluto_duck_frontend/pnpm-lock.yaml b/frontend/pluto_duck_frontend/pnpm-lock.yaml index 65c5ef3e..331b3113 100644 --- a/frontend/pluto_duck_frontend/pnpm-lock.yaml +++ b/frontend/pluto_duck_frontend/pnpm-lock.yaml @@ -14,6 +14,15 @@ importers: '@dnd-kit/utilities': specifier: ^3.2.2 version: 3.2.2(react@18.3.1) + '@json-render/core': + specifier: ^0.9.1 + version: 0.9.1(zod@4.3.6) + '@json-render/react': + specifier: ^0.9.1 + version: 0.9.1(react@18.3.1)(zod@4.3.6) + '@json-render/shadcn': + specifier: ^0.9.1 + version: 0.9.1(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.18)(zod@4.3.6) '@lexical/code': specifier: ^0.18.0 version: 0.18.0 @@ -143,6 +152,9 @@ importers: use-stick-to-bottom: specifier: ^1.1.1 version: 1.1.1(react@18.3.1) + zod: + specifier: ^4.3.6 + version: 4.3.6 devDependencies: '@tailwindcss/typography': specifier: ^0.5.19 @@ -327,6 +339,24 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@json-render/core@0.9.1': + resolution: {integrity: sha512-QVvkzrE2grGjo16P6QgC45NvMyyqMAbAW9EIt4F2PNwQn8hAVxyXDp6GtxbSoT6FzEpS4ytuzmlZ9gLRlsHAJA==} + peerDependencies: + zod: ^4.0.0 + + '@json-render/react@0.9.1': + resolution: {integrity: sha512-r4WXuHhywiJjp7fTEpHwwmlyxcqmaoYdVJFkBJWf31BmKebEYd1+t4HiR51VlLpXOAYkcui+nZ2Mr4sJlyVmCA==} + peerDependencies: + react: ^19.2.3 + + '@json-render/shadcn@0.9.1': + resolution: {integrity: sha512-s3EBibz+zashft/eWDFwBa8TVJe31uyt0/xBxRZ/ROmQ4XOSiD1uG4EOg83DIIJRbgKQrfFsbgO2mZre7W0ohQ==} + peerDependencies: + react: ^19.0.0 + react-dom: ^19.0.0 + tailwindcss: ^4.0.0 + zod: ^4.0.0 + '@lexical/clipboard@0.18.0': resolution: {integrity: sha512-ybc+hx14wj0n2ZjdOkLcZ02MRB3UprXjpLDXlByFIuVcZpUxVcp3NzA0UBPOKXYKvdt0bmgjnAsFWM5OSbwS0w==} @@ -600,6 +630,45 @@ packages: '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + '@radix-ui/react-accessible-icon@1.1.7': + resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-accordion@1.2.12': + resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-arrow@1.1.7': resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: @@ -613,6 +682,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-aspect-ratio@1.1.7': + resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-avatar@1.1.10': resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} peerDependencies: @@ -626,6 +708,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collapsible@1.1.12': resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} peerDependencies: @@ -661,6 +756,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-context@1.1.2': resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} peerDependencies: @@ -740,6 +848,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-form@0.1.8': + resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-hover-card@1.1.15': resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} peerDependencies: @@ -762,6 +883,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-menu@2.1.16': resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} peerDependencies: @@ -775,6 +909,71 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-menubar@1.1.16': + resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-navigation-menu@1.2.14': + resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-one-time-password-field@0.1.8': + resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-password-toggle-field@0.1.3': + resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.8': resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: @@ -840,6 +1039,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-radio-group@1.3.8': + resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-roving-focus@1.1.11': resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} peerDependencies: @@ -879,6 +1091,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.3.6': + resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.2.3': resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: @@ -888,6 +1126,84 @@ packages: '@types/react': optional: true + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toast@1.2.15': + resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle-group@1.1.11': + resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toolbar@1.1.11': + resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-tooltip@1.2.8': resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} peerDependencies: @@ -2740,6 +3056,11 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lucide-react@0.564.0: + resolution: {integrity: sha512-JJ8GVTQqFwuliifD48U6+h7DXEHdkhJ/E87kksGByII3qHxtPciVb8T8woQONHBQgHVOl7rSMrrip3SeVNy7Fg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -3230,6 +3551,19 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + radix-ui@1.4.3: + resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -3575,6 +3909,9 @@ packages: tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + tailwind-merge@3.5.0: + resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} peerDependencies: @@ -3754,6 +4091,12 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true + vaul@1.1.2: + resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -3845,6 +4188,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zustand@4.5.7: resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} engines: {node: '>=12.7.0'} @@ -4050,6 +4396,36 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@json-render/core@0.9.1(zod@4.3.6)': + dependencies: + zod: 4.3.6 + + '@json-render/react@0.9.1(react@18.3.1)(zod@4.3.6)': + dependencies: + '@json-render/core': 0.9.1(zod@4.3.6) + react: 18.3.1 + transitivePeerDependencies: + - zod + + '@json-render/shadcn@0.9.1(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.18)(zod@4.3.6)': + dependencies: + '@json-render/core': 0.9.1(zod@4.3.6) + '@json-render/react': 0.9.1(react@18.3.1)(zod@4.3.6) + class-variance-authority: 0.7.1 + clsx: 2.1.1 + embla-carousel-react: 8.6.0(react@18.3.1) + lucide-react: 0.564.0(react@18.3.1) + radix-ui: 1.4.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tailwind-merge: 3.5.0 + tailwindcss: 3.4.18 + vaul: 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + zod: 4.3.6 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + '@lexical/clipboard@0.18.0': dependencies: '@lexical/html': 0.18.0 @@ -4372,6 +4748,46 @@ snapshots: '@radix-ui/primitive@1.1.3': {} + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-accordion@1.2.12(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4381,6 +4797,15 @@ snapshots: '@types/react': 18.3.26 '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-avatar@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) @@ -4394,6 +4819,22 @@ snapshots: '@types/react': 18.3.26 '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -4415,39 +4856,177 @@ snapshots: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.26)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.26 + + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-context@1.1.2(@types/react@18.3.26)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.26 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.1(@types/react@18.3.26)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-direction@1.1.1(@types/react@18.3.26)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.26 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.26)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.26 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-form@0.1.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-label': 2.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.26 '@types/react-dom': 18.3.7(@types/react@18.3.26) - '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.26)(react@18.3.1)': + '@radix-ui/react-id@1.1.1(@types/react@18.3.26)(react@18.3.1)': dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@18.3.1) react: 18.3.1 optionalDependencies: '@types/react': 18.3.26 - '@radix-ui/react-context@1.1.2(@types/react@18.3.26)(react@18.3.1)': + '@radix-ui/react-label@2.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) - '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@18.3.1) aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -4456,88 +5035,87 @@ snapshots: '@types/react': 18.3.26 '@types/react-dom': 18.3.7(@types/react@18.3.26) - '@radix-ui/react-direction@1.1.1(@types/react@18.3.26)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.26 - - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-menubar@1.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.26 '@types/react-dom': 18.3.7(@types/react@18.3.26) - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@18.3.1) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.26 '@types/react-dom': 18.3.7(@types/react@18.3.26) - '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.26)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.26 - - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.26 '@types/react-dom': 18.3.7(@types/react@18.3.26) - '@radix-ui/react-hover-card@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.26)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.26 '@types/react-dom': 18.3.7(@types/react@18.3.26) - '@radix-ui/react-id@1.1.1(@types/react@18.3.26)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.26 - - '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.26)(react@18.3.1) '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4546,9 +5124,8 @@ snapshots: '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -4614,6 +5191,24 @@ snapshots: '@types/react': 18.3.26 '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -4677,6 +5272,34 @@ snapshots: '@types/react': 18.3.26 '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-separator@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-slider@1.3.6(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-slot@1.2.3(@types/react@18.3.26)(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) @@ -4684,6 +5307,98 @@ snapshots: optionalDependencies: '@types/react': 18.3.26 + '@radix-ui/react-switch@1.2.6(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-toast@1.2.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-toggle@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -6820,6 +7535,10 @@ snapshots: dependencies: react: 18.3.1 + lucide-react@0.564.0(react@18.3.1): + dependencies: + react: 18.3.1 + markdown-table@3.0.4: {} marked@16.4.1: {} @@ -7547,6 +8266,69 @@ snapshots: queue-microtask@1.2.3: {} + radix-ui@1.4.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-form': 0.1.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-label': 2.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-menubar': 1.1.16(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.26)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -8024,6 +8806,8 @@ snapshots: tailwind-merge@3.3.1: {} + tailwind-merge@3.5.0: {} + tailwindcss-animate@1.0.7(tailwindcss@3.4.18): dependencies: tailwindcss: 3.4.18 @@ -8271,6 +9055,15 @@ snapshots: uuid@11.1.0: {} + vaul@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -8391,6 +9184,8 @@ snapshots: yocto-queue@0.1.0: {} + zod@4.3.6: {} + zustand@4.5.7(@types/react@18.3.26)(react@18.3.1): dependencies: use-sync-external-store: 1.6.0(react@18.3.1) diff --git a/pluto-duck-ui.svg b/pluto-duck-ui.svg new file mode 100644 index 00000000..bb22a735 --- /dev/null +++ b/pluto-duck-ui.svg @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default Workspace + + + + + + ๋ณด๋“œ + ๋ฐ์ดํ„ฐ์…‹ + + + ์ƒˆ ๋ณด๋“œ + + + + + + ์ƒˆ๋กœ์šด ๋ณด๋“œ๋ž๋‹ˆ๋‹ค + 1d ago + + ๋ณด๋“œ ์ด๋ฆ„ ๋ฐ”๊พธ๋ฉด + 2์›” 6์ผ ์˜คํ›„ 10:20 + + ์ƒˆ๋กœ ๋งŒ๋“ค๊ธฐ + 2์›” 6์ผ ์˜คํ›„ 10:20 + + ๊ธฐ๋ณธ๋ณด๋“œ + 2์›” 6์ผ ์˜คํ›„ 10:19 + + + ๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ + ์—์…‹ + + + YK + Yoojung Kim + your@gmail.com + + + + + + + ๊ด‘๊ณ  ์„ฑ๊ณผ ๋ฐ์ดํ„ฐ ํƒ... + 2025-12์›” + + + + + + + + + + + + + + + + February 25, 2026 at 8:35 PM + ๊ด‘๊ณ  ์„ฑ๊ณผ ๋ฐ์ดํ„ฐ ํƒ์ƒ‰ + + + + + + + ads_meta_timeseries_campaign_performance_202512_metrics_nonzero_view + + Refresh + + + + + 16.0K + 12.0K + 8.0K + 4.0K + 0 + + + + + + + + + + + + + + value + + + + ์ฃผ์š” ์ธ์‚ฌ์ดํŠธ + + + + ์ „์ฒด ์ถ”์ด: 12/25์— ํฐ ๊ธ‰๋“ฑ(+39.9%)์ด ์žˆ๊ณ  ๋‹ค์Œ ๋‚ (12/26)์—” ๊ธ‰๋ฝ(-27.1%) โ€” ํฌ๋ฆฌ์Šค๋งˆ์Šค(๋˜๋Š” ์บ ํŽ˜์ธ + ์˜ˆ์‚ฐ ๋ณ€๊ฒฝ) ๊ด€๋ จ ์ผ์‹œ์  ๋ณ€๋™์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. ๊ทธ ์™ธ ๋‚ ์งœ๋Š” ๋Œ€์ฒด๋กœ 30k~37k ๋ฒ”์œ„์—์„œ ๋ณ€๋™ํ•ฉ๋‹ˆ๋‹ค. + + + + + ํ”Œ๋žซํผ ๊ธฐ์—ฌ๋„: Instagram์ด ์ผ๊ฐ„ ์ตœ๋Œ€ ๊ธฐ์—ฌ(์•ฝ 12k~15k)๋กœ ๊ฐ€์žฅ ํฐ ๋น„์ค‘์„ ์ฐจ์ง€ํ•˜๊ณ , Facebook๋„ ๊พธ์ค€ํžˆ + ๋†’์€ ๋…ธ์ถœ(์•ฝ 10k~14k)์ž…๋‹ˆ๋‹ค. Google์€ ์ผ๋ณ„ 3~4k ์ˆ˜์ค€, LinkedIn์€ ์ผ๋ณ„ 4~5k ์ˆ˜์ค€์œผ๋กœ ๋น„๊ต์  ์ž‘์ง€๋งŒ + (๋…ธ์ถœ์€ ์ ์Œ) ๋น„์šฉ(Spend)/CPM์ด ๋งค์šฐ ๋†’์•„ ๋ณด์ž…๋‹ˆ๋‹ค. + + + + + ์ƒ์œ„ ์บ ํŽ˜์ธ(๊ธฐ๊ฐ„ ํ•ฉ๊ณ„, ์ƒ์œ„ 6๊ฐœ): Instagram Acquisition(119,331), Facebook Acquisition(107,548), + LinkedIn Acquisition(48,009), Google Acquisition(37,212), Facebook Retargeting(18,754), + + + + + + ๋ฐ์ดํ„ฐ ๋ถ„์„ํ•ด๋ณผ๋ž˜ + ๋ฐ์ดํ„ฐ ๋ถ„์„ํ•ด๋ณด์ž + + + + ๋ฐ์ดํ„ฐ ๋ถ„์„ํ•ด + + + + + + + + ๋ฐ์ดํ„ฐ ๋ถ„์„ํ•ด๋ณผ๋ž˜! + + + + Thought for a few seconds + + + + ํ˜„์žฌ ๋ฐ์ดํ„ฐ์…‹ 5๊ฐœ๊ฐ€ ์ „์ฒ˜๋ฆฌ ์ค€๋น„๊ฐ€ ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค: + + + meta_facebook_ads_20251222_20251231 + + + meta_facebook_ads_20251201_20251214 + + + meta_instagram_ads_20251222_20251231 + + + linkedin_ads_20251222_20251231 + + + google_ads_20251222_20251231 + + ์ „์ฒ˜๋ฆฌ๋Š” ๋ถ„์„ ํ’ˆ์งˆ์„ ๋†’์ž…๋‹ˆ๋‹ค. ์ง€๊ธˆ ์ „์ฒ˜๋ฆฌ๋ฅผ ์‹คํ–‰ํ• ๊นŒ์š”? + (์˜ˆ/์•„๋‹ˆ์˜ค) + + + + + + + + + + + + + + + Thought for a few seconds + + + + + meta_facebook_ads_20251222_ + 20251231 + ์ด๊ฑฐ์— ๋Œ€ํ•œ ์ „์ฒ˜๋ฆฌ๋งŒ ์ง„ํ–‰ + + + ๋‹ค์Œ ์ด์Šˆ(์ง„๋‹จ ๊ฒฐ๊ณผ)๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค โ€” ๊ฐ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ์ œ์•ˆ๋œ + ์ˆ˜์ •์•ˆ์„ ์ ์šฉํ• ๊นŒ์š”? (์˜ˆ: "๋ชจ๋‘ ์ ์šฉ", ๋˜๋Š” ๋ฒˆํ˜ธ๋กœ ์˜ˆ/์•„๋‹ˆ + ์˜ค ์„ ํƒ) + + 1. ์ง€์ถœ ์žˆ์Œ์—๋„ ์ „ํ™˜์ด 0์ธ ๊ฐ’ ์กด์žฌ + + ์ œ์•ˆ: ํ–‰์„ ์‚ญ์ œํ•˜์ง€ ์•Š๊ณ ... + + + + + Table + + + Bar + + + Line + + + + + Continue this conversation... + + @ Context + + + GPT-5 Mini + + + + + + + + + + \ No newline at end of file