feat(message-input): add search box to slash-command popups
Both the inline autocomplete (triggered by `/` in the textarea) and the dropdown popup (triggered by the slash-command button) now show a search field at the top. Matching uses substring on name and description, and ranks name matches above description/id-only matches.
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
|||||||
GitFork,
|
GitFork,
|
||||||
ListPlus,
|
ListPlus,
|
||||||
Plus,
|
Plus,
|
||||||
|
Search,
|
||||||
Send,
|
Send,
|
||||||
Command,
|
Command,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
@@ -543,19 +544,58 @@ export function MessageInput({
|
|||||||
() => (availableCommands ?? []).filter((cmd) => !expertIdSet.has(cmd.name)),
|
() => (availableCommands ?? []).filter((cmd) => !expertIdSet.has(cmd.name)),
|
||||||
[availableCommands, expertIdSet]
|
[availableCommands, expertIdSet]
|
||||||
)
|
)
|
||||||
|
const [slashDropdownOpen, setSlashDropdownOpen] = useState(false)
|
||||||
|
const [slashDropdownSearch, setSlashDropdownSearch] = useState("")
|
||||||
|
const slashDropdownInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
const filteredSlashDropdownCommands = useMemo(() => {
|
||||||
|
const q = slashDropdownSearch.toLowerCase().trim()
|
||||||
|
if (!q) return slashCommands
|
||||||
|
const nameMatches: typeof slashCommands = []
|
||||||
|
const descOnlyMatches: typeof slashCommands = []
|
||||||
|
for (const cmd of slashCommands) {
|
||||||
|
if (cmd.name.toLowerCase().includes(q)) {
|
||||||
|
nameMatches.push(cmd)
|
||||||
|
} else if (cmd.description?.toLowerCase().includes(q)) {
|
||||||
|
descOnlyMatches.push(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...nameMatches, ...descOnlyMatches]
|
||||||
|
}, [slashCommands, slashDropdownSearch])
|
||||||
|
const handleSlashDropdownOpenChange = useCallback((open: boolean) => {
|
||||||
|
setSlashDropdownOpen(open)
|
||||||
|
if (!open) setSlashDropdownSearch("")
|
||||||
|
}, [])
|
||||||
|
// Radix composes this handler with its own content-focus via
|
||||||
|
// composeEventHandlers (default `checkForDefaultPrevented: true`), so
|
||||||
|
// calling preventDefault here skips radix's autofocus entirely. The prop
|
||||||
|
// is accepted at runtime but omitted from DropdownMenuContent's public
|
||||||
|
// TypeScript surface, so it has to be passed through an untyped spread.
|
||||||
|
const slashDropdownFocusProps = useMemo(
|
||||||
|
() => ({
|
||||||
|
onOpenAutoFocus: (event: Event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
slashDropdownInputRef.current?.focus()
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const slashFilterText = useMemo(() => {
|
||||||
|
if (!slashMenuOpen || slashTriggerPos == null) return ""
|
||||||
|
const trigger = text[slashTriggerPos]
|
||||||
|
if (trigger !== "/" && trigger !== "$") return ""
|
||||||
|
const afterTrigger = text.slice(slashTriggerPos + 1)
|
||||||
|
const endIdx = afterTrigger.search(/\s/)
|
||||||
|
return endIdx === -1 ? afterTrigger : afterTrigger.slice(0, endIdx)
|
||||||
|
}, [slashMenuOpen, text, slashTriggerPos])
|
||||||
const filteredSlashCommands = useMemo(() => {
|
const filteredSlashCommands = useMemo(() => {
|
||||||
if (!slashMenuOpen || slashCommands.length === 0 || slashTriggerPos == null)
|
if (!slashMenuOpen || slashCommands.length === 0 || slashTriggerPos == null)
|
||||||
return []
|
return []
|
||||||
if (text[slashTriggerPos] !== "/") return []
|
if (text[slashTriggerPos] !== "/") return []
|
||||||
const afterTrigger = text.slice(slashTriggerPos + 1)
|
const filter = slashFilterText.toLowerCase()
|
||||||
const endIdx = afterTrigger.search(/\s/)
|
|
||||||
const filter = (
|
|
||||||
endIdx === -1 ? afterTrigger : afterTrigger.slice(0, endIdx)
|
|
||||||
).toLowerCase()
|
|
||||||
return slashCommands.filter((cmd) =>
|
return slashCommands.filter((cmd) =>
|
||||||
cmd.name.toLowerCase().startsWith(filter)
|
cmd.name.toLowerCase().includes(filter)
|
||||||
)
|
)
|
||||||
}, [slashMenuOpen, slashCommands, text, slashTriggerPos])
|
}, [slashMenuOpen, slashCommands, text, slashTriggerPos, slashFilterText])
|
||||||
const filteredSlashSkills = useMemo(() => {
|
const filteredSlashSkills = useMemo(() => {
|
||||||
// Skills autocomplete is Codex-only and triggered by `$`.
|
// Skills autocomplete is Codex-only and triggered by `$`.
|
||||||
if (agentType !== "codex") return []
|
if (agentType !== "codex") return []
|
||||||
@@ -566,15 +606,26 @@ export function MessageInput({
|
|||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
if (text[slashTriggerPos] !== "$") return []
|
if (text[slashTriggerPos] !== "$") return []
|
||||||
const afterTrigger = text.slice(slashTriggerPos + 1)
|
const filter = slashFilterText.toLowerCase()
|
||||||
const endIdx = afterTrigger.search(/\s/)
|
if (!filter) return nonExpertSkills
|
||||||
const filter = (
|
const nameMatches: typeof nonExpertSkills = []
|
||||||
endIdx === -1 ? afterTrigger : afterTrigger.slice(0, endIdx)
|
const idOnlyMatches: typeof nonExpertSkills = []
|
||||||
).toLowerCase()
|
for (const skill of nonExpertSkills) {
|
||||||
return nonExpertSkills.filter((skill) =>
|
if (skill.name.toLowerCase().includes(filter)) {
|
||||||
skill.id.toLowerCase().startsWith(filter)
|
nameMatches.push(skill)
|
||||||
)
|
} else if (skill.id.toLowerCase().includes(filter)) {
|
||||||
}, [slashMenuOpen, nonExpertSkills, text, agentType, slashTriggerPos])
|
idOnlyMatches.push(skill)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...nameMatches, ...idOnlyMatches]
|
||||||
|
}, [
|
||||||
|
slashMenuOpen,
|
||||||
|
nonExpertSkills,
|
||||||
|
text,
|
||||||
|
agentType,
|
||||||
|
slashTriggerPos,
|
||||||
|
slashFilterText,
|
||||||
|
])
|
||||||
const slashAutocompleteCount =
|
const slashAutocompleteCount =
|
||||||
filteredSlashCommands.length + filteredSlashSkills.length
|
filteredSlashCommands.length + filteredSlashSkills.length
|
||||||
|
|
||||||
@@ -950,6 +1001,25 @@ export function MessageInput({
|
|||||||
[onModeChange]
|
[onModeChange]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleSlashSearchChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const pos = slashTriggerPosRef.current
|
||||||
|
const current = textRef.current
|
||||||
|
if (pos == null || pos < 0 || pos >= current.length) return
|
||||||
|
const trigger = current[pos]
|
||||||
|
if (trigger !== "/" && trigger !== "$") return
|
||||||
|
const afterTrigger = current.slice(pos + 1)
|
||||||
|
const endIdx = afterTrigger.search(/\s/)
|
||||||
|
const tokenEnd = endIdx === -1 ? current.length : pos + 1 + endIdx
|
||||||
|
const before = current.slice(0, pos + 1)
|
||||||
|
const rest = current.slice(tokenEnd)
|
||||||
|
const sanitized = e.target.value.replace(/\s+/g, "")
|
||||||
|
setText(before + sanitized + rest)
|
||||||
|
setSlashSelectedIndex(0)
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
const handleSlashSelect = useCallback((cmd: AvailableCommandInfo) => {
|
const handleSlashSelect = useCallback((cmd: AvailableCommandInfo) => {
|
||||||
const pos = slashTriggerPosRef.current
|
const pos = slashTriggerPosRef.current
|
||||||
const current = textRef.current
|
const current = textRef.current
|
||||||
@@ -1046,6 +1116,49 @@ export function MessageInput({
|
|||||||
[expertPrefix]
|
[expertPrefix]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleSlashSearchKeyDown = useCallback(
|
||||||
|
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
const total = filteredSlashCommands.length + filteredSlashSkills.length
|
||||||
|
if (e.key === "ArrowDown") {
|
||||||
|
e.preventDefault()
|
||||||
|
if (total === 0) return
|
||||||
|
setSlashSelectedIndex((i) => (i < total - 1 ? i + 1 : 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.key === "ArrowUp") {
|
||||||
|
e.preventDefault()
|
||||||
|
if (total === 0) return
|
||||||
|
setSlashSelectedIndex((i) => (i > 0 ? i - 1 : total - 1))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.key === "Enter" || e.key === "Tab") {
|
||||||
|
if (total === 0) return
|
||||||
|
e.preventDefault()
|
||||||
|
if (slashSelectedIndex < filteredSlashCommands.length) {
|
||||||
|
handleSlashSelect(filteredSlashCommands[slashSelectedIndex])
|
||||||
|
} else {
|
||||||
|
const skillIndex = slashSelectedIndex - filteredSlashCommands.length
|
||||||
|
const skill = filteredSlashSkills[skillIndex]
|
||||||
|
if (skill) handleSkillAutocompleteSelect(skill)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
e.preventDefault()
|
||||||
|
setSlashMenuOpen(false)
|
||||||
|
setSlashTriggerPos(null)
|
||||||
|
requestAnimationFrame(() => textareaRef.current?.focus())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
filteredSlashCommands,
|
||||||
|
filteredSlashSkills,
|
||||||
|
slashSelectedIndex,
|
||||||
|
handleSlashSelect,
|
||||||
|
handleSkillAutocompleteSelect,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
// Experts always inject `prefix + expert-id ` at the very front of the
|
// Experts always inject `prefix + expert-id ` at the very front of the
|
||||||
// input, never at the cursor. The expert skill is a whole-turn directive
|
// input, never at the cursor. The expert skill is a whole-turn directive
|
||||||
// that the agent inspects first, so prepending keeps semantics unambiguous
|
// that the agent inspects first, so prepending keeps semantics unambiguous
|
||||||
@@ -1719,63 +1832,77 @@ export function MessageInput({
|
|||||||
onDrop={handleContainerDrop}
|
onDrop={handleContainerDrop}
|
||||||
>
|
>
|
||||||
{slashMenuOpen && slashAutocompleteCount > 0 && (
|
{slashMenuOpen && slashAutocompleteCount > 0 && (
|
||||||
<div
|
<div className="absolute bottom-full left-0 right-0 mb-1 z-50 flex max-h-[min(16rem,40dvh)] flex-col overflow-hidden rounded-xl border border-border bg-popover shadow-lg">
|
||||||
ref={slashMenuListRef}
|
<div className="flex shrink-0 items-center gap-2 border-b border-border/60 px-3 py-2">
|
||||||
className="absolute bottom-full left-0 right-0 mb-1 z-50 max-h-[min(16rem,40dvh)] overflow-y-auto rounded-xl border border-border bg-popover p-1 shadow-lg"
|
<Search className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||||
>
|
<input
|
||||||
{filteredSlashCommands.map((cmd, i) => (
|
type="text"
|
||||||
<button
|
role="searchbox"
|
||||||
key={`cmd-${cmd.name}`}
|
aria-label={t("slashSearchPlaceholder")}
|
||||||
type="button"
|
value={slashFilterText}
|
||||||
className={cn(
|
onChange={handleSlashSearchChange}
|
||||||
"flex w-full items-start gap-2 rounded-lg px-3 py-2 text-left text-sm",
|
onKeyDown={handleSlashSearchKeyDown}
|
||||||
i === slashSelectedIndex
|
placeholder={t("slashSearchPlaceholder")}
|
||||||
? "bg-accent text-accent-foreground"
|
className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
||||||
: "hover:bg-muted"
|
autoComplete="off"
|
||||||
)}
|
spellCheck={false}
|
||||||
onMouseDown={(e) => {
|
/>
|
||||||
e.preventDefault()
|
</div>
|
||||||
handleSlashSelect(cmd)
|
<div ref={slashMenuListRef} className="flex-1 overflow-y-auto p-1">
|
||||||
}}
|
{filteredSlashCommands.map((cmd, i) => (
|
||||||
>
|
|
||||||
<span className="shrink-0 font-mono text-primary">
|
|
||||||
/{cmd.name}
|
|
||||||
</span>
|
|
||||||
<span className="truncate text-xs text-muted-foreground">
|
|
||||||
{cmd.description}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
{filteredSlashSkills.map((skill, i) => {
|
|
||||||
const absoluteIndex = filteredSlashCommands.length + i
|
|
||||||
return (
|
|
||||||
<button
|
<button
|
||||||
key={`skill-${skill.scope}-${skill.id}`}
|
key={`cmd-${cmd.name}`}
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full items-start gap-2 rounded-lg px-3 py-2 text-left text-sm",
|
"flex w-full items-start gap-2 rounded-lg px-3 py-2 text-left text-sm",
|
||||||
absoluteIndex === slashSelectedIndex
|
i === slashSelectedIndex
|
||||||
? "bg-accent text-accent-foreground"
|
? "bg-accent text-accent-foreground"
|
||||||
: "hover:bg-muted"
|
: "hover:bg-muted"
|
||||||
)}
|
)}
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
handleSkillAutocompleteSelect(skill)
|
handleSlashSelect(cmd)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BookOpenText className="mt-0.5 size-4 shrink-0 text-primary/80" />
|
<span className="shrink-0 font-mono text-primary">
|
||||||
<div className="min-w-0 flex-1">
|
/{cmd.name}
|
||||||
<div className="flex items-center gap-1.5">
|
</span>
|
||||||
<span className="truncate font-medium">{skill.name}</span>
|
<span className="truncate text-xs text-muted-foreground">
|
||||||
<span className="shrink-0 font-mono text-[11px] text-muted-foreground">
|
{cmd.description}
|
||||||
{expertPrefix}
|
</span>
|
||||||
{skill.id}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
)
|
))}
|
||||||
})}
|
{filteredSlashSkills.map((skill, i) => {
|
||||||
|
const absoluteIndex = filteredSlashCommands.length + i
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={`skill-${skill.scope}-${skill.id}`}
|
||||||
|
type="button"
|
||||||
|
className={cn(
|
||||||
|
"flex w-full items-start gap-2 rounded-lg px-3 py-2 text-left text-sm",
|
||||||
|
absoluteIndex === slashSelectedIndex
|
||||||
|
? "bg-accent text-accent-foreground"
|
||||||
|
: "hover:bg-muted"
|
||||||
|
)}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
handleSkillAutocompleteSelect(skill)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BookOpenText className="mt-0.5 size-4 shrink-0 text-primary/80" />
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<span className="truncate font-medium">{skill.name}</span>
|
||||||
|
<span className="shrink-0 font-mono text-[11px] text-muted-foreground">
|
||||||
|
{expertPrefix}
|
||||||
|
{skill.id}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{atMenuOpen && filteredAtFiles.length > 0 && (
|
{atMenuOpen && filteredAtFiles.length > 0 && (
|
||||||
@@ -1946,7 +2073,10 @@ export function MessageInput({
|
|||||||
)}
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<DropdownMenu>
|
<DropdownMenu
|
||||||
|
open={slashDropdownOpen}
|
||||||
|
onOpenChange={handleSlashDropdownOpenChange}
|
||||||
|
>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled || slashCommands.length === 0}
|
disabled={disabled || slashCommands.length === 0}
|
||||||
@@ -1965,23 +2095,82 @@ export function MessageInput({
|
|||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
side="top"
|
side="top"
|
||||||
align="start"
|
align="start"
|
||||||
className="min-w-72 overflow-y-auto"
|
className="flex min-w-72 flex-col overflow-hidden p-0"
|
||||||
style={{
|
style={{
|
||||||
maxHeight:
|
maxHeight:
|
||||||
"min(32rem, var(--radix-dropdown-menu-content-available-height))",
|
"min(32rem, var(--radix-dropdown-menu-content-available-height))",
|
||||||
}}
|
}}
|
||||||
|
{...slashDropdownFocusProps}
|
||||||
>
|
>
|
||||||
{slashCommands.map((cmd) => (
|
<div className="flex shrink-0 items-center gap-2 border-b border-border/60 px-3 py-2">
|
||||||
<DropdownMenuItem
|
<Search className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||||
key={cmd.name}
|
<input
|
||||||
onClick={() => handleSlashPopoverSelect(cmd)}
|
ref={slashDropdownInputRef}
|
||||||
>
|
type="text"
|
||||||
<DropdownRadioItemContent
|
role="searchbox"
|
||||||
label={`/${cmd.name}`}
|
aria-label={t("slashSearchPlaceholder")}
|
||||||
description={cmd.description}
|
value={slashDropdownSearch}
|
||||||
/>
|
onChange={(e) => setSlashDropdownSearch(e.target.value)}
|
||||||
</DropdownMenuItem>
|
onKeyDown={(e) => {
|
||||||
))}
|
if (e.key === "ArrowDown") {
|
||||||
|
e.preventDefault()
|
||||||
|
const container = e.currentTarget.closest(
|
||||||
|
'[data-slot="dropdown-menu-content"]'
|
||||||
|
)
|
||||||
|
const firstItem =
|
||||||
|
container?.querySelector<HTMLElement>(
|
||||||
|
'[role="menuitem"]'
|
||||||
|
)
|
||||||
|
firstItem?.focus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault()
|
||||||
|
const first = filteredSlashDropdownCommands[0]
|
||||||
|
if (first) {
|
||||||
|
handleSlashPopoverSelect(first)
|
||||||
|
setSlashDropdownOpen(false)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.key === "Escape" || e.key === "Tab") return
|
||||||
|
// Prevent radix DropdownMenu's built-in typeahead from
|
||||||
|
// hijacking letter keys while the user is typing.
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
placeholder={t("slashSearchPlaceholder")}
|
||||||
|
className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-y-auto p-1">
|
||||||
|
{filteredSlashDropdownCommands.length === 0 ? (
|
||||||
|
<div className="px-3 py-6 text-center text-xs text-muted-foreground">
|
||||||
|
{t("slashSearchEmpty")}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredSlashDropdownCommands.map((cmd) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={cmd.name}
|
||||||
|
onClick={() => handleSlashPopoverSelect(cmd)}
|
||||||
|
// Radix focuses the item on pointermove, which fires
|
||||||
|
// while scrolling (items slide under the cursor) and
|
||||||
|
// steals focus from the search input. Short-circuit
|
||||||
|
// that default with preventDefault so the search
|
||||||
|
// keeps focus until the user explicitly clicks.
|
||||||
|
onPointerMove={(e) => e.preventDefault()}
|
||||||
|
onPointerLeave={(e) => e.preventDefault()}
|
||||||
|
className="hover:bg-accent hover:text-accent-foreground"
|
||||||
|
>
|
||||||
|
<DropdownRadioItemContent
|
||||||
|
label={`/${cmd.name}`}
|
||||||
|
description={cmd.description}
|
||||||
|
/>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
{/* 宽屏内联显示,窄屏(<480px)通过"更多"气泡显示 */}
|
{/* 宽屏内联显示,窄屏(<480px)通过"更多"气泡显示 */}
|
||||||
|
|||||||
@@ -1573,7 +1573,9 @@
|
|||||||
"forkAndSend": "تفريع وإرسال",
|
"forkAndSend": "تفريع وإرسال",
|
||||||
"slashCommands": "أوامر الشرطة المائلة",
|
"slashCommands": "أوامر الشرطة المائلة",
|
||||||
"expertSkills": "مهارات الخبراء",
|
"expertSkills": "مهارات الخبراء",
|
||||||
"expertsEmptyForAgent": "لا يحتوي هذا العميل على خبراء مفعّلين. فعّلهم من الإعدادات > الخبراء."
|
"expertsEmptyForAgent": "لا يحتوي هذا العميل على خبراء مفعّلين. فعّلهم من الإعدادات > الخبراء.",
|
||||||
|
"slashSearchPlaceholder": "البحث عن الأوامر...",
|
||||||
|
"slashSearchEmpty": "لا توجد أوامر مطابقة"
|
||||||
},
|
},
|
||||||
"messageQueue": {
|
"messageQueue": {
|
||||||
"addToQueue": "إضافة للقائمة",
|
"addToQueue": "إضافة للقائمة",
|
||||||
|
|||||||
@@ -1573,7 +1573,9 @@
|
|||||||
"forkAndSend": "Fork & Senden",
|
"forkAndSend": "Fork & Senden",
|
||||||
"slashCommands": "Slash-Befehle",
|
"slashCommands": "Slash-Befehle",
|
||||||
"expertSkills": "Expertenfähigkeiten",
|
"expertSkills": "Expertenfähigkeiten",
|
||||||
"expertsEmptyForAgent": "Dieser Agent hat keine aktivierten Experten. Aktivieren Sie sie unter Einstellungen > Experten."
|
"expertsEmptyForAgent": "Dieser Agent hat keine aktivierten Experten. Aktivieren Sie sie unter Einstellungen > Experten.",
|
||||||
|
"slashSearchPlaceholder": "Befehle suchen...",
|
||||||
|
"slashSearchEmpty": "Keine passenden Befehle"
|
||||||
},
|
},
|
||||||
"messageQueue": {
|
"messageQueue": {
|
||||||
"addToQueue": "Zur Warteschlange",
|
"addToQueue": "Zur Warteschlange",
|
||||||
|
|||||||
@@ -1573,7 +1573,9 @@
|
|||||||
"forkAndSend": "Fork & Send",
|
"forkAndSend": "Fork & Send",
|
||||||
"slashCommands": "Slash commands",
|
"slashCommands": "Slash commands",
|
||||||
"expertSkills": "Expert skills",
|
"expertSkills": "Expert skills",
|
||||||
"expertsEmptyForAgent": "This agent has no enabled experts. Enable them in Settings > Experts."
|
"expertsEmptyForAgent": "This agent has no enabled experts. Enable them in Settings > Experts.",
|
||||||
|
"slashSearchPlaceholder": "Search commands...",
|
||||||
|
"slashSearchEmpty": "No matching commands"
|
||||||
},
|
},
|
||||||
"messageQueue": {
|
"messageQueue": {
|
||||||
"addToQueue": "Queue message",
|
"addToQueue": "Queue message",
|
||||||
|
|||||||
@@ -1573,7 +1573,9 @@
|
|||||||
"forkAndSend": "Fork y Enviar",
|
"forkAndSend": "Fork y Enviar",
|
||||||
"slashCommands": "Comandos de barra",
|
"slashCommands": "Comandos de barra",
|
||||||
"expertSkills": "Habilidades de expertos",
|
"expertSkills": "Habilidades de expertos",
|
||||||
"expertsEmptyForAgent": "Este agente no tiene expertos habilitados. Actívalos en Configuración > Expertos."
|
"expertsEmptyForAgent": "Este agente no tiene expertos habilitados. Actívalos en Configuración > Expertos.",
|
||||||
|
"slashSearchPlaceholder": "Buscar comandos...",
|
||||||
|
"slashSearchEmpty": "Sin comandos coincidentes"
|
||||||
},
|
},
|
||||||
"messageQueue": {
|
"messageQueue": {
|
||||||
"addToQueue": "Agregar a la cola",
|
"addToQueue": "Agregar a la cola",
|
||||||
|
|||||||
@@ -1573,7 +1573,9 @@
|
|||||||
"forkAndSend": "Fork & Envoyer",
|
"forkAndSend": "Fork & Envoyer",
|
||||||
"slashCommands": "Commandes slash",
|
"slashCommands": "Commandes slash",
|
||||||
"expertSkills": "Compétences d'expert",
|
"expertSkills": "Compétences d'expert",
|
||||||
"expertsEmptyForAgent": "Cet agent n'a aucun expert activé. Activez-les dans Paramètres > Experts."
|
"expertsEmptyForAgent": "Cet agent n'a aucun expert activé. Activez-les dans Paramètres > Experts.",
|
||||||
|
"slashSearchPlaceholder": "Rechercher des commandes...",
|
||||||
|
"slashSearchEmpty": "Aucune commande correspondante"
|
||||||
},
|
},
|
||||||
"messageQueue": {
|
"messageQueue": {
|
||||||
"addToQueue": "Mettre en file",
|
"addToQueue": "Mettre en file",
|
||||||
|
|||||||
@@ -1573,7 +1573,9 @@
|
|||||||
"forkAndSend": "フォークして送信",
|
"forkAndSend": "フォークして送信",
|
||||||
"slashCommands": "スラッシュコマンド",
|
"slashCommands": "スラッシュコマンド",
|
||||||
"expertSkills": "エキスパートスキル",
|
"expertSkills": "エキスパートスキル",
|
||||||
"expertsEmptyForAgent": "このエージェントで有効なエキスパートはありません。「設定 > エキスパート」から有効にしてください。"
|
"expertsEmptyForAgent": "このエージェントで有効なエキスパートはありません。「設定 > エキスパート」から有効にしてください。",
|
||||||
|
"slashSearchPlaceholder": "コマンドを検索...",
|
||||||
|
"slashSearchEmpty": "一致するコマンドがありません"
|
||||||
},
|
},
|
||||||
"messageQueue": {
|
"messageQueue": {
|
||||||
"addToQueue": "キューに追加",
|
"addToQueue": "キューに追加",
|
||||||
|
|||||||
@@ -1573,7 +1573,9 @@
|
|||||||
"forkAndSend": "포크 & 전송",
|
"forkAndSend": "포크 & 전송",
|
||||||
"slashCommands": "슬래시 명령",
|
"slashCommands": "슬래시 명령",
|
||||||
"expertSkills": "전문가 스킬",
|
"expertSkills": "전문가 스킬",
|
||||||
"expertsEmptyForAgent": "이 에이전트에 활성화된 전문가가 없습니다. 설정 > 전문가에서 활성화하세요."
|
"expertsEmptyForAgent": "이 에이전트에 활성화된 전문가가 없습니다. 설정 > 전문가에서 활성화하세요.",
|
||||||
|
"slashSearchPlaceholder": "명령 검색...",
|
||||||
|
"slashSearchEmpty": "일치하는 명령이 없습니다"
|
||||||
},
|
},
|
||||||
"messageQueue": {
|
"messageQueue": {
|
||||||
"addToQueue": "대기열에 추가",
|
"addToQueue": "대기열에 추가",
|
||||||
|
|||||||
@@ -1573,7 +1573,9 @@
|
|||||||
"forkAndSend": "Fork & Enviar",
|
"forkAndSend": "Fork & Enviar",
|
||||||
"slashCommands": "Comandos de barra",
|
"slashCommands": "Comandos de barra",
|
||||||
"expertSkills": "Habilidades de especialistas",
|
"expertSkills": "Habilidades de especialistas",
|
||||||
"expertsEmptyForAgent": "Este agente não tem especialistas ativados. Ative-os em Configurações > Especialistas."
|
"expertsEmptyForAgent": "Este agente não tem especialistas ativados. Ative-os em Configurações > Especialistas.",
|
||||||
|
"slashSearchPlaceholder": "Buscar comandos...",
|
||||||
|
"slashSearchEmpty": "Nenhum comando correspondente"
|
||||||
},
|
},
|
||||||
"messageQueue": {
|
"messageQueue": {
|
||||||
"addToQueue": "Adicionar à fila",
|
"addToQueue": "Adicionar à fila",
|
||||||
|
|||||||
@@ -1573,7 +1573,9 @@
|
|||||||
"forkAndSend": "分叉发送",
|
"forkAndSend": "分叉发送",
|
||||||
"slashCommands": "斜杠命令",
|
"slashCommands": "斜杠命令",
|
||||||
"expertSkills": "专家技能",
|
"expertSkills": "专家技能",
|
||||||
"expertsEmptyForAgent": "该智能体没有启用任何专家。请在「设置 > 专家」中启用。"
|
"expertsEmptyForAgent": "该智能体没有启用任何专家。请在「设置 > 专家」中启用。",
|
||||||
|
"slashSearchPlaceholder": "搜索命令...",
|
||||||
|
"slashSearchEmpty": "没有匹配的命令"
|
||||||
},
|
},
|
||||||
"messageQueue": {
|
"messageQueue": {
|
||||||
"addToQueue": "加入队列",
|
"addToQueue": "加入队列",
|
||||||
|
|||||||
@@ -1573,7 +1573,9 @@
|
|||||||
"forkAndSend": "分叉發送",
|
"forkAndSend": "分叉發送",
|
||||||
"slashCommands": "斜線命令",
|
"slashCommands": "斜線命令",
|
||||||
"expertSkills": "專家技能",
|
"expertSkills": "專家技能",
|
||||||
"expertsEmptyForAgent": "該智慧代理沒有啟用任何專家。請在「設定 > 專家」中啟用。"
|
"expertsEmptyForAgent": "該智慧代理沒有啟用任何專家。請在「設定 > 專家」中啟用。",
|
||||||
|
"slashSearchPlaceholder": "搜尋命令...",
|
||||||
|
"slashSearchEmpty": "沒有符合的指令"
|
||||||
},
|
},
|
||||||
"messageQueue": {
|
"messageQueue": {
|
||||||
"addToQueue": "加入佇列",
|
"addToQueue": "加入佇列",
|
||||||
|
|||||||
Reference in New Issue
Block a user