Skip to content

Store full databunny conversation.#106

Merged
izadoesdev merged 1 commit into
databuddy-analytics:stagingfrom
sbansal1999:fix-ai-message-storing
Aug 21, 2025
Merged

Store full databunny conversation.#106
izadoesdev merged 1 commit into
databuddy-analytics:stagingfrom
sbansal1999:fix-ai-message-storing

Conversation

@sbansal1999

@sbansal1999 sbansal1999 commented Aug 20, 2025

Copy link
Copy Markdown
Contributor

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 StreamingUpdate type across backend and frontend, introduce explicit assistant message IDs for better tracking, and streamline how streaming updates are processed and saved.

Backend improvements:

  • Unified StreamingUpdate type import from @databuddy/shared across backend files, replacing local definitions and cleaning up imports. [1] [2] [3] [4] [5] [6] [7] [8]
  • Added explicit assistant message IDs (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]
  • Changed SessionContext.model to a strict union type for improved type safety.
  • Removed unused fields and redundant timestamp generation in conversation message objects for cleaner data models. [1] [2]

Frontend improvements:

These changes improve type safety, consistency, and reliability in both backend and frontend streaming chat logic.

Summary by CodeRabbit

  • New Features
    • Streaming responses now include metadata (conversation and message IDs) for better message linking and continuity.
  • Bug Fixes
    • More robust error handling with clearer error messages in streamed replies.
    • Improved rate-limit feedback with a visible cooldown to prevent repeated requests.
  • Refactor
    • Chat experience streamlined by consolidating state management into a single hook for smoother, more reliable streaming and message assembly.
    • Unified streaming types across app and API to reduce inconsistencies and improve stability.

@vercel

vercel Bot commented Aug 20, 2025

Copy link
Copy Markdown

@sbansal1999 is attempting to deploy a commit to the Databuddy Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 20, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Centralizes 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

Cohort / File(s) Summary of changes
Shared types introduction
packages/shared/src/types/assistant.ts, packages/shared/src/types/index.ts
Added exported StreamingUpdate union (includes metadata variant); re-exported from types index.
Adopt shared StreamingUpdate
apps/api/src/agent/core/response-processor.ts, apps/api/src/agent/handlers/chart-handler.ts, apps/api/src/agent/handlers/metric-handler.ts, apps/api/src/agent/processor.ts, apps/api/src/routes/assistant.ts, apps/api/src/agent/utils/stream-utils.ts
Switched StreamingUpdate imports from local to @databuddy/shared; removed local StreamingUpdate type. Functionality unchanged.
Metadata-first streaming and persistence
apps/api/src/agent/core/assistant-orchestrator.ts, apps/api/src/agent/core/conversation-repository.ts
Prepend metadata update (conversationId, messageId) to stream; compute and append AI response updates. Repository saveConversation now requires aiMessageId and stores provided IDs; removed createdAt/metadata/vote fields. Enhanced error paths to include generated messageId and saving.
Session model typing
apps/api/src/agent/core/assistant-session.ts, apps/api/src/agent/core/ai-service.ts
SessionContext.model narrowed to 'chat'
Dashboard chat refactor
apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.ts, apps/dashboard/app/(main)/websites/[id]/assistant/components/chat-section.tsx
Introduced conversationId state and handling of 'metadata' updates. Reworked SSE handling to accumulate a single assistantMessage across events and append on completion. Switched component to use consolidated useChat hook API (messages, inputValue, sendMessage, etc.).

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

A nibble of bytes, a carrot of state,
We stream little secrets, metadata first-rate.
IDs in our pockets, we hop through the flow,
Thinking, then progress—complete, off we go.
Shared types in the meadow, neat rows we arrange,
With one happy hare: “I love a well-typed change!” 🥕🐇

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sbansal1999

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 20, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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; avoid any and broaden compile-time checks.

Good centralization. You can make this safer without changing runtime by:

  • Replacing any[] with unknown[].
  • Using Record<string, unknown> for debugInfo.

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 metricValue are required only when responseType: 'metric'.


20-24: Use aiMessageId to disambiguate from user message IDs.

The PR narrative introduces “aiMessageId”. Here the metadata uses a generic messageId. Consider renaming to aiMessageId for 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 messageId consumers 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: Make AI_MODEL configurable 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 metricValue in data).

-  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 default with an exhaustive check to surface missing cases when new response_type values 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 defaults

Using || 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 AsyncIterable

This 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 them

Sends 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 39b390a and 3969f90.

📒 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.ts
  • apps/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-typed context.model improves 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: Importing StreamingUpdate from 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 metadata variant used upstream).

apps/api/src/agent/handlers/chart-handler.ts (1)

1-1: Centralized StreamingUpdate import from shared: LGTM

Type-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: LGTM

Keeps 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: LGTM

Matches 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: LGTM

Route 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: LGTM

Removes local duplication and keeps SSE payloads in sync with the shared union.


39-45: generateThinkingSteps aligns with shared union: LGTM

Simple, 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.

Comment on lines +33 to 34
const aiMessageId = createId();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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 this

And 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.

Suggested change
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.

Comment on lines +138 to +139
const messageId = createId('NANOID');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Comment on lines +149 to 156
let assistantMessage: Message = {
id: '',
type: 'assistant',
content: '',
timestamp: new Date(),
hasVisualization: false,
thinkingSteps: [],
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Comment thread apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.ts
Comment on lines +327 to 328
setMessages((prev) => [...prev, assistantMessage]);
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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]);
}
},

@sbansal1999
sbansal1999 force-pushed the fix-ai-message-storing branch from 3969f90 to f6b06de Compare August 20, 2025 21:42
@sbansal1999

Copy link
Copy Markdown
Contributor Author

@izadoesdev can you test this?

@izadoesdev
izadoesdev merged commit 93ad8f8 into databuddy-analytics:staging Aug 21, 2025
3 of 5 checks passed
@izadoesdev

Copy link
Copy Markdown
Member

works wondafully

@sbansal1999
sbansal1999 deleted the fix-ai-message-storing branch August 21, 2025 17:31
sbansal1999 added a commit to sbansal1999/Databuddy that referenced this pull request Aug 21, 2025
sbansal1999 added a commit to sbansal1999/Databuddy that referenced this pull request Aug 21, 2025
sbansal1999 added a commit to sbansal1999/Databuddy that referenced this pull request Aug 22, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Dec 3, 2025
7 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Dec 18, 2025
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants