mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
feat: Add search functionality to mode selector popup and reorganize layout (#6140)
Co-authored-by: Roo Code Translate Mode Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
This commit is contained in:
parent
5d4e3819e4
commit
8625b451f4
20 changed files with 399 additions and 95 deletions
|
|
@ -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<HTMLInputElement>(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 = (
|
||||
<PopoverTrigger
|
||||
disabled={disabled}
|
||||
|
|
@ -83,13 +181,7 @@ export const ModeSelector = ({
|
|||
)
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={(isOpen) => {
|
||||
if (isOpen) trackModeSelectorOpened()
|
||||
setOpen(isOpen)
|
||||
}}
|
||||
data-testid="mode-selector-root">
|
||||
<Popover open={open} onOpenChange={onOpenChange} data-testid="mode-selector-root">
|
||||
{title ? <StandardTooltip content={title}>{trigger}</StandardTooltip> : trigger}
|
||||
|
||||
<PopoverContent
|
||||
|
|
@ -98,78 +190,110 @@ export const ModeSelector = ({
|
|||
container={portalContainer}
|
||||
className="p-0 overflow-hidden min-w-80 max-w-9/10">
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="p-3 border-b border-vscode-dropdown-border cursor-default">
|
||||
<div className="flex flex-row items-center gap-1 p-0 mt-0 mb-1 w-full">
|
||||
<h4 className="m-0 pb-2 flex-1">{t("chat:modeSelector.title")}</h4>
|
||||
<div className="flex flex-row gap-1 ml-auto mb-1">
|
||||
<IconButton
|
||||
iconClass="codicon-extensions"
|
||||
title={t("chat:modeSelector.marketplace")}
|
||||
onClick={() => {
|
||||
window.postMessage(
|
||||
{
|
||||
type: "action",
|
||||
action: "marketplaceButtonClicked",
|
||||
values: { marketplaceTab: "mode" },
|
||||
},
|
||||
"*",
|
||||
)
|
||||
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
iconClass="codicon-settings-gear"
|
||||
title={t("chat:modeSelector.settings")}
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "switchTab",
|
||||
tab: "modes",
|
||||
})
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* Show search bar only when there are more than SEARCH_THRESHOLD items, otherwise show info blurb */}
|
||||
{showSearch ? (
|
||||
<div className="relative p-2 border-b border-vscode-dropdown-border">
|
||||
<input
|
||||
aria-label="Search modes"
|
||||
ref={searchInputRef}
|
||||
value={searchValue}
|
||||
onChange={(e) => 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 && (
|
||||
<div className="absolute right-4 top-0 bottom-0 flex items-center justify-center">
|
||||
<X
|
||||
className="text-vscode-input-foreground opacity-50 hover:opacity-100 size-4 p-0.5 cursor-pointer"
|
||||
onClick={onClearSearch}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="my-0 pr-4 text-sm w-full">
|
||||
{t("chat:modeSelector.description")}
|
||||
<br />
|
||||
{modeShortcutText}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-3 border-b border-vscode-dropdown-border">
|
||||
<p className="m-0 text-xs text-vscode-descriptionForeground">{instructionText}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mode List */}
|
||||
<div className="max-h-[400px] overflow-y-auto py-0">
|
||||
{modes.map((mode) => (
|
||||
<div
|
||||
className={cn(
|
||||
"p-2 text-sm cursor-pointer flex flex-row gap-4 items-center",
|
||||
"hover:bg-vscode-list-hoverBackground",
|
||||
mode.slug === value
|
||||
? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground"
|
||||
: "",
|
||||
)}
|
||||
key={mode.slug}
|
||||
<div className="max-h-[300px] overflow-y-auto">
|
||||
{filteredModes.length === 0 && searchValue ? (
|
||||
<div className="py-2 px-3 text-sm text-vscode-foreground/70">
|
||||
{t("chat:modeSelector.noResults")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-1">
|
||||
{filteredModes.map((mode) => (
|
||||
<div
|
||||
key={mode.slug}
|
||||
onClick={() => 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">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-bold truncate">{mode.name}</div>
|
||||
{mode.description && (
|
||||
<div className="text-xs text-vscode-descriptionForeground truncate">
|
||||
{mode.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{mode.slug === value && <Check className="ml-auto size-4 p-0.5" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Bottom bar with buttons on left and title on right */}
|
||||
<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-extensions"
|
||||
title={t("chat:modeSelector.marketplace")}
|
||||
onClick={() => {
|
||||
onChange(mode.slug as Mode)
|
||||
window.postMessage(
|
||||
{
|
||||
type: "action",
|
||||
action: "marketplaceButtonClicked",
|
||||
values: { marketplaceTab: "mode" },
|
||||
},
|
||||
"*",
|
||||
)
|
||||
setOpen(false)
|
||||
}}
|
||||
data-testid="mode-selector-item">
|
||||
<div className="flex-grow">
|
||||
<p className="m-0 mb-0 font-bold">{mode.name}</p>
|
||||
{mode.description && (
|
||||
<p className="m-0 py-0 pl-4 h-4 flex-1 text-xs overflow-hidden">
|
||||
{mode.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{mode.slug === value ? (
|
||||
<Check className="m-0 size-4 p-0.5" />
|
||||
) : (
|
||||
<div className="size-4" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
/>
|
||||
<IconButton
|
||||
iconClass="codicon-settings-gear"
|
||||
title={t("chat:modeSelector.settings")}
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "switchTab",
|
||||
tab: "modes",
|
||||
})
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info icon and title on the right - only show info icon when search bar is visible */}
|
||||
<div className="flex items-center gap-1 pr-1">
|
||||
{showSearch && (
|
||||
<StandardTooltip content={instructionText}>
|
||||
<span className="codicon codicon-info text-xs text-vscode-descriptionForeground opacity-70 hover:opacity-100 cursor-help" />
|
||||
</StandardTooltip>
|
||||
)}
|
||||
<h4 className="m-0 font-medium text-sm text-vscode-descriptionForeground">
|
||||
{t("chat:modeSelector.title")}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
|
|
|||
|
|
@ -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<typeof import("@roo/modes")>("@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(<ModeSelector 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
|
||||
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(<ModeSelector 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 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(<ModeSelector value={"mode-0" as Mode} onChange={vi.fn()} modeShortcutText="Ctrl+M" />)
|
||||
|
||||
// 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(
|
||||
<ModeSelector value={"mode-0" as Mode} onChange={vi.fn()} modeShortcutText="Ctrl+M" disableSearch={true} />,
|
||||
)
|
||||
|
||||
// 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(<ModeSelector 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()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/ca/chat.json
generated
4
webview-ui/src/i18n/locales/ca/chat.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/de/chat.json
generated
4
webview-ui/src/i18n/locales/de/chat.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/es/chat.json
generated
4
webview-ui/src/i18n/locales/es/chat.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/fr/chat.json
generated
4
webview-ui/src/i18n/locales/fr/chat.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/hi/chat.json
generated
4
webview-ui/src/i18n/locales/hi/chat.json
generated
|
|
@ -116,7 +116,9 @@
|
|||
"title": "मोड्स",
|
||||
"marketplace": "मोड मार्केटप्लेस",
|
||||
"settings": "मोड सेटिंग्स",
|
||||
"description": "विशेष व्यक्तित्व जो Roo के व्यवहार को अनुकूलित करते हैं।"
|
||||
"description": "विशेष व्यक्तित्व जो Roo के व्यवहार को अनुकूलित करते हैं।",
|
||||
"searchPlaceholder": "मोड खोजें...",
|
||||
"noResults": "कोई परिणाम नहीं मिला"
|
||||
},
|
||||
"errorReadingFile": "फ़ाइल पढ़ने में त्रुटि:",
|
||||
"noValidImages": "कोई मान्य चित्र प्रोसेस नहीं किया गया",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/id/chat.json
generated
4
webview-ui/src/i18n/locales/id/chat.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/it/chat.json
generated
4
webview-ui/src/i18n/locales/it/chat.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/ja/chat.json
generated
4
webview-ui/src/i18n/locales/ja/chat.json
generated
|
|
@ -116,7 +116,9 @@
|
|||
"title": "モード",
|
||||
"marketplace": "モードマーケットプレイス",
|
||||
"settings": "モード設定",
|
||||
"description": "Rooの動作をカスタマイズする専門的なペルソナ。"
|
||||
"description": "Rooの動作をカスタマイズする専門的なペルソナ。",
|
||||
"searchPlaceholder": "モードを検索...",
|
||||
"noResults": "結果が見つかりません"
|
||||
},
|
||||
"errorReadingFile": "ファイル読み込みエラー:",
|
||||
"noValidImages": "有効な画像が処理されませんでした",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/ko/chat.json
generated
4
webview-ui/src/i18n/locales/ko/chat.json
generated
|
|
@ -116,7 +116,9 @@
|
|||
"title": "모드",
|
||||
"marketplace": "모드 마켓플레이스",
|
||||
"settings": "모드 설정",
|
||||
"description": "Roo의 행동을 맞춤화하는 전문화된 페르소나."
|
||||
"description": "Roo의 행동을 맞춤화하는 전문화된 페르소나.",
|
||||
"searchPlaceholder": "모드 검색...",
|
||||
"noResults": "결과를 찾을 수 없습니다"
|
||||
},
|
||||
"errorReadingFile": "파일 읽기 오류:",
|
||||
"noValidImages": "처리된 유효한 이미지가 없습니다",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/nl/chat.json
generated
4
webview-ui/src/i18n/locales/nl/chat.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/pl/chat.json
generated
4
webview-ui/src/i18n/locales/pl/chat.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
4
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/ru/chat.json
generated
4
webview-ui/src/i18n/locales/ru/chat.json
generated
|
|
@ -108,7 +108,9 @@
|
|||
"title": "Режимы",
|
||||
"marketplace": "Маркетплейс режимов",
|
||||
"settings": "Настройки режимов",
|
||||
"description": "Специализированные персоны, которые настраивают поведение Roo."
|
||||
"description": "Специализированные персоны, которые настраивают поведение Roo.",
|
||||
"searchPlaceholder": "Поиск режимов...",
|
||||
"noResults": "Ничего не найдено"
|
||||
},
|
||||
"addImages": "Добавить изображения к сообщению",
|
||||
"sendMessage": "Отправить сообщение",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/tr/chat.json
generated
4
webview-ui/src/i18n/locales/tr/chat.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/vi/chat.json
generated
4
webview-ui/src/i18n/locales/vi/chat.json
generated
|
|
@ -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ý",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
4
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
|
|
@ -116,7 +116,9 @@
|
|||
"title": "模式",
|
||||
"marketplace": "模式市场",
|
||||
"settings": "模式设置",
|
||||
"description": "专门定制Roo行为的角色。"
|
||||
"description": "专门定制Roo行为的角色。",
|
||||
"searchPlaceholder": "搜索模式...",
|
||||
"noResults": "未找到结果"
|
||||
},
|
||||
"errorReadingFile": "读取文件时出错:",
|
||||
"noValidImages": "没有处理有效图片",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
4
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
|
|
@ -116,7 +116,9 @@
|
|||
"title": "模式",
|
||||
"marketplace": "模式市集",
|
||||
"settings": "模式設定",
|
||||
"description": "專門定制Roo行為的角色。"
|
||||
"description": "專門定制Roo行為的角色。",
|
||||
"searchPlaceholder": "搜尋模式...",
|
||||
"noResults": "沒有找到結果"
|
||||
},
|
||||
"errorReadingFile": "讀取檔案時發生錯誤:",
|
||||
"noValidImages": "未處理到任何有效圖片",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue