@k2b/ui 0.26.0 → 0.28.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.md +1 -1
- package/dist/base.css +147 -2
- package/dist/browser/actions/ContextMenu.js +30 -4
- package/dist/browser/actions/ContextMenu.js.map +1 -1
- package/dist/browser/actions/Dropdown.js +184 -158
- package/dist/browser/actions/Dropdown.js.map +1 -1
- package/dist/browser/actions/GestureMenu.js +71 -138
- package/dist/browser/actions/GestureMenu.js.map +1 -1
- package/dist/browser/actions/Toolbar.js +379 -48
- package/dist/browser/actions/Toolbar.js.map +1 -1
- package/dist/browser/actions/index.js.map +1 -1
- package/dist/browser/actions/menu-rows.js +15 -0
- package/dist/browser/actions/menu-rows.js.map +1 -0
- package/dist/browser/actions/menu-sheet.js +154 -0
- package/dist/browser/actions/menu-sheet.js.map +1 -0
- package/dist/browser/actions/toolbar-messages.js +20 -0
- package/dist/browser/actions/toolbar-messages.js.map +1 -0
- package/dist/browser/chart-inspection.js +3 -0
- package/dist/browser/chart-inspection.js.map +1 -0
- package/dist/browser/chat/ChatComposer.js +1020 -431
- package/dist/browser/chat/ChatComposer.js.map +1 -1
- package/dist/browser/chat/chat-behavior.js +4 -0
- package/dist/browser/chat/chat-behavior.js.map +1 -1
- package/dist/browser/chat/chat-messages.js +33 -1
- package/dist/browser/chat/chat-messages.js.map +1 -1
- package/dist/browser/chat/command-search.js +173 -0
- package/dist/browser/chat/command-search.js.map +1 -0
- package/dist/browser/chat/composer-document.js +1 -1
- package/dist/browser/chat/composer-document.js.map +1 -1
- package/dist/browser/chat/index.js +2 -0
- package/dist/browser/chat/index.js.map +1 -1
- package/dist/browser/chat/reaction-people.js +15 -0
- package/dist/browser/chat/reaction-people.js.map +1 -0
- package/dist/browser/content/CalendarDayView.js +28 -3
- package/dist/browser/content/CalendarDayView.js.map +1 -1
- package/dist/browser/content/Chart.js +6 -15
- package/dist/browser/content/Chart.js.map +1 -1
- package/dist/browser/content/ChartSnapshotView.js +7 -10
- package/dist/browser/content/ChartSnapshotView.js.map +1 -1
- package/dist/browser/content/MessageRow.js +330 -448
- package/dist/browser/content/MessageRow.js.map +1 -1
- package/dist/browser/content/ReactionBar.js +419 -0
- package/dist/browser/content/ReactionBar.js.map +1 -0
- package/dist/browser/content/chart-inspection-core.js +557 -0
- package/dist/browser/content/chart-inspection-core.js.map +1 -0
- package/dist/browser/content/chart-inspection.js +10 -445
- package/dist/browser/content/chart-inspection.js.map +1 -1
- package/dist/browser/content/chart-line-gaps.js +1 -1
- package/dist/browser/content/chart-line-gaps.js.map +1 -1
- package/dist/browser/content/chart-snapshot.js +1 -1
- package/dist/browser/content/chart-snapshot.js.map +1 -1
- package/dist/browser/content/index.js +1 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/feedback/dialog-core.js +135 -23
- package/dist/browser/feedback/dialog-core.js.map +1 -1
- package/dist/browser/index.js +1 -0
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/input-mode.js +40 -0
- package/dist/browser/input-mode.js.map +1 -0
- package/dist/browser/inputs/Combobox.js +4 -2
- package/dist/browser/inputs/Combobox.js.map +1 -1
- package/dist/browser/inputs/FileDropTarget.js +24 -9
- package/dist/browser/inputs/FileDropTarget.js.map +1 -1
- package/dist/browser/inputs/FileInputs.js +2 -2
- package/dist/browser/inputs/FileInputs.js.map +1 -1
- package/dist/browser/inputs/SelectChip.js +52 -12
- package/dist/browser/inputs/SelectChip.js.map +1 -1
- package/dist/browser/inputs/emoji/EmojiPicker.js +13 -0
- package/dist/browser/inputs/emoji/EmojiPicker.js.map +1 -1
- package/dist/browser/inputs/file-drop.js.map +1 -1
- package/dist/browser/layout/AppWorkspace.js +708 -515
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/BottomSheet.js +8 -0
- package/dist/browser/layout/BottomSheet.js.map +1 -1
- package/dist/browser/layout/DetailPanel.js +283 -197
- package/dist/browser/layout/DetailPanel.js.map +1 -1
- package/dist/browser/layout/Discussion.js +233 -185
- package/dist/browser/layout/Discussion.js.map +1 -1
- package/dist/browser/layout/TableOfContents.js +10 -2
- package/dist/browser/layout/TableOfContents.js.map +1 -1
- package/dist/browser/layout/app-workspace-controller.js +155 -23
- package/dist/browser/layout/app-workspace-controller.js.map +1 -1
- package/dist/browser/layout/app-workspace-state.js +5 -1
- package/dist/browser/layout/app-workspace-state.js.map +1 -1
- package/dist/browser/layout/detail-panel-messages.js +35 -0
- package/dist/browser/layout/detail-panel-messages.js.map +1 -0
- package/dist/browser/layout/index.js +1 -1
- package/dist/browser/layout/index.js.map +1 -1
- package/dist/browser/surfaces/Avatar.js +50 -20
- package/dist/browser/surfaces/Avatar.js.map +1 -1
- package/dist/browser/surfaces/AvatarStack.js +406 -0
- package/dist/browser/surfaces/AvatarStack.js.map +1 -0
- package/dist/browser/surfaces/avatar-messages.js +8 -2
- package/dist/browser/surfaces/avatar-messages.js.map +1 -1
- package/dist/browser/surfaces/index.js +1 -0
- package/dist/browser/surfaces/index.js.map +1 -1
- package/dist/ssr/actions/ContextMenu.js +21 -3
- package/dist/ssr/actions/ContextMenu.js.map +1 -1
- package/dist/ssr/actions/Dropdown.js +140 -72
- package/dist/ssr/actions/Dropdown.js.map +1 -1
- package/dist/ssr/actions/GestureMenu.js +52 -106
- package/dist/ssr/actions/GestureMenu.js.map +1 -1
- package/dist/ssr/actions/Toolbar.js +294 -14
- package/dist/ssr/actions/Toolbar.js.map +1 -1
- package/dist/ssr/actions/index.js.map +1 -1
- package/dist/ssr/actions/menu-rows.js +15 -0
- package/dist/ssr/actions/menu-rows.js.map +1 -0
- package/dist/ssr/actions/menu-sheet.js +138 -0
- package/dist/ssr/actions/menu-sheet.js.map +1 -0
- package/dist/ssr/actions/toolbar-messages.js +20 -0
- package/dist/ssr/actions/toolbar-messages.js.map +1 -0
- package/dist/ssr/chart-inspection.js +3 -0
- package/dist/ssr/chart-inspection.js.map +1 -0
- package/dist/ssr/chat/ChatComposer.js +604 -113
- package/dist/ssr/chat/ChatComposer.js.map +1 -1
- package/dist/ssr/chat/chat-behavior.js +4 -0
- package/dist/ssr/chat/chat-behavior.js.map +1 -1
- package/dist/ssr/chat/chat-messages.js +33 -1
- package/dist/ssr/chat/chat-messages.js.map +1 -1
- package/dist/ssr/chat/command-search.js +173 -0
- package/dist/ssr/chat/command-search.js.map +1 -0
- package/dist/ssr/chat/composer-document.js +1 -1
- package/dist/ssr/chat/composer-document.js.map +1 -1
- package/dist/ssr/chat/index.js +2 -0
- package/dist/ssr/chat/index.js.map +1 -1
- package/dist/ssr/chat/reaction-people.js +15 -0
- package/dist/ssr/chat/reaction-people.js.map +1 -0
- package/dist/ssr/content/CalendarDayView.js +28 -3
- package/dist/ssr/content/CalendarDayView.js.map +1 -1
- package/dist/ssr/content/Chart.js +6 -15
- package/dist/ssr/content/Chart.js.map +1 -1
- package/dist/ssr/content/ChartSnapshotView.js +7 -10
- package/dist/ssr/content/ChartSnapshotView.js.map +1 -1
- package/dist/ssr/content/MessageRow.js +98 -152
- package/dist/ssr/content/MessageRow.js.map +1 -1
- package/dist/ssr/content/ReactionBar.js +304 -0
- package/dist/ssr/content/ReactionBar.js.map +1 -0
- package/dist/ssr/content/chart-inspection-core.js +557 -0
- package/dist/ssr/content/chart-inspection-core.js.map +1 -0
- package/dist/ssr/content/chart-inspection.js +10 -445
- package/dist/ssr/content/chart-inspection.js.map +1 -1
- package/dist/ssr/content/chart-line-gaps.js +1 -1
- package/dist/ssr/content/chart-line-gaps.js.map +1 -1
- package/dist/ssr/content/chart-snapshot.js +1 -1
- package/dist/ssr/content/chart-snapshot.js.map +1 -1
- package/dist/ssr/content/index.js +1 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/feedback/dialog-core.js +135 -23
- package/dist/ssr/feedback/dialog-core.js.map +1 -1
- package/dist/ssr/index.js +1 -0
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/input-mode.js +40 -0
- package/dist/ssr/input-mode.js.map +1 -0
- package/dist/ssr/inputs/Combobox.js +4 -2
- package/dist/ssr/inputs/Combobox.js.map +1 -1
- package/dist/ssr/inputs/FileDropTarget.js +17 -5
- package/dist/ssr/inputs/FileDropTarget.js.map +1 -1
- package/dist/ssr/inputs/FileInputs.js +2 -2
- package/dist/ssr/inputs/FileInputs.js.map +1 -1
- package/dist/ssr/inputs/SelectChip.js +44 -11
- package/dist/ssr/inputs/SelectChip.js.map +1 -1
- package/dist/ssr/inputs/emoji/EmojiPicker.js +12 -0
- package/dist/ssr/inputs/emoji/EmojiPicker.js.map +1 -1
- package/dist/ssr/inputs/file-drop.js.map +1 -1
- package/dist/ssr/layout/AppWorkspace.js +242 -105
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/BottomSheet.js +8 -0
- package/dist/ssr/layout/BottomSheet.js.map +1 -1
- package/dist/ssr/layout/DetailPanel.js +112 -58
- package/dist/ssr/layout/DetailPanel.js.map +1 -1
- package/dist/ssr/layout/Discussion.js +63 -36
- package/dist/ssr/layout/Discussion.js.map +1 -1
- package/dist/ssr/layout/TableOfContents.js +10 -2
- package/dist/ssr/layout/TableOfContents.js.map +1 -1
- package/dist/ssr/layout/app-workspace-controller.js +155 -23
- package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
- package/dist/ssr/layout/app-workspace-state.js +5 -1
- package/dist/ssr/layout/app-workspace-state.js.map +1 -1
- package/dist/ssr/layout/detail-panel-messages.js +35 -0
- package/dist/ssr/layout/detail-panel-messages.js.map +1 -0
- package/dist/ssr/layout/index.js +1 -1
- package/dist/ssr/layout/index.js.map +1 -1
- package/dist/ssr/surfaces/Avatar.js +20 -2
- package/dist/ssr/surfaces/Avatar.js.map +1 -1
- package/dist/ssr/surfaces/AvatarStack.js +286 -0
- package/dist/ssr/surfaces/AvatarStack.js.map +1 -0
- package/dist/ssr/surfaces/avatar-messages.js +8 -2
- package/dist/ssr/surfaces/avatar-messages.js.map +1 -1
- package/dist/ssr/surfaces/index.js +1 -0
- package/dist/ssr/surfaces/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/actions/ContextMenu.d.ts +8 -0
- package/dist/types/actions/Dropdown.d.ts +12 -5
- package/dist/types/actions/GestureMenu.d.ts +6 -4
- package/dist/types/actions/Toolbar.d.ts +20 -0
- package/dist/types/actions/index.d.ts +1 -1
- package/dist/types/actions/menu-rows.d.ts +5 -0
- package/dist/types/actions/menu-sheet.d.ts +22 -0
- package/dist/types/actions/toolbar-messages.d.ts +8 -0
- package/dist/types/chart-inspection.d.ts +2 -0
- package/dist/types/chat/ChatComposer.d.ts +51 -2
- package/dist/types/chat/chat-behavior.d.ts +4 -0
- package/dist/types/chat/chat-messages.d.ts +17 -2
- package/dist/types/chat/command-search.d.ts +41 -0
- package/dist/types/chat/index.d.ts +3 -1
- package/dist/types/chat/reaction-people.d.ts +9 -0
- package/dist/types/content/Chart.d.ts +2 -2
- package/dist/types/content/MessageRow.d.ts +3 -10
- package/dist/types/content/ReactionBar.d.ts +32 -0
- package/dist/types/content/chart-inspection-core.d.ts +61 -0
- package/dist/types/content/chart-inspection.d.ts +4 -20
- package/dist/types/content/chart-snapshot.d.ts +1 -1
- package/dist/types/content/index.d.ts +2 -1
- package/dist/types/feedback/dialog-core.d.ts +16 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/input-mode.d.ts +7 -0
- package/dist/types/inputs/FileDropTarget.d.ts +6 -3
- package/dist/types/inputs/SelectChip.d.ts +6 -0
- package/dist/types/inputs/file-drop.d.ts +2 -0
- package/dist/types/layout/AppWorkspace.d.ts +13 -1
- package/dist/types/layout/BottomSheet.d.ts +8 -0
- package/dist/types/layout/DetailPanel.d.ts +11 -0
- package/dist/types/layout/Discussion.d.ts +8 -0
- package/dist/types/layout/TableOfContents.d.ts +2 -0
- package/dist/types/layout/detail-panel-messages.d.ts +15 -0
- package/dist/types/layout/index.d.ts +1 -1
- package/dist/types/surfaces/Avatar.d.ts +12 -0
- package/dist/types/surfaces/AvatarStack.d.ts +41 -0
- package/dist/types/surfaces/avatar-messages.d.ts +3 -0
- package/dist/types/surfaces/index.d.ts +3 -1
- package/package.json +9 -4
|
@@ -17,34 +17,44 @@ var _tmpl$ = ["<span><strong", ">", "</strong></span>"],
|
|
|
17
17
|
_tmpl$0 = "<textarea class=\"k2b-chat-composer__measure\" rows=\"1\" tabindex=\"-1\" aria-hidden=\"true\" readonly></textarea>",
|
|
18
18
|
_tmpl$1 = ["<div class=\"k2b-chat-composer__input\" role=\"group\"", ">", "<textarea class=\"", "\" rows=\"1\"", "", "></textarea>", "</div>"],
|
|
19
19
|
_tmpl$10 = ["<span class=\"", "\">", "</span>"],
|
|
20
|
-
_tmpl$11 = ["<
|
|
21
|
-
_tmpl$12 = ["<
|
|
22
|
-
_tmpl$13 = ["<
|
|
23
|
-
_tmpl$14 = ["<
|
|
24
|
-
_tmpl$15 = ["<div class=\"k2b-chat-composer-accessory\"><div", " class=\"k2b-chat-composer__commands\" role=\"listbox\"", ">", "
|
|
25
|
-
_tmpl$16 = ["<div class=\"k2b-chat-
|
|
26
|
-
_tmpl$17 = ["<div
|
|
27
|
-
_tmpl$18 = ["<div
|
|
28
|
-
_tmpl$19 = ["<div class=\"k2b-chat-
|
|
29
|
-
_tmpl$20 = ["<div class=\"k2b-chat-
|
|
30
|
-
_tmpl$21 = ["<div class=\"k2b-chat-
|
|
31
|
-
_tmpl$22 = ["<
|
|
32
|
-
_tmpl$23 = ["<
|
|
33
|
-
_tmpl$24 = ["<
|
|
34
|
-
_tmpl$25 = ["<
|
|
35
|
-
_tmpl$26 = ["<
|
|
36
|
-
_tmpl$27 = ["<
|
|
37
|
-
_tmpl$28 =
|
|
38
|
-
_tmpl$29 =
|
|
39
|
-
_tmpl$30 = ["<
|
|
40
|
-
_tmpl$31 = ["<
|
|
41
|
-
_tmpl$32 = ["<
|
|
42
|
-
_tmpl$33 = ["<button type=\"button\" class=\"k2b-chat-
|
|
43
|
-
_tmpl$34 = ["<
|
|
44
|
-
_tmpl$35 = ["<div class=\"k2b-chat-
|
|
45
|
-
_tmpl$36 = ["<
|
|
46
|
-
|
|
20
|
+
_tmpl$11 = ["<small", ">", "</small>"],
|
|
21
|
+
_tmpl$12 = ["<span class=\"k2b-chat-composer__command-end\"><i class=\"ti ti-check\" aria-hidden=\"true\"></i><span class=\"k2b-sr-only\">", "</span></span>"],
|
|
22
|
+
_tmpl$13 = ["<button class=\"k2b-chat-composer__command\"", " type=\"button\" role=\"option\" tabindex=\"-1\"", "><i", " aria-hidden=\"true\"></i><span class=\"k2b-chat-composer__command-text\"><strong>", "</strong>", "</span>", "</button>"],
|
|
23
|
+
_tmpl$14 = ["<b>", "</b>"],
|
|
24
|
+
_tmpl$15 = ["<div class=\"k2b-chat-composer-accessory\"><div", " class=\"k2b-chat-composer__commands\" role=\"listbox\"", ">", "</div></div>"],
|
|
25
|
+
_tmpl$16 = ["<div role=\"status\" class=\"k2b-chat-composer__loading\"><i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i><strong>", "</strong></div>"],
|
|
26
|
+
_tmpl$17 = ["<div role=\"status\"><i class=\"ti ti-alert-circle\"></i> ", "</div>"],
|
|
27
|
+
_tmpl$18 = ["<div role=\"status\">", "</div>"],
|
|
28
|
+
_tmpl$19 = ["<div class=\"k2b-chat-composer-accessory\" data-commands><div", " class=\"k2b-chat-composer__commands\" role=\"listbox\"", ">", "", "", "", "</div></div>"],
|
|
29
|
+
_tmpl$20 = ["<div class=\"k2b-chat-composer-slot\"", "><div style=\"", "\"", ">", "</div>", "", "</div>"],
|
|
30
|
+
_tmpl$21 = ["<div class=\"k2b-chat-composer__hint\" aria-live=\"polite\">", "</div>"],
|
|
31
|
+
_tmpl$22 = ["<div class=\"k2b-chat-composer__attachments\" role=\"list\"", " tabindex=\"0\">", "</div>"],
|
|
32
|
+
_tmpl$23 = ["<div class=\"k2b-chat-composer__error\" role=\"alert\"><i class=\"ti ti-alert-circle\" aria-hidden=\"true\"></i>", "</div>"],
|
|
33
|
+
_tmpl$24 = ["<div class=\"k2b-chat-composer__row\">", "", "<div class=\"k2b-chat-composer__field\">", "<div class=\"k2b-chat-composer__field-tools\">", "", "", "</div></div>", "</div>"],
|
|
34
|
+
_tmpl$25 = ["<div class=\"k2b-chat-composer-shell\">", "<section class=\"", "\"", " role=\"group\"", ">", "", "", "", "</section><div class=\"k2b-sr-only\" role=\"status\" aria-live=\"polite\">", "</div><div class=\"k2b-chat-composer__status\" role=\"status\">", "", "</div></div>"],
|
|
35
|
+
_tmpl$26 = ["<button id=\"", "\" type=\"button\" role=\"option\" tabindex=\"-1\"", "><span class=\"k2b-chat-composer__emoji-glyph\" aria-hidden=\"true\">", "</span><strong>", "</strong>", "</button>"],
|
|
36
|
+
_tmpl$27 = ["<small>:", ":</small>"],
|
|
37
|
+
_tmpl$28 = "<i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i>",
|
|
38
|
+
_tmpl$29 = "<div class=\"k2b-chat-composer__command-placeholder\" aria-hidden=\"true\"><span></span><span><span></span><span></span></span></div>",
|
|
39
|
+
_tmpl$30 = ["<div class=\"k2b-chat-composer__command-group\" role=\"group\"", "><div class=\"k2b-chat-composer__command-heading\"", " role=\"presentation\"><span>", "</span>", "</div>", "", "</div>"],
|
|
40
|
+
_tmpl$31 = ["<span class=\"k2b-chat-composer__hint-text\">", "</span>"],
|
|
41
|
+
_tmpl$32 = ["<span class=\"k2b-chat-composer__dictation\"", "><i", " aria-hidden=\"true\"></i><span>", "</span></span>"],
|
|
42
|
+
_tmpl$33 = ["<button type=\"button\" class=\"k2b-chat-composer__hint-action\"", ">", "</button>"],
|
|
43
|
+
_tmpl$34 = ["<button type=\"button\" class=\"k2b-chat-composer__attachment-remove\"", "", "><i class=\"ti ti-x\" aria-hidden=\"true\"></i></button>"],
|
|
44
|
+
_tmpl$35 = ["<div class=\"k2b-chat-composer__attachment\" role=\"listitem\"><div class=\"", "\">", "", "</div>", "</div>"],
|
|
45
|
+
_tmpl$36 = ["<img", ">"],
|
|
46
|
+
_tmpl$37 = ["<div class=\"k2b-chat-composer__attachment-identity\">", "", "</div>"],
|
|
47
|
+
_tmpl$38 = ["<a class=\"k2b-chat-composer__attachment-link\"", " target=\"_blank\" rel=\"noopener noreferrer\"", ">", "", "</a>"],
|
|
48
|
+
_tmpl$39 = ["<button type=\"button\" class=\"k2b-chat-composer__attachment-action\"", ">", "", "</button>"],
|
|
49
|
+
_tmpl$40 = ["<footer class=\"k2b-chat-composer__footer\">", "</footer>"],
|
|
50
|
+
_tmpl$41 = ["<div class=\"k2b-chat-composer__tools\">", "", "", "", "", "", "", "</div>"],
|
|
51
|
+
_tmpl$42 = ["<div class=\"k2b-chat-composer__submit\">", "", "", "", "", "</div>"],
|
|
52
|
+
_tmpl$43 = ["<span>", "</span>"],
|
|
53
|
+
_tmpl$44 = ["<div class=\"k2b-chat-composer__status-line\"", "><i aria-hidden=\"true\"", "></i><span>", "</span></div>"],
|
|
54
|
+
_tmpl$45 = ["<span class=\"k2b-sr-only\">", "</span>"];
|
|
55
|
+
import { batch, children, createEffect, createMemo, createSignal, createUniqueId, For, onCleanup, onMount, Show, untrack } from "solid-js";
|
|
47
56
|
import { Dropdown } from "../actions/Dropdown";
|
|
57
|
+
import { dialogHistorySettled, holdDialogHistory } from "../feedback/dialog-core";
|
|
48
58
|
import { Tooltip } from "../feedback/Tooltip";
|
|
49
59
|
import { replaceTextareaRange } from "../inputs/editor-dom";
|
|
50
60
|
import { completedEmojiShortcode, emojiInTone, emojiName, emojiShortcodeQuery, loadEmojiIndex, suggestEmoji } from "../inputs/emoji/emoji-index";
|
|
@@ -57,14 +67,25 @@ import { useSelectMessages } from "../inputs/select-messages";
|
|
|
57
67
|
import { useLocale } from "../intl/locale";
|
|
58
68
|
import { useUiMessages } from "../intl/messages";
|
|
59
69
|
import { ChatContextUsage as ContextUsage } from "./ChatPrimitives";
|
|
60
|
-
import { conversationEnterAction, executeChatAction, nextChatCommandIndex, reportChatFailure, runChatSubmission } from "./chat-behavior";
|
|
70
|
+
import { chatCommandKey, conversationEnterAction, executeChatAction, nextChatCommandIndex, reportChatFailure, runChatSubmission } from "./chat-behavior";
|
|
61
71
|
import { useChatMessages } from "./chat-messages";
|
|
72
|
+
import { chatCommandListModel, parseChatCommandQuery } from "./command-search";
|
|
62
73
|
import { chatCommandQuery, chatMentionSegments, reconcileChatMentions } from "./composer-document";
|
|
63
74
|
const composerMaxInputHeight = 309;
|
|
75
|
+
const commandDoneMs = 3_000;
|
|
64
76
|
/** Emoji suggestions for a `:shortcode`, as many as fit the list without scrolling on a phone. */
|
|
65
77
|
const emojiSuggestionLimit = 8;
|
|
66
78
|
// Keep editor undo text within approximately 2 MiB, plus the current edit.
|
|
67
79
|
const composerUndoBudgetChars = 1_000_000;
|
|
80
|
+
|
|
81
|
+
/** An action can cancel and restore its token, or report a short completion message. */
|
|
82
|
+
|
|
83
|
+
/** A fixed place in the slash list. Sections keep the given order, also while searching. */
|
|
84
|
+
|
|
85
|
+
/** First-word narrowing; Tab writes `words[0]` followed by a space. */
|
|
86
|
+
|
|
87
|
+
/** Passed as the third argument to `searchCommands`. */
|
|
88
|
+
|
|
68
89
|
/** The row stays short: what a chat message needs. Other Markdown, typed by hand, keeps working. */
|
|
69
90
|
const formatTools = [{
|
|
70
91
|
id: "bold",
|
|
@@ -95,21 +116,80 @@ const isTouchOnly = () => typeof matchMedia === "function" && matchMedia(touchOn
|
|
|
95
116
|
const microphoneHoldMs = 500;
|
|
96
117
|
/** Keeps the field focused (and a phone's keyboard open) when a composer button is pressed with a mouse or finger. */
|
|
97
118
|
const keepFieldFocus = event => event.preventDefault();
|
|
119
|
+
/**
|
|
120
|
+
* Keeps one tap from reaching the page: its pointer, touch and mouse events stop at the window, and neither focus nor a
|
|
121
|
+
* click happens. A finger's tap ends with `touchend`, a scroll with `pointercancel`, a mouse's press with `click`.
|
|
122
|
+
*/
|
|
123
|
+
const swallowTap = down => {
|
|
124
|
+
const blocked = ["pointerup", "pointercancel", "touchstart", "touchend", "mousedown", "mouseup", "click", "contextmenu"];
|
|
125
|
+
const stop = () => {
|
|
126
|
+
window.removeEventListener("pointerdown", next, true);
|
|
127
|
+
for (const type of blocked) window.removeEventListener(type, block, true);
|
|
128
|
+
};
|
|
129
|
+
const block = event => {
|
|
130
|
+
event.stopPropagation();
|
|
131
|
+
if (event.type !== "pointercancel" && event.type !== "touchstart" && event.type !== "pointerup") event.preventDefault();
|
|
132
|
+
if (event.type === "click" || event.type === "touchend" || event.type === "pointercancel") stop();
|
|
133
|
+
};
|
|
134
|
+
// The next press is a new gesture, whatever became of this one.
|
|
135
|
+
const next = event => {
|
|
136
|
+
if (event !== down) stop();
|
|
137
|
+
};
|
|
138
|
+
down.stopPropagation();
|
|
139
|
+
down.preventDefault();
|
|
140
|
+
window.addEventListener("pointerdown", next, true);
|
|
141
|
+
for (const type of blocked) window.addEventListener(type, block, {
|
|
142
|
+
capture: true,
|
|
143
|
+
passive: false
|
|
144
|
+
});
|
|
145
|
+
};
|
|
98
146
|
const attachmentIcon = attachment => attachment.icon ?? (attachment.kind === "image" ? "ti ti-photo" : attachment.kind === "resource" ? "ti ti-link" : "ti ti-file");
|
|
99
147
|
const hasContent = items => items.some(item => item != null && typeof item !== "boolean" && item !== "");
|
|
100
148
|
export function ChatComposer(props) {
|
|
101
149
|
const accessory = children(() => props.accessory);
|
|
102
150
|
const hasAccessory = () => hasContent(accessory.toArray());
|
|
103
151
|
const hint = children(() => props.hint);
|
|
152
|
+
const footer = children(() => props.footerContent);
|
|
104
153
|
const messages = useUiMessages();
|
|
105
154
|
const markdownToolbarMessages = useMarkdownToolbarMessages();
|
|
106
155
|
const chatMessages = useChatMessages();
|
|
107
156
|
const selectMessages = useSelectMessages();
|
|
108
157
|
const commandListId = `k2b-chat-commands-${createUniqueId().replace(/[^A-Za-z0-9_-]/g, "-")}`;
|
|
109
158
|
const formatGroupId = `${commandListId}-format`;
|
|
159
|
+
// Emoji suggestions keep their index-based selection and ids.
|
|
110
160
|
const [selectedCommandIndex, setSelectedCommandIndex] = createSignal(0);
|
|
161
|
+
const [activeCommandKey, setActiveCommandKey] = createSignal();
|
|
162
|
+
const [commandMoved, setCommandMoved] = createSignal(false);
|
|
163
|
+
const commandIds = new Map();
|
|
164
|
+
let commandIdCounter = 0;
|
|
165
|
+
const commandOptionId = command => {
|
|
166
|
+
const key = chatCommandKey(command);
|
|
167
|
+
if (!commandIds.has(key)) commandIds.set(key, commandIdCounter++);
|
|
168
|
+
return `${commandListId}-c${commandIds.get(key)}`;
|
|
169
|
+
};
|
|
170
|
+
const [closedAt, setClosedAt] = createSignal();
|
|
171
|
+
// Where the pending native edit starts; a selection replaced as a whole starts at its beginning.
|
|
172
|
+
let inputStart;
|
|
173
|
+
let inputEnd;
|
|
174
|
+
const [commandStatus, setCommandStatus] = createSignal();
|
|
175
|
+
const [announcement, setAnnouncement] = createSignal();
|
|
176
|
+
let statusTimer;
|
|
177
|
+
let disposed = false;
|
|
178
|
+
const showCommandStatus = status => {
|
|
179
|
+
if (disposed) return;
|
|
180
|
+
clearTimeout(statusTimer);
|
|
181
|
+
setCommandStatus(status);
|
|
182
|
+
if (status?.tone === "done") {
|
|
183
|
+
statusTimer = setTimeout(() => setCommandStatus(undefined), commandDoneMs);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
onCleanup(() => {
|
|
187
|
+
disposed = true;
|
|
188
|
+
clearTimeout(statusTimer);
|
|
189
|
+
});
|
|
111
190
|
const [addingFiles, setAddingFiles] = createSignal(false);
|
|
112
191
|
const [submitting, setSubmitting] = createSignal(false);
|
|
192
|
+
let shellRef;
|
|
113
193
|
let composerRef;
|
|
114
194
|
let textareaRef;
|
|
115
195
|
let fileInputRef;
|
|
@@ -131,9 +211,51 @@ export function ChatComposer(props) {
|
|
|
131
211
|
const [dismissed, setDismissed] = createSignal(false);
|
|
132
212
|
const [composing, setComposing] = createSignal(false);
|
|
133
213
|
const [searchResults, setSearchResults] = createSignal([]);
|
|
214
|
+
const [searchResultsQuery, setSearchResultsQuery] = createSignal();
|
|
215
|
+
const [searchResultsScope, setSearchResultsScope] = createSignal();
|
|
216
|
+
const [suggestionAnnouncement, setSuggestionAnnouncement] = createSignal();
|
|
134
217
|
const [searching, setSearching] = createSignal(false);
|
|
135
218
|
const [searchError, setSearchError] = createSignal(false);
|
|
136
219
|
const [executing, setExecuting] = createSignal(false);
|
|
220
|
+
const [modelOpen, setModelOpen] = createSignal();
|
|
221
|
+
const [choosingModel, setChoosingModel] = createSignal(false);
|
|
222
|
+
let resolveModel;
|
|
223
|
+
const finishModelSelection = id => {
|
|
224
|
+
const resolve = resolveModel;
|
|
225
|
+
resolveModel = undefined;
|
|
226
|
+
if (choosingModel()) {
|
|
227
|
+
setModelOpen(false);
|
|
228
|
+
// Let the controlled close reach Dropdown before returning normal clicks to its own state.
|
|
229
|
+
queueMicrotask(() => {
|
|
230
|
+
if (!choosingModel()) setModelOpen(undefined);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
setChoosingModel(false);
|
|
234
|
+
resolve?.(id);
|
|
235
|
+
};
|
|
236
|
+
const blockedBesidesCommand = () => Boolean(props.disabled || stopping() || state() === "submitting" || addingFiles() || submitting());
|
|
237
|
+
const usableModelControl = () => !conversation() && !footer() && !blockedBesidesCommand() && !running() && Boolean(props.models?.length && props.onModelChange);
|
|
238
|
+
const openModelSelection = () => {
|
|
239
|
+
if (!usableModelControl()) return Promise.resolve(null);
|
|
240
|
+
finishModelSelection(null);
|
|
241
|
+
return new Promise(resolve => {
|
|
242
|
+
resolveModel = resolve;
|
|
243
|
+
setChoosingModel(true);
|
|
244
|
+
setModelOpen(true);
|
|
245
|
+
queueMicrotask(() => {
|
|
246
|
+
if (!choosingModel() || !modelOpen()) return;
|
|
247
|
+
// Start on the current model, as a click on the chip would show it checked.
|
|
248
|
+
const menu = composerRef?.querySelector(".k2b-chat-composer__model");
|
|
249
|
+
(menu?.querySelector('[role="menuitemradio"][aria-checked="true"]:not(:disabled)') ?? menu?.querySelector('[role="menuitemradio"]:not(:disabled)'))?.focus();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
createEffect(() => {
|
|
254
|
+
if (!usableModelControl()) finishModelSelection(null);
|
|
255
|
+
});
|
|
256
|
+
onCleanup(() => finishModelSelection(null));
|
|
257
|
+
const [commandOpening, setCommandOpening] = createSignal(0);
|
|
258
|
+
let retrySearch = false;
|
|
137
259
|
let highlightRef;
|
|
138
260
|
let commandListRef;
|
|
139
261
|
const mentions = () => props.mentions ?? [];
|
|
@@ -150,27 +272,96 @@ export function ChatComposer(props) {
|
|
|
150
272
|
return token && index ? suggestEmoji(index, token.query, emojiSuggestionLimit) : [];
|
|
151
273
|
});
|
|
152
274
|
const emojiOpen = () => emojiMatches().length > 0;
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
275
|
+
const caretCommandToken = createMemo(() => chatCommandQuery(props.value, caret(), selectionEnd()));
|
|
276
|
+
const caretCommandStart = createMemo(() => caretCommandToken()?.start);
|
|
277
|
+
// Retained server rows belong to one `/`: they go when it does or when another `/` starts.
|
|
278
|
+
createEffect(() => {
|
|
279
|
+
caretCommandStart();
|
|
280
|
+
setSearchResults([]);
|
|
281
|
+
setSearchResultsQuery(undefined);
|
|
282
|
+
});
|
|
283
|
+
// Escape closes the list for one `/` character, wherever the caret or selection goes; only that `/` going away ends it.
|
|
284
|
+
createEffect(() => {
|
|
285
|
+
const closed = closedAt();
|
|
286
|
+
if (closed !== undefined && props.value[closed] !== "/") setClosedAt(undefined);
|
|
287
|
+
});
|
|
288
|
+
const commandQuery = createMemo(() => {
|
|
289
|
+
const token = caretCommandToken();
|
|
290
|
+
return dismissed() || composing() || token?.start === closedAt() ? null : token;
|
|
291
|
+
});
|
|
292
|
+
const openingStart = createMemo(() => commandQuery()?.start);
|
|
293
|
+
createEffect(() => {
|
|
294
|
+
props.draftKey;
|
|
295
|
+
const start = openingStart();
|
|
296
|
+
if (start !== undefined) setCommandOpening(opening => opening + 1);
|
|
297
|
+
});
|
|
298
|
+
const parsedCommandQuery = createMemo(() => parseChatCommandQuery(commandQuery()?.query ?? "", props.commandScopes ?? []));
|
|
299
|
+
const currentCommandScope = createMemo(() => parsedCommandQuery().scope?.id);
|
|
300
|
+
createEffect(() => {
|
|
301
|
+
currentCommandScope();
|
|
302
|
+
setSearchResults([]);
|
|
303
|
+
setSearchResultsQuery(undefined);
|
|
304
|
+
});
|
|
305
|
+
const localCommands = createMemo(() => props.commands ?? []);
|
|
306
|
+
const commandModel = createMemo(() => chatCommandListModel({
|
|
307
|
+
query: commandQuery()?.query ?? "",
|
|
308
|
+
scopes: props.commandScopes,
|
|
309
|
+
sections: props.commandSections,
|
|
310
|
+
commands: localCommands(),
|
|
311
|
+
searchResults: searchResultsScope() === parsedCommandQuery().scope?.id ? searchResults() : [],
|
|
312
|
+
searchResultsFresh: searchResultsQuery() === commandQuery()?.query && searchResultsScope() === parsedCommandQuery().scope?.id,
|
|
313
|
+
searchSettled: searchResultsQuery() !== undefined && searchResultsScope() === parsedCommandQuery().scope?.id,
|
|
314
|
+
searching: searching()
|
|
315
|
+
}));
|
|
316
|
+
const commandMatches = createMemo(() => commandQuery() ? commandModel().rows.map(row => row.command) : []);
|
|
317
|
+
const currentCommandQuery = createMemo(() => caretCommandToken()?.query);
|
|
318
|
+
// Arrow marking belongs to one token in one draft; another draft or `/` starts at the top row again.
|
|
319
|
+
createEffect(() => {
|
|
320
|
+
props.draftKey;
|
|
321
|
+
caretCommandStart();
|
|
322
|
+
currentCommandQuery();
|
|
323
|
+
setCommandMoved(false);
|
|
324
|
+
});
|
|
325
|
+
const commandsOpen = () => Boolean(!emojiOpen() && commandQuery() && (localCommands().length || props.searchCommands));
|
|
326
|
+
const commandSheetHeld = createMemo(() => commandsOpen() && isTouchOnly());
|
|
327
|
+
createEffect(() => {
|
|
328
|
+
if (!commandSheetHeld()) return;
|
|
329
|
+
onCleanup(holdDialogHistory(() => setClosedAt(commandQuery()?.start)));
|
|
158
330
|
});
|
|
159
|
-
const commandsOpen = () => Boolean(!emojiOpen() && commandQuery() && (props.commands?.length || props.searchCommands));
|
|
160
331
|
const suggestionsOpen = () => commandsOpen() || emojiOpen();
|
|
161
332
|
createEffect(() => {
|
|
162
333
|
if (!suggestionsOpen()) return;
|
|
163
334
|
let disposed = false;
|
|
164
335
|
const measure = () => {
|
|
165
336
|
if (disposed || !commandListRef) return;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
337
|
+
const accessory = commandListRef.parentElement;
|
|
338
|
+
const viewportTop = window.visualViewport?.offsetTop ?? 0;
|
|
339
|
+
let top = viewportTop;
|
|
340
|
+
// The column a phone sheet spans: the nearest ancestor that clips sideways, or the page.
|
|
341
|
+
let column;
|
|
342
|
+
for (let parent = accessory.parentElement; parent; parent = parent.parentElement) {
|
|
343
|
+
const style = window.getComputedStyle(parent);
|
|
344
|
+
if (/(auto|scroll|hidden|clip)/.test(style.overflowY)) {
|
|
169
345
|
top = Math.max(top, parent.getBoundingClientRect().top);
|
|
170
346
|
}
|
|
347
|
+
if (!column && /(auto|scroll|hidden|clip)/.test(style.overflowX)) {
|
|
348
|
+
const left = parent.getBoundingClientRect().left + parent.clientLeft;
|
|
349
|
+
column = {
|
|
350
|
+
left,
|
|
351
|
+
right: left + parent.clientWidth
|
|
352
|
+
};
|
|
353
|
+
}
|
|
171
354
|
}
|
|
172
|
-
const
|
|
355
|
+
const page = document.documentElement.clientWidth;
|
|
356
|
+
const left = Math.max(0, column?.left ?? 0);
|
|
357
|
+
const right = Math.min(page, column?.right ?? page);
|
|
358
|
+
const slot = accessory.parentElement.getBoundingClientRect();
|
|
359
|
+
const bottom = accessory.getBoundingClientRect().bottom;
|
|
173
360
|
commandListRef.style.setProperty("--k2b-chat-command-space", `${Math.max(40, bottom - top - 8)}px`);
|
|
361
|
+
// Only a list that reaches the screen's top edge keeps clear of a notch or the status bar.
|
|
362
|
+
commandListRef.style.setProperty("--k2b-chat-command-safe-top", top === viewportTop ? "env(safe-area-inset-top, 0px)" : "0px");
|
|
363
|
+
accessory.style.setProperty("--k2b-chat-command-reach-left", `${Math.max(0, slot.left - left)}px`);
|
|
364
|
+
accessory.style.setProperty("--k2b-chat-command-reach-right", `${Math.max(0, right - slot.right)}px`);
|
|
174
365
|
};
|
|
175
366
|
queueMicrotask(measure);
|
|
176
367
|
window.addEventListener("resize", measure);
|
|
@@ -183,30 +374,108 @@ export function ChatComposer(props) {
|
|
|
183
374
|
window.visualViewport?.removeEventListener("resize", measure);
|
|
184
375
|
});
|
|
185
376
|
});
|
|
377
|
+
// A press outside the command list and the field closes the list, as Escape does; the text stays. With a mouse or a
|
|
378
|
+
// trackpad the press then acts as usual. On a touch-only device a tap outside the composer closes only the list: it
|
|
379
|
+
// reaches nothing beneath, and the field keeps focus, so the keyboard stays open.
|
|
380
|
+
createEffect(() => {
|
|
381
|
+
if (!commandsOpen()) return;
|
|
382
|
+
const onPointerDown = event => {
|
|
383
|
+
const target = event.target;
|
|
384
|
+
if (!(target instanceof Node) || commandListRef?.contains(target) || textareaRef?.parentElement?.contains(target)) return;
|
|
385
|
+
setClosedAt(commandQuery()?.start);
|
|
386
|
+
if (isTouchOnly() && !shellRef?.contains(target)) swallowTap(event);
|
|
387
|
+
};
|
|
388
|
+
window.addEventListener("pointerdown", onPointerDown, true);
|
|
389
|
+
onCleanup(() => window.removeEventListener("pointerdown", onPointerDown, true));
|
|
390
|
+
});
|
|
186
391
|
createEffect(() => {
|
|
392
|
+
props.draftKey;
|
|
187
393
|
const token = commandQuery();
|
|
188
|
-
setSearchResults([]);
|
|
189
394
|
setSearchError(false);
|
|
190
395
|
if (!token || !props.searchCommands) {
|
|
191
396
|
setSearching(false);
|
|
192
397
|
return;
|
|
193
398
|
}
|
|
399
|
+
running();
|
|
400
|
+
const parsed = parsedCommandQuery();
|
|
401
|
+
const opening = commandOpening();
|
|
194
402
|
const abort = new AbortController();
|
|
195
403
|
setSearching(true);
|
|
404
|
+
const delay = retrySearch ? 0 : 120;
|
|
405
|
+
retrySearch = false;
|
|
196
406
|
const timer = setTimeout(() => {
|
|
197
|
-
Promise.resolve(props.searchCommands(
|
|
198
|
-
|
|
407
|
+
Promise.resolve().then(() => props.searchCommands(parsed.query, abort.signal, {
|
|
408
|
+
scope: parsed.scope?.id,
|
|
409
|
+
opening
|
|
410
|
+
})).then(results => {
|
|
411
|
+
if (!abort.signal.aborted) {
|
|
412
|
+
batch(() => {
|
|
413
|
+
setSearchResults(results);
|
|
414
|
+
setSearchResultsQuery(token.query);
|
|
415
|
+
setSearchResultsScope(parsed.scope?.id);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
199
418
|
}).catch(() => {
|
|
200
|
-
if (!abort.signal.aborted)
|
|
419
|
+
if (!abort.signal.aborted) {
|
|
420
|
+
setSearchResults([]);
|
|
421
|
+
setSearchResultsQuery(undefined);
|
|
422
|
+
setSearchError(true);
|
|
423
|
+
}
|
|
201
424
|
}).finally(() => {
|
|
202
425
|
if (!abort.signal.aborted) setSearching(false);
|
|
203
426
|
});
|
|
204
|
-
},
|
|
427
|
+
}, delay);
|
|
205
428
|
onCleanup(() => {
|
|
206
429
|
clearTimeout(timer);
|
|
207
430
|
abort.abort();
|
|
208
431
|
});
|
|
209
432
|
});
|
|
433
|
+
let settledEmpty;
|
|
434
|
+
createEffect(() => {
|
|
435
|
+
const token = commandQuery();
|
|
436
|
+
if (!token || parsedCommandQuery().scope || searchError()) {
|
|
437
|
+
settledEmpty = undefined;
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const previous = settledEmpty;
|
|
441
|
+
if (/\s$/.test(token.query) && previous?.start === token.start && previous.query.trimEnd() === token.query.trimEnd()) {
|
|
442
|
+
setClosedAt(token.start);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const settled = !searching() && (!props.searchCommands || searchResultsQuery() === token.query);
|
|
446
|
+
if (settled && commandModel().empty) {
|
|
447
|
+
settledEmpty = {
|
|
448
|
+
start: token.start,
|
|
449
|
+
query: token.query
|
|
450
|
+
};
|
|
451
|
+
if (/\s$/.test(token.query)) setClosedAt(token.start);
|
|
452
|
+
} else if (!searching()) settledEmpty = undefined;
|
|
453
|
+
});
|
|
454
|
+
let lastSuggestion;
|
|
455
|
+
createEffect(() => {
|
|
456
|
+
const model = commandModel();
|
|
457
|
+
const query = commandQuery()?.query;
|
|
458
|
+
if (!commandsOpen()) {
|
|
459
|
+
setSuggestionAnnouncement(undefined);
|
|
460
|
+
lastSuggestion = undefined;
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (searching() || searchError() || props.searchCommands && searchResultsQuery() !== query) return;
|
|
464
|
+
const text = chatMessages().commandSuggestions({
|
|
465
|
+
count: model.rows.length
|
|
466
|
+
});
|
|
467
|
+
const timer = setTimeout(() => {
|
|
468
|
+
if (lastSuggestion && lastSuggestion.query === query && lastSuggestion.text === text) return;
|
|
469
|
+
lastSuggestion = {
|
|
470
|
+
query: query ?? "",
|
|
471
|
+
text
|
|
472
|
+
};
|
|
473
|
+
setSuggestionAnnouncement({
|
|
474
|
+
text
|
|
475
|
+
});
|
|
476
|
+
}, 350);
|
|
477
|
+
onCleanup(() => clearTimeout(timer));
|
|
478
|
+
});
|
|
210
479
|
const history = [];
|
|
211
480
|
let historyIndex = -1;
|
|
212
481
|
createEffect(() => {
|
|
@@ -214,7 +483,13 @@ export function ChatComposer(props) {
|
|
|
214
483
|
untrack(() => {
|
|
215
484
|
history.length = 0;
|
|
216
485
|
historyIndex = -1;
|
|
486
|
+
finishModelSelection(null);
|
|
217
487
|
setDismissed(true);
|
|
488
|
+
setClosedAt(undefined);
|
|
489
|
+
setSearchResults([]);
|
|
490
|
+
setSearchResultsQuery(undefined);
|
|
491
|
+
// A failure belongs to the draft it happened in; success notes may follow a command into the chat it opened.
|
|
492
|
+
if (commandStatus()?.tone === "error") showCommandStatus();
|
|
218
493
|
});
|
|
219
494
|
});
|
|
220
495
|
const snapshot = () => ({
|
|
@@ -233,6 +508,23 @@ export function ChatComposer(props) {
|
|
|
233
508
|
}
|
|
234
509
|
};
|
|
235
510
|
const edit = (value, nextMentions = reconcileChatMentions(props.value, value, mentions())) => {
|
|
511
|
+
if (commandStatus()?.tone === "error") showCommandStatus();
|
|
512
|
+
// Replacing `/` can leave its position and query identical, but starts a new opening.
|
|
513
|
+
if (inputStart !== undefined && inputEnd !== undefined && props.value.slice(inputStart, inputEnd).includes("/")) {
|
|
514
|
+
setCommandOpening(opening => opening + 1);
|
|
515
|
+
setSearchResults([]);
|
|
516
|
+
setSearchResultsQuery(undefined);
|
|
517
|
+
setCommandMoved(false);
|
|
518
|
+
}
|
|
519
|
+
// An edit that starts at or before the closed `/` (select all, then type `/`) replaces it; the new `/` may open again.
|
|
520
|
+
const closed = closedAt();
|
|
521
|
+
if (closed !== undefined) {
|
|
522
|
+
let same = 0;
|
|
523
|
+
while (same <= closed && props.value[same] === value[same]) same++;
|
|
524
|
+
if (Math.min(same, inputStart ?? same) <= closed) setClosedAt(undefined);
|
|
525
|
+
}
|
|
526
|
+
inputStart = undefined;
|
|
527
|
+
inputEnd = undefined;
|
|
236
528
|
record();
|
|
237
529
|
props.onValueChange(value);
|
|
238
530
|
props.onMentionsChange?.(nextMentions);
|
|
@@ -252,16 +544,33 @@ export function ChatComposer(props) {
|
|
|
252
544
|
});
|
|
253
545
|
};
|
|
254
546
|
const syncCaret = () => {
|
|
255
|
-
|
|
256
|
-
|
|
547
|
+
// One update, so no effect sees a half-moved selection (it would read as "no `/` here" and reopen a closed list).
|
|
548
|
+
batch(() => {
|
|
549
|
+
setCaret(textareaRef?.selectionStart ?? props.value.length);
|
|
550
|
+
setSelectionEnd(textareaRef?.selectionEnd ?? props.value.length);
|
|
551
|
+
});
|
|
257
552
|
};
|
|
258
|
-
const selectedCommand = () =>
|
|
553
|
+
const selectedCommand = createMemo(() => {
|
|
554
|
+
const rows = commandMatches();
|
|
555
|
+
return commandMoved() && rows.find(command => chatCommandKey(command) === activeCommandKey()) || rows[0];
|
|
556
|
+
});
|
|
557
|
+
const selectedCommandKey = createMemo(() => {
|
|
558
|
+
const command = selectedCommand();
|
|
559
|
+
return command ? chatCommandKey(command) : undefined;
|
|
560
|
+
});
|
|
561
|
+
createEffect(() => {
|
|
562
|
+
if (!commandsOpen()) commandIds.clear();
|
|
563
|
+
selectedCommandKey();
|
|
564
|
+
setAnnouncement(undefined);
|
|
565
|
+
});
|
|
259
566
|
const busy = () => Boolean(props.disabled || stopping() || state() === "submitting" || executing() || addingFiles());
|
|
260
567
|
const blocked = () => busy() || submitting();
|
|
261
568
|
/** The conversation field stays editable while a message is sent, so it keeps the focus and a phone its keyboard. */
|
|
262
569
|
const editBlocked = () => conversation() ? busy() : blocked();
|
|
263
570
|
const hasDraft = () => Boolean(props.value.trim() || !running() && attachments().length > 0);
|
|
264
|
-
|
|
571
|
+
/** The draft may go out now, apart from a submission that is already on its way. */
|
|
572
|
+
const submittable = () => !busy() && hasDraft() && !(running() && runningSubmitIntent() === "steer" && mentions().length);
|
|
573
|
+
const canSubmit = () => !submitting() && submittable();
|
|
265
574
|
const canSelectFiles = () => Boolean(props.fileSelection && !props.fileSelection.disabled && !running() && !blocked());
|
|
266
575
|
const hasAddMenu = () => Boolean(props.fileSelection || props.menuActions?.length);
|
|
267
576
|
const hasContextUsage = () => {
|
|
@@ -318,21 +627,23 @@ export function ChatComposer(props) {
|
|
|
318
627
|
}
|
|
319
628
|
});
|
|
320
629
|
createEffect(() => {
|
|
321
|
-
commandMatches();
|
|
322
630
|
emojiMatches();
|
|
323
631
|
setSelectedCommandIndex(0);
|
|
324
632
|
});
|
|
325
633
|
createEffect(() => {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
emojiMatches();
|
|
634
|
+
const command = selectedCommand();
|
|
635
|
+
const id = emojiOpen() ? `${commandListId}-${selectedCommandIndex()}` : command ? commandOptionId(command) : undefined;
|
|
329
636
|
queueMicrotask(() => {
|
|
330
637
|
const list = commandListRef;
|
|
331
|
-
const item = list?.querySelector(`[id="${
|
|
638
|
+
const item = id ? list?.querySelector(`[id="${id}"]`) : undefined;
|
|
332
639
|
if (!list || !item?.isConnected) return;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
640
|
+
// Measured against the visible part of the list: a sticky section heading covers the top of it.
|
|
641
|
+
const heading = item.closest(".k2b-chat-composer__command-group")?.querySelector(".k2b-chat-composer__command-heading");
|
|
642
|
+
const listTop = list.getBoundingClientRect().top + list.clientTop;
|
|
643
|
+
const box = item.getBoundingClientRect();
|
|
644
|
+
const top = box.top - listTop - (heading?.offsetHeight ?? 0);
|
|
645
|
+
const bottom = box.bottom - listTop - list.clientHeight;
|
|
646
|
+
if (top < 0) list.scrollTop += top;else if (bottom > 0) list.scrollTop += bottom;
|
|
336
647
|
});
|
|
337
648
|
});
|
|
338
649
|
createEffect(() => {
|
|
@@ -379,18 +690,26 @@ export function ChatComposer(props) {
|
|
|
379
690
|
};
|
|
380
691
|
const submit = async () => {
|
|
381
692
|
if (!canSubmit()) return;
|
|
382
|
-
const
|
|
383
|
-
const previousValue = props.value;
|
|
384
|
-
const previousAttachments = attachments();
|
|
385
|
-
const previousMentions = mentions();
|
|
386
|
-
const input = {
|
|
387
|
-
intent,
|
|
388
|
-
text: previousValue,
|
|
389
|
-
mentions: previousMentions,
|
|
390
|
-
attachments: intent !== "steer" ? previousAttachments : []
|
|
391
|
-
};
|
|
693
|
+
const key = props.draftKey;
|
|
392
694
|
setSubmitting(true);
|
|
393
695
|
try {
|
|
696
|
+
if (isTouchOnly()) {
|
|
697
|
+
if (commandSheetHeld()) setClosedAt(commandQuery()?.start);
|
|
698
|
+
// The sheet's Back entry leaves first, so a submit handler that navigates finds the history settled.
|
|
699
|
+
await dialogHistorySettled();
|
|
700
|
+
// A draft that another one replaced, or that may no longer go out, is not sent and keeps its text.
|
|
701
|
+
if (disposed || props.draftKey !== key || !submittable()) return;
|
|
702
|
+
}
|
|
703
|
+
const intent = running() ? runningSubmitIntent() : "send";
|
|
704
|
+
const previousValue = props.value;
|
|
705
|
+
const previousAttachments = attachments();
|
|
706
|
+
const previousMentions = mentions();
|
|
707
|
+
const input = {
|
|
708
|
+
intent,
|
|
709
|
+
text: previousValue,
|
|
710
|
+
mentions: previousMentions,
|
|
711
|
+
attachments: intent !== "steer" ? previousAttachments : []
|
|
712
|
+
};
|
|
394
713
|
await runChatSubmission({
|
|
395
714
|
clear: () => {
|
|
396
715
|
props.onValueChange("");
|
|
@@ -422,9 +741,32 @@ export function ChatComposer(props) {
|
|
|
422
741
|
});
|
|
423
742
|
}
|
|
424
743
|
};
|
|
425
|
-
const
|
|
744
|
+
const narrowCommand = command => {
|
|
745
|
+
const scope = props.commandScopes?.find(scope => scope.id === command.scope);
|
|
746
|
+
const word = scope?.words[0];
|
|
747
|
+
const token = commandQuery();
|
|
748
|
+
if (!word || !token || !textareaRef) return false;
|
|
749
|
+
replaceTextareaRange(textareaRef, token.start + 1, token.end, `${word} `);
|
|
750
|
+
syncCaret();
|
|
751
|
+
return true;
|
|
752
|
+
};
|
|
753
|
+
const executeCommand = async (command, tab = false) => {
|
|
426
754
|
const token = commandQuery();
|
|
427
|
-
|
|
755
|
+
const heldHistory = commandSheetHeld();
|
|
756
|
+
if (!token || blocked()) return;
|
|
757
|
+
if (command.disabled || command.reason) {
|
|
758
|
+
// A new object each time, so choosing the same row again inserts and announces its reason again.
|
|
759
|
+
setAnnouncement(command.reason ? {
|
|
760
|
+
text: command.reason
|
|
761
|
+
} : undefined);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (command.searchAgain) {
|
|
765
|
+
retrySearch = true;
|
|
766
|
+
setCommandOpening(opening => opening + 1);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (command.scope && (tab || !command.action && !command.mention) && narrowCommand(command)) return;
|
|
428
770
|
const previous = snapshot();
|
|
429
771
|
const key = props.draftKey;
|
|
430
772
|
const replacement = command.mention ? command.mention.name + " " : "";
|
|
@@ -435,32 +777,73 @@ export function ChatComposer(props) {
|
|
|
435
777
|
end: token.start + command.mention.name.length,
|
|
436
778
|
attachment: command.mention
|
|
437
779
|
});
|
|
780
|
+
// A cancelled or failed command leaves no undo step behind: restoring puts the history back as well.
|
|
781
|
+
const previousHistory = {
|
|
782
|
+
entries: [...history],
|
|
783
|
+
index: historyIndex
|
|
784
|
+
};
|
|
438
785
|
edit(nextValue, nextMentions);
|
|
439
786
|
setDismissed(true);
|
|
440
787
|
setExecuting(true);
|
|
441
788
|
let submitRequested = false;
|
|
789
|
+
let restored = false;
|
|
790
|
+
const restore = () => {
|
|
791
|
+
if (props.draftKey !== key || props.value !== nextValue) return;
|
|
792
|
+
setDismissed(true);
|
|
793
|
+
props.onValueChange(previous.value);
|
|
794
|
+
props.onMentionsChange?.(previous.mentions);
|
|
795
|
+
history.splice(0, history.length, ...previousHistory.entries);
|
|
796
|
+
historyIndex = previousHistory.index;
|
|
797
|
+
restored = true;
|
|
798
|
+
};
|
|
799
|
+
showCommandStatus(command.busyLabel ? {
|
|
800
|
+
tone: "busy",
|
|
801
|
+
text: command.busyLabel
|
|
802
|
+
} : undefined);
|
|
442
803
|
try {
|
|
443
|
-
|
|
804
|
+
// The sheet's Back entry leaves first, so an action that navigates or opens a dialog finds the history settled.
|
|
805
|
+
if (heldHistory && command.action) {
|
|
806
|
+
await dialogHistorySettled();
|
|
807
|
+
// Another draft or no composer any more: the command does not run against it.
|
|
808
|
+
if (disposed || props.draftKey !== key) return;
|
|
809
|
+
}
|
|
810
|
+
const result = await command.action?.({
|
|
444
811
|
setValue: value => edit(value),
|
|
445
812
|
submit: () => {
|
|
446
813
|
submitRequested = true;
|
|
447
814
|
},
|
|
448
|
-
focus
|
|
815
|
+
focus,
|
|
816
|
+
openModelSelection
|
|
449
817
|
});
|
|
818
|
+
if (result === "cancelled") {
|
|
819
|
+
submitRequested = false;
|
|
820
|
+
restore();
|
|
821
|
+
showCommandStatus();
|
|
822
|
+
} else if (result) {
|
|
823
|
+
showCommandStatus({
|
|
824
|
+
tone: "done",
|
|
825
|
+
text: result.done
|
|
826
|
+
});
|
|
827
|
+
}
|
|
450
828
|
} catch (error) {
|
|
451
829
|
submitRequested = false;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
830
|
+
restore();
|
|
831
|
+
// An unmounted composer has no status line left; the app's error channel keeps the failure from going silent.
|
|
832
|
+
if (disposed) props.onError?.(error);else showCommandStatus({
|
|
833
|
+
tone: "error",
|
|
834
|
+
text: error instanceof Error && error.message ? error.message : chatMessages().commandFailed
|
|
835
|
+
});
|
|
457
836
|
} finally {
|
|
837
|
+
if (commandStatus()?.tone === "busy") showCommandStatus();
|
|
458
838
|
setExecuting(false);
|
|
459
839
|
}
|
|
460
840
|
if (submitRequested && props.draftKey === key) await submit();
|
|
461
841
|
queueMicrotask(() => {
|
|
462
842
|
if (props.draftKey !== key) return;
|
|
463
|
-
|
|
843
|
+
if (restored || props.value === nextValue) {
|
|
844
|
+
const position = restored ? token.end : token.start + replacement.length;
|
|
845
|
+
textareaRef?.setSelectionRange(position, position);
|
|
846
|
+
}
|
|
464
847
|
syncCaret();
|
|
465
848
|
autoResize();
|
|
466
849
|
focus();
|
|
@@ -538,19 +921,28 @@ export function ChatComposer(props) {
|
|
|
538
921
|
if (commandsOpen()) {
|
|
539
922
|
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
540
923
|
event.preventDefault();
|
|
541
|
-
|
|
924
|
+
const index = matches.indexOf(selectedCommand());
|
|
925
|
+
const next = matches[nextChatCommandIndex(index, matches.length, event.key === "ArrowUp" ? -1 : 1)];
|
|
926
|
+
setActiveCommandKey(next ? chatCommandKey(next) : undefined);
|
|
927
|
+
setCommandMoved(true);
|
|
928
|
+
setAnnouncement(undefined);
|
|
542
929
|
return;
|
|
543
930
|
}
|
|
544
931
|
// Ctrl/⌘+Enter sends, also while suggestions are open.
|
|
545
|
-
|
|
932
|
+
const plain = !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
933
|
+
if (plain && (event.key === "Tab" || event.key === "Enter") && matches.length > 0) {
|
|
546
934
|
event.preventDefault();
|
|
547
935
|
const command = selectedCommand();
|
|
548
|
-
|
|
936
|
+
// A server row answering an earlier query may no longer match; wait for fresh results. Local rows match now.
|
|
937
|
+
const row = commandModel().rows.find(row => chatCommandKey(row.command) === selectedCommandKey());
|
|
938
|
+
const staleServerRow = row?.server && (searchResultsQuery() !== commandQuery()?.query || searchResultsScope() !== parsedCommandQuery().scope?.id);
|
|
939
|
+
if (staleServerRow) return;
|
|
940
|
+
if (command) void executeCommand(command, event.key === "Tab");
|
|
549
941
|
return;
|
|
550
942
|
}
|
|
551
943
|
if (event.key === "Escape") {
|
|
552
944
|
event.preventDefault();
|
|
553
|
-
|
|
945
|
+
setClosedAt(commandQuery()?.start);
|
|
554
946
|
return;
|
|
555
947
|
}
|
|
556
948
|
}
|
|
@@ -964,7 +1356,7 @@ export function ChatComposer(props) {
|
|
|
964
1356
|
children: part => _$ssr(_tmpl$10, Boolean(part.mention) ? "k2b-chat-composer__mention" : "", _$escape(part.text))
|
|
965
1357
|
})));
|
|
966
1358
|
}
|
|
967
|
-
})), mentions().length > 0 ? "k2b-chat-composer__textarea--highlighted" : "", _$ssrAttribute("value", _$escape(props.value, true), false), _$ssrAttribute("disabled", editBlocked(), true) + _$ssrAttribute("enterkeyhint", conversation() ? "enter" : _$escape(undefined, true), false) + _$ssrAttribute("placeholder", _$escape(props.placeholder ?? (running() ? chatMessages().addGuidance : conversation() ? chatMessages().writeConversationMessage : chatMessages().writeMessage), true), false) + _$ssrAttribute("aria-label", _$escape(props.inputLabel ?? messages().message, true), false) + _$ssrAttribute("role", suggestionsOpen() ? "combobox" : _$escape(undefined, true), false) + _$ssrAttribute("aria-autocomplete", suggestionsOpen() ? "list" : _$escape(undefined, true), false) + _$ssrAttribute("aria-controls", suggestionsOpen() ? _$escape(commandListId, true) : _$escape(undefined, true), false) + _$ssrAttribute("aria-expanded", suggestionsOpen() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("aria-activedescendant",
|
|
1359
|
+
})), mentions().length > 0 ? "k2b-chat-composer__textarea--highlighted" : "", _$ssrAttribute("value", _$escape(props.value, true), false), _$ssrAttribute("disabled", editBlocked(), true) + _$ssrAttribute("enterkeyhint", conversation() ? "enter" : _$escape(undefined, true), false) + _$ssrAttribute("placeholder", _$escape(props.placeholder ?? (running() ? chatMessages().addGuidance : conversation() ? chatMessages().writeConversationMessage : chatMessages().writeMessage), true), false) + _$ssrAttribute("aria-label", _$escape(props.inputLabel ?? messages().message, true), false) + _$ssrAttribute("role", suggestionsOpen() ? "combobox" : _$escape(undefined, true), false) + _$ssrAttribute("aria-autocomplete", suggestionsOpen() ? "list" : _$escape(undefined, true), false) + _$ssrAttribute("aria-controls", suggestionsOpen() ? _$escape(commandListId, true) : _$escape(undefined, true), false) + _$ssrAttribute("aria-expanded", suggestionsOpen() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("aria-activedescendant", emojiOpen() ? `${_$escape(commandListId, true)}-${_$escape(selectedCommandIndex(), true)}` : selectedCommand() ? _$escape(commandOptionId(selectedCommand()), true) : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
968
1360
|
get when() {
|
|
969
1361
|
return conversation();
|
|
970
1362
|
},
|
|
@@ -972,25 +1364,54 @@ export function ChatComposer(props) {
|
|
|
972
1364
|
return _$ssr(_tmpl$0);
|
|
973
1365
|
}
|
|
974
1366
|
})));
|
|
975
|
-
|
|
1367
|
+
const commandRowMap = createMemo(() => new Map(commandModel().rows.map(row => [row.command, row])));
|
|
1368
|
+
const commandGroupKey = group => group.scopeTitle ? "scope" : group.section ? `section:${group.section.id}` : "flat";
|
|
1369
|
+
const renderCommandRow = command => {
|
|
1370
|
+
const highlight = () => commandRowMap().get(command)?.highlight;
|
|
1371
|
+
const title = () => command.label ?? command.name;
|
|
1372
|
+
return _$ssr(_tmpl$13, _$ssrAttribute("id", _$escape(commandOptionId(command), true), false), _$ssrAttribute("aria-selected", _$escape(selectedCommand(), true) === _$escape(command, true), false) + _$ssrAttribute("aria-disabled", _$escape(Boolean(command.disabled || command.reason), true), false) + _$ssrAttribute("aria-describedby", command.reason ? `${_$escape(commandOptionId(command), true)}-reason` : _$escape(undefined, true), false) + _$ssrAttribute("data-reason", command.reason ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-active", selectedCommand() === command ? "true" : _$escape(undefined, true), false), _$ssrAttribute("class", _$escape(command.icon ?? "ti ti-slash", true), false), _$escape(_$createComponent(Show, {
|
|
1373
|
+
get when() {
|
|
1374
|
+
return highlight();
|
|
1375
|
+
},
|
|
1376
|
+
get fallback() {
|
|
1377
|
+
return title();
|
|
1378
|
+
},
|
|
1379
|
+
children: range => [title().slice(0, range()[0]), _$ssr(_tmpl$14, _$escape(title().slice(range()[0], range()[1]))), title().slice(range()[1])]
|
|
1380
|
+
})), _$escape(_$createComponent(Show, {
|
|
1381
|
+
get when() {
|
|
1382
|
+
return command.reason ?? command.description;
|
|
1383
|
+
},
|
|
1384
|
+
get children() {
|
|
1385
|
+
return _$ssr(_tmpl$11, _$ssrAttribute("id", command.reason ? `${_$escape(commandOptionId(command), true)}-reason` : _$escape(undefined, true), false), _$escape(command.reason ?? command.description));
|
|
1386
|
+
}
|
|
1387
|
+
})), _$escape(_$createComponent(Show, {
|
|
1388
|
+
get when() {
|
|
1389
|
+
return command.selected;
|
|
1390
|
+
},
|
|
1391
|
+
get children() {
|
|
1392
|
+
return _$ssr(_tmpl$12, _$escape(chatMessages().commandSelected));
|
|
1393
|
+
}
|
|
1394
|
+
})));
|
|
1395
|
+
};
|
|
1396
|
+
return _$ssr(_tmpl$25, _$escape(_$createComponent(Show, {
|
|
976
1397
|
get when() {
|
|
977
1398
|
return suggestionsOpen() || hasAccessory();
|
|
978
1399
|
},
|
|
979
1400
|
get children() {
|
|
980
|
-
return _$ssr(_tmpl$
|
|
1401
|
+
return _$ssr(_tmpl$20, _$ssrAttribute("data-overlay", hasAccessory() ? _$escape(undefined, true) : "true", false), _$ssrStyleProperty("visibility:", suggestionsOpen() ? "hidden" : _$escape(undefined, true)), _$ssrAttribute("inert", suggestionsOpen(), true), _$escape(accessory()), _$escape(_$createComponent(Show, {
|
|
981
1402
|
get when() {
|
|
982
1403
|
return emojiOpen();
|
|
983
1404
|
},
|
|
984
1405
|
get children() {
|
|
985
|
-
return _$ssr(_tmpl$
|
|
1406
|
+
return _$ssr(_tmpl$15, _$ssrAttribute("id", _$escape(commandListId, true), false), _$ssrAttribute("aria-label", _$escape(chatMessages().emojiSuggestions, true), false), _$escape(_$createComponent(For, {
|
|
986
1407
|
get each() {
|
|
987
1408
|
return emojiMatches();
|
|
988
1409
|
},
|
|
989
|
-
children: (entry, index) => _$ssr(_tmpl$
|
|
1410
|
+
children: (entry, index) => _$ssr(_tmpl$26, `${_$escape(commandListId, true)}-${_$escape(index(), true)}`, _$ssrAttribute("aria-selected", _$escape(index(), true) === _$escape(selectedCommandIndex(), true), false) + _$ssrAttribute("aria-label", _$escape(emojiName(entry, locale()), true), false) + _$ssrAttribute("data-active", index() === selectedCommandIndex() ? "true" : _$escape(undefined, true), false), _$escape(emojiInTone(entry, props.emoji?.skinTone ?? 0)), _$escape(emojiName(entry, locale())), _$escape(_$createComponent(Show, {
|
|
990
1411
|
get when() {
|
|
991
1412
|
return entry.shortcodes[0];
|
|
992
1413
|
},
|
|
993
|
-
children: code => _$ssr(_tmpl$
|
|
1414
|
+
children: code => _$ssr(_tmpl$27, _$escape(code()))
|
|
994
1415
|
})))
|
|
995
1416
|
})));
|
|
996
1417
|
}
|
|
@@ -999,31 +1420,70 @@ export function ChatComposer(props) {
|
|
|
999
1420
|
return commandsOpen();
|
|
1000
1421
|
},
|
|
1001
1422
|
get children() {
|
|
1002
|
-
return _$ssr(_tmpl$
|
|
1423
|
+
return _$ssr(_tmpl$19, _$ssrAttribute("id", _$escape(commandListId, true), false), _$ssrAttribute("aria-label", _$escape(chatMessages().commands, true), false) + _$ssrAttribute("aria-busy", searching() ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(For, {
|
|
1003
1424
|
get each() {
|
|
1004
|
-
return
|
|
1425
|
+
return commandModel().groups.map(commandGroupKey);
|
|
1005
1426
|
},
|
|
1006
|
-
children:
|
|
1427
|
+
children: key => {
|
|
1428
|
+
const group = createMemo(() => commandModel().groups.find(group => commandGroupKey(group) === key));
|
|
1429
|
+
const headingId = () => `${commandListId}-g-${key === "scope" ? "scope" : props.commandSections?.findIndex(section => section.id === group()?.section?.id)}`;
|
|
1430
|
+
const rows = () => group()?.rows.map(row => row.command) ?? [];
|
|
1431
|
+
return _$createComponent(Show, {
|
|
1432
|
+
when: key !== "flat",
|
|
1433
|
+
get fallback() {
|
|
1434
|
+
return _$createComponent(For, {
|
|
1435
|
+
get each() {
|
|
1436
|
+
return rows();
|
|
1437
|
+
},
|
|
1438
|
+
children: renderCommandRow
|
|
1439
|
+
});
|
|
1440
|
+
},
|
|
1441
|
+
get children() {
|
|
1442
|
+
return _$ssr(_tmpl$30, _$ssrAttribute("aria-labelledby", _$escape(headingId(), true), false), _$ssrAttribute("id", _$escape(headingId(), true), false), _$escape(group()?.scopeTitle ?? group()?.section?.label), _$escape(_$createComponent(Show, {
|
|
1443
|
+
get when() {
|
|
1444
|
+
return group()?.loading;
|
|
1445
|
+
},
|
|
1446
|
+
get children() {
|
|
1447
|
+
return _$ssr(_tmpl$28);
|
|
1448
|
+
}
|
|
1449
|
+
})), _$escape(_$createComponent(For, {
|
|
1450
|
+
get each() {
|
|
1451
|
+
return rows();
|
|
1452
|
+
},
|
|
1453
|
+
children: renderCommandRow
|
|
1454
|
+
})), _$escape(_$createComponent(Show, {
|
|
1455
|
+
get when() {
|
|
1456
|
+
return group()?.placeholder;
|
|
1457
|
+
},
|
|
1458
|
+
get children() {
|
|
1459
|
+
return _$ssr(_tmpl$29);
|
|
1460
|
+
}
|
|
1461
|
+
})));
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1007
1465
|
})), _$escape(_$createComponent(Show, {
|
|
1008
1466
|
get when() {
|
|
1009
|
-
return searching();
|
|
1467
|
+
return _$memo(() => !!(searching() && commandMatches().length === 0))() && !commandModel().groups.some(group => group.section || group.scopeTitle);
|
|
1010
1468
|
},
|
|
1011
1469
|
get children() {
|
|
1012
|
-
return _$ssr(_tmpl$
|
|
1470
|
+
return _$ssr(_tmpl$16, _$escape(messages().loading));
|
|
1013
1471
|
}
|
|
1014
1472
|
})), _$escape(_$createComponent(Show, {
|
|
1015
1473
|
get when() {
|
|
1016
1474
|
return searchError();
|
|
1017
1475
|
},
|
|
1018
1476
|
get children() {
|
|
1019
|
-
return _$ssr(_tmpl$
|
|
1477
|
+
return _$ssr(_tmpl$17, _$escape(messages().error));
|
|
1020
1478
|
}
|
|
1021
1479
|
})), _$escape(_$createComponent(Show, {
|
|
1022
1480
|
get when() {
|
|
1023
1481
|
return _$memo(() => !!(!searching() && !searchError()))() && commandMatches().length === 0;
|
|
1024
1482
|
},
|
|
1025
1483
|
get children() {
|
|
1026
|
-
return _$ssr(_tmpl$
|
|
1484
|
+
return _$ssr(_tmpl$18, !parsedCommandQuery().query.trim() ? _$escape(messages().noResults) : _$escape(chatMessages().commandsNothingFound({
|
|
1485
|
+
query: parsedCommandQuery().query.trim()
|
|
1486
|
+
})));
|
|
1027
1487
|
}
|
|
1028
1488
|
})));
|
|
1029
1489
|
}
|
|
@@ -1034,18 +1494,18 @@ export function ChatComposer(props) {
|
|
|
1034
1494
|
return showHint();
|
|
1035
1495
|
},
|
|
1036
1496
|
get children() {
|
|
1037
|
-
return _$ssr(_tmpl$
|
|
1497
|
+
return _$ssr(_tmpl$21, _$escape(_$createComponent(Show, {
|
|
1038
1498
|
get when() {
|
|
1039
1499
|
return dictationStatus();
|
|
1040
1500
|
},
|
|
1041
1501
|
get fallback() {
|
|
1042
|
-
return _$ssr(_tmpl$
|
|
1502
|
+
return _$ssr(_tmpl$31, _$escape(hint()));
|
|
1043
1503
|
},
|
|
1044
|
-
children: status => [_$ssr(_tmpl$
|
|
1504
|
+
children: status => [_$ssr(_tmpl$32, _$ssrAttribute("data-state", _$escape(dictation() ?? undefined, true), false), _$ssrAttribute("class", _$escape(status().icon, true), false), _$escape(status().text)), _$createComponent(Show, {
|
|
1045
1505
|
get when() {
|
|
1046
1506
|
return _$memo(() => dictation() === "refined")() ? props.microphone?.onRestoreOriginal : undefined;
|
|
1047
1507
|
},
|
|
1048
|
-
children: restore => _$ssr(_tmpl$
|
|
1508
|
+
children: restore => _$ssr(_tmpl$33, _$ssrAttribute("disabled", microphoneDisabled(), true), _$escape(chatMessages().restoreOriginal))
|
|
1049
1509
|
})]
|
|
1050
1510
|
})));
|
|
1051
1511
|
}
|
|
@@ -1054,16 +1514,16 @@ export function ChatComposer(props) {
|
|
|
1054
1514
|
return attachments().length > 0;
|
|
1055
1515
|
},
|
|
1056
1516
|
get children() {
|
|
1057
|
-
return _$ssr(_tmpl$
|
|
1517
|
+
return _$ssr(_tmpl$22, _$ssrAttribute("aria-label", _$escape(chatMessages().attachments, true), false), _$escape(_$createComponent(For, {
|
|
1058
1518
|
get each() {
|
|
1059
1519
|
return attachments();
|
|
1060
1520
|
},
|
|
1061
|
-
children: attachment => _$ssr(_tmpl$
|
|
1521
|
+
children: attachment => _$ssr(_tmpl$35, `k2b-chat-composer__attachment-content ${Boolean(attachment.action) ? "k2b-chat-composer__attachment-content--action" : ""}`, _$escape(_$createComponent(Show, {
|
|
1062
1522
|
get when() {
|
|
1063
1523
|
return attachment.href;
|
|
1064
1524
|
},
|
|
1065
1525
|
get fallback() {
|
|
1066
|
-
return _$ssr(_tmpl$
|
|
1526
|
+
return _$ssr(_tmpl$37, _$escape(_$createComponent(Show, {
|
|
1067
1527
|
get when() {
|
|
1068
1528
|
return _$memo(() => attachment.kind === "image")() && attachment.previewUrl;
|
|
1069
1529
|
},
|
|
@@ -1071,11 +1531,11 @@ export function ChatComposer(props) {
|
|
|
1071
1531
|
return _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(attachmentIcon(attachment), true), false));
|
|
1072
1532
|
},
|
|
1073
1533
|
get children() {
|
|
1074
|
-
return _$ssr(_tmpl$
|
|
1534
|
+
return _$ssr(_tmpl$36, _$ssrAttribute("src", _$escape(attachment.previewUrl, true), false) + _$ssrAttribute("alt", _$escape(attachment.alt ?? "", true), false));
|
|
1075
1535
|
}
|
|
1076
1536
|
})), _$escape(renderAttachmentCopy(attachment)));
|
|
1077
1537
|
},
|
|
1078
|
-
children: href => _$ssr(_tmpl$
|
|
1538
|
+
children: href => _$ssr(_tmpl$38, _$ssrAttribute("href", _$escape(href(), true), false), _$ssrAttribute("aria-label", _$escape(chatMessages().openInNewTab({
|
|
1079
1539
|
name: attachment.name
|
|
1080
1540
|
}), true), false), _$escape(_$createComponent(Show, {
|
|
1081
1541
|
get when() {
|
|
@@ -1085,14 +1545,14 @@ export function ChatComposer(props) {
|
|
|
1085
1545
|
return _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(attachmentIcon(attachment), true), false));
|
|
1086
1546
|
},
|
|
1087
1547
|
get children() {
|
|
1088
|
-
return _$ssr(_tmpl$
|
|
1548
|
+
return _$ssr(_tmpl$36, _$ssrAttribute("src", _$escape(attachment.previewUrl, true), false) + _$ssrAttribute("alt", _$escape(attachment.alt ?? "", true), false));
|
|
1089
1549
|
}
|
|
1090
1550
|
})), _$escape(renderAttachmentCopy(attachment)))
|
|
1091
1551
|
})), _$escape(_$createComponent(Show, {
|
|
1092
1552
|
get when() {
|
|
1093
1553
|
return attachment.action;
|
|
1094
1554
|
},
|
|
1095
|
-
children: action => _$ssr(_tmpl$
|
|
1555
|
+
children: action => _$ssr(_tmpl$39, _$ssrAttribute("disabled", blocked() || action().disabled, true), _$escape(_$createComponent(Show, {
|
|
1096
1556
|
get when() {
|
|
1097
1557
|
return action().icon;
|
|
1098
1558
|
},
|
|
@@ -1103,7 +1563,7 @@ export function ChatComposer(props) {
|
|
|
1103
1563
|
return props.onAttachmentsChange;
|
|
1104
1564
|
},
|
|
1105
1565
|
get children() {
|
|
1106
|
-
return _$ssr(_tmpl$
|
|
1566
|
+
return _$ssr(_tmpl$34, _$ssrAttribute("aria-label", _$escape(selectMessages().removeNamed({
|
|
1107
1567
|
name: attachment.name
|
|
1108
1568
|
}), true), false), _$ssrAttribute("disabled", blocked(), true));
|
|
1109
1569
|
}
|
|
@@ -1120,14 +1580,14 @@ export function ChatComposer(props) {
|
|
|
1120
1580
|
return props.error;
|
|
1121
1581
|
},
|
|
1122
1582
|
get children() {
|
|
1123
|
-
return _$ssr(_tmpl$
|
|
1583
|
+
return _$ssr(_tmpl$23, _$escape(props.error));
|
|
1124
1584
|
}
|
|
1125
|
-
}), formatRow(), _$ssr(_tmpl$
|
|
1585
|
+
}), formatRow(), _$ssr(_tmpl$40, _$escape(_$createComponent(Show, {
|
|
1126
1586
|
get when() {
|
|
1127
|
-
return
|
|
1587
|
+
return footer();
|
|
1128
1588
|
},
|
|
1129
1589
|
get fallback() {
|
|
1130
|
-
return [_$ssr(_tmpl$
|
|
1590
|
+
return [_$ssr(_tmpl$41, _$escape(props.footerTools), _$escape(_$createComponent(Show, {
|
|
1131
1591
|
get when() {
|
|
1132
1592
|
return hasAddMenu();
|
|
1133
1593
|
},
|
|
@@ -1159,16 +1619,28 @@ export function ChatComposer(props) {
|
|
|
1159
1619
|
image: model.image
|
|
1160
1620
|
}));
|
|
1161
1621
|
},
|
|
1622
|
+
get open() {
|
|
1623
|
+
return modelOpen();
|
|
1624
|
+
},
|
|
1625
|
+
onOpenChange: open => {
|
|
1626
|
+
if (!choosingModel()) return;
|
|
1627
|
+
setModelOpen(open);
|
|
1628
|
+
// Dropdown closes before committing the choice; let onValueChange settle it first.
|
|
1629
|
+
if (!open) queueMicrotask(() => {
|
|
1630
|
+
if (!modelOpen()) finishModelSelection(null);
|
|
1631
|
+
});
|
|
1632
|
+
},
|
|
1162
1633
|
get disabled() {
|
|
1163
|
-
return blocked() || running() || !props.onModelChange;
|
|
1634
|
+
return (choosingModel() ? blockedBesidesCommand() : blocked()) || running() || !props.onModelChange;
|
|
1164
1635
|
},
|
|
1165
1636
|
onValueChange: modelId => {
|
|
1166
1637
|
props.onModelChange?.(modelId);
|
|
1638
|
+
finishModelSelection(modelId);
|
|
1167
1639
|
queueMicrotask(focus);
|
|
1168
1640
|
}
|
|
1169
1641
|
});
|
|
1170
1642
|
}
|
|
1171
|
-
})), _$escape(props.modelDetails)), _$ssr(_tmpl$
|
|
1643
|
+
})), _$escape(props.modelDetails)), _$ssr(_tmpl$42, _$escape(_$createComponent(For, {
|
|
1172
1644
|
get each() {
|
|
1173
1645
|
return props.contextActions;
|
|
1174
1646
|
},
|
|
@@ -1209,7 +1681,9 @@ export function ChatComposer(props) {
|
|
|
1209
1681
|
}))
|
|
1210
1682
|
})), _$escape(props.submitTools), _$escape(microphoneControl()), _$escape(sendControl()))];
|
|
1211
1683
|
},
|
|
1212
|
-
children
|
|
1684
|
+
get children() {
|
|
1685
|
+
return footer();
|
|
1686
|
+
}
|
|
1213
1687
|
})))];
|
|
1214
1688
|
},
|
|
1215
1689
|
get children() {
|
|
@@ -1218,9 +1692,9 @@ export function ChatComposer(props) {
|
|
|
1218
1692
|
return props.error;
|
|
1219
1693
|
},
|
|
1220
1694
|
get children() {
|
|
1221
|
-
return _$ssr(_tmpl$
|
|
1695
|
+
return _$ssr(_tmpl$23, _$escape(props.error));
|
|
1222
1696
|
}
|
|
1223
|
-
}), formatRow(), _$ssr(_tmpl$
|
|
1697
|
+
}), formatRow(), _$ssr(_tmpl$24, _$escape(attachControl()), _$escape(fileInput()), _$escape(field()), _$escape(formatToggle()), _$escape(emojiButton()), _$escape(microphoneControl()), _$escape(sendControl()))];
|
|
1224
1698
|
}
|
|
1225
1699
|
})), _$escape(_$createComponent(Show, {
|
|
1226
1700
|
get when() {
|
|
@@ -1241,6 +1715,23 @@ export function ChatComposer(props) {
|
|
|
1241
1715
|
},
|
|
1242
1716
|
onDrop: files => void runFiles(files)
|
|
1243
1717
|
})
|
|
1718
|
+
})), _$escape(_$createComponent(Show, {
|
|
1719
|
+
get when() {
|
|
1720
|
+
return suggestionAnnouncement();
|
|
1721
|
+
},
|
|
1722
|
+
keyed: true,
|
|
1723
|
+
children: announced => _$ssr(_tmpl$43, _$escape(announced.text))
|
|
1724
|
+
})), _$escape(_$createComponent(Show, {
|
|
1725
|
+
get when() {
|
|
1726
|
+
return _$memo(() => !!!suggestionsOpen())() && commandStatus();
|
|
1727
|
+
},
|
|
1728
|
+
children: status => _$ssr(_tmpl$44, _$ssrAttribute("data-tone", _$escape(status().tone, true), false), _$ssrAttribute("class", status().tone === "busy" ? "ti ti-loader-2 k2b-spin" : status().tone === "done" ? "ti ti-check" : "ti ti-alert-circle", false), _$escape(status().text))
|
|
1729
|
+
})), _$escape(_$createComponent(Show, {
|
|
1730
|
+
get when() {
|
|
1731
|
+
return _$memo(() => !!suggestionsOpen())() && announcement();
|
|
1732
|
+
},
|
|
1733
|
+
keyed: true,
|
|
1734
|
+
children: announced => _$ssr(_tmpl$45, _$escape(announced.text))
|
|
1244
1735
|
})));
|
|
1245
1736
|
}
|
|
1246
1737
|
//# sourceMappingURL=ChatComposer.js.map
|