@meistrari/chat-nuxt 1.6.0 → 1.8.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 (106) hide show
  1. package/README.md +25 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +23 -2
  4. package/dist/runtime/components/MeistrariChatEmbed.vue +8 -1
  5. package/dist/runtime/components/chat/bash-widget.vue +3 -3
  6. package/dist/runtime/components/chat/doc-sdk-widget.vue +3 -3
  7. package/dist/runtime/components/chat/files-view.vue +3 -64
  8. package/dist/runtime/components/chat/generic-tool-widget.vue +3 -3
  9. package/dist/runtime/components/chat/grep-widget.vue +3 -3
  10. package/dist/runtime/components/chat/message-bubble.vue +1 -1
  11. package/dist/runtime/components/chat/message-input.d.vue.ts +2 -0
  12. package/dist/runtime/components/chat/message-input.vue +21 -5
  13. package/dist/runtime/components/chat/message-input.vue.d.ts +2 -0
  14. package/dist/runtime/components/chat/message-list.vue +1 -1
  15. package/dist/runtime/components/chat/mobile/files/file-preview.d.vue.ts +11 -0
  16. package/dist/runtime/components/chat/mobile/files/file-preview.vue +112 -0
  17. package/dist/runtime/components/chat/mobile/files/file-preview.vue.d.ts +11 -0
  18. package/dist/runtime/components/chat/mobile/files/files-view.d.vue.ts +10 -0
  19. package/dist/runtime/components/chat/mobile/files/files-view.vue +384 -0
  20. package/dist/runtime/components/chat/mobile/files/files-view.vue.d.ts +10 -0
  21. package/dist/runtime/components/chat/mobile/home/header.d.vue.ts +19 -0
  22. package/dist/runtime/components/chat/mobile/home/header.vue +55 -0
  23. package/dist/runtime/components/chat/mobile/home/header.vue.d.ts +19 -0
  24. package/dist/runtime/components/chat/mobile/home/recents.d.vue.ts +14 -0
  25. package/dist/runtime/components/chat/mobile/home/recents.vue +48 -0
  26. package/dist/runtime/components/chat/mobile/home/recents.vue.d.ts +14 -0
  27. package/dist/runtime/components/chat/mobile/home/suggestions.d.vue.ts +7 -0
  28. package/dist/runtime/components/chat/mobile/home/suggestions.vue +31 -0
  29. package/dist/runtime/components/chat/mobile/home/suggestions.vue.d.ts +7 -0
  30. package/dist/runtime/components/chat/mobile/primitives/bottom-sheet.d.vue.ts +21 -0
  31. package/dist/runtime/components/chat/mobile/primitives/bottom-sheet.vue +48 -0
  32. package/dist/runtime/components/chat/mobile/primitives/bottom-sheet.vue.d.ts +21 -0
  33. package/dist/runtime/components/chat/mobile/primitives/info-bubble.d.vue.ts +6 -0
  34. package/dist/runtime/components/chat/mobile/primitives/info-bubble.vue +50 -0
  35. package/dist/runtime/components/chat/mobile/primitives/info-bubble.vue.d.ts +6 -0
  36. package/dist/runtime/components/chat/mobile/shell/drawer.d.vue.ts +22 -0
  37. package/dist/runtime/components/chat/mobile/shell/drawer.vue +143 -0
  38. package/dist/runtime/components/chat/mobile/shell/drawer.vue.d.ts +22 -0
  39. package/dist/runtime/components/chat/mobile/shell/header.d.vue.ts +40 -0
  40. package/dist/runtime/components/chat/mobile/shell/header.vue +139 -0
  41. package/dist/runtime/components/chat/mobile/shell/header.vue.d.ts +40 -0
  42. package/dist/runtime/components/chat/mobile/shell/outdated-settings-sheet.d.vue.ts +14 -0
  43. package/dist/runtime/components/chat/mobile/shell/outdated-settings-sheet.vue +47 -0
  44. package/dist/runtime/components/chat/mobile/shell/outdated-settings-sheet.vue.d.ts +14 -0
  45. package/dist/runtime/components/chat/mobile/shell/shell.d.vue.ts +122 -0
  46. package/dist/runtime/components/chat/mobile/shell/shell.vue +283 -0
  47. package/dist/runtime/components/chat/mobile/shell/shell.vue.d.ts +122 -0
  48. package/dist/runtime/components/chat/mobile/shell/workspace-sheet.d.vue.ts +19 -0
  49. package/dist/runtime/components/chat/mobile/shell/workspace-sheet.vue +51 -0
  50. package/dist/runtime/components/chat/mobile/shell/workspace-sheet.vue.d.ts +19 -0
  51. package/dist/runtime/components/chat/mobile/usage/usage-view.d.vue.ts +8 -0
  52. package/dist/runtime/components/chat/mobile/usage/usage-view.vue +243 -0
  53. package/dist/runtime/components/chat/mobile/usage/usage-view.vue.d.ts +8 -0
  54. package/dist/runtime/components/chat/model-selector.vue +6 -0
  55. package/dist/runtime/components/chat/read-widget.vue +3 -3
  56. package/dist/runtime/components/chat/reasoning-step-item.vue +1 -1
  57. package/dist/runtime/components/chat/reasoning-steps.vue +1 -1
  58. package/dist/runtime/components/chat/reasoning-thought.vue +2 -3
  59. package/dist/runtime/components/chat/tela-skill-widget.vue +3 -3
  60. package/dist/runtime/components/chat/thinking-widget.vue +3 -3
  61. package/dist/runtime/components/chat/usage-view.vue +20 -101
  62. package/dist/runtime/components/chat/web-search-widget.vue +3 -3
  63. package/dist/runtime/components/chat/workstation-query-widget.vue +6 -6
  64. package/dist/runtime/components/chat/write-widget.vue +3 -3
  65. package/dist/runtime/composables/useChat.js +2 -1
  66. package/dist/runtime/composables/useChatApi.js +4 -0
  67. package/dist/runtime/composables/useConversationFileDownloads.d.ts +6 -0
  68. package/dist/runtime/composables/useConversationFileDownloads.js +50 -0
  69. package/dist/runtime/composables/useConversationUsage.d.ts +13 -0
  70. package/dist/runtime/composables/useConversationUsage.js +121 -0
  71. package/dist/runtime/composables/useConversations.js +3 -1
  72. package/dist/runtime/composables/useEmbedConfig.d.ts +4 -0
  73. package/dist/runtime/composables/useEmbedConfig.js +9 -0
  74. package/dist/runtime/composables/useMentionAutocomplete.d.ts +6 -0
  75. package/dist/runtime/composables/useMentionAutocomplete.js +71 -23
  76. package/dist/runtime/composables/useWorkspaceSwitchModal.d.ts +5 -0
  77. package/dist/runtime/composables/useWorkspaceSwitchModal.js +14 -0
  78. package/dist/runtime/embed/components/ChatConfigurationModal.vue +119 -13
  79. package/dist/runtime/embed/components/ChatEmbed.vue +9 -2
  80. package/dist/runtime/embed/components/ChatEmbedInner.d.vue.ts +2 -0
  81. package/dist/runtime/embed/components/ChatEmbedInner.vue +201 -11
  82. package/dist/runtime/embed/components/ChatEmbedInner.vue.d.ts +2 -0
  83. package/dist/runtime/embed/components/configuration/ChatConfigurationMobileShell.d.vue.ts +34 -0
  84. package/dist/runtime/embed/components/configuration/ChatConfigurationMobileShell.vue +95 -0
  85. package/dist/runtime/embed/components/configuration/ChatConfigurationMobileShell.vue.d.ts +34 -0
  86. package/dist/runtime/server/api/conversations/[id]/duplicate.post.js +1 -0
  87. package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +3 -1
  88. package/dist/runtime/server/api/conversations/[id]/messages/index.get.js +29 -5
  89. package/dist/runtime/server/api/conversations/index.post.js +1 -0
  90. package/dist/runtime/server/db/schema/conversations.d.ts +17 -0
  91. package/dist/runtime/server/db/schema/conversations.js +1 -0
  92. package/dist/runtime/server/utils/chat-context.d.ts +2 -0
  93. package/dist/runtime/server/utils/chat-context.js +15 -1
  94. package/dist/runtime/server/utils/conversation-message-sync.d.ts +8 -2
  95. package/dist/runtime/server/utils/conversation-message-sync.js +52 -6
  96. package/dist/runtime/server/utils/conversation-scope.js +2 -1
  97. package/dist/runtime/types/chat-auth.d.ts +9 -8
  98. package/dist/runtime/types/embed.d.ts +21 -0
  99. package/dist/runtime/utils/breakpoints.d.ts +1 -0
  100. package/dist/runtime/utils/breakpoints.js +1 -0
  101. package/dist/runtime/utils/tela-chat.d.ts +3 -1
  102. package/dist/runtime/utils/tela-chat.js +11 -2
  103. package/drizzle/0015_illegal_blindfold.sql +1 -0
  104. package/drizzle/meta/0015_snapshot.json +738 -0
  105. package/drizzle/meta/_journal.json +7 -0
  106. package/package.json +6 -1
@@ -0,0 +1,384 @@
1
+ <script setup>
2
+ import ChatMobileBottomSheet from "../primitives/bottom-sheet.vue";
3
+ import ChatMobileFilePreview from "./file-preview.vue";
4
+ import { formatFileSize, formatUploadDate, getFileTypeLabel } from "../../../../utils/file";
5
+ const props = defineProps({
6
+ conversationId: { type: String, required: true }
7
+ });
8
+ const emit = defineEmits(["viewInChat"]);
9
+ const conversationIdRef = computed(() => props.conversationId);
10
+ const { files, loading } = useConversationFiles(conversationIdRef);
11
+ const { downloadFile } = useConversationFileDownloads();
12
+ const typeFilter = ref(null);
13
+ const originFilter = ref(null);
14
+ const searchExpanded = ref(false);
15
+ const searchQuery = ref("");
16
+ const searchInput = ref(null);
17
+ const typeOptions = [
18
+ { value: "pdf", label: "PDF" },
19
+ { value: "document", label: "Documento" },
20
+ { value: "spreadsheet", label: "Planilha" },
21
+ { value: "text", label: "Texto" },
22
+ { value: "image", label: "Imagem" },
23
+ { value: "video", label: "V\xEDdeo" },
24
+ { value: "audio", label: "\xC1udio" }
25
+ ];
26
+ const originOptions = [
27
+ { value: "user", label: "Usu\xE1rio" },
28
+ { value: "assistant", label: "Assistente" }
29
+ ];
30
+ function matchesType(mime, filter) {
31
+ const m = mime.toLowerCase();
32
+ switch (filter) {
33
+ case "pdf":
34
+ return m.includes("pdf");
35
+ case "document":
36
+ return m.includes("word") || m.includes("document");
37
+ case "spreadsheet":
38
+ return m.includes("excel") || m.includes("spreadsheet") || m.includes("csv");
39
+ case "text":
40
+ return m.startsWith("text/") || m.includes("json");
41
+ case "image":
42
+ return m.startsWith("image/");
43
+ case "video":
44
+ return m.startsWith("video/");
45
+ case "audio":
46
+ return m.startsWith("audio/");
47
+ }
48
+ }
49
+ const filteredFiles = computed(() => {
50
+ let result = files.value;
51
+ if (searchQuery.value.trim()) {
52
+ const q = searchQuery.value.toLowerCase();
53
+ result = result.filter((f) => f.name.toLowerCase().includes(q));
54
+ }
55
+ if (typeFilter.value) {
56
+ result = result.filter((f) => matchesType(f.mimeType, typeFilter.value));
57
+ }
58
+ if (originFilter.value) {
59
+ result = result.filter((f) => f.messageRole === originFilter.value);
60
+ }
61
+ return result;
62
+ });
63
+ const totalCount = computed(() => files.value.length);
64
+ const filteredCount = computed(() => filteredFiles.value.length);
65
+ const hasActiveFilters = computed(
66
+ () => typeFilter.value !== null || originFilter.value !== null || searchQuery.value.trim().length > 0
67
+ );
68
+ const countLabel = computed(() => {
69
+ if (hasActiveFilters.value) {
70
+ return `${filteredCount.value} de ${totalCount.value} ${totalCount.value === 1 ? "arquivo" : "arquivos"}`;
71
+ }
72
+ return `${totalCount.value} ${totalCount.value === 1 ? "arquivo" : "arquivos"}`;
73
+ });
74
+ const typeChipLabel = computed(() => {
75
+ if (!typeFilter.value)
76
+ return "Tipo";
77
+ return typeOptions.find((o) => o.value === typeFilter.value)?.label ?? "Tipo";
78
+ });
79
+ const originChipLabel = computed(() => {
80
+ if (!originFilter.value)
81
+ return "Origem";
82
+ return originOptions.find((o) => o.value === originFilter.value)?.label ?? "Origem";
83
+ });
84
+ const typeSheetOpen = ref(false);
85
+ const originSheetOpen = ref(false);
86
+ const actionsFile = ref(null);
87
+ const previewFile = ref(null);
88
+ function clearAll() {
89
+ typeFilter.value = null;
90
+ originFilter.value = null;
91
+ searchQuery.value = "";
92
+ searchExpanded.value = false;
93
+ }
94
+ async function expandSearch() {
95
+ searchExpanded.value = true;
96
+ await nextTick();
97
+ searchInput.value?.focus();
98
+ }
99
+ function collapseSearch() {
100
+ searchExpanded.value = false;
101
+ searchQuery.value = "";
102
+ }
103
+ function pickType(value) {
104
+ typeFilter.value = value;
105
+ typeSheetOpen.value = false;
106
+ }
107
+ function pickOrigin(value) {
108
+ originFilter.value = value;
109
+ originSheetOpen.value = false;
110
+ }
111
+ function openActions(file, event) {
112
+ event.stopPropagation();
113
+ actionsFile.value = file;
114
+ }
115
+ function handleRowClick(file) {
116
+ previewFile.value = file;
117
+ }
118
+ function handleVisualize() {
119
+ if (actionsFile.value) {
120
+ previewFile.value = actionsFile.value;
121
+ }
122
+ actionsFile.value = null;
123
+ }
124
+ function handleDownloadFromSheet() {
125
+ if (actionsFile.value) {
126
+ void downloadFile(actionsFile.value);
127
+ }
128
+ actionsFile.value = null;
129
+ }
130
+ function handleViewInChat() {
131
+ emit("viewInChat");
132
+ actionsFile.value = null;
133
+ }
134
+ function getTilePalette(file) {
135
+ const m = file.mimeType.toLowerCase();
136
+ if (m.startsWith("image/")) {
137
+ return { bg: "#EEF2FF", fg: "#4D46DC", icon: "i-ph-image" };
138
+ }
139
+ if (m.includes("pdf")) {
140
+ return { bg: "#FEE2E2", fg: "#DC2626", icon: "i-ph-file-pdf" };
141
+ }
142
+ if (m.includes("excel") || m.includes("spreadsheet") || m.includes("csv")) {
143
+ return { bg: "#DCFCE7", fg: "#16A34A", icon: "i-ph-file-xls" };
144
+ }
145
+ if (m.startsWith("audio/")) {
146
+ return { bg: "#FEF3C7", fg: "#D97706", icon: "i-ph-music-note" };
147
+ }
148
+ return { bg: "#F5F5F5", fg: "#737373", icon: "i-ph-file" };
149
+ }
150
+ function metaLine(file) {
151
+ const parts = [getFileTypeLabel(file.mimeType)];
152
+ if (file.size) {
153
+ parts.push(formatFileSize(file.size));
154
+ }
155
+ if (file.createdAt) {
156
+ parts.push(formatUploadDate(file.createdAt));
157
+ }
158
+ return parts.join(" \xB7 ");
159
+ }
160
+ </script>
161
+
162
+ <template>
163
+ <div h-full flex="~ col" pt-54px overflow-hidden relative>
164
+ <div
165
+ flex items-center gap-8px overflow-x-auto
166
+ px-14px pt-10px pb-6px shrink-0
167
+ class="filter-bar"
168
+ >
169
+ <button
170
+ class="filter-chip"
171
+ :class="{ 'filter-chip--active': typeFilter !== null }"
172
+ aria-label="Filtrar por tipo"
173
+ @click="typeSheetOpen = true"
174
+ >
175
+ <span>{{ typeChipLabel }}</span>
176
+ <span i-ph-caret-down text-12px />
177
+ </button>
178
+
179
+ <button
180
+ class="filter-chip"
181
+ :class="{ 'filter-chip--active': originFilter !== null }"
182
+ aria-label="Filtrar por origem"
183
+ @click="originSheetOpen = true"
184
+ >
185
+ <span>{{ originChipLabel }}</span>
186
+ <span i-ph-caret-down text-12px />
187
+ </button>
188
+
189
+ <button
190
+ v-if="!searchExpanded"
191
+ class="filter-search-icon"
192
+ aria-label="Buscar"
193
+ @click="expandSearch"
194
+ >
195
+ <span i-ph-magnifying-glass text-16px />
196
+ </button>
197
+
198
+ <div v-else class="filter-search">
199
+ <span i-ph-magnifying-glass text-14px text-neutral-400 />
200
+ <input
201
+ ref="searchInput"
202
+ v-model="searchQuery"
203
+ type="text"
204
+ placeholder="Buscar"
205
+ >
206
+ <button
207
+ aria-label="Limpar busca"
208
+ class="filter-search__clear"
209
+ @click="collapseSearch"
210
+ >
211
+ <span i-ph-x text-12px />
212
+ </button>
213
+ </div>
214
+ </div>
215
+
216
+ <div
217
+ flex items-center justify-between gap-8px
218
+ px-16px pt-2px pb-6px shrink-0
219
+ >
220
+ <span text-12px text-neutral-400 leading-16px>
221
+ {{ countLabel }}
222
+ </span>
223
+ <button
224
+ v-if="hasActiveFilters"
225
+ text-12px text-neutral-900 leading-16px underline
226
+ @click="clearAll"
227
+ >
228
+ Limpar
229
+ </button>
230
+ </div>
231
+
232
+ <div flex-1 min-h-0 overflow-y-auto px-8px pb-16px>
233
+ <div v-if="loading" flex="~ col" items-center justify-center py-64px>
234
+ <span i-ph-spinner animate-spin text-32px text-neutral-300 />
235
+ </div>
236
+
237
+ <div
238
+ v-else-if="totalCount === 0"
239
+ flex="~ col" items-center justify-center
240
+ gap-14px px-28px py-40px text-center
241
+ >
242
+ <div
243
+ w-48px h-48px rounded-14px
244
+ bg-neutral-100 text-neutral-500
245
+ flex items-center justify-center
246
+ >
247
+ <span i-ph-folder text-22px />
248
+ </div>
249
+ <div flex="~ col" gap-4px>
250
+ <p text-16px font-500 text-neutral-900 leading-20px tracking="-.2px">
251
+ Sem arquivos
252
+ </p>
253
+ <p text-14px text-neutral-500 leading-18px max-w-240px>
254
+ Envie arquivos no chat e eles aparecerão aqui.
255
+ </p>
256
+ </div>
257
+ </div>
258
+
259
+ <div
260
+ v-else-if="filteredCount === 0"
261
+ px-24px py-48px text-center
262
+ >
263
+ <p text-14px text-neutral-400 leading-18px>
264
+ Nenhum arquivo encontrado.
265
+ </p>
266
+ </div>
267
+
268
+ <div v-else flex="~ col" gap-2px>
269
+ <div
270
+ v-for="file in filteredFiles"
271
+ :key="file.id"
272
+ class="file-row"
273
+ >
274
+ <button
275
+ class="file-row__main"
276
+ @click="handleRowClick(file)"
277
+ >
278
+ <div
279
+ class="file-tile"
280
+ :style="{ background: getTilePalette(file).bg, color: getTilePalette(file).fg }"
281
+ >
282
+ <span :class="getTilePalette(file).icon" text-22px />
283
+ </div>
284
+
285
+ <div flex="~ col" gap-3px min-w-0 flex-1 text-left>
286
+ <span text-14px font-500 text-neutral-900 leading-18px truncate>
287
+ {{ file.name }}
288
+ </span>
289
+ <span text-12px text-neutral-400 leading-16px truncate>
290
+ {{ metaLine(file) }}
291
+ </span>
292
+ </div>
293
+ </button>
294
+
295
+ <button
296
+ class="file-row__more"
297
+ aria-label="Ações do arquivo"
298
+ @click="openActions(file, $event)"
299
+ >
300
+ <span i-ph-dots-three text-18px />
301
+ </button>
302
+ </div>
303
+ </div>
304
+ </div>
305
+
306
+ <ChatMobileBottomSheet
307
+ :open="typeSheetOpen"
308
+ title="Tipo de arquivo"
309
+ @close="typeSheetOpen = false"
310
+ >
311
+ <button
312
+ class="sheet-option"
313
+ :class="{ 'sheet-option--active': typeFilter === null }"
314
+ @click="pickType(null)"
315
+ >
316
+ <span>Todos</span>
317
+ <span v-if="typeFilter === null" class="sheet-option__dot" />
318
+ </button>
319
+ <button
320
+ v-for="opt in typeOptions"
321
+ :key="opt.value"
322
+ class="sheet-option"
323
+ :class="{ 'sheet-option--active': typeFilter === opt.value }"
324
+ @click="pickType(opt.value)"
325
+ >
326
+ <span>{{ opt.label }}</span>
327
+ <span v-if="typeFilter === opt.value" class="sheet-option__dot" />
328
+ </button>
329
+ </ChatMobileBottomSheet>
330
+
331
+ <ChatMobileBottomSheet
332
+ :open="originSheetOpen"
333
+ title="Origem do arquivo"
334
+ @close="originSheetOpen = false"
335
+ >
336
+ <button
337
+ class="sheet-option"
338
+ :class="{ 'sheet-option--active': originFilter === null }"
339
+ @click="pickOrigin(null)"
340
+ >
341
+ <span>Todas as origens</span>
342
+ <span v-if="originFilter === null" class="sheet-option__dot" />
343
+ </button>
344
+ <button
345
+ v-for="opt in originOptions"
346
+ :key="opt.value"
347
+ class="sheet-option"
348
+ :class="{ 'sheet-option--active': originFilter === opt.value }"
349
+ @click="pickOrigin(opt.value)"
350
+ >
351
+ <span>{{ opt.label }}</span>
352
+ <span v-if="originFilter === opt.value" class="sheet-option__dot" />
353
+ </button>
354
+ </ChatMobileBottomSheet>
355
+
356
+ <ChatMobileBottomSheet
357
+ :open="actionsFile !== null"
358
+ :title="actionsFile?.name"
359
+ @close="actionsFile = null"
360
+ >
361
+ <button class="sheet-action" @click="handleVisualize">
362
+ <span i-ph-eye text-18px />
363
+ <span>Visualizar</span>
364
+ </button>
365
+ <button class="sheet-action" @click="handleDownloadFromSheet">
366
+ <span i-ph-download-simple text-18px />
367
+ <span>Baixar</span>
368
+ </button>
369
+ <button class="sheet-action" @click="handleViewInChat">
370
+ <span i-ph-chat-circle text-18px />
371
+ <span>Ver no chat</span>
372
+ </button>
373
+ </ChatMobileBottomSheet>
374
+
375
+ <ChatMobileFilePreview
376
+ :file="previewFile"
377
+ @close="previewFile = null"
378
+ />
379
+ </div>
380
+ </template>
381
+
382
+ <style scoped>
383
+ .filter-bar{scrollbar-width:none}.filter-bar::-webkit-scrollbar{display:none}.filter-chip{align-items:center;background:#fff;border:.5px solid #d9d9d9;border-radius:999px;color:#171717;display:inline-flex;flex-shrink:0;font-size:13px;font-weight:500;gap:4px;line-height:16px;padding:7px 12px;transition:background .15s,border-color .15s;white-space:nowrap}.filter-chip--active{background:#f5f5f5;border-color:#171717}.filter-search-icon{color:#171717;height:34px;justify-content:center;width:34px}.filter-search,.filter-search-icon{align-items:center;background:#fff;border:.5px solid #d9d9d9;border-radius:999px;display:inline-flex;flex-shrink:0}.filter-search{gap:6px;min-width:160px;padding:7px 12px}.filter-search input{background:transparent;border:none;color:#171717;flex:1;font-size:13px;line-height:16px;min-width:0;outline:none}.filter-search input::-moz-placeholder{color:#a3a3a3}.filter-search input::placeholder{color:#a3a3a3}.filter-search__clear{align-items:center;background:#f5f5f5;border-radius:999px;color:#737373;display:inline-flex;flex-shrink:0;height:18px;justify-content:center;width:18px}.file-row{align-items:center;background:transparent;border-radius:10px;display:flex;gap:4px;padding:0 8px 0 0;transition:background .12s;width:100%}.file-row:has(.file-row__main:active){background:#f5f5f5}.file-row__main{align-items:center;background:transparent;border-radius:10px;display:flex;flex:1;gap:12px;min-width:0;padding:10px 8px}.file-tile{border-radius:10px;height:44px;width:44px}.file-row__more,.file-tile{align-items:center;display:inline-flex;flex-shrink:0;justify-content:center}.file-row__more{background:transparent;border-radius:8px;color:#737373;height:32px;transition:background .12s;width:32px}.file-row__more:hover{background:#f5f5f5}.sheet-option{align-items:center;background:transparent;color:#171717;display:flex;font-size:14px;font-weight:500;justify-content:space-between;letter-spacing:-.15px;line-height:18px;padding:14px 18px;transition:background .12s;width:100%}.sheet-option--active{background:#f5f5f5}.sheet-option__dot{background:#171717;border-radius:50%;height:8px;width:8px}.sheet-action{align-items:center;background:transparent;color:#171717;display:flex;font-size:14px;font-weight:500;gap:14px;letter-spacing:-.15px;line-height:18px;padding:14px 18px;transition:background .12s;width:100%}.sheet-action:active{background:#f5f5f5}.sheet-action--danger{color:#dc2626}
384
+ </style>
@@ -0,0 +1,10 @@
1
+ type __VLS_Props = {
2
+ conversationId: string;
3
+ };
4
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
5
+ viewInChat: () => any;
6
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
7
+ onViewInChat?: (() => any) | undefined;
8
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
@@ -0,0 +1,19 @@
1
+ type __VLS_Props = {
2
+ showDrawerButton?: boolean;
3
+ showWorkspaceButton?: boolean;
4
+ workspaceInitial?: string;
5
+ workspaceLogo?: string;
6
+ workspaceName?: string;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
9
+ openDrawer: () => any;
10
+ openWorkspace: () => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onOpenDrawer?: (() => any) | undefined;
13
+ onOpenWorkspace?: (() => any) | undefined;
14
+ }>, {
15
+ showDrawerButton: boolean;
16
+ showWorkspaceButton: boolean;
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,55 @@
1
+ <script setup>
2
+ defineProps({
3
+ showDrawerButton: { type: Boolean, required: false, default: true },
4
+ showWorkspaceButton: { type: Boolean, required: false, default: true },
5
+ workspaceInitial: { type: String, required: false },
6
+ workspaceLogo: { type: String, required: false },
7
+ workspaceName: { type: String, required: false }
8
+ });
9
+ const emit = defineEmits(["openDrawer", "openWorkspace"]);
10
+ </script>
11
+
12
+ <template>
13
+ <div
14
+ flex items-center gap-8px
15
+ px-10px pt-10px pb-8px
16
+ b-b=".5px solid rgba(0,0,0,0.08)"
17
+ bg-white
18
+ absolute top-0 right-0 left-0 z-10
19
+ >
20
+ <button
21
+ v-if="showDrawerButton"
22
+ flex items-center justify-center
23
+ w-32px h-32px rounded-8px
24
+ class="hover:bg-neutral-100 active:scale-94"
25
+ transition-all
26
+ aria-label="Abrir menu"
27
+ @click="emit('openDrawer')"
28
+ >
29
+ <span i-ph-list text-20px text-neutral-700 />
30
+ </button>
31
+
32
+ <span flex-1 />
33
+
34
+ <button
35
+ v-if="showWorkspaceButton"
36
+ class="workspace-btn"
37
+ :aria-label="`Workspace ${workspaceName ?? ''}`"
38
+ @click="emit('openWorkspace')"
39
+ >
40
+ <img
41
+ v-if="workspaceLogo"
42
+ :src="workspaceLogo"
43
+ :alt="workspaceName ?? 'Workspace'"
44
+ class="workspace-btn__img"
45
+ >
46
+ <span v-else>
47
+ {{ (workspaceInitial ?? "M").charAt(0).toUpperCase() }}
48
+ </span>
49
+ </button>
50
+ </div>
51
+ </template>
52
+
53
+ <style scoped>
54
+ .workspace-btn{align-items:center;background:#5167f4;border-radius:8px;color:#fff;display:inline-flex;font-family:inherit;font-size:14px;font-weight:580;height:32px;justify-content:center;overflow:hidden;transition:transform .12s;width:32px}.workspace-btn:active{transform:scale(.94)}.workspace-btn__img{height:100%;-o-object-fit:cover;object-fit:cover;width:100%}
55
+ </style>
@@ -0,0 +1,19 @@
1
+ type __VLS_Props = {
2
+ showDrawerButton?: boolean;
3
+ showWorkspaceButton?: boolean;
4
+ workspaceInitial?: string;
5
+ workspaceLogo?: string;
6
+ workspaceName?: string;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
9
+ openDrawer: () => any;
10
+ openWorkspace: () => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onOpenDrawer?: (() => any) | undefined;
13
+ onOpenWorkspace?: (() => any) | undefined;
14
+ }>, {
15
+ showDrawerButton: boolean;
16
+ showWorkspaceButton: boolean;
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,14 @@
1
+ import type { DeepReadonly } from 'vue';
2
+ import type { Conversation } from '../../../../types/chat.js';
3
+ type __VLS_Props = {
4
+ conversations: DeepReadonly<Conversation[]>;
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
7
+ select: (id: string) => any;
8
+ seeAll: () => any;
9
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
10
+ onSelect?: ((id: string) => any) | undefined;
11
+ onSeeAll?: (() => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
@@ -0,0 +1,48 @@
1
+ <script setup>
2
+ import { formatRelativeTime } from "../../../../utils/format-time";
3
+ const props = defineProps({
4
+ conversations: { type: null, required: true }
5
+ });
6
+ const emit = defineEmits(["select", "seeAll"]);
7
+ const recents = computed(() => props.conversations.slice(0, 2));
8
+ </script>
9
+
10
+ <template>
11
+ <div v-if="recents.length > 0" flex="~ col">
12
+ <div class="header-row">
13
+ <span class="eyebrow">Conversas recentes</span>
14
+ <span flex-1 />
15
+ <button
16
+ type="button"
17
+ class="see-all"
18
+ @click="emit('seeAll')"
19
+ >
20
+ Ver todas
21
+ </button>
22
+ </div>
23
+
24
+ <div flex="~ col" gap-2px px-8px>
25
+ <button
26
+ v-for="c in recents"
27
+ :key="c.id"
28
+ type="button"
29
+ class="row"
30
+ @click="emit('select', c.id)"
31
+ >
32
+ <div class="row__icon">
33
+ <span i-ph-chat-circle text-16px />
34
+ </div>
35
+ <div flex="~ col 1" gap-2px min-w-0 text-left>
36
+ <span class="row__title">{{ c.title || "Nova conversa" }}</span>
37
+ </div>
38
+ <span class="row__time">
39
+ {{ formatRelativeTime(c.updatedAt) }}
40
+ </span>
41
+ </button>
42
+ </div>
43
+ </div>
44
+ </template>
45
+
46
+ <style scoped>
47
+ .header-row{align-items:center;display:flex;padding:0 18px 4px}.eyebrow{color:#a3a3a3;font-size:11px;font-weight:580;letter-spacing:.6px;text-transform:uppercase}.see-all{color:#5167f4;font-size:12px;font-weight:500;padding:4px 6px;white-space:nowrap}.row,.see-all{background:transparent}.row{align-items:center;border-radius:10px;display:flex;gap:12px;padding:10px 12px;transition:background .12s;width:100%}.row:active,.row__icon{background:#f5f5f5}.row__icon{align-items:center;border-radius:8px;color:#737373;display:inline-flex;flex-shrink:0;height:32px;justify-content:center;width:32px}.row__title{color:#171717;font-size:13.5px;font-weight:500;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.row__time{color:#a3a3a3;flex-shrink:0;font-size:11px}
48
+ </style>
@@ -0,0 +1,14 @@
1
+ import type { DeepReadonly } from 'vue';
2
+ import type { Conversation } from '../../../../types/chat.js';
3
+ type __VLS_Props = {
4
+ conversations: DeepReadonly<Conversation[]>;
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
7
+ select: (id: string) => any;
8
+ seeAll: () => any;
9
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
10
+ onSelect?: ((id: string) => any) | undefined;
11
+ onSeeAll?: (() => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const _default: typeof __VLS_export;
14
+ export default _default;
@@ -0,0 +1,7 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
2
+ pick: (label: string) => any;
3
+ }, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
4
+ onPick?: ((label: string) => any) | undefined;
5
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,31 @@
1
+ <script setup>
2
+ const emit = defineEmits(["pick"]);
3
+ const suggestions = [
4
+ { id: "s1", icon: "i-ph-file-text", label: "Resumir um documento longo" },
5
+ { id: "s2", icon: "i-ph-envelope", label: "Escrever um e-mail formal" },
6
+ { id: "s3", icon: "i-ph-lightbulb", label: "Brainstorm de campanha de marketing" }
7
+ ];
8
+ </script>
9
+
10
+ <template>
11
+ <div flex="~ col">
12
+ <span class="eyebrow">Sugestões</span>
13
+
14
+ <div class="chip-row">
15
+ <button
16
+ v-for="s in suggestions"
17
+ :key="s.id"
18
+ type="button"
19
+ class="chip"
20
+ @click="emit('pick', s.label)"
21
+ >
22
+ <span :class="s.icon" class="chip__icon" />
23
+ <span>{{ s.label }}</span>
24
+ </button>
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <style scoped>
30
+ .eyebrow{color:#a3a3a3;font-size:11px;font-weight:580;letter-spacing:.6px;padding:0 18px 6px;text-transform:uppercase}.chip-row{display:flex;gap:8px;overflow-x:auto;padding:4px 14px 8px;scrollbar-width:none}.chip-row::-webkit-scrollbar{display:none}.chip{align-items:center;background:#fff;border:.5px solid #d9d9d9;border-radius:999px;color:#171717;display:inline-flex;flex-shrink:0;font-size:13px;font-weight:460;gap:8px;line-height:16px;padding:9px 13px;transition:background .12s;white-space:nowrap}.chip:active{background:#f5f5f5}.chip__icon{color:#737373;font-size:16px}
31
+ </style>
@@ -0,0 +1,7 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
2
+ pick: (label: string) => any;
3
+ }, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
4
+ onPick?: ((label: string) => any) | undefined;
5
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,21 @@
1
+ type __VLS_Props = {
2
+ open: boolean;
3
+ title?: string;
4
+ };
5
+ declare var __VLS_13: {};
6
+ type __VLS_Slots = {} & {
7
+ default?: (props: typeof __VLS_13) => any;
8
+ };
9
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
10
+ close: () => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onClose?: (() => any) | undefined;
13
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
17
+ type __VLS_WithSlots<T, S> = T & {
18
+ new (): {
19
+ $slots: S;
20
+ };
21
+ };