From d2ca39f37183d769ed228e0861c36becfe6a3c65 Mon Sep 17 00:00:00 2001 From: "pouriade.com" Date: Tue, 27 Oct 2026 22:36:10 -0700 Subject: [PATCH] Regenerate a reply as before through one helper Try again and Regenerate both rebuilt the reply's parent and model; regenerateAsBefore keeps the never-switch-the-model rule in one place. --- .../conversation/components/ConversationView.tsx | 8 ++------ .../conversation/components/message-actions.tsx | 8 ++------ apps/web/src/features/conversation/lib/branch.ts | 12 ++++++++++++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/apps/web/src/features/conversation/components/ConversationView.tsx b/apps/web/src/features/conversation/components/ConversationView.tsx index 14d1f29..8981d31 100644 --- a/apps/web/src/features/conversation/components/ConversationView.tsx +++ b/apps/web/src/features/conversation/components/ConversationView.tsx @@ -12,6 +12,7 @@ import { useConversationActions } from '../hooks/useConversationActions.ts' import { useFileDrop } from '../hooks/useFileDrop.ts' import { type Following, useAtBottom, useStickToBottom } from '../hooks/useStickToBottom.ts' import { blobUrlFor } from '../lib/blob-url.ts' +import { regenerateAsBefore } from '../lib/branch.ts' import { outlineOf } from '../lib/outline.ts' import { BranchComposer } from './BranchComposer.tsx' import { BranchView } from './BranchView.tsx' @@ -103,13 +104,8 @@ export function ConversationView(props: Props) { blobUrl={blobUrl} onPick={pick} actions={(message) => } - // A failed reply tries again with its own model, never one the branch falls back to. retry={(message) => ( - - regenerate(message.record.parent ?? null, message.record.model ?? null) - } - /> + regenerateAsBefore(regenerate, message)} /> )} arrived={(message) => !opened?.has(message.rkey)} /> diff --git a/apps/web/src/features/conversation/components/message-actions.tsx b/apps/web/src/features/conversation/components/message-actions.tsx index cab0416..3b29cc9 100644 --- a/apps/web/src/features/conversation/components/message-actions.tsx +++ b/apps/web/src/features/conversation/components/message-actions.tsx @@ -7,7 +7,7 @@ import { IconButton } from '../../../shared/ui/Button.tsx' import { CopyButton } from '../../../shared/ui/CopyButton.tsx' import { messageText } from '../../../store/core.ts' import type { ModelOption } from '../../models/models.ts' -import type { BranchMessage } from '../lib/branch.ts' +import { type BranchMessage, parentOf, regenerateAsBefore } from '../lib/branch.ts' import { MoreActions } from './MoreActions.tsx' import { DetailsButton, recordRows } from './RecordDetails.tsx' @@ -46,7 +46,6 @@ export interface MessageAction { } const isUser = (message: BranchMessage) => message.record.role === 'user' -const parentOf = (message: BranchMessage) => message.record.parent ?? null const rowsOf = (props: MessageActionProps) => recordRows({ rkey: props.message.rkey, @@ -79,10 +78,7 @@ function RegenerateAction(props: MessageActionProps) { - props.operations.regenerate(parentOf(props.message), props.message.record.model ?? null) - } + onClick={() => regenerateAsBefore(props.operations.regenerate, props.message)} > diff --git a/apps/web/src/features/conversation/lib/branch.ts b/apps/web/src/features/conversation/lib/branch.ts index 006e75c..6507f8c 100644 --- a/apps/web/src/features/conversation/lib/branch.ts +++ b/apps/web/src/features/conversation/lib/branch.ts @@ -3,6 +3,18 @@ import type { MessageRecord } from '../../../shared/message.ts' export type BranchMessage = { rkey: string; record: MessageRecord } +/** The message a message answers, or null at the root. */ +export const parentOf = (message: BranchMessage) => message.record.parent ?? null + +/** + * Regenerate a reply as it was made: under the same message, with the reply's own model, so a + * reply never changes model on the user's behalf, even when it failed. + */ +export const regenerateAsBefore = ( + regenerate: (parent: string | null, model: ModelRef | null) => unknown, + reply: BranchMessage, +) => regenerate(parentOf(reply), reply.record.model ?? null) + const replyAttempt = (rkey: string) => Number(rkey.match(/\.r(\d+)$/)?.[1] ?? -1) /** Oldest first: regenerations by attempt number, everything else by creation time. */ -- knotmirror 0.0.1