mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-22 10:14:22 +01:00
feat(tui): add /export and /copy commands (#3883)
Signed-off-by: Christian Stewart <christian@aperture.us>
This commit is contained in:
committed by
GitHub
parent
3b1ab444fd
commit
b90c0b5fac
@@ -243,6 +243,16 @@ export function Autocomplete(props: {
|
||||
description: "rename session",
|
||||
onSelect: () => command.trigger("session.rename"),
|
||||
},
|
||||
{
|
||||
display: "/copy",
|
||||
description: "copy session transcript to clipboard",
|
||||
onSelect: () => command.trigger("session.copy"),
|
||||
},
|
||||
{
|
||||
display: "/export",
|
||||
description: "export session transcript to file",
|
||||
onSelect: () => command.trigger("session.export"),
|
||||
},
|
||||
{
|
||||
display: "/timeline",
|
||||
description: "jump to message",
|
||||
|
||||
@@ -65,6 +65,9 @@ import parsers from "../../../../../../parsers-config.ts"
|
||||
import { Clipboard } from "../../util/clipboard"
|
||||
import { Toast, useToast } from "../../ui/toast"
|
||||
import { useKV } from "../../context/kv.tsx"
|
||||
import { Editor } from "../../util/editor"
|
||||
import { Global } from "@/global"
|
||||
import fs from "fs/promises"
|
||||
|
||||
addDefaultParsers(parsers.parsers)
|
||||
|
||||
@@ -446,6 +449,105 @@ export function Session() {
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Copy session transcript",
|
||||
value: "session.copy",
|
||||
keybind: "session_copy",
|
||||
category: "Session",
|
||||
onSelect: async (dialog) => {
|
||||
try {
|
||||
// Format session transcript as markdown
|
||||
const sessionData = session()
|
||||
const sessionMessages = messages()
|
||||
|
||||
let transcript = `# ${sessionData.title}\n\n`
|
||||
transcript += `**Session ID:** ${sessionData.id}\n`
|
||||
transcript += `**Created:** ${new Date(sessionData.time.created).toLocaleString()}\n`
|
||||
transcript += `**Updated:** ${new Date(sessionData.time.updated).toLocaleString()}\n\n`
|
||||
transcript += `---\n\n`
|
||||
|
||||
for (const msg of sessionMessages) {
|
||||
const parts = sync.data.part[msg.id] ?? []
|
||||
const role = msg.role === "user" ? "User" : "Assistant"
|
||||
transcript += `## ${role}\n\n`
|
||||
|
||||
for (const part of parts) {
|
||||
if (part.type === "text" && !part.synthetic) {
|
||||
transcript += `${part.text}\n\n`
|
||||
} else if (part.type === "tool") {
|
||||
transcript += `\`\`\`\nTool: ${part.tool}\n\`\`\`\n\n`
|
||||
}
|
||||
}
|
||||
|
||||
transcript += `---\n\n`
|
||||
}
|
||||
|
||||
// Copy to clipboard
|
||||
await Clipboard.copy(transcript)
|
||||
toast.show({ message: "Session transcript copied to clipboard!", variant: "success" })
|
||||
} catch (error) {
|
||||
toast.show({ message: "Failed to copy session transcript", variant: "error" })
|
||||
}
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Export session transcript to file",
|
||||
value: "session.export",
|
||||
keybind: "session_export",
|
||||
category: "Session",
|
||||
onSelect: async (dialog) => {
|
||||
try {
|
||||
// Format session transcript as markdown
|
||||
const sessionData = session()
|
||||
const sessionMessages = messages()
|
||||
|
||||
let transcript = `# ${sessionData.title}\n\n`
|
||||
transcript += `**Session ID:** ${sessionData.id}\n`
|
||||
transcript += `**Created:** ${new Date(sessionData.time.created).toLocaleString()}\n`
|
||||
transcript += `**Updated:** ${new Date(sessionData.time.updated).toLocaleString()}\n\n`
|
||||
transcript += `---\n\n`
|
||||
|
||||
for (const msg of sessionMessages) {
|
||||
const parts = sync.data.part[msg.id] ?? []
|
||||
const role = msg.role === "user" ? "User" : "Assistant"
|
||||
transcript += `## ${role}\n\n`
|
||||
|
||||
for (const part of parts) {
|
||||
if (part.type === "text" && !part.synthetic) {
|
||||
transcript += `${part.text}\n\n`
|
||||
} else if (part.type === "tool") {
|
||||
transcript += `\`\`\`\nTool: ${part.tool}\n\`\`\`\n\n`
|
||||
}
|
||||
}
|
||||
|
||||
transcript += `---\n\n`
|
||||
}
|
||||
|
||||
// Save to file in data directory
|
||||
const exportDir = path.join(Global.Path.data, "exports")
|
||||
await fs.mkdir(exportDir, { recursive: true })
|
||||
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
|
||||
const filename = `session-${sessionData.id.slice(0, 8)}-${timestamp}.md`
|
||||
const filepath = path.join(exportDir, filename)
|
||||
|
||||
await Bun.write(filepath, transcript)
|
||||
|
||||
// Open with EDITOR if available
|
||||
const result = await Editor.open({ value: transcript, renderer })
|
||||
if (result !== undefined) {
|
||||
// User edited the file, save the changes
|
||||
await Bun.write(filepath, result)
|
||||
}
|
||||
|
||||
toast.show({ message: `Session exported to ${filename}`, variant: "success" })
|
||||
} catch (error) {
|
||||
toast.show({ message: "Failed to export session", variant: "error" })
|
||||
}
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Next child session",
|
||||
value: "session.child.next",
|
||||
|
||||
Reference in New Issue
Block a user