mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Add memory optimizations, retry failed requests, markdown support
- Move isExpanded state up into ChatView to fix issue where virtualized list would reset ChatRow state - Add ability to retry failed requests - Add markdown rendering
This commit is contained in:
@@ -50,6 +50,7 @@ RULES
|
|||||||
- Your goal is to try to accomplish the user's task, NOT engage in a back and forth conversation.
|
- Your goal is to try to accomplish the user's task, NOT engage in a back and forth conversation.
|
||||||
- NEVER end completion_attempt with a question or request to engage in further conversation! Formulate the end of your result in a way that is final and does not require further input from the user.
|
- NEVER end completion_attempt with a question or request to engage in further conversation! Formulate the end of your result in a way that is final and does not require further input from the user.
|
||||||
- NEVER start your responses with affirmations like "Certaintly", "Okay", "Sure", "Great", etc. You should NOT be conversational in your responses, but rather direct and to the point.
|
- NEVER start your responses with affirmations like "Certaintly", "Okay", "Sure", "Great", etc. You should NOT be conversational in your responses, but rather direct and to the point.
|
||||||
|
- Feel free to use markdown as much as you'd like in your responses. When using code blocks, always include a language specifier.
|
||||||
|
|
||||||
====
|
====
|
||||||
|
|
||||||
@@ -222,7 +223,7 @@ export class ClaudeDev {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
async say(type: ClaudeSay, text: string): Promise<undefined> {
|
async say(type: ClaudeSay, text?: string): Promise<undefined> {
|
||||||
if (this.abort) {
|
if (this.abort) {
|
||||||
throw new Error("ClaudeDev instance aborted")
|
throw new Error("ClaudeDev instance aborted")
|
||||||
}
|
}
|
||||||
@@ -502,6 +503,38 @@ export class ClaudeDev {
|
|||||||
return `The user is not pleased with the results. Use the feedback they provided to successfully complete the task, and then attempt completion again.\nUser's feedback:\n\"${text}\"`
|
return `The user is not pleased with the results. Use the feedback they provided to successfully complete the task, and then attempt completion again.\nUser's feedback:\n\"${text}\"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async attemptApiRequest(): Promise<Anthropic.Messages.Message> {
|
||||||
|
try {
|
||||||
|
const response = await this.client.messages.create(
|
||||||
|
{
|
||||||
|
model: "claude-3-5-sonnet-20240620", // https://docs.anthropic.com/en/docs/about-claude/models
|
||||||
|
// beta max tokens
|
||||||
|
max_tokens: 8192,
|
||||||
|
system: SYSTEM_PROMPT,
|
||||||
|
messages: (await this.providerRef.deref()?.getApiConversationHistory()) || [],
|
||||||
|
tools: tools,
|
||||||
|
tool_choice: { type: "auto" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// https://github.com/anthropics/anthropic-sdk-typescript?tab=readme-ov-file#default-headers
|
||||||
|
headers: { "anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15" },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
const { response } = await this.ask(
|
||||||
|
"api_req_failed",
|
||||||
|
error.message ?? JSON.stringify(serializeError(error), null, 2)
|
||||||
|
)
|
||||||
|
if (response !== "yesButtonTapped") {
|
||||||
|
// this will never happen since if noButtonTapped, we will clear current task, aborting this instance
|
||||||
|
throw new Error("API request failed")
|
||||||
|
}
|
||||||
|
await this.say("api_req_retried")
|
||||||
|
return this.attemptApiRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async recursivelyMakeClaudeRequests(
|
async recursivelyMakeClaudeRequests(
|
||||||
userContent: Array<
|
userContent: Array<
|
||||||
| Anthropic.TextBlockParam
|
| Anthropic.TextBlockParam
|
||||||
@@ -537,37 +570,22 @@ export class ClaudeDev {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// what the user sees in the webview
|
||||||
// what the user sees in the webview
|
await this.say(
|
||||||
await this.say(
|
"api_req_started",
|
||||||
"api_req_started",
|
JSON.stringify({
|
||||||
JSON.stringify({
|
request: {
|
||||||
request: {
|
model: "claude-3-5-sonnet-20240620",
|
||||||
model: "claude-3-5-sonnet-20240620",
|
|
||||||
max_tokens: 8192,
|
|
||||||
system: "(see SYSTEM_PROMPT in https://github.com/saoudrizwan/claude-dev/blob/main/src/ClaudeDev.ts)",
|
|
||||||
messages: [{ conversation_history: "..." }, { role: "user", content: userContent }],
|
|
||||||
tools: "(see tools in https://github.com/saoudrizwan/claude-dev/blob/main/src/ClaudeDev.ts)",
|
|
||||||
tool_choice: { type: "auto" },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const response = await this.client.messages.create(
|
|
||||||
{
|
|
||||||
model: "claude-3-5-sonnet-20240620", // https://docs.anthropic.com/en/docs/about-claude/models
|
|
||||||
// beta max tokens
|
|
||||||
max_tokens: 8192,
|
max_tokens: 8192,
|
||||||
system: SYSTEM_PROMPT,
|
system: "(see SYSTEM_PROMPT in https://github.com/saoudrizwan/claude-dev/blob/main/src/ClaudeDev.ts)",
|
||||||
messages: (await this.providerRef.deref()?.getApiConversationHistory()) || [],
|
messages: [{ conversation_history: "..." }, { role: "user", content: userContent }],
|
||||||
tools: tools,
|
tools: "(see tools in https://github.com/saoudrizwan/claude-dev/blob/main/src/ClaudeDev.ts)",
|
||||||
tool_choice: { type: "auto" },
|
tool_choice: { type: "auto" },
|
||||||
},
|
},
|
||||||
{
|
})
|
||||||
// https://github.com/anthropics/anthropic-sdk-typescript?tab=readme-ov-file#default-headers
|
)
|
||||||
headers: { "anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15" },
|
try {
|
||||||
}
|
const response = await this.attemptApiRequest()
|
||||||
)
|
|
||||||
this.requestCount++
|
this.requestCount++
|
||||||
|
|
||||||
let assistantResponses: Anthropic.Messages.ContentBlock[] = []
|
let assistantResponses: Anthropic.Messages.ContentBlock[] = []
|
||||||
@@ -674,8 +692,7 @@ export class ClaudeDev {
|
|||||||
|
|
||||||
return { didEndLoop, inputTokens, outputTokens }
|
return { didEndLoop, inputTokens, outputTokens }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// only called if the API request fails (executeTool errors are returned back to claude)
|
// this should never happen since the only thing that can throw an error is the attemptApiRequest, which is wrapped in a try catch that sends an ask where if noButtonTapped, will clear current task and destroy this instance. However to avoid unhandled promise rejection, we will end this loop which will end execution of this instance (see startTask)
|
||||||
this.say("error", `API request failed:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`)
|
|
||||||
return { didEndLoop: true, inputTokens: 0, outputTokens: 0 }
|
return { didEndLoop: true, inputTokens: 0, outputTokens: 0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,14 @@ export interface ClaudeMessage {
|
|||||||
text?: string
|
text?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClaudeAsk = "request_limit_reached" | "followup" | "command" | "completion_result" | "tool"
|
export type ClaudeAsk =
|
||||||
|
| "request_limit_reached"
|
||||||
|
| "followup"
|
||||||
|
| "command"
|
||||||
|
| "completion_result"
|
||||||
|
| "tool"
|
||||||
|
| "api_req_failed"
|
||||||
|
|
||||||
export type ClaudeSay =
|
export type ClaudeSay =
|
||||||
| "task"
|
| "task"
|
||||||
| "error"
|
| "error"
|
||||||
@@ -34,6 +41,7 @@ export type ClaudeSay =
|
|||||||
| "command_output"
|
| "command_output"
|
||||||
| "completion_result"
|
| "completion_result"
|
||||||
| "user_feedback"
|
| "user_feedback"
|
||||||
|
| "api_req_retried"
|
||||||
|
|
||||||
export interface ClaudeSayTool {
|
export interface ClaudeSayTool {
|
||||||
tool: "editedExistingFile" | "newFileCreated" | "readFile" | "listFiles"
|
tool: "editedExistingFile" | "newFileCreated" | "readFile" | "listFiles"
|
||||||
|
|||||||
1224
webview-ui/package-lock.json
generated
1224
webview-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@
|
|||||||
"@vscode/webview-ui-toolkit": "^1.4.0",
|
"@vscode/webview-ui-toolkit": "^1.4.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-markdown": "^9.0.1",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"react-text-truncate": "^0.19.0",
|
"react-text-truncate": "^0.19.0",
|
||||||
|
|||||||
@@ -1,17 +1,27 @@
|
|||||||
import { ClaudeAsk, ClaudeMessage, ClaudeSay, ClaudeSayTool } from "@shared/ExtensionMessage"
|
import { ClaudeAsk, ClaudeMessage, ClaudeSay, ClaudeSayTool } from "@shared/ExtensionMessage"
|
||||||
import { VSCodeBadge, VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
|
import { VSCodeBadge, VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
|
||||||
import React, { useState } from "react"
|
import React from "react"
|
||||||
import { COMMAND_OUTPUT_STRING } from "../utilities/combineCommandSequences"
|
import { COMMAND_OUTPUT_STRING } from "../utilities/combineCommandSequences"
|
||||||
import { SyntaxHighlighterStyle } from "../utilities/getSyntaxHighlighterStyleFromTheme"
|
import { SyntaxHighlighterStyle } from "../utilities/getSyntaxHighlighterStyleFromTheme"
|
||||||
import CodeBlock from "./CodeBlock/CodeBlock"
|
import CodeBlock from "./CodeBlock/CodeBlock"
|
||||||
|
import Markdown from "react-markdown"
|
||||||
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
|
||||||
|
|
||||||
interface ChatRowProps {
|
interface ChatRowProps {
|
||||||
message: ClaudeMessage
|
message: ClaudeMessage
|
||||||
syntaxHighlighterStyle: SyntaxHighlighterStyle
|
syntaxHighlighterStyle: SyntaxHighlighterStyle
|
||||||
|
isExpanded: boolean
|
||||||
|
onToggleExpand: () => void
|
||||||
|
apiRequestFailedMessage?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) => {
|
const ChatRow: React.FC<ChatRowProps> = ({
|
||||||
const [isExpanded, setIsExpanded] = useState(false)
|
message,
|
||||||
|
syntaxHighlighterStyle,
|
||||||
|
isExpanded,
|
||||||
|
onToggleExpand,
|
||||||
|
apiRequestFailedMessage,
|
||||||
|
}) => {
|
||||||
const cost = message.text != null && message.say === "api_req_started" ? JSON.parse(message.text).cost : undefined
|
const cost = message.text != null && message.say === "api_req_started" ? JSON.parse(message.text).cost : undefined
|
||||||
|
|
||||||
const getIconAndTitle = (type: ClaudeAsk | ClaudeSay | undefined): [JSX.Element | null, JSX.Element | null] => {
|
const getIconAndTitle = (type: ClaudeAsk | ClaudeSay | undefined): [JSX.Element | null, JSX.Element | null] => {
|
||||||
@@ -56,6 +66,10 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
<span
|
<span
|
||||||
className="codicon codicon-check"
|
className="codicon codicon-check"
|
||||||
style={{ color: successColor, marginBottom: "-1.5px" }}></span>
|
style={{ color: successColor, marginBottom: "-1.5px" }}></span>
|
||||||
|
) : apiRequestFailedMessage ? (
|
||||||
|
<span
|
||||||
|
className="codicon codicon-error"
|
||||||
|
style={{ color: errorColor, marginBottom: "-1.5px" }}></span>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -70,15 +84,73 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
<span style={{ color: normalColor, fontWeight: "bold" }}>
|
cost ? (
|
||||||
{cost ? "API Request Complete" : "Making API Request..."}
|
<span style={{ color: normalColor, fontWeight: "bold" }}>API Request Complete</span>
|
||||||
</span>,
|
) : apiRequestFailedMessage ? (
|
||||||
|
<span style={{ color: errorColor, fontWeight: "bold" }}>API Request Failed</span>
|
||||||
|
) : (
|
||||||
|
<span style={{ color: normalColor, fontWeight: "bold" }}>Making API Request...</span>
|
||||||
|
),
|
||||||
]
|
]
|
||||||
default:
|
default:
|
||||||
return [null, null]
|
return [null, null]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const convertToMarkdown = (markdown: string = "") => {
|
||||||
|
// react-markdown lets us customize elements, so here we're using their example of replacing code blocks with SyntaxHighlighter. However when there are no language matches (` or ``` without a language specifier) then we default to a normal code element for inline code. Code blocks without a language specifier shouldn't be a common occurrence as we prompt Claude to always use a language specifier.
|
||||||
|
return (
|
||||||
|
<Markdown
|
||||||
|
children={markdown}
|
||||||
|
components={{
|
||||||
|
p(props) {
|
||||||
|
const { style, ...rest } = props
|
||||||
|
return <p style={{ ...style, margin: 0, marginTop: 0, marginBottom: 0 }} {...rest} />
|
||||||
|
},
|
||||||
|
//p: "span",
|
||||||
|
// https://github.com/remarkjs/react-markdown?tab=readme-ov-file#use-custom-components-syntax-highlight
|
||||||
|
code(props) {
|
||||||
|
const { children, className, node, ...rest } = props
|
||||||
|
const match = /language-(\w+)/.exec(className || "")
|
||||||
|
return match ? (
|
||||||
|
<SyntaxHighlighter
|
||||||
|
{...(rest as any)} // will be passed down to pre
|
||||||
|
PreTag="div"
|
||||||
|
children={String(children).replace(/\n$/, "")}
|
||||||
|
language={match[1]}
|
||||||
|
style={{
|
||||||
|
...syntaxHighlighterStyle,
|
||||||
|
'code[class*="language-"]': {
|
||||||
|
background: "var(--vscode-editor-background)",
|
||||||
|
},
|
||||||
|
'pre[class*="language-"]': {
|
||||||
|
background: "var(--vscode-editor-background)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
customStyle={{
|
||||||
|
overflowX: "auto",
|
||||||
|
overflowY: "hidden",
|
||||||
|
maxWidth: "100%",
|
||||||
|
margin: 0,
|
||||||
|
padding: "10px",
|
||||||
|
borderRadius: 3,
|
||||||
|
border: "1px solid var(--vscode-sideBar-border)",
|
||||||
|
fontSize: "var(--vscode-editor-font-size)",
|
||||||
|
lineHeight: "var(--vscode-editor-line-height)",
|
||||||
|
fontFamily: "var(--vscode-editor-font-family)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<code {...rest} className={className}>
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
const [icon, title] = getIconAndTitle(message.type === "ask" ? message.ask : message.say)
|
const [icon, title] = getIconAndTitle(message.type === "ask" ? message.ask : message.say)
|
||||||
|
|
||||||
@@ -89,7 +161,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
marginBottom: "10px",
|
marginBottom: "10px",
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentStyle: React.CSSProperties = {
|
const pStyle: React.CSSProperties = {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
whiteSpace: "pre-line",
|
whiteSpace: "pre-line",
|
||||||
}
|
}
|
||||||
@@ -99,24 +171,37 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
switch (message.say) {
|
switch (message.say) {
|
||||||
case "api_req_started":
|
case "api_req_started":
|
||||||
return (
|
return (
|
||||||
<div style={{ ...headerStyle, marginBottom: 0, justifyContent: "space-between" }}>
|
<>
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
<div
|
||||||
{icon}
|
style={{
|
||||||
{title}
|
...headerStyle,
|
||||||
{cost && <VSCodeBadge>${Number(cost).toFixed(4)}</VSCodeBadge>}
|
marginBottom: cost == null && apiRequestFailedMessage ? 10 : 0,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||||
|
{icon}
|
||||||
|
{title}
|
||||||
|
{cost && <VSCodeBadge>${Number(cost).toFixed(4)}</VSCodeBadge>}
|
||||||
|
</div>
|
||||||
|
<VSCodeButton
|
||||||
|
appearance="icon"
|
||||||
|
aria-label="Toggle Details"
|
||||||
|
onClick={onToggleExpand}>
|
||||||
|
<span
|
||||||
|
className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
|
||||||
|
</VSCodeButton>
|
||||||
</div>
|
</div>
|
||||||
<VSCodeButton
|
{cost == null && apiRequestFailedMessage && (
|
||||||
appearance="icon"
|
<p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}>
|
||||||
aria-label="Toggle Details"
|
{apiRequestFailedMessage}
|
||||||
onClick={() => setIsExpanded(!isExpanded)}>
|
</p>
|
||||||
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
|
)}
|
||||||
</VSCodeButton>
|
</>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
case "api_req_finished":
|
case "api_req_finished":
|
||||||
return null // we should never see this message type
|
return null // we should never see this message type
|
||||||
case "text":
|
case "text":
|
||||||
return <p style={contentStyle}>{message.text}</p>
|
return <div>{convertToMarkdown(message.text)}</div>
|
||||||
case "user_feedback":
|
case "user_feedback":
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -140,9 +225,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p style={{ ...contentStyle, color: "var(--vscode-errorForeground)" }}>
|
<p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}>{message.text}</p>
|
||||||
{message.text}
|
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
case "completion_result":
|
case "completion_result":
|
||||||
@@ -152,9 +235,9 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
{icon}
|
{icon}
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
<p style={{ ...contentStyle, color: "var(--vscode-testing-iconPassed)" }}>
|
<div style={{ color: "var(--vscode-testing-iconPassed)" }}>
|
||||||
{message.text}
|
{convertToMarkdown(message.text)}
|
||||||
</p>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
@@ -166,7 +249,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p style={contentStyle}>{message.text}</p>
|
<div>{convertToMarkdown(message.text)}</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -192,6 +275,8 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
diff={tool.diff!}
|
diff={tool.diff!}
|
||||||
path={tool.path!}
|
path={tool.path!}
|
||||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
onToggleExpand={onToggleExpand}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -208,6 +293,8 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
code={tool.content!}
|
code={tool.content!}
|
||||||
path={tool.path!}
|
path={tool.path!}
|
||||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
onToggleExpand={onToggleExpand}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -222,6 +309,8 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
code={tool.content!}
|
code={tool.content!}
|
||||||
path={tool.path!}
|
path={tool.path!}
|
||||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
onToggleExpand={onToggleExpand}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -239,6 +328,8 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
path={tool.path!}
|
path={tool.path!}
|
||||||
language="shell-session"
|
language="shell-session"
|
||||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
onToggleExpand={onToggleExpand}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -251,9 +342,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
{icon}
|
{icon}
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
<p style={{ ...contentStyle, color: "var(--vscode-errorForeground)" }}>
|
<p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}>{message.text}</p>
|
||||||
{message.text}
|
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
case "command":
|
case "command":
|
||||||
@@ -275,24 +364,28 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
{icon}
|
{icon}
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
<div style={contentStyle}>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
code={command}
|
code={command}
|
||||||
language="shell-session"
|
language="shell-session"
|
||||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
onToggleExpand={onToggleExpand}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{output && (
|
{output && (
|
||||||
<>
|
<>
|
||||||
<p style={{ ...contentStyle, margin: "10px 0 10px 0" }}>
|
<p style={{ ...pStyle, margin: "10px 0 10px 0" }}>
|
||||||
{COMMAND_OUTPUT_STRING}
|
{COMMAND_OUTPUT_STRING}
|
||||||
</p>
|
</p>
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
code={output}
|
code={output}
|
||||||
language="shell-session"
|
language="shell-session"
|
||||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||||
|
isExpanded={isExpanded}
|
||||||
|
onToggleExpand={onToggleExpand}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -307,15 +400,15 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
{icon}
|
{icon}
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
<p style={{ ...contentStyle, color: "var(--vscode-testing-iconPassed)" }}>
|
<div style={{ color: "var(--vscode-testing-iconPassed)" }}>
|
||||||
{message.text}
|
{convertToMarkdown(message.text)}
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return null // Don't render anything when we get a completion_result ask without text
|
return null // Don't render anything when we get a completion_result ask without text
|
||||||
}
|
}
|
||||||
default:
|
case "followup":
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{title && (
|
{title && (
|
||||||
@@ -324,17 +417,14 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p style={contentStyle}>{message.text}</p>
|
<div>{convertToMarkdown(message.text)}</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need to return null here instead of in getContent since that way would result in padding being applied
|
// NOTE: we cannot return null as virtuoso does not support it, so we must use a separate visibleMessages array to filter out messages that should not be rendered
|
||||||
if (!shouldShowChatRow(message)) {
|
|
||||||
return null // Don't render anything for this message type
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -348,6 +438,8 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
code={JSON.stringify(JSON.parse(message.text || "{}").request, null, 2)}
|
code={JSON.stringify(JSON.parse(message.text || "{}").request, null, 2)}
|
||||||
language="json"
|
language="json"
|
||||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||||
|
isExpanded={true}
|
||||||
|
onToggleExpand={onToggleExpand}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -355,18 +447,4 @@ const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) =>
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const shouldShowChatRow = (message: ClaudeMessage) => {
|
|
||||||
// combineApiRequests removes this from modifiedMessages anyways
|
|
||||||
if (message.say === "api_req_finished") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't show a chat row for a completion_result ask without text. This specific type of message only occurs if Claude wants to execute a command as part of its completion result, in which case we interject the completion_result tool with the execute_command tool.
|
|
||||||
if (message.type === "ask" && message.ask === "completion_result" && message.text === "") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ChatRow
|
export default ChatRow
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { combineCommandSequences } from "../utilities/combineCommandSequences"
|
|||||||
import { getApiMetrics } from "../utilities/getApiMetrics"
|
import { getApiMetrics } from "../utilities/getApiMetrics"
|
||||||
import { getSyntaxHighlighterStyleFromTheme } from "../utilities/getSyntaxHighlighterStyleFromTheme"
|
import { getSyntaxHighlighterStyleFromTheme } from "../utilities/getSyntaxHighlighterStyleFromTheme"
|
||||||
import { vscode } from "../utilities/vscode"
|
import { vscode } from "../utilities/vscode"
|
||||||
import ChatRow, { shouldShowChatRow } from "./ChatRow"
|
import ChatRow from "./ChatRow"
|
||||||
import TaskHeader from "./TaskHeader"
|
import TaskHeader from "./TaskHeader"
|
||||||
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
|
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
|
||||||
|
|
||||||
@@ -42,6 +42,15 @@ const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => {
|
|||||||
|
|
||||||
const virtuosoRef = useRef<VirtuosoHandle>(null)
|
const virtuosoRef = useRef<VirtuosoHandle>(null)
|
||||||
|
|
||||||
|
const [expandedRows, setExpandedRows] = useState<Record<number, boolean>>({})
|
||||||
|
|
||||||
|
const toggleRowExpansion = (ts: number) => {
|
||||||
|
setExpandedRows((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[ts]: !prev[ts],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!vscodeThemeName) return
|
if (!vscodeThemeName) return
|
||||||
const theme = getSyntaxHighlighterStyleFromTheme(vscodeThemeName)
|
const theme = getSyntaxHighlighterStyleFromTheme(vscodeThemeName)
|
||||||
@@ -68,6 +77,13 @@ const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => {
|
|||||||
setPrimaryButtonText("Proceed")
|
setPrimaryButtonText("Proceed")
|
||||||
setSecondaryButtonText("Start New Task")
|
setSecondaryButtonText("Start New Task")
|
||||||
break
|
break
|
||||||
|
case "api_req_failed":
|
||||||
|
setTextAreaDisabled(true)
|
||||||
|
setClaudeAsk("api_req_failed")
|
||||||
|
setEnableButtons(true)
|
||||||
|
setPrimaryButtonText("Retry")
|
||||||
|
setSecondaryButtonText("Start New Task")
|
||||||
|
break
|
||||||
case "followup":
|
case "followup":
|
||||||
setTextAreaDisabled(false)
|
setTextAreaDisabled(false)
|
||||||
setClaudeAsk("followup")
|
setClaudeAsk("followup")
|
||||||
@@ -170,6 +186,7 @@ const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => {
|
|||||||
const handlePrimaryButtonClick = () => {
|
const handlePrimaryButtonClick = () => {
|
||||||
switch (claudeAsk) {
|
switch (claudeAsk) {
|
||||||
case "request_limit_reached":
|
case "request_limit_reached":
|
||||||
|
case "api_req_failed":
|
||||||
case "command":
|
case "command":
|
||||||
case "tool":
|
case "tool":
|
||||||
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonTapped" })
|
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonTapped" })
|
||||||
@@ -189,6 +206,7 @@ const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => {
|
|||||||
const handleSecondaryButtonClick = () => {
|
const handleSecondaryButtonClick = () => {
|
||||||
switch (claudeAsk) {
|
switch (claudeAsk) {
|
||||||
case "request_limit_reached":
|
case "request_limit_reached":
|
||||||
|
case "api_req_failed":
|
||||||
startNewTask()
|
startNewTask()
|
||||||
break
|
break
|
||||||
case "command":
|
case "command":
|
||||||
@@ -262,6 +280,41 @@ const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => {
|
|||||||
}
|
}
|
||||||
}, [isHidden, textAreaDisabled, enableButtons])
|
}, [isHidden, textAreaDisabled, enableButtons])
|
||||||
|
|
||||||
|
const visibleMessages = useMemo(() => {
|
||||||
|
return modifiedMessages.filter((message) => {
|
||||||
|
switch (message.ask) {
|
||||||
|
case "completion_result":
|
||||||
|
// don't show a chat row for a completion_result ask without text. This specific type of message only occurs if Claude wants to execute a command as part of its completion result, in which case we interject the completion_result tool with the execute_command tool.
|
||||||
|
if (message.text === "") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "api_req_failed": // this message is used to update the latest api_req_started that the request failed
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch (message.say) {
|
||||||
|
case "api_req_finished": // combineApiRequests removes this from modifiedMessages anyways
|
||||||
|
case "api_req_retried": // this message is used to update the latest api_req_started that the request was retried
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}, [modifiedMessages])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// We use a setTimeout to ensure new content is rendered before scrolling to the bottom. virtuoso's followOutput would scroll to the bottom before the new content could render.
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
// TODO: we can use virtuoso's isAtBottom to prevent scrolling if user is scrolled up, and show a 'scroll to bottom' button for better UX
|
||||||
|
// NOTE: scroll to bottom may not work if you use margin, see virtuoso's troubleshooting
|
||||||
|
virtuosoRef.current?.scrollToIndex({
|
||||||
|
index: "LAST",
|
||||||
|
behavior: "smooth",
|
||||||
|
})
|
||||||
|
}, 50)
|
||||||
|
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}, [visibleMessages])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -306,18 +359,28 @@ const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => {
|
|||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
overflowY: "scroll", // always show scrollbar
|
overflowY: "scroll", // always show scrollbar
|
||||||
}}
|
}}
|
||||||
followOutput={(isAtBottom) => {
|
// followOutput={(isAtBottom) => {
|
||||||
// TODO: we can use isAtBottom to prevent scrolling if user is scrolled up, and show a 'scroll to bottom' button for better UX
|
// const lastMessage = modifiedMessages.at(-1)
|
||||||
const lastMessage = modifiedMessages.at(-1)
|
// if (lastMessage && shouldShowChatRow(lastMessage)) {
|
||||||
if (lastMessage && shouldShowChatRow(lastMessage)) {
|
// return "smooth"
|
||||||
return "smooth" // NOTE: scroll to bottom may not work if you use margin, see virtuoso's troubleshooting
|
// }
|
||||||
}
|
// return false
|
||||||
return false
|
// }}
|
||||||
}}
|
|
||||||
increaseViewportBy={{ top: 0, bottom: Number.MAX_SAFE_INTEGER }} // hack to make sure the last message is always rendered to get truly perfect scroll to bottom animation when new messages are added (Number.MAX_SAFE_INTEGER is safe for arithmetic operations, which is all virtuoso uses this value for in src/sizeRangeSystem.ts)
|
increaseViewportBy={{ top: 0, bottom: Number.MAX_SAFE_INTEGER }} // hack to make sure the last message is always rendered to get truly perfect scroll to bottom animation when new messages are added (Number.MAX_SAFE_INTEGER is safe for arithmetic operations, which is all virtuoso uses this value for in src/sizeRangeSystem.ts)
|
||||||
data={modifiedMessages}
|
data={visibleMessages} // messages is the raw format returned by extension, modifiedMessages is the manipulated structure that combines certain messages of related type, and visibleMessages is the filtered structure that removes messages that should not be rendered
|
||||||
itemContent={(index, message) => (
|
itemContent={(index, message) => (
|
||||||
<ChatRow key={message.ts} message={message} syntaxHighlighterStyle={syntaxHighlighterStyle} />
|
<ChatRow
|
||||||
|
key={message.ts}
|
||||||
|
message={message}
|
||||||
|
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||||
|
isExpanded={expandedRows[message.ts] || false}
|
||||||
|
onToggleExpand={() => toggleRowExpansion(message.ts)}
|
||||||
|
apiRequestFailedMessage={
|
||||||
|
index === visibleMessages.length - 1 && modifiedMessages.at(-1)?.ask === "api_req_failed" // if request is retried then the latest message is a api_req_retried
|
||||||
|
? modifiedMessages.at(-1)?.text
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||||
import { useMemo, useState } from "react"
|
import { useMemo } from "react"
|
||||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
|
||||||
import { getLanguageFromPath } from "../../utilities/getLanguageFromPath"
|
import { getLanguageFromPath } from "../../utilities/getLanguageFromPath"
|
||||||
import { SyntaxHighlighterStyle } from "../../utilities/getSyntaxHighlighterStyleFromTheme"
|
import { SyntaxHighlighterStyle } from "../../utilities/getSyntaxHighlighterStyleFromTheme"
|
||||||
@@ -64,11 +64,19 @@ interface CodeBlockProps {
|
|||||||
language?: string | undefined
|
language?: string | undefined
|
||||||
path?: string
|
path?: string
|
||||||
syntaxHighlighterStyle: SyntaxHighlighterStyle
|
syntaxHighlighterStyle: SyntaxHighlighterStyle
|
||||||
|
isExpanded: boolean
|
||||||
|
onToggleExpand: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const CodeBlock = ({ code, diff, language, path, syntaxHighlighterStyle }: CodeBlockProps) => {
|
const CodeBlock = ({
|
||||||
const [isExpanded, setIsExpanded] = useState(false)
|
code,
|
||||||
|
diff,
|
||||||
|
language,
|
||||||
|
path,
|
||||||
|
syntaxHighlighterStyle,
|
||||||
|
isExpanded,
|
||||||
|
onToggleExpand,
|
||||||
|
}: CodeBlockProps) => {
|
||||||
/*
|
/*
|
||||||
We need to remove leading non-alphanumeric characters from the path in order for our leading ellipses trick to work.
|
We need to remove leading non-alphanumeric characters from the path in order for our leading ellipses trick to work.
|
||||||
|
|
||||||
@@ -100,7 +108,7 @@ const CodeBlock = ({ code, diff, language, path, syntaxHighlighterStyle }: CodeB
|
|||||||
padding: "6px 10px",
|
padding: "6px 10px",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
onClick={() => setIsExpanded(!isExpanded)}>
|
onClick={onToggleExpand}>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
color: "#BABCC3",
|
color: "#BABCC3",
|
||||||
@@ -115,14 +123,14 @@ const CodeBlock = ({ code, diff, language, path, syntaxHighlighterStyle }: CodeB
|
|||||||
}}>
|
}}>
|
||||||
{removeLeadingNonAlphanumeric(path) + "\u200E"}
|
{removeLeadingNonAlphanumeric(path) + "\u200E"}
|
||||||
</span>
|
</span>
|
||||||
<VSCodeButton appearance="icon" aria-label="Toggle Code" onClick={() => setIsExpanded(!isExpanded)}>
|
<VSCodeButton appearance="icon" aria-label="Toggle Code">
|
||||||
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
|
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
|
||||||
</VSCodeButton>
|
</VSCodeButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(!path || isExpanded) && (
|
{(!path || isExpanded) && (
|
||||||
<div
|
<div
|
||||||
className="code-block-scrollable"
|
//className="code-block-scrollable" this doesn't seem to be necessary anymore, on silicon macs it shows the native mac scrollbar instead of the vscode styled one
|
||||||
style={{
|
style={{
|
||||||
overflowX: "auto",
|
overflowX: "auto",
|
||||||
overflowY: "hidden",
|
overflowY: "hidden",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { VSCodeButton, VSCodeDivider, VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
import { VSCodeButton, VSCodeDivider, VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
import { useMount } from "react-use"
|
import { useEffectOnce } from "react-use"
|
||||||
import { vscode } from "../utilities/vscode"
|
import { vscode } from "../utilities/vscode"
|
||||||
|
|
||||||
type SettingsViewProps = {
|
type SettingsViewProps = {
|
||||||
@@ -70,9 +70,13 @@ const SettingsView = ({ apiKey, setApiKey, maxRequestsPerTask, setMaxRequestsPer
|
|||||||
|
|
||||||
If we only want to run code once on mount we can use react-use's useEffectOnce or useMount
|
If we only want to run code once on mount we can use react-use's useEffectOnce or useMount
|
||||||
*/
|
*/
|
||||||
useMount(() => {
|
useEffectOnce(() => {
|
||||||
validateApiKey(apiKey)
|
const timeoutId = setTimeout(() => {
|
||||||
validateMaxRequests(maxRequestsPerTask)
|
validateApiKey(apiKey)
|
||||||
|
validateMaxRequests(maxRequestsPerTask)
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId)
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body.scrollable,
|
body.scrollable,
|
||||||
.scrollable, body.code-block-scrollable, .code-block-scrollable {
|
.scrollable,
|
||||||
border-color: transparent;
|
body.code-block-scrollable,
|
||||||
transition: border-color 0.7s linear;
|
.code-block-scrollable {
|
||||||
|
border-color: transparent;
|
||||||
|
transition: border-color 0.7s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
body:hover.scrollable,
|
body:hover.scrollable,
|
||||||
@@ -46,30 +48,29 @@ body:focus-within .scrollable,
|
|||||||
body:hover.code-block-scrollable,
|
body:hover.code-block-scrollable,
|
||||||
body:hover .code-block-scrollable,
|
body:hover .code-block-scrollable,
|
||||||
body:focus-within.code-block-scrollable,
|
body:focus-within.code-block-scrollable,
|
||||||
body:focus-within .code-block-scrollable
|
body:focus-within .code-block-scrollable {
|
||||||
{
|
border-color: var(--vscode-scrollbarSlider-background);
|
||||||
border-color: var(--vscode-scrollbarSlider-background);
|
transition: none;
|
||||||
transition: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollable::-webkit-scrollbar-corner {
|
.scrollable::-webkit-scrollbar-corner {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollable::-webkit-scrollbar-thumb {
|
.scrollable::-webkit-scrollbar-thumb {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: inherit;
|
border-color: inherit;
|
||||||
border-right-style: inset;
|
border-right-style: inset;
|
||||||
border-right-width: calc(100vw + 100vh);
|
border-right-width: calc(100vw + 100vh);
|
||||||
border-radius: unset !important;
|
border-radius: unset !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollable::-webkit-scrollbar-thumb:hover {
|
.scrollable::-webkit-scrollbar-thumb:hover {
|
||||||
border-color: var(--vscode-scrollbarSlider-hoverBackground);
|
border-color: var(--vscode-scrollbarSlider-hoverBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollable::-webkit-scrollbar-thumb:active {
|
.scrollable::-webkit-scrollbar-thumb:active {
|
||||||
border-color: var(--vscode-scrollbarSlider-activeBackground);
|
border-color: var(--vscode-scrollbarSlider-activeBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -87,24 +88,24 @@ The above scrollbar styling uses some transparent background color magic to acco
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.code-block-scrollable::-webkit-scrollbar-track {
|
.code-block-scrollable::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-block-scrollable::-webkit-scrollbar-thumb {
|
.code-block-scrollable::-webkit-scrollbar-thumb {
|
||||||
background-color: var(--vscode-scrollbarSlider-background);
|
background-color: var(--vscode-scrollbarSlider-background);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
background-clip: content-box;
|
background-clip: content-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-block-scrollable::-webkit-scrollbar-thumb:hover {
|
.code-block-scrollable::-webkit-scrollbar-thumb:hover {
|
||||||
background-color: var(--vscode-scrollbarSlider-hoverBackground);
|
background-color: var(--vscode-scrollbarSlider-hoverBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-block-scrollable::-webkit-scrollbar-thumb:active {
|
.code-block-scrollable::-webkit-scrollbar-thumb:active {
|
||||||
background-color: var(--vscode-scrollbarSlider-activeBackground);
|
background-color: var(--vscode-scrollbarSlider-activeBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-block-scrollable::-webkit-scrollbar-corner {
|
.code-block-scrollable::-webkit-scrollbar-corner {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user