diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 69065a8fa7a0..46eba95193f8 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1177,6 +1177,33 @@ export default function Page() { onStepsExpandedToggle={() => setStore("expanded", message.id, (open: boolean | undefined) => !open) } + onRevert={async (messageID) => { + const sessionID = params.id + if (!sessionID) return + if (status()?.type !== "idle") { + await sdk.client.session.abort({ sessionID }).catch(() => {}) + } + // Find the message in the list + const msgs = userMessages() + const idx = msgs.findIndex((m) => m.id === messageID) + if (idx === -1) return + + // If there's a next message, revert to that (keeping this message) + // If this is the last message, revert this message itself (classic undo) + const targetID = idx < msgs.length - 1 ? msgs[idx + 1].id : messageID + + await sdk.client.session.revert({ sessionID, messageID: targetID }) + + // Restore prompt from the target message + const parts = sync.data.part[targetID] + if (parts) { + const restored = extractPromptFromParts(parts, { directory: sdk.directory }) + prompt.set(restored) + } + + // Update active message to the one we reverted to or the one before + setActiveMessage(idx < msgs.length - 1 ? msgs[idx] : msgs[idx - 1]) + }} classes={{ root: "min-w-0 w-full relative", content: diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 25d4b4f36f5f..58d7bdb77b68 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -4,6 +4,7 @@ const icons = { "align-right": ``, "arrow-up": ``, "arrow-left": ``, + undo: ``, archive: ``, "bubble-5": ``, brain: ``, diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index ae1321bac140..bdd2d465c739 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -34,6 +34,8 @@ import { createStore } from "solid-js/store" import { DateTime, DurationUnit, Interval } from "luxon" import { createAutoScroll } from "../hooks" +import { DropdownMenu } from "./dropdown-menu" + function computeStatusFromPart(part: PartType | undefined): string | undefined { if (!part) return undefined @@ -124,6 +126,7 @@ export function SessionTurn( stepsExpanded?: boolean onStepsExpandedToggle?: () => void onUserInteracted?: () => void + onRevert?: (messageID: string) => void classes?: { root?: string content?: string @@ -514,7 +517,42 @@ export function SessionTurn( {/* User Message */}
- + + ( +
{ + e.preventDefault() + p.onClick(e) + }} + > + +
+ )} + /> + + + + props.onRevert?.(msg().id)}> + + Undo from here + + + { + const text = parts() + .map((p) => (p?.type === "text" ? (p as TextPart).text : "")) + .join("") + navigator.clipboard.writeText(text) + }} + > + + Copy message + + + +
{/* Trigger (sticky) */}