@meistrari/chat-nuxt 3.9.1 → 3.10.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/README.md +50 -440
- package/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +56 -3
- package/dist/runtime/assets/css/markstream.css +1 -1
- package/dist/runtime/conversations/components/chat-conversation-creator-filter.vue +11 -67
- package/dist/runtime/conversations/components/chat-conversation-item.d.vue.ts +2 -1
- package/dist/runtime/conversations/components/chat-conversation-item.vue +29 -33
- package/dist/runtime/conversations/components/chat-conversation-item.vue.d.ts +2 -1
- package/dist/runtime/conversations/components/chat-conversation-list.d.vue.ts +0 -1
- package/dist/runtime/conversations/components/chat-conversation-list.vue +117 -158
- package/dist/runtime/conversations/components/chat-conversation-list.vue.d.ts +0 -1
- package/dist/runtime/conversations/components/chat-conversation-new-group-button.vue +34 -73
- package/dist/runtime/conversations/components/chat-conversation-search.vue +1 -21
- package/dist/runtime/conversations/components/chat-widget-history-list.d.vue.ts +12 -0
- package/dist/runtime/conversations/components/chat-widget-history-list.vue +27 -0
- package/dist/runtime/conversations/components/chat-widget-history-list.vue.d.ts +12 -0
- package/dist/runtime/conversations/composables/conversation-groups.d.ts +1 -0
- package/dist/runtime/conversations/composables/conversation-groups.js +2 -0
- package/dist/runtime/conversations/composables/conversation-item.js +4 -4
- package/dist/runtime/conversations/composables/conversation-list.js +14 -5
- package/dist/runtime/embed/components/chat-configuration-modal.d.vue.ts +3 -3
- package/dist/runtime/embed/components/chat-configuration-modal.vue.d.ts +3 -3
- package/dist/runtime/embed/components/chat-embed-inner.d.vue.ts +6 -3
- package/dist/runtime/embed/components/chat-embed-inner.vue +365 -152
- package/dist/runtime/embed/components/chat-embed-inner.vue.d.ts +6 -3
- package/dist/runtime/embed/components/chat-embed.d.vue.ts +31 -4
- package/dist/runtime/embed/components/chat-embed.vue +10 -4
- package/dist/runtime/embed/components/chat-embed.vue.d.ts +31 -4
- package/dist/runtime/embed/components/chat-topbar.vue +83 -85
- package/dist/runtime/embed/components/meistrari-chat-embed.d.vue.ts +5 -3
- package/dist/runtime/embed/components/meistrari-chat-embed.vue +118 -57
- package/dist/runtime/embed/components/meistrari-chat-embed.vue.d.ts +5 -3
- package/dist/runtime/embed/components/mobile/chat-mobile-drawer.d.vue.ts +0 -1
- package/dist/runtime/embed/components/mobile/chat-mobile-drawer.vue +2 -45
- package/dist/runtime/embed/components/mobile/chat-mobile-drawer.vue.d.ts +0 -1
- package/dist/runtime/embed/components/mobile/chat-mobile-home-suggestions.d.vue.ts +9 -5
- package/dist/runtime/embed/components/mobile/chat-mobile-home-suggestions.vue +14 -25
- package/dist/runtime/embed/components/mobile/chat-mobile-home-suggestions.vue.d.ts +9 -5
- package/dist/runtime/embed/components/mobile/chat-mobile-shell.d.vue.ts +6 -6
- package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue +2 -3
- package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue.d.ts +6 -6
- package/dist/runtime/embed/feature-config.d.ts +7 -0
- package/dist/runtime/embed/types.d.ts +47 -8
- package/dist/runtime/files/icons.d.ts +1 -1
- package/dist/runtime/files/icons.js +1 -1
- package/dist/runtime/messages/components/chat-mention-dropdown.d.vue.ts +6 -1
- package/dist/runtime/messages/components/chat-mention-dropdown.vue +46 -22
- package/dist/runtime/messages/components/chat-mention-dropdown.vue.d.ts +6 -1
- package/dist/runtime/messages/components/chat-message-bubble.vue +53 -71
- package/dist/runtime/messages/components/chat-message-feedback.vue +25 -229
- package/dist/runtime/messages/components/chat-message-input.vue +176 -134
- package/dist/runtime/messages/components/chat-message-list.d.vue.ts +6 -0
- package/dist/runtime/messages/components/chat-message-list.vue +27 -23
- package/dist/runtime/messages/components/chat-message-list.vue.d.ts +6 -0
- package/dist/runtime/messages/components/chat-model-selector.vue +10 -22
- package/dist/runtime/messages/components/reasoning/chat-reasoning-message.vue +2 -3
- package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue +60 -69
- package/dist/runtime/messages/components/reasoning/chat-thinking-indicator.vue +3 -9
- package/dist/runtime/messages/components/widgets/chat-bash-widget.vue +3 -12
- package/dist/runtime/messages/components/widgets/chat-doc-sdk-widget.vue +3 -12
- package/dist/runtime/messages/components/widgets/chat-generic-tool-widget.vue +3 -12
- package/dist/runtime/messages/components/widgets/chat-grep-widget.vue +7 -17
- package/dist/runtime/messages/components/widgets/chat-read-widget.vue +7 -17
- package/dist/runtime/messages/components/widgets/chat-tela-skill-widget.vue +4 -13
- package/dist/runtime/messages/components/widgets/chat-thinking-widget.vue +11 -22
- package/dist/runtime/messages/components/widgets/chat-web-search-widget.vue +13 -40
- package/dist/runtime/messages/components/widgets/chat-workstation-query-widget.vue +52 -65
- package/dist/runtime/messages/components/widgets/chat-write-widget.vue +8 -18
- package/dist/runtime/messages/composables/mention-autocomplete.js +4 -0
- package/dist/runtime/messages/message-reasoning.js +4 -0
- package/dist/runtime/messages/message-state.js +1 -0
- package/dist/runtime/public/files/generic.svg +2 -5
- package/dist/runtime/public/files/image.svg +2 -1
- package/dist/runtime/types/embed.d.ts +1 -1
- package/dist/runtime/types/schemas/chat/conversations.d.ts +2 -0
- package/dist/runtime/types/schemas/chat/conversations.js +3 -0
- package/dist/runtime/types/schemas/chat/messages.d.ts +4 -0
- package/dist/runtime/types/schemas/chat/messages.js +7 -0
- package/dist/types.d.mts +1 -1
- package/package.json +5 -4
- package/dist/runtime/conversations/components/chat-conversation-new-group-popover-content.d.vue.ts +0 -23
- package/dist/runtime/conversations/components/chat-conversation-new-group-popover-content.vue +0 -75
- package/dist/runtime/conversations/components/chat-conversation-new-group-popover-content.vue.d.ts +0 -23
- package/dist/runtime/messages/components/reasoning/chat-reasoning-step-item.d.vue.ts +0 -37
- package/dist/runtime/messages/components/reasoning/chat-reasoning-step-item.vue +0 -125
- package/dist/runtime/messages/components/reasoning/chat-reasoning-step-item.vue.d.ts +0 -37
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const FILE_ICONS: Record<string, string>;
|
|
2
|
-
export declare function getFileIcon(mimeType
|
|
2
|
+
export declare function getFileIcon(mimeType?: string): string;
|
|
3
3
|
export declare function getMimeIcon(mimeType?: string): string;
|
|
4
4
|
export declare function getMimeLabel(mimeType?: string): string;
|
|
@@ -42,7 +42,7 @@ export const FILE_ICONS = {
|
|
|
42
42
|
"unknown": "/chat-files/generic.svg"
|
|
43
43
|
};
|
|
44
44
|
export function getFileIcon(mimeType) {
|
|
45
|
-
return FILE_ICONS[mimeType] ?? "/chat-files/generic.svg";
|
|
45
|
+
return FILE_ICONS[mimeType ?? "unknown"] ?? "/chat-files/generic.svg";
|
|
46
46
|
}
|
|
47
47
|
export function getMimeIcon(mimeType) {
|
|
48
48
|
if (!mimeType)
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { MentionItem } from '../../types/schemas/chat/ui.js';
|
|
2
|
+
type MentionDropdownItem = MentionItem & {
|
|
3
|
+
iconUrl?: string;
|
|
4
|
+
};
|
|
2
5
|
type __VLS_Props = {
|
|
3
|
-
items:
|
|
6
|
+
items: MentionDropdownItem[];
|
|
4
7
|
highlightedIndex: number;
|
|
5
8
|
trigger: '@' | '/';
|
|
6
9
|
query: string;
|
|
10
|
+
/** Viewport rect of the trigger character ('@' / '/') the panel anchors to. */
|
|
11
|
+
caretRect?: DOMRect | null;
|
|
7
12
|
};
|
|
8
13
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
14
|
select: (item: MentionItem) => any;
|
|
@@ -3,10 +3,21 @@ const props = defineProps({
|
|
|
3
3
|
items: { type: Array, required: true },
|
|
4
4
|
highlightedIndex: { type: Number, required: true },
|
|
5
5
|
trigger: { type: String, required: true },
|
|
6
|
-
query: { type: String, required: true }
|
|
6
|
+
query: { type: String, required: true },
|
|
7
|
+
caretRect: { type: null, required: false }
|
|
7
8
|
});
|
|
8
9
|
const emit = defineEmits(["select"]);
|
|
9
10
|
const listRef = ref(null);
|
|
11
|
+
const panelStyle = computed(() => {
|
|
12
|
+
const rect = props.caretRect;
|
|
13
|
+
if (!rect)
|
|
14
|
+
return {};
|
|
15
|
+
return {
|
|
16
|
+
position: "fixed",
|
|
17
|
+
left: `${Math.max(8, Math.min(rect.left, window.innerWidth - 416))}px`,
|
|
18
|
+
bottom: `${window.innerHeight - rect.top + 4}px`
|
|
19
|
+
};
|
|
20
|
+
});
|
|
10
21
|
const sections = computed(() => {
|
|
11
22
|
if (props.trigger === "@") {
|
|
12
23
|
return [{
|
|
@@ -43,41 +54,54 @@ watch(() => props.highlightedIndex, () => {
|
|
|
43
54
|
</script>
|
|
44
55
|
|
|
45
56
|
<template>
|
|
57
|
+
<!-- Custom listbox (not a reka menu: it must stay open and keep focus in
|
|
58
|
+
the contenteditable while the user types). Surface and item styling
|
|
59
|
+
mirror TelaMenubarContent / TelaMenubarItem exactly. -->
|
|
46
60
|
<div
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
:style="panelStyle"
|
|
62
|
+
class="mention-dropdown"
|
|
63
|
+
w-max min-w-244px max-w-400px
|
|
64
|
+
max-h-260px overflow-y-auto no-scrollbar
|
|
65
|
+
rounded-2xl
|
|
66
|
+
b-0.5px border-strong
|
|
67
|
+
bg
|
|
68
|
+
p-4px
|
|
54
69
|
>
|
|
55
|
-
<div v-if="items.length === 0" px-12px py-
|
|
56
|
-
<span
|
|
70
|
+
<div v-if="items.length === 0" px-12px py-6px>
|
|
71
|
+
<span body-14-regular text-tertiary>{{ emptyMessage }}</span>
|
|
57
72
|
</div>
|
|
58
73
|
|
|
59
|
-
<div ref="listRef"
|
|
74
|
+
<div v-else ref="listRef" flex="~ col">
|
|
60
75
|
<template v-for="section in sections" :key="section.title">
|
|
61
|
-
<div px-12px
|
|
62
|
-
<span
|
|
76
|
+
<div px-12px pb-2px>
|
|
77
|
+
<span body-12-medium text-tertiary>{{ section.title }}</span>
|
|
63
78
|
</div>
|
|
64
79
|
<button
|
|
65
80
|
v-for="entry in section.items"
|
|
66
81
|
:key="entry.item.id"
|
|
82
|
+
type="button"
|
|
67
83
|
:data-highlighted="entry.globalIndex === highlightedIndex"
|
|
68
84
|
flex items-center gap-8px
|
|
69
|
-
w-full
|
|
70
|
-
|
|
71
|
-
px-12px
|
|
85
|
+
w-full min-w-0
|
|
86
|
+
rounded-10px
|
|
87
|
+
px-12px py-4px
|
|
72
88
|
text-left
|
|
73
|
-
|
|
74
|
-
|
|
89
|
+
cursor-pointer select-none
|
|
90
|
+
outline-none
|
|
91
|
+
:class="entry.globalIndex === highlightedIndex ? 'bg-muted' : 'hover:bg-muted'"
|
|
75
92
|
@mousedown.prevent="emit('select', entry.item)"
|
|
76
93
|
>
|
|
77
|
-
<
|
|
94
|
+
<img
|
|
95
|
+
v-if="entry.item.iconUrl"
|
|
96
|
+
:src="entry.item.iconUrl"
|
|
97
|
+
alt=""
|
|
98
|
+
aria-hidden="true"
|
|
99
|
+
w-18px h-18px shrink-0
|
|
100
|
+
>
|
|
101
|
+
<TelaIcon v-else :name="entry.item.icon" size="18px" color="icon-secondary" shrink-0 />
|
|
78
102
|
<div flex="~ col" min-w-0>
|
|
79
|
-
<span
|
|
80
|
-
<span v-if="entry.item.description"
|
|
103
|
+
<span body-12-medium text-primary truncate>{{ entry.item.label }}</span>
|
|
104
|
+
<span v-if="entry.item.description" body-10-regular leading-12px text-tertiary truncate>{{ entry.item.description }}</span>
|
|
81
105
|
</div>
|
|
82
106
|
</button>
|
|
83
107
|
</template>
|
|
@@ -86,5 +110,5 @@ watch(() => props.highlightedIndex, () => {
|
|
|
86
110
|
</template>
|
|
87
111
|
|
|
88
112
|
<style scoped>
|
|
89
|
-
.mention-dropdown{
|
|
113
|
+
.mention-dropdown{bottom:100%;box-shadow:0 4px 16px 0 rgba(85,91,98,.14);margin-left:-4px;position:absolute;z-index:50}
|
|
90
114
|
</style>
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { MentionItem } from '../../types/schemas/chat/ui.js';
|
|
2
|
+
type MentionDropdownItem = MentionItem & {
|
|
3
|
+
iconUrl?: string;
|
|
4
|
+
};
|
|
2
5
|
type __VLS_Props = {
|
|
3
|
-
items:
|
|
6
|
+
items: MentionDropdownItem[];
|
|
4
7
|
highlightedIndex: number;
|
|
5
8
|
trigger: '@' | '/';
|
|
6
9
|
query: string;
|
|
10
|
+
/** Viewport rect of the trigger character ('@' / '/') the panel anchors to. */
|
|
11
|
+
caretRect?: DOMRect | null;
|
|
7
12
|
};
|
|
8
13
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
14
|
select: (item: MentionItem) => any;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useChatAuth } from "#chat-auth";
|
|
3
3
|
import { useEmbedConfig } from "../../embed/composables/embed-config";
|
|
4
4
|
import { useWorkspaceMembers } from "../../workspace/composables/workspace-members";
|
|
5
|
+
import { isPromptTooLongFailure } from "../../types/schemas/chat/messages.js";
|
|
5
6
|
import { parseContentWithFiles } from "../../files/content-detection";
|
|
6
7
|
import ChatVaultLink from "../../files/components/chat-vault-link.vue";
|
|
7
8
|
const props = defineProps({
|
|
@@ -39,6 +40,7 @@ const isOwnMessage = computed(() => {
|
|
|
39
40
|
});
|
|
40
41
|
const isPending = computed(() => props.message.status === "pending");
|
|
41
42
|
const isFailed = computed(() => props.message.status === "failed");
|
|
43
|
+
const isPromptTooLong = computed(() => isFailed.value && isPromptTooLongFailure(props.message.failReason));
|
|
42
44
|
const hasReasoning = computed(() => {
|
|
43
45
|
const data = props.message.reasoningData;
|
|
44
46
|
return props.message.role === "assistant" && data && Array.isArray(data) && data.length > 0;
|
|
@@ -104,9 +106,9 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
104
106
|
</script>
|
|
105
107
|
|
|
106
108
|
<template>
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
:
|
|
109
|
+
<TelaChatMessage
|
|
110
|
+
:from="message.role === 'user' ? 'user' : 'assistant'"
|
|
111
|
+
:align="isOwnMessage ? 'end' : 'start'"
|
|
110
112
|
>
|
|
111
113
|
<template v-if="message.role === 'user'">
|
|
112
114
|
<div
|
|
@@ -121,46 +123,20 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
121
123
|
items-center
|
|
122
124
|
gap-4px
|
|
123
125
|
>
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
h-full w-full object-cover
|
|
130
|
-
>
|
|
131
|
-
<div
|
|
132
|
-
v-else
|
|
133
|
-
h-full w-full
|
|
134
|
-
flex
|
|
135
|
-
items-center
|
|
136
|
-
justify-center
|
|
137
|
-
bg-neutral-200
|
|
138
|
-
>
|
|
139
|
-
<span text-8px text-neutral-500 font-medium>
|
|
140
|
-
{{ senderName.charAt(0).toUpperCase() }}
|
|
141
|
-
</span>
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
126
|
+
<TelaChatMessageAvatar
|
|
127
|
+
:src="senderImage ?? void 0"
|
|
128
|
+
:name="senderName"
|
|
129
|
+
size="2xs"
|
|
130
|
+
/>
|
|
144
131
|
<span text-12px leading-16px font-semibold text-primary>
|
|
145
132
|
{{ senderName }}
|
|
146
133
|
</span>
|
|
147
134
|
</div>
|
|
148
135
|
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
pt-16px
|
|
154
|
-
pb-20px
|
|
155
|
-
bg-subtle
|
|
156
|
-
text-primary
|
|
157
|
-
border-0.5px
|
|
158
|
-
border-solid
|
|
159
|
-
border-subtle
|
|
160
|
-
:class="[
|
|
161
|
-
isOwnMessage ? 'rounded-tl-[16px] rounded-tr-[16px] rounded-br-[4px] rounded-bl-[16px]' : 'rounded-tl-[4px] rounded-tr-[16px] rounded-br-[16px] rounded-bl-[16px]',
|
|
162
|
-
isPending && 'op-80'
|
|
163
|
-
]"
|
|
136
|
+
<TelaChatMessageContent
|
|
137
|
+
variant="contained"
|
|
138
|
+
:align="isOwnMessage ? 'end' : 'start'"
|
|
139
|
+
:class="isPending && 'op-80'"
|
|
164
140
|
>
|
|
165
141
|
<div v-if="hasFiles" flex="~ col" gap-4px mb-8px>
|
|
166
142
|
<a
|
|
@@ -186,7 +162,7 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
186
162
|
<div v-else body-16-regular whitespace-pre-wrap>
|
|
187
163
|
{{ message.content }}
|
|
188
164
|
</div>
|
|
189
|
-
</
|
|
165
|
+
</TelaChatMessageContent>
|
|
190
166
|
|
|
191
167
|
<div
|
|
192
168
|
mt-8px
|
|
@@ -213,35 +189,43 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
213
189
|
v-if="!isPending && message.content"
|
|
214
190
|
max-w-prose
|
|
215
191
|
:class="[
|
|
216
|
-
isFailed ? 'flex gap-12px items-start
|
|
192
|
+
isFailed ? 'flex gap-12px items-start bg-subtle border border-solid rounded-12px w-full' : 'rounded-12px code-theme'
|
|
217
193
|
]"
|
|
218
194
|
>
|
|
219
195
|
<template v-if="isFailed">
|
|
220
196
|
<div i-ph-warning-circle-fill text-16px text-red-600 shrink-0 mt-2px />
|
|
221
197
|
<div flex flex-col gap-4px grow>
|
|
222
198
|
<div flex items-center gap-4px>
|
|
223
|
-
<span heading-h5-semibold text-primary>Erro</span>
|
|
199
|
+
<span heading-h5-semibold text-primary>{{ isPromptTooLong ? "Limite de contexto atingido" : "Erro" }}</span>
|
|
224
200
|
<span body-10-regular text-secondary>{{ formatTime(message.createdAt) }}</span>
|
|
225
201
|
</div>
|
|
226
|
-
<div body-12-regular text-secondary whitespace-pre-wrap>
|
|
227
|
-
|
|
202
|
+
<div v-if="isPromptTooLong" body-12-regular text-secondary whitespace-pre-wrap>
|
|
203
|
+
Esta conversa atingiu o limite de contexto do modelo e não pode continuar. Inicie uma nova conversa para seguir com o assunto.
|
|
228
204
|
</div>
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
205
|
+
<template v-else>
|
|
206
|
+
<div body-12-regular text-secondary whitespace-pre-wrap>
|
|
207
|
+
{{ displayContent }}
|
|
208
|
+
</div>
|
|
209
|
+
<div v-if="message.failReason" body-10-regular text-tertiary whitespace-pre-wrap>
|
|
210
|
+
{{ message.failReason }}
|
|
211
|
+
</div>
|
|
212
|
+
<button
|
|
213
|
+
mt-8px
|
|
214
|
+
flex items-center gap-6px
|
|
215
|
+
px-12px py-6px
|
|
216
|
+
rounded-6px
|
|
217
|
+
bg-gray-100
|
|
218
|
+
hover:bg-gray-200
|
|
219
|
+
text-13px text-gray-700
|
|
220
|
+
transition-colors
|
|
221
|
+
cursor-pointer
|
|
222
|
+
self-start
|
|
223
|
+
@click="emit('retry', message.id)"
|
|
224
|
+
>
|
|
225
|
+
<div i-ph-arrow-clockwise text-14px />
|
|
226
|
+
<span>Tentar novamente</span>
|
|
227
|
+
</button>
|
|
228
|
+
</template>
|
|
245
229
|
</div>
|
|
246
230
|
</template>
|
|
247
231
|
<template v-else>
|
|
@@ -285,23 +269,21 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
285
269
|
mt-8px
|
|
286
270
|
flex
|
|
287
271
|
items-center
|
|
288
|
-
gap-
|
|
272
|
+
gap-8px
|
|
289
273
|
body-11-regular
|
|
290
|
-
text-
|
|
274
|
+
text-tertiary
|
|
291
275
|
>
|
|
292
|
-
<
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
<ChatMessageFeedback v-if="hasMessageFeedback" :message="message" />
|
|
300
|
-
</div>
|
|
276
|
+
<span>{{ formatTime(message.updatedAt || message.createdAt) }}</span>
|
|
277
|
+
<TelaDotSeparator v-if="formattedTtft || hasMessageFeedback" />
|
|
278
|
+
<div flex items-center gap-8px>
|
|
279
|
+
<span v-if="formattedTtft" flex items-center gap-4px>
|
|
280
|
+
{{ formattedTtft }}
|
|
281
|
+
</span>
|
|
282
|
+
<ChatMessageFeedback v-if="hasMessageFeedback" :message="message" />
|
|
301
283
|
</div>
|
|
302
284
|
</div>
|
|
303
285
|
</template>
|
|
304
286
|
</div>
|
|
305
287
|
</template>
|
|
306
|
-
</
|
|
288
|
+
</TelaChatMessage>
|
|
307
289
|
</template>
|
|
@@ -7,245 +7,41 @@ const props = defineProps({
|
|
|
7
7
|
const embedConfig = useEmbedConfig();
|
|
8
8
|
const { emit: emitFeedback } = useMessageFeedback();
|
|
9
9
|
const config = computed(() => embedConfig?.feedbackConfig.value ?? null);
|
|
10
|
-
const positiveReasons = computed(() => config.value?.positiveReasons ?? []);
|
|
11
|
-
const negativeReasons = computed(() => config.value?.negativeReasons ?? []);
|
|
10
|
+
const positiveReasons = computed(() => [...config.value?.positiveReasons ?? []]);
|
|
11
|
+
const negativeReasons = computed(() => [...config.value?.negativeReasons ?? []]);
|
|
12
12
|
const persistedMap = computed(() => embedConfig?.messageFeedback.value ?? null);
|
|
13
13
|
const persistedRating = computed(() => persistedMap.value?.[props.message.id] ?? null);
|
|
14
14
|
const localRating = ref(null);
|
|
15
15
|
const rating = computed(() => persistedMap.value ? persistedRating.value : localRating.value);
|
|
16
|
-
|
|
17
|
-
const positiveOpen = computed({
|
|
18
|
-
get: () => openRating.value === "positive",
|
|
19
|
-
set: (open) => {
|
|
20
|
-
openRating.value = open ? "positive" : null;
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
const negativeOpen = computed({
|
|
24
|
-
get: () => openRating.value === "negative",
|
|
25
|
-
set: (open) => {
|
|
26
|
-
openRating.value = open ? "negative" : null;
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
const selectedReasons = ref([]);
|
|
30
|
-
const comment = ref("");
|
|
31
|
-
watch(openRating, (opened) => {
|
|
32
|
-
if (opened) {
|
|
33
|
-
selectedReasons.value = [];
|
|
34
|
-
comment.value = "";
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
const canSubmitNegative = computed(() => {
|
|
38
|
-
if (!config.value?.requireReason)
|
|
39
|
-
return true;
|
|
40
|
-
if (negativeReasons.value.length > 0)
|
|
41
|
-
return selectedReasons.value.length > 0;
|
|
42
|
-
return comment.value.trim().length > 0;
|
|
43
|
-
});
|
|
44
|
-
function submitRating(next) {
|
|
16
|
+
function handleSubmit(payload) {
|
|
45
17
|
emitFeedback({
|
|
46
18
|
conversationId: props.message.conversationId,
|
|
47
19
|
messageId: props.message.id,
|
|
48
|
-
rating:
|
|
49
|
-
reasons:
|
|
50
|
-
comment: comment
|
|
20
|
+
rating: payload.rating,
|
|
21
|
+
reasons: payload.reasons,
|
|
22
|
+
comment: payload.comment
|
|
51
23
|
});
|
|
52
|
-
localRating.value =
|
|
53
|
-
openRating.value = null;
|
|
54
|
-
}
|
|
55
|
-
function toggleReason(reason) {
|
|
56
|
-
selectedReasons.value = selectedReasons.value.includes(reason) ? selectedReasons.value.filter((item) => item !== reason) : [...selectedReasons.value, reason];
|
|
24
|
+
localRating.value = payload.rating;
|
|
57
25
|
}
|
|
58
26
|
</script>
|
|
59
27
|
|
|
60
28
|
<template>
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<TelaPopover v-else v-model:open="positiveOpen">
|
|
80
|
-
<TelaPopoverTrigger as-child>
|
|
81
|
-
<button
|
|
82
|
-
type="button"
|
|
83
|
-
h-28px w-28px
|
|
84
|
-
flex items-center justify-center
|
|
85
|
-
rounded-8px
|
|
86
|
-
transition-colors
|
|
87
|
-
cursor-pointer
|
|
88
|
-
:class="rating === 'positive' ? 'bg-green-100 text-green-700' : 'text-black-400 hover:bg-neutral-100 hover:text-black-600'"
|
|
89
|
-
aria-label="Resposta útil"
|
|
90
|
-
title="Resposta útil"
|
|
91
|
-
>
|
|
92
|
-
<span :class="rating === 'positive' ? 'i-ph-thumbs-up-fill' : 'i-ph-thumbs-up'" text-15px />
|
|
93
|
-
</button>
|
|
94
|
-
</TelaPopoverTrigger>
|
|
95
|
-
<TelaPopoverContent
|
|
96
|
-
align="end"
|
|
97
|
-
:side-offset="4"
|
|
98
|
-
class="!w-auto !rounded-12px !border-neutral-200 !bg-white !p-0 !text-neutral-900 !shadow-lg"
|
|
99
|
-
>
|
|
100
|
-
<div w-288px p-12px flex="~ col" gap-10px>
|
|
101
|
-
<div flex="~ col" gap-2px>
|
|
102
|
-
<span text-14px font-medium text-neutral-900 leading-18px>O que ajudou?</span>
|
|
103
|
-
<span text-12px text-neutral-500 leading-16px>Marcar o que funcionou refina as próximas respostas.</span>
|
|
104
|
-
</div>
|
|
105
|
-
<div flex flex-wrap gap-6px>
|
|
106
|
-
<button
|
|
107
|
-
v-for="reason in positiveReasons"
|
|
108
|
-
:key="reason"
|
|
109
|
-
type="button"
|
|
110
|
-
h-26px px-10px
|
|
111
|
-
rounded-full
|
|
112
|
-
b=".5px solid"
|
|
113
|
-
text-12px leading-16px
|
|
114
|
-
transition-colors
|
|
115
|
-
cursor-pointer
|
|
116
|
-
:class="selectedReasons.includes(reason) ? 'bg-green-100 text-green-700 border-transparent font-medium' : 'bg-white text-neutral-600 border-neutral-300 hover:bg-neutral-50'"
|
|
117
|
-
@click="toggleReason(reason)"
|
|
118
|
-
>
|
|
119
|
-
{{ reason }}
|
|
120
|
-
</button>
|
|
121
|
-
</div>
|
|
122
|
-
<textarea
|
|
123
|
-
v-model="comment"
|
|
124
|
-
rows="2"
|
|
125
|
-
placeholder="Comentário (opcional)…"
|
|
126
|
-
aria-label="Comentário"
|
|
127
|
-
w-full
|
|
128
|
-
px-10px py-8px
|
|
129
|
-
rounded-8px
|
|
130
|
-
b=".5px neutral-300"
|
|
131
|
-
bg-white
|
|
132
|
-
outline-none
|
|
133
|
-
resize-none
|
|
134
|
-
text-13px text-neutral-900 leading-18px
|
|
135
|
-
focus:b-neutral-900
|
|
136
|
-
@keydown.escape.prevent.stop="positiveOpen = false"
|
|
137
|
-
/>
|
|
138
|
-
<div flex justify-end gap-6px>
|
|
139
|
-
<button
|
|
140
|
-
type="button"
|
|
141
|
-
h-28px px-10px rounded-8px
|
|
142
|
-
text-13px font-medium text-neutral-700
|
|
143
|
-
hover:bg-neutral-100
|
|
144
|
-
transition-colors
|
|
145
|
-
@click="positiveOpen = false"
|
|
146
|
-
>
|
|
147
|
-
Cancelar
|
|
148
|
-
</button>
|
|
149
|
-
<button
|
|
150
|
-
type="button"
|
|
151
|
-
h-28px px-10px rounded-8px
|
|
152
|
-
bg-neutral-900 text-white
|
|
153
|
-
text-13px font-medium
|
|
154
|
-
transition-opacity
|
|
155
|
-
@click="submitRating('positive')"
|
|
156
|
-
>
|
|
157
|
-
Enviar
|
|
158
|
-
</button>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
</TelaPopoverContent>
|
|
162
|
-
</TelaPopover>
|
|
163
|
-
|
|
164
|
-
<TelaPopover v-model:open="negativeOpen">
|
|
165
|
-
<TelaPopoverTrigger as-child>
|
|
166
|
-
<button
|
|
167
|
-
type="button"
|
|
168
|
-
h-28px w-28px
|
|
169
|
-
flex items-center justify-center
|
|
170
|
-
rounded-8px
|
|
171
|
-
transition-colors
|
|
172
|
-
cursor-pointer
|
|
173
|
-
:class="rating === 'negative' ? 'bg-red-100 text-red-700' : 'text-black-400 hover:bg-neutral-100 hover:text-black-600'"
|
|
174
|
-
aria-label="Resposta não útil"
|
|
175
|
-
title="Resposta não útil"
|
|
176
|
-
>
|
|
177
|
-
<span :class="rating === 'negative' ? 'i-ph-thumbs-down-fill' : 'i-ph-thumbs-down'" text-15px />
|
|
178
|
-
</button>
|
|
179
|
-
</TelaPopoverTrigger>
|
|
180
|
-
<TelaPopoverContent
|
|
181
|
-
align="end"
|
|
182
|
-
:side-offset="4"
|
|
183
|
-
class="!w-auto !rounded-12px !border-neutral-200 !bg-white !p-0 !text-neutral-900 !shadow-lg"
|
|
184
|
-
>
|
|
185
|
-
<div w-288px p-12px flex="~ col" gap-10px>
|
|
186
|
-
<div flex="~ col" gap-2px>
|
|
187
|
-
<span text-14px font-medium text-neutral-900 leading-18px>O que não funcionou?</span>
|
|
188
|
-
<span text-12px text-neutral-500 leading-16px>Seu retorno vai para a triagem de qualidade.</span>
|
|
189
|
-
</div>
|
|
190
|
-
<div v-if="negativeReasons.length > 0" flex flex-wrap gap-6px>
|
|
191
|
-
<button
|
|
192
|
-
v-for="reason in negativeReasons"
|
|
193
|
-
:key="reason"
|
|
194
|
-
type="button"
|
|
195
|
-
h-26px px-10px
|
|
196
|
-
rounded-full
|
|
197
|
-
b=".5px solid"
|
|
198
|
-
text-12px leading-16px
|
|
199
|
-
transition-colors
|
|
200
|
-
cursor-pointer
|
|
201
|
-
:class="selectedReasons.includes(reason) ? 'bg-blue-100 text-blue-900 border-transparent font-medium' : 'bg-white text-neutral-600 border-neutral-300 hover:bg-neutral-50'"
|
|
202
|
-
@click="toggleReason(reason)"
|
|
203
|
-
>
|
|
204
|
-
{{ reason }}
|
|
205
|
-
</button>
|
|
206
|
-
</div>
|
|
207
|
-
<textarea
|
|
208
|
-
v-model="comment"
|
|
209
|
-
rows="2"
|
|
210
|
-
placeholder="Conte o que faltou (opcional)…"
|
|
211
|
-
aria-label="Comentário"
|
|
212
|
-
w-full
|
|
213
|
-
px-10px py-8px
|
|
214
|
-
rounded-8px
|
|
215
|
-
b=".5px neutral-300"
|
|
216
|
-
bg-white
|
|
217
|
-
outline-none
|
|
218
|
-
resize-none
|
|
219
|
-
text-13px text-neutral-900 leading-18px
|
|
220
|
-
focus:b-neutral-900
|
|
221
|
-
@keydown.escape.prevent.stop="negativeOpen = false"
|
|
222
|
-
/>
|
|
223
|
-
<div flex justify-end gap-6px>
|
|
224
|
-
<button
|
|
225
|
-
type="button"
|
|
226
|
-
h-28px px-10px rounded-8px
|
|
227
|
-
text-13px font-medium text-neutral-700
|
|
228
|
-
hover:bg-neutral-100
|
|
229
|
-
transition-colors
|
|
230
|
-
@click="negativeOpen = false"
|
|
231
|
-
>
|
|
232
|
-
Cancelar
|
|
233
|
-
</button>
|
|
234
|
-
<button
|
|
235
|
-
type="button"
|
|
236
|
-
h-28px px-10px rounded-8px
|
|
237
|
-
bg-neutral-900 text-white
|
|
238
|
-
text-13px font-medium
|
|
239
|
-
transition-opacity
|
|
240
|
-
:disabled="!canSubmitNegative"
|
|
241
|
-
:class="!canSubmitNegative && 'opacity-40 cursor-not-allowed'"
|
|
242
|
-
@click="submitRating('negative')"
|
|
243
|
-
>
|
|
244
|
-
Enviar
|
|
245
|
-
</button>
|
|
246
|
-
</div>
|
|
247
|
-
</div>
|
|
248
|
-
</TelaPopoverContent>
|
|
249
|
-
</TelaPopover>
|
|
250
|
-
</div>
|
|
29
|
+
<TelaChatMessageFeedback
|
|
30
|
+
:rating="rating"
|
|
31
|
+
:positive-reasons="positiveReasons"
|
|
32
|
+
:negative-reasons="negativeReasons"
|
|
33
|
+
:require-reason="config?.requireReason ?? false"
|
|
34
|
+
positive-title="O que ajudou?"
|
|
35
|
+
positive-description="Marcar o que funcionou refina as próximas respostas."
|
|
36
|
+
positive-comment-placeholder="Comentário (opcional)…"
|
|
37
|
+
negative-title="O que não funcionou?"
|
|
38
|
+
negative-description="Seu retorno vai para a triagem de qualidade."
|
|
39
|
+
negative-comment-placeholder="Conte o que faltou (opcional)…"
|
|
40
|
+
cancel-label="Cancelar"
|
|
41
|
+
submit-label="Enviar"
|
|
42
|
+
helpful-label="Resposta útil"
|
|
43
|
+
not-helpful-label="Resposta não útil"
|
|
44
|
+
comment-label="Comentário"
|
|
45
|
+
@submit="handleSubmit"
|
|
46
|
+
/>
|
|
251
47
|
</template>
|