Open the hood (#3949)

* add open disk conversation history button

* changeset

* change icon due to lack of artistic freedom

---------

Co-authored-by: Elephant Lumps <celestial_vault@Elephants-MacBook-Pro.local>
This commit is contained in:
Evan 2025-06-01 14:51:07 -07:00 committed by GitHub
parent f73172aeae
commit 3a325a6445
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 164 additions and 137 deletions

View File

@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Add dev only button to open task conversation history

View File

@ -52,6 +52,9 @@ service FileService {
// Refreshes all rule toggles (Cline, External, and Workflows)
rpc refreshRules(EmptyRequest) returns (RefreshedRules);
// Opens a task's conversation history file on disk
rpc openTaskHistory(StringRequest) returns (Empty);
}
// Response for refreshRules operation

View File

@ -0,0 +1,19 @@
import { Controller } from ".."
import { Empty, StringRequest } from "@shared/proto/common"
import { openFile as openFileIntegration } from "@integrations/misc/open-file"
import { FileMethodHandler } from "./index"
import path from "path"
/**
* Opens a file in the editor
* @param controller The controller instance
* @param request The request message containing the file path in the 'value' field
* @returns Empty response
*/
export const openTaskHistory: FileMethodHandler = async (controller: Controller, request: StringRequest): Promise<Empty> => {
const globalStoragePath = controller.context.globalStorageUri.fsPath
const taskHistoryPath = path.join(globalStoragePath, "tasks", request.value, "api_conversation_history.json")
if (request.value) {
openFileIntegration(taskHistoryPath)
}
return Empty.create()
}

View File

@ -26,7 +26,7 @@ import BrowserSessionRow from "@/components/chat/BrowserSessionRow"
import ChatRow from "@/components/chat/ChatRow"
import ChatTextArea from "@/components/chat/ChatTextArea"
import QuotedMessagePreview from "@/components/chat/QuotedMessagePreview"
import TaskHeader from "@/components/chat/TaskHeader"
import TaskHeader from "@/components/chat/task-header/TaskHeader"
import TelemetryBanner from "@/components/common/TelemetryBanner"
import { unified } from "unified"
import remarkStringify from "remark-stringify"

View File

@ -5,7 +5,7 @@ import { ClineCheckpointRestore } from "@shared/WebviewMessage"
import { CheckpointRestoreRequest } from "@shared/proto/checkpoints"
import React, { forwardRef, useRef, useState } from "react"
import DynamicTextArea from "react-textarea-autosize"
import { highlightText } from "./TaskHeader"
import { highlightText } from "./task-header/TaskHeader"
interface UserMessageProps {
text?: string

View File

@ -12,6 +12,10 @@ import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import React, { memo, useEffect, useMemo, useRef, useState } from "react"
import { useWindowSize } from "react-use"
import TaskTimeline from "./TaskTimeline"
import DeleteTaskButton from "./buttons/DeleteTaskButton"
import CopyTaskButton from "./buttons/CopyTaskButton"
import OpenDiskTaskHistoryButton from "./buttons/OpenDiskTaskHistoryButton"
const { IS_DEV } = process.env
interface TaskHeaderProps {
@ -412,9 +416,12 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
</div>
{!shouldShowPromptCacheInfo() && (
<div className="flex items-center flex-wrap">
<CopyButton taskText={task.text} />
{IS_DEV === '"true"' && <TaskFolderButton taskId={currentTaskItem?.id} />}
<DeleteButton taskSize={formatSize(currentTaskItem?.size)} taskId={currentTaskItem?.id} />
{IS_DEV === '"true"' && <OpenDiskTaskHistoryButton taskId={currentTaskItem?.id} />}
<CopyTaskButton taskText={task.text} />
<DeleteTaskButton
taskSize={formatSize(currentTaskItem?.size)}
taskId={currentTaskItem?.id}
/>
</div>
)}
</div>
@ -468,9 +475,12 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
)}
</div>
<div className="flex items-center flex-wrap">
<CopyButton taskText={task.text} />
{IS_DEV === '"true"' && <TaskFolderButton taskId={currentTaskItem?.id} />}
<DeleteButton taskSize={formatSize(currentTaskItem?.size)} taskId={currentTaskItem?.id} />
{IS_DEV === '"true"' && <OpenDiskTaskHistoryButton taskId={currentTaskItem?.id} />}
<CopyTaskButton taskText={task.text} />
<DeleteTaskButton
taskSize={formatSize(currentTaskItem?.size)}
taskId={currentTaskItem?.id}
/>
</div>
</div>
)}
@ -533,34 +543,6 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
</>
)}
</div>
{/* {apiProvider === "" && (
<div
style={{
backgroundColor: "color-mix(in srgb, var(--vscode-badge-background) 50%, transparent)",
color: "var(--vscode-badge-foreground)",
borderRadius: "0 0 3px 3px",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "4px 12px 6px 12px",
fontSize: "0.9em",
marginLeft: "10px",
marginRight: "10px",
}}>
<div style={{ fontWeight: "500" }}>Credits Remaining:</div>
<div>
{formatPrice(Credits || 0)}
{(Credits || 0) < 1 && (
<>
{" "}
<VSCodeLink style={{ fontSize: "0.9em" }} href={getAddCreditsUrl(vscodeUriScheme)}>
(get more?)
</VSCodeLink>
</>
)}
</div>
</div>
)} */}
</div>
)
}
@ -643,103 +625,4 @@ export const highlightText = (text?: string, withShadow = true) => {
return [text]
}
const CopyButton: React.FC<{
taskText?: string
}> = ({ taskText }) => {
const [copied, setCopied] = useState(false)
const handleCopy = () => {
if (!taskText) return
navigator.clipboard.writeText(taskText).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 1500)
})
}
return (
<HeroTooltip content="Copy Task">
<VSCodeButton
appearance="icon"
onClick={handleCopy}
style={{ padding: "0px 0px" }}
className="p-0"
aria-label="Copy Task">
<div className="flex items-center gap-[3px] text-[8px] font-bold opacity-60">
<i className={`codicon codicon-${copied ? "check" : "copy"}`} />
</div>
</VSCodeButton>
</HeroTooltip>
)
}
const TaskFolderButton: React.FC<{
taskId?: string
}> = ({ taskId }) => {
const [copied, setCopied] = useState(false)
const handleCopy = () => {
if (!taskId) return
navigator.clipboard.writeText(taskId).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 1500)
})
}
return (
<HeroTooltip content="Copy Task ID">
<VSCodeButton
appearance="icon"
onClick={handleCopy}
style={{ padding: "0px 0px" }}
className="p-0"
aria-label="Copy Task ID">
<div className="flex items-center gap-[3px] text-[8px] font-bold opacity-60">
<i className={`codicon codicon-${copied ? "check" : "folder"}`} />
</div>
</VSCodeButton>
</HeroTooltip>
)
}
const DeleteButton: React.FC<{
taskSize: string
taskId?: string
}> = ({ taskSize, taskId }) => (
<HeroTooltip content="Delete Task & Checkpoints">
<VSCodeButton
appearance="icon"
onClick={() => taskId && TaskServiceClient.deleteTasksWithIds(StringArrayRequest.create({ value: [taskId] }))}
style={{ padding: "0px 0px" }}>
<div
style={{
display: "flex",
alignItems: "center",
gap: "3px",
fontSize: "10px",
fontWeight: "bold",
opacity: 0.6,
}}>
<i className={`codicon codicon-trash`} />
{taskSize}
</div>
</VSCodeButton>
</HeroTooltip>
)
// const ExportButton = () => (
// <VSCodeButton
// appearance="icon"
// onClick={() => vscode.postMessage({ type: "exportCurrentTask" })}
// style={
// {
// // marginBottom: "-2px",
// // marginRight: "-2.5px",
// }
// }>
// <div style={{ fontSize: "10.5px", fontWeight: "bold", opacity: 0.6 }}>EXPORT</div>
// </VSCodeButton>
// )
export default memo(TaskHeader)

View File

@ -4,7 +4,16 @@ import { ClineMessage } from "@shared/ExtensionMessage"
import { combineApiRequests } from "@shared/combineApiRequests"
import { combineCommandSequences } from "@shared/combineCommandSequences"
import TaskTimelineTooltip from "./TaskTimelineTooltip"
import { COLOR_WHITE, COLOR_GRAY, COLOR_DARK_GRAY, COLOR_BEIGE, COLOR_BLUE, COLOR_RED, COLOR_PURPLE, COLOR_GREEN } from "./colors"
import {
COLOR_WHITE,
COLOR_GRAY,
COLOR_DARK_GRAY,
COLOR_BEIGE,
COLOR_BLUE,
COLOR_RED,
COLOR_PURPLE,
COLOR_GREEN,
} from "../colors"
// Timeline dimensions and spacing
const TIMELINE_HEIGHT = "18px"

View File

@ -1,6 +1,15 @@
import React from "react"
import { ClineMessage } from "@shared/ExtensionMessage"
import { COLOR_WHITE, COLOR_GRAY, COLOR_DARK_GRAY, COLOR_BEIGE, COLOR_BLUE, COLOR_RED, COLOR_PURPLE, COLOR_GREEN } from "./colors"
import {
COLOR_WHITE,
COLOR_GRAY,
COLOR_DARK_GRAY,
COLOR_BEIGE,
COLOR_BLUE,
COLOR_RED,
COLOR_PURPLE,
COLOR_GREEN,
} from "../colors"
import { Tooltip } from "@heroui/react"
// Color mapping for different message types

View File

@ -0,0 +1,35 @@
import HeroTooltip from "@/components/common/HeroTooltip"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { useState } from "react"
const CopyTaskButton: React.FC<{
taskText?: string
}> = ({ taskText }) => {
const [copied, setCopied] = useState(false)
const handleCopy = () => {
if (!taskText) return
navigator.clipboard.writeText(taskText).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 1500)
})
}
return (
<HeroTooltip content="Copy Task">
<VSCodeButton
appearance="icon"
onClick={handleCopy}
style={{ padding: "0px 0px" }}
className="p-0"
aria-label="Copy Task">
<div className="flex items-center gap-[3px] text-[8px] font-bold opacity-60">
<i className={`codicon codicon-${copied ? "check" : "copy"}`} />
</div>
</VSCodeButton>
</HeroTooltip>
)
}
export default CopyTaskButton

View File

@ -0,0 +1,31 @@
import HeroTooltip from "@/components/common/HeroTooltip"
import { TaskServiceClient } from "@/services/grpc-client"
import { StringArrayRequest } from "@shared/proto/common"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
const DeleteTaskButton: React.FC<{
taskSize: string
taskId?: string
}> = ({ taskSize, taskId }) => (
<HeroTooltip content="Delete Task & Checkpoints">
<VSCodeButton
appearance="icon"
onClick={() => taskId && TaskServiceClient.deleteTasksWithIds(StringArrayRequest.create({ value: [taskId] }))}
style={{ padding: "0px 0px" }}>
<div
style={{
display: "flex",
alignItems: "center",
gap: "3px",
fontSize: "10px",
fontWeight: "bold",
opacity: 0.6,
}}>
<i className={`codicon codicon-trash`} />
{taskSize}
</div>
</VSCodeButton>
</HeroTooltip>
)
export default DeleteTaskButton

View File

@ -0,0 +1,33 @@
import HeroTooltip from "@/components/common/HeroTooltip"
import { FileServiceClient } from "@/services/grpc-client"
import { StringRequest } from "@shared/proto/common"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
const OpenDiskTaskHistoryButton: React.FC<{
taskId?: string
}> = ({ taskId }) => {
const handleOpenDiskTaskHistory = () => {
if (!taskId) return
FileServiceClient.openTaskHistory(StringRequest.create({ value: taskId })).catch((err) => {
console.error(err)
})
}
return (
<HeroTooltip content="Open Disk Task History">
<VSCodeButton
appearance="icon"
onClick={handleOpenDiskTaskHistory}
style={{ padding: "0px 0px" }}
className="p-0"
aria-label="Open Disk Task History">
<div className="flex items-center gap-[3px] text-[8px] font-bold opacity-60">
<i className={`codicon codicon-folder`} />
</div>
</VSCodeButton>
</HeroTooltip>
)
}
export default OpenDiskTaskHistoryButton