@meistrari/chat-nuxt 1.3.0 → 1.4.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 (26) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/MeistrariChatEmbed.d.vue.ts +6 -1
  3. package/dist/runtime/components/MeistrariChatEmbed.vue +7 -2
  4. package/dist/runtime/components/MeistrariChatEmbed.vue.d.ts +6 -1
  5. package/dist/runtime/components/app-markdown-render.d.vue.ts +3 -1
  6. package/dist/runtime/components/app-markdown-render.vue +15 -4
  7. package/dist/runtime/components/app-markdown-render.vue.d.ts +3 -1
  8. package/dist/runtime/components/chat/message-bubble.vue +10 -2
  9. package/dist/runtime/composables/useChat.js +8 -1
  10. package/dist/runtime/composables/useChatAction.d.ts +10 -0
  11. package/dist/runtime/composables/useChatAction.js +12 -0
  12. package/dist/runtime/composables/useEmbedConfig.d.ts +5 -1
  13. package/dist/runtime/composables/useEmbedConfig.js +16 -3
  14. package/dist/runtime/embed/components/ChatEmbed.d.vue.ts +6 -1
  15. package/dist/runtime/embed/components/ChatEmbed.vue +11 -3
  16. package/dist/runtime/embed/components/ChatEmbed.vue.d.ts +6 -1
  17. package/dist/runtime/server/api/conversations/[id]/messages/index.get.js +1 -1
  18. package/dist/runtime/server/api/conversations/[id]/messages/index.post.js +8 -5
  19. package/dist/runtime/server/db/index.js +62 -0
  20. package/dist/runtime/server/utils/agent-api.d.ts +1 -1
  21. package/dist/runtime/server/utils/agent-api.js +2 -5
  22. package/dist/runtime/server/utils/chat-workspace-settings.d.ts +2 -1
  23. package/dist/runtime/server/utils/chat-workspace-settings.js +10 -1
  24. package/dist/runtime/utils/custom-markdown-components.d.ts +9 -0
  25. package/dist/runtime/utils/custom-markdown-components.js +54 -0
  26. 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": "1.3.0",
4
+ "version": "1.4.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -1,5 +1,6 @@
1
- import type { DeepReadonly } from 'vue';
1
+ import type { Component, DeepReadonly } from 'vue';
2
2
  import type { WorkspaceSettings } from '../composables/useWorkspaceSettings.js';
3
+ import type { ChatActionPayload } from '../composables/useChatAction.js';
3
4
  import type { ChatLoadingMessageMode } from '../utils/chat-loading-messages.js';
4
5
  import type { ChatActor, ChatFeatureConfig } from '../utils/tela-chat.js';
5
6
  type __VLS_Props = {
@@ -11,11 +12,15 @@ type __VLS_Props = {
11
12
  features?: Partial<ChatFeatureConfig>;
12
13
  loadingMessages?: readonly string[] | null;
13
14
  loadingMessagesMode?: ChatLoadingMessageMode | null;
15
+ customComponents?: Record<string, Component>;
16
+ customHtmlTags?: readonly string[];
14
17
  };
15
18
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
16
19
  "update:conversationId": (value: string | null) => any;
20
+ action: (payload: ChatActionPayload) => any;
17
21
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
22
  "onUpdate:conversationId"?: ((value: string | null) => any) | undefined;
23
+ onAction?: ((payload: ChatActionPayload) => any) | undefined;
19
24
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
25
  declare const _default: typeof __VLS_export;
21
26
  export default _default;
@@ -7,9 +7,11 @@ defineProps({
7
7
  hideSidebar: { type: Boolean, required: false },
8
8
  features: { type: Object, required: false },
9
9
  loadingMessages: { type: [Array, null], required: false },
10
- loadingMessagesMode: { type: [String, null], required: false }
10
+ loadingMessagesMode: { type: [String, null], required: false },
11
+ customComponents: { type: Object, required: false },
12
+ customHtmlTags: { type: Array, required: false }
11
13
  });
12
- defineEmits(["update:conversationId"]);
14
+ defineEmits(["update:conversationId", "action"]);
13
15
  </script>
14
16
 
15
17
  <template>
@@ -22,6 +24,9 @@ defineEmits(["update:conversationId"]);
22
24
  :features="features"
23
25
  :loading-messages="loadingMessages"
24
26
  :loading-messages-mode="loadingMessagesMode"
27
+ :custom-components="customComponents"
28
+ :custom-html-tags="customHtmlTags"
25
29
  @update:conversation-id="$emit('update:conversationId', $event)"
30
+ @action="$emit('action', $event)"
26
31
  />
27
32
  </template>
@@ -1,5 +1,6 @@
1
- import type { DeepReadonly } from 'vue';
1
+ import type { Component, DeepReadonly } from 'vue';
2
2
  import type { WorkspaceSettings } from '../composables/useWorkspaceSettings.js';
3
+ import type { ChatActionPayload } from '../composables/useChatAction.js';
3
4
  import type { ChatLoadingMessageMode } from '../utils/chat-loading-messages.js';
4
5
  import type { ChatActor, ChatFeatureConfig } from '../utils/tela-chat.js';
5
6
  type __VLS_Props = {
@@ -11,11 +12,15 @@ type __VLS_Props = {
11
12
  features?: Partial<ChatFeatureConfig>;
12
13
  loadingMessages?: readonly string[] | null;
13
14
  loadingMessagesMode?: ChatLoadingMessageMode | null;
15
+ customComponents?: Record<string, Component>;
16
+ customHtmlTags?: readonly string[];
14
17
  };
15
18
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
16
19
  "update:conversationId": (value: string | null) => any;
20
+ action: (payload: ChatActionPayload) => any;
17
21
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
22
  "onUpdate:conversationId"?: ((value: string | null) => any) | undefined;
23
+ onAction?: ((payload: ChatActionPayload) => any) | undefined;
19
24
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
25
  declare const _default: typeof __VLS_export;
21
26
  export default _default;
@@ -4,12 +4,14 @@ type __VLS_Props = {
4
4
  renderCodeBlocksAsPre?: boolean;
5
5
  typewriter?: boolean;
6
6
  customComponents?: Record<string, Component>;
7
+ customHtmlTags?: readonly string[];
7
8
  };
8
9
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
9
10
  content: string | null;
11
+ customComponents: Record<string, Component>;
12
+ customHtmlTags: readonly string[];
10
13
  renderCodeBlocksAsPre: boolean;
11
14
  typewriter: boolean;
12
- customComponents: Record<string, Component>;
13
15
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
16
  declare const _default: typeof __VLS_export;
15
17
  export default _default;
@@ -2,16 +2,18 @@
2
2
  import MarkdownRender, { getMarkdown, parseMarkdownToStructure, removeCustomComponents, setCustomComponents } from "markstream-vue";
3
3
  import MermaidDiagram from "./renderers/mermaid-diagram.vue";
4
4
  import { transformMarkdownNodes } from "../utils/markdown-nodes";
5
+ import { wrapCustomMarkdownComponents } from "../utils/custom-markdown-components";
5
6
  const props = defineProps({
6
7
  content: { type: [String, null], required: false, default: "" },
7
8
  renderCodeBlocksAsPre: { type: Boolean, required: false, default: false },
8
9
  typewriter: { type: Boolean, required: false, default: true },
9
- customComponents: { type: Object, required: false, default: () => ({}) }
10
+ customComponents: { type: Object, required: false, default: () => ({}) },
11
+ customHtmlTags: { type: Array, required: false, default: () => [] }
10
12
  });
11
13
  const attrs = useAttrs();
12
- const markdown = getMarkdown();
13
14
  const instance = getCurrentInstance();
14
15
  const scopeId = `app-markdown-render-${instance?.uid ?? Math.random().toString(36).slice(2)}`;
16
+ const markdown = computed(() => getMarkdown(scopeId, { customHtmlTags: props.customHtmlTags }));
15
17
  function escapeSingleDollarSigns(text) {
16
18
  return text.replace(/(```[\s\S]*?```|``+[^`]*``+|`[^`]*`|!?\[(?:[^\[\]]|\[[^\[\]]*\])*\]\([^)]*\))|(?<![$\\])\$(?!\$)/g, (_match, codeSpan) => {
17
19
  if (codeSpan)
@@ -19,14 +21,23 @@ function escapeSingleDollarSigns(text) {
19
21
  return "\\$";
20
22
  });
21
23
  }
24
+ const parseOptions = computed(() => {
25
+ const tags = props.customHtmlTags;
26
+ return tags && tags.length > 0 ? { customHtmlTags: [...tags] } : void 0;
27
+ });
22
28
  const nodes = computed(() => {
23
- const parsedNodes = parseMarkdownToStructure(escapeSingleDollarSigns(props.content ?? ""), markdown);
29
+ const parsedNodes = parseMarkdownToStructure(
30
+ escapeSingleDollarSigns(props.content ?? ""),
31
+ markdown.value,
32
+ parseOptions.value
33
+ );
24
34
  return transformMarkdownNodes(parsedNodes);
25
35
  });
36
+ const customMarkdownComponents = computed(() => wrapCustomMarkdownComponents(props.customComponents));
26
37
  watchEffect(() => {
27
38
  setCustomComponents(scopeId, {
28
39
  mermaid: MermaidDiagram,
29
- ...props.customComponents
40
+ ...customMarkdownComponents.value
30
41
  });
31
42
  });
32
43
  onBeforeUnmount(() => {
@@ -4,12 +4,14 @@ type __VLS_Props = {
4
4
  renderCodeBlocksAsPre?: boolean;
5
5
  typewriter?: boolean;
6
6
  customComponents?: Record<string, Component>;
7
+ customHtmlTags?: readonly string[];
7
8
  };
8
9
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
9
10
  content: string | null;
11
+ customComponents: Record<string, Component>;
12
+ customHtmlTags: readonly string[];
10
13
  renderCodeBlocksAsPre: boolean;
11
14
  typewriter: boolean;
12
- customComponents: Record<string, Component>;
13
15
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
16
  declare const _default: typeof __VLS_export;
15
17
  export default _default;
@@ -7,6 +7,12 @@ const props = defineProps({
7
7
  const emit = defineEmits(["retry"]);
8
8
  const { user } = useTelaApplicationAuth();
9
9
  const { getMemberByEmail } = useWorkspaceMembers();
10
+ const embedConfig = useEmbedConfig();
11
+ const mergedCustomComponents = computed(() => ({
12
+ link: ChatVaultLink,
13
+ ...embedConfig?.customComponents.value ?? {}
14
+ }));
15
+ const mergedCustomHtmlTags = computed(() => embedConfig?.customHtmlTags.value ?? []);
10
16
  const senderMember = computed(() => {
11
17
  const createdBy = props.message.createdBy?.trim();
12
18
  if (!createdBy) {
@@ -241,7 +247,8 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
241
247
  <AppMarkdownRender
242
248
  :id="`message-${message.id}-segment-${index}`"
243
249
  :content="segment.content"
244
- :custom-components="{ link: ChatVaultLink }"
250
+ :custom-components="mergedCustomComponents"
251
+ :custom-html-tags="mergedCustomHtmlTags"
245
252
  :render-code-blocks-as-pre="true"
246
253
  :typewriter="shouldAnimate"
247
254
  />
@@ -259,7 +266,8 @@ const hasDetectedFiles = computed(() => contentSegments.value.some((s) => s.type
259
266
  <ClientOnly v-else>
260
267
  <AppMarkdownRender
261
268
  :content="displayContent"
262
- :custom-components="{ link: ChatVaultLink }"
269
+ :custom-components="mergedCustomComponents"
270
+ :custom-html-tags="mergedCustomHtmlTags"
263
271
  :render-code-blocks-as-pre="true"
264
272
  :typewriter="shouldAnimate"
265
273
  text-black-900
@@ -307,11 +307,18 @@ export function useChat() {
307
307
  };
308
308
  messages.value = [...messages.value, optimisticUserMessage, optimisticAssistantMessage];
309
309
  try {
310
+ const workspaceSettingsSnapshot = embedConfig?.workspaceSettings.value;
311
+ const body = {
312
+ content,
313
+ files,
314
+ model,
315
+ ...workspaceSettingsSnapshot !== void 0 ? { workspaceSettingsSnapshot } : {}
316
+ };
310
317
  const response = await $fetch(
311
318
  chatApi.path(`/conversations/${currentConversation.value.id}/messages`),
312
319
  chatApi.withChatHeaders({
313
320
  method: "POST",
314
- body: { content, files, model }
321
+ body
315
322
  })
316
323
  );
317
324
  messages.value = messages.value.map((msg) => {
@@ -0,0 +1,10 @@
1
+ export type ChatActionPayload = {
2
+ name: string;
3
+ data: unknown;
4
+ messageId?: string;
5
+ };
6
+ export type ChatActionHandler = (payload: ChatActionPayload) => void;
7
+ export declare function provideChatAction(handler: ChatActionHandler): void;
8
+ export declare function useChatAction(): {
9
+ emit(name: string, data?: unknown, messageId?: string): void;
10
+ };
@@ -0,0 +1,12 @@
1
+ const CHAT_ACTION_KEY = Symbol("chat-nuxt:action");
2
+ export function provideChatAction(handler) {
3
+ provide(CHAT_ACTION_KEY, handler);
4
+ }
5
+ export function useChatAction() {
6
+ const handler = inject(CHAT_ACTION_KEY, null);
7
+ return {
8
+ emit(name, data, messageId) {
9
+ handler?.({ name, data, messageId });
10
+ }
11
+ };
12
+ }
@@ -1,4 +1,4 @@
1
- import type { DeepReadonly, MaybeRefOrGetter, Ref } from 'vue';
1
+ import type { Component, DeepReadonly, MaybeRefOrGetter, Ref } from 'vue';
2
2
  import type { ChatLoadingMessageMode } from '../utils/chat-loading-messages.js';
3
3
  import type { WorkspaceSettings } from './useWorkspaceSettings.js';
4
4
  export type EmbedConfig = {
@@ -7,6 +7,8 @@ export type EmbedConfig = {
7
7
  hideSidebar: Ref<boolean>;
8
8
  loadingMessages: Ref<readonly string[] | null | undefined>;
9
9
  loadingMessagesMode: Ref<ChatLoadingMessageMode | null | undefined>;
10
+ customComponents: Ref<Record<string, Component> | undefined>;
11
+ customHtmlTags: Ref<readonly string[] | undefined>;
10
12
  };
11
13
  type EmbedConfigInput = {
12
14
  workspaceId: MaybeRefOrGetter<string>;
@@ -14,6 +16,8 @@ type EmbedConfigInput = {
14
16
  hideSidebar?: MaybeRefOrGetter<boolean | undefined>;
15
17
  loadingMessages?: MaybeRefOrGetter<readonly string[] | null | undefined>;
16
18
  loadingMessagesMode?: MaybeRefOrGetter<ChatLoadingMessageMode | null | undefined>;
19
+ customComponents?: MaybeRefOrGetter<Record<string, Component> | undefined>;
20
+ customHtmlTags?: MaybeRefOrGetter<readonly string[] | undefined>;
17
21
  };
18
22
  type ProvideEmbedConfigOptions = {
19
23
  syncGlobal?: boolean;
@@ -1,4 +1,9 @@
1
1
  const EMBED_CONFIG_KEY = "embed-config";
2
+ function useGlobalCustomComponentsState() {
3
+ const nuxtApp = useNuxtApp();
4
+ nuxtApp._chatEmbedConfigCustomComponents ??= shallowRef(void 0);
5
+ return nuxtApp._chatEmbedConfigCustomComponents;
6
+ }
2
7
  function useGlobalEmbedConfigState() {
3
8
  return {
4
9
  provided: useState("chat-embed-config-provided", () => false),
@@ -6,7 +11,9 @@ function useGlobalEmbedConfigState() {
6
11
  workspaceSettings: useState("chat-embed-config-workspace-settings", () => void 0),
7
12
  hideSidebar: useState("chat-embed-config-hide-sidebar", () => void 0),
8
13
  loadingMessages: useState("chat-embed-config-loading-messages", () => void 0),
9
- loadingMessagesMode: useState("chat-embed-config-loading-messages-mode", () => void 0)
14
+ loadingMessagesMode: useState("chat-embed-config-loading-messages-mode", () => void 0),
15
+ customComponents: useGlobalCustomComponentsState(),
16
+ customHtmlTags: useState("chat-embed-config-custom-html-tags", () => void 0)
10
17
  };
11
18
  }
12
19
  export function provideEmbedConfig(input, options = {}) {
@@ -15,7 +22,9 @@ export function provideEmbedConfig(input, options = {}) {
15
22
  workspaceSettings: computed(() => toValue(input.workspaceSettings)),
16
23
  hideSidebar: computed(() => toValue(input.hideSidebar) ?? true),
17
24
  loadingMessages: computed(() => toValue(input.loadingMessages)),
18
- loadingMessagesMode: computed(() => toValue(input.loadingMessagesMode))
25
+ loadingMessagesMode: computed(() => toValue(input.loadingMessagesMode)),
26
+ customComponents: computed(() => toValue(input.customComponents)),
27
+ customHtmlTags: computed(() => toValue(input.customHtmlTags))
19
28
  };
20
29
  const globalConfig = useGlobalEmbedConfigState();
21
30
  if (options.syncGlobal ?? true) {
@@ -26,6 +35,8 @@ export function provideEmbedConfig(input, options = {}) {
26
35
  globalConfig.hideSidebar.value = config.hideSidebar.value;
27
36
  globalConfig.loadingMessages.value = config.loadingMessages.value;
28
37
  globalConfig.loadingMessagesMode.value = config.loadingMessagesMode.value;
38
+ globalConfig.customComponents.value = config.customComponents.value;
39
+ globalConfig.customHtmlTags.value = config.customHtmlTags.value;
29
40
  });
30
41
  }
31
42
  provide(EMBED_CONFIG_KEY, config);
@@ -42,6 +53,8 @@ export function useEmbedConfig() {
42
53
  workspaceSettings: computed(() => globalConfig.workspaceSettings.value),
43
54
  hideSidebar: computed(() => globalConfig.hideSidebar.value ?? true),
44
55
  loadingMessages: computed(() => globalConfig.loadingMessages.value),
45
- loadingMessagesMode: computed(() => globalConfig.loadingMessagesMode.value)
56
+ loadingMessagesMode: computed(() => globalConfig.loadingMessagesMode.value),
57
+ customComponents: computed(() => globalConfig.customComponents.value),
58
+ customHtmlTags: computed(() => globalConfig.customHtmlTags.value)
46
59
  };
47
60
  }
@@ -1,4 +1,5 @@
1
- import type { DeepReadonly } from 'vue';
1
+ import type { Component, DeepReadonly } from 'vue';
2
+ import { type ChatActionPayload } from '../../composables/useChatAction.js';
2
3
  import type { WorkspaceSettings } from '../../composables/useWorkspaceSettings.js';
3
4
  import type { ChatLoadingMessageMode } from '../../utils/chat-loading-messages.js';
4
5
  import type { ChatActor, ChatFeatureConfig } from '../../utils/tela-chat.js';
@@ -12,11 +13,15 @@ type __VLS_Props = {
12
13
  features?: Partial<ChatFeatureConfig>;
13
14
  loadingMessages?: readonly string[] | null;
14
15
  loadingMessagesMode?: ChatLoadingMessageMode | null;
16
+ customComponents?: Record<string, Component>;
17
+ customHtmlTags?: readonly string[];
15
18
  };
16
19
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
17
20
  "update:conversationId": (value: string | null) => any;
21
+ action: (payload: ChatActionPayload) => any;
18
22
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
19
23
  "onUpdate:conversationId"?: ((value: string | null) => any) | undefined;
24
+ onAction?: ((payload: ChatActionPayload) => any) | undefined;
20
25
  }>, {
21
26
  initialConversationId: string | null;
22
27
  hideSidebar: boolean;
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { provideEmbedConfig, useEmbedConfig } from "../../composables/useEmbedConfig";
3
+ import { provideChatAction } from "../../composables/useChatAction";
3
4
  import ChatEmbedInner from "./ChatEmbedInner.vue";
4
5
  const props = defineProps({
5
6
  workspaceSettings: { type: null, required: false },
@@ -9,9 +10,11 @@ const props = defineProps({
9
10
  user: { type: [Object, null], required: false },
10
11
  features: { type: Object, required: false },
11
12
  loadingMessages: { type: [Array, null], required: false },
12
- loadingMessagesMode: { type: [String, null], required: false }
13
+ loadingMessagesMode: { type: [String, null], required: false },
14
+ customComponents: { type: Object, required: false },
15
+ customHtmlTags: { type: Array, required: false }
13
16
  });
14
- defineEmits(["update:conversationId"]);
17
+ const emit = defineEmits(["update:conversationId", "action"]);
15
18
  const parentEmbedConfig = useEmbedConfig();
16
19
  const { activeOrganization } = useTelaApplicationAuth();
17
20
  const workspaceId = computed(() => activeOrganization.value?.id?.trim() ?? "");
@@ -20,8 +23,13 @@ provideEmbedConfig({
20
23
  workspaceSettings: computed(() => props.workspaceSettings),
21
24
  hideSidebar: computed(() => props.hideSidebar),
22
25
  loadingMessages: computed(() => props.loadingMessages),
23
- loadingMessagesMode: computed(() => props.loadingMessagesMode)
26
+ loadingMessagesMode: computed(() => props.loadingMessagesMode),
27
+ customComponents: computed(() => props.customComponents),
28
+ customHtmlTags: computed(() => props.customHtmlTags)
24
29
  }, { syncGlobal: !parentEmbedConfig });
30
+ provideChatAction((payload) => {
31
+ emit("action", payload);
32
+ });
25
33
  </script>
26
34
 
27
35
  <template>
@@ -1,4 +1,5 @@
1
- import type { DeepReadonly } from 'vue';
1
+ import type { Component, DeepReadonly } from 'vue';
2
+ import { type ChatActionPayload } from '../../composables/useChatAction.js';
2
3
  import type { WorkspaceSettings } from '../../composables/useWorkspaceSettings.js';
3
4
  import type { ChatLoadingMessageMode } from '../../utils/chat-loading-messages.js';
4
5
  import type { ChatActor, ChatFeatureConfig } from '../../utils/tela-chat.js';
@@ -12,11 +13,15 @@ type __VLS_Props = {
12
13
  features?: Partial<ChatFeatureConfig>;
13
14
  loadingMessages?: readonly string[] | null;
14
15
  loadingMessagesMode?: ChatLoadingMessageMode | null;
16
+ customComponents?: Record<string, Component>;
17
+ customHtmlTags?: readonly string[];
15
18
  };
16
19
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
17
20
  "update:conversationId": (value: string | null) => any;
21
+ action: (payload: ChatActionPayload) => any;
18
22
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
19
23
  "onUpdate:conversationId"?: ((value: string | null) => any) | undefined;
24
+ onAction?: ((payload: ChatActionPayload) => any) | undefined;
20
25
  }>, {
21
26
  initialConversationId: string | null;
22
27
  hideSidebar: boolean;
@@ -131,7 +131,7 @@ export default defineEventHandler(async (event) => {
131
131
  }
132
132
  if (agentSession.status === "completed" || agentSession.status === "waiting_messages") {
133
133
  if (hasNewReasoning) {
134
- const content = extractContentFromSession(agentSession, newReasoning);
134
+ const content = extractContentFromSession(agentSession);
135
135
  updateData.content = content;
136
136
  updateData.status = "completed";
137
137
  updateData.updatedAt = /* @__PURE__ */ new Date();
@@ -9,6 +9,7 @@ import { logger } from "#chat-runtime/server/utils/logger";
9
9
  import {
10
10
  getConversationAppliedSettings,
11
11
  getWorkspaceSettingsUpdatedAt,
12
+ parseWorkspaceSettingsSnapshot,
12
13
  resolveWorkspaceSettings,
13
14
  serializeResolvedWorkspaceSettings
14
15
  } from "#chat-runtime/server/utils/chat-workspace-settings";
@@ -53,14 +54,16 @@ export default defineEventHandler(async (event) => {
53
54
  });
54
55
  }
55
56
  const effectiveModel = body.model ?? (isValidModel(conversation.model) ? conversation.model : void 0);
56
- const trustedSettings = conversation.appliedSettingsSnapshot ? null : await resolveWorkspaceSettings(event, context, db);
57
- const appliedSettings = getConversationAppliedSettings(conversation, trustedSettings);
57
+ const explicitSettings = !conversation.appliedSettingsSnapshot && body.workspaceSettingsSnapshot !== void 0 ? parseWorkspaceSettingsSnapshot(body.workspaceSettingsSnapshot) : void 0;
58
+ const trustedSettings = conversation.appliedSettingsSnapshot || explicitSettings ? null : await resolveWorkspaceSettings(event, context, db);
59
+ const appliedSettings = getConversationAppliedSettings(conversation, trustedSettings, explicitSettings);
58
60
  const now = /* @__PURE__ */ new Date();
59
61
  const assistantCreatedAt = new Date(now.getTime() + 1);
60
- if (!conversation.appliedSettingsSnapshot && trustedSettings) {
62
+ const snapshotSettings = explicitSettings ?? trustedSettings;
63
+ if (!conversation.appliedSettingsSnapshot && snapshotSettings) {
61
64
  await db.update(schema.conversations).set({
62
- appliedSettingsAt: getWorkspaceSettingsUpdatedAt(trustedSettings),
63
- appliedSettingsSnapshot: serializeResolvedWorkspaceSettings(trustedSettings),
65
+ appliedSettingsAt: getWorkspaceSettingsUpdatedAt(snapshotSettings),
66
+ appliedSettingsSnapshot: serializeResolvedWorkspaceSettings(snapshotSettings),
64
67
  updatedAt: now
65
68
  }).where(and(
66
69
  eq(schema.conversations.id, conversationId),
@@ -1,6 +1,7 @@
1
1
  import { mkdir } from "node:fs/promises";
2
2
  import { dirname } from "node:path";
3
3
  import { useRuntimeConfig } from "nitropack/runtime";
4
+ import { sql } from "drizzle-orm";
4
5
  import postgres from "postgres";
5
6
  import { drizzle as drizzlePostgres } from "drizzle-orm/postgres-js";
6
7
  import { migrate as migratePostgres } from "drizzle-orm/postgres-js/migrator";
@@ -9,6 +10,9 @@ import { CHAT_SCHEMA_NAME } from "./schema/_schema.js";
9
10
  import { logger } from "../utils/logger.js";
10
11
  let dbInstance = null;
11
12
  let dbInitPromise = null;
13
+ const LEGACY_MIGRATIONS_SCHEMA = "drizzle";
14
+ const MIGRATIONS_TABLE = "__drizzle_migrations";
15
+ const MIGRATIONS_LEDGER_LOCK_KEY = 581073927;
12
16
  export function isPgliteUrl(url) {
13
17
  return url.startsWith("pglite:") || url.startsWith("file:");
14
18
  }
@@ -25,6 +29,59 @@ function resolvePgliteDataDir(url) {
25
29
  return url;
26
30
  }
27
31
  const IGNORED_NOTICE_CODES = /* @__PURE__ */ new Set(["42P06", "42P07"]);
32
+ function getRows(result) {
33
+ if (Array.isArray(result)) {
34
+ return result;
35
+ }
36
+ if (result && typeof result === "object" && "rows" in result && Array.isArray(result.rows)) {
37
+ return result.rows;
38
+ }
39
+ return [];
40
+ }
41
+ async function tableExists(db, schemaName, tableName) {
42
+ const result = await db.execute(sql`
43
+ select exists (
44
+ select 1
45
+ from information_schema.tables
46
+ where table_schema = ${schemaName}
47
+ and table_name = ${tableName}
48
+ ) as "exists"
49
+ `);
50
+ const [row] = getRows(result);
51
+ return row?.exists ?? false;
52
+ }
53
+ async function tableHasRows(db, schemaName, tableName) {
54
+ const result = await db.execute(sql`
55
+ select exists (
56
+ select 1
57
+ from ${sql.identifier(schemaName)}.${sql.identifier(tableName)}
58
+ ) as "exists"
59
+ `);
60
+ const [row] = getRows(result);
61
+ return row?.exists ?? false;
62
+ }
63
+ async function seedChatMigrationsLedger(db) {
64
+ await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.identifier(CHAT_SCHEMA_NAME)}`);
65
+ await db.execute(sql`
66
+ CREATE TABLE IF NOT EXISTS ${sql.identifier(CHAT_SCHEMA_NAME)}.${sql.identifier(MIGRATIONS_TABLE)} (
67
+ id SERIAL PRIMARY KEY,
68
+ hash text NOT NULL,
69
+ created_at bigint
70
+ )
71
+ `);
72
+ if (await tableHasRows(db, CHAT_SCHEMA_NAME, MIGRATIONS_TABLE)) {
73
+ return;
74
+ }
75
+ if (!await tableExists(db, LEGACY_MIGRATIONS_SCHEMA, MIGRATIONS_TABLE)) {
76
+ return;
77
+ }
78
+ await db.execute(sql`
79
+ INSERT INTO ${sql.identifier(CHAT_SCHEMA_NAME)}.${sql.identifier(MIGRATIONS_TABLE)} ("hash", "created_at")
80
+ SELECT "hash", "created_at"
81
+ FROM ${sql.identifier(LEGACY_MIGRATIONS_SCHEMA)}.${sql.identifier(MIGRATIONS_TABLE)}
82
+ ORDER BY "created_at"
83
+ `);
84
+ }
28
85
  async function initPostgresAsync(databaseUrl, migrationsDir) {
29
86
  const client = postgres(databaseUrl, {
30
87
  max: 10,
@@ -36,6 +93,10 @@ async function initPostgresAsync(databaseUrl, migrationsDir) {
36
93
  });
37
94
  const db = drizzlePostgres(client, { schema });
38
95
  if (migrationsDir) {
96
+ await db.transaction(async (tx) => {
97
+ await tx.execute(sql`SELECT pg_advisory_xact_lock(${MIGRATIONS_LEDGER_LOCK_KEY})`);
98
+ await seedChatMigrationsLedger(tx);
99
+ });
39
100
  await migratePostgres(db, { migrationsFolder: migrationsDir, migrationsSchema: CHAT_SCHEMA_NAME });
40
101
  logger.info({ migrationsSchema: CHAT_SCHEMA_NAME }, "Postgres connection pool initialized and migrations applied");
41
102
  } else {
@@ -54,6 +115,7 @@ async function initPgliteAsync(databaseUrl, migrationsDir) {
54
115
  const client = new PGlite(dataDir);
55
116
  const db = drizzle(client, { schema });
56
117
  if (migrationsDir) {
118
+ await seedChatMigrationsLedger(db);
57
119
  await migrate(db, { migrationsFolder: migrationsDir, migrationsSchema: CHAT_SCHEMA_NAME });
58
120
  logger.info({ dataDir, migrationsSchema: CHAT_SCHEMA_NAME }, "pglite database initialized and migrated");
59
121
  } else {
@@ -36,4 +36,4 @@ export declare function cancelSession(token: string, sessionId: string): Promise
36
36
  error: string;
37
37
  }>;
38
38
  export declare function extractContentFromReasoning(reasoning: ReasoningItem[]): string;
39
- export declare function extractContentFromSession(session: AgentApiSession, turnReasoning?: ReasoningItem[]): string;
39
+ export declare function extractContentFromSession(session: AgentApiSession): string;
@@ -150,11 +150,8 @@ export function extractContentFromReasoning(reasoning) {
150
150
  }
151
151
  return "";
152
152
  }
153
- export function extractContentFromSession(session, turnReasoning) {
154
- if (turnReasoning !== void 0) {
155
- return extractContentFromReasoning(turnReasoning);
156
- }
157
- if (session.result?.content) {
153
+ export function extractContentFromSession(session) {
154
+ if (typeof session.result?.content === "string") {
158
155
  return session.result.content;
159
156
  }
160
157
  if (session.result?.structuredContent) {
@@ -8,8 +8,9 @@ export declare function normalizeResolvedWorkspaceSettings(settings: ResolvedWor
8
8
  export declare function serializeResolvedWorkspaceSettings(settings: ResolvedWorkspaceSettings): ResolvedWorkspaceSettings;
9
9
  export declare function getConversationAppliedSettings(conversation: {
10
10
  appliedSettingsSnapshot?: ResolvedWorkspaceSettingsInput | null;
11
- }, trustedSettings?: ResolvedWorkspaceSettingsInput | null): ResolvedWorkspaceSettings;
11
+ }, trustedSettings?: ResolvedWorkspaceSettingsInput | null, explicitSettings?: ResolvedWorkspaceSettingsInput | null): ResolvedWorkspaceSettings;
12
12
  export declare function hasMaterialWorkspaceSettingsMismatch(currentSettings: ResolvedWorkspaceSettingsInput | null | undefined, trustedSettings: ResolvedWorkspaceSettingsInput | null | undefined): boolean;
13
+ export declare function parseWorkspaceSettingsSnapshot(settings: unknown): ResolvedWorkspaceSettings;
13
14
  export declare function resolveDefaultWorkspaceSettings(db: DbInstance, workspaceId: string): Promise<ResolvedWorkspaceSettings>;
14
15
  export declare function resolveWorkspaceSettings(event: H3Event, context: ChatContext, db: DbInstance): Promise<ResolvedWorkspaceSettings>;
15
16
  export declare function getWorkspaceSettingsUpdatedAt(settings: ResolvedWorkspaceSettings): Date;
@@ -40,10 +40,13 @@ export function serializeResolvedWorkspaceSettings(settings) {
40
40
  updatedAt: settings.updatedAt instanceof Date ? settings.updatedAt.toISOString() : settings.updatedAt
41
41
  };
42
42
  }
43
- export function getConversationAppliedSettings(conversation, trustedSettings) {
43
+ export function getConversationAppliedSettings(conversation, trustedSettings, explicitSettings) {
44
44
  if (conversation.appliedSettingsSnapshot) {
45
45
  return normalizeResolvedWorkspaceSettings(conversation.appliedSettingsSnapshot);
46
46
  }
47
+ if (explicitSettings !== void 0) {
48
+ return normalizeResolvedWorkspaceSettings(explicitSettings);
49
+ }
47
50
  return normalizeResolvedWorkspaceSettings(trustedSettings);
48
51
  }
49
52
  export function hasMaterialWorkspaceSettingsMismatch(currentSettings, trustedSettings) {
@@ -69,6 +72,12 @@ function parseResolverResult(settings) {
69
72
  }
70
73
  return normalizeResolvedWorkspaceSettings(parsedSettings.data);
71
74
  }
75
+ export function parseWorkspaceSettingsSnapshot(settings) {
76
+ if (settings === null) {
77
+ return normalizeResolvedWorkspaceSettings(null);
78
+ }
79
+ return parseResolverResult(settings);
80
+ }
72
81
  export async function resolveDefaultWorkspaceSettings(db, workspaceId) {
73
82
  const [settings, skillRows] = await Promise.all([
74
83
  db.select().from(schema.workspaceSettings).where(eq(schema.workspaceSettings.workspaceId, workspaceId)),
@@ -0,0 +1,9 @@
1
+ import { type Component } from 'vue';
2
+ import type { BaseNode } from 'markstream-vue';
3
+ export type CustomMarkdownNode = BaseNode & {
4
+ attrs?: [string, unknown][];
5
+ content?: string;
6
+ };
7
+ export declare function customMarkdownNodeProps(node: CustomMarkdownNode): Record<string, unknown>;
8
+ export declare function wrapCustomMarkdownComponent(component: Component, tagName?: string): Component;
9
+ export declare function wrapCustomMarkdownComponents(components: Record<string, Component>): Record<string, Component>;
@@ -0,0 +1,54 @@
1
+ import { defineComponent, h } from "vue";
2
+ function camelizeAttributeName(name) {
3
+ return name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
4
+ }
5
+ export function customMarkdownNodeProps(node) {
6
+ const attrProps = {};
7
+ for (const attr of node.attrs ?? []) {
8
+ const [name, value] = attr;
9
+ if (!name)
10
+ continue;
11
+ const propName = camelizeAttributeName(name);
12
+ if (propName === "node")
13
+ continue;
14
+ attrProps[propName] = value;
15
+ }
16
+ return { node, ...attrProps };
17
+ }
18
+ function customMarkdownComponentAdapterName(tagName, component) {
19
+ if (typeof component === "function" && component.name)
20
+ return component.name;
21
+ if (typeof component === "object" && component) {
22
+ const namedComponent = component;
23
+ return namedComponent.displayName ?? namedComponent.name ?? tagName;
24
+ }
25
+ return tagName;
26
+ }
27
+ export function wrapCustomMarkdownComponent(component, tagName = "unknown") {
28
+ return defineComponent({
29
+ name: `CustomMarkdownComponentAdapter(${customMarkdownComponentAdapterName(tagName, component)})`,
30
+ props: {
31
+ node: {
32
+ type: Object,
33
+ required: true
34
+ }
35
+ },
36
+ setup(props, { attrs }) {
37
+ return () => h(
38
+ component,
39
+ {
40
+ ...attrs,
41
+ ...customMarkdownNodeProps(props.node)
42
+ },
43
+ {
44
+ default: () => props.node.content ?? ""
45
+ }
46
+ );
47
+ }
48
+ });
49
+ }
50
+ export function wrapCustomMarkdownComponents(components) {
51
+ return Object.fromEntries(
52
+ Object.entries(components).map(([name, component]) => [name, wrapCustomMarkdownComponent(component, name)])
53
+ );
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {