@8wave/ai-elements 0.111.0-beta.3 → 0.111.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{Agent-Su_LDF9_.js → Agent-tE7ZJPxG.js} +80 -78
- package/dist/_chunks/Agent-tE7ZJPxG.js.map +1 -0
- package/dist/_chunks/{Document-DecVclEe.js → Document-Bmx6774B.js} +33 -21
- package/dist/_chunks/Document-Bmx6774B.js.map +1 -0
- package/dist/_chunks/Media-Cft7Pwrh.js +108 -0
- package/dist/_chunks/Media-Cft7Pwrh.js.map +1 -0
- package/dist/_chunks/{PkLazyImg-ClL4nr95.js → PkLazyImg-Dbj2dDXA.js} +2 -2
- package/dist/_chunks/{PkLazyImg-ClL4nr95.js.map → PkLazyImg-Dbj2dDXA.js.map} +1 -1
- package/dist/_chunks/{PkStreamingMarkdown-CwqsJnJJ.js → PkStreamingMarkdown-CNI8aV8t.js} +636 -427
- package/dist/_chunks/PkStreamingMarkdown-CNI8aV8t.js.map +1 -0
- package/dist/_chunks/{PkToolRequestGeolocation-OokLeTf1.js → PkToolRequestGeolocation-Bzy4IYSp.js} +3 -3
- package/{dist-vue/_chunks/PkToolRequestGeolocation-Bxqz9oSo.js.map → dist/_chunks/PkToolRequestGeolocation-Bzy4IYSp.js.map} +1 -1
- package/dist/_chunks/{PkToolRequestOAuthConnection-CtqKG6Yb.js → PkToolRequestOAuthConnection-CWEKoD71.js} +3 -3
- package/dist/_chunks/{PkToolRequestOAuthConnection-CtqKG6Yb.js.map → PkToolRequestOAuthConnection-CWEKoD71.js.map} +1 -1
- package/dist/_chunks/{PkToolShowArtifact-M6jlSmyJ.js → PkToolShowArtifact-CGbHDEie.js} +4 -4
- package/dist/_chunks/{PkToolShowArtifact-M6jlSmyJ.js.map → PkToolShowArtifact-CGbHDEie.js.map} +1 -1
- package/dist/_chunks/{PkToolShowCalendarEvent-C41JgOUA.js → PkToolShowCalendarEvent-DIFX_rUz.js} +3 -3
- package/dist/_chunks/{PkToolShowCalendarEvent-C41JgOUA.js.map → PkToolShowCalendarEvent-DIFX_rUz.js.map} +1 -1
- package/dist/_chunks/{PkToolShowComparison-DbbUD9pg.js → PkToolShowComparison-B38hHWq2.js} +3 -3
- package/dist/_chunks/{PkToolShowComparison-DbbUD9pg.js.map → PkToolShowComparison-B38hHWq2.js.map} +1 -1
- package/dist/_chunks/{PkToolShowContactForm-D_dW59Dl.js → PkToolShowContactForm-DUw_bZiJ.js} +4 -4
- package/dist/_chunks/{PkToolShowContactForm-D_dW59Dl.js.map → PkToolShowContactForm-DUw_bZiJ.js.map} +1 -1
- package/dist/_chunks/{PkToolShowDiagram-BdmG9gm8.js → PkToolShowDiagram-Dx1Ba0PG.js} +3 -3
- package/dist/_chunks/PkToolShowDiagram-Dx1Ba0PG.js.map +1 -0
- package/dist/_chunks/{PkToolShowEmail-Dn3JHYIR.js → PkToolShowEmail-DnFFgzom.js} +3 -3
- package/dist/_chunks/{PkToolShowEmail-Dn3JHYIR.js.map → PkToolShowEmail-DnFFgzom.js.map} +1 -1
- package/dist/_chunks/{PkToolShowForm-DbJHMDbt.js → PkToolShowForm-CKQQltJ6.js} +6 -7
- package/dist/_chunks/PkToolShowForm-CKQQltJ6.js.map +1 -0
- package/dist/_chunks/{PkToolShowImageGallery-C1lzlO0y.js → PkToolShowImageGallery-D99N_Plf.js} +2 -2
- package/dist/_chunks/{PkToolShowImageGallery-C1lzlO0y.js.map → PkToolShowImageGallery-D99N_Plf.js.map} +1 -1
- package/dist/_chunks/{PkToolShowLocation-CKhPxKFA.js → PkToolShowLocation-AfnrtFVJ.js} +3 -3
- package/dist/_chunks/{PkToolShowLocation-CKhPxKFA.js.map → PkToolShowLocation-AfnrtFVJ.js.map} +1 -1
- package/dist/_chunks/{PkToolShowMessage-U9CCU7eG.js → PkToolShowMessage-DUo2c-im.js} +3 -3
- package/dist/_chunks/{PkToolShowMessage-U9CCU7eG.js.map → PkToolShowMessage-DUo2c-im.js.map} +1 -1
- package/dist/_chunks/{PkToolShowProductList-UeiRMLHu.js → PkToolShowProductList-BN6Qk1_y.js} +3 -3
- package/dist/_chunks/{PkToolShowProductList-UeiRMLHu.js.map → PkToolShowProductList-BN6Qk1_y.js.map} +1 -1
- package/dist/_chunks/{PkToolShowQrCode-CypLrfh5.js → PkToolShowQrCode-BLqeBecX.js} +3 -3
- package/dist/_chunks/{PkToolShowQrCode-CypLrfh5.js.map → PkToolShowQrCode-BLqeBecX.js.map} +1 -1
- package/dist/_chunks/{PkToolShowSources-Dot-W0Mj.js → PkToolShowSources-DpZb5FUP.js} +4 -4
- package/dist/_chunks/{PkToolShowSources-Dot-W0Mj.js.map → PkToolShowSources-DpZb5FUP.js.map} +1 -1
- package/dist/_chunks/{PkToolShowSuggestedReply-CKxSH0Y9.js → PkToolShowSuggestedReply-C10nNgBs.js} +3 -3
- package/dist/_chunks/{PkToolShowSuggestedReply-CKxSH0Y9.js.map → PkToolShowSuggestedReply-C10nNgBs.js.map} +1 -1
- package/dist/_chunks/{PkToolShowVideo-BgKxdBcC.js → PkToolShowVideo-S9b4QHUj.js} +2 -2
- package/dist/_chunks/{PkToolShowVideo-BgKxdBcC.js.map → PkToolShowVideo-S9b4QHUj.js.map} +1 -1
- package/dist/_chunks/{PkToolShowWeather-LPd3tC77.js → PkToolShowWeather-CqhG430w.js} +2 -2
- package/dist/_chunks/PkToolShowWeather-CqhG430w.js.map +1 -0
- package/dist/_chunks/{PkToolShowWebPages-ep_erFF0.js → PkToolShowWebPages-txAZi_iG.js} +3 -3
- package/dist/_chunks/{PkToolShowWebPages-ep_erFF0.js.map → PkToolShowWebPages-txAZi_iG.js.map} +1 -1
- package/dist/_chunks/PkUrl-BPbjZJ1A.js.map +1 -1
- package/dist/_chunks/{VvCheckboxGroup.es-Bdu2gFLN.js → VvCheckboxGroup.es-CTrF3pue.js} +2 -2
- package/dist/_chunks/{VvCheckboxGroup.es-Bdu2gFLN.js.map → VvCheckboxGroup.es-CTrF3pue.js.map} +1 -1
- package/dist/_chunks/{VvCombobox.es-C0wpJIr_.js → VvCombobox.es-BWiXmQ_S.js} +10 -10
- package/dist/_chunks/{VvCombobox.es-C0wpJIr_.js.map → VvCombobox.es-BWiXmQ_S.js.map} +1 -1
- package/dist/_chunks/{VvInputText.es-Zgtw2YNw.js → VvInputText.es-Bo9I6g8H.js} +6 -6
- package/dist/_chunks/{VvInputText.es-Zgtw2YNw.js.map → VvInputText.es-Bo9I6g8H.js.map} +1 -1
- package/dist/_chunks/{VvRadioGroup.es-DZLL19SC.js → VvRadioGroup.es-9ooHCuik.js} +2 -2
- package/dist/_chunks/{VvRadioGroup.es-DZLL19SC.js.map → VvRadioGroup.es-9ooHCuik.js.map} +1 -1
- package/dist/_chunks/{VvSelect.es-ovnZRHG5.js → VvSelect.es-DG0VuWtP.js} +2 -2
- package/dist/_chunks/{VvSelect.es-ovnZRHG5.js.map → VvSelect.es-DG0VuWtP.js.map} +1 -1
- package/dist/_chunks/{VvTextarea.es-DbP5Lt2T.js → VvTextarea.es-Dg3pxdZy.js} +6 -6
- package/dist/_chunks/{VvTextarea.es-DbP5Lt2T.js.map → VvTextarea.es-Dg3pxdZy.js.map} +1 -1
- package/dist/_chunks/{browser-NgY6LXg-.js → browser-B0Xnbqi7.js} +726 -751
- package/dist/_chunks/browser-B0Xnbqi7.js.map +1 -0
- package/dist/_chunks/{dist-n5iFNYf-.js → dist-QdjZA6MJ.js} +143 -133
- package/dist/_chunks/{dist-n5iFNYf-.js.map → dist-QdjZA6MJ.js.map} +1 -1
- package/dist/_chunks/{index.es-dtdl3VFG.js → index.es-0YpCa4c5.js} +6 -6
- package/dist/_chunks/{index.es-dtdl3VFG.js.map → index.es-0YpCa4c5.js.map} +1 -1
- package/dist/_chunks/{index.es-BJnUA_36.js → index.es-CHkJ2TMJ.js} +10 -10
- package/dist/_chunks/{index.es-BJnUA_36.js.map → index.es-CHkJ2TMJ.js.map} +1 -1
- package/dist/_chunks/text-DbSEK8si.js.map +1 -1
- package/dist/ai-elements.es.js +3064 -3094
- package/dist/ai-elements.es.js.map +1 -1
- package/dist-vue/PkChatbot.js +1 -1
- package/dist-vue/PkChatbotEmptyState.js +1 -1
- package/dist-vue/PkChatbotError.js +1 -1
- package/dist-vue/PkChatbotInput.js +1 -1
- package/dist-vue/PkChatbotMessages.js +1 -1
- package/dist-vue/PkChatbotStepPayload.js +1 -1
- package/dist-vue/PkChatbotStepToolDetail.js +1 -1
- package/dist-vue/PkChatbotSteps.js +1 -1
- package/dist-vue/PkChatbotViewChat.js +1 -1
- package/dist-vue/PkChatbotViewConversations.js +1 -1
- package/dist-vue/PkChatbotViewFiles.js +1 -1
- package/dist-vue/PkChatbotViewProfile.js +1 -1
- package/dist-vue/_chunks/{Agent-C8BsTAxk.js → Agent-De2gFNjR.js} +165 -176
- package/dist-vue/_chunks/Agent-De2gFNjR.js.map +1 -0
- package/dist-vue/_chunks/{Document-CaUfI_SZ.js → Document-8umjeTVS.js} +84 -72
- package/dist-vue/_chunks/Document-8umjeTVS.js.map +1 -0
- package/dist-vue/_chunks/Media-_HfFLHGD.js.map +1 -1
- package/dist-vue/_chunks/{PkChatbot-BPKSPIeC.js → PkChatbot-DE1bKmDi.js} +61 -61
- package/dist-vue/_chunks/PkChatbot-DE1bKmDi.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotEmptyState-BvaeBxhx.js → PkChatbotEmptyState-BtzBYOHJ.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotEmptyState-BvaeBxhx.js.map → PkChatbotEmptyState-BtzBYOHJ.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotError-CBP2mf-n.js → PkChatbotError-gvDpRcEz.js} +21 -19
- package/dist-vue/_chunks/PkChatbotError-gvDpRcEz.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotInput-JnHaRlfk.js → PkChatbotInput-MSpF6SB6.js} +139 -124
- package/dist-vue/_chunks/PkChatbotInput-MSpF6SB6.js.map +1 -0
- package/dist-vue/_chunks/PkChatbotMessages-B6ONEJnZ.js +776 -0
- package/dist-vue/_chunks/PkChatbotMessages-B6ONEJnZ.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotStepPayload-BH6piqOi.js → PkChatbotStepPayload-XW4gT9AZ.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotStepPayload-BH6piqOi.js.map → PkChatbotStepPayload-XW4gT9AZ.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotStepToolDetail-BJ7xUvgi.js → PkChatbotStepToolDetail-C4-K6Glw.js} +3 -3
- package/dist-vue/_chunks/{PkChatbotStepToolDetail-BJ7xUvgi.js.map → PkChatbotStepToolDetail-C4-K6Glw.js.map} +1 -1
- package/dist-vue/_chunks/PkChatbotSteps-CrqjWpv0.js +190 -0
- package/dist-vue/_chunks/PkChatbotSteps-CrqjWpv0.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotViewChat-CEqzF1G9.js → PkChatbotViewChat-BMQtx1Gi.js} +10 -10
- package/dist-vue/_chunks/{PkChatbotViewChat-CEqzF1G9.js.map → PkChatbotViewChat-BMQtx1Gi.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewConversations-C787v5ll.js → PkChatbotViewConversations-3LaZPFyw.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewConversations-C787v5ll.js.map → PkChatbotViewConversations-3LaZPFyw.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewFiles-CNFLt8YO.js → PkChatbotViewFiles-CTqnbsCj.js} +3 -3
- package/dist-vue/_chunks/{PkChatbotViewFiles-CNFLt8YO.js.map → PkChatbotViewFiles-CTqnbsCj.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewProfile-7p0ci3qm.js → PkChatbotViewProfile-B-Mp1jGQ.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewProfile-7p0ci3qm.js.map → PkChatbotViewProfile-B-Mp1jGQ.js.map} +1 -1
- package/dist-vue/_chunks/PkRelativeTime--1cIphlA.js.map +1 -1
- package/dist-vue/_chunks/{PkStreamingMarkdown-D8_LwZMR.js → PkStreamingMarkdown-D0IOaETI.js} +432 -419
- package/dist-vue/_chunks/PkStreamingMarkdown-D0IOaETI.js.map +1 -0
- package/dist-vue/_chunks/PkSvgViewer-D-0M5bqg.js.map +1 -1
- package/dist-vue/_chunks/{PkToolRequestGeolocation-Bxqz9oSo.js → PkToolRequestGeolocation-Q3N3jlO8.js} +2 -2
- package/{dist/_chunks/PkToolRequestGeolocation-OokLeTf1.js.map → dist-vue/_chunks/PkToolRequestGeolocation-Q3N3jlO8.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolRequestOAuthConnection-C6rXgb0s.js → PkToolRequestOAuthConnection-C0LW5Evp.js} +2 -2
- package/dist-vue/_chunks/{PkToolRequestOAuthConnection-C6rXgb0s.js.map → PkToolRequestOAuthConnection-C0LW5Evp.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowArtifact-CEXdfOGG.js → PkToolShowArtifact-DHdJuzDi.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowArtifact-CEXdfOGG.js.map → PkToolShowArtifact-DHdJuzDi.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowContactForm-DHmzAIVF.js → PkToolShowContactForm-BB2X4UdQ.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowContactForm-DHmzAIVF.js.map → PkToolShowContactForm-BB2X4UdQ.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowDiagram-dwCVZ1up.js → PkToolShowDiagram-BH6ihikd.js} +2 -2
- package/dist-vue/_chunks/PkToolShowDiagram-BH6ihikd.js.map +1 -0
- package/dist-vue/_chunks/{PkToolShowEmail-CavhsPvi.js → PkToolShowEmail-DF__nKpR.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowEmail-CavhsPvi.js.map → PkToolShowEmail-DF__nKpR.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowForm-CO5A_j9L.js → PkToolShowForm-DUKIyj9V.js} +6 -6
- package/dist-vue/_chunks/PkToolShowForm-DUKIyj9V.js.map +1 -0
- package/dist-vue/_chunks/{PkToolShowMessage-DQojxDiV.js → PkToolShowMessage-Ck6VP-gw.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowMessage-DQojxDiV.js.map → PkToolShowMessage-Ck6VP-gw.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowProductList-Cn0yewM3.js → PkToolShowProductList-zfM8cx-l.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowProductList-Cn0yewM3.js.map → PkToolShowProductList-zfM8cx-l.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowSources-7Q5Iogxi.js → PkToolShowSources-Dlaz2Y77.js} +4 -4
- package/dist-vue/_chunks/{PkToolShowSources-7Q5Iogxi.js.map → PkToolShowSources-Dlaz2Y77.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowSuggestedReply-DQFOVonE.js → PkToolShowSuggestedReply-DWLkUh2L.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowSuggestedReply-DQFOVonE.js.map → PkToolShowSuggestedReply-DWLkUh2L.js.map} +1 -1
- package/dist-vue/_chunks/PkToolShowWeather-Bzqalzh8.js.map +1 -1
- package/dist-vue/_chunks/{PkToolShowWebPages-Bzkkn9GV.js → PkToolShowWebPages-CosBYLrE.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowWebPages-Bzkkn9GV.js.map → PkToolShowWebPages-CosBYLrE.js.map} +1 -1
- package/dist-vue/_chunks/{PkUrl-xUBKVJCv.js → PkUrl-B5CNTQj9.js} +2 -2
- package/dist-vue/_chunks/PkUrl-B5CNTQj9.js.map +1 -0
- package/dist-vue/_chunks/{createChatbotApiClient-D9f69brW.js → createChatbotApiClient-MkaPbGWB.js} +2 -2
- package/dist-vue/_chunks/createChatbotApiClient-MkaPbGWB.js.map +1 -0
- package/dist-vue/_chunks/{useChatbotStore-9Cw_oR6J.js → useChatbotStore-BzfqjiYz.js} +6 -3
- package/dist-vue/_chunks/useChatbotStore-BzfqjiYz.js.map +1 -0
- package/dist-vue/_chunks/useDialogConfirm-KWz0ohCQ.js.map +1 -1
- package/dist-vue/_chunks/{utils-Coqik0XI.js → utils-C1666_6C.js} +58 -43
- package/dist-vue/_chunks/utils-C1666_6C.js.map +1 -0
- package/dist-vue/api.js +1 -1
- package/dist-vue/composables.js +3 -3
- package/dist-vue/index.js +3853 -3485
- package/dist-vue/index.js.map +1 -1
- package/dist-vue/locales.js +36 -8
- package/dist-vue/packages/components/src/chat/PkAgentSettingsPanel.d.ts +20 -3
- package/dist-vue/packages/components/src/chat/PkChatButton.d.ts +3 -2
- package/dist-vue/packages/components/src/chat/PkLanguageModel.d.ts +26 -2
- package/dist-vue/packages/components/src/chat/PkLanguageModelCombobox.d.ts +53 -1
- package/dist-vue/packages/components/src/chat/PkModelOption.d.ts +19 -0
- package/dist-vue/packages/components/src/chat/PkReasoningSettings.d.ts +6 -0
- package/dist-vue/packages/components/src/chat/PkStreamingMarkdown.d.ts +6 -0
- package/dist-vue/packages/components/src/chat/PkStreamingMarkdownAutoscroll.d.ts +1 -0
- package/dist-vue/packages/components/src/chat/constants.d.ts +5 -18
- package/dist-vue/packages/components/src/chat/index.d.ts +3 -1
- package/dist-vue/packages/components/src/chat/modelCatalog.d.ts +46 -0
- package/dist-vue/packages/components/src/chat/utils.d.ts +16 -0
- package/dist-vue/packages/components/src/table/PkTableSortable.d.ts +5 -1
- package/dist-vue/packages/composable/src/chatbot/useChatbotStore.d.ts +18 -0
- package/dist-vue/packages/composable/src/useChatbotError.d.ts +2 -0
- package/dist-vue/packages/models/src/schema/Agent.d.ts +70 -10
- package/dist-vue/packages/models/src/schema/Chat.d.ts +10 -0
- package/dist-vue/packages/models/src/schema/ModelProvider.d.ts +316 -0
- package/dist-vue/packages/models/src/schema/Organization.d.ts +142 -2
- package/dist-vue/packages/models/src/schema/ReasoningChat.d.ts +20 -0
- package/dist-vue/packages/models/src/schema/SubAgent.d.ts +20 -0
- package/dist-vue/packages/models/src/schema/UserOverview.d.ts +41 -0
- package/dist-vue/packages/models/src/schema/constants.d.ts +56 -91
- package/dist-vue/packages/models/src/schema/index.d.ts +1 -0
- package/dist-vue/style.css +1 -1
- package/package.json +1 -1
- package/dist/_chunks/Agent-Su_LDF9_.js.map +0 -1
- package/dist/_chunks/Document-DecVclEe.js.map +0 -1
- package/dist/_chunks/Media-C6SOQfJZ.js +0 -122
- package/dist/_chunks/Media-C6SOQfJZ.js.map +0 -1
- package/dist/_chunks/PkStreamingMarkdown-CwqsJnJJ.js.map +0 -1
- package/dist/_chunks/PkToolShowDiagram-BdmG9gm8.js.map +0 -1
- package/dist/_chunks/PkToolShowForm-DbJHMDbt.js.map +0 -1
- package/dist/_chunks/PkToolShowWeather-LPd3tC77.js.map +0 -1
- package/dist/_chunks/browser-NgY6LXg-.js.map +0 -1
- package/dist/_chunks/utils-YJ0ZOLDG.js +0 -185
- package/dist/_chunks/utils-YJ0ZOLDG.js.map +0 -1
- package/dist-vue/_chunks/Agent-C8BsTAxk.js.map +0 -1
- package/dist-vue/_chunks/Document-CaUfI_SZ.js.map +0 -1
- package/dist-vue/_chunks/PkChatbot-BPKSPIeC.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotError-CBP2mf-n.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotInput-JnHaRlfk.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotMessages-DAlkoMkY.js +0 -771
- package/dist-vue/_chunks/PkChatbotMessages-DAlkoMkY.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotSteps-rommD48p.js +0 -180
- package/dist-vue/_chunks/PkChatbotSteps-rommD48p.js.map +0 -1
- package/dist-vue/_chunks/PkStreamingMarkdown-D8_LwZMR.js.map +0 -1
- package/dist-vue/_chunks/PkToolShowDiagram-dwCVZ1up.js.map +0 -1
- package/dist-vue/_chunks/PkToolShowForm-CO5A_j9L.js.map +0 -1
- package/dist-vue/_chunks/PkUrl-xUBKVJCv.js.map +0 -1
- package/dist-vue/_chunks/createChatbotApiClient-D9f69brW.js.map +0 -1
- package/dist-vue/_chunks/useChatbotStore-9Cw_oR6J.js.map +0 -1
- package/dist-vue/_chunks/utils-Coqik0XI.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PkToolShowDiagram-BdmG9gm8.js","names":[],"sources":["../../../../packages/components/src/PkSvgViewer.vue","../../../../packages/components/src/PkSvgViewer.vue","../../../../packages/components/src/PkSvgViewer.vue","../../../../packages/components/src/chat/PkToolShowDiagram.vue","../../../../packages/components/src/chat/PkToolShowDiagram.vue","../../../../packages/components/src/chat/PkToolShowDiagram.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { ref, onMounted, computed, watch } from 'vue'\n import { useLightbox } from './composables/useLightbox'\n import { useI18n } from 'vue-i18n'\n\n const props = defineProps<{\n svg?: string\n title?: string\n }>()\n\n const { t: $t } = useI18n()\n\n const svgRef = ref<HTMLDivElement | null>(null)\n const galleryRef = ref<HTMLElement>()\n\n const scale = ref(1)\n const translateX = ref(0)\n const translateY = ref(0)\n const startX = ref(0)\n const startY = ref(0)\n\n // PhotoSwipe integration\n const svgDataUrl = ref('')\n const svgWidth = ref(0)\n const svgHeight = ref(0)\n\n const { lightbox, init } = useLightbox({\n gallery: galleryRef,\n downloadable: true,\n openInNewTab: false,\n })\n\n const svgStyle = computed(() => ({\n transform: `translate(${translateX.value}px, ${translateY.value}px) scale(${scale.value})`,\n }))\n\n const zoomIn = () => {\n scale.value = Math.min(scale.value * 1.2, 10)\n }\n\n const zoomOut = () => {\n scale.value = Math.max(scale.value / 1.2, 0.1)\n }\n\n const reset = () => {\n scale.value = 1\n translateX.value = 0\n translateY.value = 0\n }\n\n const openLightbox = () => {\n if (svgDataUrl.value) {\n lightbox.value?.loadAndOpen(0)\n }\n }\n\n const isDragging = ref(false)\n\n // Pan logic\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault()\n isDragging.value = true\n startX.value = e.clientX - translateX.value\n startY.value = e.clientY - translateY.value\n }\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isDragging.value) return\n translateX.value = e.clientX - startX.value\n translateY.value = e.clientY - startY.value\n }\n\n const onMouseUp = () => {\n isDragging.value = false\n }\n\n function serializeSvg(): string | null {\n const svg = svgRef.value?.querySelector('svg')\n if (!svg) return null\n return new XMLSerializer().serializeToString(svg)\n }\n\n function updateSvgExport() {\n const svg = svgRef.value?.querySelector('svg')\n const serialized = serializeSvg()\n if (!svg || !serialized) return\n\n const viewBox = svg.viewBox?.baseVal\n svgWidth.value =\n viewBox?.width || svg.getBoundingClientRect().width || 1200\n svgHeight.value =\n viewBox?.height || svg.getBoundingClientRect().height || 800\n svgDataUrl.value = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(serialized)}`\n }\n\n watch(\n () => props.svg,\n () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n },\n )\n\n onMounted(async () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n await init()\n })\n</script>\n\n<template>\n <div\n class=\"pk-svg-viewer\"\n :class=\"{\n 'pk-svg-viewer--dragging': isDragging,\n }\">\n <!-- Hidden Gallery for Lightbox -->\n <div ref=\"galleryRef\" class=\"hidden\">\n <a\n v-if=\"svgDataUrl\"\n :href=\"svgDataUrl\"\n :data-pswp-src=\"svgDataUrl\"\n :data-pswp-width=\"svgWidth\"\n :data-pswp-height=\"svgHeight\"\n :title=\"title\" />\n </div>\n\n <!-- SVG Stage -->\n <div\n class=\"pk-svg-viewer__stage\"\n @mousedown=\"onMouseDown\"\n @mousemove=\"onMouseMove\"\n @mouseup=\"onMouseUp\"\n @mouseleave=\"onMouseUp\">\n <div\n ref=\"svgRef\"\n :style=\"svgStyle\"\n class=\"pk-svg-viewer__content\" />\n </div>\n\n <!-- Floating Toolbar -->\n <div class=\"pk-svg-viewer__toolbar\">\n <VvButtonGroup\n modifiers=\"compact\"\n class=\"bg-surface-1 shadow-md overflow-hidden\">\n <transition name=\"fade\">\n <VvButton\n key=\"reset\"\n modifiers=\"action-small\"\n :disabled=\"\n scale === 1 && translateX === 0 && translateY === 0\n \"\n icon=\"ri:aspect-ratio-line\"\n :title=\"$t('action.reset')\"\n @click=\"reset\" />\n </transition>\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:zoom-out-line\"\n :title=\"$t('action.zoomOut')\"\n @click=\"zoomOut\" />\n <VvButton\n modifiers=\"action-small\"\n icon=\"ri:zoom-in-line\"\n :title=\"$t('action.zoomIn')\"\n @click=\"zoomIn\" />\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:fullscreen-line\"\n :title=\"$t('action.expand')\"\n @click=\"openLightbox\" />\n </VvButtonGroup>\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-svg-viewer {\n position: relative;\n overflow: hidden;\n background-color: var(--color-surface-2);\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &__content {\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n transition: transform 0.3s ease-out;\n transform-origin: center center;\n\n svg {\n max-width: 100%;\n max-height: 100%;\n height: auto;\n width: auto;\n }\n }\n\n &__toolbar {\n position: absolute;\n top: var(--spacing-8);\n right: var(--spacing-8);\n z-index: var(--z-dropdown);\n opacity: 0;\n transition: var(--transition-opacity);\n }\n\n &__stage {\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n user-select: none;\n }\n\n &:hover {\n .pk-svg-viewer__toolbar {\n opacity: 1;\n }\n }\n\n &--dragging {\n .pk-svg-viewer__toolbar {\n opacity: 0.5 !important;\n pointer-events: none;\n\n & * {\n pointer-events: none;\n }\n }\n\n .pk-svg-viewer__content {\n cursor: grabbing !important;\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { ref, onMounted, computed, watch } from 'vue'\n import { useLightbox } from './composables/useLightbox'\n import { useI18n } from 'vue-i18n'\n\n const props = defineProps<{\n svg?: string\n title?: string\n }>()\n\n const { t: $t } = useI18n()\n\n const svgRef = ref<HTMLDivElement | null>(null)\n const galleryRef = ref<HTMLElement>()\n\n const scale = ref(1)\n const translateX = ref(0)\n const translateY = ref(0)\n const startX = ref(0)\n const startY = ref(0)\n\n // PhotoSwipe integration\n const svgDataUrl = ref('')\n const svgWidth = ref(0)\n const svgHeight = ref(0)\n\n const { lightbox, init } = useLightbox({\n gallery: galleryRef,\n downloadable: true,\n openInNewTab: false,\n })\n\n const svgStyle = computed(() => ({\n transform: `translate(${translateX.value}px, ${translateY.value}px) scale(${scale.value})`,\n }))\n\n const zoomIn = () => {\n scale.value = Math.min(scale.value * 1.2, 10)\n }\n\n const zoomOut = () => {\n scale.value = Math.max(scale.value / 1.2, 0.1)\n }\n\n const reset = () => {\n scale.value = 1\n translateX.value = 0\n translateY.value = 0\n }\n\n const openLightbox = () => {\n if (svgDataUrl.value) {\n lightbox.value?.loadAndOpen(0)\n }\n }\n\n const isDragging = ref(false)\n\n // Pan logic\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault()\n isDragging.value = true\n startX.value = e.clientX - translateX.value\n startY.value = e.clientY - translateY.value\n }\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isDragging.value) return\n translateX.value = e.clientX - startX.value\n translateY.value = e.clientY - startY.value\n }\n\n const onMouseUp = () => {\n isDragging.value = false\n }\n\n function serializeSvg(): string | null {\n const svg = svgRef.value?.querySelector('svg')\n if (!svg) return null\n return new XMLSerializer().serializeToString(svg)\n }\n\n function updateSvgExport() {\n const svg = svgRef.value?.querySelector('svg')\n const serialized = serializeSvg()\n if (!svg || !serialized) return\n\n const viewBox = svg.viewBox?.baseVal\n svgWidth.value =\n viewBox?.width || svg.getBoundingClientRect().width || 1200\n svgHeight.value =\n viewBox?.height || svg.getBoundingClientRect().height || 800\n svgDataUrl.value = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(serialized)}`\n }\n\n watch(\n () => props.svg,\n () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n },\n )\n\n onMounted(async () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n await init()\n })\n</script>\n\n<template>\n <div\n class=\"pk-svg-viewer\"\n :class=\"{\n 'pk-svg-viewer--dragging': isDragging,\n }\">\n <!-- Hidden Gallery for Lightbox -->\n <div ref=\"galleryRef\" class=\"hidden\">\n <a\n v-if=\"svgDataUrl\"\n :href=\"svgDataUrl\"\n :data-pswp-src=\"svgDataUrl\"\n :data-pswp-width=\"svgWidth\"\n :data-pswp-height=\"svgHeight\"\n :title=\"title\" />\n </div>\n\n <!-- SVG Stage -->\n <div\n class=\"pk-svg-viewer__stage\"\n @mousedown=\"onMouseDown\"\n @mousemove=\"onMouseMove\"\n @mouseup=\"onMouseUp\"\n @mouseleave=\"onMouseUp\">\n <div\n ref=\"svgRef\"\n :style=\"svgStyle\"\n class=\"pk-svg-viewer__content\" />\n </div>\n\n <!-- Floating Toolbar -->\n <div class=\"pk-svg-viewer__toolbar\">\n <VvButtonGroup\n modifiers=\"compact\"\n class=\"bg-surface-1 shadow-md overflow-hidden\">\n <transition name=\"fade\">\n <VvButton\n key=\"reset\"\n modifiers=\"action-small\"\n :disabled=\"\n scale === 1 && translateX === 0 && translateY === 0\n \"\n icon=\"ri:aspect-ratio-line\"\n :title=\"$t('action.reset')\"\n @click=\"reset\" />\n </transition>\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:zoom-out-line\"\n :title=\"$t('action.zoomOut')\"\n @click=\"zoomOut\" />\n <VvButton\n modifiers=\"action-small\"\n icon=\"ri:zoom-in-line\"\n :title=\"$t('action.zoomIn')\"\n @click=\"zoomIn\" />\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:fullscreen-line\"\n :title=\"$t('action.expand')\"\n @click=\"openLightbox\" />\n </VvButtonGroup>\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-svg-viewer {\n position: relative;\n overflow: hidden;\n background-color: var(--color-surface-2);\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &__content {\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n transition: transform 0.3s ease-out;\n transform-origin: center center;\n\n svg {\n max-width: 100%;\n max-height: 100%;\n height: auto;\n width: auto;\n }\n }\n\n &__toolbar {\n position: absolute;\n top: var(--spacing-8);\n right: var(--spacing-8);\n z-index: var(--z-dropdown);\n opacity: 0;\n transition: var(--transition-opacity);\n }\n\n &__stage {\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n user-select: none;\n }\n\n &:hover {\n .pk-svg-viewer__toolbar {\n opacity: 1;\n }\n }\n\n &--dragging {\n .pk-svg-viewer__toolbar {\n opacity: 0.5 !important;\n pointer-events: none;\n\n & * {\n pointer-events: none;\n }\n }\n\n .pk-svg-viewer__content {\n cursor: grabbing !important;\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { ref, onMounted, computed, watch } from 'vue'\n import { useLightbox } from './composables/useLightbox'\n import { useI18n } from 'vue-i18n'\n\n const props = defineProps<{\n svg?: string\n title?: string\n }>()\n\n const { t: $t } = useI18n()\n\n const svgRef = ref<HTMLDivElement | null>(null)\n const galleryRef = ref<HTMLElement>()\n\n const scale = ref(1)\n const translateX = ref(0)\n const translateY = ref(0)\n const startX = ref(0)\n const startY = ref(0)\n\n // PhotoSwipe integration\n const svgDataUrl = ref('')\n const svgWidth = ref(0)\n const svgHeight = ref(0)\n\n const { lightbox, init } = useLightbox({\n gallery: galleryRef,\n downloadable: true,\n openInNewTab: false,\n })\n\n const svgStyle = computed(() => ({\n transform: `translate(${translateX.value}px, ${translateY.value}px) scale(${scale.value})`,\n }))\n\n const zoomIn = () => {\n scale.value = Math.min(scale.value * 1.2, 10)\n }\n\n const zoomOut = () => {\n scale.value = Math.max(scale.value / 1.2, 0.1)\n }\n\n const reset = () => {\n scale.value = 1\n translateX.value = 0\n translateY.value = 0\n }\n\n const openLightbox = () => {\n if (svgDataUrl.value) {\n lightbox.value?.loadAndOpen(0)\n }\n }\n\n const isDragging = ref(false)\n\n // Pan logic\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault()\n isDragging.value = true\n startX.value = e.clientX - translateX.value\n startY.value = e.clientY - translateY.value\n }\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isDragging.value) return\n translateX.value = e.clientX - startX.value\n translateY.value = e.clientY - startY.value\n }\n\n const onMouseUp = () => {\n isDragging.value = false\n }\n\n function serializeSvg(): string | null {\n const svg = svgRef.value?.querySelector('svg')\n if (!svg) return null\n return new XMLSerializer().serializeToString(svg)\n }\n\n function updateSvgExport() {\n const svg = svgRef.value?.querySelector('svg')\n const serialized = serializeSvg()\n if (!svg || !serialized) return\n\n const viewBox = svg.viewBox?.baseVal\n svgWidth.value =\n viewBox?.width || svg.getBoundingClientRect().width || 1200\n svgHeight.value =\n viewBox?.height || svg.getBoundingClientRect().height || 800\n svgDataUrl.value = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(serialized)}`\n }\n\n watch(\n () => props.svg,\n () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n },\n )\n\n onMounted(async () => {\n if (props.svg && svgRef.value) {\n svgRef.value.innerHTML = props.svg\n updateSvgExport()\n }\n await init()\n })\n</script>\n\n<template>\n <div\n class=\"pk-svg-viewer\"\n :class=\"{\n 'pk-svg-viewer--dragging': isDragging,\n }\">\n <!-- Hidden Gallery for Lightbox -->\n <div ref=\"galleryRef\" class=\"hidden\">\n <a\n v-if=\"svgDataUrl\"\n :href=\"svgDataUrl\"\n :data-pswp-src=\"svgDataUrl\"\n :data-pswp-width=\"svgWidth\"\n :data-pswp-height=\"svgHeight\"\n :title=\"title\" />\n </div>\n\n <!-- SVG Stage -->\n <div\n class=\"pk-svg-viewer__stage\"\n @mousedown=\"onMouseDown\"\n @mousemove=\"onMouseMove\"\n @mouseup=\"onMouseUp\"\n @mouseleave=\"onMouseUp\">\n <div\n ref=\"svgRef\"\n :style=\"svgStyle\"\n class=\"pk-svg-viewer__content\" />\n </div>\n\n <!-- Floating Toolbar -->\n <div class=\"pk-svg-viewer__toolbar\">\n <VvButtonGroup\n modifiers=\"compact\"\n class=\"bg-surface-1 shadow-md overflow-hidden\">\n <transition name=\"fade\">\n <VvButton\n key=\"reset\"\n modifiers=\"action-small\"\n :disabled=\"\n scale === 1 && translateX === 0 && translateY === 0\n \"\n icon=\"ri:aspect-ratio-line\"\n :title=\"$t('action.reset')\"\n @click=\"reset\" />\n </transition>\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:zoom-out-line\"\n :title=\"$t('action.zoomOut')\"\n @click=\"zoomOut\" />\n <VvButton\n modifiers=\"action-small\"\n icon=\"ri:zoom-in-line\"\n :title=\"$t('action.zoomIn')\"\n @click=\"zoomIn\" />\n <VvButton\n v-if=\"svgDataUrl\"\n modifiers=\"action-small\"\n icon=\"ri:fullscreen-line\"\n :title=\"$t('action.expand')\"\n @click=\"openLightbox\" />\n </VvButtonGroup>\n </div>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-svg-viewer {\n position: relative;\n overflow: hidden;\n background-color: var(--color-surface-2);\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &__content {\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n transition: transform 0.3s ease-out;\n transform-origin: center center;\n\n svg {\n max-width: 100%;\n max-height: 100%;\n height: auto;\n width: auto;\n }\n }\n\n &__toolbar {\n position: absolute;\n top: var(--spacing-8);\n right: var(--spacing-8);\n z-index: var(--z-dropdown);\n opacity: 0;\n transition: var(--transition-opacity);\n }\n\n &__stage {\n width: 100%;\n height: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n user-select: none;\n }\n\n &:hover {\n .pk-svg-viewer__toolbar {\n opacity: 1;\n }\n }\n\n &--dragging {\n .pk-svg-viewer__toolbar {\n opacity: 0.5 !important;\n pointer-events: none;\n\n & * {\n pointer-events: none;\n }\n }\n\n .pk-svg-viewer__content {\n cursor: grabbing !important;\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { 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) return\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) return\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) return\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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PkToolShowForm-DbJHMDbt.js","names":[],"sources":["../../../../packages/components/src/PkEditorRadioButtons.vue","../../../../packages/components/src/PkEditorRadioButtons.vue","../../../../packages/components/src/PkEditorCheckboxButtons.vue","../../../../packages/components/src/PkEditorCheckboxButtons.vue","../../../../packages/components/src/chat/PkToolShowForm.vue","../../../../packages/components/src/chat/PkToolShowForm.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import type { Option } from '@volverjs/ui-vue'\n import { computed, useId } from 'vue'\n\n const props = defineProps<{\n name: string\n label?: string\n hintLabel?: string\n invalid?: boolean\n invalidLabel?: string | string[]\n required?: boolean\n options: Option[]\n readonly?: boolean\n disabled?: boolean\n modifiers?: string | string[]\n /**\n * Render the options as one segmented control (a recessed track with\n * the chosen option raised) instead of a row of boxed cards. Suits a\n * choice between a few short named states, such as a level or a mode.\n */\n segmented?: boolean\n }>()\n\n const modelValue = defineModel<\n string | number | boolean | symbol | unknown[] | undefined\n >({ default: undefined })\n\n // Native radios group by their name attribute across the whole document, so\n // two instances sharing one name become a single group: the browser unchecks\n // the other instance's inputs and Vue never puts them back, because its own\n // model did not change. It happened with \"theme\", offered by the profile\n // menu and the agent settings panel at the same time, and it can happen to\n // any two tool forms with the same step id. Scope the name to the instance.\n const uid = useId()\n const groupName = computed(() => `${props.name}-${uid}`)\n\n const radioModifiers = computed(() => {\n const toReturn = props.modifiers\n ? Array.isArray(props.modifiers)\n ? props.modifiers\n : [props.modifiers]\n : []\n return [...toReturn, props.segmented ? 'segment' : 'button']\n })\n</script>\n\n<template>\n <VvRadioGroup\n v-model=\"modelValue\"\n :name=\"groupName\"\n :label\n :hint-label=\"hintLabel\"\n :invalid\n :invalid-label=\"invalidLabel\"\n :modifiers=\"segmented ? 'segmented' : 'buttons'\">\n <VvRadio\n v-for=\"(option, key) in options\"\n :key\n :name=\"groupName\"\n :required\n :readonly\n :disabled\n :value=\"option.value\"\n :modifiers=\"radioModifiers\">\n <slot :name=\"option.value\" v-bind=\"{ option }\">\n {{ option.label }}\n </slot>\n </VvRadio>\n </VvRadioGroup>\n</template>\n","<script setup lang=\"ts\">\n import type { Option } from '@volverjs/ui-vue'\n import { computed, useId } from 'vue'\n\n const props = defineProps<{\n name: string\n label?: string\n hintLabel?: string\n invalid?: boolean\n invalidLabel?: string | string[]\n required?: boolean\n options: Option[]\n readonly?: boolean\n disabled?: boolean\n modifiers?: string | string[]\n /**\n * Render the options as one segmented control (a recessed track with\n * the chosen option raised) instead of a row of boxed cards. Suits a\n * choice between a few short named states, such as a level or a mode.\n */\n segmented?: boolean\n }>()\n\n const modelValue = defineModel<\n string | number | boolean | symbol | unknown[] | undefined\n >({ default: undefined })\n\n // Native radios group by their name attribute across the whole document, so\n // two instances sharing one name become a single group: the browser unchecks\n // the other instance's inputs and Vue never puts them back, because its own\n // model did not change. It happened with \"theme\", offered by the profile\n // menu and the agent settings panel at the same time, and it can happen to\n // any two tool forms with the same step id. Scope the name to the instance.\n const uid = useId()\n const groupName = computed(() => `${props.name}-${uid}`)\n\n const radioModifiers = computed(() => {\n const toReturn = props.modifiers\n ? Array.isArray(props.modifiers)\n ? props.modifiers\n : [props.modifiers]\n : []\n return [...toReturn, props.segmented ? 'segment' : 'button']\n })\n</script>\n\n<template>\n <VvRadioGroup\n v-model=\"modelValue\"\n :name=\"groupName\"\n :label\n :hint-label=\"hintLabel\"\n :invalid\n :invalid-label=\"invalidLabel\"\n :modifiers=\"segmented ? 'segmented' : 'buttons'\">\n <VvRadio\n v-for=\"(option, key) in options\"\n :key\n :name=\"groupName\"\n :required\n :readonly\n :disabled\n :value=\"option.value\"\n :modifiers=\"radioModifiers\">\n <slot :name=\"option.value\" v-bind=\"{ option }\">\n {{ option.label }}\n </slot>\n </VvRadio>\n </VvRadioGroup>\n</template>\n","<script setup lang=\"ts\">\n import type { Option } from '@volverjs/ui-vue'\n\n defineProps<{\n name: string\n required?: boolean\n options: Option[]\n readonly?: boolean\n disabled?: boolean\n }>()\n\n const modelValue = defineModel<unknown[] | undefined>({\n default: undefined,\n })\n</script>\n\n<template>\n <VvCheckboxGroup v-model=\"modelValue\" :name modifiers=\"buttons\">\n <VvCheckbox\n v-for=\"(option, key) in options\"\n :key\n :name\n :required\n :readonly\n :disabled\n :value=\"option.value\"\n modifiers=\"button\">\n <slot :name=\"String(option.value)\" v-bind=\"{ option }\">\n {{ option.label }}\n </slot>\n </VvCheckbox>\n </VvCheckboxGroup>\n</template>\n","<script setup lang=\"ts\">\n import type { Option } from '@volverjs/ui-vue'\n\n defineProps<{\n name: string\n required?: boolean\n options: Option[]\n readonly?: boolean\n disabled?: boolean\n }>()\n\n const modelValue = defineModel<unknown[] | undefined>({\n default: undefined,\n })\n</script>\n\n<template>\n <VvCheckboxGroup v-model=\"modelValue\" :name modifiers=\"buttons\">\n <VvCheckbox\n v-for=\"(option, key) in options\"\n :key\n :name\n :required\n :readonly\n :disabled\n :value=\"option.value\"\n modifiers=\"button\">\n <slot :name=\"String(option.value)\" v-bind=\"{ option }\">\n {{ option.label }}\n </slot>\n </VvCheckbox>\n </VvCheckboxGroup>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, reactive, watch } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n import { formAnswerSlots, formStepOptions } from './utils'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkEditorRadioButtons from '../PkEditorRadioButtons.vue'\n import PkEditorCheckboxButtons from '../PkEditorCheckboxButtons.vue'\n\n // --- Types ---\n\n interface FormOption {\n label: string\n description?: string\n }\n\n interface FormStep {\n id: string\n question: string\n message?: string\n type?: 'text' | 'number' | 'date'\n options?: FormOption[]\n multiSelect?: boolean\n allowFreeformInput?: boolean\n placeholder?: string\n }\n\n interface FormAnswer {\n id: string\n value: string | string[] | number | null\n }\n\n interface ToolPartShape {\n state?: 'input-available' | 'output-available' | 'output-error'\n toolCallId?: string\n // AI SDK v5 encodes the tool name in `type` (e.g. 'tool-showMultipleChoice').\n // `toolName` is kept for backward compatibility but is normally absent.\n type?: string\n toolName?: string\n input?: {\n title?: string\n steps?: FormStep[]\n // Legacy showMultipleChoice fields\n question?: string\n options?: (string | FormOption)[]\n }\n // The web card submits `{ answers: [...] }`; the platform channels\n // (Teams modal) submit a flat record keyed by step id.\n output?: string | { answers: FormAnswer[] } | Record<string, unknown>\n }\n\n type AnswerValue = string | string[] | number | null\n\n // --- Props & Emits ---\n\n const props = defineProps<{\n part: unknown\n allowCustomAnswer?: boolean\n /**\n * Answered during a call. Nothing in the part says so (the answer went\n * to the agent as speech, not to the tool call), so the surface that\n * sent it is the only one that knows, and without it the same form\n * could be submitted again and again.\n */\n answered?: boolean\n }>()\n\n const emit = defineEmits<{\n select: [answer: string | { answers: FormAnswer[] }]\n }>()\n\n const { t: $t, d: $d } = useI18n({ useScope: 'global' })\n\n // --- Parsed tool part ---\n\n const toolPart = computed(\n () => JSON.parse(JSON.stringify(props.part)) as ToolPartShape,\n )\n\n const isLegacy = computed(\n () =>\n toolPart.value.toolName === 'showMultipleChoice' ||\n toolPart.value.type === 'tool-showMultipleChoice',\n )\n\n /**\n * A form drawn during a CALL is already \"answered\" as far as the tool loop\n * is concerned: the turn could not wait for it, so the backend closed it\n * with a note saying the widget was drawn (see `isVoiceRenderAck`). That\n * note is not an answer, and the caller still has to give one.\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n const isInteractive = computed(\n () =>\n !props.answered &&\n (toolPart.value.state === 'input-available' || isRenderAck.value),\n )\n\n /** Single-step with no custom-answer and no freeform → auto-submit on option pick */\n const isAutoSubmitStep = computed(() => {\n const step = currentStep.value\n if (!step) return false\n return (\n steps.value.length === 1 &&\n !step.allowFreeformInput &&\n !props.allowCustomAnswer\n )\n })\n\n /** Legacy tool should auto-submit on option pick regardless of allowCustomAnswer */\n const isLegacyAutoSubmit = computed(\n () =>\n isLegacy.value &&\n steps.value.length === 1 &&\n !currentStep.value?.multiSelect,\n )\n\n /**\n * True only for the steps that send themselves as soon as an option is\n * picked: the single case where the footer button would be redundant.\n * Every other shape needs it, single-step forms included: with\n * `allowFreeformInput` (or `allowCustomAnswer`) the radio pick no longer\n * auto-submits, so without the footer the user was left with a chosen\n * option and no way to send it.\n */\n const isSelfSubmitting = computed(\n () =>\n Boolean(currentStep.value?.options) &&\n !currentStep.value?.multiSelect &&\n (isAutoSubmitStep.value || isLegacyAutoSubmit.value),\n )\n\n // --- Steps ---\n\n const steps = computed<FormStep[]>(() => {\n const input = toolPart.value.input\n if (input?.steps?.length) {\n return input.steps\n }\n // Legacy showMultipleChoice → normalize into a single step\n if (input?.question && input?.options) {\n return [\n {\n id: 'legacy',\n question: input.question,\n options: input.options.map((o) =>\n typeof o === 'string' ? { label: o } : o,\n ),\n },\n ]\n }\n return []\n })\n\n /**\n * The reason the card has nothing to draw. A call the SDK rejected keeps\n * `state: 'output-error'` and moves the arguments it could not validate to\n * `rawInput`, so `input` is empty and there are no steps: without this the\n * component rendered NOTHING, and the user was left with a question that\n * had been announced and never appeared.\n */\n const hasFailed = computed(\n () => toolPart.value.state === 'output-error' && !steps.value.length,\n )\n\n // --- Navigation state ---\n\n const currentStepIndex = ref(0)\n const currentStep = computed(() => steps.value[currentStepIndex.value])\n const canGoBack = computed(() => currentStepIndex.value > 0)\n const canGoNext = computed(\n () => currentStepIndex.value < steps.value.length - 1,\n )\n const isMultiStep = computed(() => steps.value.length > 1)\n\n const progress = computed(() => {\n if (!isMultiStep.value) return null\n return Math.round(\n ((currentStepIndex.value + 1) / steps.value.length) * 100,\n )\n })\n\n // --- Answers ---\n\n /**\n * One slot PER STEP, keyed by position and not by step id: the ids come\n * from the model and nothing enforces they are unique, and keyed by id two\n * steps sharing one collapsed onto a single answer. See `formAnswerSlots`.\n */\n const answers = reactive<Record<number, AnswerValue>>({})\n const customAnswer = ref('')\n\n // The freeform field belongs to the step showing it: text left behind\n // (unsubmitted, or arriving from a late input update) must not follow the\n // user to the next or previous question.\n watch(currentStepIndex, () => {\n customAnswer.value = ''\n })\n\n // Single scalar answer shared by radio buttons and freeform text/number/date inputs.\n const scalarValue = computed<string | number | undefined>({\n get: () => {\n const val = currentStep.value\n ? answers[currentStepIndex.value]\n : undefined\n if (val == null || Array.isArray(val)) return undefined\n return val\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStepIndex.value] = val ?? null\n }\n },\n })\n\n const activeCheckboxValue = computed<unknown[] | undefined>({\n get: () => {\n const val = currentStep.value\n ? answers[currentStepIndex.value]\n : undefined\n return Array.isArray(val) ? val : []\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStepIndex.value] = (val as string[]) ?? []\n }\n },\n })\n\n // --- Initialize answers from existing output ---\n\n /** The `{ answers: [...] }` list, when the output carries one. */\n function outputAnswers(output: ToolPartShape['output']) {\n if (!output || typeof output === 'string') return undefined\n const list = (output as { answers?: unknown }).answers\n return Array.isArray(list) ? (list as FormAnswer[]) : undefined\n }\n\n watch(\n () => toolPart.value.output,\n (output) => {\n if (!output || isVoiceRenderAck(output)) return\n // Every shape an answered form comes back in (the web card's list,\n // a legacy plain string, the flat record a platform channel posts)\n // lands on the same per-position slots: see `formAnswerSlots`.\n Object.assign(\n answers,\n formAnswerSlots(steps.value, output) as Record<\n number,\n AnswerValue\n >,\n )\n },\n { immediate: true },\n )\n\n // --- Restore custom answer from output when it doesn't match any option ---\n\n const normalizedOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || isVoiceRenderAck(out)) return undefined\n if (typeof out === 'string') return out\n const list = outputAnswers(out)\n if (list?.length === 1) {\n const val = list[0].value\n return Array.isArray(val) ? val.join(', ') : String(val)\n }\n return undefined\n })\n\n watch(\n normalizedOutput,\n (value) => {\n if (!value || !currentStep.value) return\n const optionLabels = (currentStep.value.options ?? []).map(\n (o) => o.label,\n )\n if (!optionLabels.includes(value)) {\n customAnswer.value = value\n }\n },\n { immediate: true },\n )\n\n // --- Option mapping for radio/checkbox components ---\n\n const radioOptions = computed(() => formStepOptions(currentStep.value))\n\n // --- Navigation & Submission ---\n\n /** Footer button, and Enter on a pure freeform step. */\n function goNext() {\n // A typed freeform answer wins over the picked option: it is the more\n // recent intent, and the footer button is now the visible way to send\n // it (the freeform field only submits on Enter).\n if (customAnswer.value.trim()) {\n submitCustomAnswer()\n return\n }\n if (canGoNext.value) {\n currentStepIndex.value++\n } else {\n submitForm()\n }\n }\n\n function goBack() {\n if (canGoBack.value) {\n currentStepIndex.value--\n }\n }\n\n function submitForm() {\n if (!isInteractive.value) return\n\n const finalAnswers: FormAnswer[] = steps.value.map((step, index) => ({\n id: step.id,\n value: answers[index] ?? null,\n }))\n\n // Legacy backward compatibility: single-step, single-select → return plain string\n if (\n isLegacyAutoSubmit.value &&\n !currentStep.value?.allowFreeformInput\n ) {\n const val = finalAnswers[0].value\n emit('select', Array.isArray(val) ? val[0] : (val as string))\n } else {\n emit('select', { answers: finalAnswers })\n }\n }\n\n /** Enter inside the freeform field shown alongside the options. */\n function submitCustomAnswer() {\n const value = customAnswer.value.trim()\n if (!value) return\n\n answers[currentStepIndex.value] = value\n // Cleared before navigating: `goNext()` reads it back to route a typed\n // answer here, so leaving it set would recurse.\n customAnswer.value = ''\n if (steps.value.length === 1) {\n submitForm()\n } else {\n goNext()\n }\n }\n\n function onRadioChange() {\n if (!isInteractive.value) return\n if (isAutoSubmitStep.value || isLegacyAutoSubmit.value) {\n submitForm()\n }\n }\n\n // --- Summary helpers ---\n\n function getSummaryValue(step: FormStep, index: number) {\n const val = answers[index]\n if (val == null) return '—'\n if (Array.isArray(val)) return val.join(', ')\n if (step.type === 'date') return $d(new Date(val), 'short')\n return val\n }\n</script>\n\n<template>\n <div\n v-if=\"hasFailed\"\n class=\"text-12 text-danger flex items-center gap-6 py-8\">\n <span class=\"i-vv-alert-circle text-16 shrink-0\" />\n <span>{{ $t('message.formRenderError') }}</span>\n </div>\n <div\n v-else-if=\"steps.length\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden flex flex-col bg-surface\">\n <!-- Header -->\n <div\n v-if=\"toolPart.input?.title || (isInteractive && steps.length > 1)\"\n class=\"text-12 border-b border-surface-3 text-word-3 bg-surface-1\">\n <div\n v-if=\"toolPart.input?.title\"\n class=\"px-sm py-10 font-bold\"\n :class=\"{\n 'border-b border-surface-3':\n isInteractive && steps.length > 1,\n }\">\n {{ toolPart.input?.title }}\n </div>\n <!-- Progress for multi-step -->\n <div\n v-if=\"isInteractive && steps.length > 1\"\n class=\"flex flex-col gap-4 p-sm\">\n <div class=\"flex justify-between text-10 text-word-3\">\n <span>\n {{\n $t('showForm.step', {\n current: currentStepIndex + 1,\n total: steps.length,\n })\n }}\n </span>\n <span>{{ progress }}%</span>\n </div>\n <div class=\"h-4 bg-surface-3 rounded-full overflow-hidden\">\n <div\n class=\"h-full bg-word-4 transition-all duration-300\"\n :style=\"{ width: `${progress}%` }\"></div>\n </div>\n </div>\n </div>\n\n <!-- Input Available State -->\n <template v-if=\"isInteractive\">\n <!-- Current Step Content -->\n <div class=\"px-sm py-16 flex flex-col gap-12\">\n <div class=\"leading-normal text-12\">\n <strong class=\"font-semibold\">\n {{ currentStep.question }}\n </strong>\n <PkStreamingMarkdown\n v-if=\"currentStep.message\"\n :markdown=\"currentStep.message\"\n class=\"text-word-2 mt-4\" />\n </div>\n\n <!-- Checkbox list for multi-select -->\n <PkEditorCheckboxButtons\n v-if=\"currentStep.options && currentStep.multiSelect\"\n v-model=\"activeCheckboxValue\"\n :name=\"currentStep.id\"\n :options=\"radioOptions\"\n :readonly=\"!isInteractive\"\n class=\"mb-0\"\n vertical>\n <template\n v-for=\"option in radioOptions\"\n :key=\"option.value\"\n #[option.value]>\n <!-- Shared option layout: label + optional description -->\n <div class=\"text-12\">\n <div class=\"font-semibold\">{{ option.label }}</div>\n <div v-if=\"option.description\" class=\"text-word-3\">\n {{ option.description }}\n </div>\n </div>\n </template>\n </PkEditorCheckboxButtons>\n\n <!-- Radio buttons for single-select -->\n <PkEditorRadioButtons\n v-else-if=\"currentStep.options && !currentStep.multiSelect\"\n v-model=\"scalarValue\"\n :name=\"currentStep.id\"\n :options=\"radioOptions\"\n :readonly=\"!isInteractive\"\n vertical\n class=\"mb-0\"\n @update:model-value=\"onRadioChange\">\n <template\n v-for=\"option in radioOptions\"\n :key=\"option.value\"\n #[option.value]>\n <!-- Shared option layout: label + optional description -->\n <div class=\"text-12\">\n <div class=\"font-semibold\">{{ option.label }}</div>\n <div v-if=\"option.description\" class=\"text-word-3\">\n {{ option.description }}\n </div>\n </div>\n </template>\n </PkEditorRadioButtons>\n\n <!-- Pure Freeform Input (text, number, date) -->\n <div\n v-else-if=\"!currentStep.options\"\n class=\"flex flex-col gap-6 w-full\">\n <VvInputText\n :key=\"currentStep.id\"\n v-model=\"scalarValue\"\n :placeholder=\"\n currentStep.placeholder || $t('placeholder.insert')\n \"\n :type=\"currentStep.type\"\n class=\"w-full\"\n :name=\"currentStep.id\"\n modifiers=\"compact\"\n :disabled=\"!isInteractive\"\n @keyup.enter=\"goNext\" />\n </div>\n </div>\n\n <!-- Custom footer freeform input (shown alongside options when allowFreeformInput) -->\n <div\n v-if=\"currentStep.allowFreeformInput && currentStep.options\"\n class=\"px-sm pb-sm border-t border-surface-3\">\n <form\n class=\"flex gap-xs pt-sm\"\n @submit.prevent=\"submitCustomAnswer\">\n <VvInputText\n :key=\"`${currentStep.id}-custom`\"\n v-model=\"customAnswer\"\n class=\"flex-1\"\n :name=\"`${currentStep.id}-custom`\"\n modifiers=\"compact\"\n :disabled=\"!isInteractive\"\n :placeholder=\"\n currentStep.placeholder ||\n $t('showForm.otherPlaceholder')\n \" />\n </form>\n </div>\n\n <!-- Footer navigation: shown for every step that does not submit\n itself on option pick (see `isSelfSubmitting`). -->\n <div\n v-if=\"!isSelfSubmitting\"\n class=\"px-sm py-10 bg-surface border-t border-surface-3 flex justify-between gap-12\">\n <VvButton\n v-if=\"canGoBack\"\n modifiers=\"action-quiet\"\n icon=\"ri:arrow-left-s-line\"\n :label=\"$t('action.back')\"\n @click.stop=\"goBack\" />\n <div v-else></div>\n\n <VvButton\n :modifiers=\"canGoNext ? 'action-quiet' : 'action-primary'\"\n :icon=\"\n canGoNext\n ? 'ri:arrow-right-s-line'\n : 'ri:send-plane-2-line'\n \"\n icon-position=\"right\"\n :label=\"canGoNext ? $t('action.next') : $t('action.submit')\"\n @click.stop=\"goNext\" />\n </div>\n </template>\n\n <!-- Output Available State (Multi-step Summary) -->\n <template v-else>\n <div class=\"px-sm py-12 bg-surface space-y-10\">\n <ul class=\"flex flex-col gap-8\">\n <li\n v-for=\"(step, index) in steps\"\n :key=\"index\"\n class=\"leading-normal\">\n <div class=\"text-12 text-word-3\">\n {{ step.question }}\n </div>\n <span class=\"text-12 text-word-2 font-semibold\">\n {{ getSummaryValue(step, index) }}\n </span>\n </li>\n </ul>\n </div>\n <div\n class=\"px-sm py-12 bg-surface border-t border-surface-3 flex items-center gap-10\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-word-3\" />\n <div class=\"flex flex-col\">\n <span class=\"font-medium text-12 text-word-1 leading-tight\">\n {{ $t('showForm.completed') }}\n </span>\n </div>\n </div>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, reactive, watch } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n import { formAnswerSlots, formStepOptions } from './utils'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkEditorRadioButtons from '../PkEditorRadioButtons.vue'\n import PkEditorCheckboxButtons from '../PkEditorCheckboxButtons.vue'\n\n // --- Types ---\n\n interface FormOption {\n label: string\n description?: string\n }\n\n interface FormStep {\n id: string\n question: string\n message?: string\n type?: 'text' | 'number' | 'date'\n options?: FormOption[]\n multiSelect?: boolean\n allowFreeformInput?: boolean\n placeholder?: string\n }\n\n interface FormAnswer {\n id: string\n value: string | string[] | number | null\n }\n\n interface ToolPartShape {\n state?: 'input-available' | 'output-available' | 'output-error'\n toolCallId?: string\n // AI SDK v5 encodes the tool name in `type` (e.g. 'tool-showMultipleChoice').\n // `toolName` is kept for backward compatibility but is normally absent.\n type?: string\n toolName?: string\n input?: {\n title?: string\n steps?: FormStep[]\n // Legacy showMultipleChoice fields\n question?: string\n options?: (string | FormOption)[]\n }\n // The web card submits `{ answers: [...] }`; the platform channels\n // (Teams modal) submit a flat record keyed by step id.\n output?: string | { answers: FormAnswer[] } | Record<string, unknown>\n }\n\n type AnswerValue = string | string[] | number | null\n\n // --- Props & Emits ---\n\n const props = defineProps<{\n part: unknown\n allowCustomAnswer?: boolean\n /**\n * Answered during a call. Nothing in the part says so (the answer went\n * to the agent as speech, not to the tool call), so the surface that\n * sent it is the only one that knows, and without it the same form\n * could be submitted again and again.\n */\n answered?: boolean\n }>()\n\n const emit = defineEmits<{\n select: [answer: string | { answers: FormAnswer[] }]\n }>()\n\n const { t: $t, d: $d } = useI18n({ useScope: 'global' })\n\n // --- Parsed tool part ---\n\n const toolPart = computed(\n () => JSON.parse(JSON.stringify(props.part)) as ToolPartShape,\n )\n\n const isLegacy = computed(\n () =>\n toolPart.value.toolName === 'showMultipleChoice' ||\n toolPart.value.type === 'tool-showMultipleChoice',\n )\n\n /**\n * A form drawn during a CALL is already \"answered\" as far as the tool loop\n * is concerned: the turn could not wait for it, so the backend closed it\n * with a note saying the widget was drawn (see `isVoiceRenderAck`). That\n * note is not an answer, and the caller still has to give one.\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n const isInteractive = computed(\n () =>\n !props.answered &&\n (toolPart.value.state === 'input-available' || isRenderAck.value),\n )\n\n /** Single-step with no custom-answer and no freeform → auto-submit on option pick */\n const isAutoSubmitStep = computed(() => {\n const step = currentStep.value\n if (!step) return false\n return (\n steps.value.length === 1 &&\n !step.allowFreeformInput &&\n !props.allowCustomAnswer\n )\n })\n\n /** Legacy tool should auto-submit on option pick regardless of allowCustomAnswer */\n const isLegacyAutoSubmit = computed(\n () =>\n isLegacy.value &&\n steps.value.length === 1 &&\n !currentStep.value?.multiSelect,\n )\n\n /**\n * True only for the steps that send themselves as soon as an option is\n * picked: the single case where the footer button would be redundant.\n * Every other shape needs it, single-step forms included: with\n * `allowFreeformInput` (or `allowCustomAnswer`) the radio pick no longer\n * auto-submits, so without the footer the user was left with a chosen\n * option and no way to send it.\n */\n const isSelfSubmitting = computed(\n () =>\n Boolean(currentStep.value?.options) &&\n !currentStep.value?.multiSelect &&\n (isAutoSubmitStep.value || isLegacyAutoSubmit.value),\n )\n\n // --- Steps ---\n\n const steps = computed<FormStep[]>(() => {\n const input = toolPart.value.input\n if (input?.steps?.length) {\n return input.steps\n }\n // Legacy showMultipleChoice → normalize into a single step\n if (input?.question && input?.options) {\n return [\n {\n id: 'legacy',\n question: input.question,\n options: input.options.map((o) =>\n typeof o === 'string' ? { label: o } : o,\n ),\n },\n ]\n }\n return []\n })\n\n /**\n * The reason the card has nothing to draw. A call the SDK rejected keeps\n * `state: 'output-error'` and moves the arguments it could not validate to\n * `rawInput`, so `input` is empty and there are no steps: without this the\n * component rendered NOTHING, and the user was left with a question that\n * had been announced and never appeared.\n */\n const hasFailed = computed(\n () => toolPart.value.state === 'output-error' && !steps.value.length,\n )\n\n // --- Navigation state ---\n\n const currentStepIndex = ref(0)\n const currentStep = computed(() => steps.value[currentStepIndex.value])\n const canGoBack = computed(() => currentStepIndex.value > 0)\n const canGoNext = computed(\n () => currentStepIndex.value < steps.value.length - 1,\n )\n const isMultiStep = computed(() => steps.value.length > 1)\n\n const progress = computed(() => {\n if (!isMultiStep.value) return null\n return Math.round(\n ((currentStepIndex.value + 1) / steps.value.length) * 100,\n )\n })\n\n // --- Answers ---\n\n /**\n * One slot PER STEP, keyed by position and not by step id: the ids come\n * from the model and nothing enforces they are unique, and keyed by id two\n * steps sharing one collapsed onto a single answer. See `formAnswerSlots`.\n */\n const answers = reactive<Record<number, AnswerValue>>({})\n const customAnswer = ref('')\n\n // The freeform field belongs to the step showing it: text left behind\n // (unsubmitted, or arriving from a late input update) must not follow the\n // user to the next or previous question.\n watch(currentStepIndex, () => {\n customAnswer.value = ''\n })\n\n // Single scalar answer shared by radio buttons and freeform text/number/date inputs.\n const scalarValue = computed<string | number | undefined>({\n get: () => {\n const val = currentStep.value\n ? answers[currentStepIndex.value]\n : undefined\n if (val == null || Array.isArray(val)) return undefined\n return val\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStepIndex.value] = val ?? null\n }\n },\n })\n\n const activeCheckboxValue = computed<unknown[] | undefined>({\n get: () => {\n const val = currentStep.value\n ? answers[currentStepIndex.value]\n : undefined\n return Array.isArray(val) ? val : []\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStepIndex.value] = (val as string[]) ?? []\n }\n },\n })\n\n // --- Initialize answers from existing output ---\n\n /** The `{ answers: [...] }` list, when the output carries one. */\n function outputAnswers(output: ToolPartShape['output']) {\n if (!output || typeof output === 'string') return undefined\n const list = (output as { answers?: unknown }).answers\n return Array.isArray(list) ? (list as FormAnswer[]) : undefined\n }\n\n watch(\n () => toolPart.value.output,\n (output) => {\n if (!output || isVoiceRenderAck(output)) return\n // Every shape an answered form comes back in (the web card's list,\n // a legacy plain string, the flat record a platform channel posts)\n // lands on the same per-position slots: see `formAnswerSlots`.\n Object.assign(\n answers,\n formAnswerSlots(steps.value, output) as Record<\n number,\n AnswerValue\n >,\n )\n },\n { immediate: true },\n )\n\n // --- Restore custom answer from output when it doesn't match any option ---\n\n const normalizedOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || isVoiceRenderAck(out)) return undefined\n if (typeof out === 'string') return out\n const list = outputAnswers(out)\n if (list?.length === 1) {\n const val = list[0].value\n return Array.isArray(val) ? val.join(', ') : String(val)\n }\n return undefined\n })\n\n watch(\n normalizedOutput,\n (value) => {\n if (!value || !currentStep.value) return\n const optionLabels = (currentStep.value.options ?? []).map(\n (o) => o.label,\n )\n if (!optionLabels.includes(value)) {\n customAnswer.value = value\n }\n },\n { immediate: true },\n )\n\n // --- Option mapping for radio/checkbox components ---\n\n const radioOptions = computed(() => formStepOptions(currentStep.value))\n\n // --- Navigation & Submission ---\n\n /** Footer button, and Enter on a pure freeform step. */\n function goNext() {\n // A typed freeform answer wins over the picked option: it is the more\n // recent intent, and the footer button is now the visible way to send\n // it (the freeform field only submits on Enter).\n if (customAnswer.value.trim()) {\n submitCustomAnswer()\n return\n }\n if (canGoNext.value) {\n currentStepIndex.value++\n } else {\n submitForm()\n }\n }\n\n function goBack() {\n if (canGoBack.value) {\n currentStepIndex.value--\n }\n }\n\n function submitForm() {\n if (!isInteractive.value) return\n\n const finalAnswers: FormAnswer[] = steps.value.map((step, index) => ({\n id: step.id,\n value: answers[index] ?? null,\n }))\n\n // Legacy backward compatibility: single-step, single-select → return plain string\n if (\n isLegacyAutoSubmit.value &&\n !currentStep.value?.allowFreeformInput\n ) {\n const val = finalAnswers[0].value\n emit('select', Array.isArray(val) ? val[0] : (val as string))\n } else {\n emit('select', { answers: finalAnswers })\n }\n }\n\n /** Enter inside the freeform field shown alongside the options. */\n function submitCustomAnswer() {\n const value = customAnswer.value.trim()\n if (!value) return\n\n answers[currentStepIndex.value] = value\n // Cleared before navigating: `goNext()` reads it back to route a typed\n // answer here, so leaving it set would recurse.\n customAnswer.value = ''\n if (steps.value.length === 1) {\n submitForm()\n } else {\n goNext()\n }\n }\n\n function onRadioChange() {\n if (!isInteractive.value) return\n if (isAutoSubmitStep.value || isLegacyAutoSubmit.value) {\n submitForm()\n }\n }\n\n // --- Summary helpers ---\n\n function getSummaryValue(step: FormStep, index: number) {\n const val = answers[index]\n if (val == null) return '—'\n if (Array.isArray(val)) return val.join(', ')\n if (step.type === 'date') return $d(new Date(val), 'short')\n return val\n }\n</script>\n\n<template>\n <div\n v-if=\"hasFailed\"\n class=\"text-12 text-danger flex items-center gap-6 py-8\">\n <span class=\"i-vv-alert-circle text-16 shrink-0\" />\n <span>{{ $t('message.formRenderError') }}</span>\n </div>\n <div\n v-else-if=\"steps.length\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden flex flex-col bg-surface\">\n <!-- Header -->\n <div\n v-if=\"toolPart.input?.title || (isInteractive && steps.length > 1)\"\n class=\"text-12 border-b border-surface-3 text-word-3 bg-surface-1\">\n <div\n v-if=\"toolPart.input?.title\"\n class=\"px-sm py-10 font-bold\"\n :class=\"{\n 'border-b border-surface-3':\n isInteractive && steps.length > 1,\n }\">\n {{ toolPart.input?.title }}\n </div>\n <!-- Progress for multi-step -->\n <div\n v-if=\"isInteractive && steps.length > 1\"\n class=\"flex flex-col gap-4 p-sm\">\n <div class=\"flex justify-between text-10 text-word-3\">\n <span>\n {{\n $t('showForm.step', {\n current: currentStepIndex + 1,\n total: steps.length,\n })\n }}\n </span>\n <span>{{ progress }}%</span>\n </div>\n <div class=\"h-4 bg-surface-3 rounded-full overflow-hidden\">\n <div\n class=\"h-full bg-word-4 transition-all duration-300\"\n :style=\"{ width: `${progress}%` }\"></div>\n </div>\n </div>\n </div>\n\n <!-- Input Available State -->\n <template v-if=\"isInteractive\">\n <!-- Current Step Content -->\n <div class=\"px-sm py-16 flex flex-col gap-12\">\n <div class=\"leading-normal text-12\">\n <strong class=\"font-semibold\">\n {{ currentStep.question }}\n </strong>\n <PkStreamingMarkdown\n v-if=\"currentStep.message\"\n :markdown=\"currentStep.message\"\n class=\"text-word-2 mt-4\" />\n </div>\n\n <!-- Checkbox list for multi-select -->\n <PkEditorCheckboxButtons\n v-if=\"currentStep.options && currentStep.multiSelect\"\n v-model=\"activeCheckboxValue\"\n :name=\"currentStep.id\"\n :options=\"radioOptions\"\n :readonly=\"!isInteractive\"\n class=\"mb-0\"\n vertical>\n <template\n v-for=\"option in radioOptions\"\n :key=\"option.value\"\n #[option.value]>\n <!-- Shared option layout: label + optional description -->\n <div class=\"text-12\">\n <div class=\"font-semibold\">{{ option.label }}</div>\n <div v-if=\"option.description\" class=\"text-word-3\">\n {{ option.description }}\n </div>\n </div>\n </template>\n </PkEditorCheckboxButtons>\n\n <!-- Radio buttons for single-select -->\n <PkEditorRadioButtons\n v-else-if=\"currentStep.options && !currentStep.multiSelect\"\n v-model=\"scalarValue\"\n :name=\"currentStep.id\"\n :options=\"radioOptions\"\n :readonly=\"!isInteractive\"\n vertical\n class=\"mb-0\"\n @update:model-value=\"onRadioChange\">\n <template\n v-for=\"option in radioOptions\"\n :key=\"option.value\"\n #[option.value]>\n <!-- Shared option layout: label + optional description -->\n <div class=\"text-12\">\n <div class=\"font-semibold\">{{ option.label }}</div>\n <div v-if=\"option.description\" class=\"text-word-3\">\n {{ option.description }}\n </div>\n </div>\n </template>\n </PkEditorRadioButtons>\n\n <!-- Pure Freeform Input (text, number, date) -->\n <div\n v-else-if=\"!currentStep.options\"\n class=\"flex flex-col gap-6 w-full\">\n <VvInputText\n :key=\"currentStep.id\"\n v-model=\"scalarValue\"\n :placeholder=\"\n currentStep.placeholder || $t('placeholder.insert')\n \"\n :type=\"currentStep.type\"\n class=\"w-full\"\n :name=\"currentStep.id\"\n modifiers=\"compact\"\n :disabled=\"!isInteractive\"\n @keyup.enter=\"goNext\" />\n </div>\n </div>\n\n <!-- Custom footer freeform input (shown alongside options when allowFreeformInput) -->\n <div\n v-if=\"currentStep.allowFreeformInput && currentStep.options\"\n class=\"px-sm pb-sm border-t border-surface-3\">\n <form\n class=\"flex gap-xs pt-sm\"\n @submit.prevent=\"submitCustomAnswer\">\n <VvInputText\n :key=\"`${currentStep.id}-custom`\"\n v-model=\"customAnswer\"\n class=\"flex-1\"\n :name=\"`${currentStep.id}-custom`\"\n modifiers=\"compact\"\n :disabled=\"!isInteractive\"\n :placeholder=\"\n currentStep.placeholder ||\n $t('showForm.otherPlaceholder')\n \" />\n </form>\n </div>\n\n <!-- Footer navigation: shown for every step that does not submit\n itself on option pick (see `isSelfSubmitting`). -->\n <div\n v-if=\"!isSelfSubmitting\"\n class=\"px-sm py-10 bg-surface border-t border-surface-3 flex justify-between gap-12\">\n <VvButton\n v-if=\"canGoBack\"\n modifiers=\"action-quiet\"\n icon=\"ri:arrow-left-s-line\"\n :label=\"$t('action.back')\"\n @click.stop=\"goBack\" />\n <div v-else></div>\n\n <VvButton\n :modifiers=\"canGoNext ? 'action-quiet' : 'action-primary'\"\n :icon=\"\n canGoNext\n ? 'ri:arrow-right-s-line'\n : 'ri:send-plane-2-line'\n \"\n icon-position=\"right\"\n :label=\"canGoNext ? $t('action.next') : $t('action.submit')\"\n @click.stop=\"goNext\" />\n </div>\n </template>\n\n <!-- Output Available State (Multi-step Summary) -->\n <template v-else>\n <div class=\"px-sm py-12 bg-surface space-y-10\">\n <ul class=\"flex flex-col gap-8\">\n <li\n v-for=\"(step, index) in steps\"\n :key=\"index\"\n class=\"leading-normal\">\n <div class=\"text-12 text-word-3\">\n {{ step.question }}\n </div>\n <span class=\"text-12 text-word-2 font-semibold\">\n {{ getSummaryValue(step, index) }}\n </span>\n </li>\n </ul>\n </div>\n <div\n class=\"px-sm py-12 bg-surface border-t border-surface-3 flex items-center gap-10\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-word-3\" />\n <div class=\"flex flex-col\">\n <span class=\"font-medium text-12 text-word-1 leading-tight\">\n {{ $t('showForm.completed') }}\n </span>\n </div>\n </div>\n </template>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PkToolShowWeather-LPd3tC77.js","names":[],"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) return 'unknown'\n if (code === 0) return 'clear'\n if (code === 1) return 'mainlyClear'\n if (code === 2) return 'partlyCloudy'\n if (code === 3) return 'overcast'\n if (code === 45 || code === 48) return 'fog'\n if (code >= 51 && code <= 55) return 'drizzle'\n if (code === 56 || code === 57) return 'freezingDrizzle'\n if (code >= 61 && code <= 65) return 'rain'\n if (code === 66 || code === 67) return 'freezingRain'\n if (code >= 71 && code <= 77) return 'snow'\n if (code >= 80 && code <= 82) return 'rainShowers'\n if (code === 85 || code === 86) return 'snowShowers'\n if (code === 95) return 'thunderstorm'\n if (code === 96 || code === 99) return 'thunderstormHail'\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) return { index: 0, key: 'good' }\n if (aqi <= 40) return { index: 1, key: 'fair' }\n if (aqi <= 60) return { index: 2, key: 'moderate' }\n if (aqi <= 80) return { index: 3, key: 'poor' }\n if (aqi <= 100) return { index: 4, key: 'veryPoor' }\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') }}°\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') }}°\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') }}°\n </span>\n <span class=\"text-20 text-word-3\">\n {{ $n(selectedDay.minTemp, 'integer') }}°\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') }}°\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') }}°\n </span>\n <span>{{ $n(day.minTemp, 'integer') }}°</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') }}°\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') }}°\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') }}°\n </span>\n <span class=\"text-20 text-word-3\">\n {{ $n(selectedDay.minTemp, 'integer') }}°\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') }}°\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') }}°\n </span>\n <span>{{ $n(day.minTemp, 'integer') }}°</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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"}
|