prompt_objects 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/CLAUDE.md +112 -44
  4. data/README.md +5 -0
  5. data/frontend/index.html +5 -1
  6. data/frontend/src/App.tsx +70 -78
  7. data/frontend/src/canvas/CanvasView.tsx +5 -5
  8. data/frontend/src/canvas/constants.ts +31 -31
  9. data/frontend/src/canvas/inspector/InspectorPanel.tsx +4 -4
  10. data/frontend/src/canvas/inspector/POInspector.tsx +35 -35
  11. data/frontend/src/canvas/inspector/ToolCallInspector.tsx +13 -13
  12. data/frontend/src/canvas/nodes/PONode.ts +2 -2
  13. data/frontend/src/components/ContextMenu.tsx +5 -4
  14. data/frontend/src/components/Inspector.tsx +232 -0
  15. data/frontend/src/components/MarkdownMessage.tsx +22 -20
  16. data/frontend/src/components/MethodList.tsx +90 -0
  17. data/frontend/src/components/ModelSelector.tsx +13 -14
  18. data/frontend/src/components/NotificationPanel.tsx +29 -33
  19. data/frontend/src/components/ObjectList.tsx +78 -0
  20. data/frontend/src/components/PaneSlot.tsx +30 -0
  21. data/frontend/src/components/SourcePane.tsx +202 -0
  22. data/frontend/src/components/SystemBar.tsx +74 -0
  23. data/frontend/src/components/Transcript.tsx +76 -0
  24. data/frontend/src/components/UsagePanel.tsx +27 -27
  25. data/frontend/src/components/Workspace.tsx +260 -0
  26. data/frontend/src/components/index.ts +10 -9
  27. data/frontend/src/hooks/useResize.ts +55 -0
  28. data/frontend/src/hooks/useWebSocket.ts +28 -0
  29. data/frontend/src/index.css +28 -10
  30. data/frontend/src/store/index.ts +4 -0
  31. data/frontend/src/types/index.ts +2 -0
  32. data/frontend/tailwind.config.js +28 -9
  33. data/lib/prompt_objects/capability.rb +23 -1
  34. data/lib/prompt_objects/connectors/mcp.rb +2 -16
  35. data/lib/prompt_objects/llm/openai_adapter.rb +22 -0
  36. data/lib/prompt_objects/mcp/tools/inspect_po.rb +1 -31
  37. data/lib/prompt_objects/mcp/tools/list_prompt_objects.rb +1 -6
  38. data/lib/prompt_objects/prompt_object.rb +126 -0
  39. data/lib/prompt_objects/server/api/routes.rb +3 -48
  40. data/lib/prompt_objects/server/public/assets/{index-xvyeb-5Z.js → index-D1myxE0l.js} +211 -211
  41. data/lib/prompt_objects/server/public/assets/index-DdCcwC-Z.css +1 -0
  42. data/lib/prompt_objects/server/public/index.html +7 -3
  43. data/lib/prompt_objects/server/websocket_handler.rb +23 -100
  44. data/lib/prompt_objects/server.rb +6 -62
  45. data/prompt_objects.gemspec +1 -1
  46. data/templates/arc-agi-1/primitives/find_objects.rb +1 -1
  47. data/templates/arc-agi-1/primitives/grid_diff.rb +2 -2
  48. data/templates/arc-agi-1/primitives/grid_info.rb +1 -1
  49. data/templates/arc-agi-1/primitives/grid_transform.rb +1 -1
  50. data/templates/arc-agi-1/primitives/render_grid.rb +1 -0
  51. data/templates/arc-agi-1/primitives/test_solution.rb +3 -0
  52. metadata +12 -13
  53. data/frontend/src/components/CapabilitiesPanel.tsx +0 -141
  54. data/frontend/src/components/ChatPanel.tsx +0 -296
  55. data/frontend/src/components/Dashboard.tsx +0 -83
  56. data/frontend/src/components/Header.tsx +0 -153
  57. data/frontend/src/components/MessageBus.tsx +0 -56
  58. data/frontend/src/components/POCard.tsx +0 -56
  59. data/frontend/src/components/PODetail.tsx +0 -124
  60. data/frontend/src/components/PromptPanel.tsx +0 -156
  61. data/frontend/src/components/SessionsPanel.tsx +0 -174
  62. data/frontend/src/components/ThreadsSidebar.tsx +0 -163
  63. data/lib/prompt_objects/server/public/assets/index-6y64NXFy.css +0 -1
@@ -1,124 +0,0 @@
1
- import { useStore, useSelectedPO, usePONotifications } from '../store'
2
- import { ChatPanel } from './ChatPanel'
3
- import { SessionsPanel } from './SessionsPanel'
4
- import { CapabilitiesPanel } from './CapabilitiesPanel'
5
- import { PromptPanel } from './PromptPanel'
6
-
7
- interface PODetailProps {
8
- sendMessage: (target: string, content: string) => void
9
- createSession: (target: string, name?: string) => void
10
- switchSession: (target: string, sessionId: string) => void
11
- createThread: (target: string, name?: string) => void
12
- updatePrompt: (target: string, prompt: string) => void
13
- }
14
-
15
- export function PODetail({
16
- sendMessage,
17
- createSession,
18
- switchSession,
19
- createThread,
20
- updatePrompt,
21
- }: PODetailProps) {
22
- const { activeTab, setActiveTab, selectPO } = useStore()
23
- const po = useSelectedPO()
24
- const notifications = usePONotifications(po?.name || '')
25
-
26
- if (!po) {
27
- return (
28
- <div className="h-full flex items-center justify-center text-gray-500">
29
- Select a Prompt Object
30
- </div>
31
- )
32
- }
33
-
34
- const tabs = [
35
- { id: 'chat' as const, label: 'Chat' },
36
- { id: 'sessions' as const, label: `Threads (${po.sessions?.length || 0})` },
37
- { id: 'capabilities' as const, label: `Capabilities (${po.capabilities?.length || 0})` },
38
- { id: 'prompt' as const, label: 'Prompt' },
39
- ]
40
-
41
- return (
42
- <div className="h-full flex flex-col">
43
- {/* PO Header */}
44
- <div className="border-b border-po-border bg-po-surface px-4 py-3">
45
- <div className="flex items-center justify-between mb-2">
46
- <div className="flex items-center gap-3">
47
- <button
48
- onClick={() => selectPO(null)}
49
- className="text-gray-400 hover:text-white transition-colors"
50
- >
51
- ← Back
52
- </button>
53
- <h2 className="text-lg font-semibold text-white">{po.name}</h2>
54
- {notifications.length > 0 && (
55
- <span className="bg-po-warning text-black text-xs font-bold px-2 py-0.5 rounded-full">
56
- {notifications.length} pending
57
- </span>
58
- )}
59
- </div>
60
- <div className="flex items-center gap-2 text-sm">
61
- <StatusIndicator status={po.status} />
62
- </div>
63
- </div>
64
- <p className="text-sm text-gray-400">{po.description}</p>
65
- </div>
66
-
67
- {/* Tabs */}
68
- <div className="border-b border-po-border bg-po-surface px-4">
69
- <div className="flex gap-1">
70
- {tabs.map((tab) => (
71
- <button
72
- key={tab.id}
73
- onClick={() => setActiveTab(tab.id)}
74
- className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
75
- activeTab === tab.id
76
- ? 'text-po-accent border-po-accent'
77
- : 'text-gray-400 border-transparent hover:text-white'
78
- }`}
79
- >
80
- {tab.label}
81
- </button>
82
- ))}
83
- </div>
84
- </div>
85
-
86
- {/* Tab content */}
87
- <div className="flex-1 overflow-hidden">
88
- {activeTab === 'chat' && (
89
- <ChatPanel po={po} sendMessage={sendMessage} />
90
- )}
91
- {activeTab === 'sessions' && (
92
- <SessionsPanel
93
- po={po}
94
- createSession={createSession}
95
- switchSession={switchSession}
96
- createThread={createThread}
97
- />
98
- )}
99
- {activeTab === 'capabilities' && <CapabilitiesPanel po={po} />}
100
- {activeTab === 'prompt' && (
101
- <PromptPanel
102
- po={po}
103
- onSave={(prompt) => updatePrompt(po.name, prompt)}
104
- />
105
- )}
106
- </div>
107
- </div>
108
- )
109
- }
110
-
111
- function StatusIndicator({ status }: { status: string }) {
112
- const config = {
113
- idle: { color: 'bg-gray-500', label: 'Idle' },
114
- thinking: { color: 'bg-po-accent animate-pulse', label: 'Thinking...' },
115
- calling_tool: { color: 'bg-po-warning animate-pulse', label: 'Calling tool...' },
116
- }[status] || { color: 'bg-gray-500', label: status }
117
-
118
- return (
119
- <div className="flex items-center gap-2">
120
- <div className={`w-2 h-2 rounded-full ${config.color}`} />
121
- <span className="text-gray-400">{config.label}</span>
122
- </div>
123
- )
124
- }
@@ -1,156 +0,0 @@
1
- import { useState, useEffect, useCallback, useRef } from 'react'
2
- import type { PromptObject } from '../types'
3
- import { MarkdownMessage } from './MarkdownMessage'
4
-
5
- interface PromptPanelProps {
6
- po: PromptObject
7
- onSave?: (prompt: string) => void
8
- }
9
-
10
- export function PromptPanel({ po, onSave }: PromptPanelProps) {
11
- const prompt = po.prompt || ''
12
- const config = po.config || {}
13
- const [isEditing, setIsEditing] = useState(false)
14
- const [editedPrompt, setEditedPrompt] = useState(prompt)
15
- const [saveStatus, setSaveStatus] = useState<'saved' | 'saving' | 'unsaved'>('saved')
16
- const saveTimeoutRef = useRef<number | null>(null)
17
-
18
- // Sync editedPrompt when po.prompt changes from server
19
- useEffect(() => {
20
- if (!isEditing) {
21
- setEditedPrompt(prompt)
22
- }
23
- }, [prompt, isEditing])
24
-
25
- // Debounced auto-save
26
- const debouncedSave = useCallback((newPrompt: string) => {
27
- if (saveTimeoutRef.current) {
28
- clearTimeout(saveTimeoutRef.current)
29
- }
30
-
31
- setSaveStatus('unsaved')
32
-
33
- saveTimeoutRef.current = window.setTimeout(() => {
34
- if (onSave && newPrompt !== prompt) {
35
- setSaveStatus('saving')
36
- onSave(newPrompt)
37
- // Assume save succeeded - server will broadcast update
38
- setTimeout(() => setSaveStatus('saved'), 500)
39
- } else {
40
- setSaveStatus('saved')
41
- }
42
- }, 1000) // 1 second debounce
43
- }, [onSave, prompt])
44
-
45
- const handlePromptChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
46
- const newPrompt = e.target.value
47
- setEditedPrompt(newPrompt)
48
- debouncedSave(newPrompt)
49
- }
50
-
51
- const handleStartEditing = () => {
52
- setEditedPrompt(prompt)
53
- setIsEditing(true)
54
- }
55
-
56
- const handleStopEditing = () => {
57
- // Save any pending changes
58
- if (saveTimeoutRef.current) {
59
- clearTimeout(saveTimeoutRef.current)
60
- }
61
- if (editedPrompt !== prompt && onSave) {
62
- onSave(editedPrompt)
63
- }
64
- setIsEditing(false)
65
- setSaveStatus('saved')
66
- }
67
-
68
- // Cleanup timeout on unmount
69
- useEffect(() => {
70
- return () => {
71
- if (saveTimeoutRef.current) {
72
- clearTimeout(saveTimeoutRef.current)
73
- }
74
- }
75
- }, [])
76
-
77
- return (
78
- <div className="h-full overflow-auto p-4">
79
- {/* Config/Frontmatter */}
80
- <div className="mb-6">
81
- <h3 className="text-lg font-medium text-white mb-3">Configuration</h3>
82
- <div className="bg-po-bg rounded-lg border border-po-border p-4">
83
- <pre className="text-sm text-gray-300 font-mono whitespace-pre-wrap">
84
- {JSON.stringify(config, null, 2)}
85
- </pre>
86
- </div>
87
- </div>
88
-
89
- {/* Prompt/Body */}
90
- <div>
91
- <div className="flex items-center justify-between mb-3">
92
- <h3 className="text-lg font-medium text-white">Prompt</h3>
93
- <div className="flex items-center gap-3">
94
- {isEditing && (
95
- <span className={`text-xs ${
96
- saveStatus === 'saved' ? 'text-green-400' :
97
- saveStatus === 'saving' ? 'text-yellow-400' :
98
- 'text-gray-400'
99
- }`}>
100
- {saveStatus === 'saved' ? 'Saved' :
101
- saveStatus === 'saving' ? 'Saving...' :
102
- 'Unsaved changes'}
103
- </span>
104
- )}
105
- <button
106
- onClick={isEditing ? handleStopEditing : handleStartEditing}
107
- className={`px-3 py-1 text-sm rounded transition-colors ${
108
- isEditing
109
- ? 'bg-po-accent text-black hover:bg-po-accent/80'
110
- : 'bg-po-surface border border-po-border text-gray-300 hover:text-white hover:border-po-accent'
111
- }`}
112
- >
113
- {isEditing ? 'Done' : 'Edit'}
114
- </button>
115
- </div>
116
- </div>
117
-
118
- <div className="bg-po-bg rounded-lg border border-po-border">
119
- {isEditing ? (
120
- <textarea
121
- value={editedPrompt}
122
- onChange={handlePromptChange}
123
- className="w-full h-96 p-4 bg-transparent text-gray-200 font-mono text-sm resize-none focus:outline-none focus:ring-1 focus:ring-po-accent rounded-lg"
124
- placeholder="Enter prompt markdown..."
125
- spellCheck={false}
126
- />
127
- ) : (
128
- <div className="p-4">
129
- {prompt ? (
130
- <div className="prose prose-invert max-w-none">
131
- <MarkdownMessage content={prompt} />
132
- </div>
133
- ) : (
134
- <p className="text-gray-500 italic">No prompt defined. Click Edit to add one.</p>
135
- )}
136
- </div>
137
- )}
138
- </div>
139
- </div>
140
-
141
- {/* Raw source toggle (only in view mode) */}
142
- {!isEditing && (
143
- <details className="mt-6">
144
- <summary className="text-sm text-gray-400 cursor-pointer hover:text-white">
145
- View raw source
146
- </summary>
147
- <div className="mt-2 bg-po-bg rounded-lg border border-po-border p-4">
148
- <pre className="text-xs text-gray-400 font-mono whitespace-pre-wrap overflow-x-auto">
149
- {prompt || '(empty)'}
150
- </pre>
151
- </div>
152
- </details>
153
- )}
154
- </div>
155
- )
156
- }
@@ -1,174 +0,0 @@
1
- import { useMemo } from 'react'
2
- import type { PromptObject, Session, ThreadType } from '../types'
3
-
4
- interface SessionsPanelProps {
5
- po: PromptObject
6
- createSession: (target: string, name?: string) => void
7
- switchSession: (target: string, sessionId: string) => void
8
- createThread?: (target: string, name?: string) => void
9
- }
10
-
11
- // Build a tree structure from flat sessions list
12
- interface ThreadNode {
13
- session: Session
14
- children: ThreadNode[]
15
- depth: number
16
- }
17
-
18
- function buildThreadTree(sessions: Session[]): ThreadNode[] {
19
- const sessionMap = new Map<string, Session>()
20
- const childrenMap = new Map<string, Session[]>()
21
-
22
- // Index all sessions
23
- sessions.forEach((s) => {
24
- sessionMap.set(s.id, s)
25
- if (s.parent_session_id) {
26
- const children = childrenMap.get(s.parent_session_id) || []
27
- children.push(s)
28
- childrenMap.set(s.parent_session_id, children)
29
- }
30
- })
31
-
32
- // Build tree recursively
33
- function buildNode(session: Session, depth: number): ThreadNode {
34
- const children = childrenMap.get(session.id) || []
35
- return {
36
- session,
37
- children: children
38
- .sort((a, b) => (a.updated_at || '').localeCompare(b.updated_at || ''))
39
- .map((child) => buildNode(child, depth + 1)),
40
- depth,
41
- }
42
- }
43
-
44
- // Get root sessions (no parent)
45
- const roots = sessions
46
- .filter((s) => !s.parent_session_id)
47
- .sort((a, b) => (b.updated_at || '').localeCompare(a.updated_at || ''))
48
-
49
- return roots.map((root) => buildNode(root, 0))
50
- }
51
-
52
- // Flatten tree for rendering
53
- function flattenTree(nodes: ThreadNode[]): ThreadNode[] {
54
- const result: ThreadNode[] = []
55
- function traverse(node: ThreadNode) {
56
- result.push(node)
57
- node.children.forEach(traverse)
58
- }
59
- nodes.forEach(traverse)
60
- return result
61
- }
62
-
63
- // Thread type icons/badges
64
- function ThreadTypeBadge({ type }: { type: ThreadType }) {
65
- switch (type) {
66
- case 'delegation':
67
- return (
68
- <span className="text-xs bg-purple-600/30 text-purple-300 px-1.5 py-0.5 rounded">
69
- ↳ delegation
70
- </span>
71
- )
72
- case 'fork':
73
- return (
74
- <span className="text-xs bg-blue-600/30 text-blue-300 px-1.5 py-0.5 rounded">
75
- ⑂ fork
76
- </span>
77
- )
78
- case 'continuation':
79
- return (
80
- <span className="text-xs bg-gray-600/30 text-gray-300 px-1.5 py-0.5 rounded">
81
- → continued
82
- </span>
83
- )
84
- default:
85
- return null
86
- }
87
- }
88
-
89
- export function SessionsPanel({
90
- po,
91
- createSession,
92
- switchSession,
93
- createThread,
94
- }: SessionsPanelProps) {
95
- const sessions = po.sessions || []
96
- const currentSessionId = po.current_session?.id
97
-
98
- // Build and flatten thread tree
99
- const flatNodes = useMemo(() => {
100
- const tree = buildThreadTree(sessions)
101
- return flattenTree(tree)
102
- }, [sessions])
103
-
104
- const handleNewThread = () => {
105
- if (createThread) {
106
- createThread(po.name)
107
- } else {
108
- // Fallback to createSession
109
- const name = prompt('Thread name (optional):')
110
- createSession(po.name, name || undefined)
111
- }
112
- }
113
-
114
- return (
115
- <div className="h-full overflow-auto p-4">
116
- <div className="flex items-center justify-between mb-4">
117
- <h3 className="text-lg font-medium text-white">Threads</h3>
118
- <button
119
- onClick={handleNewThread}
120
- className="px-3 py-1.5 bg-po-accent text-white text-sm rounded hover:bg-po-accent/80 transition-colors"
121
- >
122
- + New Thread
123
- </button>
124
- </div>
125
-
126
- {flatNodes.length === 0 ? (
127
- <div className="text-gray-500 text-center py-8">
128
- No threads yet. Start a conversation to create one.
129
- </div>
130
- ) : (
131
- <div className="space-y-1">
132
- {flatNodes.map(({ session, depth }) => (
133
- <button
134
- key={session.id}
135
- onClick={() => switchSession(po.name, session.id)}
136
- className={`w-full text-left p-3 rounded-lg border transition-colors ${
137
- session.id === currentSessionId
138
- ? 'bg-po-accent/20 border-po-accent'
139
- : 'bg-po-surface border-po-border hover:border-po-accent/50'
140
- }`}
141
- style={{ marginLeft: `${depth * 16}px`, width: `calc(100% - ${depth * 16}px)` }}
142
- >
143
- <div className="flex items-center gap-2 mb-1">
144
- {depth > 0 && (
145
- <span className="text-gray-500 text-xs">↳</span>
146
- )}
147
- <span className="font-medium text-white truncate flex-1">
148
- {session.name || `Thread ${session.id.slice(0, 8)}`}
149
- </span>
150
- <ThreadTypeBadge type={session.thread_type} />
151
- {session.id === currentSessionId && (
152
- <span className="text-xs bg-po-accent text-white px-2 py-0.5 rounded flex-shrink-0">
153
- Active
154
- </span>
155
- )}
156
- </div>
157
- <div className="text-sm text-gray-400 flex items-center gap-2">
158
- <span>{session.message_count} messages</span>
159
- {session.parent_po && (
160
- <span className="text-purple-400">from {session.parent_po}</span>
161
- )}
162
- {session.updated_at && (
163
- <span className="ml-auto">
164
- {new Date(session.updated_at).toLocaleDateString()}
165
- </span>
166
- )}
167
- </div>
168
- </button>
169
- ))}
170
- </div>
171
- )}
172
- </div>
173
- )
174
- }
@@ -1,163 +0,0 @@
1
- import { useMemo, useState } from 'react'
2
- import { ContextMenu } from './ContextMenu'
3
- import type { PromptObject, Session, ThreadType } from '../types'
4
-
5
- interface ThreadsSidebarProps {
6
- po: PromptObject
7
- switchSession: (target: string, sessionId: string) => void
8
- createThread: (target: string) => void
9
- requestUsage?: (sessionId: string, includeTree?: boolean) => void
10
- exportThread?: (sessionId: string, format?: string) => void
11
- }
12
-
13
- // Build a flat list with depth info
14
- interface ThreadItem {
15
- session: Session
16
- depth: number
17
- }
18
-
19
- function buildThreadList(sessions: Session[]): ThreadItem[] {
20
- const childrenMap = new Map<string, Session[]>()
21
-
22
- // Index children
23
- sessions.forEach((s) => {
24
- if (s.parent_session_id) {
25
- const children = childrenMap.get(s.parent_session_id) || []
26
- children.push(s)
27
- childrenMap.set(s.parent_session_id, children)
28
- }
29
- })
30
-
31
- // Build flat list with depth
32
- const result: ThreadItem[] = []
33
-
34
- function traverse(session: Session, depth: number) {
35
- result.push({ session, depth })
36
- const children = childrenMap.get(session.id) || []
37
- children
38
- .sort((a, b) => (a.updated_at || '').localeCompare(b.updated_at || ''))
39
- .forEach((child) => traverse(child, depth + 1))
40
- }
41
-
42
- // Get roots and traverse
43
- const roots = sessions
44
- .filter((s) => !s.parent_session_id)
45
- .sort((a, b) => (b.updated_at || '').localeCompare(a.updated_at || ''))
46
-
47
- roots.forEach((root) => traverse(root, 0))
48
-
49
- return result
50
- }
51
-
52
- function ThreadTypeIcon({ type }: { type: ThreadType }) {
53
- switch (type) {
54
- case 'delegation':
55
- return <span className="text-purple-400" title="Delegation">↳</span>
56
- case 'fork':
57
- return <span className="text-blue-400" title="Fork">⑂</span>
58
- case 'continuation':
59
- return <span className="text-gray-400" title="Continuation">→</span>
60
- default:
61
- return null
62
- }
63
- }
64
-
65
- export function ThreadsSidebar({ po, switchSession, createThread, requestUsage, exportThread }: ThreadsSidebarProps) {
66
- const sessions = po.sessions || []
67
- const currentSessionId = po.current_session?.id
68
- const [contextMenu, setContextMenu] = useState<{ x: number; y: number; sessionId: string } | null>(null)
69
-
70
- const threadList = useMemo(() => buildThreadList(sessions), [sessions])
71
-
72
- const handleContextMenu = (e: React.MouseEvent, sessionId: string) => {
73
- e.preventDefault()
74
- setContextMenu({ x: e.clientX, y: e.clientY, sessionId })
75
- }
76
-
77
- return (
78
- <div className="h-full flex flex-col">
79
- <div className="p-3 border-b border-po-border flex items-center justify-between">
80
- <h2 className="text-sm font-medium text-gray-400">Threads</h2>
81
- <button
82
- onClick={() => createThread(po.name)}
83
- className="text-xs text-gray-500 hover:text-po-accent transition-colors"
84
- title="New thread"
85
- >
86
- + New
87
- </button>
88
- </div>
89
-
90
- <div className="flex-1 overflow-auto p-2 space-y-1">
91
- {threadList.length === 0 ? (
92
- <div className="text-xs text-gray-500 text-center py-4">
93
- No threads yet
94
- </div>
95
- ) : (
96
- threadList.map(({ session, depth }) => (
97
- <button
98
- key={session.id}
99
- onClick={() => switchSession(po.name, session.id)}
100
- onContextMenu={(e) => handleContextMenu(e, session.id)}
101
- className={`w-full text-left p-2 rounded text-xs transition-colors ${
102
- session.id === currentSessionId
103
- ? 'bg-po-accent/20 border border-po-accent'
104
- : 'hover:bg-po-surface border border-transparent'
105
- }`}
106
- style={{ paddingLeft: `${8 + depth * 12}px` }}
107
- >
108
- <div className="flex items-center gap-1">
109
- {depth > 0 && <ThreadTypeIcon type={session.thread_type} />}
110
- <span className={`truncate flex-1 ${session.id === currentSessionId ? 'text-white' : 'text-gray-300'}`}>
111
- {session.name || `Thread ${session.id.slice(0, 6)}`}
112
- </span>
113
- {session.id === currentSessionId && (
114
- <span className="w-1.5 h-1.5 rounded-full bg-po-accent flex-shrink-0" />
115
- )}
116
- </div>
117
- <div className="text-gray-500 text-[10px] mt-0.5">
118
- {session.message_count} msgs
119
- {session.parent_po && (
120
- <span className="text-purple-400 ml-1">from {session.parent_po}</span>
121
- )}
122
- </div>
123
- </button>
124
- ))
125
- )}
126
- </div>
127
-
128
- {contextMenu && (
129
- <ContextMenu
130
- x={contextMenu.x}
131
- y={contextMenu.y}
132
- onClose={() => setContextMenu(null)}
133
- items={[
134
- ...(requestUsage ? [
135
- {
136
- label: 'View Usage',
137
- icon: '📊',
138
- onClick: () => requestUsage(contextMenu.sessionId),
139
- },
140
- {
141
- label: 'View Tree Usage',
142
- icon: '🌳',
143
- onClick: () => requestUsage(contextMenu.sessionId, true),
144
- },
145
- ] : []),
146
- ...(exportThread ? [
147
- {
148
- label: 'Export as Markdown',
149
- icon: '📄',
150
- onClick: () => exportThread(contextMenu.sessionId, 'markdown'),
151
- },
152
- {
153
- label: 'Export as JSON',
154
- icon: '📋',
155
- onClick: () => exportThread(contextMenu.sessionId, 'json'),
156
- },
157
- ] : []),
158
- ]}
159
- />
160
- )}
161
- </div>
162
- )
163
- }
@@ -1 +0,0 @@
1
- *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.-right-1{right:-.25rem}.-top-1{top:-.25rem}.bottom-3{bottom:.75rem}.bottom-4{bottom:1rem}.left-2{left:.5rem}.left-3{left:.75rem}.right-0{right:0}.right-4{right:1rem}.top-0{top:0}.top-16{top:4rem}.top-3{top:.75rem}.top-full{top:100%}.z-10{z-index:10}.z-50{z-index:50}.my-3{margin-top:.75rem;margin-bottom:.75rem}.my-4{margin-top:1rem;margin-bottom:1rem}.-mb-px{margin-bottom:-1px}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-1{margin-left:.25rem}.ml-11{margin-left:2.75rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-14{height:3.5rem}.h-2{height:.5rem}.h-4{height:1rem}.h-40{height:10rem}.h-5{height:1.25rem}.h-8{height:2rem}.h-96{height:24rem}.h-full{height:100%}.h-screen{height:100vh}.max-h-32{max-height:8rem}.max-h-48{max-height:12rem}.max-h-64{max-height:16rem}.max-h-\[60vh\]{max-height:60vh}.max-h-\[80vh\]{max-height:80vh}.w-1\.5{width:.375rem}.w-2{width:.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-56{width:14rem}.w-64{width:16rem}.w-8{width:2rem}.w-80{width:20rem}.w-96{width:24rem}.w-\[420px\]{width:420px}.w-full{width:100%}.min-w-\[160px\]{min-width:160px}.min-w-full{min-width:100%}.max-w-\[80\%\]{max-width:80%}.max-w-none{max-width:none}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes bounce{0%,to{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-bounce{animation:bounce 1s infinite}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.resize{resize:both}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-0{border-bottom-width:0px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-po-accent{--tw-border-opacity: 1;border-color:rgb(124 58 237 / var(--tw-border-opacity, 1))}.border-po-border{--tw-border-opacity: 1;border-color:rgb(45 45 68 / var(--tw-border-opacity, 1))}.border-po-border\/30{border-color:#2d2d444d}.border-po-border\/50{border-color:#2d2d4480}.border-po-warning\/30{border-color:#f59e0b4d}.border-transparent{border-color:transparent}.bg-black\/50{background-color:#00000080}.bg-blue-600\/30{background-color:#2563eb4d}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-600\/30{background-color:#4b55634d}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-po-accent{--tw-bg-opacity: 1;background-color:rgb(124 58 237 / var(--tw-bg-opacity, 1))}.bg-po-accent\/20{background-color:#7c3aed33}.bg-po-bg{--tw-bg-opacity: 1;background-color:rgb(15 15 26 / var(--tw-bg-opacity, 1))}.bg-po-bg\/50{background-color:#0f0f1a80}.bg-po-bg\/80{background-color:#0f0f1acc}.bg-po-border{--tw-bg-opacity: 1;background-color:rgb(45 45 68 / var(--tw-bg-opacity, 1))}.bg-po-surface{--tw-bg-opacity: 1;background-color:rgb(26 26 46 / var(--tw-bg-opacity, 1))}.bg-po-surface\/50{background-color:#1a1a2e80}.bg-po-surface\/80{background-color:#1a1a2ecc}.bg-po-warning{--tw-bg-opacity: 1;background-color:rgb(245 158 11 / var(--tw-bg-opacity, 1))}.bg-po-warning\/10{background-color:#f59e0b1a}.bg-purple-600\/30{background-color:#9333ea4d}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-4{padding-left:1rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[10px\]{font-size:10px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.italic{font-style:italic}.leading-relaxed{line-height:1.625}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-po-accent{--tw-text-opacity: 1;color:rgb(124 58 237 / var(--tw-text-opacity, 1))}.text-po-accent\/70{color:#7c3aedb3}.text-po-success{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-po-warning{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline{outline-style:solid}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}html{color-scheme:dark}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{--tw-bg-opacity: 1;background-color:rgb(15 15 26 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb{border-radius:9999px;--tw-bg-opacity: 1;background-color:rgb(45 45 68 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb:hover{--tw-bg-opacity: 1;background-color:rgb(124 58 237 / var(--tw-bg-opacity, 1))}.canvas-node-label{text-align:center;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.canvas-node-name{display:block;color:#fff;font-size:12px;font-weight:500;text-shadow:0 1px 4px rgba(0,0,0,.8)}.canvas-node-status{display:block;color:#6b7280;font-size:10px;text-shadow:0 1px 4px rgba(0,0,0,.8)}.canvas-node-badge{display:flex;align-items:center;justify-content:center;width:20px;height:20px;background:#f59e0b;color:#000;font-size:10px;font-weight:700;border-radius:50%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;box-shadow:0 0 8px #f59e0b99}.canvas-toolcall-label{color:#93c5fd;font-size:10px;font-family:monospace;text-align:center;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;text-shadow:0 1px 4px rgba(0,0,0,.8)}.first\:mt-0:first-child{margin-top:0}.last\:mb-0:last-child{margin-bottom:0}.last\:border-0:last-child{border-width:0px}.hover\:border-po-accent:hover{--tw-border-opacity: 1;border-color:rgb(124 58 237 / var(--tw-border-opacity, 1))}.hover\:border-po-accent\/50:hover{border-color:#7c3aed80}.hover\:bg-po-accent\/50:hover{background-color:#7c3aed80}.hover\:bg-po-accent\/80:hover{background-color:#7c3aedcc}.hover\:bg-po-bg:hover{--tw-bg-opacity: 1;background-color:rgb(15 15 26 / var(--tw-bg-opacity, 1))}.hover\:bg-po-bg\/70:hover{background-color:#0f0f1ab3}.hover\:bg-po-border:hover{--tw-bg-opacity: 1;background-color:rgb(45 45 68 / var(--tw-bg-opacity, 1))}.hover\:bg-po-surface:hover{--tw-bg-opacity: 1;background-color:rgb(26 26 46 / var(--tw-bg-opacity, 1))}.hover\:bg-po-warning\/20:hover{background-color:#f59e0b33}.hover\:text-po-accent:hover{--tw-text-opacity: 1;color:rgb(124 58 237 / var(--tw-text-opacity, 1))}.hover\:text-red-300:hover{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-po-accent:focus{--tw-border-opacity: 1;border-color:rgb(124 58 237 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-po-accent:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(124 58 237 / var(--tw-ring-opacity, 1))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:text-po-accent{--tw-text-opacity: 1;color:rgb(124 58 237 / var(--tw-text-opacity, 1))}@media(min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:1280px){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}