@8wave/ai-elements 0.119.0-beta.20 → 0.119.0-beta.22

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 (28) hide show
  1. package/dist/_chunks/PkToolShowWeather-DfLsFayn.js +340 -0
  2. package/dist/_chunks/PkToolShowWeather-DfLsFayn.js.map +1 -0
  3. package/dist/ai-elements.es.js +5 -3
  4. package/dist/ai-elements.es.js.map +1 -1
  5. package/dist-vue/PkChatbot.js +1 -1
  6. package/dist-vue/PkChatbotMessages.js +1 -1
  7. package/dist-vue/PkChatbotViewChat.js +1 -1
  8. package/dist-vue/_chunks/{PkChatbot-CM9ST43I.js → PkChatbot-536vsOfp.js} +2 -2
  9. package/dist-vue/_chunks/{PkChatbot-CM9ST43I.js.map → PkChatbot-536vsOfp.js.map} +1 -1
  10. package/dist-vue/_chunks/{PkChatbotMessages-D9uf0NMI.js → PkChatbotMessages-CnI3e9wF.js} +3 -3
  11. package/dist-vue/_chunks/{PkChatbotMessages-D9uf0NMI.js.map → PkChatbotMessages-CnI3e9wF.js.map} +1 -1
  12. package/dist-vue/_chunks/{PkChatbotViewChat-B_7IdZIz.js → PkChatbotViewChat-CVT6OUIv.js} +3 -3
  13. package/dist-vue/_chunks/{PkChatbotViewChat-B_7IdZIz.js.map → PkChatbotViewChat-CVT6OUIv.js.map} +1 -1
  14. package/dist-vue/_chunks/{PkToolShowDiagram-BJ_ryTsD.js → PkToolShowDiagram-7WxDObZ6.js} +2 -2
  15. package/dist-vue/_chunks/{PkToolShowDiagram-BJ_ryTsD.js.map → PkToolShowDiagram-7WxDObZ6.js.map} +1 -1
  16. package/dist-vue/_chunks/PkToolShowWeather-BOuNrvY2.js +339 -0
  17. package/dist-vue/_chunks/PkToolShowWeather-BOuNrvY2.js.map +1 -0
  18. package/dist-vue/composables.js +4 -2
  19. package/dist-vue/composables.js.map +1 -1
  20. package/dist-vue/index.js +3 -3
  21. package/dist-vue/locales.js +2 -0
  22. package/dist-vue/packages/models/src/schema/AgentApiTool.d.ts +8 -0
  23. package/dist-vue/packages/models/src/schema/Weather.d.ts +7 -0
  24. package/package.json +1 -1
  25. package/dist/_chunks/PkToolShowWeather-BFOlS1AS.js +0 -280
  26. package/dist/_chunks/PkToolShowWeather-BFOlS1AS.js.map +0 -1
  27. package/dist-vue/_chunks/PkToolShowWeather-BH-FtLpk.js +0 -279
  28. package/dist-vue/_chunks/PkToolShowWeather-BH-FtLpk.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { a as e } from "./PkChatbotMessages-D9uf0NMI.js";
1
+ import { a as e } from "./PkChatbotMessages-CnI3e9wF.js";
2
2
  import "./src-DCfvejyT.js";
3
3
  import { t } from "./PkSvgViewer-Ts3gQ1hB.js";
4
4
  import { VvButton as n, VvButtonGroup as r, VvIcon as i } from "@volverjs/ui-vue/components";
@@ -119,4 +119,4 @@ var x = {
119
119
  //#endregion
120
120
  export { k as default };
121
121
 
122
- //# sourceMappingURL=PkToolShowDiagram-BJ_ryTsD.js.map
122
+ //# sourceMappingURL=PkToolShowDiagram-7WxDObZ6.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkToolShowDiagram-BJ_ryTsD.js","names":["__unplugin_components_0","__unplugin_components_1","__unplugin_components_2","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_toDisplayString","_unref","_withCtx","_createBlock","_createCommentVNode","PkSvgViewer","_sfc_main","toSnakeCase","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","DiagramType","\"Sequence\"","\"Class\"","\"State\"","\"ER\"","\"XYChart\"","\"Flowchart\"","\"Graph\"","PkToolShowDiagram_default","defineComponent","props","__props","$t","$te","useI18n","useClipboard","toolPart","computed","svgError","ref","svgContent","diagramType","code","diagramTypeLabel","diagramTypeIcon","renderDiagram","theme","THEMES","renderMermaidSVG","watch","onMounted","downloadSvg","blob","url","anchor","copyCode","copy","_component_VvIcon","VvIcon","_component_VvButton","VvButton","_component_VvButtonGroup","VvButtonGroup","openBlock","createElementBlock","createElementVNode","createVNode","toDisplayString","unref","withCtx","copied","createBlock","createCommentVNode","PkSvgViewer_default"],"sources":["../../../../packages/components/src/chat/PkToolShowDiagram.vue","../../../../packages/components/src/chat/PkToolShowDiagram.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, onMounted, ref, watch } from 'vue'\n import { useClipboard } from '@vueuse/core'\n import { useI18n } from 'vue-i18n'\n import { toSnakeCase } from 'utils'\n import PkSvgViewer from '../PkSvgViewer.vue'\n\n const props = defineProps<{\n part: unknown\n isDark?: boolean\n }>()\n\n const { t: $t, te: $te } = useI18n({\n useScope: 'global',\n })\n\n const { copy, copied } = useClipboard()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n code: string\n title?: string\n caption?: string\n }\n }\n return part\n })\n\n const svgError = ref(false)\n\n // Serialized SVG content\n const svgContent = ref('')\n\n enum DiagramType {\n Sequence = 'sequenceDiagram',\n Class = 'classDiagram',\n State = 'stateDiagram',\n ER = 'erDiagram',\n XYChart = 'xychart',\n Flowchart = 'flowchart',\n Graph = 'graph',\n }\n\n const diagramType = computed<DiagramType | undefined>(() => {\n const code = toolPart.value.input?.code?.trimStart()\n if (!code) {\n return undefined\n }\n const firstWord = code.match(/^[A-Za-z-]+/)?.[0]\n switch (firstWord) {\n case 'sequenceDiagram':\n return DiagramType.Sequence\n case 'classDiagram':\n return DiagramType.Class\n case 'stateDiagram':\n return DiagramType.State\n case 'erDiagram':\n return DiagramType.ER\n case 'xychart-beta':\n return DiagramType.XYChart\n case 'flowchart':\n return DiagramType.Flowchart\n case 'graph':\n return DiagramType.Graph\n default:\n return undefined\n }\n })\n\n const diagramTypeLabel = computed(() => {\n if (!diagramType.value) {\n return ''\n }\n return $te(`diagram.${diagramType.value}`)\n ? $t(`diagram.${diagramType.value}`)\n : diagramType.value\n })\n\n const diagramTypeIcon = computed(() => {\n switch (diagramType.value) {\n case DiagramType.Sequence:\n return 'ri:timeline-view'\n case DiagramType.Class:\n return 'ri:organization-chart'\n case DiagramType.State:\n return 'ri:organization-chart'\n case DiagramType.ER:\n return 'ri:organization-chart'\n case DiagramType.XYChart:\n return 'ri:line-chart-line'\n case DiagramType.Flowchart:\n return 'ri:flow-chart'\n case DiagramType.Graph:\n return 'ri:flow-chart'\n default:\n return 'ri:flow-chart'\n }\n })\n\n async function renderDiagram() {\n const code = toolPart.value.input?.code\n if (!code) {\n return\n }\n\n svgError.value = false\n try {\n const { renderMermaidSVG, THEMES } =\n await import('beautiful-mermaid')\n const theme = props.isDark\n ? THEMES['github-dark']\n : THEMES['github-light']\n svgContent.value = renderMermaidSVG(code, theme)\n } catch {\n svgError.value = true\n }\n }\n\n watch([() => toolPart.value.input?.code, () => props.isDark], renderDiagram)\n onMounted(async () => {\n await renderDiagram()\n })\n\n const downloadSvg = () => {\n if (!svgContent.value) {\n return\n }\n const blob = new Blob([svgContent.value], {\n type: 'image/svg+xml;charset=utf-8',\n })\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = `${toSnakeCase(toolPart.value.input?.title || 'diagram')}.svg`\n document.body.appendChild(anchor)\n anchor.click()\n document.body.removeChild(anchor)\n URL.revokeObjectURL(url)\n }\n\n const copyCode = () => {\n const code = toolPart.value.input?.code\n if (code) {\n copy(code)\n }\n }\n</script>\n\n<template>\n <div\n v-if=\"toolPart?.input?.code\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon :name=\"diagramTypeIcon\" class=\"text-16\" />\n <div class=\"flex flex-col items-start gap-2\">\n <strong class=\"font-bold\">{{\n diagramTypeLabel ?? $t('label.diagram')\n }}</strong>\n </div>\n <VvButtonGroup modifiers=\"compact\" class=\"ml-auto shrink-0\">\n <VvButton\n modifiers=\"action-quiet-small\"\n :icon=\"copied ? 'ri:check-line' : 'ri:file-copy-line'\"\n :label=\"copied ? $t('action.copied') : $t('action.copy')\"\n @click=\"copyCode\" />\n <VvButton\n v-if=\"svgContent\"\n modifiers=\"action-quiet-small\"\n icon=\"ri:download-line\"\n :label=\"$t('action.download')\"\n @click=\"downloadSvg\" />\n </VvButtonGroup>\n </div>\n <div v-if=\"svgError\" class=\"text-12 text-danger py-8 px-sm\">\n {{ $t('message.diagramRenderError') }}\n </div>\n <PkSvgViewer\n v-else-if=\"svgContent\"\n class=\"pk-tool-show-diagram__stage\"\n :svg=\"svgContent\"\n :title=\"toolPart.input?.title\" />\n <div\n v-else\n class=\"pk-tool-show-diagram__stage flex justify-center items-center\">\n <VvIcon name=\"loading-loop\" class=\"w-24 h-24 text-word-5\" />\n </div>\n <div\n v-if=\"toolPart.input.caption\"\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-t border-surface-3 text-word-3\">\n {{ toolPart.input.caption }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-tool-show-diagram {\n // Fixed stage height: the diagram renders lazily (async chunk +\n // dynamic mermaid import), so the loader and the rendered SVG must\n // occupy the same space or the chat scroll position jumps around.\n // The SVG scales to fit; zoom/pan and the lightbox cover the rest.\n &__stage {\n height: 384px;\n\n .pk-svg-viewer__stage,\n .pk-svg-viewer__content {\n height: 100%;\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { computed, onMounted, ref, watch } from 'vue'\n import { useClipboard } from '@vueuse/core'\n import { useI18n } from 'vue-i18n'\n import { toSnakeCase } from 'utils'\n import PkSvgViewer from '../PkSvgViewer.vue'\n\n const props = defineProps<{\n part: unknown\n isDark?: boolean\n }>()\n\n const { t: $t, te: $te } = useI18n({\n useScope: 'global',\n })\n\n const { copy, copied } = useClipboard()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n code: string\n title?: string\n caption?: string\n }\n }\n return part\n })\n\n const svgError = ref(false)\n\n // Serialized SVG content\n const svgContent = ref('')\n\n enum DiagramType {\n Sequence = 'sequenceDiagram',\n Class = 'classDiagram',\n State = 'stateDiagram',\n ER = 'erDiagram',\n XYChart = 'xychart',\n Flowchart = 'flowchart',\n Graph = 'graph',\n }\n\n const diagramType = computed<DiagramType | undefined>(() => {\n const code = toolPart.value.input?.code?.trimStart()\n if (!code) {\n return undefined\n }\n const firstWord = code.match(/^[A-Za-z-]+/)?.[0]\n switch (firstWord) {\n case 'sequenceDiagram':\n return DiagramType.Sequence\n case 'classDiagram':\n return DiagramType.Class\n case 'stateDiagram':\n return DiagramType.State\n case 'erDiagram':\n return DiagramType.ER\n case 'xychart-beta':\n return DiagramType.XYChart\n case 'flowchart':\n return DiagramType.Flowchart\n case 'graph':\n return DiagramType.Graph\n default:\n return undefined\n }\n })\n\n const diagramTypeLabel = computed(() => {\n if (!diagramType.value) {\n return ''\n }\n return $te(`diagram.${diagramType.value}`)\n ? $t(`diagram.${diagramType.value}`)\n : diagramType.value\n })\n\n const diagramTypeIcon = computed(() => {\n switch (diagramType.value) {\n case DiagramType.Sequence:\n return 'ri:timeline-view'\n case DiagramType.Class:\n return 'ri:organization-chart'\n case DiagramType.State:\n return 'ri:organization-chart'\n case DiagramType.ER:\n return 'ri:organization-chart'\n case DiagramType.XYChart:\n return 'ri:line-chart-line'\n case DiagramType.Flowchart:\n return 'ri:flow-chart'\n case DiagramType.Graph:\n return 'ri:flow-chart'\n default:\n return 'ri:flow-chart'\n }\n })\n\n async function renderDiagram() {\n const code = toolPart.value.input?.code\n if (!code) {\n return\n }\n\n svgError.value = false\n try {\n const { renderMermaidSVG, THEMES } =\n await import('beautiful-mermaid')\n const theme = props.isDark\n ? THEMES['github-dark']\n : THEMES['github-light']\n svgContent.value = renderMermaidSVG(code, theme)\n } catch {\n svgError.value = true\n }\n }\n\n watch([() => toolPart.value.input?.code, () => props.isDark], renderDiagram)\n onMounted(async () => {\n await renderDiagram()\n })\n\n const downloadSvg = () => {\n if (!svgContent.value) {\n return\n }\n const blob = new Blob([svgContent.value], {\n type: 'image/svg+xml;charset=utf-8',\n })\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = `${toSnakeCase(toolPart.value.input?.title || 'diagram')}.svg`\n document.body.appendChild(anchor)\n anchor.click()\n document.body.removeChild(anchor)\n URL.revokeObjectURL(url)\n }\n\n const copyCode = () => {\n const code = toolPart.value.input?.code\n if (code) {\n copy(code)\n }\n }\n</script>\n\n<template>\n <div\n v-if=\"toolPart?.input?.code\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon :name=\"diagramTypeIcon\" class=\"text-16\" />\n <div class=\"flex flex-col items-start gap-2\">\n <strong class=\"font-bold\">{{\n diagramTypeLabel ?? $t('label.diagram')\n }}</strong>\n </div>\n <VvButtonGroup modifiers=\"compact\" class=\"ml-auto shrink-0\">\n <VvButton\n modifiers=\"action-quiet-small\"\n :icon=\"copied ? 'ri:check-line' : 'ri:file-copy-line'\"\n :label=\"copied ? $t('action.copied') : $t('action.copy')\"\n @click=\"copyCode\" />\n <VvButton\n v-if=\"svgContent\"\n modifiers=\"action-quiet-small\"\n icon=\"ri:download-line\"\n :label=\"$t('action.download')\"\n @click=\"downloadSvg\" />\n </VvButtonGroup>\n </div>\n <div v-if=\"svgError\" class=\"text-12 text-danger py-8 px-sm\">\n {{ $t('message.diagramRenderError') }}\n </div>\n <PkSvgViewer\n v-else-if=\"svgContent\"\n class=\"pk-tool-show-diagram__stage\"\n :svg=\"svgContent\"\n :title=\"toolPart.input?.title\" />\n <div\n v-else\n class=\"pk-tool-show-diagram__stage flex justify-center items-center\">\n <VvIcon name=\"loading-loop\" class=\"w-24 h-24 text-word-5\" />\n </div>\n <div\n v-if=\"toolPart.input.caption\"\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-t border-surface-3 text-word-3\">\n {{ toolPart.input.caption }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-tool-show-diagram {\n // Fixed stage height: the diagram renders lazily (async chunk +\n // dynamic mermaid import), so the loader and the rendered SVG must\n // occupy the same space or the chat scroll position jumps around.\n // The SVG scales to fit; zoom/pan and the lightbox cover the rest.\n &__stage {\n height: 384px;\n\n .pk-svg-viewer__stage,\n .pk-svg-viewer__content {\n height: 100%;\n }\n }\n }\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PkToolShowDiagram-7WxDObZ6.js","names":["__unplugin_components_0","__unplugin_components_1","__unplugin_components_2","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_toDisplayString","_unref","_withCtx","_createBlock","_createCommentVNode","PkSvgViewer","_sfc_main","toSnakeCase","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","DiagramType","\"Sequence\"","\"Class\"","\"State\"","\"ER\"","\"XYChart\"","\"Flowchart\"","\"Graph\"","PkToolShowDiagram_default","defineComponent","props","__props","$t","$te","useI18n","useClipboard","toolPart","computed","svgError","ref","svgContent","diagramType","code","diagramTypeLabel","diagramTypeIcon","renderDiagram","theme","THEMES","renderMermaidSVG","watch","onMounted","downloadSvg","blob","url","anchor","copyCode","copy","_component_VvIcon","VvIcon","_component_VvButton","VvButton","_component_VvButtonGroup","VvButtonGroup","openBlock","createElementBlock","createElementVNode","createVNode","toDisplayString","unref","withCtx","copied","createBlock","createCommentVNode","PkSvgViewer_default"],"sources":["../../../../packages/components/src/chat/PkToolShowDiagram.vue","../../../../packages/components/src/chat/PkToolShowDiagram.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, onMounted, ref, watch } from 'vue'\n import { useClipboard } from '@vueuse/core'\n import { useI18n } from 'vue-i18n'\n import { toSnakeCase } from 'utils'\n import PkSvgViewer from '../PkSvgViewer.vue'\n\n const props = defineProps<{\n part: unknown\n isDark?: boolean\n }>()\n\n const { t: $t, te: $te } = useI18n({\n useScope: 'global',\n })\n\n const { copy, copied } = useClipboard()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n code: string\n title?: string\n caption?: string\n }\n }\n return part\n })\n\n const svgError = ref(false)\n\n // Serialized SVG content\n const svgContent = ref('')\n\n enum DiagramType {\n Sequence = 'sequenceDiagram',\n Class = 'classDiagram',\n State = 'stateDiagram',\n ER = 'erDiagram',\n XYChart = 'xychart',\n Flowchart = 'flowchart',\n Graph = 'graph',\n }\n\n const diagramType = computed<DiagramType | undefined>(() => {\n const code = toolPart.value.input?.code?.trimStart()\n if (!code) {\n return undefined\n }\n const firstWord = code.match(/^[A-Za-z-]+/)?.[0]\n switch (firstWord) {\n case 'sequenceDiagram':\n return DiagramType.Sequence\n case 'classDiagram':\n return DiagramType.Class\n case 'stateDiagram':\n return DiagramType.State\n case 'erDiagram':\n return DiagramType.ER\n case 'xychart-beta':\n return DiagramType.XYChart\n case 'flowchart':\n return DiagramType.Flowchart\n case 'graph':\n return DiagramType.Graph\n default:\n return undefined\n }\n })\n\n const diagramTypeLabel = computed(() => {\n if (!diagramType.value) {\n return ''\n }\n return $te(`diagram.${diagramType.value}`)\n ? $t(`diagram.${diagramType.value}`)\n : diagramType.value\n })\n\n const diagramTypeIcon = computed(() => {\n switch (diagramType.value) {\n case DiagramType.Sequence:\n return 'ri:timeline-view'\n case DiagramType.Class:\n return 'ri:organization-chart'\n case DiagramType.State:\n return 'ri:organization-chart'\n case DiagramType.ER:\n return 'ri:organization-chart'\n case DiagramType.XYChart:\n return 'ri:line-chart-line'\n case DiagramType.Flowchart:\n return 'ri:flow-chart'\n case DiagramType.Graph:\n return 'ri:flow-chart'\n default:\n return 'ri:flow-chart'\n }\n })\n\n async function renderDiagram() {\n const code = toolPart.value.input?.code\n if (!code) {\n return\n }\n\n svgError.value = false\n try {\n const { renderMermaidSVG, THEMES } =\n await import('beautiful-mermaid')\n const theme = props.isDark\n ? THEMES['github-dark']\n : THEMES['github-light']\n svgContent.value = renderMermaidSVG(code, theme)\n } catch {\n svgError.value = true\n }\n }\n\n watch([() => toolPart.value.input?.code, () => props.isDark], renderDiagram)\n onMounted(async () => {\n await renderDiagram()\n })\n\n const downloadSvg = () => {\n if (!svgContent.value) {\n return\n }\n const blob = new Blob([svgContent.value], {\n type: 'image/svg+xml;charset=utf-8',\n })\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = `${toSnakeCase(toolPart.value.input?.title || 'diagram')}.svg`\n document.body.appendChild(anchor)\n anchor.click()\n document.body.removeChild(anchor)\n URL.revokeObjectURL(url)\n }\n\n const copyCode = () => {\n const code = toolPart.value.input?.code\n if (code) {\n copy(code)\n }\n }\n</script>\n\n<template>\n <div\n v-if=\"toolPart?.input?.code\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon :name=\"diagramTypeIcon\" class=\"text-16\" />\n <div class=\"flex flex-col items-start gap-2\">\n <strong class=\"font-bold\">{{\n diagramTypeLabel ?? $t('label.diagram')\n }}</strong>\n </div>\n <VvButtonGroup modifiers=\"compact\" class=\"ml-auto shrink-0\">\n <VvButton\n modifiers=\"action-quiet-small\"\n :icon=\"copied ? 'ri:check-line' : 'ri:file-copy-line'\"\n :label=\"copied ? $t('action.copied') : $t('action.copy')\"\n @click=\"copyCode\" />\n <VvButton\n v-if=\"svgContent\"\n modifiers=\"action-quiet-small\"\n icon=\"ri:download-line\"\n :label=\"$t('action.download')\"\n @click=\"downloadSvg\" />\n </VvButtonGroup>\n </div>\n <div v-if=\"svgError\" class=\"text-12 text-danger py-8 px-sm\">\n {{ $t('message.diagramRenderError') }}\n </div>\n <PkSvgViewer\n v-else-if=\"svgContent\"\n class=\"pk-tool-show-diagram__stage\"\n :svg=\"svgContent\"\n :title=\"toolPart.input?.title\" />\n <div\n v-else\n class=\"pk-tool-show-diagram__stage flex justify-center items-center\">\n <VvIcon name=\"loading-loop\" class=\"w-24 h-24 text-word-5\" />\n </div>\n <div\n v-if=\"toolPart.input.caption\"\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-t border-surface-3 text-word-3\">\n {{ toolPart.input.caption }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-tool-show-diagram {\n // Fixed stage height: the diagram renders lazily (async chunk +\n // dynamic mermaid import), so the loader and the rendered SVG must\n // occupy the same space or the chat scroll position jumps around.\n // The SVG scales to fit; zoom/pan and the lightbox cover the rest.\n &__stage {\n height: 384px;\n\n .pk-svg-viewer__stage,\n .pk-svg-viewer__content {\n height: 100%;\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { computed, onMounted, ref, watch } from 'vue'\n import { useClipboard } from '@vueuse/core'\n import { useI18n } from 'vue-i18n'\n import { toSnakeCase } from 'utils'\n import PkSvgViewer from '../PkSvgViewer.vue'\n\n const props = defineProps<{\n part: unknown\n isDark?: boolean\n }>()\n\n const { t: $t, te: $te } = useI18n({\n useScope: 'global',\n })\n\n const { copy, copied } = useClipboard()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n code: string\n title?: string\n caption?: string\n }\n }\n return part\n })\n\n const svgError = ref(false)\n\n // Serialized SVG content\n const svgContent = ref('')\n\n enum DiagramType {\n Sequence = 'sequenceDiagram',\n Class = 'classDiagram',\n State = 'stateDiagram',\n ER = 'erDiagram',\n XYChart = 'xychart',\n Flowchart = 'flowchart',\n Graph = 'graph',\n }\n\n const diagramType = computed<DiagramType | undefined>(() => {\n const code = toolPart.value.input?.code?.trimStart()\n if (!code) {\n return undefined\n }\n const firstWord = code.match(/^[A-Za-z-]+/)?.[0]\n switch (firstWord) {\n case 'sequenceDiagram':\n return DiagramType.Sequence\n case 'classDiagram':\n return DiagramType.Class\n case 'stateDiagram':\n return DiagramType.State\n case 'erDiagram':\n return DiagramType.ER\n case 'xychart-beta':\n return DiagramType.XYChart\n case 'flowchart':\n return DiagramType.Flowchart\n case 'graph':\n return DiagramType.Graph\n default:\n return undefined\n }\n })\n\n const diagramTypeLabel = computed(() => {\n if (!diagramType.value) {\n return ''\n }\n return $te(`diagram.${diagramType.value}`)\n ? $t(`diagram.${diagramType.value}`)\n : diagramType.value\n })\n\n const diagramTypeIcon = computed(() => {\n switch (diagramType.value) {\n case DiagramType.Sequence:\n return 'ri:timeline-view'\n case DiagramType.Class:\n return 'ri:organization-chart'\n case DiagramType.State:\n return 'ri:organization-chart'\n case DiagramType.ER:\n return 'ri:organization-chart'\n case DiagramType.XYChart:\n return 'ri:line-chart-line'\n case DiagramType.Flowchart:\n return 'ri:flow-chart'\n case DiagramType.Graph:\n return 'ri:flow-chart'\n default:\n return 'ri:flow-chart'\n }\n })\n\n async function renderDiagram() {\n const code = toolPart.value.input?.code\n if (!code) {\n return\n }\n\n svgError.value = false\n try {\n const { renderMermaidSVG, THEMES } =\n await import('beautiful-mermaid')\n const theme = props.isDark\n ? THEMES['github-dark']\n : THEMES['github-light']\n svgContent.value = renderMermaidSVG(code, theme)\n } catch {\n svgError.value = true\n }\n }\n\n watch([() => toolPart.value.input?.code, () => props.isDark], renderDiagram)\n onMounted(async () => {\n await renderDiagram()\n })\n\n const downloadSvg = () => {\n if (!svgContent.value) {\n return\n }\n const blob = new Blob([svgContent.value], {\n type: 'image/svg+xml;charset=utf-8',\n })\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = `${toSnakeCase(toolPart.value.input?.title || 'diagram')}.svg`\n document.body.appendChild(anchor)\n anchor.click()\n document.body.removeChild(anchor)\n URL.revokeObjectURL(url)\n }\n\n const copyCode = () => {\n const code = toolPart.value.input?.code\n if (code) {\n copy(code)\n }\n }\n</script>\n\n<template>\n <div\n v-if=\"toolPart?.input?.code\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon :name=\"diagramTypeIcon\" class=\"text-16\" />\n <div class=\"flex flex-col items-start gap-2\">\n <strong class=\"font-bold\">{{\n diagramTypeLabel ?? $t('label.diagram')\n }}</strong>\n </div>\n <VvButtonGroup modifiers=\"compact\" class=\"ml-auto shrink-0\">\n <VvButton\n modifiers=\"action-quiet-small\"\n :icon=\"copied ? 'ri:check-line' : 'ri:file-copy-line'\"\n :label=\"copied ? $t('action.copied') : $t('action.copy')\"\n @click=\"copyCode\" />\n <VvButton\n v-if=\"svgContent\"\n modifiers=\"action-quiet-small\"\n icon=\"ri:download-line\"\n :label=\"$t('action.download')\"\n @click=\"downloadSvg\" />\n </VvButtonGroup>\n </div>\n <div v-if=\"svgError\" class=\"text-12 text-danger py-8 px-sm\">\n {{ $t('message.diagramRenderError') }}\n </div>\n <PkSvgViewer\n v-else-if=\"svgContent\"\n class=\"pk-tool-show-diagram__stage\"\n :svg=\"svgContent\"\n :title=\"toolPart.input?.title\" />\n <div\n v-else\n class=\"pk-tool-show-diagram__stage flex justify-center items-center\">\n <VvIcon name=\"loading-loop\" class=\"w-24 h-24 text-word-5\" />\n </div>\n <div\n v-if=\"toolPart.input.caption\"\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-t border-surface-3 text-word-3\">\n {{ toolPart.input.caption }}\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-tool-show-diagram {\n // Fixed stage height: the diagram renders lazily (async chunk +\n // dynamic mermaid import), so the loader and the rendered SVG must\n // occupy the same space or the chat scroll position jumps around.\n // The SVG scales to fit; zoom/pan and the lightbox cover the rest.\n &__stage {\n height: 384px;\n\n .pk-svg-viewer__stage,\n .pk-svg-viewer__content {\n height: 100%;\n }\n }\n }\n</style>\n"],"mappings":"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"}
@@ -0,0 +1,339 @@
1
+ import { S as e, _ as t, o as n, v as r } from "./schemas-Bu1lick6.js";
2
+ import "./src-DCfvejyT.js";
3
+ import { VvAccordion as i, VvIcon as a } from "@volverjs/ui-vue/components";
4
+ import { Fragment as o, Transition as s, computed as c, createBlock as ee, createCommentVNode as l, createElementBlock as u, createElementVNode as d, createTextVNode as f, createVNode as p, defineComponent as m, normalizeClass as h, openBlock as g, ref as _, renderList as v, toDisplayString as y, unref as b, watch as te, withCtx as x } from "vue";
5
+ import { useI18n as ne } from "vue-i18n";
6
+ //#region ../../packages/models/src/schema/Weather.ts
7
+ var S = r({
8
+ temperature: t().describe("Temperature in °C"),
9
+ feelsLike: t().describe("Apparent temperature in °C"),
10
+ humidity: t().describe("Relative humidity in %"),
11
+ precipitation: t().describe("Precipitation in mm"),
12
+ weatherCode: t().describe("WMO weather interpretation code"),
13
+ windSpeed: t().describe("Wind speed in km/h"),
14
+ windDirection: t().describe("Wind direction in degrees"),
15
+ uvIndex: t().describe("UV index")
16
+ }), C = r({
17
+ time: e(),
18
+ temperature: t(),
19
+ precipitationProbability: t(),
20
+ precipitation: t(),
21
+ weatherCode: t(),
22
+ windSpeed: t()
23
+ }), w = r({
24
+ date: e(),
25
+ maxTemp: t(),
26
+ minTemp: t(),
27
+ weatherCode: t(),
28
+ precipitationSum: t(),
29
+ precipitationProbabilityMax: t(),
30
+ uvIndexMax: t(),
31
+ sunrise: e(),
32
+ sunset: e()
33
+ }), T = r({
34
+ aqi: t().describe("European AQI index"),
35
+ pm10: t().describe("PM10 in µg/m³"),
36
+ pm25: t().describe("PM2.5 in µg/m³"),
37
+ no2: t().describe("NO₂ in µg/m³"),
38
+ ozone: t().describe("Ozone in µg/m³")
39
+ });
40
+ r({
41
+ locationName: e(),
42
+ latitude: t(),
43
+ longitude: t(),
44
+ timezone: e(),
45
+ current: S.nullable().describe("Current conditions — null when viewing a future date range"),
46
+ hourly: n(C).describe("Hourly forecast for the requested range"),
47
+ daily: n(w).describe("Daily forecast for the requested range"),
48
+ airQuality: T.nullable()
49
+ });
50
+ function E(e) {
51
+ if (!e || typeof e != "object") return !1;
52
+ let { current: t, hourly: n, daily: r } = e;
53
+ return typeof t?.temperature == "number" || Array.isArray(n) && n.length > 0 || Array.isArray(r) && r.length > 0;
54
+ }
55
+ //#endregion
56
+ //#region ../../packages/utils/src/weather.ts
57
+ function re(e) {
58
+ 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";
59
+ }
60
+ function ie(e) {
61
+ return e <= 20 ? {
62
+ index: 0,
63
+ key: "good"
64
+ } : e <= 40 ? {
65
+ index: 1,
66
+ key: "fair"
67
+ } : e <= 60 ? {
68
+ index: 2,
69
+ key: "moderate"
70
+ } : e <= 80 ? {
71
+ index: 3,
72
+ key: "poor"
73
+ } : e <= 100 ? {
74
+ index: 4,
75
+ key: "veryPoor"
76
+ } : {
77
+ index: 5,
78
+ key: "hazardous"
79
+ };
80
+ }
81
+ //#endregion
82
+ //#region ../../packages/components/src/chat/PkToolShowWeather.vue?vue&type=script&setup=true&lang.ts
83
+ var ae = {
84
+ key: 0,
85
+ class: "text-12 text-danger flex items-center gap-6 py-8"
86
+ }, oe = {
87
+ key: 1,
88
+ class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-surface"
89
+ }, D = { 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" }, se = { class: "font-bold truncate" }, O = { class: "ml-auto shrink-0 capitalize" }, k = {
90
+ key: 0,
91
+ class: "px-sm py-md flex items-center gap-md flex-wrap"
92
+ }, A = { class: "flex items-center gap-8" }, j = { class: "text-48 font-light leading-none text-word-1" }, M = { class: "flex flex-col gap-4 text-12 text-word-3" }, N = { class: "flex items-center gap-6" }, P = { class: "flex items-center gap-6" }, F = { class: "flex items-center gap-6" }, I = {
93
+ key: 0,
94
+ class: "flex items-center gap-6"
95
+ }, L = {
96
+ key: 1,
97
+ class: "px-sm py-md flex items-center gap-md flex-wrap"
98
+ }, R = { class: "flex items-center gap-8" }, z = { class: "flex flex-col leading-none" }, B = { class: "text-48 font-light text-word-1" }, V = { class: "text-20 text-word-3" }, H = { class: "flex flex-col gap-4 text-12 text-word-3" }, U = {
99
+ key: 0,
100
+ class: "flex items-center gap-6"
101
+ }, W = {
102
+ key: 1,
103
+ class: "flex items-center gap-6"
104
+ }, G = { class: "flex items-center gap-6" }, K = {
105
+ key: 0,
106
+ class: "border-t border-surface-3"
107
+ }, ce = { class: "flex overflow-x-auto gap-2 px-sm py-8 light-scrollbar" }, le = { class: "text-word-3" }, ue = { class: "font-medium text-word-1" }, de = {
108
+ key: 0,
109
+ class: "text-word-5 text-smaller"
110
+ }, fe = ["onClick"], pe = { class: "w-48 shrink-0 text-word-3 capitalize" }, me = {
111
+ key: 0,
112
+ class: "text-word-5 text-11 w-32 shrink-0"
113
+ }, he = {
114
+ key: 1,
115
+ class: "w-32 shrink-0"
116
+ }, ge = { class: "ml-auto flex items-center gap-8 text-word-3" }, _e = { class: "text-word-1 font-semibold" }, ve = {
117
+ key: 2,
118
+ class: "border-t border-surface-3 px-sm py-8 flex items-center gap-8 text-12"
119
+ }, ye = { class: "text-word-3" }, q = /* @__PURE__ */ m({
120
+ __name: "PkToolShowWeather",
121
+ props: { part: {} },
122
+ setup(e) {
123
+ let t = e, { t: n, d: r, n: m } = ne({ useScope: "global" }), S = c(() => t.part), C = c(() => {
124
+ let { output: e, input: t } = S.value;
125
+ return E(e) ? e : E(t) ? t : null;
126
+ }), w = c(() => !C.value && S.value.state !== "input-streaming" && S.value.state !== "input-available"), T = _(0), q = _(!C.value?.current);
127
+ te(T, (e) => {
128
+ e > 0 && (q.value = !0);
129
+ });
130
+ let J = c(() => C.value?.daily?.[T.value]), Y = c(() => {
131
+ if (!C.value?.hourly?.length || !J.value) return C.value?.hourly ?? [];
132
+ let e = J.value.date;
133
+ return C.value.hourly.filter((t) => t.time.startsWith(e));
134
+ });
135
+ function X(e) {
136
+ let t = new Date(e);
137
+ if (!Number.isNaN(t.getTime())) return t;
138
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test(e)) {
139
+ let t = new Date(e.replace(" ", "T"));
140
+ if (!Number.isNaN(t.getTime())) return t;
141
+ }
142
+ let n = e.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
143
+ if (n) {
144
+ let e = /* @__PURE__ */ new Date();
145
+ return e.setHours(Number(n[1]), Number(n[2]), Number(n[3] ?? 0), 0), e;
146
+ }
147
+ if (/^\d{4}-\d{2}-\d{2}$/.test(e)) {
148
+ let t = /* @__PURE__ */ new Date(e + "T12:00:00");
149
+ if (!Number.isNaN(t.getTime())) return t;
150
+ }
151
+ return /* @__PURE__ */ new Date(NaN);
152
+ }
153
+ let be = {
154
+ clear: "wi:day-sunny",
155
+ mainlyClear: "wi:day-sunny-overcast",
156
+ partlyCloudy: "wi:day-cloudy",
157
+ overcast: "wi:cloudy",
158
+ fog: "wi:fog",
159
+ drizzle: "wi:sprinkle",
160
+ freezingDrizzle: "wi:sleet",
161
+ rain: "wi:rain",
162
+ freezingRain: "wi:rain-mix",
163
+ snow: "wi:snow",
164
+ rainShowers: "wi:showers",
165
+ snowShowers: "wi:snow-wind",
166
+ thunderstorm: "wi:thunderstorm",
167
+ thunderstormHail: "wi:storm-showers",
168
+ unknown: "wi:na"
169
+ };
170
+ function Z(e) {
171
+ return be[re(e)];
172
+ }
173
+ function Q(e, t, i) {
174
+ let a = X(e + "T12:00:00");
175
+ if (!Number.isNaN(a.getTime())) {
176
+ let e = /* @__PURE__ */ new Date();
177
+ return e.setHours(12, 0, 0, 0), Math.round((a.getTime() - e.getTime()) / 864e5) === 0 ? n("weather.today") : r(a, "weekday-day");
178
+ }
179
+ if (t === 0 && i <= 7) return n("weather.today");
180
+ let o = /* @__PURE__ */ new Date();
181
+ return o.setDate(o.getDate() + t), r(o, "weekday-day");
182
+ }
183
+ let xe = c(() => {
184
+ if (J.value) {
185
+ let e = X(J.value.date + "T12:00:00");
186
+ if (!Number.isNaN(e.getTime())) return Q(J.value.date, T.value, C.value?.daily?.length ?? 1);
187
+ }
188
+ return Q((/* @__PURE__ */ new Date()).toISOString().slice(0, 10), 0, 1);
189
+ }), Se = {
190
+ good: {
191
+ labelKey: "weather.aqiGood",
192
+ class: "text-green-600 bg-green-50"
193
+ },
194
+ fair: {
195
+ labelKey: "weather.aqiFair",
196
+ class: "text-lime-600 bg-lime-50"
197
+ },
198
+ moderate: {
199
+ labelKey: "weather.aqiModerate",
200
+ class: "text-yellow-600 bg-yellow-50"
201
+ },
202
+ poor: {
203
+ labelKey: "weather.aqiPoor",
204
+ class: "text-orange-600 bg-orange-50"
205
+ },
206
+ veryPoor: {
207
+ labelKey: "weather.aqiVeryPoor",
208
+ class: "text-red-600 bg-red-50"
209
+ },
210
+ hazardous: {
211
+ labelKey: "weather.aqiHazardous",
212
+ class: "text-purple-700 bg-purple-50"
213
+ }
214
+ };
215
+ function $(e) {
216
+ let { labelKey: t, class: r } = Se[ie(e).key];
217
+ return {
218
+ label: n(t),
219
+ class: r
220
+ };
221
+ }
222
+ function Ce(e) {
223
+ return [
224
+ "N",
225
+ "NE",
226
+ "E",
227
+ "SE",
228
+ "S",
229
+ "SO",
230
+ "O",
231
+ "NO"
232
+ ][Math.round(e / 45) % 8];
233
+ }
234
+ return (e, t) => {
235
+ let c = a, _ = i;
236
+ return w.value ? (g(), u("div", ae, [p(c, {
237
+ name: "ri:error-warning-line",
238
+ class: "w-16 h-16 shrink-0"
239
+ }), d("span", null, y(b(n)("weather.unavailable")), 1)])) : C.value ? (g(), u("div", oe, [
240
+ d("div", D, [
241
+ p(c, {
242
+ name: "ri:temp-cold-line",
243
+ class: "text-16"
244
+ }),
245
+ d("strong", se, y(C.value.locationName), 1),
246
+ d("span", O, y(xe.value), 1)
247
+ ]),
248
+ C.value.current && T.value === 0 ? (g(), u("div", k, [d("div", A, [p(c, {
249
+ name: Z(C.value.current.weatherCode),
250
+ class: "text-48 text-word-2 shrink-0"
251
+ }, null, 8, ["name"]), d("span", j, y(b(m)(C.value.current.temperature, "integer")) + "° ", 1)]), d("div", M, [
252
+ d("div", N, y(b(n)("weather.feelsLike")) + " " + y(b(m)(C.value.current.feelsLike, "integer")) + "° ", 1),
253
+ d("div", P, [p(c, {
254
+ name: "ri:water-percent-line",
255
+ class: "text-14"
256
+ }), f(" " + y(C.value.current.humidity) + "% ", 1)]),
257
+ d("div", F, [p(c, {
258
+ name: "ri:windy-line",
259
+ class: "text-14"
260
+ }), f(" " + y(b(m)(C.value.current.windSpeed, "integer")) + " km/h " + y(Ce(C.value.current.windDirection)), 1)]),
261
+ C.value.current.uvIndex > 0 ? (g(), u("div", I, [p(c, {
262
+ name: "ri:sun-line",
263
+ class: "text-14"
264
+ }), f(" UV " + y(b(m)(C.value.current.uvIndex, "integer")), 1)])) : l("", !0)
265
+ ])])) : J.value ? (g(), u("div", L, [d("div", R, [p(c, {
266
+ name: Z(J.value.weatherCode),
267
+ class: "text-48 text-word-2 shrink-0"
268
+ }, null, 8, ["name"]), d("div", z, [d("span", B, y(b(m)(J.value.maxTemp, "integer")) + "° ", 1), d("span", V, y(b(m)(J.value.minTemp, "integer")) + "° ", 1)])]), d("div", H, [
269
+ J.value.precipitationProbabilityMax > 0 ? (g(), u("div", U, [p(c, {
270
+ name: "ri:drop-line",
271
+ class: "text-14"
272
+ }), f(" " + y(J.value.precipitationProbabilityMax) + "% (" + y(J.value.precipitationSum) + " mm) ", 1)])) : l("", !0),
273
+ J.value.uvIndexMax > 0 ? (g(), u("div", W, [p(c, {
274
+ name: "ri:sun-line",
275
+ class: "text-14"
276
+ }), f(" UV " + y(b(m)(J.value.uvIndexMax, "integer")), 1)])) : l("", !0),
277
+ d("div", G, [p(c, {
278
+ name: "ri:sun-foggy-line",
279
+ class: "text-14"
280
+ }), f(" " + y(b(r)(X(J.value.sunrise), "time")) + " – " + y(b(r)(X(J.value.sunset), "time")), 1)])
281
+ ])])) : l("", !0),
282
+ p(s, null, {
283
+ default: x(() => [Y.value.length > 0 ? (g(), u("div", K, [d("div", ce, [(g(!0), u(o, null, v(Y.value, (e) => (g(), u("div", {
284
+ key: e.time,
285
+ class: "flex flex-col items-center gap-4 min-w-48 px-4 py-6 rounded-xl text-11 text-word-3 shrink-0"
286
+ }, [
287
+ d("span", le, y(Number.isNaN(X(e.time).getTime()) ? e.time : b(r)(X(e.time), "time")), 1),
288
+ p(c, {
289
+ name: Z(e.weatherCode),
290
+ class: "text-20 text-word-2"
291
+ }, null, 8, ["name"]),
292
+ d("span", ue, y(b(m)(e.temperature, "integer")) + "° ", 1),
293
+ e.precipitationProbability > 0 ? (g(), u("span", de, y(e.precipitationProbability) + "% ", 1)) : l("", !0)
294
+ ]))), 128))])])) : l("", !0)]),
295
+ _: 1
296
+ }),
297
+ p(s, null, {
298
+ default: x(() => [C.value.daily?.length > 1 ? (g(), ee(_, {
299
+ key: 0,
300
+ modelValue: q.value,
301
+ "onUpdate:modelValue": t[0] ||= (e) => q.value = e,
302
+ modifiers: "details no-padding",
303
+ title: b(n)("weather.dailyForecast"),
304
+ class: "border-t border-surface-3"
305
+ }, {
306
+ default: x(() => [(g(!0), u(o, null, v(C.value.daily, (e, t) => (g(), u("button", {
307
+ key: e.date,
308
+ type: "button",
309
+ class: h(["flex items-center gap-sm px-sm py-8 text-12 w-full text-left transition-colors cursor-pointer border-t border-surface-3", t === T.value ? "bg-surface-2" : "hover:bg-surface-1"]),
310
+ onClick: (e) => T.value = t
311
+ }, [
312
+ d("span", pe, y(Q(e.date, t, C.value.daily.length)), 1),
313
+ p(c, {
314
+ name: Z(e.weatherCode),
315
+ class: "text-18 text-word-2 shrink-0"
316
+ }, null, 8, ["name"]),
317
+ e.precipitationProbabilityMax > 0 ? (g(), u("span", me, y(e.precipitationProbabilityMax) + "% ", 1)) : (g(), u("span", he)),
318
+ d("div", ge, [d("span", _e, y(b(m)(e.maxTemp, "integer")) + "° ", 1), d("span", null, y(b(m)(e.minTemp, "integer")) + "°", 1)])
319
+ ], 10, fe))), 128))]),
320
+ _: 1
321
+ }, 8, ["modelValue", "title"])) : l("", !0)]),
322
+ _: 1
323
+ }),
324
+ C.value.airQuality ? (g(), u("div", ve, [
325
+ p(c, {
326
+ name: "ri:windy-line",
327
+ class: "text-14 text-word-3"
328
+ }),
329
+ d("span", ye, y(b(n)("weather.airQuality")), 1),
330
+ d("span", { class: h(["ml-auto px-8 py-2 text-11 font-semibold", $(C.value.airQuality.aqi).class]) }, y($(C.value.airQuality.aqi).label) + " (" + y(C.value.airQuality.aqi) + ") ", 3)
331
+ ])) : l("", !0)
332
+ ])) : l("", !0);
333
+ };
334
+ }
335
+ });
336
+ //#endregion
337
+ export { q as default };
338
+
339
+ //# sourceMappingURL=PkToolShowWeather-BOuNrvY2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PkToolShowWeather-BOuNrvY2.js","names":["z.object","z.number","z.string","z.array","__unplugin_components_0","__unplugin_components_1","_openBlock","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_unref","_createTextVNode","_createCommentVNode","_Transition","_withCtx","_Fragment","_renderList","_createBlock","_normalizeClass","_sfc_main","string","number","array","object","WeatherCurrentSchema","WeatherHourlySchema","WeatherDailySchema","AirQualitySchema","hasWeatherForecast","value","current","hourly","daily","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","_hoisted_35","PkToolShowWeather_default","defineComponent","props","__props","$t","$d","$n","useI18n","toolPart","computed","data","output","input","isUnavailable","selectedDayIndex","ref","dailyOpen","index","selectedDay","filteredHourly","dayDate","h","safeDate","direct","withT","timeOnly","today","noon","WMO_ICON","wmoToIcon","formatDay","parsed","isoDate","total","fallback","headerDate","AQI_PRESENTATION","aqiInfo","cssClass","labelKey","windDirectionLabel","deg","_component_VvIcon","VvIcon","_component_VvAccordion","VvAccordion","openBlock","createElementBlock","createVNode","createElementVNode","toDisplayString","unref","createTextVNode","createCommentVNode","Transition","withCtx","Fragment","renderList","hour","createBlock","_cache","$event","day","normalizeClass"],"sources":["../../../../packages/models/src/schema/Weather.ts","../../../../packages/utils/src/weather.ts","../../../../packages/components/src/chat/PkToolShowWeather.vue","../../../../packages/components/src/chat/PkToolShowWeather.vue"],"sourcesContent":["import { z } from 'zod'\n\n/**\n * The weather data contract shared by the `getWeather`/`showWeather` backend\n * tools, the web widget (`PkToolShowWeather.vue`), and the platform card\n * renderer. Single source of truth — do not redeclare these shapes elsewhere.\n */\n\nconst WeatherCurrentSchema = z.object({\n temperature: z.number().describe('Temperature in °C'),\n feelsLike: z.number().describe('Apparent temperature in °C'),\n humidity: z.number().describe('Relative humidity in %'),\n precipitation: z.number().describe('Precipitation in mm'),\n weatherCode: z.number().describe('WMO weather interpretation code'),\n windSpeed: z.number().describe('Wind speed in km/h'),\n windDirection: z.number().describe('Wind direction in degrees'),\n uvIndex: z.number().describe('UV index'),\n})\n\nconst WeatherHourlySchema = z.object({\n time: z.string(),\n temperature: z.number(),\n precipitationProbability: z.number(),\n precipitation: z.number(),\n weatherCode: z.number(),\n windSpeed: z.number(),\n})\n\nconst WeatherDailySchema = z.object({\n date: z.string(),\n maxTemp: z.number(),\n minTemp: z.number(),\n weatherCode: z.number(),\n precipitationSum: z.number(),\n precipitationProbabilityMax: z.number(),\n uvIndexMax: z.number(),\n sunrise: z.string(),\n sunset: z.string(),\n})\n\nconst AirQualitySchema = z.object({\n aqi: z.number().describe('European AQI index'),\n pm10: z.number().describe('PM10 in µg/m³'),\n pm25: z.number().describe('PM2.5 in µg/m³'),\n no2: z.number().describe('NO₂ in µg/m³'),\n ozone: z.number().describe('Ozone in µg/m³'),\n})\n\nexport const WeatherDataSchema = z.object({\n locationName: z.string(),\n latitude: z.number(),\n longitude: z.number(),\n timezone: z.string(),\n current: WeatherCurrentSchema.nullable().describe(\n 'Current conditions — null when viewing a future date range',\n ),\n hourly: z\n .array(WeatherHourlySchema)\n .describe('Hourly forecast for the requested range'),\n daily: z\n .array(WeatherDailySchema)\n .describe('Daily forecast for the requested range'),\n airQuality: AirQualitySchema.nullable(),\n})\n\nexport type WeatherData = z.infer<typeof WeatherDataSchema>\n\n/**\n * Whether a value carries something the weather card can draw: current\n * conditions, or at least one hourly or daily entry. `showWeather` takes only\n * a `weatherKey`, so its input is a forecast only in conversations from before\n * the key, and a call that failed (a mangled key, expired data) has no output.\n */\nexport function hasWeatherForecast(value: unknown): value is WeatherData {\n if (!value || typeof value !== 'object') {\n return false\n }\n const { current, hourly, daily } = value as Partial<WeatherData>\n return (\n typeof current?.temperature === 'number' ||\n (Array.isArray(hourly) && hourly.length > 0) ||\n (Array.isArray(daily) && daily.length > 0)\n )\n}\n","/**\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 { hasWeatherForecast } 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 {\n state?: string\n input?: unknown\n output?: unknown\n }\n })\n\n const data = computed(() => {\n const { output, input } = toolPart.value\n if (hasWeatherForecast(output)) {\n return output\n }\n // Conversations from before `weatherKey` passed the whole forecast\n return hasWeatherForecast(input) ? input : null\n })\n\n /**\n * The call is over and left nothing to draw: a mangled or expired key\n * fails the tool, and its input is only the key. Without this the card\n * drew a thermometer and \"Oggi\" around nothing. While the call runs there\n * is nothing to draw yet, and nothing has failed either: the card waits.\n */\n const isUnavailable = computed(\n () =>\n !data.value &&\n toolPart.value.state !== 'input-streaming' &&\n toolPart.value.state !== 'input-available',\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=\"isUnavailable\"\n class=\"text-12 text-danger flex items-center gap-6 py-8\">\n <VvIcon name=\"ri:error-warning-line\" class=\"w-16 h-16 shrink-0\" />\n <span>{{ $t('weather.unavailable') }}</span>\n </div>\n <div\n v-else-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 { hasWeatherForecast } 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 {\n state?: string\n input?: unknown\n output?: unknown\n }\n })\n\n const data = computed(() => {\n const { output, input } = toolPart.value\n if (hasWeatherForecast(output)) {\n return output\n }\n // Conversations from before `weatherKey` passed the whole forecast\n return hasWeatherForecast(input) ? input : null\n })\n\n /**\n * The call is over and left nothing to draw: a mangled or expired key\n * fails the tool, and its input is only the key. Without this the card\n * drew a thermometer and \"Oggi\" around nothing. While the call runs there\n * is nothing to draw yet, and nothing has failed either: the card waits.\n */\n const isUnavailable = computed(\n () =>\n !data.value &&\n toolPart.value.state !== 'input-streaming' &&\n toolPart.value.state !== 'input-available',\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=\"isUnavailable\"\n class=\"text-12 text-danger flex items-center gap-6 py-8\">\n <VvIcon name=\"ri:error-warning-line\" class=\"w-16 h-16 shrink-0\" />\n <span>{{ $t('weather.unavailable') }}</span>\n </div>\n <div\n v-else-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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"}
@@ -1721,7 +1721,8 @@ var j = {
1721
1721
  aqiVeryPoor: "Molto scarsa",
1722
1722
  dailyForecast: "Previsioni giornaliere",
1723
1723
  feelsLike: "Percepita",
1724
- today: "Oggi"
1724
+ today: "Oggi",
1725
+ unavailable: "Le previsioni meteo non sono disponibili."
1725
1726
  },
1726
1727
  unsubscribe: {
1727
1728
  title: "Disiscriviti dalle mail",
@@ -3364,7 +3365,8 @@ var j = {
3364
3365
  aqiVeryPoor: "Very poor",
3365
3366
  dailyForecast: "Daily forecast",
3366
3367
  feelsLike: "Feels like",
3367
- today: "Today"
3368
+ today: "Today",
3369
+ unavailable: "The weather forecast is not available."
3368
3370
  },
3369
3371
  unsubscribe: {
3370
3372
  title: "Unsubscribe from these emails",