diff --git a/icons/icon.png b/icons/icon.png index f981fe0..c9dc6a8 100644 Binary files a/icons/icon.png and b/icons/icon.png differ diff --git a/package.json b/package.json index 2b10aba..3d2b49e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "version": "1.6.2", "icon": "icons/icon.png", "galleryBanner": { - "color": "#C1DCEA", + "color": "#C7D1D9", "theme": "light" }, "engines": { diff --git a/src/integrations/TerminalManager.ts b/src/integrations/TerminalManager.ts index dec8851..cb75979 100644 --- a/src/integrations/TerminalManager.ts +++ b/src/integrations/TerminalManager.ts @@ -330,6 +330,9 @@ export class TerminalProcess extends EventEmitter { data = lines.join("\n") } + // sometimes chunks have leading/trailing commas which we'll remove too + data = data.replace(/^,+|,+$/g, "") + // For non-immediately returning commands we want to show loading spinner right away but this wouldnt happen until it emits a line break, so as soon as we get any output we emit "" to let webview know to show spinner if (!didEmitEmptyLine && !this.fullOutput && data) { this.emit("line", "") // empty line to indicate start of command output stream diff --git a/webview-ui/src/components/ChatRow.tsx b/webview-ui/src/components/ChatRow.tsx index 2ca2557..7bad2bf 100644 --- a/webview-ui/src/components/ChatRow.tsx +++ b/webview-ui/src/components/ChatRow.tsx @@ -1,4 +1,4 @@ -import { VSCodeBadge, VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react" +import { VSCodeBadge, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react" import deepEqual from "fast-deep-equal" import React, { memo, useMemo } from "react" import ReactMarkdown from "react-markdown" @@ -103,11 +103,11 @@ const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessa ), cost != null ? ( - API Request Complete + API Request ) : apiRequestFailedMessage ? ( API Request Failed ) : ( - Making API Request... + API Request... ), ] case "followup": @@ -299,15 +299,19 @@ const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessa ...headerStyle, marginBottom: cost == null && apiRequestFailedMessage ? 10 : 0, justifyContent: "space-between", - }}> + cursor: "pointer", + userSelect: "none", + WebkitUserSelect: "none", + MozUserSelect: "none", + msUserSelect: "none", + }} + onClick={onToggleExpand}>
{icon} {title} {cost != null && cost > 0 && ${Number(cost)?.toFixed(4)}}
- - - + {cost == null && apiRequestFailedMessage && ( <> @@ -391,24 +395,12 @@ const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessa return (
- - The user made the following changes: - @@ -627,7 +619,8 @@ const Markdown = memo(({ markdown }: { 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. // when claude wraps text in thinking tags, he doesnt use line breaks so we need to insert those ourselves to render markdown correctly const parsed = markdown?.replace(/([\s\S]*?)<\/thinking>/g, (match, content) => { - return `__\n\n${content}\n\n__` + return content + // return `__\n\n${content}\n\n__` }) return (
@@ -704,6 +697,13 @@ const Markdown = memo(({ markdown }: { markdown?: string }) => { whiteSpace: "pre-line", wordBreak: "break-word", overflowWrap: "anywhere", + backgroundColor: "var(--vscode-textCodeBlock-background)", + color: "var(--vscode-textPreformat-foreground)", + fontFamily: "var(--vscode-editor-font-family)", + fontSize: "var(--vscode-editor-font-size)", + borderRadius: "3px", + border: "1px solid var(--vscode-textSeparator-foreground)", + // padding: "2px 4px", }}> {children} diff --git a/webview-ui/src/components/ChatView.tsx b/webview-ui/src/components/ChatView.tsx index 126bb57..10b7e00 100644 --- a/webview-ui/src/components/ChatView.tsx +++ b/webview-ui/src/components/ChatView.tsx @@ -602,11 +602,12 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie // Since we have maxRows, when text is long enough it starts to overflow the bottom padding, appearing behind the thumbnails. To fix this, we use a transparent border to push the text up instead. (https://stackoverflow.com/questions/42631947/maintaining-a-padding-inside-of-text-area/52538410#52538410) borderTop: "9px solid transparent", borderBottom: `${thumbnailsHeight + 9}px solid transparent`, - borderRight: "54px solid transparent", - borderLeft: "9px solid transparent", + borderColor: "transparent", + // borderRight: "54px solid transparent", + // borderLeft: "9px solid transparent", // NOTE: react-textarea-autosize doesn't calculate correct height when using borderLeft/borderRight so we need to use horizontal padding instead // Instead of using boxShadow, we use a div with a border to better replicate the behavior when the textarea is focused // boxShadow: "0px 0px 0px 1px var(--vscode-input-border)", - padding: 0, + padding: "0 50px 0 9px", cursor: textAreaDisabled ? "not-allowed" : undefined, flex: 1, }} diff --git a/webview-ui/src/components/CodeAccordian.tsx b/webview-ui/src/components/CodeAccordian.tsx index d9e2f76..358118c 100644 --- a/webview-ui/src/components/CodeAccordian.tsx +++ b/webview-ui/src/components/CodeAccordian.tsx @@ -7,6 +7,7 @@ interface CodeAccordianProps { diff?: string language?: string | undefined path?: string + isFeedback?: boolean isExpanded: boolean onToggleExpand: () => void } @@ -19,7 +20,7 @@ The replace method removes these matched characters, effectively trimming the st */ const removeLeadingNonAlphanumeric = (path: string): string => path.replace(/^[^a-zA-Z0-9]+/, "") -const CodeAccordian = ({ code, diff, language, path, isExpanded, onToggleExpand }: CodeAccordianProps) => { +const CodeAccordian = ({ code, diff, language, path, isFeedback, isExpanded, onToggleExpand }: CodeAccordianProps) => { const inferredLanguage = useMemo( () => code && (language ?? (path ? getLanguageFromPath(path) : undefined)), [path, language, code] @@ -33,7 +34,7 @@ const CodeAccordian = ({ code, diff, language, path, isExpanded, onToggleExpand overflow: "hidden", // This ensures the inner scrollable area doesn't overflow the rounded corners border: "1px solid var(--vscode-editorGroup-border)", }}> - {path && ( + {(path || isFeedback) && (
- - {removeLeadingNonAlphanumeric(path) + "\u200E"} - +
+ {isFeedback && ( + + )} + + {isFeedback ? "User Edits" : removeLeadingNonAlphanumeric(path ?? "") + "\u200E"} + +
)} - {(!path || isExpanded) && ( + {(!(path || isFeedback) || isExpanded) && (
= ({ onClose, }) => { const { apiConfiguration } = useExtensionState() - const [isExpanded, setIsExpanded] = useState(false) + const [isTaskExpanded, setIsTaskExpanded] = useState(true) + const [isTextExpanded, setIsTextExpanded] = useState(false) const [showSeeMore, setShowSeeMore] = useState(false) const textContainerRef = useRef(null) const textRef = useRef(null) @@ -68,11 +69,11 @@ const TaskHeader: React.FC = ({ const { height: windowHeight, width: windowWidth } = useWindowSize() useEffect(() => { - if (isExpanded && textContainerRef.current) { + if (isTextExpanded && textContainerRef.current) { const maxHeight = windowHeight * (1 / 2) textContainerRef.current.style.maxHeight = `${maxHeight}px` } - }, [isExpanded, windowHeight]) + }, [isTextExpanded, windowHeight]) useEffect(() => { if (textRef.current && textContainerRef.current) { @@ -83,7 +84,7 @@ const TaskHeader: React.FC = ({ const isOverflowing = textRef.current.scrollHeight > textContainerHeight // necessary to show see more button again if user resizes window to expand and then back to collapse if (!isOverflowing) { - setIsExpanded(false) + setIsTextExpanded(false) } setShowSeeMore(isOverflowing) } @@ -96,10 +97,10 @@ const TaskHeader: React.FC = ({ backgroundColor: "var(--vscode-badge-background)", color: "var(--vscode-badge-foreground)", borderRadius: "3px", - padding: "12px", + padding: "9px 10px 9px 14px", display: "flex", flexDirection: "column", - gap: "8px", + gap: 6, position: "relative", zIndex: 1, }}> @@ -109,144 +110,191 @@ const TaskHeader: React.FC = ({ justifyContent: "space-between", alignItems: "center", }}> - Task - +
setIsTaskExpanded(!isTaskExpanded)}> +
+ +
+
+ Task{!isTaskExpanded && ":"} + {!isTaskExpanded && {task.text}} +
+
+ {!isTaskExpanded && ( +
+ ${totalCost?.toFixed(4)} +
+ )} +
-
-
- {task.text} -
- {!isExpanded && showSeeMore && ( + {isTaskExpanded && ( + <>
+ display: "-webkit-box", + WebkitLineClamp: isTextExpanded ? "unset" : 3, + WebkitBoxOrient: "vertical", + overflow: "hidden", + whiteSpace: "pre-wrap", + wordBreak: "break-word", + overflowWrap: "anywhere", + }}> + {task.text} +
+ {!isTextExpanded && showSeeMore && ( +
+
+
setIsTextExpanded(!isTextExpanded)}> + See more +
+
+ )} +
+ {isTextExpanded && showSeeMore && (
setIsExpanded(!isExpanded)}> - See more + onClick={() => setIsTextExpanded(!isTextExpanded)}> + See less
-
- )} -
- {isExpanded && showSeeMore && ( -
setIsExpanded(!isExpanded)}> - See less -
- )} - {task.images && task.images.length > 0 && } -
-
-
- Tokens: - - - {tokensIn?.toLocaleString()} - - - - {tokensOut?.toLocaleString()} - -
- {(apiConfiguration?.apiProvider === "openai" || apiConfiguration?.apiProvider === "ollama") && ( - )} -
- - {(doesModelSupportPromptCache || cacheReads !== undefined || cacheWrites !== undefined) && ( -
- Cache: - - - +{(cacheWrites || 0)?.toLocaleString()} - - - - {(cacheReads || 0)?.toLocaleString()} - -
- )} - {apiConfiguration?.apiProvider !== "openai" && apiConfiguration?.apiProvider !== "ollama" && ( -
-
- API Cost: - ${totalCost?.toFixed(4)} + {task.images && task.images.length > 0 && } +
+
+
+ Tokens: + + + {tokensIn?.toLocaleString()} + + + + {tokensOut?.toLocaleString()} + +
+ {(apiConfiguration?.apiProvider === "openai" || + apiConfiguration?.apiProvider === "ollama") && }
- + + {(doesModelSupportPromptCache || cacheReads !== undefined || cacheWrites !== undefined) && ( +
+ Cache: + + + +{(cacheWrites || 0)?.toLocaleString()} + + + + {(cacheReads || 0)?.toLocaleString()} + +
+ )} + {apiConfiguration?.apiProvider !== "openai" && + apiConfiguration?.apiProvider !== "ollama" && ( +
+
+ API Cost: + ${totalCost?.toFixed(4)} +
+ +
+ )}
- )} -
+ + )}
{/* {apiProvider === "kodu" && (
( vscode.postMessage({ type: "exportCurrentTask" })} - style={{ - marginBottom: "-2px", - marginRight: "-2.5px", - }}> + style={ + { + // marginBottom: "-2px", + // marginRight: "-2.5px", + } + }>
EXPORT
)