@8wave/ai-elements 0.101.0 → 0.102.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{Agent-DMCeO6o-.js → Agent-Cq-9OZHI.js} +77 -65
- package/dist/_chunks/Agent-Cq-9OZHI.js.map +1 -0
- package/dist/_chunks/{Document-C1Fp99qu.js → Document-s7Ct8xXa.js} +2 -2
- package/dist/_chunks/{Document-C1Fp99qu.js.map → Document-s7Ct8xXa.js.map} +1 -1
- package/dist/_chunks/{Media-c-RubI3_.js → Media-CMWDdtLL.js} +2 -2
- package/dist/_chunks/Media-CMWDdtLL.js.map +1 -0
- package/dist/_chunks/{PkToolRequestGeolocation-OZYwTIls.js → PkToolRequestGeolocation-hAaQJW-t.js} +2 -2
- package/dist/_chunks/{PkToolRequestGeolocation-OZYwTIls.js.map → PkToolRequestGeolocation-hAaQJW-t.js.map} +1 -1
- package/dist/_chunks/{PkToolRequestOAuthConnection-HmWtu2b3.js → PkToolRequestOAuthConnection-Bf_zoT2r.js} +2 -2
- package/dist/_chunks/{PkToolRequestOAuthConnection-HmWtu2b3.js.map → PkToolRequestOAuthConnection-Bf_zoT2r.js.map} +1 -1
- package/dist/_chunks/{PkToolShowForm-BzopoW7z.js → PkToolShowForm-CdUUAjPc.js} +2 -2
- package/dist/_chunks/{PkToolShowForm-BzopoW7z.js.map → PkToolShowForm-CdUUAjPc.js.map} +1 -1
- package/dist/_chunks/{PkToolShowSources-Blw5XOA7.js → PkToolShowSources-U7etupUP.js} +2 -2
- package/dist/_chunks/{PkToolShowSources-Blw5XOA7.js.map → PkToolShowSources-U7etupUP.js.map} +1 -1
- package/dist/_chunks/{PkToolShowSuggestedReply-nsXsvLUW.js → PkToolShowSuggestedReply-BvHgjMQ4.js} +2 -2
- package/dist/_chunks/{PkToolShowSuggestedReply-nsXsvLUW.js.map → PkToolShowSuggestedReply-BvHgjMQ4.js.map} +1 -1
- package/dist/ai-elements.es.js +6006 -5923
- package/dist/ai-elements.es.js.map +1 -1
- package/dist-vue/PkChatbot.js +1 -1
- package/dist-vue/PkChatbotMessages.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-WEwgRwnY.js → Agent-Cy-XE9N1.js} +77 -66
- package/dist-vue/_chunks/Agent-Cy-XE9N1.js.map +1 -0
- package/dist-vue/_chunks/{Document-Vx3oKWp3.js → Document-B6xVkCfQ.js} +8 -8
- package/dist-vue/_chunks/{Document-Vx3oKWp3.js.map → Document-B6xVkCfQ.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbot-1Y7fGR-C.js → PkChatbot-CHe6t-LR.js} +6 -6
- package/dist-vue/_chunks/{PkChatbot-1Y7fGR-C.js.map → PkChatbot-CHe6t-LR.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotMessages-C6X92Igt.js → PkChatbotMessages-CWPSx2ce.js} +8 -8
- package/dist-vue/_chunks/{PkChatbotMessages-C6X92Igt.js.map → PkChatbotMessages-CWPSx2ce.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewChat-CBvcSg0J.js → PkChatbotViewChat-DeeGCFJf.js} +2674 -2651
- package/dist-vue/_chunks/PkChatbotViewChat-DeeGCFJf.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotViewConversations-BT5ojZEm.js → PkChatbotViewConversations-DXwqumyn.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewConversations-BT5ojZEm.js.map → PkChatbotViewConversations-DXwqumyn.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewFiles-DHzFAQZP.js → PkChatbotViewFiles-B4u87oH6.js} +3 -3
- package/dist-vue/_chunks/{PkChatbotViewFiles-DHzFAQZP.js.map → PkChatbotViewFiles-B4u87oH6.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewProfile-DgN0Pydw.js → PkChatbotViewProfile-C_A0AP8x.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewProfile-DgN0Pydw.js.map → PkChatbotViewProfile-C_A0AP8x.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolRequestGeolocation-B1K2fN7U.js → PkToolRequestGeolocation-CaHo_418.js} +2 -2
- package/dist-vue/_chunks/{PkToolRequestGeolocation-B1K2fN7U.js.map → PkToolRequestGeolocation-CaHo_418.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolRequestOAuthConnection-D-qoxuPc.js → PkToolRequestOAuthConnection-URjYDFhm.js} +2 -2
- package/dist-vue/_chunks/{PkToolRequestOAuthConnection-D-qoxuPc.js.map → PkToolRequestOAuthConnection-URjYDFhm.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowDiagram-BvOy4aeQ.js → PkToolShowDiagram-CuQyY0uv.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowDiagram-BvOy4aeQ.js.map → PkToolShowDiagram-CuQyY0uv.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowForm-BzzeoKss.js → PkToolShowForm-vXgu3bZO.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowForm-BzzeoKss.js.map → PkToolShowForm-vXgu3bZO.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowSources-DdfHFaPA.js → PkToolShowSources-Die_j1bn.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowSources-DdfHFaPA.js.map → PkToolShowSources-Die_j1bn.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowSuggestedReply-DSd1dnZM.js → PkToolShowSuggestedReply-CryAdy6t.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowSuggestedReply-DSd1dnZM.js.map → PkToolShowSuggestedReply-CryAdy6t.js.map} +1 -1
- package/dist-vue/_chunks/{createChatbotApiClient-xbpuy6e1.js → createChatbotApiClient-CoTSUkuj.js} +107 -99
- package/dist-vue/_chunks/createChatbotApiClient-CoTSUkuj.js.map +1 -0
- package/dist-vue/_chunks/{useChatbotStore-DWLLr7sg.js → useChatbotStore-BcLvyaIV.js} +1043 -1014
- package/dist-vue/_chunks/{useChatbotStore-DWLLr7sg.js.map → useChatbotStore-BcLvyaIV.js.map} +1 -1
- package/dist-vue/api.js +1 -1
- package/dist-vue/composables.js +2 -2
- package/dist-vue/index.js +63 -63
- package/dist-vue/index.js.map +1 -1
- package/dist-vue/locales.js +2 -0
- package/dist-vue/packages/components/src/chat/PkAgentSettingsPanel.d.ts +2 -2
- package/dist-vue/packages/components/src/chat/PkReasoningSettings.d.ts +2 -2
- package/dist-vue/packages/components/src/chat/PkVoiceSession.d.ts +12 -0
- package/dist-vue/packages/components/src/chat/useVoiceSession.d.ts +7 -0
- package/dist-vue/packages/composable/src/chatbot/useChatbotStore.d.ts +40 -10
- package/dist-vue/packages/models/src/schema/Agent.d.ts +39 -7
- package/dist-vue/packages/models/src/schema/AgentChatStatistics.d.ts +42 -0
- package/dist-vue/packages/models/src/schema/Chat.d.ts +46 -1
- package/dist-vue/packages/models/src/schema/ReasoningChat.d.ts +2 -2
- package/dist-vue/packages/models/src/schema/SubAgent.d.ts +2 -2
- package/dist-vue/packages/models/src/schema/constants.d.ts +7 -3
- package/package.json +1 -1
- package/dist/_chunks/Agent-DMCeO6o-.js.map +0 -1
- package/dist/_chunks/Media-c-RubI3_.js.map +0 -1
- package/dist-vue/_chunks/Agent-WEwgRwnY.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotViewChat-CBvcSg0J.js.map +0 -1
- package/dist-vue/_chunks/createChatbotApiClient-xbpuy6e1.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PkToolShowForm-BzopoW7z.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 } from 'vue'\n\n const props = defineProps<{\n name: string\n required?: boolean\n options: Option[]\n readonly?: boolean\n disabled?: boolean\n modifiers?: string | string[]\n }>()\n\n const modelValue = defineModel<\n string | number | boolean | symbol | unknown[] | undefined\n >()\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, 'button']\n })\n</script>\n\n<template>\n <VvRadioGroup v-model=\"modelValue\" :name modifiers=\"buttons\">\n <VvRadio\n v-for=\"(option, key) in options\"\n :key\n :name\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 } from 'vue'\n\n const props = defineProps<{\n name: string\n required?: boolean\n options: Option[]\n readonly?: boolean\n disabled?: boolean\n modifiers?: string | string[]\n }>()\n\n const modelValue = defineModel<\n string | number | boolean | symbol | unknown[] | undefined\n >()\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, 'button']\n })\n</script>\n\n<template>\n <VvRadioGroup v-model=\"modelValue\" :name modifiers=\"buttons\">\n <VvRadio\n v-for=\"(option, key) in options\"\n :key\n :name\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</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</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 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'\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 // --- 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 const answers = reactive<Record<string, 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[currentStep.value.id]\n : undefined\n if (val == null || Array.isArray(val)) return undefined\n return val\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStep.value.id] = val ?? null\n }\n },\n })\n\n const activeCheckboxValue = computed<unknown[] | undefined>({\n get: () => {\n const val = currentStep.value\n ? answers[currentStep.value.id]\n : undefined\n return Array.isArray(val) ? val : []\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStep.value.id] = (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 if (typeof output === 'string') {\n if (steps.value.length === 1) {\n answers[steps.value[0].id] = output\n }\n return\n }\n const list = outputAnswers(output)\n if (list) {\n for (const a of list) {\n answers[a.id] = a.value\n }\n return\n }\n // Forms answered on a platform channel come back as a flat record\n // keyed by step id: without this the read-only summary showed every\n // answer as a placeholder.\n const record = output as Record<string, unknown>\n for (const step of steps.value) {\n const value = record[step.id]\n if (value !== undefined) {\n answers[step.id] = value as 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(() => {\n if (!currentStep.value?.options) return []\n return currentStep.value.options.map((option) => ({\n label: option.label,\n value: option.label,\n description: option.description,\n }))\n })\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) => ({\n id: step.id,\n value: answers[step.id] ?? 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[currentStep.value.id] = 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) {\n const val = answers[step.id]\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=\"steps.length\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden flex flex-col transition-all 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 rounded\"\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 rounded\"\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 in steps\"\n :key=\"step.id\"\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) }}\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 <div\n class=\"w-20 h-20 rounded-full border border-success text-success flex items-center justify-center\">\n <VvIcon name=\"ri:check-line\" class=\"text-12\" />\n </div>\n <div class=\"flex flex-col\">\n <span\n class=\"font-semibold 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 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'\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 // --- 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 const answers = reactive<Record<string, 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[currentStep.value.id]\n : undefined\n if (val == null || Array.isArray(val)) return undefined\n return val\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStep.value.id] = val ?? null\n }\n },\n })\n\n const activeCheckboxValue = computed<unknown[] | undefined>({\n get: () => {\n const val = currentStep.value\n ? answers[currentStep.value.id]\n : undefined\n return Array.isArray(val) ? val : []\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStep.value.id] = (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 if (typeof output === 'string') {\n if (steps.value.length === 1) {\n answers[steps.value[0].id] = output\n }\n return\n }\n const list = outputAnswers(output)\n if (list) {\n for (const a of list) {\n answers[a.id] = a.value\n }\n return\n }\n // Forms answered on a platform channel come back as a flat record\n // keyed by step id: without this the read-only summary showed every\n // answer as a placeholder.\n const record = output as Record<string, unknown>\n for (const step of steps.value) {\n const value = record[step.id]\n if (value !== undefined) {\n answers[step.id] = value as 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(() => {\n if (!currentStep.value?.options) return []\n return currentStep.value.options.map((option) => ({\n label: option.label,\n value: option.label,\n description: option.description,\n }))\n })\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) => ({\n id: step.id,\n value: answers[step.id] ?? 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[currentStep.value.id] = 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) {\n const val = answers[step.id]\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=\"steps.length\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden flex flex-col transition-all 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 rounded\"\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 rounded\"\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 in steps\"\n :key=\"step.id\"\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) }}\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 <div\n class=\"w-20 h-20 rounded-full border border-success text-success flex items-center justify-center\">\n <VvIcon name=\"ri:check-line\" class=\"text-12\" />\n </div>\n <div class=\"flex flex-col\">\n <span\n class=\"font-semibold 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAII,IAAM,IAAQ,GASR,IAAa,EAElB,GAAA,YAAC,GAEI,IAAiB,QAMZ,CAAC,GALS,EAAM,YACjB,MAAM,QAAQ,EAAM,SAAS,IACzB,EAAM,YACN,CAAC,EAAM,SAAS,IACpB,CAAC,GACc,QAAQ,CAChC;;;GAID,OAAA,EAAA,GAAA,EAce,GAAA;IAdQ,YAAA,EAAA;IAAA,uBAAA,AAAA,EAAA,QAAA,MAAA,EAAU,QAAA;IAAG,MAAA,EAAA;IAAK,WAAU;;IAE3C,SAAA,QAAgC,EADpC,EAAA,EAAA,GAAA,EAYU,GAAA,MAAA,EAXkB,EAAA,UAAhB,GAAQ,OADpB,EAAA,GAAA,EAYU,GAAA;KAVL;KACA,MAAA,EAAA;KACA,UAAA,EAAA;KACA,UAAA,EAAA;KACA,UAAA,EAAA;KACA,OAAO,EAAO;KACd,WAAW,EAAA;;KACZ,SAAA,QAEO,CAFP,EAEO,EAAA,QAFM,EAAO,OAApB,EAEO,EAAA,SAAA,GAAA,GAAA,EAF8B,UAAM,CAAA,SAEpC,CADA,EAAA,EAAA,EAAO,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EE5B3B,IAAM,IAAa,EAAkC,GAAA,YAAC;;;GAItD,OAAA,EAAA,GAAA,EAckB,GAAA;IAdQ,YAAA,EAAA;IAAA,uBAAA,AAAA,EAAA,QAAA,MAAA,EAAU,QAAA;IAAG,MAAA,EAAA;IAAK,WAAU;;IAE9C,SAAA,QAAgC,EADpC,EAAA,EAAA,GAAA,EAYa,GAAA,MAAA,EAXe,EAAA,UAAhB,GAAQ,OADpB,EAAA,GAAA,EAYa,GAAA;KAVR;KACA,MAAA,EAAA;KACA,UAAA,EAAA;KACA,UAAA,EAAA;KACA,UAAA,EAAA;KACA,OAAO,EAAO;KACf,WAAU;;KACV,SAAA,QAEO,CAFP,EAEO,EAAA,QAFM,OAAO,EAAO,KAAK,GAAhC,EAEO,EAAA,SAAA,GAAA,GAAA,EAFsC,UAAM,CAAA,SAE5C,CADA,EAAA,EAAA,EAAO,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EE4B3B,IAAM,IAAQ,GAYR,IAAO,GAIP,EAAE,GAAG,GAAI,GAAG,MAAO,GAAQ,EAAE,UAAU,SAAS,CAAC,GAIjD,IAAW,QACP,KAAK,MAAM,KAAK,UAAU,EAAM,IAAI,CAAC,CAC/C,GAEM,IAAW,QAET,EAAS,MAAM,aAAa,wBAC5B,EAAS,MAAM,SAAS,yBAChC,GAQM,IAAc,QAAe,EAAiB,EAAS,MAAM,MAAM,CAAC,GAEpE,IAAgB,QAEd,CAAC,EAAM,aACN,EAAS,MAAM,UAAU,qBAAqB,EAAY,MACnE,GAGM,IAAmB,QAAe;GACpC,IAAM,IAAO,EAAY;GAEzB,OADK,IAED,EAAM,MAAM,WAAW,KACvB,CAAC,EAAK,sBACN,CAAC,EAAM,oBAJO;EAMtB,CAAC,GAGK,IAAqB,QAEnB,EAAS,SACT,EAAM,MAAM,WAAW,KACvB,CAAC,EAAY,OAAO,WAC5B,GAUM,IAAmB,QAEjB,EAAQ,EAAY,OAAO,WAC3B,CAAC,EAAY,OAAO,gBACnB,EAAiB,SAAS,EAAmB,MACtD,GAIM,IAAQ,QAA2B;GACrC,IAAM,IAAQ,EAAS,MAAM;GAgB7B,OAfI,GAAO,OAAO,SACP,EAAM,QAGb,GAAO,YAAY,GAAO,UACnB,CACH;IACI,IAAI;IACJ,UAAU,EAAM;IAChB,SAAS,EAAM,QAAQ,KAAK,MACxB,OAAO,KAAM,WAAW,EAAE,OAAO,EAAE,IAAI,CAC3C;GACJ,CACJ,IAEG,CAAC;EACZ,CAAC,GAIK,IAAmB,EAAI,CAAC,GACxB,IAAc,QAAe,EAAM,MAAM,EAAiB,MAAM,GAChE,IAAY,QAAe,EAAiB,QAAQ,CAAC,GACrD,IAAY,QACR,EAAiB,QAAQ,EAAM,MAAM,SAAS,CACxD,GACM,KAAc,QAAe,EAAM,MAAM,SAAS,CAAC,GAEnD,IAAW,QACR,GAAY,QACV,KAAK,OACN,EAAiB,QAAQ,KAAK,EAAM,MAAM,SAAU,GAC1D,IAH+B,IAIlC,GAIK,IAAU,GAAsC,CAAC,CAAC,GAClD,IAAe,EAAI,EAAE;EAK3B,EAAM,SAAwB;GAC1B,EAAa,QAAQ;EACzB,CAAC;EAGD,IAAM,IAAc,EAAsC;GACtD,WAAW;IACP,IAAM,IAAM,EAAY,QAClB,EAAQ,EAAY,MAAM,MAC1B,KAAA;IACF,WAAO,QAAQ,MAAM,QAAQ,CAAG,IACpC,OAAO;GACX;GACA,MAAM,MAAQ;IACV,AAAI,EAAY,UACZ,EAAQ,EAAY,MAAM,MAAM,KAAO;GAE/C;EACJ,CAAC,GAEK,IAAsB,EAAgC;GACxD,WAAW;IACP,IAAM,IAAM,EAAY,QAClB,EAAQ,EAAY,MAAM,MAC1B,KAAA;IACN,OAAO,MAAM,QAAQ,CAAG,IAAI,IAAM,CAAC;GACvC;GACA,MAAM,MAAQ;IACV,AAAI,EAAY,UACZ,EAAQ,EAAY,MAAM,MAAO,KAAoB,CAAC;GAE9D;EACJ,CAAC;EAKD,SAAS,EAAc,GAAiC;GACpD,IAAI,CAAC,KAAU,OAAO,KAAW,UAAU;GAC3C,IAAM,IAAQ,EAAiC;GAC/C,OAAO,MAAM,QAAQ,CAAI,IAAK,IAAwB,KAAA;EAC1D;EAEA,QACU,EAAS,MAAM,SACpB,MAAW;GACR,IAAI,CAAC,KAAU,EAAiB,CAAM,GAAG;GACzC,IAAI,OAAO,KAAW,UAAU;IAC5B,AAAI,EAAM,MAAM,WAAW,MACvB,EAAQ,EAAM,MAAM,EAAE,CAAC,MAAM;IAEjC;GACJ;GACA,IAAM,IAAO,EAAc,CAAM;GACjC,IAAI,GAAM;IACN,KAAK,IAAM,KAAK,GACZ,EAAQ,EAAE,MAAM,EAAE;IAEtB;GACJ;GAIA,IAAM,IAAS;GACf,KAAK,IAAM,KAAQ,EAAM,OAAO;IAC5B,IAAM,IAAQ,EAAO,EAAK;IAC1B,AAAI,MAAU,KAAA,MACV,EAAQ,EAAK,MAAM;GAE3B;EACJ,GACA,EAAE,WAAW,GAAK,CACtB;EAIA,IAAM,KAAmB,QAAe;GACpC,IAAM,IAAM,EAAS,MAAM;GAC3B,IAAI,CAAC,KAAO,EAAiB,CAAG,GAAG;GACnC,IAAI,OAAO,KAAQ,UAAU,OAAO;GACpC,IAAM,IAAO,EAAc,CAAG;GAC9B,IAAI,GAAM,WAAW,GAAG;IACpB,IAAM,IAAM,EAAK,EAAE,CAAC;IACpB,OAAO,MAAM,QAAQ,CAAG,IAAI,EAAI,KAAK,IAAI,IAAI,OAAO,CAAG;GAC3D;EAEJ,CAAC;EAED,EACI,KACC,MAAU;GACH,CAAC,KAAS,CAAC,EAAY,UACL,EAAY,MAAM,WAAW,CAAC,EAAA,CAAG,KAClD,MAAM,EAAE,KAER,CAAA,CAAa,SAAS,CAAK,MAC5B,EAAa,QAAQ;EAE7B,GACA,EAAE,WAAW,GAAK,CACtB;EAIA,IAAM,IAAe,QACZ,EAAY,OAAO,UACjB,EAAY,MAAM,QAAQ,KAAK,OAAY;GAC9C,OAAO,EAAO;GACd,OAAO,EAAO;GACd,aAAa,EAAO;EACxB,EAAE,IALsC,CAAC,CAM5C;EAKD,SAAS,IAAS;GAId,IAAI,EAAa,MAAM,KAAK,GAAG;IAC3B,EAAmB;IACnB;GACJ;GACA,AAAI,EAAU,QACV,EAAiB,UAEjB,EAAW;EAEnB;EAEA,SAAS,KAAS;GACd,AAAI,EAAU,SACV,EAAiB;EAEzB;EAEA,SAAS,IAAa;GAClB,IAAI,CAAC,EAAc,OAAO;GAE1B,IAAM,IAA6B,EAAM,MAAM,KAAK,OAAU;IAC1D,IAAI,EAAK;IACT,OAAO,EAAQ,EAAK,OAAO;GAC/B,EAAE;GAGF,IACI,EAAmB,SACnB,CAAC,EAAY,OAAO,oBACtB;IACE,IAAM,IAAM,EAAa,EAAE,CAAC;IAC5B,EAAK,UAAU,MAAM,QAAQ,CAAG,IAAI,EAAI,KAAM,CAAc;GAChE,OACI,EAAK,UAAU,EAAE,SAAS,EAAa,CAAC;EAEhD;EAGA,SAAS,IAAqB;GAC1B,IAAM,IAAQ,EAAa,MAAM,KAAK;GACjC,MAEL,EAAQ,EAAY,MAAM,MAAM,GAGhC,EAAa,QAAQ,IACjB,EAAM,MAAM,WAAW,IACvB,EAAW,IAEX,EAAO;EAEf;EAEA,SAAS,KAAgB;GAChB,EAAc,UACf,EAAiB,SAAS,EAAmB,UAC7C,EAAW;EAEnB;EAIA,SAAS,GAAgB,GAAgB;GACrC,IAAM,IAAM,EAAQ,EAAK;GAIzB,OAHI,KAAO,OAAa,MACpB,MAAM,QAAQ,CAAG,IAAU,EAAI,KAAK,IAAI,IACxC,EAAK,SAAS,SAAe,EAAG,IAAI,KAAK,CAAG,GAAG,OAAO,IACnD;EACX;;;GAKU,OAAA,EAAA,MAAM,UADhB,EAAA,GAAA,EAqMM,OArMN,IAqMM,CAhMQ,EAAA,MAAS,OAAO,SAAU,EAAA,SAAiB,EAAA,MAAM,SAAM,KADjE,EAAA,GAAA,EAiCM,OAjCN,IAiCM,CA7BQ,EAAA,MAAS,OAAO,SAD1B,EAAA,GAAA,EAQM,OAAA;;IANF,OAAK,EAAA,CAAC,yBAAuB,EAAA,6BACsD,EAAA,SAAiB,EAAA,MAAM,SAAM,EAAA,CAAA,CAAA;GAI7G,GAAA,EAAA,EAAA,MAAS,OAAO,KAAK,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GAIlB,EAAA,SAAiB,EAAA,MAAM,SAAM,KADvC,EAAA,GAAA,EAmBM,OAnBN,IAmBM,CAhBF,EAUM,OAVN,IAUM,CATF,EAOO,QAAA,MAAA,EALC,EAAA,CAAA,CAAE,CAAA,iBAAA;IAA6D,SAAA,EAAA,QAAgB;IAA6C,OAAA,EAAA,MAAM;GAM1I,CAAA,CAAA,GAAA,CAAA,GAAA,EAA4B,QAAA,MAAA,EAAnB,EAAA,KAAQ,IAAG,KAAC,CAAA,CAAA,CAAA,GAEzB,EAIM,OAJN,IAIM,CAHF,EAE6C,OAAA;IADzC,OAAM;IACL,OAAK,EAAA,EAAA,OAAA,GAAc,EAAA,MAAQ,GAAA,CAAA;GAM5B,GAAA,MAAA,CAAA,CAAA,CAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GAAA,EAAA,SAAhB,EAAA,GAAA,EA4HW,GAAA,EAAA,KAAA,EAAA,GAAA;IA1HP,EA2EM,OA3EN,IA2EM,CA1EF,EAQM,OARN,IAQM,CAPF,EAES,UAFT,GAES,EADF,EAAA,MAAY,QAAQ,GAAA,CAAA,GAGjB,EAAA,MAAY,WADtB,EAAA,GAAA,EAG+B,IAAA;;KAD1B,UAAU,EAAA,MAAY;KACvB,OAAM;IAKJ,GAAA,MAAA,GAAA,CAAA,UAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,GAAA,EAAA,MAAY,WAAW,EAAA,MAAY,eAD7C,EAAA,GAAA,EAoB0B,IAAA;;KAlBb,YAAA,EAAA;KAAA,uBAAA,AAAA,EAAA,QAAA,MAAA,EAAmB,QAAA;KAC3B,MAAM,EAAA,MAAY;KAClB,SAAS,EAAA;KACT,UAAQ,CAAG,EAAA;KACZ,OAAM;KACN,UAAA;IAEqB,GAAA,EAAA,EAAA,GAAA,EAAA,GAAA,CAAA,EAAA,EAAA,QAAV;KAEN,MAAA,EAAO;KAER,IAAA,QAKM,CALN,EAKM,OALN,GAKM,CAJF,EAAmD,OAAnD,GAAmD,EAArB,EAAO,KAAK,GAAA,CAAA,GAC/B,EAAO,eAAlB,EAAA,GAAA,EAEM,OAFN,GAEM,EADC,EAAO,WAAW,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,CAAA,CAAA;;;;;;IAQtB,CAAA,KAAA,EAAA,MAAY,WAAO,CAAK,EAAA,MAAY,eADnD,EAAA,GAAA,EAqBuB,IAAA;;KAnBV,YAAA,EAAA;KAAA,uBAAA,CAAA,AAAA,EAAA,QAAA,MAAA,EAAW,QAAA,GAMC,EAAA;KALpB,MAAM,EAAA,MAAY;KAClB,SAAS,EAAA;KACT,UAAQ,CAAG,EAAA;KACZ,UAAA;KACA,OAAM;IAGe,GAAA,EAAA,EAAA,GAAA,EAAA,GAAA,CAAA,EAAA,EAAA,QAAV;KAEN,MAAA,EAAO;KAER,IAAA,QAKM,CALN,EAKM,OALN,GAKM,CAJF,EAAmD,OAAnD,GAAmD,EAArB,EAAO,KAAK,GAAA,CAAA,GAC/B,EAAO,eAAlB,EAAA,GAAA,EAEM,OAFN,IAEM,EADC,EAAO,WAAW,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,CAAA,CAAA;;;;;;IAQrB,CAAA,KAAA,EAAA,MAAY,uBAD5B,EAAA,GAAA,EAeM,OAfN,IAeM,EAZF,EAAA,GAAA,EAW4B,GAAA;KAVvB,KAAK,EAAA,MAAY;KACT,YAAA,EAAA;KAAA,uBAAA,AAAA,EAAA,QAAA,MAAA,EAAW,QAAA;KACnB,aAA0C,EAAA,MAAY,eAAe,EAAA,CAAA,CAAE,CAAA,oBAAA;KAGvE,MAAM,EAAA,MAAY;KACnB,OAAM;KACL,MAAM,EAAA,MAAY;KACnB,WAAU;KACT,UAAQ,CAAG,EAAA;KACX,SAAK,GAAQ,GAAM,CAAA,OAAA,CAAA;;;;;;;;IAMtB,EAAA,MAAY,sBAAsB,EAAA,MAAY,WADxD,EAAA,GAAA,EAkBM,OAlBN,IAkBM,CAfF,EAcO,QAAA;KAbH,OAAM;KACL,UAAM,EAAU,GAAkB,CAAA,SAAA,CAAA;IACnC,GAAA,EAAA,EAAA,GAAA,EAUQ,GAAA;KATH,KAAG,GAAK,EAAA,MAAY,GAAE;KACd,YAAA,EAAA;KAAA,uBAAA,AAAA,EAAA,QAAA,MAAA,EAAY,QAAA;KACrB,OAAM;KACL,MAAI,GAAK,EAAA,MAAY,GAAE;KACxB,WAAU;KACT,UAAQ,CAAG,EAAA;KACX,aAA0C,EAAA,MAAY,eAA2C,EAAA,CAAA,CAAE,CAAA,2BAAA;;;;;;;IAUrG,EAAA,qBADX,EAAA,GAAA,EAqBM,OArBN,IAqBM,CAjBQ,EAAA,SADV,EAAA,GAAA,EAK2B,GAAA;;KAHvB,WAAU;KACV,MAAK;KACJ,OAAO,EAAA,CAAA,CAAE,CAAA,aAAA;KACT,SAAK,EAAO,IAAM,CAAA,MAAA,CAAA;IACvB,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,MAAA,EAAA,GAAA,EAAkB,OAAA,EAAA,IAElB,EAS2B,GAAA;KARtB,WAAW,EAAA,QAAS,iBAAA;KACpB,MAA+B,EAAA,QAAA,0BAAA;KAKhC,iBAAc;KACb,OAAO,EAAA,QAAY,EAAA,CAAA,CAAE,CAAA,aAAA,IAAkB,EAAA,CAAA,CAAE,CAAA,eAAA;KACzC,SAAK,EAAO,GAAM,CAAA,MAAA,CAAA;;;;;;GAK/B,GAAA,EAAA,MAAA,EAAA,GAAA,EA6BW,GAAA,EAAA,KAAA,EAAA,GAAA,CA5BP,EAcM,OAdN,GAcM,CAbF,EAYK,MAZL,IAYK,EAXD,EAAA,EAAA,GAAA,EAUK,GAAA,MAAA,EATc,EAAA,QAAR,OADX,EAAA,GAAA,EAUK,MAAA;IARA,KAAK,EAAK;IACX,OAAM;GACN,GAAA,CAAA,EAEM,OAFN,IAEM,EADC,EAAK,QAAQ,GAAA,CAAA,GAEpB,EAEO,QAFP,IAEO,EADA,GAAgB,CAAI,CAAA,GAAA,CAAA,CAAA,CAAA,EAKvC,GAAA,GAAA,EAAA,CAAA,CAAA,CAAA,GAAA,EAYM,OAZN,IAYM,CAVF,EAGM,OAHN,IAGM,CADF,EAA+C,GAAA;IAAvC,MAAK;IAAgB,OAAM;GAEvC,CAAA,CAAA,CAAA,GAAA,EAKM,OALN,IAKM,CAJF,EAGO,QAHP,IAGO,EADA,EAAA,CAAA,CAAE,CAAA,oBAAA,CAAA,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,GAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA"}
|
|
1
|
+
{"version":3,"file":"PkToolShowForm-CdUUAjPc.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 } from 'vue'\n\n const props = defineProps<{\n name: string\n required?: boolean\n options: Option[]\n readonly?: boolean\n disabled?: boolean\n modifiers?: string | string[]\n }>()\n\n const modelValue = defineModel<\n string | number | boolean | symbol | unknown[] | undefined\n >()\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, 'button']\n })\n</script>\n\n<template>\n <VvRadioGroup v-model=\"modelValue\" :name modifiers=\"buttons\">\n <VvRadio\n v-for=\"(option, key) in options\"\n :key\n :name\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 } from 'vue'\n\n const props = defineProps<{\n name: string\n required?: boolean\n options: Option[]\n readonly?: boolean\n disabled?: boolean\n modifiers?: string | string[]\n }>()\n\n const modelValue = defineModel<\n string | number | boolean | symbol | unknown[] | undefined\n >()\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, 'button']\n })\n</script>\n\n<template>\n <VvRadioGroup v-model=\"modelValue\" :name modifiers=\"buttons\">\n <VvRadio\n v-for=\"(option, key) in options\"\n :key\n :name\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</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</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 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'\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 // --- 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 const answers = reactive<Record<string, 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[currentStep.value.id]\n : undefined\n if (val == null || Array.isArray(val)) return undefined\n return val\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStep.value.id] = val ?? null\n }\n },\n })\n\n const activeCheckboxValue = computed<unknown[] | undefined>({\n get: () => {\n const val = currentStep.value\n ? answers[currentStep.value.id]\n : undefined\n return Array.isArray(val) ? val : []\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStep.value.id] = (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 if (typeof output === 'string') {\n if (steps.value.length === 1) {\n answers[steps.value[0].id] = output\n }\n return\n }\n const list = outputAnswers(output)\n if (list) {\n for (const a of list) {\n answers[a.id] = a.value\n }\n return\n }\n // Forms answered on a platform channel come back as a flat record\n // keyed by step id: without this the read-only summary showed every\n // answer as a placeholder.\n const record = output as Record<string, unknown>\n for (const step of steps.value) {\n const value = record[step.id]\n if (value !== undefined) {\n answers[step.id] = value as 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(() => {\n if (!currentStep.value?.options) return []\n return currentStep.value.options.map((option) => ({\n label: option.label,\n value: option.label,\n description: option.description,\n }))\n })\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) => ({\n id: step.id,\n value: answers[step.id] ?? 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[currentStep.value.id] = 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) {\n const val = answers[step.id]\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=\"steps.length\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden flex flex-col transition-all 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 rounded\"\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 rounded\"\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 in steps\"\n :key=\"step.id\"\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) }}\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 <div\n class=\"w-20 h-20 rounded-full border border-success text-success flex items-center justify-center\">\n <VvIcon name=\"ri:check-line\" class=\"text-12\" />\n </div>\n <div class=\"flex flex-col\">\n <span\n class=\"font-semibold 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 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'\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 // --- 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 const answers = reactive<Record<string, 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[currentStep.value.id]\n : undefined\n if (val == null || Array.isArray(val)) return undefined\n return val\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStep.value.id] = val ?? null\n }\n },\n })\n\n const activeCheckboxValue = computed<unknown[] | undefined>({\n get: () => {\n const val = currentStep.value\n ? answers[currentStep.value.id]\n : undefined\n return Array.isArray(val) ? val : []\n },\n set: (val) => {\n if (currentStep.value) {\n answers[currentStep.value.id] = (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 if (typeof output === 'string') {\n if (steps.value.length === 1) {\n answers[steps.value[0].id] = output\n }\n return\n }\n const list = outputAnswers(output)\n if (list) {\n for (const a of list) {\n answers[a.id] = a.value\n }\n return\n }\n // Forms answered on a platform channel come back as a flat record\n // keyed by step id: without this the read-only summary showed every\n // answer as a placeholder.\n const record = output as Record<string, unknown>\n for (const step of steps.value) {\n const value = record[step.id]\n if (value !== undefined) {\n answers[step.id] = value as 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(() => {\n if (!currentStep.value?.options) return []\n return currentStep.value.options.map((option) => ({\n label: option.label,\n value: option.label,\n description: option.description,\n }))\n })\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) => ({\n id: step.id,\n value: answers[step.id] ?? 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[currentStep.value.id] = 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) {\n const val = answers[step.id]\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=\"steps.length\"\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden flex flex-col transition-all 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 rounded\"\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 rounded\"\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 in steps\"\n :key=\"step.id\"\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) }}\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 <div\n class=\"w-20 h-20 rounded-full border border-success text-success flex items-center justify-center\">\n <VvIcon name=\"ri:check-line\" class=\"text-12\" />\n </div>\n <div class=\"flex flex-col\">\n <span\n class=\"font-semibold 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,7 +1,7 @@
|
|
|
1
1
|
import { B as e, Ht as t, R as n, S as r, _ as i, at as a, et as o, f as s, ft as c, g as l, h as u, k as d, kt as f, mt as p, qt as m, r as h, t as g, v as _, w as v, x as y, xt as b, y as x } from "./vue.runtime.esm-bundler-DF2of3lp.js";
|
|
2
2
|
import { t as S } from "./PkStreamingMarkdown-YJDdYD0E.js";
|
|
3
3
|
import "./src-BjFv9Ag8.js";
|
|
4
|
-
import { a as C, n as w } from "./Document-
|
|
4
|
+
import { a as C, n as w } from "./Document-s7Ct8xXa.js";
|
|
5
5
|
import { u as T } from "./index.es-DvV0VhDa.js";
|
|
6
6
|
import { t as E } from "./PkUrl-C3gMPCjz.js";
|
|
7
7
|
d(), a(), f();
|
|
@@ -241,4 +241,4 @@ var F = {
|
|
|
241
241
|
//#endregion
|
|
242
242
|
export { Q as default };
|
|
243
243
|
|
|
244
|
-
//# sourceMappingURL=PkToolShowSources-
|
|
244
|
+
//# sourceMappingURL=PkToolShowSources-U7etupUP.js.map
|
package/dist/_chunks/{PkToolShowSources-Blw5XOA7.js.map → PkToolShowSources-U7etupUP.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PkToolShowSources-Blw5XOA7.js","names":["$t"],"sources":["../../../../packages/components/src/chat/PkChunkPreview.vue","../../../../packages/components/src/chat/PkChunkPreview.vue","../../../../packages/components/src/chat/PkDocumentHeader.vue","../../../../packages/components/src/chat/PkDocumentHeader.vue","../../../../packages/components/src/chat/PkToolShowSources.vue","../../../../packages/components/src/chat/PkToolShowSources.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { stripMarkdown } from 'utils'\n\n const props = defineProps<{\n content: string\n similarity?: number\n maxSimilarity?: number\n expanded: boolean\n }>()\n\n const similarityPercent = computed(() => {\n if (props.similarity == null) {\n return null\n }\n const max =\n props.maxSimilarity && props.maxSimilarity > 0\n ? props.maxSimilarity\n : 1\n return Math.round((props.similarity / max) * 100)\n })\n</script>\n\n<template>\n <VvIcon name=\"ri:text-snippet\" class=\"shrink-0 text-12 text-word-4\" />\n <span class=\"text-10 text-word-2 truncate flex-1\" :title=\"content\">\n {{ stripMarkdown(content) }}\n </span>\n <span v-if=\"similarityPercent != null\" class=\"text-10 text-word-4 shrink-0\">\n {{ similarityPercent }}%\n </span>\n <VvIcon\n :name=\"expanded ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-14 text-word-4 shrink-0\" />\n</template>\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { stripMarkdown } from 'utils'\n\n const props = defineProps<{\n content: string\n similarity?: number\n maxSimilarity?: number\n expanded: boolean\n }>()\n\n const similarityPercent = computed(() => {\n if (props.similarity == null) {\n return null\n }\n const max =\n props.maxSimilarity && props.maxSimilarity > 0\n ? props.maxSimilarity\n : 1\n return Math.round((props.similarity / max) * 100)\n })\n</script>\n\n<template>\n <VvIcon name=\"ri:text-snippet\" class=\"shrink-0 text-12 text-word-4\" />\n <span class=\"text-10 text-word-2 truncate flex-1\" :title=\"content\">\n {{ stripMarkdown(content) }}\n </span>\n <span v-if=\"similarityPercent != null\" class=\"text-10 text-word-4 shrink-0\">\n {{ similarityPercent }}%\n </span>\n <VvIcon\n :name=\"expanded ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-14 text-word-4 shrink-0\" />\n</template>\n","<script setup lang=\"ts\">\n import type { DocumentType } from 'models'\n import { getTypeIcon } from 'models'\n import PkUrl from '../PkUrl.vue'\n\n defineProps<{\n type: string\n name: string\n url?: string\n filename?: string\n }>()\n</script>\n\n<template>\n <VvIcon\n :name=\"getTypeIcon(type as DocumentType)\"\n class=\"shrink-0 text-14 text-word-3\" />\n <div class=\"flex flex-col gap-2 min-w-0 flex-1\">\n <strong class=\"font-bold text-12 text-word-1 truncate block\">\n {{ name }}\n </strong>\n <span v-if=\"filename\" class=\"text-10 text-word-4 truncate\">\n {{ filename }}\n </span>\n <span\n v-if=\"url\"\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-10\" />\n <PkUrl :url=\"url\" />\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import type { DocumentType } from 'models'\n import { getTypeIcon } from 'models'\n import PkUrl from '../PkUrl.vue'\n\n defineProps<{\n type: string\n name: string\n url?: string\n filename?: string\n }>()\n</script>\n\n<template>\n <VvIcon\n :name=\"getTypeIcon(type as DocumentType)\"\n class=\"shrink-0 text-14 text-word-3\" />\n <div class=\"flex flex-col gap-2 min-w-0 flex-1\">\n <strong class=\"font-bold text-12 text-word-1 truncate block\">\n {{ name }}\n </strong>\n <span v-if=\"filename\" class=\"text-10 text-word-4 truncate\">\n {{ filename }}\n </span>\n <span\n v-if=\"url\"\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-10\" />\n <PkUrl :url=\"url\" />\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, reactive } from 'vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkChunkPreview from './PkChunkPreview.vue'\n import PkDocumentHeader from './PkDocumentHeader.vue'\n\n interface SourceItem {\n chunkId: string\n documentId: string\n documentName: string\n documentType: string\n language: string\n content: string\n chunkIndex?: number | null\n similarity?: number\n sourceUrl?: string\n sourceFilename?: string\n sourceDescription?: string\n sourceImageUrl?: string\n }\n\n const props = defineProps<{\n part: unknown\n onExpandContext?: (payload: {\n documentId: string\n chunkIndex: number\n }) => Promise<string>\n onDownload?: (documentId: string) => void\n }>()\n\n const toolPart = computed(() => {\n return props.part as {\n input?: {\n sources: SourceItem[]\n }\n }\n })\n\n const sources = computed(() => toolPart.value.input?.sources || [])\n\n const groupedSources = computed(() => {\n const groups = new Map<\n string,\n { document: SourceItem; chunks: SourceItem[] }\n >()\n for (const source of sources.value) {\n const existing = groups.get(source.documentId)\n if (existing) {\n existing.chunks.push(source)\n } else {\n groups.set(source.documentId, {\n document: source,\n chunks: [source],\n })\n }\n }\n return [...groups.values()]\n })\n\n const maxSimilarity = computed(() => {\n return Math.max(...sources.value.map((s) => s.similarity ?? 0), 0)\n })\n\n const expandedDocuments = reactive(new Set<string>())\n const expandedChunks = reactive(new Set<string>())\n const contextData = reactive(new Map<string, string>())\n const loadingContext = reactive(new Set<string>())\n\n const toggleDocument = (documentId: string) => {\n if (expandedDocuments.has(documentId)) {\n expandedDocuments.delete(documentId)\n } else {\n expandedDocuments.add(documentId)\n }\n }\n\n const toggleChunk = (chunkId: string) => {\n if (expandedChunks.has(chunkId)) {\n expandedChunks.delete(chunkId)\n } else {\n expandedChunks.add(chunkId)\n }\n }\n\n const handleExpandContext = async (source: SourceItem) => {\n if (source.chunkIndex == null || !props.onExpandContext) {\n return\n }\n loadingContext.add(source.chunkId)\n try {\n const content = await props.onExpandContext({\n documentId: source.documentId,\n chunkIndex: source.chunkIndex,\n })\n contextData.set(source.chunkId, content)\n } finally {\n loadingContext.delete(source.chunkId)\n }\n }\n\n const clearContext = (chunkId: string) => {\n contextData.delete(chunkId)\n }\n\n const showMore = ref(false)\n const visibleGroups = computed(() => {\n if (showMore.value) {\n return groupedSources.value\n }\n return groupedSources.value.slice(0, 3)\n })\n const hasMore = computed(() => groupedSources.value.length > 3)\n</script>\n\n<template>\n <div v-if=\"sources.length\" class=\"flex flex-col gap-xs w-full\">\n <div class=\"flex items-center gap-4 text-12 text-word-3 font-semibold\">\n <VvIcon name=\"ri:article-line\" class=\"text-14\" />\n {{ $t('label.sources') }}\n <span class=\"text-word-4 font-normal\">\n ({{ groupedSources.length }})\n </span>\n </div>\n\n <ul class=\"flex flex-col gap-xs\">\n <li\n v-for=\"group in visibleGroups\"\n :key=\"group.document.documentId\"\n class=\"border border-surface-3 rounded-xl overflow-hidden\">\n <!-- Document header -->\n <button\n class=\"flex items-center gap-8 p-8 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleDocument(group.document.documentId)\">\n <PkDocumentHeader\n :type=\"group.document.documentType\"\n :name=\"group.document.documentName\"\n :url=\"group.document.sourceUrl\"\n :filename=\"group.document.sourceFilename\" />\n <div class=\"flex items-center gap-4 shrink-0\">\n <span\n v-if=\"group.chunks.length > 1\"\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2\">\n {{ group.chunks.length }}\n </span>\n <span\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2 uppercase\">\n {{ group.document.language }}\n </span>\n <VvIcon\n :name=\"\n expandedDocuments.has(group.document.documentId)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"text-16 text-word-3\" />\n </div>\n </button>\n\n <!-- Expanded content -->\n <div\n v-if=\"expandedDocuments.has(group.document.documentId)\"\n class=\"border-t border-surface-3\">\n <PkStreamingMarkdown\n v-if=\"group.document.sourceDescription\"\n :markdown=\"group.document.sourceDescription\"\n class=\"px-8 py-4 text-10 text-word-3 bg-surface-1 border-b border-surface-3\" />\n\n <!-- Chunk excerpts -->\n <div\n v-for=\"chunk in group.chunks\"\n :key=\"chunk.chunkId\"\n class=\"border-b border-surface-3 last:border-b-0\">\n <button\n class=\"flex items-center gap-8 px-8 py-6 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleChunk(chunk.chunkId)\">\n <PkChunkPreview\n :content=\"chunk.content\"\n :similarity=\"chunk.similarity\"\n :max-similarity=\"maxSimilarity\"\n :expanded=\"expandedChunks.has(chunk.chunkId)\" />\n </button>\n\n <!-- Expanded chunk content -->\n <div\n v-if=\"expandedChunks.has(chunk.chunkId)\"\n class=\"bg-surface-1 border-t border-surface-3\">\n <div class=\"p-8 text-10 text-word-3\">\n <transition mode=\"out-in\">\n <!-- Context expansion -->\n <PkStreamingMarkdown\n v-if=\"contextData.has(chunk.chunkId)\"\n :markdown=\"\n contextData.get(chunk.chunkId)!\n \"\n class=\"wysiwyg\" />\n\n <!-- Chunk content -->\n <PkStreamingMarkdown\n v-else\n :markdown=\"chunk.content\"\n class=\"wysiwyg\" />\n </transition>\n </div>\n\n <!-- Actions bar -->\n <div\n class=\"flex items-center gap-8 px-8 py-6 border-t border-surface-3 bg-surface\">\n <!-- Expand / collapse context -->\n <button\n v-if=\"\n onExpandContext &&\n chunk.chunkIndex != null &&\n !contextData.has(chunk.chunkId)\n \"\n class=\"text-10 text-word-3 hover:text-word flex items-center gap-4 cursor-pointer\"\n :disabled=\"\n loadingContext.has(chunk.chunkId)\n \"\n @click=\"handleExpandContext(chunk)\">\n <VvIcon\n :name=\"\n loadingContext.has(chunk.chunkId)\n ? 'line-md:loading-loop'\n : 'ri:expand-up-down-line'\n \"\n class=\"text-12\" />\n {{ $t('label.expandContext') }}\n </button>\n <button\n v-else-if=\"contextData.has(chunk.chunkId)\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n @click=\"clearContext(chunk.chunkId)\">\n <VvIcon\n name=\"ri:collapse-vertical-line\"\n class=\"text-12\" />\n {{ $t('label.collapseContext') }}\n </button>\n\n <!-- Open source URL -->\n <a\n v-if=\"chunk.sourceUrl\"\n :href=\"chunk.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 ml-auto\">\n <VvIcon\n name=\"ri:external-link-line\"\n class=\"text-12\" />\n {{ $t('label.openSource') }}\n </a>\n\n <!-- Download file -->\n <button\n v-if=\"\n onDownload &&\n chunk.documentType === 'file'\n \"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n :class=\"{\n 'ml-auto': !chunk.sourceUrl,\n }\"\n @click=\"onDownload(chunk.documentId)\">\n <VvIcon\n name=\"ri:download-line\"\n class=\"text-12\" />\n {{ $t('label.downloadFile') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </li>\n </ul>\n\n <!-- Show more -->\n <button\n v-if=\"hasMore\"\n class=\"text-12 text-word-3 cursor-pointer hover:underline flex items-center gap-4 mx-auto\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: groupedSources.length - 3,\n })\n }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, reactive } from 'vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkChunkPreview from './PkChunkPreview.vue'\n import PkDocumentHeader from './PkDocumentHeader.vue'\n\n interface SourceItem {\n chunkId: string\n documentId: string\n documentName: string\n documentType: string\n language: string\n content: string\n chunkIndex?: number | null\n similarity?: number\n sourceUrl?: string\n sourceFilename?: string\n sourceDescription?: string\n sourceImageUrl?: string\n }\n\n const props = defineProps<{\n part: unknown\n onExpandContext?: (payload: {\n documentId: string\n chunkIndex: number\n }) => Promise<string>\n onDownload?: (documentId: string) => void\n }>()\n\n const toolPart = computed(() => {\n return props.part as {\n input?: {\n sources: SourceItem[]\n }\n }\n })\n\n const sources = computed(() => toolPart.value.input?.sources || [])\n\n const groupedSources = computed(() => {\n const groups = new Map<\n string,\n { document: SourceItem; chunks: SourceItem[] }\n >()\n for (const source of sources.value) {\n const existing = groups.get(source.documentId)\n if (existing) {\n existing.chunks.push(source)\n } else {\n groups.set(source.documentId, {\n document: source,\n chunks: [source],\n })\n }\n }\n return [...groups.values()]\n })\n\n const maxSimilarity = computed(() => {\n return Math.max(...sources.value.map((s) => s.similarity ?? 0), 0)\n })\n\n const expandedDocuments = reactive(new Set<string>())\n const expandedChunks = reactive(new Set<string>())\n const contextData = reactive(new Map<string, string>())\n const loadingContext = reactive(new Set<string>())\n\n const toggleDocument = (documentId: string) => {\n if (expandedDocuments.has(documentId)) {\n expandedDocuments.delete(documentId)\n } else {\n expandedDocuments.add(documentId)\n }\n }\n\n const toggleChunk = (chunkId: string) => {\n if (expandedChunks.has(chunkId)) {\n expandedChunks.delete(chunkId)\n } else {\n expandedChunks.add(chunkId)\n }\n }\n\n const handleExpandContext = async (source: SourceItem) => {\n if (source.chunkIndex == null || !props.onExpandContext) {\n return\n }\n loadingContext.add(source.chunkId)\n try {\n const content = await props.onExpandContext({\n documentId: source.documentId,\n chunkIndex: source.chunkIndex,\n })\n contextData.set(source.chunkId, content)\n } finally {\n loadingContext.delete(source.chunkId)\n }\n }\n\n const clearContext = (chunkId: string) => {\n contextData.delete(chunkId)\n }\n\n const showMore = ref(false)\n const visibleGroups = computed(() => {\n if (showMore.value) {\n return groupedSources.value\n }\n return groupedSources.value.slice(0, 3)\n })\n const hasMore = computed(() => groupedSources.value.length > 3)\n</script>\n\n<template>\n <div v-if=\"sources.length\" class=\"flex flex-col gap-xs w-full\">\n <div class=\"flex items-center gap-4 text-12 text-word-3 font-semibold\">\n <VvIcon name=\"ri:article-line\" class=\"text-14\" />\n {{ $t('label.sources') }}\n <span class=\"text-word-4 font-normal\">\n ({{ groupedSources.length }})\n </span>\n </div>\n\n <ul class=\"flex flex-col gap-xs\">\n <li\n v-for=\"group in visibleGroups\"\n :key=\"group.document.documentId\"\n class=\"border border-surface-3 rounded-xl overflow-hidden\">\n <!-- Document header -->\n <button\n class=\"flex items-center gap-8 p-8 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleDocument(group.document.documentId)\">\n <PkDocumentHeader\n :type=\"group.document.documentType\"\n :name=\"group.document.documentName\"\n :url=\"group.document.sourceUrl\"\n :filename=\"group.document.sourceFilename\" />\n <div class=\"flex items-center gap-4 shrink-0\">\n <span\n v-if=\"group.chunks.length > 1\"\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2\">\n {{ group.chunks.length }}\n </span>\n <span\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2 uppercase\">\n {{ group.document.language }}\n </span>\n <VvIcon\n :name=\"\n expandedDocuments.has(group.document.documentId)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"text-16 text-word-3\" />\n </div>\n </button>\n\n <!-- Expanded content -->\n <div\n v-if=\"expandedDocuments.has(group.document.documentId)\"\n class=\"border-t border-surface-3\">\n <PkStreamingMarkdown\n v-if=\"group.document.sourceDescription\"\n :markdown=\"group.document.sourceDescription\"\n class=\"px-8 py-4 text-10 text-word-3 bg-surface-1 border-b border-surface-3\" />\n\n <!-- Chunk excerpts -->\n <div\n v-for=\"chunk in group.chunks\"\n :key=\"chunk.chunkId\"\n class=\"border-b border-surface-3 last:border-b-0\">\n <button\n class=\"flex items-center gap-8 px-8 py-6 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleChunk(chunk.chunkId)\">\n <PkChunkPreview\n :content=\"chunk.content\"\n :similarity=\"chunk.similarity\"\n :max-similarity=\"maxSimilarity\"\n :expanded=\"expandedChunks.has(chunk.chunkId)\" />\n </button>\n\n <!-- Expanded chunk content -->\n <div\n v-if=\"expandedChunks.has(chunk.chunkId)\"\n class=\"bg-surface-1 border-t border-surface-3\">\n <div class=\"p-8 text-10 text-word-3\">\n <transition mode=\"out-in\">\n <!-- Context expansion -->\n <PkStreamingMarkdown\n v-if=\"contextData.has(chunk.chunkId)\"\n :markdown=\"\n contextData.get(chunk.chunkId)!\n \"\n class=\"wysiwyg\" />\n\n <!-- Chunk content -->\n <PkStreamingMarkdown\n v-else\n :markdown=\"chunk.content\"\n class=\"wysiwyg\" />\n </transition>\n </div>\n\n <!-- Actions bar -->\n <div\n class=\"flex items-center gap-8 px-8 py-6 border-t border-surface-3 bg-surface\">\n <!-- Expand / collapse context -->\n <button\n v-if=\"\n onExpandContext &&\n chunk.chunkIndex != null &&\n !contextData.has(chunk.chunkId)\n \"\n class=\"text-10 text-word-3 hover:text-word flex items-center gap-4 cursor-pointer\"\n :disabled=\"\n loadingContext.has(chunk.chunkId)\n \"\n @click=\"handleExpandContext(chunk)\">\n <VvIcon\n :name=\"\n loadingContext.has(chunk.chunkId)\n ? 'line-md:loading-loop'\n : 'ri:expand-up-down-line'\n \"\n class=\"text-12\" />\n {{ $t('label.expandContext') }}\n </button>\n <button\n v-else-if=\"contextData.has(chunk.chunkId)\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n @click=\"clearContext(chunk.chunkId)\">\n <VvIcon\n name=\"ri:collapse-vertical-line\"\n class=\"text-12\" />\n {{ $t('label.collapseContext') }}\n </button>\n\n <!-- Open source URL -->\n <a\n v-if=\"chunk.sourceUrl\"\n :href=\"chunk.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 ml-auto\">\n <VvIcon\n name=\"ri:external-link-line\"\n class=\"text-12\" />\n {{ $t('label.openSource') }}\n </a>\n\n <!-- Download file -->\n <button\n v-if=\"\n onDownload &&\n chunk.documentType === 'file'\n \"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n :class=\"{\n 'ml-auto': !chunk.sourceUrl,\n }\"\n @click=\"onDownload(chunk.documentId)\">\n <VvIcon\n name=\"ri:download-line\"\n class=\"text-12\" />\n {{ $t('label.downloadFile') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </li>\n </ul>\n\n <!-- Show more -->\n <button\n v-if=\"hasMore\"\n class=\"text-12 text-word-3 cursor-pointer hover:underline flex items-center gap-4 mx-auto\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: groupedSources.length - 3,\n })\n }}\n </button>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PkToolShowSources-U7etupUP.js","names":["$t"],"sources":["../../../../packages/components/src/chat/PkChunkPreview.vue","../../../../packages/components/src/chat/PkChunkPreview.vue","../../../../packages/components/src/chat/PkDocumentHeader.vue","../../../../packages/components/src/chat/PkDocumentHeader.vue","../../../../packages/components/src/chat/PkToolShowSources.vue","../../../../packages/components/src/chat/PkToolShowSources.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { stripMarkdown } from 'utils'\n\n const props = defineProps<{\n content: string\n similarity?: number\n maxSimilarity?: number\n expanded: boolean\n }>()\n\n const similarityPercent = computed(() => {\n if (props.similarity == null) {\n return null\n }\n const max =\n props.maxSimilarity && props.maxSimilarity > 0\n ? props.maxSimilarity\n : 1\n return Math.round((props.similarity / max) * 100)\n })\n</script>\n\n<template>\n <VvIcon name=\"ri:text-snippet\" class=\"shrink-0 text-12 text-word-4\" />\n <span class=\"text-10 text-word-2 truncate flex-1\" :title=\"content\">\n {{ stripMarkdown(content) }}\n </span>\n <span v-if=\"similarityPercent != null\" class=\"text-10 text-word-4 shrink-0\">\n {{ similarityPercent }}%\n </span>\n <VvIcon\n :name=\"expanded ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-14 text-word-4 shrink-0\" />\n</template>\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { stripMarkdown } from 'utils'\n\n const props = defineProps<{\n content: string\n similarity?: number\n maxSimilarity?: number\n expanded: boolean\n }>()\n\n const similarityPercent = computed(() => {\n if (props.similarity == null) {\n return null\n }\n const max =\n props.maxSimilarity && props.maxSimilarity > 0\n ? props.maxSimilarity\n : 1\n return Math.round((props.similarity / max) * 100)\n })\n</script>\n\n<template>\n <VvIcon name=\"ri:text-snippet\" class=\"shrink-0 text-12 text-word-4\" />\n <span class=\"text-10 text-word-2 truncate flex-1\" :title=\"content\">\n {{ stripMarkdown(content) }}\n </span>\n <span v-if=\"similarityPercent != null\" class=\"text-10 text-word-4 shrink-0\">\n {{ similarityPercent }}%\n </span>\n <VvIcon\n :name=\"expanded ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-14 text-word-4 shrink-0\" />\n</template>\n","<script setup lang=\"ts\">\n import type { DocumentType } from 'models'\n import { getTypeIcon } from 'models'\n import PkUrl from '../PkUrl.vue'\n\n defineProps<{\n type: string\n name: string\n url?: string\n filename?: string\n }>()\n</script>\n\n<template>\n <VvIcon\n :name=\"getTypeIcon(type as DocumentType)\"\n class=\"shrink-0 text-14 text-word-3\" />\n <div class=\"flex flex-col gap-2 min-w-0 flex-1\">\n <strong class=\"font-bold text-12 text-word-1 truncate block\">\n {{ name }}\n </strong>\n <span v-if=\"filename\" class=\"text-10 text-word-4 truncate\">\n {{ filename }}\n </span>\n <span\n v-if=\"url\"\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-10\" />\n <PkUrl :url=\"url\" />\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import type { DocumentType } from 'models'\n import { getTypeIcon } from 'models'\n import PkUrl from '../PkUrl.vue'\n\n defineProps<{\n type: string\n name: string\n url?: string\n filename?: string\n }>()\n</script>\n\n<template>\n <VvIcon\n :name=\"getTypeIcon(type as DocumentType)\"\n class=\"shrink-0 text-14 text-word-3\" />\n <div class=\"flex flex-col gap-2 min-w-0 flex-1\">\n <strong class=\"font-bold text-12 text-word-1 truncate block\">\n {{ name }}\n </strong>\n <span v-if=\"filename\" class=\"text-10 text-word-4 truncate\">\n {{ filename }}\n </span>\n <span\n v-if=\"url\"\n class=\"text-10 text-word-4 truncate flex items-center gap-4\">\n <VvIcon name=\"ri:link\" class=\"shrink-0 text-10\" />\n <PkUrl :url=\"url\" />\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, reactive } from 'vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkChunkPreview from './PkChunkPreview.vue'\n import PkDocumentHeader from './PkDocumentHeader.vue'\n\n interface SourceItem {\n chunkId: string\n documentId: string\n documentName: string\n documentType: string\n language: string\n content: string\n chunkIndex?: number | null\n similarity?: number\n sourceUrl?: string\n sourceFilename?: string\n sourceDescription?: string\n sourceImageUrl?: string\n }\n\n const props = defineProps<{\n part: unknown\n onExpandContext?: (payload: {\n documentId: string\n chunkIndex: number\n }) => Promise<string>\n onDownload?: (documentId: string) => void\n }>()\n\n const toolPart = computed(() => {\n return props.part as {\n input?: {\n sources: SourceItem[]\n }\n }\n })\n\n const sources = computed(() => toolPart.value.input?.sources || [])\n\n const groupedSources = computed(() => {\n const groups = new Map<\n string,\n { document: SourceItem; chunks: SourceItem[] }\n >()\n for (const source of sources.value) {\n const existing = groups.get(source.documentId)\n if (existing) {\n existing.chunks.push(source)\n } else {\n groups.set(source.documentId, {\n document: source,\n chunks: [source],\n })\n }\n }\n return [...groups.values()]\n })\n\n const maxSimilarity = computed(() => {\n return Math.max(...sources.value.map((s) => s.similarity ?? 0), 0)\n })\n\n const expandedDocuments = reactive(new Set<string>())\n const expandedChunks = reactive(new Set<string>())\n const contextData = reactive(new Map<string, string>())\n const loadingContext = reactive(new Set<string>())\n\n const toggleDocument = (documentId: string) => {\n if (expandedDocuments.has(documentId)) {\n expandedDocuments.delete(documentId)\n } else {\n expandedDocuments.add(documentId)\n }\n }\n\n const toggleChunk = (chunkId: string) => {\n if (expandedChunks.has(chunkId)) {\n expandedChunks.delete(chunkId)\n } else {\n expandedChunks.add(chunkId)\n }\n }\n\n const handleExpandContext = async (source: SourceItem) => {\n if (source.chunkIndex == null || !props.onExpandContext) {\n return\n }\n loadingContext.add(source.chunkId)\n try {\n const content = await props.onExpandContext({\n documentId: source.documentId,\n chunkIndex: source.chunkIndex,\n })\n contextData.set(source.chunkId, content)\n } finally {\n loadingContext.delete(source.chunkId)\n }\n }\n\n const clearContext = (chunkId: string) => {\n contextData.delete(chunkId)\n }\n\n const showMore = ref(false)\n const visibleGroups = computed(() => {\n if (showMore.value) {\n return groupedSources.value\n }\n return groupedSources.value.slice(0, 3)\n })\n const hasMore = computed(() => groupedSources.value.length > 3)\n</script>\n\n<template>\n <div v-if=\"sources.length\" class=\"flex flex-col gap-xs w-full\">\n <div class=\"flex items-center gap-4 text-12 text-word-3 font-semibold\">\n <VvIcon name=\"ri:article-line\" class=\"text-14\" />\n {{ $t('label.sources') }}\n <span class=\"text-word-4 font-normal\">\n ({{ groupedSources.length }})\n </span>\n </div>\n\n <ul class=\"flex flex-col gap-xs\">\n <li\n v-for=\"group in visibleGroups\"\n :key=\"group.document.documentId\"\n class=\"border border-surface-3 rounded-xl overflow-hidden\">\n <!-- Document header -->\n <button\n class=\"flex items-center gap-8 p-8 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleDocument(group.document.documentId)\">\n <PkDocumentHeader\n :type=\"group.document.documentType\"\n :name=\"group.document.documentName\"\n :url=\"group.document.sourceUrl\"\n :filename=\"group.document.sourceFilename\" />\n <div class=\"flex items-center gap-4 shrink-0\">\n <span\n v-if=\"group.chunks.length > 1\"\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2\">\n {{ group.chunks.length }}\n </span>\n <span\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2 uppercase\">\n {{ group.document.language }}\n </span>\n <VvIcon\n :name=\"\n expandedDocuments.has(group.document.documentId)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"text-16 text-word-3\" />\n </div>\n </button>\n\n <!-- Expanded content -->\n <div\n v-if=\"expandedDocuments.has(group.document.documentId)\"\n class=\"border-t border-surface-3\">\n <PkStreamingMarkdown\n v-if=\"group.document.sourceDescription\"\n :markdown=\"group.document.sourceDescription\"\n class=\"px-8 py-4 text-10 text-word-3 bg-surface-1 border-b border-surface-3\" />\n\n <!-- Chunk excerpts -->\n <div\n v-for=\"chunk in group.chunks\"\n :key=\"chunk.chunkId\"\n class=\"border-b border-surface-3 last:border-b-0\">\n <button\n class=\"flex items-center gap-8 px-8 py-6 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleChunk(chunk.chunkId)\">\n <PkChunkPreview\n :content=\"chunk.content\"\n :similarity=\"chunk.similarity\"\n :max-similarity=\"maxSimilarity\"\n :expanded=\"expandedChunks.has(chunk.chunkId)\" />\n </button>\n\n <!-- Expanded chunk content -->\n <div\n v-if=\"expandedChunks.has(chunk.chunkId)\"\n class=\"bg-surface-1 border-t border-surface-3\">\n <div class=\"p-8 text-10 text-word-3\">\n <transition mode=\"out-in\">\n <!-- Context expansion -->\n <PkStreamingMarkdown\n v-if=\"contextData.has(chunk.chunkId)\"\n :markdown=\"\n contextData.get(chunk.chunkId)!\n \"\n class=\"wysiwyg\" />\n\n <!-- Chunk content -->\n <PkStreamingMarkdown\n v-else\n :markdown=\"chunk.content\"\n class=\"wysiwyg\" />\n </transition>\n </div>\n\n <!-- Actions bar -->\n <div\n class=\"flex items-center gap-8 px-8 py-6 border-t border-surface-3 bg-surface\">\n <!-- Expand / collapse context -->\n <button\n v-if=\"\n onExpandContext &&\n chunk.chunkIndex != null &&\n !contextData.has(chunk.chunkId)\n \"\n class=\"text-10 text-word-3 hover:text-word flex items-center gap-4 cursor-pointer\"\n :disabled=\"\n loadingContext.has(chunk.chunkId)\n \"\n @click=\"handleExpandContext(chunk)\">\n <VvIcon\n :name=\"\n loadingContext.has(chunk.chunkId)\n ? 'line-md:loading-loop'\n : 'ri:expand-up-down-line'\n \"\n class=\"text-12\" />\n {{ $t('label.expandContext') }}\n </button>\n <button\n v-else-if=\"contextData.has(chunk.chunkId)\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n @click=\"clearContext(chunk.chunkId)\">\n <VvIcon\n name=\"ri:collapse-vertical-line\"\n class=\"text-12\" />\n {{ $t('label.collapseContext') }}\n </button>\n\n <!-- Open source URL -->\n <a\n v-if=\"chunk.sourceUrl\"\n :href=\"chunk.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 ml-auto\">\n <VvIcon\n name=\"ri:external-link-line\"\n class=\"text-12\" />\n {{ $t('label.openSource') }}\n </a>\n\n <!-- Download file -->\n <button\n v-if=\"\n onDownload &&\n chunk.documentType === 'file'\n \"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n :class=\"{\n 'ml-auto': !chunk.sourceUrl,\n }\"\n @click=\"onDownload(chunk.documentId)\">\n <VvIcon\n name=\"ri:download-line\"\n class=\"text-12\" />\n {{ $t('label.downloadFile') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </li>\n </ul>\n\n <!-- Show more -->\n <button\n v-if=\"hasMore\"\n class=\"text-12 text-word-3 cursor-pointer hover:underline flex items-center gap-4 mx-auto\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: groupedSources.length - 3,\n })\n }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, reactive } from 'vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkChunkPreview from './PkChunkPreview.vue'\n import PkDocumentHeader from './PkDocumentHeader.vue'\n\n interface SourceItem {\n chunkId: string\n documentId: string\n documentName: string\n documentType: string\n language: string\n content: string\n chunkIndex?: number | null\n similarity?: number\n sourceUrl?: string\n sourceFilename?: string\n sourceDescription?: string\n sourceImageUrl?: string\n }\n\n const props = defineProps<{\n part: unknown\n onExpandContext?: (payload: {\n documentId: string\n chunkIndex: number\n }) => Promise<string>\n onDownload?: (documentId: string) => void\n }>()\n\n const toolPart = computed(() => {\n return props.part as {\n input?: {\n sources: SourceItem[]\n }\n }\n })\n\n const sources = computed(() => toolPart.value.input?.sources || [])\n\n const groupedSources = computed(() => {\n const groups = new Map<\n string,\n { document: SourceItem; chunks: SourceItem[] }\n >()\n for (const source of sources.value) {\n const existing = groups.get(source.documentId)\n if (existing) {\n existing.chunks.push(source)\n } else {\n groups.set(source.documentId, {\n document: source,\n chunks: [source],\n })\n }\n }\n return [...groups.values()]\n })\n\n const maxSimilarity = computed(() => {\n return Math.max(...sources.value.map((s) => s.similarity ?? 0), 0)\n })\n\n const expandedDocuments = reactive(new Set<string>())\n const expandedChunks = reactive(new Set<string>())\n const contextData = reactive(new Map<string, string>())\n const loadingContext = reactive(new Set<string>())\n\n const toggleDocument = (documentId: string) => {\n if (expandedDocuments.has(documentId)) {\n expandedDocuments.delete(documentId)\n } else {\n expandedDocuments.add(documentId)\n }\n }\n\n const toggleChunk = (chunkId: string) => {\n if (expandedChunks.has(chunkId)) {\n expandedChunks.delete(chunkId)\n } else {\n expandedChunks.add(chunkId)\n }\n }\n\n const handleExpandContext = async (source: SourceItem) => {\n if (source.chunkIndex == null || !props.onExpandContext) {\n return\n }\n loadingContext.add(source.chunkId)\n try {\n const content = await props.onExpandContext({\n documentId: source.documentId,\n chunkIndex: source.chunkIndex,\n })\n contextData.set(source.chunkId, content)\n } finally {\n loadingContext.delete(source.chunkId)\n }\n }\n\n const clearContext = (chunkId: string) => {\n contextData.delete(chunkId)\n }\n\n const showMore = ref(false)\n const visibleGroups = computed(() => {\n if (showMore.value) {\n return groupedSources.value\n }\n return groupedSources.value.slice(0, 3)\n })\n const hasMore = computed(() => groupedSources.value.length > 3)\n</script>\n\n<template>\n <div v-if=\"sources.length\" class=\"flex flex-col gap-xs w-full\">\n <div class=\"flex items-center gap-4 text-12 text-word-3 font-semibold\">\n <VvIcon name=\"ri:article-line\" class=\"text-14\" />\n {{ $t('label.sources') }}\n <span class=\"text-word-4 font-normal\">\n ({{ groupedSources.length }})\n </span>\n </div>\n\n <ul class=\"flex flex-col gap-xs\">\n <li\n v-for=\"group in visibleGroups\"\n :key=\"group.document.documentId\"\n class=\"border border-surface-3 rounded-xl overflow-hidden\">\n <!-- Document header -->\n <button\n class=\"flex items-center gap-8 p-8 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleDocument(group.document.documentId)\">\n <PkDocumentHeader\n :type=\"group.document.documentType\"\n :name=\"group.document.documentName\"\n :url=\"group.document.sourceUrl\"\n :filename=\"group.document.sourceFilename\" />\n <div class=\"flex items-center gap-4 shrink-0\">\n <span\n v-if=\"group.chunks.length > 1\"\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2\">\n {{ group.chunks.length }}\n </span>\n <span\n class=\"text-10 text-word-4 bg-surface-2 rounded px-4 py-2 uppercase\">\n {{ group.document.language }}\n </span>\n <VvIcon\n :name=\"\n expandedDocuments.has(group.document.documentId)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"text-16 text-word-3\" />\n </div>\n </button>\n\n <!-- Expanded content -->\n <div\n v-if=\"expandedDocuments.has(group.document.documentId)\"\n class=\"border-t border-surface-3\">\n <PkStreamingMarkdown\n v-if=\"group.document.sourceDescription\"\n :markdown=\"group.document.sourceDescription\"\n class=\"px-8 py-4 text-10 text-word-3 bg-surface-1 border-b border-surface-3\" />\n\n <!-- Chunk excerpts -->\n <div\n v-for=\"chunk in group.chunks\"\n :key=\"chunk.chunkId\"\n class=\"border-b border-surface-3 last:border-b-0\">\n <button\n class=\"flex items-center gap-8 px-8 py-6 w-full text-left hover:bg-surface-1 transition-colors cursor-pointer\"\n @click=\"toggleChunk(chunk.chunkId)\">\n <PkChunkPreview\n :content=\"chunk.content\"\n :similarity=\"chunk.similarity\"\n :max-similarity=\"maxSimilarity\"\n :expanded=\"expandedChunks.has(chunk.chunkId)\" />\n </button>\n\n <!-- Expanded chunk content -->\n <div\n v-if=\"expandedChunks.has(chunk.chunkId)\"\n class=\"bg-surface-1 border-t border-surface-3\">\n <div class=\"p-8 text-10 text-word-3\">\n <transition mode=\"out-in\">\n <!-- Context expansion -->\n <PkStreamingMarkdown\n v-if=\"contextData.has(chunk.chunkId)\"\n :markdown=\"\n contextData.get(chunk.chunkId)!\n \"\n class=\"wysiwyg\" />\n\n <!-- Chunk content -->\n <PkStreamingMarkdown\n v-else\n :markdown=\"chunk.content\"\n class=\"wysiwyg\" />\n </transition>\n </div>\n\n <!-- Actions bar -->\n <div\n class=\"flex items-center gap-8 px-8 py-6 border-t border-surface-3 bg-surface\">\n <!-- Expand / collapse context -->\n <button\n v-if=\"\n onExpandContext &&\n chunk.chunkIndex != null &&\n !contextData.has(chunk.chunkId)\n \"\n class=\"text-10 text-word-3 hover:text-word flex items-center gap-4 cursor-pointer\"\n :disabled=\"\n loadingContext.has(chunk.chunkId)\n \"\n @click=\"handleExpandContext(chunk)\">\n <VvIcon\n :name=\"\n loadingContext.has(chunk.chunkId)\n ? 'line-md:loading-loop'\n : 'ri:expand-up-down-line'\n \"\n class=\"text-12\" />\n {{ $t('label.expandContext') }}\n </button>\n <button\n v-else-if=\"contextData.has(chunk.chunkId)\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n @click=\"clearContext(chunk.chunkId)\">\n <VvIcon\n name=\"ri:collapse-vertical-line\"\n class=\"text-12\" />\n {{ $t('label.collapseContext') }}\n </button>\n\n <!-- Open source URL -->\n <a\n v-if=\"chunk.sourceUrl\"\n :href=\"chunk.sourceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 ml-auto\">\n <VvIcon\n name=\"ri:external-link-line\"\n class=\"text-12\" />\n {{ $t('label.openSource') }}\n </a>\n\n <!-- Download file -->\n <button\n v-if=\"\n onDownload &&\n chunk.documentType === 'file'\n \"\n class=\"text-10 text-word-3 hover:text-word-1 flex items-center gap-4 cursor-pointer\"\n :class=\"{\n 'ml-auto': !chunk.sourceUrl,\n }\"\n @click=\"onDownload(chunk.documentId)\">\n <VvIcon\n name=\"ri:download-line\"\n class=\"text-12\" />\n {{ $t('label.downloadFile') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </li>\n </ul>\n\n <!-- Show more -->\n <button\n v-if=\"hasMore\"\n class=\"text-12 text-word-3 cursor-pointer hover:underline flex items-center gap-4 mx-auto\"\n @click=\"showMore = !showMore\">\n <VvIcon\n :name=\"showMore ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"text-16\" />\n {{\n showMore\n ? $t('action.showLess')\n : $t('action.showMore', {\n count: groupedSources.length - 3,\n })\n }}\n </button>\n </div>\n</template>\n"],"mappings":"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"}
|
package/dist/_chunks/{PkToolShowSuggestedReply-nsXsvLUW.js → PkToolShowSuggestedReply-BvHgjMQ4.js}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { B as e, Ht as t, R as n, S as r, at as i, d as a, f as o, g as s, h as c, k as l, kt as u, mt as d, qt as f, r as p, v as m, w as h, y as g } from "./vue.runtime.esm-bundler-DF2of3lp.js";
|
|
2
2
|
import { u as _ } from "./index.es-DvV0VhDa.js";
|
|
3
|
-
import {
|
|
3
|
+
import { d as v } from "./Agent-Cq-9OZHI.js";
|
|
4
4
|
import { t as y } from "./_plugin-vue_export-helper-B3ysoDQm.js";
|
|
5
5
|
l(), u(), p(), i();
|
|
6
6
|
var b = {
|
|
@@ -45,4 +45,4 @@ var b = {
|
|
|
45
45
|
//#endregion
|
|
46
46
|
export { S as default };
|
|
47
47
|
|
|
48
|
-
//# sourceMappingURL=PkToolShowSuggestedReply-
|
|
48
|
+
//# sourceMappingURL=PkToolShowSuggestedReply-BvHgjMQ4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PkToolShowSuggestedReply-nsXsvLUW.js","names":[],"sources":["../../../../packages/components/src/chat/PkToolShowSuggestedReply.vue","../../../../packages/components/src/chat/PkToolShowSuggestedReply.vue","../../../../packages/components/src/chat/PkToolShowSuggestedReply.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, ref } from 'vue'\n import { isVoiceRenderAck } from 'models'\n\n type Suggestion = { label: string; prompt?: string }\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const emit = defineEmits<{\n select: [value: string]\n }>()\n\n const toolPart = computed(() => {\n return JSON.parse(JSON.stringify(props.part)) as {\n state?: string\n toolCallId?: string\n input?: {\n /** New schema: array of suggestions */\n suggestions?: Suggestion[]\n /** Legacy schema: single suggestion */\n label?: string\n prompt?: string\n }\n output?: string\n }\n })\n\n /** Normalize to always work with an array of suggestions */\n const suggestions = computed<Suggestion[]>(() => {\n const input = toolPart.value?.input\n if (!input) {\n return []\n }\n if (input.suggestions?.length) {\n return input.suggestions\n }\n // Legacy single-suggestion format (client-side fallback)\n if (input.label) {\n return [{ label: input.label, prompt: input.prompt ?? input.label }]\n }\n return []\n })\n\n const clickedIndex = ref<number | null>(null)\n\n const isInteractive = computed(() => {\n // A chip drawn during a CALL arrives already resolved: the turn could\n // not wait for a tap, so the backend closed the tool with the note\n // that it was drawn (see `isVoiceRenderAck`, same rule as the forms).\n // The note is not a tap, and without this the chips of every spoken\n // turn rendered dead.\n return (\n (toolPart.value?.state === 'input-available' ||\n isVoiceRenderAck(toolPart.value?.output)) &&\n clickedIndex.value === null\n )\n })\n\n function handleClick(index: number) {\n if (!isInteractive.value) {\n return\n }\n clickedIndex.value = index\n const suggestion = suggestions.value[index]\n const value = suggestion?.prompt ?? suggestion?.label\n if (value) {\n emit('select', value)\n }\n }\n</script>\n\n<template>\n <div v-if=\"suggestions.length\" class=\"pk-tool-suggested-reply\">\n <button\n v-for=\"(suggestion, index) in suggestions\"\n :key=\"index\"\n type=\"button\"\n class=\"pk-tool-suggested-reply__link\"\n :class=\"{\n 'is-clicked': clickedIndex === index,\n 'is-disabled': !isInteractive,\n }\"\n :disabled=\"!isInteractive\"\n @click.stop=\"handleClick(index)\">\n <VvIcon\n name=\"ri:corner-down-right-line\"\n class=\"pk-tool-suggested-reply__icon\" />\n <span>{{ suggestion.label }}</span>\n </button>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-tool-suggested-reply {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--spacing-4);\n\n &__link {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-4);\n background: none;\n border: none;\n padding: 0;\n font: inherit;\n font-size: var(--text-12);\n text-decoration: underline;\n text-underline-offset: 2px;\n cursor: pointer;\n color: var(--color-word-3);\n transition: var(--transition-colors);\n\n &:hover:not(:disabled) {\n color: var(--color-word);\n }\n\n &.is-disabled {\n cursor: default;\n pointer-events: none;\n color: var(--color-word-5);\n }\n\n &.is-clicked {\n color: var(--color-word-5);\n text-decoration: none;\n }\n }\n\n &__icon {\n width: 1em;\n height: 1em;\n flex-shrink: 0;\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { computed, ref } from 'vue'\n import { isVoiceRenderAck } from 'models'\n\n type Suggestion = { label: string; prompt?: string }\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const emit = defineEmits<{\n select: [value: string]\n }>()\n\n const toolPart = computed(() => {\n return JSON.parse(JSON.stringify(props.part)) as {\n state?: string\n toolCallId?: string\n input?: {\n /** New schema: array of suggestions */\n suggestions?: Suggestion[]\n /** Legacy schema: single suggestion */\n label?: string\n prompt?: string\n }\n output?: string\n }\n })\n\n /** Normalize to always work with an array of suggestions */\n const suggestions = computed<Suggestion[]>(() => {\n const input = toolPart.value?.input\n if (!input) {\n return []\n }\n if (input.suggestions?.length) {\n return input.suggestions\n }\n // Legacy single-suggestion format (client-side fallback)\n if (input.label) {\n return [{ label: input.label, prompt: input.prompt ?? input.label }]\n }\n return []\n })\n\n const clickedIndex = ref<number | null>(null)\n\n const isInteractive = computed(() => {\n // A chip drawn during a CALL arrives already resolved: the turn could\n // not wait for a tap, so the backend closed the tool with the note\n // that it was drawn (see `isVoiceRenderAck`, same rule as the forms).\n // The note is not a tap, and without this the chips of every spoken\n // turn rendered dead.\n return (\n (toolPart.value?.state === 'input-available' ||\n isVoiceRenderAck(toolPart.value?.output)) &&\n clickedIndex.value === null\n )\n })\n\n function handleClick(index: number) {\n if (!isInteractive.value) {\n return\n }\n clickedIndex.value = index\n const suggestion = suggestions.value[index]\n const value = suggestion?.prompt ?? suggestion?.label\n if (value) {\n emit('select', value)\n }\n }\n</script>\n\n<template>\n <div v-if=\"suggestions.length\" class=\"pk-tool-suggested-reply\">\n <button\n v-for=\"(suggestion, index) in suggestions\"\n :key=\"index\"\n type=\"button\"\n class=\"pk-tool-suggested-reply__link\"\n :class=\"{\n 'is-clicked': clickedIndex === index,\n 'is-disabled': !isInteractive,\n }\"\n :disabled=\"!isInteractive\"\n @click.stop=\"handleClick(index)\">\n <VvIcon\n name=\"ri:corner-down-right-line\"\n class=\"pk-tool-suggested-reply__icon\" />\n <span>{{ suggestion.label }}</span>\n </button>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-tool-suggested-reply {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--spacing-4);\n\n &__link {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-4);\n background: none;\n border: none;\n padding: 0;\n font: inherit;\n font-size: var(--text-12);\n text-decoration: underline;\n text-underline-offset: 2px;\n cursor: pointer;\n color: var(--color-word-3);\n transition: var(--transition-colors);\n\n &:hover:not(:disabled) {\n color: var(--color-word);\n }\n\n &.is-disabled {\n cursor: default;\n pointer-events: none;\n color: var(--color-word-5);\n }\n\n &.is-clicked {\n color: var(--color-word-5);\n text-decoration: none;\n }\n }\n\n &__icon {\n width: 1em;\n height: 1em;\n flex-shrink: 0;\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { computed, ref } from 'vue'\n import { isVoiceRenderAck } from 'models'\n\n type Suggestion = { label: string; prompt?: string }\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const emit = defineEmits<{\n select: [value: string]\n }>()\n\n const toolPart = computed(() => {\n return JSON.parse(JSON.stringify(props.part)) as {\n state?: string\n toolCallId?: string\n input?: {\n /** New schema: array of suggestions */\n suggestions?: Suggestion[]\n /** Legacy schema: single suggestion */\n label?: string\n prompt?: string\n }\n output?: string\n }\n })\n\n /** Normalize to always work with an array of suggestions */\n const suggestions = computed<Suggestion[]>(() => {\n const input = toolPart.value?.input\n if (!input) {\n return []\n }\n if (input.suggestions?.length) {\n return input.suggestions\n }\n // Legacy single-suggestion format (client-side fallback)\n if (input.label) {\n return [{ label: input.label, prompt: input.prompt ?? input.label }]\n }\n return []\n })\n\n const clickedIndex = ref<number | null>(null)\n\n const isInteractive = computed(() => {\n // A chip drawn during a CALL arrives already resolved: the turn could\n // not wait for a tap, so the backend closed the tool with the note\n // that it was drawn (see `isVoiceRenderAck`, same rule as the forms).\n // The note is not a tap, and without this the chips of every spoken\n // turn rendered dead.\n return (\n (toolPart.value?.state === 'input-available' ||\n isVoiceRenderAck(toolPart.value?.output)) &&\n clickedIndex.value === null\n )\n })\n\n function handleClick(index: number) {\n if (!isInteractive.value) {\n return\n }\n clickedIndex.value = index\n const suggestion = suggestions.value[index]\n const value = suggestion?.prompt ?? suggestion?.label\n if (value) {\n emit('select', value)\n }\n }\n</script>\n\n<template>\n <div v-if=\"suggestions.length\" class=\"pk-tool-suggested-reply\">\n <button\n v-for=\"(suggestion, index) in suggestions\"\n :key=\"index\"\n type=\"button\"\n class=\"pk-tool-suggested-reply__link\"\n :class=\"{\n 'is-clicked': clickedIndex === index,\n 'is-disabled': !isInteractive,\n }\"\n :disabled=\"!isInteractive\"\n @click.stop=\"handleClick(index)\">\n <VvIcon\n name=\"ri:corner-down-right-line\"\n class=\"pk-tool-suggested-reply__icon\" />\n <span>{{ suggestion.label }}</span>\n </button>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-tool-suggested-reply {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--spacing-4);\n\n &__link {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-4);\n background: none;\n border: none;\n padding: 0;\n font: inherit;\n font-size: var(--text-12);\n text-decoration: underline;\n text-underline-offset: 2px;\n cursor: pointer;\n color: var(--color-word-3);\n transition: var(--transition-colors);\n\n &:hover:not(:disabled) {\n color: var(--color-word);\n }\n\n &.is-disabled {\n cursor: default;\n pointer-events: none;\n color: var(--color-word-5);\n }\n\n &.is-clicked {\n color: var(--color-word-5);\n text-decoration: none;\n }\n }\n\n &__icon {\n width: 1em;\n height: 1em;\n flex-shrink: 0;\n }\n }\n</style>\n"],"mappings":";;;;;;;;;;;;;EAMI,IAAM,IAAQ,GAIR,IAAO,GAIP,IAAW,QACN,KAAK,MAAM,KAAK,UAAU,EAAM,IAAI,CAAC,CAY/C,GAGK,IAAc,QAA6B;GAC7C,IAAM,IAAQ,EAAS,OAAO;GAW9B,OAVK,IAGD,EAAM,aAAa,SACZ,EAAM,cAGb,EAAM,QACC,CAAC;IAAE,OAAO,EAAM;IAAO,QAAQ,EAAM,UAAU,EAAM;GAAM,CAAC,IAEhE,CAAC,IATG,CAAC;EAUhB,CAAC,GAEK,IAAe,EAAmB,IAAI,GAEtC,IAAgB,SAOb,EAAS,OAAO,UAAU,qBACvB,EAAiB,EAAS,OAAO,MAAM,MAC3C,EAAa,UAAU,IAE9B;EAED,SAAS,EAAY,GAAe;GAChC,IAAI,CAAC,EAAc,OACf;GAEJ,EAAa,QAAQ;GACrB,IAAM,IAAa,EAAY,MAAM,IAC/B,IAAQ,GAAY,UAAU,GAAY;GAChD,AAAI,KACA,EAAK,UAAU,CAAK;EAE5B;;;GAIW,OAAA,EAAA,MAAY,UAAvB,EAAA,GAAA,EAiBM,OAjBN,GAiBM,EAhBF,EAAA,EAAA,GAAA,EAeS,GAAA,MAAA,EAdyB,EAAA,QAAtB,GAAY,OADxB,EAAA,GAAA,EAeS,UAAA;IAbJ,KAAK;IACN,MAAK;IACL,OAAK,EAAA,CAAC,iCAA+B;KACG,cAAA,EAAA,UAAiB;KAAuC,eAAA,CAAA,EAAA;;IAI/F,UAAQ,CAAG,EAAA;IACX,SAAK,GAAA,MAAO,EAAY,CAAK,GAAA,CAAA,MAAA,CAAA;GAC9B,GAAA,CAAA,EAE4C,GAAA;IADxC,MAAK;IACL,OAAM;GACV,CAAA,GAAA,EAAmC,QAAA,MAAA,EAA1B,EAAW,KAAK,GAAA,CAAA,CAAA,GAAA,IAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"PkToolShowSuggestedReply-BvHgjMQ4.js","names":[],"sources":["../../../../packages/components/src/chat/PkToolShowSuggestedReply.vue","../../../../packages/components/src/chat/PkToolShowSuggestedReply.vue","../../../../packages/components/src/chat/PkToolShowSuggestedReply.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, ref } from 'vue'\n import { isVoiceRenderAck } from 'models'\n\n type Suggestion = { label: string; prompt?: string }\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const emit = defineEmits<{\n select: [value: string]\n }>()\n\n const toolPart = computed(() => {\n return JSON.parse(JSON.stringify(props.part)) as {\n state?: string\n toolCallId?: string\n input?: {\n /** New schema: array of suggestions */\n suggestions?: Suggestion[]\n /** Legacy schema: single suggestion */\n label?: string\n prompt?: string\n }\n output?: string\n }\n })\n\n /** Normalize to always work with an array of suggestions */\n const suggestions = computed<Suggestion[]>(() => {\n const input = toolPart.value?.input\n if (!input) {\n return []\n }\n if (input.suggestions?.length) {\n return input.suggestions\n }\n // Legacy single-suggestion format (client-side fallback)\n if (input.label) {\n return [{ label: input.label, prompt: input.prompt ?? input.label }]\n }\n return []\n })\n\n const clickedIndex = ref<number | null>(null)\n\n const isInteractive = computed(() => {\n // A chip drawn during a CALL arrives already resolved: the turn could\n // not wait for a tap, so the backend closed the tool with the note\n // that it was drawn (see `isVoiceRenderAck`, same rule as the forms).\n // The note is not a tap, and without this the chips of every spoken\n // turn rendered dead.\n return (\n (toolPart.value?.state === 'input-available' ||\n isVoiceRenderAck(toolPart.value?.output)) &&\n clickedIndex.value === null\n )\n })\n\n function handleClick(index: number) {\n if (!isInteractive.value) {\n return\n }\n clickedIndex.value = index\n const suggestion = suggestions.value[index]\n const value = suggestion?.prompt ?? suggestion?.label\n if (value) {\n emit('select', value)\n }\n }\n</script>\n\n<template>\n <div v-if=\"suggestions.length\" class=\"pk-tool-suggested-reply\">\n <button\n v-for=\"(suggestion, index) in suggestions\"\n :key=\"index\"\n type=\"button\"\n class=\"pk-tool-suggested-reply__link\"\n :class=\"{\n 'is-clicked': clickedIndex === index,\n 'is-disabled': !isInteractive,\n }\"\n :disabled=\"!isInteractive\"\n @click.stop=\"handleClick(index)\">\n <VvIcon\n name=\"ri:corner-down-right-line\"\n class=\"pk-tool-suggested-reply__icon\" />\n <span>{{ suggestion.label }}</span>\n </button>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-tool-suggested-reply {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--spacing-4);\n\n &__link {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-4);\n background: none;\n border: none;\n padding: 0;\n font: inherit;\n font-size: var(--text-12);\n text-decoration: underline;\n text-underline-offset: 2px;\n cursor: pointer;\n color: var(--color-word-3);\n transition: var(--transition-colors);\n\n &:hover:not(:disabled) {\n color: var(--color-word);\n }\n\n &.is-disabled {\n cursor: default;\n pointer-events: none;\n color: var(--color-word-5);\n }\n\n &.is-clicked {\n color: var(--color-word-5);\n text-decoration: none;\n }\n }\n\n &__icon {\n width: 1em;\n height: 1em;\n flex-shrink: 0;\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { computed, ref } from 'vue'\n import { isVoiceRenderAck } from 'models'\n\n type Suggestion = { label: string; prompt?: string }\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const emit = defineEmits<{\n select: [value: string]\n }>()\n\n const toolPart = computed(() => {\n return JSON.parse(JSON.stringify(props.part)) as {\n state?: string\n toolCallId?: string\n input?: {\n /** New schema: array of suggestions */\n suggestions?: Suggestion[]\n /** Legacy schema: single suggestion */\n label?: string\n prompt?: string\n }\n output?: string\n }\n })\n\n /** Normalize to always work with an array of suggestions */\n const suggestions = computed<Suggestion[]>(() => {\n const input = toolPart.value?.input\n if (!input) {\n return []\n }\n if (input.suggestions?.length) {\n return input.suggestions\n }\n // Legacy single-suggestion format (client-side fallback)\n if (input.label) {\n return [{ label: input.label, prompt: input.prompt ?? input.label }]\n }\n return []\n })\n\n const clickedIndex = ref<number | null>(null)\n\n const isInteractive = computed(() => {\n // A chip drawn during a CALL arrives already resolved: the turn could\n // not wait for a tap, so the backend closed the tool with the note\n // that it was drawn (see `isVoiceRenderAck`, same rule as the forms).\n // The note is not a tap, and without this the chips of every spoken\n // turn rendered dead.\n return (\n (toolPart.value?.state === 'input-available' ||\n isVoiceRenderAck(toolPart.value?.output)) &&\n clickedIndex.value === null\n )\n })\n\n function handleClick(index: number) {\n if (!isInteractive.value) {\n return\n }\n clickedIndex.value = index\n const suggestion = suggestions.value[index]\n const value = suggestion?.prompt ?? suggestion?.label\n if (value) {\n emit('select', value)\n }\n }\n</script>\n\n<template>\n <div v-if=\"suggestions.length\" class=\"pk-tool-suggested-reply\">\n <button\n v-for=\"(suggestion, index) in suggestions\"\n :key=\"index\"\n type=\"button\"\n class=\"pk-tool-suggested-reply__link\"\n :class=\"{\n 'is-clicked': clickedIndex === index,\n 'is-disabled': !isInteractive,\n }\"\n :disabled=\"!isInteractive\"\n @click.stop=\"handleClick(index)\">\n <VvIcon\n name=\"ri:corner-down-right-line\"\n class=\"pk-tool-suggested-reply__icon\" />\n <span>{{ suggestion.label }}</span>\n </button>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-tool-suggested-reply {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--spacing-4);\n\n &__link {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-4);\n background: none;\n border: none;\n padding: 0;\n font: inherit;\n font-size: var(--text-12);\n text-decoration: underline;\n text-underline-offset: 2px;\n cursor: pointer;\n color: var(--color-word-3);\n transition: var(--transition-colors);\n\n &:hover:not(:disabled) {\n color: var(--color-word);\n }\n\n &.is-disabled {\n cursor: default;\n pointer-events: none;\n color: var(--color-word-5);\n }\n\n &.is-clicked {\n color: var(--color-word-5);\n text-decoration: none;\n }\n }\n\n &__icon {\n width: 1em;\n height: 1em;\n flex-shrink: 0;\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { computed, ref } from 'vue'\n import { isVoiceRenderAck } from 'models'\n\n type Suggestion = { label: string; prompt?: string }\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const emit = defineEmits<{\n select: [value: string]\n }>()\n\n const toolPart = computed(() => {\n return JSON.parse(JSON.stringify(props.part)) as {\n state?: string\n toolCallId?: string\n input?: {\n /** New schema: array of suggestions */\n suggestions?: Suggestion[]\n /** Legacy schema: single suggestion */\n label?: string\n prompt?: string\n }\n output?: string\n }\n })\n\n /** Normalize to always work with an array of suggestions */\n const suggestions = computed<Suggestion[]>(() => {\n const input = toolPart.value?.input\n if (!input) {\n return []\n }\n if (input.suggestions?.length) {\n return input.suggestions\n }\n // Legacy single-suggestion format (client-side fallback)\n if (input.label) {\n return [{ label: input.label, prompt: input.prompt ?? input.label }]\n }\n return []\n })\n\n const clickedIndex = ref<number | null>(null)\n\n const isInteractive = computed(() => {\n // A chip drawn during a CALL arrives already resolved: the turn could\n // not wait for a tap, so the backend closed the tool with the note\n // that it was drawn (see `isVoiceRenderAck`, same rule as the forms).\n // The note is not a tap, and without this the chips of every spoken\n // turn rendered dead.\n return (\n (toolPart.value?.state === 'input-available' ||\n isVoiceRenderAck(toolPart.value?.output)) &&\n clickedIndex.value === null\n )\n })\n\n function handleClick(index: number) {\n if (!isInteractive.value) {\n return\n }\n clickedIndex.value = index\n const suggestion = suggestions.value[index]\n const value = suggestion?.prompt ?? suggestion?.label\n if (value) {\n emit('select', value)\n }\n }\n</script>\n\n<template>\n <div v-if=\"suggestions.length\" class=\"pk-tool-suggested-reply\">\n <button\n v-for=\"(suggestion, index) in suggestions\"\n :key=\"index\"\n type=\"button\"\n class=\"pk-tool-suggested-reply__link\"\n :class=\"{\n 'is-clicked': clickedIndex === index,\n 'is-disabled': !isInteractive,\n }\"\n :disabled=\"!isInteractive\"\n @click.stop=\"handleClick(index)\">\n <VvIcon\n name=\"ri:corner-down-right-line\"\n class=\"pk-tool-suggested-reply__icon\" />\n <span>{{ suggestion.label }}</span>\n </button>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-tool-suggested-reply {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--spacing-4);\n\n &__link {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-4);\n background: none;\n border: none;\n padding: 0;\n font: inherit;\n font-size: var(--text-12);\n text-decoration: underline;\n text-underline-offset: 2px;\n cursor: pointer;\n color: var(--color-word-3);\n transition: var(--transition-colors);\n\n &:hover:not(:disabled) {\n color: var(--color-word);\n }\n\n &.is-disabled {\n cursor: default;\n pointer-events: none;\n color: var(--color-word-5);\n }\n\n &.is-clicked {\n color: var(--color-word-5);\n text-decoration: none;\n }\n }\n\n &__icon {\n width: 1em;\n height: 1em;\n flex-shrink: 0;\n }\n }\n</style>\n"],"mappings":";;;;;;;;;;;;;EAMI,IAAM,IAAQ,GAIR,IAAO,GAIP,IAAW,QACN,KAAK,MAAM,KAAK,UAAU,EAAM,IAAI,CAAC,CAY/C,GAGK,IAAc,QAA6B;GAC7C,IAAM,IAAQ,EAAS,OAAO;GAW9B,OAVK,IAGD,EAAM,aAAa,SACZ,EAAM,cAGb,EAAM,QACC,CAAC;IAAE,OAAO,EAAM;IAAO,QAAQ,EAAM,UAAU,EAAM;GAAM,CAAC,IAEhE,CAAC,IATG,CAAC;EAUhB,CAAC,GAEK,IAAe,EAAmB,IAAI,GAEtC,IAAgB,SAOb,EAAS,OAAO,UAAU,qBACvB,EAAiB,EAAS,OAAO,MAAM,MAC3C,EAAa,UAAU,IAE9B;EAED,SAAS,EAAY,GAAe;GAChC,IAAI,CAAC,EAAc,OACf;GAEJ,EAAa,QAAQ;GACrB,IAAM,IAAa,EAAY,MAAM,IAC/B,IAAQ,GAAY,UAAU,GAAY;GAChD,AAAI,KACA,EAAK,UAAU,CAAK;EAE5B;;;GAIW,OAAA,EAAA,MAAY,UAAvB,EAAA,GAAA,EAiBM,OAjBN,GAiBM,EAhBF,EAAA,EAAA,GAAA,EAeS,GAAA,MAAA,EAdyB,EAAA,QAAtB,GAAY,OADxB,EAAA,GAAA,EAeS,UAAA;IAbJ,KAAK;IACN,MAAK;IACL,OAAK,EAAA,CAAC,iCAA+B;KACG,cAAA,EAAA,UAAiB;KAAuC,eAAA,CAAA,EAAA;;IAI/F,UAAQ,CAAG,EAAA;IACX,SAAK,GAAA,MAAO,EAAY,CAAK,GAAA,CAAA,MAAA,CAAA;GAC9B,GAAA,CAAA,EAE4C,GAAA;IADxC,MAAK;IACL,OAAM;GACV,CAAA,GAAA,EAAmC,QAAA,MAAA,EAA1B,EAAW,KAAK,GAAA,CAAA,CAAA,GAAA,IAAA,CAAA"}
|