@8wave/ai-elements 0.110.0-beta.1 → 0.110.0
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/ai-elements.es.js +678 -674
- 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/{PkChatbot-CDciy1W-.js → PkChatbot-o7k7d-fj.js} +6 -6
- package/dist-vue/_chunks/{PkChatbot-CDciy1W-.js.map → PkChatbot-o7k7d-fj.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotMessages-5dW-ElPy.js → PkChatbotMessages-BFlU046I.js} +3 -3
- package/dist-vue/_chunks/{PkChatbotMessages-5dW-ElPy.js.map → PkChatbotMessages-BFlU046I.js.map} +1 -1
- package/dist-vue/_chunks/PkChatbotStepPayload-BH6piqOi.js.map +1 -1
- package/dist-vue/_chunks/PkChatbotStepToolDetail-BJ7xUvgi.js.map +1 -1
- package/dist-vue/_chunks/PkChatbotSteps-DtV1beDc.js.map +1 -1
- package/dist-vue/_chunks/{PkChatbotViewChat-C9PQWz8z.js → PkChatbotViewChat-CNH_glPI.js} +4 -4
- package/dist-vue/_chunks/{PkChatbotViewChat-C9PQWz8z.js.map → PkChatbotViewChat-CNH_glPI.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewConversations-Duc-I3pp.js → PkChatbotViewConversations-BENvHLZt.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewConversations-Duc-I3pp.js.map → PkChatbotViewConversations-BENvHLZt.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewFiles-B7KaE7rU.js → PkChatbotViewFiles-qmLESi1V.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewFiles-B7KaE7rU.js.map → PkChatbotViewFiles-qmLESi1V.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewProfile-tKK5Yazl.js → PkChatbotViewProfile-DzfZnitQ.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewProfile-tKK5Yazl.js.map → PkChatbotViewProfile-DzfZnitQ.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowDiagram-CNBaJZlp.js → PkToolShowDiagram-DuDp5EHd.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowDiagram-CNBaJZlp.js.map → PkToolShowDiagram-DuDp5EHd.js.map} +1 -1
- package/dist-vue/_chunks/{useChatbotStore-DufJlucU.js → useChatbotStore-CiRAuq7v.js} +82 -78
- package/dist-vue/_chunks/useChatbotStore-CiRAuq7v.js.map +1 -0
- package/dist-vue/apps/web-component/src/composables/useChatbotAgent.d.ts +14 -14
- package/dist-vue/composables.js +1 -1
- package/dist-vue/index.js +7 -7
- package/dist-vue/index.js.map +1 -1
- package/dist-vue/packages/components/src/PkEditorMedia.d.ts +42 -42
- package/dist-vue/packages/components/src/chat/PkAgentSettingsPanel.d.ts +14 -14
- package/dist-vue/packages/composable/src/chatbot/useChatbotStore.d.ts +42 -42
- package/dist-vue/packages/models/src/schema/Agent.d.ts +112 -112
- package/dist-vue/packages/models/src/schema/Document.d.ts +126 -126
- package/dist-vue/packages/models/src/schema/Member.d.ts +14 -14
- package/dist-vue/packages/models/src/schema/Organization.d.ts +28 -28
- package/dist-vue/packages/models/src/schema/ReasoningChat.d.ts +28 -28
- package/dist-vue/packages/models/src/schema/SubAgent.d.ts +28 -28
- package/dist-vue/packages/models/src/schema/User.d.ts +42 -42
- package/dist-vue/style.css +1 -1
- package/package.json +2 -2
- package/dist-vue/_chunks/useChatbotStore-DufJlucU.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PkChatbotStepPayload-BH6piqOi.js","names":[],"sources":["../../../../packages/components/src/chat/PkChatbotStepPayload.vue","../../../../packages/components/src/chat/PkChatbotStepPayload.vue"],"sourcesContent":["<script lang=\"ts\" setup>\n import { computed, reactive, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { stringifyPayload } from './utils'\n\n interface PayloadRow {\n key: string\n preview: string\n full?: string\n }\n\n const props = defineProps<{\n label: string\n value: unknown\n }>()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n // A preview stays on a single line: long enough to recognize the value,\n // short enough to keep the payload readable without expanding anything.\n const PREVIEW_LENGTH = 120\n\n const stringify = (value: unknown, indent = 0): string | undefined => {\n try {\n return JSON.stringify(value, null, indent)\n } catch {\n return undefined\n }\n }\n\n const toPreview = (value: unknown): string => {\n if (value === undefined) {\n return ''\n }\n const text =\n typeof value === 'string' ? value : (stringify(value) ?? '')\n const single = text.replace(/\\s+/g, ' ').trim()\n return single.length > PREVIEW_LENGTH\n ? `${single.slice(0, PREVIEW_LENGTH)}…`\n : single\n }\n\n // Objects are listed by key, arrays by position: either way the payload\n // becomes a list of short lines instead of one wall of JSON.\n const entries = computed<[string, unknown][] | undefined>(() => {\n const value = props.value\n if (Array.isArray(value)) {\n return value.length\n ? value.map(\n (item, index) =>\n [`${index + 1}`, item] as [string, unknown],\n )\n : undefined\n }\n if (value && typeof value === 'object') {\n const own = Object.entries(value as Record<string, unknown>)\n return own.length ? own : undefined\n }\n return undefined\n })\n\n const rows = computed<PayloadRow[] | undefined>(() =>\n entries.value?.map(([key, value]) => {\n const preview = toPreview(value)\n const full = typeof value === 'string' ? value : stringify(value, 2)\n return {\n key,\n preview: preview || '-',\n // Only a value the preview had to cut (long, multiline or\n // nested) is worth its own expandable block.\n full: full && full.trim() !== preview ? full : undefined,\n }\n }),\n )\n\n // Scalars and strings have no rows: they fall back to the plain payload.\n const text = computed(() =>\n rows.value ? undefined : stringifyPayload(props.value),\n )\n\n const summary = computed(() => {\n const list = rows.value\n if (!list) {\n return toPreview(props.value)\n }\n if (Array.isArray(props.value)) {\n return $t('messagePart.stepItems', list.length)\n }\n if (list.length === 1) {\n return `${list[0].key}: ${list[0].preview}`\n }\n return list.map((row) => row.key).join(' · ')\n })\n\n const isOpen = ref(false)\n const expandedRows = reactive(new Set<string>())\n const toggleRow = (key: string) => {\n if (expandedRows.has(key)) {\n expandedRows.delete(key)\n } else {\n expandedRows.add(key)\n }\n }\n</script>\n\n<template>\n <div v-if=\"rows || text\" class=\"pk-chatbot-step-payload\">\n <button\n type=\"button\"\n class=\"pk-chatbot-step-payload__header\"\n :aria-expanded=\"isOpen\"\n @click=\"isOpen = !isOpen\">\n <span class=\"pk-chatbot-step-payload__label\">{{ label }}</span>\n <span class=\"pk-chatbot-step-payload__summary\">\n {{ summary }}\n </span>\n <VvIcon\n :name=\"isOpen ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"shrink-0\" />\n </button>\n <ul v-if=\"isOpen && rows\" class=\"pk-chatbot-step-payload__rows\">\n <li v-for=\"row in rows\" :key=\"row.key\">\n <component\n :is=\"row.full ? 'button' : 'div'\"\n :type=\"row.full ? 'button' : undefined\"\n class=\"pk-chatbot-step-payload__row\"\n :aria-expanded=\"\n row.full ? expandedRows.has(row.key) : undefined\n \"\n @click=\"row.full ? toggleRow(row.key) : undefined\">\n <span class=\"pk-chatbot-step-payload__key\">\n {{ row.key }}\n </span>\n <span class=\"pk-chatbot-step-payload__value\">\n {{ row.preview }}\n </span>\n <VvIcon\n v-if=\"row.full\"\n :name=\"\n expandedRows.has(row.key)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"shrink-0\" />\n </component>\n <pre\n v-if=\"row.full && expandedRows.has(row.key)\"\n class=\"pk-chatbot-step-payload__full\"\n >{{ row.full }}</pre>\n </li>\n </ul>\n <pre v-else-if=\"isOpen\" class=\"pk-chatbot-step-payload__full\">{{\n text\n }}</pre>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-step-payload {\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n\n &__header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n width: 100%;\n min-height: var(--spacing-28);\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n\n &__label {\n flex-shrink: 0;\n font-weight: 600;\n font-size: var(--text-10);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--color-word-3);\n }\n\n &__summary {\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n font-size: var(--text-10);\n color: var(--color-word-4);\n }\n\n &__rows > li {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n\n &__row {\n display: flex;\n align-items: baseline;\n gap: var(--spacing-8);\n width: 100%;\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n font-size: var(--text-10);\n\n &:is(button) {\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n &__key {\n flex-shrink: 0;\n max-width: 40%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n color: var(--color-word-4);\n }\n\n &__value {\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-word-2);\n }\n\n &__full {\n padding: var(--spacing-8);\n border-top: var(--spacing-px) solid var(--color-surface-3);\n max-height: var(--spacing-160);\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: var(--font-mono);\n font-size: var(--text-10);\n color: var(--color-word-3);\n }\n }\n</style>\n","<script lang=\"ts\" setup>\n import { computed, reactive, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { stringifyPayload } from './utils'\n\n interface PayloadRow {\n key: string\n preview: string\n full?: string\n }\n\n const props = defineProps<{\n label: string\n value: unknown\n }>()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n // A preview stays on a single line: long enough to recognize the value,\n // short enough to keep the payload readable without expanding anything.\n const PREVIEW_LENGTH = 120\n\n const stringify = (value: unknown, indent = 0): string | undefined => {\n try {\n return JSON.stringify(value, null, indent)\n } catch {\n return undefined\n }\n }\n\n const toPreview = (value: unknown): string => {\n if (value === undefined) {\n return ''\n }\n const text =\n typeof value === 'string' ? value : (stringify(value) ?? '')\n const single = text.replace(/\\s+/g, ' ').trim()\n return single.length > PREVIEW_LENGTH\n ? `${single.slice(0, PREVIEW_LENGTH)}…`\n : single\n }\n\n // Objects are listed by key, arrays by position: either way the payload\n // becomes a list of short lines instead of one wall of JSON.\n const entries = computed<[string, unknown][] | undefined>(() => {\n const value = props.value\n if (Array.isArray(value)) {\n return value.length\n ? value.map(\n (item, index) =>\n [`${index + 1}`, item] as [string, unknown],\n )\n : undefined\n }\n if (value && typeof value === 'object') {\n const own = Object.entries(value as Record<string, unknown>)\n return own.length ? own : undefined\n }\n return undefined\n })\n\n const rows = computed<PayloadRow[] | undefined>(() =>\n entries.value?.map(([key, value]) => {\n const preview = toPreview(value)\n const full = typeof value === 'string' ? value : stringify(value, 2)\n return {\n key,\n preview: preview || '-',\n // Only a value the preview had to cut (long, multiline or\n // nested) is worth its own expandable block.\n full: full && full.trim() !== preview ? full : undefined,\n }\n }),\n )\n\n // Scalars and strings have no rows: they fall back to the plain payload.\n const text = computed(() =>\n rows.value ? undefined : stringifyPayload(props.value),\n )\n\n const summary = computed(() => {\n const list = rows.value\n if (!list) {\n return toPreview(props.value)\n }\n if (Array.isArray(props.value)) {\n return $t('messagePart.stepItems', list.length)\n }\n if (list.length === 1) {\n return `${list[0].key}: ${list[0].preview}`\n }\n return list.map((row) => row.key).join(' · ')\n })\n\n const isOpen = ref(false)\n const expandedRows = reactive(new Set<string>())\n const toggleRow = (key: string) => {\n if (expandedRows.has(key)) {\n expandedRows.delete(key)\n } else {\n expandedRows.add(key)\n }\n }\n</script>\n\n<template>\n <div v-if=\"rows || text\" class=\"pk-chatbot-step-payload\">\n <button\n type=\"button\"\n class=\"pk-chatbot-step-payload__header\"\n :aria-expanded=\"isOpen\"\n @click=\"isOpen = !isOpen\">\n <span class=\"pk-chatbot-step-payload__label\">{{ label }}</span>\n <span class=\"pk-chatbot-step-payload__summary\">\n {{ summary }}\n </span>\n <VvIcon\n :name=\"isOpen ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"shrink-0\" />\n </button>\n <ul v-if=\"isOpen && rows\" class=\"pk-chatbot-step-payload__rows\">\n <li v-for=\"row in rows\" :key=\"row.key\">\n <component\n :is=\"row.full ? 'button' : 'div'\"\n :type=\"row.full ? 'button' : undefined\"\n class=\"pk-chatbot-step-payload__row\"\n :aria-expanded=\"\n row.full ? expandedRows.has(row.key) : undefined\n \"\n @click=\"row.full ? toggleRow(row.key) : undefined\">\n <span class=\"pk-chatbot-step-payload__key\">\n {{ row.key }}\n </span>\n <span class=\"pk-chatbot-step-payload__value\">\n {{ row.preview }}\n </span>\n <VvIcon\n v-if=\"row.full\"\n :name=\"\n expandedRows.has(row.key)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"shrink-0\" />\n </component>\n <pre\n v-if=\"row.full && expandedRows.has(row.key)\"\n class=\"pk-chatbot-step-payload__full\"\n >{{ row.full }}</pre>\n </li>\n </ul>\n <pre v-else-if=\"isOpen\" class=\"pk-chatbot-step-payload__full\">{{\n text\n }}</pre>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-step-payload {\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n\n &__header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n width: 100%;\n min-height: var(--spacing-28);\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n\n &__label {\n flex-shrink: 0;\n font-weight: 600;\n font-size: var(--text-10);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--color-word-3);\n }\n\n &__summary {\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n font-size: var(--text-10);\n color: var(--color-word-4);\n }\n\n &__rows > li {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n\n &__row {\n display: flex;\n align-items: baseline;\n gap: var(--spacing-8);\n width: 100%;\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n font-size: var(--text-10);\n\n &:is(button) {\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n &__key {\n flex-shrink: 0;\n max-width: 40%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n color: var(--color-word-4);\n }\n\n &__value {\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-word-2);\n }\n\n &__full {\n padding: var(--spacing-8);\n border-top: var(--spacing-px) solid var(--color-surface-3);\n max-height: var(--spacing-160);\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: var(--font-mono);\n font-size: var(--text-10);\n color: var(--color-word-3);\n }\n }\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PkChatbotStepPayload-BH6piqOi.js","names":[],"sources":["../../../../packages/components/src/chat/PkChatbotStepPayload.vue","../../../../packages/components/src/chat/PkChatbotStepPayload.vue"],"sourcesContent":["<script lang=\"ts\" setup>\n import { computed, reactive, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { stringifyPayload } from './utils'\n\n interface PayloadRow {\n key: string\n preview: string\n full?: string\n }\n\n const props = defineProps<{\n label: string\n value: unknown\n }>()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n // A preview stays on a single line: long enough to recognize the value,\n // short enough to keep the payload readable without expanding anything.\n const PREVIEW_LENGTH = 120\n\n const stringify = (value: unknown, indent = 0): string | undefined => {\n try {\n return JSON.stringify(value, null, indent)\n } catch {\n return undefined\n }\n }\n\n const toPreview = (value: unknown): string => {\n if (value === undefined) {\n return ''\n }\n const text =\n typeof value === 'string' ? value : (stringify(value) ?? '')\n const single = text.replace(/\\s+/g, ' ').trim()\n return single.length > PREVIEW_LENGTH\n ? `${single.slice(0, PREVIEW_LENGTH)}…`\n : single\n }\n\n // Objects are listed by key, arrays by position: either way the payload\n // becomes a list of short lines instead of one wall of JSON.\n const entries = computed<[string, unknown][] | undefined>(() => {\n const value = props.value\n if (Array.isArray(value)) {\n return value.length\n ? value.map(\n (item, index) =>\n [`${index + 1}`, item] as [string, unknown],\n )\n : undefined\n }\n if (value && typeof value === 'object') {\n const own = Object.entries(value as Record<string, unknown>)\n return own.length ? own : undefined\n }\n return undefined\n })\n\n const rows = computed<PayloadRow[] | undefined>(() =>\n entries.value?.map(([key, value]) => {\n const preview = toPreview(value)\n const full = typeof value === 'string' ? value : stringify(value, 2)\n return {\n key,\n preview: preview || '-',\n // Only a value the preview had to cut (long, multiline or\n // nested) is worth its own expandable block.\n full: full && full.trim() !== preview ? full : undefined,\n }\n }),\n )\n\n // Scalars and strings have no rows: they fall back to the plain payload.\n const text = computed(() =>\n rows.value ? undefined : stringifyPayload(props.value),\n )\n\n const summary = computed(() => {\n const list = rows.value\n if (!list) {\n return toPreview(props.value)\n }\n if (Array.isArray(props.value)) {\n return $t('messagePart.stepItems', list.length)\n }\n if (list.length === 1) {\n return `${list[0].key}: ${list[0].preview}`\n }\n return list.map((row) => row.key).join(' · ')\n })\n\n const isOpen = ref(false)\n const expandedRows = reactive(new Set<string>())\n const toggleRow = (key: string) => {\n if (expandedRows.has(key)) {\n expandedRows.delete(key)\n } else {\n expandedRows.add(key)\n }\n }\n</script>\n\n<template>\n <div v-if=\"rows || text\" class=\"pk-chatbot-step-payload\">\n <button\n type=\"button\"\n class=\"pk-chatbot-step-payload__header\"\n :aria-expanded=\"isOpen\"\n @click=\"isOpen = !isOpen\">\n <span class=\"pk-chatbot-step-payload__label\">{{ label }}</span>\n <span class=\"pk-chatbot-step-payload__summary\">\n {{ summary }}\n </span>\n <VvIcon\n :name=\"isOpen ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"shrink-0\" />\n </button>\n <ul v-if=\"isOpen && rows\" class=\"pk-chatbot-step-payload__rows\">\n <li v-for=\"row in rows\" :key=\"row.key\">\n <component\n :is=\"row.full ? 'button' : 'div'\"\n :type=\"row.full ? 'button' : undefined\"\n class=\"pk-chatbot-step-payload__row\"\n :aria-expanded=\"\n row.full ? expandedRows.has(row.key) : undefined\n \"\n @click=\"row.full ? toggleRow(row.key) : undefined\">\n <span class=\"pk-chatbot-step-payload__key\">\n {{ row.key }}\n </span>\n <span class=\"pk-chatbot-step-payload__value\">\n {{ row.preview }}\n </span>\n <VvIcon\n v-if=\"row.full\"\n :name=\"\n expandedRows.has(row.key)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"shrink-0\" />\n </component>\n <pre\n v-if=\"row.full && expandedRows.has(row.key)\"\n class=\"pk-chatbot-step-payload__full\"\n >{{ row.full }}</pre>\n </li>\n </ul>\n <pre v-else-if=\"isOpen\" class=\"pk-chatbot-step-payload__full\">{{\n text\n }}</pre>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-step-payload {\n border: var(--spacing-px) solid var(--color-surface-3);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n\n &__header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n width: 100%;\n min-height: var(--spacing-28);\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n\n &__label {\n flex-shrink: 0;\n font-weight: 600;\n font-size: var(--text-10);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--color-word-3);\n }\n\n &__summary {\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n font-size: var(--text-10);\n color: var(--color-word-4);\n }\n\n &__rows > li {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n\n &__row {\n display: flex;\n align-items: baseline;\n gap: var(--spacing-8);\n width: 100%;\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n font-size: var(--text-10);\n\n &:is(button) {\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n &__key {\n flex-shrink: 0;\n max-width: 40%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n color: var(--color-word-4);\n }\n\n &__value {\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-word-2);\n }\n\n &__full {\n padding: var(--spacing-8);\n border-top: var(--spacing-px) solid var(--color-surface-3);\n max-height: var(--spacing-160);\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: var(--font-mono);\n font-size: var(--text-10);\n color: var(--color-word-3);\n }\n }\n</style>\n","<script lang=\"ts\" setup>\n import { computed, reactive, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { stringifyPayload } from './utils'\n\n interface PayloadRow {\n key: string\n preview: string\n full?: string\n }\n\n const props = defineProps<{\n label: string\n value: unknown\n }>()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n // A preview stays on a single line: long enough to recognize the value,\n // short enough to keep the payload readable without expanding anything.\n const PREVIEW_LENGTH = 120\n\n const stringify = (value: unknown, indent = 0): string | undefined => {\n try {\n return JSON.stringify(value, null, indent)\n } catch {\n return undefined\n }\n }\n\n const toPreview = (value: unknown): string => {\n if (value === undefined) {\n return ''\n }\n const text =\n typeof value === 'string' ? value : (stringify(value) ?? '')\n const single = text.replace(/\\s+/g, ' ').trim()\n return single.length > PREVIEW_LENGTH\n ? `${single.slice(0, PREVIEW_LENGTH)}…`\n : single\n }\n\n // Objects are listed by key, arrays by position: either way the payload\n // becomes a list of short lines instead of one wall of JSON.\n const entries = computed<[string, unknown][] | undefined>(() => {\n const value = props.value\n if (Array.isArray(value)) {\n return value.length\n ? value.map(\n (item, index) =>\n [`${index + 1}`, item] as [string, unknown],\n )\n : undefined\n }\n if (value && typeof value === 'object') {\n const own = Object.entries(value as Record<string, unknown>)\n return own.length ? own : undefined\n }\n return undefined\n })\n\n const rows = computed<PayloadRow[] | undefined>(() =>\n entries.value?.map(([key, value]) => {\n const preview = toPreview(value)\n const full = typeof value === 'string' ? value : stringify(value, 2)\n return {\n key,\n preview: preview || '-',\n // Only a value the preview had to cut (long, multiline or\n // nested) is worth its own expandable block.\n full: full && full.trim() !== preview ? full : undefined,\n }\n }),\n )\n\n // Scalars and strings have no rows: they fall back to the plain payload.\n const text = computed(() =>\n rows.value ? undefined : stringifyPayload(props.value),\n )\n\n const summary = computed(() => {\n const list = rows.value\n if (!list) {\n return toPreview(props.value)\n }\n if (Array.isArray(props.value)) {\n return $t('messagePart.stepItems', list.length)\n }\n if (list.length === 1) {\n return `${list[0].key}: ${list[0].preview}`\n }\n return list.map((row) => row.key).join(' · ')\n })\n\n const isOpen = ref(false)\n const expandedRows = reactive(new Set<string>())\n const toggleRow = (key: string) => {\n if (expandedRows.has(key)) {\n expandedRows.delete(key)\n } else {\n expandedRows.add(key)\n }\n }\n</script>\n\n<template>\n <div v-if=\"rows || text\" class=\"pk-chatbot-step-payload\">\n <button\n type=\"button\"\n class=\"pk-chatbot-step-payload__header\"\n :aria-expanded=\"isOpen\"\n @click=\"isOpen = !isOpen\">\n <span class=\"pk-chatbot-step-payload__label\">{{ label }}</span>\n <span class=\"pk-chatbot-step-payload__summary\">\n {{ summary }}\n </span>\n <VvIcon\n :name=\"isOpen ? 'ri:arrow-up-s-line' : 'ri:arrow-down-s-line'\"\n class=\"shrink-0\" />\n </button>\n <ul v-if=\"isOpen && rows\" class=\"pk-chatbot-step-payload__rows\">\n <li v-for=\"row in rows\" :key=\"row.key\">\n <component\n :is=\"row.full ? 'button' : 'div'\"\n :type=\"row.full ? 'button' : undefined\"\n class=\"pk-chatbot-step-payload__row\"\n :aria-expanded=\"\n row.full ? expandedRows.has(row.key) : undefined\n \"\n @click=\"row.full ? toggleRow(row.key) : undefined\">\n <span class=\"pk-chatbot-step-payload__key\">\n {{ row.key }}\n </span>\n <span class=\"pk-chatbot-step-payload__value\">\n {{ row.preview }}\n </span>\n <VvIcon\n v-if=\"row.full\"\n :name=\"\n expandedRows.has(row.key)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"shrink-0\" />\n </component>\n <pre\n v-if=\"row.full && expandedRows.has(row.key)\"\n class=\"pk-chatbot-step-payload__full\"\n >{{ row.full }}</pre>\n </li>\n </ul>\n <pre v-else-if=\"isOpen\" class=\"pk-chatbot-step-payload__full\">{{\n text\n }}</pre>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-step-payload {\n border: var(--spacing-px) solid var(--color-surface-3);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n\n &__header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n width: 100%;\n min-height: var(--spacing-28);\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n\n &__label {\n flex-shrink: 0;\n font-weight: 600;\n font-size: var(--text-10);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--color-word-3);\n }\n\n &__summary {\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n font-size: var(--text-10);\n color: var(--color-word-4);\n }\n\n &__rows > li {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n\n &__row {\n display: flex;\n align-items: baseline;\n gap: var(--spacing-8);\n width: 100%;\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n font-size: var(--text-10);\n\n &:is(button) {\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n &__key {\n flex-shrink: 0;\n max-width: 40%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n color: var(--color-word-4);\n }\n\n &__value {\n flex: 1 1 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-word-2);\n }\n\n &__full {\n padding: var(--spacing-8);\n border-top: var(--spacing-px) solid var(--color-surface-3);\n max-height: var(--spacing-160);\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: var(--font-mono);\n font-size: var(--text-10);\n color: var(--color-word-3);\n }\n }\n</style>\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PkChatbotStepToolDetail-BJ7xUvgi.js","names":[],"sources":["../../../../packages/components/src/chat/PkChatbotStepToolDetail.vue","../../../../packages/components/src/chat/PkChatbotStepToolDetail.vue"],"sourcesContent":["<script lang=\"ts\" setup>\n import { computed, reactive } from 'vue'\n import { useI18n } from 'vue-i18n'\n import type { UIChatMessage } from 'models'\n import PkChatbotStepPayload from './PkChatbotStepPayload.vue'\n import { hasPayload, WEB_SEARCH_TOOL_TYPES } from './utils'\n\n type MessagePart = UIChatMessage['parts'][number]\n\n interface DetailHit {\n key: string\n icon: string\n title: string\n url?: string\n meta?: string\n relevance?: number\n body?: string\n }\n\n interface DetailFact {\n icon: string\n text: string\n }\n\n interface Detail {\n chips?: string[]\n facts?: DetailFact[]\n hits?: DetailHit[]\n showEmpty?: boolean\n passage?: { title?: string; meta?: string; content: string }\n rawInput?: unknown\n rawOutput?: unknown\n }\n\n const props = defineProps<{\n part: MessagePart\n }>()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const asRecord = (value: unknown): Record<string, unknown> | undefined =>\n value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined\n\n const pickString = (\n record: Record<string, unknown> | undefined,\n ...keys: string[]\n ): string | undefined => {\n for (const key of keys) {\n const value = record?.[key]\n if (typeof value === 'string' && value.trim()) {\n return value\n }\n }\n return undefined\n }\n\n const formatCoordinates = (\n record?: Record<string, unknown>,\n ): string | undefined => {\n const latitude = record?.latitude\n const longitude = record?.longitude\n if (typeof latitude !== 'number' || typeof longitude !== 'number') {\n return undefined\n }\n return `${latitude.toFixed(4)}, ${longitude.toFixed(4)}`\n }\n\n const rawInputValue = computed(() =>\n 'input' in props.part ? props.part.input : undefined,\n )\n const rawOutputValue = computed(() =>\n 'output' in props.part ? props.part.output : undefined,\n )\n const input = computed(() => asRecord(rawInputValue.value))\n const output = computed(() => asRecord(rawOutputValue.value))\n\n // Search queries shown as chips (queries[], query or objective)\n const queryChips = computed<string[]>(() => {\n const raw =\n input.value?.queries ?? input.value?.query ?? input.value?.objective\n if (typeof raw === 'string' && raw.trim()) {\n return [raw]\n }\n if (Array.isArray(raw)) {\n return raw.filter(\n (query): query is string =>\n typeof query === 'string' && Boolean(query.trim()),\n )\n }\n return []\n })\n\n // Raw scores live on different scales depending on the agent search mode\n // (RRF ~0.03, cosine 0 to 1) and tend to cluster, so a plain ratio on the\n // max renders every bar as ~100%. Spread them on a min/max scale with a\n // floor so the ordering stays visible, and hide the bar entirely when it\n // carries no information (single hit or identical scores).\n const spreadRelevance = (scores: number[]): (number | undefined)[] => {\n const min = Math.min(...scores)\n const max = Math.max(...scores)\n if (scores.length < 2 || max <= min) {\n return scores.map(() => undefined)\n }\n const floor = 0.15\n return scores.map(\n (score) => floor + ((score - min) / (max - min)) * (1 - floor),\n )\n }\n\n // Normalizes a raw result array into records paired with a spread\n // relevance derived from each record's `similarity` score.\n const scoredRecords = (\n raw: unknown,\n ):\n | { record: Record<string, unknown>; relevance?: number }[]\n | undefined => {\n if (!Array.isArray(raw)) {\n return undefined\n }\n const records = raw\n .map((item) => asRecord(item))\n .filter((record): record is Record<string, unknown> =>\n Boolean(record),\n )\n const relevances = spreadRelevance(\n records.map((record) =>\n typeof record.similarity === 'number' ? record.similarity : 0,\n ),\n )\n return records.map((record, index) => ({\n record,\n relevance: relevances[index],\n }))\n }\n\n // searchInKnowledgeBase: group chunks by document, relevance compares the\n // best chunk score of each document within the result set.\n const knowledgeHits = computed<DetailHit[] | undefined>(() => {\n const results = output.value?.results\n if (!Array.isArray(results)) {\n return undefined\n }\n interface DocumentGroup {\n id: string\n type: string\n title: string\n url?: string\n similarity: number\n chunks: string[]\n }\n const groups = new Map<string, DocumentGroup>()\n for (const [index, raw] of results.entries()) {\n const result = asRecord(raw)\n if (!result) {\n continue\n }\n const document = asRecord(result.document)\n const source = asRecord(document?.source)\n const id =\n typeof document?.id === 'string' ? document.id : `doc-${index}`\n const similarity =\n typeof result.similarity === 'number' ? result.similarity : 0\n let group = groups.get(id)\n if (!group) {\n group = {\n id,\n type:\n typeof document?.type === 'string' ? document.type : '',\n title:\n pickString(document, 'name') ??\n pickString(source, 'filename', 'url') ??\n $t('messagePart.stepDocument'),\n url: pickString(source, 'url'),\n similarity,\n chunks: [],\n }\n groups.set(id, group)\n }\n group.similarity = Math.max(group.similarity, similarity)\n if (typeof result.content === 'string' && result.content.trim()) {\n group.chunks.push(result.content)\n }\n }\n const list = [...groups.values()]\n const relevances = spreadRelevance(list.map((g) => g.similarity))\n return list.map((group, index) => ({\n key: group.id,\n icon: group.type === 'url' ? 'ri:global-line' : 'ri:file-text-line',\n title: group.title,\n url: group.url,\n meta: $t('messagePart.stepExcerpts', group.chunks.length || 1),\n relevance: relevances[index],\n body: group.chunks.join('\\n\\n· · ·\\n\\n') || undefined,\n }))\n })\n\n // Chunks this turn had already returned come back as references without\n // content (spec retrieval-payload-per-turn, R2). They carry no document\n // name to group by, so they are summarized in one line: without it a\n // search that found only known passages reads as \"no results\".\n const repeatedCount = computed<number>(() => {\n const count = asRecord(output.value?.repeated)?.count\n return typeof count === 'number' ? count : 0\n })\n\n // Two declarations every search tool can attach to its output, and the\n // only ones the model is explicitly told not to read as an answer: a\n // query dropped for exceeding the tool's cap, and a search that ran out\n // of time. Without them a timeout renders as \"no results\", which is the\n // reading whoever opens a chat log would take away too.\n const searchFacts = computed<DetailFact[]>(() => {\n const facts: DetailFact[] = []\n const dropped = asRecord(output.value?.inputTruncated)?.dropped\n if (Array.isArray(dropped) && dropped.length) {\n facts.push({\n icon: 'ri:scissors-cut-line',\n text: $t('messagePart.stepDropped', dropped.length),\n })\n }\n const unavailable = asRecord(output.value?.searchUnavailable)\n if (unavailable) {\n // The corrected-answers tool declares one lookup without naming\n // its query, so an absent list means one search, not none.\n const queries = unavailable.queries\n facts.push({\n icon: 'ri:time-line',\n text: $t(\n 'messagePart.stepTimedOut',\n Array.isArray(queries) ? queries.length : 1,\n ),\n })\n }\n return facts\n })\n\n // retrieveRevisedAnswers: one hit per matched question, the corrected\n // answer is the expandable body.\n const revisedAnswerHits = computed<DetailHit[] | undefined>(() =>\n scoredRecords(output.value?.results)?.map(\n ({ record, relevance }, index) => ({\n key:\n pickString(record, 'revisedAnswerId') ?? `revised-${index}`,\n icon: 'ri:file-edit-line',\n title:\n pickString(record, 'userQuestion') ??\n $t('messagePart.revisedAnswers'),\n relevance,\n body: pickString(record, 'revisedMessage'),\n }),\n ),\n )\n\n // recallUserMemory / saveUserMemory delete candidates: one hit per\n // memory, long facts expand to their full text.\n const memoryHits = (raw: unknown): DetailHit[] | undefined =>\n scoredRecords(raw)?.map(({ record, relevance }, index) => {\n const content = pickString(record, 'content') ?? ''\n return {\n key: pickString(record, 'id') ?? `memory-${index}`,\n icon: 'ri:brain-line',\n title: content,\n meta: pickString(record, 'updatedAt')?.slice(0, 10),\n relevance,\n body: content.length > 120 ? content : undefined,\n }\n })\n\n // Query chips over a list of hits, flagging an explicit empty state so the\n // \"no results\" message shows once the tool has run (hits defined, length 0).\n const hitsDetail = (hits: DetailHit[] | undefined): Detail => ({\n chips: queryChips.value,\n hits,\n showEmpty: hits !== undefined && !hits.length,\n })\n\n // Rich, tool-specific view of the step. Returns {} when the tool has\n // nothing meaningful to show yet (the raw fallback is applied by `detail`).\n const buildDetail = (): Detail => {\n const type = props.part.type\n if (type === 'tool-searchInKnowledgeBase') {\n const knowledge = hitsDetail(knowledgeHits.value)\n if (repeatedCount.value) {\n knowledge.facts = [\n {\n icon: 'ri:history-line',\n text: $t(\n 'messagePart.stepRepeated',\n repeatedCount.value,\n ),\n },\n ]\n knowledge.showEmpty = false\n }\n return knowledge\n }\n if (type === 'tool-retrieveRevisedAnswers') {\n return hitsDetail(revisedAnswerHits.value)\n }\n if (type === 'tool-expandChunkContext') {\n const content = pickString(output.value, 'content')\n if (!content) {\n return {}\n }\n const totalChunks = output.value?.totalChunksReturned\n return {\n passage: {\n title: pickString(output.value, 'documentName'),\n meta:\n typeof totalChunks === 'number'\n ? $t('messagePart.stepExcerpts', totalChunks)\n : undefined,\n content,\n },\n }\n }\n if (type === 'tool-readDocumentFile') {\n const name = pickString(output.value, 'name')\n if (!name) {\n return {}\n }\n const mimeType = pickString(output.value, 'mimeType')\n return {\n facts: [\n {\n icon: 'ri:file-text-line',\n text: mimeType ? `${name} (${mimeType})` : name,\n },\n ],\n }\n }\n if (type === 'tool-getWeather') {\n const location =\n pickString(output.value, 'location') ??\n pickString(input.value, 'locationName') ??\n formatCoordinates(input.value)\n if (!location) {\n return {}\n }\n const startDate = pickString(input.value, 'startDate')\n const endDate = pickString(input.value, 'endDate')\n const dateRange = startDate\n ? endDate && endDate !== startDate\n ? ` · ${startDate} → ${endDate}`\n : ` · ${startDate}`\n : ''\n return {\n facts: [\n {\n icon: 'ri:sun-cloudy-line',\n text: `${location}${dateRange}`,\n },\n ],\n }\n }\n if (type === 'tool-geocode') {\n const from =\n pickString(input.value, 'locationName') ??\n formatCoordinates(input.value)\n const to =\n pickString(\n output.value,\n 'name',\n 'displayName',\n 'locationName',\n 'label',\n ) ?? formatCoordinates(output.value)\n const text =\n from && to && from !== to ? `${from} → ${to}` : (from ?? to)\n if (!text) {\n return {}\n }\n return {\n facts: [\n {\n icon: 'ri:map-pin-line',\n text,\n },\n ],\n }\n }\n if (type === 'tool-recallUserMemory') {\n return hitsDetail(memoryHits(output.value?.memories))\n }\n if (type === 'tool-saveUserMemory') {\n const candidates = memoryHits(output.value?.candidates)\n if (candidates) {\n return hitsDetail(candidates)\n }\n const text =\n pickString(input.value, 'content') ??\n pickString(output.value, 'message', 'error')\n if (!text) {\n return {}\n }\n return {\n facts: [\n {\n icon:\n pickString(input.value, 'action') === 'delete'\n ? 'ri:delete-bin-line'\n : 'ri:save-3-line',\n text,\n },\n ],\n }\n }\n if (WEB_SEARCH_TOOL_TYPES.includes(type)) {\n return {\n chips: queryChips.value,\n rawOutput: rawOutputValue.value,\n }\n }\n return {}\n }\n\n const detail = computed<Detail>(() => {\n const result = buildDetail()\n if (searchFacts.value.length) {\n result.facts = [...searchFacts.value, ...(result.facts ?? [])]\n }\n if (output.value?.searchUnavailable) {\n // \"No results found\" under a search that never answered is the\n // one sentence this fact exists to prevent.\n result.showEmpty = false\n }\n // Nothing meaningful to show for this tool yet (e.g. still running):\n // fall back to the raw payloads so the step body is never empty.\n const hasRichContent =\n result.chips?.length ||\n result.facts?.length ||\n result.passage ||\n result.showEmpty ||\n result.hits?.length\n if (!hasRichContent) {\n result.rawInput = rawInputValue.value\n result.rawOutput ??= rawOutputValue.value\n }\n return result\n })\n\n const expandedHits = reactive(new Set<string>())\n const toggleHit = (key: string) => {\n if (expandedHits.has(key)) {\n expandedHits.delete(key)\n } else {\n expandedHits.add(key)\n }\n }\n</script>\n\n<template>\n <div class=\"pk-chatbot-step-detail\">\n <ul v-if=\"detail.chips?.length\" class=\"pk-chatbot-step-detail__chips\">\n <li\n v-for=\"(chip, chipIndex) in detail.chips\"\n :key=\"`${chipIndex}-${chip}`\"\n class=\"pk-chatbot-step-detail__chip\">\n <VvIcon name=\"ri:search-line\" class=\"shrink-0\" />\n <span class=\"pk-chatbot-step-detail__chip-text\">\n {{ chip }}\n </span>\n </li>\n </ul>\n <div\n v-for=\"(fact, factIndex) in detail.facts\"\n :key=\"`${factIndex}-${fact.text}`\"\n class=\"pk-chatbot-step-detail__fact\">\n <VvIcon :name=\"fact.icon\" class=\"shrink-0\" />\n <span>{{ fact.text }}</span>\n </div>\n <div v-if=\"detail.showEmpty\" class=\"pk-chatbot-step-detail__empty\">\n {{ $t('messagePart.stepNoResults') }}\n </div>\n <ul\n v-else-if=\"detail.hits?.length\"\n class=\"pk-chatbot-step-detail__hits\">\n <li v-for=\"hit in detail.hits\" :key=\"hit.key\">\n <component\n :is=\"hit.body ? 'button' : 'div'\"\n :type=\"hit.body ? 'button' : undefined\"\n class=\"pk-chatbot-step-detail__hit-header\"\n :aria-expanded=\"\n hit.body ? expandedHits.has(hit.key) : undefined\n \"\n @click=\"hit.body ? toggleHit(hit.key) : undefined\">\n <VvIcon\n :name=\"hit.icon\"\n class=\"pk-chatbot-step-detail__hit-icon\" />\n <span class=\"pk-chatbot-step-detail__hit-heading\">\n <span class=\"pk-chatbot-step-detail__hit-title\">\n {{ hit.title }}\n </span>\n <a\n v-if=\"hit.url\"\n :href=\"hit.url\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pk-chatbot-step-detail__link\"\n @click.stop>\n <VvIcon name=\"ri:external-link-line\" />\n </a>\n </span>\n <span\n v-if=\"hit.meta\"\n class=\"pk-chatbot-step-detail__hit-meta\">\n {{ hit.meta }}\n </span>\n <span\n v-if=\"hit.relevance !== undefined\"\n class=\"pk-chatbot-step-detail__relevance\"\n aria-hidden=\"true\">\n <span\n class=\"pk-chatbot-step-detail__relevance-fill\"\n :style=\"{\n width: `${Math.round(hit.relevance * 100)}%`,\n }\" />\n </span>\n <VvIcon\n v-if=\"hit.body\"\n :name=\"\n expandedHits.has(hit.key)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"shrink-0\" />\n </component>\n <div\n v-if=\"hit.body && expandedHits.has(hit.key)\"\n class=\"pk-chatbot-step-detail__hit-body\">\n {{ hit.body }}\n </div>\n </li>\n </ul>\n <div v-if=\"detail.passage\" class=\"pk-chatbot-step-detail__passage\">\n <div class=\"pk-chatbot-step-detail__passage-header\">\n <VvIcon name=\"ri:file-text-line\" class=\"shrink-0\" />\n <span class=\"pk-chatbot-step-detail__hit-heading\">\n <span class=\"pk-chatbot-step-detail__hit-title\">\n {{\n detail.passage.title ??\n $t('messagePart.stepDocument')\n }}\n </span>\n </span>\n <span\n v-if=\"detail.passage.meta\"\n class=\"pk-chatbot-step-detail__hit-meta\">\n {{ detail.passage.meta }}\n </span>\n </div>\n <div class=\"pk-chatbot-step-detail__passage-content\">\n {{ detail.passage.content }}\n </div>\n </div>\n <PkChatbotStepPayload\n v-if=\"hasPayload(detail.rawInput)\"\n :label=\"$t('messagePart.stepInput')\"\n :value=\"detail.rawInput\" />\n <PkChatbotStepPayload\n v-if=\"hasPayload(detail.rawOutput)\"\n :label=\"$t('messagePart.stepOutput')\"\n :value=\"detail.rawOutput\" />\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-step-detail {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-8);\n font-size: var(--text-12);\n color: var(--color-word-3);\n\n &__chips {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-4);\n }\n\n &__chip {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-4);\n max-width: 100%;\n padding: var(--spacing-2) var(--spacing-8);\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface-1);\n color: var(--color-word-2);\n }\n\n &__chip-text {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__fact {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n color: var(--color-word-2);\n }\n\n &__empty {\n color: var(--color-word-4);\n font-style: italic;\n }\n\n &__hits {\n display: flex;\n flex-direction: column;\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n\n > li + li {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n }\n\n &__hit-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n width: 100%;\n min-height: var(--spacing-32);\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n\n &:is(button) {\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n &__hit-icon {\n flex-shrink: 0;\n color: var(--color-word-3);\n }\n\n // Grows to fill the row so meta, relevance and chevron stay aligned;\n // the title inside only shrinks, keeping the link icon next to the\n // title text instead of pushed to the right edge.\n &__hit-heading {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n flex: 1 1 0;\n min-width: 0;\n }\n\n &__hit-title {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-word-2);\n }\n\n &__hit-meta {\n color: var(--color-word-4);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n\n &__relevance {\n flex-shrink: 0;\n width: var(--spacing-40);\n height: var(--spacing-4);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface-3);\n overflow: hidden;\n }\n\n &__relevance-fill {\n display: block;\n height: 100%;\n border-radius: inherit;\n background-color: var(--chatbot-main-color, var(--color-word-4));\n }\n\n &__link {\n flex-shrink: 0;\n display: inline-flex;\n color: var(--color-word-4);\n\n &:hover {\n color: var(--color-word-1);\n }\n }\n\n &__hit-body,\n &__passage-content {\n padding: var(--spacing-8);\n max-height: var(--spacing-160);\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--color-word-3);\n }\n\n &__hit-body {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n\n &__passage {\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n }\n\n &__passage-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n min-height: var(--spacing-32);\n padding: var(--spacing-4) var(--spacing-8);\n border-bottom: var(--spacing-px) solid var(--color-surface-3);\n }\n }\n</style>\n","<script lang=\"ts\" setup>\n import { computed, reactive } from 'vue'\n import { useI18n } from 'vue-i18n'\n import type { UIChatMessage } from 'models'\n import PkChatbotStepPayload from './PkChatbotStepPayload.vue'\n import { hasPayload, WEB_SEARCH_TOOL_TYPES } from './utils'\n\n type MessagePart = UIChatMessage['parts'][number]\n\n interface DetailHit {\n key: string\n icon: string\n title: string\n url?: string\n meta?: string\n relevance?: number\n body?: string\n }\n\n interface DetailFact {\n icon: string\n text: string\n }\n\n interface Detail {\n chips?: string[]\n facts?: DetailFact[]\n hits?: DetailHit[]\n showEmpty?: boolean\n passage?: { title?: string; meta?: string; content: string }\n rawInput?: unknown\n rawOutput?: unknown\n }\n\n const props = defineProps<{\n part: MessagePart\n }>()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const asRecord = (value: unknown): Record<string, unknown> | undefined =>\n value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined\n\n const pickString = (\n record: Record<string, unknown> | undefined,\n ...keys: string[]\n ): string | undefined => {\n for (const key of keys) {\n const value = record?.[key]\n if (typeof value === 'string' && value.trim()) {\n return value\n }\n }\n return undefined\n }\n\n const formatCoordinates = (\n record?: Record<string, unknown>,\n ): string | undefined => {\n const latitude = record?.latitude\n const longitude = record?.longitude\n if (typeof latitude !== 'number' || typeof longitude !== 'number') {\n return undefined\n }\n return `${latitude.toFixed(4)}, ${longitude.toFixed(4)}`\n }\n\n const rawInputValue = computed(() =>\n 'input' in props.part ? props.part.input : undefined,\n )\n const rawOutputValue = computed(() =>\n 'output' in props.part ? props.part.output : undefined,\n )\n const input = computed(() => asRecord(rawInputValue.value))\n const output = computed(() => asRecord(rawOutputValue.value))\n\n // Search queries shown as chips (queries[], query or objective)\n const queryChips = computed<string[]>(() => {\n const raw =\n input.value?.queries ?? input.value?.query ?? input.value?.objective\n if (typeof raw === 'string' && raw.trim()) {\n return [raw]\n }\n if (Array.isArray(raw)) {\n return raw.filter(\n (query): query is string =>\n typeof query === 'string' && Boolean(query.trim()),\n )\n }\n return []\n })\n\n // Raw scores live on different scales depending on the agent search mode\n // (RRF ~0.03, cosine 0 to 1) and tend to cluster, so a plain ratio on the\n // max renders every bar as ~100%. Spread them on a min/max scale with a\n // floor so the ordering stays visible, and hide the bar entirely when it\n // carries no information (single hit or identical scores).\n const spreadRelevance = (scores: number[]): (number | undefined)[] => {\n const min = Math.min(...scores)\n const max = Math.max(...scores)\n if (scores.length < 2 || max <= min) {\n return scores.map(() => undefined)\n }\n const floor = 0.15\n return scores.map(\n (score) => floor + ((score - min) / (max - min)) * (1 - floor),\n )\n }\n\n // Normalizes a raw result array into records paired with a spread\n // relevance derived from each record's `similarity` score.\n const scoredRecords = (\n raw: unknown,\n ):\n | { record: Record<string, unknown>; relevance?: number }[]\n | undefined => {\n if (!Array.isArray(raw)) {\n return undefined\n }\n const records = raw\n .map((item) => asRecord(item))\n .filter((record): record is Record<string, unknown> =>\n Boolean(record),\n )\n const relevances = spreadRelevance(\n records.map((record) =>\n typeof record.similarity === 'number' ? record.similarity : 0,\n ),\n )\n return records.map((record, index) => ({\n record,\n relevance: relevances[index],\n }))\n }\n\n // searchInKnowledgeBase: group chunks by document, relevance compares the\n // best chunk score of each document within the result set.\n const knowledgeHits = computed<DetailHit[] | undefined>(() => {\n const results = output.value?.results\n if (!Array.isArray(results)) {\n return undefined\n }\n interface DocumentGroup {\n id: string\n type: string\n title: string\n url?: string\n similarity: number\n chunks: string[]\n }\n const groups = new Map<string, DocumentGroup>()\n for (const [index, raw] of results.entries()) {\n const result = asRecord(raw)\n if (!result) {\n continue\n }\n const document = asRecord(result.document)\n const source = asRecord(document?.source)\n const id =\n typeof document?.id === 'string' ? document.id : `doc-${index}`\n const similarity =\n typeof result.similarity === 'number' ? result.similarity : 0\n let group = groups.get(id)\n if (!group) {\n group = {\n id,\n type:\n typeof document?.type === 'string' ? document.type : '',\n title:\n pickString(document, 'name') ??\n pickString(source, 'filename', 'url') ??\n $t('messagePart.stepDocument'),\n url: pickString(source, 'url'),\n similarity,\n chunks: [],\n }\n groups.set(id, group)\n }\n group.similarity = Math.max(group.similarity, similarity)\n if (typeof result.content === 'string' && result.content.trim()) {\n group.chunks.push(result.content)\n }\n }\n const list = [...groups.values()]\n const relevances = spreadRelevance(list.map((g) => g.similarity))\n return list.map((group, index) => ({\n key: group.id,\n icon: group.type === 'url' ? 'ri:global-line' : 'ri:file-text-line',\n title: group.title,\n url: group.url,\n meta: $t('messagePart.stepExcerpts', group.chunks.length || 1),\n relevance: relevances[index],\n body: group.chunks.join('\\n\\n· · ·\\n\\n') || undefined,\n }))\n })\n\n // Chunks this turn had already returned come back as references without\n // content (spec retrieval-payload-per-turn, R2). They carry no document\n // name to group by, so they are summarized in one line: without it a\n // search that found only known passages reads as \"no results\".\n const repeatedCount = computed<number>(() => {\n const count = asRecord(output.value?.repeated)?.count\n return typeof count === 'number' ? count : 0\n })\n\n // Two declarations every search tool can attach to its output, and the\n // only ones the model is explicitly told not to read as an answer: a\n // query dropped for exceeding the tool's cap, and a search that ran out\n // of time. Without them a timeout renders as \"no results\", which is the\n // reading whoever opens a chat log would take away too.\n const searchFacts = computed<DetailFact[]>(() => {\n const facts: DetailFact[] = []\n const dropped = asRecord(output.value?.inputTruncated)?.dropped\n if (Array.isArray(dropped) && dropped.length) {\n facts.push({\n icon: 'ri:scissors-cut-line',\n text: $t('messagePart.stepDropped', dropped.length),\n })\n }\n const unavailable = asRecord(output.value?.searchUnavailable)\n if (unavailable) {\n // The corrected-answers tool declares one lookup without naming\n // its query, so an absent list means one search, not none.\n const queries = unavailable.queries\n facts.push({\n icon: 'ri:time-line',\n text: $t(\n 'messagePart.stepTimedOut',\n Array.isArray(queries) ? queries.length : 1,\n ),\n })\n }\n return facts\n })\n\n // retrieveRevisedAnswers: one hit per matched question, the corrected\n // answer is the expandable body.\n const revisedAnswerHits = computed<DetailHit[] | undefined>(() =>\n scoredRecords(output.value?.results)?.map(\n ({ record, relevance }, index) => ({\n key:\n pickString(record, 'revisedAnswerId') ?? `revised-${index}`,\n icon: 'ri:file-edit-line',\n title:\n pickString(record, 'userQuestion') ??\n $t('messagePart.revisedAnswers'),\n relevance,\n body: pickString(record, 'revisedMessage'),\n }),\n ),\n )\n\n // recallUserMemory / saveUserMemory delete candidates: one hit per\n // memory, long facts expand to their full text.\n const memoryHits = (raw: unknown): DetailHit[] | undefined =>\n scoredRecords(raw)?.map(({ record, relevance }, index) => {\n const content = pickString(record, 'content') ?? ''\n return {\n key: pickString(record, 'id') ?? `memory-${index}`,\n icon: 'ri:brain-line',\n title: content,\n meta: pickString(record, 'updatedAt')?.slice(0, 10),\n relevance,\n body: content.length > 120 ? content : undefined,\n }\n })\n\n // Query chips over a list of hits, flagging an explicit empty state so the\n // \"no results\" message shows once the tool has run (hits defined, length 0).\n const hitsDetail = (hits: DetailHit[] | undefined): Detail => ({\n chips: queryChips.value,\n hits,\n showEmpty: hits !== undefined && !hits.length,\n })\n\n // Rich, tool-specific view of the step. Returns {} when the tool has\n // nothing meaningful to show yet (the raw fallback is applied by `detail`).\n const buildDetail = (): Detail => {\n const type = props.part.type\n if (type === 'tool-searchInKnowledgeBase') {\n const knowledge = hitsDetail(knowledgeHits.value)\n if (repeatedCount.value) {\n knowledge.facts = [\n {\n icon: 'ri:history-line',\n text: $t(\n 'messagePart.stepRepeated',\n repeatedCount.value,\n ),\n },\n ]\n knowledge.showEmpty = false\n }\n return knowledge\n }\n if (type === 'tool-retrieveRevisedAnswers') {\n return hitsDetail(revisedAnswerHits.value)\n }\n if (type === 'tool-expandChunkContext') {\n const content = pickString(output.value, 'content')\n if (!content) {\n return {}\n }\n const totalChunks = output.value?.totalChunksReturned\n return {\n passage: {\n title: pickString(output.value, 'documentName'),\n meta:\n typeof totalChunks === 'number'\n ? $t('messagePart.stepExcerpts', totalChunks)\n : undefined,\n content,\n },\n }\n }\n if (type === 'tool-readDocumentFile') {\n const name = pickString(output.value, 'name')\n if (!name) {\n return {}\n }\n const mimeType = pickString(output.value, 'mimeType')\n return {\n facts: [\n {\n icon: 'ri:file-text-line',\n text: mimeType ? `${name} (${mimeType})` : name,\n },\n ],\n }\n }\n if (type === 'tool-getWeather') {\n const location =\n pickString(output.value, 'location') ??\n pickString(input.value, 'locationName') ??\n formatCoordinates(input.value)\n if (!location) {\n return {}\n }\n const startDate = pickString(input.value, 'startDate')\n const endDate = pickString(input.value, 'endDate')\n const dateRange = startDate\n ? endDate && endDate !== startDate\n ? ` · ${startDate} → ${endDate}`\n : ` · ${startDate}`\n : ''\n return {\n facts: [\n {\n icon: 'ri:sun-cloudy-line',\n text: `${location}${dateRange}`,\n },\n ],\n }\n }\n if (type === 'tool-geocode') {\n const from =\n pickString(input.value, 'locationName') ??\n formatCoordinates(input.value)\n const to =\n pickString(\n output.value,\n 'name',\n 'displayName',\n 'locationName',\n 'label',\n ) ?? formatCoordinates(output.value)\n const text =\n from && to && from !== to ? `${from} → ${to}` : (from ?? to)\n if (!text) {\n return {}\n }\n return {\n facts: [\n {\n icon: 'ri:map-pin-line',\n text,\n },\n ],\n }\n }\n if (type === 'tool-recallUserMemory') {\n return hitsDetail(memoryHits(output.value?.memories))\n }\n if (type === 'tool-saveUserMemory') {\n const candidates = memoryHits(output.value?.candidates)\n if (candidates) {\n return hitsDetail(candidates)\n }\n const text =\n pickString(input.value, 'content') ??\n pickString(output.value, 'message', 'error')\n if (!text) {\n return {}\n }\n return {\n facts: [\n {\n icon:\n pickString(input.value, 'action') === 'delete'\n ? 'ri:delete-bin-line'\n : 'ri:save-3-line',\n text,\n },\n ],\n }\n }\n if (WEB_SEARCH_TOOL_TYPES.includes(type)) {\n return {\n chips: queryChips.value,\n rawOutput: rawOutputValue.value,\n }\n }\n return {}\n }\n\n const detail = computed<Detail>(() => {\n const result = buildDetail()\n if (searchFacts.value.length) {\n result.facts = [...searchFacts.value, ...(result.facts ?? [])]\n }\n if (output.value?.searchUnavailable) {\n // \"No results found\" under a search that never answered is the\n // one sentence this fact exists to prevent.\n result.showEmpty = false\n }\n // Nothing meaningful to show for this tool yet (e.g. still running):\n // fall back to the raw payloads so the step body is never empty.\n const hasRichContent =\n result.chips?.length ||\n result.facts?.length ||\n result.passage ||\n result.showEmpty ||\n result.hits?.length\n if (!hasRichContent) {\n result.rawInput = rawInputValue.value\n result.rawOutput ??= rawOutputValue.value\n }\n return result\n })\n\n const expandedHits = reactive(new Set<string>())\n const toggleHit = (key: string) => {\n if (expandedHits.has(key)) {\n expandedHits.delete(key)\n } else {\n expandedHits.add(key)\n }\n }\n</script>\n\n<template>\n <div class=\"pk-chatbot-step-detail\">\n <ul v-if=\"detail.chips?.length\" class=\"pk-chatbot-step-detail__chips\">\n <li\n v-for=\"(chip, chipIndex) in detail.chips\"\n :key=\"`${chipIndex}-${chip}`\"\n class=\"pk-chatbot-step-detail__chip\">\n <VvIcon name=\"ri:search-line\" class=\"shrink-0\" />\n <span class=\"pk-chatbot-step-detail__chip-text\">\n {{ chip }}\n </span>\n </li>\n </ul>\n <div\n v-for=\"(fact, factIndex) in detail.facts\"\n :key=\"`${factIndex}-${fact.text}`\"\n class=\"pk-chatbot-step-detail__fact\">\n <VvIcon :name=\"fact.icon\" class=\"shrink-0\" />\n <span>{{ fact.text }}</span>\n </div>\n <div v-if=\"detail.showEmpty\" class=\"pk-chatbot-step-detail__empty\">\n {{ $t('messagePart.stepNoResults') }}\n </div>\n <ul\n v-else-if=\"detail.hits?.length\"\n class=\"pk-chatbot-step-detail__hits\">\n <li v-for=\"hit in detail.hits\" :key=\"hit.key\">\n <component\n :is=\"hit.body ? 'button' : 'div'\"\n :type=\"hit.body ? 'button' : undefined\"\n class=\"pk-chatbot-step-detail__hit-header\"\n :aria-expanded=\"\n hit.body ? expandedHits.has(hit.key) : undefined\n \"\n @click=\"hit.body ? toggleHit(hit.key) : undefined\">\n <VvIcon\n :name=\"hit.icon\"\n class=\"pk-chatbot-step-detail__hit-icon\" />\n <span class=\"pk-chatbot-step-detail__hit-heading\">\n <span class=\"pk-chatbot-step-detail__hit-title\">\n {{ hit.title }}\n </span>\n <a\n v-if=\"hit.url\"\n :href=\"hit.url\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pk-chatbot-step-detail__link\"\n @click.stop>\n <VvIcon name=\"ri:external-link-line\" />\n </a>\n </span>\n <span\n v-if=\"hit.meta\"\n class=\"pk-chatbot-step-detail__hit-meta\">\n {{ hit.meta }}\n </span>\n <span\n v-if=\"hit.relevance !== undefined\"\n class=\"pk-chatbot-step-detail__relevance\"\n aria-hidden=\"true\">\n <span\n class=\"pk-chatbot-step-detail__relevance-fill\"\n :style=\"{\n width: `${Math.round(hit.relevance * 100)}%`,\n }\" />\n </span>\n <VvIcon\n v-if=\"hit.body\"\n :name=\"\n expandedHits.has(hit.key)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"shrink-0\" />\n </component>\n <div\n v-if=\"hit.body && expandedHits.has(hit.key)\"\n class=\"pk-chatbot-step-detail__hit-body\">\n {{ hit.body }}\n </div>\n </li>\n </ul>\n <div v-if=\"detail.passage\" class=\"pk-chatbot-step-detail__passage\">\n <div class=\"pk-chatbot-step-detail__passage-header\">\n <VvIcon name=\"ri:file-text-line\" class=\"shrink-0\" />\n <span class=\"pk-chatbot-step-detail__hit-heading\">\n <span class=\"pk-chatbot-step-detail__hit-title\">\n {{\n detail.passage.title ??\n $t('messagePart.stepDocument')\n }}\n </span>\n </span>\n <span\n v-if=\"detail.passage.meta\"\n class=\"pk-chatbot-step-detail__hit-meta\">\n {{ detail.passage.meta }}\n </span>\n </div>\n <div class=\"pk-chatbot-step-detail__passage-content\">\n {{ detail.passage.content }}\n </div>\n </div>\n <PkChatbotStepPayload\n v-if=\"hasPayload(detail.rawInput)\"\n :label=\"$t('messagePart.stepInput')\"\n :value=\"detail.rawInput\" />\n <PkChatbotStepPayload\n v-if=\"hasPayload(detail.rawOutput)\"\n :label=\"$t('messagePart.stepOutput')\"\n :value=\"detail.rawOutput\" />\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-step-detail {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-8);\n font-size: var(--text-12);\n color: var(--color-word-3);\n\n &__chips {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-4);\n }\n\n &__chip {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-4);\n max-width: 100%;\n padding: var(--spacing-2) var(--spacing-8);\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface-1);\n color: var(--color-word-2);\n }\n\n &__chip-text {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__fact {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n color: var(--color-word-2);\n }\n\n &__empty {\n color: var(--color-word-4);\n font-style: italic;\n }\n\n &__hits {\n display: flex;\n flex-direction: column;\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n\n > li + li {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n }\n\n &__hit-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n width: 100%;\n min-height: var(--spacing-32);\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n\n &:is(button) {\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n &__hit-icon {\n flex-shrink: 0;\n color: var(--color-word-3);\n }\n\n // Grows to fill the row so meta, relevance and chevron stay aligned;\n // the title inside only shrinks, keeping the link icon next to the\n // title text instead of pushed to the right edge.\n &__hit-heading {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n flex: 1 1 0;\n min-width: 0;\n }\n\n &__hit-title {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-word-2);\n }\n\n &__hit-meta {\n color: var(--color-word-4);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n\n &__relevance {\n flex-shrink: 0;\n width: var(--spacing-40);\n height: var(--spacing-4);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface-3);\n overflow: hidden;\n }\n\n &__relevance-fill {\n display: block;\n height: 100%;\n border-radius: inherit;\n background-color: var(--chatbot-main-color, var(--color-word-4));\n }\n\n &__link {\n flex-shrink: 0;\n display: inline-flex;\n color: var(--color-word-4);\n\n &:hover {\n color: var(--color-word-1);\n }\n }\n\n &__hit-body,\n &__passage-content {\n padding: var(--spacing-8);\n max-height: var(--spacing-160);\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--color-word-3);\n }\n\n &__hit-body {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n\n &__passage {\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n }\n\n &__passage-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n min-height: var(--spacing-32);\n padding: var(--spacing-4) var(--spacing-8);\n border-bottom: var(--spacing-px) solid var(--color-surface-3);\n }\n }\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PkChatbotStepToolDetail-BJ7xUvgi.js","names":[],"sources":["../../../../packages/components/src/chat/PkChatbotStepToolDetail.vue","../../../../packages/components/src/chat/PkChatbotStepToolDetail.vue"],"sourcesContent":["<script lang=\"ts\" setup>\n import { computed, reactive } from 'vue'\n import { useI18n } from 'vue-i18n'\n import type { UIChatMessage } from 'models'\n import PkChatbotStepPayload from './PkChatbotStepPayload.vue'\n import { hasPayload, WEB_SEARCH_TOOL_TYPES } from './utils'\n\n type MessagePart = UIChatMessage['parts'][number]\n\n interface DetailHit {\n key: string\n icon: string\n title: string\n url?: string\n meta?: string\n relevance?: number\n body?: string\n }\n\n interface DetailFact {\n icon: string\n text: string\n }\n\n interface Detail {\n chips?: string[]\n facts?: DetailFact[]\n hits?: DetailHit[]\n showEmpty?: boolean\n passage?: { title?: string; meta?: string; content: string }\n rawInput?: unknown\n rawOutput?: unknown\n }\n\n const props = defineProps<{\n part: MessagePart\n }>()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const asRecord = (value: unknown): Record<string, unknown> | undefined =>\n value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined\n\n const pickString = (\n record: Record<string, unknown> | undefined,\n ...keys: string[]\n ): string | undefined => {\n for (const key of keys) {\n const value = record?.[key]\n if (typeof value === 'string' && value.trim()) {\n return value\n }\n }\n return undefined\n }\n\n const formatCoordinates = (\n record?: Record<string, unknown>,\n ): string | undefined => {\n const latitude = record?.latitude\n const longitude = record?.longitude\n if (typeof latitude !== 'number' || typeof longitude !== 'number') {\n return undefined\n }\n return `${latitude.toFixed(4)}, ${longitude.toFixed(4)}`\n }\n\n const rawInputValue = computed(() =>\n 'input' in props.part ? props.part.input : undefined,\n )\n const rawOutputValue = computed(() =>\n 'output' in props.part ? props.part.output : undefined,\n )\n const input = computed(() => asRecord(rawInputValue.value))\n const output = computed(() => asRecord(rawOutputValue.value))\n\n // Search queries shown as chips (queries[], query or objective)\n const queryChips = computed<string[]>(() => {\n const raw =\n input.value?.queries ?? input.value?.query ?? input.value?.objective\n if (typeof raw === 'string' && raw.trim()) {\n return [raw]\n }\n if (Array.isArray(raw)) {\n return raw.filter(\n (query): query is string =>\n typeof query === 'string' && Boolean(query.trim()),\n )\n }\n return []\n })\n\n // Raw scores live on different scales depending on the agent search mode\n // (RRF ~0.03, cosine 0 to 1) and tend to cluster, so a plain ratio on the\n // max renders every bar as ~100%. Spread them on a min/max scale with a\n // floor so the ordering stays visible, and hide the bar entirely when it\n // carries no information (single hit or identical scores).\n const spreadRelevance = (scores: number[]): (number | undefined)[] => {\n const min = Math.min(...scores)\n const max = Math.max(...scores)\n if (scores.length < 2 || max <= min) {\n return scores.map(() => undefined)\n }\n const floor = 0.15\n return scores.map(\n (score) => floor + ((score - min) / (max - min)) * (1 - floor),\n )\n }\n\n // Normalizes a raw result array into records paired with a spread\n // relevance derived from each record's `similarity` score.\n const scoredRecords = (\n raw: unknown,\n ):\n | { record: Record<string, unknown>; relevance?: number }[]\n | undefined => {\n if (!Array.isArray(raw)) {\n return undefined\n }\n const records = raw\n .map((item) => asRecord(item))\n .filter((record): record is Record<string, unknown> =>\n Boolean(record),\n )\n const relevances = spreadRelevance(\n records.map((record) =>\n typeof record.similarity === 'number' ? record.similarity : 0,\n ),\n )\n return records.map((record, index) => ({\n record,\n relevance: relevances[index],\n }))\n }\n\n // searchInKnowledgeBase: group chunks by document, relevance compares the\n // best chunk score of each document within the result set.\n const knowledgeHits = computed<DetailHit[] | undefined>(() => {\n const results = output.value?.results\n if (!Array.isArray(results)) {\n return undefined\n }\n interface DocumentGroup {\n id: string\n type: string\n title: string\n url?: string\n similarity: number\n chunks: string[]\n }\n const groups = new Map<string, DocumentGroup>()\n for (const [index, raw] of results.entries()) {\n const result = asRecord(raw)\n if (!result) {\n continue\n }\n const document = asRecord(result.document)\n const source = asRecord(document?.source)\n const id =\n typeof document?.id === 'string' ? document.id : `doc-${index}`\n const similarity =\n typeof result.similarity === 'number' ? result.similarity : 0\n let group = groups.get(id)\n if (!group) {\n group = {\n id,\n type:\n typeof document?.type === 'string' ? document.type : '',\n title:\n pickString(document, 'name') ??\n pickString(source, 'filename', 'url') ??\n $t('messagePart.stepDocument'),\n url: pickString(source, 'url'),\n similarity,\n chunks: [],\n }\n groups.set(id, group)\n }\n group.similarity = Math.max(group.similarity, similarity)\n if (typeof result.content === 'string' && result.content.trim()) {\n group.chunks.push(result.content)\n }\n }\n const list = [...groups.values()]\n const relevances = spreadRelevance(list.map((g) => g.similarity))\n return list.map((group, index) => ({\n key: group.id,\n icon: group.type === 'url' ? 'ri:global-line' : 'ri:file-text-line',\n title: group.title,\n url: group.url,\n meta: $t('messagePart.stepExcerpts', group.chunks.length || 1),\n relevance: relevances[index],\n body: group.chunks.join('\\n\\n· · ·\\n\\n') || undefined,\n }))\n })\n\n // Chunks this turn had already returned come back as references without\n // content (spec retrieval-payload-per-turn, R2). They carry no document\n // name to group by, so they are summarized in one line: without it a\n // search that found only known passages reads as \"no results\".\n const repeatedCount = computed<number>(() => {\n const count = asRecord(output.value?.repeated)?.count\n return typeof count === 'number' ? count : 0\n })\n\n // Two declarations every search tool can attach to its output, and the\n // only ones the model is explicitly told not to read as an answer: a\n // query dropped for exceeding the tool's cap, and a search that ran out\n // of time. Without them a timeout renders as \"no results\", which is the\n // reading whoever opens a chat log would take away too.\n const searchFacts = computed<DetailFact[]>(() => {\n const facts: DetailFact[] = []\n const dropped = asRecord(output.value?.inputTruncated)?.dropped\n if (Array.isArray(dropped) && dropped.length) {\n facts.push({\n icon: 'ri:scissors-cut-line',\n text: $t('messagePart.stepDropped', dropped.length),\n })\n }\n const unavailable = asRecord(output.value?.searchUnavailable)\n if (unavailable) {\n // The corrected-answers tool declares one lookup without naming\n // its query, so an absent list means one search, not none.\n const queries = unavailable.queries\n facts.push({\n icon: 'ri:time-line',\n text: $t(\n 'messagePart.stepTimedOut',\n Array.isArray(queries) ? queries.length : 1,\n ),\n })\n }\n return facts\n })\n\n // retrieveRevisedAnswers: one hit per matched question, the corrected\n // answer is the expandable body.\n const revisedAnswerHits = computed<DetailHit[] | undefined>(() =>\n scoredRecords(output.value?.results)?.map(\n ({ record, relevance }, index) => ({\n key:\n pickString(record, 'revisedAnswerId') ?? `revised-${index}`,\n icon: 'ri:file-edit-line',\n title:\n pickString(record, 'userQuestion') ??\n $t('messagePart.revisedAnswers'),\n relevance,\n body: pickString(record, 'revisedMessage'),\n }),\n ),\n )\n\n // recallUserMemory / saveUserMemory delete candidates: one hit per\n // memory, long facts expand to their full text.\n const memoryHits = (raw: unknown): DetailHit[] | undefined =>\n scoredRecords(raw)?.map(({ record, relevance }, index) => {\n const content = pickString(record, 'content') ?? ''\n return {\n key: pickString(record, 'id') ?? `memory-${index}`,\n icon: 'ri:brain-line',\n title: content,\n meta: pickString(record, 'updatedAt')?.slice(0, 10),\n relevance,\n body: content.length > 120 ? content : undefined,\n }\n })\n\n // Query chips over a list of hits, flagging an explicit empty state so the\n // \"no results\" message shows once the tool has run (hits defined, length 0).\n const hitsDetail = (hits: DetailHit[] | undefined): Detail => ({\n chips: queryChips.value,\n hits,\n showEmpty: hits !== undefined && !hits.length,\n })\n\n // Rich, tool-specific view of the step. Returns {} when the tool has\n // nothing meaningful to show yet (the raw fallback is applied by `detail`).\n const buildDetail = (): Detail => {\n const type = props.part.type\n if (type === 'tool-searchInKnowledgeBase') {\n const knowledge = hitsDetail(knowledgeHits.value)\n if (repeatedCount.value) {\n knowledge.facts = [\n {\n icon: 'ri:history-line',\n text: $t(\n 'messagePart.stepRepeated',\n repeatedCount.value,\n ),\n },\n ]\n knowledge.showEmpty = false\n }\n return knowledge\n }\n if (type === 'tool-retrieveRevisedAnswers') {\n return hitsDetail(revisedAnswerHits.value)\n }\n if (type === 'tool-expandChunkContext') {\n const content = pickString(output.value, 'content')\n if (!content) {\n return {}\n }\n const totalChunks = output.value?.totalChunksReturned\n return {\n passage: {\n title: pickString(output.value, 'documentName'),\n meta:\n typeof totalChunks === 'number'\n ? $t('messagePart.stepExcerpts', totalChunks)\n : undefined,\n content,\n },\n }\n }\n if (type === 'tool-readDocumentFile') {\n const name = pickString(output.value, 'name')\n if (!name) {\n return {}\n }\n const mimeType = pickString(output.value, 'mimeType')\n return {\n facts: [\n {\n icon: 'ri:file-text-line',\n text: mimeType ? `${name} (${mimeType})` : name,\n },\n ],\n }\n }\n if (type === 'tool-getWeather') {\n const location =\n pickString(output.value, 'location') ??\n pickString(input.value, 'locationName') ??\n formatCoordinates(input.value)\n if (!location) {\n return {}\n }\n const startDate = pickString(input.value, 'startDate')\n const endDate = pickString(input.value, 'endDate')\n const dateRange = startDate\n ? endDate && endDate !== startDate\n ? ` · ${startDate} → ${endDate}`\n : ` · ${startDate}`\n : ''\n return {\n facts: [\n {\n icon: 'ri:sun-cloudy-line',\n text: `${location}${dateRange}`,\n },\n ],\n }\n }\n if (type === 'tool-geocode') {\n const from =\n pickString(input.value, 'locationName') ??\n formatCoordinates(input.value)\n const to =\n pickString(\n output.value,\n 'name',\n 'displayName',\n 'locationName',\n 'label',\n ) ?? formatCoordinates(output.value)\n const text =\n from && to && from !== to ? `${from} → ${to}` : (from ?? to)\n if (!text) {\n return {}\n }\n return {\n facts: [\n {\n icon: 'ri:map-pin-line',\n text,\n },\n ],\n }\n }\n if (type === 'tool-recallUserMemory') {\n return hitsDetail(memoryHits(output.value?.memories))\n }\n if (type === 'tool-saveUserMemory') {\n const candidates = memoryHits(output.value?.candidates)\n if (candidates) {\n return hitsDetail(candidates)\n }\n const text =\n pickString(input.value, 'content') ??\n pickString(output.value, 'message', 'error')\n if (!text) {\n return {}\n }\n return {\n facts: [\n {\n icon:\n pickString(input.value, 'action') === 'delete'\n ? 'ri:delete-bin-line'\n : 'ri:save-3-line',\n text,\n },\n ],\n }\n }\n if (WEB_SEARCH_TOOL_TYPES.includes(type)) {\n return {\n chips: queryChips.value,\n rawOutput: rawOutputValue.value,\n }\n }\n return {}\n }\n\n const detail = computed<Detail>(() => {\n const result = buildDetail()\n if (searchFacts.value.length) {\n result.facts = [...searchFacts.value, ...(result.facts ?? [])]\n }\n if (output.value?.searchUnavailable) {\n // \"No results found\" under a search that never answered is the\n // one sentence this fact exists to prevent.\n result.showEmpty = false\n }\n // Nothing meaningful to show for this tool yet (e.g. still running):\n // fall back to the raw payloads so the step body is never empty.\n const hasRichContent =\n result.chips?.length ||\n result.facts?.length ||\n result.passage ||\n result.showEmpty ||\n result.hits?.length\n if (!hasRichContent) {\n result.rawInput = rawInputValue.value\n result.rawOutput ??= rawOutputValue.value\n }\n return result\n })\n\n const expandedHits = reactive(new Set<string>())\n const toggleHit = (key: string) => {\n if (expandedHits.has(key)) {\n expandedHits.delete(key)\n } else {\n expandedHits.add(key)\n }\n }\n</script>\n\n<template>\n <div class=\"pk-chatbot-step-detail\">\n <ul v-if=\"detail.chips?.length\" class=\"pk-chatbot-step-detail__chips\">\n <li\n v-for=\"(chip, chipIndex) in detail.chips\"\n :key=\"`${chipIndex}-${chip}`\"\n class=\"pk-chatbot-step-detail__chip\">\n <VvIcon name=\"ri:search-line\" class=\"shrink-0\" />\n <span class=\"pk-chatbot-step-detail__chip-text\">\n {{ chip }}\n </span>\n </li>\n </ul>\n <div\n v-for=\"(fact, factIndex) in detail.facts\"\n :key=\"`${factIndex}-${fact.text}`\"\n class=\"pk-chatbot-step-detail__fact\">\n <VvIcon :name=\"fact.icon\" class=\"shrink-0\" />\n <span>{{ fact.text }}</span>\n </div>\n <div v-if=\"detail.showEmpty\" class=\"pk-chatbot-step-detail__empty\">\n {{ $t('messagePart.stepNoResults') }}\n </div>\n <ul\n v-else-if=\"detail.hits?.length\"\n class=\"pk-chatbot-step-detail__hits\">\n <li v-for=\"hit in detail.hits\" :key=\"hit.key\">\n <component\n :is=\"hit.body ? 'button' : 'div'\"\n :type=\"hit.body ? 'button' : undefined\"\n class=\"pk-chatbot-step-detail__hit-header\"\n :aria-expanded=\"\n hit.body ? expandedHits.has(hit.key) : undefined\n \"\n @click=\"hit.body ? toggleHit(hit.key) : undefined\">\n <VvIcon\n :name=\"hit.icon\"\n class=\"pk-chatbot-step-detail__hit-icon\" />\n <span class=\"pk-chatbot-step-detail__hit-heading\">\n <span class=\"pk-chatbot-step-detail__hit-title\">\n {{ hit.title }}\n </span>\n <a\n v-if=\"hit.url\"\n :href=\"hit.url\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pk-chatbot-step-detail__link\"\n @click.stop>\n <VvIcon name=\"ri:external-link-line\" />\n </a>\n </span>\n <span\n v-if=\"hit.meta\"\n class=\"pk-chatbot-step-detail__hit-meta\">\n {{ hit.meta }}\n </span>\n <span\n v-if=\"hit.relevance !== undefined\"\n class=\"pk-chatbot-step-detail__relevance\"\n aria-hidden=\"true\">\n <span\n class=\"pk-chatbot-step-detail__relevance-fill\"\n :style=\"{\n width: `${Math.round(hit.relevance * 100)}%`,\n }\" />\n </span>\n <VvIcon\n v-if=\"hit.body\"\n :name=\"\n expandedHits.has(hit.key)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"shrink-0\" />\n </component>\n <div\n v-if=\"hit.body && expandedHits.has(hit.key)\"\n class=\"pk-chatbot-step-detail__hit-body\">\n {{ hit.body }}\n </div>\n </li>\n </ul>\n <div v-if=\"detail.passage\" class=\"pk-chatbot-step-detail__passage\">\n <div class=\"pk-chatbot-step-detail__passage-header\">\n <VvIcon name=\"ri:file-text-line\" class=\"shrink-0\" />\n <span class=\"pk-chatbot-step-detail__hit-heading\">\n <span class=\"pk-chatbot-step-detail__hit-title\">\n {{\n detail.passage.title ??\n $t('messagePart.stepDocument')\n }}\n </span>\n </span>\n <span\n v-if=\"detail.passage.meta\"\n class=\"pk-chatbot-step-detail__hit-meta\">\n {{ detail.passage.meta }}\n </span>\n </div>\n <div class=\"pk-chatbot-step-detail__passage-content\">\n {{ detail.passage.content }}\n </div>\n </div>\n <PkChatbotStepPayload\n v-if=\"hasPayload(detail.rawInput)\"\n :label=\"$t('messagePart.stepInput')\"\n :value=\"detail.rawInput\" />\n <PkChatbotStepPayload\n v-if=\"hasPayload(detail.rawOutput)\"\n :label=\"$t('messagePart.stepOutput')\"\n :value=\"detail.rawOutput\" />\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-step-detail {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-8);\n font-size: var(--text-12);\n color: var(--color-word-3);\n\n &__chips {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-4);\n }\n\n &__chip {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-4);\n max-width: 100%;\n padding: var(--spacing-2) var(--spacing-8);\n border: var(--spacing-px) solid var(--color-surface-3);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface-1);\n color: var(--color-word-2);\n }\n\n &__chip-text {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__fact {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n color: var(--color-word-2);\n }\n\n &__empty {\n color: var(--color-word-4);\n font-style: italic;\n }\n\n &__hits {\n display: flex;\n flex-direction: column;\n border: var(--spacing-px) solid var(--color-surface-3);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n\n > li + li {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n }\n\n &__hit-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n width: 100%;\n min-height: var(--spacing-32);\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n\n &:is(button) {\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n &__hit-icon {\n flex-shrink: 0;\n color: var(--color-word-3);\n }\n\n // Grows to fill the row so meta, relevance and chevron stay aligned;\n // the title inside only shrinks, keeping the link icon next to the\n // title text instead of pushed to the right edge.\n &__hit-heading {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n flex: 1 1 0;\n min-width: 0;\n }\n\n &__hit-title {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-word-2);\n }\n\n &__hit-meta {\n color: var(--color-word-4);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n\n &__relevance {\n flex-shrink: 0;\n width: var(--spacing-40);\n height: var(--spacing-4);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface-3);\n overflow: hidden;\n }\n\n &__relevance-fill {\n display: block;\n height: 100%;\n border-radius: inherit;\n background-color: var(--chatbot-main-color, var(--color-word-4));\n }\n\n &__link {\n flex-shrink: 0;\n display: inline-flex;\n color: var(--color-word-4);\n\n &:hover {\n color: var(--color-word-1);\n }\n }\n\n &__hit-body,\n &__passage-content {\n padding: var(--spacing-8);\n max-height: var(--spacing-160);\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--color-word-3);\n }\n\n &__hit-body {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n\n &__passage {\n border: var(--spacing-px) solid var(--color-surface-3);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n }\n\n &__passage-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n min-height: var(--spacing-32);\n padding: var(--spacing-4) var(--spacing-8);\n border-bottom: var(--spacing-px) solid var(--color-surface-3);\n }\n }\n</style>\n","<script lang=\"ts\" setup>\n import { computed, reactive } from 'vue'\n import { useI18n } from 'vue-i18n'\n import type { UIChatMessage } from 'models'\n import PkChatbotStepPayload from './PkChatbotStepPayload.vue'\n import { hasPayload, WEB_SEARCH_TOOL_TYPES } from './utils'\n\n type MessagePart = UIChatMessage['parts'][number]\n\n interface DetailHit {\n key: string\n icon: string\n title: string\n url?: string\n meta?: string\n relevance?: number\n body?: string\n }\n\n interface DetailFact {\n icon: string\n text: string\n }\n\n interface Detail {\n chips?: string[]\n facts?: DetailFact[]\n hits?: DetailHit[]\n showEmpty?: boolean\n passage?: { title?: string; meta?: string; content: string }\n rawInput?: unknown\n rawOutput?: unknown\n }\n\n const props = defineProps<{\n part: MessagePart\n }>()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const asRecord = (value: unknown): Record<string, unknown> | undefined =>\n value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined\n\n const pickString = (\n record: Record<string, unknown> | undefined,\n ...keys: string[]\n ): string | undefined => {\n for (const key of keys) {\n const value = record?.[key]\n if (typeof value === 'string' && value.trim()) {\n return value\n }\n }\n return undefined\n }\n\n const formatCoordinates = (\n record?: Record<string, unknown>,\n ): string | undefined => {\n const latitude = record?.latitude\n const longitude = record?.longitude\n if (typeof latitude !== 'number' || typeof longitude !== 'number') {\n return undefined\n }\n return `${latitude.toFixed(4)}, ${longitude.toFixed(4)}`\n }\n\n const rawInputValue = computed(() =>\n 'input' in props.part ? props.part.input : undefined,\n )\n const rawOutputValue = computed(() =>\n 'output' in props.part ? props.part.output : undefined,\n )\n const input = computed(() => asRecord(rawInputValue.value))\n const output = computed(() => asRecord(rawOutputValue.value))\n\n // Search queries shown as chips (queries[], query or objective)\n const queryChips = computed<string[]>(() => {\n const raw =\n input.value?.queries ?? input.value?.query ?? input.value?.objective\n if (typeof raw === 'string' && raw.trim()) {\n return [raw]\n }\n if (Array.isArray(raw)) {\n return raw.filter(\n (query): query is string =>\n typeof query === 'string' && Boolean(query.trim()),\n )\n }\n return []\n })\n\n // Raw scores live on different scales depending on the agent search mode\n // (RRF ~0.03, cosine 0 to 1) and tend to cluster, so a plain ratio on the\n // max renders every bar as ~100%. Spread them on a min/max scale with a\n // floor so the ordering stays visible, and hide the bar entirely when it\n // carries no information (single hit or identical scores).\n const spreadRelevance = (scores: number[]): (number | undefined)[] => {\n const min = Math.min(...scores)\n const max = Math.max(...scores)\n if (scores.length < 2 || max <= min) {\n return scores.map(() => undefined)\n }\n const floor = 0.15\n return scores.map(\n (score) => floor + ((score - min) / (max - min)) * (1 - floor),\n )\n }\n\n // Normalizes a raw result array into records paired with a spread\n // relevance derived from each record's `similarity` score.\n const scoredRecords = (\n raw: unknown,\n ):\n | { record: Record<string, unknown>; relevance?: number }[]\n | undefined => {\n if (!Array.isArray(raw)) {\n return undefined\n }\n const records = raw\n .map((item) => asRecord(item))\n .filter((record): record is Record<string, unknown> =>\n Boolean(record),\n )\n const relevances = spreadRelevance(\n records.map((record) =>\n typeof record.similarity === 'number' ? record.similarity : 0,\n ),\n )\n return records.map((record, index) => ({\n record,\n relevance: relevances[index],\n }))\n }\n\n // searchInKnowledgeBase: group chunks by document, relevance compares the\n // best chunk score of each document within the result set.\n const knowledgeHits = computed<DetailHit[] | undefined>(() => {\n const results = output.value?.results\n if (!Array.isArray(results)) {\n return undefined\n }\n interface DocumentGroup {\n id: string\n type: string\n title: string\n url?: string\n similarity: number\n chunks: string[]\n }\n const groups = new Map<string, DocumentGroup>()\n for (const [index, raw] of results.entries()) {\n const result = asRecord(raw)\n if (!result) {\n continue\n }\n const document = asRecord(result.document)\n const source = asRecord(document?.source)\n const id =\n typeof document?.id === 'string' ? document.id : `doc-${index}`\n const similarity =\n typeof result.similarity === 'number' ? result.similarity : 0\n let group = groups.get(id)\n if (!group) {\n group = {\n id,\n type:\n typeof document?.type === 'string' ? document.type : '',\n title:\n pickString(document, 'name') ??\n pickString(source, 'filename', 'url') ??\n $t('messagePart.stepDocument'),\n url: pickString(source, 'url'),\n similarity,\n chunks: [],\n }\n groups.set(id, group)\n }\n group.similarity = Math.max(group.similarity, similarity)\n if (typeof result.content === 'string' && result.content.trim()) {\n group.chunks.push(result.content)\n }\n }\n const list = [...groups.values()]\n const relevances = spreadRelevance(list.map((g) => g.similarity))\n return list.map((group, index) => ({\n key: group.id,\n icon: group.type === 'url' ? 'ri:global-line' : 'ri:file-text-line',\n title: group.title,\n url: group.url,\n meta: $t('messagePart.stepExcerpts', group.chunks.length || 1),\n relevance: relevances[index],\n body: group.chunks.join('\\n\\n· · ·\\n\\n') || undefined,\n }))\n })\n\n // Chunks this turn had already returned come back as references without\n // content (spec retrieval-payload-per-turn, R2). They carry no document\n // name to group by, so they are summarized in one line: without it a\n // search that found only known passages reads as \"no results\".\n const repeatedCount = computed<number>(() => {\n const count = asRecord(output.value?.repeated)?.count\n return typeof count === 'number' ? count : 0\n })\n\n // Two declarations every search tool can attach to its output, and the\n // only ones the model is explicitly told not to read as an answer: a\n // query dropped for exceeding the tool's cap, and a search that ran out\n // of time. Without them a timeout renders as \"no results\", which is the\n // reading whoever opens a chat log would take away too.\n const searchFacts = computed<DetailFact[]>(() => {\n const facts: DetailFact[] = []\n const dropped = asRecord(output.value?.inputTruncated)?.dropped\n if (Array.isArray(dropped) && dropped.length) {\n facts.push({\n icon: 'ri:scissors-cut-line',\n text: $t('messagePart.stepDropped', dropped.length),\n })\n }\n const unavailable = asRecord(output.value?.searchUnavailable)\n if (unavailable) {\n // The corrected-answers tool declares one lookup without naming\n // its query, so an absent list means one search, not none.\n const queries = unavailable.queries\n facts.push({\n icon: 'ri:time-line',\n text: $t(\n 'messagePart.stepTimedOut',\n Array.isArray(queries) ? queries.length : 1,\n ),\n })\n }\n return facts\n })\n\n // retrieveRevisedAnswers: one hit per matched question, the corrected\n // answer is the expandable body.\n const revisedAnswerHits = computed<DetailHit[] | undefined>(() =>\n scoredRecords(output.value?.results)?.map(\n ({ record, relevance }, index) => ({\n key:\n pickString(record, 'revisedAnswerId') ?? `revised-${index}`,\n icon: 'ri:file-edit-line',\n title:\n pickString(record, 'userQuestion') ??\n $t('messagePart.revisedAnswers'),\n relevance,\n body: pickString(record, 'revisedMessage'),\n }),\n ),\n )\n\n // recallUserMemory / saveUserMemory delete candidates: one hit per\n // memory, long facts expand to their full text.\n const memoryHits = (raw: unknown): DetailHit[] | undefined =>\n scoredRecords(raw)?.map(({ record, relevance }, index) => {\n const content = pickString(record, 'content') ?? ''\n return {\n key: pickString(record, 'id') ?? `memory-${index}`,\n icon: 'ri:brain-line',\n title: content,\n meta: pickString(record, 'updatedAt')?.slice(0, 10),\n relevance,\n body: content.length > 120 ? content : undefined,\n }\n })\n\n // Query chips over a list of hits, flagging an explicit empty state so the\n // \"no results\" message shows once the tool has run (hits defined, length 0).\n const hitsDetail = (hits: DetailHit[] | undefined): Detail => ({\n chips: queryChips.value,\n hits,\n showEmpty: hits !== undefined && !hits.length,\n })\n\n // Rich, tool-specific view of the step. Returns {} when the tool has\n // nothing meaningful to show yet (the raw fallback is applied by `detail`).\n const buildDetail = (): Detail => {\n const type = props.part.type\n if (type === 'tool-searchInKnowledgeBase') {\n const knowledge = hitsDetail(knowledgeHits.value)\n if (repeatedCount.value) {\n knowledge.facts = [\n {\n icon: 'ri:history-line',\n text: $t(\n 'messagePart.stepRepeated',\n repeatedCount.value,\n ),\n },\n ]\n knowledge.showEmpty = false\n }\n return knowledge\n }\n if (type === 'tool-retrieveRevisedAnswers') {\n return hitsDetail(revisedAnswerHits.value)\n }\n if (type === 'tool-expandChunkContext') {\n const content = pickString(output.value, 'content')\n if (!content) {\n return {}\n }\n const totalChunks = output.value?.totalChunksReturned\n return {\n passage: {\n title: pickString(output.value, 'documentName'),\n meta:\n typeof totalChunks === 'number'\n ? $t('messagePart.stepExcerpts', totalChunks)\n : undefined,\n content,\n },\n }\n }\n if (type === 'tool-readDocumentFile') {\n const name = pickString(output.value, 'name')\n if (!name) {\n return {}\n }\n const mimeType = pickString(output.value, 'mimeType')\n return {\n facts: [\n {\n icon: 'ri:file-text-line',\n text: mimeType ? `${name} (${mimeType})` : name,\n },\n ],\n }\n }\n if (type === 'tool-getWeather') {\n const location =\n pickString(output.value, 'location') ??\n pickString(input.value, 'locationName') ??\n formatCoordinates(input.value)\n if (!location) {\n return {}\n }\n const startDate = pickString(input.value, 'startDate')\n const endDate = pickString(input.value, 'endDate')\n const dateRange = startDate\n ? endDate && endDate !== startDate\n ? ` · ${startDate} → ${endDate}`\n : ` · ${startDate}`\n : ''\n return {\n facts: [\n {\n icon: 'ri:sun-cloudy-line',\n text: `${location}${dateRange}`,\n },\n ],\n }\n }\n if (type === 'tool-geocode') {\n const from =\n pickString(input.value, 'locationName') ??\n formatCoordinates(input.value)\n const to =\n pickString(\n output.value,\n 'name',\n 'displayName',\n 'locationName',\n 'label',\n ) ?? formatCoordinates(output.value)\n const text =\n from && to && from !== to ? `${from} → ${to}` : (from ?? to)\n if (!text) {\n return {}\n }\n return {\n facts: [\n {\n icon: 'ri:map-pin-line',\n text,\n },\n ],\n }\n }\n if (type === 'tool-recallUserMemory') {\n return hitsDetail(memoryHits(output.value?.memories))\n }\n if (type === 'tool-saveUserMemory') {\n const candidates = memoryHits(output.value?.candidates)\n if (candidates) {\n return hitsDetail(candidates)\n }\n const text =\n pickString(input.value, 'content') ??\n pickString(output.value, 'message', 'error')\n if (!text) {\n return {}\n }\n return {\n facts: [\n {\n icon:\n pickString(input.value, 'action') === 'delete'\n ? 'ri:delete-bin-line'\n : 'ri:save-3-line',\n text,\n },\n ],\n }\n }\n if (WEB_SEARCH_TOOL_TYPES.includes(type)) {\n return {\n chips: queryChips.value,\n rawOutput: rawOutputValue.value,\n }\n }\n return {}\n }\n\n const detail = computed<Detail>(() => {\n const result = buildDetail()\n if (searchFacts.value.length) {\n result.facts = [...searchFacts.value, ...(result.facts ?? [])]\n }\n if (output.value?.searchUnavailable) {\n // \"No results found\" under a search that never answered is the\n // one sentence this fact exists to prevent.\n result.showEmpty = false\n }\n // Nothing meaningful to show for this tool yet (e.g. still running):\n // fall back to the raw payloads so the step body is never empty.\n const hasRichContent =\n result.chips?.length ||\n result.facts?.length ||\n result.passage ||\n result.showEmpty ||\n result.hits?.length\n if (!hasRichContent) {\n result.rawInput = rawInputValue.value\n result.rawOutput ??= rawOutputValue.value\n }\n return result\n })\n\n const expandedHits = reactive(new Set<string>())\n const toggleHit = (key: string) => {\n if (expandedHits.has(key)) {\n expandedHits.delete(key)\n } else {\n expandedHits.add(key)\n }\n }\n</script>\n\n<template>\n <div class=\"pk-chatbot-step-detail\">\n <ul v-if=\"detail.chips?.length\" class=\"pk-chatbot-step-detail__chips\">\n <li\n v-for=\"(chip, chipIndex) in detail.chips\"\n :key=\"`${chipIndex}-${chip}`\"\n class=\"pk-chatbot-step-detail__chip\">\n <VvIcon name=\"ri:search-line\" class=\"shrink-0\" />\n <span class=\"pk-chatbot-step-detail__chip-text\">\n {{ chip }}\n </span>\n </li>\n </ul>\n <div\n v-for=\"(fact, factIndex) in detail.facts\"\n :key=\"`${factIndex}-${fact.text}`\"\n class=\"pk-chatbot-step-detail__fact\">\n <VvIcon :name=\"fact.icon\" class=\"shrink-0\" />\n <span>{{ fact.text }}</span>\n </div>\n <div v-if=\"detail.showEmpty\" class=\"pk-chatbot-step-detail__empty\">\n {{ $t('messagePart.stepNoResults') }}\n </div>\n <ul\n v-else-if=\"detail.hits?.length\"\n class=\"pk-chatbot-step-detail__hits\">\n <li v-for=\"hit in detail.hits\" :key=\"hit.key\">\n <component\n :is=\"hit.body ? 'button' : 'div'\"\n :type=\"hit.body ? 'button' : undefined\"\n class=\"pk-chatbot-step-detail__hit-header\"\n :aria-expanded=\"\n hit.body ? expandedHits.has(hit.key) : undefined\n \"\n @click=\"hit.body ? toggleHit(hit.key) : undefined\">\n <VvIcon\n :name=\"hit.icon\"\n class=\"pk-chatbot-step-detail__hit-icon\" />\n <span class=\"pk-chatbot-step-detail__hit-heading\">\n <span class=\"pk-chatbot-step-detail__hit-title\">\n {{ hit.title }}\n </span>\n <a\n v-if=\"hit.url\"\n :href=\"hit.url\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pk-chatbot-step-detail__link\"\n @click.stop>\n <VvIcon name=\"ri:external-link-line\" />\n </a>\n </span>\n <span\n v-if=\"hit.meta\"\n class=\"pk-chatbot-step-detail__hit-meta\">\n {{ hit.meta }}\n </span>\n <span\n v-if=\"hit.relevance !== undefined\"\n class=\"pk-chatbot-step-detail__relevance\"\n aria-hidden=\"true\">\n <span\n class=\"pk-chatbot-step-detail__relevance-fill\"\n :style=\"{\n width: `${Math.round(hit.relevance * 100)}%`,\n }\" />\n </span>\n <VvIcon\n v-if=\"hit.body\"\n :name=\"\n expandedHits.has(hit.key)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"shrink-0\" />\n </component>\n <div\n v-if=\"hit.body && expandedHits.has(hit.key)\"\n class=\"pk-chatbot-step-detail__hit-body\">\n {{ hit.body }}\n </div>\n </li>\n </ul>\n <div v-if=\"detail.passage\" class=\"pk-chatbot-step-detail__passage\">\n <div class=\"pk-chatbot-step-detail__passage-header\">\n <VvIcon name=\"ri:file-text-line\" class=\"shrink-0\" />\n <span class=\"pk-chatbot-step-detail__hit-heading\">\n <span class=\"pk-chatbot-step-detail__hit-title\">\n {{\n detail.passage.title ??\n $t('messagePart.stepDocument')\n }}\n </span>\n </span>\n <span\n v-if=\"detail.passage.meta\"\n class=\"pk-chatbot-step-detail__hit-meta\">\n {{ detail.passage.meta }}\n </span>\n </div>\n <div class=\"pk-chatbot-step-detail__passage-content\">\n {{ detail.passage.content }}\n </div>\n </div>\n <PkChatbotStepPayload\n v-if=\"hasPayload(detail.rawInput)\"\n :label=\"$t('messagePart.stepInput')\"\n :value=\"detail.rawInput\" />\n <PkChatbotStepPayload\n v-if=\"hasPayload(detail.rawOutput)\"\n :label=\"$t('messagePart.stepOutput')\"\n :value=\"detail.rawOutput\" />\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-step-detail {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-8);\n font-size: var(--text-12);\n color: var(--color-word-3);\n\n &__chips {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-4);\n }\n\n &__chip {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-4);\n max-width: 100%;\n padding: var(--spacing-2) var(--spacing-8);\n border: var(--spacing-px) solid var(--color-surface-3);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface-1);\n color: var(--color-word-2);\n }\n\n &__chip-text {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__fact {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n color: var(--color-word-2);\n }\n\n &__empty {\n color: var(--color-word-4);\n font-style: italic;\n }\n\n &__hits {\n display: flex;\n flex-direction: column;\n border: var(--spacing-px) solid var(--color-surface-3);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n\n > li + li {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n }\n\n &__hit-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n width: 100%;\n min-height: var(--spacing-32);\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n\n &:is(button) {\n cursor: pointer;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n &__hit-icon {\n flex-shrink: 0;\n color: var(--color-word-3);\n }\n\n // Grows to fill the row so meta, relevance and chevron stay aligned;\n // the title inside only shrinks, keeping the link icon next to the\n // title text instead of pushed to the right edge.\n &__hit-heading {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n flex: 1 1 0;\n min-width: 0;\n }\n\n &__hit-title {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-word-2);\n }\n\n &__hit-meta {\n color: var(--color-word-4);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n\n &__relevance {\n flex-shrink: 0;\n width: var(--spacing-40);\n height: var(--spacing-4);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface-3);\n overflow: hidden;\n }\n\n &__relevance-fill {\n display: block;\n height: 100%;\n border-radius: inherit;\n background-color: var(--chatbot-main-color, var(--color-word-4));\n }\n\n &__link {\n flex-shrink: 0;\n display: inline-flex;\n color: var(--color-word-4);\n\n &:hover {\n color: var(--color-word-1);\n }\n }\n\n &__hit-body,\n &__passage-content {\n padding: var(--spacing-8);\n max-height: var(--spacing-160);\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--color-word-3);\n }\n\n &__hit-body {\n border-top: var(--spacing-px) solid var(--color-surface-3);\n }\n\n &__passage {\n border: var(--spacing-px) solid var(--color-surface-3);\n border-radius: var(--rounded);\n background-color: var(--color-surface-1);\n overflow: hidden;\n }\n\n &__passage-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n min-height: var(--spacing-32);\n padding: var(--spacing-4) var(--spacing-8);\n border-bottom: var(--spacing-px) solid var(--color-surface-3);\n }\n }\n</style>\n"],"mappings":"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"}
|