@8wave/ai-elements 0.110.0 → 0.111.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/_chunks/{PkToolRequestGeolocation-CEVpl50q.js → PkToolRequestGeolocation-OokLeTf1.js} +2 -2
- package/dist/_chunks/PkToolRequestGeolocation-OokLeTf1.js.map +1 -0
- package/dist/_chunks/{PkToolRequestOAuthConnection-CncDU-Kl.js → PkToolRequestOAuthConnection-CtqKG6Yb.js} +2 -2
- package/dist/_chunks/PkToolRequestOAuthConnection-CtqKG6Yb.js.map +1 -0
- package/dist/_chunks/{PkToolShowDiagram-Hy9qh68O.js → PkToolShowDiagram-BdmG9gm8.js} +2 -2
- package/dist/_chunks/{PkToolShowDiagram-Hy9qh68O.js.map → PkToolShowDiagram-BdmG9gm8.js.map} +1 -1
- package/dist/_chunks/{PkToolShowSources-B69JmwbZ.js → PkToolShowSources-Dot-W0Mj.js} +2 -2
- package/dist/_chunks/PkToolShowSources-Dot-W0Mj.js.map +1 -0
- package/dist/ai-elements.es.js +18 -47
- package/dist/ai-elements.es.js.map +1 -1
- package/dist-vue/PkChatbot.js +1 -1
- package/dist-vue/PkChatbotFilePreview.js +1 -1
- package/dist-vue/PkChatbotInput.js +1 -1
- package/dist-vue/PkChatbotMessages.js +1 -1
- package/dist-vue/PkChatbotSteps.js +1 -1
- package/dist-vue/PkChatbotViewChat.js +1 -1
- package/dist-vue/PkChatbotViewConversations.js +1 -1
- package/dist-vue/PkChatbotViewFiles.js +1 -1
- package/dist-vue/PkChatbotViewProfile.js +1 -1
- package/dist-vue/_chunks/{PkChatbot-o7k7d-fj.js → PkChatbot-BPKSPIeC.js} +6 -6
- package/dist-vue/_chunks/{PkChatbot-o7k7d-fj.js.map → PkChatbot-BPKSPIeC.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotFilePreview-DO1wZPPj.js → PkChatbotFilePreview-CwiVQDF2.js} +3 -3
- package/dist-vue/_chunks/PkChatbotFilePreview-CwiVQDF2.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotInput-D8x52cVm.js → PkChatbotInput-JnHaRlfk.js} +3 -3
- package/dist-vue/_chunks/PkChatbotInput-JnHaRlfk.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotMessages-BFlU046I.js → PkChatbotMessages-DAlkoMkY.js} +9 -9
- package/dist-vue/_chunks/PkChatbotMessages-DAlkoMkY.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotSteps-DtV1beDc.js → PkChatbotSteps-rommD48p.js} +3 -3
- package/dist-vue/_chunks/PkChatbotSteps-rommD48p.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotViewChat-CNH_glPI.js → PkChatbotViewChat-CEqzF1G9.js} +5 -5
- package/dist-vue/_chunks/{PkChatbotViewChat-CNH_glPI.js.map → PkChatbotViewChat-CEqzF1G9.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewConversations-BENvHLZt.js → PkChatbotViewConversations-C787v5ll.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewConversations-BENvHLZt.js.map → PkChatbotViewConversations-C787v5ll.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewFiles-qmLESi1V.js → PkChatbotViewFiles-CNFLt8YO.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewFiles-qmLESi1V.js.map → PkChatbotViewFiles-CNFLt8YO.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewProfile-DzfZnitQ.js → PkChatbotViewProfile-7p0ci3qm.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewProfile-DzfZnitQ.js.map → PkChatbotViewProfile-7p0ci3qm.js.map} +1 -1
- package/dist-vue/_chunks/{PkSvgViewer-DPpE9fiz.js → PkSvgViewer-D-0M5bqg.js} +2 -2
- package/dist-vue/_chunks/{PkSvgViewer-DPpE9fiz.js.map → PkSvgViewer-D-0M5bqg.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolRequestGeolocation-D1ZPmWiB.js → PkToolRequestGeolocation-Bxqz9oSo.js} +2 -2
- package/dist-vue/_chunks/PkToolRequestGeolocation-Bxqz9oSo.js.map +1 -0
- package/dist-vue/_chunks/{PkToolRequestOAuthConnection-CoJyKEE6.js → PkToolRequestOAuthConnection-C6rXgb0s.js} +2 -2
- package/dist-vue/_chunks/PkToolRequestOAuthConnection-C6rXgb0s.js.map +1 -0
- package/dist-vue/_chunks/{PkToolShowDiagram-DuDp5EHd.js → PkToolShowDiagram-dwCVZ1up.js} +4 -4
- package/dist-vue/_chunks/PkToolShowDiagram-dwCVZ1up.js.map +1 -0
- package/dist-vue/_chunks/{PkToolShowImageGallery-V9U3LJfF.js → PkToolShowImageGallery-DudQzBx4.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowImageGallery-V9U3LJfF.js.map → PkToolShowImageGallery-DudQzBx4.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowSources-BvWOzVkQ.js → PkToolShowSources-7Q5Iogxi.js} +2 -2
- package/dist-vue/_chunks/PkToolShowSources-7Q5Iogxi.js.map +1 -0
- package/dist-vue/_chunks/{createChatbotApiClient-CJ_5_3IT.js → createChatbotApiClient-D9f69brW.js} +5 -2
- package/dist-vue/_chunks/createChatbotApiClient-D9f69brW.js.map +1 -0
- package/dist-vue/_chunks/{useChatbotStore-CiRAuq7v.js → useChatbotStore-9Cw_oR6J.js} +2 -2
- package/dist-vue/_chunks/{useChatbotStore-CiRAuq7v.js.map → useChatbotStore-9Cw_oR6J.js.map} +1 -1
- package/dist-vue/api.js +1 -1
- package/dist-vue/composables.js +2 -2
- package/dist-vue/index.js +28 -17
- package/dist-vue/index.js.map +1 -1
- package/dist-vue/packages/components/src/table/PkTableSortable.d.ts +8 -0
- package/dist-vue/packages/models/src/schema/Chat.d.ts +21 -0
- package/dist-vue/style.css +1 -1
- package/package.json +1 -1
- package/dist/_chunks/PkToolRequestGeolocation-CEVpl50q.js.map +0 -1
- package/dist/_chunks/PkToolRequestOAuthConnection-CncDU-Kl.js.map +0 -1
- package/dist/_chunks/PkToolShowSources-B69JmwbZ.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotFilePreview-DO1wZPPj.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotInput-D8x52cVm.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotMessages-BFlU046I.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotSteps-DtV1beDc.js.map +0 -1
- package/dist-vue/_chunks/PkToolRequestGeolocation-D1ZPmWiB.js.map +0 -1
- package/dist-vue/_chunks/PkToolRequestOAuthConnection-CoJyKEE6.js.map +0 -1
- package/dist-vue/_chunks/PkToolShowDiagram-DuDp5EHd.js.map +0 -1
- package/dist-vue/_chunks/PkToolShowSources-BvWOzVkQ.js.map +0 -1
- package/dist-vue/_chunks/createChatbotApiClient-CJ_5_3IT.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as e } from "./useChatbotStore-
|
|
1
|
+
import { n as e } from "./useChatbotStore-9Cw_oR6J.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-
|
|
195
|
+
//# sourceMappingURL=PkChatbotViewConversations-C787v5ll.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PkChatbotViewConversations-BENvHLZt.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-C787v5ll.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
1
|
import { a as e, n as t, o as n } from "./Document-CaUfI_SZ.js";
|
|
2
|
-
import { n as r } from "./useChatbotStore-
|
|
2
|
+
import { n as r } from "./useChatbotStore-9Cw_oR6J.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-
|
|
111
|
+
//# sourceMappingURL=PkChatbotViewFiles-CNFLt8YO.js.map
|
package/dist-vue/_chunks/{PkChatbotViewFiles-qmLESi1V.js.map → PkChatbotViewFiles-CNFLt8YO.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PkChatbotViewFiles-qmLESi1V.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":";;;;;;;;;AAmBA,SAAgB,EAAW,GAGxB;CACC,IAAM,EAAE,cAAW,EAAQ,EAAE,UAAU,SAAS,CAAC,GAI3C,EAAE,iBAAc,oBAAiB,EAAiB,GAClD,KAAY,MACd,EAAK,QAAQ,EAAK,UAAU;CAqBhC,OAAO;EAAE,YApBU,MACf,EAAY,EAAK,QAAQ,KAAK,EAAa,EAAS,CAAI,CAAC;EAmBzC;EAAU,eAlBR,MAA4B;GAC9C,EAAa,EAAS,CAAI,CAAC;EAC/B;EAgB4C,WAZ1B,GAAmB,IAAW,OAAgB;GAC5D,IAAM,IAAS,CAAC,KAAY,EAAiB,EAAK,QAAQ;GAC1D,OAAO,KAAK,EAAK,KAAK,EAAK,YAAY,CAAM,GAAG,UAAU,UAAU;EACxE;EASsD,aAPlC,MAChB,IAAI,KAAK,CAAG,CAAC,CAAC,mBAAmB,EAAO,OAAO;GAC3C,MAAM;GACN,OAAO;GACP,KAAK;EACT,CAAC;CAE4D;AACrE;;;;;;;;;;;;;;;;ECvCI,IAAM,IAAQ,GAER,EAAE,GAAG,MAAO,EAAQ,EAAE,UAAU,SAAS,CAAC,GAC1C,EAAE,yBAAsB,EAAiB,GAEzC,IAAQ,EAAgB,EAAM,OAAO,GACrC,EAAE,cAAW,0BAAuB,EAAY,CAAK,GACrD,EAAE,cAAW,aAAU,iBAAc,aAAU,kBACjD,EAAW;GACP,MAAM,EAAM;GACZ,SAAS,EAAM;EACnB,CAAC,GAEC,IAAa,EAAmB,IAAI,GAEpC,IAAgB,OAAO,MAAsB;GAK1C,UAJiB,EAAkB;IACpC,OAAO,EAAG,8BAA8B;IACxC,eAAe,EAAG,0BAA0B;GAChD,CAAC,GAID;MAAW,QAAQ,EAAK;IACxB,IAAI;KACA,MAAM,EAAM,eAAe,EAAK,UAAU;IAC9C,QAAQ,CAER,UAAU;KACN,EAAW,QAAQ;IACvB;GAPwB;EAQ5B;;;GAIA,OAAA,EAAA,GAAA,EA2FM,OA3FN,GA2FM,CAxFQ,EAAA,CAAA,KAAsB,EAAA,CAAA,CAAS,CAAC,WAAM,KADhD,EAAA,GAAA,EAuBM,OAvBN,GAuBM,EApBF,EAAA,GAAA,EAmBM,GAAA,MAAA,EAlBU,IAAL,MADX,EAmBM,OAAA;IAjBD,KAAK;IACN,OAAM;GACN,GAAA,CAAA,EAcM,OAdN,GAcM,CAbF,AAAA,EAAA,OAAA,EAEM,OAAA,EAFD,OAAM,4CAA2C,GAAA,CAClD,EAAiC,OAAA,EAA5B,OAAM,oBAAmB,CAAA,CAAA,GAAA,EAAA,GAElC,EASM,OATN,GASM,CARF,EAIM,OAAA,EAHF,OAAK,EAAA,CAAC,4BACE,IAAC,IAAA,UAAA,OAAA,CAAA,EAAA,GAAA,CAAA,GAAA,AAAA,EAAA,OAAA,CACT,EAAiC,OAAA,EAA5B,OAAM,oBAAmB,GAAA,MAAA,EAAA,CAAA,CAAA,GAAA,CAAA,GAElC,AAAA,EAAA,OAAA,EAEM,OAAA,EAFD,OAAM,gCAA+B,GAAA,CACtC,EAAiC,OAAA,EAA5B,OAAM,oBAAmB,CAAA,CAAA,GAAA,EAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CASnC,GAAA,EAAA,EAAA,CAAA,KAAA,EAAA,CAAA,CAAS,CAAC,WAAM,KAD/B,EAAA,GAAA,EAYM,OAZN,GAYM,CATF,EAKM,OALN,GAKM,CAHF,EAEoC,GAAA;IADhC,MAAK;IACL,OAAM;GAEd,CAAA,CAAA,CAAA,GAAA,EAEO,QAFP,GAEO,EADA,EAAA,CAAA,CAAE,CAAA,0BAAA,CAAA,GAAA,CAAA,CAAA,CAAA,MAKb,EAAA,GAAA,EA+CK,MA/CL,GA+CK,EA5CD,EAAA,EAAA,GAAA,EA2CK,GAAA,MAAA,EA1Cc,EAAA,CAAA,IAAR,OADX,EAAA,GAAA,EA2CK,MAAA;IAzCA,KAAK,EAAK;IACX,OAAM;GACN,GAAA,CAAA,EAwBS,UAAA;IAvBL,MAAK;IACL,OAAM;IACL,OAAO,EAAA,CAAA,CAAE,CAAA,gBAAA;IACT,UAAK,MAAE,EAAA,CAAA,CAAQ,CAAC,CAAI;GACrB,GAAA,CAAA,EAUO,QAVP,GAUO,CARO,EAAA,CAAA,CAAS,CAAC,CAAI,KADxB,EAAA,GAAA,EAOkC,OAAA;;IAL7B,KAAK,EAAA,CAAA,CAAQ,CAAC,CAAI;IACnB,SAAQ;IACR,UAAS;IACT,KAAI;IACJ,OAAM;IACL,UAAK,MAAE,EAAA,CAAA,CAAY,CAAC,CAAI;GAC7B,GAAA,MAAA,IAAA,CAAA,MAAA,EAAA,GAAA,EAAwD,GAAA;;IAAxC,MAAM,EAAA,CAAA,CAAe,CAAC,EAAK,QAAQ;GAEvD,GAAA,MAAA,GAAA,CAAA,MAAA,CAAA,EAAA,CAAA,GAAA,EAOO,QAPP,GAOO,CANH,EAEO,QAFP,GAEO,EADA,EAAK,QAAQ,EAAA,CAAA,CAAE,CAAA,oBAAA,CAAA,GAAA,CAAA,GAEtB,EAEO,QAFP,GAEO,EADA,EAAA,CAAA,CAAU,CAAC,EAAK,SAAS,CAAA,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,GAAA,CAAA,GAIxC,EAaM,OAbN,GAaM,CAZF,EAIoC,GAAA;IAHhC,WAAU;IACV,MAAK;IACJ,OAAO,EAAA,CAAA,CAAE,CAAA,iBAAA;IACT,UAAK,MAAE,EAAA,CAAA,CAAQ,CAAC,GAAI,EAAA;GACzB,GAAA,MAAA,GAAA,CAAA,SAAA,SAAA,CAAA,GAAA,EAMmC,GAAA;IAL/B,WAAU;IACV,MAAK;IACJ,OAAO,EAAA,CAAA,CAAE,CAAA,mBAAA;IACT,SAAS,EAAA,UAAe,EAAK;IAC7B,UAAU,EAAA,UAAe,EAAK;IAC9B,UAAK,MAAE,EAAc,CAAI"}
|
|
1
|
+
{"version":3,"file":"PkChatbotViewFiles-CNFLt8YO.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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"}
|
package/dist-vue/_chunks/{PkChatbotViewProfile-DzfZnitQ.js → PkChatbotViewProfile-7p0ci3qm.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as e } from "./useChatbotStore-
|
|
1
|
+
import { n as e } from "./useChatbotStore-9Cw_oR6J.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-
|
|
86
|
+
//# sourceMappingURL=PkChatbotViewProfile-7p0ci3qm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PkChatbotViewProfile-DzfZnitQ.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;GAClB,OAGL;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-7p0ci3qm.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;GAClB,OAGL;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,4 +1,4 @@
|
|
|
1
|
-
import { r as e } from "./PkChatbotFilePreview-
|
|
1
|
+
import { r as e } from "./PkChatbotFilePreview-CwiVQDF2.js";
|
|
2
2
|
import { VvButton as t, VvButtonGroup as n } from "@volverjs/ui-vue/components";
|
|
3
3
|
import { Transition as r, computed as i, createBlock as a, createCommentVNode as o, createElementBlock as s, createElementVNode as c, createVNode as l, defineComponent as u, normalizeClass as d, normalizeStyle as f, onMounted as p, openBlock as m, ref as h, unref as g, watch as _, withCtx as v } from "vue";
|
|
4
4
|
import { useI18n as y } from "vue-i18n";
|
|
@@ -122,4 +122,4 @@ var b = [
|
|
|
122
122
|
//#endregion
|
|
123
123
|
export { S as t };
|
|
124
124
|
|
|
125
|
-
//# sourceMappingURL=PkSvgViewer-
|
|
125
|
+
//# sourceMappingURL=PkSvgViewer-D-0M5bqg.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PkSvgViewer-DPpE9fiz.js","names":[],"sources":["../../../../packages/components/src/PkSvgViewer.vue","../../../../packages/components/src/PkSvgViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { ref, onMounted, computed, watch } from 'vue'\n import { useLightbox } from './composables/useLightbox'\n import { useI18n } from 'vue-i18n'\n\n const props = defineProps<{\n svg?: string\n title?: string\n }>()\n\n const { t: $t } = useI18n()\n\n const svgRef = ref<HTMLDivElement | null>(null)\n const galleryRef = ref<HTMLElement>()\n\n const scale = ref(1)\n const translateX = ref(0)\n const translateY = ref(0)\n const startX = ref(0)\n const startY = ref(0)\n\n // PhotoSwipe integration\n const svgDataUrl = ref('')\n const svgWidth = ref(0)\n const svgHeight = ref(0)\n\n const { lightbox, init } = useLightbox({\n gallery: galleryRef,\n downloadable: true,\n openInNewTab: false,\n })\n\n const svgStyle = computed(() => ({\n transform: `translate(${translateX.value}px, ${translateY.value}px) scale(${scale.value})`,\n }))\n\n const zoomIn = () => {\n scale.value = Math.min(scale.value * 1.2, 10)\n }\n\n const zoomOut = () => {\n scale.value = Math.max(scale.value / 1.2, 0.1)\n }\n\n const reset = () => {\n scale.value = 1\n translateX.value = 0\n translateY.value = 0\n }\n\n const openLightbox = () => {\n if (svgDataUrl.value) {\n lightbox.value?.loadAndOpen(0)\n }\n }\n\n const isDragging = ref(false)\n\n // Pan logic\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault()\n isDragging.value = true\n startX.value = e.clientX - translateX.value\n startY.value = e.clientY - translateY.value\n }\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isDragging.value) return\n translateX.value = e.clientX - startX.value\n translateY.value = e.clientY - startY.value\n }\n\n const onMouseUp = () => {\n isDragging.value = false\n }\n\n function serializeSvg(): string | null {\n const svg = svgRef.value?.querySelector('svg')\n if (!svg) return null\n return new XMLSerializer().serializeToString(svg)\n }\n\n function updateSvgExport() {\n const svg = svgRef.value?.querySelector('svg')\n const serialized = serializeSvg()\n if (!svg || !serialized) return\n\n const viewBox = svg.viewBox?.baseVal\n svgWidth.value =\n viewBox?.width || svg.getBoundingClientRect().width || 1200\n svgHeight.value =\n viewBox?.height || svg.getBoundingClientRect().height || 800\n svgDataUrl.value = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(serialized)}`\n }\n\n watch(\n () => props.svg,\n () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n },\n )\n\n onMounted(async () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n await init()\n })\n</script>\n\n<template>\n <div\n class=\"pk-svg-viewer\"\n :class=\"{\n 'pk-svg-viewer--dragging': isDragging,\n }\">\n <!-- Hidden Gallery for Lightbox -->\n <div ref=\"galleryRef\" class=\"hidden\">\n <a\n v-if=\"svgDataUrl\"\n :href=\"svgDataUrl\"\n :data-pswp-src=\"svgDataUrl\"\n :data-pswp-width=\"svgWidth\"\n :data-pswp-height=\"svgHeight\"\n :title=\"title\" />\n </div>\n\n <!-- SVG Stage -->\n <div\n class=\"pk-svg-viewer__stage\"\n @mousedown=\"onMouseDown\"\n @mousemove=\"onMouseMove\"\n @mouseup=\"onMouseUp\"\n @mouseleave=\"onMouseUp\">\n <div\n ref=\"svgRef\"\n :style=\"svgStyle\"\n class=\"pk-svg-viewer__content\" />\n </div>\n\n <!-- Floating Toolbar -->\n <div class=\"pk-svg-viewer__toolbar\">\n <VvButtonGroup\n modifiers=\"compact\"\n class=\"bg-surface-1 shadow-md overflow-hidden\">\n <transition name=\"fade\">\n <VvButton\n key=\"reset\"\n modifiers=\"action-small\"\n :disabled=\"\n scale === 1 && translateX === 0 && translateY === 0\n \"\n icon=\"ri:aspect-ratio-line\"\n :title=\"$t('action.reset')\"\n @click=\"reset\" />\n </transition>\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:zoom-out-line\"\n :title=\"$t('action.zoomOut')\"\n @click=\"zoomOut\" />\n <VvButton\n modifiers=\"action-small\"\n icon=\"ri:zoom-in-line\"\n :title=\"$t('action.zoomIn')\"\n @click=\"zoomIn\" />\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:fullscreen-line\"\n :title=\"$t('action.expand')\"\n @click=\"openLightbox\" />\n </VvButtonGroup>\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-svg-viewer {\n position: relative;\n overflow: hidden;\n background-color: var(--color-surface-2);\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &__content {\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n transition: transform 0.3s ease-out;\n transform-origin: center center;\n\n svg {\n max-width: 100%;\n max-height: 100%;\n height: auto;\n width: auto;\n }\n }\n\n &__toolbar {\n position: absolute;\n top: var(--spacing-8);\n right: var(--spacing-8);\n z-index: var(--z-dropdown);\n opacity: 0;\n transition: var(--transition-opacity);\n }\n\n &__stage {\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n user-select: none;\n }\n\n &:hover {\n .pk-svg-viewer__toolbar {\n opacity: 1;\n }\n }\n\n &--dragging {\n .pk-svg-viewer__toolbar {\n opacity: 0.5 !important;\n pointer-events: none;\n\n & * {\n pointer-events: none;\n }\n }\n\n .pk-svg-viewer__content {\n cursor: grabbing !important;\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { ref, onMounted, computed, watch } from 'vue'\n import { useLightbox } from './composables/useLightbox'\n import { useI18n } from 'vue-i18n'\n\n const props = defineProps<{\n svg?: string\n title?: string\n }>()\n\n const { t: $t } = useI18n()\n\n const svgRef = ref<HTMLDivElement | null>(null)\n const galleryRef = ref<HTMLElement>()\n\n const scale = ref(1)\n const translateX = ref(0)\n const translateY = ref(0)\n const startX = ref(0)\n const startY = ref(0)\n\n // PhotoSwipe integration\n const svgDataUrl = ref('')\n const svgWidth = ref(0)\n const svgHeight = ref(0)\n\n const { lightbox, init } = useLightbox({\n gallery: galleryRef,\n downloadable: true,\n openInNewTab: false,\n })\n\n const svgStyle = computed(() => ({\n transform: `translate(${translateX.value}px, ${translateY.value}px) scale(${scale.value})`,\n }))\n\n const zoomIn = () => {\n scale.value = Math.min(scale.value * 1.2, 10)\n }\n\n const zoomOut = () => {\n scale.value = Math.max(scale.value / 1.2, 0.1)\n }\n\n const reset = () => {\n scale.value = 1\n translateX.value = 0\n translateY.value = 0\n }\n\n const openLightbox = () => {\n if (svgDataUrl.value) {\n lightbox.value?.loadAndOpen(0)\n }\n }\n\n const isDragging = ref(false)\n\n // Pan logic\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault()\n isDragging.value = true\n startX.value = e.clientX - translateX.value\n startY.value = e.clientY - translateY.value\n }\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isDragging.value) return\n translateX.value = e.clientX - startX.value\n translateY.value = e.clientY - startY.value\n }\n\n const onMouseUp = () => {\n isDragging.value = false\n }\n\n function serializeSvg(): string | null {\n const svg = svgRef.value?.querySelector('svg')\n if (!svg) return null\n return new XMLSerializer().serializeToString(svg)\n }\n\n function updateSvgExport() {\n const svg = svgRef.value?.querySelector('svg')\n const serialized = serializeSvg()\n if (!svg || !serialized) return\n\n const viewBox = svg.viewBox?.baseVal\n svgWidth.value =\n viewBox?.width || svg.getBoundingClientRect().width || 1200\n svgHeight.value =\n viewBox?.height || svg.getBoundingClientRect().height || 800\n svgDataUrl.value = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(serialized)}`\n }\n\n watch(\n () => props.svg,\n () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n },\n )\n\n onMounted(async () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n await init()\n })\n</script>\n\n<template>\n <div\n class=\"pk-svg-viewer\"\n :class=\"{\n 'pk-svg-viewer--dragging': isDragging,\n }\">\n <!-- Hidden Gallery for Lightbox -->\n <div ref=\"galleryRef\" class=\"hidden\">\n <a\n v-if=\"svgDataUrl\"\n :href=\"svgDataUrl\"\n :data-pswp-src=\"svgDataUrl\"\n :data-pswp-width=\"svgWidth\"\n :data-pswp-height=\"svgHeight\"\n :title=\"title\" />\n </div>\n\n <!-- SVG Stage -->\n <div\n class=\"pk-svg-viewer__stage\"\n @mousedown=\"onMouseDown\"\n @mousemove=\"onMouseMove\"\n @mouseup=\"onMouseUp\"\n @mouseleave=\"onMouseUp\">\n <div\n ref=\"svgRef\"\n :style=\"svgStyle\"\n class=\"pk-svg-viewer__content\" />\n </div>\n\n <!-- Floating Toolbar -->\n <div class=\"pk-svg-viewer__toolbar\">\n <VvButtonGroup\n modifiers=\"compact\"\n class=\"bg-surface-1 shadow-md overflow-hidden\">\n <transition name=\"fade\">\n <VvButton\n key=\"reset\"\n modifiers=\"action-small\"\n :disabled=\"\n scale === 1 && translateX === 0 && translateY === 0\n \"\n icon=\"ri:aspect-ratio-line\"\n :title=\"$t('action.reset')\"\n @click=\"reset\" />\n </transition>\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:zoom-out-line\"\n :title=\"$t('action.zoomOut')\"\n @click=\"zoomOut\" />\n <VvButton\n modifiers=\"action-small\"\n icon=\"ri:zoom-in-line\"\n :title=\"$t('action.zoomIn')\"\n @click=\"zoomIn\" />\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:fullscreen-line\"\n :title=\"$t('action.expand')\"\n @click=\"openLightbox\" />\n </VvButtonGroup>\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-svg-viewer {\n position: relative;\n overflow: hidden;\n background-color: var(--color-surface-2);\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &__content {\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n transition: transform 0.3s ease-out;\n transform-origin: center center;\n\n svg {\n max-width: 100%;\n max-height: 100%;\n height: auto;\n width: auto;\n }\n }\n\n &__toolbar {\n position: absolute;\n top: var(--spacing-8);\n right: var(--spacing-8);\n z-index: var(--z-dropdown);\n opacity: 0;\n transition: var(--transition-opacity);\n }\n\n &__stage {\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n user-select: none;\n }\n\n &:hover {\n .pk-svg-viewer__toolbar {\n opacity: 1;\n }\n }\n\n &--dragging {\n .pk-svg-viewer__toolbar {\n opacity: 0.5 !important;\n pointer-events: none;\n\n & * {\n pointer-events: none;\n }\n }\n\n .pk-svg-viewer__content {\n cursor: grabbing !important;\n }\n }\n }\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PkSvgViewer-D-0M5bqg.js","names":[],"sources":["../../../../packages/components/src/PkSvgViewer.vue","../../../../packages/components/src/PkSvgViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { ref, onMounted, computed, watch } from 'vue'\n import { useLightbox } from './composables/useLightbox'\n import { useI18n } from 'vue-i18n'\n\n const props = defineProps<{\n svg?: string\n title?: string\n }>()\n\n const { t: $t } = useI18n()\n\n const svgRef = ref<HTMLDivElement | null>(null)\n const galleryRef = ref<HTMLElement>()\n\n const scale = ref(1)\n const translateX = ref(0)\n const translateY = ref(0)\n const startX = ref(0)\n const startY = ref(0)\n\n // PhotoSwipe integration\n const svgDataUrl = ref('')\n const svgWidth = ref(0)\n const svgHeight = ref(0)\n\n const { lightbox, init } = useLightbox({\n gallery: galleryRef,\n downloadable: true,\n openInNewTab: false,\n })\n\n const svgStyle = computed(() => ({\n transform: `translate(${translateX.value}px, ${translateY.value}px) scale(${scale.value})`,\n }))\n\n const zoomIn = () => {\n scale.value = Math.min(scale.value * 1.2, 10)\n }\n\n const zoomOut = () => {\n scale.value = Math.max(scale.value / 1.2, 0.1)\n }\n\n const reset = () => {\n scale.value = 1\n translateX.value = 0\n translateY.value = 0\n }\n\n const openLightbox = () => {\n if (svgDataUrl.value) {\n lightbox.value?.loadAndOpen(0)\n }\n }\n\n const isDragging = ref(false)\n\n // Pan logic\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault()\n isDragging.value = true\n startX.value = e.clientX - translateX.value\n startY.value = e.clientY - translateY.value\n }\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isDragging.value) return\n translateX.value = e.clientX - startX.value\n translateY.value = e.clientY - startY.value\n }\n\n const onMouseUp = () => {\n isDragging.value = false\n }\n\n function serializeSvg(): string | null {\n const svg = svgRef.value?.querySelector('svg')\n if (!svg) return null\n return new XMLSerializer().serializeToString(svg)\n }\n\n function updateSvgExport() {\n const svg = svgRef.value?.querySelector('svg')\n const serialized = serializeSvg()\n if (!svg || !serialized) return\n\n const viewBox = svg.viewBox?.baseVal\n svgWidth.value =\n viewBox?.width || svg.getBoundingClientRect().width || 1200\n svgHeight.value =\n viewBox?.height || svg.getBoundingClientRect().height || 800\n svgDataUrl.value = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(serialized)}`\n }\n\n watch(\n () => props.svg,\n () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n },\n )\n\n onMounted(async () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n await init()\n })\n</script>\n\n<template>\n <div\n class=\"pk-svg-viewer\"\n :class=\"{\n 'pk-svg-viewer--dragging': isDragging,\n }\">\n <!-- Hidden Gallery for Lightbox -->\n <div ref=\"galleryRef\" class=\"hidden\">\n <a\n v-if=\"svgDataUrl\"\n :href=\"svgDataUrl\"\n :data-pswp-src=\"svgDataUrl\"\n :data-pswp-width=\"svgWidth\"\n :data-pswp-height=\"svgHeight\"\n :title=\"title\" />\n </div>\n\n <!-- SVG Stage -->\n <div\n class=\"pk-svg-viewer__stage\"\n @mousedown=\"onMouseDown\"\n @mousemove=\"onMouseMove\"\n @mouseup=\"onMouseUp\"\n @mouseleave=\"onMouseUp\">\n <div\n ref=\"svgRef\"\n :style=\"svgStyle\"\n class=\"pk-svg-viewer__content\" />\n </div>\n\n <!-- Floating Toolbar -->\n <div class=\"pk-svg-viewer__toolbar\">\n <VvButtonGroup\n modifiers=\"compact\"\n class=\"bg-surface-1 shadow-md overflow-hidden\">\n <transition name=\"fade\">\n <VvButton\n key=\"reset\"\n modifiers=\"action-small\"\n :disabled=\"\n scale === 1 && translateX === 0 && translateY === 0\n \"\n icon=\"ri:aspect-ratio-line\"\n :title=\"$t('action.reset')\"\n @click=\"reset\" />\n </transition>\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:zoom-out-line\"\n :title=\"$t('action.zoomOut')\"\n @click=\"zoomOut\" />\n <VvButton\n modifiers=\"action-small\"\n icon=\"ri:zoom-in-line\"\n :title=\"$t('action.zoomIn')\"\n @click=\"zoomIn\" />\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:fullscreen-line\"\n :title=\"$t('action.expand')\"\n @click=\"openLightbox\" />\n </VvButtonGroup>\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-svg-viewer {\n position: relative;\n overflow: hidden;\n background-color: var(--color-surface-2);\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &__content {\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n transition: transform 0.3s ease-out;\n transform-origin: center center;\n\n svg {\n max-width: 100%;\n max-height: 100%;\n height: auto;\n width: auto;\n }\n }\n\n &__toolbar {\n position: absolute;\n top: var(--spacing-8);\n right: var(--spacing-8);\n z-index: var(--z-dropdown);\n opacity: 0;\n transition: var(--transition-opacity);\n }\n\n &__stage {\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n user-select: none;\n }\n\n &:hover {\n .pk-svg-viewer__toolbar {\n opacity: 1;\n }\n }\n\n &--dragging {\n .pk-svg-viewer__toolbar {\n opacity: 0.5 !important;\n pointer-events: none;\n\n & * {\n pointer-events: none;\n }\n }\n\n .pk-svg-viewer__content {\n cursor: grabbing !important;\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { ref, onMounted, computed, watch } from 'vue'\n import { useLightbox } from './composables/useLightbox'\n import { useI18n } from 'vue-i18n'\n\n const props = defineProps<{\n svg?: string\n title?: string\n }>()\n\n const { t: $t } = useI18n()\n\n const svgRef = ref<HTMLDivElement | null>(null)\n const galleryRef = ref<HTMLElement>()\n\n const scale = ref(1)\n const translateX = ref(0)\n const translateY = ref(0)\n const startX = ref(0)\n const startY = ref(0)\n\n // PhotoSwipe integration\n const svgDataUrl = ref('')\n const svgWidth = ref(0)\n const svgHeight = ref(0)\n\n const { lightbox, init } = useLightbox({\n gallery: galleryRef,\n downloadable: true,\n openInNewTab: false,\n })\n\n const svgStyle = computed(() => ({\n transform: `translate(${translateX.value}px, ${translateY.value}px) scale(${scale.value})`,\n }))\n\n const zoomIn = () => {\n scale.value = Math.min(scale.value * 1.2, 10)\n }\n\n const zoomOut = () => {\n scale.value = Math.max(scale.value / 1.2, 0.1)\n }\n\n const reset = () => {\n scale.value = 1\n translateX.value = 0\n translateY.value = 0\n }\n\n const openLightbox = () => {\n if (svgDataUrl.value) {\n lightbox.value?.loadAndOpen(0)\n }\n }\n\n const isDragging = ref(false)\n\n // Pan logic\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault()\n isDragging.value = true\n startX.value = e.clientX - translateX.value\n startY.value = e.clientY - translateY.value\n }\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isDragging.value) return\n translateX.value = e.clientX - startX.value\n translateY.value = e.clientY - startY.value\n }\n\n const onMouseUp = () => {\n isDragging.value = false\n }\n\n function serializeSvg(): string | null {\n const svg = svgRef.value?.querySelector('svg')\n if (!svg) return null\n return new XMLSerializer().serializeToString(svg)\n }\n\n function updateSvgExport() {\n const svg = svgRef.value?.querySelector('svg')\n const serialized = serializeSvg()\n if (!svg || !serialized) return\n\n const viewBox = svg.viewBox?.baseVal\n svgWidth.value =\n viewBox?.width || svg.getBoundingClientRect().width || 1200\n svgHeight.value =\n viewBox?.height || svg.getBoundingClientRect().height || 800\n svgDataUrl.value = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(serialized)}`\n }\n\n watch(\n () => props.svg,\n () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n },\n )\n\n onMounted(async () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n await init()\n })\n</script>\n\n<template>\n <div\n class=\"pk-svg-viewer\"\n :class=\"{\n 'pk-svg-viewer--dragging': isDragging,\n }\">\n <!-- Hidden Gallery for Lightbox -->\n <div ref=\"galleryRef\" class=\"hidden\">\n <a\n v-if=\"svgDataUrl\"\n :href=\"svgDataUrl\"\n :data-pswp-src=\"svgDataUrl\"\n :data-pswp-width=\"svgWidth\"\n :data-pswp-height=\"svgHeight\"\n :title=\"title\" />\n </div>\n\n <!-- SVG Stage -->\n <div\n class=\"pk-svg-viewer__stage\"\n @mousedown=\"onMouseDown\"\n @mousemove=\"onMouseMove\"\n @mouseup=\"onMouseUp\"\n @mouseleave=\"onMouseUp\">\n <div\n ref=\"svgRef\"\n :style=\"svgStyle\"\n class=\"pk-svg-viewer__content\" />\n </div>\n\n <!-- Floating Toolbar -->\n <div class=\"pk-svg-viewer__toolbar\">\n <VvButtonGroup\n modifiers=\"compact\"\n class=\"bg-surface-1 shadow-md overflow-hidden\">\n <transition name=\"fade\">\n <VvButton\n key=\"reset\"\n modifiers=\"action-small\"\n :disabled=\"\n scale === 1 && translateX === 0 && translateY === 0\n \"\n icon=\"ri:aspect-ratio-line\"\n :title=\"$t('action.reset')\"\n @click=\"reset\" />\n </transition>\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:zoom-out-line\"\n :title=\"$t('action.zoomOut')\"\n @click=\"zoomOut\" />\n <VvButton\n modifiers=\"action-small\"\n icon=\"ri:zoom-in-line\"\n :title=\"$t('action.zoomIn')\"\n @click=\"zoomIn\" />\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:fullscreen-line\"\n :title=\"$t('action.expand')\"\n @click=\"openLightbox\" />\n </VvButtonGroup>\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-svg-viewer {\n position: relative;\n overflow: hidden;\n background-color: var(--color-surface-2);\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &__content {\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n transition: transform 0.3s ease-out;\n transform-origin: center center;\n\n svg {\n max-width: 100%;\n max-height: 100%;\n height: auto;\n width: auto;\n }\n }\n\n &__toolbar {\n position: absolute;\n top: var(--spacing-8);\n right: var(--spacing-8);\n z-index: var(--z-dropdown);\n opacity: 0;\n transition: var(--transition-opacity);\n }\n\n &__stage {\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n user-select: none;\n }\n\n &:hover {\n .pk-svg-viewer__toolbar {\n opacity: 1;\n }\n }\n\n &--dragging {\n .pk-svg-viewer__toolbar {\n opacity: 0.5 !important;\n pointer-events: none;\n\n & * {\n pointer-events: none;\n }\n }\n\n .pk-svg-viewer__content {\n cursor: grabbing !important;\n }\n }\n }\n</style>\n"],"mappings":"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"}
|
|
@@ -57,7 +57,7 @@ var h = { class: "flex items-center gap-6 text-12 text-word-3 border border-surf
|
|
|
57
57
|
style: "unit",
|
|
58
58
|
unit: "meter"
|
|
59
59
|
})) + ") ", 1)], 64))])], 64)) : T.value ? (d(), a(n, { key: 2 }, [r[0] ||= o("span", { class: "i-vv-alert-circle text-16 shrink-0 text-danger" }, null, -1), o("span", null, f(T.value.error === "permission_denied" ? p(y)("message.geolocationPermissionDenied") : p(y)("message.geolocationUnavailable")), 1)], 64)) : i("", !0) : (d(), a(n, { key: 0 }, [c(l, {
|
|
60
|
-
name: "
|
|
60
|
+
name: "loading-loop",
|
|
61
61
|
class: "text-16"
|
|
62
62
|
}), o("span", null, f(p(y)("messagePart.requestGeolocation")), 1)], 64))]);
|
|
63
63
|
};
|
|
@@ -66,4 +66,4 @@ var h = { class: "flex items-center gap-6 text-12 text-word-3 border border-surf
|
|
|
66
66
|
//#endregion
|
|
67
67
|
export { g as default };
|
|
68
68
|
|
|
69
|
-
//# sourceMappingURL=PkToolRequestGeolocation-
|
|
69
|
+
//# sourceMappingURL=PkToolRequestGeolocation-Bxqz9oSo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PkToolRequestGeolocation-Bxqz9oSo.js","names":["$n"],"sources":["../../../../packages/components/src/chat/PkToolRequestGeolocation.vue","../../../../packages/components/src/chat/PkToolRequestGeolocation.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, onMounted } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n import type { ReverseGeocodeFn } from 'utils/src/device-context'\n\n type GeolocationOutput =\n | {\n latitude: number\n longitude: number\n accuracy?: number\n city?: string\n }\n | { error: 'permission_denied' | 'position_unavailable' | 'timeout' }\n\n const props = defineProps<{\n part: unknown\n reverseGeocode?: ReverseGeocodeFn\n /**\n * Whether a voice call is on the line right now. A request drawn\n * during a call arrives already resolved with the render ack, and the\n * browser prompt must fire only while the caller who was asked is\n * still there: reopening the conversation later must not ask again.\n */\n callLive?: boolean\n }>()\n\n const emit = defineEmits<{\n result: [output: GeolocationOutput]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n input?: { reason?: string }\n /** The position, or the voice render ack (a string). */\n output?: GeolocationOutput | string\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n\n /**\n * The ack is not a position: it only says the card was drawn during a\n * call (see `isVoiceRenderAck`, same rule as the forms). Besides gating\n * the prompt below, it must be kept away from the output parsing: `in`\n * on a string throws, which broke this card on every spoken turn.\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n const isOutputAvailable = computed(\n () => state.value === 'output-available' && !isRenderAck.value,\n )\n\n const successOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || 'error' in out) {\n return undefined\n }\n return out\n })\n\n const errorOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || !('error' in out)) {\n return undefined\n }\n return out\n })\n\n /** Maps GeolocationPositionError.code to a typed string */\n function mapGeolocationError(\n code: number,\n ): 'permission_denied' | 'position_unavailable' | 'timeout' {\n if (code === 1) {\n return 'permission_denied'\n }\n if (code === 3) {\n return 'timeout'\n }\n return 'position_unavailable'\n }\n\n onMounted(() => {\n const isAwaitingPosition =\n state.value === 'input-available' ||\n (isRenderAck.value && props.callLive === true)\n if (!isAwaitingPosition) {\n return\n }\n\n if (!navigator.geolocation) {\n emit('result', { error: 'position_unavailable' })\n return\n }\n\n navigator.geolocation.getCurrentPosition(\n async (position) => {\n const { latitude, longitude, accuracy } = position.coords\n const city = props.reverseGeocode\n ? await props\n .reverseGeocode(latitude, longitude)\n .catch(() => undefined)\n : undefined\n emit('result', { latitude, longitude, accuracy, city })\n },\n (error) => {\n emit('result', { error: mapGeolocationError(error.code) })\n },\n { timeout: 15000 },\n )\n })\n</script>\n\n<template>\n <div\n class=\"flex items-center gap-6 text-12 text-word-3 border border-surface-3 rounded-xl px-sm py-10\">\n <!-- Loading state -->\n <template v-if=\"!isOutputAvailable\">\n <VvIcon name=\"loading-loop\" class=\"text-16\" />\n <span>{{ $t('messagePart.requestGeolocation') }}</span>\n </template>\n\n <!-- Success state -->\n <template v-else-if=\"successOutput\">\n <VvIcon name=\"ri:map-pin-line\" class=\"text-16\" />\n <span>\n {{\n $n(successOutput.latitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }},\n {{\n $n(successOutput.longitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }}\n <template v-if=\"successOutput.accuracy !== undefined\">\n (±{{\n $n(successOutput.accuracy, {\n style: 'unit',\n unit: 'meter',\n })\n }})\n </template>\n </span>\n </template>\n\n <!-- Error state -->\n <template v-else-if=\"errorOutput\">\n <span class=\"i-vv-alert-circle text-16 shrink-0 text-danger\" />\n <span>\n {{\n errorOutput.error === 'permission_denied'\n ? $t('message.geolocationPermissionDenied')\n : $t('message.geolocationUnavailable')\n }}\n </span>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, onMounted } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n import type { ReverseGeocodeFn } from 'utils/src/device-context'\n\n type GeolocationOutput =\n | {\n latitude: number\n longitude: number\n accuracy?: number\n city?: string\n }\n | { error: 'permission_denied' | 'position_unavailable' | 'timeout' }\n\n const props = defineProps<{\n part: unknown\n reverseGeocode?: ReverseGeocodeFn\n /**\n * Whether a voice call is on the line right now. A request drawn\n * during a call arrives already resolved with the render ack, and the\n * browser prompt must fire only while the caller who was asked is\n * still there: reopening the conversation later must not ask again.\n */\n callLive?: boolean\n }>()\n\n const emit = defineEmits<{\n result: [output: GeolocationOutput]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n input?: { reason?: string }\n /** The position, or the voice render ack (a string). */\n output?: GeolocationOutput | string\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n\n /**\n * The ack is not a position: it only says the card was drawn during a\n * call (see `isVoiceRenderAck`, same rule as the forms). Besides gating\n * the prompt below, it must be kept away from the output parsing: `in`\n * on a string throws, which broke this card on every spoken turn.\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n const isOutputAvailable = computed(\n () => state.value === 'output-available' && !isRenderAck.value,\n )\n\n const successOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || 'error' in out) {\n return undefined\n }\n return out\n })\n\n const errorOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || !('error' in out)) {\n return undefined\n }\n return out\n })\n\n /** Maps GeolocationPositionError.code to a typed string */\n function mapGeolocationError(\n code: number,\n ): 'permission_denied' | 'position_unavailable' | 'timeout' {\n if (code === 1) {\n return 'permission_denied'\n }\n if (code === 3) {\n return 'timeout'\n }\n return 'position_unavailable'\n }\n\n onMounted(() => {\n const isAwaitingPosition =\n state.value === 'input-available' ||\n (isRenderAck.value && props.callLive === true)\n if (!isAwaitingPosition) {\n return\n }\n\n if (!navigator.geolocation) {\n emit('result', { error: 'position_unavailable' })\n return\n }\n\n navigator.geolocation.getCurrentPosition(\n async (position) => {\n const { latitude, longitude, accuracy } = position.coords\n const city = props.reverseGeocode\n ? await props\n .reverseGeocode(latitude, longitude)\n .catch(() => undefined)\n : undefined\n emit('result', { latitude, longitude, accuracy, city })\n },\n (error) => {\n emit('result', { error: mapGeolocationError(error.code) })\n },\n { timeout: 15000 },\n )\n })\n</script>\n\n<template>\n <div\n class=\"flex items-center gap-6 text-12 text-word-3 border border-surface-3 rounded-xl px-sm py-10\">\n <!-- Loading state -->\n <template v-if=\"!isOutputAvailable\">\n <VvIcon name=\"loading-loop\" class=\"text-16\" />\n <span>{{ $t('messagePart.requestGeolocation') }}</span>\n </template>\n\n <!-- Success state -->\n <template v-else-if=\"successOutput\">\n <VvIcon name=\"ri:map-pin-line\" class=\"text-16\" />\n <span>\n {{\n $n(successOutput.latitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }},\n {{\n $n(successOutput.longitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }}\n <template v-if=\"successOutput.accuracy !== undefined\">\n (±{{\n $n(successOutput.accuracy, {\n style: 'unit',\n unit: 'meter',\n })\n }})\n </template>\n </span>\n </template>\n\n <!-- Error state -->\n <template v-else-if=\"errorOutput\">\n <span class=\"i-vv-alert-circle text-16 shrink-0 text-danger\" />\n <span>\n {{\n errorOutput.error === 'permission_denied'\n ? $t('message.geolocationPermissionDenied')\n : $t('message.geolocationUnavailable')\n }}\n </span>\n </template>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;EAgBI,IAAM,IAAQ,GAYR,IAAO,GAIP,EAAE,GAAG,MAAO,EAAQ,EAAE,UAAU,SAAS,CAAC,GAE1C,IAAW,QACN,EAAM,IAMhB,GAEK,IAAQ,QAAe,EAAS,MAAM,SAAS,SAAS,GAQxD,IAAc,QAAe,EAAiB,EAAS,MAAM,MAAM,CAAC,GAEpE,IAAoB,QAChB,EAAM,UAAU,sBAAsB,CAAC,EAAY,KAC7D,GAEM,IAAgB,QAAe;GACjC,IAAM,IAAM,EAAS,MAAM;GACvB,OAAC,KAAO,OAAO,KAAQ,YAAY,WAAW,IAGlD,OAAO;EACX,CAAC,GAEK,IAAc,QAAe;GAC/B,IAAM,IAAM,EAAS,MAAM;GACvB,IAAC,KAAO,OAAO,KAAQ,YAAc,WAAW,GAGpD,OAAO;EACX,CAAC;EAGD,SAAS,EACL,GACwD;GAOxD,OANI,MAAS,IACF,sBAEP,MAAS,IACF,YAEJ;EACX;SAEA,QAAgB;GAER,MAAM,UAAU,qBACf,EAAY,SAAS,EAAM,aAAa,IAK7C;QAAI,CAAC,UAAU,aAAa;KACxB,EAAK,UAAU,EAAE,OAAO,uBAAuB,CAAC;KAChD;IACJ;IAEA,UAAU,YAAY,mBAClB,OAAO,MAAa;KAChB,IAAM,EAAE,aAAU,cAAW,gBAAa,EAAS,QAC7C,IAAO,EAAM,iBACb,MAAM,EACD,eAAe,GAAU,CAAS,CAAA,CAClC,YAAY,KAAA,CAAS,IAC1B,KAAA;KACN,EAAK,UAAU;MAAE;MAAU;MAAW;MAAU;KAAK,CAAC;IAC1D,IACC,MAAU;KACP,EAAK,UAAU,EAAE,OAAO,EAAoB,EAAM,IAAI,EAAE,CAAC;IAC7D,GACA,EAAE,SAAS,KAAM,CACrB;GAhBA;EAiBJ,CAAC;;GAID,OAAA,EAAA,GAAA,EAgDM,OAhDN,GAgDM,CA7Ce,EAAA,QAMI,EAAA,SAArB,EAAA,GAAA,EA0BW,GAAA,EAAA,KAAA,EAAA,GAAA,CAzBP,EAAiD,GAAA;IAAzC,MAAK;IAAkB,OAAM;GACrC,CAAA,GAAA,EAuBO,QAAA,MAAA,CArBCA,EAAAA,EAAAA,EAAAA,GAAG,EAAA,MAAc,UAAQ;;;;GAK3B,CAAA,CAAA,IAAA,OACF,EACIA,EAAAA,GAAG,EAAA,MAAc,WAAS;;;;GAK5B,CAAA,CAAA,IAAA,KACF,CAAA,GAAgB,EAAA,MAAc,aAAa,KAAA,IAO3C,EAAA,IAAA,EAAA,KAPA,EAAA,GAAA,EAOW,GAAA,EAAA,KAAA,EAAA,GAAA,CAP2C,EAAA,QAChD,EACEA,EAAAA,GAAG,EAAA,MAAc,UAAQ;;;GAI3B,CAAA,CAAA,IAAA,MACN,CAAA,CAAA,GAAA,EAAA,EAAA,CAAA,CAAA,GAAA,EAAA,KAKa,EAAA,SAArB,EAAA,GAAA,EASW,GAAA,EAAA,KAAA,EAAA,GAAA,CARP,AAAA,EAAA,OAAA,EAA+D,QAAA,EAAzD,OAAM,iDAAgD,GAAA,MAAA,EAAA,GAC5D,EAMO,QAAA,MAAA,EAJC,EAAA,MAAY,UAAK,sBAAmD,EAAA,CAAA,CAAE,CAAA,qCAAA,IAAkE,EAAA,CAAA,CAAE,CAAA,gCAAA,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA,KAAA,EAAA,IAAA,EAAA,KAvCtJ,EAAA,GAAA,EAGW,GAAA,EAAA,KAAA,EAAA,GAAA,CAFP,EAA8C,GAAA;IAAtC,MAAK;IAAe,OAAM;GAClC,CAAA,GAAA,EAAuD,QAAA,MAAA,EAA9C,EAAA,CAAA,CAAE,CAAA,gCAAA,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA,EAqCuI,CAAA"}
|
|
@@ -98,7 +98,7 @@ var v = { class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-s
|
|
|
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",
|
|
100
100
|
class: "text-16 text-word-3"
|
|
101
|
-
}), c("strong", ee, h(g(N)("messagePart.requestOAuthConnectionSuccess", { name: I.value })), 1)])) : H.value ? (p(), s("div", te, [l(u, { name: "
|
|
101
|
+
}), c("strong", ee, h(g(N)("messagePart.requestOAuthConnectionSuccess", { name: I.value })), 1)])) : H.value ? (p(), s("div", te, [l(u, { name: "loading-loop" }), c("span", ne, h(g(N)("messagePart.requestOAuthConnection")), 1)])) : U.value ? (p(), s("div", re, [c("div", C, [l(u, {
|
|
102
102
|
name: "ri:close-line",
|
|
103
103
|
class: "text-danger"
|
|
104
104
|
}), c("strong", w, h(U.value), 1)]), G.value ? (p(), a(d, {
|
|
@@ -119,4 +119,4 @@ var v = { class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-s
|
|
|
119
119
|
//#endregion
|
|
120
120
|
export { A as default };
|
|
121
121
|
|
|
122
|
-
//# sourceMappingURL=PkToolRequestOAuthConnection-
|
|
122
|
+
//# sourceMappingURL=PkToolRequestOAuthConnection-C6rXgb0s.js.map
|