From fbd9b7cf4fb27433a066a10a197c7edb420336d1 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:34:48 -0500 Subject: feat(app): restore to message and fork session (#17092) --- packages/ui/src/components/icon.tsx | 2 + packages/ui/src/components/message-part.tsx | 59 +++++++++++++++++++++++++++++ packages/ui/src/components/session-turn.tsx | 11 +++++- packages/ui/src/i18n/en.ts | 2 + 4 files changed, 72 insertions(+), 2 deletions(-) (limited to 'packages/ui') diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 6486da851..7f4598f29 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -9,6 +9,7 @@ const icons = { "bubble-5": ``, prompt: ``, brain: ``, + fork: ``, "bullet-list": ``, "check-small": ``, "chevron-down": ``, @@ -80,6 +81,7 @@ const icons = { selector: ``, "arrow-down-to-line": ``, warning: ``, + reset: ``, link: ``, providers: ``, models: ``, diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 65b6229d6..1a50ed48c 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -129,12 +129,20 @@ function DiagnosticsDisplay(props: { diagnostics: Diagnostic[] }): JSX.Element { export interface MessageProps { message: MessageType parts: PartType[] + actions?: UserActions showAssistantCopyPartID?: string | null interrupted?: boolean queued?: boolean showReasoningSummaries?: boolean } +export type SessionAction = (input: { sessionID: string; messageID: string }) => Promise | void + +export type UserActions = { + fork?: SessionAction + revert?: SessionAction +} + export interface MessagePartProps { part: PartType message: MessageType @@ -676,6 +684,7 @@ export function Message(props: MessageProps) { @@ -872,6 +881,7 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) { export function UserMessageDisplay(props: { message: UserMessage parts: PartType[] + actions?: UserActions interrupted?: boolean queued?: boolean }) { @@ -879,6 +889,7 @@ export function UserMessageDisplay(props: { const dialog = useDialog() const i18n = useI18n() const [copied, setCopied] = createSignal(false) + const [busy, setBusy] = createSignal<"fork" | "revert" | undefined>() const textPart = createMemo( () => props.parts?.find((p) => p.type === "text" && !(p as TextPart).synthetic) as TextPart | undefined, @@ -945,6 +956,22 @@ export function UserMessageDisplay(props: { setTimeout(() => setCopied(false), 2000) } + const run = (kind: "fork" | "revert") => { + const act = kind === "fork" ? props.actions?.fork : props.actions?.revert + if (!act || busy()) return + setBusy(kind) + void Promise.resolve() + .then(() => + act({ + sessionID: props.message.sessionID, + messageID: props.message.id, + }), + ) + .finally(() => { + if (busy() === kind) setBusy(undefined) + }) + } + return (
0}> @@ -1012,6 +1039,38 @@ export function UserMessageDisplay(props: { + + + e.preventDefault()} + onClick={(event) => { + event.stopPropagation() + run("fork") + }} + aria-label={i18n.t("ui.message.forkMessage")} + /> + + + + + e.preventDefault()} + onClick={(event) => { + event.stopPropagation() + run("revert") + }} + aria-label={i18n.t("ui.message.revertMessage")} + /> + +
- +
diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index 7f4a4020a..3551f889d 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -127,6 +127,8 @@ export const dict: Record = { "ui.message.collapse": "Collapse message", "ui.message.copy": "Copy", "ui.message.copyMessage": "Copy message", + "ui.message.forkMessage": "Fork to new session", + "ui.message.revertMessage": "Reset to this point", "ui.message.copyResponse": "Copy response", "ui.message.copied": "Copied", "ui.message.interrupted": "Interrupted", -- cgit v1.2.3