diff --git a/webview-ui/src/components/chat/ModeSelector.tsx b/webview-ui/src/components/chat/ModeSelector.tsx index 93dd2f1f4f..68f5c7beff 100644 --- a/webview-ui/src/components/chat/ModeSelector.tsx +++ b/webview-ui/src/components/chat/ModeSelector.tsx @@ -46,6 +46,22 @@ export const ModeSelector = ({ const { hasOpenedModeSelector, setHasOpenedModeSelector } = useExtensionState() const { t } = useAppTranslation() + // Helper to determine if a mode is custom and get its source + const getModeSource = (mode: ModeConfig): string | null => { + const isCustom = customModes?.some((m) => m.slug === mode.slug) + if (!isCustom) return null + return mode.source || "global" // Default to global if source is undefined + } + + // Helper to get display text for source + const getSourceDisplayText = (source: string | null, isShort: boolean = false): string => { + if (!source) return "" + if (isShort) { + return source === "global" ? t("chat:modeSelector.globalShort") : t("chat:modeSelector.projectShort") + } + return source === "global" ? t("chat:modeSelector.global") : t("chat:modeSelector.project") + } + const trackModeSelectorOpened = React.useCallback(() => { // Track telemetry every time the mode selector is opened telemetryClient.capture(TelemetryEventName.MODE_SELECTOR_OPENED) @@ -159,6 +175,13 @@ export const ModeSelector = ({ // Combine instruction text for tooltip const instructionText = `${t("chat:modeSelector.description")} ${modeShortcutText}` + // Helper function to render source indicator + const renderSourceIndicator = (mode: ModeConfig, isShort: boolean = false) => { + const source = getModeSource(mode) + if (!source) return null + return ({getSourceDisplayText(source, isShort)}) + } + const trigger = ( - {selectedMode?.name || ""} + + {selectedMode?.name || ""} + {selectedMode && renderSourceIndicator(selectedMode, false)} + ) @@ -238,7 +264,10 @@ export const ModeSelector = ({ )} data-testid="mode-selector-item">
-
{mode.name}
+
+ {mode.name} + {renderSourceIndicator(mode, true)} +
{mode.description && (
{mode.description} diff --git a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx index a829168893..827dcab432 100644 --- a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx @@ -21,7 +21,21 @@ vi.mock("@/context/ExtensionStateContext", () => ({ vi.mock("@/i18n/TranslationContext", () => ({ useAppTranslation: () => ({ - t: (key: string) => key, + t: (key: string) => { + const translations: Record = { + "chat:modeSelector.global": "Global", + "chat:modeSelector.project": "Project", + "chat:modeSelector.globalShort": "G", + "chat:modeSelector.projectShort": "P", + "chat:modeSelector.description": "Select a mode to change how the assistant responds.", + "chat:modeSelector.searchPlaceholder": "Search modes...", + "chat:modeSelector.noResults": "No modes found", + "chat:modeSelector.marketplace": "Browse Marketplace", + "chat:modeSelector.settings": "Mode Settings", + "chat:modeSelector.title": "Modes", + } + return translations[key] || key + }, }), })) @@ -93,7 +107,7 @@ describe("ModeSelector", () => { expect(screen.getByTestId("mode-search-input")).toBeInTheDocument() // Info icon should be visible - expect(screen.getByText("chat:modeSelector.title")).toBeInTheDocument() + expect(screen.getByText("Modes")).toBeInTheDocument() const infoIcon = document.querySelector(".codicon-info") expect(infoIcon).toBeInTheDocument() }) @@ -117,7 +131,7 @@ describe("ModeSelector", () => { expect(screen.queryByTestId("mode-search-input")).not.toBeInTheDocument() // Info blurb should be visible - expect(screen.getByText(/chat:modeSelector.description/)).toBeInTheDocument() + expect(screen.getByText(/Select a mode to change how the assistant responds./)).toBeInTheDocument() // Info icon should NOT be visible const infoIcon = document.querySelector(".codicon-info") @@ -169,7 +183,7 @@ describe("ModeSelector", () => { expect(screen.queryByTestId("mode-search-input")).not.toBeInTheDocument() // Info blurb should be visible instead - expect(screen.getByText(/chat:modeSelector.description/)).toBeInTheDocument() + expect(screen.getByText(/Select a mode to change how the assistant responds./)).toBeInTheDocument() // Info icon should NOT be visible const infoIcon = document.querySelector(".codicon-info") @@ -199,4 +213,129 @@ describe("ModeSelector", () => { const infoIcon = document.querySelector(".codicon-info") expect(infoIcon).toBeInTheDocument() }) + + test("shows source indicator for custom modes", () => { + const customModesWithSource: ModeConfig[] = [ + { + slug: "custom-global", + name: "Custom Global Mode", + roleDefinition: "Role", + groups: ["read"] as ModeConfig["groups"], + source: "global", + }, + { + slug: "custom-project", + name: "Custom Project Mode", + roleDefinition: "Role", + groups: ["read"] as ModeConfig["groups"], + source: "project", + }, + ] + + // Set up mock to return custom modes + mockModes = [ + ...customModesWithSource, + { + slug: "code", + name: "Code", + roleDefinition: "Role", + groups: ["read"] as ModeConfig["groups"], + }, + ] + + render( + , + ) + + // Check selected mode shows full indicator + const trigger = screen.getByTestId("mode-selector-trigger") + expect(trigger).toHaveTextContent("Custom Global Mode") + expect(trigger).toHaveTextContent("(Global)") + + // Open dropdown + fireEvent.click(trigger) + + // Check dropdown shows short indicators + const items = screen.getAllByTestId("mode-selector-item") + const globalItem = items.find((item) => item.textContent?.includes("Custom Global Mode")) + const projectItem = items.find((item) => item.textContent?.includes("Custom Project Mode")) + + expect(globalItem).toHaveTextContent("(G)") + expect(projectItem).toHaveTextContent("(P)") + }) + + test("shows no indicator for built-in modes", () => { + // Set up mock to return only built-in modes + mockModes = [ + { + slug: "code", + name: "Code", + roleDefinition: "Role", + groups: ["read"] as ModeConfig["groups"], + }, + { + slug: "architect", + name: "Architect", + roleDefinition: "Role", + groups: ["read"] as ModeConfig["groups"], + }, + ] + + render() + + const trigger = screen.getByTestId("mode-selector-trigger") + expect(trigger).toHaveTextContent("Code") + expect(trigger).not.toHaveTextContent("(") + + // Open dropdown + fireEvent.click(trigger) + + // Check that no items have indicators + const items = screen.getAllByTestId("mode-selector-item") + items.forEach((item) => { + expect(item).not.toHaveTextContent("(Global") + expect(item).not.toHaveTextContent("(Project") + }) + }) + + test("defaults to global for custom modes without source", () => { + const customModesNoSource: ModeConfig[] = [ + { + slug: "custom-old", + name: "Old Custom Mode", + roleDefinition: "Role", + groups: ["read"] as ModeConfig["groups"], + // No source field + }, + ] + + // Set up mock to include the custom mode + mockModes = [ + ...customModesNoSource, + { + slug: "code", + name: "Code", + roleDefinition: "Role", + groups: ["read"] as ModeConfig["groups"], + }, + ] + + render( + , + ) + + const trigger = screen.getByTestId("mode-selector-trigger") + expect(trigger).toHaveTextContent("Old Custom Mode") + expect(trigger).toHaveTextContent("(Global)") + }) }) 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..74c1116b56 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..cf4134d924 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..06dfef3917 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": "G", + "projectShort": "P" }, "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..cccd0d74fd 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": "G", + "projectShort": "P" }, "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..24106a9892 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": "G", + "projectShort": "P" }, "errorReadingFile": "讀取檔案時發生錯誤:", "noValidImages": "未處理到任何有效圖片",