Просмотр исходного кода

refactor(tui): extract shared message actions

Shoubhit Dash 2 месяцев назад
Родитель
Сommit
b1491690cd

+ 14 - 53
packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx

@@ -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()
           },
         },

+ 64 - 0
packages/opencode/src/cli/cmd/tui/routes/session/message-actions.ts

@@ -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"