mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Add ability to export tasks as markdown
This commit is contained in:
@@ -5,6 +5,8 @@ import { ClaudeDev } from "../ClaudeDev"
|
||||
import { ClaudeMessage, ExtensionMessage } from "../shared/ExtensionMessage"
|
||||
import { WebviewMessage } from "../shared/WebviewMessage"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import * as path from "path"
|
||||
import os from "os"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -279,6 +281,9 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
await this.updateGlobalState("lastShownAnnouncementId", this.latestAnnouncementId)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "downloadTask":
|
||||
this.downloadTask()
|
||||
break
|
||||
// Add more switch case statements here as more webview message commands
|
||||
// are created within the webview context (i.e. inside media/main.js)
|
||||
}
|
||||
@@ -288,6 +293,81 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
)
|
||||
}
|
||||
|
||||
async downloadTask() {
|
||||
// File name
|
||||
const date = new Date()
|
||||
const month = date.toLocaleString("en-US", { month: "short" }).toLowerCase()
|
||||
const day = date.getDate()
|
||||
const year = date.getFullYear()
|
||||
let hours = date.getHours()
|
||||
const minutes = date.getMinutes().toString().padStart(2, "0")
|
||||
const ampm = hours >= 12 ? "PM" : "AM"
|
||||
hours = hours % 12
|
||||
hours = hours ? hours : 12 // the hour '0' should be '12'
|
||||
const fileName = `claude_dev_task_${month}-${day}-${year}_${hours}-${minutes}-${ampm}.md`
|
||||
|
||||
// Generate markdown
|
||||
const conversationHistory = await this.getApiConversationHistory()
|
||||
const markdownContent = conversationHistory
|
||||
.map((message) => {
|
||||
const role = message.role === "user" ? "**User:**" : "**Assistant:**"
|
||||
const content = Array.isArray(message.content)
|
||||
? message.content.map(this.formatContentBlockToMarkdown).join("\n")
|
||||
: message.content
|
||||
|
||||
return `${role}\n\n${content}\n\n`
|
||||
})
|
||||
.join("---\n\n")
|
||||
|
||||
// Prompt user for save location
|
||||
const saveUri = await vscode.window.showSaveDialog({
|
||||
filters: { Markdown: ["md"] },
|
||||
defaultUri: vscode.Uri.file(path.join(os.homedir(), "Downloads", fileName)),
|
||||
})
|
||||
|
||||
if (saveUri) {
|
||||
// Write content to the selected location
|
||||
await vscode.workspace.fs.writeFile(saveUri, Buffer.from(markdownContent))
|
||||
}
|
||||
}
|
||||
|
||||
private formatContentBlockToMarkdown(
|
||||
block:
|
||||
| Anthropic.TextBlockParam
|
||||
| Anthropic.ImageBlockParam
|
||||
| Anthropic.ToolUseBlockParam
|
||||
| Anthropic.ToolResultBlockParam
|
||||
): string {
|
||||
switch (block.type) {
|
||||
case "text":
|
||||
return block.text
|
||||
case "image":
|
||||
return `[Image: ${block.source.media_type}]`
|
||||
case "tool_use":
|
||||
let input: string
|
||||
if (typeof block.input === "object" && block.input !== null) {
|
||||
input = Object.entries(block.input)
|
||||
.map(([key, value]) => `${key.charAt(0).toUpperCase() + key.slice(1)}: ${value}`)
|
||||
.join("\n")
|
||||
} else {
|
||||
input = String(block.input)
|
||||
}
|
||||
return `[Tool Use: ${block.name}]\n${input}`
|
||||
case "tool_result":
|
||||
if (typeof block.content === "string") {
|
||||
return `[Tool Result${block.is_error ? " (Error)" : ""}]\n${block.content}`
|
||||
} else if (Array.isArray(block.content)) {
|
||||
return `[Tool Result${block.is_error ? " (Error)" : ""}]\n${block.content
|
||||
.map(this.formatContentBlockToMarkdown)
|
||||
.join("\n")}`
|
||||
} else {
|
||||
return `[Tool Result${block.is_error ? " (Error)" : ""}]`
|
||||
}
|
||||
default:
|
||||
return "[Unexpected content type]"
|
||||
}
|
||||
}
|
||||
|
||||
async postStateToWebview() {
|
||||
const [apiKey, maxRequestsPerTask, claudeMessages, lastShownAnnouncementId] = await Promise.all([
|
||||
this.getSecret("apiKey") as Promise<string | undefined>,
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface WebviewMessage {
|
||||
| "askResponse"
|
||||
| "clearTask"
|
||||
| "didShowAnnouncement"
|
||||
| "downloadTask"
|
||||
text?: string
|
||||
askResponse?: ClaudeAskResponse
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import TextTruncate from "react-text-truncate"
|
||||
import { useWindowSize } from "react-use"
|
||||
import { vscode } from "../utilities/vscode"
|
||||
|
||||
interface TaskHeaderProps {
|
||||
taskText: string
|
||||
@@ -71,6 +72,10 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut,
|
||||
|
||||
const toggleExpand = () => setIsExpanded(!isExpanded)
|
||||
|
||||
const handleDownload = () => {
|
||||
vscode.postMessage({ type: "downloadTask" })
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: "15px 15px 10px 15px" }}>
|
||||
<div
|
||||
@@ -82,6 +87,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
position: "relative",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
@@ -157,6 +163,16 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut,
|
||||
<span>${totalCost.toFixed(4)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={handleDownload}
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: "9.5px",
|
||||
right: "9px",
|
||||
}}>
|
||||
<div style={{ fontSize: "10.5px", fontWeight: "bold", opacity: 0.6 }}>EXPORT .MD</div>
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user