@k2b/ui 0.18.0 → 0.19.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 +3 -2
- package/dist/browser/actions/ContextMenu.js +3 -1
- package/dist/browser/actions/ContextMenu.js.map +1 -1
- package/dist/browser/actions/Dropdown.js +91 -46
- package/dist/browser/actions/Dropdown.js.map +1 -1
- package/dist/browser/actions/FilterChip.js +4 -1
- package/dist/browser/actions/FilterChip.js.map +1 -1
- package/dist/browser/chat/ChatComposer.js +1293 -534
- package/dist/browser/chat/ChatComposer.js.map +1 -1
- package/dist/browser/chat/chat-behavior.js +9 -0
- package/dist/browser/chat/chat-behavior.js.map +1 -1
- package/dist/browser/chat/index.js.map +1 -1
- package/dist/browser/chat/types.js.map +1 -1
- package/dist/browser/content/Calendar.js +579 -533
- package/dist/browser/content/Calendar.js.map +1 -1
- package/dist/browser/content/Lightbox.js +76 -40
- package/dist/browser/content/Lightbox.js.map +1 -1
- package/dist/browser/content/PdfPreview.js +146 -79
- package/dist/browser/content/PdfPreview.js.map +1 -1
- package/dist/browser/content/Timeline.js +1038 -0
- package/dist/browser/content/Timeline.js.map +1 -0
- package/dist/browser/content/VideoPlayer.js +29 -9
- package/dist/browser/content/VideoPlayer.js.map +1 -1
- package/dist/browser/content/chart-inspection.js +7 -2
- package/dist/browser/content/chart-inspection.js.map +1 -1
- package/dist/browser/content/index.js +1 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/content/timeline-model.js +579 -0
- package/dist/browser/content/timeline-model.js.map +1 -0
- package/dist/browser/feedback/dialog-core.js +26 -14
- package/dist/browser/feedback/dialog-core.js.map +1 -1
- package/dist/browser/inputs/ChoiceChips.js +66 -0
- package/dist/browser/inputs/ChoiceChips.js.map +1 -0
- package/dist/browser/inputs/ChoiceGroups.js +53 -12
- package/dist/browser/inputs/ChoiceGroups.js.map +1 -1
- package/dist/browser/inputs/FileInputs.js +48 -33
- package/dist/browser/inputs/FileInputs.js.map +1 -1
- package/dist/browser/inputs/SignatureInput.js +558 -0
- package/dist/browser/inputs/SignatureInput.js.map +1 -0
- package/dist/browser/inputs/emoji/EmojiPicker.js +841 -0
- package/dist/browser/inputs/emoji/EmojiPicker.js.map +1 -0
- package/dist/browser/inputs/emoji/emoji-data.js +1903 -0
- package/dist/browser/inputs/emoji/emoji-data.js.map +1 -0
- package/dist/browser/inputs/emoji/emoji-index.js +209 -0
- package/dist/browser/inputs/emoji/emoji-index.js.map +1 -0
- package/dist/browser/inputs/emoji/emoji-messages.js +64 -0
- package/dist/browser/inputs/emoji/emoji-messages.js.map +1 -0
- package/dist/browser/inputs/index.js +5 -0
- package/dist/browser/inputs/index.js.map +1 -1
- package/dist/browser/inputs/markdown/actions.js +48 -2
- package/dist/browser/inputs/markdown/actions.js.map +1 -1
- package/dist/browser/inputs/markdown/code-zone.js +38 -2
- package/dist/browser/inputs/markdown/code-zone.js.map +1 -1
- package/dist/browser/inputs/signature-messages.js +39 -0
- package/dist/browser/inputs/signature-messages.js.map +1 -0
- package/dist/browser/inputs/signature.js +208 -0
- package/dist/browser/inputs/signature.js.map +1 -0
- package/dist/browser/internal/render-error.js +49 -0
- package/dist/browser/internal/render-error.js.map +1 -0
- package/dist/browser/intl/messages.js +108 -0
- package/dist/browser/intl/messages.js.map +1 -1
- package/dist/browser/layout/AppWorkspace.js +321 -222
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/FloatingWindow.js +4 -1
- package/dist/browser/layout/FloatingWindow.js.map +1 -1
- package/dist/browser/layout/SidebarItemPreview.js +5 -1
- package/dist/browser/layout/SidebarItemPreview.js.map +1 -1
- package/dist/browser/layout/index.js.map +1 -1
- package/dist/browser/surfaces/ResourceCard.js +212 -0
- package/dist/browser/surfaces/ResourceCard.js.map +1 -0
- package/dist/browser/surfaces/index.js +1 -0
- package/dist/browser/surfaces/index.js.map +1 -1
- package/dist/licenses/emojibase-data.txt +21 -0
- package/dist/ssr/actions/ContextMenu.js +3 -1
- package/dist/ssr/actions/ContextMenu.js.map +1 -1
- package/dist/ssr/actions/Dropdown.js +49 -12
- package/dist/ssr/actions/Dropdown.js.map +1 -1
- package/dist/ssr/actions/FilterChip.js +4 -1
- package/dist/ssr/actions/FilterChip.js.map +1 -1
- package/dist/ssr/chat/ChatComposer.js +805 -214
- package/dist/ssr/chat/ChatComposer.js.map +1 -1
- package/dist/ssr/chat/chat-behavior.js +9 -0
- package/dist/ssr/chat/chat-behavior.js.map +1 -1
- package/dist/ssr/chat/index.js.map +1 -1
- package/dist/ssr/chat/types.js.map +1 -1
- package/dist/ssr/content/Calendar.js +183 -142
- package/dist/ssr/content/Calendar.js.map +1 -1
- package/dist/ssr/content/Lightbox.js +19 -5
- package/dist/ssr/content/Lightbox.js.map +1 -1
- package/dist/ssr/content/PdfPreview.js +77 -36
- package/dist/ssr/content/PdfPreview.js.map +1 -1
- package/dist/ssr/content/Timeline.js +804 -0
- package/dist/ssr/content/Timeline.js.map +1 -0
- package/dist/ssr/content/VideoPlayer.js +29 -9
- package/dist/ssr/content/VideoPlayer.js.map +1 -1
- package/dist/ssr/content/chart-inspection.js +7 -2
- package/dist/ssr/content/chart-inspection.js.map +1 -1
- package/dist/ssr/content/index.js +1 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/content/timeline-model.js +579 -0
- package/dist/ssr/content/timeline-model.js.map +1 -0
- package/dist/ssr/feedback/dialog-core.js +26 -14
- package/dist/ssr/feedback/dialog-core.js.map +1 -1
- package/dist/ssr/inputs/ChoiceChips.js +66 -0
- package/dist/ssr/inputs/ChoiceChips.js.map +1 -0
- package/dist/ssr/inputs/ChoiceGroups.js +12 -4
- package/dist/ssr/inputs/ChoiceGroups.js.map +1 -1
- package/dist/ssr/inputs/FileInputs.js +19 -4
- package/dist/ssr/inputs/FileInputs.js.map +1 -1
- package/dist/ssr/inputs/SignatureInput.js +404 -0
- package/dist/ssr/inputs/SignatureInput.js.map +1 -0
- package/dist/ssr/inputs/emoji/EmojiPicker.js +551 -0
- package/dist/ssr/inputs/emoji/EmojiPicker.js.map +1 -0
- package/dist/ssr/inputs/emoji/emoji-data.js +1903 -0
- package/dist/ssr/inputs/emoji/emoji-data.js.map +1 -0
- package/dist/ssr/inputs/emoji/emoji-index.js +209 -0
- package/dist/ssr/inputs/emoji/emoji-index.js.map +1 -0
- package/dist/ssr/inputs/emoji/emoji-messages.js +64 -0
- package/dist/ssr/inputs/emoji/emoji-messages.js.map +1 -0
- package/dist/ssr/inputs/index.js +5 -0
- package/dist/ssr/inputs/index.js.map +1 -1
- package/dist/ssr/inputs/markdown/actions.js +48 -2
- package/dist/ssr/inputs/markdown/actions.js.map +1 -1
- package/dist/ssr/inputs/markdown/code-zone.js +38 -2
- package/dist/ssr/inputs/markdown/code-zone.js.map +1 -1
- package/dist/ssr/inputs/signature-messages.js +39 -0
- package/dist/ssr/inputs/signature-messages.js.map +1 -0
- package/dist/ssr/inputs/signature.js +208 -0
- package/dist/ssr/inputs/signature.js.map +1 -0
- package/dist/ssr/internal/render-error.js +49 -0
- package/dist/ssr/internal/render-error.js.map +1 -0
- package/dist/ssr/intl/messages.js +108 -0
- package/dist/ssr/intl/messages.js.map +1 -1
- package/dist/ssr/layout/AppWorkspace.js +112 -41
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/FloatingWindow.js +4 -1
- package/dist/ssr/layout/FloatingWindow.js.map +1 -1
- package/dist/ssr/layout/SidebarItemPreview.js +5 -1
- package/dist/ssr/layout/SidebarItemPreview.js.map +1 -1
- package/dist/ssr/layout/index.js.map +1 -1
- package/dist/ssr/surfaces/ResourceCard.js +115 -0
- package/dist/ssr/surfaces/ResourceCard.js.map +1 -0
- 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/Dropdown.d.ts +13 -1
- package/dist/types/actions/FilterChip.d.ts +5 -0
- package/dist/types/chat/ChatComposer.d.ts +24 -1
- package/dist/types/chat/chat-behavior.d.ts +15 -0
- package/dist/types/chat/index.d.ts +1 -1
- package/dist/types/chat/types.d.ts +34 -0
- package/dist/types/content/Calendar.d.ts +28 -8
- package/dist/types/content/Lightbox.d.ts +8 -0
- package/dist/types/content/Timeline.d.ts +49 -0
- package/dist/types/content/index.d.ts +4 -2
- package/dist/types/content/timeline-model.d.ts +148 -0
- package/dist/types/feedback/dialog-core.d.ts +1 -1
- package/dist/types/inputs/ChoiceChips.d.ts +20 -0
- package/dist/types/inputs/ChoiceGroups.d.ts +11 -2
- package/dist/types/inputs/FileInputs.d.ts +6 -0
- package/dist/types/inputs/SignatureInput.d.ts +22 -0
- package/dist/types/inputs/emoji/EmojiPicker.d.ts +42 -0
- package/dist/types/inputs/emoji/emoji-data.d.ts +2 -0
- package/dist/types/inputs/emoji/emoji-index.d.ts +109 -0
- package/dist/types/inputs/emoji/emoji-messages.d.ts +30 -0
- package/dist/types/inputs/index.d.ts +10 -0
- package/dist/types/inputs/markdown/actions.d.ts +4 -0
- package/dist/types/inputs/markdown/code-zone.d.ts +7 -0
- package/dist/types/inputs/signature-messages.d.ts +11 -0
- package/dist/types/inputs/signature.d.ts +72 -0
- package/dist/types/internal/render-error.d.ts +13 -0
- package/dist/types/intl/messages.d.ts +54 -0
- package/dist/types/layout/AppWorkspace.d.ts +17 -0
- package/dist/types/layout/SidebarItemPreview.d.ts +2 -0
- package/dist/types/layout/index.d.ts +1 -1
- package/dist/types/surfaces/ResourceCard.d.ts +31 -0
- package/dist/types/surfaces/index.d.ts +2 -0
- package/package.json +5 -4
|
@@ -1,58 +1,158 @@
|
|
|
1
1
|
import { mergeProps as _$mergeProps } from "solid-js/web";
|
|
2
|
-
import { createComponent as _$createComponent } from "solid-js/web";
|
|
3
2
|
import { ssrStyleProperty as _$ssrStyleProperty } from "solid-js/web";
|
|
4
3
|
import { memo as _$memo } from "solid-js/web";
|
|
4
|
+
import { createComponent as _$createComponent } from "solid-js/web";
|
|
5
5
|
import { ssr as _$ssr } from "solid-js/web";
|
|
6
6
|
import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
|
|
7
7
|
import { escape as _$escape } from "solid-js/web";
|
|
8
8
|
var _tmpl$ = ["<span><strong", ">", "</strong></span>"],
|
|
9
|
-
_tmpl$2 = ["<
|
|
10
|
-
_tmpl$3 = ["<
|
|
11
|
-
_tmpl$4 =
|
|
12
|
-
_tmpl$5 = ["<div
|
|
13
|
-
_tmpl$6 =
|
|
14
|
-
_tmpl$7 =
|
|
15
|
-
_tmpl$8 = ["<div class=\"k2b-chat-
|
|
16
|
-
_tmpl$9 = ["<div class=\"k2b-chat-
|
|
17
|
-
_tmpl$0 =
|
|
18
|
-
_tmpl$1 = ["<
|
|
19
|
-
_tmpl$10 = ["<
|
|
20
|
-
_tmpl$11 = ["<div class=\"k2b-chat-
|
|
21
|
-
_tmpl$12 = ["<
|
|
22
|
-
_tmpl$13 = ["<div class=\"
|
|
23
|
-
_tmpl$14 = ["<
|
|
24
|
-
_tmpl$15 = ["<
|
|
25
|
-
_tmpl$16 = ["<
|
|
26
|
-
_tmpl$17 = ["<
|
|
27
|
-
_tmpl$18 = "<
|
|
28
|
-
_tmpl$19 = ["<
|
|
29
|
-
_tmpl$20 = ["<div class=\"k2b-chat-
|
|
30
|
-
_tmpl$21 = ["<div class=\"k2b-chat-
|
|
9
|
+
_tmpl$2 = ["<input class=\"k2b-sr-only\" type=\"file\" tabindex=\"-1\" aria-hidden=\"true\"", "", ">"],
|
|
10
|
+
_tmpl$3 = ["<i", " aria-hidden=\"true\"></i>"],
|
|
11
|
+
_tmpl$4 = "<i class=\"ti ti-letter-case\" aria-hidden=\"true\"></i>",
|
|
12
|
+
_tmpl$5 = ["<div", " class=\"k2b-chat-composer__format\" role=\"group\"", ">", "</div>"],
|
|
13
|
+
_tmpl$6 = "<i class=\"ti ti-mood-smile\" aria-hidden=\"true\"></i>",
|
|
14
|
+
_tmpl$7 = "<i class=\"ti ti-chevron-up\" aria-hidden=\"true\"></i>",
|
|
15
|
+
_tmpl$8 = ["<div class=\"k2b-chat-composer__microphone\">", "", "</div>"],
|
|
16
|
+
_tmpl$9 = ["<div class=\"k2b-chat-composer__highlight\" aria-hidden=\"true\">", "\n</div>"],
|
|
17
|
+
_tmpl$0 = "<textarea class=\"k2b-chat-composer__measure\" rows=\"1\" tabindex=\"-1\" aria-hidden=\"true\" readonly></textarea>",
|
|
18
|
+
_tmpl$1 = ["<div class=\"k2b-chat-composer__input\" role=\"group\"", ">", "<textarea class=\"", "\" rows=\"1\"", "", "></textarea>", "</div>"],
|
|
19
|
+
_tmpl$10 = ["<span class=\"", "\">", "</span>"],
|
|
20
|
+
_tmpl$11 = ["<div class=\"k2b-chat-composer-accessory\"><div", " class=\"k2b-chat-composer__commands\" role=\"listbox\"", ">", "</div></div>"],
|
|
21
|
+
_tmpl$12 = ["<div role=\"status\" class=\"k2b-chat-composer__loading\"><i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i><strong>", "</strong></div>"],
|
|
22
|
+
_tmpl$13 = ["<div role=\"status\"><i class=\"ti ti-alert-circle\"></i> ", "</div>"],
|
|
23
|
+
_tmpl$14 = ["<div role=\"status\">", "</div>"],
|
|
24
|
+
_tmpl$15 = ["<div class=\"k2b-chat-composer-accessory\"><div", " class=\"k2b-chat-composer__commands\" role=\"listbox\"", ">", "", "", "", "</div></div>"],
|
|
25
|
+
_tmpl$16 = ["<div class=\"k2b-chat-composer-slot\"", "><div style=\"", "\"", ">", "</div>", "", "</div>"],
|
|
26
|
+
_tmpl$17 = ["<div class=\"k2b-chat-composer__hint\" aria-live=\"polite\">", "</div>"],
|
|
27
|
+
_tmpl$18 = ["<div class=\"k2b-chat-composer__attachments\" role=\"list\"", " tabindex=\"0\">", "</div>"],
|
|
28
|
+
_tmpl$19 = ["<div class=\"k2b-chat-composer__error\" role=\"alert\"><i class=\"ti ti-alert-circle\" aria-hidden=\"true\"></i>", "</div>"],
|
|
29
|
+
_tmpl$20 = ["<div class=\"k2b-chat-composer__row\">", "", "<div class=\"k2b-chat-composer__field\">", "<div class=\"k2b-chat-composer__field-tools\">", "", "", "</div></div>", "</div>"],
|
|
30
|
+
_tmpl$21 = ["<div class=\"k2b-chat-composer-shell\">", "<section class=\"", "\"", " role=\"group\"", ">", "", "", "", "</section></div>"],
|
|
31
|
+
_tmpl$22 = ["<button id=\"", "\" type=\"button\" role=\"option\" tabindex=\"-1\"", "><span class=\"k2b-chat-composer__emoji-glyph\" aria-hidden=\"true\">", "</span><strong>", "</strong>", "</button>"],
|
|
32
|
+
_tmpl$23 = ["<small>:", ":</small>"],
|
|
33
|
+
_tmpl$24 = ["<button id=\"", "\" type=\"button\" role=\"option\" tabindex=\"-1\"", "><i", " aria-hidden=\"true\"></i><strong>", "</strong><small>", "</small></button>"],
|
|
34
|
+
_tmpl$25 = ["<span class=\"k2b-chat-composer__hint-text\">", "</span>"],
|
|
35
|
+
_tmpl$26 = ["<span class=\"k2b-chat-composer__dictation\"", "><i", " aria-hidden=\"true\"></i><span>", "</span></span>"],
|
|
36
|
+
_tmpl$27 = ["<button type=\"button\" class=\"k2b-chat-composer__hint-action\"", ">", "</button>"],
|
|
37
|
+
_tmpl$28 = ["<button type=\"button\" class=\"k2b-chat-composer__attachment-remove\"", "", "><i class=\"ti ti-x\" aria-hidden=\"true\"></i></button>"],
|
|
38
|
+
_tmpl$29 = ["<div class=\"k2b-chat-composer__attachment\" role=\"listitem\"><div class=\"", "\">", "", "</div>", "</div>"],
|
|
39
|
+
_tmpl$30 = ["<img", ">"],
|
|
40
|
+
_tmpl$31 = ["<div class=\"k2b-chat-composer__attachment-identity\">", "", "</div>"],
|
|
41
|
+
_tmpl$32 = ["<a class=\"k2b-chat-composer__attachment-link\"", " target=\"_blank\" rel=\"noopener noreferrer\"", ">", "", "</a>"],
|
|
42
|
+
_tmpl$33 = ["<button type=\"button\" class=\"k2b-chat-composer__attachment-action\"", ">", "", "</button>"],
|
|
43
|
+
_tmpl$34 = ["<footer class=\"k2b-chat-composer__footer\">", "</footer>"],
|
|
44
|
+
_tmpl$35 = ["<div class=\"k2b-chat-composer__tools\">", "", "", "", "", "", "", "</div>"],
|
|
45
|
+
_tmpl$36 = ["<div class=\"k2b-chat-composer__submit\">", "", "", "", "", "</div>"];
|
|
31
46
|
import { children, createEffect, createMemo, createSignal, createUniqueId, For, onCleanup, onMount, Show, untrack } from "solid-js";
|
|
32
47
|
import { Dropdown } from "../actions/Dropdown";
|
|
33
48
|
import { Tooltip } from "../feedback/Tooltip";
|
|
49
|
+
import { replaceTextareaRange } from "../inputs/editor-dom";
|
|
50
|
+
import { completedEmojiShortcode, emojiInTone, emojiName, emojiShortcodeQuery, loadEmojiIndex, suggestEmoji } from "../inputs/emoji/emoji-index";
|
|
34
51
|
import { FileDropTarget } from "../inputs/FileDropTarget";
|
|
52
|
+
import { toggleBulletList, toggleCodeBlock, toggleInlineMarker, toggleQuote } from "../inputs/markdown/actions";
|
|
53
|
+
import { openCodeFence } from "../inputs/markdown/code-zone";
|
|
35
54
|
import { SelectChip } from "../inputs/SelectChip";
|
|
55
|
+
import { useLocale } from "../intl/locale";
|
|
36
56
|
import { useUiMessages } from "../intl/messages";
|
|
37
57
|
import { ChatContextUsage as ContextUsage } from "./ChatPrimitives";
|
|
38
|
-
import { executeChatAction, nextChatCommandIndex, reportChatFailure, runChatSubmission } from "./chat-behavior";
|
|
58
|
+
import { conversationEnterAction, executeChatAction, nextChatCommandIndex, reportChatFailure, runChatSubmission } from "./chat-behavior";
|
|
39
59
|
import { chatCommandQuery, chatMentionSegments, reconcileChatMentions } from "./composer-document";
|
|
40
60
|
const composerMaxInputHeight = 309;
|
|
61
|
+
/** Emoji suggestions for a `:shortcode`, as many as fit the list without scrolling on a phone. */
|
|
62
|
+
const emojiSuggestionLimit = 8;
|
|
41
63
|
// Keep editor undo text within approximately 2 MiB, plus the current edit.
|
|
42
64
|
const composerUndoBudgetChars = 1_000_000;
|
|
65
|
+
const insertComposerLink = textarea => {
|
|
66
|
+
const {
|
|
67
|
+
value,
|
|
68
|
+
selectionStart,
|
|
69
|
+
selectionEnd
|
|
70
|
+
} = textarea;
|
|
71
|
+
const label = value.slice(selectionStart, selectionEnd);
|
|
72
|
+
replaceTextareaRange(textarea, selectionStart, selectionEnd, `[${label}]()`);
|
|
73
|
+
// Without a selection the label comes first, otherwise the address.
|
|
74
|
+
const caret = label ? selectionStart + label.length + 3 : selectionStart + 1;
|
|
75
|
+
textarea.setSelectionRange(caret, caret);
|
|
76
|
+
};
|
|
77
|
+
const formatTools = [{
|
|
78
|
+
id: "bold",
|
|
79
|
+
icon: "ti ti-bold",
|
|
80
|
+
label: m => m.boldShortcut,
|
|
81
|
+
run: textarea => toggleInlineMarker(textarea, "**"),
|
|
82
|
+
key: "b"
|
|
83
|
+
}, {
|
|
84
|
+
id: "italic",
|
|
85
|
+
icon: "ti ti-italic",
|
|
86
|
+
label: m => m.italicShortcut,
|
|
87
|
+
run: textarea => toggleInlineMarker(textarea, "*"),
|
|
88
|
+
key: "i"
|
|
89
|
+
}, {
|
|
90
|
+
id: "strikethrough",
|
|
91
|
+
icon: "ti ti-strikethrough",
|
|
92
|
+
label: m => m.strikethroughShortcut,
|
|
93
|
+
run: textarea => toggleInlineMarker(textarea, "~~"),
|
|
94
|
+
shiftCode: "KeyX"
|
|
95
|
+
}, {
|
|
96
|
+
id: "code",
|
|
97
|
+
icon: "ti ti-code",
|
|
98
|
+
label: m => m.inlineCodeShortcut,
|
|
99
|
+
run: textarea => toggleInlineMarker(textarea, "`"),
|
|
100
|
+
key: "e"
|
|
101
|
+
}, {
|
|
102
|
+
id: "code-block",
|
|
103
|
+
icon: "ti ti-source-code",
|
|
104
|
+
label: m => m.codeBlock,
|
|
105
|
+
run: toggleCodeBlock
|
|
106
|
+
}, {
|
|
107
|
+
id: "bullet-list",
|
|
108
|
+
icon: "ti ti-list",
|
|
109
|
+
label: m => m.bulletListShortcut,
|
|
110
|
+
run: toggleBulletList,
|
|
111
|
+
shiftCode: "Digit8"
|
|
112
|
+
}, {
|
|
113
|
+
id: "quote",
|
|
114
|
+
icon: "ti ti-quote",
|
|
115
|
+
label: m => m.quote,
|
|
116
|
+
run: toggleQuote
|
|
117
|
+
}, {
|
|
118
|
+
id: "link",
|
|
119
|
+
icon: "ti ti-link",
|
|
120
|
+
label: m => m.insertLink,
|
|
121
|
+
run: insertComposerLink
|
|
122
|
+
}];
|
|
123
|
+
const formatShortcut = event => {
|
|
124
|
+
if (event.altKey || !(event.ctrlKey || event.metaKey)) return undefined;
|
|
125
|
+
return formatTools.find(tool => event.shiftKey ? tool.shiftCode === event.code : tool.key === event.key.toLowerCase());
|
|
126
|
+
};
|
|
127
|
+
const touchOnlyQuery = "(any-pointer: coarse) and (not (any-pointer: fine))";
|
|
128
|
+
const isTouchOnly = () => typeof matchMedia === "function" && matchMedia(touchOnlyQuery).matches;
|
|
129
|
+
/** How long a press on the microphone lasts before it asks for a voice message instead of dictation. */
|
|
130
|
+
const microphoneHoldMs = 500;
|
|
131
|
+
/** Keeps the field focused (and a phone's keyboard open) when a composer button is pressed with a mouse or finger. */
|
|
132
|
+
const keepFieldFocus = event => event.preventDefault();
|
|
43
133
|
const attachmentIcon = attachment => attachment.icon ?? (attachment.kind === "image" ? "ti ti-photo" : attachment.kind === "resource" ? "ti ti-link" : "ti ti-file");
|
|
134
|
+
const hasContent = items => items.some(item => item != null && typeof item !== "boolean" && item !== "");
|
|
44
135
|
export function ChatComposer(props) {
|
|
45
136
|
const accessory = children(() => props.accessory);
|
|
46
|
-
const hasAccessory = () => accessory.toArray()
|
|
137
|
+
const hasAccessory = () => hasContent(accessory.toArray());
|
|
138
|
+
const hint = children(() => props.hint);
|
|
47
139
|
const messages = useUiMessages();
|
|
48
140
|
const commandListId = `k2b-chat-commands-${createUniqueId().replace(/[^A-Za-z0-9_-]/g, "-")}`;
|
|
141
|
+
const formatGroupId = `${commandListId}-format`;
|
|
49
142
|
const [selectedCommandIndex, setSelectedCommandIndex] = createSignal(0);
|
|
50
143
|
const [addingFiles, setAddingFiles] = createSignal(false);
|
|
51
144
|
const [submitting, setSubmitting] = createSignal(false);
|
|
52
145
|
let composerRef;
|
|
53
146
|
let textareaRef;
|
|
54
147
|
let fileInputRef;
|
|
148
|
+
let measureRef;
|
|
149
|
+
let microphoneRef;
|
|
55
150
|
let sawRunning = props.state === "running";
|
|
151
|
+
const conversation = () => props.variant === "conversation";
|
|
152
|
+
const [formattingOpen, setFormattingOpen] = createSignal(false);
|
|
153
|
+
const [microphoneMenuOpen, setMicrophoneMenuOpen] = createSignal(false);
|
|
154
|
+
const dictation = () => props.microphone?.dictation ?? null;
|
|
155
|
+
const showHint = () => conversation() || dictation() !== null || hasContent(hint.toArray());
|
|
56
156
|
const state = () => props.state ?? "idle";
|
|
57
157
|
const running = () => state() === "running";
|
|
58
158
|
const stopping = () => state() === "stopping";
|
|
@@ -69,15 +169,29 @@ export function ChatComposer(props) {
|
|
|
69
169
|
let highlightRef;
|
|
70
170
|
let commandListRef;
|
|
71
171
|
const mentions = () => props.mentions ?? [];
|
|
172
|
+
const locale = useLocale();
|
|
173
|
+
const [emojiIndex, setEmojiIndex] = createSignal();
|
|
174
|
+
const emojiQuery = createMemo(() => !props.emoji || dismissed() || composing() ? null : emojiShortcodeQuery(props.value, caret(), selectionEnd()));
|
|
175
|
+
// The emoji data loads with the first `:shortcode`; a failed load only means no suggestions.
|
|
176
|
+
createEffect(() => {
|
|
177
|
+
if (emojiQuery() && !emojiIndex()) loadEmojiIndex().then(setEmojiIndex, () => undefined);
|
|
178
|
+
});
|
|
179
|
+
const emojiMatches = createMemo(() => {
|
|
180
|
+
const token = emojiQuery();
|
|
181
|
+
const index = emojiIndex();
|
|
182
|
+
return token && index ? suggestEmoji(index, token.query, emojiSuggestionLimit) : [];
|
|
183
|
+
});
|
|
184
|
+
const emojiOpen = () => emojiMatches().length > 0;
|
|
72
185
|
const commandQuery = createMemo(() => dismissed() || composing() ? null : chatCommandQuery(props.value, caret(), selectionEnd()));
|
|
73
186
|
const commandMatches = createMemo(() => {
|
|
74
187
|
const token = commandQuery();
|
|
75
188
|
if (!token) return [];
|
|
76
189
|
return [...(props.commands ?? []).filter(command => command.name.toLowerCase().includes(token.query.toLowerCase())), ...searchResults()];
|
|
77
190
|
});
|
|
78
|
-
const commandsOpen = () => Boolean(commandQuery() && (props.commands?.length || props.searchCommands));
|
|
191
|
+
const commandsOpen = () => Boolean(!emojiOpen() && commandQuery() && (props.commands?.length || props.searchCommands));
|
|
192
|
+
const suggestionsOpen = () => commandsOpen() || emojiOpen();
|
|
79
193
|
createEffect(() => {
|
|
80
|
-
if (!
|
|
194
|
+
if (!suggestionsOpen()) return;
|
|
81
195
|
let disposed = false;
|
|
82
196
|
const measure = () => {
|
|
83
197
|
if (disposed || !commandListRef) return;
|
|
@@ -174,7 +288,10 @@ export function ChatComposer(props) {
|
|
|
174
288
|
setSelectionEnd(textareaRef?.selectionEnd ?? props.value.length);
|
|
175
289
|
};
|
|
176
290
|
const selectedCommand = () => commandMatches()[selectedCommandIndex()];
|
|
177
|
-
const
|
|
291
|
+
const busy = () => Boolean(props.disabled || stopping() || state() === "submitting" || executing() || addingFiles());
|
|
292
|
+
const blocked = () => busy() || submitting();
|
|
293
|
+
/** The conversation field stays editable while a message is sent, so it keeps the focus and a phone its keyboard. */
|
|
294
|
+
const editBlocked = () => conversation() ? busy() : blocked();
|
|
178
295
|
const hasDraft = () => Boolean(props.value.trim() || !running() && attachments().length > 0);
|
|
179
296
|
const canSubmit = () => !blocked() && hasDraft() && !(running() && runningSubmitIntent() === "steer" && mentions().length);
|
|
180
297
|
const canSelectFiles = () => Boolean(props.fileSelection && !props.fileSelection.disabled && !running() && !blocked());
|
|
@@ -194,7 +311,7 @@ export function ChatComposer(props) {
|
|
|
194
311
|
icon: addingFiles() ? "ti ti-loader-2 k2b-spin" : "ti ti-paperclip",
|
|
195
312
|
label: props.fileSelection.label ?? messages().attachFiles,
|
|
196
313
|
disabled: !canSelectFiles(),
|
|
197
|
-
action: () =>
|
|
314
|
+
action: () => chooseFiles()
|
|
198
315
|
});
|
|
199
316
|
}
|
|
200
317
|
for (const action of props.menuActions ?? []) {
|
|
@@ -210,6 +327,14 @@ export function ChatComposer(props) {
|
|
|
210
327
|
};
|
|
211
328
|
const autoResize = () => {
|
|
212
329
|
if (!textareaRef) return;
|
|
330
|
+
if (conversation()) {
|
|
331
|
+
// A hidden copy measures the text. Collapsing the field itself would shrink the composer for a frame, and
|
|
332
|
+
// WebKit then clamps the scroll position of the conversation above it. CSS caps the height.
|
|
333
|
+
if (!measureRef) return;
|
|
334
|
+
measureRef.value = textareaRef.value;
|
|
335
|
+
textareaRef.style.height = `${measureRef.scrollHeight}px`;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
213
338
|
textareaRef.style.height = "auto";
|
|
214
339
|
textareaRef.style.height = `${Math.min(textareaRef.scrollHeight, composerMaxInputHeight)}px`;
|
|
215
340
|
};
|
|
@@ -217,14 +342,22 @@ export function ChatComposer(props) {
|
|
|
217
342
|
onMount(() => {
|
|
218
343
|
autoResize();
|
|
219
344
|
if (props.focusToken !== undefined) focus();
|
|
345
|
+
// The copy follows the field's width, so a narrower composer measures more lines.
|
|
346
|
+
if (measureRef && typeof ResizeObserver === "function") {
|
|
347
|
+
const observer = new ResizeObserver(() => autoResize());
|
|
348
|
+
observer.observe(measureRef);
|
|
349
|
+
onCleanup(() => observer.disconnect());
|
|
350
|
+
}
|
|
220
351
|
});
|
|
221
352
|
createEffect(() => {
|
|
222
353
|
commandMatches();
|
|
354
|
+
emojiMatches();
|
|
223
355
|
setSelectedCommandIndex(0);
|
|
224
356
|
});
|
|
225
357
|
createEffect(() => {
|
|
226
358
|
const index = selectedCommandIndex();
|
|
227
359
|
commandMatches();
|
|
360
|
+
emojiMatches();
|
|
228
361
|
queueMicrotask(() => {
|
|
229
362
|
const list = commandListRef;
|
|
230
363
|
const item = list?.querySelector(`[id="${commandListId}-${index}"]`);
|
|
@@ -254,6 +387,13 @@ export function ChatComposer(props) {
|
|
|
254
387
|
});
|
|
255
388
|
const setAttachments = next => props.onAttachmentsChange?.(next);
|
|
256
389
|
const renderAttachmentCopy = attachment => _$ssr(_tmpl$, _$ssrAttribute("title", _$escape(attachment.name, true), false), _$escape(attachment.name));
|
|
390
|
+
const chooseFiles = () => {
|
|
391
|
+
const selection = props.fileSelection;
|
|
392
|
+
if (!selection?.choose) return fileInputRef?.click();
|
|
393
|
+
selection.choose().then(
|
|
394
|
+
// A single-file composer keeps the first chosen file, as its file dialog and drops do.
|
|
395
|
+
files => void runFiles(selection.multiple === false ? files.slice(0, 1) : files), error => (props.fileSelection?.onError ?? props.onError)?.(error));
|
|
396
|
+
};
|
|
257
397
|
const runFiles = async files => {
|
|
258
398
|
if (!canSelectFiles()) return;
|
|
259
399
|
const selected = Array.from(files);
|
|
@@ -291,8 +431,17 @@ export function ChatComposer(props) {
|
|
|
291
431
|
},
|
|
292
432
|
perform: () => props.onSubmit(input),
|
|
293
433
|
restore: () => {
|
|
294
|
-
|
|
295
|
-
|
|
434
|
+
// Text typed while the submission ran stays, after the returned draft. Its mentions are read first, because
|
|
435
|
+
// an application may already move them when the text changes.
|
|
436
|
+
const typed = props.value;
|
|
437
|
+
const typedMentions = mentions();
|
|
438
|
+
const shift = previousValue.length + 1;
|
|
439
|
+
props.onValueChange(typed ? `${previousValue}\n${typed}` : previousValue);
|
|
440
|
+
props.onMentionsChange?.(typed ? [...previousMentions, ...typedMentions.map(mention => ({
|
|
441
|
+
...mention,
|
|
442
|
+
start: mention.start + shift,
|
|
443
|
+
end: mention.end + shift
|
|
444
|
+
}))] : previousMentions);
|
|
296
445
|
if (intent !== "steer") setAttachments(previousAttachments);
|
|
297
446
|
},
|
|
298
447
|
onError: props.onError
|
|
@@ -349,13 +498,74 @@ export function ChatComposer(props) {
|
|
|
349
498
|
focus();
|
|
350
499
|
});
|
|
351
500
|
};
|
|
501
|
+
|
|
502
|
+
/** Replaces `start`–`end` with the emoji through the editing path, so the browser's undo restores the shortcode. */
|
|
503
|
+
const insertEmoji = (start, end, emoji) => {
|
|
504
|
+
if (!textareaRef || editBlocked()) return;
|
|
505
|
+
replaceTextareaRange(textareaRef, start, end, emoji);
|
|
506
|
+
syncCaret();
|
|
507
|
+
reportChatFailure(() => props.emoji?.onPick?.(emoji), props.onError);
|
|
508
|
+
};
|
|
509
|
+
const pickEmojiSuggestion = position => {
|
|
510
|
+
const token = emojiQuery();
|
|
511
|
+
const entry = emojiMatches()[position];
|
|
512
|
+
if (!token || !entry) return;
|
|
513
|
+
insertEmoji(token.start, token.end, emojiInTone(entry, props.emoji?.skinTone ?? 0));
|
|
514
|
+
};
|
|
515
|
+
/**
|
|
516
|
+
* A typed colon loads the emoji data, and the closing colon of a known `:shortcode:` turns it into its emoji. A
|
|
517
|
+
* shortcode closed before the data arrived turns when it arrives, as long as nothing changed in the meantime.
|
|
518
|
+
*/
|
|
519
|
+
const completeShortcode = () => {
|
|
520
|
+
const textarea = textareaRef;
|
|
521
|
+
if (!props.emoji || !textarea) return;
|
|
522
|
+
const {
|
|
523
|
+
value,
|
|
524
|
+
selectionStart: caret
|
|
525
|
+
} = textarea;
|
|
526
|
+
const completed = completedEmojiShortcode(value, caret);
|
|
527
|
+
const convert = index => {
|
|
528
|
+
const entry = completed && index.byShortcode.get(completed.shortcode.toLowerCase());
|
|
529
|
+
if (completed && entry) insertEmoji(completed.start, completed.end, emojiInTone(entry, props.emoji?.skinTone ?? 0));
|
|
530
|
+
};
|
|
531
|
+
const index = emojiIndex();
|
|
532
|
+
if (index) {
|
|
533
|
+
convert(index);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
loadEmojiIndex().then(loaded => {
|
|
537
|
+
setEmojiIndex(loaded);
|
|
538
|
+
const untouched = textarea.value === value && textarea.selectionStart === caret && textarea.selectionEnd === caret;
|
|
539
|
+
if (untouched && textarea.matches(":focus")) convert(loaded);
|
|
540
|
+
}, () => undefined);
|
|
541
|
+
};
|
|
352
542
|
const onKeyDown = event => {
|
|
353
|
-
|
|
543
|
+
// WebKit ends the composition before the keydown of the Enter that confirms it; that keydown keeps keyCode 229.
|
|
544
|
+
if (event.isComposing || event.keyCode === 229 || composing()) return;
|
|
354
545
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "z") {
|
|
355
546
|
event.preventDefault();
|
|
356
547
|
restoreHistory(event.shiftKey ? 1 : -1);
|
|
357
548
|
return;
|
|
358
549
|
}
|
|
550
|
+
if (emojiOpen()) {
|
|
551
|
+
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
552
|
+
event.preventDefault();
|
|
553
|
+
setSelectedCommandIndex(index => nextChatCommandIndex(index, emojiMatches().length, event.key === "ArrowUp" ? -1 : 1));
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
// Only a plain Enter or Tab picks: Shift+Enter still breaks the line, Shift+Tab still moves the focus back.
|
|
557
|
+
const plain = !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
558
|
+
if (plain && (event.key === "Enter" || event.key === "Tab")) {
|
|
559
|
+
event.preventDefault();
|
|
560
|
+
pickEmojiSuggestion(selectedCommandIndex());
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (event.key === "Escape") {
|
|
564
|
+
event.preventDefault();
|
|
565
|
+
setDismissed(true);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
359
569
|
const matches = commandMatches();
|
|
360
570
|
if (commandsOpen()) {
|
|
361
571
|
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
@@ -363,7 +573,8 @@ export function ChatComposer(props) {
|
|
|
363
573
|
setSelectedCommandIndex(index => nextChatCommandIndex(index, matches.length, event.key === "ArrowUp" ? -1 : 1));
|
|
364
574
|
return;
|
|
365
575
|
}
|
|
366
|
-
|
|
576
|
+
// Ctrl/⌘+Enter sends, also while suggestions are open.
|
|
577
|
+
if ((event.key === "Tab" || event.key === "Enter" && !event.ctrlKey && !event.metaKey) && matches.length > 0) {
|
|
367
578
|
event.preventDefault();
|
|
368
579
|
const command = selectedCommand();
|
|
369
580
|
if (command) void executeCommand(command);
|
|
@@ -375,294 +586,674 @@ export function ChatComposer(props) {
|
|
|
375
586
|
return;
|
|
376
587
|
}
|
|
377
588
|
}
|
|
378
|
-
|
|
589
|
+
const format = props.formatting && textareaRef ? formatShortcut(event) : undefined;
|
|
590
|
+
if (format && textareaRef) {
|
|
591
|
+
event.preventDefault();
|
|
592
|
+
format.run(textareaRef);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (event.key !== "Enter") return;
|
|
596
|
+
if (conversation()) {
|
|
597
|
+
const action = conversationEnterAction(event, {
|
|
598
|
+
sendKey: props.sendKey ?? "enter",
|
|
599
|
+
touchOnly: isTouchOnly(),
|
|
600
|
+
inCodeBlock: openCodeFence(props.value, textareaRef?.selectionStart ?? props.value.length) !== null
|
|
601
|
+
});
|
|
602
|
+
if (action === "send") {
|
|
603
|
+
event.preventDefault();
|
|
604
|
+
void submit();
|
|
605
|
+
}
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const send = props.sendKey === "mod-enter" ? event.ctrlKey || event.metaKey : !event.shiftKey;
|
|
609
|
+
if (send && !event.isComposing) {
|
|
379
610
|
event.preventDefault();
|
|
380
611
|
void submit();
|
|
381
612
|
}
|
|
382
613
|
};
|
|
383
|
-
|
|
614
|
+
const insertText = text => {
|
|
615
|
+
if (!textareaRef || editBlocked()) return;
|
|
616
|
+
replaceTextareaRange(textareaRef, textareaRef.selectionStart, textareaRef.selectionEnd, text);
|
|
617
|
+
};
|
|
618
|
+
let holdTimer;
|
|
619
|
+
let held = false;
|
|
620
|
+
const releaseHold = () => {
|
|
621
|
+
clearTimeout(holdTimer);
|
|
622
|
+
holdTimer = undefined;
|
|
623
|
+
};
|
|
624
|
+
onCleanup(releaseHold);
|
|
625
|
+
const microphoneDisabled = () => Boolean(props.microphone?.disabled || blocked() || dictation() === "refining");
|
|
626
|
+
const microphoneItems = () => {
|
|
627
|
+
const microphone = props.microphone;
|
|
628
|
+
const items = [];
|
|
629
|
+
if (microphone?.onDictate) {
|
|
630
|
+
const dictate = microphone.onDictate;
|
|
631
|
+
items.push({
|
|
632
|
+
icon: "ti ti-microphone",
|
|
633
|
+
label: messages().dictate,
|
|
634
|
+
action: () => reportChatFailure(dictate, props.onError)
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
if (microphone?.onVoiceMessage) {
|
|
638
|
+
const record = microphone.onVoiceMessage;
|
|
639
|
+
items.push({
|
|
640
|
+
icon: "ti ti-player-record",
|
|
641
|
+
label: messages().recordVoiceMessage,
|
|
642
|
+
action: () => reportChatFailure(record, props.onError)
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
return items;
|
|
646
|
+
};
|
|
647
|
+
const dictationStatus = () => {
|
|
648
|
+
switch (dictation()) {
|
|
649
|
+
case "listening":
|
|
650
|
+
return {
|
|
651
|
+
icon: "ti ti-microphone",
|
|
652
|
+
text: messages().dictationListening
|
|
653
|
+
};
|
|
654
|
+
case "refining":
|
|
655
|
+
return {
|
|
656
|
+
icon: "ti ti-loader-2 k2b-spin",
|
|
657
|
+
text: messages().dictationRefining
|
|
658
|
+
};
|
|
659
|
+
case "refined":
|
|
660
|
+
return {
|
|
661
|
+
icon: "ti ti-sparkles",
|
|
662
|
+
text: messages().dictationRefined
|
|
663
|
+
};
|
|
664
|
+
case "unrefined":
|
|
665
|
+
return {
|
|
666
|
+
icon: "ti ti-microphone",
|
|
667
|
+
text: messages().dictationUnrefined
|
|
668
|
+
};
|
|
669
|
+
case "interrupted":
|
|
670
|
+
return {
|
|
671
|
+
icon: "ti ti-alert-triangle",
|
|
672
|
+
text: messages().dictationInterrupted
|
|
673
|
+
};
|
|
674
|
+
default:
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
const fileInput = () => _$createComponent(Show, {
|
|
384
679
|
get when() {
|
|
385
|
-
return
|
|
680
|
+
return props.fileSelection;
|
|
386
681
|
},
|
|
387
682
|
get children() {
|
|
388
|
-
return _$ssr(_tmpl$
|
|
683
|
+
return _$ssr(_tmpl$2, _$ssrAttribute("accept", _$escape(props.fileSelection?.accept, true), false), _$ssrAttribute("multiple", props.fileSelection?.multiple ?? true, true));
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
const addMenu = (icon, extraClass = "") => _$createComponent(Dropdown.Root, {
|
|
687
|
+
position: "top-right",
|
|
688
|
+
get label() {
|
|
689
|
+
return messages().addToChat;
|
|
690
|
+
},
|
|
691
|
+
get items() {
|
|
692
|
+
return menuItems();
|
|
693
|
+
},
|
|
694
|
+
get disabled() {
|
|
695
|
+
return blocked();
|
|
696
|
+
},
|
|
697
|
+
get children() {
|
|
698
|
+
return _$createComponent(Dropdown.Trigger, {
|
|
699
|
+
appearance: "plain",
|
|
700
|
+
"class": `k2b-chat-composer__icon-action${extraClass}`,
|
|
701
|
+
get label() {
|
|
702
|
+
return messages().addToChat;
|
|
703
|
+
},
|
|
704
|
+
get tooltip() {
|
|
705
|
+
return messages().addToChat;
|
|
706
|
+
},
|
|
707
|
+
get children() {
|
|
708
|
+
return _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(icon, true), false));
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
/** The conversation's attach button opens the file choice directly; other actions turn it into the add menu. */
|
|
715
|
+
const attachControl = () => _$createComponent(Show, {
|
|
716
|
+
get when() {
|
|
717
|
+
return props.menuActions?.length;
|
|
718
|
+
},
|
|
719
|
+
get fallback() {
|
|
720
|
+
return _$createComponent(Show, {
|
|
721
|
+
get when() {
|
|
722
|
+
return props.fileSelection;
|
|
723
|
+
},
|
|
724
|
+
children: selection => _$createComponent(Tooltip.Trigger, {
|
|
725
|
+
type: "button",
|
|
726
|
+
"class": "k2b-chat-composer__icon-action k2b-chat-composer__attach",
|
|
727
|
+
get ["aria-label"]() {
|
|
728
|
+
return selection().label ?? messages().attachFiles;
|
|
729
|
+
},
|
|
730
|
+
get content() {
|
|
731
|
+
return selection().label ?? messages().attachFiles;
|
|
732
|
+
},
|
|
733
|
+
get disabled() {
|
|
734
|
+
return !canSelectFiles();
|
|
735
|
+
},
|
|
736
|
+
onClick: chooseFiles,
|
|
737
|
+
get children() {
|
|
738
|
+
return _$ssr(_tmpl$3, _$ssrAttribute("class", addingFiles() ? "ti ti-loader-2 k2b-spin" : "ti ti-paperclip", false));
|
|
739
|
+
}
|
|
740
|
+
})
|
|
741
|
+
});
|
|
742
|
+
},
|
|
743
|
+
get children() {
|
|
744
|
+
return addMenu("ti ti-plus", " k2b-chat-composer__attach");
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
const formatToggle = () => _$createComponent(Show, {
|
|
748
|
+
get when() {
|
|
749
|
+
return props.formatting;
|
|
750
|
+
},
|
|
751
|
+
get children() {
|
|
752
|
+
return _$createComponent(Tooltip.Trigger, {
|
|
753
|
+
type: "button",
|
|
754
|
+
"class": "k2b-chat-composer__icon-action k2b-chat-composer__format-toggle",
|
|
755
|
+
get ["aria-label"]() {
|
|
756
|
+
return messages().composerFormatting;
|
|
757
|
+
},
|
|
758
|
+
get ["aria-pressed"]() {
|
|
759
|
+
return formattingOpen();
|
|
760
|
+
},
|
|
761
|
+
get ["aria-controls"]() {
|
|
762
|
+
return formattingOpen() ? formatGroupId : undefined;
|
|
763
|
+
},
|
|
764
|
+
get content() {
|
|
765
|
+
return messages().composerFormatting;
|
|
766
|
+
},
|
|
767
|
+
onMouseDown: keepFieldFocus,
|
|
768
|
+
onClick: () => setFormattingOpen(open => !open),
|
|
769
|
+
get children() {
|
|
770
|
+
return _$ssr(_tmpl$4);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
/** One row of fixed height. It shows every button at once, also on a phone, so nothing hides behind a scroll. */
|
|
777
|
+
const formatRow = () => _$createComponent(Show, {
|
|
778
|
+
get when() {
|
|
779
|
+
return _$memo(() => !!props.formatting)() && formattingOpen();
|
|
780
|
+
},
|
|
781
|
+
get children() {
|
|
782
|
+
return _$ssr(_tmpl$5, _$ssrAttribute("id", _$escape(formatGroupId, true), false), _$ssrAttribute("aria-label", _$escape(messages().composerFormatting, true), false), _$escape(_$createComponent(For, {
|
|
783
|
+
each: formatTools,
|
|
784
|
+
children: tool => _$createComponent(Tooltip.Trigger, {
|
|
785
|
+
type: "button",
|
|
786
|
+
"class": "k2b-chat-composer__icon-action",
|
|
787
|
+
get ["aria-label"]() {
|
|
788
|
+
return tool.label(messages());
|
|
789
|
+
},
|
|
790
|
+
get content() {
|
|
791
|
+
return tool.label(messages());
|
|
792
|
+
},
|
|
793
|
+
get disabled() {
|
|
794
|
+
return editBlocked();
|
|
795
|
+
},
|
|
796
|
+
onMouseDown: keepFieldFocus,
|
|
797
|
+
onClick: () => {
|
|
798
|
+
if (textareaRef) tool.run(textareaRef);
|
|
799
|
+
},
|
|
800
|
+
get children() {
|
|
801
|
+
return _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(tool.icon, true), false));
|
|
802
|
+
}
|
|
803
|
+
})
|
|
804
|
+
})));
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
const emojiButton = () => _$createComponent(Show, {
|
|
808
|
+
get when() {
|
|
809
|
+
return props.emoji;
|
|
810
|
+
},
|
|
811
|
+
children: emoji => _$createComponent(Tooltip.Trigger, {
|
|
812
|
+
type: "button",
|
|
813
|
+
"class": "k2b-chat-composer__icon-action k2b-chat-composer__emoji",
|
|
814
|
+
get ["aria-label"]() {
|
|
815
|
+
return messages().insertEmoji;
|
|
816
|
+
},
|
|
817
|
+
get content() {
|
|
818
|
+
return messages().insertEmoji;
|
|
819
|
+
},
|
|
820
|
+
get disabled() {
|
|
821
|
+
return editBlocked();
|
|
822
|
+
},
|
|
823
|
+
onMouseDown: keepFieldFocus,
|
|
824
|
+
onClick: event => {
|
|
825
|
+
const anchor = event.currentTarget;
|
|
826
|
+
reportChatFailure(() => emoji().onOpen({
|
|
827
|
+
anchor,
|
|
828
|
+
insert: insertText
|
|
829
|
+
}), props.onError);
|
|
830
|
+
},
|
|
831
|
+
get children() {
|
|
832
|
+
return _$ssr(_tmpl$6);
|
|
833
|
+
}
|
|
834
|
+
})
|
|
835
|
+
});
|
|
836
|
+
const microphoneControl = () => _$createComponent(Show, {
|
|
837
|
+
get when() {
|
|
838
|
+
return _$memo(() => !!(props.microphone?.onDictate || props.microphone?.onVoiceMessage))() ? props.microphone : undefined;
|
|
839
|
+
},
|
|
840
|
+
children: microphone => _$ssr(_tmpl$8, _$escape(_$createComponent(Tooltip.Trigger, {
|
|
841
|
+
type: "button",
|
|
842
|
+
"class": "k2b-chat-composer__icon-action k2b-chat-composer__microphone-button",
|
|
843
|
+
get ["data-state"]() {
|
|
844
|
+
return dictation() ?? undefined;
|
|
845
|
+
},
|
|
846
|
+
get disabled() {
|
|
847
|
+
return microphoneDisabled();
|
|
848
|
+
},
|
|
849
|
+
get ["aria-pressed"]() {
|
|
850
|
+
return _$memo(() => !!microphone().onDictate)() ? dictation() === "listening" : undefined;
|
|
851
|
+
},
|
|
852
|
+
get ["aria-haspopup"]() {
|
|
853
|
+
return microphone().onDictate ? undefined : "menu";
|
|
854
|
+
},
|
|
855
|
+
get ["aria-expanded"]() {
|
|
856
|
+
return _$memo(() => !!microphone().onDictate)() ? undefined : microphoneMenuOpen();
|
|
857
|
+
},
|
|
858
|
+
get ["aria-label"]() {
|
|
859
|
+
return _$memo(() => !!microphone().onDictate)() ? messages().dictate : messages().recordVoiceMessage;
|
|
860
|
+
},
|
|
861
|
+
get content() {
|
|
862
|
+
return _$memo(() => dictation() === "listening")() ? messages().stopDictation : _$memo(() => !!microphone().onDictate)() ? _$memo(() => !!microphone().onVoiceMessage)() ? messages().dictateOrHold : messages().dictate : messages().recordVoiceMessage;
|
|
863
|
+
},
|
|
864
|
+
onMouseDown: keepFieldFocus,
|
|
865
|
+
onContextMenu: event => event.preventDefault(),
|
|
866
|
+
onPointerDown: event => {
|
|
867
|
+
held = false;
|
|
868
|
+
releaseHold();
|
|
869
|
+
const record = microphone().onVoiceMessage;
|
|
870
|
+
if (event.button !== 0 || !record || dictation() === "listening") return;
|
|
871
|
+
holdTimer = setTimeout(() => {
|
|
872
|
+
// The composer may have become blocked, or recording withdrawn, during the press.
|
|
873
|
+
const current = props.microphone?.onVoiceMessage;
|
|
874
|
+
if (!current || microphoneDisabled()) return;
|
|
875
|
+
held = true;
|
|
876
|
+
reportChatFailure(current, props.onError);
|
|
877
|
+
}, microphoneHoldMs);
|
|
878
|
+
},
|
|
879
|
+
onPointerUp: releaseHold,
|
|
880
|
+
onPointerCancel: releaseHold,
|
|
881
|
+
onPointerLeave: releaseHold,
|
|
882
|
+
onClick: event => {
|
|
883
|
+
// A press that already asked for a voice message ends here; a key press never holds.
|
|
884
|
+
if (held) {
|
|
885
|
+
held = false;
|
|
886
|
+
if (event.detail > 0) return;
|
|
887
|
+
}
|
|
888
|
+
const dictate = microphone().onDictate;
|
|
889
|
+
if (dictate) {
|
|
890
|
+
reportChatFailure(dictate, props.onError);
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
setMicrophoneMenuOpen(true);
|
|
894
|
+
// From the keyboard it opens the menu like a menu button: the focus moves to the first item.
|
|
895
|
+
if (event.detail === 0) {
|
|
896
|
+
queueMicrotask(() => microphoneRef?.querySelector('[role="menuitem"]:not(:disabled)')?.focus());
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
get children() {
|
|
900
|
+
return _$ssr(_tmpl$3, _$ssrAttribute("class", dictation() === "listening" ? "ti ti-player-stop" : dictation() === "refining" ? "ti ti-loader-2 k2b-spin" : "ti ti-microphone", false));
|
|
901
|
+
}
|
|
902
|
+
})), _$escape(_$createComponent(Show, {
|
|
903
|
+
get when() {
|
|
904
|
+
return microphone().onVoiceMessage;
|
|
905
|
+
},
|
|
906
|
+
get children() {
|
|
907
|
+
return _$createComponent(Dropdown.Root, {
|
|
908
|
+
position: "top-left",
|
|
909
|
+
get label() {
|
|
910
|
+
return messages().microphoneOptions;
|
|
911
|
+
},
|
|
912
|
+
get items() {
|
|
913
|
+
return microphoneItems();
|
|
914
|
+
},
|
|
915
|
+
get disabled() {
|
|
916
|
+
return microphoneDisabled();
|
|
917
|
+
},
|
|
918
|
+
get open() {
|
|
919
|
+
return microphoneMenuOpen();
|
|
920
|
+
},
|
|
921
|
+
onOpenChange: setMicrophoneMenuOpen,
|
|
922
|
+
get children() {
|
|
923
|
+
return _$createComponent(Dropdown.Trigger, {
|
|
924
|
+
appearance: "plain",
|
|
925
|
+
"class": "k2b-chat-composer__icon-action k2b-chat-composer__microphone-menu",
|
|
926
|
+
get label() {
|
|
927
|
+
return messages().microphoneOptions;
|
|
928
|
+
},
|
|
929
|
+
get tooltip() {
|
|
930
|
+
return messages().microphoneOptions;
|
|
931
|
+
},
|
|
932
|
+
get children() {
|
|
933
|
+
return _$ssr(_tmpl$7);
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
})))
|
|
940
|
+
});
|
|
941
|
+
const sendControl = () => _$createComponent(Show, {
|
|
942
|
+
get when() {
|
|
943
|
+
return _$memo(() => !!(running() && !hasDraft()))() && props.onStop;
|
|
944
|
+
},
|
|
945
|
+
get fallback() {
|
|
946
|
+
return _$createComponent(Tooltip.Trigger, {
|
|
947
|
+
type: "button",
|
|
948
|
+
"class": "k2b-chat-composer__send",
|
|
949
|
+
get disabled() {
|
|
950
|
+
return !canSubmit();
|
|
951
|
+
},
|
|
952
|
+
onMouseDown: event => {
|
|
953
|
+
if (conversation()) keepFieldFocus(event);
|
|
954
|
+
},
|
|
955
|
+
get ["aria-label"]() {
|
|
956
|
+
return _$memo(() => !!submitting())() ? _$memo(() => !!running())() ? _$memo(() => runningSubmitIntent() === "queue")() ? messages().queueing : messages().steering : messages().sending : _$memo(() => !!running())() ? _$memo(() => runningSubmitIntent() === "queue")() ? messages().queueMessage : messages().steerResponse : messages().sendMessage;
|
|
957
|
+
},
|
|
958
|
+
get content() {
|
|
959
|
+
return _$memo(() => !!running())() ? _$memo(() => runningSubmitIntent() === "queue")() ? messages().queueMessage : messages().steerResponse : messages().sendMessage;
|
|
960
|
+
},
|
|
961
|
+
onClick: () => void submit(),
|
|
962
|
+
get children() {
|
|
963
|
+
return _$ssr(_tmpl$3, _$ssrAttribute("class", submitting() ? "ti ti-loader-2 k2b-spin" : conversation() ? "ti ti-send" : "ti ti-arrow-up", false));
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
},
|
|
967
|
+
get children() {
|
|
968
|
+
return _$createComponent(Tooltip.Trigger, {
|
|
969
|
+
type: "button",
|
|
970
|
+
"class": "k2b-chat-composer__stop",
|
|
971
|
+
get disabled() {
|
|
972
|
+
return stopping();
|
|
973
|
+
},
|
|
974
|
+
get ["aria-label"]() {
|
|
975
|
+
return _$memo(() => !!stopping())() ? messages().stopping : messages().stopResponse;
|
|
976
|
+
},
|
|
977
|
+
get content() {
|
|
978
|
+
return messages().stopResponse;
|
|
979
|
+
},
|
|
980
|
+
onClick: () => reportChatFailure(() => props.onStop?.(), props.onError),
|
|
981
|
+
get children() {
|
|
982
|
+
return _$ssr(_tmpl$3, _$ssrAttribute("class", stopping() ? "ti ti-loader-2 k2b-spin" : "ti ti-player-stop", false));
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
const field = () => _$ssr(_tmpl$1, _$ssrAttribute("aria-label", _$escape(messages().messageInput, true), false), _$escape(_$createComponent(Show, {
|
|
988
|
+
get when() {
|
|
989
|
+
return mentions().length > 0;
|
|
990
|
+
},
|
|
991
|
+
get children() {
|
|
992
|
+
return _$ssr(_tmpl$9, _$escape(_$createComponent(For, {
|
|
993
|
+
get each() {
|
|
994
|
+
return chatMentionSegments(props.value, mentions());
|
|
995
|
+
},
|
|
996
|
+
children: part => _$ssr(_tmpl$10, Boolean(part.mention) ? "k2b-chat-composer__mention" : "", _$escape(part.text))
|
|
997
|
+
})));
|
|
998
|
+
}
|
|
999
|
+
})), 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() ? messages().addGuidance : conversation() ? messages().writeConversationMessage : messages().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() ? emojiMatches()[selectedCommandIndex()] : selectedCommand()) ? `${_$escape(commandListId, true)}-${_$escape(selectedCommandIndex(), true)}` : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
1000
|
+
get when() {
|
|
1001
|
+
return conversation();
|
|
1002
|
+
},
|
|
1003
|
+
get children() {
|
|
1004
|
+
return _$ssr(_tmpl$0);
|
|
1005
|
+
}
|
|
1006
|
+
})));
|
|
1007
|
+
return _$ssr(_tmpl$21, _$escape(_$createComponent(Show, {
|
|
1008
|
+
get when() {
|
|
1009
|
+
return suggestionsOpen() || hasAccessory();
|
|
1010
|
+
},
|
|
1011
|
+
get children() {
|
|
1012
|
+
return _$ssr(_tmpl$16, _$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, {
|
|
1013
|
+
get when() {
|
|
1014
|
+
return emojiOpen();
|
|
1015
|
+
},
|
|
1016
|
+
get children() {
|
|
1017
|
+
return _$ssr(_tmpl$11, _$ssrAttribute("id", _$escape(commandListId, true), false), _$ssrAttribute("aria-label", _$escape(messages().emojiSuggestions, true), false), _$escape(_$createComponent(For, {
|
|
1018
|
+
get each() {
|
|
1019
|
+
return emojiMatches();
|
|
1020
|
+
},
|
|
1021
|
+
children: (entry, index) => _$ssr(_tmpl$22, `${_$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, {
|
|
1022
|
+
get when() {
|
|
1023
|
+
return entry.shortcodes[0];
|
|
1024
|
+
},
|
|
1025
|
+
children: code => _$ssr(_tmpl$23, _$escape(code()))
|
|
1026
|
+
})))
|
|
1027
|
+
})));
|
|
1028
|
+
}
|
|
1029
|
+
})), _$escape(_$createComponent(Show, {
|
|
389
1030
|
get when() {
|
|
390
1031
|
return commandsOpen();
|
|
391
1032
|
},
|
|
392
1033
|
get children() {
|
|
393
|
-
return _$ssr(_tmpl$
|
|
1034
|
+
return _$ssr(_tmpl$15, _$ssrAttribute("id", _$escape(commandListId, true), false), _$ssrAttribute("aria-label", _$escape(messages().commands, true), false), _$escape(_$createComponent(For, {
|
|
394
1035
|
get each() {
|
|
395
1036
|
return commandMatches();
|
|
396
1037
|
},
|
|
397
|
-
children: (command, index) => _$ssr(_tmpl$
|
|
1038
|
+
children: (command, index) => _$ssr(_tmpl$24, `${_$escape(commandListId, true)}-${_$escape(index(), true)}`, _$ssrAttribute("aria-selected", _$escape(index(), true) === _$escape(selectedCommandIndex(), true), false) + _$ssrAttribute("aria-disabled", _$escape(command.disabled, true), false) + _$ssrAttribute("data-active", index() === selectedCommandIndex() ? "true" : _$escape(undefined, true), false), _$ssrAttribute("class", _$escape(command.icon ?? "ti ti-slash", true), false), _$escape(command.label ?? `/${command.name}`), _$escape(command.description))
|
|
398
1039
|
})), _$escape(_$createComponent(Show, {
|
|
399
1040
|
get when() {
|
|
400
1041
|
return searching();
|
|
401
1042
|
},
|
|
402
1043
|
get children() {
|
|
403
|
-
return _$ssr(_tmpl$
|
|
1044
|
+
return _$ssr(_tmpl$12, _$escape(messages().loading));
|
|
404
1045
|
}
|
|
405
1046
|
})), _$escape(_$createComponent(Show, {
|
|
406
1047
|
get when() {
|
|
407
1048
|
return searchError();
|
|
408
1049
|
},
|
|
409
1050
|
get children() {
|
|
410
|
-
return _$ssr(_tmpl$
|
|
1051
|
+
return _$ssr(_tmpl$13, _$escape(messages().error));
|
|
411
1052
|
}
|
|
412
1053
|
})), _$escape(_$createComponent(Show, {
|
|
413
1054
|
get when() {
|
|
414
1055
|
return _$memo(() => !!(!searching() && !searchError()))() && commandMatches().length === 0;
|
|
415
1056
|
},
|
|
416
1057
|
get children() {
|
|
417
|
-
return _$ssr(_tmpl$
|
|
1058
|
+
return _$ssr(_tmpl$14, _$escape(messages().noResults));
|
|
418
1059
|
}
|
|
419
1060
|
})));
|
|
420
1061
|
}
|
|
421
1062
|
})));
|
|
422
1063
|
}
|
|
423
|
-
})), `k2b-chat-composer ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("data-running", running() ? "true" : _$escape(undefined, true), false), _$ssrAttribute("aria-label", _$escape(props.label ?? messages().messageComposer, true), false), _$escape(_$createComponent(Show, {
|
|
1064
|
+
})), `k2b-chat-composer ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("data-running", running() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-variant", conversation() ? "conversation" : _$escape(undefined, true), false), _$ssrAttribute("aria-label", _$escape(props.label ?? messages().messageComposer, true), false), _$escape(_$createComponent(Show, {
|
|
1065
|
+
get when() {
|
|
1066
|
+
return showHint();
|
|
1067
|
+
},
|
|
1068
|
+
get children() {
|
|
1069
|
+
return _$ssr(_tmpl$17, _$escape(_$createComponent(Show, {
|
|
1070
|
+
get when() {
|
|
1071
|
+
return dictationStatus();
|
|
1072
|
+
},
|
|
1073
|
+
get fallback() {
|
|
1074
|
+
return _$ssr(_tmpl$25, _$escape(hint()));
|
|
1075
|
+
},
|
|
1076
|
+
children: status => [_$ssr(_tmpl$26, _$ssrAttribute("data-state", _$escape(dictation() ?? undefined, true), false), _$ssrAttribute("class", _$escape(status().icon, true), false), _$escape(status().text)), _$createComponent(Show, {
|
|
1077
|
+
get when() {
|
|
1078
|
+
return _$memo(() => dictation() === "refined")() ? props.microphone?.onRestoreOriginal : undefined;
|
|
1079
|
+
},
|
|
1080
|
+
children: restore => _$ssr(_tmpl$27, _$ssrAttribute("disabled", microphoneDisabled(), true), _$escape(messages().restoreOriginal))
|
|
1081
|
+
})]
|
|
1082
|
+
})));
|
|
1083
|
+
}
|
|
1084
|
+
})), _$escape(_$createComponent(Show, {
|
|
424
1085
|
get when() {
|
|
425
1086
|
return attachments().length > 0;
|
|
426
1087
|
},
|
|
427
1088
|
get children() {
|
|
428
|
-
return _$ssr(_tmpl$
|
|
1089
|
+
return _$ssr(_tmpl$18, _$ssrAttribute("aria-label", _$escape(messages().attachments, true), false), _$escape(_$createComponent(For, {
|
|
429
1090
|
get each() {
|
|
430
1091
|
return attachments();
|
|
431
1092
|
},
|
|
432
|
-
children: attachment => _$ssr(_tmpl$
|
|
1093
|
+
children: attachment => _$ssr(_tmpl$29, `k2b-chat-composer__attachment-content ${Boolean(attachment.action) ? "k2b-chat-composer__attachment-content--action" : ""}`, _$escape(_$createComponent(Show, {
|
|
433
1094
|
get when() {
|
|
434
1095
|
return attachment.href;
|
|
435
1096
|
},
|
|
436
1097
|
get fallback() {
|
|
437
|
-
return _$ssr(_tmpl$
|
|
1098
|
+
return _$ssr(_tmpl$31, _$escape(_$createComponent(Show, {
|
|
438
1099
|
get when() {
|
|
439
1100
|
return _$memo(() => attachment.kind === "image")() && attachment.previewUrl;
|
|
440
1101
|
},
|
|
441
1102
|
get fallback() {
|
|
442
|
-
return _$ssr(_tmpl$
|
|
1103
|
+
return _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(attachmentIcon(attachment), true), false));
|
|
443
1104
|
},
|
|
444
1105
|
get children() {
|
|
445
|
-
return _$ssr(_tmpl$
|
|
1106
|
+
return _$ssr(_tmpl$30, _$ssrAttribute("src", _$escape(attachment.previewUrl, true), false) + _$ssrAttribute("alt", _$escape(attachment.alt ?? "", true), false));
|
|
446
1107
|
}
|
|
447
1108
|
})), _$escape(renderAttachmentCopy(attachment)));
|
|
448
1109
|
},
|
|
449
|
-
children: href => _$ssr(_tmpl$
|
|
1110
|
+
children: href => _$ssr(_tmpl$32, _$ssrAttribute("href", _$escape(href(), true), false), _$ssrAttribute("aria-label", _$escape(messages().openInNewTab({
|
|
450
1111
|
name: attachment.name
|
|
451
1112
|
}), true), false), _$escape(_$createComponent(Show, {
|
|
452
1113
|
get when() {
|
|
453
1114
|
return _$memo(() => attachment.kind === "image")() && attachment.previewUrl;
|
|
454
1115
|
},
|
|
455
1116
|
get fallback() {
|
|
456
|
-
return _$ssr(_tmpl$
|
|
1117
|
+
return _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(attachmentIcon(attachment), true), false));
|
|
457
1118
|
},
|
|
458
1119
|
get children() {
|
|
459
|
-
return _$ssr(_tmpl$
|
|
1120
|
+
return _$ssr(_tmpl$30, _$ssrAttribute("src", _$escape(attachment.previewUrl, true), false) + _$ssrAttribute("alt", _$escape(attachment.alt ?? "", true), false));
|
|
460
1121
|
}
|
|
461
1122
|
})), _$escape(renderAttachmentCopy(attachment)))
|
|
462
1123
|
})), _$escape(_$createComponent(Show, {
|
|
463
1124
|
get when() {
|
|
464
1125
|
return attachment.action;
|
|
465
1126
|
},
|
|
466
|
-
children: action => _$ssr(_tmpl$
|
|
1127
|
+
children: action => _$ssr(_tmpl$33, _$ssrAttribute("disabled", blocked() || action().disabled, true), _$escape(_$createComponent(Show, {
|
|
467
1128
|
get when() {
|
|
468
1129
|
return action().icon;
|
|
469
1130
|
},
|
|
470
|
-
children: icon => _$ssr(_tmpl$
|
|
1131
|
+
children: icon => _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(icon(), true), false))
|
|
471
1132
|
})), _$escape(action().label))
|
|
472
1133
|
})), _$escape(_$createComponent(Show, {
|
|
473
1134
|
get when() {
|
|
474
1135
|
return props.onAttachmentsChange;
|
|
475
1136
|
},
|
|
476
1137
|
get children() {
|
|
477
|
-
return _$ssr(_tmpl$
|
|
1138
|
+
return _$ssr(_tmpl$28, _$ssrAttribute("aria-label", _$escape(messages().removeNamed({
|
|
478
1139
|
name: attachment.name
|
|
479
1140
|
}), true), false), _$ssrAttribute("disabled", blocked(), true));
|
|
480
1141
|
}
|
|
481
1142
|
})))
|
|
482
1143
|
})));
|
|
483
1144
|
}
|
|
484
|
-
})), _$ssrAttribute("aria-label", _$escape(messages().messageInput, true), false), _$escape(_$createComponent(Show, {
|
|
485
|
-
get when() {
|
|
486
|
-
return mentions().length > 0;
|
|
487
|
-
},
|
|
488
|
-
get children() {
|
|
489
|
-
return _$ssr(_tmpl$8, _$escape(_$createComponent(For, {
|
|
490
|
-
get each() {
|
|
491
|
-
return chatMentionSegments(props.value, mentions());
|
|
492
|
-
},
|
|
493
|
-
children: part => _$ssr(_tmpl$17, Boolean(part.mention) ? "k2b-chat-composer__mention" : "", _$escape(part.text))
|
|
494
|
-
})));
|
|
495
|
-
}
|
|
496
|
-
})), mentions().length > 0 ? "k2b-chat-composer__textarea--highlighted" : "", _$ssrAttribute("value", _$escape(props.value, true), false), _$ssrAttribute("disabled", blocked(), true) + _$ssrAttribute("placeholder", _$escape(props.placeholder ?? (running() ? messages().addGuidance : messages().writeMessage), true), false) + _$ssrAttribute("aria-label", _$escape(props.inputLabel ?? messages().message, true), false) + _$ssrAttribute("role", commandsOpen() ? "combobox" : _$escape(undefined, true), false) + _$ssrAttribute("aria-autocomplete", commandsOpen() ? "list" : _$escape(undefined, true), false) + _$ssrAttribute("aria-controls", commandsOpen() ? _$escape(commandListId, true) : _$escape(undefined, true), false) + _$ssrAttribute("aria-expanded", commandsOpen() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("aria-activedescendant", selectedCommand() ? `${_$escape(commandListId, true)}-${_$escape(selectedCommandIndex(), true)}` : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
497
|
-
get when() {
|
|
498
|
-
return props.error;
|
|
499
|
-
},
|
|
500
|
-
get children() {
|
|
501
|
-
return _$ssr(_tmpl$9, _$escape(props.error));
|
|
502
|
-
}
|
|
503
1145
|
})), _$escape(_$createComponent(Show, {
|
|
504
1146
|
get when() {
|
|
505
|
-
return
|
|
1147
|
+
return conversation();
|
|
506
1148
|
},
|
|
507
1149
|
get fallback() {
|
|
508
|
-
return [
|
|
1150
|
+
return [field(), _$createComponent(Show, {
|
|
509
1151
|
get when() {
|
|
510
|
-
return
|
|
1152
|
+
return props.error;
|
|
511
1153
|
},
|
|
512
1154
|
get children() {
|
|
513
|
-
return _$
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
1155
|
+
return _$ssr(_tmpl$19, _$escape(props.error));
|
|
1156
|
+
}
|
|
1157
|
+
}), formatRow(), _$ssr(_tmpl$34, _$escape(_$createComponent(Show, {
|
|
1158
|
+
get when() {
|
|
1159
|
+
return props.footerContent;
|
|
1160
|
+
},
|
|
1161
|
+
get fallback() {
|
|
1162
|
+
return [_$ssr(_tmpl$35, _$escape(props.footerTools), _$escape(_$createComponent(Show, {
|
|
1163
|
+
get when() {
|
|
1164
|
+
return hasAddMenu();
|
|
520
1165
|
},
|
|
521
|
-
get
|
|
522
|
-
return
|
|
1166
|
+
get children() {
|
|
1167
|
+
return addMenu("ti ti-plus");
|
|
1168
|
+
}
|
|
1169
|
+
})), _$escape(fileInput()), _$escape(formatToggle()), _$escape(emojiButton()), _$escape(_$createComponent(Show, {
|
|
1170
|
+
get when() {
|
|
1171
|
+
return (props.models?.length ?? 0) > 0;
|
|
523
1172
|
},
|
|
524
1173
|
get children() {
|
|
525
|
-
return _$createComponent(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
1174
|
+
return _$createComponent(SelectChip, {
|
|
1175
|
+
get ["aria-label"]() {
|
|
1176
|
+
return messages().chooseModel;
|
|
1177
|
+
},
|
|
1178
|
+
position: "top-right",
|
|
1179
|
+
"class": "k2b-chat-composer__model",
|
|
1180
|
+
menuWidth: "15rem",
|
|
1181
|
+
get placeholder() {
|
|
1182
|
+
return messages().model;
|
|
530
1183
|
},
|
|
531
|
-
|
|
532
|
-
|
|
1184
|
+
value: () => props.selectedModelId ?? "",
|
|
1185
|
+
get options() {
|
|
1186
|
+
return (props.models ?? []).map(model => ({
|
|
1187
|
+
value: model.id,
|
|
1188
|
+
label: model.label,
|
|
1189
|
+
description: model.description,
|
|
1190
|
+
icon: model.icon,
|
|
1191
|
+
image: model.image
|
|
1192
|
+
}));
|
|
533
1193
|
},
|
|
534
|
-
get
|
|
535
|
-
return
|
|
1194
|
+
get disabled() {
|
|
1195
|
+
return blocked() || running() || !props.onModelChange;
|
|
1196
|
+
},
|
|
1197
|
+
onValueChange: modelId => {
|
|
1198
|
+
props.onModelChange?.(modelId);
|
|
1199
|
+
queueMicrotask(focus);
|
|
536
1200
|
}
|
|
537
1201
|
});
|
|
538
1202
|
}
|
|
539
|
-
})
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
get when() {
|
|
543
|
-
return props.fileSelection;
|
|
544
|
-
},
|
|
545
|
-
get children() {
|
|
546
|
-
return _$ssr(_tmpl$19, _$ssrAttribute("accept", _$escape(props.fileSelection?.accept, true), false), _$ssrAttribute("multiple", props.fileSelection?.multiple ?? true, true));
|
|
547
|
-
}
|
|
548
|
-
})), _$escape(_$createComponent(Show, {
|
|
549
|
-
get when() {
|
|
550
|
-
return (props.models?.length ?? 0) > 0;
|
|
551
|
-
},
|
|
552
|
-
get children() {
|
|
553
|
-
return _$createComponent(SelectChip, {
|
|
554
|
-
get ["aria-label"]() {
|
|
555
|
-
return messages().chooseModel;
|
|
556
|
-
},
|
|
557
|
-
position: "top-right",
|
|
558
|
-
"class": "k2b-chat-composer__model",
|
|
559
|
-
menuWidth: "15rem",
|
|
560
|
-
get placeholder() {
|
|
561
|
-
return messages().model;
|
|
562
|
-
},
|
|
563
|
-
value: () => props.selectedModelId ?? "",
|
|
564
|
-
get options() {
|
|
565
|
-
return (props.models ?? []).map(model => ({
|
|
566
|
-
value: model.id,
|
|
567
|
-
label: model.label,
|
|
568
|
-
description: model.description,
|
|
569
|
-
icon: model.icon,
|
|
570
|
-
image: model.image
|
|
571
|
-
}));
|
|
1203
|
+
})), _$escape(props.modelDetails)), _$ssr(_tmpl$36, _$escape(_$createComponent(For, {
|
|
1204
|
+
get each() {
|
|
1205
|
+
return props.contextActions;
|
|
572
1206
|
},
|
|
573
|
-
|
|
574
|
-
|
|
1207
|
+
children: action => _$createComponent(Tooltip.Trigger, {
|
|
1208
|
+
type: "button",
|
|
1209
|
+
"class": "k2b-chat-composer__icon-action",
|
|
1210
|
+
get ["data-tone"]() {
|
|
1211
|
+
return action.variant === "danger" ? "danger" : undefined;
|
|
1212
|
+
},
|
|
1213
|
+
get disabled() {
|
|
1214
|
+
return action.disabled;
|
|
1215
|
+
},
|
|
1216
|
+
get ["aria-pressed"]() {
|
|
1217
|
+
return action.pressed;
|
|
1218
|
+
},
|
|
1219
|
+
get ["aria-label"]() {
|
|
1220
|
+
return action.label;
|
|
1221
|
+
},
|
|
1222
|
+
get content() {
|
|
1223
|
+
return action.label;
|
|
1224
|
+
},
|
|
1225
|
+
onClick: () => reportChatFailure(() => executeChatAction(action), props.onError),
|
|
1226
|
+
get children() {
|
|
1227
|
+
return _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(action.icon ?? "ti ti-dots", true), false));
|
|
1228
|
+
}
|
|
1229
|
+
})
|
|
1230
|
+
})), _$escape(_$createComponent(Show, {
|
|
1231
|
+
get when() {
|
|
1232
|
+
return _$memo(() => !!hasContextUsage())() ? props.contextUsage ?? {} : undefined;
|
|
575
1233
|
},
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
},
|
|
586
|
-
children: action => _$createComponent(Tooltip.Trigger, {
|
|
587
|
-
type: "button",
|
|
588
|
-
"class": "k2b-chat-composer__icon-action",
|
|
589
|
-
get ["data-tone"]() {
|
|
590
|
-
return action.variant === "danger" ? "danger" : undefined;
|
|
591
|
-
},
|
|
592
|
-
get disabled() {
|
|
593
|
-
return action.disabled;
|
|
594
|
-
},
|
|
595
|
-
get ["aria-pressed"]() {
|
|
596
|
-
return action.pressed;
|
|
597
|
-
},
|
|
598
|
-
get ["aria-label"]() {
|
|
599
|
-
return action.label;
|
|
600
|
-
},
|
|
601
|
-
get content() {
|
|
602
|
-
return action.label;
|
|
603
|
-
},
|
|
604
|
-
onClick: () => reportChatFailure(() => executeChatAction(action), props.onError),
|
|
605
|
-
get children() {
|
|
606
|
-
return _$ssr(_tmpl$14, _$ssrAttribute("class", _$escape(action.icon ?? "ti ti-dots", true), false));
|
|
607
|
-
}
|
|
608
|
-
})
|
|
609
|
-
})), _$escape(_$createComponent(Show, {
|
|
610
|
-
get when() {
|
|
611
|
-
return _$memo(() => !!hasContextUsage())() ? props.contextUsage ?? {} : undefined;
|
|
1234
|
+
children: usage => _$createComponent(ContextUsage, _$mergeProps(usage, {
|
|
1235
|
+
get action() {
|
|
1236
|
+
return props.contextPopupAction;
|
|
1237
|
+
},
|
|
1238
|
+
get onActionError() {
|
|
1239
|
+
return props.onError;
|
|
1240
|
+
}
|
|
1241
|
+
}))
|
|
1242
|
+
})), _$escape(props.submitTools), _$escape(microphoneControl()), _$escape(sendControl()))];
|
|
612
1243
|
},
|
|
613
|
-
children:
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
return props.onError;
|
|
619
|
-
}
|
|
620
|
-
}))
|
|
621
|
-
})), _$escape(props.submitTools), _$escape(_$createComponent(Show, {
|
|
1244
|
+
children: content => content()
|
|
1245
|
+
})))];
|
|
1246
|
+
},
|
|
1247
|
+
get children() {
|
|
1248
|
+
return [_$createComponent(Show, {
|
|
622
1249
|
get when() {
|
|
623
|
-
return
|
|
624
|
-
},
|
|
625
|
-
get fallback() {
|
|
626
|
-
return _$createComponent(Tooltip.Trigger, {
|
|
627
|
-
type: "button",
|
|
628
|
-
"class": "k2b-chat-composer__send",
|
|
629
|
-
get disabled() {
|
|
630
|
-
return !canSubmit();
|
|
631
|
-
},
|
|
632
|
-
get ["aria-label"]() {
|
|
633
|
-
return _$memo(() => !!submitting())() ? _$memo(() => !!running())() ? _$memo(() => runningSubmitIntent() === "queue")() ? messages().queueing : messages().steering : messages().sending : _$memo(() => !!running())() ? _$memo(() => runningSubmitIntent() === "queue")() ? messages().queueMessage : messages().steerResponse : messages().sendMessage;
|
|
634
|
-
},
|
|
635
|
-
get content() {
|
|
636
|
-
return _$memo(() => !!running())() ? _$memo(() => runningSubmitIntent() === "queue")() ? messages().queueMessage : messages().steerResponse : messages().sendMessage;
|
|
637
|
-
},
|
|
638
|
-
onClick: () => void submit(),
|
|
639
|
-
get children() {
|
|
640
|
-
return _$ssr(_tmpl$14, _$ssrAttribute("class", submitting() ? "ti ti-loader-2 k2b-spin" : "ti ti-arrow-up", false));
|
|
641
|
-
}
|
|
642
|
-
});
|
|
1250
|
+
return props.error;
|
|
643
1251
|
},
|
|
644
1252
|
get children() {
|
|
645
|
-
return _$
|
|
646
|
-
type: "button",
|
|
647
|
-
"class": "k2b-chat-composer__stop",
|
|
648
|
-
get disabled() {
|
|
649
|
-
return stopping();
|
|
650
|
-
},
|
|
651
|
-
get ["aria-label"]() {
|
|
652
|
-
return _$memo(() => !!stopping())() ? messages().stopping : messages().stopResponse;
|
|
653
|
-
},
|
|
654
|
-
get content() {
|
|
655
|
-
return messages().stopResponse;
|
|
656
|
-
},
|
|
657
|
-
onClick: () => reportChatFailure(() => props.onStop?.(), props.onError),
|
|
658
|
-
get children() {
|
|
659
|
-
return _$ssr(_tmpl$14, _$ssrAttribute("class", stopping() ? "ti ti-loader-2 k2b-spin" : "ti ti-player-stop", false));
|
|
660
|
-
}
|
|
661
|
-
});
|
|
1253
|
+
return _$ssr(_tmpl$19, _$escape(props.error));
|
|
662
1254
|
}
|
|
663
|
-
})))];
|
|
664
|
-
}
|
|
665
|
-
children: content => content()
|
|
1255
|
+
}), formatRow(), _$ssr(_tmpl$20, _$escape(attachControl()), _$escape(fileInput()), _$escape(field()), _$escape(formatToggle()), _$escape(emojiButton()), _$escape(microphoneControl()), _$escape(sendControl()))];
|
|
1256
|
+
}
|
|
666
1257
|
})), _$escape(_$createComponent(Show, {
|
|
667
1258
|
get when() {
|
|
668
1259
|
return props.fileSelection;
|