Skip to content
Open
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
1 change: 0 additions & 1 deletion apps/bubble-studio/src/hooks/usePearlChatStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,6 @@ export function handleStreamingEvent(
case 'coffee_complete':
case 'heartbeat':
case 'stream_complete':
// Ignore these events - they're control signals, not display events
break;
}
}
Expand Down
5 changes: 5 additions & 0 deletions apps/bubble-studio/src/hooks/usePearlStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ async function startGenerationStream(
`[startGenerationStream] Stream completed successfully for flow ${flowId}`
);
pearlStore.getState().setIsCoffeeLoading(false);
// Safety net: if no generation_complete/error event was received, clear the
// generating state so the UI doesn't stay stuck in a loading loop.
if (!pearlStore.getState().generationCompleted) {
pearlStore.getState().cancelGenerationStream();
}
return;
} catch (error) {
if (abortController.signal.aborted) {
Expand Down
10 changes: 7 additions & 3 deletions apps/bubblelab-api/src/routes/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,13 @@ app.openapi(pearlRoute, async (c) => {
await stream.writeSSE({
data: JSON.stringify({
type: 'error',
error:
error instanceof Error ? error.message : 'Unknown streaming error',
recoverable: false,
data: {
error:
error instanceof Error
? error.message
: 'Unknown streaming error',
recoverable: false,
},
}),
event: 'error',
});
Expand Down
41 changes: 31 additions & 10 deletions apps/bubblelab-api/src/routes/bubble-flows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,11 +508,13 @@ app.openapi(executeBubbleFlowStreamRoute, async (c) => {
await stream.writeSSE({
data: JSON.stringify({
type: 'error',
error:
error instanceof Error
? error.message
: 'Unknown streaming error',
recoverable: false,
data: {
error:
error instanceof Error
? error.message
: 'Unknown streaming error',
recoverable: false,
},
}),
event: 'error',
});
Expand Down Expand Up @@ -1416,6 +1418,23 @@ app.openapi(generateBubbleFlowCodeRoute, async (c) => {
// Clear heartbeat and send stream completion
clearInterval(heartbeatInterval);

// Emit a proper error event when coffee fails (e.g. missing API key)
// before stream_complete so the frontend error handler picks it up.
if (!coffeeResult.success) {
await stream.writeSSE({
data: JSON.stringify({
type: 'error',
data: {
error:
coffeeResult.error ||
'Flow generation failed. Please check your API key configuration.',
recoverable: false,
},
}),
event: 'error',
});
}

await stream.writeSSE({
data: JSON.stringify({
type: 'stream_complete',
Expand Down Expand Up @@ -1690,11 +1709,13 @@ app.openapi(generateBubbleFlowCodeRoute, async (c) => {
await stream.writeSSE({
data: JSON.stringify({
type: 'error',
error:
error instanceof Error
? error.message
: 'Unknown streaming error',
recoverable: false,
data: {
error:
error instanceof Error
? error.message
: 'Unknown streaming error',
recoverable: false,
},
}),
event: 'error',
});
Expand Down