@8wave/ai-elements 0.114.0 → 0.115.0-beta.16

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 (62) hide show
  1. package/dist/_chunks/{PkToolRequestOAuthConnection-ChGMAKV7.js → PkToolRequestOAuthConnection-CgQePKKb.js} +3 -3
  2. package/dist/_chunks/PkToolRequestOAuthConnection-CgQePKKb.js.map +1 -0
  3. package/dist/_chunks/{PkToolShowSources-VFoI0YS8.js → PkToolShowSources-kGQlIKaN.js} +2 -2
  4. package/dist/_chunks/{PkToolShowSources-VFoI0YS8.js.map → PkToolShowSources-kGQlIKaN.js.map} +1 -1
  5. package/dist/_chunks/{markdown-BL7WANiX.js → markdown-B4cYQGBo.js} +19 -10
  6. package/dist/_chunks/{markdown-BL7WANiX.js.map → markdown-B4cYQGBo.js.map} +1 -1
  7. package/dist/ai-elements.es.js +1670 -1563
  8. package/dist/ai-elements.es.js.map +1 -1
  9. package/dist-vue/PkChatbot.js +1 -1
  10. package/dist-vue/PkChatbotMessages.js +1 -1
  11. package/dist-vue/PkChatbotViewChat.js +1 -1
  12. package/dist-vue/PkChatbotViewConversations.js +1 -1
  13. package/dist-vue/PkChatbotViewFiles.js +1 -1
  14. package/dist-vue/PkChatbotViewProfile.js +1 -1
  15. package/dist-vue/_chunks/{Document-nCWwJC2h.js → Document-BWz2HySu.js} +20 -11
  16. package/dist-vue/_chunks/{Document-nCWwJC2h.js.map → Document-BWz2HySu.js.map} +1 -1
  17. package/dist-vue/_chunks/{PkChatbot-BMIs94QB.js → PkChatbot-Cw5sScma.js} +6 -6
  18. package/dist-vue/_chunks/{PkChatbot-BMIs94QB.js.map → PkChatbot-Cw5sScma.js.map} +1 -1
  19. package/dist-vue/_chunks/PkChatbotAuth-jzoNJkmH.js.map +1 -1
  20. package/dist-vue/_chunks/{PkChatbotMessages-Bj7mjhqN.js → PkChatbotMessages-62rYpwcM.js} +110 -101
  21. package/dist-vue/_chunks/PkChatbotMessages-62rYpwcM.js.map +1 -0
  22. package/dist-vue/_chunks/{PkChatbotViewChat-CQ5WcZs8.js → PkChatbotViewChat-ML8aaCVN.js} +4 -4
  23. package/dist-vue/_chunks/{PkChatbotViewChat-CQ5WcZs8.js.map → PkChatbotViewChat-ML8aaCVN.js.map} +1 -1
  24. package/dist-vue/_chunks/{PkChatbotViewConversations-CHpN4vhm.js → PkChatbotViewConversations-DvmRjVND.js} +2 -2
  25. package/dist-vue/_chunks/{PkChatbotViewConversations-CHpN4vhm.js.map → PkChatbotViewConversations-DvmRjVND.js.map} +1 -1
  26. package/dist-vue/_chunks/{PkChatbotViewFiles-Bk9-K6Hw.js → PkChatbotViewFiles-DrMUsTv5.js} +3 -3
  27. package/dist-vue/_chunks/{PkChatbotViewFiles-Bk9-K6Hw.js.map → PkChatbotViewFiles-DrMUsTv5.js.map} +1 -1
  28. package/dist-vue/_chunks/{PkChatbotViewProfile-CosaHA-g.js → PkChatbotViewProfile-C1eLHU7x.js} +2 -2
  29. package/dist-vue/_chunks/{PkChatbotViewProfile-CosaHA-g.js.map → PkChatbotViewProfile-C1eLHU7x.js.map} +1 -1
  30. package/dist-vue/_chunks/{PkToolRequestOAuthConnection-DMvCGPuX.js → PkToolRequestOAuthConnection-a2LegFoE.js} +3 -3
  31. package/dist-vue/_chunks/PkToolRequestOAuthConnection-a2LegFoE.js.map +1 -0
  32. package/dist-vue/_chunks/{PkToolShowDiagram-BziggKVa.js → PkToolShowDiagram-CssUiP3o.js} +2 -2
  33. package/dist-vue/_chunks/{PkToolShowDiagram-BziggKVa.js.map → PkToolShowDiagram-CssUiP3o.js.map} +1 -1
  34. package/dist-vue/_chunks/{PkToolShowSources-TzkNgwnr.js → PkToolShowSources-C8r1vKJk.js} +2 -2
  35. package/dist-vue/_chunks/{PkToolShowSources-TzkNgwnr.js.map → PkToolShowSources-C8r1vKJk.js.map} +1 -1
  36. package/dist-vue/_chunks/{createChatbotApiClient-BlWs282E.js → createChatbotApiClient-BJz36z7v.js} +110 -110
  37. package/dist-vue/_chunks/createChatbotApiClient-BJz36z7v.js.map +1 -0
  38. package/dist-vue/_chunks/{useChatbotStore-CMo47fnS.js → useChatbotStore-D3Q1ZgQg.js} +2 -2
  39. package/dist-vue/_chunks/{useChatbotStore-CMo47fnS.js.map → useChatbotStore-D3Q1ZgQg.js.map} +1 -1
  40. package/dist-vue/api.js +1 -1
  41. package/dist-vue/composables.js +108 -10
  42. package/dist-vue/composables.js.map +1 -1
  43. package/dist-vue/index.js +15 -12
  44. package/dist-vue/index.js.map +1 -1
  45. package/dist-vue/locales.js +4 -0
  46. package/dist-vue/packages/auth/src/active-membership.d.ts +36 -0
  47. package/dist-vue/packages/auth/src/add-member-to-organization.d.ts +5 -1
  48. package/dist-vue/packages/auth/src/passkey-policy-client-plugin.d.ts +1 -1
  49. package/dist-vue/packages/auth/src/two-factor-policy-client-plugin.d.ts +3 -3
  50. package/dist-vue/packages/auth/src/two-factor-policy-plugin.d.ts +1 -1
  51. package/dist-vue/packages/components/src/chat/PkChatbotMessages.d.ts +5 -0
  52. package/dist-vue/packages/models/src/schema/AgentApiTool.d.ts +40 -8
  53. package/dist-vue/packages/models/src/schema/ConversationAnalysis.d.ts +6 -1
  54. package/dist-vue/packages/models/src/schema/Document.d.ts +477 -0
  55. package/dist-vue/packages/models/src/schema/McpServer.d.ts +56 -0
  56. package/dist-vue/packages/models/src/schema/Notification.d.ts +2 -0
  57. package/dist-vue/packages/models/src/schema/SignInSecurity.d.ts +1 -1
  58. package/package.json +1 -1
  59. package/dist/_chunks/PkToolRequestOAuthConnection-ChGMAKV7.js.map +0 -1
  60. package/dist-vue/_chunks/PkChatbotMessages-Bj7mjhqN.js.map +0 -1
  61. package/dist-vue/_chunks/PkToolRequestOAuthConnection-DMvCGPuX.js.map +0 -1
  62. package/dist-vue/_chunks/createChatbotApiClient-BlWs282E.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { n as e } from "./useChatbotStore-CMo47fnS.js";
1
+ import { n as e } from "./useChatbotStore-D3Q1ZgQg.js";
2
2
  import "./src-DT8FLE4_.js";
3
3
  import { t } from "./markdown-CCMpGUA7.js";
4
4
  import { n } from "./message-RohfrdWS.js";
@@ -192,4 +192,4 @@ var oe = { class: "flex flex-col flex-1 min-h-0 relative" }, se = {
192
192
  //#endregion
193
193
  export { O as n, k as r, V as t };
194
194
 
195
- //# sourceMappingURL=PkChatbotViewConversations-CHpN4vhm.js.map
195
+ //# sourceMappingURL=PkChatbotViewConversations-DvmRjVND.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkChatbotViewConversations-CHpN4vhm.js","names":[],"sources":["../../../../packages/composable/src/chatbot/useConversationActions.ts","../../../../packages/components/src/chat/PkChatbotViewConversations.vue","../../../../packages/components/src/chat/PkChatbotViewConversations.vue"],"sourcesContent":["import { ref } from 'vue'\nimport type { Chat as Conversation } from 'models'\nimport { getTextPart, stripMarkdown } from 'utils'\nimport { useDialogConfirm } from '../useDialogConfirm'\n\n/**\n * Single-line preview for a conversation: explicit title, otherwise the\n * first user message. Used as list label and as search haystack.\n */\nexport function getConversationPreview(conversation: Conversation): string {\n return (\n stripMarkdown(conversation.title) ||\n stripMarkdown(\n getTextPart(\n // The list endpoint isn't Zod-parsed: don't trust the\n // declared types at runtime\n conversation.messages?.find(\n (message) => message.role === 'user',\n ),\n ),\n ) ||\n ''\n )\n}\n\n/**\n * Inline-rename and delete-with-confirm state shared by the conversation\n * lists (PkChatbotViewConversations, PkChatSidebar). Renames are optimistic:\n * on failure the next conversations load reconciles with the server state.\n */\nexport function useConversationActions(actions: {\n renameChatTitle: (chatId: string, title: string) => Promise<unknown>\n deleteChat: (chatId: string) => Promise<unknown>\n}) {\n const editingId = ref<string | null>(null)\n const editingTitle = ref('')\n\n const startEdit = (conversation: Conversation, fallbackTitle = '') => {\n editingId.value = conversation.id\n editingTitle.value = stripMarkdown(conversation.title) || fallbackTitle\n }\n\n const confirmEdit = async (id: string) => {\n const trimmed = editingTitle.value.trim()\n editingId.value = null\n if (trimmed && trimmed.length <= 255) {\n try {\n await actions.renameChatTitle(id, trimmed)\n } catch {\n // Revert optimistically: reload will pick up server state\n }\n }\n }\n\n const cancelEdit = () => {\n editingId.value = null\n }\n\n const onRenameKeydown = (event: KeyboardEvent, id: string) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n confirmEdit(id)\n } else if (event.key === 'Escape') {\n event.preventDefault()\n cancelEdit()\n }\n }\n\n const { openDialogConfirm } = useDialogConfirm()\n const requestDelete = async (id: string) => {\n try {\n const proceed = await openDialogConfirm()\n if (!proceed) {\n return\n }\n await actions.deleteChat(id)\n } catch {\n // Silently fail: next load will reconcile\n }\n }\n\n return {\n editingId,\n editingTitle,\n startEdit,\n confirmEdit,\n cancelEdit,\n onRenameKeydown,\n requestDelete,\n }\n}\n","<script setup lang=\"ts\">\n import { computed, inject, ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type { Chat as Conversation, UIChatMessage } from 'models'\n import { stripMarkdown, getTextPart } from 'utils'\n import {\n useChatbotStore,\n useConversationActions,\n useDialogConfirm,\n } from 'composables'\n import PkRelativeTime from '../PkRelativeTime.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const store = useChatbotStore(props.agentId)\n const { conversations, isLoadingConversations, localChatId } =\n storeToRefs(store)\n const { navigate, startNewChat, renameChatTitle, deleteChat } = store\n\n const {\n editingId,\n editingTitle,\n startEdit: startEditConversation,\n confirmEdit,\n cancelEdit,\n onRenameKeydown,\n requestDelete: handleDelete,\n } = useConversationActions({ renameChatTitle, deleteChat })\n\n const openConversation = (id: string) => {\n if (editingId.value) {\n return\n }\n localChatId.value = id\n navigate('chat')\n }\n\n const isCurrentConversation = (conversation: Conversation) => {\n return localChatId.value === conversation.id\n }\n\n const getLastMessage = (\n messages: UIChatMessage[],\n role: 'user' | 'assistant',\n ) => {\n return stripMarkdown(\n getTextPart(messages.filter((m) => m.role === role).slice(-1)[0]) ||\n '',\n )\n }\n\n const startEdit = (conversation: Conversation) => {\n startEditConversation(\n conversation,\n getLastMessage(conversation.messages, 'assistant'),\n )\n }\n\n const { openDialogConfirm } = useDialogConfirm()\n\n // GDPR B.3 — bulk \"delete my conversations\", available to anonymous\n // sessions too (the host wires the endpoint through the profile context).\n // Hidden when the host application owns the session (external token or\n // host-session mode): the endpoint erases ALL conversations of the\n // session's user platform-wide, an account-scoped action that belongs to\n // the host, not to an embedded widget. Same gating as sign-out and\n // manage-profile in PkChatbotViewProfile.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canEraseConversations = computed(\n () =>\n !!profile?.eraseConversations &&\n !(profile.isExternalAuth?.value ?? false),\n )\n const isErasingConversations = ref(false)\n const handleEraseAll = async () => {\n const eraseConversations = profile?.eraseConversations\n if (!eraseConversations || !canEraseConversations.value) {\n return\n }\n const proceed = await openDialogConfirm({\n title: $t('confirmTitle.deleteMyConversations'),\n questionLabel: $t('message.deleteMyConversationsConfirm'),\n })\n if (!proceed) {\n return\n }\n isErasingConversations.value = true\n try {\n await eraseConversations()\n conversations.value = []\n startNewChat()\n } catch {\n // Same policy as single delete: fail silently and reconcile\n // with the server state\n store.loadConversations(props.agentId)\n } finally {\n isErasingConversations.value = false\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 relative\">\n <!-- Loading skeleton -->\n <div\n v-if=\"isLoadingConversations && conversations.length === 0\"\n class=\"u-pick-list__list u-pick-list__list--fill\">\n <div\n v-for=\"i in 5\"\n :key=\"i\"\n class=\"u-pick-list__row u-pick-list__row--skeleton\">\n <div class=\"u-pick-list__entry u-pick-list__entry--roomy\">\n <div class=\"flex flex-col gap-6 flex-1 min-w-0\">\n <div\n class=\"vv-skeleton h-16 rounded\"\n :class=\"i % 2 ? 'w-192' : 'w-128'\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded w-160\">\n <div class=\"vv-skeleton__item\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"conversations.length === 0\"\n class=\"flex flex-col flex-1 items-center justify-center gap-12 p-24 text-center\">\n <div\n class=\"w-64 h-64 rounded-full bg-surface-1 border border-surface-3 flex items-center justify-center\">\n <VvIcon name=\"ri:chat-3-line\" class=\"w-28 h-28 text-word-4\" />\n </div>\n <div class=\"flex flex-col gap-4\">\n <strong class=\"text-16 font-semibold text-word-1\">\n {{ $t('message.noConversations') }}\n </strong>\n <span class=\"text-14 text-word-3 leading-relaxed\">\n {{ $t('message.noConversationsHint') }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary rounded\"\n icon=\"ri:add-line\"\n class=\"mt-8\"\n :label=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n </div>\n\n <!-- Conversation list. The row is a container, not a button: the menu\n and the rename field are siblings of the entry, because a button\n cannot legally contain other controls (Firefox and Safari drop\n them from the accessibility tree). -->\n <ul\n v-else\n class=\"u-pick-list__list u-pick-list__list--fill overflow-y-auto pb-64 light-scrollbar\">\n <li\n v-for=\"conversation in conversations\"\n :key=\"conversation.id\"\n class=\"u-pick-list__row\"\n :class=\"{\n 'u-pick-list__row--current':\n isCurrentConversation(conversation),\n }\">\n <div\n v-if=\"editingId === conversation.id\"\n class=\"flex flex-1 items-center gap-8 min-w-0 px-md py-12\">\n <VvInputText\n v-model=\"editingTitle\"\n class=\"flex-1 mb-0\"\n modifiers=\"compact\"\n autofocus\n select-on-focus\n :maxlength=\"255\"\n :placeholder=\"$t('placeholder.insert')\"\n @keydown=\"onRenameKeydown($event, conversation.id)\" />\n <VvButtonGroup modifiers=\"compact\">\n <VvButton\n icon=\"ri:save-line\"\n :title=\"$t('action.save')\"\n modifiers=\"action-quiet-small\"\n @click=\"confirmEdit(conversation.id)\" />\n <VvButton\n icon=\"ri:close-line\"\n :title=\"$t('action.cancel')\"\n modifiers=\"action-quiet-small\"\n @click=\"cancelEdit()\" />\n </VvButtonGroup>\n </div>\n <template v-else>\n <button\n type=\"button\"\n class=\"u-pick-list__entry u-pick-list__entry--roomy leading-relaxed\"\n :aria-current=\"\n isCurrentConversation(conversation)\n ? 'true'\n : undefined\n \"\n @click=\"openConversation(conversation.id)\">\n <div class=\"min-w-0 flex-1\">\n <div class=\"flex items-center gap-8\">\n <strong\n class=\"u-pick-list__label font-medium flex-1\">\n {{\n stripMarkdown(conversation.title) ||\n getLastMessage(\n conversation.messages,\n 'assistant',\n )\n }}\n </strong>\n <PkRelativeTime\n class=\"text-word-4 text-smaller shrink-0\"\n :date=\"conversation.lastMessageAt\" />\n </div>\n <span class=\"text-12 text-word-4 line-clamp-2\">\n {{\n stripMarkdown(conversation.summary) ||\n getLastMessage(\n conversation.messages,\n 'user',\n )\n }}\n </span>\n </div>\n </button>\n <div class=\"u-pick-list__actions\">\n <VvDropdown\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n icon=\"ri:more-2-fill\"\n :title=\"$t('action.moreActions')\"\n modifiers=\"action-quiet-small\" />\n <template #items>\n <VvDropdownAction\n @click=\"startEdit(conversation)\">\n <VvIcon name=\"ri:pencil-line\" />\n {{ $t('action.renameChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n @click=\"handleDelete(conversation.id)\">\n <VvIcon name=\"ri:delete-bin-line\" />\n {{ $t('action.delete') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n </div>\n </template>\n </li>\n </ul>\n\n <!-- Sticky \"new chat\" CTA (hidden on empty state, which has its own CTA) -->\n <div\n v-if=\"conversations.length > 0\"\n class=\"absolute bottom-0 left-0 right-0 flex flex-col items-center gap-4 px-16 pb-16 pt-32 pointer-events-none\"\n :style=\"{\n background:\n 'linear-gradient(to top, var(--color-surface) 55%, transparent)',\n }\">\n <VvButton\n class=\"text-14 pointer-events-auto\"\n modifiers=\"primary rounded\"\n icon=\"ri:add-line\"\n :label=\"$t('action.startNewChat')\"\n @click.stop=\"startNewChat()\" />\n <VvButton\n v-if=\"canEraseConversations\"\n class=\"text-12 pointer-events-auto\"\n modifiers=\"action-quiet-danger\"\n icon=\"ri:delete-bin-line\"\n :loading=\"isErasingConversations\"\n :disabled=\"isErasingConversations\"\n :label=\"$t('action.deleteMyConversations')\"\n @click.stop=\"handleEraseAll()\" />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, inject, ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type { Chat as Conversation, UIChatMessage } from 'models'\n import { stripMarkdown, getTextPart } from 'utils'\n import {\n useChatbotStore,\n useConversationActions,\n useDialogConfirm,\n } from 'composables'\n import PkRelativeTime from '../PkRelativeTime.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const store = useChatbotStore(props.agentId)\n const { conversations, isLoadingConversations, localChatId } =\n storeToRefs(store)\n const { navigate, startNewChat, renameChatTitle, deleteChat } = store\n\n const {\n editingId,\n editingTitle,\n startEdit: startEditConversation,\n confirmEdit,\n cancelEdit,\n onRenameKeydown,\n requestDelete: handleDelete,\n } = useConversationActions({ renameChatTitle, deleteChat })\n\n const openConversation = (id: string) => {\n if (editingId.value) {\n return\n }\n localChatId.value = id\n navigate('chat')\n }\n\n const isCurrentConversation = (conversation: Conversation) => {\n return localChatId.value === conversation.id\n }\n\n const getLastMessage = (\n messages: UIChatMessage[],\n role: 'user' | 'assistant',\n ) => {\n return stripMarkdown(\n getTextPart(messages.filter((m) => m.role === role).slice(-1)[0]) ||\n '',\n )\n }\n\n const startEdit = (conversation: Conversation) => {\n startEditConversation(\n conversation,\n getLastMessage(conversation.messages, 'assistant'),\n )\n }\n\n const { openDialogConfirm } = useDialogConfirm()\n\n // GDPR B.3 — bulk \"delete my conversations\", available to anonymous\n // sessions too (the host wires the endpoint through the profile context).\n // Hidden when the host application owns the session (external token or\n // host-session mode): the endpoint erases ALL conversations of the\n // session's user platform-wide, an account-scoped action that belongs to\n // the host, not to an embedded widget. Same gating as sign-out and\n // manage-profile in PkChatbotViewProfile.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canEraseConversations = computed(\n () =>\n !!profile?.eraseConversations &&\n !(profile.isExternalAuth?.value ?? false),\n )\n const isErasingConversations = ref(false)\n const handleEraseAll = async () => {\n const eraseConversations = profile?.eraseConversations\n if (!eraseConversations || !canEraseConversations.value) {\n return\n }\n const proceed = await openDialogConfirm({\n title: $t('confirmTitle.deleteMyConversations'),\n questionLabel: $t('message.deleteMyConversationsConfirm'),\n })\n if (!proceed) {\n return\n }\n isErasingConversations.value = true\n try {\n await eraseConversations()\n conversations.value = []\n startNewChat()\n } catch {\n // Same policy as single delete: fail silently and reconcile\n // with the server state\n store.loadConversations(props.agentId)\n } finally {\n isErasingConversations.value = false\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 relative\">\n <!-- Loading skeleton -->\n <div\n v-if=\"isLoadingConversations && conversations.length === 0\"\n class=\"u-pick-list__list u-pick-list__list--fill\">\n <div\n v-for=\"i in 5\"\n :key=\"i\"\n class=\"u-pick-list__row u-pick-list__row--skeleton\">\n <div class=\"u-pick-list__entry u-pick-list__entry--roomy\">\n <div class=\"flex flex-col gap-6 flex-1 min-w-0\">\n <div\n class=\"vv-skeleton h-16 rounded\"\n :class=\"i % 2 ? 'w-192' : 'w-128'\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded w-160\">\n <div class=\"vv-skeleton__item\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"conversations.length === 0\"\n class=\"flex flex-col flex-1 items-center justify-center gap-12 p-24 text-center\">\n <div\n class=\"w-64 h-64 rounded-full bg-surface-1 border border-surface-3 flex items-center justify-center\">\n <VvIcon name=\"ri:chat-3-line\" class=\"w-28 h-28 text-word-4\" />\n </div>\n <div class=\"flex flex-col gap-4\">\n <strong class=\"text-16 font-semibold text-word-1\">\n {{ $t('message.noConversations') }}\n </strong>\n <span class=\"text-14 text-word-3 leading-relaxed\">\n {{ $t('message.noConversationsHint') }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary rounded\"\n icon=\"ri:add-line\"\n class=\"mt-8\"\n :label=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n </div>\n\n <!-- Conversation list. The row is a container, not a button: the menu\n and the rename field are siblings of the entry, because a button\n cannot legally contain other controls (Firefox and Safari drop\n them from the accessibility tree). -->\n <ul\n v-else\n class=\"u-pick-list__list u-pick-list__list--fill overflow-y-auto pb-64 light-scrollbar\">\n <li\n v-for=\"conversation in conversations\"\n :key=\"conversation.id\"\n class=\"u-pick-list__row\"\n :class=\"{\n 'u-pick-list__row--current':\n isCurrentConversation(conversation),\n }\">\n <div\n v-if=\"editingId === conversation.id\"\n class=\"flex flex-1 items-center gap-8 min-w-0 px-md py-12\">\n <VvInputText\n v-model=\"editingTitle\"\n class=\"flex-1 mb-0\"\n modifiers=\"compact\"\n autofocus\n select-on-focus\n :maxlength=\"255\"\n :placeholder=\"$t('placeholder.insert')\"\n @keydown=\"onRenameKeydown($event, conversation.id)\" />\n <VvButtonGroup modifiers=\"compact\">\n <VvButton\n icon=\"ri:save-line\"\n :title=\"$t('action.save')\"\n modifiers=\"action-quiet-small\"\n @click=\"confirmEdit(conversation.id)\" />\n <VvButton\n icon=\"ri:close-line\"\n :title=\"$t('action.cancel')\"\n modifiers=\"action-quiet-small\"\n @click=\"cancelEdit()\" />\n </VvButtonGroup>\n </div>\n <template v-else>\n <button\n type=\"button\"\n class=\"u-pick-list__entry u-pick-list__entry--roomy leading-relaxed\"\n :aria-current=\"\n isCurrentConversation(conversation)\n ? 'true'\n : undefined\n \"\n @click=\"openConversation(conversation.id)\">\n <div class=\"min-w-0 flex-1\">\n <div class=\"flex items-center gap-8\">\n <strong\n class=\"u-pick-list__label font-medium flex-1\">\n {{\n stripMarkdown(conversation.title) ||\n getLastMessage(\n conversation.messages,\n 'assistant',\n )\n }}\n </strong>\n <PkRelativeTime\n class=\"text-word-4 text-smaller shrink-0\"\n :date=\"conversation.lastMessageAt\" />\n </div>\n <span class=\"text-12 text-word-4 line-clamp-2\">\n {{\n stripMarkdown(conversation.summary) ||\n getLastMessage(\n conversation.messages,\n 'user',\n )\n }}\n </span>\n </div>\n </button>\n <div class=\"u-pick-list__actions\">\n <VvDropdown\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n icon=\"ri:more-2-fill\"\n :title=\"$t('action.moreActions')\"\n modifiers=\"action-quiet-small\" />\n <template #items>\n <VvDropdownAction\n @click=\"startEdit(conversation)\">\n <VvIcon name=\"ri:pencil-line\" />\n {{ $t('action.renameChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n @click=\"handleDelete(conversation.id)\">\n <VvIcon name=\"ri:delete-bin-line\" />\n {{ $t('action.delete') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n </div>\n </template>\n </li>\n </ul>\n\n <!-- Sticky \"new chat\" CTA (hidden on empty state, which has its own CTA) -->\n <div\n v-if=\"conversations.length > 0\"\n class=\"absolute bottom-0 left-0 right-0 flex flex-col items-center gap-4 px-16 pb-16 pt-32 pointer-events-none\"\n :style=\"{\n background:\n 'linear-gradient(to top, var(--color-surface) 55%, transparent)',\n }\">\n <VvButton\n class=\"text-14 pointer-events-auto\"\n modifiers=\"primary rounded\"\n icon=\"ri:add-line\"\n :label=\"$t('action.startNewChat')\"\n @click.stop=\"startNewChat()\" />\n <VvButton\n v-if=\"canEraseConversations\"\n class=\"text-12 pointer-events-auto\"\n modifiers=\"action-quiet-danger\"\n icon=\"ri:delete-bin-line\"\n :loading=\"isErasingConversations\"\n :disabled=\"isErasingConversations\"\n :label=\"$t('action.deleteMyConversations')\"\n @click.stop=\"handleEraseAll()\" />\n </div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PkChatbotViewConversations-DvmRjVND.js","names":[],"sources":["../../../../packages/composable/src/chatbot/useConversationActions.ts","../../../../packages/components/src/chat/PkChatbotViewConversations.vue","../../../../packages/components/src/chat/PkChatbotViewConversations.vue"],"sourcesContent":["import { ref } from 'vue'\nimport type { Chat as Conversation } from 'models'\nimport { getTextPart, stripMarkdown } from 'utils'\nimport { useDialogConfirm } from '../useDialogConfirm'\n\n/**\n * Single-line preview for a conversation: explicit title, otherwise the\n * first user message. Used as list label and as search haystack.\n */\nexport function getConversationPreview(conversation: Conversation): string {\n return (\n stripMarkdown(conversation.title) ||\n stripMarkdown(\n getTextPart(\n // The list endpoint isn't Zod-parsed: don't trust the\n // declared types at runtime\n conversation.messages?.find(\n (message) => message.role === 'user',\n ),\n ),\n ) ||\n ''\n )\n}\n\n/**\n * Inline-rename and delete-with-confirm state shared by the conversation\n * lists (PkChatbotViewConversations, PkChatSidebar). Renames are optimistic:\n * on failure the next conversations load reconciles with the server state.\n */\nexport function useConversationActions(actions: {\n renameChatTitle: (chatId: string, title: string) => Promise<unknown>\n deleteChat: (chatId: string) => Promise<unknown>\n}) {\n const editingId = ref<string | null>(null)\n const editingTitle = ref('')\n\n const startEdit = (conversation: Conversation, fallbackTitle = '') => {\n editingId.value = conversation.id\n editingTitle.value = stripMarkdown(conversation.title) || fallbackTitle\n }\n\n const confirmEdit = async (id: string) => {\n const trimmed = editingTitle.value.trim()\n editingId.value = null\n if (trimmed && trimmed.length <= 255) {\n try {\n await actions.renameChatTitle(id, trimmed)\n } catch {\n // Revert optimistically: reload will pick up server state\n }\n }\n }\n\n const cancelEdit = () => {\n editingId.value = null\n }\n\n const onRenameKeydown = (event: KeyboardEvent, id: string) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n confirmEdit(id)\n } else if (event.key === 'Escape') {\n event.preventDefault()\n cancelEdit()\n }\n }\n\n const { openDialogConfirm } = useDialogConfirm()\n const requestDelete = async (id: string) => {\n try {\n const proceed = await openDialogConfirm()\n if (!proceed) {\n return\n }\n await actions.deleteChat(id)\n } catch {\n // Silently fail: next load will reconcile\n }\n }\n\n return {\n editingId,\n editingTitle,\n startEdit,\n confirmEdit,\n cancelEdit,\n onRenameKeydown,\n requestDelete,\n }\n}\n","<script setup lang=\"ts\">\n import { computed, inject, ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type { Chat as Conversation, UIChatMessage } from 'models'\n import { stripMarkdown, getTextPart } from 'utils'\n import {\n useChatbotStore,\n useConversationActions,\n useDialogConfirm,\n } from 'composables'\n import PkRelativeTime from '../PkRelativeTime.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const store = useChatbotStore(props.agentId)\n const { conversations, isLoadingConversations, localChatId } =\n storeToRefs(store)\n const { navigate, startNewChat, renameChatTitle, deleteChat } = store\n\n const {\n editingId,\n editingTitle,\n startEdit: startEditConversation,\n confirmEdit,\n cancelEdit,\n onRenameKeydown,\n requestDelete: handleDelete,\n } = useConversationActions({ renameChatTitle, deleteChat })\n\n const openConversation = (id: string) => {\n if (editingId.value) {\n return\n }\n localChatId.value = id\n navigate('chat')\n }\n\n const isCurrentConversation = (conversation: Conversation) => {\n return localChatId.value === conversation.id\n }\n\n const getLastMessage = (\n messages: UIChatMessage[],\n role: 'user' | 'assistant',\n ) => {\n return stripMarkdown(\n getTextPart(messages.filter((m) => m.role === role).slice(-1)[0]) ||\n '',\n )\n }\n\n const startEdit = (conversation: Conversation) => {\n startEditConversation(\n conversation,\n getLastMessage(conversation.messages, 'assistant'),\n )\n }\n\n const { openDialogConfirm } = useDialogConfirm()\n\n // GDPR B.3 — bulk \"delete my conversations\", available to anonymous\n // sessions too (the host wires the endpoint through the profile context).\n // Hidden when the host application owns the session (external token or\n // host-session mode): the endpoint erases ALL conversations of the\n // session's user platform-wide, an account-scoped action that belongs to\n // the host, not to an embedded widget. Same gating as sign-out and\n // manage-profile in PkChatbotViewProfile.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canEraseConversations = computed(\n () =>\n !!profile?.eraseConversations &&\n !(profile.isExternalAuth?.value ?? false),\n )\n const isErasingConversations = ref(false)\n const handleEraseAll = async () => {\n const eraseConversations = profile?.eraseConversations\n if (!eraseConversations || !canEraseConversations.value) {\n return\n }\n const proceed = await openDialogConfirm({\n title: $t('confirmTitle.deleteMyConversations'),\n questionLabel: $t('message.deleteMyConversationsConfirm'),\n })\n if (!proceed) {\n return\n }\n isErasingConversations.value = true\n try {\n await eraseConversations()\n conversations.value = []\n startNewChat()\n } catch {\n // Same policy as single delete: fail silently and reconcile\n // with the server state\n store.loadConversations(props.agentId)\n } finally {\n isErasingConversations.value = false\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 relative\">\n <!-- Loading skeleton -->\n <div\n v-if=\"isLoadingConversations && conversations.length === 0\"\n class=\"u-pick-list__list u-pick-list__list--fill\">\n <div\n v-for=\"i in 5\"\n :key=\"i\"\n class=\"u-pick-list__row u-pick-list__row--skeleton\">\n <div class=\"u-pick-list__entry u-pick-list__entry--roomy\">\n <div class=\"flex flex-col gap-6 flex-1 min-w-0\">\n <div\n class=\"vv-skeleton h-16 rounded\"\n :class=\"i % 2 ? 'w-192' : 'w-128'\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded w-160\">\n <div class=\"vv-skeleton__item\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"conversations.length === 0\"\n class=\"flex flex-col flex-1 items-center justify-center gap-12 p-24 text-center\">\n <div\n class=\"w-64 h-64 rounded-full bg-surface-1 border border-surface-3 flex items-center justify-center\">\n <VvIcon name=\"ri:chat-3-line\" class=\"w-28 h-28 text-word-4\" />\n </div>\n <div class=\"flex flex-col gap-4\">\n <strong class=\"text-16 font-semibold text-word-1\">\n {{ $t('message.noConversations') }}\n </strong>\n <span class=\"text-14 text-word-3 leading-relaxed\">\n {{ $t('message.noConversationsHint') }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary rounded\"\n icon=\"ri:add-line\"\n class=\"mt-8\"\n :label=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n </div>\n\n <!-- Conversation list. The row is a container, not a button: the menu\n and the rename field are siblings of the entry, because a button\n cannot legally contain other controls (Firefox and Safari drop\n them from the accessibility tree). -->\n <ul\n v-else\n class=\"u-pick-list__list u-pick-list__list--fill overflow-y-auto pb-64 light-scrollbar\">\n <li\n v-for=\"conversation in conversations\"\n :key=\"conversation.id\"\n class=\"u-pick-list__row\"\n :class=\"{\n 'u-pick-list__row--current':\n isCurrentConversation(conversation),\n }\">\n <div\n v-if=\"editingId === conversation.id\"\n class=\"flex flex-1 items-center gap-8 min-w-0 px-md py-12\">\n <VvInputText\n v-model=\"editingTitle\"\n class=\"flex-1 mb-0\"\n modifiers=\"compact\"\n autofocus\n select-on-focus\n :maxlength=\"255\"\n :placeholder=\"$t('placeholder.insert')\"\n @keydown=\"onRenameKeydown($event, conversation.id)\" />\n <VvButtonGroup modifiers=\"compact\">\n <VvButton\n icon=\"ri:save-line\"\n :title=\"$t('action.save')\"\n modifiers=\"action-quiet-small\"\n @click=\"confirmEdit(conversation.id)\" />\n <VvButton\n icon=\"ri:close-line\"\n :title=\"$t('action.cancel')\"\n modifiers=\"action-quiet-small\"\n @click=\"cancelEdit()\" />\n </VvButtonGroup>\n </div>\n <template v-else>\n <button\n type=\"button\"\n class=\"u-pick-list__entry u-pick-list__entry--roomy leading-relaxed\"\n :aria-current=\"\n isCurrentConversation(conversation)\n ? 'true'\n : undefined\n \"\n @click=\"openConversation(conversation.id)\">\n <div class=\"min-w-0 flex-1\">\n <div class=\"flex items-center gap-8\">\n <strong\n class=\"u-pick-list__label font-medium flex-1\">\n {{\n stripMarkdown(conversation.title) ||\n getLastMessage(\n conversation.messages,\n 'assistant',\n )\n }}\n </strong>\n <PkRelativeTime\n class=\"text-word-4 text-smaller shrink-0\"\n :date=\"conversation.lastMessageAt\" />\n </div>\n <span class=\"text-12 text-word-4 line-clamp-2\">\n {{\n stripMarkdown(conversation.summary) ||\n getLastMessage(\n conversation.messages,\n 'user',\n )\n }}\n </span>\n </div>\n </button>\n <div class=\"u-pick-list__actions\">\n <VvDropdown\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n icon=\"ri:more-2-fill\"\n :title=\"$t('action.moreActions')\"\n modifiers=\"action-quiet-small\" />\n <template #items>\n <VvDropdownAction\n @click=\"startEdit(conversation)\">\n <VvIcon name=\"ri:pencil-line\" />\n {{ $t('action.renameChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n @click=\"handleDelete(conversation.id)\">\n <VvIcon name=\"ri:delete-bin-line\" />\n {{ $t('action.delete') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n </div>\n </template>\n </li>\n </ul>\n\n <!-- Sticky \"new chat\" CTA (hidden on empty state, which has its own CTA) -->\n <div\n v-if=\"conversations.length > 0\"\n class=\"absolute bottom-0 left-0 right-0 flex flex-col items-center gap-4 px-16 pb-16 pt-32 pointer-events-none\"\n :style=\"{\n background:\n 'linear-gradient(to top, var(--color-surface) 55%, transparent)',\n }\">\n <VvButton\n class=\"text-14 pointer-events-auto\"\n modifiers=\"primary rounded\"\n icon=\"ri:add-line\"\n :label=\"$t('action.startNewChat')\"\n @click.stop=\"startNewChat()\" />\n <VvButton\n v-if=\"canEraseConversations\"\n class=\"text-12 pointer-events-auto\"\n modifiers=\"action-quiet-danger\"\n icon=\"ri:delete-bin-line\"\n :loading=\"isErasingConversations\"\n :disabled=\"isErasingConversations\"\n :label=\"$t('action.deleteMyConversations')\"\n @click.stop=\"handleEraseAll()\" />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, inject, ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type { Chat as Conversation, UIChatMessage } from 'models'\n import { stripMarkdown, getTextPart } from 'utils'\n import {\n useChatbotStore,\n useConversationActions,\n useDialogConfirm,\n } from 'composables'\n import PkRelativeTime from '../PkRelativeTime.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const store = useChatbotStore(props.agentId)\n const { conversations, isLoadingConversations, localChatId } =\n storeToRefs(store)\n const { navigate, startNewChat, renameChatTitle, deleteChat } = store\n\n const {\n editingId,\n editingTitle,\n startEdit: startEditConversation,\n confirmEdit,\n cancelEdit,\n onRenameKeydown,\n requestDelete: handleDelete,\n } = useConversationActions({ renameChatTitle, deleteChat })\n\n const openConversation = (id: string) => {\n if (editingId.value) {\n return\n }\n localChatId.value = id\n navigate('chat')\n }\n\n const isCurrentConversation = (conversation: Conversation) => {\n return localChatId.value === conversation.id\n }\n\n const getLastMessage = (\n messages: UIChatMessage[],\n role: 'user' | 'assistant',\n ) => {\n return stripMarkdown(\n getTextPart(messages.filter((m) => m.role === role).slice(-1)[0]) ||\n '',\n )\n }\n\n const startEdit = (conversation: Conversation) => {\n startEditConversation(\n conversation,\n getLastMessage(conversation.messages, 'assistant'),\n )\n }\n\n const { openDialogConfirm } = useDialogConfirm()\n\n // GDPR B.3 — bulk \"delete my conversations\", available to anonymous\n // sessions too (the host wires the endpoint through the profile context).\n // Hidden when the host application owns the session (external token or\n // host-session mode): the endpoint erases ALL conversations of the\n // session's user platform-wide, an account-scoped action that belongs to\n // the host, not to an embedded widget. Same gating as sign-out and\n // manage-profile in PkChatbotViewProfile.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canEraseConversations = computed(\n () =>\n !!profile?.eraseConversations &&\n !(profile.isExternalAuth?.value ?? false),\n )\n const isErasingConversations = ref(false)\n const handleEraseAll = async () => {\n const eraseConversations = profile?.eraseConversations\n if (!eraseConversations || !canEraseConversations.value) {\n return\n }\n const proceed = await openDialogConfirm({\n title: $t('confirmTitle.deleteMyConversations'),\n questionLabel: $t('message.deleteMyConversationsConfirm'),\n })\n if (!proceed) {\n return\n }\n isErasingConversations.value = true\n try {\n await eraseConversations()\n conversations.value = []\n startNewChat()\n } catch {\n // Same policy as single delete: fail silently and reconcile\n // with the server state\n store.loadConversations(props.agentId)\n } finally {\n isErasingConversations.value = false\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 relative\">\n <!-- Loading skeleton -->\n <div\n v-if=\"isLoadingConversations && conversations.length === 0\"\n class=\"u-pick-list__list u-pick-list__list--fill\">\n <div\n v-for=\"i in 5\"\n :key=\"i\"\n class=\"u-pick-list__row u-pick-list__row--skeleton\">\n <div class=\"u-pick-list__entry u-pick-list__entry--roomy\">\n <div class=\"flex flex-col gap-6 flex-1 min-w-0\">\n <div\n class=\"vv-skeleton h-16 rounded\"\n :class=\"i % 2 ? 'w-192' : 'w-128'\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded w-160\">\n <div class=\"vv-skeleton__item\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"conversations.length === 0\"\n class=\"flex flex-col flex-1 items-center justify-center gap-12 p-24 text-center\">\n <div\n class=\"w-64 h-64 rounded-full bg-surface-1 border border-surface-3 flex items-center justify-center\">\n <VvIcon name=\"ri:chat-3-line\" class=\"w-28 h-28 text-word-4\" />\n </div>\n <div class=\"flex flex-col gap-4\">\n <strong class=\"text-16 font-semibold text-word-1\">\n {{ $t('message.noConversations') }}\n </strong>\n <span class=\"text-14 text-word-3 leading-relaxed\">\n {{ $t('message.noConversationsHint') }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary rounded\"\n icon=\"ri:add-line\"\n class=\"mt-8\"\n :label=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n </div>\n\n <!-- Conversation list. The row is a container, not a button: the menu\n and the rename field are siblings of the entry, because a button\n cannot legally contain other controls (Firefox and Safari drop\n them from the accessibility tree). -->\n <ul\n v-else\n class=\"u-pick-list__list u-pick-list__list--fill overflow-y-auto pb-64 light-scrollbar\">\n <li\n v-for=\"conversation in conversations\"\n :key=\"conversation.id\"\n class=\"u-pick-list__row\"\n :class=\"{\n 'u-pick-list__row--current':\n isCurrentConversation(conversation),\n }\">\n <div\n v-if=\"editingId === conversation.id\"\n class=\"flex flex-1 items-center gap-8 min-w-0 px-md py-12\">\n <VvInputText\n v-model=\"editingTitle\"\n class=\"flex-1 mb-0\"\n modifiers=\"compact\"\n autofocus\n select-on-focus\n :maxlength=\"255\"\n :placeholder=\"$t('placeholder.insert')\"\n @keydown=\"onRenameKeydown($event, conversation.id)\" />\n <VvButtonGroup modifiers=\"compact\">\n <VvButton\n icon=\"ri:save-line\"\n :title=\"$t('action.save')\"\n modifiers=\"action-quiet-small\"\n @click=\"confirmEdit(conversation.id)\" />\n <VvButton\n icon=\"ri:close-line\"\n :title=\"$t('action.cancel')\"\n modifiers=\"action-quiet-small\"\n @click=\"cancelEdit()\" />\n </VvButtonGroup>\n </div>\n <template v-else>\n <button\n type=\"button\"\n class=\"u-pick-list__entry u-pick-list__entry--roomy leading-relaxed\"\n :aria-current=\"\n isCurrentConversation(conversation)\n ? 'true'\n : undefined\n \"\n @click=\"openConversation(conversation.id)\">\n <div class=\"min-w-0 flex-1\">\n <div class=\"flex items-center gap-8\">\n <strong\n class=\"u-pick-list__label font-medium flex-1\">\n {{\n stripMarkdown(conversation.title) ||\n getLastMessage(\n conversation.messages,\n 'assistant',\n )\n }}\n </strong>\n <PkRelativeTime\n class=\"text-word-4 text-smaller shrink-0\"\n :date=\"conversation.lastMessageAt\" />\n </div>\n <span class=\"text-12 text-word-4 line-clamp-2\">\n {{\n stripMarkdown(conversation.summary) ||\n getLastMessage(\n conversation.messages,\n 'user',\n )\n }}\n </span>\n </div>\n </button>\n <div class=\"u-pick-list__actions\">\n <VvDropdown\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n icon=\"ri:more-2-fill\"\n :title=\"$t('action.moreActions')\"\n modifiers=\"action-quiet-small\" />\n <template #items>\n <VvDropdownAction\n @click=\"startEdit(conversation)\">\n <VvIcon name=\"ri:pencil-line\" />\n {{ $t('action.renameChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n @click=\"handleDelete(conversation.id)\">\n <VvIcon name=\"ri:delete-bin-line\" />\n {{ $t('action.delete') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n </div>\n </template>\n </li>\n </ul>\n\n <!-- Sticky \"new chat\" CTA (hidden on empty state, which has its own CTA) -->\n <div\n v-if=\"conversations.length > 0\"\n class=\"absolute bottom-0 left-0 right-0 flex flex-col items-center gap-4 px-16 pb-16 pt-32 pointer-events-none\"\n :style=\"{\n background:\n 'linear-gradient(to top, var(--color-surface) 55%, transparent)',\n }\">\n <VvButton\n class=\"text-14 pointer-events-auto\"\n modifiers=\"primary rounded\"\n icon=\"ri:add-line\"\n :label=\"$t('action.startNewChat')\"\n @click.stop=\"startNewChat()\" />\n <VvButton\n v-if=\"canEraseConversations\"\n class=\"text-12 pointer-events-auto\"\n modifiers=\"action-quiet-danger\"\n icon=\"ri:delete-bin-line\"\n :loading=\"isErasingConversations\"\n :disabled=\"isErasingConversations\"\n :label=\"$t('action.deleteMyConversations')\"\n @click.stop=\"handleEraseAll()\" />\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { a as e, n as t, o as n } from "./Document-nCWwJC2h.js";
2
- import { n as r } from "./useChatbotStore-CMo47fnS.js";
1
+ import { a as e, n as t, o as n } from "./Document-BWz2HySu.js";
2
+ import { n as r } from "./useChatbotStore-D3Q1ZgQg.js";
3
3
  import { t as i } from "./useDialogConfirm-KWz0ohCQ.js";
4
4
  import { t as a } from "./useImageFallback-5vQIO0pC.js";
5
5
  import { VvButton as o, VvIcon as s } from "@volverjs/ui-vue/components";
@@ -108,4 +108,4 @@ var C = { class: "flex flex-col flex-1 min-h-0 relative" }, w = {
108
108
  //#endregion
109
109
  export { R as t };
110
110
 
111
- //# sourceMappingURL=PkChatbotViewFiles-Bk9-K6Hw.js.map
111
+ //# sourceMappingURL=PkChatbotViewFiles-DrMUsTv5.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkChatbotViewFiles-Bk9-K6Hw.js","names":[],"sources":["../../../../packages/composable/src/useFileRow.ts","../../../../packages/components/src/chat/PkChatbotViewFiles.vue","../../../../packages/components/src/chat/PkChatbotViewFiles.vue"],"sourcesContent":["import { useI18n } from 'vue-i18n'\nimport { isImageMime, isInlineViewable } from 'models'\nimport { useImageFallback } from './useImageFallback'\n\n/** Minimal shape a file-list row needs for the shared display helpers. */\nexport interface FileRowLike {\n documentId: string\n mimeType: string | null\n}\n\n/**\n * Shared display logic for the user-files list rows, used by both the in-app\n * table (PjUserFilesTable) and the embedded widget (PkChatbotViewFiles). The two\n * surfaces differ only in how they build URLs (authenticated store vs widget API\n * client), so the URL builders are injected.\n *\n * - `view(id, inline)` → opens/downloads the original file.\n * - `preview(id)` → small resized blob for the row thumbnail.\n */\nexport function useFileRow(urls: {\n view: (documentId: string, inline: boolean) => string\n preview: (documentId: string) => string\n}) {\n const { locale } = useI18n({ useScope: 'global' })\n\n // Image rows show a thumbnail; if it fails to load (auth/redirect, or the\n // preview isn't generated yet) we fall back to the file-type icon.\n const { canShowImage, onImageError } = useImageFallback()\n const thumbUrl = (file: FileRowLike): string =>\n urls.preview(file.documentId)\n const showThumb = (file: FileRowLike): boolean =>\n isImageMime(file.mimeType) && canShowImage(thumbUrl(file))\n const onThumbError = (file: FileRowLike): void => {\n onImageError(thumbUrl(file))\n }\n\n // Name click previews images/PDF inline; an explicit download forces an\n // attachment (inline=false → download SAS).\n const openFile = (file: FileRowLike, download = false): void => {\n const inline = !download && isInlineViewable(file.mimeType)\n window.open(urls.view(file.documentId, inline), '_blank', 'noopener')\n }\n\n const formatDate = (iso: string): string =>\n new Date(iso).toLocaleDateString(locale.value, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n\n return { showThumb, thumbUrl, onThumbError, openFile, formatDate }\n}\n","<script setup lang=\"ts\">\n import { ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import { fileIconForMime } from 'models'\n import {\n useChatbotStore,\n useDialogConfirm,\n useFileRow,\n type ChatbotFile,\n } from 'composables'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n const { openDialogConfirm } = useDialogConfirm()\n\n const store = useChatbotStore(props.agentId)\n const { chatFiles, isLoadingChatFiles } = storeToRefs(store)\n const { showThumb, thumbUrl, onThumbError, openFile, formatDate } =\n useFileRow({\n view: store.getChatFileUrl,\n preview: store.getChatFilePreviewUrl,\n })\n\n const deletingId = ref<string | null>(null)\n\n const handleExclude = async (file: ChatbotFile) => {\n const proceed = await openDialogConfirm({\n title: $t('confirmTitle.excludeUserFile'),\n questionLabel: $t('userFiles.excludeConfirm'),\n })\n if (!proceed) {\n return\n }\n deletingId.value = file.documentId\n try {\n await store.deleteChatFile(file.documentId)\n } catch {\n // surfaced by the host's error handling\n } finally {\n deletingId.value = null\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 relative\">\n <!-- Loading skeleton, shaped like the rows it replaces -->\n <div\n v-if=\"isLoadingChatFiles && chatFiles.length === 0\"\n class=\"u-pick-list__list u-pick-list__list--fill\">\n <div\n v-for=\"i in 4\"\n :key=\"i\"\n class=\"u-pick-list__row u-pick-list__row--skeleton\">\n <div class=\"u-pick-list__entry u-pick-list__entry--roomy\">\n <div class=\"vv-skeleton w-36 h-36 shrink-0 rounded-md\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"flex flex-col gap-6 flex-1 min-w-0\">\n <div\n class=\"vv-skeleton h-14 rounded\"\n :class=\"i % 2 ? 'w-192' : 'w-128'\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded w-96\">\n <div class=\"vv-skeleton__item\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"chatFiles.length === 0\"\n class=\"flex flex-col flex-1 items-center justify-center gap-12 p-24 text-center\">\n <div\n class=\"w-64 h-64 rounded-full bg-surface-1 border border-surface-3 flex items-center justify-center\">\n <VvIcon\n name=\"ri:folder-open-line\"\n class=\"w-28 h-28 text-word-4\" />\n </div>\n <span class=\"text-14 text-word-3 leading-relaxed\">\n {{ $t('userFiles.chatFilesEmpty') }}\n </span>\n </div>\n\n <!-- File list -->\n <ul\n v-else\n class=\"pk-chatbot-files u-pick-list__list u-pick-list__list--fill overflow-y-auto light-scrollbar\">\n <li\n v-for=\"file in chatFiles\"\n :key=\"file.documentId\"\n class=\"u-pick-list__row\">\n <button\n type=\"button\"\n class=\"u-pick-list__entry u-pick-list__entry--roomy\"\n :title=\"$t('userFiles.open')\"\n @click=\"openFile(file)\">\n <span class=\"pk-chatbot-files__icon\">\n <img\n v-if=\"showThumb(file)\"\n :src=\"thumbUrl(file)\"\n loading=\"lazy\"\n decoding=\"async\"\n alt=\"\"\n class=\"pk-chatbot-files__thumb\"\n @error=\"onThumbError(file)\" />\n <VvIcon v-else :name=\"fileIconForMime(file.mimeType)\" />\n </span>\n <span class=\"flex flex-col min-w-0 flex-1\">\n <span class=\"u-pick-list__label\">\n {{ file.name || $t('userFiles.untitled') }}\n </span>\n <span class=\"text-12 text-word-3\">\n {{ formatDate(file.createdAt) }}\n </span>\n </span>\n </button>\n <div class=\"u-pick-list__actions pk-chatbot-files__actions\">\n <VvButton\n modifiers=\"action-quiet-small\"\n icon=\"ri:download-line\"\n :title=\"$t('action.download')\"\n @click=\"openFile(file, true)\" />\n <VvButton\n modifiers=\"action-quiet-small\"\n icon=\"ri:delete-bin-line\"\n :title=\"$t('userFiles.exclude')\"\n :loading=\"deletingId === file.documentId\"\n :disabled=\"deletingId === file.documentId\"\n @click=\"handleExclude(file)\" />\n </div>\n </li>\n </ul>\n </div>\n</template>\n\n<style lang=\"scss\">\n // The frame, the rows and the label come from `u-pick-list`; what is left\n // here is the file tile and the pair of row actions.\n .pk-chatbot-files {\n list-style: none;\n margin: 0;\n padding: 0;\n\n // The box exists to clip a thumbnail and to keep the labels of rows\n // with and without one on the same left edge: with a file icon in it\n // there is nothing to draw, so it stays transparent.\n &__icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--spacing-36);\n height: var(--spacing-36);\n flex-shrink: 0;\n border-radius: var(--rounded-md);\n color: var(--color-word-3);\n overflow: hidden;\n\n svg {\n width: var(--spacing-20);\n height: var(--spacing-20);\n }\n }\n\n &__thumb {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n &__actions {\n gap: var(--spacing-2);\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import { fileIconForMime } from 'models'\n import {\n useChatbotStore,\n useDialogConfirm,\n useFileRow,\n type ChatbotFile,\n } from 'composables'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n const { openDialogConfirm } = useDialogConfirm()\n\n const store = useChatbotStore(props.agentId)\n const { chatFiles, isLoadingChatFiles } = storeToRefs(store)\n const { showThumb, thumbUrl, onThumbError, openFile, formatDate } =\n useFileRow({\n view: store.getChatFileUrl,\n preview: store.getChatFilePreviewUrl,\n })\n\n const deletingId = ref<string | null>(null)\n\n const handleExclude = async (file: ChatbotFile) => {\n const proceed = await openDialogConfirm({\n title: $t('confirmTitle.excludeUserFile'),\n questionLabel: $t('userFiles.excludeConfirm'),\n })\n if (!proceed) {\n return\n }\n deletingId.value = file.documentId\n try {\n await store.deleteChatFile(file.documentId)\n } catch {\n // surfaced by the host's error handling\n } finally {\n deletingId.value = null\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 relative\">\n <!-- Loading skeleton, shaped like the rows it replaces -->\n <div\n v-if=\"isLoadingChatFiles && chatFiles.length === 0\"\n class=\"u-pick-list__list u-pick-list__list--fill\">\n <div\n v-for=\"i in 4\"\n :key=\"i\"\n class=\"u-pick-list__row u-pick-list__row--skeleton\">\n <div class=\"u-pick-list__entry u-pick-list__entry--roomy\">\n <div class=\"vv-skeleton w-36 h-36 shrink-0 rounded-md\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"flex flex-col gap-6 flex-1 min-w-0\">\n <div\n class=\"vv-skeleton h-14 rounded\"\n :class=\"i % 2 ? 'w-192' : 'w-128'\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded w-96\">\n <div class=\"vv-skeleton__item\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"chatFiles.length === 0\"\n class=\"flex flex-col flex-1 items-center justify-center gap-12 p-24 text-center\">\n <div\n class=\"w-64 h-64 rounded-full bg-surface-1 border border-surface-3 flex items-center justify-center\">\n <VvIcon\n name=\"ri:folder-open-line\"\n class=\"w-28 h-28 text-word-4\" />\n </div>\n <span class=\"text-14 text-word-3 leading-relaxed\">\n {{ $t('userFiles.chatFilesEmpty') }}\n </span>\n </div>\n\n <!-- File list -->\n <ul\n v-else\n class=\"pk-chatbot-files u-pick-list__list u-pick-list__list--fill overflow-y-auto light-scrollbar\">\n <li\n v-for=\"file in chatFiles\"\n :key=\"file.documentId\"\n class=\"u-pick-list__row\">\n <button\n type=\"button\"\n class=\"u-pick-list__entry u-pick-list__entry--roomy\"\n :title=\"$t('userFiles.open')\"\n @click=\"openFile(file)\">\n <span class=\"pk-chatbot-files__icon\">\n <img\n v-if=\"showThumb(file)\"\n :src=\"thumbUrl(file)\"\n loading=\"lazy\"\n decoding=\"async\"\n alt=\"\"\n class=\"pk-chatbot-files__thumb\"\n @error=\"onThumbError(file)\" />\n <VvIcon v-else :name=\"fileIconForMime(file.mimeType)\" />\n </span>\n <span class=\"flex flex-col min-w-0 flex-1\">\n <span class=\"u-pick-list__label\">\n {{ file.name || $t('userFiles.untitled') }}\n </span>\n <span class=\"text-12 text-word-3\">\n {{ formatDate(file.createdAt) }}\n </span>\n </span>\n </button>\n <div class=\"u-pick-list__actions pk-chatbot-files__actions\">\n <VvButton\n modifiers=\"action-quiet-small\"\n icon=\"ri:download-line\"\n :title=\"$t('action.download')\"\n @click=\"openFile(file, true)\" />\n <VvButton\n modifiers=\"action-quiet-small\"\n icon=\"ri:delete-bin-line\"\n :title=\"$t('userFiles.exclude')\"\n :loading=\"deletingId === file.documentId\"\n :disabled=\"deletingId === file.documentId\"\n @click=\"handleExclude(file)\" />\n </div>\n </li>\n </ul>\n </div>\n</template>\n\n<style lang=\"scss\">\n // The frame, the rows and the label come from `u-pick-list`; what is left\n // here is the file tile and the pair of row actions.\n .pk-chatbot-files {\n list-style: none;\n margin: 0;\n padding: 0;\n\n // The box exists to clip a thumbnail and to keep the labels of rows\n // with and without one on the same left edge: with a file icon in it\n // there is nothing to draw, so it stays transparent.\n &__icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--spacing-36);\n height: var(--spacing-36);\n flex-shrink: 0;\n border-radius: var(--rounded-md);\n color: var(--color-word-3);\n overflow: hidden;\n\n svg {\n width: var(--spacing-20);\n height: var(--spacing-20);\n }\n }\n\n &__thumb {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n &__actions {\n gap: var(--spacing-2);\n }\n }\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PkChatbotViewFiles-DrMUsTv5.js","names":[],"sources":["../../../../packages/composable/src/useFileRow.ts","../../../../packages/components/src/chat/PkChatbotViewFiles.vue","../../../../packages/components/src/chat/PkChatbotViewFiles.vue"],"sourcesContent":["import { useI18n } from 'vue-i18n'\nimport { isImageMime, isInlineViewable } from 'models'\nimport { useImageFallback } from './useImageFallback'\n\n/** Minimal shape a file-list row needs for the shared display helpers. */\nexport interface FileRowLike {\n documentId: string\n mimeType: string | null\n}\n\n/**\n * Shared display logic for the user-files list rows, used by both the in-app\n * table (PjUserFilesTable) and the embedded widget (PkChatbotViewFiles). The two\n * surfaces differ only in how they build URLs (authenticated store vs widget API\n * client), so the URL builders are injected.\n *\n * - `view(id, inline)` → opens/downloads the original file.\n * - `preview(id)` → small resized blob for the row thumbnail.\n */\nexport function useFileRow(urls: {\n view: (documentId: string, inline: boolean) => string\n preview: (documentId: string) => string\n}) {\n const { locale } = useI18n({ useScope: 'global' })\n\n // Image rows show a thumbnail; if it fails to load (auth/redirect, or the\n // preview isn't generated yet) we fall back to the file-type icon.\n const { canShowImage, onImageError } = useImageFallback()\n const thumbUrl = (file: FileRowLike): string =>\n urls.preview(file.documentId)\n const showThumb = (file: FileRowLike): boolean =>\n isImageMime(file.mimeType) && canShowImage(thumbUrl(file))\n const onThumbError = (file: FileRowLike): void => {\n onImageError(thumbUrl(file))\n }\n\n // Name click previews images/PDF inline; an explicit download forces an\n // attachment (inline=false → download SAS).\n const openFile = (file: FileRowLike, download = false): void => {\n const inline = !download && isInlineViewable(file.mimeType)\n window.open(urls.view(file.documentId, inline), '_blank', 'noopener')\n }\n\n const formatDate = (iso: string): string =>\n new Date(iso).toLocaleDateString(locale.value, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n\n return { showThumb, thumbUrl, onThumbError, openFile, formatDate }\n}\n","<script setup lang=\"ts\">\n import { ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import { fileIconForMime } from 'models'\n import {\n useChatbotStore,\n useDialogConfirm,\n useFileRow,\n type ChatbotFile,\n } from 'composables'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n const { openDialogConfirm } = useDialogConfirm()\n\n const store = useChatbotStore(props.agentId)\n const { chatFiles, isLoadingChatFiles } = storeToRefs(store)\n const { showThumb, thumbUrl, onThumbError, openFile, formatDate } =\n useFileRow({\n view: store.getChatFileUrl,\n preview: store.getChatFilePreviewUrl,\n })\n\n const deletingId = ref<string | null>(null)\n\n const handleExclude = async (file: ChatbotFile) => {\n const proceed = await openDialogConfirm({\n title: $t('confirmTitle.excludeUserFile'),\n questionLabel: $t('userFiles.excludeConfirm'),\n })\n if (!proceed) {\n return\n }\n deletingId.value = file.documentId\n try {\n await store.deleteChatFile(file.documentId)\n } catch {\n // surfaced by the host's error handling\n } finally {\n deletingId.value = null\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 relative\">\n <!-- Loading skeleton, shaped like the rows it replaces -->\n <div\n v-if=\"isLoadingChatFiles && chatFiles.length === 0\"\n class=\"u-pick-list__list u-pick-list__list--fill\">\n <div\n v-for=\"i in 4\"\n :key=\"i\"\n class=\"u-pick-list__row u-pick-list__row--skeleton\">\n <div class=\"u-pick-list__entry u-pick-list__entry--roomy\">\n <div class=\"vv-skeleton w-36 h-36 shrink-0 rounded-md\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"flex flex-col gap-6 flex-1 min-w-0\">\n <div\n class=\"vv-skeleton h-14 rounded\"\n :class=\"i % 2 ? 'w-192' : 'w-128'\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded w-96\">\n <div class=\"vv-skeleton__item\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"chatFiles.length === 0\"\n class=\"flex flex-col flex-1 items-center justify-center gap-12 p-24 text-center\">\n <div\n class=\"w-64 h-64 rounded-full bg-surface-1 border border-surface-3 flex items-center justify-center\">\n <VvIcon\n name=\"ri:folder-open-line\"\n class=\"w-28 h-28 text-word-4\" />\n </div>\n <span class=\"text-14 text-word-3 leading-relaxed\">\n {{ $t('userFiles.chatFilesEmpty') }}\n </span>\n </div>\n\n <!-- File list -->\n <ul\n v-else\n class=\"pk-chatbot-files u-pick-list__list u-pick-list__list--fill overflow-y-auto light-scrollbar\">\n <li\n v-for=\"file in chatFiles\"\n :key=\"file.documentId\"\n class=\"u-pick-list__row\">\n <button\n type=\"button\"\n class=\"u-pick-list__entry u-pick-list__entry--roomy\"\n :title=\"$t('userFiles.open')\"\n @click=\"openFile(file)\">\n <span class=\"pk-chatbot-files__icon\">\n <img\n v-if=\"showThumb(file)\"\n :src=\"thumbUrl(file)\"\n loading=\"lazy\"\n decoding=\"async\"\n alt=\"\"\n class=\"pk-chatbot-files__thumb\"\n @error=\"onThumbError(file)\" />\n <VvIcon v-else :name=\"fileIconForMime(file.mimeType)\" />\n </span>\n <span class=\"flex flex-col min-w-0 flex-1\">\n <span class=\"u-pick-list__label\">\n {{ file.name || $t('userFiles.untitled') }}\n </span>\n <span class=\"text-12 text-word-3\">\n {{ formatDate(file.createdAt) }}\n </span>\n </span>\n </button>\n <div class=\"u-pick-list__actions pk-chatbot-files__actions\">\n <VvButton\n modifiers=\"action-quiet-small\"\n icon=\"ri:download-line\"\n :title=\"$t('action.download')\"\n @click=\"openFile(file, true)\" />\n <VvButton\n modifiers=\"action-quiet-small\"\n icon=\"ri:delete-bin-line\"\n :title=\"$t('userFiles.exclude')\"\n :loading=\"deletingId === file.documentId\"\n :disabled=\"deletingId === file.documentId\"\n @click=\"handleExclude(file)\" />\n </div>\n </li>\n </ul>\n </div>\n</template>\n\n<style lang=\"scss\">\n // The frame, the rows and the label come from `u-pick-list`; what is left\n // here is the file tile and the pair of row actions.\n .pk-chatbot-files {\n list-style: none;\n margin: 0;\n padding: 0;\n\n // The box exists to clip a thumbnail and to keep the labels of rows\n // with and without one on the same left edge: with a file icon in it\n // there is nothing to draw, so it stays transparent.\n &__icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--spacing-36);\n height: var(--spacing-36);\n flex-shrink: 0;\n border-radius: var(--rounded-md);\n color: var(--color-word-3);\n overflow: hidden;\n\n svg {\n width: var(--spacing-20);\n height: var(--spacing-20);\n }\n }\n\n &__thumb {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n &__actions {\n gap: var(--spacing-2);\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import { fileIconForMime } from 'models'\n import {\n useChatbotStore,\n useDialogConfirm,\n useFileRow,\n type ChatbotFile,\n } from 'composables'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n const { openDialogConfirm } = useDialogConfirm()\n\n const store = useChatbotStore(props.agentId)\n const { chatFiles, isLoadingChatFiles } = storeToRefs(store)\n const { showThumb, thumbUrl, onThumbError, openFile, formatDate } =\n useFileRow({\n view: store.getChatFileUrl,\n preview: store.getChatFilePreviewUrl,\n })\n\n const deletingId = ref<string | null>(null)\n\n const handleExclude = async (file: ChatbotFile) => {\n const proceed = await openDialogConfirm({\n title: $t('confirmTitle.excludeUserFile'),\n questionLabel: $t('userFiles.excludeConfirm'),\n })\n if (!proceed) {\n return\n }\n deletingId.value = file.documentId\n try {\n await store.deleteChatFile(file.documentId)\n } catch {\n // surfaced by the host's error handling\n } finally {\n deletingId.value = null\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 relative\">\n <!-- Loading skeleton, shaped like the rows it replaces -->\n <div\n v-if=\"isLoadingChatFiles && chatFiles.length === 0\"\n class=\"u-pick-list__list u-pick-list__list--fill\">\n <div\n v-for=\"i in 4\"\n :key=\"i\"\n class=\"u-pick-list__row u-pick-list__row--skeleton\">\n <div class=\"u-pick-list__entry u-pick-list__entry--roomy\">\n <div class=\"vv-skeleton w-36 h-36 shrink-0 rounded-md\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"flex flex-col gap-6 flex-1 min-w-0\">\n <div\n class=\"vv-skeleton h-14 rounded\"\n :class=\"i % 2 ? 'w-192' : 'w-128'\">\n <div class=\"vv-skeleton__item\" />\n </div>\n <div class=\"vv-skeleton h-12 rounded w-96\">\n <div class=\"vv-skeleton__item\" />\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Empty state -->\n <div\n v-else-if=\"chatFiles.length === 0\"\n class=\"flex flex-col flex-1 items-center justify-center gap-12 p-24 text-center\">\n <div\n class=\"w-64 h-64 rounded-full bg-surface-1 border border-surface-3 flex items-center justify-center\">\n <VvIcon\n name=\"ri:folder-open-line\"\n class=\"w-28 h-28 text-word-4\" />\n </div>\n <span class=\"text-14 text-word-3 leading-relaxed\">\n {{ $t('userFiles.chatFilesEmpty') }}\n </span>\n </div>\n\n <!-- File list -->\n <ul\n v-else\n class=\"pk-chatbot-files u-pick-list__list u-pick-list__list--fill overflow-y-auto light-scrollbar\">\n <li\n v-for=\"file in chatFiles\"\n :key=\"file.documentId\"\n class=\"u-pick-list__row\">\n <button\n type=\"button\"\n class=\"u-pick-list__entry u-pick-list__entry--roomy\"\n :title=\"$t('userFiles.open')\"\n @click=\"openFile(file)\">\n <span class=\"pk-chatbot-files__icon\">\n <img\n v-if=\"showThumb(file)\"\n :src=\"thumbUrl(file)\"\n loading=\"lazy\"\n decoding=\"async\"\n alt=\"\"\n class=\"pk-chatbot-files__thumb\"\n @error=\"onThumbError(file)\" />\n <VvIcon v-else :name=\"fileIconForMime(file.mimeType)\" />\n </span>\n <span class=\"flex flex-col min-w-0 flex-1\">\n <span class=\"u-pick-list__label\">\n {{ file.name || $t('userFiles.untitled') }}\n </span>\n <span class=\"text-12 text-word-3\">\n {{ formatDate(file.createdAt) }}\n </span>\n </span>\n </button>\n <div class=\"u-pick-list__actions pk-chatbot-files__actions\">\n <VvButton\n modifiers=\"action-quiet-small\"\n icon=\"ri:download-line\"\n :title=\"$t('action.download')\"\n @click=\"openFile(file, true)\" />\n <VvButton\n modifiers=\"action-quiet-small\"\n icon=\"ri:delete-bin-line\"\n :title=\"$t('userFiles.exclude')\"\n :loading=\"deletingId === file.documentId\"\n :disabled=\"deletingId === file.documentId\"\n @click=\"handleExclude(file)\" />\n </div>\n </li>\n </ul>\n </div>\n</template>\n\n<style lang=\"scss\">\n // The frame, the rows and the label come from `u-pick-list`; what is left\n // here is the file tile and the pair of row actions.\n .pk-chatbot-files {\n list-style: none;\n margin: 0;\n padding: 0;\n\n // The box exists to clip a thumbnail and to keep the labels of rows\n // with and without one on the same left edge: with a file icon in it\n // there is nothing to draw, so it stays transparent.\n &__icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--spacing-36);\n height: var(--spacing-36);\n flex-shrink: 0;\n border-radius: var(--rounded-md);\n color: var(--color-word-3);\n overflow: hidden;\n\n svg {\n width: var(--spacing-20);\n height: var(--spacing-20);\n }\n }\n\n &__thumb {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n &__actions {\n gap: var(--spacing-2);\n }\n }\n</style>\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { n as e } from "./useChatbotStore-CMo47fnS.js";
1
+ import { n as e } from "./useChatbotStore-D3Q1ZgQg.js";
2
2
  import { t } from "./PkAvatar-paEJByEA.js";
3
3
  import { t as n } from "./chatbotProfileContext-CgnFhNPn.js";
4
4
  import { VvAlert as r, VvButton as i, VvIcon as a } from "@volverjs/ui-vue/components";
@@ -83,4 +83,4 @@ var x = { class: "flex flex-col flex-1 min-h-0 overflow-y-auto" }, S = { class:
83
83
  //#endregion
84
84
  export { N as t };
85
85
 
86
- //# sourceMappingURL=PkChatbotViewProfile-CosaHA-g.js.map
86
+ //# sourceMappingURL=PkChatbotViewProfile-C1eLHU7x.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkChatbotViewProfile-CosaHA-g.js","names":[],"sources":["../../../../packages/components/src/chat/PkChatbotViewProfile.vue","../../../../packages/components/src/chat/PkChatbotViewProfile.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, inject, ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import { useChatbotStore } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const store = useChatbotStore(props.agentId)\n const { name: agentName, agentInterface } = storeToRefs(store)\n\n const profile = inject(ChatbotProfileContextKey, undefined)\n const user = computed(() => profile?.user.value ?? null)\n const canManage = computed(() => profile?.canManageProfile.value ?? false)\n const profileUrl = computed(() => profile?.profileUrl.value)\n // External token mode: the host owns the auth lifecycle, so the widget\n // offers no sign-out nor profile management (the main site is not\n // accessible to externally authenticated users).\n const isExternalAuth = computed(\n () => profile?.isExternalAuth?.value ?? false,\n )\n\n const isSigningOut = ref(false)\n const errorMessage = ref<string>()\n\n async function onSignOut() {\n if (!profile) {\n return\n }\n isSigningOut.value = true\n try {\n await profile.signOut()\n } catch (error) {\n errorMessage.value =\n error instanceof Error ? error.message : $t('message.error')\n } finally {\n isSigningOut.value = false\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 overflow-y-auto\">\n <!-- Hero: user identity -->\n <div\n class=\"flex flex-col items-center gap-sm pt-lg px-lg pb-md bg-surface-1 border-b border-surface-3\">\n <PkAvatar\n modifiers=\"surface ring\"\n class=\"w-64 h-64\"\n :name=\"user?.name ?? agentName\"\n :img-src=\"user?.image ?? agentInterface?.logo\" />\n <div class=\"flex flex-col items-center gap-6 text-center\">\n <strong\n v-if=\"user?.name ?? agentName\"\n class=\"text-16 font-semibold text-word-1 leading-tight\">\n {{ user?.name ?? agentName }}\n </strong>\n <span\n v-if=\"user?.email\"\n class=\"text-12 text-word-3 leading-none\">\n {{ user.email }}\n </span>\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"flex flex-col gap-sm p-md flex-1\">\n <VvAlert\n v-if=\"errorMessage\"\n modifiers=\"danger\"\n :content=\"errorMessage\" />\n\n <!-- Manage profile link card -->\n <a\n v-if=\"canManage && profileUrl && !isExternalAuth\"\n :href=\"profileUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"flex items-center gap-12 p-12 rounded-lg border border-surface-3 hover:border-surface-5 hover:bg-surface-1 transition-colors cursor-pointer no-underline\">\n <div\n class=\"w-40 h-40 rounded-lg bg-surface-2 border border-surface-3 flex items-center justify-center shrink-0\">\n <VvIcon\n name=\"ri:user-settings-line\"\n class=\"w-20 h-20 text-word-2\" />\n </div>\n <div class=\"flex flex-col flex-1 min-w-0 gap-1\">\n <span class=\"text-14 font-medium text-word-1 leading-tight\">\n {{ $t('action.manageProfile') }}\n </span>\n <span class=\"text-12 text-word-3 leading-tight\">\n {{ $t('label.manageProfileDescription') }}\n </span>\n </div>\n <VvIcon\n name=\"ri:arrow-right-s-line\"\n class=\"w-16 h-16 text-word-4 shrink-0\" />\n </a>\n </div>\n\n <!-- Footer: sign out -->\n <div v-if=\"!isExternalAuth\" class=\"px-md py-sm\">\n <VvButton\n modifiers=\"secondary-danger\"\n class=\"w-full\"\n icon=\"ri:logout-box-line\"\n :loading=\"isSigningOut\"\n :disabled=\"isSigningOut\"\n :label=\"$t('action.signOut')\"\n @click=\"onSignOut\" />\n </div>\n\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, inject, ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import { useChatbotStore } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const store = useChatbotStore(props.agentId)\n const { name: agentName, agentInterface } = storeToRefs(store)\n\n const profile = inject(ChatbotProfileContextKey, undefined)\n const user = computed(() => profile?.user.value ?? null)\n const canManage = computed(() => profile?.canManageProfile.value ?? false)\n const profileUrl = computed(() => profile?.profileUrl.value)\n // External token mode: the host owns the auth lifecycle, so the widget\n // offers no sign-out nor profile management (the main site is not\n // accessible to externally authenticated users).\n const isExternalAuth = computed(\n () => profile?.isExternalAuth?.value ?? false,\n )\n\n const isSigningOut = ref(false)\n const errorMessage = ref<string>()\n\n async function onSignOut() {\n if (!profile) {\n return\n }\n isSigningOut.value = true\n try {\n await profile.signOut()\n } catch (error) {\n errorMessage.value =\n error instanceof Error ? error.message : $t('message.error')\n } finally {\n isSigningOut.value = false\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 overflow-y-auto\">\n <!-- Hero: user identity -->\n <div\n class=\"flex flex-col items-center gap-sm pt-lg px-lg pb-md bg-surface-1 border-b border-surface-3\">\n <PkAvatar\n modifiers=\"surface ring\"\n class=\"w-64 h-64\"\n :name=\"user?.name ?? agentName\"\n :img-src=\"user?.image ?? agentInterface?.logo\" />\n <div class=\"flex flex-col items-center gap-6 text-center\">\n <strong\n v-if=\"user?.name ?? agentName\"\n class=\"text-16 font-semibold text-word-1 leading-tight\">\n {{ user?.name ?? agentName }}\n </strong>\n <span\n v-if=\"user?.email\"\n class=\"text-12 text-word-3 leading-none\">\n {{ user.email }}\n </span>\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"flex flex-col gap-sm p-md flex-1\">\n <VvAlert\n v-if=\"errorMessage\"\n modifiers=\"danger\"\n :content=\"errorMessage\" />\n\n <!-- Manage profile link card -->\n <a\n v-if=\"canManage && profileUrl && !isExternalAuth\"\n :href=\"profileUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"flex items-center gap-12 p-12 rounded-lg border border-surface-3 hover:border-surface-5 hover:bg-surface-1 transition-colors cursor-pointer no-underline\">\n <div\n class=\"w-40 h-40 rounded-lg bg-surface-2 border border-surface-3 flex items-center justify-center shrink-0\">\n <VvIcon\n name=\"ri:user-settings-line\"\n class=\"w-20 h-20 text-word-2\" />\n </div>\n <div class=\"flex flex-col flex-1 min-w-0 gap-1\">\n <span class=\"text-14 font-medium text-word-1 leading-tight\">\n {{ $t('action.manageProfile') }}\n </span>\n <span class=\"text-12 text-word-3 leading-tight\">\n {{ $t('label.manageProfileDescription') }}\n </span>\n </div>\n <VvIcon\n name=\"ri:arrow-right-s-line\"\n class=\"w-16 h-16 text-word-4 shrink-0\" />\n </a>\n </div>\n\n <!-- Footer: sign out -->\n <div v-if=\"!isExternalAuth\" class=\"px-md py-sm\">\n <VvButton\n modifiers=\"secondary-danger\"\n class=\"w-full\"\n icon=\"ri:logout-box-line\"\n :loading=\"isSigningOut\"\n :disabled=\"isSigningOut\"\n :label=\"$t('action.signOut')\"\n @click=\"onSignOut\" />\n </div>\n\n <slot />\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;EAQI,IAAM,IAAQ,GAER,EAAE,GAAG,MAAO,EAAQ,EAAE,UAAU,SAAS,CAAC,GAE1C,IAAQ,EAAgB,EAAM,OAAO,GACrC,EAAE,MAAM,GAAW,sBAAmB,EAAY,CAAK,GAEvD,IAAU,EAAO,GAA0B,KAAA,CAAS,GACpD,IAAO,QAAe,GAAS,KAAK,SAAS,IAAI,GACjD,IAAY,QAAe,GAAS,iBAAiB,SAAS,EAAK,GACnE,IAAa,QAAe,GAAS,WAAW,KAAK,GAIrD,IAAiB,QACb,GAAS,gBAAgB,SAAS,EAC5C,GAEM,IAAe,EAAI,EAAK,GACxB,IAAe,EAAY;EAEjC,eAAe,IAAY;GACvB,IAAK,GAGL;MAAa,QAAQ;IACrB,IAAI;KACA,MAAM,EAAQ,QAAQ;IAC1B,SAAS,GAAO;KACZ,EAAa,QACT,aAAiB,QAAQ,EAAM,UAAU,EAAG,eAAe;IACnE,UAAU;KACN,EAAa,QAAQ;IACzB;GARqB;EASzB;;;GAIA,OAAA,EAAA,GAAA,EAsEM,OAtEN,GAsEM;IApEF,EAmBM,OAnBN,GAmBM,CAjBF,EAIqD,GAAA;KAHjD,WAAU;KACV,OAAM;KACL,MAAM,EAAA,OAAM,QAAQ,EAAA,CAAA;KACpB,WAAS,EAAA,OAAM,SAAS,EAAA,CAAA,CAAc,EAAE;IAC7C,GAAA,MAAA,GAAA,CAAA,QAAA,SAAA,CAAA,GAAA,EAWM,OAXN,GAWM,CATQ,EAAA,OAAM,QAAQ,EAAA,CAAA,KADxB,EAAA,GAAA,EAIS,UAJT,GAIS,EADF,EAAA,OAAM,QAAQ,EAAA,CAAA,CAAS,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GAGpB,EAAA,OAAM,SADhB,EAAA,GAAA,EAIO,QAJP,GAIO,EADA,EAAA,MAAK,KAAK,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,CAAA,CAAA;IAMzB,EA+BM,OA/BN,GA+BM,CA7BQ,EAAA,SADV,EAAA,GAAA,EAG8B,GAAA;;KAD1B,WAAU;KACT,SAAS,EAAA;IAIJ,GAAA,MAAA,GAAA,CAAA,SAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GAAA,EAAA,SAAa,EAAA,SAAU,CAAK,EAAA,SADtC,EAAA,GAAA,EAuBI,KAAA;;KArBC,MAAM,EAAA;KACP,QAAO;KACP,KAAI;KACJ,OAAM;;KACN,EAKM,OALN,GAKM,CAHF,EAEoC,GAAA;MADhC,MAAK;MACL,OAAM;;KAEd,EAOM,OAPN,GAOM,CANF,EAEO,QAFP,GAEO,EADA,EAAA,CAAA,CAAE,CAAA,sBAAA,CAAA,GAAA,CAAA,GAET,EAEO,QAFP,GAEO,EADA,EAAA,CAAA,CAAE,CAAA,gCAAA,CAAA,GAAA,CAAA,CAAA,CAAA;KAGb,EAE6C,GAAA;MADzC,MAAK;MACL,OAAM;;;IAKN,EAAA,qBAAZ,EAAA,GAAA,EASM,OATN,GASM,CARF,EAOyB,GAAA;KANrB,WAAU;KACV,OAAM;KACN,MAAK;KACJ,SAAS,EAAA;KACT,UAAU,EAAA;KACV,OAAO,EAAA,CAAA,CAAE,CAAA,gBAAA;KACT,SAAO;;;;;;IAGhB,EAAQ,EAAA,QAAA,SAAA"}
1
+ {"version":3,"file":"PkChatbotViewProfile-C1eLHU7x.js","names":[],"sources":["../../../../packages/components/src/chat/PkChatbotViewProfile.vue","../../../../packages/components/src/chat/PkChatbotViewProfile.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, inject, ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import { useChatbotStore } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const store = useChatbotStore(props.agentId)\n const { name: agentName, agentInterface } = storeToRefs(store)\n\n const profile = inject(ChatbotProfileContextKey, undefined)\n const user = computed(() => profile?.user.value ?? null)\n const canManage = computed(() => profile?.canManageProfile.value ?? false)\n const profileUrl = computed(() => profile?.profileUrl.value)\n // External token mode: the host owns the auth lifecycle, so the widget\n // offers no sign-out nor profile management (the main site is not\n // accessible to externally authenticated users).\n const isExternalAuth = computed(\n () => profile?.isExternalAuth?.value ?? false,\n )\n\n const isSigningOut = ref(false)\n const errorMessage = ref<string>()\n\n async function onSignOut() {\n if (!profile) {\n return\n }\n isSigningOut.value = true\n try {\n await profile.signOut()\n } catch (error) {\n errorMessage.value =\n error instanceof Error ? error.message : $t('message.error')\n } finally {\n isSigningOut.value = false\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 overflow-y-auto\">\n <!-- Hero: user identity -->\n <div\n class=\"flex flex-col items-center gap-sm pt-lg px-lg pb-md bg-surface-1 border-b border-surface-3\">\n <PkAvatar\n modifiers=\"surface ring\"\n class=\"w-64 h-64\"\n :name=\"user?.name ?? agentName\"\n :img-src=\"user?.image ?? agentInterface?.logo\" />\n <div class=\"flex flex-col items-center gap-6 text-center\">\n <strong\n v-if=\"user?.name ?? agentName\"\n class=\"text-16 font-semibold text-word-1 leading-tight\">\n {{ user?.name ?? agentName }}\n </strong>\n <span\n v-if=\"user?.email\"\n class=\"text-12 text-word-3 leading-none\">\n {{ user.email }}\n </span>\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"flex flex-col gap-sm p-md flex-1\">\n <VvAlert\n v-if=\"errorMessage\"\n modifiers=\"danger\"\n :content=\"errorMessage\" />\n\n <!-- Manage profile link card -->\n <a\n v-if=\"canManage && profileUrl && !isExternalAuth\"\n :href=\"profileUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"flex items-center gap-12 p-12 rounded-lg border border-surface-3 hover:border-surface-5 hover:bg-surface-1 transition-colors cursor-pointer no-underline\">\n <div\n class=\"w-40 h-40 rounded-lg bg-surface-2 border border-surface-3 flex items-center justify-center shrink-0\">\n <VvIcon\n name=\"ri:user-settings-line\"\n class=\"w-20 h-20 text-word-2\" />\n </div>\n <div class=\"flex flex-col flex-1 min-w-0 gap-1\">\n <span class=\"text-14 font-medium text-word-1 leading-tight\">\n {{ $t('action.manageProfile') }}\n </span>\n <span class=\"text-12 text-word-3 leading-tight\">\n {{ $t('label.manageProfileDescription') }}\n </span>\n </div>\n <VvIcon\n name=\"ri:arrow-right-s-line\"\n class=\"w-16 h-16 text-word-4 shrink-0\" />\n </a>\n </div>\n\n <!-- Footer: sign out -->\n <div v-if=\"!isExternalAuth\" class=\"px-md py-sm\">\n <VvButton\n modifiers=\"secondary-danger\"\n class=\"w-full\"\n icon=\"ri:logout-box-line\"\n :loading=\"isSigningOut\"\n :disabled=\"isSigningOut\"\n :label=\"$t('action.signOut')\"\n @click=\"onSignOut\" />\n </div>\n\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, inject, ref } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import { useChatbotStore } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n\n const props = defineProps<{ agentId: string }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const store = useChatbotStore(props.agentId)\n const { name: agentName, agentInterface } = storeToRefs(store)\n\n const profile = inject(ChatbotProfileContextKey, undefined)\n const user = computed(() => profile?.user.value ?? null)\n const canManage = computed(() => profile?.canManageProfile.value ?? false)\n const profileUrl = computed(() => profile?.profileUrl.value)\n // External token mode: the host owns the auth lifecycle, so the widget\n // offers no sign-out nor profile management (the main site is not\n // accessible to externally authenticated users).\n const isExternalAuth = computed(\n () => profile?.isExternalAuth?.value ?? false,\n )\n\n const isSigningOut = ref(false)\n const errorMessage = ref<string>()\n\n async function onSignOut() {\n if (!profile) {\n return\n }\n isSigningOut.value = true\n try {\n await profile.signOut()\n } catch (error) {\n errorMessage.value =\n error instanceof Error ? error.message : $t('message.error')\n } finally {\n isSigningOut.value = false\n }\n }\n</script>\n\n<template>\n <div class=\"flex flex-col flex-1 min-h-0 overflow-y-auto\">\n <!-- Hero: user identity -->\n <div\n class=\"flex flex-col items-center gap-sm pt-lg px-lg pb-md bg-surface-1 border-b border-surface-3\">\n <PkAvatar\n modifiers=\"surface ring\"\n class=\"w-64 h-64\"\n :name=\"user?.name ?? agentName\"\n :img-src=\"user?.image ?? agentInterface?.logo\" />\n <div class=\"flex flex-col items-center gap-6 text-center\">\n <strong\n v-if=\"user?.name ?? agentName\"\n class=\"text-16 font-semibold text-word-1 leading-tight\">\n {{ user?.name ?? agentName }}\n </strong>\n <span\n v-if=\"user?.email\"\n class=\"text-12 text-word-3 leading-none\">\n {{ user.email }}\n </span>\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"flex flex-col gap-sm p-md flex-1\">\n <VvAlert\n v-if=\"errorMessage\"\n modifiers=\"danger\"\n :content=\"errorMessage\" />\n\n <!-- Manage profile link card -->\n <a\n v-if=\"canManage && profileUrl && !isExternalAuth\"\n :href=\"profileUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"flex items-center gap-12 p-12 rounded-lg border border-surface-3 hover:border-surface-5 hover:bg-surface-1 transition-colors cursor-pointer no-underline\">\n <div\n class=\"w-40 h-40 rounded-lg bg-surface-2 border border-surface-3 flex items-center justify-center shrink-0\">\n <VvIcon\n name=\"ri:user-settings-line\"\n class=\"w-20 h-20 text-word-2\" />\n </div>\n <div class=\"flex flex-col flex-1 min-w-0 gap-1\">\n <span class=\"text-14 font-medium text-word-1 leading-tight\">\n {{ $t('action.manageProfile') }}\n </span>\n <span class=\"text-12 text-word-3 leading-tight\">\n {{ $t('label.manageProfileDescription') }}\n </span>\n </div>\n <VvIcon\n name=\"ri:arrow-right-s-line\"\n class=\"w-16 h-16 text-word-4 shrink-0\" />\n </a>\n </div>\n\n <!-- Footer: sign out -->\n <div v-if=\"!isExternalAuth\" class=\"px-md py-sm\">\n <VvButton\n modifiers=\"secondary-danger\"\n class=\"w-full\"\n icon=\"ri:logout-box-line\"\n :loading=\"isSigningOut\"\n :disabled=\"isSigningOut\"\n :label=\"$t('action.signOut')\"\n @click=\"onSignOut\" />\n </div>\n\n <slot />\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;EAQI,IAAM,IAAQ,GAER,EAAE,GAAG,MAAO,EAAQ,EAAE,UAAU,SAAS,CAAC,GAE1C,IAAQ,EAAgB,EAAM,OAAO,GACrC,EAAE,MAAM,GAAW,sBAAmB,EAAY,CAAK,GAEvD,IAAU,EAAO,GAA0B,KAAA,CAAS,GACpD,IAAO,QAAe,GAAS,KAAK,SAAS,IAAI,GACjD,IAAY,QAAe,GAAS,iBAAiB,SAAS,EAAK,GACnE,IAAa,QAAe,GAAS,WAAW,KAAK,GAIrD,IAAiB,QACb,GAAS,gBAAgB,SAAS,EAC5C,GAEM,IAAe,EAAI,EAAK,GACxB,IAAe,EAAY;EAEjC,eAAe,IAAY;GACvB,IAAK,GAGL;MAAa,QAAQ;IACrB,IAAI;KACA,MAAM,EAAQ,QAAQ;IAC1B,SAAS,GAAO;KACZ,EAAa,QACT,aAAiB,QAAQ,EAAM,UAAU,EAAG,eAAe;IACnE,UAAU;KACN,EAAa,QAAQ;IACzB;GARqB;EASzB;;;GAIA,OAAA,EAAA,GAAA,EAsEM,OAtEN,GAsEM;IApEF,EAmBM,OAnBN,GAmBM,CAjBF,EAIqD,GAAA;KAHjD,WAAU;KACV,OAAM;KACL,MAAM,EAAA,OAAM,QAAQ,EAAA,CAAA;KACpB,WAAS,EAAA,OAAM,SAAS,EAAA,CAAA,CAAc,EAAE;IAC7C,GAAA,MAAA,GAAA,CAAA,QAAA,SAAA,CAAA,GAAA,EAWM,OAXN,GAWM,CATQ,EAAA,OAAM,QAAQ,EAAA,CAAA,KADxB,EAAA,GAAA,EAIS,UAJT,GAIS,EADF,EAAA,OAAM,QAAQ,EAAA,CAAA,CAAS,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GAGpB,EAAA,OAAM,SADhB,EAAA,GAAA,EAIO,QAJP,GAIO,EADA,EAAA,MAAK,KAAK,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,CAAA,CAAA;IAMzB,EA+BM,OA/BN,GA+BM,CA7BQ,EAAA,SADV,EAAA,GAAA,EAG8B,GAAA;;KAD1B,WAAU;KACT,SAAS,EAAA;IAIJ,GAAA,MAAA,GAAA,CAAA,SAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GAAA,EAAA,SAAa,EAAA,SAAU,CAAK,EAAA,SADtC,EAAA,GAAA,EAuBI,KAAA;;KArBC,MAAM,EAAA;KACP,QAAO;KACP,KAAI;KACJ,OAAM;;KACN,EAKM,OALN,GAKM,CAHF,EAEoC,GAAA;MADhC,MAAK;MACL,OAAM;;KAEd,EAOM,OAPN,GAOM,CANF,EAEO,QAFP,GAEO,EADA,EAAA,CAAA,CAAE,CAAA,sBAAA,CAAA,GAAA,CAAA,GAET,EAEO,QAFP,GAEO,EADA,EAAA,CAAA,CAAE,CAAA,gCAAA,CAAA,GAAA,CAAA,CAAA,CAAA;KAGb,EAE6C,GAAA;MADzC,MAAK;MACL,OAAM;;;IAKN,EAAA,qBAAZ,EAAA,GAAA,EASM,OATN,GASM,CARF,EAOyB,GAAA;KANrB,WAAU;KACV,OAAM;KACN,MAAK;KACJ,SAAS,EAAA;KACT,UAAU,EAAA;KACV,OAAO,EAAA,CAAA,CAAE,CAAA,gBAAA;KACT,SAAO;;;;;;IAGhB,EAAQ,EAAA,QAAA,SAAA"}
@@ -93,7 +93,7 @@ var v = { class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-s
93
93
  }), (e, i) => {
94
94
  let u = n, d = t;
95
95
  return p(), s("div", v, [c("div", y, [l(u, {
96
- name: "ri:link-m",
96
+ name: "ri:link",
97
97
  class: "text-16 shrink-0"
98
98
  }), c("strong", b, h(I.value), 1)]), c("div", x, [W.value ? (p(), s("div", S, [l(u, {
99
99
  name: "ri:check-line",
@@ -112,11 +112,11 @@ var v = { class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-s
112
112
  icon: "ri:external-link-line",
113
113
  label: g(N)("messagePart.requestOAuthConnectionButton", { name: I.value }),
114
114
  onClick: $
115
- }, null, 8, ["label"])], 64)) : (p(), s("div", E, [l(u, { name: "ri:link-m" }), c("span", D, h(g(N)("messagePart.requestOAuthConnectionInfo", { name: I.value })), 1)]))])]);
115
+ }, null, 8, ["label"])], 64)) : (p(), s("div", E, [l(u, { name: "ri:link" }), c("span", D, h(g(N)("messagePart.requestOAuthConnectionInfo", { name: I.value })), 1)]))])]);
116
116
  };
117
117
  }
118
118
  });
119
119
  //#endregion
120
120
  export { A as default };
121
121
 
122
- //# sourceMappingURL=PkToolRequestOAuthConnection-DMvCGPuX.js.map
122
+ //# sourceMappingURL=PkToolRequestOAuthConnection-a2LegFoE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PkToolRequestOAuthConnection-a2LegFoE.js","names":[],"sources":["../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue","../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import "./src-DT8FLE4_.js";
2
- import { a as e } from "./PkChatbotMessages-Bj7mjhqN.js";
2
+ import { a as e } from "./PkChatbotMessages-62rYpwcM.js";
3
3
  import { t } from "./PkSvgViewer-D-0M5bqg.js";
4
4
  import { VvButton as n, VvButtonGroup as r, VvIcon as i } from "@volverjs/ui-vue/components";
5
5
  import { computed as a, createBlock as o, createCommentVNode as s, createElementBlock as c, createElementVNode as l, createVNode as u, defineComponent as d, onMounted as f, openBlock as p, ref as m, toDisplayString as h, unref as g, watch as _, withCtx as v } from "vue";
@@ -119,4 +119,4 @@ var x = {
119
119
  //#endregion
120
120
  export { k as default };
121
121
 
122
- //# sourceMappingURL=PkToolShowDiagram-BziggKVa.js.map
122
+ //# sourceMappingURL=PkToolShowDiagram-CssUiP3o.js.map