mirror of
https://fastgit.cc/https://github.com/anomalyco/opencode
synced 2026-04-21 05:10:58 +08:00
fix: preserve prompt input across unmount/remount cycles (#22508)
This commit is contained in:
@@ -420,12 +420,8 @@ 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
|
||||
route.navigate({
|
||||
type: "home",
|
||||
initialPrompt: currentPrompt,
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useRoute } from "@tui/context/route"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { useEvent } from "@tui/context/event"
|
||||
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"
|
||||
@@ -75,6 +75,8 @@ function randomIndex(count: number) {
|
||||
return Math.floor(Math.random() * count)
|
||||
}
|
||||
|
||||
let stashed: { prompt: PromptInfo; cursor: number } | undefined
|
||||
|
||||
export function Prompt(props: PromptProps) {
|
||||
let input: TextareaRenderable
|
||||
let anchor: BoxRenderable
|
||||
@@ -433,7 +435,22 @@ export function Prompt(props: PromptProps) {
|
||||
},
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const saved = stashed
|
||||
stashed = undefined
|
||||
if (store.prompt.input) return
|
||||
if (saved && saved.prompt.input) {
|
||||
input.setText(saved.prompt.input)
|
||||
setStore("prompt", saved.prompt)
|
||||
restoreExtmarksFromParts(saved.prompt.parts)
|
||||
input.cursorOffset = saved.cursor
|
||||
}
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
if (store.prompt.input) {
|
||||
stashed = { prompt: unwrap(store.prompt), cursor: input.cursorOffset }
|
||||
}
|
||||
props.ref?.(undefined)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createStore, reconcile } from "solid-js/store"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import type { PromptInfo } from "../component/prompt/history"
|
||||
|
||||
export type HomeRoute = {
|
||||
type: "home"
|
||||
initialPrompt?: PromptInfo
|
||||
prompt?: PromptInfo
|
||||
}
|
||||
|
||||
export type SessionRoute = {
|
||||
type: "session"
|
||||
sessionID: string
|
||||
initialPrompt?: PromptInfo
|
||||
prompt?: PromptInfo
|
||||
}
|
||||
|
||||
export type PluginRoute = {
|
||||
@@ -37,7 +37,7 @@ export const { use: useRoute, provider: RouteProvider } = createSimpleContext({
|
||||
return store
|
||||
},
|
||||
navigate(route: Route) {
|
||||
setStore(route)
|
||||
setStore(reconcile(route))
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -91,7 +91,7 @@ function routeCurrent(route: ReturnType<typeof useRoute>): TuiPluginApi["route"]
|
||||
name: "session",
|
||||
params: {
|
||||
sessionID: route.data.sessionID,
|
||||
initialPrompt: route.data.initialPrompt,
|
||||
prompt: route.data.prompt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { 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
|
||||
const placeholder = {
|
||||
normal: ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"],
|
||||
@@ -31,8 +30,8 @@ export function Home() {
|
||||
setRef(r)
|
||||
promptRef.set(r)
|
||||
if (once || !r) return
|
||||
if (route.initialPrompt) {
|
||||
r.set(route.initialPrompt)
|
||||
if (route.prompt) {
|
||||
r.set(route.prompt)
|
||||
once = true
|
||||
return
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function DialogForkFromTimeline(props: { sessionID: string; onMove: (mess
|
||||
messageID: message.id,
|
||||
})
|
||||
const parts = sync.data.part[message.id] ?? []
|
||||
const initialPrompt = parts.reduce(
|
||||
const prompt = parts.reduce(
|
||||
(agg, part) => {
|
||||
if (part.type === "text") {
|
||||
if (!part.synthetic) agg.input += part.text
|
||||
@@ -51,7 +51,7 @@ export function DialogForkFromTimeline(props: { sessionID: string; onMove: (mess
|
||||
route.navigate({
|
||||
sessionID: forked.data!.id,
|
||||
type: "session",
|
||||
initialPrompt,
|
||||
prompt,
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
|
||||
@@ -81,25 +81,23 @@ export function DialogMessage(props: {
|
||||
sessionID: props.sessionID,
|
||||
messageID: props.messageID,
|
||||
})
|
||||
const initialPrompt = (() => {
|
||||
const msg = message()
|
||||
if (!msg) return undefined
|
||||
const parts = sync.data.part[msg.id]
|
||||
return parts.reduce(
|
||||
(agg, part) => {
|
||||
if (part.type === "text") {
|
||||
if (!part.synthetic) agg.input += part.text
|
||||
}
|
||||
if (part.type === "file") agg.parts.push(part)
|
||||
return agg
|
||||
},
|
||||
{ input: "", parts: [] as PromptInfo["parts"] },
|
||||
)
|
||||
})()
|
||||
const msg = message()
|
||||
const prompt = msg
|
||||
? sync.data.part[msg.id].reduce(
|
||||
(agg, part) => {
|
||||
if (part.type === "text") {
|
||||
if (!part.synthetic) agg.input += part.text
|
||||
}
|
||||
if (part.type === "file") agg.parts.push(part)
|
||||
return agg
|
||||
},
|
||||
{ input: "", parts: [] as PromptInfo["parts"] },
|
||||
)
|
||||
: undefined
|
||||
route.navigate({
|
||||
sessionID: result.data!.id,
|
||||
type: "session",
|
||||
initialPrompt,
|
||||
prompt,
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
|
||||
@@ -207,8 +207,6 @@ export function Session() {
|
||||
if (scroll) scroll.scrollBy(100_000)
|
||||
})
|
||||
|
||||
// Handle initial prompt from fork
|
||||
let seeded = false
|
||||
let lastSwitch: string | undefined = undefined
|
||||
event.on("message.part.updated", (evt) => {
|
||||
const part = evt.properties.part
|
||||
@@ -226,14 +224,15 @@ export function Session() {
|
||||
}
|
||||
})
|
||||
|
||||
let seeded = false
|
||||
let scroll: ScrollBoxRenderable
|
||||
let prompt: PromptRef | undefined
|
||||
const bind = (r: PromptRef | undefined) => {
|
||||
prompt = r
|
||||
promptRef.set(r)
|
||||
if (seeded || !route.initialPrompt || !r) return
|
||||
if (seeded || !route.prompt || !r) return
|
||||
seeded = true
|
||||
r.set(route.initialPrompt)
|
||||
r.set(route.prompt)
|
||||
}
|
||||
const keybind = useKeybind()
|
||||
const dialog = useDialog()
|
||||
|
||||
@@ -29,7 +29,7 @@ export type TuiRouteCurrent =
|
||||
name: "session"
|
||||
params: {
|
||||
sessionID: string
|
||||
initialPrompt?: unknown
|
||||
prompt?: unknown
|
||||
}
|
||||
}
|
||||
| {
|
||||
|
||||
Reference in New Issue
Block a user