mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-28 16:55:44 +00:00
feat: add assistant metadata to session export (#6611)
This commit is contained in:
@@ -68,6 +68,7 @@ import { usePromptRef } from "../../context/prompt"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { PermissionPrompt } from "./permission"
|
||||
import { DialogExportOptions } from "../../ui/dialog-export-options"
|
||||
import { formatTranscript } from "../../util/transcript"
|
||||
|
||||
addDefaultParsers(parsers.parsers)
|
||||
|
||||
@@ -134,6 +135,7 @@ export function Session() {
|
||||
const [showTimestamps, setShowTimestamps] = createSignal(kv.get("timestamps", "hide") === "show")
|
||||
const [usernameVisible, setUsernameVisible] = createSignal(kv.get("username_visible", true))
|
||||
const [showDetails, setShowDetails] = createSignal(kv.get("tool_details_visibility", true))
|
||||
const [showAssistantMetadata, setShowAssistantMetadata] = createSignal(kv.get("assistant_metadata_visibility", true))
|
||||
const [showScrollbar, setShowScrollbar] = createSignal(kv.get("scrollbar_visible", false))
|
||||
const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word")
|
||||
const [animationsEnabled, setAnimationsEnabled] = createSignal(kv.get("animations_enabled", true))
|
||||
@@ -712,47 +714,17 @@ export function Session() {
|
||||
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 === "reasoning") {
|
||||
if (showThinking()) {
|
||||
transcript += `_Thinking:_\n\n${part.text}\n\n`
|
||||
}
|
||||
} else if (part.type === "tool") {
|
||||
transcript += `\`\`\`\nTool: ${part.tool}\n`
|
||||
if (showDetails() && part.state.input) {
|
||||
transcript += `\n**Input:**\n\`\`\`json\n${JSON.stringify(part.state.input, null, 2)}\n\`\`\``
|
||||
}
|
||||
if (showDetails() && part.state.status === "completed" && part.state.output) {
|
||||
transcript += `\n**Output:**\n\`\`\`\n${part.state.output}\n\`\`\``
|
||||
}
|
||||
if (showDetails() && part.state.status === "error" && part.state.error) {
|
||||
transcript += `\n**Error:**\n\`\`\`\n${part.state.error}\n\`\`\``
|
||||
}
|
||||
transcript += `\n\`\`\`\n\n`
|
||||
}
|
||||
}
|
||||
|
||||
transcript += `---\n\n`
|
||||
}
|
||||
|
||||
// Copy to clipboard
|
||||
const transcript = formatTranscript(
|
||||
sessionData,
|
||||
sessionMessages.map((msg) => ({ info: msg, parts: sync.data.part[msg.id] ?? [] })),
|
||||
{
|
||||
thinking: showThinking(),
|
||||
toolDetails: showDetails(),
|
||||
assistantMetadata: showAssistantMetadata(),
|
||||
},
|
||||
)
|
||||
await Clipboard.copy(transcript)
|
||||
toast.show({ message: "Session transcript copied to clipboard!", variant: "success" })
|
||||
} catch (error) {
|
||||
@@ -762,75 +734,56 @@ export function Session() {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Export session transcript to file",
|
||||
title: "Export session transcript",
|
||||
value: "session.export",
|
||||
keybind: "session_export",
|
||||
category: "Session",
|
||||
onSelect: async (dialog) => {
|
||||
try {
|
||||
// Format session transcript as markdown
|
||||
const sessionData = session()
|
||||
const sessionMessages = messages()
|
||||
|
||||
const defaultFilename = `session-${sessionData.id.slice(0, 8)}.md`
|
||||
|
||||
const options = await DialogExportOptions.show(dialog, defaultFilename, showThinking(), showDetails())
|
||||
const options = await DialogExportOptions.show(
|
||||
dialog,
|
||||
defaultFilename,
|
||||
showThinking(),
|
||||
showDetails(),
|
||||
showAssistantMetadata(),
|
||||
false,
|
||||
)
|
||||
|
||||
if (options === null) return
|
||||
|
||||
const { filename: customFilename, thinking: includeThinking, toolDetails: includeToolDetails } = options
|
||||
const transcript = formatTranscript(
|
||||
sessionData,
|
||||
sessionMessages.map((msg) => ({ info: msg, parts: sync.data.part[msg.id] ?? [] })),
|
||||
{
|
||||
thinking: options.thinking,
|
||||
toolDetails: options.toolDetails,
|
||||
assistantMetadata: options.assistantMetadata,
|
||||
},
|
||||
)
|
||||
|
||||
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`
|
||||
if (options.openWithoutSaving) {
|
||||
// Just open in editor without saving
|
||||
await Editor.open({ value: transcript, renderer })
|
||||
} else {
|
||||
const exportDir = process.cwd()
|
||||
const filename = options.filename.trim()
|
||||
const filepath = path.join(exportDir, filename)
|
||||
|
||||
for (const msg of sessionMessages) {
|
||||
const parts = sync.data.part[msg.id] ?? []
|
||||
const role = msg.role === "user" ? "User" : "Assistant"
|
||||
transcript += `## ${role}\n\n`
|
||||
await Bun.write(filepath, transcript)
|
||||
|
||||
for (const part of parts) {
|
||||
if (part.type === "text" && !part.synthetic) {
|
||||
transcript += `${part.text}\n\n`
|
||||
} else if (part.type === "reasoning") {
|
||||
if (includeThinking) {
|
||||
transcript += `_Thinking:_\n\n${part.text}\n\n`
|
||||
}
|
||||
} else if (part.type === "tool") {
|
||||
transcript += `\`\`\`\nTool: ${part.tool}\n`
|
||||
if (includeToolDetails && part.state.input) {
|
||||
transcript += `\n**Input:**\n\`\`\`json\n${JSON.stringify(part.state.input, null, 2)}\n\`\`\``
|
||||
}
|
||||
if (includeToolDetails && part.state.status === "completed" && part.state.output) {
|
||||
transcript += `\n**Output:**\n\`\`\`\n${part.state.output}\n\`\`\``
|
||||
}
|
||||
if (includeToolDetails && part.state.status === "error" && part.state.error) {
|
||||
transcript += `\n**Error:**\n\`\`\`\n${part.state.error}\n\`\`\``
|
||||
}
|
||||
transcript += `\n\`\`\`\n\n`
|
||||
}
|
||||
// Open with EDITOR if available
|
||||
const result = await Editor.open({ value: transcript, renderer })
|
||||
if (result !== undefined) {
|
||||
await Bun.write(filepath, result)
|
||||
}
|
||||
|
||||
transcript += `---\n\n`
|
||||
toast.show({ message: `Session exported to ${filename}`, variant: "success" })
|
||||
}
|
||||
|
||||
// Save to file in current working directory
|
||||
const exportDir = process.cwd()
|
||||
const filename = customFilename.trim()
|
||||
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" })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user