Store full databunny conversation.#106
Conversation
|
@sbansal1999 is attempting to deploy a commit to the Databuddy Team on Vercel. A member of the Team first needs to authorize it. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughCentralizes StreamingUpdate type into shared package, updates imports across API and dashboard. Adds metadata streaming (conversationId, messageId) and persists AI messageId through orchestrator and repository. Tightens session model typing. Refactors dashboard useChat to accumulate assistant message across SSE and track conversationId. Minor typing adjustment in AI service. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant UI as ChatSection/useChat (Dashboard)
participant Route as POST /assistant (API)
participant Proc as processAssistantRequest
participant Orch as AssistantOrchestrator
participant AI as AI Service
participant RP as ResponseProcessor
participant Repo as ConversationRepository
UI->>Route: Send message (includes conversationId if any)
Route->>Proc: processAssistantRequest(payload)
Proc->>Orch: orchestrate(session, userMsg)
Orch->>AI: generateResponse(session, userMsg)
AI-->>Orch: AIResponseContent
note over Orch: Create aiMessageId
Orch->>UI: SSE update: { type: 'metadata', data: { conversationId, messageId } }
Orch->>RP: process(AIResponseContent)
RP-->>Orch: [thinking/progress/complete...] updates
Orch->>UI: SSE stream of thinking/progress updates
Orch->>Repo: saveConversation(session, aiResponse, aiMessageId, finalResult, metrics)
Repo-->>Orch: Promise<void>
Orch->>UI: SSE update: { type: 'complete', ... }
alt Error
Orch->>UI: SSE update: { type: 'error', content/debugInfo }
Orch->>Repo: saveErrorConversation(session, error, messageId)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/api/src/agent/core/conversation-repository.ts (1)
40-41: Unify ID format across messages.User messages use createId('NANOID') while assistant messages use aiMessageId (created via createId() upstream). Mixed formats can complicate indexing and cross-system joins.
Apply this diff to standardize on createId() for user messages:
- id: createId('NANOID'), + id: createId(),apps/api/src/agent/core/assistant-orchestrator.ts (2)
65-76: Guard against saving with a non-terminal finalResult.finalResult = streamingUpdates.at(-1) could be 'metadata' if processing yields no additional updates, leading to assistant content = undefined. Pick the last 'complete' or 'error' update instead; otherwise skip persistence and log.
- const finalResult = streamingUpdates.at(-1); - if (finalResult) { + const finalResult = + [...streamingUpdates].reverse().find((u) => u.type === 'complete' || u.type === 'error'); + if (finalResult) { const metrics = session.finalize(); // Save asynchronously but handle errors this.saveConversationAsync( session, aiResponse.content, aiMessageId, finalResult, metrics ); }
35-44: Persist “empty content” errors for full auditability.When aiResponse.content is falsy, you return an error but don’t persist the exchange. For consistency with catch-path errors, persist it with metadata too.
- if (!aiResponse.content) { - session.log('AI response was empty'); - return [ - { - type: 'error', - content: - "I'm having trouble understanding that request. Could you try asking in a different way?", - }, - ]; - } + if (!aiResponse.content) { + session.log('AI response was empty'); + const aiMessageId = createId(); + const metrics = session.finalize(); + const errorUpdate: StreamingUpdate = { + type: 'error', + content: + "I'm having trouble understanding that request. Could you try asking in a different way?", + }; + // Save asynchronously + this.saveErrorConversationAsync(session, new Error('Empty AI content'), errorUpdate, metrics); + // Return metadata followed by error so UI can bind IDs + return [ + { + type: 'metadata', + data: { + conversationId: session.getContext().conversationId, + messageId: aiMessageId, + }, + }, + errorUpdate, + ]; + }apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.ts (1)
181-209: Rate-limit handling returns early and drops the user-facing message.You set assistantMessage to a rate-limit warning, but return immediately. The message never gets appended or persisted. Also, throwing after handling 429 would overwrite the warning.
- if (!response.ok) { - // Handle rate limit specifically - if (response.status === 429) { - const errorData = await response.json(); - if (errorData.code === 'RATE_LIMIT_EXCEEDED') { - assistantMessage = { - ...assistantMessage, - content: - "⏱️ You've reached the rate limit. Please wait 60 seconds before sending another message.", - }; - setIsLoading(false); - setIsRateLimited(true); - // Clear any existing timeout - if (rateLimitTimeoutRef.current) { - clearTimeout(rateLimitTimeoutRef.current); - } - // Set a 60-second timeout to re-enable messaging - rateLimitTimeoutRef.current = setTimeout(() => { - setIsRateLimited(false); - }, 60_000); - return; - } - } - throw new Error('Failed to start stream'); - } + if (!response.ok) { + // Handle rate limit specifically + if (response.status === 429) { + const errorData = await response.json(); + if (errorData.code === 'RATE_LIMIT_EXCEEDED') { + assistantMessage = { + ...assistantMessage, + content: + "⏱️ You've reached the rate limit. Please wait 60 seconds before sending another message.", + }; + setIsRateLimited(true); + // Clear any existing timeout + if (rateLimitTimeoutRef.current) { + clearTimeout(rateLimitTimeoutRef.current); + } + // Set a 60-second timeout to re-enable messaging + rateLimitTimeoutRef.current = setTimeout(() => { + setIsRateLimited(false); + }, 60_000); + } else { + throw new Error('Failed to start stream'); + } + } else { + throw new Error('Failed to start stream'); + } + } else { + const reader = response.body?.getReader(); + if (!reader) { + throw new Error('No response stream available'); + } + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + // ... existing SSE parsing logic ... +``` Note: The rest of the function stays the same; this preserves the warning message and still appends/persists it at the end. </blockquote></details> </blockquote></details>🧹 Nitpick comments (17)
packages/shared/src/types/assistant.ts (3)
5-18: Tighten StreamingUpdate typing; avoidanyand broaden compile-time checks.Good centralization. You can make this safer without changing runtime by:
- Replacing
any[]withunknown[].- Using
Record<string, unknown>fordebugInfo.This preserves flexibility but prevents accidental unsafe usage.
export type StreamingUpdate = | { type: 'thinking' | 'progress' | 'complete' | 'error'; content: string; data?: { hasVisualization?: boolean; chartType?: string; - data?: any[]; + data?: unknown[]; responseType?: 'chart' | 'text' | 'metric'; metricValue?: string | number; metricLabel?: string; }; - debugInfo?: Record<string, any>; + debugInfo?: Record<string, unknown>; }If you want stronger guarantees next, split variant-specific payloads (TextCompleteUpdate | MetricCompleteUpdate | ChartCompleteUpdate) so fields like
metricValueare required only whenresponseType: 'metric'.
20-24: UseaiMessageIdto disambiguate from user message IDs.The PR narrative introduces “aiMessageId”. Here the metadata uses a generic
messageId. Consider renaming toaiMessageIdfor clarity and to match the new backend flow. This is a breaking rename and will require updates in orchestrator, repository, SSE producers, and dashboard hook.Would you like me to scan the repo for all
messageIdconsumers in streaming to estimate the blast radius?
1-2: Prefer readable discriminants over “short codes”.Short codes save bytes but hurt debuggability and DX. Keep human-readable string unions in types; compress at the wire level (gzip/brotli) if payload size is a concern.
apps/api/src/agent/core/ai-service.ts (1)
14-14: Optional: MakeAI_MODELconfigurable via env with a safe default.Model swaps then don’t require code changes, while still honoring the “app-mode vs. model” separation.
// outside changed range; for reference only const AI_MODEL = process.env.OPENROUTER_MODEL?.trim() || 'google/gemini-2.5-flash-lite-preview-06-17';apps/api/src/agent/handlers/metric-handler.ts (2)
18-27: Preserve error context when falling back to default metric.You currently swallow the query error and silently fall back to
metric_value. Attach minimal debug info to the update (it’s already typed) so QA/dev can diagnose, while keeping UX unchanged.try { - const queryResult = await executeQuery(parsedAiJson.sql); - const metricValue = extractMetricValue( - queryResult.data, - parsedAiJson.metric_value - ); + const queryResult = await executeQuery(parsedAiJson.sql); + const metricValue = extractMetricValue( + queryResult?.data ?? [], + parsedAiJson.metric_value + ); return sendMetricResponse(parsedAiJson, metricValue); - } catch { - return sendMetricResponse(parsedAiJson, parsedAiJson.metric_value); + } catch (err) { + const base = sendMetricResponse(parsedAiJson, parsedAiJson.metric_value); + return { + ...base, + debugInfo: { + fallback: true, + reason: 'query_failed', + error: err instanceof Error ? err.message : String(err), + }, + }; }
53-56: Stabilize numeric formatting across environments.toLocaleString() depends on server locale. Use Intl with an explicit locale or push formatting to the client (you already include raw
metricValueindata).- const formattedValue = - typeof metricValue === 'number' - ? metricValue.toLocaleString() - : metricValue; + const formattedValue = + typeof metricValue === 'number' + ? new Intl.NumberFormat('en-US').format(metricValue) + : metricValue;apps/api/src/agent/core/response-processor.ts (1)
46-80: Make the switch exhaustive to catch future response types at compile time.Replace the generic
defaultwith an exhaustive check to surface missing cases when newresponse_typevalues are added.// outside selected lines; helper function assertNever(x: never): never { throw new Error(`Unexpected response_type: ${x as unknown as string}`); } // within the switch: default: { // Prefer compile-time safety: assertNever(response.response_type as never); }apps/api/src/agent/core/assistant-session.ts (1)
42-43: Prefer nullish coalescing (??) over || for defaultsUsing || treats empty strings as absent; ?? is semantically correct for “undefined/null fallback.” Apply to both conversationId and model.
- conversationId: request.conversationId || createId(), - model: request.model || 'chat', + conversationId: request.conversationId ?? createId(), + model: request.model ?? 'chat',apps/api/src/agent/utils/stream-utils.ts (2)
3-3: Allow true streaming by accepting Iterable or AsyncIterableThis enables the orchestrator to yield updates as they’re produced (no pre-buffering). The existing loop already uses “for await,” so both sync arrays and async iterables will work.
-export function createStreamingResponse(updates: StreamingUpdate[]): Response { +export function createStreamingResponse( + updates: Iterable<StreamingUpdate> | AsyncIterable<StreamingUpdate> +): Response {
7-12: Emit metadata and first event immediately; avoid artificial delay for themSends conversationId/messageId to clients ASAP and removes the initial 200ms stall, improving perceived responsiveness and reducing race conditions on the dashboard when persisting aiMessageId.
- for await (const update of updates) { - const data = `data: ${JSON.stringify(update)}\n\n`; - await new Promise((resolve) => setTimeout(resolve, 200)); - controller.enqueue(new TextEncoder().encode(data)); - } + let isFirst = true; + const delayMs = 200; + for await (const update of updates) { + const data = `data: ${JSON.stringify(update)}\n\n`; + if (!(isFirst || update.type === 'metadata')) { + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } + controller.enqueue(new TextEncoder().encode(data)); + isFirst = false; + }apps/api/src/agent/core/conversation-repository.ts (4)
21-24: Accept externally generated AI message ID (rename for consistency?).Passing aiMessageId down to the repository is the right direction. Consider renaming the parameter to messageId here and in callers for symmetry with AssistantOrchestrator.saveConversationAsync.
129-143: Reduce duplication: insert messages in bulk.You can insert both messages in one call. It’s simpler and more efficient.
- if (messages.length === 1) { - const only = messages[0]; - if (only) { - await tx.insert(assistantMessages).values(only); - } - } else if (messages.length > 1) { - const first = messages[0]; - const second = messages[1]; - if (first) { - await tx.insert(assistantMessages).values(first); - } - if (second) { - await tx.insert(assistantMessages).values(second); - } - } + if (messages.length > 0) { + await tx.insert(assistantMessages).values(messages); + }
151-166: Same bulk-insert simplification applies here.- if (messages.length === 1) { - const only = messages[0]; - if (only) { - await tx.insert(assistantMessages).values(only); - } - } else if (messages.length > 1) { - const first = messages[0]; - const second = messages[1]; - if (first) { - await tx.insert(assistantMessages).values(first); - } - if (second) { - await tx.insert(assistantMessages).values(second); - } - } + if (messages.length > 0) { + await tx.insert(assistantMessages).values(messages); + }
169-172: Prefer Date over ISO string for timestamp columns.If assistantConversations.updatedAt is a timestamp, pass a Date instance to match driver typing.
- .set({ updatedAt: new Date().toISOString() }) + .set({ updatedAt: new Date() })apps/dashboard/app/(main)/websites/[id]/assistant/components/chat-section.tsx (2)
88-99: Hook adoption looks clean; drop unused members to avoid noise.You destructure handleUpVote and handleDownVote but don’t use them in this component.
const { messages, inputValue, setInputValue, isLoading, isRateLimited, sendMessage, scrollToBottom, resetChat, - handleUpVote, - handleDownVote, } = useChat();
185-193: Optional: mirror input focus behavior for quick suggestions.After sending a quick suggestion you scroll, but don’t refocus the textarea like handleSend does. Consider refocusing for consistent UX.
onClick={(suggestion) => { if (!(isLoading || isRateLimited)) { sendMessage(suggestion); scrollToBottom(); + setTimeout(() => inputRef.current?.focus(), 100); } }}apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.ts (1)
246-253: Tighten types to avoid ‘as any’.chartType is cast as any. Prefer aligning Message.chartType with the shared union to maintain type safety.
- chartType: update.data?.chartType as any, + chartType: update.data?.chartType,If Message doesn’t yet support the shared union, extend it accordingly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (14)
apps/api/src/agent/core/ai-service.ts(1 hunks)apps/api/src/agent/core/assistant-orchestrator.ts(6 hunks)apps/api/src/agent/core/assistant-session.ts(1 hunks)apps/api/src/agent/core/conversation-repository.ts(3 hunks)apps/api/src/agent/core/response-processor.ts(1 hunks)apps/api/src/agent/handlers/chart-handler.ts(1 hunks)apps/api/src/agent/handlers/metric-handler.ts(1 hunks)apps/api/src/agent/processor.ts(1 hunks)apps/api/src/agent/utils/stream-utils.ts(2 hunks)apps/api/src/routes/assistant.ts(1 hunks)apps/dashboard/app/(main)/websites/[id]/assistant/components/chat-section.tsx(2 hunks)apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.ts(8 hunks)packages/shared/src/types/assistant.ts(1 hunks)packages/shared/src/types/index.ts(1 hunks)🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-17T08:13:57.191Z
Learnt from: sbansal1999 PR: databuddy-analytics/Databuddy#92 File: apps/api/src/agent/utils/ai-client.ts:40-41 Timestamp: 2025-08-17T08:13:57.191Z Learning: In apps/api/src/agent/utils/ai-client.ts, the `request.model` field represents application-level modes ('chat' | 'agent' | 'agent-max') used for customizing system prompts, not actual AI model identifiers. The hardcoded `AI_MODEL` constant should be used for the OpenRouter API call to specify the actual LLM model.Applied to files:
apps/api/src/agent/core/ai-service.tsapps/api/src/agent/core/assistant-session.ts🔇 Additional comments (17)
apps/api/src/agent/core/ai-service.ts (1)
41-41: LGTM: Passing the strictly-typedcontext.modelimproves type safety.This aligns with the learning that app-level modes (
'chat' | 'agent' | 'agent-max') are distinct from the actual LLM identifier (AI_MODEL). Nice cleanup.packages/shared/src/types/index.ts (1)
14-14: Re-export looks good.Centralizing the assistant types in the shared barrel will simplify imports across API and dashboard.
apps/api/src/agent/handlers/metric-handler.ts (1)
1-1: ImportingStreamingUpdatefrom shared is the right direction.Consistent typing across server and client reduces drift and mismatches.
apps/api/src/agent/core/response-processor.ts (1)
5-5: Good: unified StreamingUpdate import.This keeps response shaping consistent with the shared contract (including the new
metadatavariant used upstream).apps/api/src/agent/handlers/chart-handler.ts (1)
1-1: Centralized StreamingUpdate import from shared: LGTMType-only import from @databuddy/shared aligns this handler with the unified StreamingUpdate definition and avoids runtime overhead.
apps/api/src/agent/processor.ts (1)
2-2: Unifying types (StreamingUpdate, Website) via @databuddy/shared: LGTMKeeps the API layer consistent with shared contracts without altering runtime behavior.
apps/api/src/agent/core/assistant-session.ts (1)
24-24: Tightened SessionContext.model to a strict union: LGTMMatches the “prompt mode” semantics noted previously for request.model and prevents accidental leakage of raw model identifiers.
apps/api/src/routes/assistant.ts (1)
2-2: Shared StreamingUpdate type import: LGTMRoute types now align with the shared contract used across the stack.
apps/api/src/agent/utils/stream-utils.ts (2)
1-1: Switch to shared StreamingUpdate: LGTMRemoves local duplication and keeps SSE payloads in sync with the shared union.
39-45: generateThinkingSteps aligns with shared union: LGTMSimple, readable construction of thinking updates.
apps/api/src/agent/core/conversation-repository.ts (3)
7-7: Type import alignment looks good.Switching StreamingUpdate to the shared package keeps API/FE in sync. No issues.
39-45: Confirm last message role assumption.userMsg.content uses messages.at(-1)?.content. This assumes the last session message is always the triggering user message. Please confirm AssistantSession never appends an assistant message before saveConversation is called; otherwise content could be wrong.
65-85: Assistant message mapping looks correct.
- Uses caller-provided aiMessageId.
- Maps sql/chart_type/response_type/text/thinking consistently.
- Error flag/message derived from finalResult.type.
apps/api/src/agent/core/assistant-orchestrator.ts (1)
46-55: Nice: metadata event precedes content.Prepending conversation/message IDs as metadata enables the UI to correlate the streamed content reliably.
apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.ts (3)
1-1: Shared StreamingUpdate type import is correct.This keeps FE/BE in lockstep and avoids drift.
42-43: Conversation ID state is a good addition.Passing conversationId in the request and updating it from metadata enables proper threading on the backend.
355-362: Reset clears conversationId: good.Clearing conversationId on reset is correct, ensuring a fresh thread on next message.
| const aiMessageId = createId(); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Use consistent ID generator across success and error flows.
aiMessageId uses createId(), while error flow (Line 138) uses createId('NANOID'). Choose one format for stability and analytics.
- const aiMessageId = createId();
+ const aiMessageId = createId(); // keep thisAnd update error flow (see separate comment) to use createId() as well.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const aiMessageId = createId(); | |
| const aiMessageId = createId(); // keep this |
🤖 Prompt for AI Agents
In apps/api/src/agent/core/assistant-orchestrator.ts around lines 33-34 (and
update the error flow at line 138), the success path generates aiMessageId with
createId() while the error path uses createId('NANOID'); make both paths use the
same createId() call for consistent ID format and analytics stability, replacing
createId('NANOID') in the error flow with createId(), and verify any downstream
code/typing that expects the ID shape still functions.
| const messageId = createId('NANOID'); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Align error message ID generation with success path.
Unify on createId() to keep ID shapes identical across flows.
- const messageId = createId('NANOID');
+ const messageId = createId();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const messageId = createId('NANOID'); | |
| const messageId = createId(); |
🤖 Prompt for AI Agents
In apps/api/src/agent/core/assistant-orchestrator.ts around lines 138-139, the
error-path uses createId('NANOID') which produces a different ID shape than the
success path; change the call to createId() so both success and error flows
generate identical ID shapes, and confirm any imports/types still align with the
default createId signature.
| let assistantMessage: Message = { | ||
| id: '', | ||
| type: 'assistant', | ||
| content: '', | ||
| timestamp: new Date(), | ||
| hasVisualization: false, | ||
| thinkingSteps: [], | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Initialize assistant message with a stable temporary ID.
Starting with id: '' risks duplicate/empty keys if metadata never arrives (aborted stream, network errors). Use a temp ID and replace it upon metadata.
- let assistantMessage: Message = {
- id: '',
+ let assistantMessage: Message = {
+ id: `temp-${Date.now()}`,
type: 'assistant',
content: '',
timestamp: new Date(),
hasVisualization: false,
thinkingSteps: [],
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let assistantMessage: Message = { | |
| id: '', | |
| type: 'assistant', | |
| content: '', | |
| timestamp: new Date(), | |
| hasVisualization: false, | |
| thinkingSteps: [], | |
| }; | |
| let assistantMessage: Message = { | |
| id: `temp-${Date.now()}`, | |
| type: 'assistant', | |
| content: '', | |
| timestamp: new Date(), | |
| hasVisualization: false, | |
| thinkingSteps: [], | |
| }; |
🤖 Prompt for AI Agents
In apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.ts around
lines 149–156, the assistant message is initialized with id: '' which can
produce duplicate/empty keys if stream metadata never arrives; change to assign
a stable temporary ID (for example a UUID or a deterministic
"temp-<timestamp|counter>" string) when creating the placeholder message and
then, when metadata arrives, replace or update the message by finding it via
that temp ID and swapping in the real ID and metadata; ensure any state updates
(add/replace) use that temp ID to avoid mismatches and guarantee uniqueness.
| setMessages((prev) => [...prev, assistantMessage]); | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Persist assistant message after streaming, regardless of outcome.
Ensure the assistantMessage (including rate-limit or error) is saved to IndexedDB before appending to state.
- setMessages((prev) => [...prev, assistantMessage]);
+ try {
+ if (!assistantMessage.id) {
+ assistantMessage.id = `temp-${Date.now()}`;
+ }
+ await chatDB.saveMessage(assistantMessage, websiteId || '');
+ } catch (e) {
+ console.error('Failed to persist assistant message to IndexedDB:', e);
+ } finally {
+ setMessages((prev) => [...prev, assistantMessage]);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setMessages((prev) => [...prev, assistantMessage]); | |
| }, | |
| try { | |
| if (!assistantMessage.id) { | |
| assistantMessage.id = `temp-${Date.now()}`; | |
| } | |
| await chatDB.saveMessage(assistantMessage, websiteId || ''); | |
| } catch (e) { | |
| console.error('Failed to persist assistant message to IndexedDB:', e); | |
| } finally { | |
| setMessages((prev) => [...prev, assistantMessage]); | |
| } | |
| }, |
3969f90 to
f6b06de
Compare
|
@izadoesdev can you test this? |
|
works wondafully |
This pull request refactors the streaming update and conversation handling logic for the assistant chat feature, focusing on type safety, improved message tracking, and code simplification. The changes unify the
StreamingUpdatetype across backend and frontend, introduce explicit assistant message IDs for better tracking, and streamline how streaming updates are processed and saved.Backend improvements:
StreamingUpdatetype import from@databuddy/sharedacross backend files, replacing local definitions and cleaning up imports. [1] [2] [3] [4] [5] [6] [7] [8]aiMessageId) for each AI response, passing and saving them throughout the orchestration and repository layers for better message tracking. [1] [2] [3] [4] [5] [6] [7] [8]SessionContext.modelto a strict union type for improved type safety.Frontend improvements:
StreamingUpdatetype and removed local interface definitions in frontend hooks. (apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.tsR1-R4, apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.tsL16-L30)useChat) to track the assistant message in a local variable, updating its state in response to streaming updates for more accurate and atomic message handling. (apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.tsL161-L172, apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.tsL200-R191, apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.tsL250-R258)These changes improve type safety, consistency, and reliability in both backend and frontend streaming chat logic.
Summary by CodeRabbit