diff --git a/backend/src/main/java/com/dbaagent/controller/DashboardGenerationController.java b/backend/src/main/java/com/dbaagent/controller/DashboardGenerationController.java index 5824c57..0ce3faf 100644 --- a/backend/src/main/java/com/dbaagent/controller/DashboardGenerationController.java +++ b/backend/src/main/java/com/dbaagent/controller/DashboardGenerationController.java @@ -28,8 +28,9 @@ * * * Read-only: generates a config and validates queries by running them read-only; it never @@ -101,8 +102,20 @@ public SseEmitter generateStream(@RequestBody GenerateRequest request) { throw new ClientGoneException(io); } }); - emitter.send(SseEmitter.event().name("done") - .data(Map.of("success", true, "dashboardConfig", config))); + // Chat-only replies (greetings / tool questions) must not share the + // `done` event with a real artifact — the FE's done handler always + // appends "Done — built…" and auto-saves. A dedicated `chat` event + // keeps that path from swallowing out-of-context messages. + if (Boolean.TRUE.equals(config.get("chat"))) { + emitter.send(SseEmitter.event().name("chat") + .data(Map.of( + "success", true, + "reply", String.valueOf(config.getOrDefault("reply", "")), + "dashboardConfig", config))); + } else { + emitter.send(SseEmitter.event().name("done") + .data(Map.of("success", true, "dashboardConfig", config))); + } emitter.complete(); } catch (ClientGoneException gone) { emitter.complete(); diff --git a/backend/src/main/java/com/dbaagent/service/DashboardAgentService.java b/backend/src/main/java/com/dbaagent/service/DashboardAgentService.java index 47ac0e9..15e0c05 100644 --- a/backend/src/main/java/com/dbaagent/service/DashboardAgentService.java +++ b/backend/src/main/java/com/dbaagent/service/DashboardAgentService.java @@ -97,6 +97,9 @@ public Map generate(String connectionId, String prompt, Object c chat.put("chat", true); chat.put("reply", chatReply.text().trim()); chat.put("trace", trace); + log.info("Dashboard chat-only reply ({} chars) for prompt: {}", + chat.get("reply").toString().length(), + prompt == null ? "" : prompt.trim()); return chat; } // Ambiguous or the agent didn't just answer — fall through to a real build diff --git a/src/components/sections/DashboardWorkspace.jsx b/src/components/sections/DashboardWorkspace.jsx index 40964de..e1ac508 100644 --- a/src/components/sections/DashboardWorkspace.jsx +++ b/src/components/sections/DashboardWorkspace.jsx @@ -135,9 +135,15 @@ export default function DashboardWorkspace({ connectionId, dashboard, onClose }) }, onDone: (next) => { abortRef.current = null - setConfig(next) setThinking(false) setSteps([]) + // Belt-and-braces: a chat-shaped payload must never hit the "built" path + // (that appends the canned save line and would clobber a real artifact). + if (!next?.html || next?.chat) { + setMessages((m) => [...m, { role: 'agent', text: next?.reply || '…' }]) + return + } + setConfig(next) // Auto-save as a draft so a refresh never loses it (create first time, update after). const updated = [...messagesRef.current, { role: 'agent', text: 'Done — built and verified against your data. Saved as a draft — tell me what to change.' }] setMessages(updated) diff --git a/src/lib/api/client.js b/src/lib/api/client.js index d70424f..c1e0663 100644 --- a/src/lib/api/client.js +++ b/src/lib/api/client.js @@ -2724,6 +2724,9 @@ export const dashboardGenAPI = { let data = {}; try { data = JSON.parse(dataLines.join("\n")); } catch { return; } if (event === "step") onStep && onStep(data); + // `chat` = out-of-context reply (hi/thanks); must not fall through to `done` + // or the workspace appends the canned "Done — built…" save message. + else if (event === "chat") { finished = true; onDone && onDone(data); } else if (event === "done") { finished = true; onDone && onDone(data); } else if (event === "error") { finished = true; onError && onError(new Error(data?.error || "Generation failed")); } }; diff --git a/src/lib/dashboardGenerator.js b/src/lib/dashboardGenerator.js index b4166d9..53bbaa5 100644 --- a/src/lib/dashboardGenerator.js +++ b/src/lib/dashboardGenerator.js @@ -15,15 +15,20 @@ export function generateDashboardStream(connectionId, prompt, currentConfig, { o return dashboardGenAPI.generateStream(connectionId, prompt, currentConfig, { onStep, onDone: (data) => { - if (data?.dashboardConfig?.chat) { - onChat && onChat(data.dashboardConfig.reply || '') + // Chat-only can arrive as event:chat (reply at top level) or legacy done + // with dashboardConfig.chat=true. Treat either as a plain reply — never as + // a successful build (that path hardcodes "Done — built…" in the UI). + const cfg = data?.dashboardConfig + const chatReply = (typeof data?.reply === 'string' && data.reply) || cfg?.reply || '' + if (data?.chat === true || cfg?.chat === true || (chatReply && !cfg?.html && !cfg?.renderMode)) { + onChat && onChat(chatReply) return } - if (!data?.dashboardConfig) { + if (!cfg) { onError && onError(new Error(data?.error || 'Generation returned no dashboard.')) return } - onDone && onDone({ ...data.dashboardConfig, updatedAt: new Date().toISOString() }) + onDone && onDone({ ...cfg, updatedAt: new Date().toISOString() }) }, onError, })