@meistrari/chat-nuxt 4.4.0-rc.9 → 4.4.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.
Files changed (36) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +5 -0
  3. package/dist/runtime/conversations/components/chat-conversation-item.vue +50 -69
  4. package/dist/runtime/conversations/components/chat-conversation-list.vue +84 -61
  5. package/dist/runtime/conversations/composables/conversation-item.js +4 -0
  6. package/dist/runtime/conversations/composables/conversation-list.d.ts +2 -0
  7. package/dist/runtime/conversations/composables/conversation-list.js +33 -4
  8. package/dist/runtime/embed/components/chat-embed-inner.d.vue.ts +6 -2
  9. package/dist/runtime/embed/components/chat-embed-inner.vue +13 -2
  10. package/dist/runtime/embed/components/chat-embed-inner.vue.d.ts +6 -2
  11. package/dist/runtime/embed/components/chat-embed.d.vue.ts +4 -2
  12. package/dist/runtime/embed/components/chat-embed.vue +3 -0
  13. package/dist/runtime/embed/components/chat-embed.vue.d.ts +4 -2
  14. package/dist/runtime/embed/components/chat-topbar.d.vue.ts +11 -1
  15. package/dist/runtime/embed/components/chat-topbar.vue +7 -8
  16. package/dist/runtime/embed/components/chat-topbar.vue.d.ts +11 -1
  17. package/dist/runtime/embed/components/configuration/chat-configuration-mobile-shell.d.vue.ts +2 -2
  18. package/dist/runtime/embed/components/configuration/chat-configuration-mobile-shell.vue.d.ts +2 -2
  19. package/dist/runtime/embed/components/meistrari-chat-embed.d.vue.ts +2 -0
  20. package/dist/runtime/embed/components/meistrari-chat-embed.vue +3 -0
  21. package/dist/runtime/embed/components/meistrari-chat-embed.vue.d.ts +2 -0
  22. package/dist/runtime/embed/components/mobile/chat-mobile-drawer.vue +0 -1
  23. package/dist/runtime/embed/components/mobile/chat-mobile-shell.d.vue.ts +2 -2
  24. package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue.d.ts +2 -2
  25. package/dist/runtime/messages/components/chat-message-input.vue +148 -138
  26. package/dist/runtime/messages/components/chat-message-list.d.vue.ts +2 -2
  27. package/dist/runtime/messages/components/chat-message-list.vue +6 -10
  28. package/dist/runtime/messages/components/chat-message-list.vue.d.ts +2 -2
  29. package/dist/runtime/messages/components/chat-reasoning-effort-selector.vue +11 -7
  30. package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue +1 -1
  31. package/dist/runtime/messages/components/reasoning/chat-thinking-indicator.d.vue.ts +4 -1
  32. package/dist/runtime/messages/components/reasoning/chat-thinking-indicator.vue +15 -3
  33. package/dist/runtime/messages/components/reasoning/chat-thinking-indicator.vue.d.ts +4 -1
  34. package/dist/runtime/rendering/components/app-markdown-render.d.vue.ts +2 -2
  35. package/dist/runtime/rendering/components/app-markdown-render.vue.d.ts +2 -2
  36. package/package.json +2 -2
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "4.4.0-rc.9",
4
+ "version": "4.4.0-rc.10",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -190,7 +190,12 @@ const FULL_NAME_COMPONENT_DIRS = ["citations", "conversations", "embed", "files"
190
190
  const PATH_PREFIX_COMPONENT_DIRS = ["rendering", "shared"];
191
191
  const DYNAMIC_ICON_SAFELIST = [
192
192
  "i-ph-folder-minus",
193
+ // Folder actions menu (conversation-list.ts).
194
+ "i-ph-folder-minus-bold",
193
195
  "i-ph-folder-open",
196
+ // Sidebar folder rows build their icon name in conversation-list.ts.
197
+ "i-ph-folder-notch",
198
+ "i-ph-folder-notch-open",
194
199
  // Neutral pill icon for host-defined user mentions (user-mentions.ts).
195
200
  "i-ph-paperclip"
196
201
  ];
@@ -33,23 +33,37 @@ function setRenameInputRef(element) {
33
33
  </script>
34
34
 
35
35
  <template>
36
- <div
37
- v-if="isRenamingInline"
38
- pl-10px pr-4px
39
- h-32px
40
- rounded-10px
41
- :class="active ? 'bg-muted' : ''"
36
+ <a
37
+ :href="conversationPath"
38
+ :draggable="draggable && !isRenamingInline"
39
+ block
40
+ pl-12px pr-4px
41
+ h-30px
42
+ rounded-8px
43
+ class="group data-[active=true]:bg-muted data-[active=false]:hover:bg-subtle"
44
+ :class="isRenamingInline ? '' : 'cursor-pointer'"
45
+ :data-active="active"
46
+ @click="handleConversationClick"
47
+ @dblclick.prevent.stop="startRename"
48
+ @dragstart="handleConversationDragStart"
49
+ @dragend="handleConversationDragEnd"
42
50
  >
43
- <div flex items-center justify-between gap-8px h-full :class="props.itemClass">
44
- <div v-if="userImage" w-16px h-16px flex items-center justify-center rounded-full overflow-hidden flex-shrink-0>
51
+ <div flex items-center justify-between h-full :class="props.itemClass">
52
+ <!-- User Avatar -->
53
+ <div
54
+ v-if="userImage"
55
+ translate-x--48px w-0 opacity-0 shrink-0 overflow-hidden
56
+ class="[transition:width_0.26s_cubic-bezier(0.22,1,0.36,1),transform_0.26s_cubic-bezier(0.22,1,0.36,1),opacity_0.2s] group-hover:w-22px group-hover:translate-x-0 group-hover:opacity-100"
57
+ >
45
58
  <TelaAvatar
46
59
  :image="userImage"
47
60
  alt="Usuário"
48
61
  size="2xs"
62
+ class="rounded-full!"
49
63
  />
50
64
  </div>
51
65
 
52
- <div relative flex-1 min-w-0 flex items-center>
66
+ <div v-if="isRenamingInline" relative flex-1 min-w-0 flex items-center>
53
67
  <input
54
68
  :ref="setRenameInputRef"
55
69
  v-model="renameTitle"
@@ -58,7 +72,7 @@ function setRenameInputRef(element) {
58
72
  aria-label="Nome da conversa"
59
73
  w-full min-w-0
60
74
  bg-transparent border-none outline-none
61
- body-14-medium text-neutral-700
75
+ body-14-medium text-taupe-600
62
76
  p-0
63
77
  @blur="handleRenameBlur"
64
78
  @keydown.enter.prevent="submitRename"
@@ -66,69 +80,36 @@ function setRenameInputRef(element) {
66
80
  >
67
81
  <div aria-hidden="true" absolute inset-x-0 bottom--0.5px h-0.5px bg-border pointer-events-none />
68
82
  </div>
69
- </div>
70
- </div>
71
83
 
72
- <a
73
- v-else
74
- :href="conversationPath"
75
- :draggable="draggable"
76
- block
77
- pl-10px pr-4px
78
- h-32px
79
- rounded-10px
80
- cursor-pointer
81
- class="group data-[active=true]:bg-muted data-[active=false]:hover:bg-subtle"
82
- :data-active="active"
83
- @click="handleConversationClick"
84
- @dblclick.prevent.stop="startRename"
85
- @dragstart="handleConversationDragStart"
86
- @dragend="handleConversationDragEnd"
87
- >
88
- <div flex items-center justify-between gap-8px h-full :class="props.itemClass">
89
- <!-- User Avatar -->
90
- <div v-if="userImage" w-16px h-16px flex items-center justify-center rounded-full overflow-hidden flex-shrink-0>
91
- <TelaAvatar
92
- :image="userImage"
93
- alt="Usuário"
94
- size="2xs"
95
- />
96
- </div>
84
+ <template v-else>
85
+ <div flex-1 min-w-0 flex items-center>
86
+ <span
87
+ body-14-medium
88
+ text-taupe-700
89
+ truncate
90
+ >
91
+ {{ displayTitle }}
92
+ </span>
93
+ </div>
97
94
 
98
- <div flex-1 min-w-0 flex items-center>
99
- <span
100
- body-14-medium
101
- text-secondary
102
- class="group-hover:text-primary group-data-[active=true]:text-primary"
103
- truncate
95
+ <div
96
+ flex
97
+ items-center
98
+ class="opacity-0 group-hover:opacity-100 focus-within:opacity-100 [&:has([data-state=open])]:opacity-100"
104
99
  >
105
- {{ displayTitle }}
106
- </span>
107
- </div>
108
-
109
- <div
110
- mt-2px
111
- flex
112
- items-center
113
- class="opacity-0 group-hover:opacity-100 focus-within:opacity-100 [&:has([data-state=open])]:opacity-100"
114
- >
115
- <TelaDropdownMenu :items="menuItems" content-class="chat-sidebar-floating">
116
- <button
117
- p-4px
118
- rounded-8px
119
- hover:bg-lowered
120
- cursor-pointer
121
- class="group data-[state=open]:!bg-lowered"
122
- @click.prevent.stop
123
- >
124
- <TelaIcon
125
- name="i-ph-dots-three"
126
- size="18px"
127
- color="icon-subtle group-hover:icon-tertiary group-data-[state=open]:icon-tertiary"
100
+ <TelaDropdownMenu :items="menuItems" content-class="chat-sidebar-floating">
101
+ <TelaIconButton
102
+ icon="i-ph-dots-three"
103
+ color="secondary"
104
+ size="3xs"
105
+ aria-label="Ações da conversa"
106
+ class="group hover:!bg-lowered data-[state=open]:!bg-lowered"
107
+ icon-class="group-hover:icon-tertiary group-data-[state=open]:icon-tertiary"
108
+ @click.prevent.stop
128
109
  />
129
- </button>
130
- </TelaDropdownMenu>
131
- </div>
110
+ </TelaDropdownMenu>
111
+ </div>
112
+ </template>
132
113
  </div>
133
114
  </a>
134
115
 
@@ -47,29 +47,37 @@ const {
47
47
  handleDragLeave,
48
48
  getConversationItemProps,
49
49
  handleConversationDrop,
50
- startConversationInGroup
50
+ startConversationInGroup,
51
+ startUngroupedConversation,
52
+ createNewGroup
51
53
  } = useConversationList(props);
54
+ const ROW_HEIGHT = 32;
55
+ const HEADING_HEIGHT = 28;
56
+ const ERROR_ROW_HEIGHT = 20;
57
+ const SECTION_GAP = 10;
52
58
  const rows = computed(() => {
53
59
  const result = [];
54
60
  if (groupItems.value.length > 0)
55
- result.push({ type: "folders", key: "folders", height: 18, groupId: null });
61
+ result.push({ type: "folders", key: "folders", height: HEADING_HEIGHT, groupId: null });
56
62
  for (const groupBucket of groupItems.value) {
57
63
  const groupId = groupBucket.group.id;
58
- result.push({ type: "group", key: `group:${groupId}`, height: 34, groupId, groupBucket });
64
+ result.push({ type: "group", key: `group:${groupId}`, height: ROW_HEIGHT, groupId, groupBucket });
59
65
  if (groupBucket.isRenaming && renameGroupError.value)
60
- result.push({ type: "error", key: `error:${groupId}`, height: 20, groupId, groupBucket });
66
+ result.push({ type: "error", key: `error:${groupId}`, height: ERROR_ROW_HEIGHT, groupId, groupBucket });
61
67
  if (!groupBucket.open)
62
68
  continue;
63
69
  for (const conversation of groupBucket.items)
64
- result.push({ type: "conversation", key: conversation.id, height: 33, groupId, conversation });
70
+ result.push({ type: "conversation", key: conversation.id, height: ROW_HEIGHT, groupId, conversation });
65
71
  if (groupBucket.showNewConversationAction)
66
- result.push({ type: "new", key: `new:${groupId}`, height: 33, groupId, groupBucket });
72
+ result.push({ type: "new", key: `new:${groupId}`, height: ROW_HEIGHT, groupId, groupBucket });
67
73
  else if (groupBucket.showSearchEmpty)
68
- result.push({ type: "empty", key: `empty:${groupId}`, height: 33, groupId, groupBucket });
74
+ result.push({ type: "empty", key: `empty:${groupId}`, height: ROW_HEIGHT, groupId, groupBucket });
69
75
  }
70
- result.push({ type: "ungrouped", key: "ungrouped", height: 24, groupId: null });
76
+ if (groupItems.value.length > 0)
77
+ result.push({ type: "spacer", key: "spacer", height: SECTION_GAP, groupId: null });
78
+ result.push({ type: "ungrouped", key: "ungrouped", height: HEADING_HEIGHT, groupId: null });
71
79
  for (const conversation of ungroupedConversations.value)
72
- result.push({ type: "conversation", key: conversation.id, height: 33, groupId: null, conversation });
80
+ result.push({ type: "conversation", key: conversation.id, height: ROW_HEIGHT, groupId: null, conversation });
73
81
  let offset = 0;
74
82
  return result.map((row) => {
75
83
  const positionedRow = { ...row, offset };
@@ -117,7 +125,7 @@ async function handleRowKeydown(event, index) {
117
125
  return;
118
126
  const direction = event.shiftKey ? -1 : 1;
119
127
  let nextIndex = index + direction;
120
- while (rows.value[nextIndex] && ["folders", "ungrouped", "empty", "error"].includes(rows.value[nextIndex].type))
128
+ while (rows.value[nextIndex] && ["folders", "spacer", "ungrouped", "empty", "error"].includes(rows.value[nextIndex].type))
121
129
  nextIndex += direction;
122
130
  const nextRow = rows.value[nextIndex];
123
131
  if (!nextRow)
@@ -132,7 +140,7 @@ async function handleRowKeydown(event, index) {
132
140
  }
133
141
  watch(conversationSearch, () => scrollTo(0), { flush: "post" });
134
142
  function acceptsDrops(row) {
135
- return row.type !== "folders";
143
+ return row.type !== "folders" && row.type !== "spacer";
136
144
  }
137
145
  function dropListeners(row) {
138
146
  if (!acceptsDrops(row))
@@ -153,6 +161,13 @@ function dropListeners(row) {
153
161
  }
154
162
  };
155
163
  }
164
+ function isNestedRow(row) {
165
+ return Boolean(row.groupId) && (row.type === "conversation" || row.type === "new" || row.type === "empty");
166
+ }
167
+ function isLastNestedRow(index) {
168
+ const next = rows.value[index + 1];
169
+ return !next || !isNestedRow(next) || next.groupId !== rows.value[index].groupId;
170
+ }
156
171
  function dropTargetClass(row) {
157
172
  if (!acceptsDrops(row))
158
173
  return "";
@@ -163,10 +178,9 @@ function dropTargetClass(row) {
163
178
  </script>
164
179
 
165
180
  <template>
166
- <div flex="~ col" gap-12px h-full min-h-0 overflow-hidden>
167
- <div px-8px flex="~ col" gap-8px shrink-0>
181
+ <div flex="~ col" gap-6px h-full min-h-0 overflow-hidden>
182
+ <div px-8px shrink-0>
168
183
  <ChatConversationSearch v-model="conversationSearch" />
169
- <ChatConversationCreatorFilter />
170
184
  </div>
171
185
 
172
186
  <div v-if="shouldShowSkeleton" flex="~ col" gap-4px>
@@ -208,19 +222,41 @@ function dropTargetClass(row) {
208
222
  @dragend.capture="draggedRowKey = null"
209
223
  v-on="dropListeners(row)"
210
224
  >
211
- <span v-if="row.type === 'folders'" body-12-medium text-secondary pl-10px>
212
- Pastas · {{ groupItems.length }}
213
- </span>
214
- <span v-else-if="row.type === 'ungrouped'" block body-12-medium text-secondary pl-10px pt-4px>
215
- {{ ungroupedConversationLabel }}
216
- </span>
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>
217
251
  <div
218
252
  v-if="row.type === 'group'"
219
- rounded-10px
220
- class="group"
221
- :class="row.groupBucket.dropTarget ? '' : row.groupBucket.open ? 'bg-muted' : 'hover:bg-subtle'"
253
+ pl-12px pr-4px
254
+ h-30px
255
+ rounded-8px
256
+ class="group hover:bg-subtle"
257
+ :data-active="row.groupBucket.open && !row.groupBucket.dropTarget"
222
258
  >
223
- <div h-32px w-full pl-10px pr-4px flex items-center gap-8px text-left>
259
+ <div flex items-center justify-between h-full gap-8px>
224
260
  <template v-if="row.groupBucket.isRenaming">
225
261
  <TelaIcon
226
262
  :name="row.groupBucket.folderIcon"
@@ -228,11 +264,7 @@ function dropTargetClass(row) {
228
264
  color="icon-secondary"
229
265
  shrink-0
230
266
  />
231
- <div
232
- relative
233
- flex-1 min-w-0 flex items-center
234
- mr-4px
235
- >
267
+ <div relative flex-1 min-w-0 flex items-center>
236
268
  <input
237
269
  :ref="(element) => setRenameGroupInputRef(element, row.groupBucket.group.id)"
238
270
  v-model="renameGroupName"
@@ -242,7 +274,7 @@ function dropTargetClass(row) {
242
274
  aria-label="Nome da pasta"
243
275
  w-full min-w-0
244
276
  bg-transparent border-none outline-none
245
- body-14-medium text-primary
277
+ body-14-medium text-taupe-600
246
278
  p-0
247
279
  :disabled="groupActionLoading"
248
280
  @blur="handleRenameGroupBlur"
@@ -253,7 +285,7 @@ function dropTargetClass(row) {
253
285
  >
254
286
  <div
255
287
  aria-hidden="true"
256
- absolute inset-x-0 bottom--2px h-0.5px pointer-events-none
288
+ absolute inset-x-0 bottom--0.5px h-0.5px pointer-events-none
257
289
  :class="renameGroupError ? 'bg-red-500' : 'bg-border'"
258
290
  />
259
291
  </div>
@@ -272,41 +304,31 @@ function dropTargetClass(row) {
272
304
  <TelaIcon
273
305
  :name="row.groupBucket.folderIcon"
274
306
  size="16px"
275
- color="icon-secondary"
307
+ color="icon-tertiary"
276
308
  shrink-0
277
309
  />
278
- <div flex items-center gap-6px>
279
- <span min-w-0 truncate body-14-medium :class="row.groupBucket.open ? 'text-primary' : 'text-secondary group-hover:text-primary'">
280
- {{ row.groupBucket.group.name }}
281
- </span>
282
- <TelaAnimatedNumber v-if="row.groupBucket.conversationCountLabel" :value="row.groupBucket.conversationCountLabel" body-12-medium text-tertiary />
283
- </div>
310
+ <span min-w-0 truncate body-14-medium text-taupe-700>
311
+ {{ row.groupBucket.group.name }}
312
+ </span>
284
313
  </button>
285
314
 
286
315
  <div
287
- flex items-center justify-center shrink-0
316
+ flex items-center
288
317
  class="opacity-0 group-hover:opacity-100 focus-within:opacity-100 [&:has([data-state=open])]:opacity-100"
289
318
  >
290
319
  <TelaDropdownMenu
291
320
  :items="row.groupBucket.menuItems"
292
321
  content-class="chat-sidebar-floating"
293
322
  >
294
- <button
295
- type="button"
323
+ <TelaIconButton
324
+ icon="i-ph-dots-three"
325
+ color="secondary"
326
+ size="3xs"
296
327
  aria-label="Ações da pasta"
297
- p-4px mt-2px
298
- rounded-8px
299
- hover:bg-lowered
300
- cursor-pointer
301
- class="group data-[state=open]:!bg-lowered"
328
+ class="group hover:!bg-lowered data-[state=open]:!bg-lowered"
329
+ icon-class="group-hover:icon-tertiary group-data-[state=open]:icon-tertiary"
302
330
  @click.prevent.stop
303
- >
304
- <TelaIcon
305
- name="i-ph-dots-three"
306
- size="18px"
307
- color="icon-subtle group-hover:icon-tertiary group-data-[state=open]:icon-tertiary"
308
- />
309
- </button>
331
+ />
310
332
  </TelaDropdownMenu>
311
333
  </div>
312
334
  </template>
@@ -332,25 +354,26 @@ function dropTargetClass(row) {
332
354
  <button
333
355
  v-if="row.type === 'new'"
334
356
  type="button"
335
- h-32px px-12px w-full
336
- flex items-center gap-9px
337
- rounded-10px
357
+ relative h-30px pl-12px w-full
358
+ flex items-center justify-between
359
+ rounded-8px
338
360
  text-left
339
361
  hover:bg-subtle
340
362
  cursor-pointer
341
363
  @click="startConversationInGroup(row.groupBucket.group.id)"
342
364
  >
343
- <span body-14-medium text-secondary leading-18px ml-24px>
365
+ <span body-14-medium text-taupe-500 ml-24px>
344
366
  Nova conversa
345
367
  </span>
346
- <TelaIcon name="i-ph-plus" size="14px" color="icon-secondary" shrink-0 />
347
368
  </button>
348
- <div v-if="row.type === 'empty'" h-32px flex items-center ml-36px>
369
+ <div v-if="row.type === 'empty'" h-full flex items-center ml-36px>
349
370
  <span body-14-medium text-tertiary>Sem resultados</span>
350
371
  </div>
351
372
  <div
352
- v-if="row.groupId && (row.type === 'conversation' || row.type === 'new' || row.type === 'empty')"
353
- aria-hidden="true" absolute top-0 left-17px h-full w-0.5px bg-border pointer-events-none
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'"
354
377
  />
355
378
  </div>
356
379
  </div>
@@ -55,6 +55,10 @@ export function useConversationItem(options, emit) {
55
55
  showDeleteModal.value = false;
56
56
  }
57
57
  function handleConversationClick(event) {
58
+ if (isRenamingInline.value) {
59
+ event.preventDefault();
60
+ return;
61
+ }
58
62
  if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
59
63
  return;
60
64
  event.preventDefault();
@@ -123,4 +123,6 @@ export declare function useConversationList(options: ConversationListOptions): {
123
123
  };
124
124
  handleConversationDrop: (event: DragEvent, groupId: string | null) => Promise<void>;
125
125
  startConversationInGroup: (groupId: string) => void;
126
+ startUngroupedConversation: () => void;
127
+ createNewGroup: () => Promise<void>;
126
128
  };
@@ -15,7 +15,7 @@ export function useConversationList(options) {
15
15
  const embedConfig = useEmbedConfig();
16
16
  const { activeOrganization } = useChatAuth();
17
17
  const { deleteConversation, renameConversation, duplicateConversation, moveConversationToGroup } = useConversations();
18
- const { groups, fetchGroups, deleteGroup, renameGroup, error: groupError, pendingRenameGroupId } = useConversationGroups();
18
+ const { groups, fetchGroups, createGroup, deleteGroup, renameGroup, error: groupError, pendingRenameGroupId } = useConversationGroups();
19
19
  const { setConversation } = useChat();
20
20
  const route = useRoute();
21
21
  const router = useRouter();
@@ -239,7 +239,7 @@ export function useConversationList(options) {
239
239
  ...groupBucket,
240
240
  open,
241
241
  dropTarget,
242
- folderIcon: open ? "i-ph-folder-notch-open-bold" : "i-ph-folder-notch-bold",
242
+ folderIcon: open ? "i-ph-folder-notch-open" : "i-ph-folder-notch",
243
243
  isRenaming: renamingGroupId.value === groupBucket.group.id,
244
244
  conversationCountLabel: groupBucket.items.length > 0 ? String(groupBucket.items.length) : null,
245
245
  showNewConversationAction: !hasConversationSearchQuery.value,
@@ -249,7 +249,7 @@ export function useConversationList(options) {
249
249
  }));
250
250
  const ungroupedConversations = computed(() => groupedConversations.value.unfiled);
251
251
  const ungroupedDropTarget = computed(() => isDropTarget(null));
252
- const ungroupedConversationLabel = computed(() => `Conversas \xB7 ${ungroupedConversations.value.length}`);
252
+ const ungroupedConversationLabel = computed(() => `Conversas`);
253
253
  const shouldShowUngroupedHeader = computed(() => groupItems.value.length > 0);
254
254
  const showGroupCreationPrompt = computed(
255
255
  () => groupsReady.value && !hasConversationSearchQuery.value && groups.value.length === 0
@@ -496,6 +496,33 @@ export function useConversationList(options) {
496
496
  return;
497
497
  await moveConversationToGroup(conversationId, groupId);
498
498
  }
499
+ const DEFAULT_GROUP_NAME = "Nova pasta";
500
+ const isCreatingGroup = ref(false);
501
+ function nextGroupName() {
502
+ const names = new Set(groups.value.map((group) => group.name));
503
+ if (!names.has(DEFAULT_GROUP_NAME))
504
+ return DEFAULT_GROUP_NAME;
505
+ let index = 2;
506
+ while (names.has(`${DEFAULT_GROUP_NAME} ${index}`))
507
+ index += 1;
508
+ return `${DEFAULT_GROUP_NAME} ${index}`;
509
+ }
510
+ async function createNewGroup() {
511
+ if (isCreatingGroup.value)
512
+ return;
513
+ isCreatingGroup.value = true;
514
+ const group = await createGroup(nextGroupName());
515
+ isCreatingGroup.value = false;
516
+ if (group)
517
+ pendingRenameGroupId.value = group.id;
518
+ }
519
+ function startUngroupedConversation() {
520
+ if (options.startConversation) {
521
+ void options.startConversation(null);
522
+ } else if (options.resetConversation) {
523
+ void options.resetConversation();
524
+ }
525
+ }
499
526
  function startConversationInGroup(groupId) {
500
527
  setGroupOpen(groupId, true);
501
528
  if (options.startConversation) {
@@ -562,6 +589,8 @@ export function useConversationList(options) {
562
589
  isDropTarget,
563
590
  getConversationItemProps,
564
591
  handleConversationDrop,
565
- startConversationInGroup
592
+ startConversationInGroup,
593
+ startUngroupedConversation,
594
+ createNewGroup
566
595
  };
567
596
  }
@@ -19,7 +19,7 @@ type __VLS_Props = {
19
19
  };
20
20
  declare var __VLS_11: {}, __VLS_96: {
21
21
  message: useChatAuth;
22
- }, __VLS_204: {
22
+ }, __VLS_127: {}, __VLS_129: {}, __VLS_210: {
23
23
  message: useChatAuth;
24
24
  };
25
25
  type __VLS_Slots = {} & {
@@ -27,7 +27,11 @@ type __VLS_Slots = {} & {
27
27
  } & {
28
28
  'failed-message-actions'?: (props: typeof __VLS_96) => any;
29
29
  } & {
30
- 'failed-message-actions'?: (props: typeof __VLS_204) => any;
30
+ 'topbar-leading'?: (props: typeof __VLS_127) => any;
31
+ } & {
32
+ 'topbar-leading'?: (props: typeof __VLS_129) => any;
33
+ } & {
34
+ 'failed-message-actions'?: (props: typeof __VLS_210) => any;
31
35
  };
32
36
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
33
37
  "update:conversationId": (value: string | null) => any;
@@ -888,7 +888,7 @@ async function handleDelete() {
888
888
  </script>
889
889
 
890
890
  <template>
891
- <div ref="dropZoneRef" h-full flex bg-white relative>
891
+ <div ref="dropZoneRef" h-full flex bg relative>
892
892
  <div
893
893
  v-if="chatError"
894
894
  role="alert"
@@ -1205,7 +1205,18 @@ async function handleDelete() {
1205
1205
  @debug="showDebugModal = true"
1206
1206
  @delete="showDeleteModal = true"
1207
1207
  @tab-change="handleTabChange"
1208
- />
1208
+ >
1209
+ <template v-if="$slots['topbar-leading']" #leading>
1210
+ <slot name="topbar-leading" />
1211
+ </template>
1212
+ </ChatTopbar>
1213
+ <div
1214
+ v-else-if="$slots['topbar-leading'] && !isMobile && !compact"
1215
+ absolute top-0 z-10 h-56px flex items-center pl-16px
1216
+ :style="{ left: 'var(--chat-panel-offset)' }"
1217
+ >
1218
+ <slot name="topbar-leading" />
1219
+ </div>
1209
1220
 
1210
1221
  <div
1211
1222
  v-if="!isMobile && showOutdatedBanner"
@@ -19,7 +19,7 @@ type __VLS_Props = {
19
19
  };
20
20
  declare var __VLS_11: {}, __VLS_96: {
21
21
  message: useChatAuth;
22
- }, __VLS_204: {
22
+ }, __VLS_127: {}, __VLS_129: {}, __VLS_210: {
23
23
  message: useChatAuth;
24
24
  };
25
25
  type __VLS_Slots = {} & {
@@ -27,7 +27,11 @@ type __VLS_Slots = {} & {
27
27
  } & {
28
28
  'failed-message-actions'?: (props: typeof __VLS_96) => any;
29
29
  } & {
30
- 'failed-message-actions'?: (props: typeof __VLS_204) => any;
30
+ 'topbar-leading'?: (props: typeof __VLS_127) => any;
31
+ } & {
32
+ 'topbar-leading'?: (props: typeof __VLS_129) => any;
33
+ } & {
34
+ 'failed-message-actions'?: (props: typeof __VLS_210) => any;
31
35
  };
32
36
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
33
37
  "update:conversationId": (value: string | null) => any;
@@ -1,8 +1,10 @@
1
- declare var __VLS_26: {}, __VLS_29: any;
1
+ declare var __VLS_26: {}, __VLS_29: {}, __VLS_32: any;
2
2
  type __VLS_Slots = {} & {
3
3
  'sidebar-bottom'?: (props: typeof __VLS_26) => any;
4
4
  } & {
5
- 'failed-message-actions'?: (props: typeof __VLS_29) => any;
5
+ 'topbar-leading'?: (props: typeof __VLS_29) => any;
6
+ } & {
7
+ 'failed-message-actions'?: (props: typeof __VLS_32) => any;
6
8
  };
7
9
  declare const __VLS_base: import("vue").DefineSetupFnComponent<Record<string, any>, {}, {}, Record<string, any> & {}, import("vue").PublicProps>;
8
10
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -173,6 +173,9 @@ provideChatMessageTerminal((payload) => {
173
173
  <template v-if="$slots['sidebar-bottom']" #sidebar-bottom>
174
174
  <slot name="sidebar-bottom" />
175
175
  </template>
176
+ <template v-if="$slots['topbar-leading']" #topbar-leading>
177
+ <slot name="topbar-leading" />
178
+ </template>
176
179
  <template v-if="$slots['failed-message-actions']" #failed-message-actions="slotProps">
177
180
  <slot name="failed-message-actions" v-bind="slotProps" />
178
181
  </template>
@@ -1,8 +1,10 @@
1
- declare var __VLS_26: {}, __VLS_29: any;
1
+ declare var __VLS_26: {}, __VLS_29: {}, __VLS_32: any;
2
2
  type __VLS_Slots = {} & {
3
3
  'sidebar-bottom'?: (props: typeof __VLS_26) => any;
4
4
  } & {
5
- 'failed-message-actions'?: (props: typeof __VLS_29) => any;
5
+ 'topbar-leading'?: (props: typeof __VLS_29) => any;
6
+ } & {
7
+ 'failed-message-actions'?: (props: typeof __VLS_32) => any;
6
8
  };
7
9
  declare const __VLS_base: import("vue").DefineSetupFnComponent<Record<string, any>, {}, {}, Record<string, any> & {}, import("vue").PublicProps>;
8
10
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;