prompt_objects 0.3.1 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/CLAUDE.md +112 -44
- data/Gemfile.lock +31 -29
- data/README.md +5 -0
- data/frontend/index.html +5 -1
- data/frontend/package-lock.json +123 -0
- data/frontend/package.json +4 -0
- data/frontend/src/App.tsx +70 -71
- data/frontend/src/canvas/CanvasView.tsx +113 -0
- data/frontend/src/canvas/ForceLayout.ts +115 -0
- data/frontend/src/canvas/SceneManager.ts +587 -0
- data/frontend/src/canvas/canvasStore.ts +47 -0
- data/frontend/src/canvas/constants.ts +95 -0
- data/frontend/src/canvas/controls/CameraControls.ts +98 -0
- data/frontend/src/canvas/edges/MessageArc.ts +149 -0
- data/frontend/src/canvas/inspector/InspectorPanel.tsx +31 -0
- data/frontend/src/canvas/inspector/POInspector.tsx +262 -0
- data/frontend/src/canvas/inspector/ToolCallInspector.tsx +67 -0
- data/frontend/src/canvas/nodes/PONode.ts +249 -0
- data/frontend/src/canvas/nodes/ToolCallNode.ts +116 -0
- data/frontend/src/canvas/types.ts +24 -0
- data/frontend/src/components/ContextMenu.tsx +5 -4
- data/frontend/src/components/Inspector.tsx +232 -0
- data/frontend/src/components/MarkdownMessage.tsx +22 -20
- data/frontend/src/components/MethodList.tsx +90 -0
- data/frontend/src/components/ModelSelector.tsx +13 -14
- data/frontend/src/components/NotificationPanel.tsx +29 -33
- data/frontend/src/components/ObjectList.tsx +78 -0
- data/frontend/src/components/PaneSlot.tsx +30 -0
- data/frontend/src/components/SourcePane.tsx +202 -0
- data/frontend/src/components/SystemBar.tsx +74 -0
- data/frontend/src/components/Transcript.tsx +76 -0
- data/frontend/src/components/UsagePanel.tsx +27 -27
- data/frontend/src/components/Workspace.tsx +260 -0
- data/frontend/src/components/index.ts +10 -9
- data/frontend/src/hooks/useResize.ts +55 -0
- data/frontend/src/hooks/useWebSocket.ts +274 -189
- data/frontend/src/index.css +69 -3
- data/frontend/src/store/index.ts +23 -0
- data/frontend/src/types/index.ts +5 -0
- data/frontend/tailwind.config.js +28 -9
- data/lib/prompt_objects/capability.rb +23 -1
- data/lib/prompt_objects/connectors/mcp.rb +5 -22
- data/lib/prompt_objects/environment.rb +8 -0
- data/lib/prompt_objects/llm/openai_adapter.rb +22 -0
- data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +1 -2
- data/lib/prompt_objects/mcp/tools/inspect_po.rb +1 -31
- data/lib/prompt_objects/mcp/tools/list_prompt_objects.rb +2 -8
- data/lib/prompt_objects/primitives/list_files.rb +1 -2
- data/lib/prompt_objects/prompt_object.rb +150 -6
- data/lib/prompt_objects/server/api/routes.rb +3 -48
- data/lib/prompt_objects/server/app.rb +9 -0
- data/lib/prompt_objects/server/public/assets/index-D1myxE0l.js +4345 -0
- data/lib/prompt_objects/server/public/assets/index-DdCcwC-Z.css +1 -0
- data/lib/prompt_objects/server/public/index.html +7 -3
- data/lib/prompt_objects/server/websocket_handler.rb +23 -100
- data/lib/prompt_objects/server.rb +6 -62
- data/prompt_objects.gemspec +1 -1
- data/templates/arc-agi-1/primitives/find_objects.rb +1 -1
- data/templates/arc-agi-1/primitives/grid_diff.rb +2 -2
- data/templates/arc-agi-1/primitives/grid_info.rb +1 -1
- data/templates/arc-agi-1/primitives/grid_transform.rb +1 -1
- data/templates/arc-agi-1/primitives/render_grid.rb +1 -0
- data/templates/arc-agi-1/primitives/test_solution.rb +3 -0
- metadata +26 -14
- data/frontend/src/components/CapabilitiesPanel.tsx +0 -141
- data/frontend/src/components/ChatPanel.tsx +0 -288
- data/frontend/src/components/Dashboard.tsx +0 -83
- data/frontend/src/components/Header.tsx +0 -141
- data/frontend/src/components/MessageBus.tsx +0 -56
- data/frontend/src/components/POCard.tsx +0 -56
- data/frontend/src/components/PODetail.tsx +0 -124
- data/frontend/src/components/PromptPanel.tsx +0 -156
- data/frontend/src/components/SessionsPanel.tsx +0 -174
- data/frontend/src/components/ThreadsSidebar.tsx +0 -163
- data/lib/prompt_objects/server/public/assets/index-Bkme6COu.css +0 -1
- data/lib/prompt_objects/server/public/assets/index-CQ7lVDF_.js +0 -77
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import type { PromptObject, CapabilityInfo } from '../types'
|
|
3
|
-
|
|
4
|
-
interface CapabilitiesPanelProps {
|
|
5
|
-
po: PromptObject
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function CapabilitiesPanel({ po }: CapabilitiesPanelProps) {
|
|
9
|
-
const capabilities = po.capabilities || []
|
|
10
|
-
const universalCapabilities = po.universal_capabilities || []
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<div className="h-full overflow-auto p-4">
|
|
14
|
-
{/* Declared Capabilities */}
|
|
15
|
-
<div className="mb-6">
|
|
16
|
-
<h3 className="text-lg font-medium text-white mb-3">
|
|
17
|
-
Declared Capabilities
|
|
18
|
-
<span className="ml-2 text-sm text-gray-500">({capabilities.length})</span>
|
|
19
|
-
</h3>
|
|
20
|
-
|
|
21
|
-
{capabilities.length === 0 ? (
|
|
22
|
-
<div className="text-gray-500 text-sm py-4 px-3 bg-po-bg rounded-lg border border-po-border">
|
|
23
|
-
No capabilities declared. This PO can only use universal capabilities.
|
|
24
|
-
</div>
|
|
25
|
-
) : (
|
|
26
|
-
<div className="space-y-1">
|
|
27
|
-
{capabilities.map((cap) => (
|
|
28
|
-
<CapabilityItem key={cap.name} capability={cap} accent />
|
|
29
|
-
))}
|
|
30
|
-
</div>
|
|
31
|
-
)}
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
{/* Universal Capabilities */}
|
|
35
|
-
<div>
|
|
36
|
-
<h3 className="text-lg font-medium text-white mb-3">
|
|
37
|
-
Universal Capabilities
|
|
38
|
-
<span className="ml-2 text-sm text-gray-500">({universalCapabilities.length})</span>
|
|
39
|
-
</h3>
|
|
40
|
-
<p className="text-xs text-gray-500 mb-3">
|
|
41
|
-
Available to all Prompt Objects automatically.
|
|
42
|
-
</p>
|
|
43
|
-
|
|
44
|
-
<div className="space-y-1">
|
|
45
|
-
{universalCapabilities.map((cap) => (
|
|
46
|
-
<CapabilityItem key={cap.name} capability={cap} />
|
|
47
|
-
))}
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface CapabilityItemProps {
|
|
55
|
-
capability: CapabilityInfo
|
|
56
|
-
accent?: boolean // Use accent color for name (for declared caps)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function CapabilityItem({ capability, accent }: CapabilityItemProps) {
|
|
60
|
-
const [expanded, setExpanded] = useState(false)
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<div className="bg-po-bg border border-po-border rounded-lg overflow-hidden">
|
|
64
|
-
<button
|
|
65
|
-
onClick={() => setExpanded(!expanded)}
|
|
66
|
-
className="w-full px-3 py-2 flex items-center justify-between hover:bg-po-surface transition-colors"
|
|
67
|
-
>
|
|
68
|
-
<span className={`font-mono text-sm ${accent ? 'text-po-accent' : 'text-gray-300'}`}>
|
|
69
|
-
{capability.name}
|
|
70
|
-
</span>
|
|
71
|
-
<span className="text-gray-500 text-xs">{expanded ? '▼' : '▶'}</span>
|
|
72
|
-
</button>
|
|
73
|
-
{expanded && (
|
|
74
|
-
<div className="px-3 py-2 border-t border-po-border bg-po-surface space-y-3">
|
|
75
|
-
<p className="text-xs text-gray-400">{capability.description}</p>
|
|
76
|
-
|
|
77
|
-
{capability.parameters && (
|
|
78
|
-
<ParametersDisplay parameters={capability.parameters} />
|
|
79
|
-
)}
|
|
80
|
-
</div>
|
|
81
|
-
)}
|
|
82
|
-
</div>
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface ParametersDisplayProps {
|
|
87
|
-
parameters: Record<string, unknown>
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function ParametersDisplay({ parameters }: ParametersDisplayProps) {
|
|
91
|
-
const properties = (parameters.properties as Record<string, unknown>) || {}
|
|
92
|
-
const required = (parameters.required as string[]) || []
|
|
93
|
-
|
|
94
|
-
const propertyNames = Object.keys(properties)
|
|
95
|
-
|
|
96
|
-
if (propertyNames.length === 0) {
|
|
97
|
-
return null
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return (
|
|
101
|
-
<div>
|
|
102
|
-
<div className="text-xs text-gray-500 mb-2 font-medium">Parameters</div>
|
|
103
|
-
<div className="space-y-2">
|
|
104
|
-
{propertyNames.map((propName) => {
|
|
105
|
-
const prop = properties[propName] as Record<string, unknown>
|
|
106
|
-
const isRequired = required.includes(propName)
|
|
107
|
-
|
|
108
|
-
const propType = prop.type ? String(prop.type) : null
|
|
109
|
-
const propDescription = prop.description ? String(prop.description) : null
|
|
110
|
-
const propEnum = prop.enum as string[] | undefined
|
|
111
|
-
|
|
112
|
-
return (
|
|
113
|
-
<div key={propName} className="bg-po-bg rounded p-2">
|
|
114
|
-
<div className="flex items-center gap-2">
|
|
115
|
-
<span className="font-mono text-xs text-po-accent">{propName}</span>
|
|
116
|
-
{propType && (
|
|
117
|
-
<span className="text-xs text-gray-600">({propType})</span>
|
|
118
|
-
)}
|
|
119
|
-
{isRequired && (
|
|
120
|
-
<span className="text-xs text-red-400">required</span>
|
|
121
|
-
)}
|
|
122
|
-
</div>
|
|
123
|
-
{propDescription && (
|
|
124
|
-
<p className="text-xs text-gray-500 mt-1">{propDescription}</p>
|
|
125
|
-
)}
|
|
126
|
-
{propEnum && propEnum.length > 0 && (
|
|
127
|
-
<div className="mt-1 flex flex-wrap gap-1">
|
|
128
|
-
{propEnum.map((val) => (
|
|
129
|
-
<span key={val} className="text-xs bg-po-surface px-1.5 py-0.5 rounded text-gray-400">
|
|
130
|
-
{val}
|
|
131
|
-
</span>
|
|
132
|
-
))}
|
|
133
|
-
</div>
|
|
134
|
-
)}
|
|
135
|
-
</div>
|
|
136
|
-
)
|
|
137
|
-
})}
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
)
|
|
141
|
-
}
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
import { useState, useRef, useEffect } from 'react'
|
|
2
|
-
import { useStore } from '../store'
|
|
3
|
-
import { MarkdownMessage } from './MarkdownMessage'
|
|
4
|
-
import type { PromptObject, Message, ToolCall } from '../types'
|
|
5
|
-
|
|
6
|
-
interface ChatPanelProps {
|
|
7
|
-
po: PromptObject
|
|
8
|
-
sendMessage: (target: string, content: string, newThread?: boolean) => void
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function ChatPanel({ po, sendMessage }: ChatPanelProps) {
|
|
12
|
-
const [input, setInput] = useState('')
|
|
13
|
-
const [continueThread, setContinueThread] = useState(false)
|
|
14
|
-
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
15
|
-
const { streamingContent } = useStore()
|
|
16
|
-
|
|
17
|
-
const messages = po.current_session?.messages || []
|
|
18
|
-
const streaming = streamingContent[po.name]
|
|
19
|
-
const hasMessages = messages.length > 0
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
23
|
-
}, [messages, streaming])
|
|
24
|
-
|
|
25
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
26
|
-
e.preventDefault()
|
|
27
|
-
if (!input.trim()) return
|
|
28
|
-
|
|
29
|
-
const content = input.trim()
|
|
30
|
-
|
|
31
|
-
// Determine if we should create a new thread
|
|
32
|
-
const shouldCreateNewThread = !continueThread && hasMessages
|
|
33
|
-
|
|
34
|
-
// Send message to server - it will handle thread creation + message in one operation
|
|
35
|
-
// Server sends immediate session_updated with user message for instant feedback
|
|
36
|
-
sendMessage(po.name, content, shouldCreateNewThread)
|
|
37
|
-
setInput('')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<div className="h-full flex flex-col">
|
|
42
|
-
{/* Messages */}
|
|
43
|
-
<div className="flex-1 overflow-auto p-4 space-y-4">
|
|
44
|
-
{messages.length === 0 && !streaming && (
|
|
45
|
-
<div className="h-full flex items-center justify-center text-gray-500">
|
|
46
|
-
<div className="text-center">
|
|
47
|
-
<div className="text-2xl mb-2">💬</div>
|
|
48
|
-
<div>Start a conversation with {po.name}</div>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
)}
|
|
52
|
-
|
|
53
|
-
{messages.map((message, index) => (
|
|
54
|
-
<MessageBubble key={index} message={message} />
|
|
55
|
-
))}
|
|
56
|
-
|
|
57
|
-
{/* Streaming content */}
|
|
58
|
-
{streaming && (
|
|
59
|
-
<div className="flex gap-3">
|
|
60
|
-
<div className="w-8 h-8 rounded-full bg-po-accent flex items-center justify-center text-white text-sm font-medium flex-shrink-0">
|
|
61
|
-
AI
|
|
62
|
-
</div>
|
|
63
|
-
<div className="flex-1 bg-po-surface rounded-lg p-3 text-gray-200">
|
|
64
|
-
<MarkdownMessage content={streaming} />
|
|
65
|
-
<span className="inline-block w-2 h-4 bg-po-accent animate-pulse ml-1" />
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
)}
|
|
69
|
-
|
|
70
|
-
<div ref={messagesEndRef} />
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
{/* Input */}
|
|
74
|
-
<form onSubmit={handleSubmit} className="border-t border-po-border p-4">
|
|
75
|
-
<div className="flex gap-3">
|
|
76
|
-
<input
|
|
77
|
-
type="text"
|
|
78
|
-
value={input}
|
|
79
|
-
onChange={(e) => setInput(e.target.value)}
|
|
80
|
-
placeholder={`Message ${po.name}...`}
|
|
81
|
-
className="flex-1 bg-po-surface border border-po-border rounded-lg px-4 py-2 text-white placeholder-gray-500 focus:outline-none focus:border-po-accent"
|
|
82
|
-
disabled={po.status !== 'idle'}
|
|
83
|
-
/>
|
|
84
|
-
<button
|
|
85
|
-
type="submit"
|
|
86
|
-
disabled={!input.trim() || po.status !== 'idle'}
|
|
87
|
-
className="px-4 py-2 bg-po-accent text-white rounded-lg font-medium hover:bg-po-accent/80 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
88
|
-
>
|
|
89
|
-
Send
|
|
90
|
-
</button>
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
{/* Thread toggle - only show when there are existing messages */}
|
|
94
|
-
{hasMessages && (
|
|
95
|
-
<div className="flex items-center gap-3 mt-2 text-xs">
|
|
96
|
-
<button
|
|
97
|
-
type="button"
|
|
98
|
-
onClick={() => setContinueThread(false)}
|
|
99
|
-
className={`px-2 py-1 rounded transition-colors ${
|
|
100
|
-
!continueThread
|
|
101
|
-
? 'bg-po-accent/20 text-po-accent border border-po-accent'
|
|
102
|
-
: 'text-gray-400 hover:text-white'
|
|
103
|
-
}`}
|
|
104
|
-
>
|
|
105
|
-
New thread
|
|
106
|
-
</button>
|
|
107
|
-
<button
|
|
108
|
-
type="button"
|
|
109
|
-
onClick={() => setContinueThread(true)}
|
|
110
|
-
className={`px-2 py-1 rounded transition-colors ${
|
|
111
|
-
continueThread
|
|
112
|
-
? 'bg-po-accent/20 text-po-accent border border-po-accent'
|
|
113
|
-
: 'text-gray-400 hover:text-white'
|
|
114
|
-
}`}
|
|
115
|
-
>
|
|
116
|
-
Continue thread
|
|
117
|
-
</button>
|
|
118
|
-
<span className="text-gray-500">
|
|
119
|
-
{continueThread
|
|
120
|
-
? 'Will add to current conversation'
|
|
121
|
-
: 'Will start a fresh conversation'}
|
|
122
|
-
</span>
|
|
123
|
-
</div>
|
|
124
|
-
)}
|
|
125
|
-
</form>
|
|
126
|
-
</div>
|
|
127
|
-
)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function MessageBubble({ message }: { message: Message }) {
|
|
131
|
-
const isUser = message.role === 'user'
|
|
132
|
-
const isAssistant = message.role === 'assistant'
|
|
133
|
-
const isTool = message.role === 'tool'
|
|
134
|
-
|
|
135
|
-
if (isTool) {
|
|
136
|
-
// Tool messages contain results from tool calls
|
|
137
|
-
const results = message.results || []
|
|
138
|
-
if (results.length === 0) return null
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<div className="space-y-2 ml-11">
|
|
142
|
-
{results.map((result, idx) => (
|
|
143
|
-
<ToolResultDisplay key={result.tool_call_id || idx} result={result} />
|
|
144
|
-
))}
|
|
145
|
-
</div>
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<div className={`flex gap-3 ${isUser ? 'flex-row-reverse' : ''}`}>
|
|
151
|
-
<div
|
|
152
|
-
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium flex-shrink-0 ${
|
|
153
|
-
isUser ? 'bg-po-border text-white' : 'bg-po-accent text-white'
|
|
154
|
-
}`}
|
|
155
|
-
>
|
|
156
|
-
{isUser ? 'You' : 'AI'}
|
|
157
|
-
</div>
|
|
158
|
-
<div
|
|
159
|
-
className={`flex-1 max-w-[80%] rounded-lg p-3 ${
|
|
160
|
-
isUser
|
|
161
|
-
? 'bg-po-accent text-white'
|
|
162
|
-
: 'bg-po-surface text-gray-200'
|
|
163
|
-
}`}
|
|
164
|
-
>
|
|
165
|
-
{message.content && (
|
|
166
|
-
isAssistant ? (
|
|
167
|
-
<MarkdownMessage content={message.content} />
|
|
168
|
-
) : (
|
|
169
|
-
<div className="whitespace-pre-wrap">{message.content}</div>
|
|
170
|
-
)
|
|
171
|
-
)}
|
|
172
|
-
|
|
173
|
-
{/* Tool calls */}
|
|
174
|
-
{isAssistant && message.tool_calls && message.tool_calls.length > 0 && (
|
|
175
|
-
<div className="mt-2 space-y-2">
|
|
176
|
-
{message.tool_calls.map((tc) => (
|
|
177
|
-
<ToolCallDisplay key={tc.id} toolCall={tc} />
|
|
178
|
-
))}
|
|
179
|
-
</div>
|
|
180
|
-
)}
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function ToolCallDisplay({ toolCall }: { toolCall: ToolCall }) {
|
|
187
|
-
const [expanded, setExpanded] = useState(false)
|
|
188
|
-
const { notifications } = useStore()
|
|
189
|
-
|
|
190
|
-
// Special display for ask_human
|
|
191
|
-
if (toolCall.name === 'ask_human') {
|
|
192
|
-
const question = toolCall.arguments.question as string
|
|
193
|
-
const options = toolCall.arguments.options as string[] | undefined
|
|
194
|
-
|
|
195
|
-
// Check if there's a pending notification for this (by matching question)
|
|
196
|
-
const isPending = notifications.some((n) => n.message === question)
|
|
197
|
-
|
|
198
|
-
return (
|
|
199
|
-
<div className="bg-po-warning/10 border border-po-warning/30 rounded-lg p-3">
|
|
200
|
-
<div className="flex items-center gap-2 mb-2">
|
|
201
|
-
<span className="text-po-warning text-sm font-medium">
|
|
202
|
-
Waiting for human input
|
|
203
|
-
</span>
|
|
204
|
-
{isPending ? (
|
|
205
|
-
<span className="text-xs bg-po-warning text-black px-2 py-0.5 rounded animate-pulse">
|
|
206
|
-
PENDING
|
|
207
|
-
</span>
|
|
208
|
-
) : (
|
|
209
|
-
<span className="text-xs bg-green-600 text-white px-2 py-0.5 rounded">
|
|
210
|
-
RESOLVED
|
|
211
|
-
</span>
|
|
212
|
-
)}
|
|
213
|
-
</div>
|
|
214
|
-
<p className="text-gray-200 text-sm mb-2">{question}</p>
|
|
215
|
-
{options && options.length > 0 && (
|
|
216
|
-
<div className="flex flex-wrap gap-2">
|
|
217
|
-
{options.map((opt, i) => (
|
|
218
|
-
<span
|
|
219
|
-
key={i}
|
|
220
|
-
className="text-xs bg-po-bg px-2 py-1 rounded text-gray-400"
|
|
221
|
-
>
|
|
222
|
-
{opt}
|
|
223
|
-
</span>
|
|
224
|
-
))}
|
|
225
|
-
</div>
|
|
226
|
-
)}
|
|
227
|
-
</div>
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Default display for other tool calls - expandable
|
|
232
|
-
return (
|
|
233
|
-
<div className="text-xs bg-po-bg/50 rounded overflow-hidden">
|
|
234
|
-
<button
|
|
235
|
-
onClick={() => setExpanded(!expanded)}
|
|
236
|
-
className="w-full px-2 py-1 text-left font-mono flex items-center gap-1 hover:bg-po-bg/70 transition-colors"
|
|
237
|
-
>
|
|
238
|
-
<span className="text-gray-500">{expanded ? '▼' : '▶'}</span>
|
|
239
|
-
<span className="text-po-accent">{toolCall.name}</span>
|
|
240
|
-
<span className="text-gray-500">
|
|
241
|
-
({Object.keys(toolCall.arguments).length} args)
|
|
242
|
-
</span>
|
|
243
|
-
</button>
|
|
244
|
-
{expanded && (
|
|
245
|
-
<div className="px-2 pb-2 border-t border-po-border/50">
|
|
246
|
-
<pre className="text-gray-400 whitespace-pre-wrap break-all mt-1 text-[10px] leading-relaxed">
|
|
247
|
-
{JSON.stringify(toolCall.arguments, null, 2)}
|
|
248
|
-
</pre>
|
|
249
|
-
</div>
|
|
250
|
-
)}
|
|
251
|
-
</div>
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function ToolResultDisplay({ result }: { result: { tool_call_id: string; name?: string; content: string } }) {
|
|
256
|
-
const [expanded, setExpanded] = useState(false)
|
|
257
|
-
const content = result.content || ''
|
|
258
|
-
const truncatedContent = content.slice(0, 200)
|
|
259
|
-
const isTruncated = content.length > 200
|
|
260
|
-
|
|
261
|
-
return (
|
|
262
|
-
<div className="text-xs bg-po-surface/50 border border-po-border/30 rounded overflow-hidden">
|
|
263
|
-
<button
|
|
264
|
-
onClick={() => setExpanded(!expanded)}
|
|
265
|
-
className="w-full px-2 py-1 text-left font-mono flex items-center gap-1 hover:bg-po-surface transition-colors"
|
|
266
|
-
>
|
|
267
|
-
<span className="text-gray-500">{expanded ? '▼' : '▶'}</span>
|
|
268
|
-
<span className="text-gray-400">↳ Result</span>
|
|
269
|
-
{result.name && <span className="text-po-accent/70">from {result.name}</span>}
|
|
270
|
-
</button>
|
|
271
|
-
{expanded && (
|
|
272
|
-
<div className="px-2 pb-2 border-t border-po-border/30">
|
|
273
|
-
<pre className="text-gray-400 whitespace-pre-wrap break-all mt-1 text-[10px] leading-relaxed">
|
|
274
|
-
{content}
|
|
275
|
-
</pre>
|
|
276
|
-
</div>
|
|
277
|
-
)}
|
|
278
|
-
{!expanded && (
|
|
279
|
-
<div className="px-2 pb-1 text-gray-500">
|
|
280
|
-
<span className="whitespace-pre-wrap break-all">
|
|
281
|
-
{truncatedContent}
|
|
282
|
-
{isTruncated && '...'}
|
|
283
|
-
</span>
|
|
284
|
-
</div>
|
|
285
|
-
)}
|
|
286
|
-
</div>
|
|
287
|
-
)
|
|
288
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { usePromptObjects, useStore, usePONotifications } from '../store'
|
|
2
|
-
import { POCard } from './POCard'
|
|
3
|
-
import type { PromptObject } from '../types'
|
|
4
|
-
|
|
5
|
-
interface DashboardProps {
|
|
6
|
-
compact?: boolean
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function Dashboard({ compact = false }: DashboardProps) {
|
|
10
|
-
const promptObjects = usePromptObjects()
|
|
11
|
-
|
|
12
|
-
if (promptObjects.length === 0) {
|
|
13
|
-
return (
|
|
14
|
-
<div className="h-full flex items-center justify-center text-gray-500">
|
|
15
|
-
<div className="text-center">
|
|
16
|
-
<div className="text-4xl mb-4">🔮</div>
|
|
17
|
-
<div className="text-lg">No Prompt Objects loaded</div>
|
|
18
|
-
<div className="text-sm mt-2">
|
|
19
|
-
Waiting for environment to connect...
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Compact mode: simple list for sidebar
|
|
27
|
-
if (compact) {
|
|
28
|
-
return (
|
|
29
|
-
<div className="p-2 space-y-1">
|
|
30
|
-
{promptObjects.map((po) => (
|
|
31
|
-
<CompactPOItem key={po.name} po={po} />
|
|
32
|
-
))}
|
|
33
|
-
</div>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Full dashboard view
|
|
38
|
-
return (
|
|
39
|
-
<div className="h-full overflow-auto p-6">
|
|
40
|
-
<h1 className="text-2xl font-semibold text-white mb-6">
|
|
41
|
-
Prompt Objects
|
|
42
|
-
</h1>
|
|
43
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
|
44
|
-
{promptObjects.map((po) => (
|
|
45
|
-
<POCard key={po.name} po={po} />
|
|
46
|
-
))}
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function CompactPOItem({ po }: { po: PromptObject }) {
|
|
53
|
-
const { selectPO, selectedPO } = useStore()
|
|
54
|
-
const notifications = usePONotifications(po.name)
|
|
55
|
-
const isSelected = selectedPO === po.name
|
|
56
|
-
|
|
57
|
-
const statusColors = {
|
|
58
|
-
idle: 'bg-gray-500',
|
|
59
|
-
thinking: 'bg-po-accent animate-pulse',
|
|
60
|
-
calling_tool: 'bg-po-warning animate-pulse',
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<button
|
|
65
|
-
onClick={() => selectPO(po.name)}
|
|
66
|
-
className={`w-full text-left px-3 py-2 rounded transition-colors flex items-center gap-2 ${
|
|
67
|
-
isSelected
|
|
68
|
-
? 'bg-po-accent/20 border border-po-accent'
|
|
69
|
-
: 'hover:bg-po-bg border border-transparent'
|
|
70
|
-
}`}
|
|
71
|
-
>
|
|
72
|
-
<div className={`w-2 h-2 rounded-full flex-shrink-0 ${statusColors[po.status]}`} />
|
|
73
|
-
<span className={`flex-1 truncate text-sm ${isSelected ? 'text-white' : 'text-gray-300'}`}>
|
|
74
|
-
{po.name}
|
|
75
|
-
</span>
|
|
76
|
-
{notifications.length > 0 && (
|
|
77
|
-
<span className="bg-po-warning text-black text-xs font-bold px-1.5 py-0.5 rounded-full">
|
|
78
|
-
{notifications.length}
|
|
79
|
-
</span>
|
|
80
|
-
)}
|
|
81
|
-
</button>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react'
|
|
2
|
-
import { useStore, useNotificationCount } from '../store'
|
|
3
|
-
import { ModelSelector } from './ModelSelector'
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
switchLLM: (provider: string, model?: string) => void
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function Header({ switchLLM }: Props) {
|
|
10
|
-
const { connected, environment, selectedPO, selectPO, toggleBus, busOpen, notifications } =
|
|
11
|
-
useStore()
|
|
12
|
-
const notificationCount = useNotificationCount()
|
|
13
|
-
const [showNotifications, setShowNotifications] = useState(false)
|
|
14
|
-
const [animate, setAnimate] = useState(false)
|
|
15
|
-
const prevCount = useRef(notificationCount)
|
|
16
|
-
|
|
17
|
-
// Animate badge when count increases
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (notificationCount > prevCount.current) {
|
|
20
|
-
setAnimate(true)
|
|
21
|
-
const timer = setTimeout(() => setAnimate(false), 500)
|
|
22
|
-
return () => clearTimeout(timer)
|
|
23
|
-
}
|
|
24
|
-
prevCount.current = notificationCount
|
|
25
|
-
}, [notificationCount])
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<header className="h-14 bg-po-surface border-b border-po-border flex items-center px-4 gap-4">
|
|
29
|
-
{/* Logo / Title */}
|
|
30
|
-
<button
|
|
31
|
-
onClick={() => selectPO(null)}
|
|
32
|
-
className="text-lg font-semibold text-white hover:text-po-accent transition-colors"
|
|
33
|
-
>
|
|
34
|
-
PromptObjects
|
|
35
|
-
</button>
|
|
36
|
-
|
|
37
|
-
{/* Environment info */}
|
|
38
|
-
{environment && (
|
|
39
|
-
<div className="text-sm text-gray-400">
|
|
40
|
-
<span className="text-gray-500">/</span>
|
|
41
|
-
<span className="ml-2">{environment.name}</span>
|
|
42
|
-
<span className="ml-3 text-gray-500">
|
|
43
|
-
{environment.po_count} POs, {environment.primitive_count} primitives
|
|
44
|
-
</span>
|
|
45
|
-
</div>
|
|
46
|
-
)}
|
|
47
|
-
|
|
48
|
-
{/* Breadcrumb for selected PO */}
|
|
49
|
-
{selectedPO && (
|
|
50
|
-
<div className="text-sm text-gray-400">
|
|
51
|
-
<span className="text-gray-500">/</span>
|
|
52
|
-
<span className="ml-2 text-po-accent">{selectedPO}</span>
|
|
53
|
-
</div>
|
|
54
|
-
)}
|
|
55
|
-
|
|
56
|
-
<div className="flex-1" />
|
|
57
|
-
|
|
58
|
-
{/* Model selector */}
|
|
59
|
-
<ModelSelector switchLLM={switchLLM} />
|
|
60
|
-
|
|
61
|
-
{/* Connection status */}
|
|
62
|
-
<div className="flex items-center gap-2 text-sm">
|
|
63
|
-
<div
|
|
64
|
-
className={`w-2 h-2 rounded-full ${
|
|
65
|
-
connected ? 'bg-green-500' : 'bg-red-500'
|
|
66
|
-
}`}
|
|
67
|
-
/>
|
|
68
|
-
<span className="text-gray-400">
|
|
69
|
-
{connected ? 'Connected' : 'Disconnected'}
|
|
70
|
-
</span>
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
{/* Notification bell with badge */}
|
|
74
|
-
<div className="relative">
|
|
75
|
-
<button
|
|
76
|
-
onClick={() => setShowNotifications(!showNotifications)}
|
|
77
|
-
className={`relative p-2 rounded transition-colors ${
|
|
78
|
-
notificationCount > 0
|
|
79
|
-
? 'text-po-warning hover:bg-po-warning/20'
|
|
80
|
-
: 'text-gray-400 hover:text-white hover:bg-po-border'
|
|
81
|
-
}`}
|
|
82
|
-
title={notificationCount > 0 ? `${notificationCount} pending requests` : 'No notifications'}
|
|
83
|
-
>
|
|
84
|
-
{/* Bell icon */}
|
|
85
|
-
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
86
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
|
87
|
-
</svg>
|
|
88
|
-
{/* Badge */}
|
|
89
|
-
{notificationCount > 0 && (
|
|
90
|
-
<span
|
|
91
|
-
className={`absolute -top-1 -right-1 bg-po-warning text-black text-xs font-bold w-5 h-5 flex items-center justify-center rounded-full ${
|
|
92
|
-
animate ? 'animate-bounce' : ''
|
|
93
|
-
}`}
|
|
94
|
-
>
|
|
95
|
-
{notificationCount}
|
|
96
|
-
</span>
|
|
97
|
-
)}
|
|
98
|
-
</button>
|
|
99
|
-
|
|
100
|
-
{/* Dropdown with notification summaries */}
|
|
101
|
-
{showNotifications && notifications.length > 0 && (
|
|
102
|
-
<div className="absolute right-0 top-full mt-2 w-80 bg-po-surface border border-po-border rounded-lg shadow-xl z-50">
|
|
103
|
-
<div className="p-3 border-b border-po-border">
|
|
104
|
-
<h3 className="font-medium text-white">Pending Requests</h3>
|
|
105
|
-
</div>
|
|
106
|
-
<div className="max-h-64 overflow-auto">
|
|
107
|
-
{notifications.map((n) => (
|
|
108
|
-
<div key={n.id} className="p-3 border-b border-po-border last:border-0 hover:bg-po-bg">
|
|
109
|
-
<div className="flex items-center gap-2 mb-1">
|
|
110
|
-
<span className="text-xs bg-po-warning text-black px-1.5 py-0.5 rounded font-medium">
|
|
111
|
-
{n.type}
|
|
112
|
-
</span>
|
|
113
|
-
<span className="text-xs text-po-accent">{n.po_name}</span>
|
|
114
|
-
</div>
|
|
115
|
-
<p className="text-sm text-gray-300 line-clamp-2">{n.message}</p>
|
|
116
|
-
</div>
|
|
117
|
-
))}
|
|
118
|
-
</div>
|
|
119
|
-
<div className="p-2 border-t border-po-border">
|
|
120
|
-
<p className="text-xs text-gray-500 text-center">
|
|
121
|
-
Respond in the notification panel below
|
|
122
|
-
</p>
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
)}
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
{/* Message Bus toggle */}
|
|
129
|
-
<button
|
|
130
|
-
onClick={toggleBus}
|
|
131
|
-
className={`px-3 py-1.5 text-sm rounded transition-colors ${
|
|
132
|
-
busOpen
|
|
133
|
-
? 'bg-po-accent text-white'
|
|
134
|
-
: 'bg-po-border text-gray-300 hover:bg-po-accent/50'
|
|
135
|
-
}`}
|
|
136
|
-
>
|
|
137
|
-
Bus
|
|
138
|
-
</button>
|
|
139
|
-
</header>
|
|
140
|
-
)
|
|
141
|
-
}
|