feat(webui): present chats as topics

This commit is contained in:
chengyongru
2026-07-24 10:29:13 +08:00
committed by chengyongru
parent 8bcab8885e
commit 6a9157f477
18 changed files with 233 additions and 233 deletions
+1 -1
View File
@@ -295,7 +295,7 @@ Need help with `PATH`, API keys, provider/model matching, or JSON errors? See th
## 🌐 WebUI
The WebUI ships **inside the published wheel** — no extra build step. It is the browser workbench for chat sessions, workspace controls, Apps, Skills, Automations, and settings. For the full user guide, see [`docs/webui.md`](./docs/webui.md).
The WebUI ships **inside the published wheel** — no extra build step. It is the browser workbench for topics, workspace controls, Apps, Skills, Automations, and settings. For the full user guide, see [`docs/webui.md`](./docs/webui.md).
<p align="center">
<img src="images/nanobot_webui.png" alt="nanobot webui preview" width="900">
+16 -16
View File
@@ -2,21 +2,21 @@
<!-- Meta description: Create, run, and manage nanobot scheduled automations, local triggers, and heartbeat-backed background checks. -->
Automations are agent turns that run later in a linked chat/session. Use them
Automations are agent turns that run later in a linked topic. Use them
when nanobot should do work without someone actively typing: reminders,
recurring checks, nightly summaries, CI follow-ups, local script reports, or
webhook-driven events.
Create automations from the chat, channel, or WebUI session where the result
should appear. That lets nanobot keep the right session history, workspace, and
reply target.
Create automations from the chat channel or WebUI topic where the
result should appear. That lets nanobot keep the right session history,
workspace, and reply target.
## Choose an Automation Type
| Type | Starts from | Best for | Created with |
|---|---|---|---|
| Scheduled automation | Time, interval, or cron expression | Recurring reminders, scheduled summaries, one-time future tasks | Ask nanobot in the target session to schedule it with the `cron` tool |
| Local trigger | A local `nanobot trigger ...` command | CI jobs, webhooks, shell scripts, generated reports | `/trigger <name>` in the target session |
| Scheduled automation | Time, interval, or cron expression | Recurring reminders, scheduled summaries, one-time future tasks | Ask nanobot in the target topic to schedule it with the `cron` tool |
| Local trigger | A local `nanobot trigger ...` command | CI jobs, webhooks, shell scripts, generated reports | `/trigger <name>` in the target topic |
| Heartbeat | Protected system schedule | Quiet recurring checks that should only report useful results | Edit `<workspace>/HEARTBEAT.md` |
The two user-created automation types are scheduled automations and local
@@ -26,21 +26,21 @@ protected from normal automation edits.
## Before You Create One
Keep `nanobot gateway` running. The gateway owns background delivery for chat
apps, WebUI sessions, scheduled automations, local triggers, heartbeat, and
apps, WebUI topics, scheduled automations, local triggers, heartbeat, and
Dream jobs.
Use the same workspace and config for the gateway and any process that sends
local trigger messages. If you run multiple nanobot instances, pass the matching
`--config` or `--workspace` option to `nanobot trigger`.
Create each automation from the target session. An automation without a linked
chat/session cannot be enabled or run from the WebUI because nanobot would not
know where to deliver the turn.
Create each automation from the target topic. An automation without a linked
topic cannot be enabled or run from the WebUI because nanobot would not know
where to deliver the turn.
## Scheduled Automations
Scheduled automations are created by the agent's `cron` tool. In practice, ask
nanobot from the target chat or WebUI session:
nanobot from the target chat or WebUI topic:
```text
Every weekday at 9am, check open pull requests and summarize blockers here.
@@ -68,7 +68,7 @@ report, use heartbeat instead of a user-created scheduled automation.
Local triggers let a local script or external service send a message into a
specific nanobot session later.
Create the trigger from the chat or WebUI session where future messages should
Create the trigger from the chat or WebUI topic where future messages should
arrive:
```text
@@ -120,7 +120,7 @@ Heartbeat is enabled by default when `nanobot gateway` starts. Configure it in
Use the WebUI Automations view to:
- filter by all, active, paused, needs-attention, or system jobs;
- search by task name, message, trigger command, linked chat, schedule, or
- search by task name, message, trigger command, linked topic, schedule, or
status;
- sort by next run, last run, updated time, or name;
- run scheduled automations now;
@@ -138,7 +138,7 @@ Automation delivery is workspace-local. Scheduled jobs and local trigger
deliveries use the same workspace as the gateway.
Local trigger messages are written to a durable queue. If the gateway is not
running yet, the message waits in that workspace. If the linked session is
running yet, the message waits in that workspace. If the linked topic is
already running a turn, the trigger waits until the session becomes idle instead
of being injected into the active turn.
@@ -154,7 +154,7 @@ queue is not a distributed multi-consumer queue.
## Common Patterns
For a nightly report, ask from the target session:
For a nightly report, ask from the target topic:
```text
Every night at 9pm, review today's workspace changes and summarize anything I should handle tomorrow.
@@ -181,7 +181,7 @@ generate-report | nanobot trigger <trigger-id>
## Troubleshooting
If an automation does not run, check that `nanobot gateway` is running, the
automation is enabled, and it was created from a linked chat/session.
automation is enabled, and it was created from a linked topic.
If a local trigger waits forever, confirm the command uses the same workspace or
config as the gateway.
+17 -17
View File
@@ -1,8 +1,8 @@
# Nanobot WebUI: Browser Workbench for Self-Hosted AI Agents
<!-- Meta description: Run nanobot from a browser WebUI with persistent chat sessions, visible tool activity, workspace controls, Apps, MCP presets, Skills, settings, and Automations. -->
<!-- Meta description: Run nanobot from a browser WebUI with persistent topics, visible tool activity, workspace controls, Apps, MCP presets, Skills, settings, and Automations. -->
The WebUI is nanobot's browser workbench for persistent chat sessions, visible
The WebUI is nanobot's browser workbench for persistent topics, visible
agent activity, workspace controls, Apps, Skills, settings, and Automations in
one place.
@@ -57,9 +57,9 @@ gateway health endpoint, `18790` by default, is not the browser UI.
Use the WebUI as the primary setup surface after Quick Start:
1. Send `Hello!` in a new chat to prove the selected model works.
1. Send `Hello!` in a new topic to prove the selected model works.
2. Open **Settings → Models** and confirm the active model preset.
3. Start a separate chat before project work, then choose the intended workspace and access mode.
3. Start a separate topic before project work, then choose the intended workspace and access mode.
4. Add only one capability next: a chat channel in **Settings → Channels**, a web/voice/image provider in **Settings**, or an App/MCP integration in **Apps**.
5. Restart when the WebUI shows a restart requirement, then test that capability with the smallest possible request.
@@ -69,7 +69,7 @@ This path avoids hand-editing `config.json` for normal setup. Use the reference
| Area | Use it for |
|---|---|
| Chat | Start, switch, search, fork, and delete browser sessions |
| Topics | Start, switch, search, fork, and delete browser topics |
| Agent activity | See thinking, tool calls, file edits with diffs, command output, and generated artifacts in context |
| Workspace | Pick the project workspace before asking for file or shell work |
| Access | Choose the access mode for local capabilities allowed by your gateway configuration |
@@ -80,10 +80,10 @@ This path avoids hand-editing `config.json` for normal setup. Use the reference
| Automations | Review, search, run, pause, edit, and delete scheduled and local-trigger agent turns |
| Settings | Adjust models, providers, image generation, voice, web tools, runtime, and safety options |
## Chat Workspace
## Topic Workspace
The sidebar is the session switcher. A session keeps its own history, title,
workspace metadata, and linked automations. Use a new session when you want a
The sidebar is the topic switcher. Each topic keeps its own history, title,
workspace selection, and linked automations. Use a new topic when you want a
separate context; use fork when you want to continue from an existing point
without changing the original thread.
@@ -123,7 +123,7 @@ directory.
The access control in the composer controls the local capability level for the
chat. It does not bypass your gateway, provider, shell sandbox, or operating
system configuration; it only selects among the capabilities that are already
available to this WebUI session.
available to the current topic.
In Restricted mode, ordinary file and shell work stays inside the selected
project. To preserve agent continuity, filesystem/search tools receive narrow,
@@ -133,7 +133,7 @@ neighboring memory or profile files, and it does not allow writes outside the
selected project. These tool exceptions do not broaden the browser's file
preview boundary.
Remote WebUI sessions may reduce access for the current workspace. Selecting a
Remote WebUI connections may reduce access for the current workspace. Selecting a
different workspace or enabling Full Access remains limited to local and native
clients.
@@ -199,10 +199,10 @@ to perform that task.
## Automations
Automations are agent turns that run later in a linked chat/session. They should
be created from the chat, channel, or session where they are supposed to run so
nanobot keeps the correct target context. When an automation runs, it normally
delivers the result back to that linked chat.
Automations are agent turns that run later in a linked topic. Create them from
the topic or channel where they are supposed to run so nanobot keeps the
correct target context. When an automation runs, it normally delivers the
result back to that topic.
For the full automation model, creation flow, trigger CLI usage, and delivery
semantics, see [`automations.md`](./automations.md).
@@ -221,7 +221,7 @@ instead of creating a chat automation.
Use the Automations view to:
- Filter by all, active, paused, needs-attention, or system jobs.
- Search by task name, message, trigger command, linked chat, schedule, or status.
- Search by task name, message, trigger command, linked topic, schedule, or status.
- Sort by next run, last run, updated time, or name.
- Run scheduled automations now.
- Pause or resume, rename, or delete user-created automations.
@@ -232,9 +232,9 @@ Search accepts plain text and field filters such as `name:backup`,
`chat:WeChat`, `schedule:09:30`, `cron:"0 23 * * *"`, `trigger`, and
`status:paused`.
An automation without a linked chat cannot be enabled or run from the WebUI,
An automation without a linked topic cannot be enabled or run from the WebUI,
because nanobot would not know where to deliver the scheduled turn. Recreate it
from the target chat or channel so the automation has complete context.
from the target topic or channel so the automation has complete context.
Local triggers do not have a WebUI "Run now" action because each run needs a
message. Use the copied `nanobot trigger ...` command and replace `"message"`
+1 -1
View File
@@ -516,7 +516,7 @@ function ChatsFoldFooter({
const { t, i18n } = useTranslation();
const collapsedFallback = i18n.resolvedLanguage?.startsWith("zh")
? `已折叠 ${hiddenCount} 个对话`
: `${hiddenCount} hidden chats`;
: `${hiddenCount} hidden topics`;
return (
<div className="px-2 pb-1 pt-1">
+16 -16
View File
@@ -43,12 +43,12 @@
"sidebar": {
"navigation": "Sidebar navigation",
"collapse": "Collapse sidebar",
"newChat": "New chat",
"newChat": "New topic",
"searchAria": "Search",
"searchPlaceholder": "Search",
"searchResults": "Results",
"noSearchResults": "No matching chats.",
"recent": "Recent",
"noSearchResults": "No matching topics.",
"recent": "Recent topics",
"settings": "Settings",
"language": {
"label": "Language",
@@ -819,15 +819,15 @@
}
},
"chat": {
"fallbackTitle": "Chat {{id}}",
"fallbackTitle": "Topic {{id}}",
"forkTitle": "Fork: {{title}}",
"loading": "Loading…",
"noSessions": "No sessions yet.",
"showMore": "Show {{count}} more",
"collapsed": "{{count}} hidden chats",
"collapsed": "{{count}} hidden topics",
"showLess": "Show less",
"actions": "Chat actions for {{title}}",
"newInProject": "Start a new chat in {{project}}",
"actions": "Topic actions for {{title}}",
"newInProject": "Start a new topic in {{project}}",
"activity": {
"running": "Agent running",
"complete": "Agent finished",
@@ -836,9 +836,9 @@
"pin": "Pin",
"unpin": "Unpin",
"rename": "Rename",
"renameTitle": "Rename chat",
"renameDescription": "Choose a local sidebar name for this chat.",
"renamePlaceholder": "Chat name",
"renameTitle": "Rename topic",
"renameDescription": "Choose a local sidebar name for this topic.",
"renamePlaceholder": "Topic name",
"renameProjectTitle": "Rename project",
"renameProjectDescription": "Choose a local sidebar name for this project.",
"renameProjectPlaceholder": "Project name",
@@ -848,10 +848,10 @@
"showArchived": "Show archived",
"hideArchived": "Hide archived",
"delete": "Delete",
"newChat": "New chat",
"newChat": "New topic",
"groups": {
"pinned": "Pinned",
"all": "Chats",
"all": "Topics",
"projects": "Projects",
"today": "Today",
"yesterday": "Yesterday",
@@ -860,7 +860,7 @@
}
},
"deleteConfirm": {
"title": "Delete this chat?",
"title": "Delete this topic?",
"description": "This action cannot be undone.",
"cancel": "Cancel",
"confirm": "Delete",
@@ -954,14 +954,14 @@
},
"header": {
"toggleSidebar": "Toggle sidebar",
"newChat": "Start a new chat",
"newChat": "Start a new topic",
"toggleTheme": "Toggle theme from header",
"settings": "Open settings",
"sessionInfo": "Session details"
},
"sessionInfo": {
"title": "Session",
"untitled": "Untitled chat",
"untitled": "Untitled topic",
"automations": "Automations",
"count": "{{count}}",
"loading": "Loading automations...",
@@ -987,7 +987,7 @@
"composer": {
"placeholderThread": "Type your message…",
"placeholderHero": "Ask anything...",
"placeholderOpening": "Opening a new chat…",
"placeholderOpening": "Opening a new topic…",
"placeholderStreaming": "Model is responding…",
"inputAria": "Message input",
"sendHint": "Enter to send · Shift+Enter for newline",
+15 -15
View File
@@ -43,12 +43,12 @@
"sidebar": {
"navigation": "Navegación de la barra lateral",
"collapse": "Contraer barra lateral",
"newChat": "Nuevo chat",
"newChat": "Nuevo tema",
"searchAria": "Buscar",
"searchPlaceholder": "Buscar",
"searchResults": "Resultados",
"noSearchResults": "No hay chats coincidentes.",
"recent": "Recientes",
"noSearchResults": "No hay temas coincidentes.",
"recent": "Temas recientes",
"settings": "Configuración",
"language": {
"label": "Idioma",
@@ -806,15 +806,15 @@
}
},
"chat": {
"fallbackTitle": "Chat {{id}}",
"fallbackTitle": "Tema {{id}}",
"forkTitle": "Bifurcación: {{title}}",
"loading": "Cargando…",
"noSessions": "Todavía no hay sesiones.",
"showMore": "Mostrar {{count}} más",
"collapsed": "{{count}} chats ocultos",
"collapsed": "{{count}} temas ocultos",
"showLess": "Mostrar menos",
"actions": "Acciones del chat {{title}}",
"newInProject": "Iniciar un chat nuevo en {{project}}",
"actions": "Acciones del tema {{title}}",
"newInProject": "Iniciar un tema nuevo en {{project}}",
"activity": {
"running": "Agent running",
"complete": "Agent finished",
@@ -823,9 +823,9 @@
"pin": "Pin",
"unpin": "Unpin",
"rename": "Rename",
"renameTitle": "Rename chat",
"renameDescription": "Choose a local sidebar name for this chat.",
"renamePlaceholder": "Chat name",
"renameTitle": "Renombrar tema",
"renameDescription": "Elige un nombre local de la barra lateral para este tema.",
"renamePlaceholder": "Nombre del tema",
"renameProjectTitle": "Rename project",
"renameProjectDescription": "Choose a local sidebar name for this project.",
"renameProjectPlaceholder": "Project name",
@@ -835,10 +835,10 @@
"showArchived": "Show archived",
"hideArchived": "Hide archived",
"delete": "Eliminar",
"newChat": "Nuevo chat",
"newChat": "Nuevo tema",
"groups": {
"pinned": "Pinned",
"all": "Chats",
"all": "Temas",
"projects": "Projects",
"today": "Today",
"yesterday": "Yesterday",
@@ -941,14 +941,14 @@
},
"header": {
"toggleSidebar": "Mostrar u ocultar la barra lateral",
"newChat": "Iniciar un chat nuevo",
"newChat": "Iniciar un tema nuevo",
"toggleTheme": "Cambiar tema desde el encabezado",
"settings": "Abrir configuración",
"sessionInfo": "Detalles de la sesión"
},
"sessionInfo": {
"title": "Sesión",
"untitled": "Chat sin título",
"untitled": "Tema sin título",
"automations": "Automatizaciones",
"count": "{{count}}",
"loading": "Cargando automatizaciones...",
@@ -974,7 +974,7 @@
"composer": {
"placeholderThread": "Escribe tu mensaje…",
"placeholderHero": "¿Qué tienes en mente?",
"placeholderOpening": "Abriendo un nuevo chat…",
"placeholderOpening": "Abriendo un nuevo tema…",
"placeholderStreaming": "El modelo está respondiendo…",
"inputAria": "Entrada de mensaje",
"sendHint": "Enter para enviar · Shift+Enter para nueva línea",
+15 -15
View File
@@ -43,12 +43,12 @@
"sidebar": {
"navigation": "Navigation de la barre latérale",
"collapse": "Réduire la barre latérale",
"newChat": "Nouvelle discussion",
"newChat": "Nouveau sujet",
"searchAria": "Rechercher",
"searchPlaceholder": "Rechercher",
"searchResults": "Résultats",
"noSearchResults": "Aucun chat correspondant.",
"recent": "Récentes",
"noSearchResults": "Aucun sujet correspondant.",
"recent": "Sujets récents",
"settings": "Paramètres",
"language": {
"label": "Langue",
@@ -805,15 +805,15 @@
}
},
"chat": {
"fallbackTitle": "Discussion {{id}}",
"fallbackTitle": "Sujet {{id}}",
"forkTitle": "Branche : {{title}}",
"loading": "Chargement…",
"noSessions": "Aucune session pour le moment.",
"showMore": "Afficher {{count}} de plus",
"collapsed": "{{count}} discussions masquées",
"collapsed": "{{count}} sujets masqués",
"showLess": "Afficher moins",
"actions": "Actions de la discussion {{title}}",
"newInProject": "Démarrer une nouvelle discussion dans {{project}}",
"actions": "Actions du sujet {{title}}",
"newInProject": "Démarrer un nouveau sujet dans {{project}}",
"activity": {
"running": "Agent running",
"complete": "Agent finished",
@@ -822,9 +822,9 @@
"pin": "Pin",
"unpin": "Unpin",
"rename": "Rename",
"renameTitle": "Rename chat",
"renameDescription": "Choose a local sidebar name for this chat.",
"renamePlaceholder": "Chat name",
"renameTitle": "Renommer le sujet",
"renameDescription": "Choisissez un nom local dans la barre latérale pour ce sujet.",
"renamePlaceholder": "Nom du sujet",
"renameProjectTitle": "Rename project",
"renameProjectDescription": "Choose a local sidebar name for this project.",
"renameProjectPlaceholder": "Project name",
@@ -834,10 +834,10 @@
"showArchived": "Show archived",
"hideArchived": "Hide archived",
"delete": "Supprimer",
"newChat": "Nouvelle discussion",
"newChat": "Nouveau sujet",
"groups": {
"pinned": "Pinned",
"all": "Chats",
"all": "Sujets",
"projects": "Projects",
"today": "Today",
"yesterday": "Yesterday",
@@ -940,14 +940,14 @@
},
"header": {
"toggleSidebar": "Afficher ou masquer la barre latérale",
"newChat": "Démarrer un nouveau chat",
"newChat": "Démarrer un nouveau sujet",
"toggleTheme": "Changer le thème depuis len-tête",
"settings": "Ouvrir les paramètres",
"sessionInfo": "Détails de la session"
},
"sessionInfo": {
"title": "Session",
"untitled": "Chat sans titre",
"untitled": "Sujet sans titre",
"automations": "Automatisations",
"count": "{{count}}",
"loading": "Chargement des automatisations...",
@@ -973,7 +973,7 @@
"composer": {
"placeholderThread": "Saisissez votre message…",
"placeholderHero": "Quavez-vous en tête ?",
"placeholderOpening": "Ouverture dune nouvelle discussion…",
"placeholderOpening": "Ouverture dun nouveau sujet…",
"placeholderStreaming": "Le modèle est en train de répondre…",
"inputAria": "Champ de message",
"sendHint": "Entrée pour envoyer · Maj+Entrée pour un retour à la ligne",
+15 -15
View File
@@ -43,12 +43,12 @@
"sidebar": {
"navigation": "Navigasi bilah samping",
"collapse": "Ciutkan sidebar",
"newChat": "Obrolan baru",
"newChat": "Topik baru",
"searchAria": "Cari",
"searchPlaceholder": "Cari",
"searchResults": "Hasil",
"noSearchResults": "Tidak ada chat yang cocok.",
"recent": "Terbaru",
"noSearchResults": "Tidak ada topik yang cocok.",
"recent": "Topik terbaru",
"settings": "Pengaturan",
"language": {
"label": "Bahasa",
@@ -805,15 +805,15 @@
}
},
"chat": {
"fallbackTitle": "Obrolan {{id}}",
"fallbackTitle": "Topik {{id}}",
"forkTitle": "Cabang: {{title}}",
"loading": "Memuat…",
"noSessions": "Belum ada sesi.",
"showMore": "Tampilkan {{count}} lagi",
"collapsed": "{{count}} obrolan diciutkan",
"collapsed": "{{count}} topik diciutkan",
"showLess": "Tampilkan lebih sedikit",
"actions": "Aksi obrolan untuk {{title}}",
"newInProject": "Mulai obrolan baru di {{project}}",
"actions": "Aksi topik untuk {{title}}",
"newInProject": "Mulai topik baru di {{project}}",
"activity": {
"running": "Agent running",
"complete": "Agent finished",
@@ -822,9 +822,9 @@
"pin": "Pin",
"unpin": "Unpin",
"rename": "Rename",
"renameTitle": "Rename chat",
"renameDescription": "Choose a local sidebar name for this chat.",
"renamePlaceholder": "Chat name",
"renameTitle": "Ganti nama topik",
"renameDescription": "Pilih nama lokal di bilah sisi untuk topik ini.",
"renamePlaceholder": "Nama topik",
"renameProjectTitle": "Rename project",
"renameProjectDescription": "Choose a local sidebar name for this project.",
"renameProjectPlaceholder": "Project name",
@@ -834,10 +834,10 @@
"showArchived": "Show archived",
"hideArchived": "Hide archived",
"delete": "Hapus",
"newChat": "Obrolan baru",
"newChat": "Topik baru",
"groups": {
"pinned": "Pinned",
"all": "Chats",
"all": "Topik",
"projects": "Projects",
"today": "Today",
"yesterday": "Yesterday",
@@ -940,14 +940,14 @@
},
"header": {
"toggleSidebar": "Tampilkan atau sembunyikan sidebar",
"newChat": "Mulai chat baru",
"newChat": "Mulai topik baru",
"toggleTheme": "Alihkan tema dari header",
"settings": "Buka pengaturan",
"sessionInfo": "Detail sesi"
},
"sessionInfo": {
"title": "Sesi",
"untitled": "Chat tanpa judul",
"untitled": "Topik tanpa judul",
"automations": "Otomasi",
"count": "{{count}}",
"loading": "Memuat otomasi...",
@@ -973,7 +973,7 @@
"composer": {
"placeholderThread": "Ketik pesan Anda…",
"placeholderHero": "Apa yang sedang Anda pikirkan?",
"placeholderOpening": "Membuka obrolan baru…",
"placeholderOpening": "Membuka topik baru…",
"placeholderStreaming": "Model sedang merespons…",
"inputAria": "Input pesan",
"sendHint": "Enter untuk kirim · Shift+Enter untuk baris baru",
+15 -15
View File
@@ -43,12 +43,12 @@
"sidebar": {
"navigation": "サイドバーのナビゲーション",
"collapse": "サイドバーを閉じる",
"newChat": "新しいチャット",
"newChat": "新しいトピック",
"searchAria": "検索",
"searchPlaceholder": "検索",
"searchResults": "検索結果",
"noSearchResults": "一致するチャットはありません。",
"recent": "最近のチャット",
"noSearchResults": "一致するトピックはありません。",
"recent": "最近のトピック",
"settings": "設定",
"language": {
"label": "言語",
@@ -805,15 +805,15 @@
}
},
"chat": {
"fallbackTitle": "チャット {{id}}",
"fallbackTitle": "トピック {{id}}",
"forkTitle": "分岐:{{title}}",
"loading": "読み込み中…",
"noSessions": "まだセッションがありません。",
"showMore": "さらに {{count}} 件表示",
"collapsed": "{{count}} 件のチャットを折りたたみ中",
"collapsed": "{{count}} 件のトピックを折りたたみ中",
"showLess": "折りたたむ",
"actions": "「{{title}}」のチャット操作",
"newInProject": "「{{project}}」で新しいチャットを開始",
"actions": "「{{title}}」のトピック操作",
"newInProject": "「{{project}}」で新しいトピックを開始",
"activity": {
"running": "Agent running",
"complete": "Agent finished",
@@ -822,9 +822,9 @@
"pin": "Pin",
"unpin": "Unpin",
"rename": "Rename",
"renameTitle": "Rename chat",
"renameDescription": "Choose a local sidebar name for this chat.",
"renamePlaceholder": "Chat name",
"renameTitle": "トピック名を変更",
"renameDescription": "このトピックのサイドバー表示名を選択します。",
"renamePlaceholder": "トピック名",
"renameProjectTitle": "Rename project",
"renameProjectDescription": "Choose a local sidebar name for this project.",
"renameProjectPlaceholder": "Project name",
@@ -834,10 +834,10 @@
"showArchived": "Show archived",
"hideArchived": "Hide archived",
"delete": "削除",
"newChat": "新しいチャット",
"newChat": "新しいトピック",
"groups": {
"pinned": "Pinned",
"all": "Chats",
"all": "トピック",
"projects": "Projects",
"today": "Today",
"yesterday": "Yesterday",
@@ -940,14 +940,14 @@
},
"header": {
"toggleSidebar": "サイドバーを切り替える",
"newChat": "新しいチャットを開始",
"newChat": "新しいトピックを開始",
"toggleTheme": "ヘッダーからテーマを切り替える",
"settings": "設定を開く",
"sessionInfo": "セッション詳細"
},
"sessionInfo": {
"title": "セッション",
"untitled": "無題のチャット",
"untitled": "無題のトピック",
"automations": "自動タスク",
"count": "{{count}}",
"loading": "自動タスクを読み込み中...",
@@ -973,7 +973,7 @@
"composer": {
"placeholderThread": "メッセージを入力…",
"placeholderHero": "何を考えていますか?",
"placeholderOpening": "新しいチャットを開いています…",
"placeholderOpening": "新しいトピックを開いています…",
"placeholderStreaming": "モデルが応答しています…",
"inputAria": "メッセージ入力欄",
"sendHint": "Enter で送信 · Shift+Enter で改行",
+15 -15
View File
@@ -43,12 +43,12 @@
"sidebar": {
"navigation": "사이드바 탐색",
"collapse": "사이드바 접기",
"newChat": "새 채팅",
"newChat": "새 주제",
"searchAria": "검색",
"searchPlaceholder": "검색",
"searchResults": "결과",
"noSearchResults": "일치하는 채팅이 없습니다.",
"recent": "최근 대화",
"noSearchResults": "일치하는 주제가 없습니다.",
"recent": "최근 주제",
"settings": "설정",
"language": {
"label": "언어",
@@ -805,15 +805,15 @@
}
},
"chat": {
"fallbackTitle": "채팅 {{id}}",
"fallbackTitle": "주제 {{id}}",
"forkTitle": "분기: {{title}}",
"loading": "불러오는 중…",
"noSessions": "아직 세션이 없습니다.",
"showMore": "{{count}}개 더 보기",
"collapsed": "{{count}}개 채팅 접힘",
"collapsed": "{{count}}개 주제 접힘",
"showLess": "접기",
"actions": "{{title}} 채팅 작업",
"newInProject": "{{project}}에서 새 채팅 시작",
"actions": "{{title}} 주제 작업",
"newInProject": "{{project}}에서 새 주제 시작",
"activity": {
"running": "Agent running",
"complete": "Agent finished",
@@ -822,9 +822,9 @@
"pin": "Pin",
"unpin": "Unpin",
"rename": "Rename",
"renameTitle": "Rename chat",
"renameDescription": "Choose a local sidebar name for this chat.",
"renamePlaceholder": "Chat name",
"renameTitle": "주제 이름 변경",
"renameDescription": "이 주제에 사용할 사이드바 이름을 선택하세요.",
"renamePlaceholder": "주제 이름",
"renameProjectTitle": "Rename project",
"renameProjectDescription": "Choose a local sidebar name for this project.",
"renameProjectPlaceholder": "Project name",
@@ -834,10 +834,10 @@
"showArchived": "Show archived",
"hideArchived": "Hide archived",
"delete": "삭제",
"newChat": "새 채팅",
"newChat": "새 주제",
"groups": {
"pinned": "Pinned",
"all": "Chats",
"all": "주제",
"projects": "Projects",
"today": "Today",
"yesterday": "Yesterday",
@@ -940,14 +940,14 @@
},
"header": {
"toggleSidebar": "사이드바 전환",
"newChat": "새 채팅 시작",
"newChat": "새 주제 시작",
"toggleTheme": "헤더에서 테마 전환",
"settings": "설정 열기",
"sessionInfo": "세션 세부 정보"
},
"sessionInfo": {
"title": "세션",
"untitled": "제목 없는 채팅",
"untitled": "제목 없는 주제",
"automations": "자동화",
"count": "{{count}}",
"loading": "자동화를 불러오는 중...",
@@ -973,7 +973,7 @@
"composer": {
"placeholderThread": "메시지를 입력하세요…",
"placeholderHero": "무슨 생각을 하고 있나요?",
"placeholderOpening": "새 채팅을 여는 중…",
"placeholderOpening": "새 주제를 여는 중…",
"placeholderStreaming": "모델이 응답 중입니다…",
"inputAria": "메시지 입력",
"sendHint": "Enter로 전송 · Shift+Enter로 줄바꿈",
+15 -15
View File
@@ -43,12 +43,12 @@
"sidebar": {
"navigation": "Navegação da barra lateral",
"collapse": "Recolher barra lateral",
"newChat": "Nova conversa",
"newChat": "Novo tópico",
"searchAria": "Buscar",
"searchPlaceholder": "Buscar",
"searchResults": "Resultados",
"noSearchResults": "Nenhuma conversa encontrada.",
"recent": "Recentes",
"noSearchResults": "Nenhum tópico encontrado.",
"recent": "Tópicos recentes",
"settings": "Configurações",
"language": {
"label": "Idioma",
@@ -819,15 +819,15 @@
}
},
"chat": {
"fallbackTitle": "Conversa {{id}}",
"fallbackTitle": "Tópico {{id}}",
"forkTitle": "Fork: {{title}}",
"loading": "Carregando…",
"noSessions": "Nenhuma sessão ainda.",
"showMore": "Mostrar mais {{count}}",
"collapsed": "{{count}} conversas ocultas",
"collapsed": "{{count}} tópicos ocultos",
"showLess": "Mostrar menos",
"actions": "Ações da conversa {{title}}",
"newInProject": "Iniciar uma nova conversa em {{project}}",
"actions": "Ações do tópico {{title}}",
"newInProject": "Iniciar um novo tópico em {{project}}",
"activity": {
"running": "Agente em execução",
"complete": "Agente finalizado",
@@ -836,9 +836,9 @@
"pin": "Fixar",
"unpin": "Desafixar",
"rename": "Renomear",
"renameTitle": "Renomear conversa",
"renameDescription": "Escolha um nome local da barra lateral para esta conversa.",
"renamePlaceholder": "Nome da conversa",
"renameTitle": "Renomear tópico",
"renameDescription": "Escolha um nome local da barra lateral para este tópico.",
"renamePlaceholder": "Nome do tópico",
"renameProjectTitle": "Renomear projeto",
"renameProjectDescription": "Escolha um nome local da barra lateral para este projeto.",
"renameProjectPlaceholder": "Nome do projeto",
@@ -848,10 +848,10 @@
"showArchived": "Mostrar arquivadas",
"hideArchived": "Ocultar arquivadas",
"delete": "Excluir",
"newChat": "Nova conversa",
"newChat": "Novo tópico",
"groups": {
"pinned": "Fixadas",
"all": "Conversas",
"all": "Tópicos",
"projects": "Projetos",
"today": "Hoje",
"yesterday": "Ontem",
@@ -954,14 +954,14 @@
},
"header": {
"toggleSidebar": "Alternar barra lateral",
"newChat": "Iniciar uma nova conversa",
"newChat": "Iniciar um novo tópico",
"toggleTheme": "Alternar tema pelo cabeçalho",
"settings": "Abrir configurações",
"sessionInfo": "Detalhes da sessão"
},
"sessionInfo": {
"title": "Sessão",
"untitled": "Conversa sem título",
"untitled": "Tópico sem título",
"automations": "Automações",
"count": "{{count}}",
"loading": "Carregando automações...",
@@ -987,7 +987,7 @@
"composer": {
"placeholderThread": "Digite sua mensagem…",
"placeholderHero": "Pergunte qualquer coisa...",
"placeholderOpening": "Abrindo uma nova conversa…",
"placeholderOpening": "Abrindo um novo tópico…",
"placeholderStreaming": "O modelo está respondendo…",
"inputAria": "Campo de mensagem",
"sendHint": "Enter para enviar · Shift+Enter para nova linha",
+15 -15
View File
@@ -43,12 +43,12 @@
"sidebar": {
"navigation": "Điều hướng thanh bên",
"collapse": "Thu gọn thanh bên",
"newChat": "Cuộc trò chuyện mới",
"newChat": "Chủ đề mới",
"searchAria": "Tìm kiếm",
"searchPlaceholder": "Tìm kiếm",
"searchResults": "Kết quả",
"noSearchResults": "Không có cuộc trò chuyện phù hợp.",
"recent": "Gần đây",
"noSearchResults": "Không có chủ đề phù hợp.",
"recent": "Chủ đề gần đây",
"settings": "Cài đặt",
"language": {
"label": "Ngôn ngữ",
@@ -805,15 +805,15 @@
}
},
"chat": {
"fallbackTitle": "Trò chuyện {{id}}",
"fallbackTitle": "Chủ đề {{id}}",
"forkTitle": "Nhánh: {{title}}",
"loading": "Đang tải…",
"noSessions": "Chưa có phiên nào.",
"showMore": "Hiển thị thêm {{count}}",
"collapsed": "Đã thu gọn {{count}} cuộc trò chuyện",
"collapsed": "Đã thu gọn {{count}} chủ đề",
"showLess": "Thu gọn",
"actions": "Tác vụ cho cuộc trò chuyện {{title}}",
"newInProject": "Bắt đầu cuộc trò chuyện mới trong {{project}}",
"actions": "Tác vụ cho chủ đề {{title}}",
"newInProject": "Bắt đầu chủ đề mới trong {{project}}",
"activity": {
"running": "Agent running",
"complete": "Agent finished",
@@ -822,9 +822,9 @@
"pin": "Pin",
"unpin": "Unpin",
"rename": "Rename",
"renameTitle": "Rename chat",
"renameDescription": "Choose a local sidebar name for this chat.",
"renamePlaceholder": "Chat name",
"renameTitle": "Đổi tên chủ đề",
"renameDescription": "Chọn tên hiển thị trong thanh bên cho chủ đề này.",
"renamePlaceholder": "Tên chủ đề",
"renameProjectTitle": "Rename project",
"renameProjectDescription": "Choose a local sidebar name for this project.",
"renameProjectPlaceholder": "Project name",
@@ -834,10 +834,10 @@
"showArchived": "Show archived",
"hideArchived": "Hide archived",
"delete": "Xóa",
"newChat": "Cuộc trò chuyện mới",
"newChat": "Chủ đề mới",
"groups": {
"pinned": "Pinned",
"all": "Chats",
"all": "Chủ đề",
"projects": "Projects",
"today": "Today",
"yesterday": "Yesterday",
@@ -940,14 +940,14 @@
},
"header": {
"toggleSidebar": "Bật/tắt thanh bên",
"newChat": "Bắt đầu chat mới",
"newChat": "Bắt đầu chủ đề mới",
"toggleTheme": "Chuyển chủ đề từ header",
"settings": "Mở cài đặt",
"sessionInfo": "Chi tiết phiên"
},
"sessionInfo": {
"title": "Phiên",
"untitled": "Chat chưa đặt tên",
"untitled": "Chủ đề chưa đặt tên",
"automations": "Tự động hóa",
"count": "{{count}}",
"loading": "Đang tải tự động hóa...",
@@ -973,7 +973,7 @@
"composer": {
"placeholderThread": "Nhập tin nhắn…",
"placeholderHero": "Bạn đang nghĩ gì?",
"placeholderOpening": "Đang mở cuộc trò chuyện mới…",
"placeholderOpening": "Đang mở chủ đề mới…",
"placeholderStreaming": "Mô hình đang trả lời…",
"inputAria": "Ô nhập tin nhắn",
"sendHint": "Enter để gửi · Shift+Enter để xuống dòng",
+21 -21
View File
@@ -43,12 +43,12 @@
"sidebar": {
"navigation": "侧边栏导航",
"collapse": "收起侧边栏",
"newChat": "新建话",
"newChat": "新建话",
"searchAria": "搜索",
"searchPlaceholder": "搜索",
"searchResults": "搜索结果",
"noSearchResults": "没有匹配的话。",
"recent": "最近话",
"noSearchResults": "没有匹配的话。",
"recent": "最近话",
"settings": "设置",
"language": {
"label": "语言",
@@ -819,15 +819,15 @@
}
},
"chat": {
"fallbackTitle": "话 {{id}}",
"fallbackTitle": "话 {{id}}",
"forkTitle": "分叉:{{title}}",
"loading": "加载中…",
"noSessions": "还没有话。",
"noSessions": "还没有话。",
"showMore": "再显示 {{count}} 个",
"collapsed": "已折叠 {{count}} 个话",
"collapsed": "已折叠 {{count}} 个话",
"showLess": "收起",
"actions": "“{{title}}” 的话操作",
"newInProject": "在 {{project}} 中开始新话",
"actions": "“{{title}}” 的话操作",
"newInProject": "在 {{project}} 中开始新话",
"activity": {
"running": "Agent 正在运行",
"complete": "Agent 已完成",
@@ -836,9 +836,9 @@
"pin": "置顶",
"unpin": "取消置顶",
"rename": "重命名",
"renameTitle": "重命名话",
"renameDescription": "为这个话设置一个仅用于 WebUI 侧边栏的名称。",
"renamePlaceholder": "话名称",
"renameTitle": "重命名话",
"renameDescription": "为这个话设置一个仅用于 WebUI 侧边栏的名称。",
"renamePlaceholder": "话名称",
"renameProjectTitle": "重命名项目",
"renameProjectDescription": "为这个项目设置一个仅用于 WebUI 侧边栏的名称。",
"renameProjectPlaceholder": "项目名称",
@@ -848,10 +848,10 @@
"showArchived": "显示归档",
"hideArchived": "隐藏归档",
"delete": "删除",
"newChat": "新建话",
"newChat": "新建话",
"groups": {
"pinned": "置顶",
"all": "话",
"all": "话",
"projects": "项目",
"today": "今天",
"yesterday": "昨天",
@@ -860,11 +860,11 @@
}
},
"deleteConfirm": {
"title": "删除这个话?",
"title": "删除这个话",
"description": "此操作无法撤销。",
"cancel": "取消",
"confirm": "删除",
"automationsDescription": "这个话有关联的自动任务。删除话也会删除这些自动任务。",
"automationsDescription": "这个话有关联的自动任务。删除话也会删除这些自动任务。",
"moreAutomations": "另有 {{count}} 个",
"confirmWithAutomations": "删除",
"schedule": {
@@ -891,7 +891,7 @@
"error": "连接出错"
},
"thread": {
"loadingConversation": "正在加载话…",
"loadingConversation": "正在加载话…",
"empty": {
"greetings": {
"workOn": "我们要一起做点什么?",
@@ -954,14 +954,14 @@
},
"header": {
"toggleSidebar": "切换侧边栏",
"newChat": "从顶部新建话",
"newChat": "从顶部新建话",
"toggleTheme": "从顶部切换主题",
"settings": "打开设置",
"sessionInfo": "会话详情"
},
"sessionInfo": {
"title": "会话",
"untitled": "未命名话",
"untitled": "未命名话",
"automations": "自动任务",
"count": "{{count}}",
"loading": "正在加载自动任务...",
@@ -987,7 +987,7 @@
"composer": {
"placeholderThread": "输入消息…",
"placeholderHero": "问任何问题...",
"placeholderOpening": "正在打开新话…",
"placeholderOpening": "正在打开新话…",
"placeholderStreaming": "模型正在回复…",
"inputAria": "消息输入框",
"sendHint": "Enter 发送 · Shift+Enter 换行",
@@ -1065,8 +1065,8 @@
},
"commands": {
"new": {
"title": "新建话",
"description": "重置当前话,并开始一个新的话。"
"title": "新建话",
"description": "重置当前话,并开始一个新的话。"
},
"stop": {
"title": "停止当前任务",
+21 -21
View File
@@ -43,12 +43,12 @@
"sidebar": {
"navigation": "側邊欄導覽",
"collapse": "收合側邊欄",
"newChat": "新增話",
"newChat": "新增話",
"searchAria": "搜尋",
"searchPlaceholder": "搜尋",
"searchResults": "搜尋結果",
"noSearchResults": "沒有符合的話。",
"recent": "最近話",
"noSearchResults": "沒有符合的話。",
"recent": "最近話",
"settings": "設定",
"language": {
"label": "語言",
@@ -805,15 +805,15 @@
}
},
"chat": {
"fallbackTitle": "話 {{id}}",
"fallbackTitle": "話 {{id}}",
"forkTitle": "分支:{{title}}",
"loading": "載入中…",
"noSessions": "目前還沒有話。",
"noSessions": "目前還沒有話。",
"showMore": "再顯示 {{count}} 個",
"collapsed": "已隱藏 {{count}} 個話",
"collapsed": "已隱藏 {{count}} 個話",
"showLess": "收起",
"actions": "「{{title}}」的話操作",
"newInProject": "在 {{project}} 中開始新話",
"actions": "「{{title}}」的話操作",
"newInProject": "在 {{project}} 中開始新話",
"activity": {
"running": "Agent 正在執行",
"complete": "Agent 已完成",
@@ -822,9 +822,9 @@
"pin": "置頂",
"unpin": "取消置頂",
"rename": "重新命名",
"renameTitle": "重新命名話",
"renameDescription": "為這個話選擇僅顯示於本機側邊欄的名稱。",
"renamePlaceholder": "話名稱",
"renameTitle": "重新命名話",
"renameDescription": "為這個話選擇僅顯示於本機側邊欄的名稱。",
"renamePlaceholder": "話名稱",
"renameProjectTitle": "重新命名專案",
"renameProjectDescription": "為這個專案選擇僅顯示於本機側邊欄的名稱。",
"renameProjectPlaceholder": "專案名稱",
@@ -834,10 +834,10 @@
"showArchived": "顯示封存",
"hideArchived": "隱藏封存",
"delete": "刪除",
"newChat": "新增話",
"newChat": "新增話",
"groups": {
"pinned": "置頂",
"all": "話",
"all": "話",
"projects": "專案",
"today": "今天",
"yesterday": "昨天",
@@ -846,11 +846,11 @@
}
},
"deleteConfirm": {
"title": "刪除這個話?",
"title": "刪除這個話",
"description": "此操作無法復原。",
"cancel": "取消",
"confirm": "刪除",
"automationsDescription": "這個話含有已排程的自動任務。刪除話時也會一併刪除這些任務。",
"automationsDescription": "這個話含有已排程的自動任務。刪除話時也會一併刪除這些任務。",
"moreAutomations": "另有 {{count}} 個",
"confirmWithAutomations": "刪除",
"schedule": {
@@ -877,7 +877,7 @@
"error": "連線錯誤"
},
"thread": {
"loadingConversation": "正在載入話…",
"loadingConversation": "正在載入話…",
"empty": {
"greetings": {
"workOn": "我們要一起做點什麼?",
@@ -940,14 +940,14 @@
},
"header": {
"toggleSidebar": "切換側邊欄",
"newChat": "開始新話",
"newChat": "開始新話",
"toggleTheme": "切換佈景主題",
"settings": "開啟設定",
"sessionInfo": "對話詳細資料"
},
"sessionInfo": {
"title": "對話",
"untitled": "未命名話",
"untitled": "未命名話",
"automations": "自動任務",
"count": "{{count}}",
"loading": "正在載入自動任務…",
@@ -973,7 +973,7 @@
"composer": {
"placeholderThread": "輸入訊息…",
"placeholderHero": "任何問題都可以問…",
"placeholderOpening": "正在開啟新話…",
"placeholderOpening": "正在開啟新話…",
"placeholderStreaming": "模型正在回覆…",
"inputAria": "訊息輸入框",
"sendHint": "Enter 送出 · Shift+Enter 換行",
@@ -1042,8 +1042,8 @@
"closeHint": "Esc 關閉",
"commands": {
"new": {
"title": "新增話",
"description": "重置目前話,並開始新的話。"
"title": "新增話",
"description": "重置目前話,並開始新的話。"
},
"stop": {
"title": "停止目前任務",
+20 -20
View File
@@ -858,13 +858,13 @@ describe("App layout", () => {
).toBeInTheDocument(),
);
fireEvent.pointerDown(screen.getByLabelText("Chat actions for First chat"), {
fireEvent.pointerDown(screen.getByLabelText("Topic actions for First chat"), {
button: 0,
});
fireEvent.click(await screen.findByRole("menuitem", { name: "Delete" }));
await waitFor(() =>
expect(screen.getByText("Delete this chat?")).toBeInTheDocument(),
expect(screen.getByText("Delete this topic?")).toBeInTheDocument(),
);
fireEvent.click(screen.getByRole("button", { name: "Delete" }));
@@ -876,7 +876,7 @@ describe("App layout", () => {
within(sidebar).getByRole("button", { name: /^Second chat$/ }),
).toBeInTheDocument(),
);
expect(screen.queryByText("Delete this chat?")).not.toBeInTheDocument();
expect(screen.queryByText("Delete this topic?")).not.toBeInTheDocument();
expect(document.body.style.pointerEvents).not.toBe("none");
}, 15_000);
@@ -921,7 +921,7 @@ describe("App layout", () => {
).toBeInTheDocument(),
);
fireEvent.pointerDown(screen.getByLabelText(/First chat.*话操作/), {
fireEvent.pointerDown(screen.getByLabelText(/First chat.*话操作/), {
button: 0,
});
fireEvent.click(await screen.findByRole("menuitem", { name: "删除" }));
@@ -931,7 +931,7 @@ describe("App layout", () => {
);
expect(getSessionAutomationsSpy).toHaveBeenCalledWith("websocket:chat-a");
expect(
screen.getByText("这个话有关联的自动任务。删除话也会删除这些自动任务。"),
screen.getByText("这个话有关联的自动任务。删除话也会删除这些自动任务。"),
).toBeInTheDocument();
expect(
screen.queryByText("This chat has scheduled automations. Deleting it will also delete them."),
@@ -989,7 +989,7 @@ describe("App layout", () => {
);
fireEvent.pointerDown(
within(mobileSidebar).getByLabelText("Chat actions for Existing chat"),
within(mobileSidebar).getByLabelText("Topic actions for Existing chat"),
{ button: 0 },
);
@@ -1000,7 +1000,7 @@ describe("App layout", () => {
fireEvent.click(deleteItem);
await waitFor(() =>
expect(screen.getByText("Delete this chat?")).toBeInTheDocument(),
expect(screen.getByText("Delete this topic?")).toBeInTheDocument(),
);
}, 15_000);
@@ -1143,16 +1143,16 @@ describe("App layout", () => {
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
await waitFor(() =>
expect(within(sidebar).getByText("Chats")).toBeInTheDocument(),
expect(within(sidebar).getByText("Topics")).toBeInTheDocument(),
);
const group = within(sidebar).getByText("Chats").closest("section");
const group = within(sidebar).getByText("Topics").closest("section");
expect(group).toBeTruthy();
const labels = within(group as HTMLElement)
.getAllByRole("button")
.map((button) => button.textContent?.trim())
.filter(Boolean);
expect(labels).toEqual(["Alpha plan", "New chat", "Zulu work"]);
expect(labels).toEqual(["Alpha plan", "New topic", "Zulu work"]);
});
it("shows running and completed session indicators in the sidebar", async () => {
@@ -1969,7 +1969,7 @@ describe("App layout", () => {
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
fireEvent.click(within(sidebar).getByRole("button", { name: "New chat" }));
fireEvent.click(within(sidebar).getByRole("button", { name: "New topic" }));
await waitFor(() => expect(document.title).toBe("nanobot"));
fireEvent.click(within(sidebar).getByRole("button", { name: "Settings" }));
@@ -2009,7 +2009,7 @@ describe("App layout", () => {
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
expect(within(sidebar).getByText("Q2 roadmap")).toBeInTheDocument();
expect(within(sidebar).getByText("Travel ideas")).toBeInTheDocument();
const newChatButton = within(sidebar).getByRole("button", { name: "New chat" });
const newChatButton = within(sidebar).getByRole("button", { name: "New topic" });
const searchButton = within(sidebar).getByRole("button", { name: "Search" });
expect(
newChatButton.compareDocumentPosition(searchButton) &
@@ -2139,10 +2139,10 @@ describe("App layout", () => {
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
const newChatButton = within(sidebar).getByRole("button", { name: "New chat" });
const newChatButton = within(sidebar).getByRole("button", { name: "New topic" });
expect(newChatButton).toHaveAttribute(
"title",
"New chat (Ctrl+Shift+O)",
"New topic (Ctrl+Shift+O)",
);
expect(newChatButton).toHaveAttribute(
"aria-keyshortcuts",
@@ -2157,9 +2157,9 @@ describe("App layout", () => {
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
expect(within(sidebar).getByRole("button", { name: "New chat" })).toHaveAttribute(
expect(within(sidebar).getByRole("button", { name: "New topic" })).toHaveAttribute(
"title",
"New chat (⌘⇧O)",
"New topic (⌘⇧O)",
);
});
@@ -2230,9 +2230,9 @@ describe("App layout", () => {
const sidebarAside = container.querySelector("aside.lg\\:block") as HTMLElement;
await waitFor(() => expect(sidebarAside.style.width).toBe("56px"));
expect(screen.queryByRole("button", { name: "Start a new chat" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Start a new topic" })).not.toBeInTheDocument();
const rail = screen.getByRole("navigation", { name: "Sidebar navigation" });
expect(within(rail).getByRole("button", { name: "New chat" })).toBeInTheDocument();
expect(within(rail).getByRole("button", { name: "New topic" })).toBeInTheDocument();
expect(within(rail).getByRole("button", { name: "Search" })).toBeInTheDocument();
expect(within(rail).queryByRole("button", { name: "View" })).not.toBeInTheDocument();
expect(within(rail).queryByText("Existing chat")).not.toBeInTheDocument();
@@ -2241,10 +2241,10 @@ describe("App layout", () => {
await waitFor(() => expect(sidebarAside.style.width).toBe("272px"));
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
fireEvent.click(within(sidebar).getByRole("button", { name: "New chat" }));
fireEvent.click(within(sidebar).getByRole("button", { name: "New topic" }));
expect(createChatSpy).not.toHaveBeenCalled();
expect(screen.getByText(HERO_GREETING_PATTERN)).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Start a new chat" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Start a new topic" })).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: "Toggle theme from header" })).toBeInTheDocument();
expect(within(sidebar).getByRole("button", { name: "Settings" })).toBeInTheDocument();
+13 -13
View File
@@ -142,7 +142,7 @@ describe("ChatList", () => {
expect(screen.queryByText("Today")).not.toBeInTheDocument();
});
it("keeps default workspace chats in the Chats section instead of a project folder", () => {
it("keeps default workspace topics in the Topics section instead of a project folder", () => {
const sessions = [
session({
chatId: "default",
@@ -184,7 +184,7 @@ describe("ChatList", () => {
expect(screen.getByRole("region", { name: "nanobot" })).toBeInTheDocument();
expect(screen.queryByRole("region", { name: "workspace" })).not.toBeInTheDocument();
const chatsSection = screen.getByRole("region", { name: "Chats" });
const chatsSection = screen.getByRole("region", { name: "Topics" });
expect(within(chatsSection).getByText("Default workspace chat")).toBeInTheDocument();
expect(within(chatsSection).queryByText("Project chat")).not.toBeInTheDocument();
});
@@ -229,13 +229,13 @@ describe("ChatList", () => {
expect(within(projectSection).queryByText("Alpha task")).not.toBeInTheDocument();
fireEvent.click(
within(projectSection).getByRole("button", { name: "Start a new chat in Photos" }),
within(projectSection).getByRole("button", { name: "Start a new topic in Photos" }),
);
expect(onNewChatInProject).toHaveBeenCalledWith("/Users/me/nanobot", "Photos");
expect(onToggleGroup).toHaveBeenCalledTimes(1);
fireEvent.pointerDown(
within(projectSection).getByLabelText("Chat actions for Photos"),
within(projectSection).getByLabelText("Topic actions for Photos"),
{ button: 0 },
);
fireEvent.click(await screen.findByRole("menuitem", { name: "Rename" }));
@@ -300,13 +300,13 @@ describe("ChatList", () => {
};
const { rerender } = render(<ChatList {...baseProps} />);
const chatsSection = screen.getByRole("region", { name: "Chats" });
const chatsSection = screen.getByRole("region", { name: "Topics" });
expect(within(chatsSection).getByText("Chat 9")).toBeInTheDocument();
expect(within(chatsSection).getByText("Chat 2")).toBeInTheDocument();
expect(within(chatsSection).queryByText("Chat 1")).not.toBeInTheDocument();
expect(within(chatsSection).queryByRole("button", { name: "Show all" })).not.toBeInTheDocument();
fireEvent.click(within(chatsSection).getByRole("button", { name: "2 hidden chats" }));
fireEvent.click(within(chatsSection).getByRole("button", { name: "2 hidden topics" }));
expect(onToggleGroup).toHaveBeenCalledWith("workspace:chats");
@@ -321,7 +321,7 @@ describe("ChatList", () => {
expect(within(chatsSection).getByRole("button", { name: "Show less" })).toBeInTheDocument();
});
it("sorts Chats section among project groups by recency, not always last", () => {
it("sorts Topics section among project groups by recency, not always last", () => {
const sessions = [
session({
chatId: "recent-chat",
@@ -367,8 +367,8 @@ describe("ChatList", () => {
const regionNames = allRegions.map((r) => r.getAttribute("aria-label") ?? r.textContent);
// The most recently updated conversation ("Recent chat" at 12:00) must be
// in the first group — Chats should come before both projects.
const chatsIdx = regionNames.findIndex((n) => n?.includes("Chats"));
// in the first group — Topics should come before both projects.
const chatsIdx = regionNames.findIndex((n) => n?.includes("Topics"));
const projAIdx = regionNames.findIndex((n) => n?.includes("project-a"));
const projBIdx = regionNames.findIndex((n) => n?.includes("project-b"));
@@ -377,7 +377,7 @@ describe("ChatList", () => {
expect(within(allRegions[chatsIdx]).getByText("Recent chat")).toBeInTheDocument();
});
it("keeps one Projects heading when Chats sorts between project groups", () => {
it("keeps one Projects heading when Topics sorts between project groups", () => {
const sessions = [
session({
chatId: "project-a",
@@ -423,11 +423,11 @@ describe("ChatList", () => {
.getAllByRole("region")
.map((r) => r.getAttribute("aria-label") ?? "");
expect(regionNames).toEqual(["project-a", "Chats", "project-b"]);
expect(regionNames).toEqual(["project-a", "Topics", "project-b"]);
expect(screen.getAllByText("Projects")).toHaveLength(1);
});
it("keeps Chats last when its latest conversation is older than all projects", () => {
it("keeps Topics last when its latest conversation is older than all projects", () => {
const sessions = [
session({
chatId: "project-a",
@@ -473,7 +473,7 @@ describe("ChatList", () => {
.getAllByRole("region")
.map((r) => r.getAttribute("aria-label") ?? "");
expect(regionNames).toEqual(["project-a", "project-b", "Chats"]);
expect(regionNames).toEqual(["project-a", "project-b", "Topics"]);
expect(screen.getAllByText("Projects")).toHaveLength(1);
});
});
+1 -1
View File
@@ -445,7 +445,7 @@ describe("webui i18n", () => {
const chat = resources["pt-BR"].common.chat;
expect(sidebar.settings).toBe("Configurações");
expect(chat.newChat).toBe("Nova conversa");
expect(chat.newChat).toBe("Novo tópico");
expect(settings.nav.browser).toBe("Web");
expect(settings.sections.webSearch).toBe("Busca na web");
expect(settings.byok.tabs.webSearch).toBe("Busca na web");
+1 -1
View File
@@ -71,7 +71,7 @@ describe("useSessions", () => {
updatedAt: "2026-04-16T10:00:00Z",
title: "",
preview: "hi",
})).toBe("New chat");
})).toBe("New topic");
expect(sessionTitle({
key: "websocket:chat-work",