Sebastian Herrlinger 4 日 前
コミット
6cabcc84a8

+ 9 - 4
packages/opencode/src/cli/cmd/tui/app.tsx

@@ -55,7 +55,7 @@ import { KVProvider, useKV } from "./context/kv"
 import { Provider } from "@/provider/provider"
 import { ArgsProvider, useArgs, type Args } from "./context/args"
 import open from "open"
-import { PromptRefProvider, usePromptRef } from "./context/prompt"
+import { homeScope, PromptRefProvider, sessionScope, usePromptRef } from "./context/prompt"
 import { TuiConfigProvider, useTuiConfig } from "./context/tui-config"
 import { TuiConfig } from "@/config/tui"
 import { createTuiApi, TuiPluginRuntime, type RouteMap } from "./plugin"
@@ -195,6 +195,7 @@ export function tui(input: {
 function App(props: { onSnapshot?: () => Promise<string[]> }) {
   const tuiConfig = useTuiConfig()
   const route = useRoute()
+  const project = useProject()
   const dimensions = useTerminalDimensions()
   const renderer = useRenderer()
   const dialog = useDialog()
@@ -416,9 +417,13 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
         aliases: ["clear"],
       },
       onSelect: () => {
-        const current = promptRef.current
-        // Don't require focus - if there's any text, preserve it
-        const currentPrompt = current?.current?.input ? current.current : undefined
+        const currentPrompt =
+          route.data.type === "session" ? promptRef.current(sessionScope(route.data.sessionID)) : undefined
+
+        if (currentPrompt) {
+          promptRef.apply(homeScope(project.workspace.current()), currentPrompt)
+        }
+
         route.navigate({
           type: "home",
           initialPrompt: currentPrompt,

+ 131 - 56
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

@@ -11,8 +11,9 @@ import { useSDK } from "@tui/context/sdk"
 import { useRoute } from "@tui/context/route"
 import { useSync } from "@tui/context/sync"
 import { useEvent } from "@tui/context/event"
+import { homeScope, sessionScope, usePromptRef } from "@tui/context/prompt"
 import { MessageID, PartID } from "@/session/schema"
-import { createStore, produce } from "solid-js/store"
+import { createStore, produce, unwrap } from "solid-js/store"
 import { useKeybind } from "@tui/context/keybind"
 import { usePromptHistory, type PromptInfo } from "./history"
 import { assign } from "./part"
@@ -57,6 +58,7 @@ export type PromptProps = {
 export type PromptRef = {
   focused: boolean
   current: PromptInfo
+  snapshot(): PromptInfo
   set(prompt: PromptInfo): void
   reset(): void
   blur(): void
@@ -85,6 +87,7 @@ export function Prompt(props: PromptProps) {
   const route = useRoute()
   const sync = useSync()
   const dialog = useDialog()
+  const promptState = usePromptRef()
   const toast = useToast()
   const status = createMemo(() => sync.data.session_status?.[props.sessionID ?? ""] ?? { type: "idle" })
   const history = usePromptHistory()
@@ -98,6 +101,14 @@ export function Prompt(props: PromptProps) {
   const [auto, setAuto] = createSignal<AutocompleteRef>()
   const currentProviderLabel = createMemo(() => local.model.parsed().provider)
   const hasRightContent = createMemo(() => Boolean(props.right))
+  const scope = createMemo(() => {
+    if (props.sessionID) return sessionScope(props.sessionID)
+    if (props.workspaceID !== undefined) return homeScope(props.workspaceID)
+    if (route.data.type === "session") return sessionScope(route.data.sessionID)
+    if (route.data.type === "plugin") return `plugin:${route.data.id}`
+    return homeScope()
+  })
+  let active: string | undefined
 
   function promptModelWarning() {
     toast.show({
@@ -177,6 +188,7 @@ export function Prompt(props: PromptProps) {
     extmarkToPartIndex: new Map(),
     interrupt: 0,
   })
+  const [ready, setReady] = createSignal(false)
 
   createEffect(
     on(
@@ -219,8 +231,7 @@ export function Prompt(props: PromptProps) {
         category: "Prompt",
         hidden: true,
         onSelect: (dialog) => {
-          input.extmarks.clear()
-          input.clear()
+          ref.reset()
           dialog.clear()
         },
       },
@@ -397,12 +408,61 @@ export function Prompt(props: PromptProps) {
     ]
   })
 
+  function clearPrompt(mode: "keep" | "normal" = "keep") {
+    input.extmarks.clear()
+    input.clear()
+    setStore("prompt", {
+      input: "",
+      parts: [],
+    })
+    setStore("extmarkToPartIndex", new Map())
+    if (mode === "normal") {
+      setStore("mode", "normal")
+    }
+  }
+
+  function restorePrompt(prompt: PromptInfo) {
+    const next = structuredClone(unwrap(prompt))
+    input.setText(next.input)
+    setStore("mode", next.mode ?? "normal")
+    setStore("prompt", {
+      input: next.input,
+      parts: next.parts,
+    })
+    restoreExtmarksFromParts(next.parts)
+    input.gotoBufferEnd()
+  }
+
+  function snapshot() {
+    const value = input && !input.isDestroyed ? input.plainText : store.prompt.input
+
+    if (input && !input.isDestroyed) {
+      if (value !== store.prompt.input) {
+        setStore("prompt", "input", value)
+      }
+      syncExtmarksWithPromptParts()
+    }
+
+    return {
+      input: value,
+      mode: store.mode,
+      parts: structuredClone(unwrap(store.prompt.parts)),
+    } satisfies PromptInfo
+  }
+
   const ref: PromptRef = {
     get focused() {
       return input.focused
     },
     get current() {
-      return store.prompt
+      return {
+        input: store.prompt.input,
+        mode: store.mode,
+        parts: store.prompt.parts,
+      }
+    },
+    snapshot() {
+      return snapshot()
     },
     focus() {
       input.focus()
@@ -411,26 +471,58 @@ export function Prompt(props: PromptProps) {
       input.blur()
     },
     set(prompt) {
-      input.setText(prompt.input)
-      setStore("prompt", prompt)
-      restoreExtmarksFromParts(prompt.parts)
-      input.gotoBufferEnd()
+      restorePrompt(prompt)
+      if (active) {
+        promptState.save(active, snapshot())
+      }
     },
     reset() {
-      input.clear()
-      input.extmarks.clear()
-      setStore("prompt", {
-        input: "",
-        parts: [],
-      })
-      setStore("extmarkToPartIndex", new Map())
+      clearPrompt()
+      if (active) {
+        promptState.drop(active)
+      }
     },
     submit() {
       submit()
     },
   }
 
+  createEffect(() => {
+    if (!ready()) return
+
+    const next = scope()
+    if (active === next) return
+
+    const prev = active
+    if (prev) {
+      promptState.save(prev, snapshot())
+      promptState.unbind(prev, ref)
+    }
+
+    active = next
+
+    const draft = promptState.load(next)
+    if (draft) {
+      restorePrompt(draft)
+    } else if (!prev) {
+      const prompt = snapshot()
+      if (prompt.input || prompt.parts.length > 0) {
+        promptState.save(next, prompt)
+      } else {
+        clearPrompt("normal")
+      }
+    } else {
+      clearPrompt("normal")
+    }
+
+    promptState.bind(next, ref)
+  })
+
   onCleanup(() => {
+    if (active) {
+      promptState.save(active, snapshot())
+      promptState.unbind(active, ref)
+    }
     props.ref?.(undefined)
   })
 
@@ -538,17 +630,15 @@ export function Prompt(props: PromptProps) {
       title: "Stash prompt",
       value: "prompt.stash",
       category: "Prompt",
-      enabled: !!store.prompt.input,
+      enabled: !!store.prompt.input || store.prompt.parts.length > 0,
       onSelect: (dialog) => {
-        if (!store.prompt.input) return
+        const prompt = snapshot()
+        if (!prompt.input && prompt.parts.length === 0) return
         stash.push({
-          input: store.prompt.input,
-          parts: store.prompt.parts,
+          input: prompt.input,
+          parts: prompt.parts,
         })
-        input.extmarks.clear()
-        input.clear()
-        setStore("prompt", { input: "", parts: [] })
-        setStore("extmarkToPartIndex", new Map())
+        ref.reset()
         dialog.clear()
       },
     },
@@ -589,17 +679,13 @@ export function Prompt(props: PromptProps) {
   ])
 
   async function submit() {
-    // IME: double-defer may fire before onContentChange flushes the last
-    // composed character (e.g. Korean hangul) to the store, so read
-    // plainText directly and sync before any downstream reads.
-    if (input && !input.isDestroyed && input.plainText !== store.prompt.input) {
-      setStore("prompt", "input", input.plainText)
-      syncExtmarksWithPromptParts()
-    }
+    const prompt = snapshot()
+
     if (props.disabled) return
     if (autocomplete?.visible) return
-    if (!store.prompt.input) return
-    const trimmed = store.prompt.input.trim()
+    if (!prompt.input) return
+
+    const trimmed = prompt.input.trim()
     if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") {
       exit()
       return
@@ -631,7 +717,7 @@ export function Prompt(props: PromptProps) {
     }
 
     const messageID = MessageID.ascending()
-    let inputText = store.prompt.input
+    let inputText = prompt.input
 
     // Expand pasted text inline before submitting
     const allExtmarks = input.extmarks.getAllForTypeId(promptPartTypeId)
@@ -640,7 +726,7 @@ export function Prompt(props: PromptProps) {
     for (const extmark of sortedExtmarks) {
       const partIndex = store.extmarkToPartIndex.get(extmark.id)
       if (partIndex !== undefined) {
-        const part = store.prompt.parts[partIndex]
+        const part = prompt.parts[partIndex]
         if (part?.type === "text" && part.text) {
           const before = inputText.slice(0, extmark.start)
           const after = inputText.slice(extmark.end)
@@ -650,13 +736,13 @@ export function Prompt(props: PromptProps) {
     }
 
     // Filter out text parts (pasted content) since they're now expanded inline
-    const nonTextParts = store.prompt.parts.filter((part) => part.type !== "text")
+    const nonTextParts = prompt.parts.filter((part) => part.type !== "text")
 
     // Capture mode before it gets reset
-    const currentMode = store.mode
+    const currentMode = prompt.mode ?? "normal"
     const variant = local.model.variant.current()
 
-    if (store.mode === "shell") {
+    if (currentMode === "shell") {
       sdk.client.session.shell({
         sessionID,
         agent: local.agent.current().name,
@@ -717,16 +803,11 @@ export function Prompt(props: PromptProps) {
         })
         .catch(() => {})
     }
-    history.append({
-      ...store.prompt,
-      mode: currentMode,
-    })
-    input.extmarks.clear()
-    setStore("prompt", {
-      input: "",
-      parts: [],
-    })
-    setStore("extmarkToPartIndex", new Map())
+    history.append(prompt)
+    clearPrompt()
+    if (active) {
+      promptState.drop(active)
+    }
     props.onSubmit?.()
 
     // temporary hack to make sure the message is sent
@@ -737,7 +818,6 @@ export function Prompt(props: PromptProps) {
           sessionID,
         })
       }, 50)
-    input.clear()
   }
   const exit = useExit()
 
@@ -944,14 +1024,8 @@ export function Prompt(props: PromptProps) {
                   }
                   // If no image, let the default paste behavior continue
                 }
-                if (keybind.match("input_clear", e) && store.prompt.input !== "") {
-                  input.clear()
-                  input.extmarks.clear()
-                  setStore("prompt", {
-                    input: "",
-                    parts: [],
-                  })
-                  setStore("extmarkToPartIndex", new Map())
+                if (keybind.match("input_clear", e) && (store.prompt.input !== "" || store.prompt.parts.length > 0)) {
+                  ref.reset()
                   return
                 }
                 if (keybind.match("app_exit", e)) {
@@ -1090,6 +1164,7 @@ export function Prompt(props: PromptProps) {
                 if (promptPartTypeId === 0) {
                   promptPartTypeId = input.extmarks.registerType("prompt-part")
                 }
+                setReady(true)
                 props.ref?.(ref)
                 setTimeout(() => {
                   // setTimeout is a workaround and needs to be addressed properly

+ 63 - 5
packages/opencode/src/cli/cmd/tui/context/prompt.tsx

@@ -1,17 +1,75 @@
 import { createSimpleContext } from "./helper"
 import type { PromptRef } from "../component/prompt"
+import type { PromptInfo } from "../component/prompt/history"
+
+export function homeScope(workspaceID?: string) {
+  if (!workspaceID) return "home"
+  return `home:${workspaceID}`
+}
+
+export function sessionScope(sessionID: string) {
+  return `session:${sessionID}`
+}
+
+function clone(prompt: PromptInfo) {
+  return structuredClone(prompt)
+}
+
+function empty(prompt?: PromptInfo) {
+  if (!prompt) return true
+  if (prompt.input) return false
+  return prompt.parts.length === 0
+}
 
 export const { use: usePromptRef, provider: PromptRefProvider } = createSimpleContext({
   name: "PromptRef",
   init: () => {
-    let current: PromptRef | undefined
+    const drafts = new Map<string, PromptInfo>()
+    const refs = new Map<string, PromptRef>()
+
+    function load(scope: string) {
+      const prompt = drafts.get(scope)
+      if (!prompt) return
+      return clone(prompt)
+    }
+
+    function save(scope: string, prompt: PromptInfo) {
+      if (empty(prompt)) {
+        drafts.delete(scope)
+        return
+      }
+
+      drafts.set(scope, clone(prompt))
+    }
 
     return {
-      get current() {
-        return current
+      current(scope: string) {
+        const ref = refs.get(scope)
+        if (ref) {
+          const prompt = ref.snapshot()
+          if (!empty(prompt)) return prompt
+          return
+        }
+
+        return load(scope)
+      },
+      load,
+      save,
+      apply(scope: string, prompt: PromptInfo) {
+        save(scope, prompt)
+        const ref = refs.get(scope)
+        if (!ref) return
+        ref.set(prompt)
+      },
+      drop(scope: string) {
+        drafts.delete(scope)
+      },
+      bind(scope: string, ref: PromptRef) {
+        refs.set(scope, ref)
       },
-      set(ref: PromptRef | undefined) {
-        current = ref
+      unbind(scope: string, ref: PromptRef) {
+        if (refs.get(scope) !== ref) return
+        refs.delete(scope)
       },
     }
   },

+ 32 - 14
packages/opencode/src/cli/cmd/tui/routes/home.tsx

@@ -1,45 +1,63 @@
 import { Prompt, type PromptRef } from "@tui/component/prompt"
-import { createEffect, createSignal } from "solid-js"
+import { createEffect, createMemo, createSignal } from "solid-js"
 import { Logo } from "../component/logo"
 import { useProject } from "../context/project"
 import { useSync } from "../context/sync"
 import { Toast } from "../ui/toast"
 import { useArgs } from "../context/args"
 import { useRouteData } from "@tui/context/route"
-import { usePromptRef } from "../context/prompt"
+import { homeScope, usePromptRef } from "../context/prompt"
 import { useLocal } from "../context/local"
 import { TuiPluginRuntime } from "../plugin"
 
-// TODO: what is the best way to do this?
-let once = false
+let startup = false
 const placeholder = {
   normal: ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"],
   shell: ["ls -la", "git status", "pwd"],
 }
 
+type Ref = Pick<PromptRef, "current" | "submit">
+
 export function Home() {
   const sync = useSync()
   const project = useProject()
   const route = useRouteData("home")
   const promptRef = usePromptRef()
-  const [ref, setRef] = createSignal<PromptRef | undefined>()
+  const [ref, setRef] = createSignal<Ref | undefined>()
   const args = useArgs()
   const local = useLocal()
+  const scope = createMemo(() => homeScope(project.workspace.current()))
   let sent = false
+  let seeded: string | undefined
 
-  const bind = (r: PromptRef | undefined) => {
+  const bind = (r: Ref | undefined) => {
     setRef(r)
-    promptRef.set(r)
-    if (once || !r) return
+  }
+
+  createEffect(() => {
+    const key = scope()
+    if (seeded === key) return
+
+    if (promptRef.current(key)) {
+      if (route.initialPrompt) {
+        seeded = key
+        startup = true
+      }
+      return
+    }
+
     if (route.initialPrompt) {
-      r.set(route.initialPrompt)
-      once = true
+      promptRef.apply(key, route.initialPrompt)
+      seeded = key
+      startup = true
       return
     }
-    if (!args.prompt) return
-    r.set({ input: args.prompt, parts: [] })
-    once = true
-  }
+
+    if (startup || !args.prompt) return
+
+    promptRef.apply(key, { input: args.prompt, parts: [] })
+    startup = true
+  })
 
   // Wait for sync and model store to be ready before auto-submitting --prompt
   createEffect(() => {

+ 16 - 20
packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx

@@ -4,18 +4,16 @@ import { DialogSelect } from "@tui/ui/dialog-select"
 import { useSDK } from "@tui/context/sdk"
 import { useRoute } from "@tui/context/route"
 import { Clipboard } from "@tui/util/clipboard"
+import { sessionScope, usePromptRef } from "@tui/context/prompt"
 import type { PromptInfo } from "@tui/component/prompt/history"
 import { strip } from "@tui/component/prompt/part"
 
-export function DialogMessage(props: {
-  messageID: string
-  sessionID: string
-  setPrompt?: (prompt: PromptInfo) => void
-}) {
+export function DialogMessage(props: { messageID: string; sessionID: string }) {
   const sync = useSync()
   const sdk = useSDK()
   const message = createMemo(() => sync.data.message[props.sessionID]?.find((x) => x.id === props.messageID))
   const route = useRoute()
+  const promptRef = usePromptRef()
 
   return (
     <DialogSelect
@@ -34,20 +32,18 @@ export function DialogMessage(props: {
               messageID: msg.id,
             })
 
-            if (props.setPrompt) {
-              const parts = sync.data.part[msg.id]
-              const promptInfo = parts.reduce(
-                (agg, part) => {
-                  if (part.type === "text") {
-                    if (!part.synthetic) agg.input += part.text
-                  }
-                  if (part.type === "file") agg.parts.push(strip(part))
-                  return agg
-                },
-                { input: "", parts: [] as PromptInfo["parts"] },
-              )
-              props.setPrompt(promptInfo)
-            }
+            const parts = sync.data.part[msg.id]
+            const promptInfo = parts.reduce(
+              (agg, part) => {
+                if (part.type === "text") {
+                  if (!part.synthetic) agg.input += part.text
+                }
+                if (part.type === "file") agg.parts.push(strip(part))
+                return agg
+              },
+              { input: "", parts: [] as PromptInfo["parts"] },
+            )
+            promptRef.apply(sessionScope(props.sessionID), promptInfo)
 
             dialog.clear()
           },
@@ -90,7 +86,7 @@ export function DialogMessage(props: {
                   if (part.type === "text") {
                     if (!part.synthetic) agg.input += part.text
                   }
-                  if (part.type === "file") agg.parts.push(part)
+                  if (part.type === "file") agg.parts.push(strip(part))
                   return agg
                 },
                 { input: "", parts: [] as PromptInfo["parts"] },

+ 2 - 9
packages/opencode/src/cli/cmd/tui/routes/session/dialog-timeline.tsx

@@ -5,13 +5,8 @@ import type { TextPart } from "@opencode-ai/sdk/v2"
 import { Locale } from "@/util/locale"
 import { DialogMessage } from "./dialog-message"
 import { useDialog } from "../../ui/dialog"
-import type { PromptInfo } from "../../component/prompt/history"
 
-export function DialogTimeline(props: {
-  sessionID: string
-  onMove: (messageID: string) => void
-  setPrompt?: (prompt: PromptInfo) => void
-}) {
+export function DialogTimeline(props: { sessionID: string; onMove: (messageID: string) => void }) {
   const sync = useSync()
   const dialog = useDialog()
 
@@ -33,9 +28,7 @@ export function DialogTimeline(props: {
         value: message.id,
         footer: Locale.time(message.time.created),
         onSelect: (dialog) => {
-          dialog.replace(() => (
-            <DialogMessage messageID={message.id} sessionID={props.sessionID} setPrompt={props.setPrompt} />
-          ))
+          dialog.replace(() => <DialogMessage messageID={message.id} sessionID={props.sessionID} />)
         },
       })
     }

+ 26 - 23
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

@@ -22,7 +22,7 @@ import { SplitBorder } from "@tui/component/border"
 import { Spinner } from "@tui/component/spinner"
 import { selectedForeground, useTheme } from "@tui/context/theme"
 import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA } from "@opentui/core"
-import { Prompt, type PromptRef } from "@tui/component/prompt"
+import { Prompt } from "@tui/component/prompt"
 import type {
   AssistantMessage,
   Part,
@@ -72,7 +72,7 @@ import { Toast, useToast } from "../../ui/toast"
 import { useKV } from "../../context/kv.tsx"
 import { Editor } from "../../util/editor"
 import stripAnsi from "strip-ansi"
-import { usePromptRef } from "../../context/prompt"
+import { sessionScope, usePromptRef } from "../../context/prompt"
 import { useExit } from "../../context/exit"
 import { Filesystem } from "@/util/filesystem"
 import { Global } from "@/global"
@@ -87,6 +87,7 @@ import { getScrollAcceleration } from "../../util/scroll"
 import { TuiPluginRuntime } from "../../plugin"
 import { DialogGoUpsell } from "../../component/dialog-go-upsell"
 import { SessionRetry } from "@/session/retry"
+import { strip } from "../../component/prompt/part"
 
 addDefaultParsers(parsers.parsers)
 
@@ -142,6 +143,8 @@ export function Session() {
   })
   const visible = createMemo(() => !session()?.parentID && permissions().length === 0 && questions().length === 0)
   const disabled = createMemo(() => permissions().length > 0 || questions().length > 0)
+  const scope = createMemo(() => sessionScope(route.sessionID))
+  let seeded: string | undefined
 
   const pending = createMemo(() => {
     return messages().findLast((x) => x.role === "assistant" && !x.time.completed)?.id
@@ -199,8 +202,6 @@ export function Session() {
       })
   })
 
-  // Handle initial prompt from fork
-  let seeded = false
   let lastSwitch: string | undefined = undefined
   event.on("message.part.updated", (evt) => {
     const part = evt.properties.part
@@ -218,15 +219,23 @@ export function Session() {
     }
   })
 
+  createEffect(() => {
+    const key = scope()
+    if (seeded === key) return
+
+    if (promptRef.current(key)) {
+      if (route.initialPrompt) seeded = key
+      return
+    }
+
+    if (!route.initialPrompt) return
+
+    promptRef.apply(key, route.initialPrompt)
+    seeded = key
+  })
+
   let scroll: ScrollBoxRenderable
-  let prompt: PromptRef | undefined
-  const bind = (r: PromptRef | undefined) => {
-    prompt = r
-    promptRef.set(r)
-    if (seeded || !route.initialPrompt || !r) return
-    seeded = true
-    r.set(route.initialPrompt)
-  }
+  const bind = () => {}
   const keybind = useKeybind()
   const dialog = useDialog()
   const renderer = useRenderer()
@@ -438,7 +447,6 @@ export function Session() {
               if (child) scroll.scrollBy(child.y - scroll.y - 1)
             }}
             sessionID={route.sessionID}
-            setPrompt={(promptInfo) => prompt?.set(promptInfo)}
           />
         ))
       },
@@ -539,13 +547,14 @@ export function Session() {
             toBottom()
           })
         const parts = sync.data.part[message.id]
-        prompt?.set(
+        promptRef.apply(
+          scope(),
           parts.reduce(
             (agg, part) => {
               if (part.type === "text") {
                 if (!part.synthetic) agg.input += part.text
               }
-              if (part.type === "file") agg.parts.push(part)
+              if (part.type === "file") agg.parts.push(strip(part))
               return agg
             },
             { input: "", parts: [] as PromptInfo["parts"] },
@@ -572,7 +581,7 @@ export function Session() {
           sdk.client.session.unrevert({
             sessionID: route.sessionID,
           })
-          prompt?.set({ input: "", parts: [] })
+          promptRef.apply(scope(), { input: "", parts: [] })
           return
         }
         sdk.client.session.revert({
@@ -1149,13 +1158,7 @@ export function Session() {
                         index={index()}
                         onMouseUp={() => {
                           if (renderer.getSelection()?.getSelectedText()) return
-                          dialog.replace(() => (
-                            <DialogMessage
-                              messageID={message.id}
-                              sessionID={route.sessionID}
-                              setPrompt={(promptInfo) => prompt?.set(promptInfo)}
-                            />
-                          ))
+                          dialog.replace(() => <DialogMessage messageID={message.id} sessionID={route.sessionID} />)
                         }}
                         message={message as UserMessage}
                         parts={sync.data.part[message.id] ?? []}