@meistrari/chat-nuxt 2.0.0 → 3.0.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 +7 -9
- package/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +0 -39
- package/dist/runtime/conversations/composables/conversations.d.ts +1 -4
- package/dist/runtime/conversations/composables/conversations.js +3 -6
- package/dist/runtime/embed/components/chat-embed-inner.vue +4 -35
- package/dist/runtime/embed/components/chat-embed.d.vue.ts +3 -0
- package/dist/runtime/embed/components/chat-embed.vue +10 -2
- package/dist/runtime/embed/components/chat-embed.vue.d.ts +3 -0
- package/dist/runtime/embed/components/configuration/chat-configuration-knowledge-sources-tab.d.vue.ts +2 -2
- package/dist/runtime/embed/components/configuration/chat-configuration-knowledge-sources-tab.vue.d.ts +2 -2
- package/dist/runtime/embed/components/meistrari-chat-embed.vue +9 -1
- package/dist/runtime/embed/components/mobile/chat-mobile-shell.d.vue.ts +2 -8
- package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue +2 -12
- package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue.d.ts +2 -8
- package/dist/runtime/embed/composables/embed-config.d.ts +6 -0
- package/dist/runtime/embed/composables/embed-config.js +11 -3
- package/dist/runtime/embed/feature-config.d.ts +0 -2
- package/dist/runtime/embed/feature-config.js +0 -1
- package/dist/runtime/embed/types.d.ts +14 -0
- package/dist/runtime/messages/components/chat-message-bubble.vue +8 -4
- package/dist/runtime/messages/components/chat-message-feedback.d.vue.ts +7 -0
- package/dist/runtime/messages/components/chat-message-feedback.vue +251 -0
- package/dist/runtime/messages/components/chat-message-feedback.vue.d.ts +7 -0
- package/dist/runtime/messages/components/chat-message-input.d.vue.ts +5 -15
- package/dist/runtime/messages/components/chat-message-input.vue +0 -7
- package/dist/runtime/messages/components/chat-message-input.vue.d.ts +5 -15
- package/dist/runtime/messages/composables/chat-feedback.d.ts +21 -0
- package/dist/runtime/messages/composables/chat-feedback.js +12 -0
- package/dist/runtime/messages/composables/chat.d.ts +1 -1
- package/dist/runtime/messages/composables/chat.js +34 -12
- package/dist/runtime/messages/message-state.d.ts +3 -0
- package/dist/runtime/messages/message-state.js +13 -0
- package/dist/runtime/types/embed.d.ts +1 -0
- package/dist/runtime/types/schemas/chat/conversations.d.ts +2 -3
- package/dist/runtime/types/schemas/chat/conversations.js +2 -3
- package/dist/runtime/types/schemas/chat/messages.d.ts +6 -48
- package/dist/runtime/types/schemas/chat/messages.js +5 -4
- package/dist/runtime/types/schemas/chat/workspace-settings.d.ts +0 -47
- package/dist/runtime/types/schemas/chat/workspace-settings.js +0 -1
- package/dist/runtime/types/schemas/integrations/tela-agent-api.d.ts +34 -0
- package/dist/runtime/types/schemas/integrations/tela-agent-api.js +3 -0
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
- package/dist/runtime/internal/agent-environment-resolver.stub.d.ts +0 -2
- package/dist/runtime/internal/agent-environment-resolver.stub.js +0 -1
- package/dist/runtime/messages/components/chat-model-selector.d.vue.ts +0 -15
- package/dist/runtime/messages/components/chat-model-selector.vue +0 -42
- package/dist/runtime/messages/components/chat-model-selector.vue.d.ts +0 -15
- package/dist/runtime/messages/composables/selected-model.d.ts +0 -4
- package/dist/runtime/messages/composables/selected-model.js +0 -48
- package/dist/runtime/types/chat-agent-environment-resolver.d.ts +0 -4
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { DeepReadonly } from 'vue';
|
|
2
2
|
import type { Conversation, ConversationWithMessages, Message } from '../../../types/schemas/chat/conversations.js';
|
|
3
3
|
import type { ConversationFile } from '../../../types/schemas/chat/files.js';
|
|
4
|
-
import type { ChatModel } from '../../../types/schemas/chat/models.js';
|
|
5
4
|
import type { TopbarTab } from '../../../types/schemas/chat/ui.js';
|
|
6
5
|
import type { WorkspaceSettingsResponse as WorkspaceSettings } from '../../../types/schemas/chat/workspace-settings.js';
|
|
7
6
|
import type { TelaAgentInputVariable } from '../../../types/schemas/integrations/tela-agent-api.js';
|
|
@@ -13,7 +12,6 @@ type __VLS_Props = {
|
|
|
13
12
|
currentUser?: ChatActor | null;
|
|
14
13
|
activeTab: TopbarTab;
|
|
15
14
|
activeConversationId: string | null;
|
|
16
|
-
allowModelSelection?: boolean;
|
|
17
15
|
agentInputSchema?: readonly TelaAgentInputVariable[] | null;
|
|
18
16
|
agentInputSchemaLoading?: boolean;
|
|
19
17
|
conversations: DeepReadonly<Conversation[]>;
|
|
@@ -30,10 +28,8 @@ type __VLS_Props = {
|
|
|
30
28
|
isEditing: boolean;
|
|
31
29
|
loading?: boolean;
|
|
32
30
|
messages: Message[];
|
|
33
|
-
model: ChatModel;
|
|
34
31
|
showCancelButton?: boolean;
|
|
35
32
|
showDebugOption?: boolean;
|
|
36
|
-
showModelSelector?: boolean;
|
|
37
33
|
showOutdatedBadge?: boolean;
|
|
38
34
|
showOutdatedBanner?: boolean;
|
|
39
35
|
showUsageTab?: boolean;
|
|
@@ -58,9 +54,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
58
54
|
copyLink: () => any;
|
|
59
55
|
selectConversation: (id: string) => any;
|
|
60
56
|
cancel: () => any;
|
|
61
|
-
send: (content: string, files: FileUploadInput[], model?: any, agentInputs?: TelaAgentExecutionInput[] | undefined) => any;
|
|
62
57
|
settings: () => any;
|
|
63
|
-
|
|
58
|
+
send: (content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any;
|
|
64
59
|
debug: () => any;
|
|
65
60
|
tabChange: (tab: TopbarTab) => any;
|
|
66
61
|
newConversation: (groupId?: string | null | undefined) => any;
|
|
@@ -80,9 +75,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
80
75
|
onCopyLink?: (() => any) | undefined;
|
|
81
76
|
onSelectConversation?: ((id: string) => any) | undefined;
|
|
82
77
|
onCancel?: (() => any) | undefined;
|
|
83
|
-
onSend?: ((content: string, files: FileUploadInput[], model?: any, agentInputs?: TelaAgentExecutionInput[] | undefined) => any) | undefined;
|
|
84
78
|
onSettings?: (() => any) | undefined;
|
|
85
|
-
|
|
79
|
+
onSend?: ((content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any) | undefined;
|
|
86
80
|
onDebug?: (() => any) | undefined;
|
|
87
81
|
onTabChange?: ((tab: TopbarTab) => any) | undefined;
|
|
88
82
|
onNewConversation?: ((groupId?: string | null | undefined) => any) | undefined;
|
|
@@ -3,7 +3,9 @@ import type { ConversationCreatorFilter } from '../../types/schemas/chat/primiti
|
|
|
3
3
|
import type { WorkspaceSettingsResponse as WorkspaceSettings } from '../../types/schemas/chat/workspace-settings.js';
|
|
4
4
|
import type { TelaAgentExecutionInput } from '../../types/schemas/integrations/tela-agent-api.js';
|
|
5
5
|
import type { ChatLoadingMessageMode } from '../loading-messages.js';
|
|
6
|
+
import type { MessageFeedbackConfig, MessageFeedbackRating } from '../../messages/composables/chat-feedback.js';
|
|
6
7
|
type ConfiguredTelaAgentInputs = DeepReadonly<TelaAgentExecutionInput[]> | null | undefined;
|
|
8
|
+
type ConfiguredMessageFeedback = Readonly<Record<string, MessageFeedbackRating>> | null | undefined;
|
|
7
9
|
export type EmbedConfig = {
|
|
8
10
|
workspaceId: Ref<string>;
|
|
9
11
|
telaAgentId: Ref<string | null>;
|
|
@@ -17,6 +19,8 @@ export type EmbedConfig = {
|
|
|
17
19
|
loadingMessagesMode: Ref<ChatLoadingMessageMode | null | undefined>;
|
|
18
20
|
customComponents: Ref<Record<string, Component> | undefined>;
|
|
19
21
|
customHtmlTags: Ref<readonly string[] | undefined>;
|
|
22
|
+
feedbackConfig: Ref<MessageFeedbackConfig | null | undefined>;
|
|
23
|
+
messageFeedback: Ref<ConfiguredMessageFeedback>;
|
|
20
24
|
};
|
|
21
25
|
type EmbedConfigInput = {
|
|
22
26
|
workspaceId: MaybeRefOrGetter<string>;
|
|
@@ -31,6 +35,8 @@ type EmbedConfigInput = {
|
|
|
31
35
|
loadingMessagesMode?: MaybeRefOrGetter<ChatLoadingMessageMode | null | undefined>;
|
|
32
36
|
customComponents?: MaybeRefOrGetter<Record<string, Component> | undefined>;
|
|
33
37
|
customHtmlTags?: MaybeRefOrGetter<readonly string[] | undefined>;
|
|
38
|
+
feedbackConfig?: MaybeRefOrGetter<MessageFeedbackConfig | null | undefined>;
|
|
39
|
+
messageFeedback?: MaybeRefOrGetter<ConfiguredMessageFeedback>;
|
|
34
40
|
};
|
|
35
41
|
type ProvideEmbedConfigOptions = {
|
|
36
42
|
syncGlobal?: boolean;
|
|
@@ -19,7 +19,9 @@ function useGlobalEmbedConfigState() {
|
|
|
19
19
|
loadingMessages: useState("chat-embed-config-loading-messages", () => void 0),
|
|
20
20
|
loadingMessagesMode: useState("chat-embed-config-loading-messages-mode", () => void 0),
|
|
21
21
|
customComponents: useGlobalCustomComponentsState(),
|
|
22
|
-
customHtmlTags: useState("chat-embed-config-custom-html-tags", () => void 0)
|
|
22
|
+
customHtmlTags: useState("chat-embed-config-custom-html-tags", () => void 0),
|
|
23
|
+
feedbackConfig: useState("chat-embed-config-feedback-config", () => void 0),
|
|
24
|
+
messageFeedback: useState("chat-embed-config-message-feedback", () => void 0)
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
27
|
export function provideEmbedConfig(input, options = {}) {
|
|
@@ -35,7 +37,9 @@ export function provideEmbedConfig(input, options = {}) {
|
|
|
35
37
|
loadingMessages: computed(() => toValue(input.loadingMessages)),
|
|
36
38
|
loadingMessagesMode: computed(() => toValue(input.loadingMessagesMode)),
|
|
37
39
|
customComponents: computed(() => toValue(input.customComponents)),
|
|
38
|
-
customHtmlTags: computed(() => toValue(input.customHtmlTags))
|
|
40
|
+
customHtmlTags: computed(() => toValue(input.customHtmlTags)),
|
|
41
|
+
feedbackConfig: computed(() => toValue(input.feedbackConfig)),
|
|
42
|
+
messageFeedback: computed(() => toValue(input.messageFeedback))
|
|
39
43
|
};
|
|
40
44
|
const globalConfig = useGlobalEmbedConfigState();
|
|
41
45
|
if (options.syncGlobal ?? true) {
|
|
@@ -53,6 +57,8 @@ export function provideEmbedConfig(input, options = {}) {
|
|
|
53
57
|
globalConfig.loadingMessagesMode.value = config.loadingMessagesMode.value;
|
|
54
58
|
globalConfig.customComponents.value = config.customComponents.value;
|
|
55
59
|
globalConfig.customHtmlTags.value = config.customHtmlTags.value;
|
|
60
|
+
globalConfig.feedbackConfig.value = config.feedbackConfig.value;
|
|
61
|
+
globalConfig.messageFeedback.value = config.messageFeedback.value;
|
|
56
62
|
});
|
|
57
63
|
}
|
|
58
64
|
provide(EMBED_CONFIG_KEY, config);
|
|
@@ -76,6 +82,8 @@ export function useEmbedConfig() {
|
|
|
76
82
|
loadingMessages: computed(() => globalConfig.loadingMessages.value),
|
|
77
83
|
loadingMessagesMode: computed(() => globalConfig.loadingMessagesMode.value),
|
|
78
84
|
customComponents: computed(() => globalConfig.customComponents.value),
|
|
79
|
-
customHtmlTags: computed(() => globalConfig.customHtmlTags.value)
|
|
85
|
+
customHtmlTags: computed(() => globalConfig.customHtmlTags.value),
|
|
86
|
+
feedbackConfig: computed(() => globalConfig.feedbackConfig.value),
|
|
87
|
+
messageFeedback: computed(() => globalConfig.messageFeedback.value)
|
|
80
88
|
};
|
|
81
89
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
export type ChatFeatureConfig = {
|
|
2
2
|
showUsageTab?: boolean;
|
|
3
3
|
showDebugOption?: boolean;
|
|
4
|
-
showModelSelector?: boolean;
|
|
5
4
|
showCancelButton?: boolean;
|
|
6
5
|
};
|
|
7
6
|
export type ResolvedChatFeatureConfig = {
|
|
8
7
|
showUsageTab: boolean;
|
|
9
8
|
showDebugOption: boolean;
|
|
10
|
-
showModelSelector: boolean;
|
|
11
9
|
showCancelButton: boolean;
|
|
12
10
|
};
|
|
13
11
|
export declare function resolveChatFeatures(features?: Partial<ChatFeatureConfig>): ResolvedChatFeatureConfig;
|
|
@@ -2,7 +2,6 @@ export function resolveChatFeatures(features) {
|
|
|
2
2
|
return {
|
|
3
3
|
showUsageTab: features?.showUsageTab ?? false,
|
|
4
4
|
showDebugOption: features?.showDebugOption ?? false,
|
|
5
|
-
showModelSelector: features?.showModelSelector ?? false,
|
|
6
5
|
showCancelButton: features?.showCancelButton ?? false
|
|
7
6
|
};
|
|
8
7
|
}
|
|
@@ -4,6 +4,7 @@ import type { TelaAgentExecutionInput } from '../types/schemas/integrations/tela
|
|
|
4
4
|
import type { ConversationCreatorFilter } from '../types/schemas/chat/primitives.js';
|
|
5
5
|
import type { ChatLoadingMessageMode } from './loading-messages.js';
|
|
6
6
|
import type { ChatFeatureConfig } from './feature-config.js';
|
|
7
|
+
import type { MessageFeedbackConfig, MessageFeedbackRating } from '../messages/composables/chat-feedback.js';
|
|
7
8
|
export type ChatActor = {
|
|
8
9
|
name?: string | null;
|
|
9
10
|
email?: string | null;
|
|
@@ -30,6 +31,19 @@ export type ChatEmbedSharedProps = {
|
|
|
30
31
|
customComponents?: Record<string, Component>;
|
|
31
32
|
/** Extra HTML tags allowed by the markdown renderer. */
|
|
32
33
|
customHtmlTags?: readonly string[];
|
|
34
|
+
/**
|
|
35
|
+
* Enables 👍/👎 feedback on assistant messages and configures the reason
|
|
36
|
+
* popovers. Providing the prop (even `{}`) shows the controls; votes reach
|
|
37
|
+
* the host through the `message-feedback` event and are never persisted here.
|
|
38
|
+
*/
|
|
39
|
+
feedbackConfig?: MessageFeedbackConfig | null;
|
|
40
|
+
/**
|
|
41
|
+
* Ratings the host persisted, keyed by message id. When provided, this map is
|
|
42
|
+
* the single source of truth for the selected thumbs — update it as
|
|
43
|
+
* message-feedback events are saved (optimistically or after persistence).
|
|
44
|
+
* When omitted, votes are kept in component-local state only.
|
|
45
|
+
*/
|
|
46
|
+
messageFeedback?: Readonly<Record<string, MessageFeedbackRating>> | null;
|
|
33
47
|
};
|
|
34
48
|
export type DefaultChatEmbedProps = ChatEmbedSharedProps & {
|
|
35
49
|
/** Omitted in default chat mode. Set this to switch to Tela agent mode. */
|
|
@@ -16,6 +16,7 @@ const mergedCustomComponents = computed(() => ({
|
|
|
16
16
|
...embedConfig?.customComponents.value ?? {}
|
|
17
17
|
}));
|
|
18
18
|
const mergedCustomHtmlTags = computed(() => embedConfig?.customHtmlTags.value ?? []);
|
|
19
|
+
const hasMessageFeedback = computed(() => embedConfig?.feedbackConfig.value != null);
|
|
19
20
|
const senderMember = computed(() => {
|
|
20
21
|
const createdBy = props.message.createdBy?.trim();
|
|
21
22
|
if (!createdBy) {
|
|
@@ -286,10 +287,13 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
|
|
|
286
287
|
>
|
|
287
288
|
<div w-full flex items-center justify-between gap-12px>
|
|
288
289
|
<span>{{ formatTime(message.updatedAt || message.createdAt) }}</span>
|
|
289
|
-
<
|
|
290
|
-
<span
|
|
291
|
-
|
|
292
|
-
|
|
290
|
+
<div flex items-center gap-8px>
|
|
291
|
+
<span v-if="formattedTtft" flex items-center gap-4px text-black-300>
|
|
292
|
+
<span i-ph-timer text-12px />
|
|
293
|
+
TTFT: {{ formattedTtft }}
|
|
294
|
+
</span>
|
|
295
|
+
<ChatMessageFeedback v-if="hasMessageFeedback" :message="message" />
|
|
296
|
+
</div>
|
|
293
297
|
</div>
|
|
294
298
|
</div>
|
|
295
299
|
</template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Message } from '../../types/schemas/chat/conversations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
message: Message;
|
|
4
|
+
};
|
|
5
|
+
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>;
|
|
6
|
+
declare const _default: typeof __VLS_export;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useEmbedConfig } from "../../embed/composables/embed-config";
|
|
3
|
+
import { useMessageFeedback } from "../composables/chat-feedback";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
message: { type: null, required: true }
|
|
6
|
+
});
|
|
7
|
+
const embedConfig = useEmbedConfig();
|
|
8
|
+
const { emit: emitFeedback } = useMessageFeedback();
|
|
9
|
+
const config = computed(() => embedConfig?.feedbackConfig.value ?? null);
|
|
10
|
+
const positiveReasons = computed(() => config.value?.positiveReasons ?? []);
|
|
11
|
+
const negativeReasons = computed(() => config.value?.negativeReasons ?? []);
|
|
12
|
+
const persistedMap = computed(() => embedConfig?.messageFeedback.value ?? null);
|
|
13
|
+
const persistedRating = computed(() => persistedMap.value?.[props.message.id] ?? null);
|
|
14
|
+
const localRating = ref(null);
|
|
15
|
+
const rating = computed(() => persistedMap.value ? persistedRating.value : localRating.value);
|
|
16
|
+
const openRating = ref(null);
|
|
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) {
|
|
45
|
+
emitFeedback({
|
|
46
|
+
conversationId: props.message.conversationId,
|
|
47
|
+
messageId: props.message.id,
|
|
48
|
+
rating: next,
|
|
49
|
+
reasons: [...selectedReasons.value],
|
|
50
|
+
comment: comment.value.trim()
|
|
51
|
+
});
|
|
52
|
+
localRating.value = next;
|
|
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];
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<template>
|
|
61
|
+
<div flex items-center gap-2px>
|
|
62
|
+
<!-- Frictionless 👍: with no positive reasons configured there is nothing
|
|
63
|
+
to ask, so the vote submits directly instead of opening an empty popover. -->
|
|
64
|
+
<button
|
|
65
|
+
v-if="positiveReasons.length === 0"
|
|
66
|
+
type="button"
|
|
67
|
+
h-28px w-28px
|
|
68
|
+
flex items-center justify-center
|
|
69
|
+
rounded-8px
|
|
70
|
+
transition-colors
|
|
71
|
+
cursor-pointer
|
|
72
|
+
:class="rating === 'positive' ? 'bg-green-100 text-green-700' : 'text-black-400 hover:bg-neutral-100 hover:text-black-600'"
|
|
73
|
+
aria-label="Resposta útil"
|
|
74
|
+
title="Resposta útil"
|
|
75
|
+
@click="submitRating('positive')"
|
|
76
|
+
>
|
|
77
|
+
<span :class="rating === 'positive' ? 'i-ph-thumbs-up-fill' : 'i-ph-thumbs-up'" text-15px />
|
|
78
|
+
</button>
|
|
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>
|
|
251
|
+
</template>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Message } from '../../types/schemas/chat/conversations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
message: Message;
|
|
4
|
+
};
|
|
5
|
+
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>;
|
|
6
|
+
declare const _default: typeof __VLS_export;
|
|
7
|
+
export default _default;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { DeepReadonly } from 'vue';
|
|
2
2
|
import type { Message } from '../../types/schemas/chat/conversations.js';
|
|
3
3
|
import type { ConversationFile } from '../../types/schemas/chat/files.js';
|
|
4
|
-
import type { ChatModel } from '../../types/schemas/chat/models.js';
|
|
5
4
|
import type { WorkspaceSettingsResponse as WorkspaceSettings } from '../../types/schemas/chat/workspace-settings.js';
|
|
6
5
|
import type { TelaAgentInputVariable } from '../../types/schemas/integrations/tela-agent-api.js';
|
|
7
6
|
type __VLS_Props = {
|
|
@@ -15,30 +14,21 @@ type __VLS_Props = {
|
|
|
15
14
|
messages?: Message[];
|
|
16
15
|
conversationFiles?: ConversationFile[];
|
|
17
16
|
workspaceSettings?: DeepReadonly<WorkspaceSettings> | null;
|
|
18
|
-
allowModelSelection?: boolean;
|
|
19
17
|
agentInputSchema?: readonly TelaAgentInputVariable[] | null;
|
|
20
18
|
agentInputSchemaLoading?: boolean;
|
|
21
|
-
showModelSelector?: boolean;
|
|
22
19
|
showCancelButton?: boolean;
|
|
23
20
|
};
|
|
24
21
|
declare function addFiles(files: File[], inputName?: string): void;
|
|
25
22
|
declare function setDraft(text: string): void;
|
|
26
|
-
|
|
27
|
-
'model'?: ChatModel;
|
|
28
|
-
};
|
|
29
|
-
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
30
|
-
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
23
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
31
24
|
addFiles: typeof addFiles;
|
|
32
25
|
setDraft: typeof setDraft;
|
|
33
|
-
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
34
|
-
"update:model": (value: ChatModel) => any;
|
|
35
|
-
} & {
|
|
26
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
36
27
|
cancel: () => any;
|
|
37
|
-
send: (content: string, files: FileUploadInput[],
|
|
38
|
-
}, string, import("vue").PublicProps, Readonly<
|
|
28
|
+
send: (content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any;
|
|
29
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
39
30
|
onCancel?: (() => any) | undefined;
|
|
40
|
-
onSend?: ((content: string, files: FileUploadInput[],
|
|
41
|
-
"onUpdate:model"?: ((value: ChatModel) => any) | undefined;
|
|
31
|
+
onSend?: ((content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any) | undefined;
|
|
42
32
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
43
33
|
declare const _default: typeof __VLS_export;
|
|
44
34
|
export default _default;
|
|
@@ -3,7 +3,6 @@ import { useFileUpload } from "../../files/composables/file-upload";
|
|
|
3
3
|
import { useInputHistory } from "../composables/input-history";
|
|
4
4
|
import { useMentionAutocomplete } from "../composables/mention-autocomplete";
|
|
5
5
|
import { MAX_FILE_SIZE, MAX_FILES } from "../../workspace/composables/workspace-context-files";
|
|
6
|
-
import { defaultChatModel } from "../../types/schemas/chat/models.js";
|
|
7
6
|
import { getAgentInputReadiness } from "../input-readiness";
|
|
8
7
|
import { formatFileSize } from "../../files/formatting";
|
|
9
8
|
import { getMimeIcon, getMimeLabel } from "../../files/icons";
|
|
@@ -18,17 +17,13 @@ const props = defineProps({
|
|
|
18
17
|
messages: { type: Array, required: false },
|
|
19
18
|
conversationFiles: { type: Array, required: false },
|
|
20
19
|
workspaceSettings: { type: null, required: false },
|
|
21
|
-
allowModelSelection: { type: Boolean, required: false },
|
|
22
20
|
agentInputSchema: { type: [Array, null], required: false },
|
|
23
21
|
agentInputSchemaLoading: { type: Boolean, required: false },
|
|
24
|
-
showModelSelector: { type: Boolean, required: false },
|
|
25
22
|
showCancelButton: { type: Boolean, required: false }
|
|
26
23
|
});
|
|
27
24
|
const emit = defineEmits(["send", "cancel"]);
|
|
28
|
-
const selectedModel = defineModel("model", { type: null, ...{ default: defaultChatModel } });
|
|
29
25
|
const { uploadFiles, uploading, uploadError } = useFileUpload();
|
|
30
26
|
const isCancelEnabled = computed(() => props.showCancelButton === true);
|
|
31
|
-
const isModelSelectorEnabled = computed(() => props.showModelSelector === true && props.allowModelSelection !== false);
|
|
32
27
|
const content = ref("");
|
|
33
28
|
const editorRef = ref(null);
|
|
34
29
|
const fileInputRef = ref(null);
|
|
@@ -240,7 +235,6 @@ async function handleSend() {
|
|
|
240
235
|
"send",
|
|
241
236
|
taggedContent.trim(),
|
|
242
237
|
files,
|
|
243
|
-
isModelSelectorEnabled.value ? selectedModel.value : void 0,
|
|
244
238
|
agentInputs.length > 0 ? agentInputs : void 0
|
|
245
239
|
);
|
|
246
240
|
content.value = "";
|
|
@@ -639,7 +633,6 @@ defineExpose({
|
|
|
639
633
|
@blur="handleBlur"
|
|
640
634
|
/>
|
|
641
635
|
<div flex items-center gap-8px>
|
|
642
|
-
<ChatModelSelector v-if="isModelSelectorEnabled" v-model="selectedModel" :disabled="isLocked" @click.stop />
|
|
643
636
|
<div flex-1 />
|
|
644
637
|
<TelaTooltip :content="pendingFiles.length >= MAX_FILES ? 'Limite de arquivos atingido' : 'Anexar arquivo'" side="top">
|
|
645
638
|
<TelaIconButton
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { DeepReadonly } from 'vue';
|
|
2
2
|
import type { Message } from '../../types/schemas/chat/conversations.js';
|
|
3
3
|
import type { ConversationFile } from '../../types/schemas/chat/files.js';
|
|
4
|
-
import type { ChatModel } from '../../types/schemas/chat/models.js';
|
|
5
4
|
import type { WorkspaceSettingsResponse as WorkspaceSettings } from '../../types/schemas/chat/workspace-settings.js';
|
|
6
5
|
import type { TelaAgentInputVariable } from '../../types/schemas/integrations/tela-agent-api.js';
|
|
7
6
|
type __VLS_Props = {
|
|
@@ -15,30 +14,21 @@ type __VLS_Props = {
|
|
|
15
14
|
messages?: Message[];
|
|
16
15
|
conversationFiles?: ConversationFile[];
|
|
17
16
|
workspaceSettings?: DeepReadonly<WorkspaceSettings> | null;
|
|
18
|
-
allowModelSelection?: boolean;
|
|
19
17
|
agentInputSchema?: readonly TelaAgentInputVariable[] | null;
|
|
20
18
|
agentInputSchemaLoading?: boolean;
|
|
21
|
-
showModelSelector?: boolean;
|
|
22
19
|
showCancelButton?: boolean;
|
|
23
20
|
};
|
|
24
21
|
declare function addFiles(files: File[], inputName?: string): void;
|
|
25
22
|
declare function setDraft(text: string): void;
|
|
26
|
-
|
|
27
|
-
'model'?: ChatModel;
|
|
28
|
-
};
|
|
29
|
-
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
30
|
-
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
23
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
31
24
|
addFiles: typeof addFiles;
|
|
32
25
|
setDraft: typeof setDraft;
|
|
33
|
-
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
34
|
-
"update:model": (value: ChatModel) => any;
|
|
35
|
-
} & {
|
|
26
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
36
27
|
cancel: () => any;
|
|
37
|
-
send: (content: string, files: FileUploadInput[],
|
|
38
|
-
}, string, import("vue").PublicProps, Readonly<
|
|
28
|
+
send: (content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any;
|
|
29
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
39
30
|
onCancel?: (() => any) | undefined;
|
|
40
|
-
onSend?: ((content: string, files: FileUploadInput[],
|
|
41
|
-
"onUpdate:model"?: ((value: ChatModel) => any) | undefined;
|
|
31
|
+
onSend?: ((content: string, files: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[] | undefined) => any) | undefined;
|
|
42
32
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
43
33
|
declare const _default: typeof __VLS_export;
|
|
44
34
|
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type MessageFeedbackRating = 'positive' | 'negative';
|
|
2
|
+
export type MessageFeedbackConfig = {
|
|
3
|
+
/** Reason chips offered on a positive vote. Empty or omitted: 👍 submits directly. */
|
|
4
|
+
positiveReasons?: readonly string[];
|
|
5
|
+
/** Reason chips offered on a negative vote. */
|
|
6
|
+
negativeReasons?: readonly string[];
|
|
7
|
+
/** Require a justification on a negative vote: at least one reason chip when negativeReasons are configured, otherwise a non-empty comment. */
|
|
8
|
+
requireReason?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type MessageFeedbackPayload = {
|
|
11
|
+
conversationId: string;
|
|
12
|
+
messageId: string;
|
|
13
|
+
rating: MessageFeedbackRating;
|
|
14
|
+
reasons: string[];
|
|
15
|
+
comment: string;
|
|
16
|
+
};
|
|
17
|
+
export type MessageFeedbackHandler = (payload: MessageFeedbackPayload) => void;
|
|
18
|
+
export declare function provideMessageFeedback(handler: MessageFeedbackHandler): void;
|
|
19
|
+
export declare function useMessageFeedback(): {
|
|
20
|
+
emit(payload: MessageFeedbackPayload): void;
|
|
21
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const MESSAGE_FEEDBACK_KEY = Symbol("chat-nuxt:message-feedback");
|
|
2
|
+
export function provideMessageFeedback(handler) {
|
|
3
|
+
provide(MESSAGE_FEEDBACK_KEY, handler);
|
|
4
|
+
}
|
|
5
|
+
export function useMessageFeedback() {
|
|
6
|
+
const handler = inject(MESSAGE_FEEDBACK_KEY, null);
|
|
7
|
+
return {
|
|
8
|
+
emit(payload) {
|
|
9
|
+
handler?.(payload);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -14,7 +14,7 @@ export declare function useChat(): {
|
|
|
14
14
|
animatedTitle: any;
|
|
15
15
|
loadConversation: (id: string) => Promise<void>;
|
|
16
16
|
setConversation: (conv: Conversation | ConversationWithMessages) => void;
|
|
17
|
-
sendMessage: (content: string, files?: FileUploadInput[],
|
|
17
|
+
sendMessage: (content: string, files?: FileUploadInput[], agentInputs?: TelaAgentExecutionInput[]) => Promise<Message | null>;
|
|
18
18
|
retryMessage: (messageId: string) => Promise<boolean>;
|
|
19
19
|
cancelConversation: () => Promise<boolean>;
|
|
20
20
|
startPolling: () => void;
|