@mixd-id/web-scaffold 0.2.250801013 → 0.2.250801014

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.
Files changed (185) hide show
  1. package/docs/components/AIDashboard.md +472 -0
  2. package/docs/components/WebPageBuilder.md +125 -0
  3. package/package.json +7 -30
  4. package/src/components/AIChat.vue +479 -0
  5. package/src/components/AIChatItem.vue +168 -0
  6. package/src/components/AIChatTextbox.vue +56 -0
  7. package/src/components/AIDashboard.vue +1113 -0
  8. package/src/components/Card.vue +3 -1
  9. package/src/components/Chart.vue +2 -1
  10. package/src/components/CodeEditor.vue +121 -13
  11. package/src/components/ColorPicker.vue +6 -2
  12. package/src/components/Dashboard.vue +493 -212
  13. package/src/components/Dropdown.vue +0 -1
  14. package/src/components/HTMLEditor.vue +9 -1
  15. package/src/components/List.vue +226 -58
  16. package/src/components/MarkdownEdit.vue +123 -4
  17. package/src/components/PresetSelectorFilterItem.vue +1 -0
  18. package/src/components/TextWithTag.vue +3 -3
  19. package/src/components/TreeView.vue +1 -1
  20. package/src/components/VirtualTable.vue +166 -75
  21. package/src/index.js +3 -55
  22. package/src/instructions/dashboard.js +203 -0
  23. package/src/utils/ai-chat-answer.mjs +240 -0
  24. package/src/utils/ai-chat.js +167 -0
  25. package/src/utils/ai-dashboard-chat.mjs +371 -0
  26. package/src/utils/ai-dashboard.js +321 -0
  27. package/src/utils/ai-list-chat.mjs +407 -0
  28. package/src/utils/dashboard.js +13 -0
  29. package/src/utils/list.mjs +146 -91
  30. package/src/utils/preset-selector.mjs +183 -27
  31. package/src/utils/wss.js +0 -2
  32. package/src/widgets/ColumnSelector.vue +22 -33
  33. package/src/widgets/LLMChat.vue +327 -0
  34. package/src/widgets/PresetBar.vue +31 -8
  35. package/src/widgets/PresetBarPivot.vue +147 -15
  36. package/src/widgets/PresetBarPivotColumnEdit.vue +8 -19
  37. package/src/widgets/PresetBarPivotValueEdit.vue +172 -0
  38. package/src/widgets/PresetSelector.vue +8 -4
  39. package/src/widgets/WebPageBuilder.vue +153 -352
  40. package/docs/components/ArrayList.md +0 -39
  41. package/docs/components/Box.md +0 -15
  42. package/docs/components/ChartBar.md +0 -34
  43. package/docs/components/DayTimeRange.md +0 -20
  44. package/docs/components/ErrorText.md +0 -15
  45. package/docs/components/FBLogin.md +0 -21
  46. package/docs/components/GHeatMaps.md +0 -16
  47. package/docs/components/GSignIn.md +0 -21
  48. package/docs/components/GmapsDirection.md +0 -20
  49. package/docs/components/IconMenu.md +0 -20
  50. package/src/components/ArrayList.vue +0 -49
  51. package/src/components/Box.vue +0 -34
  52. package/src/components/ChartBar.vue +0 -192
  53. package/src/components/DayTimeRange.vue +0 -126
  54. package/src/components/ErrorText.vue +0 -57
  55. package/src/components/FBLogin.vue +0 -194
  56. package/src/components/GHeatMaps.vue +0 -317
  57. package/src/components/GSignIn.vue +0 -166
  58. package/src/components/GmapsDirection.vue +0 -191
  59. package/src/components/IconMenu.vue +0 -95
  60. package/src/components/IconPlus.vue +0 -89
  61. package/src/components/Image360.vue +0 -140
  62. package/src/components/ImageFullScreen.vue +0 -101
  63. package/src/components/ImportModal.vue +0 -260
  64. package/src/components/ListContextMenu.vue +0 -88
  65. package/src/components/LogViewerItem.vue +0 -116
  66. package/src/components/MultilineText.vue +0 -174
  67. package/src/components/PageBuilder.vue +0 -449
  68. package/src/components/SplitPane.vue +0 -129
  69. package/src/components/TabView.vue +0 -105
  70. package/src/components/TableView.vue +0 -200
  71. package/src/components/TableViewHead.vue +0 -159
  72. package/src/components/Test.vue +0 -23
  73. package/src/components/TextEditor.vue +0 -85
  74. package/src/components/Tooltip.vue +0 -143
  75. package/src/components/TreeViewItem1.vue +0 -282
  76. package/src/components/VirtualScroll.vue +0 -282
  77. package/src/configs/dashboard/bar.js +0 -10
  78. package/src/configs/dashboard/collection-1.js +0 -5
  79. package/src/configs/dashboard/data-table.js +0 -9
  80. package/src/configs/dashboard/doughnut.js +0 -7
  81. package/src/configs/dashboard/gheatmaps.js +0 -9
  82. package/src/configs/dashboard/grid-2.js +0 -34
  83. package/src/configs/dashboard/grid-3.js +0 -34
  84. package/src/configs/dashboard/grid-4.js +0 -34
  85. package/src/configs/dashboard/grid.js +0 -15
  86. package/src/configs/dashboard/metric.js +0 -10
  87. package/src/configs/dashboard/pie.js +0 -7
  88. package/src/configs/dashboard/polar-area.js +0 -7
  89. package/src/configs/dashboard/virtual-table.js +0 -9
  90. package/src/configs/icons-fa.json +0 -12
  91. package/src/middleware/http/trim-string.js +0 -20
  92. package/src/mixin/ready-state.js +0 -37
  93. package/src/stores/components.js +0 -36
  94. package/src/stores/datasource.js +0 -11
  95. package/src/utils/event-bus.js +0 -8
  96. package/src/utils/helpers.cjs +0 -635
  97. package/src/utils/importer.js +0 -209
  98. package/src/utils/listview.js +0 -1287
  99. package/src/utils/preset-selector.cjs +0 -1455
  100. package/src/utils/preset-selector.js +0 -1452
  101. package/src/utils/queue.js +0 -63
  102. package/src/utils/web.js +0 -50
  103. package/src/utils/web.mjs +0 -120
  104. package/src/widgets/BackgroundSetting.vue +0 -132
  105. package/src/widgets/BorderSetting.vue +0 -201
  106. package/src/widgets/BotEditor/BotEditorActions.vue +0 -163
  107. package/src/widgets/BotEditor/BotEditorSettings.vue +0 -21
  108. package/src/widgets/BotEditor.vue +0 -248
  109. package/src/widgets/BoxSetting.vue +0 -53
  110. package/src/widgets/CheckoutSetting.vue +0 -46
  111. package/src/widgets/ComponentPropertyLabel.vue +0 -35
  112. package/src/widgets/ComponentPropertySelector.vue +0 -115
  113. package/src/widgets/ComponentSetting3.vue +0 -231
  114. package/src/widgets/EffectSetting.vue +0 -94
  115. package/src/widgets/FiltersSetting.vue +0 -88
  116. package/src/widgets/Header.vue +0 -15
  117. package/src/widgets/Header0.vue +0 -41
  118. package/src/widgets/Header0Setting.vue +0 -75
  119. package/src/widgets/HeaderSetting.vue +0 -102
  120. package/src/widgets/LayoutSetting.vue +0 -251
  121. package/src/widgets/LogViewer.vue +0 -85
  122. package/src/widgets/MarginPaddingSetting.vue +0 -58
  123. package/src/widgets/MarginSetting.vue +0 -142
  124. package/src/widgets/MenuEditor.vue +0 -251
  125. package/src/widgets/PaddingSetting.vue +0 -142
  126. package/src/widgets/SizingSetting.vue +0 -306
  127. package/src/widgets/SpacingSetting.vue +0 -77
  128. package/src/widgets/StyleSetting.vue +0 -543
  129. package/src/widgets/StyleSetting2.vue +0 -90
  130. package/src/widgets/TransitionSetting.vue +0 -64
  131. package/src/widgets/TypographySetting.vue +0 -334
  132. package/src/widgets/UserActionBuilder/UserActionCondition.vue +0 -99
  133. package/src/widgets/UserActionBuilder/UserActionConsole.vue +0 -97
  134. package/src/widgets/UserActionBuilder/UserActionItem.vue +0 -273
  135. package/src/widgets/UserActionBuilder/UserActionOutput.vue +0 -150
  136. package/src/widgets/UserActionBuilder/UserActionOutputDelay.vue +0 -27
  137. package/src/widgets/UserActionBuilder/UserActionOutputLog.vue +0 -28
  138. package/src/widgets/UserActionBuilder/UserActionOutputReply.vue +0 -112
  139. package/src/widgets/UserActionBuilder/UserActionProps.vue +0 -213
  140. package/src/widgets/UserActionBuilder.vue +0 -314
  141. package/src/widgets/WebPageBuilder4/BackgroundColorSetting.vue +0 -54
  142. package/src/widgets/WebPageBuilder4/BackgroundPositionSetting.vue +0 -75
  143. package/src/widgets/WebPageBuilder4/BackgroundRepeatSetting.vue +0 -72
  144. package/src/widgets/WebPageBuilder4/BackgroundSizeSetting.vue +0 -72
  145. package/src/widgets/WebPageBuilder4/BlurSetting.vue +0 -72
  146. package/src/widgets/WebPageBuilder4/BorderColorSetting.vue +0 -54
  147. package/src/widgets/WebPageBuilder4/BorderRadiusSetting.vue +0 -75
  148. package/src/widgets/WebPageBuilder4/BorderSizeSetting.vue +0 -69
  149. package/src/widgets/WebPageBuilder4/BorderStyleSetting.vue +0 -72
  150. package/src/widgets/WebPageBuilder4/BoxShadowSetting.vue +0 -73
  151. package/src/widgets/WebPageBuilder4/ButtonSetting.vue +0 -65
  152. package/src/widgets/WebPageBuilder4/CarouselSetting.vue +0 -178
  153. package/src/widgets/WebPageBuilder4/DisplaySetting.vue +0 -76
  154. package/src/widgets/WebPageBuilder4/FlexAlignSetting.vue +0 -71
  155. package/src/widgets/WebPageBuilder4/FlexDirectionSetting.vue +0 -70
  156. package/src/widgets/WebPageBuilder4/FlexJustifySetting.vue +0 -74
  157. package/src/widgets/WebPageBuilder4/FlexPropertySetting.vue +0 -71
  158. package/src/widgets/WebPageBuilder4/FlexSetting.vue +0 -96
  159. package/src/widgets/WebPageBuilder4/FlexWrapSetting.vue +0 -69
  160. package/src/widgets/WebPageBuilder4/GapSetting.vue +0 -75
  161. package/src/widgets/WebPageBuilder4/GrayscaleSetting.vue +0 -68
  162. package/src/widgets/WebPageBuilder4/GridSetting.vue +0 -216
  163. package/src/widgets/WebPageBuilder4/HeightSetting.vue +0 -98
  164. package/src/widgets/WebPageBuilder4/ImageSetting.vue +0 -102
  165. package/src/widgets/WebPageBuilder4/MarginSetting.vue +0 -73
  166. package/src/widgets/WebPageBuilder4/MaxHeightSetting.vue +0 -82
  167. package/src/widgets/WebPageBuilder4/MaxWidthSetting.vue +0 -90
  168. package/src/widgets/WebPageBuilder4/MinHeightSetting.vue +0 -81
  169. package/src/widgets/WebPageBuilder4/MinWidthSetting.vue +0 -101
  170. package/src/widgets/WebPageBuilder4/MultiValueSetting.vue +0 -163
  171. package/src/widgets/WebPageBuilder4/OpacitySetting.vue +0 -77
  172. package/src/widgets/WebPageBuilder4/OverflowSetting.vue +0 -81
  173. package/src/widgets/WebPageBuilder4/PaddingSetting.vue +0 -57
  174. package/src/widgets/WebPageBuilder4/PositionSetting.vue +0 -191
  175. package/src/widgets/WebPageBuilder4/PropertySetting.vue +0 -176
  176. package/src/widgets/WebPageBuilder4/TextSetting.vue +0 -72
  177. package/src/widgets/WebPageBuilder4/TreeView.vue +0 -46
  178. package/src/widgets/WebPageBuilder4/TreeViewItem.vue +0 -303
  179. package/src/widgets/WebPageBuilder4/WebPageComponentSelector.vue +0 -141
  180. package/src/widgets/WebPageBuilder4/WebPagePropertySelector.vue +0 -168
  181. package/src/widgets/WebPageBuilder4/WidthSetting.vue +0 -105
  182. package/src/widgets/WebPageBuilder4/ZIndexSetting.vue +0 -72
  183. package/src/widgets/WebPagePreview.vue +0 -295
  184. package/src/widgets/WebPageSelector.vue +0 -173
  185. package/src/widgets/WebTemplateCreator.vue +0 -156
@@ -0,0 +1,168 @@
1
+ <template>
2
+ <div :class="['flex w-full', message.direction === 1 ? 'justify-end' : 'justify-start']">
3
+
4
+ <!-- Typing indicator (assistant, no content yet) -->
5
+ <div v-if="message.isLoading && !message.display"
6
+ class="rounded-3xl px-5 py-4">
7
+ <div class="flex gap-1.5 items-center">
8
+ <span v-for="d in 3" :key="d" class="w-2 h-2 rounded-full bg-text-300 animate-bounce"
9
+ :style="`animation-delay:${(d - 1) * 160}ms`" />
10
+ </div>
11
+ </div>
12
+
13
+ <!-- User bubble -->
14
+ <div v-else-if="message.direction === 1" class="max-w-[85%] flex flex-col items-end gap-2">
15
+ <!-- Image attachments -->
16
+ <div v-if="images.length" class="grid gap-2" :class="images.length > 1 ? 'grid-cols-2' : 'grid-cols-1'">
17
+ <img v-for="(img, n) in images" :key="n" :src="img" loading="lazy"
18
+ class="rounded-2xl max-h-60 w-full object-cover" />
19
+ </div>
20
+ <div v-if="message.body"
21
+ class="bg-base-500 rounded-3xl rounded-br-lg px-5 py-3 whitespace-pre-wrap md:text-lg leading-relaxed">
22
+ {{ message.body }}
23
+ </div>
24
+ </div>
25
+
26
+ <!-- Assistant bubble (markdown + streaming) -->
27
+ <div v-else class="max-w-full w-full">
28
+ <div :class="$style.md" class="md:text-lg leading-relaxed" v-html="renderedContent" />
29
+ <span v-if="message.isLoading" :class="$style.caret">▍</span>
30
+
31
+ <!-- Image attachments -->
32
+ <div v-if="images.length" class="mt-3 grid gap-2" :class="images.length > 1 ? 'grid-cols-2' : 'grid-cols-1 max-w-md'">
33
+ <img v-for="(img, n) in images" :key="n" :src="img" loading="lazy"
34
+ class="rounded-2xl w-full object-cover" />
35
+ </div>
36
+
37
+ <!-- File attachments -->
38
+ <div v-if="files.length" class="mt-3 flex flex-wrap gap-2">
39
+ <a v-for="(url, n) in files" :key="n" :href="url" target="_blank" rel="noopener"
40
+ class="flex items-center gap-2 p-2 pr-3 rounded-xl bg-base-300 hover:bg-base-500 transition-colors no-underline max-w-[260px]">
41
+ <span class="shrink-0 px-2 py-1 rounded-lg text-[10px] font-bold uppercase text-white" :style="{ background: fileColor(url) }">{{ fileExt(url) }}</span>
42
+ <span class="text-sm text-text truncate">{{ fileName(url) }}</span>
43
+ </a>
44
+ </div>
45
+
46
+ <!-- Footer meta -->
47
+ <div v-if="!message.isLoading && (message.ellapsed || message.model)"
48
+ class="mt-2 flex items-center gap-1.5 text-xs text-text-300">
49
+ <span v-if="message.ellapsed">{{ message.ellapsed }}ms</span>
50
+ <span v-if="message.ellapsed && message.model">·</span>
51
+ <span v-if="message.model">{{ message.model }}</span>
52
+ </div>
53
+ </div>
54
+
55
+ </div>
56
+ </template>
57
+
58
+ <script setup>
59
+ import { computed } from 'vue'
60
+ import { Marked } from 'marked'
61
+ import hljs from 'highlight.js'
62
+
63
+ const { message } = defineProps({
64
+ message: { type: Object, required: true },
65
+ })
66
+
67
+ // Local Marked instance — avoids mutating global marked state on each mount.
68
+ const markdown = new Marked({
69
+ gfm: true,
70
+ breaks: true,
71
+ renderer: {
72
+ code({ text, lang }) {
73
+ const language = lang && hljs.getLanguage(lang) ? lang : 'plaintext'
74
+ const html = hljs.highlight(text, { language }).value
75
+ return `<pre><code class="hljs language-${language}">${html}</code></pre>`
76
+ },
77
+ },
78
+ })
79
+
80
+ const renderedContent = computed(() => markdown.parse(message.display ?? message.body ?? ''))
81
+
82
+ /* ---------- Attachments ---------- */
83
+ const attachments = computed(() => message.attachments ?? [])
84
+ const images = computed(() =>
85
+ attachments.value.filter(a => a.type === 'image').map(a => a.url ?? a.image),
86
+ )
87
+ const files = computed(() =>
88
+ attachments.value.filter(a => a.type === 'file' || a.type === 'url').map(a => a.url),
89
+ )
90
+
91
+ function fileName(url) {
92
+ return (url || '').split('/').pop() || url
93
+ }
94
+ function fileExt(url) {
95
+ return ((url || '').split('.').pop() || 'file').slice(0, 4)
96
+ }
97
+
98
+ const EXT_COLORS = {
99
+ pdf: '#c0392b', doc: '#2b5fa8', docx: '#2b5fa8',
100
+ xls: '#1d6f42', xlsx: '#1d6f42', csv: '#1d6f42',
101
+ zip: '#7f8c8d', png: '#8e44ad', jpg: '#8e44ad',
102
+ }
103
+ function fileColor(url) {
104
+ return EXT_COLORS[fileExt(url).toLowerCase()] ?? '#444'
105
+ }
106
+ </script>
107
+
108
+ <style module>
109
+ /* Animated caret shown while the assistant is streaming */
110
+ .caret {
111
+ @apply inline-block ml-0.5 text-text-300;
112
+ animation: blink 1s steps(2, start) infinite;
113
+ }
114
+ @keyframes blink {
115
+ to { visibility: hidden; }
116
+ }
117
+
118
+ /* Markdown layout. Explicit !important is required because tailwind.config.js
119
+ uses `important: true`, which makes preflight resets beat these rules. */
120
+ .md :global(p) { margin: 0.5em 0 !important; }
121
+ .md :global(p:first-child){ margin-top: 0 !important; }
122
+ .md :global(p:last-child) { margin-bottom: 0 !important; }
123
+ .md :global(ul) { list-style-type: disc !important; padding-left: 1.5em !important; margin: 0.6em 0 !important; }
124
+ .md :global(ol) { list-style-type: decimal !important; padding-left: 1.5em !important; margin: 0.6em 0 !important; }
125
+ .md :global(li) { display: list-item !important; margin: 0.2em 0 !important; }
126
+ .md :global(strong) { font-weight: 700 !important; }
127
+ .md :global(em) { font-style: italic !important; }
128
+ .md :global(a) { color: #89b4fa !important; text-decoration: underline !important; }
129
+ .md :global(h1), .md :global(h2),
130
+ .md :global(h3), .md :global(h4) { font-weight: 700 !important; margin: 0.8em 0 0.3em !important; line-height: 1.3 !important; }
131
+ .md :global(h1) { font-size: 1.5em !important; }
132
+ .md :global(h2) { font-size: 1.3em !important; }
133
+ .md :global(h3) { font-size: 1.1em !important; }
134
+ .md :global(table) { border-collapse: collapse !important; width: 100% !important; margin: 0.8em 0 !important; display: block; overflow-x: auto; }
135
+ .md :global(th), .md :global(td) { border: 1px solid rgba(128,128,128,0.35) !important; padding: 0.4em 0.7em !important; text-align: left !important; }
136
+ .md :global(th) { font-weight: 600 !important; background: rgba(128,128,128,0.1) !important; }
137
+ .md :global(blockquote) { border-left: 3px solid rgba(128,128,128,0.4) !important; padding-left: 1em !important; margin: 0.6em 0 !important; opacity: 0.8; }
138
+ .md :global(hr) { border-color: rgba(128,128,128,0.3) !important; margin: 1em 0 !important; }
139
+ .md :global(:not(pre) > code) {
140
+ background: rgba(128,128,128,0.18) !important;
141
+ padding: 0.12em 0.4em !important;
142
+ border-radius: 0.3em !important;
143
+ font-size: 0.88em !important;
144
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace !important;
145
+ }
146
+
147
+ /* highlight.js — Catppuccin Mocha. !important needed for the same reason. */
148
+ .md :global(code.hljs) {
149
+ display: block !important;
150
+ overflow-x: auto !important;
151
+ padding: 1rem !important;
152
+ border-radius: 0.5rem !important;
153
+ margin: 0.5rem 0 !important;
154
+ font-size: 0.85em !important;
155
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace !important;
156
+ background: #1e1e2e !important;
157
+ color: #cdd6f4 !important;
158
+ }
159
+ .md :global(code.hljs *) { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace !important; }
160
+ .md :global(.hljs-keyword), .md :global(.hljs-selector-tag), .md :global(.hljs-title) { color: #cba6f7 !important; }
161
+ .md :global(.hljs-string), .md :global(.hljs-attr) { color: #a6e3a1 !important; }
162
+ .md :global(.hljs-number), .md :global(.hljs-literal) { color: #fab387 !important; }
163
+ .md :global(.hljs-comment) { color: #6c7086 !important; font-style: italic; }
164
+ .md :global(.hljs-built_in), .md :global(.hljs-name) { color: #89b4fa !important; }
165
+ .md :global(.hljs-variable), .md :global(.hljs-template-variable) { color: #f38ba8 !important; }
166
+ .md :global(.hljs-type) { color: #f9e2af !important; }
167
+ .md :global(.hljs-tag) { color: #f38ba8 !important; }
168
+ </style>
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <div class="flex items-end gap-2 bg-base-500 rounded-3xl border-[1px] border-border-50 p-2 pl-4 focus-within:border-border-200 transition-colors">
3
+ <textarea ref="textarea"
4
+ v-model="draft"
5
+ rows="1"
6
+ class="resize-none flex-1 bg-transparent outline-none py-2 md:text-lg leading-relaxed max-h-[40vh]"
7
+ placeholder="Message AI…"
8
+ @input="resize"
9
+ @keydown="onKeydown" />
10
+ <button type="button"
11
+ class="shrink-0 w-9 h-9 rounded-full flex items-center justify-center transition-colors"
12
+ :class="canSend ? 'bg-primary hover:opacity-90' : 'bg-base-200 opacity-50 cursor-default'"
13
+ :disabled="!canSend"
14
+ @click="sending ? emit('cancel') : submit()">
15
+ <svg v-if="!sending" width="15" height="15" class="fill-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>
16
+ <svg v-else width="13" height="13" class="fill-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M0 128C0 92.7 28.7 64 64 64H320c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128z"/></svg>
17
+ </button>
18
+ </div>
19
+ </template>
20
+
21
+ <script setup>
22
+ import { computed, nextTick, ref, useTemplateRef } from 'vue'
23
+
24
+ const { sending } = defineProps({
25
+ sending: { type: Boolean, default: false },
26
+ })
27
+
28
+ const emit = defineEmits(['send', 'cancel'])
29
+
30
+ const draft = ref('')
31
+ const textarea = useTemplateRef('textarea')
32
+
33
+ const canSend = computed(() => sending || draft.value.trim().length > 0)
34
+
35
+ function resize() {
36
+ const el = textarea.value
37
+ if (!el) return
38
+ el.style.height = 'auto'
39
+ el.style.height = `${el.scrollHeight}px`
40
+ }
41
+
42
+ function onKeydown(e) {
43
+ if (e.key === 'Enter' && !e.shiftKey) {
44
+ e.preventDefault()
45
+ submit()
46
+ }
47
+ }
48
+
49
+ function submit() {
50
+ const content = draft.value.trim()
51
+ if (!content || sending) return
52
+ draft.value = ''
53
+ nextTick(resize)
54
+ emit('send', content)
55
+ }
56
+ </script>