Skip to content

Commit ad2a080

Browse files
fix(dashboards): emit SSE chat event so greetings are not treated as builds
Out-of-context messages like "hi" were correctly classified as chat-only, but still arrived on the done event. The workspace done handler always appends the canned "Done — built…" line, so greetings looked like rebuilds.
1 parent 3ed7d82 commit ad2a080

5 files changed

Lines changed: 39 additions & 9 deletions

File tree

backend/src/main/java/com/dbaagent/controller/DashboardGenerationController.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@
2828
* <ul>
2929
* <li>{@code POST /api/dashboards/generate} — blocking; returns the validated config.</li>
3030
* <li>{@code POST /api/dashboards/generate/stream} — SSE; streams the agent's live
31-
* steps ({@code step} events: grounding → planning → validating) then a {@code done}
32-
* event with the final config (or an {@code error} event).</li>
31+
* steps ({@code step} events: grounding → planning → validating) then either a
32+
* {@code chat} event (out-of-context reply) or a {@code done} event with the
33+
* artifact config (or an {@code error} event).</li>
3334
* </ul>
3435
*
3536
* 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) {
101102
throw new ClientGoneException(io);
102103
}
103104
});
104-
emitter.send(SseEmitter.event().name("done")
105-
.data(Map.of("success", true, "dashboardConfig", config)));
105+
// Chat-only replies (greetings / tool questions) must not share the
106+
// `done` event with a real artifact — the FE's done handler always
107+
// appends "Done — built…" and auto-saves. A dedicated `chat` event
108+
// keeps that path from swallowing out-of-context messages.
109+
if (Boolean.TRUE.equals(config.get("chat"))) {
110+
emitter.send(SseEmitter.event().name("chat")
111+
.data(Map.of(
112+
"success", true,
113+
"reply", String.valueOf(config.getOrDefault("reply", "")),
114+
"dashboardConfig", config)));
115+
} else {
116+
emitter.send(SseEmitter.event().name("done")
117+
.data(Map.of("success", true, "dashboardConfig", config)));
118+
}
106119
emitter.complete();
107120
} catch (ClientGoneException gone) {
108121
emitter.complete();

backend/src/main/java/com/dbaagent/service/DashboardAgentService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ public Map<String, Object> generate(String connectionId, String prompt, Object c
9797
chat.put("chat", true);
9898
chat.put("reply", chatReply.text().trim());
9999
chat.put("trace", trace);
100+
log.info("Dashboard chat-only reply ({} chars) for prompt: {}",
101+
chat.get("reply").toString().length(),
102+
prompt == null ? "" : prompt.trim());
100103
return chat;
101104
}
102105
// Ambiguous or the agent didn't just answer — fall through to a real build

src/components/sections/DashboardWorkspace.jsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,15 @@ export default function DashboardWorkspace({ connectionId, dashboard, onClose })
135135
},
136136
onDone: (next) => {
137137
abortRef.current = null
138-
setConfig(next)
139138
setThinking(false)
140139
setSteps([])
140+
// Belt-and-braces: a chat-shaped payload must never hit the "built" path
141+
// (that appends the canned save line and would clobber a real artifact).
142+
if (!next?.html || next?.chat) {
143+
setMessages((m) => [...m, { role: 'agent', text: next?.reply || '…' }])
144+
return
145+
}
146+
setConfig(next)
141147
// Auto-save as a draft so a refresh never loses it (create first time, update after).
142148
const updated = [...messagesRef.current, { role: 'agent', text: 'Done — built and verified against your data. Saved as a draft — tell me what to change.' }]
143149
setMessages(updated)

src/lib/api/client.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2724,6 +2724,9 @@ export const dashboardGenAPI = {
27242724
let data = {};
27252725
try { data = JSON.parse(dataLines.join("\n")); } catch { return; }
27262726
if (event === "step") onStep && onStep(data);
2727+
// `chat` = out-of-context reply (hi/thanks); must not fall through to `done`
2728+
// or the workspace appends the canned "Done — built…" save message.
2729+
else if (event === "chat") { finished = true; onDone && onDone(data); }
27272730
else if (event === "done") { finished = true; onDone && onDone(data); }
27282731
else if (event === "error") { finished = true; onError && onError(new Error(data?.error || "Generation failed")); }
27292732
};

src/lib/dashboardGenerator.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@ export function generateDashboardStream(connectionId, prompt, currentConfig, { o
1515
return dashboardGenAPI.generateStream(connectionId, prompt, currentConfig, {
1616
onStep,
1717
onDone: (data) => {
18-
if (data?.dashboardConfig?.chat) {
19-
onChat && onChat(data.dashboardConfig.reply || '')
18+
// Chat-only can arrive as event:chat (reply at top level) or legacy done
19+
// with dashboardConfig.chat=true. Treat either as a plain reply — never as
20+
// a successful build (that path hardcodes "Done — built…" in the UI).
21+
const cfg = data?.dashboardConfig
22+
const chatReply = (typeof data?.reply === 'string' && data.reply) || cfg?.reply || ''
23+
if (data?.chat === true || cfg?.chat === true || (chatReply && !cfg?.html && !cfg?.renderMode)) {
24+
onChat && onChat(chatReply)
2025
return
2126
}
22-
if (!data?.dashboardConfig) {
27+
if (!cfg) {
2328
onError && onError(new Error(data?.error || 'Generation returned no dashboard.'))
2429
return
2530
}
26-
onDone && onDone({ ...data.dashboardConfig, updatedAt: new Date().toISOString() })
31+
onDone && onDone({ ...cfg, updatedAt: new Date().toISOString() })
2732
},
2833
onError,
2934
})

0 commit comments

Comments
 (0)