hyperclaw 5.2.2 → 5.2.3

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.
@@ -0,0 +1,213 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HyperClaw Chat</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
9
+ <style>
10
+ body{background:#0f172a;color:#e2e8f0;font-family:system-ui}
11
+ .msg-user{background:linear-gradient(135deg,#0891b2 0%,#06b6d4 100%)}
12
+ .msg-asst{background:#1e293b;border:1px solid #334155}
13
+ .typing span{animation:dot 1.4s infinite}
14
+ .typing span:nth-child(2){animation-delay:.2s}
15
+ .typing span:nth-child(3){animation-delay:.4s}
16
+ @keyframes dot{0%,80%,100%{opacity:.3}40%{opacity:1}}
17
+ .prose pre{background:#0f172a;padding:.75rem;border-radius:6px;overflow-x:auto}
18
+ .prose code{background:#1e293b;padding:.1em .3em;border-radius:4px;font-size:.9em}
19
+ </style>
20
+ </head>
21
+ <body class="min-h-screen flex flex-col">
22
+ <header class="p-4 border-b border-slate-700 flex items-center justify-between flex-wrap gap-2">
23
+ <div class="flex items-center gap-3">
24
+ <span class="text-2xl">🦅</span>
25
+ <div>
26
+ <h1 class="text-xl font-bold text-cyan-400">HyperClaw Chat</h1>
27
+ <p id="status" class="text-sm text-slate-400">Connecting...</p>
28
+ </div>
29
+ </div>
30
+ <div class="flex items-center gap-2 text-sm">
31
+ <span id="ws-dot" class="w-2 h-2 rounded-full bg-slate-500"></span>
32
+ <span id="ws-status">WebSocket</span>
33
+ <span id="agent-info" class="text-slate-500"></span>
34
+ </div>
35
+ </header>
36
+ <main class="flex-1 overflow-y-auto p-4 space-y-4" id="messages"></main>
37
+ <footer class="p-4 border-t border-slate-700">
38
+ <form id="form" class="flex gap-2 items-end">
39
+ <div class="flex-1">
40
+ <textarea id="input" rows="1" placeholder="Message HyperClaw..."
41
+ class="w-full bg-slate-800 border border-slate-600 rounded-xl px-4 py-3 text-white resize-none focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:border-transparent"
42
+ style="min-height:48px;max-height:120px" autocomplete="off"></textarea>
43
+ </div>
44
+ <button type="submit" id="send-btn" class="bg-cyan-600 hover:bg-cyan-700 disabled:opacity-50 px-5 py-3 rounded-xl font-medium h-12">
45
+
46
+ </button>
47
+ </form>
48
+ </footer>
49
+ <script>
50
+ const port = location.port || 18789;
51
+ const wsUrl = `ws://${location.hostname}:${port}`;
52
+ const apiUrl = `http://${location.hostname}:${port}`;
53
+ const messagesEl = document.getElementById('messages');
54
+ const statusEl = document.getElementById('status');
55
+ const wsDot = document.getElementById('ws-dot');
56
+ const wsStatus = document.getElementById('ws-status');
57
+ const agentInfo = document.getElementById('agent-info');
58
+ const form = document.getElementById('form');
59
+ const input = document.getElementById('input');
60
+ const sendBtn = document.getElementById('send-btn');
61
+
62
+ let ws = null;
63
+ let isStreaming = false;
64
+
65
+ function setWsConnected(ok) {
66
+ wsDot.className = 'w-2 h-2 rounded-full ' + (ok ? 'bg-cyan-400 animate-pulse' : 'bg-red-500');
67
+ wsStatus.textContent = ok ? 'Connected' : 'Disconnected';
68
+ }
69
+
70
+ function addMsg(role, content, streaming = false) {
71
+ const div = document.createElement('div');
72
+ div.className = 'flex gap-3 ' + (role === 'user' ? 'flex-row-reverse' : '');
73
+ const bubble = document.createElement('div');
74
+ bubble.className = 'max-w-[85%] rounded-2xl px-4 py-3 ' + (role === 'user' ? 'msg-user text-slate-900' : 'msg-asst');
75
+ if (role === 'assistant' && !streaming) {
76
+ bubble.innerHTML = '<div class="prose prose-invert prose-sm max-w-none">' + marked.parse(content || '') + '</div>';
77
+ } else {
78
+ bubble.textContent = content || '';
79
+ }
80
+ if (role === 'assistant') {
81
+ const icon = document.createElement('div');
82
+ icon.className = 'w-8 h-8 rounded-full bg-cyan-900/30 flex items-center justify-center flex-shrink-0';
83
+ icon.textContent = '🦅';
84
+ div.appendChild(icon);
85
+ }
86
+ div.appendChild(bubble);
87
+ div.dataset.streaming = streaming ? '1' : '0';
88
+ messagesEl.appendChild(div);
89
+ messagesEl.scrollTop = messagesEl.scrollHeight;
90
+ return bubble;
91
+ }
92
+
93
+ function addTyping() {
94
+ const div = document.createElement('div');
95
+ div.className = 'flex gap-3';
96
+ div.id = 'typing-indicator';
97
+ div.innerHTML = '<div class="w-8 h-8 rounded-full bg-cyan-900/30 flex items-center justify-center">🦅</div><div class="msg-asst rounded-2xl px-4 py-3"><span class="typing"><span>.</span><span>.</span><span>.</span></span></div>';
98
+ messagesEl.appendChild(div);
99
+ messagesEl.scrollTop = messagesEl.scrollHeight;
100
+ }
101
+
102
+ function removeTyping() {
103
+ document.getElementById('typing-indicator')?.remove();
104
+ }
105
+
106
+ function connectWs() {
107
+ ws = new WebSocket(wsUrl);
108
+ ws.onopen = () => {
109
+ setWsConnected(true);
110
+ statusEl.textContent = 'Connected';
111
+ };
112
+ ws.onclose = () => {
113
+ setWsConnected(false);
114
+ statusEl.textContent = 'Disconnected — reconnecting…';
115
+ setTimeout(connectWs, 3000);
116
+ };
117
+ ws.onerror = () => setWsConnected(false);
118
+ ws.onmessage = (e) => {
119
+ try {
120
+ const msg = JSON.parse(e.data);
121
+ if (msg.type === 'connect.ok' || msg.type === 'auth.ok') {
122
+ statusEl.textContent = 'Ready';
123
+ } else if (msg.type === 'chat:chunk') {
124
+ const last = messagesEl.querySelector('[data-streaming="1"]');
125
+ if (last) {
126
+ last.textContent = (last.textContent || '') + (msg.content || '');
127
+ } else {
128
+ removeTyping();
129
+ const bubble = addMsg('assistant', msg.content || '', true);
130
+ bubble.dataset.streaming = '1';
131
+ }
132
+ messagesEl.scrollTop = messagesEl.scrollHeight;
133
+ } else if (msg.type === 'chat:response') {
134
+ removeTyping();
135
+ const last = messagesEl.querySelector('[data-streaming="1"]');
136
+ if (last) {
137
+ last.dataset.streaming = '0';
138
+ last.innerHTML = '<div class="prose prose-invert prose-sm max-w-none">' + marked.parse(last.textContent || msg.content || '') + '</div>';
139
+ } else {
140
+ addMsg('assistant', msg.content || '');
141
+ }
142
+ isStreaming = false;
143
+ sendBtn.disabled = false;
144
+ messagesEl.scrollTop = messagesEl.scrollHeight;
145
+ } else if (msg.type === 'error') {
146
+ removeTyping();
147
+ addMsg('assistant', 'Error: ' + (msg.message || 'Unknown'));
148
+ isStreaming = false;
149
+ sendBtn.disabled = false;
150
+ }
151
+ } catch (_) {}
152
+ };
153
+ }
154
+
155
+ function sendViaWs(text) {
156
+ if (!ws || ws.readyState !== WebSocket.OPEN) return false;
157
+ ws.send(JSON.stringify({ type: 'chat:message', content: text, source: 'webchat' }));
158
+ return true;
159
+ }
160
+
161
+ form.onsubmit = async (e) => {
162
+ e.preventDefault();
163
+ const msg = input.value.trim();
164
+ if (!msg || isStreaming) return;
165
+ addMsg('user', msg);
166
+ input.value = '';
167
+ input.style.height = 'auto';
168
+ sendBtn.disabled = true;
169
+ isStreaming = true;
170
+ addTyping();
171
+
172
+ if (sendViaWs(msg)) {
173
+ // WebSocket handles response
174
+ } else {
175
+ // Fallback to REST
176
+ removeTyping();
177
+ statusEl.textContent = 'Thinking...';
178
+ try {
179
+ const res = await fetch(apiUrl + '/api/chat', {
180
+ method: 'POST',
181
+ headers: { 'Content-Type': 'application/json' },
182
+ body: JSON.stringify({ message: msg })
183
+ });
184
+ const data = await res.json();
185
+ addMsg('assistant', data.response || data.error || '(no response)');
186
+ statusEl.textContent = 'Ready';
187
+ } catch (err) {
188
+ addMsg('assistant', 'Error: ' + err.message);
189
+ statusEl.textContent = 'Error';
190
+ }
191
+ isStreaming = false;
192
+ sendBtn.disabled = false;
193
+ }
194
+ };
195
+
196
+ input.addEventListener('input', () => {
197
+ input.style.height = 'auto';
198
+ input.style.height = Math.min(input.scrollHeight, 120) + 'px';
199
+ });
200
+ input.addEventListener('keydown', (e) => {
201
+ if (e.key === 'Enter' && !e.shiftKey) {
202
+ e.preventDefault();
203
+ form.dispatchEvent(new Event('submit'));
204
+ }
205
+ });
206
+
207
+ connectWs();
208
+ fetch(apiUrl + '/api/status').then(r => r.json()).then(d => {
209
+ agentInfo.textContent = d.agentName ? `${d.agentName} • ${d.model || ''}` : '';
210
+ }).catch(() => {});
211
+ </script>
212
+ </body>
213
+ </html>
@@ -0,0 +1,33 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HyperClaw Dashboard</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>body{background:#0f172a;color:#e2e8f0;font-family:system-ui}</style>
9
+ </head>
10
+ <body class="min-h-screen p-8">
11
+ <h1 class="text-2xl font-bold text-cyan-400 mb-6">🦅 HyperClaw Dashboard</h1>
12
+ <div id="content" class="grid gap-4 md:grid-cols-2 max-w-4xl">
13
+ <div class="bg-slate-800 rounded-lg p-4">
14
+ <h2 class="text-lg font-semibold text-cyan-300 mb-2">Gateway</h2>
15
+ <pre id="status" class="text-sm text-slate-300 overflow-auto">Loading...</pre>
16
+ </div>
17
+ <div class="bg-slate-800 rounded-lg p-4">
18
+ <h2 class="text-lg font-semibold text-cyan-300 mb-2">Quick links</h2>
19
+ <ul class="space-y-2 text-cyan-400">
20
+ <li><a href="/chat" class="hover:underline">/chat</a> — Web chat</li>
21
+ <li><a href="/api/status" class="hover:underline">/api/status</a> — API status</li>
22
+ </ul>
23
+ </div>
24
+ </div>
25
+ <script>
26
+ const port = location.port || 18789;
27
+ const apiUrl = `http://${location.hostname}:${port}`;
28
+ fetch(apiUrl + '/api/status').then(r => r.json()).then(d => {
29
+ document.getElementById('status').textContent = JSON.stringify(d, null, 2);
30
+ }).catch(() => document.getElementById('status').textContent = 'Gateway unreachable');
31
+ </script>
32
+ </body>
33
+ </html>