mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Add chat settings
This commit is contained in:
parent
d6e308d679
commit
77b722c077
9 changed files with 403 additions and 170 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
7
src/shared/ChatSettings.ts
Normal file
7
src/shared/ChatSettings.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export interface ChatSettings {
|
||||
mode: "code" | "chat"
|
||||
}
|
||||
|
||||
export const DEFAULT_CHAT_SETTINGS: ChatSettings = {
|
||||
mode: "code",
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue