@meistrari/chat-nuxt 1.7.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.
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -0
- package/dist/runtime/components/MeistrariChatEmbed.vue +5 -1
- package/dist/runtime/components/chat/bash-widget.vue +3 -3
- package/dist/runtime/components/chat/doc-sdk-widget.vue +3 -3
- package/dist/runtime/components/chat/files-view.vue +3 -64
- package/dist/runtime/components/chat/generic-tool-widget.vue +3 -3
- package/dist/runtime/components/chat/grep-widget.vue +3 -3
- package/dist/runtime/components/chat/message-bubble.vue +1 -1
- package/dist/runtime/components/chat/message-input.d.vue.ts +2 -0
- package/dist/runtime/components/chat/message-input.vue +21 -5
- package/dist/runtime/components/chat/message-input.vue.d.ts +2 -0
- package/dist/runtime/components/chat/message-list.vue +1 -1
- package/dist/runtime/components/chat/mobile/files/file-preview.d.vue.ts +11 -0
- package/dist/runtime/components/chat/mobile/files/file-preview.vue +112 -0
- package/dist/runtime/components/chat/mobile/files/file-preview.vue.d.ts +11 -0
- package/dist/runtime/components/chat/mobile/files/files-view.d.vue.ts +10 -0
- package/dist/runtime/components/chat/mobile/files/files-view.vue +384 -0
- package/dist/runtime/components/chat/mobile/files/files-view.vue.d.ts +10 -0
- package/dist/runtime/components/chat/mobile/home/header.d.vue.ts +19 -0
- package/dist/runtime/components/chat/mobile/home/header.vue +55 -0
- package/dist/runtime/components/chat/mobile/home/header.vue.d.ts +19 -0
- package/dist/runtime/components/chat/mobile/home/recents.d.vue.ts +14 -0
- package/dist/runtime/components/chat/mobile/home/recents.vue +48 -0
- package/dist/runtime/components/chat/mobile/home/recents.vue.d.ts +14 -0
- package/dist/runtime/components/chat/mobile/home/suggestions.d.vue.ts +7 -0
- package/dist/runtime/components/chat/mobile/home/suggestions.vue +31 -0
- package/dist/runtime/components/chat/mobile/home/suggestions.vue.d.ts +7 -0
- package/dist/runtime/components/chat/mobile/primitives/bottom-sheet.d.vue.ts +21 -0
- package/dist/runtime/components/chat/mobile/primitives/bottom-sheet.vue +48 -0
- package/dist/runtime/components/chat/mobile/primitives/bottom-sheet.vue.d.ts +21 -0
- package/dist/runtime/components/chat/mobile/primitives/info-bubble.d.vue.ts +6 -0
- package/dist/runtime/components/chat/mobile/primitives/info-bubble.vue +50 -0
- package/dist/runtime/components/chat/mobile/primitives/info-bubble.vue.d.ts +6 -0
- package/dist/runtime/components/chat/mobile/shell/drawer.d.vue.ts +22 -0
- package/dist/runtime/components/chat/mobile/shell/drawer.vue +143 -0
- package/dist/runtime/components/chat/mobile/shell/drawer.vue.d.ts +22 -0
- package/dist/runtime/components/chat/mobile/shell/header.d.vue.ts +40 -0
- package/dist/runtime/components/chat/mobile/shell/header.vue +139 -0
- package/dist/runtime/components/chat/mobile/shell/header.vue.d.ts +40 -0
- package/dist/runtime/components/chat/mobile/shell/outdated-settings-sheet.d.vue.ts +14 -0
- package/dist/runtime/components/chat/mobile/shell/outdated-settings-sheet.vue +47 -0
- package/dist/runtime/components/chat/mobile/shell/outdated-settings-sheet.vue.d.ts +14 -0
- package/dist/runtime/components/chat/mobile/shell/shell.d.vue.ts +122 -0
- package/dist/runtime/components/chat/mobile/shell/shell.vue +283 -0
- package/dist/runtime/components/chat/mobile/shell/shell.vue.d.ts +122 -0
- package/dist/runtime/components/chat/mobile/shell/workspace-sheet.d.vue.ts +19 -0
- package/dist/runtime/components/chat/mobile/shell/workspace-sheet.vue +51 -0
- package/dist/runtime/components/chat/mobile/shell/workspace-sheet.vue.d.ts +19 -0
- package/dist/runtime/components/chat/mobile/usage/usage-view.d.vue.ts +8 -0
- package/dist/runtime/components/chat/mobile/usage/usage-view.vue +243 -0
- package/dist/runtime/components/chat/mobile/usage/usage-view.vue.d.ts +8 -0
- package/dist/runtime/components/chat/model-selector.vue +6 -0
- package/dist/runtime/components/chat/read-widget.vue +3 -3
- package/dist/runtime/components/chat/reasoning-step-item.vue +1 -1
- package/dist/runtime/components/chat/reasoning-steps.vue +1 -1
- package/dist/runtime/components/chat/reasoning-thought.vue +2 -3
- package/dist/runtime/components/chat/tela-skill-widget.vue +3 -3
- package/dist/runtime/components/chat/thinking-widget.vue +3 -3
- package/dist/runtime/components/chat/usage-view.vue +20 -101
- package/dist/runtime/components/chat/web-search-widget.vue +3 -3
- package/dist/runtime/components/chat/workstation-query-widget.vue +6 -6
- package/dist/runtime/components/chat/write-widget.vue +3 -3
- package/dist/runtime/composables/useConversationFileDownloads.d.ts +6 -0
- package/dist/runtime/composables/useConversationFileDownloads.js +50 -0
- package/dist/runtime/composables/useConversationUsage.d.ts +13 -0
- package/dist/runtime/composables/useConversationUsage.js +121 -0
- package/dist/runtime/composables/useEmbedConfig.d.ts +2 -0
- package/dist/runtime/composables/useEmbedConfig.js +4 -0
- package/dist/runtime/composables/useMentionAutocomplete.d.ts +6 -0
- package/dist/runtime/composables/useMentionAutocomplete.js +71 -23
- package/dist/runtime/composables/useWorkspaceSwitchModal.d.ts +5 -0
- package/dist/runtime/composables/useWorkspaceSwitchModal.js +14 -0
- package/dist/runtime/embed/components/ChatConfigurationModal.vue +119 -13
- package/dist/runtime/embed/components/ChatEmbed.vue +4 -0
- package/dist/runtime/embed/components/ChatEmbedInner.d.vue.ts +2 -0
- package/dist/runtime/embed/components/ChatEmbedInner.vue +201 -11
- package/dist/runtime/embed/components/ChatEmbedInner.vue.d.ts +2 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationMobileShell.d.vue.ts +34 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationMobileShell.vue +95 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationMobileShell.vue.d.ts +34 -0
- package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +3 -1
- package/dist/runtime/server/api/conversations/[id]/messages/index.get.js +29 -5
- package/dist/runtime/server/utils/conversation-message-sync.d.ts +8 -2
- package/dist/runtime/server/utils/conversation-message-sync.js +52 -6
- package/dist/runtime/types/chat-auth.d.ts +1 -0
- package/dist/runtime/types/embed.d.ts +2 -0
- package/dist/runtime/utils/breakpoints.d.ts +1 -0
- package/dist/runtime/utils/breakpoints.js +1 -0
- package/package.json +1 -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
|
+
};
|