@8wave/ai-elements 0.117.0-beta.14 → 0.117.0-beta.24

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 (133) hide show
  1. package/dist/_chunks/Agent-CFkAjuTy.js.map +1 -1
  2. package/dist/_chunks/{PkChatbotFilePreview-C-02Ovgi.js → PkChatbotFilePreview-BCWqFkY8.js} +2 -2
  3. package/dist/_chunks/{PkChatbotFilePreview-C-02Ovgi.js.map → PkChatbotFilePreview-BCWqFkY8.js.map} +1 -1
  4. package/dist/_chunks/{PkHeldImage-B4gZSfa6.js → PkHeldImage-CEFdfFts.js} +2 -2
  5. package/dist/_chunks/{PkHeldImage-B4gZSfa6.js.map → PkHeldImage-CEFdfFts.js.map} +1 -1
  6. package/dist/_chunks/{PkLazyImg-DT3U67q-.js → PkLazyImg-BfqUTZXg.js} +2 -2
  7. package/dist/_chunks/{PkLazyImg-DT3U67q-.js.map → PkLazyImg-BfqUTZXg.js.map} +1 -1
  8. package/dist/_chunks/{PkStreamingMarkdown-BoVR7gtl.js → PkStreamingMarkdown-DhViwQEZ.js} +2 -2
  9. package/dist/_chunks/PkStreamingMarkdown-DhViwQEZ.js.map +1 -0
  10. package/dist/_chunks/{PkToolEditSpreadsheet-BlfsM7Qp.js → PkToolEditSpreadsheet-CQ6wLZyD.js} +2 -2
  11. package/dist/_chunks/{PkToolEditSpreadsheet-BlfsM7Qp.js.map → PkToolEditSpreadsheet-CQ6wLZyD.js.map} +1 -1
  12. package/dist/_chunks/{PkToolRequestGeolocation-DdQ91rpB.js → PkToolRequestGeolocation-B5wbvcC1.js} +2 -2
  13. package/dist/_chunks/{PkToolRequestGeolocation-DdQ91rpB.js.map → PkToolRequestGeolocation-B5wbvcC1.js.map} +1 -1
  14. package/dist/_chunks/{PkToolRequestOAuthConnection-yuTNMEFj.js → PkToolRequestOAuthConnection-Dma-POc7.js} +2 -2
  15. package/dist/_chunks/{PkToolRequestOAuthConnection-yuTNMEFj.js.map → PkToolRequestOAuthConnection-Dma-POc7.js.map} +1 -1
  16. package/dist/_chunks/{PkToolShowArtifact-DaTlDsDo.js → PkToolShowArtifact-DoLn-Tb8.js} +3 -3
  17. package/dist/_chunks/{PkToolShowArtifact-DaTlDsDo.js.map → PkToolShowArtifact-DoLn-Tb8.js.map} +1 -1
  18. package/dist/_chunks/{PkToolShowCalendarEvent-DmHdErMH.js → PkToolShowCalendarEvent-CqGz7T6h.js} +2 -2
  19. package/dist/_chunks/{PkToolShowCalendarEvent-DmHdErMH.js.map → PkToolShowCalendarEvent-CqGz7T6h.js.map} +1 -1
  20. package/dist/_chunks/{PkToolShowComparison-DcXSOyMw.js → PkToolShowComparison-Doa9DkEp.js} +2 -2
  21. package/dist/_chunks/{PkToolShowComparison-DcXSOyMw.js.map → PkToolShowComparison-Doa9DkEp.js.map} +1 -1
  22. package/dist/_chunks/{PkToolShowContactForm-Dxh6bEZt.js → PkToolShowContactForm-B6_h5oSO.js} +4 -4
  23. package/dist/_chunks/{PkToolShowContactForm-Dxh6bEZt.js.map → PkToolShowContactForm-B6_h5oSO.js.map} +1 -1
  24. package/dist/_chunks/{PkToolShowDiagram-B_kuxtaZ.js → PkToolShowDiagram-R91cRscb.js} +2 -2
  25. package/dist/_chunks/{PkToolShowDiagram-B_kuxtaZ.js.map → PkToolShowDiagram-R91cRscb.js.map} +1 -1
  26. package/dist/_chunks/{PkToolShowEmail-AWhVo3l3.js → PkToolShowEmail-D-ZrJpBo.js} +2 -2
  27. package/dist/_chunks/{PkToolShowEmail-AWhVo3l3.js.map → PkToolShowEmail-D-ZrJpBo.js.map} +1 -1
  28. package/dist/_chunks/{PkToolShowForm-D1VzwMmq.js → PkToolShowForm-Bw98qrZ_.js} +3 -3
  29. package/dist/_chunks/{PkToolShowForm-D1VzwMmq.js.map → PkToolShowForm-Bw98qrZ_.js.map} +1 -1
  30. package/dist/_chunks/{PkToolShowImageGallery-Cs8FxK9V.js → PkToolShowImageGallery-o4OXj4kF.js} +3 -3
  31. package/dist/_chunks/{PkToolShowImageGallery-Cs8FxK9V.js.map → PkToolShowImageGallery-o4OXj4kF.js.map} +1 -1
  32. package/dist/_chunks/{PkToolShowLocation-BcqMFhXM.js → PkToolShowLocation-DXpfaKpJ.js} +2 -2
  33. package/dist/_chunks/{PkToolShowLocation-BcqMFhXM.js.map → PkToolShowLocation-DXpfaKpJ.js.map} +1 -1
  34. package/dist/_chunks/{PkToolShowMessage-DlckklB3.js → PkToolShowMessage-BwAo9Qx2.js} +2 -2
  35. package/dist/_chunks/{PkToolShowMessage-DlckklB3.js.map → PkToolShowMessage-BwAo9Qx2.js.map} +1 -1
  36. package/dist/_chunks/{PkToolShowProductList-B4fxqFaf.js → PkToolShowProductList-BKydg1YK.js} +4 -4
  37. package/dist/_chunks/{PkToolShowProductList-B4fxqFaf.js.map → PkToolShowProductList-BKydg1YK.js.map} +1 -1
  38. package/dist/_chunks/{PkToolShowQrCode-CFueYo4P.js → PkToolShowQrCode-CbBvVSmj.js} +2 -2
  39. package/dist/_chunks/{PkToolShowQrCode-CFueYo4P.js.map → PkToolShowQrCode-CbBvVSmj.js.map} +1 -1
  40. package/dist/_chunks/{PkToolShowSources-DNiiBz9b.js → PkToolShowSources-gleJ91hl.js} +3 -3
  41. package/dist/_chunks/{PkToolShowSources-DNiiBz9b.js.map → PkToolShowSources-gleJ91hl.js.map} +1 -1
  42. package/dist/_chunks/{PkToolShowSuggestedReply-Bn1w6HdI.js → PkToolShowSuggestedReply-fsG-mX-c.js} +2 -2
  43. package/dist/_chunks/{PkToolShowSuggestedReply-Bn1w6HdI.js.map → PkToolShowSuggestedReply-fsG-mX-c.js.map} +1 -1
  44. package/dist/_chunks/{PkToolShowVideo-Be5bKhUf.js → PkToolShowVideo-CHbk5VXp.js} +3 -3
  45. package/dist/_chunks/{PkToolShowVideo-Be5bKhUf.js.map → PkToolShowVideo-CHbk5VXp.js.map} +1 -1
  46. package/dist/_chunks/{PkToolShowWeather-B2rcVmEJ.js → PkToolShowWeather-rTcxGcWl.js} +2 -2
  47. package/dist/_chunks/{PkToolShowWeather-B2rcVmEJ.js.map → PkToolShowWeather-rTcxGcWl.js.map} +1 -1
  48. package/dist/_chunks/{PkToolShowWebPages-Bs251x0h.js → PkToolShowWebPages-BzH1_SjA.js} +4 -4
  49. package/dist/_chunks/{PkToolShowWebPages-Bs251x0h.js.map → PkToolShowWebPages-BzH1_SjA.js.map} +1 -1
  50. package/dist/_chunks/{VvCheckbox.es-DKT8ys4U.js → VvCheckbox.es-CGVgFeBD.js} +2 -2
  51. package/dist/_chunks/{VvCheckbox.es-DKT8ys4U.js.map → VvCheckbox.es-CGVgFeBD.js.map} +1 -1
  52. package/dist/_chunks/{VvCheckboxGroup.es-BR2Zrit_.js → VvCheckboxGroup.es-CWqFL8RR.js} +2 -2
  53. package/dist/_chunks/{VvCheckboxGroup.es-BR2Zrit_.js.map → VvCheckboxGroup.es-CWqFL8RR.js.map} +1 -1
  54. package/dist/_chunks/{VvCombobox.es-DWJtN8ha.js → VvCombobox.es-kUT6EeIn.js} +2 -2
  55. package/dist/_chunks/{VvCombobox.es-DWJtN8ha.js.map → VvCombobox.es-kUT6EeIn.js.map} +1 -1
  56. package/dist/_chunks/{VvInputText.es-BH6ohnZF.js → VvInputText.es-CJfMyJ0G.js} +2 -2
  57. package/dist/_chunks/{VvInputText.es-BH6ohnZF.js.map → VvInputText.es-CJfMyJ0G.js.map} +1 -1
  58. package/dist/_chunks/{VvRadio.es-L9HmgYh4.js → VvRadio.es-BkIuydtE.js} +2 -2
  59. package/dist/_chunks/{VvRadio.es-L9HmgYh4.js.map → VvRadio.es-BkIuydtE.js.map} +1 -1
  60. package/dist/_chunks/{VvRadioGroup.es-Dyip2x3c.js → VvRadioGroup.es-jFTh_3Gw.js} +2 -2
  61. package/dist/_chunks/{VvRadioGroup.es-Dyip2x3c.js.map → VvRadioGroup.es-jFTh_3Gw.js.map} +1 -1
  62. package/dist/_chunks/{VvSelect.es-B8GZOw4X.js → VvSelect.es-Cy_TK4HS.js} +2 -2
  63. package/dist/_chunks/{VvSelect.es-B8GZOw4X.js.map → VvSelect.es-Cy_TK4HS.js.map} +1 -1
  64. package/dist/_chunks/{VvTextarea.es-7E1SuavW.js → VvTextarea.es-aCy57_J_.js} +2 -2
  65. package/dist/_chunks/{VvTextarea.es-7E1SuavW.js.map → VvTextarea.es-aCy57_J_.js.map} +1 -1
  66. package/dist/_chunks/{index.es-a98v5vZY.js → index.es-7xRVenur.js} +10 -10
  67. package/dist/_chunks/{index.es-a98v5vZY.js.map → index.es-7xRVenur.js.map} +1 -1
  68. package/dist/_chunks/{index.es-D_E1Gj_U.js → index.es-dzrOH5E9.js} +2 -2
  69. package/dist/_chunks/{index.es-D_E1Gj_U.js.map → index.es-dzrOH5E9.js.map} +1 -1
  70. package/dist/ai-elements.es.js +623 -593
  71. package/dist/ai-elements.es.js.map +1 -1
  72. package/dist-vue/PkChatbot.js +1 -1
  73. package/dist-vue/PkChatbotAudioTranscript.js +1 -1
  74. package/dist-vue/PkChatbotAuth.js +1 -1
  75. package/dist-vue/PkChatbotEmptyState.js +1 -1
  76. package/dist-vue/PkChatbotFeedbackForm.js +1 -1
  77. package/dist-vue/PkChatbotInput.js +1 -1
  78. package/dist-vue/PkChatbotMessages.js +1 -1
  79. package/dist-vue/PkChatbotSteps.js +1 -1
  80. package/dist-vue/PkChatbotViewChat.js +1 -1
  81. package/dist-vue/_chunks/Media-C6Xsb1Lm.js.map +1 -1
  82. package/dist-vue/_chunks/{PkChatbot-CSMWib9A.js → PkChatbot-CqjOLnzZ.js} +3 -3
  83. package/dist-vue/_chunks/{PkChatbot-CSMWib9A.js.map → PkChatbot-CqjOLnzZ.js.map} +1 -1
  84. package/dist-vue/_chunks/PkChatbotAudioTranscript-BUxBHanA.js +80 -0
  85. package/dist-vue/_chunks/PkChatbotAudioTranscript-BUxBHanA.js.map +1 -0
  86. package/dist-vue/_chunks/{PkChatbotAuth-C0qfDOGh.js → PkChatbotAuth-Byn_3wdb.js} +2 -2
  87. package/dist-vue/_chunks/{PkChatbotAuth-C0qfDOGh.js.map → PkChatbotAuth-Byn_3wdb.js.map} +1 -1
  88. package/dist-vue/_chunks/{PkChatbotEmptyState-DjmF3dcI.js → PkChatbotEmptyState-eS8safl9.js} +2 -2
  89. package/dist-vue/_chunks/{PkChatbotEmptyState-DjmF3dcI.js.map → PkChatbotEmptyState-eS8safl9.js.map} +1 -1
  90. package/dist-vue/_chunks/{PkChatbotFeedbackForm-DrPKjh-0.js → PkChatbotFeedbackForm-Cqbz2qey.js} +2 -2
  91. package/dist-vue/_chunks/{PkChatbotFeedbackForm-DrPKjh-0.js.map → PkChatbotFeedbackForm-Cqbz2qey.js.map} +1 -1
  92. package/dist-vue/_chunks/{PkChatbotInput-B8hKYdD9.js → PkChatbotInput-CMAklEDF.js} +2 -2
  93. package/dist-vue/_chunks/{PkChatbotInput-B8hKYdD9.js.map → PkChatbotInput-CMAklEDF.js.map} +1 -1
  94. package/dist-vue/_chunks/{PkChatbotMessages-DJmRPFB8.js → PkChatbotMessages-DHcfZNYn.js} +287 -287
  95. package/dist-vue/_chunks/PkChatbotMessages-DHcfZNYn.js.map +1 -0
  96. package/dist-vue/_chunks/{PkChatbotSteps-qyUOh42z.js → PkChatbotSteps-CLjlBbYH.js} +2 -2
  97. package/dist-vue/_chunks/{PkChatbotSteps-qyUOh42z.js.map → PkChatbotSteps-CLjlBbYH.js.map} +1 -1
  98. package/dist-vue/_chunks/{PkChatbotViewChat-C7eA7hef.js → PkChatbotViewChat-CmZ1E6ip.js} +5 -5
  99. package/dist-vue/_chunks/{PkChatbotViewChat-C7eA7hef.js.map → PkChatbotViewChat-CmZ1E6ip.js.map} +1 -1
  100. package/dist-vue/_chunks/{PkStreamingMarkdown-NWO3WTw9.js → PkStreamingMarkdown-DdA0SZ4P.js} +2 -2
  101. package/dist-vue/_chunks/PkStreamingMarkdown-DdA0SZ4P.js.map +1 -0
  102. package/dist-vue/_chunks/{PkToolShowArtifact-Dr0NHXDG.js → PkToolShowArtifact-DzCxmsLc.js} +2 -2
  103. package/dist-vue/_chunks/{PkToolShowArtifact-Dr0NHXDG.js.map → PkToolShowArtifact-DzCxmsLc.js.map} +1 -1
  104. package/dist-vue/_chunks/{PkToolShowContactForm-BDm1w3UK.js → PkToolShowContactForm-BNqRlhkB.js} +3 -3
  105. package/dist-vue/_chunks/{PkToolShowContactForm-BDm1w3UK.js.map → PkToolShowContactForm-BNqRlhkB.js.map} +1 -1
  106. package/dist-vue/_chunks/{PkToolShowDiagram-BKz7OPHC.js → PkToolShowDiagram-CtLg5U0A.js} +2 -2
  107. package/dist-vue/_chunks/{PkToolShowDiagram-BKz7OPHC.js.map → PkToolShowDiagram-CtLg5U0A.js.map} +1 -1
  108. package/dist-vue/_chunks/{PkToolShowForm-cKgWwvPD.js → PkToolShowForm-CMKKzfZU.js} +2 -2
  109. package/dist-vue/_chunks/{PkToolShowForm-cKgWwvPD.js.map → PkToolShowForm-CMKKzfZU.js.map} +1 -1
  110. package/dist-vue/_chunks/{PkToolShowImageGallery-DsAQhyOq.js → PkToolShowImageGallery-C-LVA9Eg.js} +2 -2
  111. package/dist-vue/_chunks/{PkToolShowImageGallery-DsAQhyOq.js.map → PkToolShowImageGallery-C-LVA9Eg.js.map} +1 -1
  112. package/dist-vue/_chunks/{PkToolShowProductList-OwGaM5qj.js → PkToolShowProductList-DU8Am4mJ.js} +2 -2
  113. package/dist-vue/_chunks/{PkToolShowProductList-OwGaM5qj.js.map → PkToolShowProductList-DU8Am4mJ.js.map} +1 -1
  114. package/dist-vue/_chunks/{PkToolShowSources-BVOqH-ZV.js → PkToolShowSources-vmHitTkY.js} +2 -2
  115. package/dist-vue/_chunks/{PkToolShowSources-BVOqH-ZV.js.map → PkToolShowSources-vmHitTkY.js.map} +1 -1
  116. package/dist-vue/_chunks/{PkToolShowVideo-BAqSAfdq.js → PkToolShowVideo-BcWVHiPb.js} +2 -2
  117. package/dist-vue/_chunks/{PkToolShowVideo-BAqSAfdq.js.map → PkToolShowVideo-BcWVHiPb.js.map} +1 -1
  118. package/dist-vue/_chunks/{PkToolShowWebPages-BeMJqIkW.js → PkToolShowWebPages-DemGs1ZO.js} +2 -2
  119. package/dist-vue/_chunks/{PkToolShowWebPages-BeMJqIkW.js.map → PkToolShowWebPages-DemGs1ZO.js.map} +1 -1
  120. package/dist-vue/_chunks/{index.es-C_tAUcFb.js → index.es-Tp61H9fG.js} +2 -2
  121. package/dist-vue/_chunks/{index.es-C_tAUcFb.js.map → index.es-Tp61H9fG.js.map} +1 -1
  122. package/dist-vue/composables.js +2 -2
  123. package/dist-vue/composables.js.map +1 -1
  124. package/dist-vue/index.js +16 -16
  125. package/dist-vue/locales.js +2 -2
  126. package/dist-vue/packages/models/src/schema/Media.d.ts +8 -0
  127. package/dist-vue/style.css +1 -1
  128. package/package.json +1 -1
  129. package/dist/_chunks/PkStreamingMarkdown-BoVR7gtl.js.map +0 -1
  130. package/dist-vue/_chunks/PkChatbotAudioTranscript-B-cSKvWR.js +0 -52
  131. package/dist-vue/_chunks/PkChatbotAudioTranscript-B-cSKvWR.js.map +0 -1
  132. package/dist-vue/_chunks/PkChatbotMessages-DJmRPFB8.js.map +0 -1
  133. package/dist-vue/_chunks/PkStreamingMarkdown-NWO3WTw9.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PkToolShowVideo-Be5bKhUf.js","names":["$t","__unplugin_components_0","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_toDisplayString","_Fragment","_renderList","_createCommentVNode","PkHeldImage","_unref","_sfc_main","defineComponent","openBlock","init_runtime_core_esm_bundler","toDisplayString","createCommentVNode","unref","Fragment","createBaseVNode","init_reactivity_esm_bundler","computed","createElementBlock","createVNode","init_shared_esm_bundler","renderList","Ae","useChatImageGate","PkHeldImage_default","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","props","__props","PLAYER_HOSTS","imageGate","videos","source","video","gated","src","release","urls","_component_VvIcon","_ctx"],"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 <div class=\"p-sm flex flex-col gap-16\">\n <div v-for=\"video in videos\" :key=\"video.url\" class=\"flex flex-col\">\n <strong v-if=\"video.title\" class=\"font-bold text-14 mb-sm\">\n {{ video.title }}\n </strong>\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 rounded-xl 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 rounded-xl bg-word-1\"\n style=\"aspect-ratio: 16 / 9; max-height: 480px\">\n <a\n :href=\"video.url\"\n target=\"_blank\"\n 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 rounded-xl bg-word-1\"\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 <span\n v-if=\"video.description\"\n class=\"text-12 text-word-3 mt-sm\">\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 <div class=\"p-sm flex flex-col gap-16\">\n <div v-for=\"video in videos\" :key=\"video.url\" class=\"flex flex-col\">\n <strong v-if=\"video.title\" class=\"font-bold text-14 mb-sm\">\n {{ video.title }}\n </strong>\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 rounded-xl 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 rounded-xl bg-word-1\"\n style=\"aspect-ratio: 16 / 9; max-height: 480px\">\n <a\n :href=\"video.url\"\n target=\"_blank\"\n 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 rounded-xl bg-word-1\"\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 <span\n v-if=\"video.description\"\n class=\"text-12 text-word-3 mt-sm\">\n {{ video.description }}\n </span>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PkToolShowVideo-CHbk5VXp.js","names":["$t","__unplugin_components_0","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_toDisplayString","_Fragment","_renderList","_createCommentVNode","PkHeldImage","_unref","_sfc_main","defineComponent","openBlock","init_runtime_core_esm_bundler","toDisplayString","createCommentVNode","unref","Fragment","createBaseVNode","init_reactivity_esm_bundler","computed","createElementBlock","createVNode","init_shared_esm_bundler","renderList","Ae","useChatImageGate","PkHeldImage_default","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","props","__props","PLAYER_HOSTS","imageGate","videos","source","video","gated","src","release","urls","_component_VvIcon","_ctx"],"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 <div class=\"p-sm flex flex-col gap-16\">\n <div v-for=\"video in videos\" :key=\"video.url\" class=\"flex flex-col\">\n <strong v-if=\"video.title\" class=\"font-bold text-14 mb-sm\">\n {{ video.title }}\n </strong>\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 rounded-xl 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 rounded-xl bg-word-1\"\n style=\"aspect-ratio: 16 / 9; max-height: 480px\">\n <a\n :href=\"video.url\"\n target=\"_blank\"\n 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 rounded-xl bg-word-1\"\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 <span\n v-if=\"video.description\"\n class=\"text-12 text-word-3 mt-sm\">\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 <div class=\"p-sm flex flex-col gap-16\">\n <div v-for=\"video in videos\" :key=\"video.url\" class=\"flex flex-col\">\n <strong v-if=\"video.title\" class=\"font-bold text-14 mb-sm\">\n {{ video.title }}\n </strong>\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 rounded-xl 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 rounded-xl bg-word-1\"\n style=\"aspect-ratio: 16 / 9; max-height: 480px\">\n <a\n :href=\"video.url\"\n target=\"_blank\"\n 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 rounded-xl bg-word-1\"\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 <span\n v-if=\"video.description\"\n class=\"text-12 text-word-3 mt-sm\">\n {{ video.description }}\n </span>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { $ as e, C as t, L as n, O as r, St as i, Tt as a, X as o, _ as s, b as c, bt as l, d as u, g as ee, h as d, it as f, m as p, pt as m, r as h, t as g, v as _, x as v, xt as y, z as b } from "./vue.runtime.esm-bundler-B-NVlZCY.js";
2
2
  import { r as te } from "./vue-i18n-BAndfHYX.js";
3
3
  import "./src-BBUM0qsk.js";
4
- import { n as ne, t as re } from "./index.es-D_E1Gj_U.js";
4
+ import { n as ne, t as re } from "./index.es-dzrOH5E9.js";
5
5
  //#region ../../packages/utils/src/weather.ts
6
6
  function ie(e) {
7
7
  return e === void 0 ? "unknown" : e === 0 ? "clear" : e === 1 ? "mainlyClear" : e === 2 ? "partlyCloudy" : e === 3 ? "overcast" : e === 45 || e === 48 ? "fog" : e >= 51 && e <= 55 ? "drizzle" : e === 56 || e === 57 ? "freezingDrizzle" : e >= 61 && e <= 65 ? "rain" : e === 66 || e === 67 ? "freezingRain" : e >= 71 && e <= 77 ? "snow" : e >= 80 && e <= 82 ? "rainShowers" : e === 85 || e === 86 ? "snowShowers" : e === 95 ? "thunderstorm" : e === 96 || e === 99 ? "thunderstormHail" : "unknown";
@@ -277,4 +277,4 @@ var S = {
277
277
  //#endregion
278
278
  export { W as default };
279
279
 
280
- //# sourceMappingURL=PkToolShowWeather-B2rcVmEJ.js.map
280
+ //# sourceMappingURL=PkToolShowWeather-rTcxGcWl.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkToolShowWeather-B2rcVmEJ.js","names":["__unplugin_components_0","__unplugin_components_1","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_toDisplayString","_unref","_createTextVNode","_createCommentVNode","_Transition","_withCtx","_Fragment","_renderList","_createBlock","_normalizeClass","_sfc_main","withCtx","defineComponent","openBlock","init_runtime_core_esm_bundler","normalizeClass","toDisplayString","watch","createCommentVNode","createTextVNode","unref","Fragment","createBlock","createBaseVNode","init_reactivity_esm_bundler","computed","ref","init_runtime_dom_esm_bundler","Transition","createElementBlock","createVNode","init_shared_esm_bundler","renderList","useI18n","Ar","Ae","wmoCategory","code","aqiBand","aqi","_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","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_hoisted_29","_hoisted_30","_hoisted_31","_hoisted_32","_hoisted_33","_hoisted_34","PkToolShowWeather_default","props","__props","$t","$d","$n","toolPart","data","selectedDayIndex","dailyOpen","index","selectedDay","filteredHourly","dayDate","h","safeDate","direct","value","withT","timeOnly","today","noon","WMO_ICON","wmoToIcon","formatDay","parsed","isoDate","total","fallback","headerDate","AQI_PRESENTATION","aqiInfo","cssClass","labelKey","windDirectionLabel","deg","_component_VvIcon","_component_VvAccordion","hour","_cache","$event","day"],"sources":["../../../../packages/utils/src/weather.ts","../../../../packages/components/src/chat/PkToolShowWeather.vue","../../../../packages/components/src/chat/PkToolShowWeather.vue"],"sourcesContent":["/**\n * Framework-agnostic weather presentation primitives. They classify raw\n * open-meteo values into canonical categories; each consumer (web widget,\n * platform card renderer) maps a category to its own representation (an\n * Iconify name, an emoji, a CSS class, an i18n key). Keeping the classification\n * here means the WMO code bands and AQI thresholds live in exactly one place.\n */\n\n/** Canonical WMO weather-code category (resolution matches the web widget). */\nexport type WmoCategory =\n | 'clear'\n | 'mainlyClear'\n | 'partlyCloudy'\n | 'overcast'\n | 'fog'\n | 'drizzle'\n | 'freezingDrizzle'\n | 'rain'\n | 'freezingRain'\n | 'snow'\n | 'rainShowers'\n | 'snowShowers'\n | 'thunderstorm'\n | 'thunderstormHail'\n | 'unknown'\n\n/** Map a WMO weather interpretation code to a canonical category. */\nexport function wmoCategory(code: number | undefined): WmoCategory {\n if (code === undefined) {\n return 'unknown'\n }\n if (code === 0) {\n return 'clear'\n }\n if (code === 1) {\n return 'mainlyClear'\n }\n if (code === 2) {\n return 'partlyCloudy'\n }\n if (code === 3) {\n return 'overcast'\n }\n if (code === 45 || code === 48) {\n return 'fog'\n }\n if (code >= 51 && code <= 55) {\n return 'drizzle'\n }\n if (code === 56 || code === 57) {\n return 'freezingDrizzle'\n }\n if (code >= 61 && code <= 65) {\n return 'rain'\n }\n if (code === 66 || code === 67) {\n return 'freezingRain'\n }\n if (code >= 71 && code <= 77) {\n return 'snow'\n }\n if (code >= 80 && code <= 82) {\n return 'rainShowers'\n }\n if (code === 85 || code === 86) {\n return 'snowShowers'\n }\n if (code === 95) {\n return 'thunderstorm'\n }\n if (code === 96 || code === 99) {\n return 'thunderstormHail'\n }\n return 'unknown'\n}\n\n/** Emoji per category (used by the language-neutral platform card renderer). */\nconst WMO_EMOJI: Record<WmoCategory, string> = {\n clear: '☀️',\n mainlyClear: '⛅',\n partlyCloudy: '⛅',\n overcast: '⛅',\n fog: '🌫️',\n drizzle: '🌦️',\n freezingDrizzle: '🌦️',\n rain: '🌧️',\n freezingRain: '🌧️',\n snow: '❄️',\n rainShowers: '🌧️',\n snowShowers: '🌨️',\n thunderstorm: '⛈️',\n thunderstormHail: '⛈️',\n unknown: '🌡️',\n}\n\n/** WMO code → emoji (convenience for non-UI consumers). */\nexport function wmoEmoji(code: number | undefined): string {\n return WMO_EMOJI[wmoCategory(code)]\n}\n\n/** Canonical European-AQI band, ordered best → worst. */\nexport type AqiBandKey =\n 'good' | 'fair' | 'moderate' | 'poor' | 'veryPoor' | 'hazardous'\n\n/** Classify a European AQI value into a band (index + canonical key). */\nexport function aqiBand(aqi: number): { index: number; key: AqiBandKey } {\n if (aqi <= 20) {\n return { index: 0, key: 'good' }\n }\n if (aqi <= 40) {\n return { index: 1, key: 'fair' }\n }\n if (aqi <= 60) {\n return { index: 2, key: 'moderate' }\n }\n if (aqi <= 80) {\n return { index: 3, key: 'poor' }\n }\n if (aqi <= 100) {\n return { index: 4, key: 'veryPoor' }\n }\n return { index: 5, key: 'hazardous' }\n}\n\n/** Colored dot per AQI band index (used by the platform card renderer). */\nconst AQI_DOTS = ['🟢', '🟡', '🟠', '🔴', '🟣', '⚫']\n\n/** AQI value → colored dot (convenience for non-UI consumers). */\nexport function aqiDot(aqi: number): string {\n return AQI_DOTS[aqiBand(aqi).index]\n}\n","<script setup lang=\"ts\">\n import { computed, ref, watch } from 'vue'\n import { useI18n } from 'vue-i18n'\n import type { WeatherData } from 'models'\n import {\n aqiBand,\n wmoCategory,\n type AqiBandKey,\n type WmoCategory,\n } from 'utils'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const { t: $t, d: $d, n: $n } = useI18n({ useScope: 'global' })\n\n const toolPart = computed(() => {\n return props.part as { input?: WeatherData; output?: WeatherData }\n })\n\n const data = computed(\n () => toolPart.value.output ?? toolPart.value.input ?? null,\n )\n\n const selectedDayIndex = ref(0)\n\n const dailyOpen = ref(!data.value?.current)\n\n watch(selectedDayIndex, (index) => {\n if (index > 0) {\n dailyOpen.value = true\n }\n })\n\n const selectedDay = computed(\n () => data.value?.daily?.[selectedDayIndex.value],\n )\n\n /** Hourly entries filtered to the selected day's date */\n const filteredHourly = computed(() => {\n if (!data.value?.hourly?.length || !selectedDay.value) {\n return data.value?.hourly ?? []\n }\n const dayDate = selectedDay.value.date\n return data.value.hourly.filter((h) => h.time.startsWith(dayDate))\n })\n\n /**\n * Parses a date/time string in various formats the LLM might produce.\n * Tries ISO, space-separated, time-only, and common human-readable formats.\n */\n function safeDate(value: string): Date {\n // 1. Try native parsing (works for ISO 8601 and many standard formats)\n const direct = new Date(value)\n if (!Number.isNaN(direct.getTime())) {\n return direct\n }\n\n // 2. Replace space separator → T (\"2024-04-15 14:00\" → \"2024-04-15T14:00\")\n if (/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}/.test(value)) {\n const withT = new Date(value.replace(' ', 'T'))\n if (!Number.isNaN(withT.getTime())) {\n return withT\n }\n }\n\n // 3. Time-only (\"14:00\" or \"14:00:00\") → today at that hour\n const timeOnly = value.match(/^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/)\n if (timeOnly) {\n const today = new Date()\n today.setHours(\n Number(timeOnly[1]),\n Number(timeOnly[2]),\n Number(timeOnly[3] ?? 0),\n 0,\n )\n return today\n }\n\n // 4. Date-only (\"2024-04-15\") → noon to avoid timezone edge\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n const noon = new Date(value + 'T12:00:00')\n if (!Number.isNaN(noon.getTime())) {\n return noon\n }\n }\n\n // Last resort: return an obviously invalid sentinel so callers can handle it\n return new Date(NaN)\n }\n\n /** Iconify `wi:` icon per canonical WMO category. */\n const WMO_ICON: Record<WmoCategory, string> = {\n clear: 'wi:day-sunny',\n mainlyClear: 'wi:day-sunny-overcast',\n partlyCloudy: 'wi:day-cloudy',\n overcast: 'wi:cloudy',\n fog: 'wi:fog',\n drizzle: 'wi:sprinkle',\n freezingDrizzle: 'wi:sleet',\n rain: 'wi:rain',\n freezingRain: 'wi:rain-mix',\n snow: 'wi:snow',\n rainShowers: 'wi:showers',\n snowShowers: 'wi:snow-wind',\n thunderstorm: 'wi:thunderstorm',\n thunderstormHail: 'wi:storm-showers',\n unknown: 'wi:na',\n }\n\n /** Maps WMO weather code to an Iconify wi: icon name */\n function wmoToIcon(code: number): string {\n return WMO_ICON[wmoCategory(code)]\n }\n\n /**\n * Returns a formatted day label for daily forecast.\n * When daily has 1-2 entries, use the date string directly.\n * Otherwise, use index-based computation from today.\n */\n function formatDay(isoDate: string, index: number, total: number): string {\n const parsed = safeDate(isoDate + 'T12:00:00')\n if (!Number.isNaN(parsed.getTime())) {\n const today = new Date()\n today.setHours(12, 0, 0, 0)\n const diffDays = Math.round(\n (parsed.getTime() - today.getTime()) / 86_400_000,\n )\n if (diffDays === 0) {\n return $t('weather.today')\n }\n return $d(parsed, 'weekday-day')\n }\n if (index === 0 && total <= 7) {\n return $t('weather.today')\n }\n const fallback = new Date()\n fallback.setDate(fallback.getDate() + index)\n return $d(fallback, 'weekday-day')\n }\n\n const headerDate = computed(() => {\n if (selectedDay.value) {\n const parsed = safeDate(selectedDay.value.date + 'T12:00:00')\n if (!Number.isNaN(parsed.getTime())) {\n return formatDay(\n selectedDay.value.date,\n selectedDayIndex.value,\n data.value?.daily?.length ?? 1,\n )\n }\n }\n return formatDay(new Date().toISOString().slice(0, 10), 0, 1)\n })\n\n /** i18n label key + CSS class per canonical AQI band. */\n const AQI_PRESENTATION: Record<\n AqiBandKey,\n { labelKey: string; class: string }\n > = {\n good: {\n labelKey: 'weather.aqiGood',\n class: 'text-green-600 bg-green-50',\n },\n fair: {\n labelKey: 'weather.aqiFair',\n class: 'text-lime-600 bg-lime-50',\n },\n moderate: {\n labelKey: 'weather.aqiModerate',\n class: 'text-yellow-600 bg-yellow-50',\n },\n poor: {\n labelKey: 'weather.aqiPoor',\n class: 'text-orange-600 bg-orange-50',\n },\n veryPoor: {\n labelKey: 'weather.aqiVeryPoor',\n class: 'text-red-600 bg-red-50',\n },\n hazardous: {\n labelKey: 'weather.aqiHazardous',\n class: 'text-purple-700 bg-purple-50',\n },\n }\n\n /** Returns AQI label and CSS class for European AQI */\n function aqiInfo(aqi: number): { label: string; class: string } {\n const { labelKey, class: cssClass } = AQI_PRESENTATION[aqiBand(aqi).key]\n return { label: $t(labelKey), class: cssClass }\n }\n\n /** Formats wind direction degrees to compass label */\n function windDirectionLabel(deg: number): string {\n const dirs = ['N', 'NE', 'E', 'SE', 'S', 'SO', 'O', 'NO']\n return dirs[Math.round(deg / 45) % 8]\n }\n</script>\n\n<template>\n <div\n v-if=\"data\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface\">\n <!-- Header -->\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8 min-h-40\">\n <VvIcon name=\"ri:temp-cold-line\" class=\"text-16\" />\n <strong class=\"font-bold truncate\">\n {{ data.locationName }}\n </strong>\n <span class=\"ml-auto shrink-0 capitalize\">\n {{ headerDate }}\n </span>\n </div>\n\n <!-- Current conditions (only when real-time current is available AND first day is selected) -->\n <div\n v-if=\"data.current && selectedDayIndex === 0\"\n class=\"px-sm py-md flex items-center gap-md flex-wrap\">\n <div class=\"flex items-center gap-8\">\n <VvIcon\n :name=\"wmoToIcon(data.current.weatherCode)\"\n class=\"text-48 text-word-2 shrink-0\" />\n <span class=\"text-48 font-light leading-none text-word-1\">\n {{ $n(data.current.temperature, 'integer') }}&deg;\n </span>\n </div>\n <div class=\"flex flex-col gap-4 text-12 text-word-3\">\n <div class=\"flex items-center gap-6\">\n {{ $t('weather.feelsLike') }}\n {{ $n(data.current.feelsLike, 'integer') }}&deg;\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:water-percent-line\" class=\"text-14\" />\n {{ data.current.humidity }}%\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:windy-line\" class=\"text-14\" />\n {{ $n(data.current.windSpeed, 'integer') }} km/h\n {{ windDirectionLabel(data.current.windDirection) }}\n </div>\n <div\n v-if=\"data.current.uvIndex > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-line\" class=\"text-14\" />\n UV {{ $n(data.current.uvIndex, 'integer') }}\n </div>\n </div>\n </div>\n\n <!-- Day summary (when no real-time current, or a non-today day is selected) -->\n <div\n v-else-if=\"selectedDay\"\n class=\"px-sm py-md flex items-center gap-md flex-wrap\">\n <div class=\"flex items-center gap-8\">\n <VvIcon\n :name=\"wmoToIcon(selectedDay.weatherCode)\"\n class=\"text-48 text-word-2 shrink-0\" />\n <div class=\"flex flex-col leading-none\">\n <span class=\"text-48 font-light text-word-1\">\n {{ $n(selectedDay.maxTemp, 'integer') }}&deg;\n </span>\n <span class=\"text-20 text-word-3\">\n {{ $n(selectedDay.minTemp, 'integer') }}&deg;\n </span>\n </div>\n </div>\n <div class=\"flex flex-col gap-4 text-12 text-word-3\">\n <div\n v-if=\"selectedDay.precipitationProbabilityMax > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:drop-line\" class=\"text-14\" />\n {{ selectedDay.precipitationProbabilityMax }}% ({{\n selectedDay.precipitationSum\n }}\n mm)\n </div>\n <div\n v-if=\"selectedDay.uvIndexMax > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-line\" class=\"text-14\" />\n UV {{ $n(selectedDay.uvIndexMax, 'integer') }}\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-foggy-line\" class=\"text-14\" />\n {{ $d(safeDate(selectedDay.sunrise), 'time') }}\n –\n {{ $d(safeDate(selectedDay.sunset), 'time') }}\n </div>\n </div>\n </div>\n\n <!-- Hourly forecast (filtered to selected day) -->\n <transition>\n <div\n v-if=\"filteredHourly.length > 0\"\n class=\"border-t border-surface-3\">\n <div\n class=\"flex overflow-x-auto gap-2 px-sm py-8 light-scrollbar\">\n <div\n v-for=\"hour in filteredHourly\"\n :key=\"hour.time\"\n class=\"flex flex-col items-center gap-4 min-w-48 px-4 py-6 rounded-xl text-11 text-word-3 shrink-0\">\n <span class=\"text-word-3\">{{\n Number.isNaN(safeDate(hour.time).getTime())\n ? hour.time\n : $d(safeDate(hour.time), 'time')\n }}</span>\n <VvIcon\n :name=\"wmoToIcon(hour.weatherCode)\"\n class=\"text-20 text-word-2\" />\n <span class=\"font-medium text-word-1\">\n {{ $n(hour.temperature, 'integer') }}&deg;\n </span>\n <span\n v-if=\"hour.precipitationProbability > 0\"\n class=\"text-word-5 text-smaller\">\n {{ hour.precipitationProbability }}%\n </span>\n </div>\n </div>\n </div>\n </transition>\n\n <!-- Daily forecast (clickable, inside accordion) -->\n <transition>\n <VvAccordion\n v-if=\"data.daily?.length > 1\"\n v-model=\"dailyOpen\"\n modifiers=\"details no-padding\"\n :title=\"$t('weather.dailyForecast')\"\n class=\"border-t border-surface-3\">\n <button\n v-for=\"(day, index) in data.daily\"\n :key=\"day.date\"\n type=\"button\"\n class=\"flex items-center gap-sm px-sm py-8 text-12 w-full text-left transition-colors cursor-pointer border-t border-surface-3\"\n :class=\"\n index === selectedDayIndex\n ? 'bg-surface-2'\n : 'hover:bg-surface-1'\n \"\n @click=\"selectedDayIndex = index\">\n <span class=\"w-48 shrink-0 text-word-3 capitalize\">\n {{ formatDay(day.date, index, data.daily.length) }}\n </span>\n <VvIcon\n :name=\"wmoToIcon(day.weatherCode)\"\n class=\"text-18 text-word-2 shrink-0\" />\n <span\n v-if=\"day.precipitationProbabilityMax > 0\"\n class=\"text-word-5 text-11 w-32 shrink-0\">\n {{ day.precipitationProbabilityMax }}%\n </span>\n <span v-else class=\"w-32 shrink-0\" />\n <div class=\"ml-auto flex items-center gap-8 text-word-3\">\n <span class=\"text-word-1 font-semibold\">\n {{ $n(day.maxTemp, 'integer') }}&deg;\n </span>\n <span>{{ $n(day.minTemp, 'integer') }}&deg;</span>\n </div>\n </button>\n </VvAccordion>\n </transition>\n\n <!-- Air quality -->\n <div\n v-if=\"data.airQuality\"\n class=\"border-t border-surface-3 px-sm py-8 flex items-center gap-8 text-12\">\n <VvIcon name=\"ri:windy-line\" class=\"text-14 text-word-3\" />\n <span class=\"text-word-3\">{{ $t('weather.airQuality') }}</span>\n <span\n :class=\"[\n 'ml-auto px-8 py-2 text-11 font-semibold',\n aqiInfo(data.airQuality.aqi).class,\n ]\">\n {{ aqiInfo(data.airQuality.aqi).label }}\n ({{ data.airQuality.aqi }})\n </span>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, watch } from 'vue'\n import { useI18n } from 'vue-i18n'\n import type { WeatherData } from 'models'\n import {\n aqiBand,\n wmoCategory,\n type AqiBandKey,\n type WmoCategory,\n } from 'utils'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const { t: $t, d: $d, n: $n } = useI18n({ useScope: 'global' })\n\n const toolPart = computed(() => {\n return props.part as { input?: WeatherData; output?: WeatherData }\n })\n\n const data = computed(\n () => toolPart.value.output ?? toolPart.value.input ?? null,\n )\n\n const selectedDayIndex = ref(0)\n\n const dailyOpen = ref(!data.value?.current)\n\n watch(selectedDayIndex, (index) => {\n if (index > 0) {\n dailyOpen.value = true\n }\n })\n\n const selectedDay = computed(\n () => data.value?.daily?.[selectedDayIndex.value],\n )\n\n /** Hourly entries filtered to the selected day's date */\n const filteredHourly = computed(() => {\n if (!data.value?.hourly?.length || !selectedDay.value) {\n return data.value?.hourly ?? []\n }\n const dayDate = selectedDay.value.date\n return data.value.hourly.filter((h) => h.time.startsWith(dayDate))\n })\n\n /**\n * Parses a date/time string in various formats the LLM might produce.\n * Tries ISO, space-separated, time-only, and common human-readable formats.\n */\n function safeDate(value: string): Date {\n // 1. Try native parsing (works for ISO 8601 and many standard formats)\n const direct = new Date(value)\n if (!Number.isNaN(direct.getTime())) {\n return direct\n }\n\n // 2. Replace space separator → T (\"2024-04-15 14:00\" → \"2024-04-15T14:00\")\n if (/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}/.test(value)) {\n const withT = new Date(value.replace(' ', 'T'))\n if (!Number.isNaN(withT.getTime())) {\n return withT\n }\n }\n\n // 3. Time-only (\"14:00\" or \"14:00:00\") → today at that hour\n const timeOnly = value.match(/^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/)\n if (timeOnly) {\n const today = new Date()\n today.setHours(\n Number(timeOnly[1]),\n Number(timeOnly[2]),\n Number(timeOnly[3] ?? 0),\n 0,\n )\n return today\n }\n\n // 4. Date-only (\"2024-04-15\") → noon to avoid timezone edge\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n const noon = new Date(value + 'T12:00:00')\n if (!Number.isNaN(noon.getTime())) {\n return noon\n }\n }\n\n // Last resort: return an obviously invalid sentinel so callers can handle it\n return new Date(NaN)\n }\n\n /** Iconify `wi:` icon per canonical WMO category. */\n const WMO_ICON: Record<WmoCategory, string> = {\n clear: 'wi:day-sunny',\n mainlyClear: 'wi:day-sunny-overcast',\n partlyCloudy: 'wi:day-cloudy',\n overcast: 'wi:cloudy',\n fog: 'wi:fog',\n drizzle: 'wi:sprinkle',\n freezingDrizzle: 'wi:sleet',\n rain: 'wi:rain',\n freezingRain: 'wi:rain-mix',\n snow: 'wi:snow',\n rainShowers: 'wi:showers',\n snowShowers: 'wi:snow-wind',\n thunderstorm: 'wi:thunderstorm',\n thunderstormHail: 'wi:storm-showers',\n unknown: 'wi:na',\n }\n\n /** Maps WMO weather code to an Iconify wi: icon name */\n function wmoToIcon(code: number): string {\n return WMO_ICON[wmoCategory(code)]\n }\n\n /**\n * Returns a formatted day label for daily forecast.\n * When daily has 1-2 entries, use the date string directly.\n * Otherwise, use index-based computation from today.\n */\n function formatDay(isoDate: string, index: number, total: number): string {\n const parsed = safeDate(isoDate + 'T12:00:00')\n if (!Number.isNaN(parsed.getTime())) {\n const today = new Date()\n today.setHours(12, 0, 0, 0)\n const diffDays = Math.round(\n (parsed.getTime() - today.getTime()) / 86_400_000,\n )\n if (diffDays === 0) {\n return $t('weather.today')\n }\n return $d(parsed, 'weekday-day')\n }\n if (index === 0 && total <= 7) {\n return $t('weather.today')\n }\n const fallback = new Date()\n fallback.setDate(fallback.getDate() + index)\n return $d(fallback, 'weekday-day')\n }\n\n const headerDate = computed(() => {\n if (selectedDay.value) {\n const parsed = safeDate(selectedDay.value.date + 'T12:00:00')\n if (!Number.isNaN(parsed.getTime())) {\n return formatDay(\n selectedDay.value.date,\n selectedDayIndex.value,\n data.value?.daily?.length ?? 1,\n )\n }\n }\n return formatDay(new Date().toISOString().slice(0, 10), 0, 1)\n })\n\n /** i18n label key + CSS class per canonical AQI band. */\n const AQI_PRESENTATION: Record<\n AqiBandKey,\n { labelKey: string; class: string }\n > = {\n good: {\n labelKey: 'weather.aqiGood',\n class: 'text-green-600 bg-green-50',\n },\n fair: {\n labelKey: 'weather.aqiFair',\n class: 'text-lime-600 bg-lime-50',\n },\n moderate: {\n labelKey: 'weather.aqiModerate',\n class: 'text-yellow-600 bg-yellow-50',\n },\n poor: {\n labelKey: 'weather.aqiPoor',\n class: 'text-orange-600 bg-orange-50',\n },\n veryPoor: {\n labelKey: 'weather.aqiVeryPoor',\n class: 'text-red-600 bg-red-50',\n },\n hazardous: {\n labelKey: 'weather.aqiHazardous',\n class: 'text-purple-700 bg-purple-50',\n },\n }\n\n /** Returns AQI label and CSS class for European AQI */\n function aqiInfo(aqi: number): { label: string; class: string } {\n const { labelKey, class: cssClass } = AQI_PRESENTATION[aqiBand(aqi).key]\n return { label: $t(labelKey), class: cssClass }\n }\n\n /** Formats wind direction degrees to compass label */\n function windDirectionLabel(deg: number): string {\n const dirs = ['N', 'NE', 'E', 'SE', 'S', 'SO', 'O', 'NO']\n return dirs[Math.round(deg / 45) % 8]\n }\n</script>\n\n<template>\n <div\n v-if=\"data\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface\">\n <!-- Header -->\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8 min-h-40\">\n <VvIcon name=\"ri:temp-cold-line\" class=\"text-16\" />\n <strong class=\"font-bold truncate\">\n {{ data.locationName }}\n </strong>\n <span class=\"ml-auto shrink-0 capitalize\">\n {{ headerDate }}\n </span>\n </div>\n\n <!-- Current conditions (only when real-time current is available AND first day is selected) -->\n <div\n v-if=\"data.current && selectedDayIndex === 0\"\n class=\"px-sm py-md flex items-center gap-md flex-wrap\">\n <div class=\"flex items-center gap-8\">\n <VvIcon\n :name=\"wmoToIcon(data.current.weatherCode)\"\n class=\"text-48 text-word-2 shrink-0\" />\n <span class=\"text-48 font-light leading-none text-word-1\">\n {{ $n(data.current.temperature, 'integer') }}&deg;\n </span>\n </div>\n <div class=\"flex flex-col gap-4 text-12 text-word-3\">\n <div class=\"flex items-center gap-6\">\n {{ $t('weather.feelsLike') }}\n {{ $n(data.current.feelsLike, 'integer') }}&deg;\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:water-percent-line\" class=\"text-14\" />\n {{ data.current.humidity }}%\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:windy-line\" class=\"text-14\" />\n {{ $n(data.current.windSpeed, 'integer') }} km/h\n {{ windDirectionLabel(data.current.windDirection) }}\n </div>\n <div\n v-if=\"data.current.uvIndex > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-line\" class=\"text-14\" />\n UV {{ $n(data.current.uvIndex, 'integer') }}\n </div>\n </div>\n </div>\n\n <!-- Day summary (when no real-time current, or a non-today day is selected) -->\n <div\n v-else-if=\"selectedDay\"\n class=\"px-sm py-md flex items-center gap-md flex-wrap\">\n <div class=\"flex items-center gap-8\">\n <VvIcon\n :name=\"wmoToIcon(selectedDay.weatherCode)\"\n class=\"text-48 text-word-2 shrink-0\" />\n <div class=\"flex flex-col leading-none\">\n <span class=\"text-48 font-light text-word-1\">\n {{ $n(selectedDay.maxTemp, 'integer') }}&deg;\n </span>\n <span class=\"text-20 text-word-3\">\n {{ $n(selectedDay.minTemp, 'integer') }}&deg;\n </span>\n </div>\n </div>\n <div class=\"flex flex-col gap-4 text-12 text-word-3\">\n <div\n v-if=\"selectedDay.precipitationProbabilityMax > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:drop-line\" class=\"text-14\" />\n {{ selectedDay.precipitationProbabilityMax }}% ({{\n selectedDay.precipitationSum\n }}\n mm)\n </div>\n <div\n v-if=\"selectedDay.uvIndexMax > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-line\" class=\"text-14\" />\n UV {{ $n(selectedDay.uvIndexMax, 'integer') }}\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-foggy-line\" class=\"text-14\" />\n {{ $d(safeDate(selectedDay.sunrise), 'time') }}\n –\n {{ $d(safeDate(selectedDay.sunset), 'time') }}\n </div>\n </div>\n </div>\n\n <!-- Hourly forecast (filtered to selected day) -->\n <transition>\n <div\n v-if=\"filteredHourly.length > 0\"\n class=\"border-t border-surface-3\">\n <div\n class=\"flex overflow-x-auto gap-2 px-sm py-8 light-scrollbar\">\n <div\n v-for=\"hour in filteredHourly\"\n :key=\"hour.time\"\n class=\"flex flex-col items-center gap-4 min-w-48 px-4 py-6 rounded-xl text-11 text-word-3 shrink-0\">\n <span class=\"text-word-3\">{{\n Number.isNaN(safeDate(hour.time).getTime())\n ? hour.time\n : $d(safeDate(hour.time), 'time')\n }}</span>\n <VvIcon\n :name=\"wmoToIcon(hour.weatherCode)\"\n class=\"text-20 text-word-2\" />\n <span class=\"font-medium text-word-1\">\n {{ $n(hour.temperature, 'integer') }}&deg;\n </span>\n <span\n v-if=\"hour.precipitationProbability > 0\"\n class=\"text-word-5 text-smaller\">\n {{ hour.precipitationProbability }}%\n </span>\n </div>\n </div>\n </div>\n </transition>\n\n <!-- Daily forecast (clickable, inside accordion) -->\n <transition>\n <VvAccordion\n v-if=\"data.daily?.length > 1\"\n v-model=\"dailyOpen\"\n modifiers=\"details no-padding\"\n :title=\"$t('weather.dailyForecast')\"\n class=\"border-t border-surface-3\">\n <button\n v-for=\"(day, index) in data.daily\"\n :key=\"day.date\"\n type=\"button\"\n class=\"flex items-center gap-sm px-sm py-8 text-12 w-full text-left transition-colors cursor-pointer border-t border-surface-3\"\n :class=\"\n index === selectedDayIndex\n ? 'bg-surface-2'\n : 'hover:bg-surface-1'\n \"\n @click=\"selectedDayIndex = index\">\n <span class=\"w-48 shrink-0 text-word-3 capitalize\">\n {{ formatDay(day.date, index, data.daily.length) }}\n </span>\n <VvIcon\n :name=\"wmoToIcon(day.weatherCode)\"\n class=\"text-18 text-word-2 shrink-0\" />\n <span\n v-if=\"day.precipitationProbabilityMax > 0\"\n class=\"text-word-5 text-11 w-32 shrink-0\">\n {{ day.precipitationProbabilityMax }}%\n </span>\n <span v-else class=\"w-32 shrink-0\" />\n <div class=\"ml-auto flex items-center gap-8 text-word-3\">\n <span class=\"text-word-1 font-semibold\">\n {{ $n(day.maxTemp, 'integer') }}&deg;\n </span>\n <span>{{ $n(day.minTemp, 'integer') }}&deg;</span>\n </div>\n </button>\n </VvAccordion>\n </transition>\n\n <!-- Air quality -->\n <div\n v-if=\"data.airQuality\"\n class=\"border-t border-surface-3 px-sm py-8 flex items-center gap-8 text-12\">\n <VvIcon name=\"ri:windy-line\" class=\"text-14 text-word-3\" />\n <span class=\"text-word-3\">{{ $t('weather.airQuality') }}</span>\n <span\n :class=\"[\n 'ml-auto px-8 py-2 text-11 font-semibold',\n aqiInfo(data.airQuality.aqi).class,\n ]\">\n {{ aqiInfo(data.airQuality.aqi).label }}\n ({{ data.airQuality.aqi }})\n </span>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PkToolShowWeather-rTcxGcWl.js","names":["__unplugin_components_0","__unplugin_components_1","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_toDisplayString","_unref","_createTextVNode","_createCommentVNode","_Transition","_withCtx","_Fragment","_renderList","_createBlock","_normalizeClass","_sfc_main","withCtx","defineComponent","openBlock","init_runtime_core_esm_bundler","normalizeClass","toDisplayString","watch","createCommentVNode","createTextVNode","unref","Fragment","createBlock","createBaseVNode","init_reactivity_esm_bundler","computed","ref","init_runtime_dom_esm_bundler","Transition","createElementBlock","createVNode","init_shared_esm_bundler","renderList","useI18n","Ar","Ae","wmoCategory","code","aqiBand","aqi","_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","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_hoisted_29","_hoisted_30","_hoisted_31","_hoisted_32","_hoisted_33","_hoisted_34","PkToolShowWeather_default","props","__props","$t","$d","$n","toolPart","data","selectedDayIndex","dailyOpen","index","selectedDay","filteredHourly","dayDate","h","safeDate","direct","value","withT","timeOnly","today","noon","WMO_ICON","wmoToIcon","formatDay","parsed","isoDate","total","fallback","headerDate","AQI_PRESENTATION","aqiInfo","cssClass","labelKey","windDirectionLabel","deg","_component_VvIcon","_component_VvAccordion","hour","_cache","$event","day"],"sources":["../../../../packages/utils/src/weather.ts","../../../../packages/components/src/chat/PkToolShowWeather.vue","../../../../packages/components/src/chat/PkToolShowWeather.vue"],"sourcesContent":["/**\n * Framework-agnostic weather presentation primitives. They classify raw\n * open-meteo values into canonical categories; each consumer (web widget,\n * platform card renderer) maps a category to its own representation (an\n * Iconify name, an emoji, a CSS class, an i18n key). Keeping the classification\n * here means the WMO code bands and AQI thresholds live in exactly one place.\n */\n\n/** Canonical WMO weather-code category (resolution matches the web widget). */\nexport type WmoCategory =\n | 'clear'\n | 'mainlyClear'\n | 'partlyCloudy'\n | 'overcast'\n | 'fog'\n | 'drizzle'\n | 'freezingDrizzle'\n | 'rain'\n | 'freezingRain'\n | 'snow'\n | 'rainShowers'\n | 'snowShowers'\n | 'thunderstorm'\n | 'thunderstormHail'\n | 'unknown'\n\n/** Map a WMO weather interpretation code to a canonical category. */\nexport function wmoCategory(code: number | undefined): WmoCategory {\n if (code === undefined) {\n return 'unknown'\n }\n if (code === 0) {\n return 'clear'\n }\n if (code === 1) {\n return 'mainlyClear'\n }\n if (code === 2) {\n return 'partlyCloudy'\n }\n if (code === 3) {\n return 'overcast'\n }\n if (code === 45 || code === 48) {\n return 'fog'\n }\n if (code >= 51 && code <= 55) {\n return 'drizzle'\n }\n if (code === 56 || code === 57) {\n return 'freezingDrizzle'\n }\n if (code >= 61 && code <= 65) {\n return 'rain'\n }\n if (code === 66 || code === 67) {\n return 'freezingRain'\n }\n if (code >= 71 && code <= 77) {\n return 'snow'\n }\n if (code >= 80 && code <= 82) {\n return 'rainShowers'\n }\n if (code === 85 || code === 86) {\n return 'snowShowers'\n }\n if (code === 95) {\n return 'thunderstorm'\n }\n if (code === 96 || code === 99) {\n return 'thunderstormHail'\n }\n return 'unknown'\n}\n\n/** Emoji per category (used by the language-neutral platform card renderer). */\nconst WMO_EMOJI: Record<WmoCategory, string> = {\n clear: '☀️',\n mainlyClear: '⛅',\n partlyCloudy: '⛅',\n overcast: '⛅',\n fog: '🌫️',\n drizzle: '🌦️',\n freezingDrizzle: '🌦️',\n rain: '🌧️',\n freezingRain: '🌧️',\n snow: '❄️',\n rainShowers: '🌧️',\n snowShowers: '🌨️',\n thunderstorm: '⛈️',\n thunderstormHail: '⛈️',\n unknown: '🌡️',\n}\n\n/** WMO code → emoji (convenience for non-UI consumers). */\nexport function wmoEmoji(code: number | undefined): string {\n return WMO_EMOJI[wmoCategory(code)]\n}\n\n/** Canonical European-AQI band, ordered best → worst. */\nexport type AqiBandKey =\n 'good' | 'fair' | 'moderate' | 'poor' | 'veryPoor' | 'hazardous'\n\n/** Classify a European AQI value into a band (index + canonical key). */\nexport function aqiBand(aqi: number): { index: number; key: AqiBandKey } {\n if (aqi <= 20) {\n return { index: 0, key: 'good' }\n }\n if (aqi <= 40) {\n return { index: 1, key: 'fair' }\n }\n if (aqi <= 60) {\n return { index: 2, key: 'moderate' }\n }\n if (aqi <= 80) {\n return { index: 3, key: 'poor' }\n }\n if (aqi <= 100) {\n return { index: 4, key: 'veryPoor' }\n }\n return { index: 5, key: 'hazardous' }\n}\n\n/** Colored dot per AQI band index (used by the platform card renderer). */\nconst AQI_DOTS = ['🟢', '🟡', '🟠', '🔴', '🟣', '⚫']\n\n/** AQI value → colored dot (convenience for non-UI consumers). */\nexport function aqiDot(aqi: number): string {\n return AQI_DOTS[aqiBand(aqi).index]\n}\n","<script setup lang=\"ts\">\n import { computed, ref, watch } from 'vue'\n import { useI18n } from 'vue-i18n'\n import type { WeatherData } from 'models'\n import {\n aqiBand,\n wmoCategory,\n type AqiBandKey,\n type WmoCategory,\n } from 'utils'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const { t: $t, d: $d, n: $n } = useI18n({ useScope: 'global' })\n\n const toolPart = computed(() => {\n return props.part as { input?: WeatherData; output?: WeatherData }\n })\n\n const data = computed(\n () => toolPart.value.output ?? toolPart.value.input ?? null,\n )\n\n const selectedDayIndex = ref(0)\n\n const dailyOpen = ref(!data.value?.current)\n\n watch(selectedDayIndex, (index) => {\n if (index > 0) {\n dailyOpen.value = true\n }\n })\n\n const selectedDay = computed(\n () => data.value?.daily?.[selectedDayIndex.value],\n )\n\n /** Hourly entries filtered to the selected day's date */\n const filteredHourly = computed(() => {\n if (!data.value?.hourly?.length || !selectedDay.value) {\n return data.value?.hourly ?? []\n }\n const dayDate = selectedDay.value.date\n return data.value.hourly.filter((h) => h.time.startsWith(dayDate))\n })\n\n /**\n * Parses a date/time string in various formats the LLM might produce.\n * Tries ISO, space-separated, time-only, and common human-readable formats.\n */\n function safeDate(value: string): Date {\n // 1. Try native parsing (works for ISO 8601 and many standard formats)\n const direct = new Date(value)\n if (!Number.isNaN(direct.getTime())) {\n return direct\n }\n\n // 2. Replace space separator → T (\"2024-04-15 14:00\" → \"2024-04-15T14:00\")\n if (/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}/.test(value)) {\n const withT = new Date(value.replace(' ', 'T'))\n if (!Number.isNaN(withT.getTime())) {\n return withT\n }\n }\n\n // 3. Time-only (\"14:00\" or \"14:00:00\") → today at that hour\n const timeOnly = value.match(/^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/)\n if (timeOnly) {\n const today = new Date()\n today.setHours(\n Number(timeOnly[1]),\n Number(timeOnly[2]),\n Number(timeOnly[3] ?? 0),\n 0,\n )\n return today\n }\n\n // 4. Date-only (\"2024-04-15\") → noon to avoid timezone edge\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n const noon = new Date(value + 'T12:00:00')\n if (!Number.isNaN(noon.getTime())) {\n return noon\n }\n }\n\n // Last resort: return an obviously invalid sentinel so callers can handle it\n return new Date(NaN)\n }\n\n /** Iconify `wi:` icon per canonical WMO category. */\n const WMO_ICON: Record<WmoCategory, string> = {\n clear: 'wi:day-sunny',\n mainlyClear: 'wi:day-sunny-overcast',\n partlyCloudy: 'wi:day-cloudy',\n overcast: 'wi:cloudy',\n fog: 'wi:fog',\n drizzle: 'wi:sprinkle',\n freezingDrizzle: 'wi:sleet',\n rain: 'wi:rain',\n freezingRain: 'wi:rain-mix',\n snow: 'wi:snow',\n rainShowers: 'wi:showers',\n snowShowers: 'wi:snow-wind',\n thunderstorm: 'wi:thunderstorm',\n thunderstormHail: 'wi:storm-showers',\n unknown: 'wi:na',\n }\n\n /** Maps WMO weather code to an Iconify wi: icon name */\n function wmoToIcon(code: number): string {\n return WMO_ICON[wmoCategory(code)]\n }\n\n /**\n * Returns a formatted day label for daily forecast.\n * When daily has 1-2 entries, use the date string directly.\n * Otherwise, use index-based computation from today.\n */\n function formatDay(isoDate: string, index: number, total: number): string {\n const parsed = safeDate(isoDate + 'T12:00:00')\n if (!Number.isNaN(parsed.getTime())) {\n const today = new Date()\n today.setHours(12, 0, 0, 0)\n const diffDays = Math.round(\n (parsed.getTime() - today.getTime()) / 86_400_000,\n )\n if (diffDays === 0) {\n return $t('weather.today')\n }\n return $d(parsed, 'weekday-day')\n }\n if (index === 0 && total <= 7) {\n return $t('weather.today')\n }\n const fallback = new Date()\n fallback.setDate(fallback.getDate() + index)\n return $d(fallback, 'weekday-day')\n }\n\n const headerDate = computed(() => {\n if (selectedDay.value) {\n const parsed = safeDate(selectedDay.value.date + 'T12:00:00')\n if (!Number.isNaN(parsed.getTime())) {\n return formatDay(\n selectedDay.value.date,\n selectedDayIndex.value,\n data.value?.daily?.length ?? 1,\n )\n }\n }\n return formatDay(new Date().toISOString().slice(0, 10), 0, 1)\n })\n\n /** i18n label key + CSS class per canonical AQI band. */\n const AQI_PRESENTATION: Record<\n AqiBandKey,\n { labelKey: string; class: string }\n > = {\n good: {\n labelKey: 'weather.aqiGood',\n class: 'text-green-600 bg-green-50',\n },\n fair: {\n labelKey: 'weather.aqiFair',\n class: 'text-lime-600 bg-lime-50',\n },\n moderate: {\n labelKey: 'weather.aqiModerate',\n class: 'text-yellow-600 bg-yellow-50',\n },\n poor: {\n labelKey: 'weather.aqiPoor',\n class: 'text-orange-600 bg-orange-50',\n },\n veryPoor: {\n labelKey: 'weather.aqiVeryPoor',\n class: 'text-red-600 bg-red-50',\n },\n hazardous: {\n labelKey: 'weather.aqiHazardous',\n class: 'text-purple-700 bg-purple-50',\n },\n }\n\n /** Returns AQI label and CSS class for European AQI */\n function aqiInfo(aqi: number): { label: string; class: string } {\n const { labelKey, class: cssClass } = AQI_PRESENTATION[aqiBand(aqi).key]\n return { label: $t(labelKey), class: cssClass }\n }\n\n /** Formats wind direction degrees to compass label */\n function windDirectionLabel(deg: number): string {\n const dirs = ['N', 'NE', 'E', 'SE', 'S', 'SO', 'O', 'NO']\n return dirs[Math.round(deg / 45) % 8]\n }\n</script>\n\n<template>\n <div\n v-if=\"data\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface\">\n <!-- Header -->\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8 min-h-40\">\n <VvIcon name=\"ri:temp-cold-line\" class=\"text-16\" />\n <strong class=\"font-bold truncate\">\n {{ data.locationName }}\n </strong>\n <span class=\"ml-auto shrink-0 capitalize\">\n {{ headerDate }}\n </span>\n </div>\n\n <!-- Current conditions (only when real-time current is available AND first day is selected) -->\n <div\n v-if=\"data.current && selectedDayIndex === 0\"\n class=\"px-sm py-md flex items-center gap-md flex-wrap\">\n <div class=\"flex items-center gap-8\">\n <VvIcon\n :name=\"wmoToIcon(data.current.weatherCode)\"\n class=\"text-48 text-word-2 shrink-0\" />\n <span class=\"text-48 font-light leading-none text-word-1\">\n {{ $n(data.current.temperature, 'integer') }}&deg;\n </span>\n </div>\n <div class=\"flex flex-col gap-4 text-12 text-word-3\">\n <div class=\"flex items-center gap-6\">\n {{ $t('weather.feelsLike') }}\n {{ $n(data.current.feelsLike, 'integer') }}&deg;\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:water-percent-line\" class=\"text-14\" />\n {{ data.current.humidity }}%\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:windy-line\" class=\"text-14\" />\n {{ $n(data.current.windSpeed, 'integer') }} km/h\n {{ windDirectionLabel(data.current.windDirection) }}\n </div>\n <div\n v-if=\"data.current.uvIndex > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-line\" class=\"text-14\" />\n UV {{ $n(data.current.uvIndex, 'integer') }}\n </div>\n </div>\n </div>\n\n <!-- Day summary (when no real-time current, or a non-today day is selected) -->\n <div\n v-else-if=\"selectedDay\"\n class=\"px-sm py-md flex items-center gap-md flex-wrap\">\n <div class=\"flex items-center gap-8\">\n <VvIcon\n :name=\"wmoToIcon(selectedDay.weatherCode)\"\n class=\"text-48 text-word-2 shrink-0\" />\n <div class=\"flex flex-col leading-none\">\n <span class=\"text-48 font-light text-word-1\">\n {{ $n(selectedDay.maxTemp, 'integer') }}&deg;\n </span>\n <span class=\"text-20 text-word-3\">\n {{ $n(selectedDay.minTemp, 'integer') }}&deg;\n </span>\n </div>\n </div>\n <div class=\"flex flex-col gap-4 text-12 text-word-3\">\n <div\n v-if=\"selectedDay.precipitationProbabilityMax > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:drop-line\" class=\"text-14\" />\n {{ selectedDay.precipitationProbabilityMax }}% ({{\n selectedDay.precipitationSum\n }}\n mm)\n </div>\n <div\n v-if=\"selectedDay.uvIndexMax > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-line\" class=\"text-14\" />\n UV {{ $n(selectedDay.uvIndexMax, 'integer') }}\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-foggy-line\" class=\"text-14\" />\n {{ $d(safeDate(selectedDay.sunrise), 'time') }}\n –\n {{ $d(safeDate(selectedDay.sunset), 'time') }}\n </div>\n </div>\n </div>\n\n <!-- Hourly forecast (filtered to selected day) -->\n <transition>\n <div\n v-if=\"filteredHourly.length > 0\"\n class=\"border-t border-surface-3\">\n <div\n class=\"flex overflow-x-auto gap-2 px-sm py-8 light-scrollbar\">\n <div\n v-for=\"hour in filteredHourly\"\n :key=\"hour.time\"\n class=\"flex flex-col items-center gap-4 min-w-48 px-4 py-6 rounded-xl text-11 text-word-3 shrink-0\">\n <span class=\"text-word-3\">{{\n Number.isNaN(safeDate(hour.time).getTime())\n ? hour.time\n : $d(safeDate(hour.time), 'time')\n }}</span>\n <VvIcon\n :name=\"wmoToIcon(hour.weatherCode)\"\n class=\"text-20 text-word-2\" />\n <span class=\"font-medium text-word-1\">\n {{ $n(hour.temperature, 'integer') }}&deg;\n </span>\n <span\n v-if=\"hour.precipitationProbability > 0\"\n class=\"text-word-5 text-smaller\">\n {{ hour.precipitationProbability }}%\n </span>\n </div>\n </div>\n </div>\n </transition>\n\n <!-- Daily forecast (clickable, inside accordion) -->\n <transition>\n <VvAccordion\n v-if=\"data.daily?.length > 1\"\n v-model=\"dailyOpen\"\n modifiers=\"details no-padding\"\n :title=\"$t('weather.dailyForecast')\"\n class=\"border-t border-surface-3\">\n <button\n v-for=\"(day, index) in data.daily\"\n :key=\"day.date\"\n type=\"button\"\n class=\"flex items-center gap-sm px-sm py-8 text-12 w-full text-left transition-colors cursor-pointer border-t border-surface-3\"\n :class=\"\n index === selectedDayIndex\n ? 'bg-surface-2'\n : 'hover:bg-surface-1'\n \"\n @click=\"selectedDayIndex = index\">\n <span class=\"w-48 shrink-0 text-word-3 capitalize\">\n {{ formatDay(day.date, index, data.daily.length) }}\n </span>\n <VvIcon\n :name=\"wmoToIcon(day.weatherCode)\"\n class=\"text-18 text-word-2 shrink-0\" />\n <span\n v-if=\"day.precipitationProbabilityMax > 0\"\n class=\"text-word-5 text-11 w-32 shrink-0\">\n {{ day.precipitationProbabilityMax }}%\n </span>\n <span v-else class=\"w-32 shrink-0\" />\n <div class=\"ml-auto flex items-center gap-8 text-word-3\">\n <span class=\"text-word-1 font-semibold\">\n {{ $n(day.maxTemp, 'integer') }}&deg;\n </span>\n <span>{{ $n(day.minTemp, 'integer') }}&deg;</span>\n </div>\n </button>\n </VvAccordion>\n </transition>\n\n <!-- Air quality -->\n <div\n v-if=\"data.airQuality\"\n class=\"border-t border-surface-3 px-sm py-8 flex items-center gap-8 text-12\">\n <VvIcon name=\"ri:windy-line\" class=\"text-14 text-word-3\" />\n <span class=\"text-word-3\">{{ $t('weather.airQuality') }}</span>\n <span\n :class=\"[\n 'ml-auto px-8 py-2 text-11 font-semibold',\n aqiInfo(data.airQuality.aqi).class,\n ]\">\n {{ aqiInfo(data.airQuality.aqi).label }}\n ({{ data.airQuality.aqi }})\n </span>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, watch } from 'vue'\n import { useI18n } from 'vue-i18n'\n import type { WeatherData } from 'models'\n import {\n aqiBand,\n wmoCategory,\n type AqiBandKey,\n type WmoCategory,\n } from 'utils'\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const { t: $t, d: $d, n: $n } = useI18n({ useScope: 'global' })\n\n const toolPart = computed(() => {\n return props.part as { input?: WeatherData; output?: WeatherData }\n })\n\n const data = computed(\n () => toolPart.value.output ?? toolPart.value.input ?? null,\n )\n\n const selectedDayIndex = ref(0)\n\n const dailyOpen = ref(!data.value?.current)\n\n watch(selectedDayIndex, (index) => {\n if (index > 0) {\n dailyOpen.value = true\n }\n })\n\n const selectedDay = computed(\n () => data.value?.daily?.[selectedDayIndex.value],\n )\n\n /** Hourly entries filtered to the selected day's date */\n const filteredHourly = computed(() => {\n if (!data.value?.hourly?.length || !selectedDay.value) {\n return data.value?.hourly ?? []\n }\n const dayDate = selectedDay.value.date\n return data.value.hourly.filter((h) => h.time.startsWith(dayDate))\n })\n\n /**\n * Parses a date/time string in various formats the LLM might produce.\n * Tries ISO, space-separated, time-only, and common human-readable formats.\n */\n function safeDate(value: string): Date {\n // 1. Try native parsing (works for ISO 8601 and many standard formats)\n const direct = new Date(value)\n if (!Number.isNaN(direct.getTime())) {\n return direct\n }\n\n // 2. Replace space separator → T (\"2024-04-15 14:00\" → \"2024-04-15T14:00\")\n if (/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}/.test(value)) {\n const withT = new Date(value.replace(' ', 'T'))\n if (!Number.isNaN(withT.getTime())) {\n return withT\n }\n }\n\n // 3. Time-only (\"14:00\" or \"14:00:00\") → today at that hour\n const timeOnly = value.match(/^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/)\n if (timeOnly) {\n const today = new Date()\n today.setHours(\n Number(timeOnly[1]),\n Number(timeOnly[2]),\n Number(timeOnly[3] ?? 0),\n 0,\n )\n return today\n }\n\n // 4. Date-only (\"2024-04-15\") → noon to avoid timezone edge\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n const noon = new Date(value + 'T12:00:00')\n if (!Number.isNaN(noon.getTime())) {\n return noon\n }\n }\n\n // Last resort: return an obviously invalid sentinel so callers can handle it\n return new Date(NaN)\n }\n\n /** Iconify `wi:` icon per canonical WMO category. */\n const WMO_ICON: Record<WmoCategory, string> = {\n clear: 'wi:day-sunny',\n mainlyClear: 'wi:day-sunny-overcast',\n partlyCloudy: 'wi:day-cloudy',\n overcast: 'wi:cloudy',\n fog: 'wi:fog',\n drizzle: 'wi:sprinkle',\n freezingDrizzle: 'wi:sleet',\n rain: 'wi:rain',\n freezingRain: 'wi:rain-mix',\n snow: 'wi:snow',\n rainShowers: 'wi:showers',\n snowShowers: 'wi:snow-wind',\n thunderstorm: 'wi:thunderstorm',\n thunderstormHail: 'wi:storm-showers',\n unknown: 'wi:na',\n }\n\n /** Maps WMO weather code to an Iconify wi: icon name */\n function wmoToIcon(code: number): string {\n return WMO_ICON[wmoCategory(code)]\n }\n\n /**\n * Returns a formatted day label for daily forecast.\n * When daily has 1-2 entries, use the date string directly.\n * Otherwise, use index-based computation from today.\n */\n function formatDay(isoDate: string, index: number, total: number): string {\n const parsed = safeDate(isoDate + 'T12:00:00')\n if (!Number.isNaN(parsed.getTime())) {\n const today = new Date()\n today.setHours(12, 0, 0, 0)\n const diffDays = Math.round(\n (parsed.getTime() - today.getTime()) / 86_400_000,\n )\n if (diffDays === 0) {\n return $t('weather.today')\n }\n return $d(parsed, 'weekday-day')\n }\n if (index === 0 && total <= 7) {\n return $t('weather.today')\n }\n const fallback = new Date()\n fallback.setDate(fallback.getDate() + index)\n return $d(fallback, 'weekday-day')\n }\n\n const headerDate = computed(() => {\n if (selectedDay.value) {\n const parsed = safeDate(selectedDay.value.date + 'T12:00:00')\n if (!Number.isNaN(parsed.getTime())) {\n return formatDay(\n selectedDay.value.date,\n selectedDayIndex.value,\n data.value?.daily?.length ?? 1,\n )\n }\n }\n return formatDay(new Date().toISOString().slice(0, 10), 0, 1)\n })\n\n /** i18n label key + CSS class per canonical AQI band. */\n const AQI_PRESENTATION: Record<\n AqiBandKey,\n { labelKey: string; class: string }\n > = {\n good: {\n labelKey: 'weather.aqiGood',\n class: 'text-green-600 bg-green-50',\n },\n fair: {\n labelKey: 'weather.aqiFair',\n class: 'text-lime-600 bg-lime-50',\n },\n moderate: {\n labelKey: 'weather.aqiModerate',\n class: 'text-yellow-600 bg-yellow-50',\n },\n poor: {\n labelKey: 'weather.aqiPoor',\n class: 'text-orange-600 bg-orange-50',\n },\n veryPoor: {\n labelKey: 'weather.aqiVeryPoor',\n class: 'text-red-600 bg-red-50',\n },\n hazardous: {\n labelKey: 'weather.aqiHazardous',\n class: 'text-purple-700 bg-purple-50',\n },\n }\n\n /** Returns AQI label and CSS class for European AQI */\n function aqiInfo(aqi: number): { label: string; class: string } {\n const { labelKey, class: cssClass } = AQI_PRESENTATION[aqiBand(aqi).key]\n return { label: $t(labelKey), class: cssClass }\n }\n\n /** Formats wind direction degrees to compass label */\n function windDirectionLabel(deg: number): string {\n const dirs = ['N', 'NE', 'E', 'SE', 'S', 'SO', 'O', 'NO']\n return dirs[Math.round(deg / 45) % 8]\n }\n</script>\n\n<template>\n <div\n v-if=\"data\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface\">\n <!-- Header -->\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8 min-h-40\">\n <VvIcon name=\"ri:temp-cold-line\" class=\"text-16\" />\n <strong class=\"font-bold truncate\">\n {{ data.locationName }}\n </strong>\n <span class=\"ml-auto shrink-0 capitalize\">\n {{ headerDate }}\n </span>\n </div>\n\n <!-- Current conditions (only when real-time current is available AND first day is selected) -->\n <div\n v-if=\"data.current && selectedDayIndex === 0\"\n class=\"px-sm py-md flex items-center gap-md flex-wrap\">\n <div class=\"flex items-center gap-8\">\n <VvIcon\n :name=\"wmoToIcon(data.current.weatherCode)\"\n class=\"text-48 text-word-2 shrink-0\" />\n <span class=\"text-48 font-light leading-none text-word-1\">\n {{ $n(data.current.temperature, 'integer') }}&deg;\n </span>\n </div>\n <div class=\"flex flex-col gap-4 text-12 text-word-3\">\n <div class=\"flex items-center gap-6\">\n {{ $t('weather.feelsLike') }}\n {{ $n(data.current.feelsLike, 'integer') }}&deg;\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:water-percent-line\" class=\"text-14\" />\n {{ data.current.humidity }}%\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:windy-line\" class=\"text-14\" />\n {{ $n(data.current.windSpeed, 'integer') }} km/h\n {{ windDirectionLabel(data.current.windDirection) }}\n </div>\n <div\n v-if=\"data.current.uvIndex > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-line\" class=\"text-14\" />\n UV {{ $n(data.current.uvIndex, 'integer') }}\n </div>\n </div>\n </div>\n\n <!-- Day summary (when no real-time current, or a non-today day is selected) -->\n <div\n v-else-if=\"selectedDay\"\n class=\"px-sm py-md flex items-center gap-md flex-wrap\">\n <div class=\"flex items-center gap-8\">\n <VvIcon\n :name=\"wmoToIcon(selectedDay.weatherCode)\"\n class=\"text-48 text-word-2 shrink-0\" />\n <div class=\"flex flex-col leading-none\">\n <span class=\"text-48 font-light text-word-1\">\n {{ $n(selectedDay.maxTemp, 'integer') }}&deg;\n </span>\n <span class=\"text-20 text-word-3\">\n {{ $n(selectedDay.minTemp, 'integer') }}&deg;\n </span>\n </div>\n </div>\n <div class=\"flex flex-col gap-4 text-12 text-word-3\">\n <div\n v-if=\"selectedDay.precipitationProbabilityMax > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:drop-line\" class=\"text-14\" />\n {{ selectedDay.precipitationProbabilityMax }}% ({{\n selectedDay.precipitationSum\n }}\n mm)\n </div>\n <div\n v-if=\"selectedDay.uvIndexMax > 0\"\n class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-line\" class=\"text-14\" />\n UV {{ $n(selectedDay.uvIndexMax, 'integer') }}\n </div>\n <div class=\"flex items-center gap-6\">\n <VvIcon name=\"ri:sun-foggy-line\" class=\"text-14\" />\n {{ $d(safeDate(selectedDay.sunrise), 'time') }}\n –\n {{ $d(safeDate(selectedDay.sunset), 'time') }}\n </div>\n </div>\n </div>\n\n <!-- Hourly forecast (filtered to selected day) -->\n <transition>\n <div\n v-if=\"filteredHourly.length > 0\"\n class=\"border-t border-surface-3\">\n <div\n class=\"flex overflow-x-auto gap-2 px-sm py-8 light-scrollbar\">\n <div\n v-for=\"hour in filteredHourly\"\n :key=\"hour.time\"\n class=\"flex flex-col items-center gap-4 min-w-48 px-4 py-6 rounded-xl text-11 text-word-3 shrink-0\">\n <span class=\"text-word-3\">{{\n Number.isNaN(safeDate(hour.time).getTime())\n ? hour.time\n : $d(safeDate(hour.time), 'time')\n }}</span>\n <VvIcon\n :name=\"wmoToIcon(hour.weatherCode)\"\n class=\"text-20 text-word-2\" />\n <span class=\"font-medium text-word-1\">\n {{ $n(hour.temperature, 'integer') }}&deg;\n </span>\n <span\n v-if=\"hour.precipitationProbability > 0\"\n class=\"text-word-5 text-smaller\">\n {{ hour.precipitationProbability }}%\n </span>\n </div>\n </div>\n </div>\n </transition>\n\n <!-- Daily forecast (clickable, inside accordion) -->\n <transition>\n <VvAccordion\n v-if=\"data.daily?.length > 1\"\n v-model=\"dailyOpen\"\n modifiers=\"details no-padding\"\n :title=\"$t('weather.dailyForecast')\"\n class=\"border-t border-surface-3\">\n <button\n v-for=\"(day, index) in data.daily\"\n :key=\"day.date\"\n type=\"button\"\n class=\"flex items-center gap-sm px-sm py-8 text-12 w-full text-left transition-colors cursor-pointer border-t border-surface-3\"\n :class=\"\n index === selectedDayIndex\n ? 'bg-surface-2'\n : 'hover:bg-surface-1'\n \"\n @click=\"selectedDayIndex = index\">\n <span class=\"w-48 shrink-0 text-word-3 capitalize\">\n {{ formatDay(day.date, index, data.daily.length) }}\n </span>\n <VvIcon\n :name=\"wmoToIcon(day.weatherCode)\"\n class=\"text-18 text-word-2 shrink-0\" />\n <span\n v-if=\"day.precipitationProbabilityMax > 0\"\n class=\"text-word-5 text-11 w-32 shrink-0\">\n {{ day.precipitationProbabilityMax }}%\n </span>\n <span v-else class=\"w-32 shrink-0\" />\n <div class=\"ml-auto flex items-center gap-8 text-word-3\">\n <span class=\"text-word-1 font-semibold\">\n {{ $n(day.maxTemp, 'integer') }}&deg;\n </span>\n <span>{{ $n(day.minTemp, 'integer') }}&deg;</span>\n </div>\n </button>\n </VvAccordion>\n </transition>\n\n <!-- Air quality -->\n <div\n v-if=\"data.airQuality\"\n class=\"border-t border-surface-3 px-sm py-8 flex items-center gap-8 text-12\">\n <VvIcon name=\"ri:windy-line\" class=\"text-14 text-word-3\" />\n <span class=\"text-word-3\">{{ $t('weather.airQuality') }}</span>\n <span\n :class=\"[\n 'ml-auto px-8 py-2 text-11 font-semibold',\n aqiInfo(data.airQuality.aqi).class,\n ]\">\n {{ aqiInfo(data.airQuality.aqi).label }}\n ({{ data.airQuality.aqi }})\n </span>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  import { C as e, L as t, O as n, Tt as r, _ as i, b as a, bt as o, d as s, g as c, h as l, it as u, m as d, pt as f, v as p, x as m, xt as h, z as g } from "./vue.runtime.esm-bundler-B-NVlZCY.js";
2
2
  import "./src-BBUM0qsk.js";
3
3
  import { n as _ } from "./url-validation-9DstIfq2.js";
4
- import { t as v } from "./index.es-D_E1Gj_U.js";
4
+ import { t as v } from "./index.es-dzrOH5E9.js";
5
5
  import { n as y } from "./chatImagePolicy-BACuW8x3.js";
6
- import { t as b } from "./PkHeldImage-B4gZSfa6.js";
7
- import { t as x } from "./PkLazyImg-DT3U67q-.js";
6
+ import { t as b } from "./PkHeldImage-CEFdfFts.js";
7
+ import { t as x } from "./PkLazyImg-BfqUTZXg.js";
8
8
  import { t as S } from "./PkUrl-Bf9k9TRh.js";
9
9
  //#region ../../packages/components/src/chat/PkToolShowWebPages.vue?vue&type=script&setup=true&lang.ts
10
10
  n(), u(), h();
@@ -83,4 +83,4 @@ var C = {
83
83
  //#endregion
84
84
  export { A as default };
85
85
 
86
- //# sourceMappingURL=PkToolShowWebPages-Bs251x0h.js.map
86
+ //# sourceMappingURL=PkToolShowWebPages-BzH1_SjA.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkToolShowWebPages-Bs251x0h.js","names":["$t","__unplugin_components_0","_openBlock","_createElementBlock","_Fragment","_renderList","_createElementVNode","_unref","_createBlock","PkHeldImage","PkLazyImg","_createCommentVNode","_toDisplayString","_createVNode","PkUrl","_createTextVNode","_sfc_main","defineComponent","openBlock","init_runtime_core_esm_bundler","toDisplayString","createCommentVNode","createTextVNode","unref","Fragment","createBlock","createBaseVNode","init_reactivity_esm_bundler","computed","ref","createElementBlock","createVNode","init_shared_esm_bundler","renderList","isSameSite","Ae","useChatImageGate","PkHeldImage_default","PkLazyImg_default","PkUrl_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","PkToolShowWebPages_default","props","__props","toolPart","imageGate","normalizeUrl","u","url","uniquePages","seen","result","page","key","showMore","pages","hasMore","_component_VvIcon","index","_ctx","_cache"],"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-BzH1_SjA.js","names":["$t","__unplugin_components_0","_openBlock","_createElementBlock","_Fragment","_renderList","_createElementVNode","_unref","_createBlock","PkHeldImage","PkLazyImg","_createCommentVNode","_toDisplayString","_createVNode","PkUrl","_createTextVNode","_sfc_main","defineComponent","openBlock","init_runtime_core_esm_bundler","toDisplayString","createCommentVNode","createTextVNode","unref","Fragment","createBlock","createBaseVNode","init_reactivity_esm_bundler","computed","ref","createElementBlock","createVNode","init_shared_esm_bundler","renderList","isSameSite","Ae","useChatImageGate","PkHeldImage_default","PkLazyImg_default","PkUrl_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","PkToolShowWebPages_default","props","__props","toolPart","imageGate","normalizeUrl","u","url","uniquePages","seen","result","page","key","showMore","pages","hasMore","_component_VvIcon","index","_ctx","_cache"],"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,5 +1,5 @@
1
1
  import { $ as e, B as t, C as n, Ct as r, E as i, F as a, G as o, L as s, O as c, St as l, T as u, Tt as d, X as f, Z as p, _t as m, b as ee, bt as h, ct as g, et as te, h as _, i as ne, it as v, k as y, m as b, pt as re, q as ie, r as x, v as ae, vt as S, x as oe, xt as C, y as se } from "./vue.runtime.esm-bundler-B-NVlZCY.js";
2
- //#region ../../node_modules/.pnpm/@volverjs+ui-vue@0.0.24_@json-render+core@0.19.0_zod@4.6.5__@volverjs+style@0.1.31_@vue_e1ff9dbba6414cbf054525461388e19d/node_modules/@volverjs/ui-vue/dist/components/VvCheckbox/VvCheckbox.es.js
2
+ //#region ../../node_modules/.pnpm/@volverjs+ui-vue@0.0.24_@json-render+core@0.19.0_zod@4.6.5__@volverjs+style@0.1.32_@vue_6cc277eaed9cd8251cbec80c8cf54eca/node_modules/@volverjs/ui-vue/dist/components/VvCheckbox/VvCheckbox.es.js
3
3
  c(), v(), C(), x();
4
4
  var w = /* @__PURE__ */ (function(e) {
5
5
  return e.local = "local", e.session = "session", e;
@@ -530,4 +530,4 @@ var ke = ["for"], Ae = [
530
530
  //#endregion
531
531
  export { Me as default };
532
532
 
533
- //# sourceMappingURL=VvCheckbox.es-DKT8ys4U.js.map
533
+ //# sourceMappingURL=VvCheckbox.es-CGVgFeBD.js.map