|
164 | 164 | }); |
165 | 165 |
|
166 | 166 | // --- Chat --- |
167 | | - // Chat calls go through the CronControl backend proxy (no API key in frontend) |
| 167 | + // Proxy URL: use app.croncontrol.dev in prod, localhost in dev |
| 168 | + const CHAT_API = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' |
| 169 | + ? 'http://localhost:8090/api/v1/chat/simulate' |
| 170 | + : 'https://app.croncontrol.dev/api/v1/chat/simulate'; |
168 | 171 | let chatHistory: { role: string; content: string }[] = []; |
169 | 172 | let chatAbort: AbortController | null = null; |
| 173 | + let chatBusy = false; |
170 | 174 |
|
171 | 175 | function openChat(idx: number) { |
172 | 176 | chatNodeIdx = idx; |
|
177 | 181 | chatDot.style.backgroundColor = info.statusColor; |
178 | 182 | chatMessages.innerHTML = ''; |
179 | 183 | chatHistory = []; |
| 184 | + chatBusy = false; |
180 | 185 | tooltip.style.opacity = '0'; |
181 | 186 |
|
182 | | - // Initial exchange: Director asks the node for status |
| 187 | + // Initial Director message |
183 | 188 | const directorMsg = idx === 0 |
184 | | - ? 'Orchestra "product-cleanup" is active. 3 AgentNodes running, 2 idle. Awaiting validator results before proceeding.' |
185 | | - : `Director → ${info.name}: Report your current status.`; |
| 189 | + ? 'Orchestra "product-cleanup" is active. 3 AgentNodes running, 2 idle. Awaiting validator results.' |
| 190 | + : `${info.name}, report your current status.`; |
186 | 191 |
|
187 | 192 | addMessage('director', directorMsg); |
188 | 193 |
|
189 | | - // Call Groq for the node's response |
| 194 | + // Set up system context and get first response |
190 | 195 | chatHistory = [ |
191 | | - { role: 'system', content: info.context + ' You are in a live orchestra chat. The AI Director just asked for your status. Respond naturally as this agent would.' }, |
| 196 | + { role: 'system', content: info.context + ' You are in a live orchestra chat. The AI Director just asked for your status. A human operator may also join the conversation. Respond concisely in 1-2 sentences.' }, |
192 | 197 | { role: 'user', content: directorMsg }, |
193 | 198 | ]; |
194 | 199 | callGroq(); |
|
198 | 203 | chatOpen = false; |
199 | 204 | chatPanel.style.display = 'none'; |
200 | 205 | chatNodeIdx = -1; |
| 206 | + chatBusy = false; |
201 | 207 | if (chatAbort) { chatAbort.abort(); chatAbort = null; } |
202 | 208 | } |
203 | 209 |
|
|
224 | 230 | function esc(s: string) { return s.replace(/</g, '<').replace(/>/g, '>'); } |
225 | 231 |
|
226 | 232 | async function callGroq() { |
| 233 | + if (chatBusy) return; |
| 234 | + chatBusy = true; |
227 | 235 | if (chatAbort) chatAbort.abort(); |
228 | 236 | chatAbort = new AbortController(); |
229 | 237 |
|
230 | | - // Show typing indicator |
231 | 238 | const typing = document.createElement('div'); |
232 | 239 | typing.className = 'flex gap-2 items-center'; |
233 | | - typing.id = 'typing'; |
234 | 240 | typing.innerHTML = `<span class="shrink-0 w-5 h-5 rounded-full bg-emerald-500/20 flex items-center justify-center"><span class="text-[8px] font-bold text-emerald-400">A</span></span><span class="text-zinc-500 text-[10px]">typing...</span>`; |
235 | 241 | chatMessages.appendChild(typing); |
236 | 242 | chatMessages.scrollTop = chatMessages.scrollHeight; |
237 | 243 |
|
238 | 244 | try { |
239 | | - const res = await fetch('https://app.croncontrol.dev/api/v1/chat/simulate', { |
| 245 | + const res = await fetch(CHAT_API, { |
240 | 246 | method: 'POST', |
241 | 247 | headers: { 'Content-Type': 'application/json' }, |
242 | 248 | body: JSON.stringify({ messages: chatHistory, max_tokens: 150 }), |
|
247 | 253 | const reply = data.choices?.[0]?.message?.content || 'No response.'; |
248 | 254 | addMessage('agent', reply); |
249 | 255 | chatHistory.push({ role: 'assistant', content: reply }); |
250 | | - |
251 | | - // After first response, Director follows up after a delay |
252 | | - if (chatHistory.length <= 3) { |
253 | | - setTimeout(() => { |
254 | | - if (!chatOpen || chatNodeIdx < 0) return; |
255 | | - const followUp = chatNodeIdx === 0 |
256 | | - ? 'Checking budget: 62% used. 3 movements remaining within limits.' |
257 | | - : `Director → ${nodeInfo[chatNodeIdx].name}: Acknowledged. Proceed with current task. Report when complete.`; |
258 | | - addMessage('director', followUp); |
259 | | - chatHistory.push({ role: 'user', content: followUp }); |
260 | | - callGroq(); |
261 | | - }, 2000); |
262 | | - } else if (chatHistory.length <= 5) { |
263 | | - // One more exchange |
264 | | - setTimeout(() => { |
265 | | - if (!chatOpen || chatNodeIdx < 0) return; |
266 | | - addMessage('system', 'Movement #4 completed successfully.'); |
267 | | - }, 2500); |
268 | | - } |
269 | 256 | } catch (e: any) { |
270 | 257 | typing.remove(); |
271 | | - if (e.name !== 'AbortError') addMessage('system', 'Connection error.'); |
| 258 | + if (e.name !== 'AbortError') addMessage('system', 'Connection error. Is the backend running?'); |
| 259 | + } finally { |
| 260 | + chatBusy = false; |
272 | 261 | } |
273 | 262 | } |
274 | 263 |
|
|
0 commit comments