From a7b8a9ed46304063c7ca0eb08cd1e34a4cc32c16 Mon Sep 17 00:00:00 2001 From: chengyongru Date: Tue, 7 Jul 2026 14:34:18 +0800 Subject: [PATCH] fix(webui): clarify new chat command text --- nanobot/command/builtin.py | 2 +- .../src/components/thread/ThreadComposer.tsx | 20 ++++++++++---- webui/src/i18n/locales/en/common.json | 2 +- webui/src/i18n/locales/es/common.json | 2 +- webui/src/i18n/locales/fr/common.json | 2 +- webui/src/i18n/locales/id/common.json | 2 +- webui/src/i18n/locales/ja/common.json | 2 +- webui/src/i18n/locales/ko/common.json | 2 +- webui/src/i18n/locales/vi/common.json | 2 +- webui/src/i18n/locales/zh-CN/common.json | 2 +- webui/src/i18n/locales/zh-TW/common.json | 2 +- webui/src/tests/thread-composer.test.tsx | 27 +++++++++++++++++-- 12 files changed, 50 insertions(+), 17 deletions(-) diff --git a/nanobot/command/builtin.py b/nanobot/command/builtin.py index cd05b5b5..9e6a516e 100644 --- a/nanobot/command/builtin.py +++ b/nanobot/command/builtin.py @@ -58,7 +58,7 @@ BUILTIN_COMMAND_SPECS: tuple[BuiltinCommandSpec, ...] = ( BuiltinCommandSpec( "/new", "New chat", - "Stop the current task and start a fresh conversation.", + "Reset this chat and start a fresh conversation.", "square-pen", lifecycle="finalize_active_turn", ), diff --git a/webui/src/components/thread/ThreadComposer.tsx b/webui/src/components/thread/ThreadComposer.tsx index 7c77a040..ea9291c7 100644 --- a/webui/src/components/thread/ThreadComposer.tsx +++ b/webui/src/components/thread/ThreadComposer.tsx @@ -975,12 +975,13 @@ export function ThreadComposer({ }, [cursorPosition, disabled, slashMenuDismissed, value]); const visibleSlashCommands = useMemo(() => { - const baseCommands = slashCommands.filter( - (command) => command.command !== "/stop" && command.command !== "/restart", - ); - if (!(isStreaming && onStop)) return baseCommands; + if (!(isStreaming && onStop)) return slashCommands; const stopCommand = slashCommands.find((command) => command.command === "/stop"); - return stopCommand ? [stopCommand, ...baseCommands] : baseCommands; + if (!stopCommand) return slashCommands; + return [ + stopCommand, + ...slashCommands.filter((command) => command.command !== "/stop"), + ]; }, [isStreaming, onStop, slashCommands]); const filteredSlashCommands = useMemo(() => { @@ -1012,6 +1013,15 @@ export function ThreadComposer({ if (slashQuery === null) return []; const withDetails = visibleSlashCommands .filter((command) => { + if ( + slashQuery === "" + && ( + command.command === "/restart" + || (command.command === "/stop" && !(isStreaming && onStop)) + ) + ) { + return false; + } const commandKey = slashCommandI18nKey(command.command); const title = t(`thread.composer.slash.commands.${commandKey}.title`, { defaultValue: command.title, diff --git a/webui/src/i18n/locales/en/common.json b/webui/src/i18n/locales/en/common.json index a4baf06e..2a1a7d92 100644 --- a/webui/src/i18n/locales/en/common.json +++ b/webui/src/i18n/locales/en/common.json @@ -905,7 +905,7 @@ "commands": { "new": { "title": "New chat", - "description": "Stop the current task and start a fresh conversation." + "description": "Reset this chat and start a fresh conversation." }, "stop": { "title": "Stop current task", diff --git a/webui/src/i18n/locales/es/common.json b/webui/src/i18n/locales/es/common.json index 34ec3ee1..42705d77 100644 --- a/webui/src/i18n/locales/es/common.json +++ b/webui/src/i18n/locales/es/common.json @@ -895,7 +895,7 @@ "commands": { "new": { "title": "Nuevo chat", - "description": "Detiene la tarea actual e inicia una conversación nueva." + "description": "Restablece este chat e inicia una conversación nueva." }, "stop": { "title": "Detener tarea actual", diff --git a/webui/src/i18n/locales/fr/common.json b/webui/src/i18n/locales/fr/common.json index 9f617727..66f84804 100644 --- a/webui/src/i18n/locales/fr/common.json +++ b/webui/src/i18n/locales/fr/common.json @@ -895,7 +895,7 @@ "commands": { "new": { "title": "Nouvelle discussion", - "description": "Arrêter la tâche en cours et démarrer une nouvelle conversation." + "description": "Réinitialiser cette discussion et démarrer une nouvelle conversation." }, "stop": { "title": "Arrêter la tâche en cours", diff --git a/webui/src/i18n/locales/id/common.json b/webui/src/i18n/locales/id/common.json index a509fcc8..1f58ecb7 100644 --- a/webui/src/i18n/locales/id/common.json +++ b/webui/src/i18n/locales/id/common.json @@ -895,7 +895,7 @@ "commands": { "new": { "title": "Obrolan baru", - "description": "Hentikan tugas saat ini dan mulai percakapan baru." + "description": "Reset chat ini dan mulai percakapan baru." }, "stop": { "title": "Hentikan tugas saat ini", diff --git a/webui/src/i18n/locales/ja/common.json b/webui/src/i18n/locales/ja/common.json index 458ac13e..bcb4537a 100644 --- a/webui/src/i18n/locales/ja/common.json +++ b/webui/src/i18n/locales/ja/common.json @@ -895,7 +895,7 @@ "commands": { "new": { "title": "新しいチャット", - "description": "現在のタスクを停止して、新しい会話を開始します。" + "description": "このチャットをリセットして、新しい会話を開始します。" }, "stop": { "title": "現在のタスクを停止", diff --git a/webui/src/i18n/locales/ko/common.json b/webui/src/i18n/locales/ko/common.json index cea28732..9e8e1395 100644 --- a/webui/src/i18n/locales/ko/common.json +++ b/webui/src/i18n/locales/ko/common.json @@ -895,7 +895,7 @@ "commands": { "new": { "title": "새 채팅", - "description": "현재 작업을 중지하고 새 대화를 시작합니다." + "description": "이 채팅을 초기화하고 새 대화를 시작합니다." }, "stop": { "title": "현재 작업 중지", diff --git a/webui/src/i18n/locales/vi/common.json b/webui/src/i18n/locales/vi/common.json index b93abea7..eb82f401 100644 --- a/webui/src/i18n/locales/vi/common.json +++ b/webui/src/i18n/locales/vi/common.json @@ -895,7 +895,7 @@ "commands": { "new": { "title": "Cuộc trò chuyện mới", - "description": "Dừng tác vụ hiện tại và bắt đầu một cuộc trò chuyện mới." + "description": "Đặt lại cuộc trò chuyện này và bắt đầu một cuộc trò chuyện mới." }, "stop": { "title": "Dừng tác vụ hiện tại", diff --git a/webui/src/i18n/locales/zh-CN/common.json b/webui/src/i18n/locales/zh-CN/common.json index d7eea4a5..498ee245 100644 --- a/webui/src/i18n/locales/zh-CN/common.json +++ b/webui/src/i18n/locales/zh-CN/common.json @@ -904,7 +904,7 @@ "commands": { "new": { "title": "新建对话", - "description": "停止当前任务,并开始一个新的对话。" + "description": "重置当前对话,并开始一个新的对话。" }, "stop": { "title": "停止当前任务", diff --git a/webui/src/i18n/locales/zh-TW/common.json b/webui/src/i18n/locales/zh-TW/common.json index 223c72f0..b9cd4195 100644 --- a/webui/src/i18n/locales/zh-TW/common.json +++ b/webui/src/i18n/locales/zh-TW/common.json @@ -895,7 +895,7 @@ "commands": { "new": { "title": "新增對話", - "description": "停止目前任務,並開始新的對話。" + "description": "重置目前對話,並開始新的對話。" }, "stop": { "title": "停止目前任務", diff --git a/webui/src/tests/thread-composer.test.tsx b/webui/src/tests/thread-composer.test.tsx index 4c0d376a..748c8956 100644 --- a/webui/src/tests/thread-composer.test.tsx +++ b/webui/src/tests/thread-composer.test.tsx @@ -854,6 +854,29 @@ describe("ThreadComposer", () => { expect(screen.queryByRole("listbox", { name: "Slash commands" })).not.toBeInTheDocument(); }); + it("offers stop autocomplete once the user starts typing it", () => { + const onSend = vi.fn(); + render( + , + ); + + const input = screen.getByLabelText("Message input"); + fireEvent.change(input, { target: { value: "/sto" } }); + + expect(screen.getByRole("option", { name: /\/stop/i })).toHaveAttribute( + "aria-selected", + "true", + ); + fireEvent.keyDown(input, { key: "Enter" }); + + expect(input).toHaveValue("/stop"); + expect(onSend).not.toHaveBeenCalled(); + }); + it("renders slash commands as direct actions with current status", () => { render( { { command: "/new", title: "New chat", - description: "Stop the current task and start a fresh conversation.", + description: "Reset this chat and start a fresh conversation.", icon: "square-pen", lifecycle: "finalize_active_turn", acceptsArgs: false, @@ -1393,7 +1416,7 @@ describe("ThreadComposer", () => { { command: "/new", title: "New chat", - description: "Stop the current task and start a fresh conversation.", + description: "Reset this chat and start a fresh conversation.", icon: "square-pen", lifecycle: "finalize_active_turn", acceptsArgs: false,