perf(nuxi): optimise chat composables, caching and navigation#2305
perf(nuxi): optimise chat composables, caching and navigation#2305HugoRCD wants to merge 13 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR restructures the Nuxi agent chat system. Eve session/thread-state helpers are removed and replaced by a new Estimated code review effort: 5 (Critical) | ~120 minutes Changes
Sequence Diagram(s)sequenceDiagram
participant Page
participant useNuxiChat
participant useEveChat
participant EveAgent
participant API
Page->>useNuxiChat: init(options)
useNuxiChat->>useNuxiChat: resumeChatSession()
useNuxiChat->>useEveChat: send(input)
useEveChat->>EveAgent: submit payload
EveAgent-->>useEveChat: messages/status/error
useNuxiChat->>API: PATCH state/title
useNuxiChat-->>Page: getVote/vote, onSubmit
Related issues: None specified. Related PRs: None specified. Suggested labels: area: nuxi, agent-chat, refactor Suggested reviewers: None specified. 🐇 A chat once tangled in threads and states, 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 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
🧹 Nitpick comments (1)
layers/nuxi/app/composables/useAgentChatSession.ts (1)
22-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate row-conversion logic.
The
id/role/parts/createdAt-fallback mapping here duplicatesuiMessagesToRowsinuseChatDetailCache.ts(Lines 7-14). Consider reusinguiMessagesToRowshere to avoid the two implementations drifting (e.g. one gets a createdAt-stability fix and the other doesn't).♻️ Proposed consolidation
- const rows: ChatMessageRow[] = initialMessages.map(message => ({ - id: message.id, - role: message.role, - parts: message.parts, - createdAt: (message.metadata as { createdAt?: string } | undefined)?.createdAt ?? new Date().toISOString() - })) + const rows: ChatMessageRow[] = uiMessagesToRows(initialMessages)🤖 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 `@layers/nuxi/app/composables/useAgentChatSession.ts` around lines 22 - 49, The row-mapping logic in chatDetailForResume duplicates the UI-message-to-row conversion already implemented in uiMessagesToRows, so reuse that shared helper instead of rebuilding the id/role/parts/createdAt mapping locally. Update useAgentChatSession.ts to call uiMessagesToRows when constructing rows, keeping the createdAt fallback behavior centralized in useChatDetailCache.ts so both paths stay consistent.
🤖 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 `@layers/nuxi/app/components/agent/AgentPanelChat.vue`:
- Line 122: The AgentLoginHint in AgentPanelChat.vue is missing the logged-in
guard, so it renders for authenticated users too. Update the AgentPanelChat
template to match the AgentChatBody.vue logic by gating AgentLoginHint with the
same showLoginHint condition, using the existing loggedIn state and
props.loginHintBar so the sidebar hint only appears when appropriate.
In `@layers/nuxi/app/composables/useAgentChat.ts`:
- Around line 153-159: The persistence flag in useAgentChat should distinguish
an existing chat from a message that has already been saved, because
initialDbPersistDone is being set from initialMessages presence and blocks the
existing-chat append path in the prompt submit flow. Update the logic around
useAgentChat, initialDbPersistDone, and the submit/persist branch near the
existing-chat handling so only the resume/initial message is treated as already
persisted, while normal new prompts in an existing chat still go through the
append path immediately. Keep the resume-state tracking separate from the prompt
submission state so new user messages are not skipped before Eve finish sync.
In `@layers/nuxi/app/composables/useAgentChatSession.ts`:
- Around line 16-20: The needsGeneration helper in useAgentChatSession is using
“any assistant message ever” instead of checking whether the latest turn is
still unanswered. Update needsGeneration to inspect the message order and decide
based on the last message (or the last user/assistant pair) so it returns true
when the most recent message is a user turn without a following assistant reply;
this will correctly drive chatDetailForResume, setupStaleChatRefresh, and the
resumeChatSession owner-resume branch.
In `@layers/nuxi/app/composables/useStartChat.ts`:
- Around line 41-48: The draft is being cleared in onSubmit() before
createChat() confirms success, so move the input.value and
paste.clearAttachments() reset to after createChat(parts) succeeds. Update
createChat() to return a success boolean in useStartChat and have onSubmit()
only clear the draft when that result is true, while keeping the existing
paste.canSubmit, buildMessageParts, and track flow intact.
In `@layers/nuxi/app/pages/dashboard/chat/`[id].vue:
- Line 63: The vote loading flag is being snapshotted too early in the chat page
setup, so `useChatVotes()` never fetches owner votes after `useChatDetail()`
resolves. Update the `fetchVotes` usage in the `[id].vue` chat page to be
reactive to `isOwner` or defer triggering vote loading until ownership is known,
using the `useChatDetail`/`useChatVotes` flow rather than `isOwner.value` at
initialization.
In `@layers/nuxi/app/plugins/chat-view-transitions.client.ts`:
- Around line 8-55: The module-scoped view transition state in
chat-view-transitions.client.ts can be overwritten when router.beforeResolve
runs again before the prior document.startViewTransition() finishes. Add an
in-flight guard or cancel/skip path in beforeResolve so only one active
transition exists at a time, and ensure resetTransitionState only clears the
matching transition/finishTransition for the current ViewTransition. Use the
existing transition, finishTransition, resetTransitionState, and beforeResolve
symbols to keep the active chat prompt transition from getting stuck.
---
Nitpick comments:
In `@layers/nuxi/app/composables/useAgentChatSession.ts`:
- Around line 22-49: The row-mapping logic in chatDetailForResume duplicates the
UI-message-to-row conversion already implemented in uiMessagesToRows, so reuse
that shared helper instead of rebuilding the id/role/parts/createdAt mapping
locally. Update useAgentChatSession.ts to call uiMessagesToRows when
constructing rows, keeping the createdAt fallback behavior centralized in
useChatDetailCache.ts so both paths stay consistent.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: afad961a-5be5-4c5d-9ab7-e9493d872d3a
📒 Files selected for processing (22)
layers/nuxi/app/components/agent/AgentChatBody.vuelayers/nuxi/app/components/agent/AgentPanel.vuelayers/nuxi/app/components/agent/AgentPanelChat.vuelayers/nuxi/app/composables/eve/adapter.tslayers/nuxi/app/composables/eve/init.tslayers/nuxi/app/composables/eve/session.tslayers/nuxi/app/composables/eve/thread-state.tslayers/nuxi/app/composables/eve/types.tslayers/nuxi/app/composables/eve/useEveChat.tslayers/nuxi/app/composables/useAgentChat.tslayers/nuxi/app/composables/useAgentChatSession.tslayers/nuxi/app/composables/useChatDetail.tslayers/nuxi/app/composables/useChatDetailCache.tslayers/nuxi/app/composables/useChats.tslayers/nuxi/app/composables/usePasteAttachment.tslayers/nuxi/app/composables/useStartChat.tslayers/nuxi/app/pages/dashboard/chat/[id].vuelayers/nuxi/app/pages/dashboard/chat/index.vuelayers/nuxi/app/plugins/chat-view-transitions.client.tslayers/nuxi/server/api/chats/index.post.tslayers/nuxi/shared/utils/chat.tsnuxt.config.ts
💤 Files with no reviewable changes (5)
- layers/nuxi/app/composables/eve/init.ts
- nuxt.config.ts
- layers/nuxi/app/composables/eve/session.ts
- layers/nuxi/app/composables/eve/thread-state.ts
- layers/nuxi/app/composables/eve/types.ts
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 `@app/layouts/dashboard.vue`:
- Around line 12-15: The logout flow in the `watch(loggedIn, ...)` block stops
calling `refreshChats()` when `next` is false, so the clear-chat-list branch
inside `useChats().refresh()` never runs. Update this watcher in
`app/layouts/dashboard.vue` so `refreshChats()` is invoked on logout as well as
login, while keeping `sidebarOpen.value = false` behavior intact. Use the
existing `refreshChats`/`loggedIn` watcher logic to ensure the sidebar’s
`groups`/`items` list is cleared immediately after sign-out.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a36826b1-2cc3-41c1-955e-f5aeebef9b34
📒 Files selected for processing (10)
app/layouts/dashboard.vuelayers/nuxi/app/components/agent/AgentChatMessages.vuelayers/nuxi/app/components/agent/AgentPanelChat.vuelayers/nuxi/app/composables/useAgentChat.tslayers/nuxi/app/composables/useAgentChatSession.tslayers/nuxi/app/composables/useChatVotes.tslayers/nuxi/app/composables/useChats.tslayers/nuxi/app/composables/useStartChat.tslayers/nuxi/app/pages/dashboard/chat/[id].vuelayers/nuxi/app/plugins/chat-view-transitions.client.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- layers/nuxi/app/components/agent/AgentPanelChat.vue
- layers/nuxi/app/composables/useStartChat.ts
- layers/nuxi/app/composables/useAgentChatSession.ts
- layers/nuxi/app/composables/useAgentChat.ts
- layers/nuxi/app/pages/dashboard/chat/[id].vue
- layers/nuxi/app/plugins/chat-view-transitions.client.ts
There was a problem hiding this comment.
🧹 Nitpick comments (2)
layers/nuxi/app/composables/useChatRouteId.ts (2)
4-4: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueRegex literal recompiled on every call.
The regex is created fresh on every invocation of
resolveChatRouteId. Hoisting it to a module-level constant avoids redundant compilation given this runs on every route change/computed re-evaluation.♻️ Proposed fix
+const CHAT_ROUTE_PATTERN = /^\/dashboard\/chat\/([^/]+)\/?$/ + export function resolveChatRouteId(path: string, param: string | string[] | undefined) { if (typeof param === 'string' && param) return param if (Array.isArray(param) && param[0]) return param[0] - const match = path.match(/^\/dashboard\/chat\/([^/]+)\/?$/) + const match = path.match(CHAT_ROUTE_PATTERN) return match?.[1] ?? '' }🤖 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 `@layers/nuxi/app/composables/useChatRouteId.ts` at line 4, The regex used in resolveChatRouteId is being recreated on every call, which is unnecessary for a hot path triggered by route changes. Hoist the /^\/dashboard\/chat\/([^/]+)\/?$/ pattern to a module-level constant in useChatRouteId.ts and have resolveChatRouteId reuse that shared regex instead of instantiating it inside the function.
1-6: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueRegex fallback is brittle for future
/dashboard/chatchild routes
resolveChatRouteId()works with the current/dashboard/chatand/dashboard/chat/[id]pages, but any future static child like/dashboard/chat/newwould be treated as an id. If more routes are added here, prefer deriving the id from the[id]page/context instead of parsingroute.path.🤖 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 `@layers/nuxi/app/composables/useChatRouteId.ts` around lines 1 - 6, The fallback in resolveChatRouteId is parsing route.path with a regex, which can misclassify future static /dashboard/chat child routes as an id. Update resolveChatRouteId to prefer the route param/context for the [id] page and only use path parsing as a last resort, or remove the regex fallback if the caller can pass the route param reliably. Keep the fix localized to resolveChatRouteId and the code that supplies its param/path inputs.
🤖 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.
Nitpick comments:
In `@layers/nuxi/app/composables/useChatRouteId.ts`:
- Line 4: The regex used in resolveChatRouteId is being recreated on every call,
which is unnecessary for a hot path triggered by route changes. Hoist the
/^\/dashboard\/chat\/([^/]+)\/?$/ pattern to a module-level constant in
useChatRouteId.ts and have resolveChatRouteId reuse that shared regex instead of
instantiating it inside the function.
- Around line 1-6: The fallback in resolveChatRouteId is parsing route.path with
a regex, which can misclassify future static /dashboard/chat child routes as an
id. Update resolveChatRouteId to prefer the route param/context for the [id]
page and only use path parsing as a last resort, or remove the regex fallback if
the caller can pass the route param reliably. Keep the fix localized to
resolveChatRouteId and the code that supplies its param/path inputs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d3b00555-59d6-4782-bc44-8dedb39b65aa
📒 Files selected for processing (8)
app/layouts/dashboard.vuelayers/nuxi/app/composables/eve/useEveChat.tslayers/nuxi/app/composables/useAgentChat.tslayers/nuxi/app/composables/useAgentChatSession.tslayers/nuxi/app/composables/useChatDetail.tslayers/nuxi/app/composables/useChatDetailCache.tslayers/nuxi/app/composables/useChatRouteId.tslayers/nuxi/app/pages/dashboard/chat/[id].vue
🚧 Files skipped from review as they are similar to previous changes (7)
- app/layouts/dashboard.vue
- layers/nuxi/app/composables/useChatDetailCache.ts
- layers/nuxi/app/composables/useChatDetail.ts
- layers/nuxi/app/composables/eve/useEveChat.ts
- layers/nuxi/app/composables/useAgentChat.ts
- layers/nuxi/app/composables/useAgentChatSession.ts
- layers/nuxi/app/pages/dashboard/chat/[id].vue
…tail Consolidate five overlapping composables to simplify the chat stack without changing behavior.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
layers/nuxi/app/composables/useChatDetail.ts (1)
111-140: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winRace condition: stale
$fetchresponse can overwrite data for the current chat.
refresh()captureschatIdValue = id.valueat the start but never re-checks it after theawait $fetch(...)resolves (or rejects). Ifidchanges while a fetch is in-flight (e.g. the user quickly navigates from chat A to chat B, triggering thewatch([id, loggedIn], ...)again), an older, slower response for chat A can still land and overwritedata/status/errorafter the newer chat B's own fetch — the currently-viewed chat can end up displaying another chat's messages.🛡️ Suggested fix: guard against out-of-order responses
try { - data.value = await $fetch<ChatDetail>(`/api/chats/${chatIdValue}`) - status.value = 'success' + const result = await $fetch<ChatDetail>(`/api/chats/${chatIdValue}`) + if (id.value !== chatIdValue) return + data.value = result + status.value = 'success' } catch (err) { + if (id.value !== chatIdValue) return data.value = null error.value = err as Error status.value = 'error' }🤖 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 `@layers/nuxi/app/composables/useChatDetail.ts` around lines 111 - 140, The refresh() logic in useChatDetail can apply an out-of-date $fetch result after id changes, so add an out-of-order response guard around the async request. Capture the current chatIdValue at the start of refresh(), then after await $fetch<ChatDetail>(`/api/chats/${chatIdValue}`) and inside the catch path, verify id.value still matches that same chatIdValue before mutating data, error, or status. If the active id has changed, ignore the stale result so the watch([id, loggedIn], ...) flow cannot overwrite the currently selected chat.
🧹 Nitpick comments (1)
layers/nuxi/app/composables/useNuxiChat.ts (1)
419-425: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPersistence failure silently drops the user's message.
If
persistUserMessagerejects,eveChat.sendandagent.onMessageSent()never run. SinceonSubmitalready clearedinput/attachments, the message is lost from the UI and never delivered to the agent, with no user-visible error. Consider handling the failure (e.g., surface a toast and/or still send to Eve, or restore input) rather than letting the rejection abort the flow.🤖 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 `@layers/nuxi/app/composables/useNuxiChat.ts` around lines 419 - 425, The submit flow in useNuxiChat’s onSubmit currently lets a persistUserMessage rejection abort eveChat.send and agent.onMessageSent, which drops the user’s message after the input is already cleared. Update the onSubmit path to handle persistUserMessage failures explicitly: catch the error around persistUserMessage, surface an error state/toast or restore the draft if needed, and still decide whether to continue with eveChat.send so the message is not silently lost. Use the existing onSubmit, persistUserMessage, eveChat.send, and agent.onMessageSent flow as the place to fix it.
🤖 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 `@layers/nuxi/app/composables/useChatDetail.ts`:
- Around line 25-32: uiMessagesToRows is generating a fresh timestamp with new
Date().toISOString() whenever createdAt is missing, so repeated use during
useNuxiChat streaming updates changes the same message’s time on every patch.
Update uiMessagesToRows to preserve a stable createdAt for messages without
metadata by reusing an existing timestamp from the current row/cache or by
assigning it once when the message first appears, and keep the value unchanged
on subsequent calls from patchChatDetailCache.
- Around line 34-59: useChatDetail is not bound to the shared Nuxt cache ref, so
updates from seedChatDetailCache and patchChatDetailCache do not propagate to an
active chat view. Update useChatDetail to read directly from
useNuxtData(chatDetailCacheKey(chatId)).data, or watch and mirror that shared
ref, so title/state/messages stay synchronized with the cache; keep the
client-only cache helpers seedChatDetailCache and patchChatDetailCache updating
the same source of truth.
---
Outside diff comments:
In `@layers/nuxi/app/composables/useChatDetail.ts`:
- Around line 111-140: The refresh() logic in useChatDetail can apply an
out-of-date $fetch result after id changes, so add an out-of-order response
guard around the async request. Capture the current chatIdValue at the start of
refresh(), then after await $fetch<ChatDetail>(`/api/chats/${chatIdValue}`) and
inside the catch path, verify id.value still matches that same chatIdValue
before mutating data, error, or status. If the active id has changed, ignore the
stale result so the watch([id, loggedIn], ...) flow cannot overwrite the
currently selected chat.
---
Nitpick comments:
In `@layers/nuxi/app/composables/useNuxiChat.ts`:
- Around line 419-425: The submit flow in useNuxiChat’s onSubmit currently lets
a persistUserMessage rejection abort eveChat.send and agent.onMessageSent, which
drops the user’s message after the input is already cleared. Update the onSubmit
path to handle persistUserMessage failures explicitly: catch the error around
persistUserMessage, surface an error state/toast or restore the draft if needed,
and still decide whether to continue with eveChat.send so the message is not
silently lost. Use the existing onSubmit, persistUserMessage, eveChat.send, and
agent.onMessageSent flow as the place to fix it.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2076970d-26c1-4245-9223-2bcae6bbffa5
📒 Files selected for processing (8)
layers/nuxi/app/components/agent/AgentPanelChat.vuelayers/nuxi/app/composables/useChatDetail.tslayers/nuxi/app/composables/useChatVotes.tslayers/nuxi/app/composables/useChats.tslayers/nuxi/app/composables/useNuxiChat.tslayers/nuxi/app/composables/useStartChat.tslayers/nuxi/app/pages/dashboard/chat/[id].vuenuxt.config.ts
💤 Files with no reviewable changes (1)
- layers/nuxi/app/composables/useChatVotes.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- nuxt.config.ts
- layers/nuxi/app/composables/useStartChat.ts
- layers/nuxi/app/composables/useChats.ts
- layers/nuxi/app/components/agent/AgentPanelChat.vue
- layers/nuxi/app/pages/dashboard/chat/[id].vue
| sourcemap: true, | ||
| experimental: { | ||
| extractAsyncDataHandlers: true, | ||
| viewTransition: true, |
There was a problem hiding this comment.
why are we turning off view transitions?
|
I mean, I trust you - but what should I be reviewing here? would you mind drafting a description? 🙏 |
I marked it as "ready" just to get some feedback from Code Rabbit, I forgot to change it back to "draft" (and yes, I'll add a description) |
No description provided.