From 0ef7bf929eeb5ab021508f72c8e65d8e5f5fd6ed Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Fri, 31 Jul 2026 15:53:11 +0800 Subject: [PATCH] fix(skills): refine detail interactions and rename flow --- .../skills/__tests__/detail-page.spec.tsx | 278 +++++--- web/features/skills/__tests__/page.spec.tsx | 36 +- web/features/skills/detail/builder-panel.tsx | 5 +- web/features/skills/detail/file-editor.tsx | 50 +- web/features/skills/detail/file-tree-dnd.tsx | 2 +- .../skills/detail/file-tree-items.tsx | 4 +- web/features/skills/detail/file-tree.tsx | 601 +++++++++--------- .../skills/detail/markdown-editor.tsx | 4 +- web/features/skills/detail/page.tsx | 2 +- web/features/skills/detail/shell.tsx | 2 +- .../detail/skill-display-name-editor.tsx | 179 ++++++ web/features/skills/detail/skill-metadata.tsx | 6 +- web/features/skills/detail/version-panel.tsx | 4 +- web/features/skills/page.tsx | 14 +- web/i18n-config/resources.ts | 3 + web/i18n/ar-TN/agent-v-2.json | 114 +--- web/i18n/ar-TN/skill.json | 115 ++++ web/i18n/de-DE/agent-v-2.json | 114 +--- web/i18n/de-DE/skill.json | 115 ++++ web/i18n/en-US/agent-v-2.json | 183 +----- web/i18n/en-US/skill.json | 184 ++++++ web/i18n/es-ES/agent-v-2.json | 114 +--- web/i18n/es-ES/skill.json | 115 ++++ web/i18n/fa-IR/agent-v-2.json | 114 +--- web/i18n/fa-IR/skill.json | 115 ++++ web/i18n/fr-FR/agent-v-2.json | 114 +--- web/i18n/fr-FR/skill.json | 115 ++++ web/i18n/hi-IN/agent-v-2.json | 114 +--- web/i18n/hi-IN/skill.json | 115 ++++ web/i18n/id-ID/agent-v-2.json | 114 +--- web/i18n/id-ID/skill.json | 115 ++++ web/i18n/it-IT/agent-v-2.json | 114 +--- web/i18n/it-IT/skill.json | 115 ++++ web/i18n/ja-JP/agent-v-2.json | 149 +---- web/i18n/ja-JP/skill.json | 150 +++++ web/i18n/ko-KR/agent-v-2.json | 114 +--- web/i18n/ko-KR/skill.json | 115 ++++ web/i18n/nl-NL/agent-v-2.json | 114 +--- web/i18n/nl-NL/skill.json | 115 ++++ web/i18n/pl-PL/agent-v-2.json | 114 +--- web/i18n/pl-PL/skill.json | 115 ++++ web/i18n/pt-BR/agent-v-2.json | 114 +--- web/i18n/pt-BR/skill.json | 115 ++++ web/i18n/ro-RO/agent-v-2.json | 114 +--- web/i18n/ro-RO/skill.json | 115 ++++ web/i18n/ru-RU/agent-v-2.json | 114 +--- web/i18n/ru-RU/skill.json | 115 ++++ web/i18n/sl-SI/agent-v-2.json | 114 +--- web/i18n/sl-SI/skill.json | 115 ++++ web/i18n/th-TH/agent-v-2.json | 114 +--- web/i18n/th-TH/skill.json | 115 ++++ web/i18n/tr-TR/agent-v-2.json | 114 +--- web/i18n/tr-TR/skill.json | 115 ++++ web/i18n/uk-UA/agent-v-2.json | 114 +--- web/i18n/uk-UA/skill.json | 115 ++++ web/i18n/vi-VN/agent-v-2.json | 114 +--- web/i18n/vi-VN/skill.json | 115 ++++ web/i18n/zh-Hans/agent-v-2.json | 183 +----- web/i18n/zh-Hans/skill.json | 184 ++++++ web/i18n/zh-Hant/agent-v-2.json | 114 +--- web/i18n/zh-Hant/skill.json | 115 ++++ 61 files changed, 3583 insertions(+), 3220 deletions(-) create mode 100644 web/features/skills/detail/skill-display-name-editor.tsx create mode 100644 web/i18n/ar-TN/skill.json create mode 100644 web/i18n/de-DE/skill.json create mode 100644 web/i18n/en-US/skill.json create mode 100644 web/i18n/es-ES/skill.json create mode 100644 web/i18n/fa-IR/skill.json create mode 100644 web/i18n/fr-FR/skill.json create mode 100644 web/i18n/hi-IN/skill.json create mode 100644 web/i18n/id-ID/skill.json create mode 100644 web/i18n/it-IT/skill.json create mode 100644 web/i18n/ja-JP/skill.json create mode 100644 web/i18n/ko-KR/skill.json create mode 100644 web/i18n/nl-NL/skill.json create mode 100644 web/i18n/pl-PL/skill.json create mode 100644 web/i18n/pt-BR/skill.json create mode 100644 web/i18n/ro-RO/skill.json create mode 100644 web/i18n/ru-RU/skill.json create mode 100644 web/i18n/sl-SI/skill.json create mode 100644 web/i18n/th-TH/skill.json create mode 100644 web/i18n/tr-TR/skill.json create mode 100644 web/i18n/uk-UA/skill.json create mode 100644 web/i18n/vi-VN/skill.json create mode 100644 web/i18n/zh-Hans/skill.json create mode 100644 web/i18n/zh-Hant/skill.json diff --git a/web/features/skills/__tests__/detail-page.spec.tsx b/web/features/skills/__tests__/detail-page.spec.tsx index 4908784d8e7..130f1129c3a 100644 --- a/web/features/skills/__tests__/detail-page.spec.tsx +++ b/web/features/skills/__tests__/detail-page.spec.tsx @@ -547,12 +547,31 @@ describe('SkillDetailPage', () => { }) }) + it('matches the Figma Skill sidebar navigation structure and spacing', async () => { + renderSkillDetailPage() + + const sidebar = await screen.findByTestId('skill-detail-sidebar') + const header = screen.getByTestId('skill-detail-sidebar-header') + + expect(sidebar).toHaveClass('w-[248px]', 'bg-background-body', 'p-1') + expect(sidebar.firstElementChild).toHaveClass('rounded-lg', 'bg-background-default') + expect(header).toHaveClass('h-12', 'py-2', 'pr-2', 'pl-1') + expect(header.querySelector('.i-ri-arrow-left-s-line')).toBeInTheDocument() + expect(header.querySelector('.i-custom-vender-main-nav-app-home')).toBeInTheDocument() + expect(header).toHaveTextContent('/SKILLS') + expect( + screen.getByRole('button', { + name: 'skill.skillManagement.detail.searchFiles', + }), + ).toHaveClass('size-8', 'rounded-[10px]') + }) + it('opens the inline tag selector with workspace tag options', async () => { const user = userEvent.setup() renderSkillDetailPage() const addTagButton = await screen.findByRole('combobox', { - name: 'agentV2.skillManagement.detail.addTag', + name: 'skill.skillManagement.detail.addTag', }) await user.click(addTagButton) @@ -571,7 +590,7 @@ describe('SkillDetailPage', () => { ).toBeInTheDocument() expect( screen.queryByRole('heading', { - name: 'agentV2.skillManagement.detail.addTag', + name: 'skill.skillManagement.detail.addTag', }), ).not.toBeInTheDocument() }) @@ -582,7 +601,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('combobox', { - name: 'agentV2.skillManagement.detail.addTag', + name: 'skill.skillManagement.detail.addTag', }), ) await user.click(await screen.findByRole('option', { name: 'Search' })) @@ -610,7 +629,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('combobox', { - name: 'agentV2.skillManagement.detail.addTag', + name: 'skill.skillManagement.detail.addTag', }), ) await user.click(await screen.findByRole('option', { name: 'Search' })) @@ -634,7 +653,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('combobox', { - name: 'agentV2.skillManagement.detail.addTag', + name: 'skill.skillManagement.detail.addTag', }), ) await user.type( @@ -658,7 +677,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('combobox', { - name: 'agentV2.skillManagement.detail.addTag', + name: 'skill.skillManagement.detail.addTag', }), ) await user.type( @@ -704,7 +723,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('combobox', { - name: 'agentV2.skillManagement.detail.addTag', + name: 'skill.skillManagement.detail.addTag', }), ) await user.click(await screen.findByRole('option', { name: 'Search' })) @@ -730,7 +749,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('combobox', { - name: 'agentV2.skillManagement.detail.addTag', + name: 'skill.skillManagement.detail.addTag', }), ) await user.click(await screen.findByRole('button', { name: 'common.tag.manageTags' })) @@ -751,7 +770,7 @@ describe('SkillDetailPage', () => { expect(screen.getByText('Pre-sales')).toBeInTheDocument() await user.click( screen.getByRole('button', { - name: 'agentV2.skillManagement.detail.removeTag:{"tag":"Search"}', + name: 'skill.skillManagement.detail.removeTag:{"tag":"Search"}', }), ) @@ -788,7 +807,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.removeTag:{"tag":"Search"}', + name: 'skill.skillManagement.detail.removeTag:{"tag":"Search"}', }), ) @@ -829,7 +848,7 @@ describe('SkillDetailPage', () => { }) const { container } = renderSkillDetailPage() - await screen.findByText('agentV2.skillManagement.detail.builder.title') + await screen.findByText('skill.skillManagement.detail.builder.title') fireEvent.click(getFileTreeButton('references/guide.md')) await waitFor(() => { @@ -859,10 +878,10 @@ describe('SkillDetailPage', () => { }) renderSkillDetailPage() - await screen.findByText('agentV2.skillManagement.detail.builder.title') + await screen.findByText('skill.skillManagement.detail.builder.title') fireEvent.click(getFileTreeButton('scripts/action.ts')) - expect(await screen.findByText('agentV2.skillManagement.detail.loadFailed')).toBeInTheDocument() + expect(await screen.findByText('skill.skillManagement.detail.loadFailed')).toBeInTheDocument() expect(screen.queryByLabelText('code-editor')).not.toBeInTheDocument() expect(mocks.saveDraftFileMutationFn).not.toHaveBeenCalled() }) @@ -874,7 +893,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.markdownSourceMode', + name: 'skill.skillManagement.detail.markdownSourceMode', }), ) await user.type(getSourceEditor(), '\nNew instructions') @@ -895,7 +914,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.markdownSourceMode', + name: 'skill.skillManagement.detail.markdownSourceMode', }), ) await user.type(getSourceEditor(), '\nNew instructions') @@ -948,14 +967,14 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.markdownSourceMode', + name: 'skill.skillManagement.detail.markdownSourceMode', }), ) await user.type(getSourceEditor(), '\nMy tab changes') await waitFor( () => { - expect(toast.error).toHaveBeenCalledWith('agentV2.skillManagement.detail.saveConflict') + expect(toast.error).toHaveBeenCalledWith('skill.skillManagement.detail.saveConflict') }, { timeout: 4000 }, ) @@ -963,7 +982,7 @@ describe('SkillDetailPage', () => { expect(mocks.skillDetailGetFn).toHaveBeenCalledTimes(1) await waitFor(() => { expect( - screen.getByText(/agentV2\.skillManagement\.detail\.saveConflictStatus/), + screen.getByText(/skill\.skillManagement\.detail\.saveConflictStatus/), ).toBeInTheDocument() }) @@ -1000,21 +1019,21 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.markdownSourceMode', + name: 'skill.skillManagement.detail.markdownSourceMode', }), ) await user.type(getSourceEditor(), '\nMy response error changes') await waitFor( () => { - expect(toast.error).toHaveBeenCalledWith('agentV2.skillManagement.detail.saveConflict') + expect(toast.error).toHaveBeenCalledWith('skill.skillManagement.detail.saveConflict') }, { timeout: 4000 }, ) expect(mocks.saveDraftFileMutationFn).toHaveBeenCalledTimes(1) await waitFor(() => { expect( - screen.getByText(/agentV2\.skillManagement\.detail\.saveConflictStatus/), + screen.getByText(/skill\.skillManagement\.detail\.saveConflictStatus/), ).toBeInTheDocument() }) @@ -1035,7 +1054,7 @@ describe('SkillDetailPage', () => { const displayNameInput = await screen.findByDisplayValue('Untitled skill') await user.clear(displayNameInput) await user.type(displayNameInput, '333333333') - await user.click(screen.getByRole('button', { name: 'agentV2.skillManagement.detail.publish' })) + await user.click(screen.getByRole('button', { name: 'skill.skillManagement.detail.publish' })) await waitFor(() => { expect(mocks.saveDraftFileMutationFn).toHaveBeenCalled() @@ -1055,12 +1074,101 @@ describe('SkillDetailPage', () => { }) }) + it('renames the skill from the sidebar title and keeps SKILL.md metadata in sync', async () => { + const user = userEvent.setup() + renderSkillDetailPage() + + const renameButton = await screen.findByRole('button', { name: 'common.operation.rename' }) + expect(renameButton).toHaveClass('system-md-semibold', 'hover:bg-state-base-hover') + + await user.click(renameButton) + const renameInput = screen.getByRole('textbox', { name: 'common.operation.rename' }) + expect(renameInput).toHaveFocus() + expect(renameInput).toHaveValue('Untitled skill') + expect(renameInput).toHaveProperty('selectionStart', 0) + expect(renameInput).toHaveProperty('selectionEnd', 'Untitled skill'.length) + expect(renameInput).toHaveClass( + 'border-components-input-border-active', + 'bg-components-input-bg-active', + 'shadow-xs', + ) + + await user.clear(renameInput) + await user.type(renameInput, 'Renamed skill{Enter}') + + await waitFor(() => { + expect(mocks.saveDraftFileMutationFn).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + content: expect.stringMatching( + /name: github-actions-failure-debugging[\s\S]*display-name: Renamed skill/, + ), + operation: 'upsert_text', + path: 'SKILL.md', + }), + }), + expect.anything(), + ) + }) + const savedContent = mocks.saveDraftFileMutationFn.mock.calls[0]?.[0].body.content + expect(savedContent).not.toContain('name: renamed-skill') + expect(mocks.saveDraftFileMutationFn).toHaveBeenCalledTimes(1) + expect(mocks.skillMetadataMutationFn).not.toHaveBeenCalled() + expect(toast.success).toHaveBeenCalledWith('skill.skillManagement.detail.renameSkillSuccess') + await waitFor(() => { + expect(screen.getByRole('button', { name: 'common.operation.rename' })).toHaveTextContent( + 'Renamed skill', + ) + }) + }) + + it('updates display-name from the manifest editor without changing name', async () => { + const user = userEvent.setup() + renderSkillDetailPage() + + const displayNameInput = await screen.findByDisplayValue('Untitled skill') + await user.clear(displayNameInput) + await user.type(displayNameInput, 'Editor Display Name') + await user.tab() + + await waitFor(() => { + expect(mocks.saveDraftFileMutationFn).toHaveBeenCalled() + }) + const savedContent = mocks.saveDraftFileMutationFn.mock.calls.at(-1)?.[0].body.content + expect(savedContent).toMatch( + /name: github-actions-failure-debugging[\s\S]*display-name: Editor Display Name/, + ) + expect(savedContent).not.toContain('name: editor-display-name') + expect(mocks.skillMetadataMutationFn).not.toHaveBeenCalled() + expect(toast.success).toHaveBeenCalledWith('skill.skillManagement.detail.renameSkillSuccess') + }) + + it('cancels an empty sidebar rename when the field loses focus', async () => { + const user = userEvent.setup() + renderSkillDetailPage() + + await user.click(await screen.findByRole('button', { name: 'common.operation.rename' })) + const renameInput = screen.getByRole('textbox', { name: 'common.operation.rename' }) + await user.clear(renameInput) + await user.tab() + + expect( + screen.queryByRole('textbox', { name: 'common.operation.rename' }), + ).not.toBeInTheDocument() + expect(screen.getByRole('button', { name: 'common.operation.rename' })).toHaveTextContent( + 'Untitled skill', + ) + expect(mocks.saveDraftFileMutationFn).not.toHaveBeenCalled() + expect(mocks.skillMetadataMutationFn).not.toHaveBeenCalled() + expect(toast.success).not.toHaveBeenCalled() + }) + it('marks the draft as published and disables publish until new edits are made', async () => { const user = userEvent.setup() renderSkillDetailPage() const publishButton = await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.publish', + name: 'skill.skillManagement.detail.publish', }) expect(publishButton).toBeEnabled() @@ -1071,7 +1179,7 @@ describe('SkillDetailPage', () => { }) await waitFor(() => { expect(document.body).toHaveTextContent( - 'agentV2.skillManagement.detail.publishedVersion:{"number":2}', + 'skill.skillManagement.detail.publishedVersion:{"number":2}', ) }) expect(publishButton).toBeDisabled() @@ -1089,21 +1197,21 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.addMetadata', + name: 'skill.skillManagement.detail.addMetadata', }), ) await user.type( - screen.getByPlaceholderText('agentV2.skillManagement.detail.metadataKey'), + screen.getByPlaceholderText('skill.skillManagement.detail.metadataKey'), 'owner', ) await user.type( - screen.getByPlaceholderText('agentV2.skillManagement.detail.metadataValue'), + screen.getByPlaceholderText('skill.skillManagement.detail.metadataValue'), 'support{Enter}', ) expect(await screen.findByText('owner')).toBeInTheDocument() expect(screen.getByText('support')).toBeInTheDocument() - await user.click(screen.getByRole('button', { name: 'agentV2.skillManagement.detail.publish' })) + await user.click(screen.getByRole('button', { name: 'skill.skillManagement.detail.publish' })) await waitFor(() => { expect(mocks.saveDraftFileMutationFn).toHaveBeenCalledWith( @@ -1142,7 +1250,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.markdownSourceMode', + name: 'skill.skillManagement.detail.markdownSourceMode', }), ) await user.click(await screen.findByText('references')) @@ -1160,7 +1268,7 @@ describe('SkillDetailPage', () => { expect(screen.queryByDisplayValue('Refund policy.')).not.toBeInTheDocument() expect(screen.queryByDisplayValue('Refund Policy')).not.toBeInTheDocument() expect( - screen.queryByRole('button', { name: 'agentV2.skillManagement.detail.addMetadata' }), + screen.queryByRole('button', { name: 'skill.skillManagement.detail.addMetadata' }), ).not.toBeInTheDocument() }) @@ -1194,7 +1302,7 @@ describe('SkillDetailPage', () => { await user.click(liveEditor) await user.type(liveEditor, 'First line{Enter}Second line') - await user.click(screen.getByRole('button', { name: 'agentV2.skillManagement.detail.publish' })) + await user.click(screen.getByRole('button', { name: 'skill.skillManagement.detail.publish' })) await waitFor(() => { expect(mocks.saveDraftFileMutationFn).toHaveBeenCalledWith( @@ -1212,9 +1320,9 @@ describe('SkillDetailPage', () => { const user = userEvent.setup() const { container } = renderSkillDetailPage() - await screen.findByText('agentV2.skillManagement.detail.builder.title') + await screen.findByText('skill.skillManagement.detail.builder.title') expect( - await screen.findByText('agentV2.skillManagement.detail.builder.editIntro'), + await screen.findByText('skill.skillManagement.detail.builder.editIntro'), ).toBeInTheDocument() const attachmentInput = getBuilderAttachmentInput(container) expect(attachmentInput).not.toBeNull() @@ -1229,7 +1337,7 @@ describe('SkillDetailPage', () => { await user.click( screen.getByRole('button', { - name: 'agentV2.skillManagement.detail.builder.send', + name: 'skill.skillManagement.detail.builder.send', }), ) @@ -1237,7 +1345,7 @@ describe('SkillDetailPage', () => { expect(mocks.sendSkillAssistMessage).toHaveBeenCalledWith( expect.objectContaining({ skillId: 'skill-1', - message: 'agentV2.skillManagement.detail.builder.attachmentOnlyMessage', + message: 'skill.skillManagement.detail.builder.attachmentOnlyMessage', attachments: [ { mime_type: 'text/markdown', @@ -1256,7 +1364,7 @@ describe('SkillDetailPage', () => { mocks.uploadSkillFile.mockImplementation(() => new Promise(() => undefined)) const { container } = renderSkillDetailPage() - await screen.findByText('agentV2.skillManagement.detail.builder.title') + await screen.findByText('skill.skillManagement.detail.builder.title') const attachmentInput = getBuilderAttachmentInput(container) expect(attachmentInput).not.toBeNull() @@ -1271,7 +1379,7 @@ describe('SkillDetailPage', () => { }) const promptInput = screen.getByPlaceholderText( - 'agentV2.skillManagement.detail.builder.modifyPlaceholder', + 'skill.skillManagement.detail.builder.modifyPlaceholder', ) await user.type(promptInput, 'Use the attached guide{Enter}') @@ -1285,7 +1393,7 @@ describe('SkillDetailPage', () => { mocks.uploadSkillFile.mockImplementation(() => new Promise(() => undefined)) const { container } = renderSkillDetailPage() - await screen.findByText('agentV2.skillManagement.detail.builder.title') + await screen.findByText('skill.skillManagement.detail.builder.title') const attachmentInput = getBuilderAttachmentInput(container) expect(attachmentInput).not.toBeNull() @@ -1300,7 +1408,7 @@ describe('SkillDetailPage', () => { }) const suggestion = screen.getByRole('button', { - name: 'agentV2.skillManagement.detail.builder.exampleIssueTriage', + name: 'skill.skillManagement.detail.builder.exampleIssueTriage', }) expect(suggestion).toBeDisabled() @@ -1312,7 +1420,7 @@ describe('SkillDetailPage', () => { renderSkillDetailPage() const promptInput = await screen.findByPlaceholderText( - 'agentV2.skillManagement.detail.builder.modifyPlaceholder', + 'skill.skillManagement.detail.builder.modifyPlaceholder', ) fireEvent.change(promptInput, { target: { value: 'ni' } }) fireEvent.compositionStart(promptInput) @@ -1326,7 +1434,7 @@ describe('SkillDetailPage', () => { renderSkillDetailPage() const promptInput = await screen.findByPlaceholderText( - 'agentV2.skillManagement.detail.builder.modifyPlaceholder', + 'skill.skillManagement.detail.builder.modifyPlaceholder', ) vi.useFakeTimers() try { @@ -1357,7 +1465,7 @@ describe('SkillDetailPage', () => { renderSkillDetailPage() const promptInput = await screen.findByPlaceholderText( - 'agentV2.skillManagement.detail.builder.modifyPlaceholder', + 'skill.skillManagement.detail.builder.modifyPlaceholder', ) fireEvent.change(promptInput, { target: { value: 'Create a support triage skill' } }) fireEvent.keyDown(promptInput, { isComposing: false, key: 'Enter' }) @@ -1378,13 +1486,13 @@ describe('SkillDetailPage', () => { renderSkillDetailPage() const promptInput = await screen.findByPlaceholderText( - 'agentV2.skillManagement.detail.builder.placeholder', + 'skill.skillManagement.detail.builder.placeholder', ) const sendButton = screen.getByRole('button', { - name: 'agentV2.skillManagement.detail.builder.send', + name: 'skill.skillManagement.detail.builder.send', }) const suggestion = screen.getByRole('button', { - name: 'agentV2.skillManagement.detail.builder.exampleIssueTriage', + name: 'skill.skillManagement.detail.builder.exampleIssueTriage', }) expect(sendButton).toBeDisabled() @@ -1400,7 +1508,7 @@ describe('SkillDetailPage', () => { const user = userEvent.setup({ applyAccept: false }) const { container } = renderSkillDetailPage() - await screen.findByText('agentV2.skillManagement.detail.builder.title') + await screen.findByText('skill.skillManagement.detail.builder.title') const attachmentInput = getBuilderAttachmentInput(container) expect(attachmentInput).not.toBeNull() @@ -1413,7 +1521,7 @@ describe('SkillDetailPage', () => { expect(mocks.uploadSkillFile).not.toHaveBeenCalled() expect(toast.error).toHaveBeenCalledWith( - 'agentV2.skillManagement.detail.builder.attachUnsupported', + 'skill.skillManagement.detail.builder.attachUnsupported', ) }) @@ -1430,17 +1538,17 @@ describe('SkillDetailPage', () => { renderSkillDetailPage() await user.click( - await screen.findByRole('button', { name: 'agentV2.skillManagement.detail.publish' }), + await screen.findByRole('button', { name: 'skill.skillManagement.detail.publish' }), ) expect( - await screen.findByText('agentV2.skillManagement.detail.publishReferencesTitle'), + await screen.findByText('skill.skillManagement.detail.publishReferencesTitle'), ).toBeInTheDocument() expect(await screen.findByText('Support Agent')).toBeInTheDocument() expect(mocks.publishSkillMutationFn).not.toHaveBeenCalled() await user.click( - screen.getByRole('button', { name: 'agentV2.skillManagement.detail.publishUpdate' }), + screen.getByRole('button', { name: 'skill.skillManagement.detail.publishUpdate' }), ) await waitFor(() => { @@ -1484,14 +1592,14 @@ describe('SkillDetailPage', () => { renderSkillDetailPage() await user.click( - await screen.findByRole('button', { name: 'agentV2.skillManagement.detail.versionHistory' }), + await screen.findByRole('button', { name: 'skill.skillManagement.detail.versionHistory' }), ) await user.click(await screen.findByRole('button', { name: /Rollback target/ })) expect(await screen.findByText(/Rollback instructions/)).toBeInTheDocument() await user.click( - screen.getByRole('button', { name: 'agentV2.skillManagement.detail.restoreVersion' }), + screen.getByRole('button', { name: 'skill.skillManagement.detail.restoreVersion' }), ) await waitFor(() => { @@ -1544,14 +1652,14 @@ describe('SkillDetailPage', () => { renderSkillDetailPage() await user.click( - await screen.findByRole('button', { name: 'agentV2.skillManagement.detail.versionHistory' }), + await screen.findByRole('button', { name: 'skill.skillManagement.detail.versionHistory' }), ) await user.click(await screen.findByRole('button', { name: /#2/ })) expect(await screen.findAllByText('#2')).toHaveLength(2) await user.click( - screen.getByRole('button', { name: 'agentV2.skillManagement.detail.restoreVersion' }), + screen.getByRole('button', { name: 'skill.skillManagement.detail.restoreVersion' }), ) await waitFor(() => { @@ -1590,7 +1698,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.markdownSourceMode', + name: 'skill.skillManagement.detail.markdownSourceMode', }), ) const sourceEditor = getSourceEditor() @@ -1599,7 +1707,7 @@ describe('SkillDetailPage', () => { await user.keyboard('/') expect( - await screen.findByText('agentV2.skillManagement.detail.referenceFiles.title'), + await screen.findByText('skill.skillManagement.detail.referenceFiles.title'), ).toBeInTheDocument() await user.keyboard('{ArrowRight}{Enter}') @@ -1618,26 +1726,24 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.builder.exampleIssueTriage', + name: 'skill.skillManagement.detail.builder.exampleIssueTriage', }), ) await waitFor(() => { expect(mocks.sendSkillAssistMessage).toHaveBeenCalledWith( expect.objectContaining({ - message: 'agentV2.skillManagement.detail.builder.exampleIssueTriage', + message: 'skill.skillManagement.detail.builder.exampleIssueTriage', }), ) }) expect( - await screen.findByPlaceholderText( - 'agentV2.skillManagement.detail.builder.modifyPlaceholder', - ), + await screen.findByPlaceholderText('skill.skillManagement.detail.builder.modifyPlaceholder'), ).toBeDisabled() await user.click( screen.getByRole('button', { - name: 'agentV2.skillManagement.detail.builder.followUpDisplayName', + name: 'skill.skillManagement.detail.builder.followUpDisplayName', }), ) @@ -1683,12 +1789,12 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.markdownSourceMode', + name: 'skill.skillManagement.detail.markdownSourceMode', }), ) await user.click( screen.getByRole('button', { - name: 'agentV2.skillManagement.detail.builder.exampleIssueTriage', + name: 'skill.skillManagement.detail.builder.exampleIssueTriage', }), ) @@ -1724,12 +1830,12 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.markdownSourceMode', + name: 'skill.skillManagement.detail.markdownSourceMode', }), ) await user.click( screen.getByRole('button', { - name: 'agentV2.skillManagement.detail.builder.exampleIssueTriage', + name: 'skill.skillManagement.detail.builder.exampleIssueTriage', }), ) @@ -1770,7 +1876,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.markdownSourceMode', + name: 'skill.skillManagement.detail.markdownSourceMode', }), ) await user.type(getSourceEditor(), '\nAutosave in progress') @@ -1782,7 +1888,7 @@ describe('SkillDetailPage', () => { ) await openRootCreateMenu(user) - await user.click(await screen.findByText('agentV2.skillManagement.detail.createFileMenu')) + await user.click(await screen.findByText('skill.skillManagement.detail.createFileMenu')) await user.type(await screen.findByPlaceholderText('File name'), 'notes.md{Enter}') expect(mocks.saveDraftFileMutationFn).toHaveBeenCalledTimes(1) @@ -1839,7 +1945,7 @@ describe('SkillDetailPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.detail.builder.exampleIssueTriage', + name: 'skill.skillManagement.detail.builder.exampleIssueTriage', }), ) @@ -1855,7 +1961,7 @@ describe('SkillDetailPage', () => { expect(getFileTreeItem('SKILL.md')).toBeInTheDocument() }) await openRootCreateMenu(user) - await user.click(await screen.findByText('agentV2.skillManagement.detail.createFolderMenu')) + await user.click(await screen.findByText('skill.skillManagement.detail.createFolderMenu')) const folderNameInput = await screen.findByPlaceholderText('Folder name') expect(screen.queryByRole('dialog')).not.toBeInTheDocument() @@ -1885,7 +1991,7 @@ describe('SkillDetailPage', () => { expect(getFileTreeItem('SKILL.md')).toBeInTheDocument() }) await openRootCreateMenu(user) - await user.click(await screen.findByText('agentV2.skillManagement.detail.createFileMenu')) + await user.click(await screen.findByText('skill.skillManagement.detail.createFileMenu')) const fileNameInput = await screen.findByPlaceholderText('File name') await user.type(fileNameInput, 'notes.md') @@ -1965,7 +2071,7 @@ describe('SkillDetailPage', () => { expect(getFileTreeItem('SKILL.md')).toBeInTheDocument() }) await openRootCreateMenu(user) - await user.click(await screen.findByText('agentV2.skillManagement.detail.createFileMenu')) + await user.click(await screen.findByText('skill.skillManagement.detail.createFileMenu')) const fileNameInput = await screen.findByPlaceholderText('File name') await user.click(screen.getByRole('heading', { name: 'SKILLS' })) @@ -2000,7 +2106,7 @@ describe('SkillDetailPage', () => { expect(getFileTreeItem('scripts')).toBeInTheDocument() }) await openFileTreeActions(user, 'scripts') - await user.click(await screen.findByText('agentV2.skillManagement.detail.createFileMenu')) + await user.click(await screen.findByText('skill.skillManagement.detail.createFileMenu')) const fileNameInput = await screen.findByPlaceholderText('File name') expect(fileNameInput.closest('ul')).toContainElement(getFileTreeItem('scripts/example.ts')) @@ -2043,7 +2149,7 @@ describe('SkillDetailPage', () => { expect(getFileTreeItem('scripts')).toBeInTheDocument() }) await openFileTreeActions(user, 'scripts') - await user.click(await screen.findByText('agentV2.skillManagement.detail.createFolderMenu')) + await user.click(await screen.findByText('skill.skillManagement.detail.createFolderMenu')) const folderNameInput = await screen.findByPlaceholderText('Folder name') expect(folderNameInput.closest('ul')).toContainElement(getFileTreeItem('scripts/example.ts')) @@ -2152,7 +2258,7 @@ describe('SkillDetailPage', () => { }) expect(await screen.findByText('common.operation.rename...')).toBeInTheDocument() - expect(screen.getByText('agentV2.skillManagement.detail.cutFile')).toBeInTheDocument() + expect(screen.getByText('skill.skillManagement.detail.cutFile')).toBeInTheDocument() expect(screen.getAllByText('⌘')).toHaveLength(2) expect(screen.getByText('X')).toBeInTheDocument() expect(screen.getByText('C')).toBeInTheDocument() @@ -2181,7 +2287,7 @@ describe('SkillDetailPage', () => { ...primaryModifier, }) - expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.detail.copyFileSuccess') + expect(toast.success).toHaveBeenCalledWith('skill.skillManagement.detail.copyFileSuccess') }) it('cuts the context-menu file with the displayed keyboard shortcut', async () => { @@ -2198,7 +2304,7 @@ describe('SkillDetailPage', () => { await screen.findByText('common.operation.rename...') const cutMenuItem = screen.getByRole('menuitem', { - name: /agentV2\.skillManagement\.detail\.cutFile/, + name: /skill\.skillManagement\.detail\.cutFile/, }) cutMenuItem.addEventListener('keydown', (event) => event.stopPropagation()) fireEvent.keyDown(cutMenuItem, { @@ -2207,7 +2313,7 @@ describe('SkillDetailPage', () => { ...primaryModifier, }) - expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.detail.cutFileSuccess') + expect(toast.success).toHaveBeenCalledWith('skill.skillManagement.detail.cutFileSuccess') }) it('copies a file with the keyboard shortcut and pastes it into the selected folder', async () => { @@ -2477,7 +2583,7 @@ describe('SkillDetailPage', () => { clientY: 520, }) const rootMenuItem = ( - await screen.findByText('agentV2.skillManagement.detail.createFileMenu') + await screen.findByText('skill.skillManagement.detail.createFileMenu') ).closest('[role="menuitem"]') if (!(rootMenuItem instanceof HTMLElement)) throw new Error('root menu item not found') fireEvent.keyDown(rootMenuItem, { @@ -2515,10 +2621,10 @@ describe('SkillDetailPage', () => { }) expect( - await screen.findByText('agentV2.skillManagement.detail.createFileMenu'), + await screen.findByText('skill.skillManagement.detail.createFileMenu'), ).toBeInTheDocument() - expect(screen.getByText('agentV2.skillManagement.detail.createFolderMenu')).toBeInTheDocument() - expect(screen.getByText('agentV2.skillManagement.detail.uploadFilesMenu')).toBeInTheDocument() + expect(screen.getByText('skill.skillManagement.detail.createFolderMenu')).toBeInTheDocument() + expect(screen.getByText('skill.skillManagement.detail.uploadFilesMenu')).toBeInTheDocument() }) it('uploads externally dragged files to the highlighted folder', async () => { @@ -2721,10 +2827,10 @@ describe('SkillDetailPage', () => { renderSkillDetailPage() await user.click( - await screen.findByRole('button', { name: 'agentV2.skillManagement.detail.versionHistory' }), + await screen.findByRole('button', { name: 'skill.skillManagement.detail.versionHistory' }), ) await openVersionRowActions(user, 'Initial version') - await user.click(await screen.findByText('agentV2.skillManagement.detail.nameThisVersion')) + await user.click(await screen.findByText('skill.skillManagement.detail.nameThisVersion')) const dialog = await screen.findByRole('dialog') const [titleInput, noteInput] = within(dialog).getAllByRole('textbox') if (!titleInput || !noteInput) throw new Error('version info inputs not found') @@ -2734,7 +2840,7 @@ describe('SkillDetailPage', () => { await user.clear(noteInput) await user.type(noteInput, 'Release note') await user.click( - within(dialog).getByRole('button', { name: 'agentV2.skillManagement.detail.publish' }), + within(dialog).getByRole('button', { name: 'skill.skillManagement.detail.publish' }), ) await waitFor(() => { @@ -2770,7 +2876,7 @@ describe('SkillDetailPage', () => { renderSkillDetailPage() await user.click( - await screen.findByRole('button', { name: 'agentV2.skillManagement.detail.versionHistory' }), + await screen.findByRole('button', { name: 'skill.skillManagement.detail.versionHistory' }), ) await openVersionRowActions(user, 'Old version') await user.click(await screen.findByText('common.operation.delete')) diff --git a/web/features/skills/__tests__/page.spec.tsx b/web/features/skills/__tests__/page.spec.tsx index 17d5c680d8a..4b8da1a2bb6 100644 --- a/web/features/skills/__tests__/page.spec.tsx +++ b/web/features/skills/__tests__/page.spec.tsx @@ -244,9 +244,7 @@ describe('SkillsPage', () => { expect(screen.getByText('refund-approval')).toBeInTheDocument() expect(screen.getByText('Handle refund requests.')).toBeInTheDocument() expect(screen.getByText('support')).toBeInTheDocument() - expect( - screen.getByText('agentV2.skillManagement.referenceCount:{"count":2}'), - ).toBeInTheDocument() + expect(screen.getByText('skill.skillManagement.referenceCount:{"count":2}')).toBeInTheDocument() }) it('passes keyword and selected tags to the list query', async () => { @@ -255,7 +253,7 @@ describe('SkillsPage', () => { await user.type( await screen.findByRole('searchbox', { - name: 'agentV2.skillManagement.searchLabel', + name: 'skill.skillManagement.searchLabel', }), 'refund', ) @@ -271,7 +269,7 @@ describe('SkillsPage', () => { }) }) - await user.click(screen.getByRole('button', { name: 'agentV2.skillManagement.tags' })) + await user.click(screen.getByRole('button', { name: 'skill.skillManagement.tags' })) await waitFor(() => { expect(screen.getAllByText('support').length).toBeGreaterThan(1) }) @@ -309,7 +307,7 @@ describe('SkillsPage', () => { renderSkillsPage() const skillList = await screen.findByRole('region', { - name: 'agentV2.skillManagement.listLabel', + name: 'skill.skillManagement.listLabel', }) await screen.findByRole('heading', { name: 'Skill 1' }) expect(within(skillList).getAllByRole('article')).toHaveLength(20) @@ -338,7 +336,7 @@ describe('SkillsPage', () => { const invalidateQueries = vi.spyOn(QueryClient.prototype, 'invalidateQueries') renderSkillsPage() - await user.click(await screen.findByRole('button', { name: 'agentV2.skillManagement.create' })) + await user.click(await screen.findByRole('button', { name: 'skill.skillManagement.create' })) await waitFor(() => { expect(mocks.createSkillMutationFn).toHaveBeenCalledWith( @@ -348,7 +346,7 @@ describe('SkillsPage', () => { expect.anything(), ) }) - expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.createSuccess') + expect(toast.success).toHaveBeenCalledWith('skill.skillManagement.createSuccess') expect(invalidateQueries).toHaveBeenCalledWith({ queryKey: ['skills', { type: 'query' }] }) expect(invalidateQueries).toHaveBeenCalledWith({ queryKey: ['skills', { type: 'infinite' }] }) expect(invalidateQueries).toHaveBeenCalledWith({ @@ -379,7 +377,7 @@ describe('SkillsPage', () => { expect.anything(), ) }) - expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.importSuccess') + expect(toast.success).toHaveBeenCalledWith('skill.skillManagement.importSuccess') expect(invalidateQueries).toHaveBeenCalledWith({ queryKey: ['skills', { type: 'query' }] }) expect(invalidateQueries).toHaveBeenCalledWith({ queryKey: ['skills', { type: 'infinite' }] }) expect(invalidateQueries).toHaveBeenCalledWith({ @@ -395,7 +393,7 @@ describe('SkillsPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}', + name: 'skill.skillManagement.moreActions:{"name":"Refund approval"}', }), ) await user.click(await screen.findByText('common.operation.duplicate')) @@ -410,7 +408,7 @@ describe('SkillsPage', () => { expect.anything(), ) }) - expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.duplicateSuccess') + expect(toast.success).toHaveBeenCalledWith('skill.skillManagement.duplicateSuccess') }) it('exports a published skill from the card action menu', async () => { @@ -419,7 +417,7 @@ describe('SkillsPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}', + name: 'skill.skillManagement.moreActions:{"name":"Refund approval"}', }), ) await user.click(await screen.findByText('common.operation.export')) @@ -441,7 +439,7 @@ describe('SkillsPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}', + name: 'skill.skillManagement.moreActions:{"name":"Refund approval"}', }), ) @@ -454,7 +452,7 @@ describe('SkillsPage', () => { await user.click( await screen.findByRole('button', { - name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}', + name: 'skill.skillManagement.moreActions:{"name":"Refund approval"}', }), ) await user.click(await screen.findByText('common.operation.delete')) @@ -462,7 +460,7 @@ describe('SkillsPage', () => { expect( within(dialog).getByText( - 'agentV2.skillManagement.deleteDialog.referencedDescription:{"count":2}', + 'skill.skillManagement.deleteDialog.referencedDescription:{"count":2}', ), ).toBeInTheDocument() @@ -481,7 +479,7 @@ describe('SkillsPage', () => { expect.anything(), ) }) - expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.deleteSuccess') + expect(toast.success).toHaveBeenCalledWith('skill.skillManagement.deleteSuccess') }) it('shows the empty-search state without create or import actions', async () => { @@ -491,12 +489,12 @@ describe('SkillsPage', () => { renderSkillsPage() - expect(await screen.findByText('agentV2.skillManagement.emptySearch')).toBeInTheDocument() + expect(await screen.findByText('skill.skillManagement.emptySearch')).toBeInTheDocument() expect( - screen.queryByText('agentV2.skillManagement.emptyAction.createTitle'), + screen.queryByText('skill.skillManagement.emptyAction.createTitle'), ).not.toBeInTheDocument() expect( - screen.queryByText('agentV2.skillManagement.emptyAction.importTitle'), + screen.queryByText('skill.skillManagement.emptyAction.importTitle'), ).not.toBeInTheDocument() }) }) diff --git a/web/features/skills/detail/builder-panel.tsx b/web/features/skills/detail/builder-panel.tsx index f9043bcf592..b29dd5c698e 100644 --- a/web/features/skills/detail/builder-panel.tsx +++ b/web/features/skills/detail/builder-panel.tsx @@ -97,7 +97,8 @@ export function SkillBuilderPanel({ selectedFile: SkillFileResponse | undefined skillId: string }) { - const { t } = useTranslation('agentV2') + const { t } = useTranslation('skill') + const { t: tAgentV2 } = useTranslation('agentV2') const queryClient = useQueryClient() const [prompt, setPrompt] = useState('') const initialBuilderModeRef = useRef({ @@ -444,7 +445,7 @@ export function SkillBuilderPanel({ ) : ( - {t(($) => $['agentDetail.configure.answer.thinking'])} + {tAgentV2(($) => $['agentDetail.configure.answer.thinking'])} )} diff --git a/web/features/skills/detail/file-editor.tsx b/web/features/skills/detail/file-editor.tsx index 9274bb47e7a..606c135dd93 100644 --- a/web/features/skills/detail/file-editor.tsx +++ b/web/features/skills/detail/file-editor.tsx @@ -115,7 +115,7 @@ export function FileEditor({ selectedVersionId: string | null skillId: string }) { - const { t } = useTranslation('agentV2') + const { t } = useTranslation('skill') const queryClient = useQueryClient() const { formatTimeFromNow } = useFormatTimeFromNow() const initialContent = file && isTextFile(file) ? (file.content ?? '') : '' @@ -144,6 +144,7 @@ export function FileEditor({ const detailRef = useRef(detail) const fileRef = useRef(file) const pendingPublishAfterSaveRef = useRef(false) + const pendingDisplayNameRenameRef = useRef(false) const liveBodyTextareaRef = useRef(null) const liveBodyEditorRef = useRef(null) const sourceTextareaRef = useRef(null) @@ -152,12 +153,6 @@ export function FileEditor({ context: { silent: true }, }), ) - const { mutateAsync: updateSkillMetadata } = useMutation( - consoleQuery.workspaces.current.skills.bySkillId.patch.mutationOptions({ - context: { silent: true }, - }), - ) - const filePath = file?.path const codeLanguage = getSkillCodeLanguage(file) const isMarkdown = isMarkdownFile(file) @@ -187,6 +182,7 @@ export function FileEditor({ : null const latestPublishedAt = detail?.latest_published_at const hasUnpublishedChanges = + displayNameDraft !== markdownContent.displayName || saveStatus === 'dirty' || saveStatus === 'saving' || saveStatus === 'error' || @@ -300,11 +296,13 @@ export function FileEditor({ setHasSaveConflict(false) setSaveStatus('saving') + const shouldNotifyDisplayNameRename = + currentFile.path === 'SKILL.md' && pendingDisplayNameRenameRef.current try { const nextCachedDetail = await runSkillFileMutation( fileMutationCoordinator, - async (expectedUpdatedAt) => { - const nextDetail = await saveDraftFile({ + async (expectedUpdatedAt) => + saveDraftFile({ params: { skill_id: skillId, }, @@ -317,27 +315,7 @@ export function FileEditor({ path: currentFile.path, size: content.length, }, - }) - const nextDisplayName = - currentFile.path === 'SKILL.md' - ? parseMarkdownContent(content).displayName.trim() - : '' - return nextDisplayName && nextDisplayName !== nextDetail.display_name - ? { - ...nextDetail, - ...(await updateSkillMetadata({ - params: { - skill_id: skillId, - }, - body: { - display_name: nextDisplayName, - expected_updated_at: nextDetail.updated_at, - }, - })), - files: nextDetail.files, - } - : nextDetail - }, + }), ) detailRef.current = nextCachedDetail @@ -350,6 +328,10 @@ export function FileEditor({ setSaveStatus(draftContentRef.current === content ? 'saved' : 'dirty') setSkillDetailCache(queryClient, skillId, nextCachedDetail) onDraftDetailChange(nextCachedDetail) + if (shouldNotifyDisplayNameRename) { + pendingDisplayNameRenameRef.current = false + toast.success(t(($) => $['skillManagement.detail.renameSkillSuccess'])) + } return true } catch (error) { const errorPayload = await getAsyncSkillErrorPayload(error) @@ -435,7 +417,6 @@ export function FileEditor({ saveDraftFile, skillId, t, - updateSkillMetadata, ], ) const canEditRef = useRef(canEdit) @@ -847,6 +828,7 @@ export function FileEditor({ const handleDisplayNameCommit = () => { if (!isSkillManifestFile || readonly || displayNameDraft === markdownContent.displayName) return + pendingDisplayNameRenameRef.current = true updateDraftContent(setMarkdownDisplayName(draftContentRef.current, displayNameDraft)) } @@ -1066,11 +1048,7 @@ export function FileEditor({ className="h-8 w-[280px] max-w-full rounded-lg border border-transparent bg-transparent px-0 system-sm-regular text-text-secondary outline-hidden placeholder:text-text-quaternary hover:border-divider-regular hover:bg-background-default hover:px-2.5 focus-visible:border-divider-regular focus-visible:bg-background-default focus-visible:px-2.5 focus-visible:ring-2 focus-visible:ring-state-accent-solid" onBlur={handleDisplayNameCommit} onChange={(event) => { - const nextDisplayName = event.target.value - setDisplayNameDraft(nextDisplayName) - updateDraftContent( - setMarkdownDisplayName(draftContentRef.current, nextDisplayName), - ) + setDisplayNameDraft(event.target.value) }} onKeyDown={(event) => { if (event.key === 'Escape') { diff --git a/web/features/skills/detail/file-tree-dnd.tsx b/web/features/skills/detail/file-tree-dnd.tsx index da9959e562c..7c73e17f2d5 100644 --- a/web/features/skills/detail/file-tree-dnd.tsx +++ b/web/features/skills/detail/file-tree-dnd.tsx @@ -42,7 +42,7 @@ export function SkillUploadStatusPanel({ onDismiss: () => void onRetry: () => void }) { - const { t } = useTranslation('agentV2') + const { t } = useTranslation('skill') const { t: tCommon } = useTranslation('common') if (items.length === 0) return null diff --git a/web/features/skills/detail/file-tree-items.tsx b/web/features/skills/detail/file-tree-items.tsx index 5eaecaf5ef7..d7a8908e8f8 100644 --- a/web/features/skills/detail/file-tree-items.tsx +++ b/web/features/skills/detail/file-tree-items.tsx @@ -144,7 +144,7 @@ function FileActionMenuItems({ onRename: () => void onUploadFiles: () => void }) { - const { t } = useTranslation('agentV2') + const { t } = useTranslation('skill') const { t: tCommon } = useTranslation('common') const MenuItem = kind === 'context' ? ContextMenuItem : DropdownMenuItem const MenuSeparator = kind === 'context' ? ContextMenuSeparator : DropdownMenuSeparator @@ -323,7 +323,7 @@ export function RootFileActionMenuItems({ onCreateFolder: () => void onUploadFiles: () => void }) { - const { t } = useTranslation('agentV2') + const { t } = useTranslation('skill') const MenuItem = kind === 'context' ? ContextMenuItem : DropdownMenuItem return ( diff --git a/web/features/skills/detail/file-tree.tsx b/web/features/skills/detail/file-tree.tsx index 57134e9f188..4a4454b78b1 100644 --- a/web/features/skills/detail/file-tree.tsx +++ b/web/features/skills/detail/file-tree.tsx @@ -47,6 +47,8 @@ import { matchesKeyboardEvent, useHotkey } from '@tanstack/react-hotkeys' import { useMutation, useQueryClient } from '@tanstack/react-query' import { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import SidebarLeftArrowIcon from '@/app/components/base/icons/src/vender/SidebarLeftArrowIcon' +import { DetailSidebarToggleButton } from '@/app/components/detail-sidebar/toggle-button' import Link from '@/next/link' import { consoleQuery } from '@/service/client' import { fetchSkillFileBlob, uploadSkillFile } from '../client' @@ -79,6 +81,7 @@ import { skillFileMenuPopupClassName, toFileTree, } from './shared' +import { SkillDisplayNameEditor } from './skill-display-name-editor' import { SkillReferencesPanel, SkillTagsEditor } from './skill-metadata' function FileSearchDialog({ @@ -92,7 +95,7 @@ function FileSearchDialog({ onSelect: (path: string) => void open: boolean }) { - const { t } = useTranslation('agentV2') + const { t } = useTranslation('skill') const [query, setQuery] = useState('') const fileResults = useMemo(() => { const normalizedQuery = query.trim().toLowerCase() @@ -196,7 +199,7 @@ export function FileTree({ selectedPath: string | undefined skillId: string }) { - const { t } = useTranslation('agentV2') + const { t } = useTranslation('skill') const { t: tCommon } = useTranslation('common') const queryClient = useQueryClient() const referencesRegionRef = useRef(null) @@ -918,222 +921,103 @@ export function FileTree({ return ( <> -