@meistrari/chat-nuxt 4.6.0-rc.9 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/conversations/components/chat-conversation-item.d.vue.ts +1 -0
  3. package/dist/runtime/conversations/components/chat-conversation-item.vue +3 -2
  4. package/dist/runtime/conversations/components/chat-conversation-item.vue.d.ts +1 -0
  5. package/dist/runtime/conversations/components/chat-conversation-list.d.vue.ts +4 -1
  6. package/dist/runtime/conversations/components/chat-conversation-list.vue +1 -1
  7. package/dist/runtime/conversations/components/chat-conversation-list.vue.d.ts +4 -1
  8. package/dist/runtime/conversations/composables/conversation-item.d.ts +1 -0
  9. package/dist/runtime/conversations/composables/conversation-list.d.ts +5 -1
  10. package/dist/runtime/conversations/composables/conversation-list.js +3 -1
  11. package/dist/runtime/embed/components/chat-embed-inner.vue +2 -2
  12. package/dist/runtime/messages/components/chat-message-actions.d.vue.ts +2 -0
  13. package/dist/runtime/messages/components/chat-message-actions.vue +27 -14
  14. package/dist/runtime/messages/components/chat-message-actions.vue.d.ts +2 -0
  15. package/dist/runtime/messages/components/chat-message-bubble.vue +3 -1
  16. package/dist/runtime/messages/components/chat-message-list.vue +14 -0
  17. package/dist/runtime/messages/composables/current-day.d.ts +1 -0
  18. package/dist/runtime/messages/composables/current-day.js +17 -0
  19. package/dist/runtime/messages/message-timestamps.d.ts +5 -0
  20. package/dist/runtime/messages/message-timestamps.js +43 -0
  21. package/dist/runtime/messages/tool-registry.js +5 -0
  22. package/dist/runtime/workspace/composables/workspace-members.d.ts +0 -1
  23. package/dist/runtime/workspace/composables/workspace-members.js +0 -4
  24. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "4.6.0-rc.9",
4
+ "version": "4.6.0-rc.12",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -6,6 +6,7 @@ type __VLS_Props = {
6
6
  draggable?: boolean;
7
7
  itemClass?: HTMLAttributes['class'];
8
8
  userImage?: string;
9
+ userName?: string;
9
10
  selectConversation?: (id: string) => void | Promise<void>;
10
11
  copyLink?: (id: string) => void | Promise<void>;
11
12
  debug?: (id: string) => void;
@@ -6,6 +6,7 @@ const props = defineProps({
6
6
  draggable: { type: Boolean, required: false },
7
7
  itemClass: { type: [Boolean, null, String, Object, Array], required: false, skipCheck: true },
8
8
  userImage: { type: String, required: false },
9
+ userName: { type: String, required: false },
9
10
  selectConversation: { type: Function, required: false },
10
11
  copyLink: { type: Function, required: false },
11
12
  debug: { type: Function, required: false }
@@ -53,13 +54,13 @@ function setRenameInputRef(element) {
53
54
  <div flex items-center justify-between h-full :class="props.itemClass">
54
55
  <!-- User Avatar -->
55
56
  <div
56
- v-if="userImage"
57
+ v-if="userName"
57
58
  translate-x--48px w-0 opacity-0 shrink-0 overflow-hidden
58
59
  class="[transition:width_0.26s_cubic-bezier(0.22,1,0.36,1),transform_0.26s_cubic-bezier(0.22,1,0.36,1),opacity_0.2s] group-hover:w-22px group-hover:translate-x-0 group-hover:opacity-100"
59
60
  >
60
61
  <TelaAvatar
61
62
  :image="userImage"
62
- alt="Usuário"
63
+ :alt="userName"
63
64
  size="2xs"
64
65
  class="rounded-full!"
65
66
  />
@@ -6,6 +6,7 @@ type __VLS_Props = {
6
6
  draggable?: boolean;
7
7
  itemClass?: HTMLAttributes['class'];
8
8
  userImage?: string;
9
+ userName?: string;
9
10
  selectConversation?: (id: string) => void | Promise<void>;
10
11
  copyLink?: (id: string) => void | Promise<void>;
11
12
  debug?: (id: string) => void;
@@ -3,7 +3,10 @@ import type { Conversation } from '../../types/schemas/chat/conversations.js';
3
3
  type __VLS_Props = {
4
4
  conversations: DeepReadonly<Conversation[]>;
5
5
  currentId?: string;
6
- getMemberImage?: (userId: string) => string | undefined;
6
+ getMember?: (userId: string) => {
7
+ name: string;
8
+ image?: string;
9
+ } | undefined;
7
10
  selectConversation?: (id: string) => void | Promise<void>;
8
11
  resetConversation?: () => void | Promise<void>;
9
12
  startConversation?: (groupId?: string | null) => void | Promise<void>;
@@ -4,7 +4,7 @@ import { useConversationList } from "../composables/conversation-list";
4
4
  const props = defineProps({
5
5
  conversations: { type: null, required: true },
6
6
  currentId: { type: String, required: false },
7
- getMemberImage: { type: Function, required: false },
7
+ getMember: { type: Function, required: false },
8
8
  selectConversation: { type: Function, required: false },
9
9
  resetConversation: { type: Function, required: false },
10
10
  startConversation: { type: Function, required: false },
@@ -3,7 +3,10 @@ import type { Conversation } from '../../types/schemas/chat/conversations.js';
3
3
  type __VLS_Props = {
4
4
  conversations: DeepReadonly<Conversation[]>;
5
5
  currentId?: string;
6
- getMemberImage?: (userId: string) => string | undefined;
6
+ getMember?: (userId: string) => {
7
+ name: string;
8
+ image?: string;
9
+ } | undefined;
7
10
  selectConversation?: (id: string) => void | Promise<void>;
8
11
  resetConversation?: () => void | Promise<void>;
9
12
  startConversation?: (groupId?: string | null) => void | Promise<void>;
@@ -5,6 +5,7 @@ export type ConversationItemOptions = {
5
5
  active: boolean;
6
6
  draggable?: boolean;
7
7
  userImage?: string;
8
+ userName?: string;
8
9
  selectConversation?: (id: string) => void | Promise<void>;
9
10
  copyLink?: (id: string) => void | Promise<void>;
10
11
  debug?: (id: string) => void;
@@ -3,7 +3,10 @@ import type { Conversation } from '../../types/schemas/chat/conversations.js';
3
3
  export type ConversationListOptions = {
4
4
  conversations: DeepReadonly<Conversation[]>;
5
5
  currentId?: string;
6
- getMemberImage?: (userId: string) => string | undefined;
6
+ getMember?: (userId: string) => {
7
+ name: string;
8
+ image?: string;
9
+ } | undefined;
7
10
  selectConversation?: (id: string) => void | Promise<void>;
8
11
  resetConversation?: () => void | Promise<void>;
9
12
  startConversation?: (groupId?: string | null) => void | Promise<void>;
@@ -120,6 +123,7 @@ export declare function useConversationList(options: ConversationListOptions): {
120
123
  active: boolean;
121
124
  draggable: boolean;
122
125
  userImage: string | undefined;
126
+ userName: string | undefined;
123
127
  selectConversation: ((id: string) => void | Promise<void>) | undefined;
124
128
  copyLink: (conversationId: string) => Promise<void>;
125
129
  debug: (() => void) | undefined;
@@ -492,11 +492,13 @@ export function useConversationList(options) {
492
492
  return draggedConversationId.value !== null && dropTargetGroupId.value === groupId;
493
493
  }
494
494
  function getConversationItemProps(conversation) {
495
+ const author = options.getMember?.(conversation.userId);
495
496
  return {
496
497
  conversation,
497
498
  active: conversation.id === options.currentId,
498
499
  draggable: true,
499
- userImage: options.getMemberImage?.(conversation.userId),
500
+ userImage: author?.image,
501
+ userName: author?.name,
500
502
  selectConversation: options.selectConversation,
501
503
  copyLink: copyConversationLink,
502
504
  debug: options.showDebugOption ? () => {
@@ -85,7 +85,7 @@ const route = useRoute();
85
85
  const embedConfig = useEmbedConfig();
86
86
  const chatAction = useChatAction();
87
87
  const { user: authUser, activeOrganization, logout } = useChatAuth();
88
- const { getMember, getMemberImage } = useWorkspaceMembers();
88
+ const { getMember } = useWorkspaceMembers();
89
89
  const statusToast = useStatusToast();
90
90
  const {
91
91
  conversations,
@@ -928,7 +928,7 @@ async function handleDelete() {
928
928
  <ChatConversationList
929
929
  :conversations="visibleConversations"
930
930
  :current-id="activeConversationId ?? void 0"
931
- :get-member-image="getMemberImage"
931
+ :get-member="getMember"
932
932
  :select-conversation="openConversation"
933
933
  :reset-conversation="handleNewConversation"
934
934
  :start-conversation="handleNewConversation"
@@ -1,4 +1,6 @@
1
1
  type __VLS_Props = {
2
+ createdAt: Date | string;
3
+ forkEnabled?: boolean;
2
4
  disabled?: boolean;
3
5
  };
4
6
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -1,24 +1,37 @@
1
1
  <script setup>
2
+ import { formatMessageMenuTimestamp } from "../message-timestamps";
2
3
  defineProps({
4
+ createdAt: { type: [Date, String], required: true },
5
+ forkEnabled: { type: Boolean, required: false },
3
6
  disabled: { type: Boolean, required: false }
4
7
  });
5
8
  const emit = defineEmits(["fork"]);
6
9
  </script>
7
10
 
8
11
  <template>
9
- <div>
10
- <TelaDropdownMenu
11
- :items="[{ label: 'Continuar em nova conversa', icon: 'i-ph-git-branch', disabled, click: () => emit('fork') }]"
12
- align="end"
13
- :should-be-modal="false"
14
- >
15
- <TelaIconButton
16
- icon="i-ph-dots-three"
17
- color="secondary"
18
- size="xs"
19
- :disabled="disabled"
20
- aria-label="Ações da mensagem"
21
- />
22
- </TelaDropdownMenu>
12
+ <div data-allow-mismatch @click.stop>
13
+ <TelaDropdownMenuRoot :modal="false">
14
+ <TelaDropdownMenuTrigger as-child class="data-[state=open]:bg-muted">
15
+ <TelaIconButton
16
+ icon="i-ph-dots-three"
17
+ color="secondary"
18
+ size="xs"
19
+ aria-label="Ações da mensagem"
20
+ />
21
+ </TelaDropdownMenuTrigger>
22
+ <TelaDropdownMenuContent align="end" class="!min-w-128px !z-605">
23
+ <TelaDropdownMenuLabel body-12-regular text-tertiary class="!font-normal !pb-4px">
24
+ {{ formatMessageMenuTimestamp(createdAt) }}
25
+ </TelaDropdownMenuLabel>
26
+ <TelaDropdownMenuItem
27
+ v-if="forkEnabled"
28
+ icon="i-ph-git-branch"
29
+ :disabled="disabled"
30
+ @click="emit('fork')"
31
+ >
32
+ Continuar em nova conversa
33
+ </TelaDropdownMenuItem>
34
+ </TelaDropdownMenuContent>
35
+ </TelaDropdownMenuRoot>
23
36
  </div>
24
37
  </template>
@@ -1,4 +1,6 @@
1
1
  type __VLS_Props = {
2
+ createdAt: Date | string;
3
+ forkEnabled?: boolean;
2
4
  disabled?: boolean;
3
5
  };
4
6
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -328,7 +328,9 @@ const attachedFiles = computed(() => {
328
328
  </span>
329
329
  <ChatMessageFeedback v-if="hasMessageFeedback" :message="message" />
330
330
  <ChatMessageActions
331
- v-if="forkEnabled && message.role === 'assistant' && message.status === 'completed' && !streamActive"
331
+ v-if="message.role === 'assistant' && message.status === 'completed' && !streamActive"
332
+ :created-at="message.createdAt"
333
+ :fork-enabled="forkEnabled"
332
334
  :disabled="forkDisabled"
333
335
  @fork="emit('fork', message.id)"
334
336
  />
@@ -2,6 +2,8 @@
2
2
  import ChatMessageBubble from "./chat-message-bubble.vue";
3
3
  import { useStickToBottom } from "../composables/use-stick-to-bottom";
4
4
  import { mergeConversationTimeline } from "../conversation-timeline";
5
+ import { getTimestampSeparatorLabels } from "../message-timestamps";
6
+ import { useCurrentDay } from "../composables/current-day";
5
7
  const props = defineProps({
6
8
  messages: { type: Array, required: true },
7
9
  events: { type: [Array, null], required: false },
@@ -45,6 +47,8 @@ const skeletonMessages = [
45
47
  { id: "assistant-2", align: "start", width: "84%", height: "92px" }
46
48
  ];
47
49
  const timelineItems = computed(() => mergeConversationTimeline(props.messages, props.events));
50
+ const today = useCurrentDay();
51
+ const timestampSeparators = computed(() => getTimestampSeparatorLabels(props.messages, today.value));
48
52
  const latestMessageId = computed(() => props.messages.at(-1)?.id ?? null);
49
53
  const latestOutputSignal = computed(() => {
50
54
  const latestMessage = props.messages.at(-1);
@@ -112,6 +116,16 @@ defineExpose({
112
116
 
113
117
  <template v-else>
114
118
  <template v-for="item in timelineItems" :key="`${item.kind}:${item.id}`">
119
+ <p
120
+ v-if="item.kind === 'message' && timestampSeparators.has(item.id)"
121
+ data-chat-timestamp-separator
122
+ my-8px
123
+ text-center
124
+ body-12-regular
125
+ text-tertiary
126
+ >
127
+ {{ timestampSeparators.get(item.id) }}
128
+ </p>
115
129
  <ChatConversationEvent
116
130
  v-if="item.kind === 'event'"
117
131
  :event="item.value"
@@ -0,0 +1 @@
1
+ export declare function useCurrentDay(): import("vue").Ref<Date, Date>;
@@ -0,0 +1,17 @@
1
+ import { onScopeDispose, ref } from "vue";
2
+ function msUntilNextMidnight(now) {
3
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1).getTime() - now.getTime();
4
+ }
5
+ export function useCurrentDay() {
6
+ const today = ref(/* @__PURE__ */ new Date());
7
+ let timeout;
8
+ function scheduleNextDay() {
9
+ timeout = setTimeout(() => {
10
+ today.value = /* @__PURE__ */ new Date();
11
+ scheduleNextDay();
12
+ }, msUntilNextMidnight(today.value));
13
+ }
14
+ scheduleNextDay();
15
+ onScopeDispose(() => clearTimeout(timeout));
16
+ return today;
17
+ }
@@ -0,0 +1,5 @@
1
+ import type { Message } from '../types/schemas/chat/conversations.js';
2
+ export declare function formatMessageSeparatorTimestamp(value: Date | string, now?: Date): string;
3
+ export declare function formatMessageMenuTimestamp(value: Date | string, now?: Date): string;
4
+ export declare function shouldShowTimestampSeparator(value: Date | string, previousValue: Date | string | null): boolean;
5
+ export declare function getTimestampSeparatorLabels(messages: readonly Pick<Message, 'id' | 'role' | 'createdAt'>[], now?: Date): Map<string, string>;
@@ -0,0 +1,43 @@
1
+ const SEPARATOR_GAP_MS = 60 * 60 * 1e3;
2
+ const timeFormat = new Intl.DateTimeFormat("pt-BR", { hour: "2-digit", minute: "2-digit" });
3
+ const weekdayFormat = new Intl.DateTimeFormat("pt-BR", { weekday: "short", day: "numeric", month: "short" });
4
+ const weekdayWithYearFormat = new Intl.DateTimeFormat("pt-BR", { weekday: "short", day: "numeric", month: "short", year: "numeric" });
5
+ const menuFormat = new Intl.DateTimeFormat("pt-BR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" });
6
+ const menuWithYearFormat = new Intl.DateTimeFormat("pt-BR", { day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit" });
7
+ function toDate(value) {
8
+ return value instanceof Date ? value : new Date(value);
9
+ }
10
+ function isSameDay(left, right) {
11
+ return left.getFullYear() === right.getFullYear() && left.getMonth() === right.getMonth() && left.getDate() === right.getDate();
12
+ }
13
+ export function formatMessageSeparatorTimestamp(value, now = /* @__PURE__ */ new Date()) {
14
+ const date = toDate(value);
15
+ const time = timeFormat.format(date);
16
+ if (isSameDay(date, now))
17
+ return `Hoje ${time}`;
18
+ const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
19
+ if (isSameDay(date, yesterday))
20
+ return `Ontem ${time}`;
21
+ const day = date.getFullYear() === now.getFullYear() ? weekdayFormat : weekdayWithYearFormat;
22
+ return `${day.format(date)} \xE0s ${time}`;
23
+ }
24
+ export function formatMessageMenuTimestamp(value, now = /* @__PURE__ */ new Date()) {
25
+ const date = toDate(value);
26
+ const format = date.getFullYear() === now.getFullYear() ? menuFormat : menuWithYearFormat;
27
+ return format.format(date);
28
+ }
29
+ export function shouldShowTimestampSeparator(value, previousValue) {
30
+ if (!previousValue)
31
+ return true;
32
+ const date = toDate(value);
33
+ const previous = toDate(previousValue);
34
+ return !isSameDay(date, previous) || date.getTime() - previous.getTime() >= SEPARATOR_GAP_MS;
35
+ }
36
+ export function getTimestampSeparatorLabels(messages, now = /* @__PURE__ */ new Date()) {
37
+ const labels = /* @__PURE__ */ new Map();
38
+ messages.forEach((message, index) => {
39
+ if (message.role === "user" && shouldShowTimestampSeparator(message.createdAt, messages[index - 1]?.createdAt ?? null))
40
+ labels.set(message.id, formatMessageSeparatorTimestamp(message.createdAt, now));
41
+ });
42
+ return labels;
43
+ }
@@ -45,6 +45,11 @@ const TOOL_REGISTRY = {
45
45
  widgetId: "bash",
46
46
  labels: { active: "Executando comando", executed: "Executou comando" }
47
47
  },
48
+ "check_execution": {
49
+ icon: "i-ph-wrench",
50
+ widgetId: "generic",
51
+ labels: { active: "Verificando execu\xE7\xE3o", executed: "Verificou execu\xE7\xE3o" }
52
+ },
48
53
  "Write": {
49
54
  icon: "i-ph-file-plus",
50
55
  widgetId: "write",
@@ -6,7 +6,6 @@ type MemberUser = {
6
6
  export declare function useWorkspaceMembers(): {
7
7
  members: any;
8
8
  membersMap: any;
9
- getMemberImage: (userId: string) => string | undefined;
10
9
  getMember: (userId: string) => MemberUser | undefined;
11
10
  getMemberByEmail: (email: string) => MemberUser | undefined;
12
11
  };
@@ -19,9 +19,6 @@ export function useWorkspaceMembers() {
19
19
  }
20
20
  return map;
21
21
  });
22
- function getMemberImage(userId) {
23
- return membersMap.value.get(userId)?.image ?? void 0;
24
- }
25
22
  function getMember(userId) {
26
23
  return membersMap.value.get(userId);
27
24
  }
@@ -31,7 +28,6 @@ export function useWorkspaceMembers() {
31
28
  return {
32
29
  members,
33
30
  membersMap,
34
- getMemberImage,
35
31
  getMember,
36
32
  getMemberByEmail
37
33
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "4.6.0-rc.9",
3
+ "version": "4.6.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {