Skip to content

Comments

Ask AI feature#447

Merged
Blaumaus merged 19 commits intoswetrix-revampfrom
ask-ai
Dec 8, 2025
Merged

Ask AI feature#447
Blaumaus merged 19 commits intoswetrix-revampfrom
ask-ai

Conversation

@Blaumaus
Copy link
Member

@Blaumaus Blaumaus commented Dec 7, 2025

Changes

Community Edition support

  • Your feature is implemented for the Swetrix Community Edition
  • This PR only updates the Cloud (Enterprise) Edition code (e.g. Paddle webhooks, blog, payouts, etc.)

Database migrations

  • Clickhouse / MySQL migrations added for this PR
  • No table schemas changed in this PR

Documentation

  • You have updated the documentation according to your PR
  • This PR did not change any publicly documented endpoints

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced AI-powered assistant for project analytics with real-time streaming responses
    • Implemented persistent chat history management and conversation persistence
    • Added dynamic chart generation and visualization from AI analysis
    • Integrated AI tab into main project dashboard for easy access
  • Documentation

    • Added complete UI localization for AI features

✏️ Tip: You can customize this high-level summary in your review settings.

@Blaumaus Blaumaus self-assigned this Dec 7, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 7, 2025

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

Introduces a comprehensive AI chat feature integrating OpenRouter API for analytics queries. Adds backend service layer (AiService, AiChatService), REST endpoints for chat streaming and management, frontend chat UI component, database schema, and supporting infrastructure (DTOs, entity, module, localization, dependencies).

Changes

Cohort / File(s) Summary
Backend Configuration & Dependencies
backend/.env.example, backend/package.json
Added OPENROUTER_API_KEY environment variable and three runtime dependencies: @ai-sdk/openai, ai, and zod.
Backend AI Service Layer
backend/apps/cloud/src/ai/ai.service.ts
New NestJS AI service integrating OpenRouter API with tool calling for project analytics (data retrieval, goals, funnels). Includes system prompt building, ClickHouse data querying with timezone handling, and streaming response wrapper with tool_call normalization.
Backend AI Chat Management
backend/apps/cloud/src/ai/ai-chat.service.ts, backend/apps/cloud/src/ai/entity/ai-chat.entity.ts, backend/apps/cloud/src/ai/dto/chat.dto.ts
TypeORM entity AiChat with chat history persistence, AiChatService for CRUD operations with optional user scoping, and DTOs for validating chat messages and payloads (ChatDto, CreateChatDto, UpdateChatDto).
Backend API & Module
backend/apps/cloud/src/ai/ai.controller.ts, backend/apps/cloud/src/ai/ai.module.ts, backend/apps/cloud/src/app.module.ts
AiController exposing REST endpoints (POST /ai/:pid/chat for streaming, GET/POST/DELETE for chat CRUD), AiModule wiring services and entity, and AppModule import registration.
Database Migration
backend/migrations/mysql/2025_12_07_ai_chat.sql
MySQL migration creating ai_chat table with indexed project/user foreign keys, cascade delete, and timestamp tracking.
Frontend API Layer
web/app/api/index.ts
New askAI function implementing SSE streaming for chat with callbacks (onText, onToolCall, onReasoning, onError), plus CRUD endpoints (getRecentAIChats, getAllAIChats, getAIChat, createAIChat, updateAIChat, deleteAIChat).
Frontend UI Components
web/app/pages/Project/AskAI/AskAIView.tsx, web/app/pages/Project/AskAI/AIChart.tsx, web/app/pages/Project/AskAI/index.tsx
AskAIView main chat component with streaming message handling, chat history, modals, and keyboard input; AIChart component rendering multiple chart types (line, bar, area, pie) from AI-generated chart data; index re-export module.
Frontend Integration & Styling
web/app/pages/Project/View/ViewProject.tsx, web/app/pages/Project/View/components/ProjectSidebar.tsx, web/app/lib/constants/index.ts
Added AI tab to project tabs, integrated AskAIView in main view, styled standalone AI tab in sidebar with violet accent, updated tab color mappings.
Frontend Dependencies & Localization
web/package.json, web/public/locales/en.json
Added use-stick-to-bottom dependency and comprehensive AI-related localization strings (askAi root object and dashboard.askAi entry).

Sequence Diagram

sequenceDiagram
    participant Client as Client (Web)
    participant Controller as AiController
    participant AiSvc as AiService
    participant OpenRouter as OpenRouter API
    participant ChatSvc as AiChatService
    participant DB as Database

    Client->>Controller: POST /ai/:pid/chat<br/>(messages, timezone)
    activate Controller
    Controller->>Controller: Validate auth & project access
    Controller->>Controller: Check billing status
    Controller->>Controller: Setup SSE response stream
    
    Controller->>AiSvc: chat(project, messages, timezone)
    activate AiSvc
    AiSvc->>AiSvc: Build system prompt & tools
    AiSvc->>OpenRouter: POST (streaming)<br/>with tools & messages
    activate OpenRouter
    
    OpenRouter-->>AiSvc: Stream: text chunks
    AiSvc->>AiSvc: Normalize tool_calls
    AiSvc-->>Controller: Emit text events
    Controller-->>Client: SSE: text data
    
    OpenRouter-->>AiSvc: Stream: tool_call
    AiSvc->>AiSvc: Execute tool<br/>(getData, getGoalStats, etc.)
    AiSvc-->>Controller: Emit tool-call event
    Controller-->>Client: SSE: tool-call data
    
    AiSvc->>AiSvc: Process tool results
    AiSvc-->>Controller: Emit tool-result event
    Controller-->>Client: SSE: tool-result data
    
    OpenRouter-->>AiSvc: Stream complete
    deactivate OpenRouter
    AiSvc-->>Controller: Emit done signal
    Controller-->>Client: SSE: done
    deactivate AiSvc
    
    Controller->>ChatSvc: create/update chat<br/>(messages, response)
    activate ChatSvc
    ChatSvc->>DB: INSERT/UPDATE ai_chat
    deactivate ChatSvc
    Controller-->>Client: Response closed
    deactivate Controller
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • backend/apps/cloud/src/ai/ai.service.ts: Dense logic involving tool builders, ClickHouse query construction with dynamic time-bucketing, timezone handling, data filtering, and streaming response normalization. Requires verification of tool schemas (zod), data fetch correctness, and error handling for each analytics endpoint.
  • backend/apps/cloud/src/ai/ai.controller.ts: SSE streaming implementation with multiple event types, error handling mid-stream, and validation/access control across multiple endpoints. Verify proper cleanup on connection drops and error propagation.
  • web/app/pages/Project/AskAI/AskAIView.tsx: Complex state management with streaming callbacks, chat history sync via URL params, modal flows, and accessibility (keyboard handling, focus). Verify AbortController lifecycle and loading state consistency.
  • Security: OpenRouter API key environment configuration, project-level access control in controller, and user scoping logic in chat service all require careful review.
  • backend/migrations/mysql/2025_12_07_ai_chat.sql: Verify foreign key constraints, index placement, and column types match the TypeORM entity definitions.

Possibly related PRs

  • UI updates #441: Introduces ProjectSidebar component; this PR extends it with AI tab styling and layout changes.
  • Goals analytics #446: Introduces GoalModule; this PR's AiModule imports GoalModule for analytics data fetching.

Poem

🐰 A rabbit hops through code anew,
Chat and charts in violet hue,
OpenRouter's wisdom flows so free,
Analytics dancing on the spree,
Streaming thoughts, a clever feat,
Your queries now have an AI heartbeat!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete, providing only a reference to an issue and checked boxes without substantive details about the implementation, changes made, or testing performed. Add a comprehensive 'Changes' section describing the backend AI service, controller, DTOs, database schema, frontend UI components, and API endpoints introduced in this PR.
Title check ❓ Inconclusive The title 'Ask AI feature' is vague and generic, using non-specific terminology that does not clearly convey what the changeset does. Use a more descriptive title that specifies the primary change, such as 'Add AI chat service with streaming responses and chat history management' or 'Implement Ask AI feature with OpenRouter integration'.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@Blaumaus Blaumaus marked this pull request as ready for review December 8, 2025 21:19
@Blaumaus
Copy link
Member Author

Blaumaus commented Dec 8, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 8, 2025

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
web/app/pages/Project/View/components/ProjectSidebar.tsx (1)

16-32: Sidebar AI tab wiring and styling are consistent, but rely on the same self‑hosted tab id behavior

The new ICON_COLORS.ai mapping, hover/active class tweaks, and standalone Ask AI block above the grouped tabs all look consistent with the existing sidebar design. In cloud, resolving askAiTab from tabs and updating the tab search param works as intended.

Note that the self‑hosted case shares the same PROJECT_TABS.ai runtime undefined issue called out in ViewProject.tsx (tabs definition): in self‑hosted builds the Ask AI entry will still render here but never become the active tab. Once the tab construction is gated to cloud only (per that comment), this sidebar logic will follow automatically.

Also applies to: 57-83, 161-208, 225-266, 281-288

🧹 Nitpick comments (12)
backend/apps/cloud/src/ai/entity/ai-chat.entity.ts (2)

33-41: Consider adding an index on the project relation for query performance.

When listing chats for a project (likely a common query pattern), an index on the projectId foreign key column would improve performance. TypeORM doesn't automatically create indexes on foreign keys in all database engines.

+import { Index } from 'typeorm'

 @Entity('ai_chat')
+@Index(['project'])
 export class AiChat {

Alternatively, this can be handled in the migration if you prefer to keep the entity cleaner.


29-31: The messages JSON column could grow unbounded.

Consider whether there should be a limit on the number of messages per chat, or if older messages should be truncated. Unbounded JSON columns can impact query performance and storage over time. This may be handled at the application layer, but it's worth considering adding documentation or validation.

web/app/api/index.ts (3)

1972-1985: Use PATCH or PUT instead of POST for update operations.

The updateAIChat function uses POST for updating an existing resource, which violates REST conventions. Other update functions in this file (e.g., updateAlert, updateGoal, updateProject) use PUT or PATCH.

 export const updateAIChat = async (
   pid: string,
   chatId: string,
   data: { messages?: AIChatMessage[]; name?: string },
 ): Promise<AIChat> => {
   return api
-    .post(`ai/${pid}/chats/${chatId}`, data, {
-      headers: { Authorization: getAccessToken() ? `Bearer ${getAccessToken()}` : '' },
-    })
+    .patch(`ai/${pid}/chats/${chatId}`, data)
     .then((response): AIChat => response.data)
     .catch((error) => {
       throw _isEmpty(error.response?.data?.message) ? error.response?.data : error.response?.data?.message
     })
 }

Note: This also requires updating the backend controller to handle PATCH requests for this endpoint.


1860-1866: The while (true) loop without explicit break condition could hang if the stream stalls.

If the server connection stays open but stops sending data (stalled connection), this loop will wait indefinitely. Consider adding a timeout mechanism or ensuring the server properly closes the connection.

Verify that the backend properly closes the SSE connection on completion/error, and consider whether client-side timeouts are needed for resilience.


1893-1895: Silent JSON parsing failures may hide legitimate errors.

While ignoring incomplete JSON chunks during streaming is necessary, completely swallowing all parse errors could hide issues with malformed server responses. Consider logging in development mode or distinguishing between incomplete chunks and actual parse errors.

           } catch (e) {
-            // Ignore parsing errors for incomplete JSON
+            // Log parsing errors in development for debugging
+            if (process.env.NODE_ENV === 'development') {
+              console.debug('SSE JSON parse error (may be incomplete chunk):', data, e)
+            }
           }
web/app/pages/Project/View/ViewProject.tsx (1)

4320-4321: AskAIView mounting with projectId is straightforward

Rendering <AskAIView projectId={id} /> when activeTab === PROJECT_TABS.ai correctly scopes the AI experience to the current project and reuses the standard id from useCurrentProject().

You could mirror CaptchaView and lazily load AskAIView via React.lazy to avoid pulling the entire AI UI bundle into initial dashboard loads if code‑size becomes a concern.

web/app/pages/Project/AskAI/AIChart.tsx (1)

29-40: AIChart implementation is solid; consider a couple of small robustness tweaks

Overall this component is well‑structured:

  • Clean separation between pie/donut and x‑series charts with appropriate data validation and early returns.
  • Sensible color palette and percentage/value formatting in tooltips.
  • Dynamic axis configuration (including tick density, rotation, and date formatting) is calibrated to the data volume.

Two small robustness improvements you might consider:

  1. Guard against charts with no data series

    Right now, if chart.data has x but no additional numeric keys, seriesKeys will be empty and you’ll render an empty chart frame. You could treat this as “no chart” instead:

    const xData = chart.data.x as string[]
    const seriesKeys = _filter(_keys(chart.data), (key) => key !== 'x' && key !== 'labels' && key !== 'values')
  • if (seriesKeys.length === 0) {
  • return {}
  • }
    
    and in the render guard, mirror that by returning `null` when `seriesKeys.length === 0`.
    
    
  1. Document/assume non‑negative series values

    calculateOptimalTicks fixes y.min = 0 and computes ticks assuming non‑negative values. If AI‑generated charts ever include negative‑only data, upperBound becomes ≤ 0 and ticks can be empty. If you expect only counts or durations, that’s fine, but a short comment noting the non‑negative assumption near calculateOptimalTicks would make the intent explicit for future maintainers.

Also applies to: 41-85, 87-107, 109-213, 215-305, 308-328

web/app/pages/Project/AskAI/AskAIView.tsx (3)

453-453: Use substring instead of deprecated substr.

String.prototype.substr is deprecated. Use substring instead for better compatibility.

-  const generateMessageId = () => `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
+  const generateMessageId = () => `msg-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`

759-772: Hardcoded time strings should use i18n.

The formatRelativeTime function uses hardcoded English strings ('Just now', 'm', 'h', 'd') while the rest of the component properly uses useTranslation. Consider using translated strings for consistency.

 const formatRelativeTime = (dateStr: string) => {
   const date = new Date(dateStr)
   const now = new Date()
   const diffMs = now.getTime() - date.getTime()
   const diffMins = Math.floor(diffMs / 60000)
   const diffHours = Math.floor(diffMs / 3600000)
   const diffDays = Math.floor(diffMs / 86400000)

-  if (diffMins < 1) return 'Just now'
-  if (diffMins < 60) return `${diffMins}m`
-  if (diffHours < 24) return `${diffHours}h`
-  if (diffDays < 7) return `${diffDays}d`
+  if (diffMins < 1) return t('askAi.time.justNow')
+  if (diffMins < 60) return t('askAi.time.minutes', { count: diffMins })
+  if (diffHours < 24) return t('askAi.time.hours', { count: diffHours })
+  if (diffDays < 7) return t('askAi.time.days', { count: diffDays })
   return date.toLocaleDateString()
 }

1067-1067: Hardcoded "Load more" text should use i18n.

-                   Load more
+                   {t('askAi.loadMore')}
backend/apps/cloud/src/ai/ai.controller.ts (2)

246-250: Add validation for numeric query parameters.

parseInt on invalid strings returns NaN, which could cause unexpected behavior in the service layer. Consider adding validation or using a more defensive approach.

 const chats = await this.aiChatService.findRecentByProject(
   pid,
   uid,
-  limit ? parseInt(limit, 10) : 5,
+  limit ? Math.max(1, parseInt(limit, 10) || 5) : 5,
 )

Similarly for getAllChats:

 const result = await this.aiChatService.findAllByProject(
   pid,
   uid,
-  skip ? parseInt(skip, 10) : 0,
-  take ? parseInt(take, 10) : 20,
+  skip ? Math.max(0, parseInt(skip, 10) || 0) : 0,
+  take ? Math.min(100, Math.max(1, parseInt(take, 10) || 20)) : 20,
 )

Adding an upper bound on take also prevents potential DoS via large pagination requests.


372-415: Consider using PATCH instead of POST for the update endpoint.

REST conventions typically use PATCH (or PUT) for update operations. Using POST for updates may confuse API consumers.

 @ApiBearerAuth()
-@Post(':pid/chats/:chatId')
+@Patch(':pid/chats/:chatId')
 @Auth()
 @ApiOperation({ summary: 'Update an AI chat' })

This would require importing Patch from @nestjs/common.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e57a81 and 1672b3e.

⛔ Files ignored due to path filters (2)
  • backend/package-lock.json is excluded by !**/package-lock.json
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • backend/.env.example (1 hunks)
  • backend/apps/cloud/src/ai/ai-chat.service.ts (1 hunks)
  • backend/apps/cloud/src/ai/ai.controller.ts (1 hunks)
  • backend/apps/cloud/src/ai/ai.module.ts (1 hunks)
  • backend/apps/cloud/src/ai/ai.service.ts (1 hunks)
  • backend/apps/cloud/src/ai/dto/chat.dto.ts (1 hunks)
  • backend/apps/cloud/src/ai/entity/ai-chat.entity.ts (1 hunks)
  • backend/apps/cloud/src/app.module.ts (2 hunks)
  • backend/migrations/mysql/2025_12_07_ai_chat.sql (1 hunks)
  • backend/package.json (3 hunks)
  • web/app/api/index.ts (1 hunks)
  • web/app/lib/constants/index.ts (1 hunks)
  • web/app/pages/Project/AskAI/AIChart.tsx (1 hunks)
  • web/app/pages/Project/AskAI/AskAIView.tsx (1 hunks)
  • web/app/pages/Project/AskAI/index.tsx (1 hunks)
  • web/app/pages/Project/View/ViewProject.tsx (5 hunks)
  • web/app/pages/Project/View/components/ProjectSidebar.tsx (6 hunks)
  • web/package.json (1 hunks)
  • web/public/locales/en.json (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
backend/apps/cloud/src/ai/ai.module.ts (1)
backend/apps/cloud/src/app.module.ts (1)
  • Module (91-105)
web/app/pages/Project/View/components/ProjectSidebar.tsx (2)
web/app/lib/constants/index.ts (1)
  • PROJECT_TABS (423-425)
web/app/ui/Text.tsx (1)
  • Text (51-75)
web/app/api/index.ts (2)
web/app/utils/accessToken.ts (1)
  • getAccessToken (11-23)
web/app/lib/constants/index.ts (1)
  • API_URL (338-338)
backend/apps/cloud/src/ai/ai.controller.ts (2)
backend/apps/cloud/src/ai/dto/chat.dto.ts (3)
  • ChatDto (29-54)
  • CreateChatDto (56-73)
  • UpdateChatDto (75-94)
backend/apps/cloud/src/ai/ai.service.ts (1)
  • chat (111-142)
backend/apps/cloud/src/ai/ai-chat.service.ts (2)
backend/apps/cloud/src/ai/entity/ai-chat.entity.ts (1)
  • ChatMessage (14-17)
backend/apps/cloud/src/ai/ai.controller.ts (1)
  • chat (48-223)
web/app/pages/Project/View/ViewProject.tsx (1)
web/app/lib/constants/index.ts (1)
  • PROJECT_TABS (423-425)
🪛 ast-grep (0.40.0)
web/app/pages/Project/AskAI/AskAIView.tsx

[warning] 333-333: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
web/app/pages/Project/AskAI/AskAIView.tsx

[error] 334-334: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🔇 Additional comments (19)
backend/.env.example (1)

100-102: LGTM!

The new OPENROUTER_API_KEY environment variable is appropriately placed under the Swetrix Enterprise (Cloud) section with a clear comment explaining its purpose.

web/package.json (1)

76-77: LGTM!

The use-stick-to-bottom dependency is appropriate for implementing auto-scroll behavior in the AI chat interface, and follows the existing versioning conventions used in the project.

backend/package.json (1)

29-29: LGTM!

The new dependencies (@ai-sdk/openai, ai, and zod) are appropriate for implementing AI chat functionality with the Vercel AI SDK ecosystem. The zod library provides robust schema validation for AI tool inputs.

Also applies to: 51-51, 93-94

web/public/locales/en.json (1)

1742-1761: LGTM!

The localization strings are comprehensive and well-written. The inclusion of the disclaimer key ("AI responses may not always be accurate. Verify important data.") is a good practice for setting user expectations about AI-generated content.

web/app/api/index.ts (1)

1813-1825: LGTM on the interface definitions.

The AIChatMessage and AIStreamCallbacks interfaces are well-designed with appropriate optional callbacks for different event types (tool calls, reasoning, etc.).

web/app/pages/Project/AskAI/index.tsx (1)

1-1: AskAI index re-export is correct and minimal

Re-exporting the default from ./AskAIView keeps the route entrypoint clean and matches the ../AskAI import usage.

backend/apps/cloud/src/app.module.ts (1)

24-24: AiModule integration into AppModule looks correct

Importing AiModule and adding it to modules ensures AI endpoints and services are available in the cloud app; no structural issues stand out.

Also applies to: 83-83

web/app/lib/constants/index.ts (1)

410-421: AI tab constant added only for production tabs

Adding ai: 'ai' to PRODUCTION_PROJECT_TABS correctly scopes the AI tab to the cloud/production tab set; the self-hosted tab set remains unchanged, with behavioral implications handled in the ViewProject/ProjectSidebar logic.

backend/apps/cloud/src/ai/ai.module.ts (1)

1-24: AiModule composition is consistent with NestJS patterns

The module correctly registers the AiChat entity, AI services, and controller, and imports the needed domain modules; no structural or DI issues are apparent.

web/app/pages/Project/View/ViewProject.tsx (1)

3477-3483: Toolbar/header gating for AI tab is reasonable

Skipping the standard dashboard header and controls when activeTab is the AI tab (and alerts) avoids rendering analytics‑specific filters around a fundamentally different view; this conditional looks consistent with the layout needs of Ask AI.

backend/migrations/mysql/2025_12_07_ai_chat.sql (1)

1-16: AI chat migration schema is coherent

The ai_chat table definition (keys, JSON payload, timestamps, and foreign keys with ON DELETE CASCADE) matches a typical chat‑history model and should perform well for project/user and recent‑history queries.

backend/apps/cloud/src/ai/dto/chat.dto.ts (1)

1-94: DTOs are well-structured with proper validation.

The DTO definitions follow NestJS best practices with appropriate use of class-validator decorators and class-transformer for nested object validation. The ValidateNested({ each: true }) combined with @Type(() => ChatMessageDto) ensures proper transformation and validation of nested message arrays.

web/app/pages/Project/AskAI/AskAIView.tsx (2)

326-347: Sanitization is applied before using dangerouslySetInnerHTML - acceptable pattern.

The static analysis warning is a false positive in this context. The renderMarkdown function (lines 124-135) properly sanitizes HTML using sanitize-html before injection, which is the recommended approach when rendering user-controlled or AI-generated markdown content. The sanitization configuration restricts allowed tags and attributes appropriately.


541-551: Initialization effect correctly uses ref guard to prevent re-execution.

The hasInitializedRef pattern ensures the initialization logic runs only once, which is the correct behavior for loading initial state from URL parameters. The dependencies are listed for completeness per React lint rules, but the ref guard prevents re-runs.

backend/apps/cloud/src/ai/ai.service.ts (3)

28-92: Well-implemented stream patching for OpenRouter compatibility.

The patchedFetch wrapper correctly handles a known issue with some OpenRouter models returning empty type fields in tool_calls. The stream handling with proper cleanup and error fallback is robust.


94-142: Service initialization and chat method are well-structured.

The service properly initializes the OpenRouter client with the patched fetch and exposes a clean chat method. The logging provides good observability without exposing sensitive data. The streamText configuration with maxSteps: 10 is a reasonable limit for tool call iterations.


1011-1020: No changes needed. The timezone parameter is properly sanitized by analyticsService.getSafeTimezone() before being used in SQL strings. This method validates the timezone against the IANA timezone database using dayjs.tz(), rejecting invalid timezones and falling back to DEFAULT_TIMEZONE. All call sites in getAnalytics(), getGoalStats(), and getFunnelStats() sanitize the timezone before passing it to getTimeBucketSelect(), preventing SQL injection attacks.

Likely an incorrect or invalid review comment.

backend/apps/cloud/src/ai/ai.controller.ts (1)

43-223: Robust SSE streaming implementation with proper error handling.

The chat endpoint correctly sets up SSE headers, handles stream errors gracefully (both during iteration and as error events), and properly ends the response. The hasContent flag ensures appropriate error messaging based on whether partial content was delivered. Good practice to check res.headersSent before throwing exceptions.

backend/apps/cloud/src/ai/ai-chat.service.ts (1)

1-134: Clean repository service implementation.

The AiChatService follows NestJS/TypeORM best practices with proper dependency injection and query builder usage. The generateChatName helper provides good UX by auto-naming chats based on the first user message. The access verification logic correctly handles both user-specific and shared (null userId) chats.

@Blaumaus Blaumaus merged commit 982ec5d into swetrix-revamp Dec 8, 2025
1 check passed
@coderabbitai coderabbitai bot mentioned this pull request Dec 20, 2025
9 tasks
@coderabbitai coderabbitai bot mentioned this pull request Jan 21, 2026
6 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.

1 participant