diff --git a/webview-ui/src/components/chat/ModeSelector.tsx b/webview-ui/src/components/chat/ModeSelector.tsx index 93dd2f1f4f..ef8ea2dddc 100644 --- a/webview-ui/src/components/chat/ModeSelector.tsx +++ b/webview-ui/src/components/chat/ModeSelector.tsx @@ -7,7 +7,7 @@ import { IconButton } from "./IconButton" import { vscode } from "@/utils/vscode" import { useExtensionState } from "@/context/ExtensionStateContext" import { useAppTranslation } from "@/i18n/TranslationContext" -import { Mode, getAllModes } from "@roo/modes" +import { Mode, getAllModes, isCustomMode } from "@roo/modes" import { ModeConfig, CustomModePrompts } from "@roo-code/types" import { telemetryClient } from "@/utils/TelemetryClient" import { TelemetryEventName } from "@roo-code/types" @@ -16,6 +16,32 @@ import { Fzf } from "fzf" // Minimum number of modes required to show search functionality const SEARCH_THRESHOLD = 6 +// Helper function to get the source of a custom mode +function getModeSource(mode: ModeConfig, customModes?: ModeConfig[]): "global" | "project" | null { + if (!isCustomMode(mode.slug, customModes)) { + return null // Built-in mode, no source indicator needed + } + + // Find the mode in customModes to get its source + const customMode = customModes?.find((m) => m.slug === mode.slug) + return customMode?.source || "global" // Default to global if source is not specified +} + +// Helper function to get the display text for mode source +function getSourceDisplayText( + source: "global" | "project" | null, + t: (key: string) => string, + short: boolean = false, +): string { + if (!source) return "" + + if (short) { + return source === "global" ? t("chat:modeSelector.globalShort") : t("chat:modeSelector.projectShort") + } + + return source === "global" ? t("chat:modeSelector.global") : t("chat:modeSelector.project") +} + interface ModeSelectorProps { value: Mode onChange: (value: Mode) => void @@ -159,6 +185,10 @@ export const ModeSelector = ({ // Combine instruction text for tooltip const instructionText = `${t("chat:modeSelector.description")} ${modeShortcutText}` + // Get source indicator for selected mode + const selectedModeSource = selectedMode ? getModeSource(selectedMode, customModes) : null + const selectedModeSourceText = getSourceDisplayText(selectedModeSource, t, false) + const trigger = ( - {selectedMode?.name || ""} + + {selectedMode?.name || ""} + {selectedModeSourceText && ( + ({selectedModeSourceText}) + )} + ) @@ -225,29 +260,41 @@ export const ModeSelector = ({ ) : (
- {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} -
+ {filteredModes.map((mode) => { + const modeSource = getModeSource(mode, customModes) + const sourceShortText = getSourceDisplayText(modeSource, t, true) + + return ( +
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} + {sourceShortText && ( + + ({sourceShortText}) + + )} +
+ {mode.description && ( +
+ {mode.description} +
+ )} +
+ {mode.slug === value && }
- {mode.slug === value && } -
- ))} + ) + })}
)}
diff --git a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx index a829168893..ff97306165 100644 --- a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx @@ -199,4 +199,140 @@ describe("ModeSelector", () => { const infoIcon = document.querySelector(".codicon-info") expect(infoIcon).toBeInTheDocument() }) + + test("shows source indicators for custom modes", () => { + // Set up mock to return custom modes with source + mockModes = [ + { + slug: "custom-global", + name: "Custom Global Mode", + description: "A global custom mode", + roleDefinition: "Role definition", + groups: ["read", "edit"] as const, + source: "global", + }, + { + slug: "custom-project", + name: "Custom Project Mode", + description: "A project custom mode", + roleDefinition: "Role definition", + groups: ["read", "edit"] as const, + source: "project", + }, + { + slug: "code", + name: "Code Mode", + description: "Built-in code mode", + roleDefinition: "Role definition", + groups: ["read", "edit"] as const, + }, + ] + + const customModes: ModeConfig[] = [ + { + slug: "custom-global", + name: "Custom Global Mode", + description: "A global custom mode", + roleDefinition: "Role definition", + groups: ["read", "edit"], + source: "global", + }, + { + slug: "custom-project", + name: "Custom Project Mode", + description: "A project custom mode", + roleDefinition: "Role definition", + groups: ["read", "edit"], + source: "project", + }, + ] + + render( + , + ) + + // Click to open the popover + fireEvent.click(screen.getByTestId("mode-selector-trigger")) + + // Check that custom modes show source indicators in dropdown + const modeItems = screen.getAllByTestId("mode-selector-item") + + // Find the custom modes in the dropdown + const globalModeItem = modeItems.find((item) => item.textContent?.includes("Custom Global Mode")) + const projectModeItem = modeItems.find((item) => item.textContent?.includes("Custom Project Mode")) + const builtinModeItem = modeItems.find((item) => item.textContent?.includes("Code Mode")) + + // Custom modes should show source indicators + expect(globalModeItem?.textContent).toContain("(chat:modeSelector.globalShort)") + expect(projectModeItem?.textContent).toContain("(chat:modeSelector.projectShort)") + + // Built-in mode should not show source indicator + expect(builtinModeItem?.textContent).not.toContain("(chat:modeSelector.globalShort)") + expect(builtinModeItem?.textContent).not.toContain("(chat:modeSelector.projectShort)") + }) + + test("shows source indicator in selected mode button", () => { + // Set up mock to return custom modes with source + mockModes = [ + { + slug: "custom-project", + name: "Custom Project Mode", + description: "A project custom mode", + roleDefinition: "Role definition", + groups: ["read", "edit"] as const, + source: "project", + }, + ] + + const customModes: ModeConfig[] = [ + { + slug: "custom-project", + name: "Custom Project Mode", + description: "A project custom mode", + roleDefinition: "Role definition", + groups: ["read", "edit"], + source: "project", + }, + ] + + render( + , + ) + + // Check that the trigger button shows the source indicator + const trigger = screen.getByTestId("mode-selector-trigger") + expect(trigger.textContent).toContain("Custom Project Mode") + expect(trigger.textContent).toContain("(chat:modeSelector.project)") + }) + + test("does not show source indicator for built-in modes", () => { + // Set up mock to return only built-in modes + mockModes = [ + { + slug: "code", + name: "Code Mode", + description: "Built-in code mode", + roleDefinition: "Role definition", + groups: ["read", "edit"] as const, + }, + ] + + render() + + // Check that the trigger button does not show source indicator + const trigger = screen.getByTestId("mode-selector-trigger") + expect(trigger.textContent).toContain("Code Mode") + expect(trigger.textContent).not.toContain("(Global)") + expect(trigger.textContent).not.toContain("(Project)") + }) }) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index da0eb00a44..3f3d0bf91b 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -118,7 +118,11 @@ "settings": "Configuració de Modes", "description": "Personalitats especialitzades que adapten el comportament de Roo.", "searchPlaceholder": "Cerca modes...", - "noResults": "No s'han trobat resultats" + "noResults": "No s'han trobat resultats", + "global": "Global", + "project": "Projecte", + "globalShort": "G", + "projectShort": "P" }, "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 36c03c9309..e22e4faae4 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -118,7 +118,11 @@ "settings": "Modus-Einstellungen", "description": "Spezialisierte Personas, die Roos Verhalten anpassen.", "searchPlaceholder": "Modi suchen...", - "noResults": "Keine Ergebnisse gefunden" + "noResults": "Keine Ergebnisse gefunden", + "global": "Global", + "project": "Projekt", + "globalShort": "G", + "projectShort": "P" }, "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 b33ddd4ab4..33a11a2d7f 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -120,7 +120,11 @@ "settings": "Mode Settings", "description": "Specialized personas that tailor Roo's behavior.", "searchPlaceholder": "Search modes...", - "noResults": "No results found" + "noResults": "No results found", + "global": "Global", + "project": "Project", + "globalShort": "G", + "projectShort": "P" }, "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 156af85012..a1237b5861 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -118,7 +118,11 @@ "settings": "Configuración de Modos", "description": "Personalidades especializadas que adaptan el comportamiento de Roo.", "searchPlaceholder": "Buscar modos...", - "noResults": "No se encontraron resultados" + "noResults": "No se encontraron resultados", + "global": "Global", + "project": "Proyecto", + "globalShort": "G", + "projectShort": "P" }, "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 959db06b39..cab2746290 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -118,7 +118,11 @@ "settings": "Paramètres des Modes", "description": "Personas spécialisés qui adaptent le comportement de Roo.", "searchPlaceholder": "Rechercher des modes...", - "noResults": "Aucun résultat trouvé" + "noResults": "Aucun résultat trouvé", + "global": "Global", + "project": "Projet", + "globalShort": "G", + "projectShort": "P" }, "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 21d81e4b88..21a257588e 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -118,7 +118,11 @@ "settings": "मोड सेटिंग्स", "description": "विशेष व्यक्तित्व जो Roo के व्यवहार को अनुकूलित करते हैं।", "searchPlaceholder": "मोड खोजें...", - "noResults": "कोई परिणाम नहीं मिला" + "noResults": "कोई परिणाम नहीं मिला", + "global": "ग्लोबल", + "project": "प्रोजेक्ट", + "globalShort": "G", + "projectShort": "P" }, "errorReadingFile": "फ़ाइल पढ़ने में त्रुटि:", "noValidImages": "कोई मान्य चित्र प्रोसेस नहीं किया गया", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index fd594480a8..d734709f3c 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -124,7 +124,11 @@ "settings": "Pengaturan Mode", "description": "Persona khusus yang menyesuaikan perilaku Roo.", "searchPlaceholder": "Cari mode...", - "noResults": "Tidak ada hasil yang ditemukan" + "noResults": "Tidak ada hasil yang ditemukan", + "global": "Global", + "project": "Proyek", + "globalShort": "G", + "projectShort": "P" }, "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 5b92e06322..e58555bdf4 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -118,7 +118,11 @@ "settings": "Impostazioni Modalità", "description": "Personalità specializzate che adattano il comportamento di Roo.", "searchPlaceholder": "Cerca modalità...", - "noResults": "Nessun risultato trovato" + "noResults": "Nessun risultato trovato", + "global": "Globale", + "project": "Progetto", + "globalShort": "G", + "projectShort": "P" }, "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 0ed516f2b7..db9454f68f 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -118,7 +118,11 @@ "settings": "モード設定", "description": "Rooの動作をカスタマイズする専門的なペルソナ。", "searchPlaceholder": "モードを検索...", - "noResults": "結果が見つかりません" + "noResults": "結果が見つかりません", + "global": "グローバル", + "project": "プロジェクト", + "globalShort": "G", + "projectShort": "P" }, "errorReadingFile": "ファイル読み込みエラー:", "noValidImages": "有効な画像が処理されませんでした", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 95f783b085..2c847981c8 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -118,7 +118,11 @@ "settings": "모드 설정", "description": "Roo의 행동을 맞춤화하는 전문화된 페르소나.", "searchPlaceholder": "모드 검색...", - "noResults": "결과를 찾을 수 없습니다" + "noResults": "결과를 찾을 수 없습니다", + "global": "글로벌", + "project": "프로젝트", + "globalShort": "G", + "projectShort": "P" }, "errorReadingFile": "파일 읽기 오류:", "noValidImages": "처리된 유효한 이미지가 없습니다", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 4bfaf467f6..72f4f5d610 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -110,7 +110,11 @@ "settings": "Modus Instellingen", "description": "Gespecialiseerde persona's die het gedrag van Roo aanpassen.", "searchPlaceholder": "Zoek modi...", - "noResults": "Geen resultaten gevonden" + "noResults": "Geen resultaten gevonden", + "global": "Globaal", + "project": "Project", + "globalShort": "G", + "projectShort": "P" }, "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 10ee653582..ab7e32ef79 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -118,7 +118,11 @@ "settings": "Ustawienia Trybów", "description": "Wyspecjalizowane persony, które dostosowują zachowanie Roo.", "searchPlaceholder": "Szukaj trybów...", - "noResults": "Nie znaleziono wyników" + "noResults": "Nie znaleziono wyników", + "global": "Globalny", + "project": "Projekt", + "globalShort": "G", + "projectShort": "P" }, "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 b286f5c0ad..9f956c13ea 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -118,7 +118,11 @@ "settings": "Configurações de Modos", "description": "Personas especializadas que adaptam o comportamento do Roo.", "searchPlaceholder": "Pesquisar modos...", - "noResults": "Nenhum resultado encontrado" + "noResults": "Nenhum resultado encontrado", + "global": "Global", + "project": "Projeto", + "globalShort": "G", + "projectShort": "P" }, "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 af3e9aadf8..3ab66f217e 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -110,7 +110,11 @@ "settings": "Настройки режимов", "description": "Специализированные персоны, которые настраивают поведение Roo.", "searchPlaceholder": "Поиск режимов...", - "noResults": "Ничего не найдено" + "noResults": "Ничего не найдено", + "global": "Глобальный", + "project": "Проект", + "globalShort": "G", + "projectShort": "P" }, "addImages": "Добавить изображения к сообщению", "sendMessage": "Отправить сообщение", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index e6868b5db1..238f695f3d 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -118,7 +118,11 @@ "settings": "Mod Ayarları", "description": "Roo'nun davranışını özelleştiren uzmanlaşmış kişilikler.", "searchPlaceholder": "Modları ara...", - "noResults": "Sonuç bulunamadı" + "noResults": "Sonuç bulunamadı", + "global": "Genel", + "project": "Proje", + "globalShort": "G", + "projectShort": "P" }, "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 2b86060ceb..a5b2b0a56e 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -118,7 +118,11 @@ "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.", "searchPlaceholder": "Tìm kiếm chế độ...", - "noResults": "Không tìm thấy kết quả nào" + "noResults": "Không tìm thấy kết quả nào", + "global": "Toàn cục", + "project": "Dự án", + "globalShort": "TC", + "projectShort": "DA" }, "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 dc21acee0b..7bfb394938 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -118,7 +118,11 @@ "settings": "模式设置", "description": "专门定制Roo行为的角色。", "searchPlaceholder": "搜索模式...", - "noResults": "未找到结果" + "noResults": "未找到结果", + "global": "全局", + "project": "项目", + "globalShort": "全", + "projectShort": "项" }, "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 fc38009186..4e6d2b8872 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -118,7 +118,11 @@ "settings": "模式設定", "description": "專門定制Roo行為的角色。", "searchPlaceholder": "搜尋模式...", - "noResults": "沒有找到結果" + "noResults": "沒有找到結果", + "global": "全域", + "project": "專案", + "globalShort": "全", + "projectShort": "專" }, "errorReadingFile": "讀取檔案時發生錯誤:", "noValidImages": "未處理到任何有效圖片",