mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-31 16:33:34 +08:00
feat(desktop): group slash menu commands
This commit is contained in:
parent
5e156ec03e
commit
2346427c53
@ -1475,7 +1475,7 @@ describe('ChatInput file mentions', () => {
|
|||||||
selectionStart: 1,
|
selectionStart: 1,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(await screen.findByText('/mcp')).toBeInTheDocument()
|
expect(await screen.findByText('mcp')).toBeInTheDocument()
|
||||||
expect(panel).toHaveClass('overflow-visible')
|
expect(panel).toHaveClass('overflow-visible')
|
||||||
expect(panel).not.toHaveClass('overflow-hidden')
|
expect(panel).not.toHaveClass('overflow-hidden')
|
||||||
|
|
||||||
@ -1672,12 +1672,48 @@ describe('ChatInput file mentions', () => {
|
|||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const commandButtons = screen
|
const commandButtons = screen
|
||||||
.getAllByRole('button')
|
.getAllByRole('option')
|
||||||
.filter((button) => button.textContent?.startsWith('/'))
|
.filter((option) => option.textContent?.includes('Creative work planning.'))
|
||||||
expect(commandButtons[0]).toHaveTextContent('/superpowers:brainstorming')
|
expect(commandButtons[0]).toHaveTextContent('superpowers:brainstorming')
|
||||||
|
expect(commandButtons[0]).toHaveTextContent('Personal')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('shows app commands before the personal skills section', async () => {
|
||||||
|
useChatStore.setState({
|
||||||
|
sessions: {
|
||||||
|
[sessionId]: {
|
||||||
|
...useChatStore.getState().sessions[sessionId]!,
|
||||||
|
slashCommands: [
|
||||||
|
{
|
||||||
|
name: 'audit',
|
||||||
|
description: 'Audit product UX.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render(<ChatInput />)
|
||||||
|
|
||||||
|
const input = screen.getByRole('textbox') as HTMLTextAreaElement
|
||||||
|
fireEvent.change(input, {
|
||||||
|
target: { value: '/', selectionStart: 1 },
|
||||||
|
})
|
||||||
|
|
||||||
|
const systemCommand = await screen.findByText('mcp')
|
||||||
|
const skillsHeading = screen.getByText('Skills')
|
||||||
|
const personalSkill = screen.getByText('audit')
|
||||||
|
|
||||||
|
expect(systemCommand.compareDocumentPosition(skillsHeading)).toBe(
|
||||||
|
Node.DOCUMENT_POSITION_FOLLOWING,
|
||||||
|
)
|
||||||
|
expect(skillsHeading.compareDocumentPosition(personalSkill)).toBe(
|
||||||
|
Node.DOCUMENT_POSITION_FOLLOWING,
|
||||||
|
)
|
||||||
|
expect(personalSkill.closest('[role="option"]')).toHaveTextContent('Personal')
|
||||||
|
})
|
||||||
|
|
||||||
it('offers active agents as slash entries that insert /agent with the selected type', async () => {
|
it('offers active agents as slash entries that insert /agent with the selected type', async () => {
|
||||||
mocks.listAgents.mockResolvedValue({
|
mocks.listAgents.mockResolvedValue({
|
||||||
activeAgents: [
|
activeAgents: [
|
||||||
@ -1703,7 +1739,7 @@ describe('ChatInput file mentions', () => {
|
|||||||
target: { value: '/debug', selectionStart: 6 },
|
target: { value: '/debug', selectionStart: 6 },
|
||||||
})
|
})
|
||||||
|
|
||||||
const agentOption = await screen.findByText('/agent debugger')
|
const agentOption = await screen.findByText('agent debugger')
|
||||||
fireEvent.click(agentOption)
|
fireEvent.click(agentOption)
|
||||||
|
|
||||||
expect(input).toHaveValue('/agent debugger ')
|
expect(input).toHaveValue('/agent debugger ')
|
||||||
@ -1737,7 +1773,7 @@ describe('ChatInput file mentions', () => {
|
|||||||
target: { value: '/agent', selectionStart: 6 },
|
target: { value: '/agent', selectionStart: 6 },
|
||||||
})
|
})
|
||||||
|
|
||||||
await screen.findByText('/agent debugger')
|
await screen.findByText('agent debugger')
|
||||||
fireEvent.keyDown(input, { key: 'ArrowDown' })
|
fireEvent.keyDown(input, { key: 'ArrowDown' })
|
||||||
fireEvent.keyDown(input, { key: 'Enter' })
|
fireEvent.keyDown(input, { key: 'Enter' })
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,28 @@
|
|||||||
import { useState, useRef, useEffect, useCallback, useMemo } from 'react'
|
import { useState, useRef, useEffect, useCallback, useMemo } from 'react'
|
||||||
|
import {
|
||||||
|
Bot,
|
||||||
|
Box,
|
||||||
|
Bug,
|
||||||
|
CircleDollarSign,
|
||||||
|
CircleGauge,
|
||||||
|
Command as CommandIcon,
|
||||||
|
Eraser,
|
||||||
|
GitCommitHorizontal,
|
||||||
|
GitPullRequest,
|
||||||
|
HelpCircle,
|
||||||
|
LogIn,
|
||||||
|
LogOut,
|
||||||
|
Package,
|
||||||
|
PanelTop,
|
||||||
|
Settings,
|
||||||
|
ShieldCheck,
|
||||||
|
Sparkles,
|
||||||
|
Target,
|
||||||
|
Terminal,
|
||||||
|
Wrench,
|
||||||
|
Zap,
|
||||||
|
type LucideIcon,
|
||||||
|
} from 'lucide-react'
|
||||||
import { useDismissable } from '@/hooks/useDismissable'
|
import { useDismissable } from '@/hooks/useDismissable'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { IconButton } from '@/components/ui/IconButton'
|
import { IconButton } from '@/components/ui/IconButton'
|
||||||
@ -33,6 +57,7 @@ import {
|
|||||||
getLocalizedFallbackCommands,
|
getLocalizedFallbackCommands,
|
||||||
filterSlashCommands,
|
filterSlashCommands,
|
||||||
findSlashTrigger,
|
findSlashTrigger,
|
||||||
|
groupSlashCommands,
|
||||||
mergeSlashCommands,
|
mergeSlashCommands,
|
||||||
replaceSlashToken,
|
replaceSlashToken,
|
||||||
resolveSlashUiAction,
|
resolveSlashUiAction,
|
||||||
@ -61,6 +86,38 @@ type ChatInputProps = {
|
|||||||
|
|
||||||
const EMPTY_WORKSPACE_REFERENCES: WorkspaceChatReference[] = []
|
const EMPTY_WORKSPACE_REFERENCES: WorkspaceChatReference[] = []
|
||||||
|
|
||||||
|
const SYSTEM_SLASH_COMMAND_ICONS: Record<string, LucideIcon> = {
|
||||||
|
agent: Bot,
|
||||||
|
mcp: Wrench,
|
||||||
|
skills: Package,
|
||||||
|
help: HelpCircle,
|
||||||
|
status: CircleGauge,
|
||||||
|
cost: CircleDollarSign,
|
||||||
|
context: PanelTop,
|
||||||
|
plugin: Package,
|
||||||
|
memory: Sparkles,
|
||||||
|
doctor: Wrench,
|
||||||
|
compact: Zap,
|
||||||
|
clear: Eraser,
|
||||||
|
goal: Target,
|
||||||
|
review: ShieldCheck,
|
||||||
|
commit: GitCommitHorizontal,
|
||||||
|
pr: GitPullRequest,
|
||||||
|
bug: Bug,
|
||||||
|
config: Settings,
|
||||||
|
login: LogIn,
|
||||||
|
logout: LogOut,
|
||||||
|
model: Bot,
|
||||||
|
permissions: ShieldCheck,
|
||||||
|
'terminal-setup': Terminal,
|
||||||
|
vim: CommandIcon,
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSystemSlashCommandIcon(commandName: string): LucideIcon {
|
||||||
|
const rootCommand = commandName.trim().split(/\s+/, 1)[0] ?? ''
|
||||||
|
return SYSTEM_SLASH_COMMAND_ICONS[rootCommand] ?? CommandIcon
|
||||||
|
}
|
||||||
|
|
||||||
function workspaceReferenceToAttachment(reference: WorkspaceChatReference): Attachment {
|
function workspaceReferenceToAttachment(reference: WorkspaceChatReference): Attachment {
|
||||||
return {
|
return {
|
||||||
id: reference.id,
|
id: reference.id,
|
||||||
@ -121,7 +178,7 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro
|
|||||||
const plusMenuRef = useRef<HTMLDivElement>(null)
|
const plusMenuRef = useRef<HTMLDivElement>(null)
|
||||||
const slashMenuRef = useRef<HTMLDivElement>(null)
|
const slashMenuRef = useRef<HTMLDivElement>(null)
|
||||||
const fileSearchRef = useRef<FileSearchMenuHandle>(null)
|
const fileSearchRef = useRef<FileSearchMenuHandle>(null)
|
||||||
const slashItemRefs = useRef<(HTMLButtonElement | null)[]>([])
|
const slashItemRefs = useRef<(HTMLElement | null)[]>([])
|
||||||
const previousActiveTabIdRef = useRef<string | null>(null)
|
const previousActiveTabIdRef = useRef<string | null>(null)
|
||||||
const inputRef = useRef(input)
|
const inputRef = useRef(input)
|
||||||
const attachmentsRef = useRef(attachments)
|
const attachmentsRef = useRef(attachments)
|
||||||
@ -459,10 +516,12 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro
|
|||||||
[agentSlashCommands, slashCommands, t],
|
[agentSlashCommands, slashCommands, t],
|
||||||
)
|
)
|
||||||
|
|
||||||
const filteredCommands = useMemo(() => {
|
const filteredCommandGroups = useMemo(() => {
|
||||||
return filterSlashCommands(allSlashCommands, slashFilter)
|
return groupSlashCommands(filterSlashCommands(allSlashCommands, slashFilter))
|
||||||
}, [allSlashCommands, slashFilter])
|
}, [allSlashCommands, slashFilter])
|
||||||
|
|
||||||
|
const filteredCommands = filteredCommandGroups.ordered
|
||||||
|
|
||||||
const exactSlashCommand = useMemo(() => {
|
const exactSlashCommand = useMemo(() => {
|
||||||
const normalized = slashFilter.trim().toLowerCase()
|
const normalized = slashFilter.trim().toLowerCase()
|
||||||
if (!normalized) return null
|
if (!normalized) return null
|
||||||
@ -1065,34 +1124,97 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro
|
|||||||
// effort, branch and worktree panels above this row already use.
|
// effort, branch and worktree panels above this row already use.
|
||||||
className="absolute bottom-full left-0 right-0 z-[var(--z-dropdown)] mb-2 overflow-hidden rounded-[var(--radius-xl)] border border-[var(--color-border)] bg-[var(--color-surface-container-lowest)] shadow-[var(--shadow-overlay)]"
|
className="absolute bottom-full left-0 right-0 z-[var(--z-dropdown)] mb-2 overflow-hidden rounded-[var(--radius-xl)] border border-[var(--color-border)] bg-[var(--color-surface-container-lowest)] shadow-[var(--shadow-overlay)]"
|
||||||
>
|
>
|
||||||
<div className="max-h-[300px] overflow-y-auto py-1">
|
<div
|
||||||
{filteredCommands.map((command, index) => (
|
role="listbox"
|
||||||
<button
|
aria-label={t('chat.slashCommands')}
|
||||||
key={command.name}
|
className="max-h-[420px] overflow-y-auto p-1.5"
|
||||||
ref={(el) => { slashItemRefs.current[index] = el }}
|
>
|
||||||
onClick={() => selectSlashCommand(command.name)}
|
{filteredCommandGroups.system.map((command, index) => {
|
||||||
onMouseEnter={() => setSlashSelectedIndex(index)}
|
const Icon = getSystemSlashCommandIcon(command.name)
|
||||||
className={`flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors ${
|
return (
|
||||||
index === slashSelectedIndex
|
<div
|
||||||
? 'bg-[var(--color-surface-hover)]'
|
key={command.name}
|
||||||
: 'hover:bg-[var(--color-surface-hover)]'
|
role="option"
|
||||||
}`}
|
tabIndex={-1}
|
||||||
>
|
aria-selected={index === slashSelectedIndex}
|
||||||
<span className="flex min-w-0 max-w-[52%] shrink-0 items-baseline gap-1.5">
|
ref={(el) => { slashItemRefs.current[index] = el }}
|
||||||
<span className="shrink-0 text-sm font-semibold text-[var(--color-text-primary)]">
|
onClick={() => selectSlashCommand(command.name)}
|
||||||
/{command.name}
|
onMouseEnter={() => setSlashSelectedIndex(index)}
|
||||||
</span>
|
className={`flex w-full cursor-default items-center gap-3 rounded-[var(--radius-md)] px-3 py-2 text-left transition-colors ${
|
||||||
{command.argumentHint ? (
|
index === slashSelectedIndex
|
||||||
<span className="min-w-0 truncate font-mono text-[11px] text-[var(--color-text-tertiary)]">
|
? 'bg-[var(--color-surface-hover)]'
|
||||||
{command.argumentHint}
|
: 'hover:bg-[var(--color-surface-hover)]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="h-4 w-4 shrink-0 text-[var(--color-text-secondary)]"
|
||||||
|
strokeWidth={1.8}
|
||||||
|
/>
|
||||||
|
<span className="flex min-w-0 max-w-[52%] shrink-0 items-baseline gap-1.5">
|
||||||
|
<span className="shrink-0 text-sm font-medium text-[var(--color-text-primary)]">
|
||||||
|
{command.name}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
{command.argumentHint ? (
|
||||||
</span>
|
<span className="min-w-0 truncate font-mono text-[11px] text-[var(--color-text-tertiary)]">
|
||||||
<span className="min-w-0 flex-1 truncate text-xs text-[var(--color-text-tertiary)]">
|
{command.argumentHint}
|
||||||
{command.description}
|
</span>
|
||||||
</span>
|
) : null}
|
||||||
</button>
|
</span>
|
||||||
))}
|
<span className="min-w-0 flex-1 truncate text-right text-xs text-[var(--color-text-tertiary)]">
|
||||||
|
{command.description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
{filteredCommandGroups.skills.length > 0 ? (
|
||||||
|
<div
|
||||||
|
role="group"
|
||||||
|
aria-label={t('sidebar.skills')}
|
||||||
|
className={filteredCommandGroups.system.length > 0
|
||||||
|
? 'mt-1 border-t border-[var(--color-border-separator)] pt-1'
|
||||||
|
: ''}
|
||||||
|
>
|
||||||
|
<div className="px-2.5 py-1.5 text-xs font-medium text-[var(--color-text-tertiary)]">
|
||||||
|
{t('sidebar.skills')}
|
||||||
|
</div>
|
||||||
|
{filteredCommandGroups.skills.map((command, skillIndex) => {
|
||||||
|
const index = filteredCommandGroups.system.length + skillIndex
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={command.name}
|
||||||
|
role="option"
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-selected={index === slashSelectedIndex}
|
||||||
|
ref={(el) => { slashItemRefs.current[index] = el }}
|
||||||
|
onClick={() => selectSlashCommand(command.name)}
|
||||||
|
onMouseEnter={() => setSlashSelectedIndex(index)}
|
||||||
|
className={`flex w-full cursor-default items-center gap-3 rounded-[var(--radius-md)] px-3 py-2 text-left transition-colors ${
|
||||||
|
index === slashSelectedIndex
|
||||||
|
? 'bg-[var(--color-surface-hover)]'
|
||||||
|
: 'hover:bg-[var(--color-surface-hover)]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
aria-hidden="true"
|
||||||
|
className="h-4 w-4 shrink-0 text-[var(--color-text-secondary)]"
|
||||||
|
strokeWidth={1.8}
|
||||||
|
/>
|
||||||
|
<span className="min-w-0 shrink-0 truncate text-sm font-medium text-[var(--color-text-primary)]">
|
||||||
|
{command.name}
|
||||||
|
</span>
|
||||||
|
<span className="min-w-0 flex-1 truncate text-right text-xs text-[var(--color-text-tertiary)]">
|
||||||
|
{command.description}
|
||||||
|
</span>
|
||||||
|
<span className="shrink-0 text-xs text-[var(--color-text-tertiary)]">
|
||||||
|
{t('chat.slashSkillPersonal')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{!isMobileComposer ? (
|
{!isMobileComposer ? (
|
||||||
<div className="flex items-center gap-1.5 border-t border-[var(--color-border)] px-4 py-2 text-xs text-[var(--color-text-tertiary)]">
|
<div className="flex items-center gap-1.5 border-t border-[var(--color-border)] px-4 py-2 text-xs text-[var(--color-text-tertiary)]">
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
filterSlashCommands,
|
filterSlashCommands,
|
||||||
findSlashToken,
|
findSlashToken,
|
||||||
getLocalizedFallbackCommands,
|
getLocalizedFallbackCommands,
|
||||||
|
groupSlashCommands,
|
||||||
insertSlashTrigger,
|
insertSlashTrigger,
|
||||||
mergeSlashCommands,
|
mergeSlashCommands,
|
||||||
replaceSlashCommand,
|
replaceSlashCommand,
|
||||||
@ -161,6 +162,33 @@ describe('composerUtils', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('groups built-in app commands before personal skills without changing their relative order', () => {
|
||||||
|
const groups = groupSlashCommands([
|
||||||
|
{ name: 'amazon-review-scraper', description: 'Collect Amazon reviews' },
|
||||||
|
{ name: 'status', description: 'Show session status' },
|
||||||
|
{ name: 'agent debugger', description: 'Run the debugger agent' },
|
||||||
|
{ name: 'audit', description: 'Audit product UX' },
|
||||||
|
{ name: 'model', description: 'Switch model' },
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(groups.system.map((command) => command.name)).toEqual([
|
||||||
|
'status',
|
||||||
|
'agent debugger',
|
||||||
|
'model',
|
||||||
|
])
|
||||||
|
expect(groups.skills.map((command) => command.name)).toEqual([
|
||||||
|
'amazon-review-scraper',
|
||||||
|
'audit',
|
||||||
|
])
|
||||||
|
expect(groups.ordered.map((command) => command.name)).toEqual([
|
||||||
|
'status',
|
||||||
|
'agent debugger',
|
||||||
|
'model',
|
||||||
|
'amazon-review-scraper',
|
||||||
|
'audit',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('resolves hidden settings aliases without displaying duplicate fallback rows', () => {
|
it('resolves hidden settings aliases without displaying duplicate fallback rows', () => {
|
||||||
expect(resolveSlashUiAction('plugins')).toEqual({ type: 'settings', tab: 'plugins' })
|
expect(resolveSlashUiAction('plugins')).toEqual({ type: 'settings', tab: 'plugins' })
|
||||||
expect(resolveSlashUiAction('memory')).toEqual({ type: 'settings', tab: 'memory' })
|
expect(resolveSlashUiAction('memory')).toEqual({ type: 'settings', tab: 'memory' })
|
||||||
|
|||||||
@ -119,6 +119,38 @@ export type SlashCommandOption = {
|
|||||||
argumentHint?: string
|
argumentHint?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SlashCommandGroups = {
|
||||||
|
system: SlashCommandOption[]
|
||||||
|
skills: SlashCommandOption[]
|
||||||
|
ordered: SlashCommandOption[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSystemSlashCommand(command: SlashCommandOption): boolean {
|
||||||
|
const rootCommand = command.name.trim().split(/\s+/, 1)[0]
|
||||||
|
return Boolean(rootCommand && BUILT_IN_COMMAND_NAMES.has(rootCommand))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function groupSlashCommands(
|
||||||
|
commands: ReadonlyArray<SlashCommandOption>,
|
||||||
|
): SlashCommandGroups {
|
||||||
|
const system: SlashCommandOption[] = []
|
||||||
|
const skills: SlashCommandOption[] = []
|
||||||
|
|
||||||
|
for (const command of commands) {
|
||||||
|
if (isSystemSlashCommand(command)) {
|
||||||
|
system.push(command)
|
||||||
|
} else {
|
||||||
|
skills.push(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
system,
|
||||||
|
skills,
|
||||||
|
ordered: [...system, ...skills],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type AgentSlashCommandSource = {
|
export type AgentSlashCommandSource = {
|
||||||
agentType: string
|
agentType: string
|
||||||
description?: string
|
description?: string
|
||||||
|
|||||||
@ -1580,6 +1580,7 @@ Row 9, all 8 cells: continuing from straight down, turning left through lower-le
|
|||||||
'chat.conversationNavigator.label': 'Conversation navigation',
|
'chat.conversationNavigator.label': 'Conversation navigation',
|
||||||
'chat.conversationNavigator.attachments': '{count} attachments',
|
'chat.conversationNavigator.attachments': '{count} attachments',
|
||||||
'chat.slashCommands': 'Slash commands',
|
'chat.slashCommands': 'Slash commands',
|
||||||
|
'chat.slashSkillPersonal': 'Personal',
|
||||||
'chat.pendingMessageGuide': 'Guide',
|
'chat.pendingMessageGuide': 'Guide',
|
||||||
'chat.pendingMessageGuideNow': 'Guide now',
|
'chat.pendingMessageGuideNow': 'Guide now',
|
||||||
'chat.pendingMessageEdit': 'Edit queued message',
|
'chat.pendingMessageEdit': 'Edit queued message',
|
||||||
|
|||||||
@ -1582,6 +1582,7 @@ export const jp: Record<TranslationKey, string> = {
|
|||||||
'chat.conversationNavigator.label': '会話ナビゲーション',
|
'chat.conversationNavigator.label': '会話ナビゲーション',
|
||||||
'chat.conversationNavigator.attachments': '添付ファイル {count} 件',
|
'chat.conversationNavigator.attachments': '添付ファイル {count} 件',
|
||||||
'chat.slashCommands': 'スラッシュコマンド',
|
'chat.slashCommands': 'スラッシュコマンド',
|
||||||
|
'chat.slashSkillPersonal': '個人',
|
||||||
'chat.pendingMessageGuide': '誘導',
|
'chat.pendingMessageGuide': '誘導',
|
||||||
'chat.pendingMessageGuideNow': '今すぐ誘導',
|
'chat.pendingMessageGuideNow': '今すぐ誘導',
|
||||||
'chat.pendingMessageEdit': 'キュー中のメッセージを編集',
|
'chat.pendingMessageEdit': 'キュー中のメッセージを編集',
|
||||||
|
|||||||
@ -1582,6 +1582,7 @@ export const kr: Record<TranslationKey, string> = {
|
|||||||
'chat.conversationNavigator.label': '대화 탐색',
|
'chat.conversationNavigator.label': '대화 탐색',
|
||||||
'chat.conversationNavigator.attachments': '첨부 파일 {count}개',
|
'chat.conversationNavigator.attachments': '첨부 파일 {count}개',
|
||||||
'chat.slashCommands': '슬래시 명령',
|
'chat.slashCommands': '슬래시 명령',
|
||||||
|
'chat.slashSkillPersonal': '개인',
|
||||||
'chat.pendingMessageGuide': '가이드',
|
'chat.pendingMessageGuide': '가이드',
|
||||||
'chat.pendingMessageGuideNow': '지금 가이드',
|
'chat.pendingMessageGuideNow': '지금 가이드',
|
||||||
'chat.pendingMessageEdit': '대기 메시지 편집',
|
'chat.pendingMessageEdit': '대기 메시지 편집',
|
||||||
|
|||||||
@ -1581,6 +1581,7 @@ export const zh: Record<TranslationKey, string> = {
|
|||||||
'chat.conversationNavigator.label': '對話導覽',
|
'chat.conversationNavigator.label': '對話導覽',
|
||||||
'chat.conversationNavigator.attachments': '{count} 個附件',
|
'chat.conversationNavigator.attachments': '{count} 個附件',
|
||||||
'chat.slashCommands': '斜槓命令',
|
'chat.slashCommands': '斜槓命令',
|
||||||
|
'chat.slashSkillPersonal': '個人',
|
||||||
'chat.pendingMessageGuide': '引導',
|
'chat.pendingMessageGuide': '引導',
|
||||||
'chat.pendingMessageGuideNow': '立即引導',
|
'chat.pendingMessageGuideNow': '立即引導',
|
||||||
'chat.pendingMessageEdit': '編輯排隊訊息',
|
'chat.pendingMessageEdit': '編輯排隊訊息',
|
||||||
|
|||||||
@ -1581,6 +1581,7 @@ export const zh: Record<TranslationKey, string> = {
|
|||||||
'chat.conversationNavigator.label': '对话导航',
|
'chat.conversationNavigator.label': '对话导航',
|
||||||
'chat.conversationNavigator.attachments': '{count} 个附件',
|
'chat.conversationNavigator.attachments': '{count} 个附件',
|
||||||
'chat.slashCommands': '斜杠命令',
|
'chat.slashCommands': '斜杠命令',
|
||||||
|
'chat.slashSkillPersonal': '个人',
|
||||||
'chat.pendingMessageGuide': '引导',
|
'chat.pendingMessageGuide': '引导',
|
||||||
'chat.pendingMessageGuideNow': '立即引导',
|
'chat.pendingMessageGuideNow': '立即引导',
|
||||||
'chat.pendingMessageEdit': '编辑排队消息',
|
'chat.pendingMessageEdit': '编辑排队消息',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user