diff --git a/webview-ui/src/components/chat/ModeSelector.tsx b/webview-ui/src/components/chat/ModeSelector.tsx index 336e9f83578..93dd2f1f4fd 100644 --- a/webview-ui/src/components/chat/ModeSelector.tsx +++ b/webview-ui/src/components/chat/ModeSelector.tsx @@ -1,5 +1,5 @@ import React from "react" -import { ChevronUp, Check } from "lucide-react" +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" @@ -11,6 +11,10 @@ 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" + +// Minimum number of modes required to show search functionality +const SEARCH_THRESHOLD = 6 interface ModeSelectorProps { value: Mode @@ -21,6 +25,7 @@ interface ModeSelectorProps { modeShortcutText: string customModes?: ModeConfig[] customModePrompts?: CustomModePrompts + disableSearch?: boolean } export const ModeSelector = ({ @@ -32,13 +37,16 @@ export const ModeSelector = ({ modeShortcutText, customModes, customModePrompts, + disableSearch = false, }: ModeSelectorProps) => { const [open, setOpen] = React.useState(false) + const [searchValue, setSearchValue] = React.useState("") + const searchInputRef = React.useRef(null) const portalContainer = useRooPortal("roo-portal") const { hasOpenedModeSelector, setHasOpenedModeSelector } = useExtensionState() const { t } = useAppTranslation() - const trackModeSelectorOpened = () => { + const trackModeSelectorOpened = React.useCallback(() => { // Track telemetry every time the mode selector is opened telemetryClient.capture(TelemetryEventName.MODE_SELECTOR_OPENED) @@ -47,7 +55,7 @@ export const ModeSelector = ({ setHasOpenedModeSelector(true) vscode.postMessage({ type: "hasOpenedModeSelector", bool: true }) } - } + }, [hasOpenedModeSelector, setHasOpenedModeSelector]) // Get all modes including custom modes and merge custom prompt descriptions const modes = React.useMemo(() => { @@ -61,6 +69,96 @@ export const ModeSelector = ({ // 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 + const nameSearchItems = React.useMemo(() => { + return modes.map((mode) => ({ + original: mode, + searchStr: [mode.name, mode.slug].filter(Boolean).join(" "), + })) + }, [modes]) + + const descriptionSearchItems = React.useMemo(() => { + return modes.map((mode) => ({ + original: mode, + searchStr: mode.description || "", + })) + }, [modes]) + + // Create memoized Fzf instances for name and description searches + const nameFzfInstance = React.useMemo(() => { + return new Fzf(nameSearchItems, { + selector: (item) => item.searchStr, + }) + }, [nameSearchItems]) + + const descriptionFzfInstance = React.useMemo(() => { + return new Fzf(descriptionSearchItems, { + selector: (item) => item.searchStr, + }) + }, [descriptionSearchItems]) + + // Filter modes based on search value using fuzzy search with priority + const filteredModes = React.useMemo(() => { + if (!searchValue) return modes + + // 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 + const descriptionMatches = descriptionFzfInstance.find(searchValue) + + // Combine results: name matches first, then description matches + const combinedResults = [ + ...nameMatches.map((result) => result.item.original), + ...descriptionMatches + .filter((result) => !nameMatchedModes.has(result.item.original.slug)) + .map((result) => result.item.original), + ] + + return combinedResults + }, [modes, searchValue, nameFzfInstance, descriptionFzfInstance]) + + const onClearSearch = React.useCallback(() => { + setSearchValue("") + searchInputRef.current?.focus() + }, []) + + const handleSelect = React.useCallback( + (modeSlug: string) => { + onChange(modeSlug as Mode) + setOpen(false) + // Clear search after selection + setSearchValue("") + }, + [onChange], + ) + + const onOpenChange = React.useCallback( + (isOpen: boolean) => { + if (isOpen) trackModeSelectorOpened() + setOpen(isOpen) + // Clear search when closing + if (!isOpen) { + setSearchValue("") + } + }, + [trackModeSelectorOpened], + ) + + // Auto-focus search input when popover opens + React.useEffect(() => { + if (open && searchInputRef.current) { + searchInputRef.current.focus() + } + }, [open]) + + // Determine if search should be shown + const showSearch = !disableSearch && modes.length > SEARCH_THRESHOLD + + // Combine instruction text for tooltip + const instructionText = `${t("chat:modeSelector.description")} ${modeShortcutText}` + const trigger = ( { - if (isOpen) trackModeSelectorOpened() - setOpen(isOpen) - }} - data-testid="mode-selector-root"> + {title ? {trigger} : trigger}
-
-
-

{t("chat:modeSelector.title")}

-
- { - window.postMessage( - { - type: "action", - action: "marketplaceButtonClicked", - values: { marketplaceTab: "mode" }, - }, - "*", - ) - - setOpen(false) - }} - /> - { - vscode.postMessage({ - type: "switchTab", - tab: "modes", - }) - setOpen(false) - }} - /> -
+ {/* Show search bar only when there are more than SEARCH_THRESHOLD items, otherwise show info blurb */} + {showSearch ? ( +
+ setSearchValue(e.target.value)} + placeholder={t("chat:modeSelector.searchPlaceholder")} + className="w-full h-8 px-2 py-1 text-xs bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded focus:outline-0" + data-testid="mode-search-input" + /> + {searchValue.length > 0 && ( +
+ +
+ )}
-

- {t("chat:modeSelector.description")} -
- {modeShortcutText} -

-
+ ) : ( +
+

{instructionText}

+
+ )} {/* Mode List */} -
- {modes.map((mode) => ( -
+ {filteredModes.length === 0 && searchValue ? ( +
+ {t("chat:modeSelector.noResults")} +
+ ) : ( +
+ {filteredModes.map((mode) => ( +
handleSelect(mode.slug)} + className={cn( + "px-3 py-1.5 text-sm cursor-pointer flex items-center", + "hover:bg-vscode-list-hoverBackground", + mode.slug === value + ? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground" + : "", + )} + data-testid="mode-selector-item"> +
+
{mode.name}
+ {mode.description && ( +
+ {mode.description} +
+ )} +
+ {mode.slug === value && } +
+ ))} +
+ )} +
+ + {/* Bottom bar with buttons on left and title on right */} +
+
+ { - onChange(mode.slug as Mode) + window.postMessage( + { + type: "action", + action: "marketplaceButtonClicked", + values: { marketplaceTab: "mode" }, + }, + "*", + ) setOpen(false) }} - data-testid="mode-selector-item"> -
-

{mode.name}

- {mode.description && ( -

- {mode.description} -

- )} -
- {mode.slug === value ? ( - - ) : ( -
- )} -
- ))} + /> + { + vscode.postMessage({ + type: "switchTab", + tab: "modes", + }) + setOpen(false) + }} + /> +
+ + {/* Info icon and title on the right - only show info icon when search bar is visible */} +
+ {showSearch && ( + + + + )} +

+ {t("chat:modeSelector.title")} +

+
diff --git a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx index d6fc81368d1..a8291688936 100644 --- a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx @@ -1,8 +1,9 @@ import React from "react" -import { render, screen } from "@/utils/test-utils" +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 vi.mock("@/utils/vscode", () => ({ @@ -28,6 +29,23 @@ vi.mock("@/components/ui/hooks/useRooPortal", () => ({ useRooPortal: () => document.body, })) +vi.mock("@/utils/TelemetryClient", () => ({ + telemetryClient: { + capture: vi.fn(), + }, +})) + +// Create a variable to control what getAllModes returns +let mockModes: ModeConfig[] = [] + +vi.mock("@roo/modes", async () => { + const actual = await vi.importActual("@roo/modes") + return { + ...actual, + getAllModes: () => mockModes, + } +}) + describe("ModeSelector", () => { test("shows custom description from customModePrompts", () => { const customModePrompts = { @@ -55,4 +73,130 @@ describe("ModeSelector", () => { // 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}`, + description: `Description for mode ${i}`, + roleDefinition: "Role definition", + groups: ["read", "edit"], + })) + + render() + + // 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 + 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}`, + description: `Description for mode ${i}`, + roleDefinition: "Role definition", + groups: ["read", "edit"], + })) + + render() + + // Click to open the popover + fireEvent.click(screen.getByTestId("mode-selector-trigger")) + + // Search input should NOT be visible + expect(screen.queryByTestId("mode-search-input")).not.toBeInTheDocument() + + // Info blurb should be visible + expect(screen.getByText(/chat:modeSelector.description/)).toBeInTheDocument() + + // 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}`, + description: `Description for mode ${i}`, + roleDefinition: "Role definition", + groups: ["read", "edit"], + })) + + render() + + // Click to open the popover + fireEvent.click(screen.getByTestId("mode-selector-trigger")) + + // Type in search + const searchInput = screen.getByTestId("mode-search-input") + fireEvent.change(searchInput, { target: { value: "Mode 3" } }) + + // Should show filtered results + const modeItems = screen.getAllByTestId("mode-selector-item") + 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}`, + description: `Description for mode ${i}`, + roleDefinition: "Role definition", + groups: ["read", "edit"], + })) + + render( + , + ) + + // Click to open the popover + fireEvent.click(screen.getByTestId("mode-selector-trigger")) + + // Search input should NOT be visible even with 10 modes + expect(screen.queryByTestId("mode-search-input")).not.toBeInTheDocument() + + // Info blurb should be visible instead + expect(screen.getByText(/chat:modeSelector.description/)).toBeInTheDocument() + + // 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}`, + description: `Description for mode ${i}`, + roleDefinition: "Role definition", + groups: ["read", "edit"], + })) + + // Don't pass disableSearch prop (should default to false) + render() + + // 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() + }) }) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 01d9ee1c1a8..9bd10e1972e 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -116,7 +116,9 @@ "title": "Modes", "marketplace": "Marketplace de Modes", "settings": "Configuració de Modes", - "description": "Personalitats especialitzades que adapten el comportament de Roo." + "description": "Personalitats especialitzades que adapten el comportament de Roo.", + "searchPlaceholder": "Cerca modes...", + "noResults": "No s'han trobat resultats" }, "errorReadingFile": "Error en llegir el fitxer:", "noValidImages": "No s'ha processat cap imatge vàlida", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 032145234d9..27158a2da45 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -116,7 +116,9 @@ "title": "Modi", "marketplace": "Modus-Marketplace", "settings": "Modus-Einstellungen", - "description": "Spezialisierte Personas, die Roos Verhalten anpassen." + "description": "Spezialisierte Personas, die Roos Verhalten anpassen.", + "searchPlaceholder": "Modi suchen...", + "noResults": "Keine Ergebnisse gefunden" }, "errorReadingFile": "Fehler beim Lesen der Datei:", "noValidImages": "Keine gültigen Bilder wurden verarbeitet", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 3bbb3fbf72f..7a8870db4ac 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -118,7 +118,9 @@ "title": "Modes", "marketplace": "Mode Marketplace", "settings": "Mode Settings", - "description": "Specialized personas that tailor Roo's behavior." + "description": "Specialized personas that tailor Roo's behavior.", + "searchPlaceholder": "Search modes...", + "noResults": "No results found" }, "enhancePromptDescription": "The 'Enhance Prompt' button helps improve your prompt by providing additional context, clarification, or rephrasing. Try typing a prompt in here and clicking the button again to see how it works.", "addImages": "Add images to message", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index adcfd1d40cd..17cac725158 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -116,7 +116,9 @@ "title": "Modos", "marketplace": "Marketplace de Modos", "settings": "Configuración de Modos", - "description": "Personalidades especializadas que adaptan el comportamiento de Roo." + "description": "Personalidades especializadas que adaptan el comportamiento de Roo.", + "searchPlaceholder": "Buscar modos...", + "noResults": "No se encontraron resultados" }, "errorReadingFile": "Error al leer el archivo:", "noValidImages": "No se procesaron imágenes válidas", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 3e49a648677..2e552c32c01 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -116,7 +116,9 @@ "title": "Modes", "marketplace": "Marketplace de Modes", "settings": "Paramètres des Modes", - "description": "Personas spécialisés qui adaptent le comportement de Roo." + "description": "Personas spécialisés qui adaptent le comportement de Roo.", + "searchPlaceholder": "Rechercher des modes...", + "noResults": "Aucun résultat trouvé" }, "errorReadingFile": "Erreur lors de la lecture du fichier :", "noValidImages": "Aucune image valide n'a été traitée", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 3b5c7b8a67c..530d33ac4cb 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -116,7 +116,9 @@ "title": "मोड्स", "marketplace": "मोड मार्केटप्लेस", "settings": "मोड सेटिंग्स", - "description": "विशेष व्यक्तित्व जो Roo के व्यवहार को अनुकूलित करते हैं।" + "description": "विशेष व्यक्तित्व जो Roo के व्यवहार को अनुकूलित करते हैं।", + "searchPlaceholder": "मोड खोजें...", + "noResults": "कोई परिणाम नहीं मिला" }, "errorReadingFile": "फ़ाइल पढ़ने में त्रुटि:", "noValidImages": "कोई मान्य चित्र प्रोसेस नहीं किया गया", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 2ef1cb75e7d..1e89d4f1178 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -122,7 +122,9 @@ "title": "Mode", "marketplace": "Marketplace Mode", "settings": "Pengaturan Mode", - "description": "Persona khusus yang menyesuaikan perilaku Roo." + "description": "Persona khusus yang menyesuaikan perilaku Roo.", + "searchPlaceholder": "Cari mode...", + "noResults": "Tidak ada hasil yang ditemukan" }, "addImages": "Tambahkan gambar ke pesan", "sendMessage": "Kirim pesan", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index eb3984f1bed..a1efb4c9e52 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -116,7 +116,9 @@ "title": "Modalità", "marketplace": "Marketplace delle Modalità", "settings": "Impostazioni Modalità", - "description": "Personalità specializzate che adattano il comportamento di Roo." + "description": "Personalità specializzate che adattano il comportamento di Roo.", + "searchPlaceholder": "Cerca modalità...", + "noResults": "Nessun risultato trovato" }, "errorReadingFile": "Errore nella lettura del file:", "noValidImages": "Nessuna immagine valida è stata elaborata", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 2f6e6bfab73..88130ce6ac8 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -116,7 +116,9 @@ "title": "モード", "marketplace": "モードマーケットプレイス", "settings": "モード設定", - "description": "Rooの動作をカスタマイズする専門的なペルソナ。" + "description": "Rooの動作をカスタマイズする専門的なペルソナ。", + "searchPlaceholder": "モードを検索...", + "noResults": "結果が見つかりません" }, "errorReadingFile": "ファイル読み込みエラー:", "noValidImages": "有効な画像が処理されませんでした", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index f4a5c336026..778600d1ec9 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -116,7 +116,9 @@ "title": "모드", "marketplace": "모드 마켓플레이스", "settings": "모드 설정", - "description": "Roo의 행동을 맞춤화하는 전문화된 페르소나." + "description": "Roo의 행동을 맞춤화하는 전문화된 페르소나.", + "searchPlaceholder": "모드 검색...", + "noResults": "결과를 찾을 수 없습니다" }, "errorReadingFile": "파일 읽기 오류:", "noValidImages": "처리된 유효한 이미지가 없습니다", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 1d11db26680..f6ed85a7b48 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -108,7 +108,9 @@ "title": "Modi", "marketplace": "Modus Marktplaats", "settings": "Modus Instellingen", - "description": "Gespecialiseerde persona's die het gedrag van Roo aanpassen." + "description": "Gespecialiseerde persona's die het gedrag van Roo aanpassen.", + "searchPlaceholder": "Zoek modi...", + "noResults": "Geen resultaten gevonden" }, "addImages": "Afbeeldingen toevoegen aan bericht", "sendMessage": "Bericht verzenden", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 88c58418ad4..00eb511d80e 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -116,7 +116,9 @@ "title": "Tryby", "marketplace": "Marketplace Trybów", "settings": "Ustawienia Trybów", - "description": "Wyspecjalizowane persony, które dostosowują zachowanie Roo." + "description": "Wyspecjalizowane persony, które dostosowują zachowanie Roo.", + "searchPlaceholder": "Szukaj trybów...", + "noResults": "Nie znaleziono wyników" }, "errorReadingFile": "Błąd odczytu pliku:", "noValidImages": "Nie przetworzono żadnych prawidłowych obrazów", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 3784d6cc64d..f9eb87ae036 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -116,7 +116,9 @@ "title": "Modos", "marketplace": "Marketplace de Modos", "settings": "Configurações de Modos", - "description": "Personas especializadas que adaptam o comportamento do Roo." + "description": "Personas especializadas que adaptam o comportamento do Roo.", + "searchPlaceholder": "Pesquisar modos...", + "noResults": "Nenhum resultado encontrado" }, "errorReadingFile": "Erro ao ler arquivo:", "noValidImages": "Nenhuma imagem válida foi processada", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 0660d3e1d6d..9e3ee5f5364 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -108,7 +108,9 @@ "title": "Режимы", "marketplace": "Маркетплейс режимов", "settings": "Настройки режимов", - "description": "Специализированные персоны, которые настраивают поведение Roo." + "description": "Специализированные персоны, которые настраивают поведение Roo.", + "searchPlaceholder": "Поиск режимов...", + "noResults": "Ничего не найдено" }, "addImages": "Добавить изображения к сообщению", "sendMessage": "Отправить сообщение", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 75bc126dffd..bd0c984295c 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -116,7 +116,9 @@ "title": "Modlar", "marketplace": "Mod Pazaryeri", "settings": "Mod Ayarları", - "description": "Roo'nun davranışını özelleştiren uzmanlaşmış kişilikler." + "description": "Roo'nun davranışını özelleştiren uzmanlaşmış kişilikler.", + "searchPlaceholder": "Modları ara...", + "noResults": "Sonuç bulunamadı" }, "errorReadingFile": "Dosya okuma hatası:", "noValidImages": "Hiçbir geçerli resim işlenmedi", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 944eabcb942..83f033b0454 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -116,7 +116,9 @@ "title": "Chế độ", "marketplace": "Chợ Chế độ", "settings": "Cài đặt Chế độ", - "description": "Các nhân cách chuyên biệt điều chỉnh hành vi của Roo." + "description": "Các nhân cách chuyên biệt điều chỉnh hành vi của Roo.", + "searchPlaceholder": "Tìm kiếm chế độ...", + "noResults": "Không tìm thấy kết quả nào" }, "errorReadingFile": "Lỗi khi đọc tệp:", "noValidImages": "Không có hình ảnh hợp lệ nào được xử lý", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 616cd14fec1..bbc7f034634 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -116,7 +116,9 @@ "title": "模式", "marketplace": "模式市场", "settings": "模式设置", - "description": "专门定制Roo行为的角色。" + "description": "专门定制Roo行为的角色。", + "searchPlaceholder": "搜索模式...", + "noResults": "未找到结果" }, "errorReadingFile": "读取文件时出错:", "noValidImages": "没有处理有效图片", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 662421900ed..b20d1642a0e 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -116,7 +116,9 @@ "title": "模式", "marketplace": "模式市集", "settings": "模式設定", - "description": "專門定制Roo行為的角色。" + "description": "專門定制Roo行為的角色。", + "searchPlaceholder": "搜尋模式...", + "noResults": "沒有找到結果" }, "errorReadingFile": "讀取檔案時發生錯誤:", "noValidImages": "未處理到任何有效圖片",