From f8e5421a017a92699dbb8693c39c9e2ecb10534e Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 26 Mar 2026 14:25:11 +0800 Subject: [PATCH] feat: support hover hightlight cmd --- .../__tests__/sandbox-placeholder.spec.tsx | 63 ++++++++------ .../prompt-editor/sandbox-placeholder.tsx | 83 +++++++++++-------- web/i18n/en-US/common.json | 6 +- web/i18n/ja-JP/common.json | 6 +- web/i18n/zh-Hans/common.json | 6 +- web/i18n/zh-Hant/common.json | 6 +- 6 files changed, 102 insertions(+), 68 deletions(-) diff --git a/web/app/components/base/prompt-editor/__tests__/sandbox-placeholder.spec.tsx b/web/app/components/base/prompt-editor/__tests__/sandbox-placeholder.spec.tsx index 22d69cdd11..ed2927c7d0 100644 --- a/web/app/components/base/prompt-editor/__tests__/sandbox-placeholder.spec.tsx +++ b/web/app/components/base/prompt-editor/__tests__/sandbox-placeholder.spec.tsx @@ -1,16 +1,18 @@ -import type { ReactElement } from 'react' -import { render, screen } from '@testing-library/react' +import { render } from '@testing-library/react' import SandboxPlaceholder from '../sandbox-placeholder' vi.mock('react-i18next', () => ({ - Trans: ({ i18nKey, components = [] }: { - i18nKey: string - components?: ReactElement[] - }) => ( -
- {components} -
- ), + useTranslation: () => ({ + t: (key: string) => { + const translations: Record = { + 'promptEditor.placeholderSandboxPrefix': 'Write instructions here, ', + 'promptEditor.placeholderSandboxInsert': 'insert', + 'promptEditor.placeholderSandboxSeparator': ', ', + 'promptEditor.placeholderSandboxTools': 'tools', + } + return translations[key] ?? key + }, + }), })) describe('SandboxPlaceholder', () => { @@ -24,10 +26,9 @@ describe('SandboxPlaceholder', () => { const { container } = render() expect(container).toBeEmptyDOMElement() - expect(screen.queryByTestId('sandbox-placeholder-trans')).not.toBeInTheDocument() }) - it('should render slash and insert tokens when tool blocks are disabled', () => { + it('should render only the insert pair when tool blocks are disabled', () => { const { container } = render( { />, ) - expect(screen.getByTestId('sandbox-placeholder-trans')).toHaveAttribute('data-i18n-key', 'promptEditor.placeholderSandboxNoTools') - - const spans = container.querySelectorAll('span') - expect(spans).toHaveLength(2) - expect(spans[0]).toHaveClass('inline-flex', 'bg-components-kbd-bg-gray', 'system-kbd') - expect(spans[1]).toHaveClass('border-b', 'border-dotted', 'border-current') + expect(container).toHaveTextContent('Write instructions here, /insert') + expect(container.querySelector('.sandbox-placeholder-pair-insert')).toBeInTheDocument() + expect(container.querySelector('.sandbox-placeholder-pair-tools')).not.toBeInTheDocument() + expect(container.querySelector('.sandbox-placeholder-action-insert')).toHaveClass( + 'pointer-events-auto', + 'border-dotted', + ) }) - it('should render slash insert at and tools tokens when tool blocks are enabled', () => { + it('should render both insert and tools pairs with linked hover classes when tool blocks are enabled', () => { const { container } = render() - expect(screen.getByTestId('sandbox-placeholder-trans')).toHaveAttribute('data-i18n-key', 'promptEditor.placeholderSandbox') - - const spans = container.querySelectorAll('span') - expect(spans).toHaveLength(4) - expect(spans[0]).toHaveClass('inline-flex', 'bg-components-kbd-bg-gray', 'system-kbd') - expect(spans[1]).toHaveClass('border-b', 'border-dotted', 'border-current') - expect(spans[2]).toHaveClass('inline-flex', 'bg-components-kbd-bg-gray', 'system-kbd') - expect(spans[3]).toHaveClass('border-b', 'border-dotted', 'border-current') + expect(container).toHaveTextContent('Write instructions here, /insert, @tools') + expect(container.querySelector('.sandbox-placeholder-kbd-insert')).toHaveTextContent('/') + expect(container.querySelector('.sandbox-placeholder-kbd-tools')).toHaveTextContent('@') + expect(container.querySelector('.sandbox-placeholder-action-insert')).toHaveTextContent('insert') + expect(container.querySelector('.sandbox-placeholder-action-tools')).toHaveTextContent('tools') + expect(container.querySelector('.sandbox-placeholder-pair-insert')).toHaveClass( + 'has-[.sandbox-placeholder-action-insert:hover]:[&_.sandbox-placeholder-kbd-insert]:bg-state-accent-hover-alt', + 'has-[.sandbox-placeholder-action-insert:hover]:[&_.sandbox-placeholder-action-insert]:bg-state-accent-hover', + 'has-[.sandbox-placeholder-action-insert:hover]:[&_.sandbox-placeholder-action-insert]:text-text-accent-secondary', + ) + expect(container.querySelector('.sandbox-placeholder-pair-tools')).toHaveClass( + 'has-[.sandbox-placeholder-action-tools:hover]:[&_.sandbox-placeholder-kbd-tools]:bg-state-accent-hover-alt', + 'has-[.sandbox-placeholder-action-tools:hover]:[&_.sandbox-placeholder-action-tools]:bg-state-accent-hover', + 'has-[.sandbox-placeholder-action-tools:hover]:[&_.sandbox-placeholder-action-tools]:text-text-accent-secondary', + ) }) }) }) diff --git a/web/app/components/base/prompt-editor/sandbox-placeholder.tsx b/web/app/components/base/prompt-editor/sandbox-placeholder.tsx index 5fb348bee0..4beab897af 100644 --- a/web/app/components/base/prompt-editor/sandbox-placeholder.tsx +++ b/web/app/components/base/prompt-editor/sandbox-placeholder.tsx @@ -1,22 +1,38 @@ -import type { FC, PropsWithChildren, ReactElement } from 'react' -import { Trans } from 'react-i18next' +import type { FC } from 'react' +import { useTranslation } from 'react-i18next' +import { cn } from '@/utils/classnames' -type SandboxPlaceholderTokenProps = PropsWithChildren<{ - variant: 'kbd' | 'action' -}> - -const SandboxPlaceholderToken: FC = ({ variant, children }) => { - if (variant === 'kbd') { - return ( - - {children} - - ) - } +type SandboxPlaceholderTokenProps = { + actionLabel?: string + shortcut: '/' | '@' +} +const SandboxPlaceholderToken: FC = ({ + actionLabel, + shortcut, +}) => { return ( - - {children} + + + {shortcut} + + + {actionLabel} + ) } @@ -30,27 +46,28 @@ const SandboxPlaceholder: FC = ({ disableToolBlocks, isSupportSandbox, }) => { + const { t } = useTranslation() + if (!isSupportSandbox) return null - const components: ReactElement[] = [ - , - , - ] - - if (!disableToolBlocks) { - components.push( - , - , - ) - } - return ( - + + {t('promptEditor.placeholderSandboxPrefix', { ns: 'common' })} + + {!disableToolBlocks && ( + <> + {t('promptEditor.placeholderSandboxSeparator', { ns: 'common' })} + + + )} + ) } diff --git a/web/i18n/en-US/common.json b/web/i18n/en-US/common.json index 84d695b85d..6342957230 100644 --- a/web/i18n/en-US/common.json +++ b/web/i18n/en-US/common.json @@ -549,8 +549,10 @@ "promptEditor.history.modal.title": "EXAMPLE", "promptEditor.history.modal.user": "Hello", "promptEditor.placeholder": "Write your prompt word here, enter '{' to insert a variable, enter '/' to insert a prompt content block", - "promptEditor.placeholderSandbox": "Write instructions here, <0>/ <1>insert, <2>@ <3>tools", - "promptEditor.placeholderSandboxNoTools": "Write instructions here, <0>/ <1>insert", + "promptEditor.placeholderSandboxInsert": "insert", + "promptEditor.placeholderSandboxPrefix": "Write instructions here, ", + "promptEditor.placeholderSandboxSeparator": ", ", + "promptEditor.placeholderSandboxTools": "tools", "promptEditor.query.item.desc": "Insert user query template", "promptEditor.query.item.title": "Query", "promptEditor.requestURL.item.desc": "Insert request URL", diff --git a/web/i18n/ja-JP/common.json b/web/i18n/ja-JP/common.json index 92bbbfee7d..2c833bd9c1 100644 --- a/web/i18n/ja-JP/common.json +++ b/web/i18n/ja-JP/common.json @@ -534,8 +534,10 @@ "promptEditor.history.modal.title": "例", "promptEditor.history.modal.user": "こんにちは", "promptEditor.placeholder": "ここにプロンプトワードを入力してください。変数を挿入するには「{」を、プロンプトコンテンツブロックを挿入するには「/」を入力します。", - "promptEditor.placeholderSandbox": "ここに指示を書いて、<0>/ <1>挿入、<2>@ <3>ツール", - "promptEditor.placeholderSandboxNoTools": "ここに指示を書いて、<0>/ <1>挿入", + "promptEditor.placeholderSandboxInsert": "挿入", + "promptEditor.placeholderSandboxPrefix": "ここに指示を書いて、", + "promptEditor.placeholderSandboxSeparator": "、", + "promptEditor.placeholderSandboxTools": "ツール", "promptEditor.query.item.desc": "ユーザークエリテンプレートを挿入", "promptEditor.query.item.title": "クエリ", "promptEditor.requestURL.item.desc": "リクエストURLを挿入", diff --git a/web/i18n/zh-Hans/common.json b/web/i18n/zh-Hans/common.json index 392958686e..6357c0f6e3 100644 --- a/web/i18n/zh-Hans/common.json +++ b/web/i18n/zh-Hans/common.json @@ -549,8 +549,10 @@ "promptEditor.history.modal.title": "示例", "promptEditor.history.modal.user": "你好", "promptEditor.placeholder": "在这里写你的提示词,输入'{' 插入变量、输入'/' 插入提示内容块", - "promptEditor.placeholderSandbox": "在这里写你的指令,<0>/ <1>插入,<2>@ <3>工具", - "promptEditor.placeholderSandboxNoTools": "在这里写你的指令,<0>/ <1>插入", + "promptEditor.placeholderSandboxInsert": "插入", + "promptEditor.placeholderSandboxPrefix": "在这里写你的指令,", + "promptEditor.placeholderSandboxSeparator": ",", + "promptEditor.placeholderSandboxTools": "工具", "promptEditor.query.item.desc": "插入用户查询模板", "promptEditor.query.item.title": "查询内容", "promptEditor.requestURL.item.desc": "插入请求 URL", diff --git a/web/i18n/zh-Hant/common.json b/web/i18n/zh-Hant/common.json index 471bf3fa00..47fe026a07 100644 --- a/web/i18n/zh-Hant/common.json +++ b/web/i18n/zh-Hant/common.json @@ -509,8 +509,10 @@ "promptEditor.history.modal.title": "示例", "promptEditor.history.modal.user": "你好", "promptEditor.placeholder": "在這裡寫你的提示詞,輸入'{' 插入變數、輸入'/' 插入提示內容塊", - "promptEditor.placeholderSandbox": "在這裡寫你的指令,<0>/ <1>插入,<2>@ <3>工具", - "promptEditor.placeholderSandboxNoTools": "在這裡寫你的指令,<0>/ <1>插入", + "promptEditor.placeholderSandboxInsert": "插入", + "promptEditor.placeholderSandboxPrefix": "在這裡寫你的指令,", + "promptEditor.placeholderSandboxSeparator": ",", + "promptEditor.placeholderSandboxTools": "工具", "promptEditor.query.item.desc": "插入使用者查詢模板", "promptEditor.query.item.title": "查詢內容", "promptEditor.requestURL.item.desc": "插入請求 URL",