feat(chat): chat plan UX fixes + admin chat dashboard - #2595
Conversation
- Move Search Chats to top of the sidebar header - Keep pinned chats sticky above the scrollable history list - Default Canvas to the Chat plan context so it is selectable - Show skeleton loaders in image/video studio history while fetching - Show Sparkles 'Chat plan' branding (not an org icon) for the chat org - Hide pay-as-you-go credits in the Chat plan context Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a nullable organizationId to chat, playground_image_history and playground_video_history so history lists are scoped to the selected organization context. The dedicated Chat org (the 'Chat plan' context) also surfaces legacy rows with no org so existing history keeps showing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the DevPass admin feature for Chat Plans: subscribers list with KPIs, subscriber detail with transaction history and payment failures, revenue/cost/margin timeseries, and a model/provider/source usage breakdown. Adds 4 /admin/chat-plans* endpoints and a Chat Plans nav item. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WalkthroughThis PR adds organization-scoped history for chats and playground content (images/videos), implements comprehensive chat-plan admin analytics with KPI reporting, and refactors playground navigation to preserve selected organization context across route changes. ChangesOrganization-scoped chat history and admin chat-plans
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/playground/src/components/playground/chat-page-client.tsx (1)
1090-1097:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRequire an organization context before creating a chat.
Line 1096 can send
organizationIdasundefined. That creates new chats without org linkage, which undermines org-scoped history and reintroduces legacy null-org rows.Suggested fix
try { + const organizationId = selectedOrganization?.id ?? chatOrg?.id; + if (!organizationId) { + throw new Error("Organization context is not ready yet. Please retry."); + } + const title = userMessage ? userMessage.slice(0, 50) + (userMessage.length > 50 ? "..." : "") : "New Chat"; const chatData = await createChat.mutateAsync({ body: { title, model: selectedModel, webSearch: webSearchEnabled, comparisonEnabled, - organizationId: selectedOrganization?.id ?? chatOrg?.id, + organizationId, }, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/playground/src/components/playground/chat-page-client.tsx` around lines 1090 - 1097, The current createChat call can pass organizationId as undefined (via createChat.mutateAsync with selectedOrganization?.id ?? chatOrg?.id), which allows chats to be created without org linkage; before calling createChat.mutateAsync in the component handling chat creation, explicitly require an organization id by checking selectedOrganization and chatOrg and if neither provides an id, abort the creation (return early), surface a user-facing error/validation message, or redirect the user to select/claim an organization; update the call site to only invoke createChat.mutateAsync when a non-empty organizationId is available.apps/playground/src/components/playground/video-page-client.tsx (1)
200-205:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCapture org context per generated item before async completion.
organizationIdis taken from current UI selection when autosave runs. If the user changes org while jobs are in flight, saved video history can land in the wrong org.Suggested fix
+const itemOrgIdRef = useRef<Map<string, string | undefined>>(new Map()); // in generateVideos(), when creating itemId +const orgIdAtGeneration = selectedOrganization?.id; +itemOrgIdRef.current.set(itemId, orgIdAtGeneration); // in save payload -organizationId: selectedOrganization?.id, +organizationId: itemOrgIdRef.current.get(item.id), // when item is finalized/removed +itemOrgIdRef.current.delete(item.id);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/playground/src/components/playground/video-page-client.tsx` around lines 200 - 205, The saveVideoHistory call is using selectedOrganization at async completion, which can change before the request is sent; capture the org context early by reading selectedOrganization?.id into a local const (e.g. const orgId = selectedOrganization?.id) at the start of the per-item processing (where item is created/queued) and then pass orgId into saveVideoHistory's body (instead of selectedOrganization?.id) so each generated item is saved to the organization that was active when it was created.apps/playground/src/components/playground/image-page-client.tsx (1)
220-225:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPersist history with the org captured at generation start, not current selection.
Using
selectedOrganization?.idhere can mis-attribute records if the user switches orgs while generation is still running; the item is then saved into the wrong org history.Suggested fix
+const itemOrgIdRef = useRef<Map<string, string | undefined>>(new Map()); // in generateImages(), when creating itemId +const orgIdAtGeneration = selectedOrganization?.id; +itemOrgIdRef.current.set(itemId, orgIdAtGeneration); // in save payload -organizationId: selectedOrganization?.id, +organizationId: itemOrgIdRef.current.get(item.id), // when item is finalized/removed +itemOrgIdRef.current.delete(item.id);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/playground/src/components/playground/image-page-client.tsx` around lines 220 - 225, The call to saveImageHistory is using the mutable selectedOrganization?.id which can change mid-generation; instead persist and use the organization id captured when the generation started (e.g., a property on the image item such as item.organizationId or item.generatedOrganizationId) and pass that into saveImageHistory.body.organizationId; if the item lacks that field, set it at creation time when the generation is enqueued (populate item.generatedOrganizationId = selectedOrganization?.id) and update saveImageHistory to read that stable value rather than selectedOrganization?.id.
🧹 Nitpick comments (1)
apps/api/src/routes/playground.ts (1)
12-29: 💤 Low valueConsider extracting the shared org-filter logic to reduce duplication.
buildHistoryOrgFilterhere andbuildChatOrgFilterinchats.tsimplement nearly identical logic for scoping queries by organization with special handling for the Chat org. The only difference is that this version accepts aColumnparameter while the chat version uses a hardcoded column reference.Consider extracting a shared helper (e.g., in a utils file) that both routes can use. The
buildHistoryOrgFiltersignature is already generic enough to serve both use cases.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/routes/playground.ts` around lines 12 - 29, Extract the shared org-filter logic into a single helper (e.g., buildOrgFilter) that accepts a Column (or column reference) and organizationId and performs the same steps currently in buildHistoryOrgFilter: lookup org via db.query.organization.findFirst, check org?.isChat, and return or(eq(column, organizationId), isNull(column)) for chat orgs or eq(column, organizationId) otherwise (and undefined when no org id). Replace buildHistoryOrgFilter and buildChatOrgFilter to call this new helper (pass the Column or the hardcoded column reference used in chats.ts) so the lookup and isChat handling are centralized and duplicated logic removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/src/routes/admin.ts`:
- Around line 11805-11811: The route for /chat-plans/{orgId} currently only
verifies the organization exists but does not enforce that it is a chat
organization; update the org lookup or subsequent check so only organizations
with isChat === true are allowed: when calling
db.query.organization.findFirst(...) include isChat: { eq: true } in the where
clause (or, after loading org, check org.isChat and throw new HTTPException(404,
{ message: "Subscriber not found" }) if false) for the handler that uses the org
variable and do the same change for the second lookup referenced around the same
area (the other db.query.organization.findFirst call noted in the comment).
- Around line 11312-11327: universeRow is using a looser predicate (only
chatPlan != "none") so cycle totals include expired subscribers; update the
universeRow .where(...) to use the same “active subscriber” predicate as
activeRows (i.e. include the same isChat, chatPlan !== "none" plus the
expiry/active-date condition used in activeRows) so the SUMs
(realCostSub.realCost and tierPriceExpr) are computed over the exact same active
set; locate universeRow and mirror the exact predicate expression used by
activeRows (including any expiry checks) when building the leftJoin/where for
tables.organization and realCostSub.
In `@apps/playground/src/app/canvas/page.tsx`:
- Around line 40-45: The call to fetchServerData("GET", "/playground/chat-org")
can throw and currently will abort rendering; wrap that bootstrap call in a
try/catch (or otherwise handle Promise rejection) so failures are ignored or
logged but do not stop execution, then continue to call fetchServerData for
"/orgs" to populate initialOrganizationsData; reference the fetchServerData call
and the initialOrganizationsData variable and ensure any error handling is
non-throwing (e.g., process/log the error and proceed).
In `@ee/admin/src/app/chat-plans/`[orgId]/page.tsx:
- Around line 148-158: The code treats any falsy API result as either
unauthenticated or a 404; instead, wrap the
$api.GET("/admin/chat-plans/{orgId}", ...) call in a try/catch and differentiate
outcomes: if the call succeeds but returns data === null -> return <SignInPrompt
/>; if the call succeeds but returns no data and indicates a 404 (inspect the
response status or explicit flag from $api.GET) -> call notFound(); if the call
throws or returns an unexpected transport/backend error -> surface or rethrow
the error (or render an error UI) rather than mapping it to notFound. Update the
logic around the data variable and the $api.GET call so only real 404 responses
trigger notFound(), authentication triggers SignInPrompt, and other errors are
handled/logged explicitly.
In `@ee/admin/src/app/chat-plans/page.tsx`:
- Around line 355-374: The current check after the $api.GET call treats any
missing data as an auth issue and returns SignInPrompt; instead, inspect the
response/error from $api.GET (status/code or thrown error) and only render
SignInPrompt on explicit authentication errors (e.g., 401/403). For
non-auth/network/backend failures, render an error state (or ErrorBanner/Retry
UI) or rethrow so higher-level error boundaries handle it; update the
conditional around data (and any caught exceptions from $api.GET) to distinguish
auth vs other errors and use SignInPrompt only for auth-specific cases.
In `@ee/admin/src/components/chat-plans-usage.tsx`:
- Around line 122-129: The usage query currently only reads data and isLoading
from $api.useQuery, so failures fall through to the empty-state UI; update the
call to also destructure the error (e.g., const { data, isLoading, error } =
$api.useQuery(...)) and add an explicit error branch in the ChatPlansUsage
component that renders a clear error message/UI (an Alert or similar) when error
is truthy, returning early so failed requests are visually distinct from an
empty usage result.
---
Outside diff comments:
In `@apps/playground/src/components/playground/chat-page-client.tsx`:
- Around line 1090-1097: The current createChat call can pass organizationId as
undefined (via createChat.mutateAsync with selectedOrganization?.id ??
chatOrg?.id), which allows chats to be created without org linkage; before
calling createChat.mutateAsync in the component handling chat creation,
explicitly require an organization id by checking selectedOrganization and
chatOrg and if neither provides an id, abort the creation (return early),
surface a user-facing error/validation message, or redirect the user to
select/claim an organization; update the call site to only invoke
createChat.mutateAsync when a non-empty organizationId is available.
In `@apps/playground/src/components/playground/image-page-client.tsx`:
- Around line 220-225: The call to saveImageHistory is using the mutable
selectedOrganization?.id which can change mid-generation; instead persist and
use the organization id captured when the generation started (e.g., a property
on the image item such as item.organizationId or item.generatedOrganizationId)
and pass that into saveImageHistory.body.organizationId; if the item lacks that
field, set it at creation time when the generation is enqueued (populate
item.generatedOrganizationId = selectedOrganization?.id) and update
saveImageHistory to read that stable value rather than selectedOrganization?.id.
In `@apps/playground/src/components/playground/video-page-client.tsx`:
- Around line 200-205: The saveVideoHistory call is using selectedOrganization
at async completion, which can change before the request is sent; capture the
org context early by reading selectedOrganization?.id into a local const (e.g.
const orgId = selectedOrganization?.id) at the start of the per-item processing
(where item is created/queued) and then pass orgId into saveVideoHistory's body
(instead of selectedOrganization?.id) so each generated item is saved to the
organization that was active when it was created.
---
Nitpick comments:
In `@apps/api/src/routes/playground.ts`:
- Around line 12-29: Extract the shared org-filter logic into a single helper
(e.g., buildOrgFilter) that accepts a Column (or column reference) and
organizationId and performs the same steps currently in buildHistoryOrgFilter:
lookup org via db.query.organization.findFirst, check org?.isChat, and return
or(eq(column, organizationId), isNull(column)) for chat orgs or eq(column,
organizationId) otherwise (and undefined when no org id). Replace
buildHistoryOrgFilter and buildChatOrgFilter to call this new helper (pass the
Column or the hardcoded column reference used in chats.ts) so the lookup and
isChat handling are centralized and duplicated logic removed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: e609a72e-334d-4c70-acba-61526ac7f642
📒 Files selected for processing (24)
apps/api/src/routes/admin.tsapps/api/src/routes/chats.tsapps/api/src/routes/playground.tsapps/playground/src/app/canvas/page.tsxapps/playground/src/components/credits/credits-display.tsxapps/playground/src/components/playground/chat-page-client.tsxapps/playground/src/components/playground/chat-sidebar.tsxapps/playground/src/components/playground/history-skeleton.tsxapps/playground/src/components/playground/image-page-client.tsxapps/playground/src/components/playground/image-sidebar.tsxapps/playground/src/components/playground/organization-switcher.tsxapps/playground/src/components/playground/video-page-client.tsxapps/playground/src/components/playground/video-sidebar.tsxapps/playground/src/hooks/useChats.tsapps/playground/src/hooks/usePlaygroundHistory.tsee/admin/src/app/chat-plans/[orgId]/page.tsxee/admin/src/app/chat-plans/page.tsxee/admin/src/components/admin-shell.tsxee/admin/src/components/chat-plans-timeseries-chart.tsxee/admin/src/components/chat-plans-usage.tsxpackages/db/migrations/1780949651_narrow_skullbuster.sqlpackages/db/migrations/meta/1780949651_snapshot.jsonpackages/db/migrations/meta/_journal.jsonpackages/db/src/schema.ts
- admin chat-plans detail: enforce isChat on the org lookup (404 otherwise) - admin chat-plans KPIs: scope cycle cost/MRR universe to active (non-expired) subscribers to match the active-subscriber predicate - capture the org context on image/video items at generation start so saves are attributed to the org active when the generation began, not at save time - extract the shared org-history filter into a single helper reused by the chats and playground routes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
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/routes/chats.ts (2)
572-579:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidate
organizationIdownership before chat insert.Line 578 stores caller-provided
organizationIdwithout verifying the user can access that organization. This allows mis-attributed org data and corrupts org-scoped history/analytics boundaries. Enforce membership/access check before insert.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/routes/chats.ts` around lines 572 - 579, Before calling db.insert(tables.chat) to create newChat, validate that the provided body.organizationId actually belongs to or is accessible by the current user (user.id): query the organizations/memberships table (or OrganizationMember model) to confirm membership/access for body.organizationId, and if the check fails either set organizationId to null or return a 403/validation error; then use the verified organizationId (e.g., verifiedOrgId) in the .values call instead of directly using body.organizationId. Ensure this membership check happens before the insert and that any failure path stops the insert.
332-336:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
GET /searchis not organization-scoped.
/searchstill queries all active chats for the user and ignores organization context, so org-scoped UI can return cross-context results. Add optionalorganizationIdto query schema and applybuildOrgHistoryFilterinwhere(...).Also applies to: 374-385
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/routes/chats.ts` around lines 332 - 336, The GET /search handler is not scoped to an organization; update the request query schema (the z.object used for query in the chats route) to include an optional organizationId (e.g., organizationId: z.string().optional()), and then apply buildOrgHistoryFilter(...) when constructing the Prisma/DB where(...) clause in the search handler (and also in the other similar search/query block around the other occurrence). Locate the query schema and the where(...) usage in the chats route (references: the query z.object, the GET /search handler, and the buildOrgHistoryFilter function) and combine the existing filters with buildOrgHistoryFilter({ organizationId, userId }) so results are filtered by organization when organizationId is provided.apps/playground/src/components/playground/video-page-client.tsx (1)
200-227:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCompleted items can remain stuck after org switch.
Line 204 correctly saves with
item.organizationId, but cleanup depends on currenthistoryData(scoped by selected org). If the user switches org mid-flight,pending.dbIdmay never appear in current scope, leaving stale local items (and possible duplicates later). Remove/replace the local item on save success independent of current history scope.Also applies to: 239-249
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/playground/src/components/playground/video-page-client.tsx` around lines 200 - 227, The save callback currently assumes the saved DB id will appear in the currently-scoped historyData, so pendingSaveRef.current may never be resolved if the user switches orgs; instead, in the onSuccess handler of saveVideoHistory (the block that calls setSelectedItemId and updates router), immediately replace or remove the local item identified by pendingSaveRef.current.localId regardless of historyData scope: locate the saveVideoHistory onSuccess callback and after obtaining newId call the local-items updater (the same logic used to remove/replace local items around the other save path) to either remove the local temp item or replace it with the DB-backed item using the localId and newId, and then clear pendingSaveRef.current; do the same fix for the analogous block around lines 239-249 so local state is updated independent of selected organization.apps/api/src/routes/playground.ts (1)
314-323:⚠️ Potential issue | 🟠 Major | ⚡ Quick winEnforce org access checks before saving history rows.
Both image and video save handlers persist
body.organizationIddirectly without confirming the authenticated user can use that organization. This can mis-attribute rows to unrelated orgs. Validate access (and org status/type constraints) before insert.Also applies to: 548-557
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/src/utils/org-history-filter.ts`:
- Around line 19-21: The current guard in org-history-filter.ts uses "if (!org
|| org.isChat)" which treats missing orgs as Chat-plan and allows NULL rows;
change the logic so NULL fallback is only used when org?.isChat === true.
Replace the conditional so that when org?.isChat is true you return
or(eq(column, organizationId), isNull(column)), otherwise return eq(column,
organizationId); reference symbols: org, org.isChat, column, organizationId,
or(...), eq(...), isNull(...).
---
Outside diff comments:
In `@apps/api/src/routes/chats.ts`:
- Around line 572-579: Before calling db.insert(tables.chat) to create newChat,
validate that the provided body.organizationId actually belongs to or is
accessible by the current user (user.id): query the organizations/memberships
table (or OrganizationMember model) to confirm membership/access for
body.organizationId, and if the check fails either set organizationId to null or
return a 403/validation error; then use the verified organizationId (e.g.,
verifiedOrgId) in the .values call instead of directly using
body.organizationId. Ensure this membership check happens before the insert and
that any failure path stops the insert.
- Around line 332-336: The GET /search handler is not scoped to an organization;
update the request query schema (the z.object used for query in the chats route)
to include an optional organizationId (e.g., organizationId:
z.string().optional()), and then apply buildOrgHistoryFilter(...) when
constructing the Prisma/DB where(...) clause in the search handler (and also in
the other similar search/query block around the other occurrence). Locate the
query schema and the where(...) usage in the chats route (references: the query
z.object, the GET /search handler, and the buildOrgHistoryFilter function) and
combine the existing filters with buildOrgHistoryFilter({ organizationId, userId
}) so results are filtered by organization when organizationId is provided.
In `@apps/playground/src/components/playground/video-page-client.tsx`:
- Around line 200-227: The save callback currently assumes the saved DB id will
appear in the currently-scoped historyData, so pendingSaveRef.current may never
be resolved if the user switches orgs; instead, in the onSuccess handler of
saveVideoHistory (the block that calls setSelectedItemId and updates router),
immediately replace or remove the local item identified by
pendingSaveRef.current.localId regardless of historyData scope: locate the
saveVideoHistory onSuccess callback and after obtaining newId call the
local-items updater (the same logic used to remove/replace local items around
the other save path) to either remove the local temp item or replace it with the
DB-backed item using the localId and newId, and then clear
pendingSaveRef.current; do the same fix for the analogous block around lines
239-249 so local state is updated independent of selected organization.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 4e6fc4a1-74db-4c1b-98ee-8a6cea96dce7
📒 Files selected for processing (8)
apps/api/src/routes/admin.tsapps/api/src/routes/chats.tsapps/api/src/routes/playground.tsapps/api/src/utils/org-history-filter.tsapps/playground/src/components/playground/image-page-client.tsxapps/playground/src/components/playground/video-page-client.tsxapps/playground/src/lib/image-gen.tsapps/playground/src/lib/video-gen.ts
✅ Files skipped from review due to trivial changes (1)
- apps/playground/src/lib/video-gen.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/playground/src/components/playground/image-page-client.tsx
- apps/api/src/routes/admin.ts
Sidebar nav links (Chat, Group Chat, Image/Video Studio, Canvas, logo) now carry the current orgId query param so the selected organization persists when navigating between playground pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rovements-admin-chat
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/db/migrations/meta/_journal.json`:
- Around line 1111-1117: The new migration entry object (idx: 158, version: "8",
tag: "1780958022_easy_wolfsbane") in packages/db/migrations/meta/_journal.json
uses spaces for indentation; update this JSON entry so all indentation
characters are tabs (replace leading spaces with tabs for the entire object
block including keys "idx", "version", "when", "tag", and "breakpoints") to
comply with the repository coding guideline that .json files use tabs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d9ea9eba-311e-4e34-869d-afefd35b15d5
📒 Files selected for processing (4)
packages/db/migrations/1780958022_easy_wolfsbane.sqlpackages/db/migrations/meta/1780958022_snapshot.jsonpackages/db/migrations/meta/_journal.jsonpackages/db/src/schema.ts
💤 Files with no reviewable changes (1)
- packages/db/migrations/1780958022_easy_wolfsbane.sql
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/db/src/schema.ts
Summary
Addresses a batch of chat (apps/playground) feedback for the Chat plan context, plus a new admin "LLM Gateway Chat" dashboard.
Chat sidebar / studio UX (apps/playground)
Separate history by organization
organizationIdtochat,playground_image_history, andplayground_video_history.organizationIdis sent the API returns all of the user's rows as before).Admin: LLM Gateway Chat plans dashboard (ee/admin)
/chat-planslist (subscribers + KPIs),/chat-plans/[orgId]detail (transactions + payment failures), plus revenue/cost/margin timeseries and a model/provider/source usage breakdown — mirroring the existing DevPass admin feature but for Chat Plans (chat_plan_*transaction types,CHAT_PLAN_PRICES,isChatorgs, Starter/Plus/Pro tiers, monthly-only)./admin/chat-plans*API endpoints and a "Chat Plans" sidebar entry.Migration
packages/db/migrations/1780949651_narrow_skullbuster.sqladdsorganization_id(FK,ON DELETE SET NULL) to the three tables.Testing
tsc --noEmitclean forapi,admin, andplaygroundon all changed files.pnpm --filter api buildandpnpm --filter admin buildpass.pnpm --filter playground buildfails only on pre-existingai-elements/{reasoning,response}.tsxshiki version-mismatch errors (untouched files; the documentedpnpm.overridesissue), unrelated to this change.🤖 Generated with Claude Code
Summary by CodeRabbit