@8wave/ai-elements 0.118.0 → 0.119.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/_chunks/{PkStreamingMarkdown--CcBvs19.js → PkStreamingMarkdown-Bry3y6Ru.js} +789 -780
  2. package/dist/_chunks/PkStreamingMarkdown-Bry3y6Ru.js.map +1 -0
  3. package/dist/_chunks/{PkToolShowArtifact-BVGN7hV2.js → PkToolShowArtifact-CfJLe8vD.js} +2 -2
  4. package/dist/_chunks/{PkToolShowArtifact-BVGN7hV2.js.map → PkToolShowArtifact-CfJLe8vD.js.map} +1 -1
  5. package/dist/_chunks/{PkToolShowContactForm-Blrwqpl0.js → PkToolShowContactForm-CQQ6p4bJ.js} +2 -2
  6. package/dist/_chunks/{PkToolShowContactForm-Blrwqpl0.js.map → PkToolShowContactForm-CQQ6p4bJ.js.map} +1 -1
  7. package/dist/_chunks/{PkToolShowForm-BpGHaN6x.js → PkToolShowForm-CGAc1O6K.js} +4 -4
  8. package/dist/_chunks/{PkToolShowForm-BpGHaN6x.js.map → PkToolShowForm-CGAc1O6K.js.map} +1 -1
  9. package/dist/_chunks/{PkToolShowSources-8c9RvGV1.js → PkToolShowSources-BJ-XFrLD.js} +3 -3
  10. package/dist/_chunks/{PkToolShowSources-8c9RvGV1.js.map → PkToolShowSources-BJ-XFrLD.js.map} +1 -1
  11. package/dist/_chunks/{markdown-c59ir6tw.js → markdown-DpoAzJSk.js} +64 -61
  12. package/dist/_chunks/markdown-DpoAzJSk.js.map +1 -0
  13. package/dist/ai-elements.es.js +1944 -1865
  14. package/dist/ai-elements.es.js.map +1 -1
  15. package/dist-vue/PkChatbot.js +1 -1
  16. package/dist-vue/PkChatbotEmptyState.js +1 -1
  17. package/dist-vue/PkChatbotInput.js +1 -1
  18. package/dist-vue/PkChatbotMessages.js +1 -1
  19. package/dist-vue/PkChatbotStepPayload.js +1 -1
  20. package/dist-vue/PkChatbotStepToolDetail.js +1 -1
  21. package/dist-vue/PkChatbotSteps.js +1 -1
  22. package/dist-vue/PkChatbotViewChat.js +1 -1
  23. package/dist-vue/PkChatbotViewFiles.js +1 -1
  24. package/dist-vue/PkChatbotViewProfile.js +1 -1
  25. package/dist-vue/_chunks/{Document-B-oyVh7v.js → Document-CV-RJu_p.js} +64 -61
  26. package/dist-vue/_chunks/Document-CV-RJu_p.js.map +1 -0
  27. package/dist-vue/_chunks/PkAvatar-BTqu8tn9.js +44 -0
  28. package/dist-vue/_chunks/PkAvatar-BTqu8tn9.js.map +1 -0
  29. package/dist-vue/_chunks/PkChatbot-CcNC2r_E.js +276 -0
  30. package/dist-vue/_chunks/PkChatbot-CcNC2r_E.js.map +1 -0
  31. package/dist-vue/_chunks/{PkChatbotEmptyState-D2dhInfG.js → PkChatbotEmptyState-Toj4CqXV.js} +3 -3
  32. package/dist-vue/_chunks/{PkChatbotEmptyState-D2dhInfG.js.map → PkChatbotEmptyState-Toj4CqXV.js.map} +1 -1
  33. package/dist-vue/_chunks/{PkChatbotInput-CQdxFbBR.js → PkChatbotInput-BfZqRpQ2.js} +2 -2
  34. package/dist-vue/_chunks/{PkChatbotInput-CQdxFbBR.js.map → PkChatbotInput-BfZqRpQ2.js.map} +1 -1
  35. package/dist-vue/_chunks/{PkChatbotMessages-coscJcpy.js → PkChatbotMessages-B6qYZsMZ.js} +283 -283
  36. package/dist-vue/_chunks/{PkChatbotMessages-coscJcpy.js.map → PkChatbotMessages-B6qYZsMZ.js.map} +1 -1
  37. package/dist-vue/_chunks/{PkChatbotStepPayload-DnqFluQZ.js → PkChatbotStepPayload-D0eoPBQ8.js} +2 -2
  38. package/dist-vue/_chunks/{PkChatbotStepPayload-DnqFluQZ.js.map → PkChatbotStepPayload-D0eoPBQ8.js.map} +1 -1
  39. package/dist-vue/_chunks/{PkChatbotStepToolDetail-BMslY_BG.js → PkChatbotStepToolDetail-CMx4cw1D.js} +3 -3
  40. package/dist-vue/_chunks/{PkChatbotStepToolDetail-BMslY_BG.js.map → PkChatbotStepToolDetail-CMx4cw1D.js.map} +1 -1
  41. package/dist-vue/_chunks/{PkChatbotSteps-7FT6Ju6_.js → PkChatbotSteps-DeZQhGRU.js} +38 -38
  42. package/dist-vue/_chunks/{PkChatbotSteps-7FT6Ju6_.js.map → PkChatbotSteps-DeZQhGRU.js.map} +1 -1
  43. package/dist-vue/_chunks/{PkChatbotViewChat-Cko-Cw6c.js → PkChatbotViewChat--Q90F_Kg.js} +7 -7
  44. package/dist-vue/_chunks/PkChatbotViewChat--Q90F_Kg.js.map +1 -0
  45. package/dist-vue/_chunks/{PkChatbotViewFiles-gqCumfod.js → PkChatbotViewFiles-CA20TBFY.js} +2 -2
  46. package/dist-vue/_chunks/{PkChatbotViewFiles-gqCumfod.js.map → PkChatbotViewFiles-CA20TBFY.js.map} +1 -1
  47. package/dist-vue/_chunks/{PkChatbotViewProfile-B57VRVaO.js → PkChatbotViewProfile-CyzRxE3D.js} +2 -2
  48. package/dist-vue/_chunks/{PkChatbotViewProfile-B57VRVaO.js.map → PkChatbotViewProfile-CyzRxE3D.js.map} +1 -1
  49. package/dist-vue/_chunks/{PkStreamingMarkdown-BtyWXY2k.js → PkStreamingMarkdown-C55Fi0Pd.js} +2 -2
  50. package/dist-vue/_chunks/{PkStreamingMarkdown-BtyWXY2k.js.map → PkStreamingMarkdown-C55Fi0Pd.js.map} +1 -1
  51. package/dist-vue/_chunks/{PkToolShowArtifact-Ritx54_X.js → PkToolShowArtifact-z1qTnFkA.js} +2 -2
  52. package/dist-vue/_chunks/{PkToolShowArtifact-Ritx54_X.js.map → PkToolShowArtifact-z1qTnFkA.js.map} +1 -1
  53. package/dist-vue/_chunks/{PkToolShowContactForm-D30IsZGy.js → PkToolShowContactForm-BjNpnv6y.js} +2 -2
  54. package/dist-vue/_chunks/{PkToolShowContactForm-D30IsZGy.js.map → PkToolShowContactForm-BjNpnv6y.js.map} +1 -1
  55. package/dist-vue/_chunks/{PkToolShowDiagram-PEozSAqU.js → PkToolShowDiagram-CqkabP0e.js} +2 -2
  56. package/dist-vue/_chunks/{PkToolShowDiagram-PEozSAqU.js.map → PkToolShowDiagram-CqkabP0e.js.map} +1 -1
  57. package/dist-vue/_chunks/{PkToolShowForm-ZmdM9J9x.js → PkToolShowForm-CZFWZub7.js} +5 -5
  58. package/dist-vue/_chunks/{PkToolShowForm-ZmdM9J9x.js.map → PkToolShowForm-CZFWZub7.js.map} +1 -1
  59. package/dist-vue/_chunks/{PkToolShowImageGallery-Dkjf7Tw_.js → PkToolShowImageGallery-CkfXP_lD.js} +2 -2
  60. package/dist-vue/_chunks/{PkToolShowImageGallery-Dkjf7Tw_.js.map → PkToolShowImageGallery-CkfXP_lD.js.map} +1 -1
  61. package/dist-vue/_chunks/{PkToolShowProductList-CsHYM8vl.js → PkToolShowProductList-De-PtmD-.js} +2 -2
  62. package/dist-vue/_chunks/{PkToolShowProductList-CsHYM8vl.js.map → PkToolShowProductList-De-PtmD-.js.map} +1 -1
  63. package/dist-vue/_chunks/{PkToolShowSources-CKZkcPLA.js → PkToolShowSources-CSN7S0zo.js} +3 -3
  64. package/dist-vue/_chunks/{PkToolShowSources-CKZkcPLA.js.map → PkToolShowSources-CSN7S0zo.js.map} +1 -1
  65. package/dist-vue/_chunks/{PkToolShowVideo-CKm1fVQs.js → PkToolShowVideo-B_Q93E2y.js} +2 -2
  66. package/dist-vue/_chunks/{PkToolShowVideo-CKm1fVQs.js.map → PkToolShowVideo-B_Q93E2y.js.map} +1 -1
  67. package/dist-vue/_chunks/{PkToolShowWebPages-DLEC9cuh.js → PkToolShowWebPages-D3CdjJUc.js} +2 -2
  68. package/dist-vue/_chunks/{PkToolShowWebPages-DLEC9cuh.js.map → PkToolShowWebPages-D3CdjJUc.js.map} +1 -1
  69. package/dist-vue/_chunks/{utils-fxHiCnzf.js → utils-CwIMEuAv.js} +41 -32
  70. package/dist-vue/_chunks/utils-CwIMEuAv.js.map +1 -0
  71. package/dist-vue/apps/web-component/src/composables/useChatbotAgent.d.ts +1 -4
  72. package/dist-vue/composables.js +188 -115
  73. package/dist-vue/composables.js.map +1 -1
  74. package/dist-vue/index.js +4005 -3980
  75. package/dist-vue/index.js.map +1 -1
  76. package/dist-vue/locales.js +4 -0
  77. package/dist-vue/packages/components/src/chat/PkAvatar.d.ts +7 -0
  78. package/dist-vue/packages/components/src/chat/utils.d.ts +10 -0
  79. package/dist-vue/packages/models/src/schema/Document.d.ts +25 -0
  80. package/dist-vue/packages/models/src/schema/Notification.d.ts +2 -0
  81. package/dist-vue/packages/models/src/schema/Platform.d.ts +72 -0
  82. package/dist-vue/style.css +1 -1
  83. package/package.json +1 -1
  84. package/dist/_chunks/PkStreamingMarkdown--CcBvs19.js.map +0 -1
  85. package/dist/_chunks/markdown-c59ir6tw.js.map +0 -1
  86. package/dist-vue/_chunks/Document-B-oyVh7v.js.map +0 -1
  87. package/dist-vue/_chunks/PkAvatar-ZgCsoO-p.js +0 -42
  88. package/dist-vue/_chunks/PkAvatar-ZgCsoO-p.js.map +0 -1
  89. package/dist-vue/_chunks/PkChatbot-Cm45hwYw.js +0 -275
  90. package/dist-vue/_chunks/PkChatbot-Cm45hwYw.js.map +0 -1
  91. package/dist-vue/_chunks/PkChatbotViewChat-Cko-Cw6c.js.map +0 -1
  92. package/dist-vue/_chunks/utils-fxHiCnzf.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PkToolShowSources-CKZkcPLA.js","names":["_hoisted_1","_hoisted_2","__unplugin_components_0","_openBlock","_createElementBlock","_Fragment","_createVNode","_createElementVNode","_toDisplayString","_unref","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","__unplugin_components_0","_openBlock","_createElementBlock","_Fragment","_createVNode","_unref","_createElementVNode","_toDisplayString","_createCommentVNode","PkUrl","_sfc_main","$t","__unplugin_components_0","__unplugin_components_1","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_createTextVNode","_toDisplayString","_Fragment","_renderList","PkDocumentHeader","_createCommentVNode","_createBlock","PkStreamingMarkdown","PkChunkPreview","_Transition","_withCtx","_normalizeClass","_sfc_main","__exportAll","getTypeIcon","PkStreamingMarkdown_default","stripMarkdown","PkUrl_default","_hoisted_1$2","_hoisted_2$2","PkChunkPreview_default","defineComponent","props","__props","similarityPercent","computed","max","_component_VvIcon","VvIcon","openBlock","createElementBlock","Fragment","createVNode","createElementVNode","toDisplayString","unref","createCommentVNode","_hoisted_1$1","_hoisted_2$1","_hoisted_3$1","_hoisted_4$1","PkDocumentHeader_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","PkToolShowSources_vue_vue_type_script_setup_true_lang_default","toolPart","sources","groupedSources","groups","source","existing","maxSimilarity","s","expandedDocuments","reactive","expandedChunks","contextData","loadingContext","toggleDocument","documentId","toggleChunk","chunkId","handleExpandContext","content","clearContext","showMore","ref","visibleGroups","hasMore","_component_VvButton","VvButton","createTextVNode","_ctx","renderList","group","createBlock","chunk","Transition","withCtx","normalizeClass","_cache","PkToolShowSources_exports","PkToolShowSources_default"],"sources":["../../../../packages/components/src/chat/PkChunkPreview.vue","../../../../packages/components/src/chat/PkChunkPreview.vue","../../../../packages/components/src/chat/PkDocumentHeader.vue","../../../../packages/components/src/chat/PkDocumentHeader.vue","../../../../packages/components/src/chat/PkToolShowSources.vue","../../../../packages/components/src/chat/PkToolShowSources.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { stripMarkdown } from 'utils'\n\n const props = defineProps<{\n content: string\n similarity?: number\n maxSimilarity?: number\n expanded: boolean\n }>()\n\n const similarityPercent = computed(() => {\n if (props.similarity == null) {\n return null\n }\n const max =\n props.maxSimilarity && props.maxSimilarity > 0\n ? props.maxSimilarity\n : 1\n return Math.round((props.similarity / max) * 100)\n })\n</script>\n\n<template>\n <VvIcon name=\"ri:text-snippet\" class=\"shrink-0 text-12 text-word-4\" />\n <span class=\"text-10 text-word-2 truncate flex-1\" :title=\"content\">\n {{ stripMarkdown(content) }}\n </span>\n <span v-if=\"similarityPercent != null\" class=\"text-10 text-word-4 shrink-0\">\n {{ similarityPercent }}%\n </span>\n <VvIcon\n :name=\"expanded ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-14 text-word-4 shrink-0\" />\n</template>\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { stripMarkdown } from 'utils'\n\n const props = defineProps<{\n content: string\n similarity?: number\n maxSimilarity?: number\n expanded: boolean\n }>()\n\n const similarityPercent = computed(() => {\n if (props.similarity == null) {\n return null\n }\n const max =\n props.maxSimilarity && props.maxSimilarity > 0\n ? props.maxSimilarity\n : 1\n return Math.round((props.similarity / max) * 100)\n })\n</script>\n\n<template>\n <VvIcon name=\"ri:text-snippet\" class=\"shrink-0 text-12 text-word-4\" />\n <span class=\"text-10 text-word-2 truncate flex-1\" :title=\"content\">\n {{ stripMarkdown(content) }}\n </span>\n <span v-if=\"similarityPercent != null\" class=\"text-10 text-word-4 shrink-0\">\n {{ similarityPercent }}%\n </span>\n <VvIcon\n :name=\"expanded ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-14 text-word-4 shrink-0\" />\n</template>\n","<script setup lang=\"ts\">\n import type { DocumentType } from 'models'\n import { getTypeIcon } from 'models'\n import PkUrl from '../PkUrl.vue'\n\n defineProps<{\n type: string\n name: string\n url?: string\n filename?: string\n /**\n * The address as text, not a link: for a header drawn inside a button,\n * where a link is invalid HTML and its click also toggles the row\n */\n mutedUrl?: boolean\n }>()\n</script>\n\n<template>\n <VvIcon\n :name=\"getTypeIcon(type as DocumentType)\"\n class=\"shrink-0 text-14 text-word-3\" />\n <div class=\"flex flex-col gap-2 min-w-0 flex-1\">\n <strong class=\"font-bold text-12 text-word-1 truncate block\">\n {{ name }}\n </strong>\n <span v-if=\"filename\" class=\"text-10 text-word-4 truncate\">\n {{ filename }}\n </span>\n <span\n v-if=\"url\"\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-10\" />\n <PkUrl :url=\"url\" :muted=\"mutedUrl\" />\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import type { DocumentType } from 'models'\n import { getTypeIcon } from 'models'\n import PkUrl from '../PkUrl.vue'\n\n defineProps<{\n type: string\n name: string\n url?: string\n filename?: string\n /**\n * The address as text, not a link: for a header drawn inside a button,\n * where a link is invalid HTML and its click also toggles the row\n */\n mutedUrl?: boolean\n }>()\n</script>\n\n<template>\n <VvIcon\n :name=\"getTypeIcon(type as DocumentType)\"\n class=\"shrink-0 text-14 text-word-3\" />\n <div class=\"flex flex-col gap-2 min-w-0 flex-1\">\n <strong class=\"font-bold text-12 text-word-1 truncate block\">\n {{ name }}\n </strong>\n <span v-if=\"filename\" class=\"text-10 text-word-4 truncate\">\n {{ filename }}\n </span>\n <span\n v-if=\"url\"\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-10\" />\n <PkUrl :url=\"url\" :muted=\"mutedUrl\" />\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, reactive } from 'vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkChunkPreview from './PkChunkPreview.vue'\n import PkDocumentHeader from './PkDocumentHeader.vue'\n\n interface SourceItem {\n chunkId: string\n documentId: string\n documentName: string\n documentType: string\n language: string\n content: string\n chunkIndex?: number | null\n similarity?: number\n sourceUrl?: string\n sourceFilename?: string\n sourceDescription?: string\n sourceImageUrl?: string\n }\n\n const props = defineProps<{\n part: unknown\n onExpandContext?: (payload: {\n documentId: string\n chunkIndex: number\n }) => Promise<string>\n onDownload?: (documentId: string) => void\n }>()\n\n const toolPart = computed(() => {\n return props.part as {\n input?: {\n sources: SourceItem[]\n }\n }\n })\n\n const sources = computed(() => toolPart.value.input?.sources || [])\n\n const groupedSources = computed(() => {\n const groups = new Map<\n string,\n { document: SourceItem; chunks: SourceItem[] }\n >()\n for (const source of sources.value) {\n const existing = groups.get(source.documentId)\n if (existing) {\n existing.chunks.push(source)\n } else {\n groups.set(source.documentId, {\n document: source,\n chunks: [source],\n })\n }\n }\n return [...groups.values()]\n })\n\n const maxSimilarity = computed(() => {\n return Math.max(...sources.value.map((s) => s.similarity ?? 0), 0)\n })\n\n const expandedDocuments = reactive(new Set<string>())\n const expandedChunks = reactive(new Set<string>())\n const contextData = reactive(new Map<string, string>())\n const loadingContext = reactive(new Set<string>())\n\n const toggleDocument = (documentId: string) => {\n if (expandedDocuments.has(documentId)) {\n expandedDocuments.delete(documentId)\n } else {\n expandedDocuments.add(documentId)\n }\n }\n\n const toggleChunk = (chunkId: string) => {\n if (expandedChunks.has(chunkId)) {\n expandedChunks.delete(chunkId)\n } else {\n expandedChunks.add(chunkId)\n }\n }\n\n const handleExpandContext = async (source: SourceItem) => {\n if (source.chunkIndex == null || !props.onExpandContext) {\n return\n }\n loadingContext.add(source.chunkId)\n try {\n const content = await props.onExpandContext({\n documentId: source.documentId,\n chunkIndex: source.chunkIndex,\n })\n contextData.set(source.chunkId, content)\n } finally {\n loadingContext.delete(source.chunkId)\n }\n }\n\n const clearContext = (chunkId: string) => {\n contextData.delete(chunkId)\n }\n\n const showMore = ref(false)\n const visibleGroups = computed(() => {\n if (showMore.value) {\n return groupedSources.value\n }\n return groupedSources.value.slice(0, 3)\n })\n const hasMore = computed(() => groupedSources.value.length > 3)\n</script>\n\n<template>\n <div v-if=\"sources.length\" class=\"flex flex-col gap-xs w-full\">\n <div class=\"flex items-center gap-4 text-12 text-word-3 font-semibold\">\n <VvIcon name=\"ri:article-line\" class=\"text-14\" />\n {{ $t('label.sources') }}\n <span class=\"text-word-4 font-normal\">\n ({{ groupedSources.length }})\n </span>\n </div>\n\n <ul class=\"flex flex-col gap-xs\">\n <li\n v-for=\"group in visibleGroups\"\n :key=\"group.document.documentId\"\n class=\"border border-surface-3 rounded-xl overflow-hidden\">\n <!-- Document header: the toggle, and beside it the link to\n the source, because a link inside a button is invalid\n HTML and its click also toggled the row -->\n <div\n class=\"flex items-center hover:bg-surface-1 transition-colors\">\n <button\n type=\"button\"\n class=\"flex items-center gap-8 p-8 flex-1 min-w-0 text-left cursor-pointer\"\n :aria-expanded=\"\n expandedDocuments.has(group.document.documentId)\n \"\n @click=\"toggleDocument(group.document.documentId)\">\n <PkDocumentHeader\n :type=\"group.document.documentType\"\n :name=\"group.document.documentName\"\n :url=\"group.document.sourceUrl\"\n :filename=\"group.document.sourceFilename\"\n muted-url />\n <div class=\"flex items-center gap-4 shrink-0\">\n <span\n v-if=\"group.chunks.length > 1\"\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2\">\n {{ group.chunks.length }}\n </span>\n <span\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2 uppercase\">\n {{ group.document.language }}\n </span>\n <VvIcon\n :name=\"\n expandedDocuments.has(\n group.document.documentId,\n )\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"text-16 text-word-3\" />\n </div>\n </button>\n <VvButton\n v-if=\"group.document.sourceUrl\"\n modifiers=\"action-quiet-small\"\n icon=\"ri:external-link-line\"\n class=\"text-10 shrink-0 mr-8\"\n :href=\"group.document.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n :title=\"$t('action.open')\"\n :aria-label=\"$t('action.open')\" />\n </div>\n\n <!-- Expanded content -->\n <div\n v-if=\"expandedDocuments.has(group.document.documentId)\"\n class=\"border-t border-surface-3\">\n <PkStreamingMarkdown\n v-if=\"group.document.sourceDescription\"\n :markdown=\"group.document.sourceDescription\"\n class=\"px-8 py-4 text-10 text-word-3 bg-surface-1 border-b border-surface-3\" />\n\n <!-- Chunk excerpts -->\n <div\n v-for=\"chunk in group.chunks\"\n :key=\"chunk.chunkId\"\n class=\"border-b border-surface-3 last:border-b-0\">\n <button\n class=\"flex items-center gap-8 px-8 py-6 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleChunk(chunk.chunkId)\">\n <PkChunkPreview\n :content=\"chunk.content\"\n :similarity=\"chunk.similarity\"\n :max-similarity=\"maxSimilarity\"\n :expanded=\"expandedChunks.has(chunk.chunkId)\" />\n </button>\n\n <!-- Expanded chunk content -->\n <div\n v-if=\"expandedChunks.has(chunk.chunkId)\"\n class=\"bg-surface-1 border-t border-surface-3\">\n <div class=\"p-8 text-10 text-word-3\">\n <transition mode=\"out-in\">\n <!-- Context expansion -->\n <PkStreamingMarkdown\n v-if=\"contextData.has(chunk.chunkId)\"\n :markdown=\"\n contextData.get(chunk.chunkId)!\n \"\n class=\"wysiwyg\" />\n\n <!-- Chunk content -->\n <PkStreamingMarkdown\n v-else\n :markdown=\"chunk.content\"\n class=\"wysiwyg\" />\n </transition>\n </div>\n\n <!-- Actions bar -->\n <div\n class=\"flex items-center gap-8 px-8 py-6 border-t border-surface-3 bg-surface\">\n <!-- Expand / collapse context -->\n <button\n v-if=\"\n onExpandContext &&\n chunk.chunkIndex != null &&\n !contextData.has(chunk.chunkId)\n \"\n class=\"text-10 text-word-3 hover:text-word flex items-center gap-4 cursor-pointer\"\n :disabled=\"\n loadingContext.has(chunk.chunkId)\n \"\n @click=\"handleExpandContext(chunk)\">\n <VvIcon\n :name=\"\n loadingContext.has(chunk.chunkId)\n ? 'loading-loop'\n : 'ri:expand-up-down-line'\n \"\n class=\"text-12\" />\n {{ $t('label.expandContext') }}\n </button>\n <button\n v-else-if=\"contextData.has(chunk.chunkId)\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n @click=\"clearContext(chunk.chunkId)\">\n <VvIcon\n name=\"ri:collapse-vertical-line\"\n class=\"text-12\" />\n {{ $t('label.collapseContext') }}\n </button>\n\n <!-- Open source URL -->\n <a\n v-if=\"chunk.sourceUrl\"\n :href=\"chunk.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 ml-auto\">\n <VvIcon\n name=\"ri:external-link-line\"\n class=\"text-12\" />\n {{ $t('label.openSource') }}\n </a>\n\n <!-- Download file -->\n <button\n v-if=\"\n onDownload &&\n chunk.documentType === 'file'\n \"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n :class=\"{\n 'ml-auto': !chunk.sourceUrl,\n }\"\n @click=\"onDownload(chunk.documentId)\">\n <VvIcon\n name=\"ri:download-line\"\n class=\"text-12\" />\n {{ $t('label.downloadFile') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </li>\n </ul>\n\n <!-- Show more -->\n <button\n v-if=\"hasMore\"\n class=\"text-12 text-word-3 cursor-pointer hover:underline flex items-center gap-4 mx-auto\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: groupedSources.length - 3,\n })\n }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, reactive } from 'vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkChunkPreview from './PkChunkPreview.vue'\n import PkDocumentHeader from './PkDocumentHeader.vue'\n\n interface SourceItem {\n chunkId: string\n documentId: string\n documentName: string\n documentType: string\n language: string\n content: string\n chunkIndex?: number | null\n similarity?: number\n sourceUrl?: string\n sourceFilename?: string\n sourceDescription?: string\n sourceImageUrl?: string\n }\n\n const props = defineProps<{\n part: unknown\n onExpandContext?: (payload: {\n documentId: string\n chunkIndex: number\n }) => Promise<string>\n onDownload?: (documentId: string) => void\n }>()\n\n const toolPart = computed(() => {\n return props.part as {\n input?: {\n sources: SourceItem[]\n }\n }\n })\n\n const sources = computed(() => toolPart.value.input?.sources || [])\n\n const groupedSources = computed(() => {\n const groups = new Map<\n string,\n { document: SourceItem; chunks: SourceItem[] }\n >()\n for (const source of sources.value) {\n const existing = groups.get(source.documentId)\n if (existing) {\n existing.chunks.push(source)\n } else {\n groups.set(source.documentId, {\n document: source,\n chunks: [source],\n })\n }\n }\n return [...groups.values()]\n })\n\n const maxSimilarity = computed(() => {\n return Math.max(...sources.value.map((s) => s.similarity ?? 0), 0)\n })\n\n const expandedDocuments = reactive(new Set<string>())\n const expandedChunks = reactive(new Set<string>())\n const contextData = reactive(new Map<string, string>())\n const loadingContext = reactive(new Set<string>())\n\n const toggleDocument = (documentId: string) => {\n if (expandedDocuments.has(documentId)) {\n expandedDocuments.delete(documentId)\n } else {\n expandedDocuments.add(documentId)\n }\n }\n\n const toggleChunk = (chunkId: string) => {\n if (expandedChunks.has(chunkId)) {\n expandedChunks.delete(chunkId)\n } else {\n expandedChunks.add(chunkId)\n }\n }\n\n const handleExpandContext = async (source: SourceItem) => {\n if (source.chunkIndex == null || !props.onExpandContext) {\n return\n }\n loadingContext.add(source.chunkId)\n try {\n const content = await props.onExpandContext({\n documentId: source.documentId,\n chunkIndex: source.chunkIndex,\n })\n contextData.set(source.chunkId, content)\n } finally {\n loadingContext.delete(source.chunkId)\n }\n }\n\n const clearContext = (chunkId: string) => {\n contextData.delete(chunkId)\n }\n\n const showMore = ref(false)\n const visibleGroups = computed(() => {\n if (showMore.value) {\n return groupedSources.value\n }\n return groupedSources.value.slice(0, 3)\n })\n const hasMore = computed(() => groupedSources.value.length > 3)\n</script>\n\n<template>\n <div v-if=\"sources.length\" class=\"flex flex-col gap-xs w-full\">\n <div class=\"flex items-center gap-4 text-12 text-word-3 font-semibold\">\n <VvIcon name=\"ri:article-line\" class=\"text-14\" />\n {{ $t('label.sources') }}\n <span class=\"text-word-4 font-normal\">\n ({{ groupedSources.length }})\n </span>\n </div>\n\n <ul class=\"flex flex-col gap-xs\">\n <li\n v-for=\"group in visibleGroups\"\n :key=\"group.document.documentId\"\n class=\"border border-surface-3 rounded-xl overflow-hidden\">\n <!-- Document header: the toggle, and beside it the link to\n the source, because a link inside a button is invalid\n HTML and its click also toggled the row -->\n <div\n class=\"flex items-center hover:bg-surface-1 transition-colors\">\n <button\n type=\"button\"\n class=\"flex items-center gap-8 p-8 flex-1 min-w-0 text-left cursor-pointer\"\n :aria-expanded=\"\n expandedDocuments.has(group.document.documentId)\n \"\n @click=\"toggleDocument(group.document.documentId)\">\n <PkDocumentHeader\n :type=\"group.document.documentType\"\n :name=\"group.document.documentName\"\n :url=\"group.document.sourceUrl\"\n :filename=\"group.document.sourceFilename\"\n muted-url />\n <div class=\"flex items-center gap-4 shrink-0\">\n <span\n v-if=\"group.chunks.length > 1\"\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2\">\n {{ group.chunks.length }}\n </span>\n <span\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2 uppercase\">\n {{ group.document.language }}\n </span>\n <VvIcon\n :name=\"\n expandedDocuments.has(\n group.document.documentId,\n )\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"text-16 text-word-3\" />\n </div>\n </button>\n <VvButton\n v-if=\"group.document.sourceUrl\"\n modifiers=\"action-quiet-small\"\n icon=\"ri:external-link-line\"\n class=\"text-10 shrink-0 mr-8\"\n :href=\"group.document.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n :title=\"$t('action.open')\"\n :aria-label=\"$t('action.open')\" />\n </div>\n\n <!-- Expanded content -->\n <div\n v-if=\"expandedDocuments.has(group.document.documentId)\"\n class=\"border-t border-surface-3\">\n <PkStreamingMarkdown\n v-if=\"group.document.sourceDescription\"\n :markdown=\"group.document.sourceDescription\"\n class=\"px-8 py-4 text-10 text-word-3 bg-surface-1 border-b border-surface-3\" />\n\n <!-- Chunk excerpts -->\n <div\n v-for=\"chunk in group.chunks\"\n :key=\"chunk.chunkId\"\n class=\"border-b border-surface-3 last:border-b-0\">\n <button\n class=\"flex items-center gap-8 px-8 py-6 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleChunk(chunk.chunkId)\">\n <PkChunkPreview\n :content=\"chunk.content\"\n :similarity=\"chunk.similarity\"\n :max-similarity=\"maxSimilarity\"\n :expanded=\"expandedChunks.has(chunk.chunkId)\" />\n </button>\n\n <!-- Expanded chunk content -->\n <div\n v-if=\"expandedChunks.has(chunk.chunkId)\"\n class=\"bg-surface-1 border-t border-surface-3\">\n <div class=\"p-8 text-10 text-word-3\">\n <transition mode=\"out-in\">\n <!-- Context expansion -->\n <PkStreamingMarkdown\n v-if=\"contextData.has(chunk.chunkId)\"\n :markdown=\"\n contextData.get(chunk.chunkId)!\n \"\n class=\"wysiwyg\" />\n\n <!-- Chunk content -->\n <PkStreamingMarkdown\n v-else\n :markdown=\"chunk.content\"\n class=\"wysiwyg\" />\n </transition>\n </div>\n\n <!-- Actions bar -->\n <div\n class=\"flex items-center gap-8 px-8 py-6 border-t border-surface-3 bg-surface\">\n <!-- Expand / collapse context -->\n <button\n v-if=\"\n onExpandContext &&\n chunk.chunkIndex != null &&\n !contextData.has(chunk.chunkId)\n \"\n class=\"text-10 text-word-3 hover:text-word flex items-center gap-4 cursor-pointer\"\n :disabled=\"\n loadingContext.has(chunk.chunkId)\n \"\n @click=\"handleExpandContext(chunk)\">\n <VvIcon\n :name=\"\n loadingContext.has(chunk.chunkId)\n ? 'loading-loop'\n : 'ri:expand-up-down-line'\n \"\n class=\"text-12\" />\n {{ $t('label.expandContext') }}\n </button>\n <button\n v-else-if=\"contextData.has(chunk.chunkId)\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n @click=\"clearContext(chunk.chunkId)\">\n <VvIcon\n name=\"ri:collapse-vertical-line\"\n class=\"text-12\" />\n {{ $t('label.collapseContext') }}\n </button>\n\n <!-- Open source URL -->\n <a\n v-if=\"chunk.sourceUrl\"\n :href=\"chunk.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 ml-auto\">\n <VvIcon\n name=\"ri:external-link-line\"\n class=\"text-12\" />\n {{ $t('label.openSource') }}\n </a>\n\n <!-- Download file -->\n <button\n v-if=\"\n onDownload &&\n chunk.documentType === 'file'\n \"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n :class=\"{\n 'ml-auto': !chunk.sourceUrl,\n }\"\n @click=\"onDownload(chunk.documentId)\">\n <VvIcon\n name=\"ri:download-line\"\n class=\"text-12\" />\n {{ $t('label.downloadFile') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </li>\n </ul>\n\n <!-- Show more -->\n <button\n v-if=\"hasMore\"\n class=\"text-12 text-word-3 cursor-pointer hover:underline flex items-center gap-4 mx-auto\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: groupedSources.length - 3,\n })\n }}\n </button>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PkToolShowSources-CSN7S0zo.js","names":["_hoisted_1","_hoisted_2","__unplugin_components_0","_openBlock","_createElementBlock","_Fragment","_createVNode","_createElementVNode","_toDisplayString","_unref","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","__unplugin_components_0","_openBlock","_createElementBlock","_Fragment","_createVNode","_unref","_createElementVNode","_toDisplayString","_createCommentVNode","PkUrl","_sfc_main","$t","__unplugin_components_0","__unplugin_components_1","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_createTextVNode","_toDisplayString","_Fragment","_renderList","PkDocumentHeader","_createCommentVNode","_createBlock","PkStreamingMarkdown","PkChunkPreview","_Transition","_withCtx","_normalizeClass","_sfc_main","__exportAll","getTypeIcon","PkStreamingMarkdown_default","stripMarkdown","PkUrl_default","_hoisted_1$2","_hoisted_2$2","PkChunkPreview_default","defineComponent","props","__props","similarityPercent","computed","max","_component_VvIcon","VvIcon","openBlock","createElementBlock","Fragment","createVNode","createElementVNode","toDisplayString","unref","createCommentVNode","_hoisted_1$1","_hoisted_2$1","_hoisted_3$1","_hoisted_4$1","PkDocumentHeader_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","PkToolShowSources_vue_vue_type_script_setup_true_lang_default","toolPart","sources","groupedSources","groups","source","existing","maxSimilarity","s","expandedDocuments","reactive","expandedChunks","contextData","loadingContext","toggleDocument","documentId","toggleChunk","chunkId","handleExpandContext","content","clearContext","showMore","ref","visibleGroups","hasMore","_component_VvButton","VvButton","createTextVNode","_ctx","renderList","group","createBlock","chunk","Transition","withCtx","normalizeClass","_cache","PkToolShowSources_exports","PkToolShowSources_default"],"sources":["../../../../packages/components/src/chat/PkChunkPreview.vue","../../../../packages/components/src/chat/PkChunkPreview.vue","../../../../packages/components/src/chat/PkDocumentHeader.vue","../../../../packages/components/src/chat/PkDocumentHeader.vue","../../../../packages/components/src/chat/PkToolShowSources.vue","../../../../packages/components/src/chat/PkToolShowSources.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { stripMarkdown } from 'utils'\n\n const props = defineProps<{\n content: string\n similarity?: number\n maxSimilarity?: number\n expanded: boolean\n }>()\n\n const similarityPercent = computed(() => {\n if (props.similarity == null) {\n return null\n }\n const max =\n props.maxSimilarity && props.maxSimilarity > 0\n ? props.maxSimilarity\n : 1\n return Math.round((props.similarity / max) * 100)\n })\n</script>\n\n<template>\n <VvIcon name=\"ri:text-snippet\" class=\"shrink-0 text-12 text-word-4\" />\n <span class=\"text-10 text-word-2 truncate flex-1\" :title=\"content\">\n {{ stripMarkdown(content) }}\n </span>\n <span v-if=\"similarityPercent != null\" class=\"text-10 text-word-4 shrink-0\">\n {{ similarityPercent }}%\n </span>\n <VvIcon\n :name=\"expanded ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-14 text-word-4 shrink-0\" />\n</template>\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { stripMarkdown } from 'utils'\n\n const props = defineProps<{\n content: string\n similarity?: number\n maxSimilarity?: number\n expanded: boolean\n }>()\n\n const similarityPercent = computed(() => {\n if (props.similarity == null) {\n return null\n }\n const max =\n props.maxSimilarity && props.maxSimilarity > 0\n ? props.maxSimilarity\n : 1\n return Math.round((props.similarity / max) * 100)\n })\n</script>\n\n<template>\n <VvIcon name=\"ri:text-snippet\" class=\"shrink-0 text-12 text-word-4\" />\n <span class=\"text-10 text-word-2 truncate flex-1\" :title=\"content\">\n {{ stripMarkdown(content) }}\n </span>\n <span v-if=\"similarityPercent != null\" class=\"text-10 text-word-4 shrink-0\">\n {{ similarityPercent }}%\n </span>\n <VvIcon\n :name=\"expanded ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-14 text-word-4 shrink-0\" />\n</template>\n","<script setup lang=\"ts\">\n import type { DocumentType } from 'models'\n import { getTypeIcon } from 'models'\n import PkUrl from '../PkUrl.vue'\n\n defineProps<{\n type: string\n name: string\n url?: string\n filename?: string\n /**\n * The address as text, not a link: for a header drawn inside a button,\n * where a link is invalid HTML and its click also toggles the row\n */\n mutedUrl?: boolean\n }>()\n</script>\n\n<template>\n <VvIcon\n :name=\"getTypeIcon(type as DocumentType)\"\n class=\"shrink-0 text-14 text-word-3\" />\n <div class=\"flex flex-col gap-2 min-w-0 flex-1\">\n <strong class=\"font-bold text-12 text-word-1 truncate block\">\n {{ name }}\n </strong>\n <span v-if=\"filename\" class=\"text-10 text-word-4 truncate\">\n {{ filename }}\n </span>\n <span\n v-if=\"url\"\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-10\" />\n <PkUrl :url=\"url\" :muted=\"mutedUrl\" />\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import type { DocumentType } from 'models'\n import { getTypeIcon } from 'models'\n import PkUrl from '../PkUrl.vue'\n\n defineProps<{\n type: string\n name: string\n url?: string\n filename?: string\n /**\n * The address as text, not a link: for a header drawn inside a button,\n * where a link is invalid HTML and its click also toggles the row\n */\n mutedUrl?: boolean\n }>()\n</script>\n\n<template>\n <VvIcon\n :name=\"getTypeIcon(type as DocumentType)\"\n class=\"shrink-0 text-14 text-word-3\" />\n <div class=\"flex flex-col gap-2 min-w-0 flex-1\">\n <strong class=\"font-bold text-12 text-word-1 truncate block\">\n {{ name }}\n </strong>\n <span v-if=\"filename\" class=\"text-10 text-word-4 truncate\">\n {{ filename }}\n </span>\n <span\n v-if=\"url\"\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-10\" />\n <PkUrl :url=\"url\" :muted=\"mutedUrl\" />\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, reactive } from 'vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkChunkPreview from './PkChunkPreview.vue'\n import PkDocumentHeader from './PkDocumentHeader.vue'\n\n interface SourceItem {\n chunkId: string\n documentId: string\n documentName: string\n documentType: string\n language: string\n content: string\n chunkIndex?: number | null\n similarity?: number\n sourceUrl?: string\n sourceFilename?: string\n sourceDescription?: string\n sourceImageUrl?: string\n }\n\n const props = defineProps<{\n part: unknown\n onExpandContext?: (payload: {\n documentId: string\n chunkIndex: number\n }) => Promise<string>\n onDownload?: (documentId: string) => void\n }>()\n\n const toolPart = computed(() => {\n return props.part as {\n input?: {\n sources: SourceItem[]\n }\n }\n })\n\n const sources = computed(() => toolPart.value.input?.sources || [])\n\n const groupedSources = computed(() => {\n const groups = new Map<\n string,\n { document: SourceItem; chunks: SourceItem[] }\n >()\n for (const source of sources.value) {\n const existing = groups.get(source.documentId)\n if (existing) {\n existing.chunks.push(source)\n } else {\n groups.set(source.documentId, {\n document: source,\n chunks: [source],\n })\n }\n }\n return [...groups.values()]\n })\n\n const maxSimilarity = computed(() => {\n return Math.max(...sources.value.map((s) => s.similarity ?? 0), 0)\n })\n\n const expandedDocuments = reactive(new Set<string>())\n const expandedChunks = reactive(new Set<string>())\n const contextData = reactive(new Map<string, string>())\n const loadingContext = reactive(new Set<string>())\n\n const toggleDocument = (documentId: string) => {\n if (expandedDocuments.has(documentId)) {\n expandedDocuments.delete(documentId)\n } else {\n expandedDocuments.add(documentId)\n }\n }\n\n const toggleChunk = (chunkId: string) => {\n if (expandedChunks.has(chunkId)) {\n expandedChunks.delete(chunkId)\n } else {\n expandedChunks.add(chunkId)\n }\n }\n\n const handleExpandContext = async (source: SourceItem) => {\n if (source.chunkIndex == null || !props.onExpandContext) {\n return\n }\n loadingContext.add(source.chunkId)\n try {\n const content = await props.onExpandContext({\n documentId: source.documentId,\n chunkIndex: source.chunkIndex,\n })\n contextData.set(source.chunkId, content)\n } finally {\n loadingContext.delete(source.chunkId)\n }\n }\n\n const clearContext = (chunkId: string) => {\n contextData.delete(chunkId)\n }\n\n const showMore = ref(false)\n const visibleGroups = computed(() => {\n if (showMore.value) {\n return groupedSources.value\n }\n return groupedSources.value.slice(0, 3)\n })\n const hasMore = computed(() => groupedSources.value.length > 3)\n</script>\n\n<template>\n <div v-if=\"sources.length\" class=\"flex flex-col gap-xs w-full\">\n <div class=\"flex items-center gap-4 text-12 text-word-3 font-semibold\">\n <VvIcon name=\"ri:article-line\" class=\"text-14\" />\n {{ $t('label.sources') }}\n <span class=\"text-word-4 font-normal\">\n ({{ groupedSources.length }})\n </span>\n </div>\n\n <ul class=\"flex flex-col gap-xs\">\n <li\n v-for=\"group in visibleGroups\"\n :key=\"group.document.documentId\"\n class=\"border border-surface-3 rounded-xl overflow-hidden\">\n <!-- Document header: the toggle, and beside it the link to\n the source, because a link inside a button is invalid\n HTML and its click also toggled the row -->\n <div\n class=\"flex items-center hover:bg-surface-1 transition-colors\">\n <button\n type=\"button\"\n class=\"flex items-center gap-8 p-8 flex-1 min-w-0 text-left cursor-pointer\"\n :aria-expanded=\"\n expandedDocuments.has(group.document.documentId)\n \"\n @click=\"toggleDocument(group.document.documentId)\">\n <PkDocumentHeader\n :type=\"group.document.documentType\"\n :name=\"group.document.documentName\"\n :url=\"group.document.sourceUrl\"\n :filename=\"group.document.sourceFilename\"\n muted-url />\n <div class=\"flex items-center gap-4 shrink-0\">\n <span\n v-if=\"group.chunks.length > 1\"\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2\">\n {{ group.chunks.length }}\n </span>\n <span\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2 uppercase\">\n {{ group.document.language }}\n </span>\n <VvIcon\n :name=\"\n expandedDocuments.has(\n group.document.documentId,\n )\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"text-16 text-word-3\" />\n </div>\n </button>\n <VvButton\n v-if=\"group.document.sourceUrl\"\n modifiers=\"action-quiet-small\"\n icon=\"ri:external-link-line\"\n class=\"text-10 shrink-0 mr-8\"\n :href=\"group.document.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n :title=\"$t('action.open')\"\n :aria-label=\"$t('action.open')\" />\n </div>\n\n <!-- Expanded content -->\n <div\n v-if=\"expandedDocuments.has(group.document.documentId)\"\n class=\"border-t border-surface-3\">\n <PkStreamingMarkdown\n v-if=\"group.document.sourceDescription\"\n :markdown=\"group.document.sourceDescription\"\n class=\"px-8 py-4 text-10 text-word-3 bg-surface-1 border-b border-surface-3\" />\n\n <!-- Chunk excerpts -->\n <div\n v-for=\"chunk in group.chunks\"\n :key=\"chunk.chunkId\"\n class=\"border-b border-surface-3 last:border-b-0\">\n <button\n class=\"flex items-center gap-8 px-8 py-6 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleChunk(chunk.chunkId)\">\n <PkChunkPreview\n :content=\"chunk.content\"\n :similarity=\"chunk.similarity\"\n :max-similarity=\"maxSimilarity\"\n :expanded=\"expandedChunks.has(chunk.chunkId)\" />\n </button>\n\n <!-- Expanded chunk content -->\n <div\n v-if=\"expandedChunks.has(chunk.chunkId)\"\n class=\"bg-surface-1 border-t border-surface-3\">\n <div class=\"p-8 text-10 text-word-3\">\n <transition mode=\"out-in\">\n <!-- Context expansion -->\n <PkStreamingMarkdown\n v-if=\"contextData.has(chunk.chunkId)\"\n :markdown=\"\n contextData.get(chunk.chunkId)!\n \"\n class=\"wysiwyg\" />\n\n <!-- Chunk content -->\n <PkStreamingMarkdown\n v-else\n :markdown=\"chunk.content\"\n class=\"wysiwyg\" />\n </transition>\n </div>\n\n <!-- Actions bar -->\n <div\n class=\"flex items-center gap-8 px-8 py-6 border-t border-surface-3 bg-surface\">\n <!-- Expand / collapse context -->\n <button\n v-if=\"\n onExpandContext &&\n chunk.chunkIndex != null &&\n !contextData.has(chunk.chunkId)\n \"\n class=\"text-10 text-word-3 hover:text-word flex items-center gap-4 cursor-pointer\"\n :disabled=\"\n loadingContext.has(chunk.chunkId)\n \"\n @click=\"handleExpandContext(chunk)\">\n <VvIcon\n :name=\"\n loadingContext.has(chunk.chunkId)\n ? 'loading-loop'\n : 'ri:expand-up-down-line'\n \"\n class=\"text-12\" />\n {{ $t('label.expandContext') }}\n </button>\n <button\n v-else-if=\"contextData.has(chunk.chunkId)\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n @click=\"clearContext(chunk.chunkId)\">\n <VvIcon\n name=\"ri:collapse-vertical-line\"\n class=\"text-12\" />\n {{ $t('label.collapseContext') }}\n </button>\n\n <!-- Open source URL -->\n <a\n v-if=\"chunk.sourceUrl\"\n :href=\"chunk.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 ml-auto\">\n <VvIcon\n name=\"ri:external-link-line\"\n class=\"text-12\" />\n {{ $t('label.openSource') }}\n </a>\n\n <!-- Download file -->\n <button\n v-if=\"\n onDownload &&\n chunk.documentType === 'file'\n \"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n :class=\"{\n 'ml-auto': !chunk.sourceUrl,\n }\"\n @click=\"onDownload(chunk.documentId)\">\n <VvIcon\n name=\"ri:download-line\"\n class=\"text-12\" />\n {{ $t('label.downloadFile') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </li>\n </ul>\n\n <!-- Show more -->\n <button\n v-if=\"hasMore\"\n class=\"text-12 text-word-3 cursor-pointer hover:underline flex items-center gap-4 mx-auto\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: groupedSources.length - 3,\n })\n }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, reactive } from 'vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkChunkPreview from './PkChunkPreview.vue'\n import PkDocumentHeader from './PkDocumentHeader.vue'\n\n interface SourceItem {\n chunkId: string\n documentId: string\n documentName: string\n documentType: string\n language: string\n content: string\n chunkIndex?: number | null\n similarity?: number\n sourceUrl?: string\n sourceFilename?: string\n sourceDescription?: string\n sourceImageUrl?: string\n }\n\n const props = defineProps<{\n part: unknown\n onExpandContext?: (payload: {\n documentId: string\n chunkIndex: number\n }) => Promise<string>\n onDownload?: (documentId: string) => void\n }>()\n\n const toolPart = computed(() => {\n return props.part as {\n input?: {\n sources: SourceItem[]\n }\n }\n })\n\n const sources = computed(() => toolPart.value.input?.sources || [])\n\n const groupedSources = computed(() => {\n const groups = new Map<\n string,\n { document: SourceItem; chunks: SourceItem[] }\n >()\n for (const source of sources.value) {\n const existing = groups.get(source.documentId)\n if (existing) {\n existing.chunks.push(source)\n } else {\n groups.set(source.documentId, {\n document: source,\n chunks: [source],\n })\n }\n }\n return [...groups.values()]\n })\n\n const maxSimilarity = computed(() => {\n return Math.max(...sources.value.map((s) => s.similarity ?? 0), 0)\n })\n\n const expandedDocuments = reactive(new Set<string>())\n const expandedChunks = reactive(new Set<string>())\n const contextData = reactive(new Map<string, string>())\n const loadingContext = reactive(new Set<string>())\n\n const toggleDocument = (documentId: string) => {\n if (expandedDocuments.has(documentId)) {\n expandedDocuments.delete(documentId)\n } else {\n expandedDocuments.add(documentId)\n }\n }\n\n const toggleChunk = (chunkId: string) => {\n if (expandedChunks.has(chunkId)) {\n expandedChunks.delete(chunkId)\n } else {\n expandedChunks.add(chunkId)\n }\n }\n\n const handleExpandContext = async (source: SourceItem) => {\n if (source.chunkIndex == null || !props.onExpandContext) {\n return\n }\n loadingContext.add(source.chunkId)\n try {\n const content = await props.onExpandContext({\n documentId: source.documentId,\n chunkIndex: source.chunkIndex,\n })\n contextData.set(source.chunkId, content)\n } finally {\n loadingContext.delete(source.chunkId)\n }\n }\n\n const clearContext = (chunkId: string) => {\n contextData.delete(chunkId)\n }\n\n const showMore = ref(false)\n const visibleGroups = computed(() => {\n if (showMore.value) {\n return groupedSources.value\n }\n return groupedSources.value.slice(0, 3)\n })\n const hasMore = computed(() => groupedSources.value.length > 3)\n</script>\n\n<template>\n <div v-if=\"sources.length\" class=\"flex flex-col gap-xs w-full\">\n <div class=\"flex items-center gap-4 text-12 text-word-3 font-semibold\">\n <VvIcon name=\"ri:article-line\" class=\"text-14\" />\n {{ $t('label.sources') }}\n <span class=\"text-word-4 font-normal\">\n ({{ groupedSources.length }})\n </span>\n </div>\n\n <ul class=\"flex flex-col gap-xs\">\n <li\n v-for=\"group in visibleGroups\"\n :key=\"group.document.documentId\"\n class=\"border border-surface-3 rounded-xl overflow-hidden\">\n <!-- Document header: the toggle, and beside it the link to\n the source, because a link inside a button is invalid\n HTML and its click also toggled the row -->\n <div\n class=\"flex items-center hover:bg-surface-1 transition-colors\">\n <button\n type=\"button\"\n class=\"flex items-center gap-8 p-8 flex-1 min-w-0 text-left cursor-pointer\"\n :aria-expanded=\"\n expandedDocuments.has(group.document.documentId)\n \"\n @click=\"toggleDocument(group.document.documentId)\">\n <PkDocumentHeader\n :type=\"group.document.documentType\"\n :name=\"group.document.documentName\"\n :url=\"group.document.sourceUrl\"\n :filename=\"group.document.sourceFilename\"\n muted-url />\n <div class=\"flex items-center gap-4 shrink-0\">\n <span\n v-if=\"group.chunks.length > 1\"\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2\">\n {{ group.chunks.length }}\n </span>\n <span\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2 uppercase\">\n {{ group.document.language }}\n </span>\n <VvIcon\n :name=\"\n expandedDocuments.has(\n group.document.documentId,\n )\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"text-16 text-word-3\" />\n </div>\n </button>\n <VvButton\n v-if=\"group.document.sourceUrl\"\n modifiers=\"action-quiet-small\"\n icon=\"ri:external-link-line\"\n class=\"text-10 shrink-0 mr-8\"\n :href=\"group.document.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n :title=\"$t('action.open')\"\n :aria-label=\"$t('action.open')\" />\n </div>\n\n <!-- Expanded content -->\n <div\n v-if=\"expandedDocuments.has(group.document.documentId)\"\n class=\"border-t border-surface-3\">\n <PkStreamingMarkdown\n v-if=\"group.document.sourceDescription\"\n :markdown=\"group.document.sourceDescription\"\n class=\"px-8 py-4 text-10 text-word-3 bg-surface-1 border-b border-surface-3\" />\n\n <!-- Chunk excerpts -->\n <div\n v-for=\"chunk in group.chunks\"\n :key=\"chunk.chunkId\"\n class=\"border-b border-surface-3 last:border-b-0\">\n <button\n class=\"flex items-center gap-8 px-8 py-6 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleChunk(chunk.chunkId)\">\n <PkChunkPreview\n :content=\"chunk.content\"\n :similarity=\"chunk.similarity\"\n :max-similarity=\"maxSimilarity\"\n :expanded=\"expandedChunks.has(chunk.chunkId)\" />\n </button>\n\n <!-- Expanded chunk content -->\n <div\n v-if=\"expandedChunks.has(chunk.chunkId)\"\n class=\"bg-surface-1 border-t border-surface-3\">\n <div class=\"p-8 text-10 text-word-3\">\n <transition mode=\"out-in\">\n <!-- Context expansion -->\n <PkStreamingMarkdown\n v-if=\"contextData.has(chunk.chunkId)\"\n :markdown=\"\n contextData.get(chunk.chunkId)!\n \"\n class=\"wysiwyg\" />\n\n <!-- Chunk content -->\n <PkStreamingMarkdown\n v-else\n :markdown=\"chunk.content\"\n class=\"wysiwyg\" />\n </transition>\n </div>\n\n <!-- Actions bar -->\n <div\n class=\"flex items-center gap-8 px-8 py-6 border-t border-surface-3 bg-surface\">\n <!-- Expand / collapse context -->\n <button\n v-if=\"\n onExpandContext &&\n chunk.chunkIndex != null &&\n !contextData.has(chunk.chunkId)\n \"\n class=\"text-10 text-word-3 hover:text-word flex items-center gap-4 cursor-pointer\"\n :disabled=\"\n loadingContext.has(chunk.chunkId)\n \"\n @click=\"handleExpandContext(chunk)\">\n <VvIcon\n :name=\"\n loadingContext.has(chunk.chunkId)\n ? 'loading-loop'\n : 'ri:expand-up-down-line'\n \"\n class=\"text-12\" />\n {{ $t('label.expandContext') }}\n </button>\n <button\n v-else-if=\"contextData.has(chunk.chunkId)\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n @click=\"clearContext(chunk.chunkId)\">\n <VvIcon\n name=\"ri:collapse-vertical-line\"\n class=\"text-12\" />\n {{ $t('label.collapseContext') }}\n </button>\n\n <!-- Open source URL -->\n <a\n v-if=\"chunk.sourceUrl\"\n :href=\"chunk.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 ml-auto\">\n <VvIcon\n name=\"ri:external-link-line\"\n class=\"text-12\" />\n {{ $t('label.openSource') }}\n </a>\n\n <!-- Download file -->\n <button\n v-if=\"\n onDownload &&\n chunk.documentType === 'file'\n \"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n :class=\"{\n 'ml-auto': !chunk.sourceUrl,\n }\"\n @click=\"onDownload(chunk.documentId)\">\n <VvIcon\n name=\"ri:download-line\"\n class=\"text-12\" />\n {{ $t('label.downloadFile') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </li>\n </ul>\n\n <!-- Show more -->\n <button\n v-if=\"hasMore\"\n class=\"text-12 text-word-3 cursor-pointer hover:underline flex items-center gap-4 mx-auto\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: groupedSources.length - 3,\n })\n }}\n </button>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { r as e } from "./PkStreamingMarkdown-BtyWXY2k.js";
1
+ import { r as e } from "./PkStreamingMarkdown-C55Fi0Pd.js";
2
2
  import "./src-DCfvejyT.js";
3
3
  import { t } from "./PkHeldImage-BqnlurT_.js";
4
4
  import { VvIcon as n } from "@volverjs/ui-vue/components";
@@ -137,4 +137,4 @@ var b = {
137
137
  //#endregion
138
138
  export { M as default };
139
139
 
140
- //# sourceMappingURL=PkToolShowVideo-CKm1fVQs.js.map
140
+ //# sourceMappingURL=PkToolShowVideo-B_Q93E2y.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkToolShowVideo-CKm1fVQs.js","names":["$t","__unplugin_components_0","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_toDisplayString","_Fragment","_renderList","_normalizeClass","PkHeldImage","_unref","_createCommentVNode","_sfc_main","useChatImageGate","sanitizeId","raw","hostMatches","host","domain","youtubeId","u","v","m","vimeoId","parseVideoSource","url","id","hint","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","IFRAME_ALLOW","PkToolShowVideo_default","defineComponent","props","__props","PLAYER_HOSTS","imageGate","videos","computed","source","video","gated","src","release","urls","_component_VvIcon","VvIcon","openBlock","createElementBlock","createElementVNode","createVNode","toDisplayString","_ctx","Fragment","renderList","normalizeClass","index","PkHeldImage_default","unref","createCommentVNode"],"sources":["../../../../packages/utils/src/video-source.ts","../../../../packages/components/src/chat/PkToolShowVideo.vue","../../../../packages/components/src/chat/PkToolShowVideo.vue"],"sourcesContent":["/**\n * Video source parsing shared by the backend platform renderer and the frontend\n * player component, so both classify a video URL the same way.\n *\n * A URL is either a known provider (YouTube/Vimeo), which plays through the\n * provider's iframe embed, or a direct media file (`file`), which plays in a\n * native `<video>` element. Detection is by URL shape; an explicit `hint` is\n * used only as a fallback when the URL alone is inconclusive.\n */\n\nexport type VideoProvider = 'youtube' | 'vimeo' | 'file'\n\nexport interface ParsedVideoSource {\n provider: VideoProvider\n /** The original URL, unchanged. */\n url: string\n /**\n * The URL to actually load: the provider iframe embed URL for\n * YouTube/Vimeo, or the direct file URL for `file`.\n */\n embedUrl: string\n /** The provider video id (providers only). */\n videoId?: string\n /**\n * A preview image URL derived from the provider, when one is available\n * without an extra API call (currently YouTube only). Lets a link-only\n * surface (e.g. a Teams card) show a thumbnail instead of a bare link.\n */\n thumbnailUrl?: string\n}\n\n/** Keep only characters that are legal in a provider video id. */\nconst sanitizeId = (raw: string): string => raw.replace(/[^a-zA-Z0-9_-]/g, '')\n\n/**\n * True when `host` is exactly `domain` or a subdomain of it. Guards against\n * lookalike hosts (e.g. `notyoutube.com`) that a bare substring check would\n * misclassify.\n */\nconst hostMatches = (host: string, domain: string): boolean =>\n host === domain || host.endsWith(`.${domain}`)\n\n/** Extract the YouTube video id from any of its URL shapes, or null. */\nconst youtubeId = (u: URL): string | null => {\n // youtu.be/<id>\n if (u.hostname.endsWith('youtu.be')) {\n return sanitizeId(u.pathname.split('/')[1] ?? '') || null\n }\n // youtube.com/watch?v=<id>\n const v = u.searchParams.get('v')\n if (v) {\n return sanitizeId(v) || null\n }\n // youtube.com/shorts/<id>, /live/<id>, /embed/<id>, /v/<id>\n const m = u.pathname.match(/^\\/(?:shorts|live|embed|v)\\/([^/?#]+)/)\n return m ? sanitizeId(m[1]) || null : null\n}\n\n/** Extract the Vimeo (numeric) video id, or null. */\nconst vimeoId = (u: URL): string | null => {\n // player.vimeo.com/video/<id> or vimeo.com/<id>\n const m = u.pathname.match(/(?:^|\\/)(\\d+)(?:$|[/?#])/)\n return m ? m[1] : null\n}\n\n/**\n * Classify a video URL into a provider + a loadable embed/file URL.\n * Falls back to `file` (native `<video>`) for anything not recognized as a\n * supported provider, or when a provider is matched but no id can be extracted.\n */\nexport function parseVideoSource(\n url: string,\n hint?: VideoProvider,\n): ParsedVideoSource {\n let u: URL | undefined\n try {\n u = new URL(url)\n } catch {\n // Not an absolute URL: treat as a direct file reference.\n return { provider: 'file', url, embedUrl: url }\n }\n\n const host = u.hostname.toLowerCase()\n\n const isYouTube =\n hostMatches(host, 'youtube.com') ||\n hostMatches(host, 'youtu.be') ||\n hostMatches(host, 'youtube-nocookie.com')\n if (isYouTube) {\n const id = youtubeId(u)\n if (id) {\n return {\n provider: 'youtube',\n url,\n videoId: id,\n embedUrl: `https://www.youtube-nocookie.com/embed/${id}`,\n thumbnailUrl: `https://img.youtube.com/vi/${id}/hqdefault.jpg`,\n }\n }\n }\n\n if (hostMatches(host, 'vimeo.com')) {\n const id = vimeoId(u)\n if (id) {\n return {\n provider: 'vimeo',\n url,\n videoId: id,\n // `dnt=1`: the player sets no cookies and records no session\n // data, the Vimeo counterpart of youtube-nocookie above.\n embedUrl: `https://player.vimeo.com/video/${id}?dnt=1`,\n }\n }\n }\n\n // Honor an explicit provider hint only if we could not extract an id from a\n // provider URL (so a caller-tagged provider link still routes to an iframe\n // via its original URL rather than a broken native <video>).\n if (hint && hint !== 'file') {\n return { provider: hint, url, embedUrl: url }\n }\n\n return { provider: 'file', url, embedUrl: url }\n}\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { parseVideoSource, type VideoProvider } from 'utils'\n import { useChatImageGate } from './chatImagePolicy'\n import PkHeldImage from './PkHeldImage.vue'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n interface VideoInput {\n url: string\n title?: string\n description?: string\n poster?: string\n provider?: VideoProvider\n }\n\n // Permissions granted to provider embeds (YouTube/Vimeo players).\n const IFRAME_ALLOW =\n 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'\n\n /**\n * Where a YouTube or Vimeo player lives: the url is built here from the\n * video id, so nothing the model wrote reaches these hosts but the id.\n */\n const PLAYER_HOSTS = ['www.youtube-nocookie.com', 'player.vimeo.com']\n\n // A file, its poster, or an embed this card does not recognize, fetch the\n // url the model wrote as soon as they render: from a host the agent does\n // not list they wait for a click, like an image (chatImagePolicy.ts).\n const imageGate = useChatImageGate()\n\n const videos = computed(() => {\n const part = props.part as { input?: { videos?: VideoInput[] } }\n return (part.input?.videos ?? []).map((video) => {\n const source = parseVideoSource(video.url, video.provider)\n const fetched =\n source.provider === 'file'\n ? [source.embedUrl, video.poster]\n : [source.embedUrl]\n const gated = fetched.filter(\n (src): src is string =>\n !!src &&\n !PLAYER_HOSTS.includes(imageGate.hostOf(src) ?? ''),\n )\n return {\n ...video,\n source,\n gated,\n held: gated.some((src) => !imageGate.canLoad(src)),\n }\n })\n })\n\n function release(urls: string[]) {\n for (const url of urls) {\n imageGate.release(url)\n }\n }\n</script>\n\n<template>\n <div\n v-if=\"videos.length\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface\">\n <div\n class=\"px-sm py-6 bg-surface-1 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8 min-h-40\">\n <VvIcon name=\"ri:film-line\" class=\"text-16\" />\n <strong class=\"font-bold\">\n {{ $t('label.video') }}\n </strong>\n </div>\n <!-- Each video bleeds to the card's edges, like the map of the\n location card: the card's overflow clips the bottom corners. -->\n <div\n v-for=\"(video, index) in videos\"\n :key=\"video.url\"\n class=\"flex flex-col\"\n :class=\"{ 'border-t border-surface-3': index > 0 }\">\n <!-- Direct file: native player. Handles MP4/WebM/OGG and works\n inside the widget's Shadow DOM without any extra CSS. -->\n <div\n v-if=\"video.held\"\n class=\"w-full overflow-hidden\"\n style=\"aspect-ratio: 16 / 9\">\n <PkHeldImage\n :host=\"imageGate.hostOf(video.gated[0])\"\n kind=\"video\"\n fill\n @release=\"release(video.gated)\" />\n </div>\n <video\n v-else-if=\"video.source.provider === 'file'\"\n :src=\"video.source.embedUrl\"\n :poster=\"video.poster\"\n controls\n preload=\"metadata\"\n playsinline\n class=\"w-full bg-black\"\n style=\"aspect-ratio: 16 / 9; max-height: 480px\">\n <a :href=\"video.url\" target=\"_blank\" rel=\"noopener noreferrer\">\n {{ video.title ?? video.url }}\n </a>\n </video>\n <!-- Provider (YouTube/Vimeo): iframe embed, 16:9 responsive. -->\n <div\n v-else\n class=\"relative w-full overflow-hidden bg-black\"\n style=\"aspect-ratio: 16 / 9\">\n <iframe\n :src=\"video.source.embedUrl\"\n :title=\"video.title ?? video.url\"\n loading=\"lazy\"\n referrerpolicy=\"strict-origin-when-cross-origin\"\n :allow=\"IFRAME_ALLOW\"\n allowfullscreen\n class=\"absolute inset-0 w-full h-full border-0\"></iframe>\n </div>\n <div\n v-if=\"video.title || video.description\"\n class=\"p-sm flex flex-col gap-4 border-t border-surface-3\">\n <strong v-if=\"video.title\" class=\"font-bold text-14\">\n {{ video.title }}\n </strong>\n <span v-if=\"video.description\" class=\"text-12 text-word-3\">\n {{ video.description }}\n </span>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { parseVideoSource, type VideoProvider } from 'utils'\n import { useChatImageGate } from './chatImagePolicy'\n import PkHeldImage from './PkHeldImage.vue'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n interface VideoInput {\n url: string\n title?: string\n description?: string\n poster?: string\n provider?: VideoProvider\n }\n\n // Permissions granted to provider embeds (YouTube/Vimeo players).\n const IFRAME_ALLOW =\n 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'\n\n /**\n * Where a YouTube or Vimeo player lives: the url is built here from the\n * video id, so nothing the model wrote reaches these hosts but the id.\n */\n const PLAYER_HOSTS = ['www.youtube-nocookie.com', 'player.vimeo.com']\n\n // A file, its poster, or an embed this card does not recognize, fetch the\n // url the model wrote as soon as they render: from a host the agent does\n // not list they wait for a click, like an image (chatImagePolicy.ts).\n const imageGate = useChatImageGate()\n\n const videos = computed(() => {\n const part = props.part as { input?: { videos?: VideoInput[] } }\n return (part.input?.videos ?? []).map((video) => {\n const source = parseVideoSource(video.url, video.provider)\n const fetched =\n source.provider === 'file'\n ? [source.embedUrl, video.poster]\n : [source.embedUrl]\n const gated = fetched.filter(\n (src): src is string =>\n !!src &&\n !PLAYER_HOSTS.includes(imageGate.hostOf(src) ?? ''),\n )\n return {\n ...video,\n source,\n gated,\n held: gated.some((src) => !imageGate.canLoad(src)),\n }\n })\n })\n\n function release(urls: string[]) {\n for (const url of urls) {\n imageGate.release(url)\n }\n }\n</script>\n\n<template>\n <div\n v-if=\"videos.length\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface\">\n <div\n class=\"px-sm py-6 bg-surface-1 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8 min-h-40\">\n <VvIcon name=\"ri:film-line\" class=\"text-16\" />\n <strong class=\"font-bold\">\n {{ $t('label.video') }}\n </strong>\n </div>\n <!-- Each video bleeds to the card's edges, like the map of the\n location card: the card's overflow clips the bottom corners. -->\n <div\n v-for=\"(video, index) in videos\"\n :key=\"video.url\"\n class=\"flex flex-col\"\n :class=\"{ 'border-t border-surface-3': index > 0 }\">\n <!-- Direct file: native player. Handles MP4/WebM/OGG and works\n inside the widget's Shadow DOM without any extra CSS. -->\n <div\n v-if=\"video.held\"\n class=\"w-full overflow-hidden\"\n style=\"aspect-ratio: 16 / 9\">\n <PkHeldImage\n :host=\"imageGate.hostOf(video.gated[0])\"\n kind=\"video\"\n fill\n @release=\"release(video.gated)\" />\n </div>\n <video\n v-else-if=\"video.source.provider === 'file'\"\n :src=\"video.source.embedUrl\"\n :poster=\"video.poster\"\n controls\n preload=\"metadata\"\n playsinline\n class=\"w-full bg-black\"\n style=\"aspect-ratio: 16 / 9; max-height: 480px\">\n <a :href=\"video.url\" target=\"_blank\" rel=\"noopener noreferrer\">\n {{ video.title ?? video.url }}\n </a>\n </video>\n <!-- Provider (YouTube/Vimeo): iframe embed, 16:9 responsive. -->\n <div\n v-else\n class=\"relative w-full overflow-hidden bg-black\"\n style=\"aspect-ratio: 16 / 9\">\n <iframe\n :src=\"video.source.embedUrl\"\n :title=\"video.title ?? video.url\"\n loading=\"lazy\"\n referrerpolicy=\"strict-origin-when-cross-origin\"\n :allow=\"IFRAME_ALLOW\"\n allowfullscreen\n class=\"absolute inset-0 w-full h-full border-0\"></iframe>\n </div>\n <div\n v-if=\"video.title || video.description\"\n class=\"p-sm flex flex-col gap-4 border-t border-surface-3\">\n <strong v-if=\"video.title\" class=\"font-bold text-14\">\n {{ video.title }}\n </strong>\n <span v-if=\"video.description\" class=\"text-12 text-word-3\">\n {{ video.description }}\n </span>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PkToolShowVideo-B_Q93E2y.js","names":["$t","__unplugin_components_0","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_toDisplayString","_Fragment","_renderList","_normalizeClass","PkHeldImage","_unref","_createCommentVNode","_sfc_main","useChatImageGate","sanitizeId","raw","hostMatches","host","domain","youtubeId","u","v","m","vimeoId","parseVideoSource","url","id","hint","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","IFRAME_ALLOW","PkToolShowVideo_default","defineComponent","props","__props","PLAYER_HOSTS","imageGate","videos","computed","source","video","gated","src","release","urls","_component_VvIcon","VvIcon","openBlock","createElementBlock","createElementVNode","createVNode","toDisplayString","_ctx","Fragment","renderList","normalizeClass","index","PkHeldImage_default","unref","createCommentVNode"],"sources":["../../../../packages/utils/src/video-source.ts","../../../../packages/components/src/chat/PkToolShowVideo.vue","../../../../packages/components/src/chat/PkToolShowVideo.vue"],"sourcesContent":["/**\n * Video source parsing shared by the backend platform renderer and the frontend\n * player component, so both classify a video URL the same way.\n *\n * A URL is either a known provider (YouTube/Vimeo), which plays through the\n * provider's iframe embed, or a direct media file (`file`), which plays in a\n * native `<video>` element. Detection is by URL shape; an explicit `hint` is\n * used only as a fallback when the URL alone is inconclusive.\n */\n\nexport type VideoProvider = 'youtube' | 'vimeo' | 'file'\n\nexport interface ParsedVideoSource {\n provider: VideoProvider\n /** The original URL, unchanged. */\n url: string\n /**\n * The URL to actually load: the provider iframe embed URL for\n * YouTube/Vimeo, or the direct file URL for `file`.\n */\n embedUrl: string\n /** The provider video id (providers only). */\n videoId?: string\n /**\n * A preview image URL derived from the provider, when one is available\n * without an extra API call (currently YouTube only). Lets a link-only\n * surface (e.g. a Teams card) show a thumbnail instead of a bare link.\n */\n thumbnailUrl?: string\n}\n\n/** Keep only characters that are legal in a provider video id. */\nconst sanitizeId = (raw: string): string => raw.replace(/[^a-zA-Z0-9_-]/g, '')\n\n/**\n * True when `host` is exactly `domain` or a subdomain of it. Guards against\n * lookalike hosts (e.g. `notyoutube.com`) that a bare substring check would\n * misclassify.\n */\nconst hostMatches = (host: string, domain: string): boolean =>\n host === domain || host.endsWith(`.${domain}`)\n\n/** Extract the YouTube video id from any of its URL shapes, or null. */\nconst youtubeId = (u: URL): string | null => {\n // youtu.be/<id>\n if (u.hostname.endsWith('youtu.be')) {\n return sanitizeId(u.pathname.split('/')[1] ?? '') || null\n }\n // youtube.com/watch?v=<id>\n const v = u.searchParams.get('v')\n if (v) {\n return sanitizeId(v) || null\n }\n // youtube.com/shorts/<id>, /live/<id>, /embed/<id>, /v/<id>\n const m = u.pathname.match(/^\\/(?:shorts|live|embed|v)\\/([^/?#]+)/)\n return m ? sanitizeId(m[1]) || null : null\n}\n\n/** Extract the Vimeo (numeric) video id, or null. */\nconst vimeoId = (u: URL): string | null => {\n // player.vimeo.com/video/<id> or vimeo.com/<id>\n const m = u.pathname.match(/(?:^|\\/)(\\d+)(?:$|[/?#])/)\n return m ? m[1] : null\n}\n\n/**\n * Classify a video URL into a provider + a loadable embed/file URL.\n * Falls back to `file` (native `<video>`) for anything not recognized as a\n * supported provider, or when a provider is matched but no id can be extracted.\n */\nexport function parseVideoSource(\n url: string,\n hint?: VideoProvider,\n): ParsedVideoSource {\n let u: URL | undefined\n try {\n u = new URL(url)\n } catch {\n // Not an absolute URL: treat as a direct file reference.\n return { provider: 'file', url, embedUrl: url }\n }\n\n const host = u.hostname.toLowerCase()\n\n const isYouTube =\n hostMatches(host, 'youtube.com') ||\n hostMatches(host, 'youtu.be') ||\n hostMatches(host, 'youtube-nocookie.com')\n if (isYouTube) {\n const id = youtubeId(u)\n if (id) {\n return {\n provider: 'youtube',\n url,\n videoId: id,\n embedUrl: `https://www.youtube-nocookie.com/embed/${id}`,\n thumbnailUrl: `https://img.youtube.com/vi/${id}/hqdefault.jpg`,\n }\n }\n }\n\n if (hostMatches(host, 'vimeo.com')) {\n const id = vimeoId(u)\n if (id) {\n return {\n provider: 'vimeo',\n url,\n videoId: id,\n // `dnt=1`: the player sets no cookies and records no session\n // data, the Vimeo counterpart of youtube-nocookie above.\n embedUrl: `https://player.vimeo.com/video/${id}?dnt=1`,\n }\n }\n }\n\n // Honor an explicit provider hint only if we could not extract an id from a\n // provider URL (so a caller-tagged provider link still routes to an iframe\n // via its original URL rather than a broken native <video>).\n if (hint && hint !== 'file') {\n return { provider: hint, url, embedUrl: url }\n }\n\n return { provider: 'file', url, embedUrl: url }\n}\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { parseVideoSource, type VideoProvider } from 'utils'\n import { useChatImageGate } from './chatImagePolicy'\n import PkHeldImage from './PkHeldImage.vue'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n interface VideoInput {\n url: string\n title?: string\n description?: string\n poster?: string\n provider?: VideoProvider\n }\n\n // Permissions granted to provider embeds (YouTube/Vimeo players).\n const IFRAME_ALLOW =\n 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'\n\n /**\n * Where a YouTube or Vimeo player lives: the url is built here from the\n * video id, so nothing the model wrote reaches these hosts but the id.\n */\n const PLAYER_HOSTS = ['www.youtube-nocookie.com', 'player.vimeo.com']\n\n // A file, its poster, or an embed this card does not recognize, fetch the\n // url the model wrote as soon as they render: from a host the agent does\n // not list they wait for a click, like an image (chatImagePolicy.ts).\n const imageGate = useChatImageGate()\n\n const videos = computed(() => {\n const part = props.part as { input?: { videos?: VideoInput[] } }\n return (part.input?.videos ?? []).map((video) => {\n const source = parseVideoSource(video.url, video.provider)\n const fetched =\n source.provider === 'file'\n ? [source.embedUrl, video.poster]\n : [source.embedUrl]\n const gated = fetched.filter(\n (src): src is string =>\n !!src &&\n !PLAYER_HOSTS.includes(imageGate.hostOf(src) ?? ''),\n )\n return {\n ...video,\n source,\n gated,\n held: gated.some((src) => !imageGate.canLoad(src)),\n }\n })\n })\n\n function release(urls: string[]) {\n for (const url of urls) {\n imageGate.release(url)\n }\n }\n</script>\n\n<template>\n <div\n v-if=\"videos.length\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface\">\n <div\n class=\"px-sm py-6 bg-surface-1 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8 min-h-40\">\n <VvIcon name=\"ri:film-line\" class=\"text-16\" />\n <strong class=\"font-bold\">\n {{ $t('label.video') }}\n </strong>\n </div>\n <!-- Each video bleeds to the card's edges, like the map of the\n location card: the card's overflow clips the bottom corners. -->\n <div\n v-for=\"(video, index) in videos\"\n :key=\"video.url\"\n class=\"flex flex-col\"\n :class=\"{ 'border-t border-surface-3': index > 0 }\">\n <!-- Direct file: native player. Handles MP4/WebM/OGG and works\n inside the widget's Shadow DOM without any extra CSS. -->\n <div\n v-if=\"video.held\"\n class=\"w-full overflow-hidden\"\n style=\"aspect-ratio: 16 / 9\">\n <PkHeldImage\n :host=\"imageGate.hostOf(video.gated[0])\"\n kind=\"video\"\n fill\n @release=\"release(video.gated)\" />\n </div>\n <video\n v-else-if=\"video.source.provider === 'file'\"\n :src=\"video.source.embedUrl\"\n :poster=\"video.poster\"\n controls\n preload=\"metadata\"\n playsinline\n class=\"w-full bg-black\"\n style=\"aspect-ratio: 16 / 9; max-height: 480px\">\n <a :href=\"video.url\" target=\"_blank\" rel=\"noopener noreferrer\">\n {{ video.title ?? video.url }}\n </a>\n </video>\n <!-- Provider (YouTube/Vimeo): iframe embed, 16:9 responsive. -->\n <div\n v-else\n class=\"relative w-full overflow-hidden bg-black\"\n style=\"aspect-ratio: 16 / 9\">\n <iframe\n :src=\"video.source.embedUrl\"\n :title=\"video.title ?? video.url\"\n loading=\"lazy\"\n referrerpolicy=\"strict-origin-when-cross-origin\"\n :allow=\"IFRAME_ALLOW\"\n allowfullscreen\n class=\"absolute inset-0 w-full h-full border-0\"></iframe>\n </div>\n <div\n v-if=\"video.title || video.description\"\n class=\"p-sm flex flex-col gap-4 border-t border-surface-3\">\n <strong v-if=\"video.title\" class=\"font-bold text-14\">\n {{ video.title }}\n </strong>\n <span v-if=\"video.description\" class=\"text-12 text-word-3\">\n {{ video.description }}\n </span>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { parseVideoSource, type VideoProvider } from 'utils'\n import { useChatImageGate } from './chatImagePolicy'\n import PkHeldImage from './PkHeldImage.vue'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n interface VideoInput {\n url: string\n title?: string\n description?: string\n poster?: string\n provider?: VideoProvider\n }\n\n // Permissions granted to provider embeds (YouTube/Vimeo players).\n const IFRAME_ALLOW =\n 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'\n\n /**\n * Where a YouTube or Vimeo player lives: the url is built here from the\n * video id, so nothing the model wrote reaches these hosts but the id.\n */\n const PLAYER_HOSTS = ['www.youtube-nocookie.com', 'player.vimeo.com']\n\n // A file, its poster, or an embed this card does not recognize, fetch the\n // url the model wrote as soon as they render: from a host the agent does\n // not list they wait for a click, like an image (chatImagePolicy.ts).\n const imageGate = useChatImageGate()\n\n const videos = computed(() => {\n const part = props.part as { input?: { videos?: VideoInput[] } }\n return (part.input?.videos ?? []).map((video) => {\n const source = parseVideoSource(video.url, video.provider)\n const fetched =\n source.provider === 'file'\n ? [source.embedUrl, video.poster]\n : [source.embedUrl]\n const gated = fetched.filter(\n (src): src is string =>\n !!src &&\n !PLAYER_HOSTS.includes(imageGate.hostOf(src) ?? ''),\n )\n return {\n ...video,\n source,\n gated,\n held: gated.some((src) => !imageGate.canLoad(src)),\n }\n })\n })\n\n function release(urls: string[]) {\n for (const url of urls) {\n imageGate.release(url)\n }\n }\n</script>\n\n<template>\n <div\n v-if=\"videos.length\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface\">\n <div\n class=\"px-sm py-6 bg-surface-1 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8 min-h-40\">\n <VvIcon name=\"ri:film-line\" class=\"text-16\" />\n <strong class=\"font-bold\">\n {{ $t('label.video') }}\n </strong>\n </div>\n <!-- Each video bleeds to the card's edges, like the map of the\n location card: the card's overflow clips the bottom corners. -->\n <div\n v-for=\"(video, index) in videos\"\n :key=\"video.url\"\n class=\"flex flex-col\"\n :class=\"{ 'border-t border-surface-3': index > 0 }\">\n <!-- Direct file: native player. Handles MP4/WebM/OGG and works\n inside the widget's Shadow DOM without any extra CSS. -->\n <div\n v-if=\"video.held\"\n class=\"w-full overflow-hidden\"\n style=\"aspect-ratio: 16 / 9\">\n <PkHeldImage\n :host=\"imageGate.hostOf(video.gated[0])\"\n kind=\"video\"\n fill\n @release=\"release(video.gated)\" />\n </div>\n <video\n v-else-if=\"video.source.provider === 'file'\"\n :src=\"video.source.embedUrl\"\n :poster=\"video.poster\"\n controls\n preload=\"metadata\"\n playsinline\n class=\"w-full bg-black\"\n style=\"aspect-ratio: 16 / 9; max-height: 480px\">\n <a :href=\"video.url\" target=\"_blank\" rel=\"noopener noreferrer\">\n {{ video.title ?? video.url }}\n </a>\n </video>\n <!-- Provider (YouTube/Vimeo): iframe embed, 16:9 responsive. -->\n <div\n v-else\n class=\"relative w-full overflow-hidden bg-black\"\n style=\"aspect-ratio: 16 / 9\">\n <iframe\n :src=\"video.source.embedUrl\"\n :title=\"video.title ?? video.url\"\n loading=\"lazy\"\n referrerpolicy=\"strict-origin-when-cross-origin\"\n :allow=\"IFRAME_ALLOW\"\n allowfullscreen\n class=\"absolute inset-0 w-full h-full border-0\"></iframe>\n </div>\n <div\n v-if=\"video.title || video.description\"\n class=\"p-sm flex flex-col gap-4 border-t border-surface-3\">\n <strong v-if=\"video.title\" class=\"font-bold text-14\">\n {{ video.title }}\n </strong>\n <span v-if=\"video.description\" class=\"text-12 text-word-3\">\n {{ video.description }}\n </span>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { t as e } from "./rolldown-runtime-Dy4uBu1J.js";
2
- import { r as t } from "./PkStreamingMarkdown-BtyWXY2k.js";
2
+ import { r as t } from "./PkStreamingMarkdown-C55Fi0Pd.js";
3
3
  import "./src-DCfvejyT.js";
4
4
  import { n } from "./url-validation-9DstIfq2.js";
5
5
  import { t as r } from "./PkLazyImg-X5BMNU_R.js";
@@ -83,4 +83,4 @@ var x = {
83
83
  //#endregion
84
84
  export { k as n, A as t };
85
85
 
86
- //# sourceMappingURL=PkToolShowWebPages-DLEC9cuh.js.map
86
+ //# sourceMappingURL=PkToolShowWebPages-D3CdjJUc.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkToolShowWebPages-DLEC9cuh.js","names":["$t","__unplugin_components_0","_openBlock","_createElementBlock","_Fragment","_renderList","_createElementVNode","_unref","_createBlock","PkHeldImage","PkLazyImg","_createCommentVNode","_toDisplayString","_createVNode","PkUrl","_createTextVNode","_sfc_main","__exportAll","useChatImageGate","PkLazyImg_default","PkUrl_default","PkHeldImage_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","PkToolShowWebPages_vue_vue_type_script_setup_true_lang_default","props","__props","toolPart","computed","imageGate","normalizeUrl","u","url","uniquePages","seen","result","page","key","showMore","ref","pages","hasMore","_component_VvIcon","VvIcon","openBlock","createElementBlock","Fragment","renderList","index","createElementVNode","_ctx","unref","isSameSite","createBlock","createCommentVNode","toDisplayString","createVNode","_cache","createTextVNode","PkToolShowWebPages_exports","PkToolShowWebPages_default"],"sources":["../../../../packages/components/src/chat/PkToolShowWebPages.vue","../../../../packages/components/src/chat/PkToolShowWebPages.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, ref } from 'vue'\n import { isSameSite } from 'utils'\n import PkLazyImg from '../PkLazyImg.vue'\n import { useChatImageGate } from './chatImagePolicy'\n import PkHeldImage from './PkHeldImage.vue'\n import PkUrl from '../PkUrl.vue'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n pages: Array<{\n title: string\n url: string\n image?: string\n description?: string\n }>\n }\n }\n return part\n })\n\n // The model writes these urls: one from a host the agent does not list\n // waits for a click (chatImagePolicy.ts).\n const imageGate = useChatImageGate()\n\n // The model often passes the same URL several times (e.g. multiple KB\n // chunks from one page), so collapse duplicates, keeping the first (richest)\n // entry. Normalize by dropping the hash and any trailing slash.\n const normalizeUrl = (url: string) => {\n try {\n const u = new URL(url)\n u.hash = ''\n return u.toString().replace(/\\/$/, '')\n } catch {\n return url.trim().replace(/\\/$/, '')\n }\n }\n const uniquePages = computed(() => {\n const seen = new Set<string>()\n const result: NonNullable<typeof toolPart.value.input>['pages'] = []\n for (const page of toolPart.value.input?.pages || []) {\n const key = normalizeUrl(page.url)\n if (seen.has(key)) {\n continue\n }\n seen.add(key)\n result.push(page)\n }\n return result\n })\n\n const showMore = ref(false)\n const pages = computed(() => {\n if (showMore.value) {\n return uniquePages.value\n }\n return uniquePages.value.slice(0, 2)\n })\n const hasMore = computed(() => uniquePages.value.length > 2)\n</script>\n\n<template>\n <ul\n v-if=\"pages?.length\"\n class=\"vv-item-group vv-item-group--framed w-full shrink-0\">\n <li\n v-for=\"(page, index) in pages\"\n :key=\"index\"\n class=\"vv-item vv-item--top vv-item--roomy\">\n <a\n :href=\"page.url\"\n :title=\"$t('action.open')\"\n :target=\"isSameSite(page.url) ? undefined : '_blank'\"\n :rel=\"isSameSite(page.url) ? undefined : 'noopener noreferrer'\"\n class=\"vv-item__entry\">\n <!-- og:images the model scraped are third-party urls: a dead one\n degrades to an icon instead of breaking the card layout -->\n <PkHeldImage\n v-if=\"page.image && !imageGate.canLoad(page.image)\"\n :host=\"imageGate.hostOf(page.image)\"\n class=\"w-48 h-48 rounded-lg shrink-0 border border-surface-3 bg-surface\"\n @release=\"imageGate.release(page.image)\" />\n <PkLazyImg\n v-else-if=\"page.image\"\n :src=\"page.image\"\n :alt=\"page.title\"\n fallback-icon=\"ri:image-off-line\"\n class=\"w-48 h-48 object-contain rounded-lg shrink-0 border border-surface-3 bg-surface\" />\n <div class=\"flex flex-col gap-4 min-w-0 flex-1\">\n <strong\n class=\"vv-item__title font-medium text-12 text-word-1\">\n {{ page.title }}\n </strong>\n <span\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-12\" />\n <PkUrl :url=\"page.url\" />\n </span>\n <p\n v-if=\"page.description\"\n class=\"text-12 text-word-3 line-clamp-2\">\n {{ page.description }}\n </p>\n </div>\n </a>\n </li>\n <li v-if=\"hasMore\" class=\"vv-item\">\n <button\n class=\"vv-item__entry justify-center cursor-pointer\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"\n showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\n \"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: uniquePages.length - 2,\n })\n }}\n </button>\n </li>\n </ul>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref } from 'vue'\n import { isSameSite } from 'utils'\n import PkLazyImg from '../PkLazyImg.vue'\n import { useChatImageGate } from './chatImagePolicy'\n import PkHeldImage from './PkHeldImage.vue'\n import PkUrl from '../PkUrl.vue'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n pages: Array<{\n title: string\n url: string\n image?: string\n description?: string\n }>\n }\n }\n return part\n })\n\n // The model writes these urls: one from a host the agent does not list\n // waits for a click (chatImagePolicy.ts).\n const imageGate = useChatImageGate()\n\n // The model often passes the same URL several times (e.g. multiple KB\n // chunks from one page), so collapse duplicates, keeping the first (richest)\n // entry. Normalize by dropping the hash and any trailing slash.\n const normalizeUrl = (url: string) => {\n try {\n const u = new URL(url)\n u.hash = ''\n return u.toString().replace(/\\/$/, '')\n } catch {\n return url.trim().replace(/\\/$/, '')\n }\n }\n const uniquePages = computed(() => {\n const seen = new Set<string>()\n const result: NonNullable<typeof toolPart.value.input>['pages'] = []\n for (const page of toolPart.value.input?.pages || []) {\n const key = normalizeUrl(page.url)\n if (seen.has(key)) {\n continue\n }\n seen.add(key)\n result.push(page)\n }\n return result\n })\n\n const showMore = ref(false)\n const pages = computed(() => {\n if (showMore.value) {\n return uniquePages.value\n }\n return uniquePages.value.slice(0, 2)\n })\n const hasMore = computed(() => uniquePages.value.length > 2)\n</script>\n\n<template>\n <ul\n v-if=\"pages?.length\"\n class=\"vv-item-group vv-item-group--framed w-full shrink-0\">\n <li\n v-for=\"(page, index) in pages\"\n :key=\"index\"\n class=\"vv-item vv-item--top vv-item--roomy\">\n <a\n :href=\"page.url\"\n :title=\"$t('action.open')\"\n :target=\"isSameSite(page.url) ? undefined : '_blank'\"\n :rel=\"isSameSite(page.url) ? undefined : 'noopener noreferrer'\"\n class=\"vv-item__entry\">\n <!-- og:images the model scraped are third-party urls: a dead one\n degrades to an icon instead of breaking the card layout -->\n <PkHeldImage\n v-if=\"page.image && !imageGate.canLoad(page.image)\"\n :host=\"imageGate.hostOf(page.image)\"\n class=\"w-48 h-48 rounded-lg shrink-0 border border-surface-3 bg-surface\"\n @release=\"imageGate.release(page.image)\" />\n <PkLazyImg\n v-else-if=\"page.image\"\n :src=\"page.image\"\n :alt=\"page.title\"\n fallback-icon=\"ri:image-off-line\"\n class=\"w-48 h-48 object-contain rounded-lg shrink-0 border border-surface-3 bg-surface\" />\n <div class=\"flex flex-col gap-4 min-w-0 flex-1\">\n <strong\n class=\"vv-item__title font-medium text-12 text-word-1\">\n {{ page.title }}\n </strong>\n <span\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-12\" />\n <PkUrl :url=\"page.url\" />\n </span>\n <p\n v-if=\"page.description\"\n class=\"text-12 text-word-3 line-clamp-2\">\n {{ page.description }}\n </p>\n </div>\n </a>\n </li>\n <li v-if=\"hasMore\" class=\"vv-item\">\n <button\n class=\"vv-item__entry justify-center cursor-pointer\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"\n showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\n \"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: uniquePages.length - 2,\n })\n }}\n </button>\n </li>\n </ul>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PkToolShowWebPages-D3CdjJUc.js","names":["$t","__unplugin_components_0","_openBlock","_createElementBlock","_Fragment","_renderList","_createElementVNode","_unref","_createBlock","PkHeldImage","PkLazyImg","_createCommentVNode","_toDisplayString","_createVNode","PkUrl","_createTextVNode","_sfc_main","__exportAll","useChatImageGate","PkLazyImg_default","PkUrl_default","PkHeldImage_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","PkToolShowWebPages_vue_vue_type_script_setup_true_lang_default","props","__props","toolPart","computed","imageGate","normalizeUrl","u","url","uniquePages","seen","result","page","key","showMore","ref","pages","hasMore","_component_VvIcon","VvIcon","openBlock","createElementBlock","Fragment","renderList","index","createElementVNode","_ctx","unref","isSameSite","createBlock","createCommentVNode","toDisplayString","createVNode","_cache","createTextVNode","PkToolShowWebPages_exports","PkToolShowWebPages_default"],"sources":["../../../../packages/components/src/chat/PkToolShowWebPages.vue","../../../../packages/components/src/chat/PkToolShowWebPages.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, ref } from 'vue'\n import { isSameSite } from 'utils'\n import PkLazyImg from '../PkLazyImg.vue'\n import { useChatImageGate } from './chatImagePolicy'\n import PkHeldImage from './PkHeldImage.vue'\n import PkUrl from '../PkUrl.vue'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n pages: Array<{\n title: string\n url: string\n image?: string\n description?: string\n }>\n }\n }\n return part\n })\n\n // The model writes these urls: one from a host the agent does not list\n // waits for a click (chatImagePolicy.ts).\n const imageGate = useChatImageGate()\n\n // The model often passes the same URL several times (e.g. multiple KB\n // chunks from one page), so collapse duplicates, keeping the first (richest)\n // entry. Normalize by dropping the hash and any trailing slash.\n const normalizeUrl = (url: string) => {\n try {\n const u = new URL(url)\n u.hash = ''\n return u.toString().replace(/\\/$/, '')\n } catch {\n return url.trim().replace(/\\/$/, '')\n }\n }\n const uniquePages = computed(() => {\n const seen = new Set<string>()\n const result: NonNullable<typeof toolPart.value.input>['pages'] = []\n for (const page of toolPart.value.input?.pages || []) {\n const key = normalizeUrl(page.url)\n if (seen.has(key)) {\n continue\n }\n seen.add(key)\n result.push(page)\n }\n return result\n })\n\n const showMore = ref(false)\n const pages = computed(() => {\n if (showMore.value) {\n return uniquePages.value\n }\n return uniquePages.value.slice(0, 2)\n })\n const hasMore = computed(() => uniquePages.value.length > 2)\n</script>\n\n<template>\n <ul\n v-if=\"pages?.length\"\n class=\"vv-item-group vv-item-group--framed w-full shrink-0\">\n <li\n v-for=\"(page, index) in pages\"\n :key=\"index\"\n class=\"vv-item vv-item--top vv-item--roomy\">\n <a\n :href=\"page.url\"\n :title=\"$t('action.open')\"\n :target=\"isSameSite(page.url) ? undefined : '_blank'\"\n :rel=\"isSameSite(page.url) ? undefined : 'noopener noreferrer'\"\n class=\"vv-item__entry\">\n <!-- og:images the model scraped are third-party urls: a dead one\n degrades to an icon instead of breaking the card layout -->\n <PkHeldImage\n v-if=\"page.image && !imageGate.canLoad(page.image)\"\n :host=\"imageGate.hostOf(page.image)\"\n class=\"w-48 h-48 rounded-lg shrink-0 border border-surface-3 bg-surface\"\n @release=\"imageGate.release(page.image)\" />\n <PkLazyImg\n v-else-if=\"page.image\"\n :src=\"page.image\"\n :alt=\"page.title\"\n fallback-icon=\"ri:image-off-line\"\n class=\"w-48 h-48 object-contain rounded-lg shrink-0 border border-surface-3 bg-surface\" />\n <div class=\"flex flex-col gap-4 min-w-0 flex-1\">\n <strong\n class=\"vv-item__title font-medium text-12 text-word-1\">\n {{ page.title }}\n </strong>\n <span\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-12\" />\n <PkUrl :url=\"page.url\" />\n </span>\n <p\n v-if=\"page.description\"\n class=\"text-12 text-word-3 line-clamp-2\">\n {{ page.description }}\n </p>\n </div>\n </a>\n </li>\n <li v-if=\"hasMore\" class=\"vv-item\">\n <button\n class=\"vv-item__entry justify-center cursor-pointer\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"\n showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\n \"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: uniquePages.length - 2,\n })\n }}\n </button>\n </li>\n </ul>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref } from 'vue'\n import { isSameSite } from 'utils'\n import PkLazyImg from '../PkLazyImg.vue'\n import { useChatImageGate } from './chatImagePolicy'\n import PkHeldImage from './PkHeldImage.vue'\n import PkUrl from '../PkUrl.vue'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n pages: Array<{\n title: string\n url: string\n image?: string\n description?: string\n }>\n }\n }\n return part\n })\n\n // The model writes these urls: one from a host the agent does not list\n // waits for a click (chatImagePolicy.ts).\n const imageGate = useChatImageGate()\n\n // The model often passes the same URL several times (e.g. multiple KB\n // chunks from one page), so collapse duplicates, keeping the first (richest)\n // entry. Normalize by dropping the hash and any trailing slash.\n const normalizeUrl = (url: string) => {\n try {\n const u = new URL(url)\n u.hash = ''\n return u.toString().replace(/\\/$/, '')\n } catch {\n return url.trim().replace(/\\/$/, '')\n }\n }\n const uniquePages = computed(() => {\n const seen = new Set<string>()\n const result: NonNullable<typeof toolPart.value.input>['pages'] = []\n for (const page of toolPart.value.input?.pages || []) {\n const key = normalizeUrl(page.url)\n if (seen.has(key)) {\n continue\n }\n seen.add(key)\n result.push(page)\n }\n return result\n })\n\n const showMore = ref(false)\n const pages = computed(() => {\n if (showMore.value) {\n return uniquePages.value\n }\n return uniquePages.value.slice(0, 2)\n })\n const hasMore = computed(() => uniquePages.value.length > 2)\n</script>\n\n<template>\n <ul\n v-if=\"pages?.length\"\n class=\"vv-item-group vv-item-group--framed w-full shrink-0\">\n <li\n v-for=\"(page, index) in pages\"\n :key=\"index\"\n class=\"vv-item vv-item--top vv-item--roomy\">\n <a\n :href=\"page.url\"\n :title=\"$t('action.open')\"\n :target=\"isSameSite(page.url) ? undefined : '_blank'\"\n :rel=\"isSameSite(page.url) ? undefined : 'noopener noreferrer'\"\n class=\"vv-item__entry\">\n <!-- og:images the model scraped are third-party urls: a dead one\n degrades to an icon instead of breaking the card layout -->\n <PkHeldImage\n v-if=\"page.image && !imageGate.canLoad(page.image)\"\n :host=\"imageGate.hostOf(page.image)\"\n class=\"w-48 h-48 rounded-lg shrink-0 border border-surface-3 bg-surface\"\n @release=\"imageGate.release(page.image)\" />\n <PkLazyImg\n v-else-if=\"page.image\"\n :src=\"page.image\"\n :alt=\"page.title\"\n fallback-icon=\"ri:image-off-line\"\n class=\"w-48 h-48 object-contain rounded-lg shrink-0 border border-surface-3 bg-surface\" />\n <div class=\"flex flex-col gap-4 min-w-0 flex-1\">\n <strong\n class=\"vv-item__title font-medium text-12 text-word-1\">\n {{ page.title }}\n </strong>\n <span\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-12\" />\n <PkUrl :url=\"page.url\" />\n </span>\n <p\n v-if=\"page.description\"\n class=\"text-12 text-word-3 line-clamp-2\">\n {{ page.description }}\n </p>\n </div>\n </a>\n </li>\n <li v-if=\"hasMore\" class=\"vv-item\">\n <button\n class=\"vv-item__entry justify-center cursor-pointer\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"\n showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\n \"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: uniquePages.length - 2,\n })\n }}\n </button>\n </li>\n </ul>\n</template>\n"],"mappings":"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"}
@@ -84,9 +84,18 @@ function w(e, t) {
84
84
  return e === "white" || e === "black" ? e : C(t);
85
85
  }
86
86
  function T(e, t) {
87
+ if (e) return {
88
+ "--chatbot-main-color": e,
89
+ "--chatbot-contrast-color": t,
90
+ "--color-brand": e,
91
+ "--color-brand-contrast": t,
92
+ "--input-focus-color": "var(--color-brand-graphic)"
93
+ };
94
+ }
95
+ function E(e, t) {
87
96
  if (!(t === void 0 || t < 50)) return e(t / 1e3, "seconds");
88
97
  }
89
- function E(e) {
98
+ function D(e) {
90
99
  if (e !== void 0) {
91
100
  if (typeof e == "string") return e.trim() || void 0;
92
101
  try {
@@ -97,10 +106,10 @@ function E(e) {
97
106
  }
98
107
  }
99
108
  }
100
- function D(e) {
101
- return E(e) !== void 0;
109
+ function O(e) {
110
+ return D(e) !== void 0;
102
111
  }
103
- function O(e, t) {
112
+ function k(e, t) {
104
113
  let n = e?.input;
105
114
  if (typeof t == "string") {
106
115
  let e = n?.question ?? n?.steps?.[0]?.question;
@@ -112,14 +121,14 @@ function O(e, t) {
112
121
  return i ? `${i} ${n}` : n;
113
122
  }).join(". ");
114
123
  }
115
- function k(e) {
124
+ function A(e) {
116
125
  return e?.options ? e.options.map((e) => ({
117
126
  label: e.label,
118
127
  value: e.label,
119
128
  description: e.description
120
129
  })) : [];
121
130
  }
122
- function A(e, t) {
131
+ function j(e, t) {
123
132
  let n = {};
124
133
  if (t == null) return n;
125
134
  if (typeof t == "string") return e.length === 1 && (n[0] = t), n;
@@ -135,12 +144,12 @@ function A(e, t) {
135
144
  for (let [e, n] of Object.entries(t)) n !== void 0 && r(e, n);
136
145
  return n;
137
146
  }
138
- var j = [
147
+ var M = [
139
148
  "tool-perplexity_search",
140
149
  "tool-parallel_search",
141
150
  "tool-web_search",
142
151
  "tool-google_search"
143
- ], M = /* @__PURE__ */ new Set([
152
+ ], N = /* @__PURE__ */ new Set([
144
153
  "tool-searchInKnowledgeBase",
145
154
  "tool-retrieveRevisedAnswers",
146
155
  "tool-expandChunkContext",
@@ -149,51 +158,51 @@ var j = [
149
158
  "tool-geocode",
150
159
  "tool-recallUserMemory",
151
160
  "tool-saveUserMemory",
152
- ...j
161
+ ...M
153
162
  ]);
154
- function N(e) {
155
- return r(e) && M.has(e.type);
163
+ function P(e) {
164
+ return r(e) && N.has(e.type);
156
165
  }
157
- var P = (e) => e && typeof e == "object" && !Array.isArray(e) ? e : void 0, F = (e, ...t) => {
166
+ var F = (e) => e && typeof e == "object" && !Array.isArray(e) ? e : void 0, I = (e, ...t) => {
158
167
  for (let n of t) {
159
168
  let t = e?.[n];
160
169
  if (typeof t == "string" && t.trim()) return t;
161
170
  }
162
171
  };
163
- function I(e) {
172
+ function L(e) {
164
173
  if (!r(e)) return;
165
- let t = P("input" in e ? e.input : void 0), n = P("output" in e ? e.output : void 0);
166
- if (e.type === "tool-searchInKnowledgeBase" || j.includes(e.type)) {
174
+ let t = F("input" in e ? e.input : void 0), n = F("output" in e ? e.output : void 0);
175
+ if (e.type === "tool-searchInKnowledgeBase" || M.includes(e.type)) {
167
176
  let e = t?.queries;
168
177
  if (Array.isArray(e)) {
169
178
  let t = e.filter((e) => typeof e == "string" && !!e.trim());
170
179
  return t.length ? t.join(" · ") : void 0;
171
180
  }
172
- return F(t, "query", "objective");
181
+ return I(t, "query", "objective");
173
182
  }
174
- if (e.type === "tool-retrieveRevisedAnswers") return F(t, "query");
175
- if (e.type === "tool-getWeather") return F(n, "location") ?? F(t, "locationName");
176
- if (e.type === "tool-geocode") return F(t, "locationName");
177
- if (e.type === "tool-expandChunkContext") return F(n, "documentName");
178
- if (e.type === "tool-readDocumentFile") return F(n, "name");
183
+ if (e.type === "tool-retrieveRevisedAnswers") return I(t, "query");
184
+ if (e.type === "tool-getWeather") return I(n, "location") ?? I(t, "locationName");
185
+ if (e.type === "tool-geocode") return I(t, "locationName");
186
+ if (e.type === "tool-expandChunkContext") return I(n, "documentName");
187
+ if (e.type === "tool-readDocumentFile") return I(n, "name");
179
188
  if (e.type === "tool-readSpreadsheet") {
180
- let e = F(t, "sheet"), n = F(t, "range");
189
+ let e = I(t, "sheet"), n = I(t, "range");
181
190
  return e && n ? `${e}!${n}` : e;
182
191
  }
183
- if (e.type === "tool-editSpreadsheet") return F(n, "filename");
184
- if (e.type === "tool-querySpreadsheet") return F(n, "file");
185
- if (e.type === "tool-recallUserMemory") return F(t, "query");
186
- if (e.type === "tool-saveUserMemory") return F(t, "content", "query");
192
+ if (e.type === "tool-editSpreadsheet") return I(n, "filename");
193
+ if (e.type === "tool-querySpreadsheet") return I(n, "file");
194
+ if (e.type === "tool-recallUserMemory") return I(t, "query");
195
+ if (e.type === "tool-saveUserMemory") return I(t, "content", "query");
187
196
  }
188
- function L(e) {
189
- if (r(e)) return e.type.includes("tool-subagent") ? "ri:robot-2-line" : e.type === "tool-runWorker" ? "ri:flow-chart" : e.type === "tool-scheduleWakeup" ? "ri:alarm-line" : e.type === "tool-fetchTeamsChannelHistory" ? "ri:chat-history-line" : e.type === "tool-readEarlierMessages" ? "ri:history-line" : e.type === "tool-enableTools" ? "ri:toggle-line" : e.type.includes("tool-mcp") ? "mcp" : e.type === "tool-retrieveRevisedAnswers" ? "ri:file-edit-line" : e.type === "tool-searchInKnowledgeBase" ? "ri:search-line" : e.type === "tool-expandChunkContext" ? "ri:file-search-line" : e.type === "tool-readDocumentFile" ? "ri:file-text-line" : e.type === "tool-readSpreadsheet" ? "ri:table-line" : e.type === "tool-editSpreadsheet" ? "ri:file-excel-2-line" : e.type === "tool-querySpreadsheet" ? "ri:database-2-line" : e.type === "tool-geocode" ? "ri:map-2-line" : e.type === "tool-getWeather" ? "ri:sun-cloudy-line" : e.type === "tool-recallUserMemory" ? "ri:brain-line" : e.type === "tool-saveUserMemory" ? "ri:save-3-line" : e.type === "tool-showWeather" ? "ri:sun-line" : e.type === "tool-showDiagram" ? "ri:flow-chart" : e.type === "tool-showSources" ? "ri:book-2-line" : e.type === "tool-showForm" ? "ri:survey-line" : e.type === "tool-showMultipleChoice" ? "ri:checkbox-multiple-line" : e.type === "tool-showArtifact" ? "ri:file-code-line" : e.type === "tool-showEmail" ? "ri:mail-line" : e.type === "tool-showMessage" ? "ri:chat-1-line" : e.type === "tool-showWebPages" ? "ri:pages-line" : e.type === "tool-showProductList" ? "ri:shopping-bag-line" : e.type === "tool-showContactForm" ? "ri:send-plane-2-line" : e.type === "tool-showCalendarEvent" ? "ri:calendar-event-line" : e.type === "tool-showComparison" ? "ri:scales-line" : e.type === "tool-showLocation" ? "ri:map-pin-line" : e.type === "tool-showQrCode" ? "ri:qr-code-line" : e.type === "tool-showImageGallery" ? "ri:image-line" : e.type === "tool-showVideo" ? "ri:film-line" : e.type === "tool-showSuggestedReply" ? "ri:corner-down-right-line" : e.type === "tool-requestGeolocation" ? "ri:map-pin-user-line" : e.type === "tool-disconnectOAuthConnection" ? "ri:link-unlink" : e.type === "tool-perplexity_search" || e.type === "tool-parallel_search" || e.type === "tool-web_search" || e.type === "tool-google_search" ? "ri:global-line" : "ri:tools-line";
197
+ function R(e) {
198
+ if (r(e)) return e.type.includes("tool-subagent") ? "ri:robot-2-line" : e.type === "tool-runWorker" ? "ri:flow-chart" : e.type === "tool-scheduleWakeup" ? "ri:alarm-line" : e.type === "tool-fetchTeamsChannelHistory" ? "ri:chat-history-line" : e.type === "tool-readEarlierMessages" ? "ri:history-line" : e.type === "tool-enableTools" ? "ri:toggle-line" : e.type.includes("tool-mcp") ? "mcp" : e.type === "tool-retrieveRevisedAnswers" ? "ri:file-edit-line" : e.type === "tool-searchInKnowledgeBase" ? "ri:search-line" : e.type === "tool-expandChunkContext" ? "ri:file-search-line" : e.type === "tool-readDocumentFile" ? "ri:file-text-line" : e.type === "tool-readSpreadsheet" ? "ri:table-line" : e.type === "tool-editSpreadsheet" ? "ri:file-excel-2-line" : e.type === "tool-querySpreadsheet" ? "ri:database-2-line" : e.type === "tool-geocode" ? "ri:map-2-line" : e.type === "tool-getWeather" ? "ri:sun-cloudy-line" : e.type === "tool-recallUserMemory" ? "ri:brain-line" : e.type === "tool-saveUserMemory" ? "ri:save-3-line" : e.type === "tool-showWeather" ? "ri:sun-line" : e.type === "tool-showDiagram" ? "ri:flow-chart" : e.type === "tool-showSources" ? "ri:book-2-line" : e.type === "tool-showForm" ? "ri:survey-line" : e.type === "tool-showMultipleChoice" ? "ri:checkbox-multiple-line" : e.type === "tool-showArtifact" ? "ri:file-code-line" : e.type === "tool-showEmail" ? "ri:mail-line" : e.type === "tool-showMessage" ? "ri:chat-1-line" : e.type === "tool-showWebPages" ? "ri:pages-line" : e.type === "tool-showProductList" ? "ri:shopping-bag-line" : e.type === "tool-showContactForm" ? "ri:send-plane-2-line" : e.type === "tool-showCalendarEvent" ? "ri:calendar-event-line" : e.type === "tool-showComparison" ? "ri:scales-line" : e.type === "tool-showLocation" ? "ri:map-pin-line" : e.type === "tool-showQrCode" ? "ri:qr-code-line" : e.type === "tool-showImageGallery" ? "ri:image-line" : e.type === "tool-showVideo" ? "ri:film-line" : e.type === "tool-showSuggestedReply" ? "ri:corner-down-right-line" : e.type === "tool-requestGeolocation" ? "ri:map-pin-user-line" : e.type === "tool-disconnectOAuthConnection" ? "ri:link-unlink" : e.type === "tool-requestAccountLink" ? "ri:link" : e.type === "tool-unlinkAccount" ? "ri:link-unlink" : e.type === "tool-perplexity_search" || e.type === "tool-parallel_search" || e.type === "tool-web_search" || e.type === "tool-google_search" ? "ri:global-line" : "ri:tools-line";
190
199
  if (i(e)) return "ri:lightbulb-line";
191
200
  if (t(e)) return "ri:double-quotes-r";
192
201
  }
193
- function R(e, t) {
194
- return r(t) ? t.type.includes("tool-subagent") ? e("messagePart.runSubagent") : t.type === "tool-runWorker" ? e("messagePart.runWorker") : t.type === "tool-scheduleWakeup" ? e("messagePart.scheduleWakeup") : t.type === "tool-fetchTeamsChannelHistory" ? e("messagePart.fetchTeamsChannelHistory") : t.type === "tool-readEarlierMessages" ? e("messagePart.readEarlierMessages") : t.type === "tool-enableTools" ? e("messagePart.enableTools") : t.type === "tool-retrieveRevisedAnswers" ? e("messagePart.revisedAnswers") : t.type === "tool-searchInKnowledgeBase" ? e("messagePart.searchingDocuments") : t.type === "tool-expandChunkContext" ? e("messagePart.expandChunkContext") : t.type === "tool-readDocumentFile" ? e("messagePart.readDocumentFile") : t.type === "tool-readSpreadsheet" ? e("messagePart.readSpreadsheet") : t.type === "tool-editSpreadsheet" ? e("messagePart.editSpreadsheet") : t.type === "tool-querySpreadsheet" ? e("messagePart.querySpreadsheet") : t.type === "tool-geocode" ? e("messagePart.geocode") : t.type === "tool-getWeather" ? e("messagePart.getWeather") : t.type === "tool-recallUserMemory" ? e("messagePart.recallUserMemory") : t.type === "tool-saveUserMemory" ? e("messagePart.saveUserMemory") : t.type === "tool-showWeather" ? e("messagePart.showWeather") : t.type === "tool-showForm" ? e("messagePart.showForm") : t.type === "tool-showDiagram" ? e("messagePart.showDiagram") : t.type === "tool-showSources" ? e("messagePart.showSources") : t.type === "tool-showMultipleChoice" ? e("messagePart.showMultipleChoice") : t.type === "tool-showArtifact" ? e("messagePart.showArtifact") : t.type === "tool-showEmail" ? e("messagePart.showEmail") : t.type === "tool-showMessage" ? e("messagePart.showMessage") : t.type === "tool-showWebPages" ? e("messagePart.showWebPages") : t.type === "tool-showProducts" ? e("messagePart.showProducts") : t.type === "tool-showContactForm" ? e("messagePart.showContactForm") : t.type === "tool-showCalendarEvent" ? e("messagePart.showCalendarEvent") : t.type === "tool-showComparison" ? e("messagePart.showComparison") : t.type === "tool-showLocation" ? e("messagePart.showLocation") : t.type === "tool-showQrCode" ? e("messagePart.showQrCode") : t.type === "tool-showImageGallery" ? e("messagePart.showImageGallery") : t.type === "tool-showVideo" ? e("messagePart.showVideo") : t.type === "tool-showSuggestedReply" ? e("messagePart.showSuggestedReply") : t.type === "tool-requestGeolocation" ? e("messagePart.requestGeolocation") : t.type === "tool-disconnectOAuthConnection" ? e("messagePart.disconnectOAuthConnection") : j.includes(t.type) ? e("messagePart.webSearch") : e("messagePart.tool") : i(t) ? e("messagePart.reasoning") : e("messagePart.streaming");
202
+ function z(e, t) {
203
+ return r(t) ? t.type.includes("tool-subagent") ? e("messagePart.runSubagent") : t.type === "tool-runWorker" ? e("messagePart.runWorker") : t.type === "tool-scheduleWakeup" ? e("messagePart.scheduleWakeup") : t.type === "tool-fetchTeamsChannelHistory" ? e("messagePart.fetchTeamsChannelHistory") : t.type === "tool-readEarlierMessages" ? e("messagePart.readEarlierMessages") : t.type === "tool-enableTools" ? e("messagePart.enableTools") : t.type === "tool-retrieveRevisedAnswers" ? e("messagePart.revisedAnswers") : t.type === "tool-searchInKnowledgeBase" ? e("messagePart.searchingDocuments") : t.type === "tool-expandChunkContext" ? e("messagePart.expandChunkContext") : t.type === "tool-readDocumentFile" ? e("messagePart.readDocumentFile") : t.type === "tool-readSpreadsheet" ? e("messagePart.readSpreadsheet") : t.type === "tool-editSpreadsheet" ? e("messagePart.editSpreadsheet") : t.type === "tool-querySpreadsheet" ? e("messagePart.querySpreadsheet") : t.type === "tool-geocode" ? e("messagePart.geocode") : t.type === "tool-getWeather" ? e("messagePart.getWeather") : t.type === "tool-recallUserMemory" ? e("messagePart.recallUserMemory") : t.type === "tool-saveUserMemory" ? e("messagePart.saveUserMemory") : t.type === "tool-showWeather" ? e("messagePart.showWeather") : t.type === "tool-showForm" ? e("messagePart.showForm") : t.type === "tool-showDiagram" ? e("messagePart.showDiagram") : t.type === "tool-showSources" ? e("messagePart.showSources") : t.type === "tool-showMultipleChoice" ? e("messagePart.showMultipleChoice") : t.type === "tool-showArtifact" ? e("messagePart.showArtifact") : t.type === "tool-showEmail" ? e("messagePart.showEmail") : t.type === "tool-showMessage" ? e("messagePart.showMessage") : t.type === "tool-showWebPages" ? e("messagePart.showWebPages") : t.type === "tool-showProducts" ? e("messagePart.showProducts") : t.type === "tool-showContactForm" ? e("messagePart.showContactForm") : t.type === "tool-showCalendarEvent" ? e("messagePart.showCalendarEvent") : t.type === "tool-showComparison" ? e("messagePart.showComparison") : t.type === "tool-showLocation" ? e("messagePart.showLocation") : t.type === "tool-showQrCode" ? e("messagePart.showQrCode") : t.type === "tool-showImageGallery" ? e("messagePart.showImageGallery") : t.type === "tool-showVideo" ? e("messagePart.showVideo") : t.type === "tool-showSuggestedReply" ? e("messagePart.showSuggestedReply") : t.type === "tool-requestGeolocation" ? e("messagePart.requestGeolocation") : t.type === "tool-disconnectOAuthConnection" ? e("messagePart.disconnectOAuthConnection") : t.type === "tool-requestAccountLink" ? e("messagePart.requestAccountLink") : t.type === "tool-unlinkAccount" ? e("messagePart.unlinkAccount") : M.includes(t.type) ? e("messagePart.webSearch") : e("messagePart.tool") : i(t) ? e("messagePart.reasoning") : e("messagePart.streaming");
195
204
  }
196
205
  //#endregion
197
- export { l as C, a as S, E as T, N as _, u as a, d as b, k as c, L as d, x as f, D as g, I as h, s as i, T as l, R as m, f as n, O as o, b as p, j as r, A as s, _ as t, m as u, y as v, w, v as x, S as y };
206
+ export { a as C, D as E, v as S, w as T, O as _, s as a, S as b, j as c, m as d, R as f, L as g, z as h, T as i, A as l, b as m, f as n, u as o, x as p, M as r, k as s, _ as t, E as u, P as v, l as w, d as x, y };
198
207
 
199
- //# sourceMappingURL=utils-fxHiCnzf.js.map
208
+ //# sourceMappingURL=utils-CwIMEuAv.js.map