Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
* <ul>
* <li>{@code POST /api/dashboards/generate} — blocking; returns the validated config.</li>
* <li>{@code POST /api/dashboards/generate/stream} — SSE; streams the agent's live
* steps ({@code step} events: grounding → planning → validating) then a {@code done}
* event with the final config (or an {@code error} event).</li>
* steps ({@code step} events: grounding → planning → validating) then either a
* {@code chat} event (out-of-context reply) or a {@code done} event with the
* artifact config (or an {@code error} event).</li>
* </ul>
*
* Read-only: generates a config and validates queries by running them read-only; it never
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ public Map<String, Object> 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
Expand Down
8 changes: 7 additions & 1 deletion src/components/sections/DashboardWorkspace.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions src/lib/api/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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")); }
};
Expand Down
13 changes: 9 additions & 4 deletions src/lib/dashboardGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down
Loading