@8wave/ai-elements 0.99.0 → 0.100.0-beta.1

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 (100) hide show
  1. package/dist/_chunks/PkLazyImg-BhLarH2R.js +34 -0
  2. package/dist/_chunks/PkLazyImg-BhLarH2R.js.map +1 -0
  3. package/{dist-vue/_chunks/PkStreamingMarkdown-CjBMQ18U.js → dist/_chunks/PkStreamingMarkdown-CHhH4FdH.js} +407 -385
  4. package/dist/_chunks/PkStreamingMarkdown-CHhH4FdH.js.map +1 -0
  5. package/dist/_chunks/{PkToolShowArtifact-bYYaMGxq.js → PkToolShowArtifact-BRYd-YSp.js} +2 -2
  6. package/dist/_chunks/{PkToolShowArtifact-bYYaMGxq.js.map → PkToolShowArtifact-BRYd-YSp.js.map} +1 -1
  7. package/dist/_chunks/{PkToolShowContactForm-DtjVW-n6.js → PkToolShowContactForm-vZTYDdfX.js} +2 -2
  8. package/dist/_chunks/{PkToolShowContactForm-DtjVW-n6.js.map → PkToolShowContactForm-vZTYDdfX.js.map} +1 -1
  9. package/dist/_chunks/{PkToolShowForm-BP8asXtz.js → PkToolShowForm-DMmZ34is.js} +2 -2
  10. package/dist/_chunks/{PkToolShowForm-BP8asXtz.js.map → PkToolShowForm-DMmZ34is.js.map} +1 -1
  11. package/dist/_chunks/{PkToolShowImageGallery-WQL3T4Hr.js → PkToolShowImageGallery-B7dClMBr.js} +28 -27
  12. package/dist/_chunks/PkToolShowImageGallery-B7dClMBr.js.map +1 -0
  13. package/dist/_chunks/{PkToolShowProductList-BbY4ni7Y.js → PkToolShowProductList-BGqOPGVe.js} +10 -8
  14. package/dist/_chunks/PkToolShowProductList-BGqOPGVe.js.map +1 -0
  15. package/dist/_chunks/{PkToolShowSources-D2tBcx7n.js → PkToolShowSources-DwhjDoSn.js} +2 -2
  16. package/dist/_chunks/{PkToolShowSources-D2tBcx7n.js.map → PkToolShowSources-DwhjDoSn.js.map} +1 -1
  17. package/dist/_chunks/PkToolShowWebPages-Co1LHj17.js +78 -0
  18. package/dist/_chunks/PkToolShowWebPages-Co1LHj17.js.map +1 -0
  19. package/dist/_chunks/useImageFallback-CaxDKce2.js +19 -0
  20. package/dist/_chunks/useImageFallback-CaxDKce2.js.map +1 -0
  21. package/dist/ai-elements.es.js +3049 -3015
  22. package/dist/ai-elements.es.js.map +1 -1
  23. package/dist-vue/PkChatbot.js +1 -1
  24. package/dist-vue/PkChatbotAuth.js +1 -1
  25. package/dist-vue/PkChatbotEmptyState.js +1 -1
  26. package/dist-vue/PkChatbotInput.js +1 -1
  27. package/dist-vue/PkChatbotMessages.js +1 -1
  28. package/dist-vue/PkChatbotSteps.js +1 -1
  29. package/dist-vue/PkChatbotViewChat.js +1 -1
  30. package/dist-vue/PkChatbotViewFiles.js +1 -1
  31. package/dist-vue/PkChatbotViewProfile.js +1 -1
  32. package/dist-vue/_chunks/PkAvatar-paEJByEA.js +42 -0
  33. package/dist-vue/_chunks/PkAvatar-paEJByEA.js.map +1 -0
  34. package/dist-vue/_chunks/{PkChatbot-Bf5uY3cO.js → PkChatbot-Cs3GOau6.js} +6 -6
  35. package/dist-vue/_chunks/{PkChatbot-Bf5uY3cO.js.map → PkChatbot-Cs3GOau6.js.map} +1 -1
  36. package/dist-vue/_chunks/PkChatbotAuth-DV83Fen2.js +349 -0
  37. package/dist-vue/_chunks/PkChatbotAuth-DV83Fen2.js.map +1 -0
  38. package/dist-vue/_chunks/{PkChatbotEmptyState-CPqLqXek.js → PkChatbotEmptyState-Cxv79prz.js} +3 -3
  39. package/dist-vue/_chunks/{PkChatbotEmptyState-CPqLqXek.js.map → PkChatbotEmptyState-Cxv79prz.js.map} +1 -1
  40. package/dist-vue/_chunks/PkChatbotFilePreview-DyrszALJ.js.map +1 -1
  41. package/dist-vue/_chunks/{PkChatbotInput-jvb0vuak.js → PkChatbotInput-BSm3BI7D.js} +2 -2
  42. package/dist-vue/_chunks/{PkChatbotInput-jvb0vuak.js.map → PkChatbotInput-BSm3BI7D.js.map} +1 -1
  43. package/dist-vue/_chunks/{PkChatbotMessages-B3G12zah.js → PkChatbotMessages-BDRnBwzT.js} +11 -11
  44. package/dist-vue/_chunks/{PkChatbotMessages-B3G12zah.js.map → PkChatbotMessages-BDRnBwzT.js.map} +1 -1
  45. package/dist-vue/_chunks/{PkChatbotSteps-BdU6YC_N.js → PkChatbotSteps-BdycCDRx.js} +2 -2
  46. package/dist-vue/_chunks/{PkChatbotSteps-BdU6YC_N.js.map → PkChatbotSteps-BdycCDRx.js.map} +1 -1
  47. package/dist-vue/_chunks/{PkChatbotViewChat-c0Ecpzoc.js → PkChatbotViewChat-DwfZGp9d.js} +6 -6
  48. package/dist-vue/_chunks/{PkChatbotViewChat-c0Ecpzoc.js.map → PkChatbotViewChat-DwfZGp9d.js.map} +1 -1
  49. package/dist-vue/_chunks/{PkChatbotViewFiles-BipRoidH.js → PkChatbotViewFiles-DwrKKSjU.js} +26 -25
  50. package/dist-vue/_chunks/PkChatbotViewFiles-DwrKKSjU.js.map +1 -0
  51. package/dist-vue/_chunks/{PkChatbotViewProfile-DuLwYpGZ.js → PkChatbotViewProfile-B7yqkE-h.js} +2 -2
  52. package/dist-vue/_chunks/{PkChatbotViewProfile-DuLwYpGZ.js.map → PkChatbotViewProfile-B7yqkE-h.js.map} +1 -1
  53. package/dist-vue/_chunks/PkLazyImg-CDxX9xnq.js +34 -0
  54. package/dist-vue/_chunks/PkLazyImg-CDxX9xnq.js.map +1 -0
  55. package/{dist/_chunks/PkStreamingMarkdown-6Xx9n1ue.js → dist-vue/_chunks/PkStreamingMarkdown-B3OallmC.js} +331 -329
  56. package/dist-vue/_chunks/PkStreamingMarkdown-B3OallmC.js.map +1 -0
  57. package/dist-vue/_chunks/{PkToolShowArtifact-COwSXmF4.js → PkToolShowArtifact-N7we3fbM.js} +2 -2
  58. package/dist-vue/_chunks/{PkToolShowArtifact-COwSXmF4.js.map → PkToolShowArtifact-N7we3fbM.js.map} +1 -1
  59. package/dist-vue/_chunks/{PkToolShowContactForm-DRRtCCxB.js → PkToolShowContactForm-DgVlnLkC.js} +2 -2
  60. package/dist-vue/_chunks/{PkToolShowContactForm-DRRtCCxB.js.map → PkToolShowContactForm-DgVlnLkC.js.map} +1 -1
  61. package/dist-vue/_chunks/{PkToolShowForm-j7Ha-0X3.js → PkToolShowForm-dp0-LYl1.js} +2 -2
  62. package/dist-vue/_chunks/{PkToolShowForm-j7Ha-0X3.js.map → PkToolShowForm-dp0-LYl1.js.map} +1 -1
  63. package/dist-vue/_chunks/PkToolShowImageGallery-CoC46nGb.js +75 -0
  64. package/dist-vue/_chunks/PkToolShowImageGallery-CoC46nGb.js.map +1 -0
  65. package/dist-vue/_chunks/{PkToolShowProductList-DXMN2UuE.js → PkToolShowProductList-C6YoC3sD.js} +25 -23
  66. package/dist-vue/_chunks/PkToolShowProductList-C6YoC3sD.js.map +1 -0
  67. package/dist-vue/_chunks/{PkToolShowSources-BVLw4W5l.js → PkToolShowSources-DJwpImlN.js} +2 -2
  68. package/dist-vue/_chunks/{PkToolShowSources-BVLw4W5l.js.map → PkToolShowSources-DJwpImlN.js.map} +1 -1
  69. package/dist-vue/_chunks/PkToolShowWebPages-B9UeUWfJ.js +79 -0
  70. package/dist-vue/_chunks/PkToolShowWebPages-B9UeUWfJ.js.map +1 -0
  71. package/dist-vue/_chunks/useImageFallback-5vQIO0pC.js +18 -0
  72. package/dist-vue/_chunks/useImageFallback-5vQIO0pC.js.map +1 -0
  73. package/dist-vue/index.js +3952 -3966
  74. package/dist-vue/index.js.map +1 -1
  75. package/dist-vue/locales.js +2 -2
  76. package/dist-vue/packages/components/src/PkLazyImg.d.ts +6 -1
  77. package/dist-vue/packages/components/src/chat/PkAvatar.d.ts +8 -1
  78. package/dist-vue/packages/components/src/chat/PkChatSidebarConversationItem.d.ts +2 -2
  79. package/dist-vue/packages/composable/src/index.d.ts +1 -0
  80. package/dist-vue/packages/composable/src/useImageFallback.d.ts +19 -0
  81. package/dist-vue/packages/models/src/schema/UserOverview.d.ts +96 -0
  82. package/dist-vue/packages/models/src/schema/index.d.ts +1 -0
  83. package/dist-vue/style.css +1 -1
  84. package/package.json +1 -1
  85. package/dist/_chunks/PkStreamingMarkdown-6Xx9n1ue.js.map +0 -1
  86. package/dist/_chunks/PkToolShowImageGallery-WQL3T4Hr.js.map +0 -1
  87. package/dist/_chunks/PkToolShowProductList-BbY4ni7Y.js.map +0 -1
  88. package/dist/_chunks/PkToolShowWebPages-DkMJrWmT.js +0 -76
  89. package/dist/_chunks/PkToolShowWebPages-DkMJrWmT.js.map +0 -1
  90. package/dist-vue/_chunks/PkAvatar-CyIgsndA.js +0 -31
  91. package/dist-vue/_chunks/PkAvatar-CyIgsndA.js.map +0 -1
  92. package/dist-vue/_chunks/PkChatbotAuth-BGn2MDYD.js +0 -347
  93. package/dist-vue/_chunks/PkChatbotAuth-BGn2MDYD.js.map +0 -1
  94. package/dist-vue/_chunks/PkChatbotViewFiles-BipRoidH.js.map +0 -1
  95. package/dist-vue/_chunks/PkStreamingMarkdown-CjBMQ18U.js.map +0 -1
  96. package/dist-vue/_chunks/PkToolShowImageGallery-Bj7YlqbP.js +0 -74
  97. package/dist-vue/_chunks/PkToolShowImageGallery-Bj7YlqbP.js.map +0 -1
  98. package/dist-vue/_chunks/PkToolShowProductList-DXMN2UuE.js.map +0 -1
  99. package/dist-vue/_chunks/PkToolShowWebPages-CsACREOH.js +0 -77
  100. package/dist-vue/_chunks/PkToolShowWebPages-CsACREOH.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { t as e } from "./rolldown-runtime-Dy4uBu1J.js";
2
2
  import { i as t } from "./Document-CSdfKj_B.js";
3
- import { t as n } from "./PkStreamingMarkdown-CjBMQ18U.js";
3
+ import { t as n } from "./PkStreamingMarkdown-B3OallmC.js";
4
4
  import "./src-8QXYVWIx.js";
5
5
  import { t as r } from "./markdown-CCMpGUA7.js";
6
6
  import { t as i } from "./PkUrl-CN1kkZiH.js";
@@ -239,4 +239,4 @@ var w = ["title"], T = {
239
239
  //#endregion
240
240
  export { E as i, X as n, j as r, Z as t };
241
241
 
242
- //# sourceMappingURL=PkToolShowSources-BVLw4W5l.js.map
242
+ //# sourceMappingURL=PkToolShowSources-DJwpImlN.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkToolShowSources-BVLw4W5l.js","names":["$t"],"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</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\" />\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</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\" />\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 -->\n <button\n class=\"flex items-center gap-8 p-8 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\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 <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(group.document.documentId)\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\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 ? 'line-md: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 -->\n <button\n class=\"flex items-center gap-8 p-8 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\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 <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(group.document.documentId)\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\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 ? 'line-md: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-DJwpImlN.js","names":["$t"],"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</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\" />\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</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\" />\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 -->\n <button\n class=\"flex items-center gap-8 p-8 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\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 <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(group.document.documentId)\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\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 ? 'line-md: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 -->\n <button\n class=\"flex items-center gap-8 p-8 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\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 <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(group.document.documentId)\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\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 ? 'line-md: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":";;;;;;;;;;;;;;;;;;;;;EAII,IAAM,IAAQ,GAOR,IAAoB,QAAe;GACrC,IAAI,EAAM,cAAc,MACpB,OAAO;GAEX,IAAM,IACF,EAAM,iBAAiB,EAAM,gBAAgB,IACvC,EAAM,gBACN;GACV,OAAO,KAAK,MAAO,EAAM,aAAa,IAAO,GAAG;EACpD,CAAC;;;;IAID,EAAsE,GAAA;KAA9D,MAAK;KAAkB,OAAM;;IACrC,EAEO,QAAA;KAFD,OAAM;KAAuC,OAAO,EAAA;SACnD,EAAA,CAAA,CAAa,CAAC,EAAA,OAAO,CAAA,GAAA,GAAA,CAAA;IAEhB,EAAA,SAAiB,OAE7B,EAAA,IAAA,EAAA,KAF6B,EAAA,GAA7B,EAEO,QAFP,GAEO,EADA,EAAA,KAAiB,IAAG,MAC3B,CAAA;IACA,EAE2C,GAAA;KADtC,MAAM,EAAA,WAAQ,uBAAA;KACf,OAAM;;;;;;;;;;;;;;;;;;;;;;2BEnBV,EAE2C,GAAA;IADtC,MAAM,EAAA,CAAA,CAAW,CAAC,EAAA,IAAI;IACvB,OAAM;0BACV,EAaM,OAbN,GAaM;IAZF,EAES,UAFT,GAES,EADF,EAAA,IAAI,GAAA,CAAA;IAEC,EAAA,YAAA,EAAA,GAAZ,EAEO,QAFP,GAEO,EADA,EAAA,QAAQ,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;IAGL,EAAA,OAAA,EAAA,GADV,EAKO,QALP,GAKO,CAFH,EAAkD,GAAA;KAA1C,MAAK;KAAU,OAAM;QAC7B,EAAoB,GAAA,EAAZ,KAAK,EAAA,IAAG,GAAA,MAAA,GAAA,CAAA,KAAA,CAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;EEPxB,IAAM,IAAQ,GASR,IAAW,QACN,EAAM,IAKhB,GAEK,IAAU,QAAe,EAAS,MAAM,OAAO,WAAW,CAAC,CAAC,GAE5D,IAAiB,QAAe;GAClC,IAAM,oBAAS,IAAI,IAGjB;GACF,KAAK,IAAM,KAAU,EAAQ,OAAO;IAChC,IAAM,IAAW,EAAO,IAAI,EAAO,UAAU;IAC7C,AAAI,IACA,EAAS,OAAO,KAAK,CAAM,IAE3B,EAAO,IAAI,EAAO,YAAY;KAC1B,UAAU;KACV,QAAQ,CAAC,CAAM;IACnB,CAAC;GAET;GACA,OAAO,CAAC,GAAG,EAAO,OAAO,CAAC;EAC9B,CAAC,GAEK,IAAgB,QACX,KAAK,IAAI,GAAG,EAAQ,MAAM,KAAK,MAAM,EAAE,cAAc,CAAC,GAAG,CAAC,CACpE,GAEK,IAAoB,kBAAS,IAAI,IAAY,CAAC,GAC9C,IAAiB,kBAAS,IAAI,IAAY,CAAC,GAC3C,IAAc,kBAAS,IAAI,IAAoB,CAAC,GAChD,IAAiB,kBAAS,IAAI,IAAY,CAAC,GAE3C,KAAkB,MAAuB;GAC3C,AAAI,EAAkB,IAAI,CAAU,IAChC,EAAkB,OAAO,CAAU,IAEnC,EAAkB,IAAI,CAAU;EAExC,GAEM,KAAe,MAAoB;GACrC,AAAI,EAAe,IAAI,CAAO,IAC1B,EAAe,OAAO,CAAO,IAE7B,EAAe,IAAI,CAAO;EAElC,GAEM,IAAsB,OAAO,MAAuB;GAClD,QAAO,cAAc,QAAQ,CAAC,EAAM,kBAGxC;MAAe,IAAI,EAAO,OAAO;IACjC,IAAI;KACA,IAAM,IAAU,MAAM,EAAM,gBAAgB;MACxC,YAAY,EAAO;MACnB,YAAY,EAAO;KACvB,CAAC;KACD,EAAY,IAAI,EAAO,SAAS,CAAO;IAC3C,UAAU;KACN,EAAe,OAAO,EAAO,OAAO;IACxC;GATiC;EAUrC,GAEM,KAAgB,MAAoB;GACtC,EAAY,OAAO,CAAO;EAC9B,GAEM,IAAW,EAAI,EAAK,GACpB,IAAgB,QACd,EAAS,QACF,EAAe,QAEnB,EAAe,MAAM,MAAM,GAAG,CAAC,CACzC,GACK,IAAU,QAAe,EAAe,MAAM,SAAS,CAAC;;;UAInD,EAAA,MAAQ,UAAA,EAAA,GAAnB,EA+KM,OA/KN,GA+KM;IA9KF,EAMM,OANN,GAMM;KALF,EAAiD,GAAA;MAAzC,MAAK;MAAkB,OAAM;;OAAY,MACjD,EAAGA,EAAAA,GAAE,eAAA,CAAA,IAAoB,KACzB,CAAA;KAAA,EAEO,QAFP,GAAsC,OACjC,EAAG,EAAA,MAAe,MAAM,IAAG,MAChC,CAAA;;IAGJ,EAoJK,MApJL,GAoJK,EAAA,EAAA,EAAA,GAnJD,EAkJK,GAAA,MAAA,EAjJe,EAAA,QAAT,YADX,EAkJK,MAAA;KAhJA,KAAK,EAAM,SAAS;KACrB,OAAM;QAEN,EA0BS,UAAA;KAzBL,OAAM;KACL,UAAK,MAAE,EAAe,EAAM,SAAS,UAAU;QAChD,EAIgD,GAAA;KAH3C,MAAM,EAAM,SAAS;KACrB,MAAM,EAAM,SAAS;KACrB,KAAK,EAAM,SAAS;KACpB,UAAU,EAAM,SAAS;;;;;;QAC9B,EAiBM,OAjBN,GAiBM;KAfQ,EAAM,OAAO,SAAM,KAAA,EAAA,GAD7B,EAIO,QAJP,GAIO,EADA,EAAM,OAAO,MAAM,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;KAE1B,EAGO,QAHP,GAGO,EADA,EAAM,SAAS,QAAQ,GAAA,CAAA;KAE9B,EAMkC,GAAA;MAL7B,MAAuC,EAAkB,IAAI,EAAM,SAAS,UAAU,IAAA,uBAAA;MAKvF,OAAM;;gBAMR,EAAkB,IAAI,EAAM,SAAS,UAAU,KAAA,EAAA,GADzD,EA+GM,OA/GN,GA+GM,CA3GQ,EAAM,SAAS,qBAAA,EAAA,GADzB,EAGmF,GAAA;;KAD9E,UAAU,EAAM,SAAS;KAC1B,OAAM;oDAGV,EAqGM,GAAA,MAAA,EApGc,EAAM,SAAf,YADX,EAqGM,OAAA;KAnGD,KAAK,EAAM;KACZ,OAAM;QACN,EAQS,UAAA;KAPL,OAAM;KACL,UAAK,MAAE,EAAY,EAAM,OAAO;QACjC,EAIoD,GAAA;KAH/C,SAAS,EAAM;KACf,YAAY,EAAM;KAClB,kBAAgB,EAAA;KAChB,UAAU,EAAe,IAAI,EAAM,OAAO;;;;;;gBAKzC,EAAe,IAAI,EAAM,OAAO,KAAA,EAAA,GAD1C,EAqFM,OArFN,GAqFM,CAlFF,EAgBM,OAhBN,GAgBM,CAfF,EAca,GAAA,EAdD,MAAK,SAAQ,GAAA;sBAOC,CAJZ,EAAY,IAAI,EAAM,OAAO,KAAA,EAAA,GADvC,EAKsB,GAAA;;MAHjB,UAAuD,EAAY,IAAI,EAAM,OAAO;MAGrF,OAAM;wCAGV,EAGsB,GAAA;;MADjB,UAAU,EAAM;MACjB,OAAM;;;gBAKlB,EA8DM,OA9DN,GA8DM;KA1DiD,EAAA,mBAA2D,EAAM,cAAU,QAAA,CAAqD,EAAY,IAAI,EAAM,OAAO,KAAA,EAAA,GADhN,EAmBS,UAAA;;MAbL,OAAM;MACL,UAAmD,EAAe,IAAI,EAAM,OAAO;MAGnF,UAAK,MAAE,EAAoB,CAAK;SACjC,EAMsB,GAAA;MALjB,MAAmD,EAAe,IAAI,EAAM,OAAO,IAAA,yBAAA;MAKpF,OAAM;8BAAY,MACtB,EAAGA,EAAAA,GAAE,qBAAA,CAAA,GAAA,CAAA,CAAA,GAAA,GAAA,CAAA,KAGM,EAAY,IAAI,EAAM,OAAO,KAAA,EAAA,GAD5C,EAQS,UAAA;;MANL,OAAM;MACL,UAAK,MAAE,EAAa,EAAM,OAAO;SAClC,EAEsB,GAAA;MADlB,MAAK;MACL,OAAM;WAAY,MACtB,EAAGA,EAAAA,GAAE,uBAAA,CAAA,GAAA,CAAA,CAAA,GAAA,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;KAKC,EAAM,aAAA,EAAA,GADhB,EAUI,KAAA;;MARC,MAAM,EAAM;MACb,QAAO;MACP,KAAI;MACJ,OAAM;SACN,EAEsB,GAAA;MADlB,MAAK;MACL,OAAM;WAAY,MACtB,EAAGA,EAAAA,GAAE,kBAAA,CAAA,GAAA,CAAA,CAAA,GAAA,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;KAK0C,EAAA,cAAsD,EAAM,iBAAY,UAAA,EAAA,GAD3H,EAcS,UAAA;;MATL,OAAK,EAAA,CAAC,gFAA8E,EAAA,WAAA,CACtB,EAAM,UAAA,CAAA,CAAA;MAGnE,UAAK,MAAE,EAAA,WAAW,EAAM,UAAU;SACnC,EAEsB,GAAA;MADlB,MAAK;MACL,OAAM;WAAY,MACtB,EAAGA,EAAAA,GAAE,oBAAA,CAAA,GAAA,CAAA,CAAA,GAAA,IAAA,CAAA,KAAA,EAAA,IAAA,EAAA;;IAWvB,EAAA,SAAA,EAAA,GADV,EAcS,UAAA;;KAZL,OAAM;KACL,SAAK,AAAA,EAAA,QAAA,MAAE,EAAA,QAAQ,CAAI,EAAA;QACpB,EAEsB,GAAA;KADjB,MAAM,EAAA,QAAQ,uBAAA;KACf,OAAM;6BAAY,MACtB,EACI,EAAA,QAA+BA,EAAAA,GAAE,iBAAA,IAA0CA,EAAAA,GAAE,mBAAA,EAAA,OAAuD,EAAA,MAAe,SAAM,EAAA,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA"}
@@ -0,0 +1,79 @@
1
+ import { t as e } from "./rolldown-runtime-Dy4uBu1J.js";
2
+ import "./src-8QXYVWIx.js";
3
+ import { n as t } from "./url-validation-uLMZ5-AU.js";
4
+ import { t as n } from "./PkLazyImg-CDxX9xnq.js";
5
+ import { t as r } from "./PkUrl-CN1kkZiH.js";
6
+ import { VvIcon as i } from "@volverjs/ui-vue/components";
7
+ import { Fragment as a, computed as o, createBlock as s, createCommentVNode as c, createElementBlock as l, createElementVNode as u, createTextVNode as d, createVNode as f, defineComponent as p, openBlock as m, ref as h, renderList as g, toDisplayString as _, unref as v } from "vue";
8
+ //#region ../../packages/components/src/chat/PkToolShowWebPages.vue?vue&type=script&setup=true&lang.ts
9
+ var y = {
10
+ key: 0,
11
+ class: "flex flex-col gap-xs w-full"
12
+ }, b = [
13
+ "href",
14
+ "title",
15
+ "target",
16
+ "rel"
17
+ ], x = { class: "flex flex-col gap-4 min-w-0 flex-1" }, S = { class: "font-bold text-12 text-word-1 truncate" }, C = { class: "text-10 text-word-4 truncate flex items-center gap-4" }, w = {
18
+ key: 0,
19
+ class: "text-12 text-word-3 line-clamp-2"
20
+ }, T = {
21
+ key: 0,
22
+ class: "text-center"
23
+ }, E = /*@__PURE__*/ p({
24
+ __name: "PkToolShowWebPages",
25
+ props: { part: {} },
26
+ setup(e) {
27
+ let p = e, E = o(() => p.part), D = (e) => {
28
+ try {
29
+ let t = new URL(e);
30
+ return t.hash = "", t.toString().replace(/\/$/, "");
31
+ } catch {
32
+ return e.trim().replace(/\/$/, "");
33
+ }
34
+ }, O = o(() => {
35
+ let e = /* @__PURE__ */ new Set(), t = [];
36
+ for (let n of E.value.input?.pages || []) {
37
+ let r = D(n.url);
38
+ e.has(r) || (e.add(r), t.push(n));
39
+ }
40
+ return t;
41
+ }), k = h(!1), A = o(() => k.value ? O.value : O.value.slice(0, 2)), j = o(() => O.value.length > 2);
42
+ return (e, o) => {
43
+ let p = i;
44
+ return A.value?.length ? (m(), l("ul", y, [(m(!0), l(a, null, g(A.value, (i, a) => (m(), l("li", {
45
+ key: a,
46
+ class: "border border-surface-3 rounded-xl overflow-hidden hover:border-surface-4 transition-colors shrink-0"
47
+ }, [u("a", {
48
+ href: i.url,
49
+ title: e.$t("action.open"),
50
+ target: v(t)(i.url) ? void 0 : "_blank",
51
+ rel: v(t)(i.url) ? void 0 : "noopener noreferrer",
52
+ class: "flex gap-8 p-8 hover:bg-surface-1 transition-colors"
53
+ }, [i.image ? (m(), s(n, {
54
+ key: 0,
55
+ src: i.image,
56
+ alt: i.title,
57
+ "fallback-icon": "ri:image-off-line",
58
+ class: "w-48 h-48 object-contain rounded shrink-0 border border-surface-3 bg-surface"
59
+ }, null, 8, ["src", "alt"])) : c("", !0), u("div", x, [
60
+ u("strong", S, _(i.title), 1),
61
+ u("span", C, [f(p, {
62
+ name: "ri:link",
63
+ class: "shrink-0 text-12"
64
+ }), f(r, { url: i.url }, null, 8, ["url"])]),
65
+ i.description ? (m(), l("p", w, _(i.description), 1)) : c("", !0)
66
+ ])], 8, b)]))), 128)), j.value ? (m(), l("li", T, [u("button", {
67
+ class: "text-12 text-word-3 cursor-pointer hover:underline flex items-center gap-4 mx-auto",
68
+ onClick: o[0] ||= (e) => k.value = !k.value
69
+ }, [f(p, {
70
+ name: k.value ? "ri:arrow-up-s-line" : "ri:arrow-down-s-line",
71
+ class: "text-16"
72
+ }, null, 8, ["name"]), d(" " + _(k.value ? e.$t("action.showLess") : e.$t("action.showMore", { count: O.value.length - 2 })), 1)])])) : c("", !0)])) : c("", !0);
73
+ };
74
+ }
75
+ }), D = /* @__PURE__ */ e({ default: () => O }), O = E;
76
+ //#endregion
77
+ export { D as n, O as t };
78
+
79
+ //# sourceMappingURL=PkToolShowWebPages-B9UeUWfJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PkToolShowWebPages-B9UeUWfJ.js","names":["$t"],"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 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 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 v-if=\"pages?.length\" class=\"flex flex-col gap-xs w-full\">\n <li\n v-for=\"(page, index) in pages\"\n :key=\"index\"\n class=\"border border-surface-3 rounded-xl overflow-hidden hover:border-surface-4 transition-colors shrink-0\">\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=\"flex gap-8 p-8 hover:bg-surface-1 transition-colors\">\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 <PkLazyImg\n v-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 shrink-0 border border-surface-3 bg-surface\" />\n <div class=\"flex flex-col gap-4 min-w-0 flex-1\">\n <strong class=\"font-bold text-12 text-word-1 truncate\">\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=\"text-center\">\n <button\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=\"\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 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 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 v-if=\"pages?.length\" class=\"flex flex-col gap-xs w-full\">\n <li\n v-for=\"(page, index) in pages\"\n :key=\"index\"\n class=\"border border-surface-3 rounded-xl overflow-hidden hover:border-surface-4 transition-colors shrink-0\">\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=\"flex gap-8 p-8 hover:bg-surface-1 transition-colors\">\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 <PkLazyImg\n v-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 shrink-0 border border-surface-3 bg-surface\" />\n <div class=\"flex flex-col gap-4 min-w-0 flex-1\">\n <strong class=\"font-bold text-12 text-word-1 truncate\">\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=\"text-center\">\n <button\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=\"\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":";;;;;;;;;;;;;;;;;;;;;;;;;;EAMI,IAAM,IAAQ,GAIR,IAAW,QACA,EAAM,IAWtB,GAKK,KAAgB,MAAgB;GAClC,IAAI;IACA,IAAM,IAAI,IAAI,IAAI,CAAG;IAErB,OADA,EAAE,OAAO,IACF,EAAE,SAAS,CAAC,CAAC,QAAQ,OAAO,EAAE;GACzC,QAAQ;IACJ,OAAO,EAAI,KAAK,CAAC,CAAC,QAAQ,OAAO,EAAE;GACvC;EACJ,GACM,IAAc,QAAe;GAC/B,IAAM,oBAAO,IAAI,IAAY,GACvB,IAA4D,CAAC;GACnE,KAAK,IAAM,KAAQ,EAAS,MAAM,OAAO,SAAS,CAAC,GAAG;IAClD,IAAM,IAAM,EAAa,EAAK,GAAG;IAC7B,EAAK,IAAI,CAAG,MAGhB,EAAK,IAAI,CAAG,GACZ,EAAO,KAAK,CAAI;GACpB;GACA,OAAO;EACX,CAAC,GAEK,IAAW,EAAI,EAAK,GACpB,IAAQ,QACN,EAAS,QACF,EAAY,QAEhB,EAAY,MAAM,MAAM,GAAG,CAAC,CACtC,GACK,IAAU,QAAe,EAAY,MAAM,SAAS,CAAC;;;UAIjD,EAAA,OAAO,UAAA,EAAA,GAAjB,EAsDK,MAtDL,GAsDK,EAAA,EAAA,EAAA,GArDD,EAkCK,GAAA,MAAA,EAjCuB,EAAA,QAAhB,GAAM,YADlB,EAkCK,MAAA;IAhCA,KAAK;IACN,OAAM;OACN,EA6BI,KAAA;IA5BC,MAAM,EAAK;IACX,OAAOA,EAAAA,GAAE,aAAA;IACT,QAAQ,EAAA,CAAA,CAAU,CAAC,EAAK,GAAG,IAAI,KAAA,IAAS;IACxC,KAAK,EAAA,CAAA,CAAU,CAAC,EAAK,GAAG,IAAI,KAAA,IAAS;IACtC,OAAM;OAII,EAAK,SAAA,EAAA,GADf,EAK2F,GAAA;;IAHtF,KAAK,EAAK;IACV,KAAK,EAAK;IACX,iBAAc;IACd,OAAM;6CACV,EAcM,OAdN,GAcM;IAbF,EAES,UAFT,GAES,EADF,EAAK,KAAK,GAAA,CAAA;IAEjB,EAIO,QAJP,GAIO,CAFH,EAAkD,GAAA;KAA1C,MAAK;KAAU,OAAM;QAC7B,EAAyB,GAAA,EAAjB,KAAK,EAAK,IAAA,GAAA,MAAA,GAAA,CAAA,KAAA,CAAA,CAAA,CAAA;IAGZ,EAAK,eAAA,EAAA,GADf,EAII,KAJJ,GAII,EADG,EAAK,WAAW,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;0BAKzB,EAAA,SAAA,EAAA,GAAV,EAiBK,MAjBL,GAiBK,CAhBD,EAeS,UAAA;IAdL,OAAM;IACL,SAAK,AAAA,EAAA,QAAA,MAAE,EAAA,QAAQ,CAAI,EAAA;OACpB,EAIsB,GAAA;IAHjB,MAA+B,EAAA,QAAQ,uBAAA;IAGxC,OAAM;4BAAY,MACtB,EACI,EAAA,QAAmCA,EAAAA,GAAE,iBAAA,IAA8CA,EAAAA,GAAE,mBAAA,EAAA,OAA2D,EAAA,MAAY,SAAM,EAAA,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA"}
@@ -0,0 +1,18 @@
1
+ import { reactive as e } from "vue";
2
+ //#region ../../packages/composable/src/useImageFallback.ts
3
+ function t() {
4
+ let t = e(/* @__PURE__ */ new Set());
5
+ return {
6
+ canShowImage: (e) => !!e && !t.has(e),
7
+ onImageError: (e) => {
8
+ e && t.add(e);
9
+ },
10
+ resetImageError: (e) => {
11
+ e && t.delete(e);
12
+ }
13
+ };
14
+ }
15
+ //#endregion
16
+ export { t };
17
+
18
+ //# sourceMappingURL=useImageFallback-5vQIO0pC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useImageFallback-5vQIO0pC.js","names":[],"sources":["../../../../packages/composable/src/useImageFallback.ts"],"sourcesContent":["import { reactive } from 'vue'\n\n/**\n * Tracks image sources that failed to load, so a template can swap in a\n * placeholder (icon, initials, next-best image) instead of leaving the\n * browser's broken-image glyph.\n *\n * Any `<img>` pointing at a url we don't control needs this: OAuth avatar urls\n * expire, uploaded logos get deleted, og:images and favicons rot, and preview\n * blobs may not be generated yet.\n *\n * ```vue\n * <img v-if=\"canShowImage(page.image)\" :src=\"page.image\" @error=\"onImageError(page.image)\" />\n * <VvIcon v-else name=\"ri:image-off-line\" />\n * ```\n */\nexport function useImageFallback() {\n const failed = reactive(new Set<string>())\n\n /** True when there is a source and it has not failed yet. */\n const canShowImage = (src?: string | null): boolean =>\n !!src && !failed.has(src)\n\n const onImageError = (src?: string | null): void => {\n if (src) {\n failed.add(src)\n }\n }\n\n /** Lets a retry (new upload, refreshed sas url) show the image again. */\n const resetImageError = (src?: string | null): void => {\n if (src) {\n failed.delete(src)\n }\n }\n\n return { canShowImage, onImageError, resetImageError }\n}\n"],"mappings":";;AAgBA,SAAgB,IAAmB;CAC/B,IAAM,IAAS,kBAAS,IAAI,IAAY,CAAC;CAmBzC,OAAO;EAAE,eAhBa,MAClB,CAAC,CAAC,KAAO,CAAC,EAAO,IAAI,CAAG;EAeL,eAbD,MAA8B;GAChD,AAAI,KACA,EAAO,IAAI,CAAG;EAEtB;EASqC,kBANZ,MAA8B;GACnD,AAAI,KACA,EAAO,OAAO,CAAG;EAEzB;CAEqD;AACzD"}