mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Random chat text area cleanup (#7436)
This commit is contained in:
parent
3528f51304
commit
f02a2bb05d
32 changed files with 834 additions and 1163 deletions
|
|
@ -24,7 +24,7 @@
|
|||
"@radix-ui/react-slider": "^1.2.4",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-tabs": "^1.1.3",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@roo-code/evals": "workspace:^",
|
||||
"@roo-code/types": "workspace:^",
|
||||
"@tanstack/react-query": "^5.69.0",
|
||||
|
|
|
|||
166
pnpm-lock.yaml
generated
166
pnpm-lock.yaml
generated
|
|
@ -161,8 +161,8 @@ importers:
|
|||
specifier: ^1.1.3
|
||||
version: 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.1.8
|
||||
version: 1.2.6(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@roo-code/evals':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/evals
|
||||
|
|
@ -2575,6 +2575,9 @@ packages:
|
|||
'@radix-ui/primitive@1.1.2':
|
||||
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
|
||||
|
||||
'@radix-ui/primitive@1.1.3':
|
||||
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||
|
||||
'@radix-ui/react-alert-dialog@1.1.13':
|
||||
resolution: {integrity: sha512-/uPs78OwxGxslYOG5TKeUsv9fZC0vo376cXSADdKirTmsLJU2au6L3n34c3p6W26rFDDDze/hwy4fYeNd0qdGA==}
|
||||
peerDependencies:
|
||||
|
|
@ -2601,6 +2604,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7':
|
||||
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-checkbox@1.3.1':
|
||||
resolution: {integrity: sha512-xTaLKAO+XXMPK/BpVTSaAAhlefmvMSACjIhK9mGsImvX2ljcTDm8VGR1CuS1uYcNdR5J+oiOhoJZc5un6bh3VQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -2719,6 +2735,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.11':
|
||||
resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.9':
|
||||
resolution: {integrity: sha512-way197PiTvNp+WBP7svMJasHl+vibhWGQDb6Mgf5mhEWJkgb85z7Lfl9TUdkqpWsf8GRNmoopx9ZxCyDzmgRMQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -2846,6 +2875,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popper@1.2.8':
|
||||
resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-portal@1.1.8':
|
||||
resolution: {integrity: sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg==}
|
||||
peerDependencies:
|
||||
|
|
@ -2885,6 +2927,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-presence@1.1.5':
|
||||
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-primitive@2.1.2':
|
||||
resolution: {integrity: sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==}
|
||||
peerDependencies:
|
||||
|
|
@ -3046,6 +3101,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.8':
|
||||
resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1':
|
||||
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||
peerDependencies:
|
||||
|
|
@ -3131,6 +3199,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-visually-hidden@1.2.3':
|
||||
resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/rect@1.1.1':
|
||||
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||
|
||||
|
|
@ -11905,6 +11986,8 @@ snapshots:
|
|||
|
||||
'@radix-ui/primitive@1.1.2': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.3': {}
|
||||
|
||||
'@radix-ui/react-alert-dialog@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
|
|
@ -11928,6 +12011,15 @@ snapshots:
|
|||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/react-checkbox@1.3.1(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
|
|
@ -12059,6 +12151,19 @@ snapshots:
|
|||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
|
|
@ -12202,6 +12307,24 @@ snapshots:
|
|||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/rect': 1.1.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/react-portal@1.1.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.2(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
|
@ -12232,6 +12355,16 @@ snapshots:
|
|||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/react-primitive@2.1.2(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.2.2(@types/react@18.3.23)(react@18.3.1)
|
||||
|
|
@ -12418,6 +12551,26 @@ snapshots:
|
|||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
|
||||
'@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.23)(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
|
@ -12481,6 +12634,15 @@ snapshots:
|
|||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.23
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||
|
||||
'@radix-ui/rect@1.1.1': {}
|
||||
|
||||
'@redis/bloom@5.5.5(@redis/client@5.5.5)':
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@ import * as os from "os"
|
|||
import * as fs from "fs/promises"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import * as vscode from "vscode"
|
||||
import * as yaml from "yaml"
|
||||
|
||||
import {
|
||||
type Language,
|
||||
type ProviderSettings,
|
||||
type GlobalState,
|
||||
type ClineMessage,
|
||||
type TelemetrySetting,
|
||||
TelemetryEventName,
|
||||
} from "@roo-code/types"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
|
|
@ -21,7 +20,6 @@ import { ClineProvider } from "./ClineProvider"
|
|||
import { changeLanguage, t } from "../../i18n"
|
||||
import { Package } from "../../shared/package"
|
||||
import { RouterName, toRouterName, ModelRecord } from "../../shared/api"
|
||||
import { supportPrompt } from "../../shared/support-prompt"
|
||||
import { MessageEnhancer } from "./messageEnhancer"
|
||||
|
||||
import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage"
|
||||
|
|
@ -29,7 +27,6 @@ import { checkExistKey } from "../../shared/checkExistApiConfig"
|
|||
import { experimentDefault } from "../../shared/experiments"
|
||||
import { Terminal } from "../../integrations/terminal/Terminal"
|
||||
import { openFile } from "../../integrations/misc/open-file"
|
||||
import { CodeIndexManager } from "../../services/code-index/manager"
|
||||
import { openImage, saveImage } from "../../integrations/misc/image-handler"
|
||||
import { selectImages } from "../../integrations/misc/process-images"
|
||||
import { getTheme } from "../../integrations/theme/getTheme"
|
||||
|
|
@ -42,9 +39,7 @@ import { exportSettings, importSettingsWithFeedback } from "../config/importExpo
|
|||
import { getOpenAiModels } from "../../api/providers/openai"
|
||||
import { getVsCodeLmModels } from "../../api/providers/vscode-lm"
|
||||
import { openMention } from "../mentions"
|
||||
import { TelemetrySetting } from "../../shared/TelemetrySetting"
|
||||
import { getWorkspacePath } from "../../utils/path"
|
||||
import { ensureSettingsDirectoryExists } from "../../utils/globalContext"
|
||||
import { Mode, defaultModeSlug } from "../../shared/modes"
|
||||
import { getModels, flushModels } from "../../api/providers/fetchers/modelCache"
|
||||
import { GetModelsOptions } from "../../shared/api"
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export type TelemetrySetting = "unset" | "enabled" | "disabled"
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { ClineMessage } from "@roo-code/types"
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { safeJsonParse } from "./safeJsonParse"
|
||||
|
||||
export const COMMAND_OUTPUT_STRING = "Output:"
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
import React, { useState, useMemo, useCallback } from "react"
|
||||
import { useState, useMemo, useCallback } from "react"
|
||||
import { Fzf } from "fzf"
|
||||
import { ChevronUp } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
|
||||
import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui"
|
||||
import { IconButton } from "./IconButton"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { Fzf } from "fzf"
|
||||
import { Button } from "@/components/ui"
|
||||
|
||||
import { IconButton } from "./IconButton"
|
||||
|
||||
interface ApiConfigSelectorProps {
|
||||
value: string
|
||||
displayName: string
|
||||
disabled?: boolean
|
||||
title?: string
|
||||
title: string
|
||||
onChange: (value: string) => void
|
||||
triggerClassName?: string
|
||||
listApiConfigMeta: Array<{ id: string; name: string }>
|
||||
|
|
@ -24,7 +27,7 @@ export const ApiConfigSelector = ({
|
|||
value,
|
||||
displayName,
|
||||
disabled = false,
|
||||
title = "",
|
||||
title,
|
||||
onChange,
|
||||
triggerClassName = "",
|
||||
listApiConfigMeta,
|
||||
|
|
@ -36,30 +39,33 @@ export const ApiConfigSelector = ({
|
|||
const [searchValue, setSearchValue] = useState("")
|
||||
const portalContainer = useRooPortal("roo-portal")
|
||||
|
||||
// Create searchable items for fuzzy search
|
||||
const searchableItems = useMemo(() => {
|
||||
return listApiConfigMeta.map((config) => ({
|
||||
original: config,
|
||||
searchStr: config.name,
|
||||
}))
|
||||
}, [listApiConfigMeta])
|
||||
// Create searchable items for fuzzy search.
|
||||
const searchableItems = useMemo(
|
||||
() =>
|
||||
listApiConfigMeta.map((config) => ({
|
||||
original: config,
|
||||
searchStr: config.name,
|
||||
})),
|
||||
[listApiConfigMeta],
|
||||
)
|
||||
|
||||
// Create Fzf instance
|
||||
const fzfInstance = useMemo(() => {
|
||||
return new Fzf(searchableItems, {
|
||||
selector: (item) => item.searchStr,
|
||||
})
|
||||
}, [searchableItems])
|
||||
// Create Fzf instance.
|
||||
const fzfInstance = useMemo(
|
||||
() => new Fzf(searchableItems, { selector: (item) => item.searchStr }),
|
||||
[searchableItems],
|
||||
)
|
||||
|
||||
// Filter configs based on search
|
||||
// Filter configs based on search.
|
||||
const filteredConfigs = useMemo(() => {
|
||||
if (!searchValue) return listApiConfigMeta
|
||||
if (!searchValue) {
|
||||
return listApiConfigMeta
|
||||
}
|
||||
|
||||
const matchingItems = fzfInstance.find(searchValue).map((result) => result.item.original)
|
||||
return matchingItems
|
||||
}, [listApiConfigMeta, searchValue, fzfInstance])
|
||||
|
||||
// Separate pinned and unpinned configs
|
||||
// Separate pinned and unpinned configs.
|
||||
const { pinnedConfigs, unpinnedConfigs } = useMemo(() => {
|
||||
const pinned = filteredConfigs.filter((config) => pinnedApiConfigs?.[config.id])
|
||||
const unpinned = filteredConfigs.filter((config) => !pinnedApiConfigs?.[config.id])
|
||||
|
|
@ -76,10 +82,7 @@ export const ApiConfigSelector = ({
|
|||
)
|
||||
|
||||
const handleEditClick = useCallback(() => {
|
||||
vscode.postMessage({
|
||||
type: "switchTab",
|
||||
tab: "settings",
|
||||
})
|
||||
vscode.postMessage({ type: "switchTab", tab: "settings" })
|
||||
setOpen(false)
|
||||
}, [])
|
||||
|
||||
|
|
@ -112,10 +115,7 @@ export const ApiConfigSelector = ({
|
|||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
togglePinnedApiConfig(config.id)
|
||||
vscode.postMessage({
|
||||
type: "toggleApiConfigPin",
|
||||
text: config.id,
|
||||
})
|
||||
vscode.postMessage({ type: "toggleApiConfigPin", text: config.id })
|
||||
}}
|
||||
className={cn("size-5 flex items-center justify-center", {
|
||||
"opacity-0 group-hover:opacity-100": !isPinned && !isCurrentConfig,
|
||||
|
|
@ -131,32 +131,30 @@ export const ApiConfigSelector = ({
|
|||
[value, handleSelect, t, togglePinnedApiConfig],
|
||||
)
|
||||
|
||||
const triggerContent = (
|
||||
<PopoverTrigger
|
||||
disabled={disabled}
|
||||
data-testid="dropdown-trigger"
|
||||
className={cn(
|
||||
"w-full min-w-0 max-w-full inline-flex items-center gap-1.5 relative whitespace-nowrap px-1.5 py-1 text-xs",
|
||||
"bg-transparent border border-[rgba(255,255,255,0.08)] rounded-md text-vscode-foreground",
|
||||
"transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset",
|
||||
disabled
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: "opacity-90 hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)] cursor-pointer",
|
||||
triggerClassName,
|
||||
)}>
|
||||
<span
|
||||
className={cn(
|
||||
"codicon codicon-chevron-up pointer-events-none opacity-80 flex-shrink-0 text-xs transition-transform duration-200",
|
||||
open && "rotate-180",
|
||||
)}
|
||||
/>
|
||||
<span className="truncate">{displayName}</span>
|
||||
</PopoverTrigger>
|
||||
)
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
{title ? <StandardTooltip content={title}>{triggerContent}</StandardTooltip> : triggerContent}
|
||||
<Popover open={open} onOpenChange={setOpen} data-testid="api-config-selector-root">
|
||||
<StandardTooltip content={title}>
|
||||
<PopoverTrigger
|
||||
disabled={disabled}
|
||||
data-testid="dropdown-trigger"
|
||||
className={cn(
|
||||
"w-full min-w-0 max-w-full inline-flex items-center gap-1.5 relative whitespace-nowrap px-1.5 py-1 text-xs",
|
||||
"bg-transparent border border-[rgba(255,255,255,0.08)] rounded-md text-vscode-foreground",
|
||||
"transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset",
|
||||
disabled
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: "opacity-90 hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)] cursor-pointer",
|
||||
triggerClassName,
|
||||
)}>
|
||||
<ChevronUp
|
||||
className={cn(
|
||||
"pointer-events-none opacity-80 flex-shrink-0 size-3 transition-transform duration-200",
|
||||
open && "rotate-180",
|
||||
)}
|
||||
/>
|
||||
<span className="truncate">{displayName}</span>
|
||||
</PopoverTrigger>
|
||||
</StandardTooltip>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
sideOffset={4}
|
||||
|
|
@ -214,12 +212,13 @@ export const ApiConfigSelector = ({
|
|||
</div>
|
||||
|
||||
{/* Bottom bar with buttons on left and title on right */}
|
||||
<div className="flex flex-row items-center justify-between p-2 border-t border-vscode-dropdown-border">
|
||||
<div className="flex flex-row items-center justify-between px-2 py-2 border-t border-vscode-dropdown-border">
|
||||
<div className="flex flex-row gap-1">
|
||||
<IconButton
|
||||
iconClass="codicon-settings-gear"
|
||||
title={t("chat:edit")}
|
||||
onClick={handleEditClick}
|
||||
tooltip={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { appendImages } from "@src/utils/imageUtils"
|
||||
import { McpExecution } from "./McpExecution"
|
||||
import { useSize } from "react-use"
|
||||
import { useTranslation, Trans } from "react-i18next"
|
||||
import deepEqual from "fast-deep-equal"
|
||||
import { VSCodeBadge, VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { Mode } from "@roo/modes"
|
||||
import type { ClineMessage, FollowUpData, SuggestionItem } from "@roo-code/types"
|
||||
|
||||
import { ClineApiReqInfo, ClineAskUseMcpServer, ClineSayTool } from "@roo/ExtensionMessage"
|
||||
import { COMMAND_OUTPUT_STRING } from "@roo/combineCommandSequences"
|
||||
import { safeJsonParse } from "@roo/safeJsonParse"
|
||||
import { FollowUpData, SuggestionItem } from "@roo-code/types"
|
||||
|
||||
import { useCopyToClipboard } from "@src/utils/clipboard"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
|
|
@ -22,9 +18,6 @@ import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanu
|
|||
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
|
||||
import { Button } from "@src/components/ui"
|
||||
|
||||
import ChatTextArea from "./ChatTextArea"
|
||||
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
|
||||
|
||||
import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
|
||||
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
|
||||
import CodeAccordian from "../common/CodeAccordian"
|
||||
|
|
@ -32,6 +25,7 @@ import CodeBlock from "../common/CodeBlock"
|
|||
import MarkdownBlock from "../common/MarkdownBlock"
|
||||
import { ReasoningBlock } from "./ReasoningBlock"
|
||||
import Thumbnails from "../common/Thumbnails"
|
||||
|
||||
import McpResourceRow from "../mcp/McpResourceRow"
|
||||
|
||||
import { Mention } from "./Mention"
|
||||
|
|
@ -46,6 +40,7 @@ import { CommandExecutionError } from "./CommandExecutionError"
|
|||
import { AutoApprovedRequestLimitWarning } from "./AutoApprovedRequestLimitWarning"
|
||||
import { CondenseContextErrorRow, CondensingContextRow, ContextCondenseRow } from "./ContextCondenseRow"
|
||||
import CodebaseSearchResultsDisplay from "./CodebaseSearchResultsDisplay"
|
||||
import { McpExecution } from "./McpExecution"
|
||||
|
||||
interface ChatRowProps {
|
||||
message: ClineMessage
|
||||
|
|
@ -114,69 +109,20 @@ export const ChatRowContent = ({
|
|||
editable,
|
||||
}: ChatRowContentProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode } = useExtensionState()
|
||||
|
||||
const { mcpServers, alwaysAllowMcp, currentCheckpoint } = useExtensionState()
|
||||
|
||||
const [reasoningCollapsed, setReasoningCollapsed] = useState(true)
|
||||
const [isDiffErrorExpanded, setIsDiffErrorExpanded] = useState(false)
|
||||
const [showCopySuccess, setShowCopySuccess] = useState(false)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [editedContent, setEditedContent] = useState("")
|
||||
const [editMode, setEditMode] = useState<Mode>(mode || "code")
|
||||
const [editImages, setEditImages] = useState<string[]>([])
|
||||
|
||||
const { copyWithFeedback } = useCopyToClipboard()
|
||||
|
||||
// Handle message events for image selection during edit mode
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
const msg = event.data
|
||||
if (msg.type === "selectedImages" && msg.context === "edit" && msg.messageTs === message.ts && isEditing) {
|
||||
setEditImages((prevImages) => appendImages(prevImages, msg.images, MAX_IMAGES_PER_MESSAGE))
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleMessage)
|
||||
return () => window.removeEventListener("message", handleMessage)
|
||||
}, [isEditing, message.ts])
|
||||
|
||||
// Memoized callback to prevent re-renders caused by inline arrow functions
|
||||
// Memoized callback to prevent re-renders caused by inline arrow functions.
|
||||
const handleToggleExpand = useCallback(() => {
|
||||
onToggleExpand(message.ts)
|
||||
}, [onToggleExpand, message.ts])
|
||||
|
||||
// Handle edit button click
|
||||
const handleEditClick = useCallback(() => {
|
||||
setIsEditing(true)
|
||||
setEditedContent(message.text || "")
|
||||
setEditImages(message.images || [])
|
||||
setEditMode(mode || "code")
|
||||
// Edit mode is now handled entirely in the frontend
|
||||
// No need to notify the backend
|
||||
}, [message.text, message.images, mode])
|
||||
|
||||
// Handle cancel edit
|
||||
const handleCancelEdit = useCallback(() => {
|
||||
setIsEditing(false)
|
||||
setEditedContent(message.text || "")
|
||||
setEditImages(message.images || [])
|
||||
setEditMode(mode || "code")
|
||||
}, [message.text, message.images, mode])
|
||||
|
||||
// Handle save edit
|
||||
const handleSaveEdit = useCallback(() => {
|
||||
setIsEditing(false)
|
||||
// Send edited message to backend
|
||||
vscode.postMessage({
|
||||
type: "submitEditedMessage",
|
||||
value: message.ts,
|
||||
editedMessageContent: editedContent,
|
||||
images: editImages,
|
||||
})
|
||||
}, [message.ts, editedContent, editImages])
|
||||
|
||||
// Handle image selection for editing
|
||||
const handleSelectImages = useCallback(() => {
|
||||
vscode.postMessage({ type: "selectImages", context: "edit", messageTs: message.ts })
|
||||
}, [message.ts])
|
||||
|
||||
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
|
||||
if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
|
||||
const info = safeJsonParse<ClineApiReqInfo>(message.text)
|
||||
|
|
@ -1061,58 +1007,26 @@ export const ChatRowContent = ({
|
|||
case "user_feedback":
|
||||
return (
|
||||
<div className="bg-vscode-editor-background border rounded-xs p-1 overflow-hidden whitespace-pre-wrap">
|
||||
{isEditing ? (
|
||||
<div className="flex flex-col gap-2 p-2">
|
||||
<ChatTextArea
|
||||
inputValue={editedContent}
|
||||
setInputValue={setEditedContent}
|
||||
sendingDisabled={false}
|
||||
selectApiConfigDisabled={true}
|
||||
placeholderText={t("chat:editMessage.placeholder")}
|
||||
selectedImages={editImages}
|
||||
setSelectedImages={setEditImages}
|
||||
onSend={handleSaveEdit}
|
||||
onSelectImages={handleSelectImages}
|
||||
shouldDisableImages={false}
|
||||
mode={editMode}
|
||||
setMode={setEditMode}
|
||||
modeShortcutText=""
|
||||
isEditMode={true}
|
||||
onCancel={handleCancelEdit}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex-grow px-2 py-1 wrap-anywhere">
|
||||
<Mention text={message.text} withShadow />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-between">
|
||||
<div className="flex-grow px-2 py-1 wrap-anywhere">
|
||||
<Mention text={message.text} withShadow />
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0 hidden"
|
||||
disabled={isStreaming}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleEditClick()
|
||||
}}>
|
||||
<span className="codicon codicon-edit" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0"
|
||||
disabled={isStreaming}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
vscode.postMessage({ type: "deleteMessage", value: message.ts })
|
||||
}}>
|
||||
<span className="codicon codicon-trash" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0"
|
||||
disabled={isStreaming}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
vscode.postMessage({ type: "deleteMessage", value: message.ts })
|
||||
}}>
|
||||
<span className="codicon codicon-trash" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!isEditing && message.images && message.images.length > 0 && (
|
||||
</div>
|
||||
|
||||
{message.images && message.images.length > 0 && (
|
||||
<Thumbnails images={message.images} style={{ marginTop: "8px" }} />
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"
|
||||
import { useEvent } from "react-use"
|
||||
import DynamicTextArea from "react-textarea-autosize"
|
||||
import { VolumeX, Image, WandSparkles, SendHorizontal } from "lucide-react"
|
||||
|
||||
import { mentionRegex, mentionRegexGlobal, commandRegexGlobal, unescapeSpaces } from "@roo/context-mentions"
|
||||
import { WebviewMessage } from "@roo/WebviewMessage"
|
||||
import { Mode, getAllModes } from "@roo/modes"
|
||||
import { ExtensionMessage } from "@roo/ExtensionMessage"
|
||||
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import {
|
||||
ContextMenuOptionType,
|
||||
getContextMenuOptions,
|
||||
|
|
@ -18,20 +19,18 @@ import {
|
|||
shouldShowContextMenu,
|
||||
SearchResult,
|
||||
} from "@src/utils/context-mentions"
|
||||
import { convertToMentionPath } from "@/utils/path-mentions"
|
||||
import { StandardTooltip } from "@/components/ui"
|
||||
import { cn } from "@src/lib/utils"
|
||||
import { convertToMentionPath } from "@src/utils/path-mentions"
|
||||
import { StandardTooltip } from "@src/components/ui"
|
||||
|
||||
import Thumbnails from "../common/Thumbnails"
|
||||
import ModeSelector from "./ModeSelector"
|
||||
import { ModeSelector } from "./ModeSelector"
|
||||
import { ApiConfigSelector } from "./ApiConfigSelector"
|
||||
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
|
||||
import ContextMenu from "./ContextMenu"
|
||||
import { VolumeX, Image, WandSparkles, SendHorizontal } from "lucide-react"
|
||||
import { IndexingStatusBadge } from "./IndexingStatusBadge"
|
||||
import { SlashCommandsPopover } from "./SlashCommandsPopover"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { usePromptHistory } from "./hooks/usePromptHistory"
|
||||
import { EditModeControls } from "./EditModeControls"
|
||||
|
||||
interface ChatTextAreaProps {
|
||||
inputValue: string
|
||||
|
|
@ -48,17 +47,13 @@ interface ChatTextAreaProps {
|
|||
mode: Mode
|
||||
setMode: (value: Mode) => void
|
||||
modeShortcutText: string
|
||||
// Edit mode props
|
||||
isEditMode?: boolean
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
(
|
||||
{
|
||||
inputValue,
|
||||
setInputValue,
|
||||
sendingDisabled,
|
||||
selectApiConfigDisabled,
|
||||
placeholderText,
|
||||
selectedImages,
|
||||
|
|
@ -70,8 +65,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
mode,
|
||||
setMode,
|
||||
modeShortcutText,
|
||||
isEditMode = false,
|
||||
onCancel,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
|
|
@ -91,12 +84,12 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
commands,
|
||||
} = useExtensionState()
|
||||
|
||||
// Find the ID and display text for the currently selected API configuration
|
||||
// Find the ID and display text for the currently selected API configuration.
|
||||
const { currentConfigId, displayName } = useMemo(() => {
|
||||
const currentConfig = listApiConfigMeta?.find((config) => config.name === currentApiConfigName)
|
||||
return {
|
||||
currentConfigId: currentConfig?.id || "",
|
||||
displayName: currentApiConfigName || "", // Use the name directly for display
|
||||
displayName: currentApiConfigName || "", // Use the name directly for display.
|
||||
}
|
||||
}, [listApiConfigMeta, currentApiConfigName])
|
||||
|
||||
|
|
@ -888,7 +881,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
|
||||
const placeholderBottomText = `\n(${t("chat:addContext")}${shouldDisableImages ? `, ${t("chat:dragFiles")}` : `, ${t("chat:dragFilesImages")}`})`
|
||||
|
||||
// Common mode selector handler
|
||||
const handleModeChange = useCallback(
|
||||
(value: Mode) => {
|
||||
setMode(value)
|
||||
|
|
@ -897,261 +889,10 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
[setMode],
|
||||
)
|
||||
|
||||
// Helper function to render mode selector
|
||||
const renderModeSelector = () => (
|
||||
<ModeSelector
|
||||
value={mode}
|
||||
title={t("chat:selectMode")}
|
||||
onChange={handleModeChange}
|
||||
triggerClassName="w-full"
|
||||
modeShortcutText={modeShortcutText}
|
||||
customModes={customModes}
|
||||
customModePrompts={customModePrompts}
|
||||
/>
|
||||
)
|
||||
|
||||
// Helper function to handle API config change
|
||||
const handleApiConfigChange = useCallback((value: string) => {
|
||||
vscode.postMessage({ type: "loadApiConfigurationById", text: value })
|
||||
}, [])
|
||||
|
||||
// Helper function to render non-edit mode controls
|
||||
const renderNonEditModeControls = () => (
|
||||
<div className={cn("flex", "justify-between", "items-center", "mt-auto")}>
|
||||
<div className={cn("flex", "items-center", "gap-1", "min-w-0")}>
|
||||
<div className="shrink-0">{renderModeSelector()}</div>
|
||||
|
||||
<div className={cn("flex-1", "min-w-0", "overflow-hidden")}>
|
||||
<ApiConfigSelector
|
||||
value={currentConfigId}
|
||||
displayName={displayName}
|
||||
disabled={selectApiConfigDisabled}
|
||||
title={t("chat:selectApiConfig")}
|
||||
onChange={handleApiConfigChange}
|
||||
triggerClassName="w-full text-ellipsis overflow-hidden"
|
||||
listApiConfigMeta={listApiConfigMeta || []}
|
||||
pinnedApiConfigs={pinnedApiConfigs}
|
||||
togglePinnedApiConfig={togglePinnedApiConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn("flex", "items-center", "gap-0.5", "shrink-0")}>
|
||||
{isTtsPlaying && (
|
||||
<StandardTooltip content={t("chat:stopTts")}>
|
||||
<button
|
||||
aria-label={t("chat:stopTts")}
|
||||
onClick={() => vscode.postMessage({ type: "stopTts" })}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"text-vscode-foreground opacity-85",
|
||||
"transition-all duration-150",
|
||||
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
"cursor-pointer",
|
||||
)}>
|
||||
<VolumeX className="w-4 h-4" />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
)}
|
||||
<SlashCommandsPopover />
|
||||
<IndexingStatusBadge />
|
||||
<StandardTooltip content={t("chat:addImages")}>
|
||||
<button
|
||||
aria-label={t("chat:addImages")}
|
||||
disabled={shouldDisableImages}
|
||||
onClick={!shouldDisableImages ? onSelectImages : undefined}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"text-vscode-foreground opacity-85",
|
||||
"transition-all duration-150",
|
||||
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
!shouldDisableImages && "cursor-pointer",
|
||||
shouldDisableImages &&
|
||||
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
|
||||
"mr-1",
|
||||
)}>
|
||||
<Image className="w-4 h-4" />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
// Helper function to render the text area section
|
||||
const renderTextAreaSection = () => (
|
||||
<div
|
||||
className={cn(
|
||||
"relative",
|
||||
"flex-1",
|
||||
"flex",
|
||||
"flex-col-reverse",
|
||||
"min-h-0",
|
||||
"overflow-hidden",
|
||||
"rounded",
|
||||
)}>
|
||||
<div
|
||||
ref={highlightLayerRef}
|
||||
data-testid="highlight-layer"
|
||||
className={cn(
|
||||
"absolute",
|
||||
"inset-0",
|
||||
"pointer-events-none",
|
||||
"whitespace-pre-wrap",
|
||||
"break-words",
|
||||
"text-transparent",
|
||||
"overflow-hidden",
|
||||
"font-vscode-font-family",
|
||||
"text-vscode-editor-font-size",
|
||||
"leading-vscode-editor-line-height",
|
||||
isFocused
|
||||
? "border border-vscode-focusBorder outline outline-vscode-focusBorder"
|
||||
: isDraggingOver
|
||||
? "border-2 border-dashed border-vscode-focusBorder"
|
||||
: "border border-transparent",
|
||||
isEditMode ? "pt-1.5 pb-10 px-2" : "py-1.5 px-2",
|
||||
"px-[8px]",
|
||||
"pr-9",
|
||||
"z-10",
|
||||
"forced-color-adjust-none",
|
||||
)}
|
||||
style={{
|
||||
color: "transparent",
|
||||
}}
|
||||
/>
|
||||
<DynamicTextArea
|
||||
ref={(el) => {
|
||||
if (typeof ref === "function") {
|
||||
ref(el)
|
||||
} else if (ref) {
|
||||
ref.current = el
|
||||
}
|
||||
textAreaRef.current = el
|
||||
}}
|
||||
value={inputValue}
|
||||
onChange={(e) => {
|
||||
handleInputChange(e)
|
||||
updateHighlights()
|
||||
}}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyUp={handleKeyUp}
|
||||
onBlur={handleBlur}
|
||||
onPaste={handlePaste}
|
||||
onSelect={updateCursorPosition}
|
||||
onMouseUp={updateCursorPosition}
|
||||
onHeightChange={(height) => {
|
||||
if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) {
|
||||
setTextAreaBaseHeight(height)
|
||||
}
|
||||
|
||||
onHeightChange?.(height)
|
||||
}}
|
||||
placeholder={placeholderText}
|
||||
minRows={3}
|
||||
maxRows={15}
|
||||
autoFocus={true}
|
||||
className={cn(
|
||||
"w-full",
|
||||
"text-vscode-input-foreground",
|
||||
"font-vscode-font-family",
|
||||
"text-vscode-editor-font-size",
|
||||
"leading-vscode-editor-line-height",
|
||||
"cursor-text",
|
||||
isEditMode ? "pt-1.5 pb-10 px-2" : "py-1.5 px-2",
|
||||
isFocused
|
||||
? "border border-vscode-focusBorder outline outline-vscode-focusBorder"
|
||||
: isDraggingOver
|
||||
? "border-2 border-dashed border-vscode-focusBorder"
|
||||
: "border border-transparent",
|
||||
isDraggingOver
|
||||
? "bg-[color-mix(in_srgb,var(--vscode-input-background)_95%,var(--vscode-focusBorder))]"
|
||||
: "bg-vscode-input-background",
|
||||
"transition-background-color duration-150 ease-in-out",
|
||||
"will-change-background-color",
|
||||
"min-h-[90px]",
|
||||
"box-border",
|
||||
"rounded",
|
||||
"resize-none",
|
||||
"overflow-x-hidden",
|
||||
"overflow-y-auto",
|
||||
"pr-9",
|
||||
"flex-none flex-grow",
|
||||
"z-[2]",
|
||||
"scrollbar-none",
|
||||
"scrollbar-hide",
|
||||
)}
|
||||
onScroll={() => updateHighlights()}
|
||||
/>
|
||||
|
||||
<div className="absolute top-1 right-1 z-30">
|
||||
<StandardTooltip content={t("chat:enhancePrompt")}>
|
||||
<button
|
||||
aria-label={t("chat:enhancePrompt")}
|
||||
disabled={false}
|
||||
onClick={handleEnhancePrompt}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
|
||||
"transition-all duration-150",
|
||||
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
"cursor-pointer",
|
||||
)}>
|
||||
<WandSparkles className={cn("w-4 h-4", isEnhancingPrompt && "animate-spin")} />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
|
||||
{!isEditMode && (
|
||||
<div className="absolute bottom-1 right-1 z-30">
|
||||
<StandardTooltip content={t("chat:sendMessage")}>
|
||||
<button
|
||||
aria-label={t("chat:sendMessage")}
|
||||
disabled={false}
|
||||
onClick={onSend}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
|
||||
"transition-all duration-150",
|
||||
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
"cursor-pointer",
|
||||
)}>
|
||||
<SendHorizontal className="w-4 h-4" />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!inputValue && !isEditMode && (
|
||||
<div
|
||||
className="absolute left-2 z-30 pr-9 flex items-center h-8 font-vscode-font-family text-vscode-editor-font-size leading-vscode-editor-line-height"
|
||||
style={{
|
||||
bottom: "0.25rem",
|
||||
color: "color-mix(in oklab, var(--vscode-input-foreground) 50%, transparent)",
|
||||
userSelect: "none",
|
||||
pointerEvents: "none",
|
||||
}}>
|
||||
{placeholderBottomText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -1160,12 +901,12 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
"flex-col",
|
||||
"gap-1",
|
||||
"bg-editor-background",
|
||||
isEditMode ? "px-0" : "px-1.5",
|
||||
"px-1.5",
|
||||
"pb-1",
|
||||
"outline-none",
|
||||
"border",
|
||||
"border-none",
|
||||
isEditMode ? "w-full" : "w-[calc(100%-16px)]",
|
||||
"w-[calc(100%-16px)]",
|
||||
"ml-auto",
|
||||
"mr-auto",
|
||||
"box-border",
|
||||
|
|
@ -1228,23 +969,168 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
</div>
|
||||
)}
|
||||
|
||||
{renderTextAreaSection()}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"relative",
|
||||
"flex-1",
|
||||
"flex",
|
||||
"flex-col-reverse",
|
||||
"min-h-0",
|
||||
"overflow-hidden",
|
||||
"rounded",
|
||||
)}>
|
||||
<div
|
||||
ref={highlightLayerRef}
|
||||
data-testid="highlight-layer"
|
||||
className={cn(
|
||||
"absolute",
|
||||
"inset-0",
|
||||
"pointer-events-none",
|
||||
"whitespace-pre-wrap",
|
||||
"break-words",
|
||||
"text-transparent",
|
||||
"overflow-hidden",
|
||||
"font-vscode-font-family",
|
||||
"text-vscode-editor-font-size",
|
||||
"leading-vscode-editor-line-height",
|
||||
isFocused
|
||||
? "border border-vscode-focusBorder outline outline-vscode-focusBorder"
|
||||
: isDraggingOver
|
||||
? "border-2 border-dashed border-vscode-focusBorder"
|
||||
: "border border-transparent",
|
||||
"px-[8px]",
|
||||
"py-1.5",
|
||||
"pr-9",
|
||||
"z-10",
|
||||
"forced-color-adjust-none",
|
||||
)}
|
||||
style={{
|
||||
color: "transparent",
|
||||
}}
|
||||
/>
|
||||
<DynamicTextArea
|
||||
ref={(el) => {
|
||||
if (typeof ref === "function") {
|
||||
ref(el)
|
||||
} else if (ref) {
|
||||
ref.current = el
|
||||
}
|
||||
textAreaRef.current = el
|
||||
}}
|
||||
value={inputValue}
|
||||
onChange={(e) => {
|
||||
handleInputChange(e)
|
||||
updateHighlights()
|
||||
}}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyUp={handleKeyUp}
|
||||
onBlur={handleBlur}
|
||||
onPaste={handlePaste}
|
||||
onSelect={updateCursorPosition}
|
||||
onMouseUp={updateCursorPosition}
|
||||
onHeightChange={(height) => {
|
||||
if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) {
|
||||
setTextAreaBaseHeight(height)
|
||||
}
|
||||
|
||||
{isEditMode && (
|
||||
<EditModeControls
|
||||
mode={mode}
|
||||
onModeChange={handleModeChange}
|
||||
modeShortcutText={modeShortcutText}
|
||||
customModes={customModes}
|
||||
customModePrompts={customModePrompts}
|
||||
onCancel={onCancel}
|
||||
onSend={onSend}
|
||||
onSelectImages={onSelectImages}
|
||||
sendingDisabled={sendingDisabled}
|
||||
shouldDisableImages={shouldDisableImages}
|
||||
/>
|
||||
)}
|
||||
onHeightChange?.(height)
|
||||
}}
|
||||
placeholder={placeholderText}
|
||||
minRows={3}
|
||||
maxRows={15}
|
||||
autoFocus={true}
|
||||
className={cn(
|
||||
"w-full",
|
||||
"text-vscode-input-foreground",
|
||||
"font-vscode-font-family",
|
||||
"text-vscode-editor-font-size",
|
||||
"leading-vscode-editor-line-height",
|
||||
"cursor-text",
|
||||
"py-1.5 px-2",
|
||||
isFocused
|
||||
? "border border-vscode-focusBorder outline outline-vscode-focusBorder"
|
||||
: isDraggingOver
|
||||
? "border-2 border-dashed border-vscode-focusBorder"
|
||||
: "border border-transparent",
|
||||
isDraggingOver
|
||||
? "bg-[color-mix(in_srgb,var(--vscode-input-background)_95%,var(--vscode-focusBorder))]"
|
||||
: "bg-vscode-input-background",
|
||||
"transition-background-color duration-150 ease-in-out",
|
||||
"will-change-background-color",
|
||||
"min-h-[90px]",
|
||||
"box-border",
|
||||
"rounded",
|
||||
"resize-none",
|
||||
"overflow-x-hidden",
|
||||
"overflow-y-auto",
|
||||
"pr-9",
|
||||
"flex-none flex-grow",
|
||||
"z-[2]",
|
||||
"scrollbar-none",
|
||||
"scrollbar-hide",
|
||||
)}
|
||||
onScroll={() => updateHighlights()}
|
||||
/>
|
||||
|
||||
<div className="absolute top-1 right-1 z-30">
|
||||
<StandardTooltip content={t("chat:enhancePrompt")}>
|
||||
<button
|
||||
aria-label={t("chat:enhancePrompt")}
|
||||
disabled={false}
|
||||
onClick={handleEnhancePrompt}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
|
||||
"transition-all duration-150",
|
||||
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
"cursor-pointer",
|
||||
)}>
|
||||
<WandSparkles className={cn("w-4 h-4", isEnhancingPrompt && "animate-spin")} />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-1 right-1 z-30">
|
||||
<StandardTooltip content={t("chat:sendMessage")}>
|
||||
<button
|
||||
aria-label={t("chat:sendMessage")}
|
||||
disabled={false}
|
||||
onClick={onSend}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
|
||||
"transition-all duration-150",
|
||||
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
"cursor-pointer",
|
||||
)}>
|
||||
<SendHorizontal className="w-4 h-4" />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
|
||||
{!inputValue && (
|
||||
<div
|
||||
className="absolute left-2 z-30 pr-9 flex items-center h-8 font-vscode-font-family text-vscode-editor-font-size leading-vscode-editor-line-height"
|
||||
style={{
|
||||
bottom: "0.25rem",
|
||||
color: "color-mix(in oklab, var(--vscode-input-foreground) 50%, transparent)",
|
||||
userSelect: "none",
|
||||
pointerEvents: "none",
|
||||
}}>
|
||||
{placeholderBottomText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedImages.length > 0 && (
|
||||
|
|
@ -1259,10 +1145,81 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
/>
|
||||
)}
|
||||
|
||||
{!isEditMode && renderNonEditModeControls()}
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="max-w-32">
|
||||
<ModeSelector
|
||||
value={mode}
|
||||
title={t("chat:selectMode")}
|
||||
onChange={handleModeChange}
|
||||
triggerClassName="w-full"
|
||||
modeShortcutText={modeShortcutText}
|
||||
customModes={customModes}
|
||||
customModePrompts={customModePrompts}
|
||||
/>
|
||||
</div>
|
||||
<div className="max-w-32">
|
||||
<ApiConfigSelector
|
||||
value={currentConfigId}
|
||||
displayName={displayName}
|
||||
disabled={selectApiConfigDisabled}
|
||||
title={t("chat:selectApiConfig")}
|
||||
onChange={handleApiConfigChange}
|
||||
triggerClassName="w-full text-ellipsis overflow-hidden"
|
||||
listApiConfigMeta={listApiConfigMeta || []}
|
||||
pinnedApiConfigs={pinnedApiConfigs}
|
||||
togglePinnedApiConfig={togglePinnedApiConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5">
|
||||
{isTtsPlaying && (
|
||||
<StandardTooltip content={t("chat:stopTts")}>
|
||||
<button
|
||||
aria-label={t("chat:stopTts")}
|
||||
onClick={() => vscode.postMessage({ type: "stopTts" })}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"text-vscode-foreground opacity-85",
|
||||
"transition-all duration-150",
|
||||
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
"cursor-pointer",
|
||||
)}>
|
||||
<VolumeX className="w-4 h-4" />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
)}
|
||||
<SlashCommandsPopover />
|
||||
<IndexingStatusBadge />
|
||||
<StandardTooltip content={t("chat:addImages")}>
|
||||
<button
|
||||
aria-label={t("chat:addImages")}
|
||||
disabled={shouldDisableImages}
|
||||
onClick={!shouldDisableImages ? onSelectImages : undefined}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"text-vscode-foreground opacity-85",
|
||||
"transition-all duration-150",
|
||||
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
!shouldDisableImages && "cursor-pointer",
|
||||
shouldDisableImages &&
|
||||
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
|
||||
"mr-1",
|
||||
)}>
|
||||
<Image className="w-4 h-4" />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export default ChatTextArea
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import removeMd from "remove-markdown"
|
|||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import useSound from "use-sound"
|
||||
import { LRUCache } from "lru-cache"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { useDebounceEffect } from "@src/utils/useDebounceEffect"
|
||||
import { appendImages } from "@src/utils/imageUtils"
|
||||
|
|
@ -30,7 +31,6 @@ import {
|
|||
findLongestPrefixMatch,
|
||||
parseCommand,
|
||||
} from "@src/utils/command-validation"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel"
|
||||
|
|
@ -48,7 +48,7 @@ import HistoryPreview from "../history/HistoryPreview"
|
|||
import Announcement from "./Announcement"
|
||||
import BrowserSessionRow from "./BrowserSessionRow"
|
||||
import ChatRow from "./ChatRow"
|
||||
import ChatTextArea from "./ChatTextArea"
|
||||
import { ChatTextArea } from "./ChatTextArea"
|
||||
import TaskHeader from "./TaskHeader"
|
||||
import AutoApproveMenu from "./AutoApproveMenu"
|
||||
import SystemPromptWarning from "./SystemPromptWarning"
|
||||
|
|
@ -68,7 +68,7 @@ export interface ChatViewRef {
|
|||
acceptInput: () => void
|
||||
}
|
||||
|
||||
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
|
||||
export const MAX_IMAGES_PER_MESSAGE = 20 // This is the Anthropic limit.
|
||||
|
||||
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0
|
||||
|
||||
|
|
@ -77,13 +77,16 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
ref,
|
||||
) => {
|
||||
const isMountedRef = useRef(true)
|
||||
|
||||
const [audioBaseUri] = useState(() => {
|
||||
const w = window as any
|
||||
return w.AUDIO_BASE_URI || ""
|
||||
})
|
||||
|
||||
const { t } = useAppTranslation()
|
||||
const { t: tSettings } = useTranslation("settings")
|
||||
const modeShortcutText = `${isMac ? "⌘" : "Ctrl"} + . ${t("chat:forNextMode")}, ${isMac ? "⌘" : "Ctrl"} + Shift + . ${t("chat:forPreviousMode")}`
|
||||
|
||||
const {
|
||||
clineMessages: messages,
|
||||
currentTaskItem,
|
||||
|
|
@ -121,6 +124,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
} = useExtensionState()
|
||||
|
||||
const messagesRef = useRef(messages)
|
||||
|
||||
useEffect(() => {
|
||||
messagesRef.current = messages
|
||||
}, [messages])
|
||||
|
|
@ -238,9 +242,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
soundEnabled,
|
||||
}
|
||||
|
||||
const getAudioUrl = (path: string) => {
|
||||
return `${audioBaseUri}/${path}`
|
||||
}
|
||||
const getAudioUrl = (path: string) => `${audioBaseUri}/${path}`
|
||||
|
||||
// Use the getAudioUrl helper function
|
||||
const [playNotification] = useSound(getAudioUrl("notification.wav"), soundConfig)
|
||||
|
|
@ -1456,17 +1458,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
|
||||
const placeholderText = task ? t("chat:typeMessage") : t("chat:typeTask")
|
||||
|
||||
// Function to switch to a specific mode
|
||||
const switchToMode = useCallback(
|
||||
(modeSlug: string): void => {
|
||||
// Update local state and notify extension to sync mode change
|
||||
// Update local state and notify extension to sync mode change.
|
||||
setMode(modeSlug)
|
||||
|
||||
// Send the mode switch message
|
||||
vscode.postMessage({
|
||||
type: "mode",
|
||||
text: modeSlug,
|
||||
})
|
||||
// Send the mode switch message.
|
||||
vscode.postMessage({ type: "mode", text: modeSlug })
|
||||
},
|
||||
[setMode],
|
||||
)
|
||||
|
|
@ -1760,9 +1758,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
[switchToNextMode, switchToPreviousMode],
|
||||
)
|
||||
|
||||
// Add event listener
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleKeyDown)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown)
|
||||
}
|
||||
|
|
@ -1789,6 +1787,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
|
||||
const areButtonsVisible = showScrollToBottom || primaryButtonText || secondaryButtonText || isStreaming
|
||||
|
||||
console.log("ChatView - render()")
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="chat-view"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@ import {
|
|||
VSCodeCheckbox,
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||
import { AlertTriangle } from "lucide-react"
|
||||
|
||||
import { CODEBASE_INDEX_DEFAULTS } from "@roo-code/types"
|
||||
|
||||
import type { EmbedderProvider } from "@roo/embeddingModels"
|
||||
import type { IndexingStatus } from "@roo/ExtensionMessage"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
|
|
@ -32,16 +39,11 @@ import {
|
|||
AlertDialogTrigger,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Slider,
|
||||
StandardTooltip,
|
||||
} from "@src/components/ui"
|
||||
import { AlertTriangle } from "lucide-react"
|
||||
import { useRooPortal } from "@src/components/ui/hooks/useRooPortal"
|
||||
import { useEscapeKey } from "@src/hooks/useEscapeKey"
|
||||
import type { EmbedderProvider } from "@roo/embeddingModels"
|
||||
import type { IndexingStatus } from "@roo/ExtensionMessage"
|
||||
import { CODEBASE_INDEX_DEFAULTS } from "@roo-code/types"
|
||||
|
||||
// Default URLs for providers
|
||||
const DEFAULT_QDRANT_URL = "http://localhost:6333"
|
||||
|
|
@ -525,7 +527,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
setOpen(newOpen)
|
||||
}
|
||||
}}>
|
||||
<PopoverTrigger asChild>{children}</PopoverTrigger>
|
||||
{children}
|
||||
<PopoverContent
|
||||
className="w-[calc(100vw-32px)] max-w-[450px] max-h-[80vh] overflow-y-auto p-0"
|
||||
align="end"
|
||||
|
|
|
|||
|
|
@ -1,115 +0,0 @@
|
|||
import React from "react"
|
||||
import { Mode } from "@roo/modes"
|
||||
import { Button, StandardTooltip } from "@/components/ui"
|
||||
import { Image, SendHorizontal } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import ModeSelector from "./ModeSelector"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
|
||||
interface EditModeControlsProps {
|
||||
mode: Mode
|
||||
onModeChange: (value: Mode) => void
|
||||
modeShortcutText: string
|
||||
customModes: any
|
||||
customModePrompts: any
|
||||
onCancel?: () => void
|
||||
onSend: () => void
|
||||
onSelectImages: () => void
|
||||
sendingDisabled: boolean
|
||||
shouldDisableImages: boolean
|
||||
}
|
||||
|
||||
export const EditModeControls: React.FC<EditModeControlsProps> = ({
|
||||
mode,
|
||||
onModeChange,
|
||||
modeShortcutText,
|
||||
customModes,
|
||||
customModePrompts,
|
||||
onCancel,
|
||||
onSend,
|
||||
onSelectImages,
|
||||
sendingDisabled,
|
||||
shouldDisableImages,
|
||||
}) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex",
|
||||
"items-center",
|
||||
"justify-between",
|
||||
"absolute",
|
||||
"bottom-2",
|
||||
"left-2",
|
||||
"right-2",
|
||||
"z-30",
|
||||
)}>
|
||||
<div className={cn("flex", "items-center", "gap-1", "flex-1", "min-w-0")}>
|
||||
<div className="shrink-0">
|
||||
<ModeSelector
|
||||
value={mode}
|
||||
title={t("chat:selectMode")}
|
||||
onChange={onModeChange}
|
||||
triggerClassName="w-full"
|
||||
modeShortcutText={modeShortcutText}
|
||||
customModes={customModes}
|
||||
customModePrompts={customModePrompts}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn("flex", "items-center", "gap-0.5", "shrink-0", "ml-2")}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={onCancel}
|
||||
disabled={sendingDisabled}
|
||||
className="text-xs bg-vscode-toolbar-hoverBackground hover:bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground">
|
||||
Cancel
|
||||
</Button>
|
||||
<StandardTooltip content={t("chat:addImages")}>
|
||||
<button
|
||||
aria-label={t("chat:addImages")}
|
||||
disabled={shouldDisableImages}
|
||||
onClick={!shouldDisableImages ? onSelectImages : undefined}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
|
||||
"transition-all duration-150",
|
||||
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
!shouldDisableImages && "cursor-pointer",
|
||||
shouldDisableImages &&
|
||||
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
|
||||
)}>
|
||||
<Image className="w-4 h-4" />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
<StandardTooltip content={t("chat:save.tooltip")}>
|
||||
<button
|
||||
aria-label={t("chat:save.tooltip")}
|
||||
disabled={sendingDisabled}
|
||||
onClick={!sendingDisabled ? onSend : undefined}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
|
||||
"transition-all duration-150",
|
||||
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
!sendingDisabled && "cursor-pointer",
|
||||
sendingDisabled &&
|
||||
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
|
||||
)}>
|
||||
<SendHorizontal className="w-4 h-4" />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import { cn } from "@/lib/utils"
|
||||
import { StandardTooltip } from "@/components/ui"
|
||||
import { cn } from "@src/lib/utils"
|
||||
import { Button, StandardTooltip } from "@src/components/ui"
|
||||
|
||||
interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
iconClass: string
|
||||
title: string
|
||||
disabled?: boolean
|
||||
tooltip?: boolean
|
||||
isLoading?: boolean
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
|
@ -14,39 +15,34 @@ export const IconButton: React.FC<IconButtonProps> = ({
|
|||
title,
|
||||
className,
|
||||
disabled,
|
||||
tooltip = true,
|
||||
isLoading,
|
||||
onClick,
|
||||
style,
|
||||
...props
|
||||
}) => {
|
||||
const buttonClasses = cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"text-vscode-foreground opacity-85",
|
||||
"transition-all duration-150",
|
||||
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
!disabled && "cursor-pointer",
|
||||
disabled &&
|
||||
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
|
||||
className,
|
||||
)
|
||||
|
||||
const iconClasses = cn("codicon", iconClass, isLoading && "codicon-modifier-spin")
|
||||
|
||||
const button = (
|
||||
<button
|
||||
}) => (
|
||||
<StandardTooltip content={tooltip ? title : undefined}>
|
||||
<Button
|
||||
aria-label={title}
|
||||
className={buttonClasses}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"text-vscode-foreground opacity-85",
|
||||
"transition-all duration-150",
|
||||
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
!disabled && "cursor-pointer",
|
||||
disabled &&
|
||||
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
|
||||
className,
|
||||
)}
|
||||
disabled={disabled}
|
||||
onClick={!disabled ? onClick : undefined}
|
||||
style={{ fontSize: 16.5, ...style }}
|
||||
{...props}>
|
||||
<span className={iconClasses} />
|
||||
</button>
|
||||
)
|
||||
|
||||
return <StandardTooltip content={title}>{button}</StandardTooltip>
|
||||
}
|
||||
<span className={cn("codicon", iconClass, isLoading && "codicon-modifier-spin")} />
|
||||
</Button>
|
||||
</StandardTooltip>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import React, { useState, useEffect, useMemo } from "react"
|
||||
import { Database } from "lucide-react"
|
||||
|
||||
import { cn } from "@src/lib/utils"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { useTooltip } from "@/hooks/useTooltip"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { CodeIndexPopover } from "./CodeIndexPopover"
|
||||
|
||||
import type { IndexingStatus, IndexingStatusUpdateMessage } from "@roo/ExtensionMessage"
|
||||
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { PopoverTrigger, StandardTooltip, Button } from "@src/components/ui"
|
||||
|
||||
import { CodeIndexPopover } from "./CodeIndexPopover"
|
||||
|
||||
interface IndexingStatusBadgeProps {
|
||||
className?: string
|
||||
}
|
||||
|
|
@ -15,8 +19,6 @@ interface IndexingStatusBadgeProps {
|
|||
export const IndexingStatusBadge: React.FC<IndexingStatusBadgeProps> = ({ className }) => {
|
||||
const { t } = useAppTranslation()
|
||||
const { cwd } = useExtensionState()
|
||||
const { showTooltip, handleMouseEnter, handleMouseLeave, cleanup } = useTooltip({ delay: 300 })
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
||||
const [indexingStatus, setIndexingStatus] = useState<IndexingStatus>({
|
||||
systemStatus: "Standby",
|
||||
|
|
@ -26,10 +28,10 @@ export const IndexingStatusBadge: React.FC<IndexingStatusBadgeProps> = ({ classN
|
|||
})
|
||||
|
||||
useEffect(() => {
|
||||
// Request initial indexing status
|
||||
// Request initial indexing status.
|
||||
vscode.postMessage({ type: "requestIndexingStatus" })
|
||||
|
||||
// Set up message listener for status updates
|
||||
// Set up message listener for status updates.
|
||||
const handleMessage = (event: MessageEvent<IndexingStatusUpdateMessage>) => {
|
||||
if (event.data.type === "indexingStatusUpdate") {
|
||||
const status = event.data.values
|
||||
|
|
@ -43,11 +45,9 @@ export const IndexingStatusBadge: React.FC<IndexingStatusBadgeProps> = ({ classN
|
|||
|
||||
return () => {
|
||||
window.removeEventListener("message", handleMessage)
|
||||
cleanup()
|
||||
}
|
||||
}, [cleanup, cwd])
|
||||
}, [cwd])
|
||||
|
||||
// Calculate progress percentage with memoization
|
||||
const progressPercentage = useMemo(
|
||||
() =>
|
||||
indexingStatus.totalItems > 0
|
||||
|
|
@ -56,8 +56,7 @@ export const IndexingStatusBadge: React.FC<IndexingStatusBadgeProps> = ({ classN
|
|||
[indexingStatus.processedItems, indexingStatus.totalItems],
|
||||
)
|
||||
|
||||
// Get tooltip text with internationalization
|
||||
const getTooltipText = () => {
|
||||
const tooltipText = useMemo(() => {
|
||||
switch (indexingStatus.systemStatus) {
|
||||
case "Standby":
|
||||
return t("chat:indexingStatus.ready")
|
||||
|
|
@ -70,93 +69,44 @@ export const IndexingStatusBadge: React.FC<IndexingStatusBadgeProps> = ({ classN
|
|||
default:
|
||||
return t("chat:indexingStatus.status")
|
||||
}
|
||||
}
|
||||
}, [indexingStatus.systemStatus, progressPercentage, t])
|
||||
|
||||
const handleMouseEnterButton = () => {
|
||||
setIsHovered(true)
|
||||
handleMouseEnter()
|
||||
}
|
||||
|
||||
const handleMouseLeaveButton = () => {
|
||||
setIsHovered(false)
|
||||
handleMouseLeave()
|
||||
}
|
||||
|
||||
// Get status color classes for the badge dot
|
||||
const getStatusColorClass = () => {
|
||||
const statusColorClass = useMemo(() => {
|
||||
const statusColors = {
|
||||
Standby: {
|
||||
default: "bg-vscode-descriptionForeground/60",
|
||||
hover: "bg-vscode-descriptionForeground/80",
|
||||
},
|
||||
Indexing: {
|
||||
default: "bg-yellow-500 animate-pulse",
|
||||
hover: "bg-yellow-500 animate-pulse",
|
||||
},
|
||||
Indexed: {
|
||||
default: "bg-green-500",
|
||||
hover: "bg-green-500",
|
||||
},
|
||||
Error: {
|
||||
default: "bg-red-500",
|
||||
hover: "bg-red-500",
|
||||
},
|
||||
Standby: "bg-vscode-descriptionForeground/60",
|
||||
Indexing: "bg-yellow-500 animate-pulse",
|
||||
Indexed: "bg-green-500",
|
||||
Error: "bg-red-500",
|
||||
}
|
||||
|
||||
const colors = statusColors[indexingStatus.systemStatus as keyof typeof statusColors] || statusColors.Standby
|
||||
return isHovered ? colors.hover : colors.default
|
||||
}
|
||||
return statusColors[indexingStatus.systemStatus as keyof typeof statusColors] || statusColors.Standby
|
||||
}, [indexingStatus.systemStatus])
|
||||
|
||||
return (
|
||||
<div className={cn("relative inline-block", className)}>
|
||||
<CodeIndexPopover indexingStatus={indexingStatus}>
|
||||
<button
|
||||
onMouseEnter={handleMouseEnterButton}
|
||||
onMouseLeave={handleMouseLeaveButton}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
"rounded-md min-w-[28px] min-h-[28px]",
|
||||
"opacity-85 text-vscode-foreground",
|
||||
"transition-all duration-150",
|
||||
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
"active:bg-[rgba(255,255,255,0.1)]",
|
||||
"cursor-pointer",
|
||||
className,
|
||||
)}
|
||||
aria-label={getTooltipText()}>
|
||||
{/* File search icon */}
|
||||
<Database className="w-4 h-4 text-vscode-foreground" />
|
||||
|
||||
{/* Status dot badge */}
|
||||
<span
|
||||
<CodeIndexPopover indexingStatus={indexingStatus}>
|
||||
<StandardTooltip content={tooltipText}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label={tooltipText}
|
||||
className={cn(
|
||||
"absolute top-1 right-1 w-1.5 h-1.5 rounded-full transition-colors duration-200",
|
||||
getStatusColorClass(),
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</CodeIndexPopover>
|
||||
{showTooltip && (
|
||||
<div
|
||||
className={cn(
|
||||
"absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2",
|
||||
"px-2 py-1 text-xs font-medium text-vscode-foreground",
|
||||
"bg-vscode-editor-background border border-vscode-panel-border",
|
||||
"rounded shadow-lg whitespace-nowrap z-50",
|
||||
)}
|
||||
role="tooltip">
|
||||
{getTooltipText()}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute top-full left-1/2 transform -translate-x-1/2",
|
||||
"w-0 h-0 border-l-4 border-r-4 border-t-4",
|
||||
"border-l-transparent border-r-transparent border-t-vscode-panel-border",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
"relative h-7 w-7 p-0",
|
||||
"text-vscode-foreground opacity-85",
|
||||
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
className,
|
||||
)}>
|
||||
<Database className="w-4 h-4" />
|
||||
<span
|
||||
className={cn(
|
||||
"absolute top-1 right-1 w-1.5 h-1.5 rounded-full transition-colors duration-200",
|
||||
statusColorClass,
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</StandardTooltip>
|
||||
</CodeIndexPopover>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,28 @@
|
|||
import React from "react"
|
||||
import { Fzf } from "fzf"
|
||||
import { ChevronUp, Check, X } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
|
||||
import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui"
|
||||
import { IconButton } from "./IconButton"
|
||||
|
||||
import { type ModeConfig, type CustomModePrompts, TelemetryEventName } from "@roo-code/types"
|
||||
|
||||
import { type Mode, getAllModes } from "@roo/modes"
|
||||
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { telemetryClient } from "@/utils/TelemetryClient"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { Mode, getAllModes } from "@roo/modes"
|
||||
import { ModeConfig, CustomModePrompts } from "@roo-code/types"
|
||||
import { telemetryClient } from "@/utils/TelemetryClient"
|
||||
import { TelemetryEventName } from "@roo-code/types"
|
||||
import { Fzf } from "fzf"
|
||||
import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
|
||||
import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui"
|
||||
|
||||
import { IconButton } from "./IconButton"
|
||||
|
||||
// Minimum number of modes required to show search functionality
|
||||
const SEARCH_THRESHOLD = 6
|
||||
|
||||
interface ModeSelectorProps {
|
||||
value: Mode
|
||||
onChange: (value: Mode) => void
|
||||
disabled?: boolean
|
||||
title?: string
|
||||
title: string
|
||||
triggerClassName?: string
|
||||
modeShortcutText: string
|
||||
customModes?: ModeConfig[]
|
||||
|
|
@ -32,7 +34,7 @@ export const ModeSelector = ({
|
|||
value,
|
||||
onChange,
|
||||
disabled = false,
|
||||
title = "",
|
||||
title,
|
||||
triggerClassName = "",
|
||||
modeShortcutText,
|
||||
customModes,
|
||||
|
|
@ -47,29 +49,31 @@ export const ModeSelector = ({
|
|||
const { t } = useAppTranslation()
|
||||
|
||||
const trackModeSelectorOpened = React.useCallback(() => {
|
||||
// Track telemetry every time the mode selector is opened
|
||||
// Track telemetry every time the mode selector is opened.
|
||||
telemetryClient.capture(TelemetryEventName.MODE_SELECTOR_OPENED)
|
||||
|
||||
// Track first-time usage for UI purposes
|
||||
// Track first-time usage for UI purposes.
|
||||
if (!hasOpenedModeSelector) {
|
||||
setHasOpenedModeSelector(true)
|
||||
vscode.postMessage({ type: "hasOpenedModeSelector", bool: true })
|
||||
}
|
||||
}, [hasOpenedModeSelector, setHasOpenedModeSelector])
|
||||
|
||||
// Get all modes including custom modes and merge custom prompt descriptions
|
||||
// Get all modes including custom modes and merge custom prompt descriptions.
|
||||
const modes = React.useMemo(() => {
|
||||
const allModes = getAllModes(customModes)
|
||||
|
||||
return allModes.map((mode) => ({
|
||||
...mode,
|
||||
description: customModePrompts?.[mode.slug]?.description ?? mode.description,
|
||||
}))
|
||||
}, [customModes, customModePrompts])
|
||||
|
||||
// Find the selected mode
|
||||
// Find the selected mode.
|
||||
const selectedMode = React.useMemo(() => modes.find((mode) => mode.slug === value), [modes, value])
|
||||
|
||||
// Memoize searchable items for fuzzy search with separate name and description search
|
||||
// Memoize searchable items for fuzzy search with separate name and
|
||||
// description search.
|
||||
const nameSearchItems = React.useMemo(() => {
|
||||
return modes.map((mode) => ({
|
||||
original: mode,
|
||||
|
|
@ -84,31 +88,29 @@ export const ModeSelector = ({
|
|||
}))
|
||||
}, [modes])
|
||||
|
||||
// Create memoized Fzf instances for name and description searches
|
||||
const nameFzfInstance = React.useMemo(() => {
|
||||
return new Fzf(nameSearchItems, {
|
||||
selector: (item) => item.searchStr,
|
||||
})
|
||||
}, [nameSearchItems])
|
||||
// Create memoized Fzf instances for name and description searches.
|
||||
const nameFzfInstance = React.useMemo(
|
||||
() => new Fzf(nameSearchItems, { selector: (item) => item.searchStr }),
|
||||
[nameSearchItems],
|
||||
)
|
||||
|
||||
const descriptionFzfInstance = React.useMemo(() => {
|
||||
return new Fzf(descriptionSearchItems, {
|
||||
selector: (item) => item.searchStr,
|
||||
})
|
||||
}, [descriptionSearchItems])
|
||||
const descriptionFzfInstance = React.useMemo(
|
||||
() => new Fzf(descriptionSearchItems, { selector: (item) => item.searchStr }),
|
||||
[descriptionSearchItems],
|
||||
)
|
||||
|
||||
// Filter modes based on search value using fuzzy search with priority
|
||||
// Filter modes based on search value using fuzzy search with priority.
|
||||
const filteredModes = React.useMemo(() => {
|
||||
if (!searchValue) return modes
|
||||
|
||||
// First search in names/slugs
|
||||
// First search in names/slugs.
|
||||
const nameMatches = nameFzfInstance.find(searchValue)
|
||||
const nameMatchedModes = new Set(nameMatches.map((result) => result.item.original.slug))
|
||||
|
||||
// Then search in descriptions
|
||||
// Then search in descriptions.
|
||||
const descriptionMatches = descriptionFzfInstance.find(searchValue)
|
||||
|
||||
// Combine results: name matches first, then description matches
|
||||
// Combine results: name matches first, then description matches.
|
||||
const combinedResults = [
|
||||
...nameMatches.map((result) => result.item.original),
|
||||
...descriptionMatches
|
||||
|
|
@ -128,7 +130,7 @@ export const ModeSelector = ({
|
|||
(modeSlug: string) => {
|
||||
onChange(modeSlug as Mode)
|
||||
setOpen(false)
|
||||
// Clear search after selection
|
||||
// Clear search after selection.
|
||||
setSearchValue("")
|
||||
},
|
||||
[onChange],
|
||||
|
|
@ -138,7 +140,8 @@ export const ModeSelector = ({
|
|||
(isOpen: boolean) => {
|
||||
if (isOpen) trackModeSelectorOpened()
|
||||
setOpen(isOpen)
|
||||
// Clear search when closing
|
||||
|
||||
// Clear search when closing.
|
||||
if (!isOpen) {
|
||||
setSearchValue("")
|
||||
}
|
||||
|
|
@ -146,44 +149,46 @@ export const ModeSelector = ({
|
|||
[trackModeSelectorOpened],
|
||||
)
|
||||
|
||||
// Auto-focus search input when popover opens
|
||||
// Auto-focus search input when popover opens.
|
||||
React.useEffect(() => {
|
||||
if (open && searchInputRef.current) {
|
||||
searchInputRef.current.focus()
|
||||
}
|
||||
}, [open])
|
||||
|
||||
// Determine if search should be shown
|
||||
// Determine if search should be shown.
|
||||
const showSearch = !disableSearch && modes.length > SEARCH_THRESHOLD
|
||||
|
||||
// Combine instruction text for tooltip
|
||||
// Combine instruction text for tooltip.
|
||||
const instructionText = `${t("chat:modeSelector.description")} ${modeShortcutText}`
|
||||
|
||||
const trigger = (
|
||||
<PopoverTrigger
|
||||
disabled={disabled}
|
||||
data-testid="mode-selector-trigger"
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 relative whitespace-nowrap px-1.5 py-1 text-xs",
|
||||
"bg-transparent border border-[rgba(255,255,255,0.08)] rounded-md text-vscode-foreground",
|
||||
"transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset",
|
||||
disabled
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: "opacity-90 hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)] cursor-pointer",
|
||||
triggerClassName,
|
||||
!disabled && !hasOpenedModeSelector
|
||||
? "bg-primary opacity-90 hover:bg-primary-hover text-vscode-button-foreground"
|
||||
: null,
|
||||
)}>
|
||||
<ChevronUp className="pointer-events-none opacity-80 flex-shrink-0 size-3" />
|
||||
<span className="truncate">{selectedMode?.name || ""}</span>
|
||||
</PopoverTrigger>
|
||||
)
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={onOpenChange} data-testid="mode-selector-root">
|
||||
{title ? <StandardTooltip content={title}>{trigger}</StandardTooltip> : trigger}
|
||||
|
||||
<StandardTooltip content={title}>
|
||||
<PopoverTrigger
|
||||
disabled={disabled}
|
||||
data-testid="mode-selector-trigger"
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 relative whitespace-nowrap px-1.5 py-1 text-xs",
|
||||
"bg-transparent border border-[rgba(255,255,255,0.08)] rounded-md text-vscode-foreground",
|
||||
"transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset",
|
||||
disabled
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: "opacity-90 hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)] cursor-pointer",
|
||||
triggerClassName,
|
||||
!disabled && !hasOpenedModeSelector
|
||||
? "bg-primary opacity-90 hover:bg-primary-hover text-vscode-button-foreground"
|
||||
: null,
|
||||
)}>
|
||||
<ChevronUp
|
||||
className={cn(
|
||||
"pointer-events-none opacity-80 flex-shrink-0 size-3 transition-transform duration-200",
|
||||
open && "rotate-180",
|
||||
)}
|
||||
/>
|
||||
<span className="truncate">{selectedMode?.name || ""}</span>
|
||||
</PopoverTrigger>
|
||||
</StandardTooltip>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
sideOffset={4}
|
||||
|
|
@ -274,10 +279,7 @@ export const ModeSelector = ({
|
|||
iconClass="codicon-settings-gear"
|
||||
title={t("chat:modeSelector.settings")}
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "switchTab",
|
||||
tab: "modes",
|
||||
})
|
||||
vscode.postMessage({ type: "switchTab", tab: "modes" })
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
|
|
@ -300,5 +302,3 @@ export const ModeSelector = ({
|
|||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModeSelector
|
||||
|
|
|
|||
|
|
@ -41,26 +41,24 @@ export const SlashCommandsPopover: React.FC<SlashCommandsPopoverProps> = ({ clas
|
|||
}
|
||||
}
|
||||
|
||||
const trigger = (
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={cn(
|
||||
"h-7 w-7 p-0",
|
||||
"text-vscode-foreground opacity-85",
|
||||
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
className,
|
||||
)}>
|
||||
<Zap className="w-4 h-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
)
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={handleOpenChange}>
|
||||
<StandardTooltip content={t("chat:slashCommands.tooltip")}>{trigger}</StandardTooltip>
|
||||
<StandardTooltip content={t("chat:slashCommands.tooltip")}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={cn(
|
||||
"h-7 w-7 p-0",
|
||||
"text-vscode-foreground opacity-85",
|
||||
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)]",
|
||||
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
|
||||
className,
|
||||
)}>
|
||||
<Zap className="w-4 h-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</StandardTooltip>
|
||||
|
||||
<PopoverContent
|
||||
align="start"
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ describe("ApiConfigSelector", () => {
|
|||
const defaultProps = {
|
||||
value: "config1",
|
||||
displayName: "Config 1",
|
||||
title: "API Config",
|
||||
onChange: mockOnChange,
|
||||
listApiConfigMeta: [
|
||||
{ id: "config1", name: "Config 1" },
|
||||
|
|
@ -75,8 +76,8 @@ describe("ApiConfigSelector", () => {
|
|||
render(<ApiConfigSelector {...defaultProps} />)
|
||||
|
||||
const trigger = screen.getByTestId("dropdown-trigger")
|
||||
// Check for the icon by looking for the codicon span element
|
||||
const icon = trigger.querySelector(".codicon-chevron-up")
|
||||
// Check for the icon by looking for the svg element (ChevronUp from lucide-react renders as svg)
|
||||
const icon = trigger.querySelector("svg")
|
||||
expect(icon).toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { render, fireEvent, screen } from "@/utils/test-utils"
|
||||
|
||||
import { defaultModeSlug } from "@roo/modes"
|
||||
|
||||
import { render, fireEvent, screen } from "@src/utils/test-utils"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import * as pathMentions from "@src/utils/path-mentions"
|
||||
|
||||
import ChatTextArea from "../ChatTextArea"
|
||||
import { ChatTextArea } from "../ChatTextArea"
|
||||
|
||||
vi.mock("@src/utils/vscode", () => ({
|
||||
vscode: {
|
||||
|
|
@ -1058,54 +1057,4 @@ describe("ChatTextArea", () => {
|
|||
expect(apiConfigDropdown).toHaveAttribute("disabled")
|
||||
})
|
||||
})
|
||||
describe("edit mode integration", () => {
|
||||
it("should render edit mode UI when isEditMode is true", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
customModes: [],
|
||||
customModePrompts: {},
|
||||
})
|
||||
|
||||
render(<ChatTextArea {...defaultProps} isEditMode={true} />)
|
||||
|
||||
// The edit mode UI should be rendered
|
||||
// We can verify this by checking for the presence of elements that are unique to edit mode
|
||||
const cancelButton = screen.getByRole("button", { name: /cancel/i })
|
||||
expect(cancelButton).toBeInTheDocument()
|
||||
|
||||
// Should show save button instead of send button
|
||||
const saveButton = screen.getByRole("button", { name: /save/i })
|
||||
expect(saveButton).toBeInTheDocument()
|
||||
|
||||
// Should not show send button in edit mode
|
||||
const sendButton = screen.queryByRole("button", { name: /send.*message/i })
|
||||
expect(sendButton).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should not render edit mode UI when isEditMode is false", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
})
|
||||
|
||||
render(<ChatTextArea {...defaultProps} isEditMode={false} />)
|
||||
|
||||
// The edit mode UI should not be rendered
|
||||
const cancelButton = screen.queryByRole("button", { name: /cancel/i })
|
||||
expect(cancelButton).not.toBeInTheDocument()
|
||||
|
||||
// Should show send button when not in edit mode
|
||||
const sendButton = screen.getByRole("button", { name: /send.*message/i })
|
||||
expect(sendButton).toBeInTheDocument()
|
||||
|
||||
// Should not show save button when not in edit mode
|
||||
const saveButton = screen.queryByRole("button", { name: /save/i })
|
||||
expect(saveButton).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -73,16 +73,19 @@ vi.mock("react-i18next", () => ({
|
|||
}))
|
||||
|
||||
vi.mock("../ChatTextArea", () => {
|
||||
const ChatTextAreaComponent = React.forwardRef(function MockChatTextArea(
|
||||
_props: any,
|
||||
ref: React.ForwardedRef<{ focus: () => void }>,
|
||||
) {
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
focus: vi.fn(),
|
||||
}))
|
||||
return <div data-testid="chat-textarea" />
|
||||
})
|
||||
|
||||
return {
|
||||
default: React.forwardRef(function MockChatTextArea(
|
||||
_props: any,
|
||||
ref: React.ForwardedRef<{ focus: () => void }>,
|
||||
) {
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
focus: vi.fn(),
|
||||
}))
|
||||
return <div data-testid="chat-textarea" />
|
||||
}),
|
||||
default: ChatTextAreaComponent,
|
||||
ChatTextArea: ChatTextAreaComponent, // Export as named export too
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -181,30 +181,33 @@ vi.mock("../ChatTextArea", () => {
|
|||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const mockReact = require("react")
|
||||
|
||||
return {
|
||||
default: mockReact.forwardRef(function MockChatTextArea(
|
||||
props: ChatTextAreaProps,
|
||||
ref: React.ForwardedRef<{ focus: () => void }>,
|
||||
) {
|
||||
// Use useImperativeHandle to expose the mock focus method
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
focus: mockFocus,
|
||||
}))
|
||||
const ChatTextAreaComponent = mockReact.forwardRef(function MockChatTextArea(
|
||||
props: ChatTextAreaProps,
|
||||
ref: React.ForwardedRef<{ focus: () => void }>,
|
||||
) {
|
||||
// Use useImperativeHandle to expose the mock focus method
|
||||
mockReact.useImperativeHandle(ref, () => ({
|
||||
focus: mockFocus,
|
||||
}))
|
||||
|
||||
return (
|
||||
<div data-testid="chat-textarea">
|
||||
<input
|
||||
ref={mockInputRef}
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
// With message queueing, onSend is always called (it handles queueing internally)
|
||||
props.onSend(e.target.value)
|
||||
}}
|
||||
data-sending-disabled={props.sendingDisabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
return (
|
||||
<div data-testid="chat-textarea">
|
||||
<input
|
||||
ref={mockInputRef}
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
// With message queueing, onSend is always called (it handles queueing internally)
|
||||
props.onSend(e.target.value)
|
||||
}}
|
||||
data-sending-disabled={props.sendingDisabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
default: ChatTextAreaComponent,
|
||||
ChatTextArea: ChatTextAreaComponent, // Export as named export too
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,138 +0,0 @@
|
|||
import React from "react"
|
||||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest"
|
||||
import { EditModeControls } from "../EditModeControls"
|
||||
import { Mode } from "@roo/modes"
|
||||
|
||||
// Mock the translation hook
|
||||
vi.mock("@/i18n/TranslationContext", () => ({
|
||||
useAppTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock the UI components
|
||||
vi.mock("@/components/ui", () => ({
|
||||
Button: ({ children, onClick, disabled, ...props }: any) => (
|
||||
<button onClick={onClick} disabled={disabled} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
StandardTooltip: ({ children, content }: any) => <div title={content}>{children}</div>,
|
||||
}))
|
||||
|
||||
// Mock ModeSelector
|
||||
vi.mock("../ModeSelector", () => ({
|
||||
default: ({ value, onChange, title }: any) => (
|
||||
<select value={value} onChange={(e) => onChange(e.target.value)} title={title}>
|
||||
<option value="code">Code</option>
|
||||
<option value="architect">Architect</option>
|
||||
</select>
|
||||
),
|
||||
}))
|
||||
|
||||
describe("EditModeControls", () => {
|
||||
const defaultProps = {
|
||||
mode: "code" as Mode,
|
||||
onModeChange: vi.fn(),
|
||||
modeShortcutText: "Ctrl+M",
|
||||
customModes: [],
|
||||
customModePrompts: {},
|
||||
onCancel: vi.fn(),
|
||||
onSend: vi.fn(),
|
||||
onSelectImages: vi.fn(),
|
||||
sendingDisabled: false,
|
||||
shouldDisableImages: false,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it("renders all controls correctly", () => {
|
||||
render(<EditModeControls {...defaultProps} />)
|
||||
|
||||
// Check for mode selector
|
||||
expect(screen.getByTitle("chat:selectMode")).toBeInTheDocument()
|
||||
|
||||
// Check for Cancel button
|
||||
expect(screen.getByText("Cancel")).toBeInTheDocument()
|
||||
|
||||
// Check for image button
|
||||
expect(screen.getByTitle("chat:addImages")).toBeInTheDocument()
|
||||
|
||||
// Check for send button
|
||||
expect(screen.getByTitle("chat:save.tooltip")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("calls onCancel when Cancel button is clicked", () => {
|
||||
render(<EditModeControls {...defaultProps} />)
|
||||
|
||||
const cancelButton = screen.getByText("Cancel")
|
||||
fireEvent.click(cancelButton)
|
||||
|
||||
expect(defaultProps.onCancel).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("calls onSend when send button is clicked", () => {
|
||||
render(<EditModeControls {...defaultProps} />)
|
||||
|
||||
const sendButton = screen.getByLabelText("chat:save.tooltip")
|
||||
fireEvent.click(sendButton)
|
||||
|
||||
expect(defaultProps.onSend).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("calls onSelectImages when image button is clicked", () => {
|
||||
render(<EditModeControls {...defaultProps} />)
|
||||
|
||||
const imageButton = screen.getByLabelText("chat:addImages")
|
||||
fireEvent.click(imageButton)
|
||||
|
||||
expect(defaultProps.onSelectImages).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("disables buttons when sendingDisabled is true", () => {
|
||||
render(<EditModeControls {...defaultProps} sendingDisabled={true} />)
|
||||
|
||||
const cancelButton = screen.getByText("Cancel")
|
||||
const sendButton = screen.getByLabelText("chat:save.tooltip")
|
||||
|
||||
expect(cancelButton).toBeDisabled()
|
||||
expect(sendButton).toBeDisabled()
|
||||
})
|
||||
|
||||
it("disables image button when shouldDisableImages is true", () => {
|
||||
render(<EditModeControls {...defaultProps} shouldDisableImages={true} />)
|
||||
|
||||
const imageButton = screen.getByLabelText("chat:addImages")
|
||||
expect(imageButton).toBeDisabled()
|
||||
})
|
||||
|
||||
it("does not call onSelectImages when image button is disabled", () => {
|
||||
render(<EditModeControls {...defaultProps} shouldDisableImages={true} />)
|
||||
|
||||
const imageButton = screen.getByLabelText("chat:addImages")
|
||||
fireEvent.click(imageButton)
|
||||
|
||||
expect(defaultProps.onSelectImages).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("does not call onSend when send button is disabled", () => {
|
||||
render(<EditModeControls {...defaultProps} sendingDisabled={true} />)
|
||||
|
||||
const sendButton = screen.getByLabelText("chat:save.tooltip")
|
||||
fireEvent.click(sendButton)
|
||||
|
||||
expect(defaultProps.onSend).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("calls onModeChange when mode is changed", () => {
|
||||
render(<EditModeControls {...defaultProps} />)
|
||||
|
||||
const modeSelector = screen.getByTitle("chat:selectMode")
|
||||
fireEvent.change(modeSelector, { target: { value: "architect" } })
|
||||
|
||||
expect(defaultProps.onModeChange).toHaveBeenCalledWith("architect")
|
||||
})
|
||||
})
|
||||
|
|
@ -55,16 +55,6 @@ vi.mock("@src/utils/vscode", () => ({
|
|||
},
|
||||
}))
|
||||
|
||||
// Mock the useTooltip hook
|
||||
vi.mock("@/hooks/useTooltip", () => ({
|
||||
useTooltip: vi.fn(() => ({
|
||||
showTooltip: false,
|
||||
handleMouseEnter: vi.fn(),
|
||||
handleMouseLeave: vi.fn(),
|
||||
cleanup: vi.fn(),
|
||||
})),
|
||||
}))
|
||||
|
||||
// Mock the ExtensionStateContext
|
||||
vi.mock("@/context/ExtensionStateContext", () => ({
|
||||
useExtensionState: () => ({
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React from "react"
|
||||
import { render, screen, fireEvent } from "@/utils/test-utils"
|
||||
import { describe, test, expect, vi } from "vitest"
|
||||
import ModeSelector from "../ModeSelector"
|
||||
import { Mode } from "@roo/modes"
|
||||
import { ModeConfig } from "@roo-code/types"
|
||||
|
||||
// Mock the dependencies
|
||||
import type { ModeConfig } from "@roo-code/types"
|
||||
|
||||
import type { Mode } from "@roo/modes"
|
||||
|
||||
import { ModeSelector } from "../ModeSelector"
|
||||
|
||||
vi.mock("@/utils/vscode", () => ({
|
||||
vscode: {
|
||||
postMessage: vi.fn(),
|
||||
|
|
@ -35,7 +35,7 @@ vi.mock("@/utils/TelemetryClient", () => ({
|
|||
},
|
||||
}))
|
||||
|
||||
// Create a variable to control what getAllModes returns
|
||||
// Create a variable to control what getAllModes returns.
|
||||
let mockModes: ModeConfig[] = []
|
||||
|
||||
vi.mock("@roo/modes", async () => {
|
||||
|
|
@ -56,6 +56,7 @@ describe("ModeSelector", () => {
|
|||
|
||||
render(
|
||||
<ModeSelector
|
||||
title="Mode Selector"
|
||||
value={"code" as Mode}
|
||||
onChange={vi.fn()}
|
||||
modeShortcutText="Ctrl+M"
|
||||
|
|
@ -63,19 +64,18 @@ describe("ModeSelector", () => {
|
|||
/>,
|
||||
)
|
||||
|
||||
// The component should be rendered
|
||||
expect(screen.getByTestId("mode-selector-trigger")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("falls back to default description when no custom prompt", () => {
|
||||
render(<ModeSelector value={"code" as Mode} onChange={vi.fn()} modeShortcutText="Ctrl+M" />)
|
||||
render(
|
||||
<ModeSelector title="Mode Selector" value={"code" as Mode} onChange={vi.fn()} modeShortcutText="Ctrl+M" />,
|
||||
)
|
||||
|
||||
// The component should be rendered
|
||||
expect(screen.getByTestId("mode-selector-trigger")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("shows search bar when there are more than 6 modes", () => {
|
||||
// Set up mock to return 7 modes
|
||||
mockModes = Array.from({ length: 7 }, (_, i) => ({
|
||||
slug: `mode-${i}`,
|
||||
name: `Mode ${i}`,
|
||||
|
|
@ -84,22 +84,28 @@ describe("ModeSelector", () => {
|
|||
groups: ["read", "edit"],
|
||||
}))
|
||||
|
||||
render(<ModeSelector value={"mode-0" as Mode} onChange={vi.fn()} modeShortcutText="Ctrl+M" />)
|
||||
render(
|
||||
<ModeSelector
|
||||
title="Mode Selector"
|
||||
value={"mode-0" as Mode}
|
||||
onChange={vi.fn()}
|
||||
modeShortcutText="Ctrl+M"
|
||||
/>,
|
||||
)
|
||||
|
||||
// Click to open the popover
|
||||
// Click to open the popover.
|
||||
fireEvent.click(screen.getByTestId("mode-selector-trigger"))
|
||||
|
||||
// Search input should be visible
|
||||
// Search input should be visible.
|
||||
expect(screen.getByTestId("mode-search-input")).toBeInTheDocument()
|
||||
|
||||
// Info icon should be visible
|
||||
// Info icon should be visible.
|
||||
expect(screen.getByText("chat:modeSelector.title")).toBeInTheDocument()
|
||||
const infoIcon = document.querySelector(".codicon-info")
|
||||
expect(infoIcon).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("shows info blurb instead of search bar when there are 6 or fewer modes", () => {
|
||||
// Set up mock to return 5 modes
|
||||
mockModes = Array.from({ length: 5 }, (_, i) => ({
|
||||
slug: `mode-${i}`,
|
||||
name: `Mode ${i}`,
|
||||
|
|
@ -108,24 +114,30 @@ describe("ModeSelector", () => {
|
|||
groups: ["read", "edit"],
|
||||
}))
|
||||
|
||||
render(<ModeSelector value={"mode-0" as Mode} onChange={vi.fn()} modeShortcutText="Ctrl+M" />)
|
||||
render(
|
||||
<ModeSelector
|
||||
title="Mode Selector"
|
||||
value={"mode-0" as Mode}
|
||||
onChange={vi.fn()}
|
||||
modeShortcutText="Ctrl+M"
|
||||
/>,
|
||||
)
|
||||
|
||||
// Click to open the popover
|
||||
// Click to open the popover.
|
||||
fireEvent.click(screen.getByTestId("mode-selector-trigger"))
|
||||
|
||||
// Search input should NOT be visible
|
||||
// Search input should NOT be visible.
|
||||
expect(screen.queryByTestId("mode-search-input")).not.toBeInTheDocument()
|
||||
|
||||
// Info blurb should be visible
|
||||
// Info blurb should be visible.
|
||||
expect(screen.getByText(/chat:modeSelector.description/)).toBeInTheDocument()
|
||||
|
||||
// Info icon should NOT be visible
|
||||
// Info icon should NOT be visible.
|
||||
const infoIcon = document.querySelector(".codicon-info")
|
||||
expect(infoIcon).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("filters modes correctly when searching", () => {
|
||||
// Set up mock to return 7 modes to enable search
|
||||
mockModes = Array.from({ length: 7 }, (_, i) => ({
|
||||
slug: `mode-${i}`,
|
||||
name: `Mode ${i}`,
|
||||
|
|
@ -134,22 +146,28 @@ describe("ModeSelector", () => {
|
|||
groups: ["read", "edit"],
|
||||
}))
|
||||
|
||||
render(<ModeSelector value={"mode-0" as Mode} onChange={vi.fn()} modeShortcutText="Ctrl+M" />)
|
||||
render(
|
||||
<ModeSelector
|
||||
title="Mode Selector"
|
||||
value={"mode-0" as Mode}
|
||||
onChange={vi.fn()}
|
||||
modeShortcutText="Ctrl+M"
|
||||
/>,
|
||||
)
|
||||
|
||||
// Click to open the popover
|
||||
// Click to open the popover.
|
||||
fireEvent.click(screen.getByTestId("mode-selector-trigger"))
|
||||
|
||||
// Type in search
|
||||
// Type in search.
|
||||
const searchInput = screen.getByTestId("mode-search-input")
|
||||
fireEvent.change(searchInput, { target: { value: "Mode 3" } })
|
||||
|
||||
// Should show filtered results
|
||||
// Should show filtered results.
|
||||
const modeItems = screen.getAllByTestId("mode-selector-item")
|
||||
expect(modeItems.length).toBeLessThan(7) // Should have filtered some out
|
||||
expect(modeItems.length).toBeLessThan(7) // Should have filtered some out.
|
||||
})
|
||||
|
||||
test("respects disableSearch prop even when there are more than 6 modes", () => {
|
||||
// Set up mock to return 10 modes
|
||||
mockModes = Array.from({ length: 10 }, (_, i) => ({
|
||||
slug: `mode-${i}`,
|
||||
name: `Mode ${i}`,
|
||||
|
|
@ -159,25 +177,30 @@ describe("ModeSelector", () => {
|
|||
}))
|
||||
|
||||
render(
|
||||
<ModeSelector value={"mode-0" as Mode} onChange={vi.fn()} modeShortcutText="Ctrl+M" disableSearch={true} />,
|
||||
<ModeSelector
|
||||
title="Mode Selector"
|
||||
value={"mode-0" as Mode}
|
||||
onChange={vi.fn()}
|
||||
modeShortcutText="Ctrl+M"
|
||||
disableSearch={true}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Click to open the popover
|
||||
// Click to open the popover.
|
||||
fireEvent.click(screen.getByTestId("mode-selector-trigger"))
|
||||
|
||||
// Search input should NOT be visible even with 10 modes
|
||||
// Search input should NOT be visible even with 10 modes.
|
||||
expect(screen.queryByTestId("mode-search-input")).not.toBeInTheDocument()
|
||||
|
||||
// Info blurb should be visible instead
|
||||
// Info blurb should be visible instead.
|
||||
expect(screen.getByText(/chat:modeSelector.description/)).toBeInTheDocument()
|
||||
|
||||
// Info icon should NOT be visible
|
||||
// Info icon should NOT be visible.
|
||||
const infoIcon = document.querySelector(".codicon-info")
|
||||
expect(infoIcon).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("shows search when disableSearch is false (default) and modes > 6", () => {
|
||||
// Set up mock to return 8 modes
|
||||
mockModes = Array.from({ length: 8 }, (_, i) => ({
|
||||
slug: `mode-${i}`,
|
||||
name: `Mode ${i}`,
|
||||
|
|
@ -186,16 +209,20 @@ describe("ModeSelector", () => {
|
|||
groups: ["read", "edit"],
|
||||
}))
|
||||
|
||||
// Don't pass disableSearch prop (should default to false)
|
||||
render(<ModeSelector value={"mode-0" as Mode} onChange={vi.fn()} modeShortcutText="Ctrl+M" />)
|
||||
// Don't pass disableSearch prop (should default to false).
|
||||
render(
|
||||
<ModeSelector
|
||||
title="Mode Selector"
|
||||
value={"mode-0" as Mode}
|
||||
onChange={vi.fn()}
|
||||
modeShortcutText="Ctrl+M"
|
||||
/>,
|
||||
)
|
||||
|
||||
// Click to open the popover
|
||||
fireEvent.click(screen.getByTestId("mode-selector-trigger"))
|
||||
|
||||
// Search input should be visible
|
||||
expect(screen.getByTestId("mode-search-input")).toBeInTheDocument()
|
||||
|
||||
// Info icon should be visible
|
||||
const infoIcon = document.querySelector(".codicon-info")
|
||||
expect(infoIcon).toBeInTheDocument()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
|||
import styled from "styled-components"
|
||||
import { Trans } from "react-i18next"
|
||||
|
||||
import { TelemetrySetting } from "@roo/TelemetrySetting"
|
||||
import type { TelemetrySetting } from "@roo-code/types"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import { HTMLAttributes } from "react"
|
|||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { Trans } from "react-i18next"
|
||||
import { Info, Download, Upload, TriangleAlert } from "lucide-react"
|
||||
|
||||
import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import type { TelemetrySetting } from "@roo-code/types"
|
||||
|
||||
import { Package } from "@roo/package"
|
||||
import { TelemetrySetting } from "@roo/TelemetrySetting"
|
||||
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
|
|
|||
|
|
@ -25,11 +25,10 @@ import {
|
|||
LucideIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import type { ProviderSettings, ExperimentId } from "@roo-code/types"
|
||||
|
||||
import { TelemetrySetting } from "@roo/TelemetrySetting"
|
||||
import type { ProviderSettings, ExperimentId, TelemetrySetting } from "@roo-code/types"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { cn } from "@src/lib/utils"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { ExtensionStateContextType, useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import {
|
||||
|
|
@ -65,7 +64,6 @@ import { LanguageSettings } from "./LanguageSettings"
|
|||
import { About } from "./About"
|
||||
import { Section } from "./Section"
|
||||
import PromptsSettings from "./PromptsSettings"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
|
||||
export const settingsTabList =
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import * as React from "react"
|
||||
import { ReactNode } from "react"
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip"
|
||||
|
||||
export const STANDARD_TOOLTIP_DELAY = 300
|
||||
|
||||
interface StandardTooltipProps {
|
||||
/** The element(s) that trigger the tooltip */
|
||||
children: React.ReactNode
|
||||
children: ReactNode
|
||||
/** The content to display in the tooltip */
|
||||
content: React.ReactNode
|
||||
content: ReactNode
|
||||
/** The preferred side of the trigger to render the tooltip */
|
||||
side?: "top" | "right" | "bottom" | "left"
|
||||
/** The preferred alignment against the trigger */
|
||||
|
|
@ -51,7 +52,7 @@ export function StandardTooltip({
|
|||
asChild = true,
|
||||
maxWidth,
|
||||
}: StandardTooltipProps) {
|
||||
// Don't render tooltip if content is empty or only whitespace
|
||||
// Don't render tooltip if content is empty or only whitespace.
|
||||
if (!content || (typeof content === "string" && !content.trim())) {
|
||||
return <>{children}</>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,44 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider
|
||||
function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
||||
return <TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...props} />
|
||||
}
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root
|
||||
function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||
}
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||
function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
||||
}
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
collisionPadding={10}
|
||||
avoidCollisions={true}
|
||||
className={cn(
|
||||
"z-50 rounded-xs bg-vscode-notifications-background border border-vscode-notifications-border px-3 py-1.5 text-xs text-vscode-notifications-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
"max-w-[300px] break-words",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</TooltipPrimitive.Portal>
|
||||
))
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||
function TooltipContent({
|
||||
className,
|
||||
sideOffset = 0,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||
return (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
data-slot="tooltip-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-vscode-editorHoverWidget-background outline outline-vscode-editorHoverWidget-border text-vscode-editorHoverWidget-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-sm px-2 py-1 text-xs text-balance",
|
||||
"max-w-[300px] break-words",
|
||||
className,
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
<TooltipPrimitive.Arrow className="bg-vscode-editorHoverWidget-background border-b border-r border-vscode-editorHoverWidget-border fill-vscode-editorHoverWidget-background z-50 size-1.5 translate-y-[calc(-50%_+_1px)] rotate-45" />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import {
|
|||
type ModeConfig,
|
||||
type ExperimentId,
|
||||
type TodoItem,
|
||||
type TelemetrySetting,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { type OrganizationAllowList, ORGANIZATION_ALLOW_ALL } from "@roo/cloud"
|
||||
|
||||
import { ExtensionMessage, ExtensionState, MarketplaceInstalledMetadata, Command } from "@roo/ExtensionMessage"
|
||||
import { findLastIndex } from "@roo/array"
|
||||
import { McpServer } from "@roo/mcp"
|
||||
|
|
@ -18,7 +18,6 @@ import { checkExistKey } from "@roo/checkExistApiConfig"
|
|||
import { Mode, defaultModeSlug, defaultPrompts } from "@roo/modes"
|
||||
import { CustomSupportPrompts } from "@roo/support-prompt"
|
||||
import { experimentDefault } from "@roo/experiments"
|
||||
import { TelemetrySetting } from "@roo/TelemetrySetting"
|
||||
import { RouterModels } from "@roo/api"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
import { useState, useCallback, useRef } from "react"
|
||||
|
||||
interface UseTooltipOptions {
|
||||
delay?: number
|
||||
}
|
||||
|
||||
export const useTooltip = (options: UseTooltipOptions = {}) => {
|
||||
const { delay = 300 } = options
|
||||
const [showTooltip, setShowTooltip] = useState(false)
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = setTimeout(() => setShowTooltip(true), delay)
|
||||
}, [delay])
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = null
|
||||
}
|
||||
setShowTooltip(false)
|
||||
}, [])
|
||||
|
||||
// Cleanup on unmount
|
||||
const cleanup = useCallback(() => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = null
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
showTooltip,
|
||||
handleMouseEnter,
|
||||
handleMouseLeave,
|
||||
cleanup,
|
||||
}
|
||||
}
|
||||
|
|
@ -76,6 +76,7 @@
|
|||
--color-vscode-button-background: var(--vscode-button-background);
|
||||
--color-vscode-button-secondaryForeground: var(--vscode-button-secondaryForeground);
|
||||
--color-vscode-button-secondaryBackground: var(--vscode-button-secondaryBackground);
|
||||
--color-vscode-button-hoverBackground: var(--vscode-button-hoverBackground);
|
||||
|
||||
--color-vscode-dropdown-foreground: var(--vscode-dropdown-foreground);
|
||||
--color-vscode-dropdown-background: var(--vscode-dropdown-background);
|
||||
|
|
@ -107,6 +108,7 @@
|
|||
--color-vscode-list-activeSelectionForeground: var(--vscode-list-activeSelectionForeground);
|
||||
|
||||
--color-vscode-toolbar-hoverBackground: var(--vscode-toolbar-hoverBackground);
|
||||
--color-vscode-toolbar-hoverOutline: var(--vscode-toolbar-hoverOutline);
|
||||
|
||||
--color-vscode-panel-border: var(--vscode-panel-border);
|
||||
|
||||
|
|
@ -129,9 +131,14 @@
|
|||
--color-vscode-inputValidation-infoBorder: var(--vscode-inputValidation-infoBorder);
|
||||
|
||||
--color-vscode-widget-border: var(--vscode-widget-border);
|
||||
|
||||
--color-vscode-textLink-foreground: var(--vscode-textLink-foreground);
|
||||
|
||||
--color-vscode-textCodeBlock-background: var(--vscode-textCodeBlock-background);
|
||||
--color-vscode-button-hoverBackground: var(--vscode-button-hoverBackground);
|
||||
|
||||
--color-vscode-editorHoverWidget-foreground: var(--vscode-editorHoverWidget-foreground);
|
||||
--color-vscode-editorHoverWidget-background: var(--vscode-editorHoverWidget-background);
|
||||
--color-vscode-editorHoverWidget-border: var(--vscode-editorHoverWidget-border);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import posthog from "posthog-js"
|
||||
|
||||
import { TelemetrySetting } from "@roo/TelemetrySetting"
|
||||
import type { TelemetrySetting } from "@roo-code/types"
|
||||
|
||||
class TelemetryClient {
|
||||
private static instance: TelemetryClient
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import React from "react"
|
||||
import { render, RenderOptions } from "@testing-library/react"
|
||||
import { TooltipProvider } from "@/components/ui/tooltip"
|
||||
import { STANDARD_TOOLTIP_DELAY } from "@/components/ui/standard-tooltip"
|
||||
|
||||
import { TooltipProvider } from "@src/components/ui/tooltip"
|
||||
import { STANDARD_TOOLTIP_DELAY } from "@src/components/ui/standard-tooltip"
|
||||
|
||||
interface AllTheProvidersProps {
|
||||
children: React.ReactNode
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue