@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,243 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import ChatMobileInfoBubble from "../primitives/info-bubble.vue";
|
|
3
|
+
import { formatTokenCount } from "../../../../utils/calculate-usage";
|
|
4
|
+
import { formatDateWithTime } from "../../../../utils/format-time";
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
conversationId: { type: String, required: true },
|
|
7
|
+
createdAt: { type: [String, Date], required: false },
|
|
8
|
+
updatedAt: { type: [String, Date], required: false }
|
|
9
|
+
});
|
|
10
|
+
const conversationIdRef = computed(() => props.conversationId);
|
|
11
|
+
const {
|
|
12
|
+
usageData,
|
|
13
|
+
loading,
|
|
14
|
+
error,
|
|
15
|
+
cacheInsight,
|
|
16
|
+
costBreakdown,
|
|
17
|
+
modelRows,
|
|
18
|
+
costBarSegments,
|
|
19
|
+
formatBillingCost,
|
|
20
|
+
fetchUsage
|
|
21
|
+
} = useConversationUsage(conversationIdRef);
|
|
22
|
+
const COLOR_INPUT = "#6469E8";
|
|
23
|
+
const COLOR_OUTPUT = "#F5B86C";
|
|
24
|
+
const COLOR_REUSED = "#A57BE0";
|
|
25
|
+
const COLOR_NEW = "#7BD9A6";
|
|
26
|
+
const segmentColor = {
|
|
27
|
+
input: COLOR_INPUT,
|
|
28
|
+
output: COLOR_OUTPUT
|
|
29
|
+
};
|
|
30
|
+
const breakdownTooltip = {
|
|
31
|
+
input: "Custo dos tokens enviados (prompt + contexto).",
|
|
32
|
+
output: "Custo dos tokens gerados pelo modelo."
|
|
33
|
+
};
|
|
34
|
+
onMounted(fetchUsage);
|
|
35
|
+
watch(() => props.conversationId, fetchUsage);
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<div class="usage-screen" pt-54px h-full overflow-y-auto>
|
|
40
|
+
<div v-if="loading" flex="~ col" items-center justify-center gap-16px py-64px>
|
|
41
|
+
<span i-ph-spinner animate-spin text-32px text-neutral-400 />
|
|
42
|
+
<p text-14px text-neutral-500>
|
|
43
|
+
Carregando dados de uso...
|
|
44
|
+
</p>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div v-else-if="error" flex="~ col" items-center justify-center gap-16px py-64px px-24px>
|
|
48
|
+
<span i-ph-warning text-32px text-amber-500 />
|
|
49
|
+
<p text-14px text-neutral-500 text-center>
|
|
50
|
+
{{ error }}
|
|
51
|
+
</p>
|
|
52
|
+
<button
|
|
53
|
+
px-16px py-8px rounded-8px
|
|
54
|
+
bg-neutral-100 text-neutral-900 text-14px
|
|
55
|
+
@click="fetchUsage"
|
|
56
|
+
>
|
|
57
|
+
Tentar novamente
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div
|
|
62
|
+
v-else-if="!usageData"
|
|
63
|
+
flex="~ col" items-center justify-center
|
|
64
|
+
gap-14px px-28px py-40px text-center
|
|
65
|
+
>
|
|
66
|
+
<div
|
|
67
|
+
w-48px h-48px rounded-14px
|
|
68
|
+
bg-neutral-100 text-neutral-500
|
|
69
|
+
flex items-center justify-center
|
|
70
|
+
>
|
|
71
|
+
<span i-ph-chart-bar text-22px />
|
|
72
|
+
</div>
|
|
73
|
+
<div flex="~ col" gap-4px>
|
|
74
|
+
<p text-16px font-500 text-neutral-900 leading-20px tracking="-.2px">
|
|
75
|
+
Sem dados de uso
|
|
76
|
+
</p>
|
|
77
|
+
<p text-14px text-neutral-500 leading-18px max-w-240px>
|
|
78
|
+
Métricas de tokens e custo desta conversa aparecerão aqui.
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div v-else flex="~ col" gap-12px px-14px pt-12px pb-24px>
|
|
84
|
+
<!-- Hero card -->
|
|
85
|
+
<div class="card card--hero">
|
|
86
|
+
<div flex="~ col" gap-4px>
|
|
87
|
+
<span class="eyebrow">
|
|
88
|
+
Custo total
|
|
89
|
+
<ChatMobileInfoBubble text="Soma de todos os custos de processamento desta conversa." />
|
|
90
|
+
</span>
|
|
91
|
+
<span class="hero-number">
|
|
92
|
+
{{ formatBillingCost(usageData.cost.total) }}
|
|
93
|
+
</span>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="hero-divider" />
|
|
97
|
+
|
|
98
|
+
<div flex="~ col">
|
|
99
|
+
<div class="stat-row stat-row--bordered">
|
|
100
|
+
<span class="stat-label">
|
|
101
|
+
Tokens utilizados
|
|
102
|
+
<ChatMobileInfoBubble text="Total de tokens consumidos por todos os modelos." />
|
|
103
|
+
</span>
|
|
104
|
+
<span class="stat-value">
|
|
105
|
+
{{ formatTokenCount(usageData.tokens.total) }}
|
|
106
|
+
</span>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="stat-row stat-row--bordered">
|
|
109
|
+
<span class="stat-label">Data de criação</span>
|
|
110
|
+
<span class="stat-value">{{ formatDateWithTime(createdAt) }}</span>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="stat-row">
|
|
113
|
+
<span class="stat-label">Último uso</span>
|
|
114
|
+
<span class="stat-value">{{ formatDateWithTime(updatedAt) }}</span>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<!-- Cost breakdown card -->
|
|
120
|
+
<div class="card">
|
|
121
|
+
<span class="card-title">Detalhamento de custos</span>
|
|
122
|
+
|
|
123
|
+
<div flex="~ col" mt-12px>
|
|
124
|
+
<div
|
|
125
|
+
v-for="(item, idx) in costBreakdown"
|
|
126
|
+
:key="item.key"
|
|
127
|
+
class="breakdown-row"
|
|
128
|
+
:class="{ 'breakdown-row--bordered': idx < costBreakdown.length - 1 }"
|
|
129
|
+
>
|
|
130
|
+
<span class="dot" :style="{ background: segmentColor[item.key] }" />
|
|
131
|
+
<span class="stat-label">
|
|
132
|
+
{{ item.label }}
|
|
133
|
+
<ChatMobileInfoBubble :text="breakdownTooltip[item.key]" />
|
|
134
|
+
</span>
|
|
135
|
+
<span flex-1 />
|
|
136
|
+
<span class="breakdown-tokens">
|
|
137
|
+
{{ formatTokenCount(item.tokens) }} tokens
|
|
138
|
+
</span>
|
|
139
|
+
<span class="breakdown-cost">{{ formatBillingCost(item.cost) }}</span>
|
|
140
|
+
<span class="breakdown-pct">({{ item.percentage }}%)</span>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div v-if="costBarSegments.length > 0" mt-14px>
|
|
145
|
+
<div class="bar bar--track" aria-hidden="true">
|
|
146
|
+
<div
|
|
147
|
+
v-for="seg in costBarSegments"
|
|
148
|
+
:key="seg.key"
|
|
149
|
+
class="bar__fill"
|
|
150
|
+
:style="{ width: `${Math.max(seg.percentage, 1)}%`, background: segmentColor[seg.key] }"
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div flex items-center gap-14px mt-10px>
|
|
155
|
+
<div
|
|
156
|
+
v-for="seg in costBarSegments"
|
|
157
|
+
:key="seg.key"
|
|
158
|
+
flex items-center gap-4px
|
|
159
|
+
>
|
|
160
|
+
<span class="dot dot--sm" :style="{ background: segmentColor[seg.key] }" />
|
|
161
|
+
<span class="legend-label">{{ seg.label }}</span>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<!-- Context reuse card -->
|
|
168
|
+
<div v-if="cacheInsight.hasData" class="card">
|
|
169
|
+
<div flex items-baseline flex-wrap gap-8px>
|
|
170
|
+
<span class="reuse-number">{{ cacheInsight.percentage }}%</span>
|
|
171
|
+
<span class="reuse-tail">do contexto foi reutilizado</span>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<p class="reuse-caption">
|
|
175
|
+
Contexto reutilizado reduz o custo de processamento.
|
|
176
|
+
</p>
|
|
177
|
+
|
|
178
|
+
<div mt-14px>
|
|
179
|
+
<div class="bar bar--track" aria-hidden="true">
|
|
180
|
+
<div
|
|
181
|
+
class="bar__fill"
|
|
182
|
+
:style="{ width: `${cacheInsight.percentage}%`, background: COLOR_REUSED }"
|
|
183
|
+
/>
|
|
184
|
+
<div
|
|
185
|
+
class="bar__fill"
|
|
186
|
+
:style="{ width: `${100 - cacheInsight.percentage}%`, background: COLOR_NEW }"
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<div flex items-center justify-between mt-10px gap-8px>
|
|
192
|
+
<div flex items-center gap-4px>
|
|
193
|
+
<span class="dot dot--sm" :style="{ background: COLOR_REUSED }" />
|
|
194
|
+
<span class="reuse-token-bold">{{ formatTokenCount(cacheInsight.reusedTokens) }}</span>
|
|
195
|
+
<span class="legend-label">reutilizado</span>
|
|
196
|
+
</div>
|
|
197
|
+
<div flex items-center gap-4px>
|
|
198
|
+
<span class="dot dot--sm" :style="{ background: COLOR_NEW }" />
|
|
199
|
+
<span class="reuse-token-bold">{{ formatTokenCount(cacheInsight.storedTokens) }}</span>
|
|
200
|
+
<span class="legend-label">novo</span>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<!-- Models section -->
|
|
206
|
+
<div v-if="modelRows.length > 0" flex="~ col" gap-8px mt-4px>
|
|
207
|
+
<div class="models-header">
|
|
208
|
+
<span class="card-title">Modelos</span>
|
|
209
|
+
<ChatMobileInfoBubble text="Modelos auxiliares podem ser utilizados internamente para auxiliar o processamento." />
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<div
|
|
213
|
+
v-for="row in modelRows"
|
|
214
|
+
:key="row.modelId + row.tokens"
|
|
215
|
+
class="card card--model"
|
|
216
|
+
>
|
|
217
|
+
<div flex items-center gap-10px>
|
|
218
|
+
<div class="provider-badge">
|
|
219
|
+
<IconsAnthropic w-14px h-14px text-current />
|
|
220
|
+
</div>
|
|
221
|
+
<span class="model-name">{{ row.modelName }}</span>
|
|
222
|
+
<span class="model-cost">{{ row.cost }}</span>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<div flex items-center gap-8px mt-10px>
|
|
226
|
+
<span class="model-tokens">{{ row.tokens }} tokens</span>
|
|
227
|
+
<div class="bar bar--track bar--thin" flex-1 aria-hidden="true">
|
|
228
|
+
<div
|
|
229
|
+
class="bar__fill"
|
|
230
|
+
:style="{ width: `${Math.max(row.costShare, 1)}%`, background: COLOR_INPUT }"
|
|
231
|
+
/>
|
|
232
|
+
</div>
|
|
233
|
+
<span class="model-pct">{{ Math.round(row.costShare) }}%</span>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
</template>
|
|
240
|
+
|
|
241
|
+
<style scoped>
|
|
242
|
+
.usage-screen{background:#fafafa;font-variant-numeric:tabular-nums}.card{background:#fff;border:.5px solid #e8e8e8;border-radius:12px;box-shadow:0 1px 4px rgba(103,127,148,.03);padding:16px}.card--hero{padding:18px 16px}.card--model{padding:14px}.eyebrow{align-items:center;color:#737373;display:inline-flex;font-size:12px;font-weight:500;gap:4px;letter-spacing:.1px;white-space:nowrap}.hero-number{color:#171717;font-size:40px;font-variant-numeric:tabular-nums;font-weight:580;letter-spacing:-.8px;line-height:1.05;margin-top:4px}.hero-divider{background:#f5f5f5;height:1px;margin:14px -16px 12px}.stat-row{align-items:center;display:flex;gap:8px;justify-content:space-between;padding:8px 0}.stat-row--bordered{border-bottom:.5px solid #f5f5f5}.stat-label{align-items:center;color:#737373;display:inline-flex;font-size:13px;font-weight:460}.stat-value{color:#171717;font-size:13px;font-variant-numeric:tabular-nums;font-weight:500;text-align:right}.card-title{color:#171717;font-size:14px;font-weight:580}.breakdown-row{align-items:center;display:flex;font-size:13px;gap:8px;padding:6px 0}.breakdown-row--bordered{border-bottom:.5px solid #f5f5f5}.dot{border-radius:999px;display:inline-block;flex-shrink:0;height:8px;width:8px}.dot--sm{height:7px;width:7px}.breakdown-tokens{color:#737373;font-size:12px;font-variant-numeric:tabular-nums;white-space:nowrap}.breakdown-cost{color:#171717;font-variant-numeric:tabular-nums;font-weight:500;min-width:52px;text-align:right}.breakdown-pct{color:#a3a3a3;font-size:11px;font-variant-numeric:tabular-nums;min-width:28px;text-align:right}.bar{display:flex;overflow:hidden}.bar--track{background:#f5f5f5;border-radius:999px;height:8px}.bar--thin{height:4px}.bar__fill{border-radius:999px;height:100%;transition:width .2s ease}.legend-label{color:#737373;font-size:11.5px}.reuse-number{color:#171717;font-size:32px;font-variant-numeric:tabular-nums;font-weight:580;letter-spacing:-.5px;line-height:1}.reuse-tail{color:#737373;font-size:14px;font-weight:460}.reuse-caption{color:#a3a3a3;font-size:12.5px;line-height:1.45;margin-top:6px}.reuse-token-bold{color:#171717;font-size:11.5px;font-variant-numeric:tabular-nums;font-weight:500}.models-header{align-items:center;display:flex;gap:6px;padding:0 4px}.provider-badge{align-items:center;background:#f5f5f5;border-radius:8px;color:#c2410c;display:inline-flex;flex-shrink:0;height:28px;justify-content:center;width:28px}.model-name{flex:1;font-size:13.5px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.model-cost,.model-name{color:#171717;font-weight:500}.model-cost{font-size:13px;font-variant-numeric:tabular-nums}.model-tokens{color:#737373;white-space:nowrap}.model-pct,.model-tokens{font-size:12px;font-variant-numeric:tabular-nums}.model-pct{color:#a3a3a3;min-width:30px;text-align:right}
|
|
243
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
conversationId: string;
|
|
3
|
+
createdAt?: string | Date;
|
|
4
|
+
updatedAt?: string | Date;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { useBreakpoints } from "@vueuse/core";
|
|
2
3
|
import { AVAILABLE_MODELS, DEFAULT_MODEL } from "../../types/chat";
|
|
3
4
|
import AnthropicLogo from "../../components/icons/anthropic.vue";
|
|
5
|
+
import { CHAT_MOBILE_BREAKPOINT } from "../../utils/breakpoints";
|
|
4
6
|
defineProps({
|
|
5
7
|
disabled: { type: Boolean, required: false }
|
|
6
8
|
});
|
|
@@ -11,6 +13,9 @@ const options = AVAILABLE_MODELS.map((m) => ({
|
|
|
11
13
|
description: m.description,
|
|
12
14
|
icon: AnthropicLogo
|
|
13
15
|
}));
|
|
16
|
+
const breakpoints = useBreakpoints({ mobile: CHAT_MOBILE_BREAKPOINT });
|
|
17
|
+
const isMobile = breakpoints.smaller("mobile");
|
|
18
|
+
const contentClass = computed(() => isMobile.value ? "w-280px!" : "");
|
|
14
19
|
</script>
|
|
15
20
|
|
|
16
21
|
<template>
|
|
@@ -24,6 +29,7 @@ const options = AVAILABLE_MODELS.map((m) => ({
|
|
|
24
29
|
:has-searchbar="false"
|
|
25
30
|
:has-grouping-labels="false"
|
|
26
31
|
:trigger-class="`border-none! ${disabled ? 'bg-[#F3F4F6]!' : ''}`"
|
|
32
|
+
:content-class="contentClass"
|
|
27
33
|
label-class="!text-14px !leading-18px !font-normal -tracking-0.01em! !text-gray-500"
|
|
28
34
|
icon-class="w-16px h-16px scale-110 mr-1px !text-gray-500"
|
|
29
35
|
label-item-class="!text-gray-500"
|
|
@@ -113,9 +113,9 @@ const isContentCode = computed(() => {
|
|
|
113
113
|
<template>
|
|
114
114
|
<div class="bg-subtle rounded-16px border border-gray-200">
|
|
115
115
|
<!-- Header -->
|
|
116
|
-
<div class="px-
|
|
116
|
+
<div class="px-16px py-16px flex items-center justify-between">
|
|
117
117
|
<div class="flex-1 min-w-0">
|
|
118
|
-
<h3 class="text-
|
|
118
|
+
<h3 class="text-16px font-580 leading-20px tracking-[-0.3px] text-primary truncate">
|
|
119
119
|
Read
|
|
120
120
|
</h3>
|
|
121
121
|
<p class="text-12px text-secondary truncate mt-2px">
|
|
@@ -127,7 +127,7 @@ const isContentCode = computed(() => {
|
|
|
127
127
|
</div>
|
|
128
128
|
|
|
129
129
|
<!-- Content -->
|
|
130
|
-
<div v-if="toolResult" class="px-
|
|
130
|
+
<div v-if="toolResult" class="px-16px pb-16px space-y-16px">
|
|
131
131
|
<!-- File path section -->
|
|
132
132
|
<div>
|
|
133
133
|
<div class="text-14px font-580 leading-16px tracking-[-0.15px] text-gray-700 mb-2">
|
|
@@ -28,7 +28,7 @@ function toggle() {
|
|
|
28
28
|
<div class="relative flex gap-12px group/step">
|
|
29
29
|
<!-- Status Icon -->
|
|
30
30
|
<div class="flex flex-col items-center flex-shrink-0" :class="{ 'pb-8px': !isLast || !completed }">
|
|
31
|
-
<div class="
|
|
31
|
+
<div class="flex items-center justify-center w-24px h-24px group-hover/step:text-gray-700 transition-colors">
|
|
32
32
|
<!-- COMPLETED STATE: Show specific type icons -->
|
|
33
33
|
<template v-if="completed">
|
|
34
34
|
<!-- Show error icon if there's an error -->
|
|
@@ -24,7 +24,7 @@ defineExpose({
|
|
|
24
24
|
</script>
|
|
25
25
|
|
|
26
26
|
<template>
|
|
27
|
-
<div v-if="groupedReasoning.length > 0" flex="~ col" w-full p-
|
|
27
|
+
<div v-if="groupedReasoning.length > 0" flex="~ col" w-full p-12px>
|
|
28
28
|
<ChatReasoningMessage
|
|
29
29
|
v-for="(step, index) in groupedReasoning"
|
|
30
30
|
:key="getStepKey(step, index)"
|
|
@@ -176,7 +176,7 @@ onUnmounted(() => {
|
|
|
176
176
|
flex
|
|
177
177
|
items-center
|
|
178
178
|
gap-4px
|
|
179
|
-
px-
|
|
179
|
+
px-4px
|
|
180
180
|
pt-16px
|
|
181
181
|
pb-8px
|
|
182
182
|
w-full
|
|
@@ -237,7 +237,7 @@ onUnmounted(() => {
|
|
|
237
237
|
<div
|
|
238
238
|
v-if="!completed && !isExpanded"
|
|
239
239
|
border="~ 0.5 default rounded-16px"
|
|
240
|
-
|
|
240
|
+
mt-8px mb-16px
|
|
241
241
|
cursor-pointer
|
|
242
242
|
@click="toggleExpanded"
|
|
243
243
|
>
|
|
@@ -268,7 +268,6 @@ onUnmounted(() => {
|
|
|
268
268
|
v-if="isExpanded"
|
|
269
269
|
overflow-hidden
|
|
270
270
|
border="~ 0.5 default rounded-16px"
|
|
271
|
-
mx-16px
|
|
272
271
|
mb-16px
|
|
273
272
|
>
|
|
274
273
|
<ChatReasoningSteps
|
|
@@ -39,15 +39,15 @@ const argsValue = computed(() => {
|
|
|
39
39
|
<template>
|
|
40
40
|
<div class="bg-subtle rounded-16px border border-gray-200">
|
|
41
41
|
<!-- Header -->
|
|
42
|
-
<div class="px-
|
|
43
|
-
<h3 class="text-
|
|
42
|
+
<div class="px-16px py-16px flex items-center justify-between">
|
|
43
|
+
<h3 class="text-16px font-580 leading-20px tracking-[-0.3px] text-primary truncate">
|
|
44
44
|
{{ skillName }}
|
|
45
45
|
</h3>
|
|
46
46
|
<TelaStatus :variant="statusVariant" :label="statusLabel" />
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
49
|
<!-- Content -->
|
|
50
|
-
<div v-if="toolResult" class="px-
|
|
50
|
+
<div v-if="toolResult" class="px-16px pb-16px space-y-16px">
|
|
51
51
|
<!-- Arguments section -->
|
|
52
52
|
<div v-if="hasArgs">
|
|
53
53
|
<div class="text-14px font-580 leading-16px tracking-[-0.15px] text-gray-700 mb-2">
|
|
@@ -24,15 +24,15 @@ const title = computed(() => {
|
|
|
24
24
|
<template>
|
|
25
25
|
<div class="bg-subtle rounded-16px border border-gray-200">
|
|
26
26
|
<!-- Header -->
|
|
27
|
-
<div class="px-
|
|
28
|
-
<h3 class="text-
|
|
27
|
+
<div class="px-16px py-16px flex items-center justify-between">
|
|
28
|
+
<h3 class="text-16px font-580 leading-20px tracking-[-0.3px] text-primary truncate">
|
|
29
29
|
{{ title }}
|
|
30
30
|
</h3>
|
|
31
31
|
<TelaStatus :variant="statusVariant" :label="statusLabel" />
|
|
32
32
|
</div>
|
|
33
33
|
|
|
34
34
|
<!-- Content -->
|
|
35
|
-
<div class="px-
|
|
35
|
+
<div class="px-16px pb-16px">
|
|
36
36
|
<div class="bg-white rounded-xl border-0.5px border-gray-200 px-3 py-2">
|
|
37
37
|
<div class="reasoning-markdown text-14px text-primary">
|
|
38
38
|
<ClientOnly>
|
|
@@ -1,111 +1,30 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { formatTokenCount
|
|
2
|
+
import { formatTokenCount } from "../../utils/calculate-usage";
|
|
3
3
|
import { formatDateWithTime } from "../../utils/format-time";
|
|
4
4
|
const props = defineProps({
|
|
5
5
|
conversationId: { type: String, required: true },
|
|
6
6
|
createdAt: { type: [String, Date], required: false },
|
|
7
7
|
updatedAt: { type: [String, Date], required: false }
|
|
8
8
|
});
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const response = await $fetch(
|
|
27
|
-
chatApi.path(`/conversations/${props.conversationId}/usage`),
|
|
28
|
-
chatApi.withChatHeaders()
|
|
29
|
-
);
|
|
30
|
-
if (response.error) {
|
|
31
|
-
error.value = response.error;
|
|
32
|
-
} else {
|
|
33
|
-
usageData.value = response.usage;
|
|
34
|
-
billing.value = response.billing;
|
|
35
|
-
}
|
|
36
|
-
} catch (err) {
|
|
37
|
-
console.error("Failed to fetch usage data:", err);
|
|
38
|
-
error.value = "Falha ao carregar dados de uso";
|
|
39
|
-
} finally {
|
|
40
|
-
loading.value = false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
9
|
+
const conversationIdRef = computed(() => props.conversationId);
|
|
10
|
+
const {
|
|
11
|
+
usageData,
|
|
12
|
+
billing,
|
|
13
|
+
loading,
|
|
14
|
+
error,
|
|
15
|
+
cacheInsight,
|
|
16
|
+
costBreakdown,
|
|
17
|
+
modelRows,
|
|
18
|
+
costBarSegments,
|
|
19
|
+
formatBillingCost,
|
|
20
|
+
fetchUsage
|
|
21
|
+
} = useConversationUsage(conversationIdRef);
|
|
22
|
+
const segmentColorClass = {
|
|
23
|
+
input: "bg-indigo-400",
|
|
24
|
+
output: "bg-amber-400"
|
|
25
|
+
};
|
|
43
26
|
onMounted(fetchUsage);
|
|
44
27
|
watch(() => props.conversationId, fetchUsage);
|
|
45
|
-
const cacheInsight = computed(() => {
|
|
46
|
-
if (!usageData.value)
|
|
47
|
-
return { percentage: 0, reusedTokens: 0, storedTokens: 0, hasData: false };
|
|
48
|
-
const hasData = usageData.value.tokens.cacheRead > 0 || usageData.value.tokens.cacheCreation > 0;
|
|
49
|
-
return {
|
|
50
|
-
percentage: Math.round(usageData.value.metrics.cacheHitRate * 100),
|
|
51
|
-
reusedTokens: usageData.value.tokens.cacheRead,
|
|
52
|
-
storedTokens: usageData.value.tokens.cacheCreation,
|
|
53
|
-
hasData
|
|
54
|
-
};
|
|
55
|
-
});
|
|
56
|
-
const costBreakdown = computed(() => {
|
|
57
|
-
if (!usageData.value)
|
|
58
|
-
return [];
|
|
59
|
-
const total = usageData.value.cost.total;
|
|
60
|
-
return [
|
|
61
|
-
{
|
|
62
|
-
label: "Envio",
|
|
63
|
-
tooltip: "Texto enviado ao modelo: perguntas, contexto e instru\xE7\xF5es",
|
|
64
|
-
tokens: usageData.value.tokens.prompt,
|
|
65
|
-
cost: usageData.value.cost.prompt,
|
|
66
|
-
percentage: total > 0 ? Math.round(usageData.value.cost.prompt / total * 100) : 0
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
label: "Respostas",
|
|
70
|
-
tooltip: "Texto gerado pelo modelo",
|
|
71
|
-
tokens: usageData.value.tokens.completion,
|
|
72
|
-
cost: usageData.value.cost.completion,
|
|
73
|
-
percentage: total > 0 ? Math.round(usageData.value.cost.completion / total * 100) : 0
|
|
74
|
-
}
|
|
75
|
-
];
|
|
76
|
-
});
|
|
77
|
-
const modelRows = computed(() => {
|
|
78
|
-
if (!usageData.value)
|
|
79
|
-
return [];
|
|
80
|
-
const totalCost = usageData.value.cost.total;
|
|
81
|
-
return usageData.value.models.map((m) => {
|
|
82
|
-
const totalTokens = m.inputTokens + m.outputTokens + m.cacheReadTokens + m.cacheCreationTokens;
|
|
83
|
-
return {
|
|
84
|
-
modelId: m.model,
|
|
85
|
-
modelName: formatModelName(m.model),
|
|
86
|
-
tokens: formatTokenCount(totalTokens),
|
|
87
|
-
cost: formatBillingCost(m.cost),
|
|
88
|
-
costShare: totalCost > 0 ? m.cost / totalCost * 100 : 0
|
|
89
|
-
};
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
const costBarSegments = computed(() => {
|
|
93
|
-
if (!usageData.value || usageData.value.cost.total === 0)
|
|
94
|
-
return [];
|
|
95
|
-
const total = usageData.value.cost.total;
|
|
96
|
-
return [
|
|
97
|
-
{
|
|
98
|
-
label: "Envio",
|
|
99
|
-
percentage: usageData.value.cost.prompt / total * 100,
|
|
100
|
-
colorClass: "bg-indigo-400"
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
label: "Respostas",
|
|
104
|
-
percentage: usageData.value.cost.completion / total * 100,
|
|
105
|
-
colorClass: "bg-amber-400"
|
|
106
|
-
}
|
|
107
|
-
];
|
|
108
|
-
});
|
|
109
28
|
</script>
|
|
110
29
|
|
|
111
30
|
<template>
|
|
@@ -216,7 +135,7 @@ const costBarSegments = computed(() => {
|
|
|
216
135
|
<div
|
|
217
136
|
v-for="seg in costBarSegments"
|
|
218
137
|
:key="seg.label"
|
|
219
|
-
:class="seg.
|
|
138
|
+
:class="segmentColorClass[seg.key]"
|
|
220
139
|
:style="{ width: `${Math.max(seg.percentage, 1)}%` }"
|
|
221
140
|
h-full transition-all duration-300
|
|
222
141
|
/>
|
|
@@ -227,7 +146,7 @@ const costBarSegments = computed(() => {
|
|
|
227
146
|
:key="seg.label"
|
|
228
147
|
flex gap-4px items-center
|
|
229
148
|
>
|
|
230
|
-
<div w-6px h-6px rounded-full :class="seg.
|
|
149
|
+
<div w-6px h-6px rounded-full :class="segmentColorClass[seg.key]" />
|
|
231
150
|
<span body-11-regular text-tertiary>{{ seg.label }}</span>
|
|
232
151
|
</div>
|
|
233
152
|
</div>
|
|
@@ -44,15 +44,15 @@ const sources = computed(() => {
|
|
|
44
44
|
<template>
|
|
45
45
|
<div class="bg-subtle rounded-16px border border-gray-200">
|
|
46
46
|
<!-- Header -->
|
|
47
|
-
<div class="px-
|
|
48
|
-
<h3 class="text-
|
|
47
|
+
<div class="px-16px py-16px flex items-center justify-between">
|
|
48
|
+
<h3 class="text-16px font-580 leading-20px tracking-[-0.3px] text-primary truncate">
|
|
49
49
|
WebSearch
|
|
50
50
|
</h3>
|
|
51
51
|
<TelaStatus :variant="statusVariant" :label="statusLabel" />
|
|
52
52
|
</div>
|
|
53
53
|
|
|
54
54
|
<!-- Content -->
|
|
55
|
-
<div v-if="toolResult" class="px-
|
|
55
|
+
<div v-if="toolResult" class="px-16px pb-16px space-y-16px">
|
|
56
56
|
<!-- Sources list -->
|
|
57
57
|
<div v-if="sources.length > 0">
|
|
58
58
|
<div class="text-14px font-580 leading-16px tracking-[-0.15px] text-gray-700 mb-2">
|
|
@@ -336,9 +336,9 @@ const displayName = computed(() => {
|
|
|
336
336
|
<template>
|
|
337
337
|
<div class="bg-subtle rounded-16px border border-gray-200">
|
|
338
338
|
<!-- Header -->
|
|
339
|
-
<div class="px-
|
|
339
|
+
<div class="px-16px py-16px flex items-center justify-between">
|
|
340
340
|
<div class="flex-1 min-w-0">
|
|
341
|
-
<h3 class="text-
|
|
341
|
+
<h3 class="text-16px font-580 leading-20px tracking-[-0.3px] text-primary truncate">
|
|
342
342
|
Consulta de Dados
|
|
343
343
|
</h3>
|
|
344
344
|
<p v-if="displayName !== 'Workstation'" class="text-12px text-secondary truncate mt-2px">
|
|
@@ -355,7 +355,7 @@ const displayName = computed(() => {
|
|
|
355
355
|
<!-- Filters preview (when collapsed) -->
|
|
356
356
|
<div
|
|
357
357
|
v-if="!expanded && canExpand && (formattedFilters || formattedOutputQuery)"
|
|
358
|
-
class="px-
|
|
358
|
+
class="px-16px pb-12px"
|
|
359
359
|
>
|
|
360
360
|
<div class="flex flex-wrap items-center gap-6px">
|
|
361
361
|
<template v-if="formattedFilters">
|
|
@@ -386,7 +386,7 @@ const displayName = computed(() => {
|
|
|
386
386
|
</div>
|
|
387
387
|
|
|
388
388
|
<!-- Launch message (when only have launching output) -->
|
|
389
|
-
<div v-if="toolResult && isLaunchMessage" class="px-
|
|
389
|
+
<div v-if="toolResult && isLaunchMessage" class="px-16px pb-16px">
|
|
390
390
|
<div class="bg-blue-50 rounded-xl border-0.5px border-blue-200 px-16px py-12px">
|
|
391
391
|
<div class="flex items-center gap-8px">
|
|
392
392
|
<div class="i-ph-clock text-16px text-blue-600 animate-pulse" />
|
|
@@ -398,7 +398,7 @@ const displayName = computed(() => {
|
|
|
398
398
|
</div>
|
|
399
399
|
|
|
400
400
|
<!-- Expand/Collapse button -->
|
|
401
|
-
<div v-if="toolResult && !isLaunchMessage" class="px-
|
|
401
|
+
<div v-if="toolResult && !isLaunchMessage" class="px-16px pb-12px">
|
|
402
402
|
<button
|
|
403
403
|
type="button"
|
|
404
404
|
class="w-full flex items-center justify-center gap-6px px-12px py-8px text-13px font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-50 rounded-8px transition-colors"
|
|
@@ -413,7 +413,7 @@ const displayName = computed(() => {
|
|
|
413
413
|
</div>
|
|
414
414
|
|
|
415
415
|
<!-- Expanded content -->
|
|
416
|
-
<div v-if="expanded && toolResult && !isLaunchMessage" class="px-
|
|
416
|
+
<div v-if="expanded && toolResult && !isLaunchMessage" class="px-16px pb-16px space-y-16px">
|
|
417
417
|
<!-- Filters section -->
|
|
418
418
|
<div v-if="formattedFilters || formattedOutputQuery">
|
|
419
419
|
<div class="text-14px font-580 leading-16px tracking-[-0.15px] text-gray-700 mb-2">
|
|
@@ -92,9 +92,9 @@ const hasMoreLines = computed(() => {
|
|
|
92
92
|
<template>
|
|
93
93
|
<div class="bg-subtle rounded-16px border border-gray-200">
|
|
94
94
|
<!-- Header -->
|
|
95
|
-
<div class="px-
|
|
95
|
+
<div class="px-16px py-16px flex items-center justify-between">
|
|
96
96
|
<div class="flex-1 min-w-0">
|
|
97
|
-
<h3 class="text-
|
|
97
|
+
<h3 class="text-16px font-580 leading-20px tracking-[-0.3px] text-primary truncate">
|
|
98
98
|
Write
|
|
99
99
|
</h3>
|
|
100
100
|
<p class="text-12px text-secondary truncate mt-2px">
|
|
@@ -106,7 +106,7 @@ const hasMoreLines = computed(() => {
|
|
|
106
106
|
</div>
|
|
107
107
|
|
|
108
108
|
<!-- Content -->
|
|
109
|
-
<div v-if="toolResult" class="px-
|
|
109
|
+
<div v-if="toolResult" class="px-16px pb-16px space-y-16px">
|
|
110
110
|
<!-- File path section -->
|
|
111
111
|
<div>
|
|
112
112
|
<div class="text-14px font-580 leading-16px tracking-[-0.15px] text-gray-700 mb-2">
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ConversationFile } from '../types/chat.js';
|
|
2
|
+
export declare function useConversationFileDownloads(): {
|
|
3
|
+
resolveDownloadUrl: (file: ConversationFile) => Promise<string>;
|
|
4
|
+
downloadFile: (file: ConversationFile) => Promise<void>;
|
|
5
|
+
copyFileLink: (file: ConversationFile) => Promise<void>;
|
|
6
|
+
};
|