@meistrari/chat-nuxt 4.4.1-rc.1 → 4.5.0-rc.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/dist/module.json +1 -1
- package/dist/runtime/conversations/components/chat-conversation-creator-filter.vue +22 -11
- package/dist/runtime/conversations/components/chat-conversation-list.vue +245 -159
- package/dist/runtime/embed/components/chat-embed-inner.d.vue.ts +16 -8
- package/dist/runtime/embed/components/chat-embed-inner.vue +37 -45
- package/dist/runtime/embed/components/chat-embed-inner.vue.d.ts +16 -8
- package/dist/runtime/embed/components/chat-embed.d.vue.ts +4 -2
- package/dist/runtime/embed/components/chat-embed.vue +3 -0
- package/dist/runtime/embed/components/chat-embed.vue.d.ts +4 -2
- package/dist/runtime/embed/components/chat-sidebar-toggle.d.vue.ts +10 -0
- package/dist/runtime/embed/components/chat-sidebar-toggle.vue +49 -0
- package/dist/runtime/embed/components/chat-sidebar-toggle.vue.d.ts +10 -0
- package/dist/runtime/embed/components/meistrari-chat-embed.d.vue.ts +2 -0
- package/dist/runtime/embed/components/meistrari-chat-embed.vue +3 -0
- package/dist/runtime/embed/components/meistrari-chat-embed.vue.d.ts +2 -0
- package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue +6 -2
- package/dist/runtime/embed/empty-state-labels.d.ts +5 -0
- package/dist/runtime/embed/empty-state-labels.js +9 -0
- package/dist/runtime/embed/feature-config.d.ts +3 -0
- package/dist/runtime/embed/feature-config.js +2 -1
- package/dist/runtime/embed/types.d.ts +13 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { useConversations } from "../composables/conversations";
|
|
3
|
-
import { conversationCreatorFilterOptions } from "../../types/schemas/chat/ui.js";
|
|
4
3
|
import { hasConversationCreatorFilterResults } from "../creator-filter";
|
|
5
4
|
import { useEmbedConfig } from "../../embed/composables/embed-config";
|
|
5
|
+
const FILTER_OPTIONS = [
|
|
6
|
+
{ value: "all", label: "Todas" },
|
|
7
|
+
{ value: "mine", label: "Minhas" }
|
|
8
|
+
];
|
|
6
9
|
const embedConfig = useEmbedConfig();
|
|
7
10
|
const { conversationCreatorFilter, conversationCreatorFilterCounts } = useConversations();
|
|
8
|
-
const filterOptions = computed(() => conversationCreatorFilterOptions.map((option) => ({
|
|
9
|
-
value: option.value,
|
|
10
|
-
label: `${option.label} ${conversationCreatorFilterCounts.value[option.value]}`
|
|
11
|
-
})));
|
|
12
11
|
const showConversationCreatorFilter = computed(
|
|
13
12
|
() => !embedConfig?.hideConversationCreatorFilter.value && hasConversationCreatorFilterResults(conversationCreatorFilterCounts.value)
|
|
14
13
|
);
|
|
@@ -23,13 +22,25 @@ function handleFilterUpdate(value) {
|
|
|
23
22
|
</script>
|
|
24
23
|
|
|
25
24
|
<template>
|
|
26
|
-
<
|
|
25
|
+
<TelaSelectMenu
|
|
27
26
|
v-if="showConversationCreatorFilter"
|
|
28
27
|
:model-value="conversationCreatorFilter"
|
|
29
|
-
:options="
|
|
30
|
-
class="w-
|
|
31
|
-
|
|
32
|
-
aria-label="Filtrar conversas por criador"
|
|
28
|
+
:options="FILTER_OPTIONS"
|
|
29
|
+
content-class="min-w-130px! rounded-16px!"
|
|
30
|
+
enable-portal
|
|
33
31
|
@update:model-value="handleFilterUpdate"
|
|
34
|
-
|
|
32
|
+
>
|
|
33
|
+
<template #trigger>
|
|
34
|
+
<!-- Styled to match the sibling "+" icon button; the caret is hidden. -->
|
|
35
|
+
<TelaSelectMenuTrigger
|
|
36
|
+
aria-label="Filtrar conversas por criador"
|
|
37
|
+
class="size-22px! p-3px! gap-0! justify-center! rounded-6px! border-0! bg-transparent! shadow-none! text-icon-tertiary hover:bg-muted! hover:text-primary data-[state=open]:bg-muted! data-[state=open]:text-primary [&>:last-child]:hidden"
|
|
38
|
+
>
|
|
39
|
+
<TelaIcon
|
|
40
|
+
name="i-ph-sliders-horizontal"
|
|
41
|
+
size="16px"
|
|
42
|
+
/>
|
|
43
|
+
</TelaSelectMenuTrigger>
|
|
44
|
+
</template>
|
|
45
|
+
</TelaSelectMenu>
|
|
35
46
|
</template>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { useVirtualList } from "@vueuse/core";
|
|
2
|
+
import { usePreferredReducedMotion, useVirtualList } from "@vueuse/core";
|
|
3
3
|
import { useConversationList } from "../composables/conversation-list";
|
|
4
4
|
const props = defineProps({
|
|
5
5
|
conversations: { type: null, required: true },
|
|
@@ -55,6 +55,46 @@ const ROW_HEIGHT = 32;
|
|
|
55
55
|
const HEADING_HEIGHT = 28;
|
|
56
56
|
const ERROR_ROW_HEIGHT = 20;
|
|
57
57
|
const SECTION_GAP = 10;
|
|
58
|
+
const FOLDER_TOGGLE_DURATION = 270;
|
|
59
|
+
const togglingFolder = shallowRef(null);
|
|
60
|
+
const reducedMotion = usePreferredReducedMotion();
|
|
61
|
+
let togglingFrame = 0;
|
|
62
|
+
function easeOutCubic(t) {
|
|
63
|
+
return 1 - (1 - t) ** 3;
|
|
64
|
+
}
|
|
65
|
+
function stopFolderAnimation() {
|
|
66
|
+
if (togglingFrame)
|
|
67
|
+
cancelAnimationFrame(togglingFrame);
|
|
68
|
+
togglingFrame = 0;
|
|
69
|
+
togglingFolder.value = null;
|
|
70
|
+
}
|
|
71
|
+
function animateFolder(groupId, from, to) {
|
|
72
|
+
stopFolderAnimation();
|
|
73
|
+
if (typeof requestAnimationFrame === "undefined" || reducedMotion.value === "reduce")
|
|
74
|
+
return;
|
|
75
|
+
const startedAt = Date.now();
|
|
76
|
+
const tick = () => {
|
|
77
|
+
const t = Math.min((Date.now() - startedAt) / FOLDER_TOGGLE_DURATION, 1);
|
|
78
|
+
if (t >= 1) {
|
|
79
|
+
togglingFrame = 0;
|
|
80
|
+
togglingFolder.value = null;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
togglingFolder.value = { groupId, progress: from + (to - from) * easeOutCubic(t) };
|
|
84
|
+
togglingFrame = requestAnimationFrame(tick);
|
|
85
|
+
};
|
|
86
|
+
tick();
|
|
87
|
+
}
|
|
88
|
+
function folderProgress(groupId) {
|
|
89
|
+
return togglingFolder.value?.groupId === groupId ? togglingFolder.value.progress : null;
|
|
90
|
+
}
|
|
91
|
+
function handleToggleGroup(groupId) {
|
|
92
|
+
const wasOpen = groupItems.value.find((group) => group.group.id === groupId)?.open ?? false;
|
|
93
|
+
const from = folderProgress(groupId) ?? (wasOpen ? 1 : 0);
|
|
94
|
+
toggleGroup(groupId);
|
|
95
|
+
animateFolder(groupId, from, wasOpen ? 0 : 1);
|
|
96
|
+
}
|
|
97
|
+
onBeforeUnmount(stopFolderAnimation);
|
|
58
98
|
const rows = computed(() => {
|
|
59
99
|
const result = [];
|
|
60
100
|
if (groupItems.value.length > 0)
|
|
@@ -64,14 +104,18 @@ const rows = computed(() => {
|
|
|
64
104
|
result.push({ type: "group", key: `group:${groupId}`, height: ROW_HEIGHT, groupId, groupBucket });
|
|
65
105
|
if (groupBucket.isRenaming && renameGroupError.value)
|
|
66
106
|
result.push({ type: "error", key: `error:${groupId}`, height: ERROR_ROW_HEIGHT, groupId, groupBucket });
|
|
67
|
-
|
|
107
|
+
const progress = folderProgress(groupId);
|
|
108
|
+
if (!groupBucket.open && progress === null)
|
|
68
109
|
continue;
|
|
69
|
-
|
|
70
|
-
result.push({ type: "conversation", key: conversation.id, height: ROW_HEIGHT, groupId, conversation });
|
|
110
|
+
const children = groupBucket.items.map((conversation) => ({ type: "conversation", key: conversation.id, height: ROW_HEIGHT, groupId, conversation }));
|
|
71
111
|
if (groupBucket.showNewConversationAction)
|
|
72
|
-
|
|
112
|
+
children.push({ type: "new", key: `new:${groupId}`, height: ROW_HEIGHT, groupId, groupBucket });
|
|
73
113
|
else if (groupBucket.showSearchEmpty)
|
|
74
|
-
|
|
114
|
+
children.push({ type: "empty", key: `empty:${groupId}`, height: ROW_HEIGHT, groupId, groupBucket });
|
|
115
|
+
const scale = progress ?? 1;
|
|
116
|
+
const clipHeight = children.length * ROW_HEIGHT * scale;
|
|
117
|
+
for (const child of children)
|
|
118
|
+
result.push({ ...child, height: ROW_HEIGHT * scale, layoutHeight: ROW_HEIGHT, clipHeight });
|
|
75
119
|
}
|
|
76
120
|
if (groupItems.value.length > 0)
|
|
77
121
|
result.push({ type: "spacer", key: "spacer", height: SECTION_GAP, groupId: null });
|
|
@@ -79,8 +123,20 @@ const rows = computed(() => {
|
|
|
79
123
|
for (const conversation of ungroupedConversations.value)
|
|
80
124
|
result.push({ type: "conversation", key: conversation.id, height: ROW_HEIGHT, groupId: null, conversation });
|
|
81
125
|
let offset = 0;
|
|
126
|
+
let clip = null;
|
|
82
127
|
return result.map((row) => {
|
|
83
|
-
|
|
128
|
+
let clipTop;
|
|
129
|
+
let localOffset = 0;
|
|
130
|
+
if (row.clipHeight !== void 0 && row.groupId) {
|
|
131
|
+
if (clip?.groupId !== row.groupId)
|
|
132
|
+
clip = { groupId: row.groupId, top: offset, localOffset: 0 };
|
|
133
|
+
clipTop = clip.top;
|
|
134
|
+
localOffset = clip.localOffset;
|
|
135
|
+
clip.localOffset += row.layoutHeight ?? row.height;
|
|
136
|
+
} else {
|
|
137
|
+
clip = null;
|
|
138
|
+
}
|
|
139
|
+
const positionedRow = { ...row, offset, clipTop, localOffset };
|
|
84
140
|
offset += row.height;
|
|
85
141
|
return positionedRow;
|
|
86
142
|
});
|
|
@@ -100,6 +156,25 @@ const renderedRows = computed(() => {
|
|
|
100
156
|
}
|
|
101
157
|
return [...indexes].sort((a, b) => a - b).map((index) => ({ ...rows.value[index], index }));
|
|
102
158
|
});
|
|
159
|
+
const renderedBlocks = computed(() => {
|
|
160
|
+
const blocks = [];
|
|
161
|
+
const clipBlocks = /* @__PURE__ */ new Map();
|
|
162
|
+
for (const row of renderedRows.value) {
|
|
163
|
+
if (row.clipTop === void 0 || !row.groupId) {
|
|
164
|
+
blocks.push({ key: row.key, top: row.offset, height: row.height, clip: false, opacity: 1, rows: [row] });
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const existing = clipBlocks.get(row.groupId);
|
|
168
|
+
if (existing) {
|
|
169
|
+
existing.rows.push(row);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const block = { key: `clip:${row.groupId}`, top: row.clipTop, height: row.clipHeight ?? 0, clip: true, opacity: folderProgress(row.groupId) ?? 1, rows: [row] };
|
|
173
|
+
clipBlocks.set(row.groupId, block);
|
|
174
|
+
blocks.push(block);
|
|
175
|
+
}
|
|
176
|
+
return blocks;
|
|
177
|
+
});
|
|
103
178
|
const totalHeight = computed(() => {
|
|
104
179
|
const last = rows.value.at(-1);
|
|
105
180
|
return last ? last.offset + last.height : 0;
|
|
@@ -205,176 +280,187 @@ function dropTargetClass(row) {
|
|
|
205
280
|
</p>
|
|
206
281
|
</div>
|
|
207
282
|
|
|
208
|
-
<div v-else v-bind="containerProps" flex-1 min-h-0 mt-4px>
|
|
283
|
+
<div v-else v-bind="containerProps" flex-1 min-h-0 mt-4px no-scrollbar>
|
|
209
284
|
<div relative :style="{ height: `${totalHeight}px` }">
|
|
210
285
|
<div
|
|
211
|
-
v-for="
|
|
212
|
-
:key="
|
|
213
|
-
:style="{ height: `${row.height}px`, top: `${row.offset}px` }"
|
|
286
|
+
v-for="block in renderedBlocks"
|
|
287
|
+
:key="block.key"
|
|
214
288
|
absolute inset-x-0
|
|
215
|
-
:
|
|
216
|
-
|
|
217
|
-
:class="dropTargetClass(row)"
|
|
218
|
-
@keydown="handleRowKeydown($event, row.index)"
|
|
219
|
-
@focusin="retainedRowKey = row.key"
|
|
220
|
-
@pointerdown="retainedRowKey = row.key"
|
|
221
|
-
@dragstart.capture="draggedRowKey = row.key"
|
|
222
|
-
@dragend.capture="draggedRowKey = null"
|
|
223
|
-
v-on="dropListeners(row)"
|
|
289
|
+
:class="block.clip ? 'overflow-hidden' : ''"
|
|
290
|
+
:style="{ top: `${block.top}px`, height: `${block.height}px`, opacity: block.opacity }"
|
|
224
291
|
>
|
|
225
|
-
<div v-if="row.type === 'folders'" h-full flex items-center justify-between pl-12px pr-2px>
|
|
226
|
-
<span body-12-medium text-tertiary>
|
|
227
|
-
Pastas
|
|
228
|
-
</span>
|
|
229
|
-
<TelaIconButton
|
|
230
|
-
icon="i-ph-plus"
|
|
231
|
-
color="secondary"
|
|
232
|
-
size="sm"
|
|
233
|
-
button-class="p-3px! rounded-6px!"
|
|
234
|
-
aria-label="Nova pasta"
|
|
235
|
-
@click="createNewGroup"
|
|
236
|
-
/>
|
|
237
|
-
</div>
|
|
238
|
-
<div v-else-if="row.type === 'ungrouped'" h-full flex items-center justify-between pl-12px pr-2px>
|
|
239
|
-
<span body-12-medium text-secondary>
|
|
240
|
-
{{ ungroupedConversationLabel }}
|
|
241
|
-
</span>
|
|
242
|
-
<TelaIconButton
|
|
243
|
-
icon="i-ph-plus"
|
|
244
|
-
color="secondary"
|
|
245
|
-
size="sm"
|
|
246
|
-
button-class="p-3px! rounded-6px!"
|
|
247
|
-
aria-label="Nova conversa"
|
|
248
|
-
@click="startUngroupedConversation"
|
|
249
|
-
/>
|
|
250
|
-
</div>
|
|
251
292
|
<div
|
|
252
|
-
v-
|
|
253
|
-
|
|
254
|
-
|
|
293
|
+
v-for="row in block.rows"
|
|
294
|
+
:key="row.key"
|
|
295
|
+
:style="{ height: `${row.layoutHeight ?? row.height}px`, top: `${row.localOffset}px` }"
|
|
296
|
+
absolute inset-x-0
|
|
297
|
+
:data-row-index="row.index"
|
|
255
298
|
rounded-8px
|
|
256
|
-
class="
|
|
257
|
-
|
|
299
|
+
:class="dropTargetClass(row)"
|
|
300
|
+
@keydown="handleRowKeydown($event, row.index)"
|
|
301
|
+
@focusin="retainedRowKey = row.key"
|
|
302
|
+
@pointerdown="retainedRowKey = row.key"
|
|
303
|
+
@dragstart.capture="draggedRowKey = row.key"
|
|
304
|
+
@dragend.capture="draggedRowKey = null"
|
|
305
|
+
v-on="dropListeners(row)"
|
|
258
306
|
>
|
|
259
|
-
<div flex items-center justify-between
|
|
260
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
307
|
+
<div v-if="row.type === 'folders'" h-full flex items-center justify-between pl-12px pr-2px>
|
|
308
|
+
<span body-12-medium text-tertiary>
|
|
309
|
+
Pastas
|
|
310
|
+
</span>
|
|
311
|
+
<TelaIconButton
|
|
312
|
+
icon="i-ph-plus"
|
|
313
|
+
color="secondary"
|
|
314
|
+
size="sm"
|
|
315
|
+
button-class="p-3px! rounded-6px!"
|
|
316
|
+
aria-label="Nova pasta"
|
|
317
|
+
@click="createNewGroup"
|
|
318
|
+
/>
|
|
319
|
+
</div>
|
|
320
|
+
<div v-else-if="row.type === 'ungrouped'" h-full flex items-center justify-between pl-12px pr-2px>
|
|
321
|
+
<span body-12-medium text-tertiary>
|
|
322
|
+
{{ ungroupedConversationLabel }}
|
|
323
|
+
</span>
|
|
324
|
+
<div flex items-center gap-2px>
|
|
325
|
+
<ChatConversationCreatorFilter />
|
|
326
|
+
<TelaIconButton
|
|
327
|
+
icon="i-ph-plus"
|
|
328
|
+
color="secondary"
|
|
329
|
+
size="sm"
|
|
330
|
+
button-class="p-3px! rounded-6px!"
|
|
331
|
+
aria-label="Nova conversa"
|
|
332
|
+
@click="startUngroupedConversation"
|
|
266
333
|
/>
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
:disabled="groupActionLoading"
|
|
280
|
-
@blur="handleRenameGroupBlur"
|
|
281
|
-
@keydown.enter.prevent="submitRenameGroup"
|
|
282
|
-
@keydown.escape.prevent.stop="cancelRenameGroup"
|
|
283
|
-
@click.stop
|
|
284
|
-
@dblclick.stop
|
|
285
|
-
>
|
|
286
|
-
<div
|
|
287
|
-
aria-hidden="true"
|
|
288
|
-
absolute inset-x-0 bottom--0.5px h-0.5px pointer-events-none
|
|
289
|
-
:class="renameGroupError ? 'bg-red-500' : 'bg-border'"
|
|
290
|
-
/>
|
|
291
|
-
</div>
|
|
292
|
-
</template>
|
|
293
|
-
|
|
294
|
-
<template v-else>
|
|
295
|
-
<button
|
|
296
|
-
type="button"
|
|
297
|
-
h-full flex-1 min-w-0
|
|
298
|
-
flex items-center gap-8px
|
|
299
|
-
text-left
|
|
300
|
-
cursor-pointer
|
|
301
|
-
:aria-expanded="row.groupBucket.open"
|
|
302
|
-
@click="toggleGroup(row.groupBucket.group.id)"
|
|
303
|
-
>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
<div
|
|
337
|
+
v-if="row.type === 'group'"
|
|
338
|
+
pl-12px pr-4px
|
|
339
|
+
h-30px
|
|
340
|
+
rounded-8px
|
|
341
|
+
class="group hover:bg-subtle"
|
|
342
|
+
:data-active="row.groupBucket.open && !row.groupBucket.dropTarget"
|
|
343
|
+
>
|
|
344
|
+
<div flex items-center justify-between h-full gap-8px>
|
|
345
|
+
<template v-if="row.groupBucket.isRenaming">
|
|
304
346
|
<TelaIcon
|
|
305
347
|
:name="row.groupBucket.folderIcon"
|
|
306
|
-
size="
|
|
307
|
-
color="icon-
|
|
348
|
+
size="18px"
|
|
349
|
+
color="icon-secondary"
|
|
308
350
|
shrink-0
|
|
309
351
|
/>
|
|
310
|
-
<
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
352
|
+
<div relative flex-1 min-w-0 flex items-center>
|
|
353
|
+
<input
|
|
354
|
+
:ref="(element) => setRenameGroupInputRef(element, row.groupBucket.group.id)"
|
|
355
|
+
v-model="renameGroupName"
|
|
356
|
+
type="text"
|
|
357
|
+
autocomplete="off"
|
|
358
|
+
maxlength="80"
|
|
359
|
+
aria-label="Nome da pasta"
|
|
360
|
+
w-full min-w-0
|
|
361
|
+
bg-transparent border-none outline-none
|
|
362
|
+
body-14-medium text-taupe-600
|
|
363
|
+
p-0
|
|
364
|
+
:disabled="groupActionLoading"
|
|
365
|
+
@blur="handleRenameGroupBlur"
|
|
366
|
+
@keydown.enter.prevent="submitRenameGroup"
|
|
367
|
+
@keydown.escape.prevent.stop="cancelRenameGroup"
|
|
368
|
+
@click.stop
|
|
369
|
+
@dblclick.stop
|
|
370
|
+
>
|
|
371
|
+
<div
|
|
372
|
+
aria-hidden="true"
|
|
373
|
+
absolute inset-x-0 bottom--0.5px h-0.5px pointer-events-none
|
|
374
|
+
:class="renameGroupError ? 'bg-red-500' : 'bg-border'"
|
|
375
|
+
/>
|
|
376
|
+
</div>
|
|
377
|
+
</template>
|
|
314
378
|
|
|
315
|
-
<
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
379
|
+
<template v-else>
|
|
380
|
+
<button
|
|
381
|
+
type="button"
|
|
382
|
+
h-full flex-1 min-w-0
|
|
383
|
+
flex items-center gap-8px
|
|
384
|
+
text-left
|
|
385
|
+
cursor-pointer
|
|
386
|
+
:aria-expanded="row.groupBucket.open"
|
|
387
|
+
@click="handleToggleGroup(row.groupBucket.group.id)"
|
|
322
388
|
>
|
|
323
|
-
<
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
class="group hover:!bg-lowered data-[state=open]:!bg-lowered"
|
|
329
|
-
icon-class="group-hover:icon-tertiary group-data-[state=open]:icon-tertiary"
|
|
330
|
-
@click.prevent.stop
|
|
389
|
+
<TelaIcon
|
|
390
|
+
:name="row.groupBucket.folderIcon"
|
|
391
|
+
size="16px"
|
|
392
|
+
color="icon-tertiary"
|
|
393
|
+
shrink-0
|
|
331
394
|
/>
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
395
|
+
<span min-w-0 truncate body-14-medium text-taupe-700>
|
|
396
|
+
{{ row.groupBucket.group.name }}
|
|
397
|
+
</span>
|
|
398
|
+
</button>
|
|
399
|
+
|
|
400
|
+
<div
|
|
401
|
+
flex items-center
|
|
402
|
+
class="opacity-0 group-hover:opacity-100 focus-within:opacity-100 [&:has([data-state=open])]:opacity-100"
|
|
403
|
+
>
|
|
404
|
+
<TelaDropdownMenu
|
|
405
|
+
:items="row.groupBucket.menuItems"
|
|
406
|
+
content-class="chat-sidebar-floating"
|
|
407
|
+
>
|
|
408
|
+
<TelaIconButton
|
|
409
|
+
icon="i-ph-dots-three"
|
|
410
|
+
color="secondary"
|
|
411
|
+
size="3xs"
|
|
412
|
+
aria-label="Ações da pasta"
|
|
413
|
+
class="group hover:!bg-lowered data-[state=open]:!bg-lowered"
|
|
414
|
+
icon-class="group-hover:icon-tertiary group-data-[state=open]:icon-tertiary"
|
|
415
|
+
@click.prevent.stop
|
|
416
|
+
/>
|
|
417
|
+
</TelaDropdownMenu>
|
|
418
|
+
</div>
|
|
419
|
+
</template>
|
|
420
|
+
</div>
|
|
335
421
|
</div>
|
|
336
|
-
</div>
|
|
337
422
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
423
|
+
<div v-if="row.type === 'error'" ml-44px mr-8px>
|
|
424
|
+
<span block truncate text-12px text-red-600 leading-16px aria-live="polite" :title="renameGroupError ?? void 0">
|
|
425
|
+
{{ renameGroupError }}
|
|
426
|
+
</span>
|
|
427
|
+
</div>
|
|
428
|
+
<ChatConversationItem
|
|
429
|
+
v-if="row.type === 'conversation'"
|
|
430
|
+
v-bind="getConversationItemProps(row.conversation)"
|
|
431
|
+
:item-class="row.groupId ? 'ml-24px' : void 0"
|
|
432
|
+
@delete="handleDelete"
|
|
433
|
+
@rename="handleRename"
|
|
434
|
+
@duplicate="handleDuplicate"
|
|
435
|
+
@export="handleExport"
|
|
436
|
+
@drag-start="handleConversationDragStart"
|
|
437
|
+
@drag-end="clearDragState"
|
|
438
|
+
/>
|
|
439
|
+
<button
|
|
440
|
+
v-if="row.type === 'new'"
|
|
441
|
+
type="button"
|
|
442
|
+
relative h-30px pl-12px w-full
|
|
443
|
+
flex items-center justify-between
|
|
444
|
+
rounded-8px
|
|
445
|
+
text-left
|
|
446
|
+
hover:bg-subtle
|
|
447
|
+
cursor-pointer
|
|
448
|
+
@click="startConversationInGroup(row.groupBucket.group.id)"
|
|
449
|
+
>
|
|
450
|
+
<span body-14-medium text-taupe-500 ml-24px>
|
|
451
|
+
Nova conversa
|
|
452
|
+
</span>
|
|
453
|
+
</button>
|
|
454
|
+
<div v-if="row.type === 'empty'" h-full flex items-center ml-36px>
|
|
455
|
+
<span body-14-medium text-tertiary>Sem resultados</span>
|
|
456
|
+
</div>
|
|
457
|
+
<div
|
|
458
|
+
v-if="isNestedRow(row)"
|
|
459
|
+
aria-hidden="true"
|
|
460
|
+
absolute top-0 left-19px w-0.5px bg-border pointer-events-none
|
|
461
|
+
:class="isLastNestedRow(row.index) ? 'h-30px' : 'h-full'"
|
|
462
|
+
/>
|
|
371
463
|
</div>
|
|
372
|
-
<div
|
|
373
|
-
v-if="isNestedRow(row)"
|
|
374
|
-
aria-hidden="true"
|
|
375
|
-
absolute top-0 left-19px w-0.5px bg-border pointer-events-none
|
|
376
|
-
:class="isLastNestedRow(row.index) ? 'h-30px' : 'h-full'"
|
|
377
|
-
/>
|
|
378
464
|
</div>
|
|
379
465
|
</div>
|
|
380
466
|
</div>
|
|
@@ -19,21 +19,29 @@ type __VLS_Props = {
|
|
|
19
19
|
user?: EmbedUser | null;
|
|
20
20
|
features?: Partial<ChatFeatureConfig>;
|
|
21
21
|
};
|
|
22
|
-
declare var
|
|
22
|
+
declare var __VLS_6: {}, __VLS_80: {}, __VLS_93: {
|
|
23
23
|
message: useChatAuth;
|
|
24
|
-
},
|
|
24
|
+
}, __VLS_105: {}, __VLS_133: {}, __VLS_142: {}, __VLS_206: {}, __VLS_225: {
|
|
25
25
|
message: useChatAuth;
|
|
26
|
-
};
|
|
26
|
+
}, __VLS_237: {};
|
|
27
27
|
type __VLS_Slots = {} & {
|
|
28
|
-
'sidebar-bottom'?: (props: typeof
|
|
28
|
+
'sidebar-bottom'?: (props: typeof __VLS_6) => any;
|
|
29
|
+
} & {
|
|
30
|
+
'composer-bottom'?: (props: typeof __VLS_80) => any;
|
|
31
|
+
} & {
|
|
32
|
+
'failed-message-actions'?: (props: typeof __VLS_93) => any;
|
|
33
|
+
} & {
|
|
34
|
+
'composer-bottom'?: (props: typeof __VLS_105) => any;
|
|
35
|
+
} & {
|
|
36
|
+
'topbar-leading'?: (props: typeof __VLS_133) => any;
|
|
29
37
|
} & {
|
|
30
|
-
'
|
|
38
|
+
'topbar-leading'?: (props: typeof __VLS_142) => any;
|
|
31
39
|
} & {
|
|
32
|
-
'
|
|
40
|
+
'composer-bottom'?: (props: typeof __VLS_206) => any;
|
|
33
41
|
} & {
|
|
34
|
-
'
|
|
42
|
+
'failed-message-actions'?: (props: typeof __VLS_225) => any;
|
|
35
43
|
} & {
|
|
36
|
-
'
|
|
44
|
+
'composer-bottom'?: (props: typeof __VLS_237) => any;
|
|
37
45
|
};
|
|
38
46
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
39
47
|
"update:conversationId": (value: string | null) => any;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { inject, provide } from "vue";
|
|
2
|
+
import { inject, provide, useSlots } from "vue";
|
|
3
3
|
import { useChatHarnessAdapter } from "../composables/harness-adapter";
|
|
4
4
|
import ChatMessageList from "../../messages/components/chat-message-list.vue";
|
|
5
5
|
import { CHAT_COMPOSER_EXTENSION_KEY } from "../../types/composer";
|
|
@@ -11,6 +11,7 @@ import { exportConversationToMarkdown } from "../../conversations/export";
|
|
|
11
11
|
import { CHAT_MOBILE_BREAKPOINT } from "../../shared/breakpoints";
|
|
12
12
|
import ChatMobileFilePreview from "./mobile/chat-mobile-file-preview.vue";
|
|
13
13
|
import ChatMobileShell from "./mobile/chat-mobile-shell.vue";
|
|
14
|
+
import ChatSidebarToggle from "./chat-sidebar-toggle.vue";
|
|
14
15
|
import ChatMessageInput from "../../messages/components/chat-message-input.vue";
|
|
15
16
|
import { normalizeConversationId } from "../../conversations/scope";
|
|
16
17
|
import { resolveChatFeatures } from "../feature-config";
|
|
@@ -32,6 +33,7 @@ import { useConversationFiles } from "../../files/composables/conversation-files
|
|
|
32
33
|
import { useConversationFilesPanel } from "../../files/composables/conversation-files-panel";
|
|
33
34
|
import { useConversations } from "../../conversations/composables/conversations";
|
|
34
35
|
import { useEmbedConfig } from "../composables/embed-config";
|
|
36
|
+
import { resolveEmptyStateLabels } from "../empty-state-labels";
|
|
35
37
|
import { useAgentWake } from "../composables/use-agent-wake";
|
|
36
38
|
import { useFileDrop } from "../../files/composables/file-drop";
|
|
37
39
|
import { useFilePreviewPanel } from "../../files/composables/file-preview-panel";
|
|
@@ -68,6 +70,7 @@ const isSidebarCollapsed = ref(props.sidebar?.defaultCollapsed ?? false);
|
|
|
68
70
|
const sidebarCollapsed = computed(
|
|
69
71
|
() => sidebarCollapsible.value && isSidebarCollapsed.value
|
|
70
72
|
);
|
|
73
|
+
const sidebarToggle = computed(() => props.sidebar?.toggle ?? "edge");
|
|
71
74
|
const sidebarToggleIcon = computed(() => {
|
|
72
75
|
const pointsRight = sidebarPosition.value === "right" ? !sidebarCollapsed.value : sidebarCollapsed.value;
|
|
73
76
|
return pointsRight ? "i-ph-caret-right" : "i-ph-caret-left";
|
|
@@ -177,6 +180,9 @@ function applySuggestion(prompt) {
|
|
|
177
180
|
}
|
|
178
181
|
const breakpoints = useBreakpoints({ mobile: CHAT_MOBILE_BREAKPOINT });
|
|
179
182
|
const isMobile = breakpoints.smaller("mobile");
|
|
183
|
+
const showTopbarSidebarToggle = computed(() => sidebarCollapsible.value && sidebarToggle.value === "topbar" && !props.hideSidebar && !isMobile.value && !props.compact);
|
|
184
|
+
const slots = useSlots();
|
|
185
|
+
const hasTopbarLeading = computed(() => showTopbarSidebarToggle.value || !!slots["topbar-leading"]);
|
|
180
186
|
const drawerOpen = ref(false);
|
|
181
187
|
function restoreQueuedSend(send) {
|
|
182
188
|
messageInputRef.value?.restoreDraft(send);
|
|
@@ -548,6 +554,7 @@ watch(
|
|
|
548
554
|
{ immediate: true }
|
|
549
555
|
);
|
|
550
556
|
const currentUser = computed(() => props.user ?? authUser.value ?? null);
|
|
557
|
+
const emptyStateLabels = computed(() => resolveEmptyStateLabels(embedConfig?.labels.value, currentUser.value?.name));
|
|
551
558
|
const conversationCreator = computed(
|
|
552
559
|
() => resolveConversationCreator(currentConversation.value?.userId, {
|
|
553
560
|
currentUser: currentUser.value,
|
|
@@ -920,48 +927,18 @@ async function handleDelete() {
|
|
|
920
927
|
:inert="sidebarCollapsed || void 0"
|
|
921
928
|
h-full
|
|
922
929
|
flex="~ col"
|
|
923
|
-
bg
|
|
930
|
+
bg
|
|
931
|
+
border
|
|
924
932
|
:style="{ width: `${sidebarWidth}px` }"
|
|
925
933
|
:class="
|
|
926
|
-
sidebarPosition === 'right' ? 'border-l-0.5px
|
|
934
|
+
sidebarPosition === 'right' ? 'border-l-0.5px' : 'border-r-0.5px'
|
|
927
935
|
"
|
|
928
936
|
>
|
|
929
|
-
<div px-12px pt-16px pb-8px flex gap-8px>
|
|
930
|
-
<button
|
|
931
|
-
flex
|
|
932
|
-
items-center
|
|
933
|
-
justify-center
|
|
934
|
-
gap-6px
|
|
935
|
-
h-32px
|
|
936
|
-
px-12px
|
|
937
|
-
flex-1
|
|
938
|
-
min-w-0
|
|
939
|
-
bg-white
|
|
940
|
-
b=".5px neutral-300"
|
|
941
|
-
rounded-10px
|
|
942
|
-
hover:bg-neutral-50
|
|
943
|
-
transition-colors
|
|
944
|
-
cursor-pointer
|
|
945
|
-
@click="() => handleNewConversation()"
|
|
946
|
-
>
|
|
947
|
-
<span i-ph-chat-circle text-12px text-gray-900 />
|
|
948
|
-
<span
|
|
949
|
-
text-14px
|
|
950
|
-
font-semibold
|
|
951
|
-
text-neutral-900
|
|
952
|
-
tracking="-.15px"
|
|
953
|
-
leading-18px
|
|
954
|
-
>
|
|
955
|
-
Nova conversa
|
|
956
|
-
</span>
|
|
957
|
-
</button>
|
|
958
|
-
<ChatConversationNewGroupButton />
|
|
959
|
-
</div>
|
|
960
|
-
|
|
961
937
|
<div
|
|
962
938
|
min-h-0
|
|
963
939
|
overflow-hidden
|
|
964
|
-
|
|
940
|
+
pt-12px
|
|
941
|
+
px-6px
|
|
965
942
|
:class="$slots['sidebar-bottom'] ? '' : 'flex-1'"
|
|
966
943
|
:style="
|
|
967
944
|
$slots['sidebar-bottom'] ? { flexGrow: 100 - sidebarBottomHeight, flexBasis: 0 } : void 0
|
|
@@ -991,7 +968,7 @@ async function handleDelete() {
|
|
|
991
968
|
</div>
|
|
992
969
|
|
|
993
970
|
<button
|
|
994
|
-
v-if="sidebarCollapsible"
|
|
971
|
+
v-if="sidebarCollapsible && sidebarToggle === 'edge'"
|
|
995
972
|
type="button"
|
|
996
973
|
class="chat-sidebar__toggle"
|
|
997
974
|
:aria-label="
|
|
@@ -1091,8 +1068,8 @@ async function handleDelete() {
|
|
|
1091
1068
|
<template #empty>
|
|
1092
1069
|
<TelaChatEmptyState
|
|
1093
1070
|
compact
|
|
1094
|
-
:title="
|
|
1095
|
-
description="
|
|
1071
|
+
:title="emptyStateLabels.title"
|
|
1072
|
+
:description="emptyStateLabels.description"
|
|
1096
1073
|
>
|
|
1097
1074
|
<TelaChatSuggestions v-if="resolvedSuggestions.length">
|
|
1098
1075
|
<TelaChatSuggestion
|
|
@@ -1105,7 +1082,7 @@ async function handleDelete() {
|
|
|
1105
1082
|
</TelaChatSuggestion>
|
|
1106
1083
|
</TelaChatSuggestions>
|
|
1107
1084
|
|
|
1108
|
-
<TelaChatPromptInputHints size="sm" :hints="PROMPT_INPUT_HINTS" />
|
|
1085
|
+
<TelaChatPromptInputHints v-if="resolvedFeatures.showPromptInputHints" size="sm" :hints="PROMPT_INPUT_HINTS" />
|
|
1109
1086
|
|
|
1110
1087
|
<template #input>
|
|
1111
1088
|
<ChatMessageInput
|
|
@@ -1132,6 +1109,9 @@ async function handleDelete() {
|
|
|
1132
1109
|
@send="handleSend"
|
|
1133
1110
|
@cancel="handleCancel"
|
|
1134
1111
|
/>
|
|
1112
|
+
<div v-if="$slots['composer-bottom']" data-chat-composer-bottom w-full>
|
|
1113
|
+
<slot name="composer-bottom" />
|
|
1114
|
+
</div>
|
|
1135
1115
|
</template>
|
|
1136
1116
|
</TelaChatEmptyState>
|
|
1137
1117
|
</template>
|
|
@@ -1181,6 +1161,9 @@ async function handleDelete() {
|
|
|
1181
1161
|
@send="handleSend"
|
|
1182
1162
|
@cancel="handleCancel"
|
|
1183
1163
|
/>
|
|
1164
|
+
<div v-if="$slots['composer-bottom']" data-chat-composer-bottom w-full max-w-700px mx-auto mt--12px>
|
|
1165
|
+
<slot name="composer-bottom" />
|
|
1166
|
+
</div>
|
|
1184
1167
|
</div>
|
|
1185
1168
|
</ChatMobileShell>
|
|
1186
1169
|
|
|
@@ -1211,15 +1194,17 @@ async function handleDelete() {
|
|
|
1211
1194
|
@delete="showDeleteModal = true"
|
|
1212
1195
|
@tab-change="handleTabChange"
|
|
1213
1196
|
>
|
|
1214
|
-
<template v-if="
|
|
1197
|
+
<template v-if="hasTopbarLeading" #leading>
|
|
1198
|
+
<ChatSidebarToggle v-if="showTopbarSidebarToggle" :open="!sidebarCollapsed" @toggle="toggleSidebarCollapsed" />
|
|
1215
1199
|
<slot name="topbar-leading" />
|
|
1216
1200
|
</template>
|
|
1217
1201
|
</ChatTopbar>
|
|
1218
1202
|
<div
|
|
1219
|
-
v-else-if="
|
|
1220
|
-
absolute top-0 z-10 h-56px flex items-center pl-16px
|
|
1203
|
+
v-else-if="hasTopbarLeading && !isMobile && !compact"
|
|
1204
|
+
absolute top-0 z-10 h-56px flex items-center gap-8px pl-16px
|
|
1221
1205
|
:style="{ left: 'var(--chat-panel-offset)' }"
|
|
1222
1206
|
>
|
|
1207
|
+
<ChatSidebarToggle v-if="showTopbarSidebarToggle" :open="!sidebarCollapsed" @toggle="toggleSidebarCollapsed" />
|
|
1223
1208
|
<slot name="topbar-leading" />
|
|
1224
1209
|
</div>
|
|
1225
1210
|
|
|
@@ -1316,8 +1301,8 @@ async function handleDelete() {
|
|
|
1316
1301
|
<TelaChatEmptyState
|
|
1317
1302
|
v-else-if="!isMobile && !hasMessages"
|
|
1318
1303
|
:compact="compact"
|
|
1319
|
-
:title="
|
|
1320
|
-
description="
|
|
1304
|
+
:title="emptyStateLabels.title"
|
|
1305
|
+
:description="emptyStateLabels.description"
|
|
1321
1306
|
:class="currentConversation && !compact ? 'pt-64px' : ''"
|
|
1322
1307
|
>
|
|
1323
1308
|
<TelaChatSuggestions v-if="resolvedSuggestions.length">
|
|
@@ -1332,6 +1317,7 @@ async function handleDelete() {
|
|
|
1332
1317
|
</TelaChatSuggestions>
|
|
1333
1318
|
|
|
1334
1319
|
<TelaChatPromptInputHints
|
|
1320
|
+
v-if="resolvedFeatures.showPromptInputHints"
|
|
1335
1321
|
:size="compact ? 'sm' : 'md'"
|
|
1336
1322
|
:hints="PROMPT_INPUT_HINTS"
|
|
1337
1323
|
/>
|
|
@@ -1361,6 +1347,9 @@ async function handleDelete() {
|
|
|
1361
1347
|
@send="handleSend"
|
|
1362
1348
|
@cancel="handleCancel"
|
|
1363
1349
|
/>
|
|
1350
|
+
<div v-if="$slots['composer-bottom']" data-chat-composer-bottom w-full>
|
|
1351
|
+
<slot name="composer-bottom" />
|
|
1352
|
+
</div>
|
|
1364
1353
|
</template>
|
|
1365
1354
|
</TelaChatEmptyState>
|
|
1366
1355
|
|
|
@@ -1413,6 +1402,9 @@ async function handleDelete() {
|
|
|
1413
1402
|
@send="handleSend"
|
|
1414
1403
|
@cancel="handleCancel"
|
|
1415
1404
|
/>
|
|
1405
|
+
<div v-if="$slots['composer-bottom']" data-chat-composer-bottom w-full max-w-700px mx-auto mt--12px>
|
|
1406
|
+
<slot name="composer-bottom" />
|
|
1407
|
+
</div>
|
|
1416
1408
|
</div>
|
|
1417
1409
|
</TelaChatBody>
|
|
1418
1410
|
|
|
@@ -19,21 +19,29 @@ type __VLS_Props = {
|
|
|
19
19
|
user?: EmbedUser | null;
|
|
20
20
|
features?: Partial<ChatFeatureConfig>;
|
|
21
21
|
};
|
|
22
|
-
declare var
|
|
22
|
+
declare var __VLS_6: {}, __VLS_80: {}, __VLS_93: {
|
|
23
23
|
message: useChatAuth;
|
|
24
|
-
},
|
|
24
|
+
}, __VLS_105: {}, __VLS_133: {}, __VLS_142: {}, __VLS_206: {}, __VLS_225: {
|
|
25
25
|
message: useChatAuth;
|
|
26
|
-
};
|
|
26
|
+
}, __VLS_237: {};
|
|
27
27
|
type __VLS_Slots = {} & {
|
|
28
|
-
'sidebar-bottom'?: (props: typeof
|
|
28
|
+
'sidebar-bottom'?: (props: typeof __VLS_6) => any;
|
|
29
|
+
} & {
|
|
30
|
+
'composer-bottom'?: (props: typeof __VLS_80) => any;
|
|
31
|
+
} & {
|
|
32
|
+
'failed-message-actions'?: (props: typeof __VLS_93) => any;
|
|
33
|
+
} & {
|
|
34
|
+
'composer-bottom'?: (props: typeof __VLS_105) => any;
|
|
35
|
+
} & {
|
|
36
|
+
'topbar-leading'?: (props: typeof __VLS_133) => any;
|
|
29
37
|
} & {
|
|
30
|
-
'
|
|
38
|
+
'topbar-leading'?: (props: typeof __VLS_142) => any;
|
|
31
39
|
} & {
|
|
32
|
-
'
|
|
40
|
+
'composer-bottom'?: (props: typeof __VLS_206) => any;
|
|
33
41
|
} & {
|
|
34
|
-
'
|
|
42
|
+
'failed-message-actions'?: (props: typeof __VLS_225) => any;
|
|
35
43
|
} & {
|
|
36
|
-
'
|
|
44
|
+
'composer-bottom'?: (props: typeof __VLS_237) => any;
|
|
37
45
|
};
|
|
38
46
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
39
47
|
"update:conversationId": (value: string | null) => any;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
declare var __VLS_26: {}, __VLS_29: {}, __VLS_32: any;
|
|
1
|
+
declare var __VLS_26: {}, __VLS_29: {}, __VLS_32: {}, __VLS_35: any;
|
|
2
2
|
type __VLS_Slots = {} & {
|
|
3
3
|
'sidebar-bottom'?: (props: typeof __VLS_26) => any;
|
|
4
4
|
} & {
|
|
5
5
|
'topbar-leading'?: (props: typeof __VLS_29) => any;
|
|
6
6
|
} & {
|
|
7
|
-
'
|
|
7
|
+
'composer-bottom'?: (props: typeof __VLS_32) => any;
|
|
8
|
+
} & {
|
|
9
|
+
'failed-message-actions'?: (props: typeof __VLS_35) => any;
|
|
8
10
|
};
|
|
9
11
|
declare const __VLS_base: import("vue").DefineSetupFnComponent<Record<string, any>, {}, {}, Record<string, any> & {}, import("vue").PublicProps>;
|
|
10
12
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -177,6 +177,9 @@ provideChatMessageTerminal((payload) => {
|
|
|
177
177
|
<template v-if="$slots['topbar-leading']" #topbar-leading>
|
|
178
178
|
<slot name="topbar-leading" />
|
|
179
179
|
</template>
|
|
180
|
+
<template v-if="$slots['composer-bottom']" #composer-bottom>
|
|
181
|
+
<slot name="composer-bottom" />
|
|
182
|
+
</template>
|
|
180
183
|
<template v-if="$slots['failed-message-actions']" #failed-message-actions="slotProps">
|
|
181
184
|
<slot name="failed-message-actions" v-bind="slotProps" />
|
|
182
185
|
</template>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
declare var __VLS_26: {}, __VLS_29: {}, __VLS_32: any;
|
|
1
|
+
declare var __VLS_26: {}, __VLS_29: {}, __VLS_32: {}, __VLS_35: any;
|
|
2
2
|
type __VLS_Slots = {} & {
|
|
3
3
|
'sidebar-bottom'?: (props: typeof __VLS_26) => any;
|
|
4
4
|
} & {
|
|
5
5
|
'topbar-leading'?: (props: typeof __VLS_29) => any;
|
|
6
6
|
} & {
|
|
7
|
-
'
|
|
7
|
+
'composer-bottom'?: (props: typeof __VLS_32) => any;
|
|
8
|
+
} & {
|
|
9
|
+
'failed-message-actions'?: (props: typeof __VLS_35) => any;
|
|
8
10
|
};
|
|
9
11
|
declare const __VLS_base: import("vue").DefineSetupFnComponent<Record<string, any>, {}, {}, Record<string, any> & {}, import("vue").PublicProps>;
|
|
10
12
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
open: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
|
+
toggle: () => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
7
|
+
onToggle?: (() => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
open: { type: Boolean, required: true }
|
|
4
|
+
});
|
|
5
|
+
const emit = defineEmits(["toggle"]);
|
|
6
|
+
const panelWidth = computed(() => props.open ? 6.5 : 4.5);
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<button
|
|
11
|
+
type="button"
|
|
12
|
+
:aria-label="open ? 'Recolher conversas' : 'Expandir conversas'"
|
|
13
|
+
:aria-expanded="open"
|
|
14
|
+
h-32px w-32px
|
|
15
|
+
flex items-center justify-center
|
|
16
|
+
flex-shrink-0
|
|
17
|
+
rounded-8px
|
|
18
|
+
text-icon-tertiary
|
|
19
|
+
hover:bg-muted hover:text-icon-secondary
|
|
20
|
+
cursor-pointer
|
|
21
|
+
@click="emit('toggle')"
|
|
22
|
+
>
|
|
23
|
+
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
24
|
+
<defs>
|
|
25
|
+
<clipPath id="chat-sidebar-toggle-clip">
|
|
26
|
+
<rect x="0.5" y="0.5" width="15" height="13" rx="3.5" />
|
|
27
|
+
</clipPath>
|
|
28
|
+
</defs>
|
|
29
|
+
<rect
|
|
30
|
+
x="0.5"
|
|
31
|
+
y="0.5"
|
|
32
|
+
:width="panelWidth"
|
|
33
|
+
height="13"
|
|
34
|
+
fill="#D9D9D9"
|
|
35
|
+
clip-path="url(#chat-sidebar-toggle-clip)"
|
|
36
|
+
:style="{ opacity: open ? 1 : 0, transition: 'width 0.2s ease-out, opacity 0.2s ease-out' }"
|
|
37
|
+
/>
|
|
38
|
+
<rect x="0.5" y="0.5" width="15" height="13" rx="3.5" stroke="currentColor" />
|
|
39
|
+
<rect
|
|
40
|
+
:x="panelWidth - 0.5"
|
|
41
|
+
y="0.5"
|
|
42
|
+
width="1"
|
|
43
|
+
height="13"
|
|
44
|
+
fill="currentColor"
|
|
45
|
+
:style="{ transition: 'x 0.2s ease-out' }"
|
|
46
|
+
/>
|
|
47
|
+
</svg>
|
|
48
|
+
</button>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
open: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
|
+
toggle: () => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
7
|
+
onToggle?: (() => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
|
@@ -26,6 +26,8 @@ type MeistrariChatEmbedSlots = SlotsType<{
|
|
|
26
26
|
'sidebar-bottom'?: () => any;
|
|
27
27
|
/** Rendered at the start of the conversation topbar, before the title. Page variant only. */
|
|
28
28
|
'topbar-leading'?: () => any;
|
|
29
|
+
/** Rendered right under the prompt input, flush with it, on the empty state and in a conversation. Page variant only. */
|
|
30
|
+
'composer-bottom'?: () => any;
|
|
29
31
|
/** Rendered inside a failed assistant message's error box, next to "Tentar novamente"; receives the failed message. */
|
|
30
32
|
'failed-message-actions'?: (props: { message: Message }) => any;
|
|
31
33
|
}>;
|
|
@@ -121,6 +121,9 @@ const widgetEmbedProps = computed(() => ({
|
|
|
121
121
|
<template v-if="$slots['topbar-leading']" #topbar-leading>
|
|
122
122
|
<slot name="topbar-leading" />
|
|
123
123
|
</template>
|
|
124
|
+
<template v-if="$slots['composer-bottom']" #composer-bottom>
|
|
125
|
+
<slot name="composer-bottom" />
|
|
126
|
+
</template>
|
|
124
127
|
<template v-if="$slots['failed-message-actions']" #failed-message-actions="slotProps">
|
|
125
128
|
<slot name="failed-message-actions" v-bind="slotProps" />
|
|
126
129
|
</template>
|
|
@@ -26,6 +26,8 @@ type MeistrariChatEmbedSlots = SlotsType<{
|
|
|
26
26
|
'sidebar-bottom'?: () => any;
|
|
27
27
|
/** Rendered at the start of the conversation topbar, before the title. Page variant only. */
|
|
28
28
|
'topbar-leading'?: () => any;
|
|
29
|
+
/** Rendered right under the prompt input, flush with it, on the empty state and in a conversation. Page variant only. */
|
|
30
|
+
'composer-bottom'?: () => any;
|
|
29
31
|
/** Rendered inside a failed assistant message's error box, next to "Tentar novamente"; receives the failed message. */
|
|
30
32
|
'failed-message-actions'?: (props: { message: Message }) => any;
|
|
31
33
|
}>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import ChatMessageInput from "../../../messages/components/chat-message-input.vue";
|
|
3
|
+
import { useEmbedConfig } from "../../composables/embed-config";
|
|
4
|
+
import { resolveEmptyStateLabels } from "../../empty-state-labels";
|
|
3
5
|
import ChatMobileFilesView from "./chat-mobile-files-view.vue";
|
|
4
6
|
import ChatMobileHomeHeader from "./chat-mobile-home-header.vue";
|
|
5
7
|
import ChatMobileHomeRecents from "./chat-mobile-home-recents.vue";
|
|
@@ -47,6 +49,8 @@ const props = defineProps({
|
|
|
47
49
|
workspaceSheetOpen: { type: Boolean, required: true }
|
|
48
50
|
});
|
|
49
51
|
const emit = defineEmits(["cancel", "cancelEditing", "copyLink", "debug", "delete", "dismissOutdatedBanner", "duplicate", "export", "newConversation", "rename", "saveTitle", "selectConversation", "send", "settings", "signout", "switchWorkspace", "tabChange", "update:drawerOpen", "update:editTitle", "update:workspaceSheetOpen"]);
|
|
52
|
+
const embedConfig = useEmbedConfig();
|
|
53
|
+
const emptyStateLabels = computed(() => resolveEmptyStateLabels(embedConfig?.labels.value, props.currentUser?.name));
|
|
50
54
|
const selectedModel = defineModel("selectedModel", { type: [String, null], ...{ default: null } });
|
|
51
55
|
const selectedReasoningEffort = defineModel("selectedReasoningEffort", { type: null, ...{ default: null } });
|
|
52
56
|
const renameInputRef = ref(null);
|
|
@@ -215,10 +219,10 @@ function startNewConversationFromOutdatedSettings() {
|
|
|
215
219
|
<div flex="~ col 1" justify-center>
|
|
216
220
|
<div px-20px pb-20px text-center>
|
|
217
221
|
<h1 text-24px font-580 text-neutral-900 leading-28px tracking="-.4px">
|
|
218
|
-
|
|
222
|
+
{{ emptyStateLabels.title }}
|
|
219
223
|
</h1>
|
|
220
224
|
<p text-14px text-neutral-500 leading-18px mt-6px>
|
|
221
|
-
|
|
225
|
+
{{ emptyStateLabels.description }}
|
|
222
226
|
</p>
|
|
223
227
|
</div>
|
|
224
228
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const DEFAULT_EMPTY_STATE_TITLE = "Ol\xE1, {name}";
|
|
2
|
+
const DEFAULT_EMPTY_STATE_DESCRIPTION = "Como posso te ajudar hoje?";
|
|
3
|
+
export function resolveEmptyStateLabels(labels, userName) {
|
|
4
|
+
const firstName = userName?.split(" ")[0] || "usu\xE1rio";
|
|
5
|
+
return {
|
|
6
|
+
title: (labels?.emptyStateTitle ?? DEFAULT_EMPTY_STATE_TITLE).replaceAll("{name}", firstName),
|
|
7
|
+
description: labels?.emptyStateDescription ?? DEFAULT_EMPTY_STATE_DESCRIPTION
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -10,6 +10,8 @@ export type ChatFeatureConfig = {
|
|
|
10
10
|
showTtft?: boolean;
|
|
11
11
|
/** Reliable outbound delivery and sandbox readiness handling. Defaults to true. */
|
|
12
12
|
chatOutboundQueue?: boolean;
|
|
13
|
+
/** `@` / `/` shortcut hints below the empty-state composer. Defaults to true. */
|
|
14
|
+
showPromptInputHints?: boolean;
|
|
13
15
|
};
|
|
14
16
|
export type ResolvedChatFeatureConfig = {
|
|
15
17
|
showUsageTab: boolean;
|
|
@@ -17,5 +19,6 @@ export type ResolvedChatFeatureConfig = {
|
|
|
17
19
|
showCancelButton: boolean;
|
|
18
20
|
showTtft: boolean;
|
|
19
21
|
chatOutboundQueue: boolean;
|
|
22
|
+
showPromptInputHints: boolean;
|
|
20
23
|
};
|
|
21
24
|
export declare function resolveChatFeatures(features?: Partial<ChatFeatureConfig>): ResolvedChatFeatureConfig;
|
|
@@ -4,6 +4,7 @@ export function resolveChatFeatures(features) {
|
|
|
4
4
|
showDebugOption: features?.showDebugOption ?? true,
|
|
5
5
|
showCancelButton: features?.showCancelButton ?? true,
|
|
6
6
|
showTtft: features?.showTtft ?? true,
|
|
7
|
-
chatOutboundQueue: features?.chatOutboundQueue ?? true
|
|
7
|
+
chatOutboundQueue: features?.chatOutboundQueue ?? true,
|
|
8
|
+
showPromptInputHints: features?.showPromptInputHints ?? true
|
|
8
9
|
};
|
|
9
10
|
}
|
|
@@ -15,6 +15,13 @@ export type ChatLabels = {
|
|
|
15
15
|
conversationSearchPlaceholder: string;
|
|
16
16
|
/** Clear-button tooltip in the sidebar and mobile drawer search. Defaults to `Limpar`. */
|
|
17
17
|
conversationSearchClear: string;
|
|
18
|
+
/**
|
|
19
|
+
* Empty-state heading. `{name}` is replaced with the user's first name
|
|
20
|
+
* (or `usuário` when unknown). Defaults to `Olá, {name}`.
|
|
21
|
+
*/
|
|
22
|
+
emptyStateTitle: string;
|
|
23
|
+
/** Empty-state subheading. Defaults to `Como posso te ajudar hoje?`. */
|
|
24
|
+
emptyStateDescription: string;
|
|
18
25
|
};
|
|
19
26
|
export type ChatSuggestion = {
|
|
20
27
|
/** Short label rendered on the suggestion pill. */
|
|
@@ -45,6 +52,12 @@ export type ChatSidebarConfig = {
|
|
|
45
52
|
collapsible?: boolean;
|
|
46
53
|
/** Start collapsed on first render when `collapsible` is set. Defaults to false. */
|
|
47
54
|
defaultCollapsed?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Where the collapse control lives when `collapsible` is set: `'edge'` is the handle on the
|
|
57
|
+
* sidebar's inner edge; `'topbar'` is chat-app's sidebar toggle at the start of the
|
|
58
|
+
* conversation topbar (desktop only). Defaults to `'edge'`.
|
|
59
|
+
*/
|
|
60
|
+
toggle?: 'edge' | 'topbar';
|
|
48
61
|
};
|
|
49
62
|
export type ChatEmbedSharedProps = {
|
|
50
63
|
/** Let the embed synchronize its conversation with the browser URL and title. Defaults to true. */
|