Add chat settings

This commit is contained in:
Saoud Rizwan 2025-01-18 21:47:27 -08:00
parent d6e308d679
commit 77b722c077
9 changed files with 403 additions and 170 deletions

View file

@ -59,6 +59,7 @@ import CheckpointTracker from "../integrations/checkpoints/CheckpointTracker"
import getFolderSize from "get-folder-size"
import { BrowserSettings } from "../shared/BrowserSettings"
import { ADVISOR_SYSTEM_PROMPT } from "./prompts/advisor"
import { ChatSettings } from "../shared/ChatSettings"
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
@ -77,6 +78,7 @@ export class Cline {
customInstructions?: string
autoApprovalSettings: AutoApprovalSettings
private browserSettings: BrowserSettings
private chatSettings: ChatSettings
apiConversationHistory: Anthropic.MessageParam[] = []
clineMessages: ClineMessage[] = []
private askResponse?: ClineAskResponse
@ -97,6 +99,7 @@ export class Cline {
private advisorProblem?: string
// streaming
isWaitingForFirstChunk = false
isStreaming = false
private currentStreamingContentIndex = 0
private assistantMessageContent: AssistantMessageContent[] = []
@ -114,6 +117,7 @@ export class Cline {
apiConfiguration: ApiConfiguration,
autoApprovalSettings: AutoApprovalSettings,
browserSettings: BrowserSettings,
chatSettings: ChatSettings,
customInstructions?: string,
task?: string,
images?: string[],
@ -128,6 +132,7 @@ export class Cline {
this.customInstructions = customInstructions
this.autoApprovalSettings = autoApprovalSettings
this.browserSettings = browserSettings
this.chatSettings = chatSettings
if (historyItem) {
this.taskId = historyItem.id
this.conversationHistoryDeletedRange = historyItem.conversationHistoryDeletedRange
@ -145,6 +150,10 @@ export class Cline {
this.browserSession.browserSettings = browserSettings
}
updateChatSettings(chatSettings: ChatSettings) {
this.chatSettings = chatSettings
}
// Storing task to disk for history
private async ensureTaskDirectoryExists(): Promise<string> {
@ -1202,6 +1211,7 @@ export class Cline {
this.api.getModel().info.supportsComputerUse ?? false,
mcpHub,
this.browserSettings,
this.chatSettings,
supportsConsultAdvisor,
)
let settingsCustomInstructions = this.customInstructions?.trim()
@ -1322,8 +1332,10 @@ export class Cline {
try {
// awaiting first chunk to see if it will throw an error
this.isWaitingForFirstChunk = true
const firstChunk = await iterator.next()
yield firstChunk.value
this.isWaitingForFirstChunk = false
} catch (error) {
if (!this.didAutomaticallyRetryFailedApiRequest) {
console.log("first chunk failed, waiting 1 second before retrying")

View file

@ -3,12 +3,14 @@ import os from "os"
import osName from "os-name"
import { McpHub } from "../../services/mcp/McpHub"
import { BrowserSettings } from "../../shared/BrowserSettings"
import { ChatSettings } from "../../shared/ChatSettings"
export const SYSTEM_PROMPT = async (
cwd: string,
supportsComputerUse: boolean,
mcpHub: McpHub,
browserSettings: BrowserSettings,
chatSettings: ChatSettings,
supportsConsultAdvisor: boolean,
) => `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.

View file

@ -24,6 +24,7 @@ import { getNonce } from "./getNonce"
import { getUri } from "./getUri"
import { AutoApprovalSettings, DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../shared/AutoApprovalSettings"
import { BrowserSettings, DEFAULT_BROWSER_SETTINGS } from "../../shared/BrowserSettings"
import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "../../shared/ChatSettings"
/*
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@ -67,6 +68,7 @@ type GlobalStateKey =
| "openRouterAdvisorModelInfo"
| "autoApprovalSettings"
| "browserSettings"
| "chatSettings"
export const GlobalFileNames = {
apiConversationHistory: "api_conversation_history.json",
@ -216,18 +218,30 @@ export class ClineProvider implements vscode.WebviewViewProvider {
async initClineWithTask(task?: string, images?: string[]) {
await this.clearTask() // ensures that an exising task doesn't exist before starting a new one, although this shouldn't be possible since user must clear task before starting a new one
const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings } = await this.getState()
this.cline = new Cline(this, apiConfiguration, autoApprovalSettings, browserSettings, customInstructions, task, images)
}
async initClineWithHistoryItem(historyItem: HistoryItem) {
await this.clearTask()
const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings } = await this.getState()
const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings, chatSettings } =
await this.getState()
this.cline = new Cline(
this,
apiConfiguration,
autoApprovalSettings,
browserSettings,
chatSettings,
customInstructions,
task,
images,
)
}
async initClineWithHistoryItem(historyItem: HistoryItem) {
await this.clearTask()
const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings, chatSettings } =
await this.getState()
this.cline = new Cline(
this,
apiConfiguration,
autoApprovalSettings,
browserSettings,
chatSettings,
customInstructions,
undefined,
undefined,
@ -467,6 +481,15 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.postStateToWebview()
}
break
case "chatSettings":
if (message.chatSettings) {
await this.updateGlobalState("chatSettings", message.chatSettings)
if (this.cline) {
this.cline.updateChatSettings(message.chatSettings)
}
await this.postStateToWebview()
}
break
// case "relaunchChromeDebugMode":
// if (this.cline) {
// this.cline.browserSession.relaunchChromeDebugMode()
@ -603,7 +626,11 @@ export class ClineProvider implements vscode.WebviewViewProvider {
console.error("Failed to abort task", error)
}
await pWaitFor(
() => this.cline === undefined || this.cline.isStreaming === false || this.cline.didFinishAbortingStream,
() =>
this.cline === undefined ||
this.cline.isStreaming === false ||
this.cline.didFinishAbortingStream ||
this.cline.isWaitingForFirstChunk, // if only first chunk is processed, then there's no need to wait for graceful abort (closes edits, browser, etc)
{
timeout: 3_000,
},
@ -956,6 +983,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
taskHistory,
autoApprovalSettings,
browserSettings,
chatSettings,
} = await this.getState()
return {
version: this.context.extension?.packageJSON?.version ?? "",
@ -969,6 +997,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,
autoApprovalSettings,
browserSettings,
chatSettings,
}
}
@ -1059,6 +1088,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
taskHistory,
autoApprovalSettings,
browserSettings,
chatSettings,
] = await Promise.all([
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
this.getGlobalState("apiModelId") as Promise<string | undefined>,
@ -1094,6 +1124,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.getGlobalState("taskHistory") as Promise<HistoryItem[] | undefined>,
this.getGlobalState("autoApprovalSettings") as Promise<AutoApprovalSettings | undefined>,
this.getGlobalState("browserSettings") as Promise<BrowserSettings | undefined>,
this.getGlobalState("chatSettings") as Promise<ChatSettings | undefined>,
])
let apiProvider: ApiProvider
@ -1147,6 +1178,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
taskHistory,
autoApprovalSettings: autoApprovalSettings || DEFAULT_AUTO_APPROVAL_SETTINGS, // default value can be 0 or empty string
browserSettings: browserSettings || DEFAULT_BROWSER_SETTINGS,
chatSettings: chatSettings || DEFAULT_CHAT_SETTINGS,
}
}

View file

@ -0,0 +1,7 @@
export interface ChatSettings {
mode: "code" | "chat"
}
export const DEFAULT_CHAT_SETTINGS: ChatSettings = {
mode: "code",
}

View file

@ -3,6 +3,7 @@
import { ApiConfiguration, ModelInfo } from "./api"
import { AutoApprovalSettings } from "./AutoApprovalSettings"
import { BrowserSettings } from "./BrowserSettings"
import { ChatSettings } from "./ChatSettings"
import { HistoryItem } from "./HistoryItem"
import { McpServer } from "./mcp"
@ -47,6 +48,7 @@ export interface ExtensionState {
shouldShowAnnouncement: boolean
autoApprovalSettings: AutoApprovalSettings
browserSettings: BrowserSettings
chatSettings: ChatSettings
}
export interface ClineMessage {

View file

@ -1,6 +1,7 @@
import { ApiConfiguration } from "./api"
import { AutoApprovalSettings } from "./AutoApprovalSettings"
import { BrowserSettings } from "./BrowserSettings"
import { ChatSettings } from "./ChatSettings"
export interface WebviewMessage {
type:
@ -28,6 +29,7 @@ export interface WebviewMessage {
| "restartMcpServer"
| "autoApprovalSettings"
| "browserSettings"
| "chatSettings"
| "checkpointDiff"
| "checkpointRestore"
| "taskCompletionViewChanges"
@ -41,6 +43,7 @@ export interface WebviewMessage {
number?: number
autoApprovalSettings?: AutoApprovalSettings
browserSettings?: BrowserSettings
chatSettings?: ChatSettings
}
export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse"

View file

@ -12,6 +12,10 @@ import {
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import ContextMenu from "./ContextMenu"
import Thumbnails from "../common/Thumbnails"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import styled from "styled-components"
import { useWindowSize } from "react-use"
import { vscode } from "../../utils/vscode"
interface ChatTextAreaProps {
inputValue: string
@ -26,6 +30,74 @@ interface ChatTextAreaProps {
onHeightChange?: (height: number) => void
}
const SwitchOption = styled.div<{ isActive: boolean }>`
padding: 2px 8px;
color: ${(props) => (props.isActive ? "var(--vscode-badge-foreground)" : "var(--vscode-input-foreground)")};
z-index: 1;
transition: color 0.2s ease;
font-size: 12px;
width: 50%;
text-align: center;
&:hover {
background-color: ${(props) => (!props.isActive ? "var(--vscode-toolbar-hoverBackground)" : "transparent")};
}
`
const SwitchContainer = styled.div<{ disabled: boolean }>`
display: flex;
align-items: center;
background-color: var(--vscode-editor-background);
border: 1px solid var(--vscode-input-border);
border-radius: 12px;
overflow: hidden;
position: absolute;
right: 15px;
cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
opacity: ${(props) => (props.disabled ? 0.5 : 1)};
transform: scale(0.85);
transform-origin: right center;
flex-shrink: 0;
`
const Slider = styled.div<{ isChat: boolean }>`
position: absolute;
height: 100%;
width: 50%;
background-color: var(--vscode-badge-background);
transition: transform 0.2s ease;
transform: translateX(${(props) => (props.isChat ? "100%" : "0%")});
`
const ButtonContainer = styled.div`
display: flex;
align-items: center;
gap: 3px;
font-size: 10px;
white-space: nowrap;
`
const ACTUAL_SWITCH_WIDTH = 90
const SWITCH_WIDTH = ACTUAL_SWITCH_WIDTH * 0.85 // Account for the 0.85 scale transform
const CONTEXT_BUTTON_WIDTH = 60
const IMAGES_BUTTON_WIDTH = 80
const CONTAINER_PADDING = 30 // 15px left + 15px right
const TOTAL_WIDTH = SWITCH_WIDTH + 4 + CONTEXT_BUTTON_WIDTH + IMAGES_BUTTON_WIDTH + CONTAINER_PADDING
const ControlsContainer = styled.div`
display: flex;
align-items: center;
margin-top: -3px;
position: relative;
padding: 0px 15px 5px 15px;
`
const ButtonGroup = styled.div`
display: flex;
align-items: center;
gap: 4px;
`
const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
(
{
@ -42,7 +114,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
},
ref,
) => {
const { filePaths } = useExtensionState()
const { filePaths, chatSettings } = useExtensionState()
const [isTextAreaFocused, setIsTextAreaFocused] = useState(false)
const [thumbnailsHeight, setThumbnailsHeight] = useState(0)
const [textAreaBaseHeight, setTextAreaBaseHeight] = useState<number | undefined>(undefined)
@ -57,6 +129,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
const [justDeletedSpaceAfterMention, setJustDeletedSpaceAfterMention] = useState(false)
const [intendedCursorPosition, setIntendedCursorPosition] = useState<number | null>(null)
const contextMenuContainerRef = useRef<HTMLDivElement>(null)
const { width: windowWidth } = useWindowSize()
const showButtonText = windowWidth - CONTAINER_PADDING > TOTAL_WIDTH - CONTAINER_PADDING
const queryItems = useMemo(() => {
return [
@ -406,181 +480,280 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
[updateCursorPosition],
)
const onModeToggle = useCallback(() => {
if (textAreaDisabled) return
const newMode = chatSettings.mode === "chat" ? "code" : "chat"
vscode.postMessage({
type: "chatSettings",
chatSettings: {
mode: newMode,
},
})
}, [chatSettings.mode, textAreaDisabled])
const handleContextButtonClick = useCallback(() => {
if (textAreaDisabled) return
// Focus the textarea first
textAreaRef.current?.focus()
// If input is empty, just insert @
if (!inputValue.trim()) {
const event = {
target: {
value: "@",
selectionStart: 1,
},
} as React.ChangeEvent<HTMLTextAreaElement>
handleInputChange(event)
updateHighlights()
return
}
// If input ends with space or is empty, just append @
if (inputValue.endsWith(" ")) {
const event = {
target: {
value: inputValue + "@",
selectionStart: inputValue.length + 1,
},
} as React.ChangeEvent<HTMLTextAreaElement>
handleInputChange(event)
updateHighlights()
return
}
// Otherwise add space then @
const event = {
target: {
value: inputValue + " @",
selectionStart: inputValue.length + 2,
},
} as React.ChangeEvent<HTMLTextAreaElement>
handleInputChange(event)
updateHighlights()
}, [inputValue, textAreaDisabled, handleInputChange, updateHighlights])
return (
<div
style={{
padding: "10px 15px",
opacity: textAreaDisabled ? 0.5 : 1,
position: "relative",
display: "flex",
}}>
{showContextMenu && (
<div ref={contextMenuContainerRef}>
<ContextMenu
onSelect={handleMentionSelect}
searchQuery={searchQuery}
onMouseDown={handleMenuMouseDown}
selectedIndex={selectedMenuIndex}
setSelectedIndex={setSelectedMenuIndex}
selectedType={selectedType}
queryItems={queryItems}
/>
</div>
)}
{!isTextAreaFocused && (
<div
style={{
position: "absolute",
inset: "10px 15px",
border: "1px solid var(--vscode-input-border)",
borderRadius: 2,
pointerEvents: "none",
zIndex: 5,
}}
/>
)}
<div
ref={highlightLayerRef}
style={{
position: "absolute",
top: 10,
left: 15,
right: 15,
bottom: 10,
pointerEvents: "none",
whiteSpace: "pre-wrap",
wordWrap: "break-word",
color: "transparent",
overflow: "hidden",
backgroundColor: "var(--vscode-input-background)",
fontFamily: "var(--vscode-font-family)",
fontSize: "var(--vscode-editor-font-size)",
lineHeight: "var(--vscode-editor-line-height)",
borderRadius: 2,
borderLeft: 0,
borderRight: 0,
borderTop: 0,
borderColor: "transparent",
borderBottom: `${thumbnailsHeight + 6}px solid transparent`,
padding: "9px 49px 3px 9px",
}}
/>
<DynamicTextArea
ref={(el) => {
if (typeof ref === "function") {
ref(el)
} else if (ref) {
ref.current = el
}
textAreaRef.current = el
}}
value={inputValue}
disabled={textAreaDisabled}
onChange={(e) => {
handleInputChange(e)
updateHighlights()
}}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
onFocus={() => setIsTextAreaFocused(true)}
onBlur={handleBlur}
onPaste={handlePaste}
onSelect={updateCursorPosition}
onMouseUp={updateCursorPosition}
onHeightChange={(height) => {
if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) {
setTextAreaBaseHeight(height)
}
onHeightChange?.(height)
}}
placeholder={placeholderText}
maxRows={10}
autoFocus={true}
style={{
width: "100%",
boxSizing: "border-box",
backgroundColor: "transparent",
color: "var(--vscode-input-foreground)",
//border: "1px solid var(--vscode-input-border)",
borderRadius: 2,
fontFamily: "var(--vscode-font-family)",
fontSize: "var(--vscode-editor-font-size)",
lineHeight: "var(--vscode-editor-line-height)",
resize: "none",
overflowX: "hidden",
overflowY: "scroll",
scrollbarWidth: "none",
// 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",
borderLeft: 0,
borderRight: 0,
borderTop: 0,
borderBottom: `${thumbnailsHeight + 6}px 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: "9px 49px 3px 9px",
cursor: textAreaDisabled ? "not-allowed" : undefined,
flex: 1,
zIndex: 1,
}}
onScroll={() => updateHighlights()}
/>
{selectedImages.length > 0 && (
<Thumbnails
images={selectedImages}
setImages={setSelectedImages}
onHeightChange={handleThumbnailsHeightChange}
style={{
position: "absolute",
paddingTop: 4,
bottom: 14,
left: 22,
right: 67, // (54 + 9) + 4 extra padding
zIndex: 2,
}}
/>
)}
<div>
<div
style={{
position: "absolute",
right: 23,
padding: "10px 15px",
opacity: textAreaDisabled ? 0.5 : 1,
position: "relative",
display: "flex",
alignItems: "flex-center",
height: textAreaBaseHeight || 31,
bottom: 9.5, // should be 10 but doesnt look good on mac
zIndex: 2,
}}>
{showContextMenu && (
<div ref={contextMenuContainerRef}>
<ContextMenu
onSelect={handleMentionSelect}
searchQuery={searchQuery}
onMouseDown={handleMenuMouseDown}
selectedIndex={selectedMenuIndex}
setSelectedIndex={setSelectedMenuIndex}
selectedType={selectedType}
queryItems={queryItems}
/>
</div>
)}
{!isTextAreaFocused && (
<div
style={{
position: "absolute",
inset: "10px 15px",
border: "1px solid var(--vscode-input-border)",
borderRadius: 2,
pointerEvents: "none",
zIndex: 5,
}}
/>
)}
<div
ref={highlightLayerRef}
style={{
position: "absolute",
top: 10,
left: 15,
right: 15,
bottom: 10,
pointerEvents: "none",
whiteSpace: "pre-wrap",
wordWrap: "break-word",
color: "transparent",
overflow: "hidden",
backgroundColor: "var(--vscode-input-background)",
fontFamily: "var(--vscode-font-family)",
fontSize: "var(--vscode-editor-font-size)",
lineHeight: "var(--vscode-editor-line-height)",
borderRadius: 2,
borderLeft: 0,
borderRight: 0,
borderTop: 0,
borderColor: "transparent",
borderBottom: `${thumbnailsHeight + 6}px solid transparent`,
padding: "9px 49px 3px 9px",
}}
/>
<DynamicTextArea
ref={(el) => {
if (typeof ref === "function") {
ref(el)
} else if (ref) {
ref.current = el
}
textAreaRef.current = el
}}
value={inputValue}
disabled={textAreaDisabled}
onChange={(e) => {
handleInputChange(e)
updateHighlights()
}}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
onFocus={() => setIsTextAreaFocused(true)}
onBlur={handleBlur}
onPaste={handlePaste}
onSelect={updateCursorPosition}
onMouseUp={updateCursorPosition}
onHeightChange={(height) => {
if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) {
setTextAreaBaseHeight(height)
}
onHeightChange?.(height)
}}
placeholder={placeholderText}
maxRows={10}
autoFocus={true}
style={{
width: "100%",
boxSizing: "border-box",
backgroundColor: "transparent",
color: "var(--vscode-input-foreground)",
//border: "1px solid var(--vscode-input-border)",
borderRadius: 2,
fontFamily: "var(--vscode-font-family)",
fontSize: "var(--vscode-editor-font-size)",
lineHeight: "var(--vscode-editor-line-height)",
resize: "none",
overflowX: "hidden",
overflowY: "scroll",
scrollbarWidth: "none",
// 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",
borderLeft: 0,
borderRight: 0,
borderTop: 0,
borderBottom: `${thumbnailsHeight + 6}px 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: "9px 28px 3px 9px",
cursor: textAreaDisabled ? "not-allowed" : undefined,
flex: 1,
zIndex: 1,
}}
onScroll={() => updateHighlights()}
/>
{selectedImages.length > 0 && (
<Thumbnails
images={selectedImages}
setImages={setSelectedImages}
onHeightChange={handleThumbnailsHeightChange}
style={{
position: "absolute",
paddingTop: 4,
bottom: 14,
left: 22,
right: 47, // (54 + 9) + 4 extra padding
zIndex: 2,
}}
/>
)}
<div
style={{
position: "absolute",
right: 23,
display: "flex",
flexDirection: "row",
alignItems: "center",
alignItems: "flex-center",
height: textAreaBaseHeight || 31,
bottom: 9.5, // should be 10 but doesnt look good on mac
zIndex: 2,
}}>
<div
className={`input-icon-button ${shouldDisableImages ? "disabled" : ""} codicon codicon-device-camera`}
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
}}>
{/* <div
className={`input-icon-button ${shouldDisableImages ? "disabled" : ""} codicon codicon-device-camera`}
onClick={() => {
if (!shouldDisableImages) {
onSelectImages()
}
}}
style={{
marginRight: 5.5,
fontSize: 16.5,
}}
/> */}
<div
className={`input-icon-button ${textAreaDisabled ? "disabled" : ""} codicon codicon-send`}
onClick={() => {
if (!textAreaDisabled) {
onSend()
}
}}
style={{ fontSize: 15 }}></div>
</div>
</div>
</div>
<ControlsContainer>
<ButtonGroup>
<VSCodeButton
appearance="icon"
aria-label="Add Context"
disabled={textAreaDisabled}
onClick={handleContextButtonClick}
style={{ padding: "0px 0px", height: "20px", marginTop: -1 }}>
<ButtonContainer>
<span style={{ fontSize: "13px", marginBottom: 2 }}>@</span>
{showButtonText && <span style={{ fontSize: "10px" }}>Context</span>}
</ButtonContainer>
</VSCodeButton>
<VSCodeButton
appearance="icon"
aria-label="Add Images"
disabled={shouldDisableImages}
onClick={() => {
if (!shouldDisableImages) {
onSelectImages()
}
}}
style={{
marginRight: 5.5,
fontSize: 16.5,
}}
/>
<div
className={`input-icon-button ${textAreaDisabled ? "disabled" : ""} codicon codicon-send`}
onClick={() => {
if (!textAreaDisabled) {
onSend()
}
}}
style={{ fontSize: 15 }}></div>
</div>
</div>
padding: "0px 0px",
height: "20px",
opacity: shouldDisableImages ? 0.5 : 1,
cursor: shouldDisableImages ? "not-allowed" : undefined,
}}>
<ButtonContainer>
<span className="codicon codicon-device-camera" style={{ fontSize: "14px" }} />
{showButtonText && <span style={{ fontSize: "10px" }}>Add images</span>}
</ButtonContainer>
</VSCodeButton>
</ButtonGroup>
<SwitchContainer disabled={textAreaDisabled} onClick={onModeToggle}>
<Slider isChat={chatSettings.mode === "chat"} />
<SwitchOption isActive={chatSettings.mode === "code"}>Code</SwitchOption>
<SwitchOption isActive={chatSettings.mode === "chat"}>Chat</SwitchOption>
</SwitchContainer>
</ControlsContainer>
</div>
)
},

View file

@ -20,11 +20,11 @@ import { vscode } from "../../utils/vscode"
import HistoryPreview from "../history/HistoryPreview"
import { normalizeApiConfiguration } from "../settings/ApiOptions"
import Announcement from "./Announcement"
import AutoApproveMenu from "./AutoApproveMenu"
import BrowserSessionRow from "./BrowserSessionRow"
import ChatRow from "./ChatRow"
import ChatTextArea from "./ChatTextArea"
import TaskHeader from "./TaskHeader"
import AutoApproveMenu from "./AutoApproveMenu"
interface ChatViewProps {
isHidden: boolean
@ -670,7 +670,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
useEvent("wheel", handleWheel, window, { passive: true }) // passive improves scrolling performance
const placeholderText = useMemo(() => {
const text = task ? "Type a message (@ to add context)..." : "Type your task here (@ to add context)..."
const text = task ? "Type a message..." : "Type your task here..."
return text
}, [task])

View file

@ -15,6 +15,7 @@ import { McpServer } from "../../../src/shared/mcp"
import { convertTextMateToHljs } from "../utils/textMateToHljs"
import { vscode } from "../utils/vscode"
import { DEFAULT_BROWSER_SETTINGS } from "../../../src/shared/BrowserSettings"
import { DEFAULT_CHAT_SETTINGS } from "../../../src/shared/ChatSettings"
interface ExtensionStateContextType extends ExtensionState {
didHydrateState: boolean
@ -40,6 +41,7 @@ export const ExtensionStateContextProvider: React.FC<{
shouldShowAnnouncement: false,
autoApprovalSettings: DEFAULT_AUTO_APPROVAL_SETTINGS,
browserSettings: DEFAULT_BROWSER_SETTINGS,
chatSettings: DEFAULT_CHAT_SETTINGS,
})
const [didHydrateState, setDidHydrateState] = useState(false)
const [showWelcome, setShowWelcome] = useState(false)