@celljs/ta-web 4.6.1 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +90 -0
- package/README.md +13 -7
- package/esm/browser/chat/chat-protocol.d.ts +4 -0
- package/esm/browser/chat/chat-view.js +4 -1
- package/esm/browser/chat/chat-view.js.map +1 -1
- package/esm/browser/chat/chat.d.ts +8 -1
- package/esm/browser/chat/chat.js +34 -15
- package/esm/browser/chat/chat.js.map +1 -1
- package/esm/browser/chat/draft-model.d.ts +15 -0
- package/esm/browser/chat/draft-model.js +53 -0
- package/esm/browser/chat/draft-model.js.map +1 -0
- package/esm/browser/components/account-menu.d.ts +3 -2
- package/esm/browser/components/account-menu.js +21 -16
- package/esm/browser/components/account-menu.js.map +1 -1
- package/esm/browser/components/app-view.js +13 -4
- package/esm/browser/components/app-view.js.map +1 -1
- package/esm/browser/components/chat-list.d.ts +3 -1
- package/esm/browser/components/chat-list.js +6 -1
- package/esm/browser/components/chat-list.js.map +1 -1
- package/esm/browser/components/composer-box.d.ts +6 -0
- package/esm/browser/components/composer-box.js +50 -13
- package/esm/browser/components/composer-box.js.map +1 -1
- package/esm/browser/components/composer.js +1 -1
- package/esm/browser/components/composer.js.map +1 -1
- package/esm/browser/components/dialog-view.d.ts +4 -1
- package/esm/browser/components/dialog-view.js +11 -7
- package/esm/browser/components/dialog-view.js.map +1 -1
- package/esm/browser/components/folder-picker.d.ts +37 -6
- package/esm/browser/components/folder-picker.js +155 -33
- package/esm/browser/components/folder-picker.js.map +1 -1
- package/esm/browser/components/notice-center.js +3 -1
- package/esm/browser/components/notice-center.js.map +1 -1
- package/esm/browser/components/remote-hosts.d.ts +7 -0
- package/esm/browser/components/remote-hosts.js +94 -0
- package/esm/browser/components/remote-hosts.js.map +1 -0
- package/esm/browser/components/remote-prompts.d.ts +7 -0
- package/esm/browser/components/remote-prompts.js +72 -0
- package/esm/browser/components/remote-prompts.js.map +1 -0
- package/esm/browser/components/session-view.js +48 -4
- package/esm/browser/components/session-view.js.map +1 -1
- package/esm/browser/components/settings-view.js +8 -1
- package/esm/browser/components/settings-view.js.map +1 -1
- package/esm/browser/components/terminal-art.d.ts +7 -0
- package/esm/browser/components/terminal-art.js +12 -0
- package/esm/browser/components/terminal-art.js.map +1 -0
- package/esm/browser/components/tool-view.js +1 -1
- package/esm/browser/components/tool-view.js.map +1 -1
- package/esm/browser/components/web-composer.d.ts +4 -1
- package/esm/browser/components/web-composer.js +2 -2
- package/esm/browser/components/web-composer.js.map +1 -1
- package/esm/browser/components/working-directory.js +1 -1
- package/esm/browser/components/working-directory.js.map +1 -1
- package/esm/browser/extension/icons.js +6 -1
- package/esm/browser/extension/icons.js.map +1 -1
- package/esm/browser/extension/web-extension-api.d.ts +120 -2
- package/esm/browser/extension/web-extensions.d.ts +3 -0
- package/esm/browser/extension/web-extensions.js +33 -1
- package/esm/browser/extension/web-extensions.js.map +1 -1
- package/esm/browser/i18n/locales.d.ts +44 -1
- package/esm/browser/i18n/messages.d.ts +43 -0
- package/esm/browser/i18n/messages.js +99 -13
- package/esm/browser/i18n/messages.js.map +1 -1
- package/esm/browser/notices/notices-protocol.d.ts +3 -0
- package/esm/browser/notices/notices.d.ts +3 -0
- package/esm/browser/notices/notices.js +15 -0
- package/esm/browser/notices/notices.js.map +1 -1
- package/esm/browser/style/base.d.ts +1 -1
- package/esm/browser/style/base.js +6 -1
- package/esm/browser/style/base.js.map +1 -1
- package/esm/browser/style/conversation.d.ts +1 -1
- package/esm/browser/style/conversation.js +16 -1
- package/esm/browser/style/conversation.js.map +1 -1
- package/esm/browser/style/layout.d.ts +1 -1
- package/esm/browser/style/layout.js +53 -15
- package/esm/browser/style/layout.js.map +1 -1
- package/esm/browser/style/shell.d.ts +1 -1
- package/esm/browser/style/shell.js +1 -0
- package/esm/browser/style/shell.js.map +1 -1
- package/esm/browser/terminal/canvas-font.d.ts +10 -0
- package/esm/browser/terminal/canvas-font.js +34 -0
- package/esm/browser/terminal/canvas-font.js.map +1 -0
- package/esm/browser/terminal/terminal-links.d.ts +18 -0
- package/esm/browser/terminal/terminal-links.js +72 -0
- package/esm/browser/terminal/terminal-links.js.map +1 -0
- package/esm/browser/terminal/terminal-theme.d.ts +8 -0
- package/esm/browser/terminal/terminal-theme.js +65 -0
- package/esm/browser/terminal/terminal-theme.js.map +1 -0
- package/esm/browser/terminal/web-terminal.d.ts +74 -0
- package/esm/browser/terminal/web-terminal.js +300 -0
- package/esm/browser/terminal/web-terminal.js.map +1 -0
- package/esm/browser/terminal/xterm.d.ts +5 -0
- package/esm/browser/terminal/xterm.js +9 -0
- package/esm/browser/terminal/xterm.js.map +1 -0
- package/esm/browser/util/desktop.d.ts +43 -0
- package/esm/browser/util/desktop.js +55 -0
- package/esm/browser/util/desktop.js.map +1 -0
- package/esm/browser/util/file-drag.d.ts +26 -0
- package/esm/browser/util/file-drag.js +73 -0
- package/esm/browser/util/file-drag.js.map +1 -0
- package/esm/browser/util/message.d.ts +1 -0
- package/esm/browser/util/message.js +7 -5
- package/esm/browser/util/message.js.map +1 -1
- package/esm/browser/util/terminal-text.d.ts +33 -0
- package/esm/browser/util/terminal-text.js +209 -0
- package/esm/browser/util/terminal-text.js.map +1 -0
- package/esm/browser/util/tool.js +16 -0
- package/esm/browser/util/tool.js.map +1 -1
- package/esm/browser/workbench-protocol.d.ts +7 -1
- package/esm/browser/workbench-protocol.js.map +1 -1
- package/esm/browser/workbench.d.ts +7 -1
- package/esm/browser/workbench.js +23 -0
- package/esm/browser/workbench.js.map +1 -1
- package/lib/browser/chat/chat-protocol.d.ts +4 -0
- package/lib/browser/chat/chat-protocol.d.ts.map +1 -1
- package/lib/browser/chat/chat-view.d.ts.map +1 -1
- package/lib/browser/chat/chat-view.js +4 -1
- package/lib/browser/chat/chat-view.js.map +1 -1
- package/lib/browser/chat/chat.d.ts +8 -1
- package/lib/browser/chat/chat.d.ts.map +1 -1
- package/lib/browser/chat/chat.js +34 -15
- package/lib/browser/chat/chat.js.map +1 -1
- package/lib/browser/chat/chat.spec.js +30 -0
- package/lib/browser/chat/chat.spec.js.map +1 -1
- package/lib/browser/chat/draft-model.d.ts +16 -0
- package/lib/browser/chat/draft-model.d.ts.map +1 -0
- package/lib/browser/chat/draft-model.js +90 -0
- package/lib/browser/chat/draft-model.js.map +1 -0
- package/lib/browser/chat/draft-model.spec.d.ts +2 -0
- package/lib/browser/chat/draft-model.spec.d.ts.map +1 -0
- package/lib/browser/chat/draft-model.spec.js +31 -0
- package/lib/browser/chat/draft-model.spec.js.map +1 -0
- package/lib/browser/components/account-menu.d.ts +3 -2
- package/lib/browser/components/account-menu.d.ts.map +1 -1
- package/lib/browser/components/account-menu.js +21 -16
- package/lib/browser/components/account-menu.js.map +1 -1
- package/lib/browser/components/app-view.d.ts.map +1 -1
- package/lib/browser/components/app-view.js +13 -4
- package/lib/browser/components/app-view.js.map +1 -1
- package/lib/browser/components/chat-list.d.ts +3 -1
- package/lib/browser/components/chat-list.d.ts.map +1 -1
- package/lib/browser/components/chat-list.js +6 -1
- package/lib/browser/components/chat-list.js.map +1 -1
- package/lib/browser/components/composer-box.d.ts +6 -0
- package/lib/browser/components/composer-box.d.ts.map +1 -1
- package/lib/browser/components/composer-box.js +51 -14
- package/lib/browser/components/composer-box.js.map +1 -1
- package/lib/browser/components/composer.d.ts.map +1 -1
- package/lib/browser/components/composer.js +1 -1
- package/lib/browser/components/composer.js.map +1 -1
- package/lib/browser/components/dialog-view.d.ts +4 -1
- package/lib/browser/components/dialog-view.d.ts.map +1 -1
- package/lib/browser/components/dialog-view.js +11 -7
- package/lib/browser/components/dialog-view.js.map +1 -1
- package/lib/browser/components/folder-picker.d.ts +37 -6
- package/lib/browser/components/folder-picker.d.ts.map +1 -1
- package/lib/browser/components/folder-picker.js +156 -33
- package/lib/browser/components/folder-picker.js.map +1 -1
- package/lib/browser/components/folder-picker.spec.js +15 -0
- package/lib/browser/components/folder-picker.spec.js.map +1 -1
- package/lib/browser/components/notice-center.d.ts.map +1 -1
- package/lib/browser/components/notice-center.js +3 -1
- package/lib/browser/components/notice-center.js.map +1 -1
- package/lib/browser/components/plus-menu.spec.d.ts +2 -0
- package/lib/browser/components/plus-menu.spec.d.ts.map +1 -0
- package/lib/browser/components/plus-menu.spec.js +18 -0
- package/lib/browser/components/plus-menu.spec.js.map +1 -0
- package/lib/browser/components/remote-hosts.d.ts +8 -0
- package/lib/browser/components/remote-hosts.d.ts.map +1 -0
- package/lib/browser/components/remote-hosts.js +130 -0
- package/lib/browser/components/remote-hosts.js.map +1 -0
- package/lib/browser/components/remote-prompts.d.ts +8 -0
- package/lib/browser/components/remote-prompts.d.ts.map +1 -0
- package/lib/browser/components/remote-prompts.js +108 -0
- package/lib/browser/components/remote-prompts.js.map +1 -0
- package/lib/browser/components/session-view.d.ts.map +1 -1
- package/lib/browser/components/session-view.js +48 -4
- package/lib/browser/components/session-view.js.map +1 -1
- package/lib/browser/components/settings-view.d.ts.map +1 -1
- package/lib/browser/components/settings-view.js +7 -0
- package/lib/browser/components/settings-view.js.map +1 -1
- package/lib/browser/components/terminal-art.d.ts +8 -0
- package/lib/browser/components/terminal-art.d.ts.map +1 -0
- package/lib/browser/components/terminal-art.js +48 -0
- package/lib/browser/components/terminal-art.js.map +1 -0
- package/lib/browser/components/tool-view.js +1 -1
- package/lib/browser/components/tool-view.js.map +1 -1
- package/lib/browser/components/web-composer.d.ts +4 -1
- package/lib/browser/components/web-composer.d.ts.map +1 -1
- package/lib/browser/components/web-composer.js +2 -2
- package/lib/browser/components/web-composer.js.map +1 -1
- package/lib/browser/components/working-directory.js +1 -1
- package/lib/browser/components/working-directory.js.map +1 -1
- package/lib/browser/extension/icons.d.ts.map +1 -1
- package/lib/browser/extension/icons.js +5 -0
- package/lib/browser/extension/icons.js.map +1 -1
- package/lib/browser/extension/web-extension-api.d.ts +120 -2
- package/lib/browser/extension/web-extension-api.d.ts.map +1 -1
- package/lib/browser/extension/web-extensions.d.ts +3 -0
- package/lib/browser/extension/web-extensions.d.ts.map +1 -1
- package/lib/browser/extension/web-extensions.js +33 -1
- package/lib/browser/extension/web-extensions.js.map +1 -1
- package/lib/browser/i18n/locales.d.ts +44 -1
- package/lib/browser/i18n/locales.d.ts.map +1 -1
- package/lib/browser/i18n/messages.d.ts +43 -0
- package/lib/browser/i18n/messages.d.ts.map +1 -1
- package/lib/browser/i18n/messages.js +99 -13
- package/lib/browser/i18n/messages.js.map +1 -1
- package/lib/browser/notices/notices-protocol.d.ts +3 -0
- package/lib/browser/notices/notices-protocol.d.ts.map +1 -1
- package/lib/browser/notices/notices.d.ts +3 -0
- package/lib/browser/notices/notices.d.ts.map +1 -1
- package/lib/browser/notices/notices.js +15 -0
- package/lib/browser/notices/notices.js.map +1 -1
- package/lib/browser/notices/notices.spec.js +8 -3
- package/lib/browser/notices/notices.spec.js.map +1 -1
- package/lib/browser/style/base.d.ts +1 -1
- package/lib/browser/style/base.d.ts.map +1 -1
- package/lib/browser/style/base.js +6 -1
- package/lib/browser/style/base.js.map +1 -1
- package/lib/browser/style/conversation.d.ts +1 -1
- package/lib/browser/style/conversation.d.ts.map +1 -1
- package/lib/browser/style/conversation.js +16 -1
- package/lib/browser/style/conversation.js.map +1 -1
- package/lib/browser/style/layout.d.ts +1 -1
- package/lib/browser/style/layout.d.ts.map +1 -1
- package/lib/browser/style/layout.js +53 -15
- package/lib/browser/style/layout.js.map +1 -1
- package/lib/browser/style/shell.d.ts +1 -1
- package/lib/browser/style/shell.d.ts.map +1 -1
- package/lib/browser/style/shell.js +1 -0
- package/lib/browser/style/shell.js.map +1 -1
- package/lib/browser/terminal/canvas-font.d.ts +11 -0
- package/lib/browser/terminal/canvas-font.d.ts.map +1 -0
- package/lib/browser/terminal/canvas-font.js +37 -0
- package/lib/browser/terminal/canvas-font.js.map +1 -0
- package/lib/browser/terminal/canvas-font.spec.d.ts +2 -0
- package/lib/browser/terminal/canvas-font.spec.d.ts.map +1 -0
- package/lib/browser/terminal/canvas-font.spec.js +32 -0
- package/lib/browser/terminal/canvas-font.spec.js.map +1 -0
- package/lib/browser/terminal/terminal-links.d.ts +19 -0
- package/lib/browser/terminal/terminal-links.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-links.js +75 -0
- package/lib/browser/terminal/terminal-links.js.map +1 -0
- package/lib/browser/terminal/terminal-links.spec.d.ts +2 -0
- package/lib/browser/terminal/terminal-links.spec.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-links.spec.js +30 -0
- package/lib/browser/terminal/terminal-links.spec.js.map +1 -0
- package/lib/browser/terminal/terminal-theme.d.ts +9 -0
- package/lib/browser/terminal/terminal-theme.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-theme.js +68 -0
- package/lib/browser/terminal/terminal-theme.js.map +1 -0
- package/lib/browser/terminal/terminal-theme.spec.d.ts +2 -0
- package/lib/browser/terminal/terminal-theme.spec.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-theme.spec.js +24 -0
- package/lib/browser/terminal/terminal-theme.spec.js.map +1 -0
- package/lib/browser/terminal/web-terminal.d.ts +75 -0
- package/lib/browser/terminal/web-terminal.d.ts.map +1 -0
- package/lib/browser/terminal/web-terminal.js +304 -0
- package/lib/browser/terminal/web-terminal.js.map +1 -0
- package/lib/browser/terminal/xterm.d.ts +6 -0
- package/lib/browser/terminal/xterm.d.ts.map +1 -0
- package/lib/browser/terminal/xterm.js +16 -0
- package/lib/browser/terminal/xterm.js.map +1 -0
- package/lib/browser/util/desktop.d.ts +44 -0
- package/lib/browser/util/desktop.d.ts.map +1 -0
- package/lib/browser/util/desktop.js +58 -0
- package/lib/browser/util/desktop.js.map +1 -0
- package/lib/browser/util/desktop.spec.d.ts +2 -0
- package/lib/browser/util/desktop.spec.d.ts.map +1 -0
- package/lib/browser/util/desktop.spec.js +67 -0
- package/lib/browser/util/desktop.spec.js.map +1 -0
- package/lib/browser/util/file-drag.d.ts +27 -0
- package/lib/browser/util/file-drag.d.ts.map +1 -0
- package/lib/browser/util/file-drag.js +76 -0
- package/lib/browser/util/file-drag.js.map +1 -0
- package/lib/browser/util/file-drag.spec.d.ts +2 -0
- package/lib/browser/util/file-drag.spec.d.ts.map +1 -0
- package/lib/browser/util/file-drag.spec.js +46 -0
- package/lib/browser/util/file-drag.spec.js.map +1 -0
- package/lib/browser/util/message.d.ts +1 -0
- package/lib/browser/util/message.d.ts.map +1 -1
- package/lib/browser/util/message.js +7 -5
- package/lib/browser/util/message.js.map +1 -1
- package/lib/browser/util/message.spec.js +2 -0
- package/lib/browser/util/message.spec.js.map +1 -1
- package/lib/browser/util/terminal-text.d.ts +34 -0
- package/lib/browser/util/terminal-text.d.ts.map +1 -0
- package/lib/browser/util/terminal-text.js +212 -0
- package/lib/browser/util/terminal-text.js.map +1 -0
- package/lib/browser/util/terminal-text.spec.d.ts +2 -0
- package/lib/browser/util/terminal-text.spec.d.ts.map +1 -0
- package/lib/browser/util/terminal-text.spec.js +54 -0
- package/lib/browser/util/terminal-text.spec.js.map +1 -0
- package/lib/browser/util/tool.d.ts.map +1 -1
- package/lib/browser/util/tool.js +16 -0
- package/lib/browser/util/tool.js.map +1 -1
- package/lib/browser/util/tool.spec.js +12 -0
- package/lib/browser/util/tool.spec.js.map +1 -1
- package/lib/browser/workbench-protocol.d.ts +7 -1
- package/lib/browser/workbench-protocol.d.ts.map +1 -1
- package/lib/browser/workbench-protocol.js.map +1 -1
- package/lib/browser/workbench.d.ts +7 -1
- package/lib/browser/workbench.d.ts.map +1 -1
- package/lib/browser/workbench.js +23 -0
- package/lib/browser/workbench.js.map +1 -1
- package/package.json +11 -7
- package/src/browser/chat/chat-protocol.ts +4 -0
- package/src/browser/chat/chat-view.tsx +5 -1
- package/src/browser/chat/chat.spec.ts +31 -0
- package/src/browser/chat/chat.ts +36 -14
- package/src/browser/chat/draft-model.spec.tsx +31 -0
- package/src/browser/chat/draft-model.tsx +63 -0
- package/src/browser/components/account-menu.tsx +23 -20
- package/src/browser/components/app-view.tsx +12 -3
- package/src/browser/components/chat-list.tsx +9 -2
- package/src/browser/components/composer-box.tsx +52 -6
- package/src/browser/components/composer.tsx +1 -0
- package/src/browser/components/dialog-view.tsx +16 -7
- package/src/browser/components/folder-picker.spec.ts +17 -0
- package/src/browser/components/folder-picker.tsx +221 -39
- package/src/browser/components/notice-center.tsx +7 -1
- package/src/browser/components/plus-menu.spec.ts +16 -0
- package/src/browser/components/remote-hosts.tsx +149 -0
- package/src/browser/components/remote-prompts.tsx +111 -0
- package/src/browser/components/session-view.tsx +65 -4
- package/src/browser/components/settings-view.tsx +8 -0
- package/src/browser/components/terminal-art.tsx +21 -0
- package/src/browser/components/tool-view.tsx +1 -1
- package/src/browser/components/web-composer.tsx +18 -3
- package/src/browser/components/working-directory.tsx +1 -1
- package/src/browser/extension/icons.ts +10 -0
- package/src/browser/extension/web-extension-api.ts +134 -2
- package/src/browser/extension/web-extensions.ts +47 -2
- package/src/browser/i18n/messages.ts +99 -13
- package/src/browser/notices/notices-protocol.ts +3 -0
- package/src/browser/notices/notices.spec.ts +9 -3
- package/src/browser/notices/notices.ts +17 -1
- package/src/browser/style/base.ts +6 -1
- package/src/browser/style/conversation.ts +16 -1
- package/src/browser/style/layout.ts +53 -15
- package/src/browser/style/shell.ts +1 -0
- package/src/browser/terminal/canvas-font.spec.ts +29 -0
- package/src/browser/terminal/canvas-font.ts +32 -0
- package/src/browser/terminal/terminal-links.spec.ts +32 -0
- package/src/browser/terminal/terminal-links.ts +75 -0
- package/src/browser/terminal/terminal-theme.spec.ts +23 -0
- package/src/browser/terminal/terminal-theme.ts +66 -0
- package/src/browser/terminal/web-terminal.ts +330 -0
- package/src/browser/terminal/xterm.ts +10 -0
- package/src/browser/util/desktop.spec.ts +68 -0
- package/src/browser/util/desktop.ts +96 -0
- package/src/browser/util/file-drag.spec.ts +48 -0
- package/src/browser/util/file-drag.ts +78 -0
- package/src/browser/util/message.spec.ts +2 -0
- package/src/browser/util/message.ts +7 -4
- package/src/browser/util/terminal-text.spec.ts +60 -0
- package/src/browser/util/terminal-text.ts +223 -0
- package/src/browser/util/tool.spec.ts +12 -0
- package/src/browser/util/tool.ts +17 -0
- package/src/browser/workbench-protocol.ts +8 -0
- package/src/browser/workbench.ts +29 -0
package/src/browser/chat/chat.ts
CHANGED
|
@@ -10,8 +10,12 @@ import { type ChatState, TaChat } from './chat-protocol';
|
|
|
10
10
|
|
|
11
11
|
/** The file of the conversation shown last, in any browser: the page opens it again. */
|
|
12
12
|
const FILE: StateSpec<string> = { key: 'chat.file', tier: 'user', initial: '', parse: StateUtil.string };
|
|
13
|
+
/** That conversation is of a package's group: the page shows it again only while it is open, as its package opens it with a role of its own. */
|
|
14
|
+
const GROUPED: StateSpec<boolean> = { key: 'chat.grouped', tier: 'user', initial: false, parse: StateUtil.boolean };
|
|
13
15
|
/** The directory of the last new conversation. */
|
|
14
16
|
const CWD: StateSpec<string> = { key: 'chat.cwd', tier: 'user', initial: '', parse: StateUtil.string };
|
|
17
|
+
/** The model the user picked for the last new conversation. */
|
|
18
|
+
const MODEL: StateSpec<string> = { key: 'chat.model', tier: 'user', initial: '', parse: StateUtil.string };
|
|
15
19
|
/** A file in a directory of packages: `extensions/<name>/...`, of the user or a project. */
|
|
16
20
|
const PACKAGE_FILE = /(^|[\\/])extensions[\\/][^\\/]+[\\/]/;
|
|
17
21
|
|
|
@@ -62,6 +66,8 @@ export class TaChatImpl implements TaChat {
|
|
|
62
66
|
protected watching = false;
|
|
63
67
|
/** A web extension changed packages during a run: its end loads them again. */
|
|
64
68
|
protected reloadPending = false;
|
|
69
|
+
/** The conversation asked for last: of quick switches, only the last is shown. */
|
|
70
|
+
protected wanted?: string;
|
|
65
71
|
|
|
66
72
|
get state(): ChatState {
|
|
67
73
|
return this.current;
|
|
@@ -86,7 +92,8 @@ export class TaChatImpl implements TaChat {
|
|
|
86
92
|
await this.workbench.refresh();
|
|
87
93
|
const file = (await this.persisted.load(FILE)) || undefined;
|
|
88
94
|
const open = file && this.workbench.state.sessions.find(session => session.sessionFile === file);
|
|
89
|
-
const
|
|
95
|
+
const reopen = file && !(await this.persisted.load(GROUPED));
|
|
96
|
+
const sessionId = open ? open.sessionId : reopen ? await this.open({ sessionFile: file }).catch(() => undefined) : undefined;
|
|
90
97
|
if (sessionId) {
|
|
91
98
|
await this.attach(sessionId);
|
|
92
99
|
} else {
|
|
@@ -107,18 +114,25 @@ export class TaChatImpl implements TaChat {
|
|
|
107
114
|
setDraftCwd(cwd: string): void {
|
|
108
115
|
if (this.current.draft) {
|
|
109
116
|
this.persisted.set(CWD, cwd);
|
|
110
|
-
this.update({ draft: { cwd } });
|
|
117
|
+
this.update({ draft: { ...this.current.draft, cwd } });
|
|
111
118
|
this.extensions.connect({ cwd });
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
setDraftModel(model: string | undefined): void {
|
|
123
|
+
if (this.current.draft && this.current.draft.model !== model) {
|
|
124
|
+
this.persisted.set(MODEL, model ?? '');
|
|
125
|
+
this.update({ draft: { ...this.current.draft, model } });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
115
129
|
submit(message: string, images?: ImageContent[]): Promise<void> {
|
|
116
130
|
const next = this.queue.then(async () => {
|
|
117
131
|
const { draft } = this.current;
|
|
118
132
|
if (!draft) {
|
|
119
133
|
return;
|
|
120
134
|
}
|
|
121
|
-
const sessionId = await this.open({ cwd: draft.cwd });
|
|
135
|
+
const sessionId = await this.open({ cwd: draft.cwd, model: draft.model });
|
|
122
136
|
if (draft.cwd) {
|
|
123
137
|
this.persisted.set(CWD, draft.cwd);
|
|
124
138
|
}
|
|
@@ -130,8 +144,9 @@ export class TaChatImpl implements TaChat {
|
|
|
130
144
|
}
|
|
131
145
|
|
|
132
146
|
show(sessionId: string): Promise<void> {
|
|
147
|
+
this.wanted = sessionId;
|
|
133
148
|
return this.enqueue(async () => {
|
|
134
|
-
if (this.current.store?.sessionId !== sessionId) {
|
|
149
|
+
if (this.wanted === sessionId && this.current.store?.sessionId !== sessionId) {
|
|
135
150
|
await this.attach(sessionId);
|
|
136
151
|
}
|
|
137
152
|
});
|
|
@@ -179,18 +194,24 @@ export class TaChatImpl implements TaChat {
|
|
|
179
194
|
}
|
|
180
195
|
|
|
181
196
|
/** Follows a session, running the web tools it asks for. */
|
|
197
|
+
/** The conversation shown stays until the next one is loaded, so that switching does not flash an empty page. */
|
|
182
198
|
protected async attach(sessionId: string): Promise<void> {
|
|
183
|
-
this.leave();
|
|
184
199
|
const store = this.workbench.createStore(sessionId, { clientTools: this.extensions.state.tools.map(tool => this.clientTool(tool.name)) });
|
|
185
|
-
|
|
200
|
+
try {
|
|
201
|
+
await store.attach();
|
|
202
|
+
// The list has the sessions opened since, as those of agents: web extensions get the directory of this one.
|
|
203
|
+
await this.workbench.refresh();
|
|
204
|
+
} catch (error) {
|
|
205
|
+
store.detach().catch(() => undefined);
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
this.leave();
|
|
186
209
|
this.seenEdits.clear();
|
|
187
210
|
for (const id of Object.keys(store.state.tools)) {
|
|
188
211
|
this.seenEdits.add(id);
|
|
189
212
|
}
|
|
190
|
-
// The list has the sessions opened since, as those of agents: web extensions get the directory of this one.
|
|
191
|
-
await this.workbench.refresh();
|
|
192
213
|
this.follow(store);
|
|
193
|
-
this.remember(this.workbench.state.sessions.find(session => session.sessionId === sessionId)
|
|
214
|
+
this.remember(this.workbench.state.sessions.find(session => session.sessionId === sessionId));
|
|
194
215
|
}
|
|
195
216
|
|
|
196
217
|
/** The newest other open session, or a new conversation. */
|
|
@@ -207,7 +228,7 @@ export class TaChatImpl implements TaChat {
|
|
|
207
228
|
|
|
208
229
|
protected showDraft(): void {
|
|
209
230
|
const cwd = this.persisted.get(CWD) || this.workbench.state.server?.cwd || '';
|
|
210
|
-
this.update({ draft: { cwd }, error: undefined });
|
|
231
|
+
this.update({ draft: { cwd, model: this.persisted.get(MODEL) || undefined }, error: undefined });
|
|
211
232
|
this.extensions.connect(cwd ? { cwd } : undefined);
|
|
212
233
|
}
|
|
213
234
|
|
|
@@ -261,7 +282,7 @@ export class TaChatImpl implements TaChat {
|
|
|
261
282
|
|
|
262
283
|
protected onEvent(event: TaEvent): void {
|
|
263
284
|
if (event.type === 'session_replaced') {
|
|
264
|
-
this.remember(event.info
|
|
285
|
+
this.remember(event.info);
|
|
265
286
|
}
|
|
266
287
|
this.extensions.emit(event);
|
|
267
288
|
}
|
|
@@ -320,9 +341,10 @@ export class TaChatImpl implements TaChat {
|
|
|
320
341
|
await this.attach(await this.open({ sessionFile, cwd }));
|
|
321
342
|
}
|
|
322
343
|
|
|
323
|
-
protected remember(
|
|
324
|
-
if (sessionFile) {
|
|
325
|
-
this.persisted.set(FILE, sessionFile);
|
|
344
|
+
protected remember(info: { sessionFile?: string; group?: string } | undefined): void {
|
|
345
|
+
if (info?.sessionFile) {
|
|
346
|
+
this.persisted.set(FILE, info.sessionFile);
|
|
347
|
+
this.persisted.set(GROUPED, !!info.group);
|
|
326
348
|
}
|
|
327
349
|
}
|
|
328
350
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import type { TaSettingsSnapshot } from '@celljs/ta-protocol';
|
|
3
|
+
import { DraftModelUtil } from './draft-model';
|
|
4
|
+
|
|
5
|
+
const t = ((id: string) => id) as never;
|
|
6
|
+
|
|
7
|
+
function snapshot(fields: Partial<TaSettingsSnapshot>): TaSettingsSnapshot {
|
|
8
|
+
return { user: {}, project: {}, cwd: '/w', userFile: 'u', projectFile: 'p', projectTrusted: true, models: [], ...fields };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('DraftModelUtil', () => {
|
|
12
|
+
it('lists the models by name, the picked one checked, else the one of the settings', () => {
|
|
13
|
+
const picked: string[] = [];
|
|
14
|
+
const settings = snapshot({
|
|
15
|
+
models: ['anthropic/claude-x', 'codebuddy-code/default', 'codebuddy-code/opus'],
|
|
16
|
+
modelNames: { 'codebuddy-code/default': 'CodeBuddy Code', 'codebuddy-code/opus': 'Opus' },
|
|
17
|
+
user: { model: 'anthropic/claude-x' },
|
|
18
|
+
});
|
|
19
|
+
const action = DraftModelUtil.action(settings, undefined, model => picked.push(model), t);
|
|
20
|
+
expect(action.detail).toBe('claude-x');
|
|
21
|
+
expect(action.items?.map(item => [item.label, item.detail, !!item.checked])).toEqual([
|
|
22
|
+
['claude-x', 'anthropic', true],
|
|
23
|
+
['CodeBuddy Code', 'codebuddy-code', false],
|
|
24
|
+
['Opus', 'codebuddy-code', false],
|
|
25
|
+
]);
|
|
26
|
+
action.items?.[2].run?.();
|
|
27
|
+
expect(picked).toEqual(['codebuddy-code/opus']);
|
|
28
|
+
expect(DraftModelUtil.action(settings, 'codebuddy-code/default', () => undefined, t).detail).toBe('CodeBuddy Code');
|
|
29
|
+
expect(DraftModelUtil.settingsModel(snapshot({ user: { model: 'a/b' }, project: { model: 'c/d' } }))).toBe('c/d');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { TaSettingsServer, TaSettingsSnapshot } from '@celljs/ta-protocol';
|
|
2
|
+
import { Box } from 'lucide-react';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import type { ComposerAction } from '../components/composer-box';
|
|
5
|
+
import { Icon } from '../components/icon';
|
|
6
|
+
import type { Translate } from '../i18n/use-t';
|
|
7
|
+
import type { ChatDraft } from './chat-protocol';
|
|
8
|
+
|
|
9
|
+
export namespace DraftModelUtil {
|
|
10
|
+
/** The model the settings start a conversation in `snapshot`'s directory on: the project's, else the user's. */
|
|
11
|
+
export function settingsModel(snapshot: TaSettingsSnapshot): string | undefined {
|
|
12
|
+
const model = snapshot.project.model ?? snapshot.user.model;
|
|
13
|
+
return typeof model === 'string' && model ? model : undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The model entry of the + of a new conversation: the models with credentials, the picked one checked. */
|
|
17
|
+
export function action(snapshot: TaSettingsSnapshot, picked: string | undefined, pick: (model: string) => void, t: Translate): ComposerAction {
|
|
18
|
+
const current = picked ?? settingsModel(snapshot);
|
|
19
|
+
const name = (ref: string) => snapshot.modelNames?.[ref] ?? ref.slice(ref.indexOf('/') + 1);
|
|
20
|
+
return {
|
|
21
|
+
key: 'model',
|
|
22
|
+
label: t('ta.session.model'),
|
|
23
|
+
icon: <Icon icon={Box} size={16} />,
|
|
24
|
+
detail: current ? name(current) : t('ta.session.noModel'),
|
|
25
|
+
items: snapshot.models.map(ref => ({
|
|
26
|
+
key: ref,
|
|
27
|
+
label: name(ref),
|
|
28
|
+
detail: ref.slice(0, ref.indexOf('/')),
|
|
29
|
+
checked: ref === current,
|
|
30
|
+
run: () => pick(ref),
|
|
31
|
+
})),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The model of a new conversation in its + menu: the models of the settings of its directory, those
|
|
38
|
+
* of pi packages included. A model picked before that is gone is forgotten.
|
|
39
|
+
*/
|
|
40
|
+
export function useDraftModel(
|
|
41
|
+
settings: TaSettingsServer,
|
|
42
|
+
draft: ChatDraft,
|
|
43
|
+
pick: (model: string | undefined) => void,
|
|
44
|
+
t: Translate,
|
|
45
|
+
): ComposerAction | undefined {
|
|
46
|
+
const [snapshot, setSnapshot] = React.useState<TaSettingsSnapshot>();
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
let live = true;
|
|
49
|
+
settings.getSettings(draft.cwd || undefined).then(
|
|
50
|
+
next => live && setSnapshot(next),
|
|
51
|
+
() => undefined,
|
|
52
|
+
);
|
|
53
|
+
return () => {
|
|
54
|
+
live = false;
|
|
55
|
+
};
|
|
56
|
+
}, [settings, draft.cwd]);
|
|
57
|
+
React.useEffect(() => {
|
|
58
|
+
if (snapshot && draft.model && !snapshot.models.includes(draft.model)) {
|
|
59
|
+
pick(undefined);
|
|
60
|
+
}
|
|
61
|
+
}, [snapshot, draft.model, pick]);
|
|
62
|
+
return snapshot?.models.length ? DraftModelUtil.action(snapshot, draft.model, pick, t) : undefined;
|
|
63
|
+
}
|
|
@@ -2,7 +2,6 @@ import { Download, LogOut, User } from 'lucide-react';
|
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { usePwa, useWorkbench } from '../hooks';
|
|
4
4
|
import { useT } from '../i18n/use-t';
|
|
5
|
-
import { MessageUtil } from '../util/message';
|
|
6
5
|
import { Icon } from './icon';
|
|
7
6
|
|
|
8
7
|
/** Closes on Escape and on a click outside; the focus goes back to the button. */
|
|
@@ -31,9 +30,16 @@ export function useDismiss(open: boolean, close: () => void, root: React.RefObje
|
|
|
31
30
|
}, [open, close, root, button]);
|
|
32
31
|
}
|
|
33
32
|
|
|
33
|
+
/** The round figure of the account: the first letter of the user, or a figure before Ta has said who. */
|
|
34
|
+
function AccountFigure({ user, className }: { user?: string; className: string }) {
|
|
35
|
+
const letter = user?.trim().charAt(0).toUpperCase();
|
|
36
|
+
return <span className={className}>{letter || <Icon icon={User} size={16} />}</span>;
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
/**
|
|
35
|
-
* The account at the foot of the navigation:
|
|
36
|
-
*
|
|
40
|
+
* The account at the foot of the navigation: the user and the machine whose Ta this page is signed in
|
|
41
|
+
* to, and whether the page is connected to it (a dot on the figure while it is not). How the page looks
|
|
42
|
+
* is under the gear above it.
|
|
37
43
|
*/
|
|
38
44
|
export function AccountMenu() {
|
|
39
45
|
const t = useT();
|
|
@@ -47,6 +53,7 @@ export function AccountMenu() {
|
|
|
47
53
|
useDismiss(open, close, root, button);
|
|
48
54
|
const online = status === 'online';
|
|
49
55
|
const state = online ? t('ta.online') : t('ta.reconnecting');
|
|
56
|
+
const name = server?.user ?? t('ta.account');
|
|
50
57
|
|
|
51
58
|
return (
|
|
52
59
|
<div className="ta-account" ref={root}>
|
|
@@ -54,34 +61,30 @@ export function AccountMenu() {
|
|
|
54
61
|
ref={button}
|
|
55
62
|
type="button"
|
|
56
63
|
className={`ta-account-button ${open ? 'ta-on' : ''}`}
|
|
57
|
-
title={`${
|
|
58
|
-
aria-label={t('ta.account')}
|
|
64
|
+
title={`${name} · ${state}`}
|
|
65
|
+
aria-label={`${t('ta.account')} · ${state}`}
|
|
59
66
|
aria-haspopup="menu"
|
|
60
67
|
aria-expanded={open}
|
|
61
68
|
aria-controls={open ? id : undefined}
|
|
62
69
|
onClick={() => setOpen(!open)}
|
|
63
70
|
>
|
|
64
|
-
<
|
|
65
|
-
<span className=
|
|
71
|
+
<AccountFigure user={server?.user} className="ta-account-figure" />
|
|
72
|
+
{!online && <span className="ta-account-dot" />}
|
|
66
73
|
</button>
|
|
67
74
|
{open && (
|
|
68
75
|
<div className="ta-account-menu" id={id} role="menu" aria-label={t('ta.account')}>
|
|
69
76
|
<div className="ta-account-head">
|
|
70
|
-
<
|
|
71
|
-
<Icon icon={User} size={16} />
|
|
72
|
-
</span>
|
|
77
|
+
<AccountFigure user={server?.user} className="ta-account-figure ta-large" />
|
|
73
78
|
<div className="ta-account-text">
|
|
74
|
-
<span className="ta-account-title">
|
|
75
|
-
|
|
76
|
-
<span className={`ta-account-state ${online ? 'ta-ok' : 'ta-warn'}`}>
|
|
77
|
-
<span className="ta-dot" /> {state}
|
|
78
|
-
</span>
|
|
79
|
-
</span>
|
|
80
|
-
<span className="ta-account-sub ta-ellipsis" title={server?.cwd}>
|
|
81
|
-
{server ? `v${server.version} · ${MessageUtil.shortPath(server.cwd)}` : '…'}
|
|
82
|
-
</span>
|
|
79
|
+
<span className="ta-account-title ta-ellipsis">{name}</span>
|
|
80
|
+
{server && <span className="ta-account-sub ta-ellipsis">{server.host}</span>}
|
|
83
81
|
</div>
|
|
84
82
|
</div>
|
|
83
|
+
<div className="ta-account-service" title={server ? `Ta v${server.version}` : undefined}>
|
|
84
|
+
<span className={`ta-account-state ${online ? 'ta-ok' : 'ta-warn'}`} />
|
|
85
|
+
<span className="ta-account-service-name">{t('ta.account.service')}</span>
|
|
86
|
+
<span className="ta-account-service-state">{server && online ? `${state} · v${server.version}` : state}</span>
|
|
87
|
+
</div>
|
|
85
88
|
{installable && !standalone && (
|
|
86
89
|
<button
|
|
87
90
|
type="button"
|
|
@@ -99,7 +102,7 @@ export function AccountMenu() {
|
|
|
99
102
|
<button
|
|
100
103
|
type="button"
|
|
101
104
|
role="menuitem"
|
|
102
|
-
className="ta-account-item
|
|
105
|
+
className="ta-account-item"
|
|
103
106
|
onClick={() => {
|
|
104
107
|
close();
|
|
105
108
|
workbench.signOut();
|
|
@@ -24,6 +24,7 @@ import { LinkDialog } from './link-dialog';
|
|
|
24
24
|
import { ManageMenu } from './manage-menu';
|
|
25
25
|
import { FileUrls } from './media';
|
|
26
26
|
import { NoticeCenter } from './notice-center';
|
|
27
|
+
import { RemotePrompts } from './remote-prompts';
|
|
27
28
|
import { type SettingsSection, SettingsView } from './settings-view';
|
|
28
29
|
import { paneStyle, Sash, usePane } from './split-pane';
|
|
29
30
|
import { WorkbenchToggle, WorkbenchToggleContext } from './workbench-toggle';
|
|
@@ -287,7 +288,7 @@ export function AppView() {
|
|
|
287
288
|
const closeSettings = React.useCallback(() => setSettings(undefined), []);
|
|
288
289
|
const [rightOpen, toggleRight, revealRight] = useRight();
|
|
289
290
|
const [maximized, setMaximized] = React.useState(false);
|
|
290
|
-
const [{ theme }, settingsStore] = useSettings();
|
|
291
|
+
const [{ theme, language }, settingsStore] = useSettings();
|
|
291
292
|
const sidebar = usePane(SIDEBAR);
|
|
292
293
|
const editor = usePane(EDITOR);
|
|
293
294
|
const online = workbench.status === 'online';
|
|
@@ -296,6 +297,12 @@ export function AppView() {
|
|
|
296
297
|
const openLink = useOpenLink(undefined);
|
|
297
298
|
useDocument();
|
|
298
299
|
useRefresh(online);
|
|
300
|
+
// What the server and its packages say (errors, questions) is in the language of the page.
|
|
301
|
+
React.useEffect(() => {
|
|
302
|
+
if (online) {
|
|
303
|
+
bench.settings.setLanguage(language).catch(() => undefined);
|
|
304
|
+
}
|
|
305
|
+
}, [online, language, bench]);
|
|
299
306
|
React.useEffect(() => {
|
|
300
307
|
void bench.start();
|
|
301
308
|
}, [bench]);
|
|
@@ -333,14 +340,15 @@ export function AppView() {
|
|
|
333
340
|
// biome-ignore lint/correctness/useExhaustiveDependencies: follows what the main area shows
|
|
334
341
|
React.useEffect(() => setSettings(undefined), [page, shownSession]);
|
|
335
342
|
// Another conversation shown that no sidebar view lists (not that of an agent, which is in a group), by a page,
|
|
336
|
-
// a link or a fork: the sidebar turns to the chats, where it is selected, to be found again. Not the one the page opens with
|
|
343
|
+
// a link or a fork: the sidebar turns to the chats, where it is selected, to be found again. Not the one the page opens with,
|
|
344
|
+
// nor one the list does not have yet: it may be an agent's.
|
|
337
345
|
// biome-ignore lint/correctness/useExhaustiveDependencies: follows the conversation shown
|
|
338
346
|
React.useEffect(() => {
|
|
339
347
|
if (!shownSession || !started.current) {
|
|
340
348
|
return;
|
|
341
349
|
}
|
|
342
350
|
const info = workbench.sessions.find(session => session.sessionId === shownSession);
|
|
343
|
-
if (!info
|
|
351
|
+
if (info && !info.group) {
|
|
344
352
|
setSideId('');
|
|
345
353
|
}
|
|
346
354
|
}, [shownSession]);
|
|
@@ -540,6 +548,7 @@ export function AppView() {
|
|
|
540
548
|
)}
|
|
541
549
|
<NoticeCenter />
|
|
542
550
|
<LinkDialog />
|
|
551
|
+
<RemotePrompts />
|
|
543
552
|
</div>
|
|
544
553
|
</WorkbenchToggleContext.Provider>
|
|
545
554
|
</SessionAvatars.Provider>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { TaLocation, type TaSessionInfo } from '@celljs/ta-protocol';
|
|
2
2
|
import { Plus, Search, Trash2, X } from 'lucide-react';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import type { TaChat } from '../chat/chat-protocol';
|
|
@@ -16,6 +16,8 @@ export interface ChatSource {
|
|
|
16
16
|
title: string;
|
|
17
17
|
/** The directory a new chat of this source opens in; none for a package. */
|
|
18
18
|
cwd?: string;
|
|
19
|
+
/** The host of a directory of another machine (TaLocation). */
|
|
20
|
+
host?: string;
|
|
19
21
|
count: number;
|
|
20
22
|
/** The letter of its avatar. */
|
|
21
23
|
initial: string;
|
|
@@ -66,6 +68,7 @@ export namespace ChatListUtil {
|
|
|
66
68
|
label: name,
|
|
67
69
|
title: session.group ?? session.cwd,
|
|
68
70
|
cwd: session.group ? undefined : session.cwd,
|
|
71
|
+
host: session.group ? undefined : TaLocation.host(session.cwd),
|
|
69
72
|
count: 1,
|
|
70
73
|
initial: initial(session.group ? name.replace(/^ta-/, '') : name),
|
|
71
74
|
tone: 0,
|
|
@@ -167,7 +170,10 @@ function ChatItem({ session, source, selected, now, onSelect, onClose }: ItemPro
|
|
|
167
170
|
<span className="ta-chat-time">{ChatListUtil.clock(session.updatedAt, now)}</span>
|
|
168
171
|
</span>
|
|
169
172
|
<span className="ta-chat-line">
|
|
170
|
-
<span className="ta-chat-sub">
|
|
173
|
+
<span className="ta-chat-sub">
|
|
174
|
+
{source.host && <span className="ta-host-tag">{source.host}</span>}
|
|
175
|
+
{source.label}
|
|
176
|
+
</span>
|
|
171
177
|
{session.isStreaming && <span className="ta-spinner" />}
|
|
172
178
|
</span>
|
|
173
179
|
</span>
|
|
@@ -244,6 +250,7 @@ export function ChatList({ tabs, sessions, selected, chat, onPicked }: ChatListP
|
|
|
244
250
|
onClick={() => setFilter(candidate === source ? '' : candidate.key)}
|
|
245
251
|
>
|
|
246
252
|
<span className={`ta-chip-dot ta-tone-${candidate.tone}`} />
|
|
253
|
+
{candidate.host && <span className="ta-host-tag">{candidate.host}</span>}
|
|
247
254
|
{candidate.label}
|
|
248
255
|
<span className="ta-chip-count">{candidate.count}</span>
|
|
249
256
|
</button>
|
|
@@ -2,6 +2,7 @@ import { ArrowUp, AtSign, Check, ChevronLeft, ChevronRight, ImagePlus, Plus, Sla
|
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { useT } from '../i18n/use-t';
|
|
4
4
|
import { type ComposerAttachment, type ComposerImage, ComposerUtil, IMAGE_TYPES } from '../util/composer';
|
|
5
|
+
import { FileDragUtil } from '../util/file-drag';
|
|
5
6
|
import { ComposerMenu, type ComposerMenuItem } from './composer-menu';
|
|
6
7
|
import { Icon, IconButton } from './icon';
|
|
7
8
|
|
|
@@ -59,6 +60,8 @@ export interface ComposerBoxProps {
|
|
|
59
60
|
/** Controls on the right, before the send button, as the microphone of the double. */
|
|
60
61
|
after?: React.ReactNode;
|
|
61
62
|
autoFocus?: boolean;
|
|
63
|
+
/** The directory of the conversation: files dragged in from it become `@` references relative to it. */
|
|
64
|
+
cwd?: string;
|
|
62
65
|
onError(error: unknown): void;
|
|
63
66
|
}
|
|
64
67
|
|
|
@@ -102,13 +105,31 @@ export function Attachments({ attachments, onRemove }: { attachments: ComposerAt
|
|
|
102
105
|
);
|
|
103
106
|
}
|
|
104
107
|
|
|
105
|
-
/**
|
|
108
|
+
/** A list of more entries than this gets a box that narrows it. */
|
|
109
|
+
const SEARCH_FROM = 8;
|
|
110
|
+
|
|
111
|
+
export namespace PlusMenuUtil {
|
|
112
|
+
/** The entries whose label or detail has every word of `query`, in any case. */
|
|
113
|
+
export function filter(items: ComposerAction[], query: string): ComposerAction[] {
|
|
114
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
115
|
+
return words.length ? items.filter(item => words.every(word => `${item.label} ${item.detail ?? ''}`.toLowerCase().includes(word))) : items;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The + on the left of the box: what is not typing, in one place. A list opens in place of the menu, with a box to narrow a long one. */
|
|
106
120
|
function PlusMenu({ sections, onClose }: { sections: ComposerAction[][]; onClose(): void }) {
|
|
121
|
+
const t = useT();
|
|
107
122
|
const [open, setOpen] = React.useState<ComposerAction>();
|
|
108
|
-
const
|
|
123
|
+
const [query, setQuery] = React.useState('');
|
|
124
|
+
const searchable = (open?.items?.length ?? 0) > SEARCH_FROM;
|
|
125
|
+
const shown = open?.items ? [searchable ? PlusMenuUtil.filter(open.items, query) : open.items] : sections;
|
|
126
|
+
const enter = (action: ComposerAction | undefined) => {
|
|
127
|
+
setQuery('');
|
|
128
|
+
setOpen(action);
|
|
129
|
+
};
|
|
109
130
|
const pick = (action: ComposerAction) => {
|
|
110
131
|
if (action.items) {
|
|
111
|
-
|
|
132
|
+
enter(action);
|
|
112
133
|
return;
|
|
113
134
|
}
|
|
114
135
|
onClose();
|
|
@@ -117,12 +138,30 @@ function PlusMenu({ sections, onClose }: { sections: ComposerAction[][]; onClose
|
|
|
117
138
|
return (
|
|
118
139
|
<div className="ta-plus-menu" role="menu" aria-label={open?.label}>
|
|
119
140
|
{open && (
|
|
120
|
-
<button type="button" className="ta-plus-back" onClick={() =>
|
|
141
|
+
<button type="button" className="ta-plus-back" onClick={() => enter(undefined)}>
|
|
121
142
|
<Icon icon={ChevronLeft} size={15} />
|
|
122
143
|
<span>{open.label}</span>
|
|
123
144
|
</button>
|
|
124
145
|
)}
|
|
146
|
+
{searchable && (
|
|
147
|
+
<input
|
|
148
|
+
className="ta-plus-search"
|
|
149
|
+
autoFocus
|
|
150
|
+
value={query}
|
|
151
|
+
placeholder={t('ta.composer.filter')}
|
|
152
|
+
aria-label={t('ta.composer.filter')}
|
|
153
|
+
onChange={event => setQuery(event.target.value)}
|
|
154
|
+
onKeyDown={event => {
|
|
155
|
+
const first = shown[0]?.[0];
|
|
156
|
+
if (event.key === 'Enter' && first && !event.nativeEvent.isComposing) {
|
|
157
|
+
event.preventDefault();
|
|
158
|
+
pick(first);
|
|
159
|
+
}
|
|
160
|
+
}}
|
|
161
|
+
/>
|
|
162
|
+
)}
|
|
125
163
|
<div className="ta-plus-items">
|
|
164
|
+
{searchable && !shown[0].length && <div className="ta-plus-empty">{t('ta.composer.noMatch')}</div>}
|
|
126
165
|
{shown.map((section, index) => (
|
|
127
166
|
<React.Fragment key={index}>
|
|
128
167
|
{index > 0 && <hr className="ta-plus-line" />}
|
|
@@ -169,6 +208,7 @@ export const ComposerBox = React.forwardRef<ComposerBoxHandle, ComposerBoxProps>
|
|
|
169
208
|
beside,
|
|
170
209
|
after,
|
|
171
210
|
autoFocus,
|
|
211
|
+
cwd,
|
|
172
212
|
onError,
|
|
173
213
|
} = props;
|
|
174
214
|
const t = useT();
|
|
@@ -361,6 +401,12 @@ export const ComposerBox = React.forwardRef<ComposerBoxHandle, ComposerBoxProps>
|
|
|
361
401
|
|
|
362
402
|
const onDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
|
363
403
|
setDragging(false);
|
|
404
|
+
const files = FileDragUtil.get(event.dataTransfer);
|
|
405
|
+
if (files) {
|
|
406
|
+
event.preventDefault();
|
|
407
|
+
edit(...ComposerUtil.insert(text, caret, `${FileDragUtil.references(files, cwd)} `));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
364
410
|
if (takesImages && event.dataTransfer.files.length) {
|
|
365
411
|
event.preventDefault();
|
|
366
412
|
addFiles(event.dataTransfer.files);
|
|
@@ -390,11 +436,11 @@ export const ComposerBox = React.forwardRef<ComposerBoxHandle, ComposerBoxProps>
|
|
|
390
436
|
<div className="ta-console-composer">
|
|
391
437
|
{(items.length > 0 || note) && <ComposerMenu id={menuId} items={items} selected={selected} note={note} onPick={pick} onHover={setSelected} />}
|
|
392
438
|
{above}
|
|
393
|
-
{/* biome-ignore lint/a11y/noStaticElementInteractions: dropping images is a shortcut; the + menu does the same */}
|
|
439
|
+
{/* biome-ignore lint/a11y/noStaticElementInteractions: dropping images and files is a shortcut; the + menu does the same */}
|
|
394
440
|
<div
|
|
395
441
|
className={`ta-console-box ${dragging ? 'ta-dragging' : ''}`}
|
|
396
442
|
onDragOver={event => {
|
|
397
|
-
if (takesImages && event.dataTransfer.types.includes('Files')) {
|
|
443
|
+
if (FileDragUtil.has(event.dataTransfer) || (takesImages && event.dataTransfer.types.includes('Files'))) {
|
|
398
444
|
event.preventDefault();
|
|
399
445
|
setDragging(true);
|
|
400
446
|
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { RpcExtensionUIRequest, RpcExtensionUIResponse } from '@celljs/ta-protocol';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { useT } from '../i18n/use-t';
|
|
4
|
+
import { TerminalText } from '../util/terminal-text';
|
|
4
5
|
|
|
5
6
|
type Respond = (response: RpcExtensionUIResponse) => void;
|
|
6
7
|
|
|
7
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* The dialog an extension opened, such as the permission gate's question; one at a time, oldest first. Its texts
|
|
10
|
+
* are written for pi's terminal: shown without its escapes, while an option goes back as it came.
|
|
11
|
+
*/
|
|
8
12
|
export function DialogView({ request, respond }: { request: RpcExtensionUIRequest; respond: Respond }) {
|
|
9
13
|
const t = useT();
|
|
10
|
-
const [value, setValue] = React.useState(() => ('prefill' in request ? (request.prefill ?? '') : ''));
|
|
14
|
+
const [value, setValue] = React.useState(() => ('prefill' in request ? TerminalText.plain(request.prefill ?? '') : ''));
|
|
11
15
|
const cancel = () => respond({ type: 'extension_ui_response', id: request.id, cancelled: true });
|
|
12
16
|
const answer = (response: Omit<RpcExtensionUIResponse, 'type' | 'id'>) =>
|
|
13
17
|
respond({ type: 'extension_ui_response', id: request.id, ...response } as RpcExtensionUIResponse);
|
|
@@ -16,7 +20,7 @@ export function DialogView({ request, respond }: { request: RpcExtensionUIReques
|
|
|
16
20
|
case 'confirm':
|
|
17
21
|
body = (
|
|
18
22
|
<>
|
|
19
|
-
<p className="ta-dialog-message">{request.message}</p>
|
|
23
|
+
<p className="ta-dialog-message">{TerminalText.plain(request.message)}</p>
|
|
20
24
|
<div className="ta-dialog-actions">
|
|
21
25
|
<button type="button" className="ta-button" onClick={() => answer({ confirmed: false })}>
|
|
22
26
|
{t('ta.no')}
|
|
@@ -33,7 +37,7 @@ export function DialogView({ request, respond }: { request: RpcExtensionUIReques
|
|
|
33
37
|
<div className="ta-dialog-options">
|
|
34
38
|
{request.options.map((option, index) => (
|
|
35
39
|
<button key={option} type="button" className="ta-option" autoFocus={index === 0} onClick={() => answer({ value: option })}>
|
|
36
|
-
{option}
|
|
40
|
+
{TerminalText.plain(option)}
|
|
37
41
|
</button>
|
|
38
42
|
))}
|
|
39
43
|
</div>
|
|
@@ -52,7 +56,7 @@ export function DialogView({ request, respond }: { request: RpcExtensionUIReques
|
|
|
52
56
|
className="ta-input"
|
|
53
57
|
autoFocus
|
|
54
58
|
value={value}
|
|
55
|
-
placeholder={request.placeholder}
|
|
59
|
+
placeholder={request.placeholder && TerminalText.plain(request.placeholder)}
|
|
56
60
|
onChange={event => setValue(event.target.value)}
|
|
57
61
|
/>
|
|
58
62
|
) : (
|
|
@@ -74,8 +78,13 @@ export function DialogView({ request, respond }: { request: RpcExtensionUIReques
|
|
|
74
78
|
return null;
|
|
75
79
|
}
|
|
76
80
|
return (
|
|
77
|
-
<div
|
|
78
|
-
|
|
81
|
+
<div
|
|
82
|
+
className="ta-dialog ta-glass"
|
|
83
|
+
role="dialog"
|
|
84
|
+
aria-label={TerminalText.plain(request.title)}
|
|
85
|
+
onKeyDown={event => event.key === 'Escape' && cancel()}
|
|
86
|
+
>
|
|
87
|
+
<div className="ta-dialog-title">{TerminalText.plain(request.title)}</div>
|
|
79
88
|
{body}
|
|
80
89
|
</div>
|
|
81
90
|
);
|
|
@@ -18,6 +18,23 @@ describe('FolderPickerUtil', () => {
|
|
|
18
18
|
expect(FolderPickerUtil.child('C:\\a', 'b')).toBe('C:\\a\\b');
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
it('goes up and down the directories of a host, and not above its root', () => {
|
|
22
|
+
expect(FolderPickerUtil.parent('ssh://dev/srv/app')).toBe('ssh://dev/srv');
|
|
23
|
+
expect(FolderPickerUtil.parent('ssh://dev/srv')).toBe('ssh://dev/');
|
|
24
|
+
expect(FolderPickerUtil.parent('ssh://dev/')).toBeUndefined();
|
|
25
|
+
expect(FolderPickerUtil.child('ssh://dev/', 'srv')).toBe('ssh://dev/srv');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('reads a path typed in as one of the machine shown', () => {
|
|
29
|
+
expect(FolderPickerUtil.typed(' /srv/app ', 'dev')).toBe('ssh://dev/srv/app');
|
|
30
|
+
expect(FolderPickerUtil.typed('srv', 'dev')).toBe('ssh://dev/srv');
|
|
31
|
+
expect(FolderPickerUtil.typed('ssh://prod/x', 'dev')).toBe('ssh://prod/x');
|
|
32
|
+
expect(FolderPickerUtil.typed('/work', undefined)).toBe('/work');
|
|
33
|
+
expect(FolderPickerUtil.localStart('ssh://dev/x', '/home/me')).toBe('/home/me');
|
|
34
|
+
expect(FolderPickerUtil.localStart('ssh://dev/x', 'ssh://dev/y')).toBe('/');
|
|
35
|
+
expect(FolderPickerUtil.localStart('/work', '/home/me')).toBe('/work');
|
|
36
|
+
});
|
|
37
|
+
|
|
21
38
|
it('offers folders only, without the hidden ones', () => {
|
|
22
39
|
expect(
|
|
23
40
|
FolderPickerUtil.folders([
|