@meistrari/chat-nuxt 4.4.1-rc.1 → 4.5.0-rc.1
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 +48 -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/dist/runtime/lightweight/messages/composables/chat.d.ts +1 -0
- package/dist/runtime/messages/composables/chat.d.ts +1 -0
- package/dist/runtime/messages/composables/chat.js +48 -13
- 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_14: {}, __VLS_88: {}, __VLS_101: {
|
|
23
23
|
message: useChatAuth;
|
|
24
|
-
},
|
|
24
|
+
}, __VLS_113: {}, __VLS_141: {}, __VLS_150: {}, __VLS_214: {}, __VLS_233: {
|
|
25
25
|
message: useChatAuth;
|
|
26
|
-
};
|
|
26
|
+
}, __VLS_245: {};
|
|
27
27
|
type __VLS_Slots = {} & {
|
|
28
|
-
'sidebar-bottom'?: (props: typeof
|
|
28
|
+
'sidebar-bottom'?: (props: typeof __VLS_14) => any;
|
|
29
|
+
} & {
|
|
30
|
+
'composer-bottom'?: (props: typeof __VLS_88) => any;
|
|
31
|
+
} & {
|
|
32
|
+
'failed-message-actions'?: (props: typeof __VLS_101) => any;
|
|
33
|
+
} & {
|
|
34
|
+
'composer-bottom'?: (props: typeof __VLS_113) => any;
|
|
35
|
+
} & {
|
|
36
|
+
'topbar-leading'?: (props: typeof __VLS_141) => any;
|
|
29
37
|
} & {
|
|
30
|
-
'
|
|
38
|
+
'topbar-leading'?: (props: typeof __VLS_150) => any;
|
|
31
39
|
} & {
|
|
32
|
-
'
|
|
40
|
+
'composer-bottom'?: (props: typeof __VLS_214) => any;
|
|
33
41
|
} & {
|
|
34
|
-
'
|
|
42
|
+
'failed-message-actions'?: (props: typeof __VLS_233) => any;
|
|
35
43
|
} & {
|
|
36
|
-
'
|
|
44
|
+
'composer-bottom'?: (props: typeof __VLS_245) => 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);
|
|
@@ -197,6 +203,7 @@ const {
|
|
|
197
203
|
generatingTitle,
|
|
198
204
|
animatedTitle,
|
|
199
205
|
composerLocked,
|
|
206
|
+
isPolling,
|
|
200
207
|
outboundOccupied,
|
|
201
208
|
sessionReady,
|
|
202
209
|
agentSessionRewakeable,
|
|
@@ -215,6 +222,7 @@ const {
|
|
|
215
222
|
retryMessage,
|
|
216
223
|
cancelConversation,
|
|
217
224
|
startPolling,
|
|
225
|
+
reconnect,
|
|
218
226
|
clearConversation,
|
|
219
227
|
error: chatError
|
|
220
228
|
} = createChat({
|
|
@@ -548,6 +556,7 @@ watch(
|
|
|
548
556
|
{ immediate: true }
|
|
549
557
|
);
|
|
550
558
|
const currentUser = computed(() => props.user ?? authUser.value ?? null);
|
|
559
|
+
const emptyStateLabels = computed(() => resolveEmptyStateLabels(embedConfig?.labels.value, currentUser.value?.name));
|
|
551
560
|
const conversationCreator = computed(
|
|
552
561
|
() => resolveConversationCreator(currentConversation.value?.userId, {
|
|
553
562
|
currentUser: currentUser.value,
|
|
@@ -899,6 +908,15 @@ async function handleDelete() {
|
|
|
899
908
|
rounded-8px bg-error text-error p-12px body-14-regular
|
|
900
909
|
>
|
|
901
910
|
{{ chatError }}
|
|
911
|
+
<TelaButton
|
|
912
|
+
v-if="composerLocked && !isPolling"
|
|
913
|
+
variant="ghost-danger"
|
|
914
|
+
size="sm"
|
|
915
|
+
icon="i-ph-arrow-clockwise"
|
|
916
|
+
@click="reconnect"
|
|
917
|
+
>
|
|
918
|
+
Reconectar
|
|
919
|
+
</TelaButton>
|
|
902
920
|
</div>
|
|
903
921
|
<!-- Sidebar -->
|
|
904
922
|
<div
|
|
@@ -920,48 +938,18 @@ async function handleDelete() {
|
|
|
920
938
|
:inert="sidebarCollapsed || void 0"
|
|
921
939
|
h-full
|
|
922
940
|
flex="~ col"
|
|
923
|
-
bg
|
|
941
|
+
bg
|
|
942
|
+
border
|
|
924
943
|
:style="{ width: `${sidebarWidth}px` }"
|
|
925
944
|
:class="
|
|
926
|
-
sidebarPosition === 'right' ? 'border-l-0.5px
|
|
945
|
+
sidebarPosition === 'right' ? 'border-l-0.5px' : 'border-r-0.5px'
|
|
927
946
|
"
|
|
928
947
|
>
|
|
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
948
|
<div
|
|
962
949
|
min-h-0
|
|
963
950
|
overflow-hidden
|
|
964
|
-
|
|
951
|
+
pt-12px
|
|
952
|
+
px-6px
|
|
965
953
|
:class="$slots['sidebar-bottom'] ? '' : 'flex-1'"
|
|
966
954
|
:style="
|
|
967
955
|
$slots['sidebar-bottom'] ? { flexGrow: 100 - sidebarBottomHeight, flexBasis: 0 } : void 0
|
|
@@ -991,7 +979,7 @@ async function handleDelete() {
|
|
|
991
979
|
</div>
|
|
992
980
|
|
|
993
981
|
<button
|
|
994
|
-
v-if="sidebarCollapsible"
|
|
982
|
+
v-if="sidebarCollapsible && sidebarToggle === 'edge'"
|
|
995
983
|
type="button"
|
|
996
984
|
class="chat-sidebar__toggle"
|
|
997
985
|
:aria-label="
|
|
@@ -1091,8 +1079,8 @@ async function handleDelete() {
|
|
|
1091
1079
|
<template #empty>
|
|
1092
1080
|
<TelaChatEmptyState
|
|
1093
1081
|
compact
|
|
1094
|
-
:title="
|
|
1095
|
-
description="
|
|
1082
|
+
:title="emptyStateLabels.title"
|
|
1083
|
+
:description="emptyStateLabels.description"
|
|
1096
1084
|
>
|
|
1097
1085
|
<TelaChatSuggestions v-if="resolvedSuggestions.length">
|
|
1098
1086
|
<TelaChatSuggestion
|
|
@@ -1105,7 +1093,7 @@ async function handleDelete() {
|
|
|
1105
1093
|
</TelaChatSuggestion>
|
|
1106
1094
|
</TelaChatSuggestions>
|
|
1107
1095
|
|
|
1108
|
-
<TelaChatPromptInputHints size="sm" :hints="PROMPT_INPUT_HINTS" />
|
|
1096
|
+
<TelaChatPromptInputHints v-if="resolvedFeatures.showPromptInputHints" size="sm" :hints="PROMPT_INPUT_HINTS" />
|
|
1109
1097
|
|
|
1110
1098
|
<template #input>
|
|
1111
1099
|
<ChatMessageInput
|
|
@@ -1132,6 +1120,9 @@ async function handleDelete() {
|
|
|
1132
1120
|
@send="handleSend"
|
|
1133
1121
|
@cancel="handleCancel"
|
|
1134
1122
|
/>
|
|
1123
|
+
<div v-if="$slots['composer-bottom']" data-chat-composer-bottom w-full>
|
|
1124
|
+
<slot name="composer-bottom" />
|
|
1125
|
+
</div>
|
|
1135
1126
|
</template>
|
|
1136
1127
|
</TelaChatEmptyState>
|
|
1137
1128
|
</template>
|
|
@@ -1181,6 +1172,9 @@ async function handleDelete() {
|
|
|
1181
1172
|
@send="handleSend"
|
|
1182
1173
|
@cancel="handleCancel"
|
|
1183
1174
|
/>
|
|
1175
|
+
<div v-if="$slots['composer-bottom']" data-chat-composer-bottom w-full max-w-700px mx-auto mt--12px>
|
|
1176
|
+
<slot name="composer-bottom" />
|
|
1177
|
+
</div>
|
|
1184
1178
|
</div>
|
|
1185
1179
|
</ChatMobileShell>
|
|
1186
1180
|
|
|
@@ -1211,15 +1205,17 @@ async function handleDelete() {
|
|
|
1211
1205
|
@delete="showDeleteModal = true"
|
|
1212
1206
|
@tab-change="handleTabChange"
|
|
1213
1207
|
>
|
|
1214
|
-
<template v-if="
|
|
1208
|
+
<template v-if="hasTopbarLeading" #leading>
|
|
1209
|
+
<ChatSidebarToggle v-if="showTopbarSidebarToggle" :open="!sidebarCollapsed" @toggle="toggleSidebarCollapsed" />
|
|
1215
1210
|
<slot name="topbar-leading" />
|
|
1216
1211
|
</template>
|
|
1217
1212
|
</ChatTopbar>
|
|
1218
1213
|
<div
|
|
1219
|
-
v-else-if="
|
|
1220
|
-
absolute top-0 z-10 h-56px flex items-center pl-16px
|
|
1214
|
+
v-else-if="hasTopbarLeading && !isMobile && !compact"
|
|
1215
|
+
absolute top-0 z-10 h-56px flex items-center gap-8px pl-16px
|
|
1221
1216
|
:style="{ left: 'var(--chat-panel-offset)' }"
|
|
1222
1217
|
>
|
|
1218
|
+
<ChatSidebarToggle v-if="showTopbarSidebarToggle" :open="!sidebarCollapsed" @toggle="toggleSidebarCollapsed" />
|
|
1223
1219
|
<slot name="topbar-leading" />
|
|
1224
1220
|
</div>
|
|
1225
1221
|
|
|
@@ -1316,8 +1312,8 @@ async function handleDelete() {
|
|
|
1316
1312
|
<TelaChatEmptyState
|
|
1317
1313
|
v-else-if="!isMobile && !hasMessages"
|
|
1318
1314
|
:compact="compact"
|
|
1319
|
-
:title="
|
|
1320
|
-
description="
|
|
1315
|
+
:title="emptyStateLabels.title"
|
|
1316
|
+
:description="emptyStateLabels.description"
|
|
1321
1317
|
:class="currentConversation && !compact ? 'pt-64px' : ''"
|
|
1322
1318
|
>
|
|
1323
1319
|
<TelaChatSuggestions v-if="resolvedSuggestions.length">
|
|
@@ -1332,6 +1328,7 @@ async function handleDelete() {
|
|
|
1332
1328
|
</TelaChatSuggestions>
|
|
1333
1329
|
|
|
1334
1330
|
<TelaChatPromptInputHints
|
|
1331
|
+
v-if="resolvedFeatures.showPromptInputHints"
|
|
1335
1332
|
:size="compact ? 'sm' : 'md'"
|
|
1336
1333
|
:hints="PROMPT_INPUT_HINTS"
|
|
1337
1334
|
/>
|
|
@@ -1361,6 +1358,9 @@ async function handleDelete() {
|
|
|
1361
1358
|
@send="handleSend"
|
|
1362
1359
|
@cancel="handleCancel"
|
|
1363
1360
|
/>
|
|
1361
|
+
<div v-if="$slots['composer-bottom']" data-chat-composer-bottom w-full>
|
|
1362
|
+
<slot name="composer-bottom" />
|
|
1363
|
+
</div>
|
|
1364
1364
|
</template>
|
|
1365
1365
|
</TelaChatEmptyState>
|
|
1366
1366
|
|
|
@@ -1413,6 +1413,9 @@ async function handleDelete() {
|
|
|
1413
1413
|
@send="handleSend"
|
|
1414
1414
|
@cancel="handleCancel"
|
|
1415
1415
|
/>
|
|
1416
|
+
<div v-if="$slots['composer-bottom']" data-chat-composer-bottom w-full max-w-700px mx-auto mt--12px>
|
|
1417
|
+
<slot name="composer-bottom" />
|
|
1418
|
+
</div>
|
|
1416
1419
|
</div>
|
|
1417
1420
|
</TelaChatBody>
|
|
1418
1421
|
|
|
@@ -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_14: {}, __VLS_88: {}, __VLS_101: {
|
|
23
23
|
message: useChatAuth;
|
|
24
|
-
},
|
|
24
|
+
}, __VLS_113: {}, __VLS_141: {}, __VLS_150: {}, __VLS_214: {}, __VLS_233: {
|
|
25
25
|
message: useChatAuth;
|
|
26
|
-
};
|
|
26
|
+
}, __VLS_245: {};
|
|
27
27
|
type __VLS_Slots = {} & {
|
|
28
|
-
'sidebar-bottom'?: (props: typeof
|
|
28
|
+
'sidebar-bottom'?: (props: typeof __VLS_14) => any;
|
|
29
|
+
} & {
|
|
30
|
+
'composer-bottom'?: (props: typeof __VLS_88) => any;
|
|
31
|
+
} & {
|
|
32
|
+
'failed-message-actions'?: (props: typeof __VLS_101) => any;
|
|
33
|
+
} & {
|
|
34
|
+
'composer-bottom'?: (props: typeof __VLS_113) => any;
|
|
35
|
+
} & {
|
|
36
|
+
'topbar-leading'?: (props: typeof __VLS_141) => any;
|
|
29
37
|
} & {
|
|
30
|
-
'
|
|
38
|
+
'topbar-leading'?: (props: typeof __VLS_150) => any;
|
|
31
39
|
} & {
|
|
32
|
-
'
|
|
40
|
+
'composer-bottom'?: (props: typeof __VLS_214) => any;
|
|
33
41
|
} & {
|
|
34
|
-
'
|
|
42
|
+
'failed-message-actions'?: (props: typeof __VLS_233) => any;
|
|
35
43
|
} & {
|
|
36
|
-
'
|
|
44
|
+
'composer-bottom'?: (props: typeof __VLS_245) => 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. */
|
|
@@ -35,6 +35,7 @@ export declare function useChat(options?: Parameters<typeof useSharedChat>[0]):
|
|
|
35
35
|
retryMessage: (messageId: string, prepare?: () => Promise<void>) => Promise<boolean>;
|
|
36
36
|
cancelConversation: () => Promise<boolean>;
|
|
37
37
|
startPolling: () => void;
|
|
38
|
+
reconnect: () => void;
|
|
38
39
|
stopPolling: () => void;
|
|
39
40
|
clearConversation: () => void;
|
|
40
41
|
clearConversationThread: () => Promise<boolean>;
|
|
@@ -57,6 +57,7 @@ export declare function useChat(options?: {
|
|
|
57
57
|
retryMessage: (messageId: string, prepare?: () => Promise<void>) => Promise<boolean>;
|
|
58
58
|
cancelConversation: () => Promise<boolean>;
|
|
59
59
|
startPolling: () => void;
|
|
60
|
+
reconnect: () => void;
|
|
60
61
|
stopPolling: () => void;
|
|
61
62
|
clearConversation: () => void;
|
|
62
63
|
clearConversationThread: () => Promise<boolean>;
|
|
@@ -28,6 +28,12 @@ import { usePolling } from "../../shared/composables/polling.js";
|
|
|
28
28
|
import { ChatMessageStatus, terminalMessageTransitions, useChatMessageTerminal } from "./chat-message-terminal.js";
|
|
29
29
|
import { useDirectStream } from "./use-direct-stream.js";
|
|
30
30
|
import { useOutboundQueue } from "./outbound-queue.js";
|
|
31
|
+
const POLL_OUTAGE_NOTICE_FAILURES = 5;
|
|
32
|
+
const POLL_OUTAGE_BUDGET_MS = 6e4;
|
|
33
|
+
const POLL_INTERVAL_MS = 500;
|
|
34
|
+
const POLL_MAX_BACKOFF_MS = 5e3;
|
|
35
|
+
const CHAT_REQUEST_TIMEOUT_MS = 15e3;
|
|
36
|
+
const CHAT_UNAVAILABLE_TEXT = "Chat temporariamente indispon\xEDvel. Tente novamente.";
|
|
31
37
|
const WORKSPACE_AGENT_NOT_READY_TEXT = "O agente deste workspace ainda n\xE3o foi configurado no Tela. Abra as configura\xE7\xF5es do workspace para criar o agente.";
|
|
32
38
|
export function resolveTelaAgentInputs(messageInputs, configuredInputs) {
|
|
33
39
|
const inputs = messageInputs ?? configuredInputs;
|
|
@@ -241,6 +247,9 @@ export function useChat(options = {}, adapter = {}) {
|
|
|
241
247
|
}
|
|
242
248
|
}
|
|
243
249
|
let pollGeneration = 0;
|
|
250
|
+
let consecutivePollFailures = 0;
|
|
251
|
+
let pollOutageStartedAt = null;
|
|
252
|
+
const pollInterval = ref(POLL_INTERVAL_MS);
|
|
244
253
|
const pollMessages = async () => {
|
|
245
254
|
if (!currentConversation.value)
|
|
246
255
|
return;
|
|
@@ -251,14 +260,17 @@ export function useChat(options = {}, adapter = {}) {
|
|
|
251
260
|
try {
|
|
252
261
|
const runtime = chatApi.conversationV2Enabled ? await $fetch(
|
|
253
262
|
chatApi.runtimePath(`/conversations/${conversationId}/runtime`),
|
|
254
|
-
chatApi.withChatHeaders()
|
|
263
|
+
chatApi.withChatHeaders({ timeout: CHAT_REQUEST_TIMEOUT_MS, retry: 0 })
|
|
255
264
|
) : null;
|
|
256
265
|
const response = await $fetch(
|
|
257
266
|
chatApi.path(`/conversations/${conversationId}/messages`),
|
|
258
|
-
chatApi.withChatHeaders()
|
|
267
|
+
chatApi.withChatHeaders({ timeout: CHAT_REQUEST_TIMEOUT_MS, retry: 0 })
|
|
259
268
|
);
|
|
260
269
|
if (generation !== pollGeneration || currentConversation.value?.id !== conversationId)
|
|
261
270
|
return;
|
|
271
|
+
resetPollOutage();
|
|
272
|
+
if (error.value === CHAT_UNAVAILABLE_TEXT)
|
|
273
|
+
error.value = null;
|
|
262
274
|
if (runtime)
|
|
263
275
|
runtimeBlocked.value = runtime.conversation.runtimeState === "blocked";
|
|
264
276
|
if (response.agentSession) {
|
|
@@ -332,16 +344,28 @@ export function useChat(options = {}, adapter = {}) {
|
|
|
332
344
|
Sentry.captureException(err);
|
|
333
345
|
if (generation !== pollGeneration)
|
|
334
346
|
return;
|
|
335
|
-
const
|
|
336
|
-
const
|
|
337
|
-
|
|
347
|
+
const statusCode = getChatApiErrorStatus(err);
|
|
348
|
+
const terminalError = statusCode === 501 || statusCode !== null && statusCode >= 400 && statusCode < 500 && statusCode !== 408 && statusCode !== 429;
|
|
349
|
+
consecutivePollFailures++;
|
|
350
|
+
pollOutageStartedAt ??= Date.now();
|
|
351
|
+
pollInterval.value = Math.min(POLL_INTERVAL_MS * 2 ** consecutivePollFailures, POLL_MAX_BACKOFF_MS);
|
|
352
|
+
if (!terminalError && consecutivePollFailures >= POLL_OUTAGE_NOTICE_FAILURES)
|
|
353
|
+
error.value = CHAT_UNAVAILABLE_TEXT;
|
|
354
|
+
const outageExhausted = Date.now() - pollOutageStartedAt >= POLL_OUTAGE_BUDGET_MS;
|
|
355
|
+
if (terminalError || outageExhausted) {
|
|
356
|
+
const message = statusCode === 501 || !terminalError ? CHAT_UNAVAILABLE_TEXT : getChatApiErrorMessage(err) ?? "Erro ao atualizar mensagens";
|
|
357
|
+
if (!terminalError || statusCode === 501 || outbound.occupied.value) {
|
|
358
|
+
error.value = message;
|
|
359
|
+
stopPolling();
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
338
362
|
const previousMessages = messages.value;
|
|
339
|
-
const failedMessages = markPendingAssistantMessagesFailed(previousMessages).map((
|
|
340
|
-
if (
|
|
341
|
-
return
|
|
363
|
+
const failedMessages = markPendingAssistantMessagesFailed(previousMessages).map((message2) => {
|
|
364
|
+
if (message2.role !== "assistant" || message2.status !== ChatMessageStatus.Completed || !pendingStreamReconciliationIds.value.has(message2.id)) {
|
|
365
|
+
return message2;
|
|
342
366
|
}
|
|
343
367
|
return {
|
|
344
|
-
...
|
|
368
|
+
...message2,
|
|
345
369
|
content: GENERATION_FAILED_TEXT,
|
|
346
370
|
status: ChatMessageStatus.Failed
|
|
347
371
|
};
|
|
@@ -352,20 +376,30 @@ export function useChat(options = {}, adapter = {}) {
|
|
|
352
376
|
messages.value = failedMessages;
|
|
353
377
|
for (const terminalMessage of terminalMessages)
|
|
354
378
|
chatMessageTerminal.emit(terminalMessage);
|
|
355
|
-
error.value =
|
|
379
|
+
error.value = message;
|
|
356
380
|
stopPolling();
|
|
357
381
|
}
|
|
358
382
|
}
|
|
359
383
|
};
|
|
360
384
|
const { isPolling, start: beginPolling, stop: endPolling } = usePolling({
|
|
361
385
|
callback: pollMessages,
|
|
362
|
-
interval:
|
|
386
|
+
interval: pollInterval,
|
|
363
387
|
stopCondition
|
|
364
388
|
});
|
|
389
|
+
function resetPollOutage() {
|
|
390
|
+
consecutivePollFailures = 0;
|
|
391
|
+
pollOutageStartedAt = null;
|
|
392
|
+
pollInterval.value = POLL_INTERVAL_MS;
|
|
393
|
+
}
|
|
365
394
|
function startPolling() {
|
|
395
|
+
resetPollOutage();
|
|
366
396
|
pollGeneration++;
|
|
367
397
|
beginPolling();
|
|
368
398
|
}
|
|
399
|
+
function reconnect() {
|
|
400
|
+
error.value = null;
|
|
401
|
+
startPolling();
|
|
402
|
+
}
|
|
369
403
|
function stopPolling() {
|
|
370
404
|
pollGeneration++;
|
|
371
405
|
endPolling();
|
|
@@ -614,7 +648,7 @@ export function useChat(options = {}, adapter = {}) {
|
|
|
614
648
|
v2AdmissionRequested = true;
|
|
615
649
|
const accepted = await $fetch(
|
|
616
650
|
chatApi.runtimePath(`/conversations/${conversationId2}/messages`),
|
|
617
|
-
chatApi.withChatHeaders({ method: "POST", body: v2Body, retry: 1 })
|
|
651
|
+
chatApi.withChatHeaders({ method: "POST", body: v2Body, retry: 1, timeout: CHAT_REQUEST_TIMEOUT_MS })
|
|
618
652
|
);
|
|
619
653
|
response = accepted;
|
|
620
654
|
if (currentConversation.value?.id !== conversationId2)
|
|
@@ -661,7 +695,7 @@ export function useChat(options = {}, adapter = {}) {
|
|
|
661
695
|
} catch (err) {
|
|
662
696
|
const code = getChatApiErrorCode(err);
|
|
663
697
|
const status = getChatApiErrorStatus(err);
|
|
664
|
-
if (v2AdmissionRequested && !controller.signal.aborted && (status === null || status === 408 || status === 429 || status >= 500)) {
|
|
698
|
+
if (v2AdmissionRequested && !controller.signal.aborted && (status === null || status === 408 || status === 429 || status >= 500 && status !== 501)) {
|
|
665
699
|
const attempts = (queuedSend?.attempts ?? 0) + 1;
|
|
666
700
|
const queuedAt = queuedSend?.queuedAt ?? Date.now();
|
|
667
701
|
if (outboundQueueEnabled.value) {
|
|
@@ -1029,6 +1063,7 @@ export function useChat(options = {}, adapter = {}) {
|
|
|
1029
1063
|
retryMessage,
|
|
1030
1064
|
cancelConversation,
|
|
1031
1065
|
startPolling,
|
|
1066
|
+
reconnect,
|
|
1032
1067
|
stopPolling,
|
|
1033
1068
|
clearConversation,
|
|
1034
1069
|
clearConversationThread,
|