feat(web): add message edit, retry, and copy actions#48
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 49 minutes and 13 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe changes introduce history truncation functionality for chat conversations. A new backend method truncates session files at specified user message boundaries, a new HTTP endpoint handles history truncation requests, and the frontend adds message editing and retry capabilities with store actions and UI components to support conversation correction. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant UI as ChatMessage.vue
participant Store as useChatStore
participant API as api.ts
participant Server as server.go
participant Session as session.go
User->>UI: Click edit/retry on message
UI->>Store: Emit retry or edit event
Store->>Store: Count user messages before target
Store->>API: truncateHistory(beforeCount)
API->>Server: POST /api/history/truncate
Server->>Server: Truncate in-memory history slice
Server->>Session: TruncateAtUserMessage(beforeCount)
Session->>Session: Read JSONL, keep up to boundary
Session->>Session: Atomic file rewrite & rename
Session-->>Server: Success
Server-->>API: {status, session_id}
API-->>Store: Response received
Store->>Store: Update currentSessionId
Store->>Store: Clear streaming state
Store->>Store: Splice/update timeline
Store->>API: Send user message
UI->>UI: Update displayed history
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 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 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)
web/src/stores/chat.ts (1)
599-626: Validate the target is a user message; consider extracting shared logic.
editAndResenddoesn't verify the looked‑up message hasrole === 'user'— if it's ever invoked on an assistant message (e.g., the canEdit gating is bypassed via component reuse), the count becomes wrong (counts users before an assistant index) and the wrong portion of the conversation is truncated. Adding a guard makes the action robust independent of UI gating.Also,
retryFromMessageandeditAndResendshare the same truncation+resend pipeline (count →api.truncateHistory→ splice → reset streaming →sendMessage). Extracting a private helper would remove the duplication and keep the failure handling consistent in one place.♻️ Proposed sketch
async function editAndResend(messageId: string, newText: string) { - // Find the user message to edit - const msgIdx = timeline.value.findIndex(i => i.kind === 'message' && i.data.id === messageId) - if (msgIdx === -1) return + // Find the user message to edit + const msgIdx = timeline.value.findIndex( + i => i.kind === 'message' && i.data.id === messageId && i.data.role === 'user', + ) + if (msgIdx === -1) return🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/stores/chat.ts` around lines 599 - 626, The editAndResend function can operate on non-user messages and miscount truncation; add a guard after locating msgIdx to verify timeline.value[msgIdx].data.role === 'user' (return early if not) to ensure the count of beforeUserMessage is correct; then extract the shared truncation+resend pipeline used by editAndResend and retryFromMessage into a private helper (e.g., truncateAndResend(beforeUserCount, newText)) that calls api.truncateHistory, updates currentSessionId, splices timeline.value from the given index, resets streamingText and streamingMsgId, and finally calls sendMessage, consolidating error handling and keeping behavior consistent across both callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/web/server.go`:
- Around line 627-639: handleTruncateHistory currently mutates s.history and
rewrites the session file without checking s.running, causing races with the
agent goroutine; update the handler (function handleTruncateHistory) to check
s.running.Load() at the start and if running return HTTP 409 (like
handleSwitchModel/handleSwitchProject) before acquiring s.mu, thus rejecting
truncation while a run is in flight and preventing concurrent edits to s.history
and session file.
- Around line 674-686: The in-memory history (s.history) is being truncated
before persisting and when rec.TruncateAtUserMessage fails the client still
receives 200 OK and state diverges; fix by: perform the on-disk truncation with
rec.TruncateAtUserMessage before mutating s.history (or if you must mutate
first, revert s.history on failure), on error return an HTTP error response (use
writeJSON with 500 and the error message) instead of writing 200, and
mark/disable the recorder (e.g., set the recorder reference or a recorderActive
flag on the session) to prevent further appends until a successful truncation is
confirmed; locate the logic around rec.TruncateAtUserMessage, s.history
mutation, and the writeJSON response to implement these changes.
In `@web/src/components/ChatMessage.vue`:
- Around line 89-131: The action buttons row in ChatMessage.vue is hidden via
opacity (class "opacity-0 group-hover/msg:opacity-100") which makes keyboard
users tab to invisible controls; update the visibility rules so the buttons are
also revealed on keyboard focus — either add a focus-within variant to the
parent group (e.g., "group-focus-within/msg:opacity-100") or add a focus-visible
opacity override on each button (e.g., "focus-visible:opacity-100" on the
buttons that call copyContent, emit('retry'), and startEdit) so canRetry/canEdit
buttons become visible when focused; ensure you adjust the same element that
currently uses "group-hover/msg:opacity-100" so it covers the copyContent,
retry, and startEdit controls.
- Around line 21-26: The copyContent function calls
navigator.clipboard.writeText(props.message.content) without handling promise
rejections; update copyContent to attach a .catch handler to
navigator.clipboard.writeText so failures are logged (e.g., console.error) and
the UI still gives feedback (for example set copied.value to false or set a
transient error state) and ensure the timeout cleanup still runs; reference the
copyContent function, copied reactive ref, and props.message.content when
implementing the change.
- Around line 149-155: The textarea’s HTML autofocus is unreliable when it’s
mounted dynamically; replace the autofocus attribute with a template ref (e.g.,
editTextareaRef) on the textarea and, in the ChatMessage component, use Vue's
nextTick to call focus() (and optionally select()) when editing.value becomes
true — implement this either in the method that sets editing.value = true or in
a watch on editing to run nextTick(() => editTextareaRef.value?.focus() ||
editTextareaRef.value?.select()); remove the static autofocus attribute and
ensure the ref name matches where handleEditKeyDown and editText are used so the
textarea receives keyboard focus immediately after mount.
In `@web/src/stores/chat.ts`:
- Around line 580-597: The try/catch around api.truncateHistory should stop the
flow on error instead of proceeding to mutate the frontend timeline and call
sendMessage; update the logic in the function containing this block so that if
api.truncateHistory throws you log/surface the error to the user (e.g., via the
existing UI error handler or a visible notification), do not call
timeline.value.splice(userMsgIdx), do not reset streamingText/streamingMsgId,
and return early (preventing sendMessage from being invoked); reference
api.truncateHistory, currentSessionId, timeline.value.splice, and sendMessage to
locate and modify the code accordingly.
---
Nitpick comments:
In `@web/src/stores/chat.ts`:
- Around line 599-626: The editAndResend function can operate on non-user
messages and miscount truncation; add a guard after locating msgIdx to verify
timeline.value[msgIdx].data.role === 'user' (return early if not) to ensure the
count of beforeUserMessage is correct; then extract the shared truncation+resend
pipeline used by editAndResend and retryFromMessage into a private helper (e.g.,
truncateAndResend(beforeUserCount, newText)) that calls api.truncateHistory,
updates currentSessionId, splices timeline.value from the given index, resets
streamingText and streamingMsgId, and finally calls sendMessage, consolidating
error handling and keeping behavior consistent across both callers.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 80ac09c7-e6af-4ed4-97ca-e8c63a83560e
📒 Files selected for processing (6)
internal/session/session.gointernal/web/server.goweb/src/App.vueweb/src/components/ChatMessage.vueweb/src/composables/api.tsweb/src/stores/chat.ts
- Guard truncate endpoint with s.running check (409 if running) - Persist session file before mutating in-memory history; return 500 on failure - Add keyboard accessibility (focus-within) for action buttons - Handle clipboard writeText rejection - Replace HTML autofocus with template ref + nextTick - Return early in retry/edit if backend truncation fails - Add role === 'user' guard in editAndResend
Summary
Verification
Summary by CodeRabbit
Release Notes