@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.
- package/docs/components/AIDashboard.md +472 -0
- package/docs/components/WebPageBuilder.md +125 -0
- package/package.json +7 -30
- package/src/components/AIChat.vue +479 -0
- package/src/components/AIChatItem.vue +168 -0
- package/src/components/AIChatTextbox.vue +56 -0
- package/src/components/AIDashboard.vue +1113 -0
- package/src/components/Card.vue +3 -1
- package/src/components/Chart.vue +2 -1
- package/src/components/CodeEditor.vue +121 -13
- package/src/components/ColorPicker.vue +6 -2
- package/src/components/Dashboard.vue +493 -212
- package/src/components/Dropdown.vue +0 -1
- package/src/components/HTMLEditor.vue +9 -1
- package/src/components/List.vue +226 -58
- package/src/components/MarkdownEdit.vue +123 -4
- package/src/components/PresetSelectorFilterItem.vue +1 -0
- package/src/components/TextWithTag.vue +3 -3
- package/src/components/TreeView.vue +1 -1
- package/src/components/VirtualTable.vue +166 -75
- package/src/index.js +3 -55
- package/src/instructions/dashboard.js +203 -0
- package/src/utils/ai-chat-answer.mjs +240 -0
- package/src/utils/ai-chat.js +167 -0
- package/src/utils/ai-dashboard-chat.mjs +371 -0
- package/src/utils/ai-dashboard.js +321 -0
- package/src/utils/ai-list-chat.mjs +407 -0
- package/src/utils/dashboard.js +13 -0
- package/src/utils/list.mjs +146 -91
- package/src/utils/preset-selector.mjs +183 -27
- package/src/utils/wss.js +0 -2
- package/src/widgets/ColumnSelector.vue +22 -33
- package/src/widgets/LLMChat.vue +327 -0
- package/src/widgets/PresetBar.vue +31 -8
- package/src/widgets/PresetBarPivot.vue +147 -15
- package/src/widgets/PresetBarPivotColumnEdit.vue +8 -19
- package/src/widgets/PresetBarPivotValueEdit.vue +172 -0
- package/src/widgets/PresetSelector.vue +8 -4
- package/src/widgets/WebPageBuilder.vue +153 -352
- package/docs/components/ArrayList.md +0 -39
- package/docs/components/Box.md +0 -15
- package/docs/components/ChartBar.md +0 -34
- package/docs/components/DayTimeRange.md +0 -20
- package/docs/components/ErrorText.md +0 -15
- package/docs/components/FBLogin.md +0 -21
- package/docs/components/GHeatMaps.md +0 -16
- package/docs/components/GSignIn.md +0 -21
- package/docs/components/GmapsDirection.md +0 -20
- package/docs/components/IconMenu.md +0 -20
- package/src/components/ArrayList.vue +0 -49
- package/src/components/Box.vue +0 -34
- package/src/components/ChartBar.vue +0 -192
- package/src/components/DayTimeRange.vue +0 -126
- package/src/components/ErrorText.vue +0 -57
- package/src/components/FBLogin.vue +0 -194
- package/src/components/GHeatMaps.vue +0 -317
- package/src/components/GSignIn.vue +0 -166
- package/src/components/GmapsDirection.vue +0 -191
- package/src/components/IconMenu.vue +0 -95
- package/src/components/IconPlus.vue +0 -89
- package/src/components/Image360.vue +0 -140
- package/src/components/ImageFullScreen.vue +0 -101
- package/src/components/ImportModal.vue +0 -260
- package/src/components/ListContextMenu.vue +0 -88
- package/src/components/LogViewerItem.vue +0 -116
- package/src/components/MultilineText.vue +0 -174
- package/src/components/PageBuilder.vue +0 -449
- package/src/components/SplitPane.vue +0 -129
- package/src/components/TabView.vue +0 -105
- package/src/components/TableView.vue +0 -200
- package/src/components/TableViewHead.vue +0 -159
- package/src/components/Test.vue +0 -23
- package/src/components/TextEditor.vue +0 -85
- package/src/components/Tooltip.vue +0 -143
- package/src/components/TreeViewItem1.vue +0 -282
- package/src/components/VirtualScroll.vue +0 -282
- package/src/configs/dashboard/bar.js +0 -10
- package/src/configs/dashboard/collection-1.js +0 -5
- package/src/configs/dashboard/data-table.js +0 -9
- package/src/configs/dashboard/doughnut.js +0 -7
- package/src/configs/dashboard/gheatmaps.js +0 -9
- package/src/configs/dashboard/grid-2.js +0 -34
- package/src/configs/dashboard/grid-3.js +0 -34
- package/src/configs/dashboard/grid-4.js +0 -34
- package/src/configs/dashboard/grid.js +0 -15
- package/src/configs/dashboard/metric.js +0 -10
- package/src/configs/dashboard/pie.js +0 -7
- package/src/configs/dashboard/polar-area.js +0 -7
- package/src/configs/dashboard/virtual-table.js +0 -9
- package/src/configs/icons-fa.json +0 -12
- package/src/middleware/http/trim-string.js +0 -20
- package/src/mixin/ready-state.js +0 -37
- package/src/stores/components.js +0 -36
- package/src/stores/datasource.js +0 -11
- package/src/utils/event-bus.js +0 -8
- package/src/utils/helpers.cjs +0 -635
- package/src/utils/importer.js +0 -209
- package/src/utils/listview.js +0 -1287
- package/src/utils/preset-selector.cjs +0 -1455
- package/src/utils/preset-selector.js +0 -1452
- package/src/utils/queue.js +0 -63
- package/src/utils/web.js +0 -50
- package/src/utils/web.mjs +0 -120
- package/src/widgets/BackgroundSetting.vue +0 -132
- package/src/widgets/BorderSetting.vue +0 -201
- package/src/widgets/BotEditor/BotEditorActions.vue +0 -163
- package/src/widgets/BotEditor/BotEditorSettings.vue +0 -21
- package/src/widgets/BotEditor.vue +0 -248
- package/src/widgets/BoxSetting.vue +0 -53
- package/src/widgets/CheckoutSetting.vue +0 -46
- package/src/widgets/ComponentPropertyLabel.vue +0 -35
- package/src/widgets/ComponentPropertySelector.vue +0 -115
- package/src/widgets/ComponentSetting3.vue +0 -231
- package/src/widgets/EffectSetting.vue +0 -94
- package/src/widgets/FiltersSetting.vue +0 -88
- package/src/widgets/Header.vue +0 -15
- package/src/widgets/Header0.vue +0 -41
- package/src/widgets/Header0Setting.vue +0 -75
- package/src/widgets/HeaderSetting.vue +0 -102
- package/src/widgets/LayoutSetting.vue +0 -251
- package/src/widgets/LogViewer.vue +0 -85
- package/src/widgets/MarginPaddingSetting.vue +0 -58
- package/src/widgets/MarginSetting.vue +0 -142
- package/src/widgets/MenuEditor.vue +0 -251
- package/src/widgets/PaddingSetting.vue +0 -142
- package/src/widgets/SizingSetting.vue +0 -306
- package/src/widgets/SpacingSetting.vue +0 -77
- package/src/widgets/StyleSetting.vue +0 -543
- package/src/widgets/StyleSetting2.vue +0 -90
- package/src/widgets/TransitionSetting.vue +0 -64
- package/src/widgets/TypographySetting.vue +0 -334
- package/src/widgets/UserActionBuilder/UserActionCondition.vue +0 -99
- package/src/widgets/UserActionBuilder/UserActionConsole.vue +0 -97
- package/src/widgets/UserActionBuilder/UserActionItem.vue +0 -273
- package/src/widgets/UserActionBuilder/UserActionOutput.vue +0 -150
- package/src/widgets/UserActionBuilder/UserActionOutputDelay.vue +0 -27
- package/src/widgets/UserActionBuilder/UserActionOutputLog.vue +0 -28
- package/src/widgets/UserActionBuilder/UserActionOutputReply.vue +0 -112
- package/src/widgets/UserActionBuilder/UserActionProps.vue +0 -213
- package/src/widgets/UserActionBuilder.vue +0 -314
- package/src/widgets/WebPageBuilder4/BackgroundColorSetting.vue +0 -54
- package/src/widgets/WebPageBuilder4/BackgroundPositionSetting.vue +0 -75
- package/src/widgets/WebPageBuilder4/BackgroundRepeatSetting.vue +0 -72
- package/src/widgets/WebPageBuilder4/BackgroundSizeSetting.vue +0 -72
- package/src/widgets/WebPageBuilder4/BlurSetting.vue +0 -72
- package/src/widgets/WebPageBuilder4/BorderColorSetting.vue +0 -54
- package/src/widgets/WebPageBuilder4/BorderRadiusSetting.vue +0 -75
- package/src/widgets/WebPageBuilder4/BorderSizeSetting.vue +0 -69
- package/src/widgets/WebPageBuilder4/BorderStyleSetting.vue +0 -72
- package/src/widgets/WebPageBuilder4/BoxShadowSetting.vue +0 -73
- package/src/widgets/WebPageBuilder4/ButtonSetting.vue +0 -65
- package/src/widgets/WebPageBuilder4/CarouselSetting.vue +0 -178
- package/src/widgets/WebPageBuilder4/DisplaySetting.vue +0 -76
- package/src/widgets/WebPageBuilder4/FlexAlignSetting.vue +0 -71
- package/src/widgets/WebPageBuilder4/FlexDirectionSetting.vue +0 -70
- package/src/widgets/WebPageBuilder4/FlexJustifySetting.vue +0 -74
- package/src/widgets/WebPageBuilder4/FlexPropertySetting.vue +0 -71
- package/src/widgets/WebPageBuilder4/FlexSetting.vue +0 -96
- package/src/widgets/WebPageBuilder4/FlexWrapSetting.vue +0 -69
- package/src/widgets/WebPageBuilder4/GapSetting.vue +0 -75
- package/src/widgets/WebPageBuilder4/GrayscaleSetting.vue +0 -68
- package/src/widgets/WebPageBuilder4/GridSetting.vue +0 -216
- package/src/widgets/WebPageBuilder4/HeightSetting.vue +0 -98
- package/src/widgets/WebPageBuilder4/ImageSetting.vue +0 -102
- package/src/widgets/WebPageBuilder4/MarginSetting.vue +0 -73
- package/src/widgets/WebPageBuilder4/MaxHeightSetting.vue +0 -82
- package/src/widgets/WebPageBuilder4/MaxWidthSetting.vue +0 -90
- package/src/widgets/WebPageBuilder4/MinHeightSetting.vue +0 -81
- package/src/widgets/WebPageBuilder4/MinWidthSetting.vue +0 -101
- package/src/widgets/WebPageBuilder4/MultiValueSetting.vue +0 -163
- package/src/widgets/WebPageBuilder4/OpacitySetting.vue +0 -77
- package/src/widgets/WebPageBuilder4/OverflowSetting.vue +0 -81
- package/src/widgets/WebPageBuilder4/PaddingSetting.vue +0 -57
- package/src/widgets/WebPageBuilder4/PositionSetting.vue +0 -191
- package/src/widgets/WebPageBuilder4/PropertySetting.vue +0 -176
- package/src/widgets/WebPageBuilder4/TextSetting.vue +0 -72
- package/src/widgets/WebPageBuilder4/TreeView.vue +0 -46
- package/src/widgets/WebPageBuilder4/TreeViewItem.vue +0 -303
- package/src/widgets/WebPageBuilder4/WebPageComponentSelector.vue +0 -141
- package/src/widgets/WebPageBuilder4/WebPagePropertySelector.vue +0 -168
- package/src/widgets/WebPageBuilder4/WidthSetting.vue +0 -105
- package/src/widgets/WebPageBuilder4/ZIndexSetting.vue +0 -72
- package/src/widgets/WebPagePreview.vue +0 -295
- package/src/widgets/WebPageSelector.vue +0 -173
- 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>
|