agents-dojo 0.1.6 → 0.1.7
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.
- package/monitor/.env.development +1 -0
- package/monitor/.env.production +1 -0
- package/monitor/ANIMATION_REQUIREMENTS.md +118 -0
- package/monitor/index.html +12 -0
- package/monitor/package-lock.json +2160 -0
- package/monitor/package.json +25 -0
- package/monitor/public/bg.png +0 -0
- package/monitor/public/bg_clean.png +0 -0
- package/monitor/public/positions.json +215 -0
- package/monitor/public/sprites/agent_default.png +0 -0
- package/monitor/public/sprites/cushion_0.png +0 -0
- package/monitor/public/sprites/cushion_1.png +0 -0
- package/monitor/public/sprites/cushion_10.png +0 -0
- package/monitor/public/sprites/cushion_2.png +0 -0
- package/monitor/public/sprites/cushion_3.png +0 -0
- package/monitor/public/sprites/cushion_4.png +0 -0
- package/monitor/public/sprites/cushion_5.png +0 -0
- package/monitor/public/sprites/cushion_6.png +0 -0
- package/monitor/public/sprites/cushion_7.png +0 -0
- package/monitor/public/sprites/cushion_8.png +0 -0
- package/monitor/public/sprites/cushion_9.png +0 -0
- package/monitor/public/sprites/master.png +0 -0
- package/monitor/public/sprites/stake_0.png +0 -0
- package/monitor/public/sprites/stake_1.png +0 -0
- package/monitor/public/sprites/stake_10.png +0 -0
- package/monitor/public/sprites/stake_2.png +0 -0
- package/monitor/public/sprites/stake_3.png +0 -0
- package/monitor/public/sprites/stake_4.png +0 -0
- package/monitor/public/sprites/stake_5.png +0 -0
- package/monitor/public/sprites/stake_6.png +0 -0
- package/monitor/public/sprites/stake_7.png +0 -0
- package/monitor/public/sprites/stake_8.png +0 -0
- package/monitor/public/sprites/stake_9.png +0 -0
- package/monitor/scripts/record-gif.py +53 -0
- package/monitor/src/App.tsx +22 -0
- package/monitor/src/components/AgentMenu.tsx +67 -0
- package/monitor/src/components/ChatPanel.tsx +214 -0
- package/monitor/src/components/LogPage.tsx +173 -0
- package/monitor/src/components/Stage.tsx +39 -0
- package/monitor/src/components/StatusBar.tsx +50 -0
- package/monitor/src/lib/dojo-app.ts +799 -0
- package/monitor/src/lib/interactables.ts +162 -0
- package/monitor/src/lib/store.ts +352 -0
- package/monitor/src/lib/types.ts +72 -0
- package/monitor/src/lib/ws-client.ts +66 -0
- package/monitor/src/main.tsx +9 -0
- package/monitor/src/vite-env.d.ts +1 -0
- package/monitor/tsconfig.json +14 -0
- package/monitor/vite.config.ts +13 -0
- package/package.json +2 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import React, { useEffect, useState, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
const API_BASE = 'http://localhost:41241';
|
|
4
|
+
|
|
5
|
+
interface ChatMessage {
|
|
6
|
+
timestamp: string;
|
|
7
|
+
taskId: string;
|
|
8
|
+
from: string;
|
|
9
|
+
to: string;
|
|
10
|
+
content: string;
|
|
11
|
+
direction: 'incoming' | 'outgoing';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const PALETTE = ['#4fc3f7', '#81c784', '#ffb74d', '#ef5350', '#ab47bc', '#26a69a', '#5c6bc0', '#ec407a', '#ffa726', '#78909c'];
|
|
15
|
+
const AVATAR_COLORS: Record<string, string> = {};
|
|
16
|
+
function getColor(name: string): string {
|
|
17
|
+
if (!AVATAR_COLORS[name]) {
|
|
18
|
+
AVATAR_COLORS[name] = PALETTE[Object.keys(AVATAR_COLORS).length % PALETTE.length];
|
|
19
|
+
}
|
|
20
|
+
return AVATAR_COLORS[name];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function displayName(name: string): string {
|
|
24
|
+
return name === 'user' ? 'Client' : name.charAt(0).toUpperCase() + name.slice(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function LogPage({ onBack }: { onBack: () => void }) {
|
|
28
|
+
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
29
|
+
const [dates, setDates] = useState<string[]>([]);
|
|
30
|
+
const [selectedDate, setSelectedDate] = useState<string>('');
|
|
31
|
+
const [loading, setLoading] = useState(true);
|
|
32
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
fetch(`${API_BASE}/logs/dates`).then(r => r.json()).then(d => setDates(d.dates || [])).catch(() => {});
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
setLoading(true);
|
|
40
|
+
const url = selectedDate ? `${API_BASE}/logs?date=${selectedDate}` : `${API_BASE}/logs`;
|
|
41
|
+
fetch(url).then(r => r.json()).then(d => { setMessages(d.entries || []); setLoading(false); }).catch(() => setLoading(false));
|
|
42
|
+
}, [selectedDate]);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const iv = setInterval(() => {
|
|
46
|
+
const url = selectedDate ? `${API_BASE}/logs?date=${selectedDate}` : `${API_BASE}/logs`;
|
|
47
|
+
fetch(url).then(r => r.json()).then(d => setMessages(d.entries || [])).catch(() => {});
|
|
48
|
+
}, 3000);
|
|
49
|
+
return () => clearInterval(iv);
|
|
50
|
+
}, [selectedDate]);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
54
|
+
}, [messages.length]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div style={{
|
|
58
|
+
width: '100vw', height: '100vh', background: '#ededed',
|
|
59
|
+
display: 'flex', flexDirection: 'column',
|
|
60
|
+
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
|
|
61
|
+
}}>
|
|
62
|
+
{/* Header */}
|
|
63
|
+
<div style={{
|
|
64
|
+
padding: '10px 16px', background: '#075e54', color: 'white',
|
|
65
|
+
display: 'flex', alignItems: 'center', gap: 12,
|
|
66
|
+
boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
|
|
67
|
+
}}>
|
|
68
|
+
<button onClick={onBack} style={{
|
|
69
|
+
background: 'none', border: 'none', color: 'white',
|
|
70
|
+
fontSize: 18, cursor: 'pointer', padding: '0 4px',
|
|
71
|
+
}}>←</button>
|
|
72
|
+
<div style={{ flex: 1 }}>
|
|
73
|
+
<div style={{ fontWeight: 600, fontSize: 16 }}>AgentsDojo Chat Log</div>
|
|
74
|
+
<div style={{ fontSize: 11, opacity: 0.8 }}>{messages.length} messages</div>
|
|
75
|
+
</div>
|
|
76
|
+
<select
|
|
77
|
+
value={selectedDate}
|
|
78
|
+
onChange={(e) => setSelectedDate(e.target.value)}
|
|
79
|
+
style={{
|
|
80
|
+
background: 'rgba(255,255,255,0.15)', color: 'white',
|
|
81
|
+
border: '1px solid rgba(255,255,255,0.3)', borderRadius: 4,
|
|
82
|
+
padding: '4px 8px', fontSize: 12,
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<option value="">All dates</option>
|
|
86
|
+
{dates.map(d => <option key={d} value={d}>{d}</option>)}
|
|
87
|
+
</select>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{/* Messages — group chat style, all left-aligned */}
|
|
91
|
+
<div ref={scrollRef} style={{
|
|
92
|
+
flex: 1, overflow: 'auto', padding: '12px 16px',
|
|
93
|
+
backgroundImage: 'url("data:image/svg+xml,%3Csvg width=\'60\' height=\'60\' viewBox=\'0 0 60 60\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cg fill=\'none\' fill-rule=\'evenodd\'%3E%3Cg fill=\'%23d4d4d4\' fill-opacity=\'0.15\'%3E%3Cpath d=\'M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z\'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")',
|
|
94
|
+
}}>
|
|
95
|
+
{loading && <div style={{ textAlign: 'center', color: '#888', padding: 40 }}>Loading...</div>}
|
|
96
|
+
{!loading && messages.length === 0 && (
|
|
97
|
+
<div style={{ textAlign: 'center', color: '#888', padding: 40, fontSize: 14 }}>
|
|
98
|
+
No conversations yet. Send a message to an agent to see chat logs here.
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
{messages.map((msg, i) => {
|
|
102
|
+
const prevMsg = messages[i - 1];
|
|
103
|
+
const showDate = !prevMsg || msg.timestamp.slice(0, 10) !== prevMsg.timestamp.slice(0, 10);
|
|
104
|
+
const sameSender = prevMsg && prevMsg.from === msg.from && prevMsg.taskId === msg.taskId;
|
|
105
|
+
|
|
106
|
+
const sender = msg.from;
|
|
107
|
+
const senderDisplay = displayName(sender);
|
|
108
|
+
const receiver = displayName(msg.to);
|
|
109
|
+
const color = getColor(sender);
|
|
110
|
+
const isClient = sender === 'user';
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<React.Fragment key={`${msg.taskId}-${i}`}>
|
|
114
|
+
{showDate && (
|
|
115
|
+
<div style={{ textAlign: 'center', margin: '16px 0 8px' }}>
|
|
116
|
+
<span style={{
|
|
117
|
+
background: '#d4eaf7', color: '#5a5a5a', padding: '4px 12px',
|
|
118
|
+
borderRadius: 8, fontSize: 11,
|
|
119
|
+
}}>{new Date(msg.timestamp).toLocaleDateString()}</span>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
<div style={{
|
|
123
|
+
display: 'flex', gap: 8, marginBottom: sameSender ? 2 : 8,
|
|
124
|
+
alignItems: 'flex-start',
|
|
125
|
+
flexDirection: isClient ? 'row-reverse' : 'row',
|
|
126
|
+
}}>
|
|
127
|
+
{/* Avatar */}
|
|
128
|
+
<div style={{
|
|
129
|
+
width: 36, height: 36, borderRadius: 4, flexShrink: 0,
|
|
130
|
+
background: sameSender ? 'transparent' : (isClient ? '#607d8b' : color),
|
|
131
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
132
|
+
color: 'white', fontSize: 12, fontWeight: 700,
|
|
133
|
+
visibility: sameSender ? 'hidden' : 'visible',
|
|
134
|
+
}}>
|
|
135
|
+
{isClient ? '📱' : senderDisplay.slice(0, 2)}
|
|
136
|
+
</div>
|
|
137
|
+
{/* Bubble */}
|
|
138
|
+
<div style={{ maxWidth: '75%' }}>
|
|
139
|
+
{!sameSender && (
|
|
140
|
+
<div style={{
|
|
141
|
+
fontSize: 11, color, fontWeight: 600, marginBottom: 2,
|
|
142
|
+
textAlign: isClient ? 'right' : 'left',
|
|
143
|
+
}}>
|
|
144
|
+
{senderDisplay}
|
|
145
|
+
<span style={{ color: '#999', fontWeight: 400 }}> → {receiver}</span>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
<div style={{
|
|
149
|
+
background: isClient ? '#dcf8c6' : 'white',
|
|
150
|
+
borderRadius: 8,
|
|
151
|
+
borderTopLeftRadius: !isClient && !sameSender ? 0 : 8,
|
|
152
|
+
borderTopRightRadius: isClient && !sameSender ? 0 : 8,
|
|
153
|
+
padding: '8px 12px',
|
|
154
|
+
boxShadow: '0 1px 1px rgba(0,0,0,0.08)',
|
|
155
|
+
fontSize: 13, lineHeight: 1.5, color: '#303030',
|
|
156
|
+
wordBreak: 'break-word',
|
|
157
|
+
borderLeft: isClient ? 'none' : `3px solid ${color}`,
|
|
158
|
+
borderRight: isClient ? `3px solid #4caf50` : 'none',
|
|
159
|
+
}}>
|
|
160
|
+
{msg.content}
|
|
161
|
+
<div style={{ fontSize: 10, color: '#aaa', textAlign: 'right', marginTop: 4 }}>
|
|
162
|
+
{new Date(msg.timestamp).toLocaleTimeString()}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</React.Fragment>
|
|
168
|
+
);
|
|
169
|
+
})}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { useMonitorStore } from '../lib/store.js';
|
|
3
|
+
import { connectWs } from '../lib/ws-client.js';
|
|
4
|
+
import { DojoApp } from '../lib/dojo-app.js';
|
|
5
|
+
import { StatusBar } from './StatusBar.js';
|
|
6
|
+
import { AgentMenu } from './AgentMenu.js';
|
|
7
|
+
import { ChatPanel } from './ChatPanel.js';
|
|
8
|
+
|
|
9
|
+
export function Stage({ onShowLogs }: { onShowLogs?: () => void }) {
|
|
10
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
11
|
+
const appRef = useRef<DojoApp | null>(null);
|
|
12
|
+
|
|
13
|
+
// Connect WebSocket
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const close = connectWs();
|
|
16
|
+
return close;
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
// Init Pixi app (once)
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!canvasRef.current || appRef.current) return;
|
|
22
|
+
const dojoApp = new DojoApp(canvasRef.current);
|
|
23
|
+
appRef.current = dojoApp;
|
|
24
|
+
dojoApp.init().catch(console.error);
|
|
25
|
+
return () => {
|
|
26
|
+
try { dojoApp.destroy(); } catch {}
|
|
27
|
+
appRef.current = null;
|
|
28
|
+
};
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<canvas ref={canvasRef} style={{ width: '100vw', height: '100vh', display: 'block' }} />
|
|
34
|
+
<StatusBar onShowLogs={onShowLogs} />
|
|
35
|
+
<AgentMenu />
|
|
36
|
+
<ChatPanel />
|
|
37
|
+
</>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useMonitorStore } from '../lib/store.js';
|
|
3
|
+
|
|
4
|
+
export function StatusBar({ onShowLogs }: { onShowLogs?: () => void }) {
|
|
5
|
+
const connected = useMonitorStore((s) => s.connected);
|
|
6
|
+
const agentCount = useMonitorStore((s) => s.agents.size);
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
style={{
|
|
11
|
+
position: 'absolute',
|
|
12
|
+
top: 0,
|
|
13
|
+
left: 0,
|
|
14
|
+
right: 0,
|
|
15
|
+
padding: '8px 16px',
|
|
16
|
+
background: 'rgba(90, 60, 30, 0.75)',
|
|
17
|
+
color: '#f5e6d0',
|
|
18
|
+
fontFamily: 'monospace',
|
|
19
|
+
fontSize: 12,
|
|
20
|
+
display: 'flex',
|
|
21
|
+
alignItems: 'center',
|
|
22
|
+
gap: 16,
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<span>AgentsDojo Monitor</span>
|
|
26
|
+
<span style={{ color: connected ? '#a3be8c' : '#bf616a' }}>
|
|
27
|
+
{connected ? '● connected' : '○ disconnected'}
|
|
28
|
+
</span>
|
|
29
|
+
<span>Agents: {agentCount}</span>
|
|
30
|
+
<span style={{ flex: 1 }} />
|
|
31
|
+
{onShowLogs && (
|
|
32
|
+
<button
|
|
33
|
+
onClick={onShowLogs}
|
|
34
|
+
style={{
|
|
35
|
+
background: '#5a3c1e',
|
|
36
|
+
border: '1px solid #8b6914',
|
|
37
|
+
borderRadius: 4,
|
|
38
|
+
color: '#f5e6d0',
|
|
39
|
+
padding: '3px 10px',
|
|
40
|
+
cursor: 'pointer',
|
|
41
|
+
fontFamily: 'monospace',
|
|
42
|
+
fontSize: 11,
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
Logs →
|
|
46
|
+
</button>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|