refactor(tui): extract shared message actions
This commit is contained in:
parent
b956e9a06f
commit
b1491690cd
2 changed files with 78 additions and 53 deletions
|
|
@ -5,7 +5,7 @@ import { useSDK } from "@tui/context/sdk"
|
|||
import { useRoute } from "@tui/context/route"
|
||||
import * as Clipboard from "@tui/util/clipboard"
|
||||
import type { PromptInfo } from "@tui/component/prompt/history"
|
||||
import { strip } from "@tui/component/prompt/part"
|
||||
import { MessageActions } from "./message-actions"
|
||||
|
||||
export function DialogMessage(props: {
|
||||
messageID: string
|
||||
|
|
@ -26,29 +26,14 @@ export function DialogMessage(props: {
|
|||
value: "session.revert",
|
||||
description: "undo messages and file changes",
|
||||
onSelect: (dialog) => {
|
||||
const msg = message()
|
||||
if (!msg) return
|
||||
|
||||
void sdk.client.session.revert({
|
||||
if (!message()) return
|
||||
MessageActions.revert({
|
||||
sdk,
|
||||
sync,
|
||||
sessionID: props.sessionID,
|
||||
messageID: msg.id,
|
||||
messageID: props.messageID,
|
||||
setPrompt: props.setPrompt,
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
|
|
@ -57,18 +42,8 @@ export function DialogMessage(props: {
|
|||
value: "message.copy",
|
||||
description: "message text to clipboard",
|
||||
onSelect: async (dialog) => {
|
||||
const msg = message()
|
||||
if (!msg) return
|
||||
|
||||
const parts = sync.data.part[msg.id]
|
||||
const text = parts.reduce((agg, part) => {
|
||||
if (part.type === "text" && !part.synthetic) {
|
||||
agg += part.text
|
||||
}
|
||||
return agg
|
||||
}, "")
|
||||
|
||||
await Clipboard.copy(text)
|
||||
if (!message()) return
|
||||
await Clipboard.copy(MessageActions.collectText(sync, props.messageID))
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
|
|
@ -77,28 +52,14 @@ export function DialogMessage(props: {
|
|||
value: "session.fork",
|
||||
description: "create a new session",
|
||||
onSelect: async (dialog) => {
|
||||
const result = await sdk.client.session.fork({
|
||||
if (!message()) return
|
||||
await MessageActions.fork({
|
||||
sdk,
|
||||
sync,
|
||||
navigate: route.navigate,
|
||||
sessionID: props.sessionID,
|
||||
messageID: props.messageID,
|
||||
})
|
||||
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",
|
||||
prompt,
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import type { useSDK } from "@tui/context/sdk"
|
||||
import type { useSync } from "@tui/context/sync"
|
||||
import type { useRoute } from "@tui/context/route"
|
||||
import type { PromptInfo } from "@tui/component/prompt/history"
|
||||
import { strip } from "@tui/component/prompt/part"
|
||||
|
||||
type SDK = ReturnType<typeof useSDK>
|
||||
type Sync = ReturnType<typeof useSync>
|
||||
type Navigate = ReturnType<typeof useRoute>["navigate"]
|
||||
|
||||
// File parts keep their server identifiers when seeding a new session (fork) but
|
||||
// are stripped when the draft is re-submitted into the same session (revert).
|
||||
export function collectPrompt(sync: Sync, messageID: string, options: { stripFiles: boolean }): PromptInfo | undefined {
|
||||
const parts = sync.data.part[messageID]
|
||||
if (!parts) return
|
||||
return parts.reduce(
|
||||
(agg, part) => {
|
||||
if (part.type === "text") {
|
||||
if (!part.synthetic) agg.input += part.text
|
||||
}
|
||||
if (part.type === "file") agg.parts.push(options.stripFiles ? strip(part) : part)
|
||||
return agg
|
||||
},
|
||||
{ input: "", parts: [] as PromptInfo["parts"] },
|
||||
)
|
||||
}
|
||||
|
||||
export function collectText(sync: Sync, messageID: string) {
|
||||
const parts = sync.data.part[messageID] ?? []
|
||||
return parts.reduce((text, part) => {
|
||||
if (part.type === "text" && !part.synthetic) text += part.text
|
||||
return text
|
||||
}, "")
|
||||
}
|
||||
|
||||
export function revert(options: {
|
||||
sdk: SDK
|
||||
sync: Sync
|
||||
sessionID: string
|
||||
messageID: string
|
||||
setPrompt?: (prompt: PromptInfo) => void
|
||||
}) {
|
||||
void options.sdk.client.session.revert({ sessionID: options.sessionID, messageID: options.messageID })
|
||||
if (!options.setPrompt) return
|
||||
const prompt = collectPrompt(options.sync, options.messageID, { stripFiles: true })
|
||||
if (prompt) options.setPrompt(prompt)
|
||||
}
|
||||
|
||||
export async function fork(options: {
|
||||
sdk: SDK
|
||||
sync: Sync
|
||||
navigate: Navigate
|
||||
sessionID: string
|
||||
messageID: string
|
||||
}) {
|
||||
const result = await options.sdk.client.session.fork({ sessionID: options.sessionID, messageID: options.messageID })
|
||||
options.navigate({
|
||||
type: "session",
|
||||
sessionID: result.data!.id,
|
||||
prompt: collectPrompt(options.sync, options.messageID, { stripFiles: false }),
|
||||
})
|
||||
}
|
||||
|
||||
export * as MessageActions from "./message-actions"
|
||||
Loading…
Add table
Add a link
Reference in a new issue