@8wave/ai-elements 0.98.0 → 0.99.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -5
- package/dist/_chunks/{PkToolShowForm-BNV4Ja6g.js → PkToolShowForm-dFuoqTPs.js} +84 -80
- package/dist/_chunks/PkToolShowForm-dFuoqTPs.js.map +1 -0
- package/dist/ai-elements.es.js +3153 -2982
- 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/PkChatbotStepToolDetail.js +1 -1
- package/dist-vue/PkChatbotSteps.js +1 -1
- package/dist-vue/PkChatbotViewChat.js +1 -1
- package/dist-vue/PkChatbotViewConversations.js +1 -1
- package/dist-vue/PkChatbotViewFiles.js +1 -1
- package/dist-vue/PkChatbotViewProfile.js +1 -1
- package/dist-vue/_chunks/{PkChatbot-DHkhT0Jf.js → PkChatbot-BlG1WlAO.js} +7 -7
- package/dist-vue/_chunks/{PkChatbot-DHkhT0Jf.js.map → PkChatbot-BlG1WlAO.js.map} +1 -1
- package/dist-vue/_chunks/PkChatbotMessages-BrufA7o1.js +752 -0
- package/dist-vue/_chunks/PkChatbotMessages-BrufA7o1.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotStepToolDetail-ldThv0t5.js → PkChatbotStepToolDetail-BVI72gyx.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotStepToolDetail-ldThv0t5.js.map → PkChatbotStepToolDetail-BVI72gyx.js.map} +1 -1
- package/dist-vue/_chunks/PkChatbotSteps-Dv9NkYER.js +172 -0
- package/dist-vue/_chunks/{PkChatbotSteps-BpqdFhsG.js.map → PkChatbotSteps-Dv9NkYER.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewChat-C08TsXiL.js → PkChatbotViewChat-CAqvbbfN.js} +66 -66
- package/dist-vue/_chunks/PkChatbotViewChat-CAqvbbfN.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotViewConversations-BQZi5kSC.js → PkChatbotViewConversations-Cq41H-tc.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewConversations-BQZi5kSC.js.map → PkChatbotViewConversations-Cq41H-tc.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewFiles-TLKE_tRw.js → PkChatbotViewFiles-DNuzmBJc.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewFiles-TLKE_tRw.js.map → PkChatbotViewFiles-DNuzmBJc.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewProfile-B6ANZY96.js → PkChatbotViewProfile-C0BXIY2N.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewProfile-B6ANZY96.js.map → PkChatbotViewProfile-C0BXIY2N.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowForm-C-mWyeka.js → PkToolShowForm-CQuX08lk.js} +62 -58
- package/dist-vue/_chunks/PkToolShowForm-CQuX08lk.js.map +1 -0
- package/dist-vue/_chunks/{createChatbotApiClient-BOBdBxmf.js → createChatbotApiClient-BMZltBLh.js} +367 -363
- package/dist-vue/_chunks/createChatbotApiClient-BMZltBLh.js.map +1 -0
- package/dist-vue/_chunks/{useChatbotStore-BJ-EDmND.js → useChatbotStore-DZiorgLW.js} +113 -103
- package/dist-vue/_chunks/{useChatbotStore-BJ-EDmND.js.map → useChatbotStore-DZiorgLW.js.map} +1 -1
- package/dist-vue/_chunks/{utils-BMa8jwJS.js → utils-Cru8TJxe.js} +48 -30
- package/dist-vue/_chunks/utils-Cru8TJxe.js.map +1 -0
- package/dist-vue/api.js +1 -1
- package/dist-vue/composables.js +3 -3
- package/dist-vue/index.js +4351 -4041
- package/dist-vue/index.js.map +1 -1
- package/dist-vue/locales.js +16 -0
- package/dist-vue/packages/components/src/PkCheckboxField.d.ts +45 -0
- package/dist-vue/packages/components/src/PkEditorRepeater.d.ts +2 -2
- package/dist-vue/packages/components/src/PkPwaInstallPrompt.d.ts +4 -0
- package/dist-vue/packages/components/src/PkPwaOfflineNotice.d.ts +4 -0
- package/dist-vue/packages/components/src/chat/PkAgentSettingsPanel.d.ts +11 -2
- package/dist-vue/packages/components/src/chat/PkChatbotMessages.d.ts +24 -2
- package/dist-vue/packages/components/src/chat/PkToolApprovalBadge.d.ts +4 -0
- package/dist-vue/packages/components/src/chat/PkToolApprovalCard.d.ts +35 -0
- package/dist-vue/packages/components/src/chat/PkToolApprovalPreviewMessage.d.ts +11 -0
- package/dist-vue/packages/components/src/chat/index.d.ts +2 -1
- package/dist-vue/packages/components/src/chat/toolApprovalPreviews.d.ts +12 -0
- package/dist-vue/packages/components/src/chat/toolComponentMap.d.ts +5 -3
- package/dist-vue/packages/components/src/chat/useChatScroll.d.ts +6 -0
- package/dist-vue/packages/components/src/chat/utils.d.ts +13 -0
- package/dist-vue/packages/components/src/index.d.ts +4 -1
- package/dist-vue/packages/composable/src/chatbot/useChatbotStore.d.ts +34 -1
- package/dist-vue/packages/models/src/schema/Agent.d.ts +48 -0
- package/dist-vue/packages/models/src/schema/ReasoningChat.d.ts +6 -0
- package/dist-vue/packages/models/src/schema/SubAgent.d.ts +6 -0
- package/dist-vue/style.css +1 -1
- package/package.json +1 -1
- package/dist/_chunks/PkToolRequestConfirm-FQ9nDFXy.js +0 -48
- package/dist/_chunks/PkToolRequestConfirm-FQ9nDFXy.js.map +0 -1
- package/dist/_chunks/PkToolShowForm-BNV4Ja6g.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotMessages-B4qfTNOJ.js +0 -630
- package/dist-vue/_chunks/PkChatbotMessages-B4qfTNOJ.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotSteps-BpqdFhsG.js +0 -172
- package/dist-vue/_chunks/PkChatbotViewChat-C08TsXiL.js.map +0 -1
- package/dist-vue/_chunks/PkToolRequestConfirm-CQNa3seA.js +0 -48
- package/dist-vue/_chunks/PkToolRequestConfirm-CQNa3seA.js.map +0 -1
- package/dist-vue/_chunks/PkToolShowForm-C-mWyeka.js.map +0 -1
- package/dist-vue/_chunks/createChatbotApiClient-BOBdBxmf.js.map +0 -1
- package/dist-vue/_chunks/utils-BMa8jwJS.js.map +0 -1
- package/dist-vue/packages/components/src/chat/PkToolRequestConfirm.d.ts +0 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PkChatbotStepToolDetail-ldThv0t5.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 { stringifyPayload, 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 Detail {\n chips?: string[]\n fact?: { icon: string; text: string }\n hits?: DetailHit[]\n showEmpty?: boolean\n passage?: { title?: string; meta?: string; content: string }\n rawInput?: string\n rawOutput?: string\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 // 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 return hitsDetail(knowledgeHits.value)\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 fact: {\n icon: 'ri:file-text-line',\n text: mimeType ? `${name} (${mimeType})` : name,\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 fact: {\n icon: 'ri:sun-cloudy-line',\n text: `${location}${dateRange}`,\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 fact: {\n icon: 'ri:map-pin-line',\n text,\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 fact: {\n icon:\n pickString(input.value, 'action') === 'delete'\n ? 'ri:delete-bin-line'\n : 'ri:save-3-line',\n text,\n },\n }\n }\n if (WEB_SEARCH_TOOL_TYPES.includes(type)) {\n return {\n chips: queryChips.value,\n rawOutput: stringifyPayload(rawOutputValue.value),\n }\n }\n return {}\n }\n\n const detail = computed<Detail>(() => {\n const result = buildDetail()\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.fact ||\n result.passage ||\n result.showEmpty ||\n result.hits?.length\n if (!hasRichContent) {\n result.rawInput = stringifyPayload(rawInputValue.value)\n result.rawOutput ??= stringifyPayload(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 v-if=\"detail.fact\" class=\"pk-chatbot-step-detail__fact\">\n <VvIcon :name=\"detail.fact.icon\" class=\"shrink-0\" />\n <span>{{ detail.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 <div v-if=\"detail.rawInput\" class=\"pk-chatbot-steps__step-payload\">\n <span class=\"pk-chatbot-steps__step-payload-label\">\n {{ $t('messagePart.stepInput') }}\n </span>\n <pre>{{ detail.rawInput }}</pre>\n </div>\n <div v-if=\"detail.rawOutput\" class=\"pk-chatbot-steps__step-payload\">\n <span class=\"pk-chatbot-steps__step-payload-label\">\n {{ $t('messagePart.stepOutput') }}\n </span>\n <pre>{{ detail.rawOutput }}</pre>\n </div>\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 { stringifyPayload, 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 Detail {\n chips?: string[]\n fact?: { icon: string; text: string }\n hits?: DetailHit[]\n showEmpty?: boolean\n passage?: { title?: string; meta?: string; content: string }\n rawInput?: string\n rawOutput?: string\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 // 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 return hitsDetail(knowledgeHits.value)\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 fact: {\n icon: 'ri:file-text-line',\n text: mimeType ? `${name} (${mimeType})` : name,\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 fact: {\n icon: 'ri:sun-cloudy-line',\n text: `${location}${dateRange}`,\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 fact: {\n icon: 'ri:map-pin-line',\n text,\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 fact: {\n icon:\n pickString(input.value, 'action') === 'delete'\n ? 'ri:delete-bin-line'\n : 'ri:save-3-line',\n text,\n },\n }\n }\n if (WEB_SEARCH_TOOL_TYPES.includes(type)) {\n return {\n chips: queryChips.value,\n rawOutput: stringifyPayload(rawOutputValue.value),\n }\n }\n return {}\n }\n\n const detail = computed<Detail>(() => {\n const result = buildDetail()\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.fact ||\n result.passage ||\n result.showEmpty ||\n result.hits?.length\n if (!hasRichContent) {\n result.rawInput = stringifyPayload(rawInputValue.value)\n result.rawOutput ??= stringifyPayload(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 v-if=\"detail.fact\" class=\"pk-chatbot-step-detail__fact\">\n <VvIcon :name=\"detail.fact.icon\" class=\"shrink-0\" />\n <span>{{ detail.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 <div v-if=\"detail.rawInput\" class=\"pk-chatbot-steps__step-payload\">\n <span class=\"pk-chatbot-steps__step-payload-label\">\n {{ $t('messagePart.stepInput') }}\n </span>\n <pre>{{ detail.rawInput }}</pre>\n </div>\n <div v-if=\"detail.rawOutput\" class=\"pk-chatbot-steps__step-payload\">\n <span class=\"pk-chatbot-steps__step-payload-label\">\n {{ $t('messagePart.stepOutput') }}\n </span>\n <pre>{{ detail.rawOutput }}</pre>\n </div>\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-BVI72gyx.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 { stringifyPayload, 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 Detail {\n chips?: string[]\n fact?: { icon: string; text: string }\n hits?: DetailHit[]\n showEmpty?: boolean\n passage?: { title?: string; meta?: string; content: string }\n rawInput?: string\n rawOutput?: string\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 // 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 return hitsDetail(knowledgeHits.value)\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 fact: {\n icon: 'ri:file-text-line',\n text: mimeType ? `${name} (${mimeType})` : name,\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 fact: {\n icon: 'ri:sun-cloudy-line',\n text: `${location}${dateRange}`,\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 fact: {\n icon: 'ri:map-pin-line',\n text,\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 fact: {\n icon:\n pickString(input.value, 'action') === 'delete'\n ? 'ri:delete-bin-line'\n : 'ri:save-3-line',\n text,\n },\n }\n }\n if (WEB_SEARCH_TOOL_TYPES.includes(type)) {\n return {\n chips: queryChips.value,\n rawOutput: stringifyPayload(rawOutputValue.value),\n }\n }\n return {}\n }\n\n const detail = computed<Detail>(() => {\n const result = buildDetail()\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.fact ||\n result.passage ||\n result.showEmpty ||\n result.hits?.length\n if (!hasRichContent) {\n result.rawInput = stringifyPayload(rawInputValue.value)\n result.rawOutput ??= stringifyPayload(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 v-if=\"detail.fact\" class=\"pk-chatbot-step-detail__fact\">\n <VvIcon :name=\"detail.fact.icon\" class=\"shrink-0\" />\n <span>{{ detail.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 <div v-if=\"detail.rawInput\" class=\"pk-chatbot-steps__step-payload\">\n <span class=\"pk-chatbot-steps__step-payload-label\">\n {{ $t('messagePart.stepInput') }}\n </span>\n <pre>{{ detail.rawInput }}</pre>\n </div>\n <div v-if=\"detail.rawOutput\" class=\"pk-chatbot-steps__step-payload\">\n <span class=\"pk-chatbot-steps__step-payload-label\">\n {{ $t('messagePart.stepOutput') }}\n </span>\n <pre>{{ detail.rawOutput }}</pre>\n </div>\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 { stringifyPayload, 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 Detail {\n chips?: string[]\n fact?: { icon: string; text: string }\n hits?: DetailHit[]\n showEmpty?: boolean\n passage?: { title?: string; meta?: string; content: string }\n rawInput?: string\n rawOutput?: string\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 // 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 return hitsDetail(knowledgeHits.value)\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 fact: {\n icon: 'ri:file-text-line',\n text: mimeType ? `${name} (${mimeType})` : name,\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 fact: {\n icon: 'ri:sun-cloudy-line',\n text: `${location}${dateRange}`,\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 fact: {\n icon: 'ri:map-pin-line',\n text,\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 fact: {\n icon:\n pickString(input.value, 'action') === 'delete'\n ? 'ri:delete-bin-line'\n : 'ri:save-3-line',\n text,\n },\n }\n }\n if (WEB_SEARCH_TOOL_TYPES.includes(type)) {\n return {\n chips: queryChips.value,\n rawOutput: stringifyPayload(rawOutputValue.value),\n }\n }\n return {}\n }\n\n const detail = computed<Detail>(() => {\n const result = buildDetail()\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.fact ||\n result.passage ||\n result.showEmpty ||\n result.hits?.length\n if (!hasRichContent) {\n result.rawInput = stringifyPayload(rawInputValue.value)\n result.rawOutput ??= stringifyPayload(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 v-if=\"detail.fact\" class=\"pk-chatbot-step-detail__fact\">\n <VvIcon :name=\"detail.fact.icon\" class=\"shrink-0\" />\n <span>{{ detail.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 <div v-if=\"detail.rawInput\" class=\"pk-chatbot-steps__step-payload\">\n <span class=\"pk-chatbot-steps__step-payload-label\">\n {{ $t('messagePart.stepInput') }}\n </span>\n <pre>{{ detail.rawInput }}</pre>\n </div>\n <div v-if=\"detail.rawOutput\" class=\"pk-chatbot-steps__step-payload\">\n <span class=\"pk-chatbot-steps__step-payload-label\">\n {{ $t('messagePart.stepOutput') }}\n </span>\n <pre>{{ detail.rawOutput }}</pre>\n </div>\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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"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { c as e, o as t } from "./message-RohfrdWS.js";
|
|
2
|
+
import { t as n } from "./PkStreamingMarkdown-DEhbK-3c.js";
|
|
3
|
+
import { a as r, c as i, g as a, i as o, l as s, r as c, s as l } from "./utils-Cru8TJxe.js";
|
|
4
|
+
import { t as u } from "./PkChatbotStepToolDetail-BVI72gyx.js";
|
|
5
|
+
import { VvIcon as d } from "@volverjs/ui-vue/components";
|
|
6
|
+
import { Fragment as f, computed as p, createBlock as m, createCommentVNode as h, createElementBlock as g, createElementVNode as _, createTextVNode as v, createVNode as y, defineComponent as b, nextTick as x, normalizeClass as S, openBlock as C, reactive as ee, ref as w, renderList as te, resolveDynamicComponent as T, toDisplayString as E, unref as D, watch as O, withCtx as k } from "vue";
|
|
7
|
+
import { useI18n as A } from "vue-i18n";
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region ../../packages/components/src/chat/PkStreamingMarkdownAutoscroll.vue
|
|
10
|
+
var j = /* @__PURE__ */ b({
|
|
11
|
+
__name: "PkStreamingMarkdownAutoscroll",
|
|
12
|
+
props: {
|
|
13
|
+
markdown: {},
|
|
14
|
+
innerClass: {},
|
|
15
|
+
isDark: { type: Boolean }
|
|
16
|
+
},
|
|
17
|
+
setup(e) {
|
|
18
|
+
let t = e, r = w(), i = w(0), a = w(!1), o = () => {
|
|
19
|
+
if (!r.value) return;
|
|
20
|
+
let e = r.value.scrollTop;
|
|
21
|
+
e < i.value && i.value - e < 50 && (a.value = !0), e > i.value && e === r.value.scrollHeight - r.value.clientHeight && (a.value = !1), i.value = e;
|
|
22
|
+
};
|
|
23
|
+
return O(() => t.markdown, async () => {
|
|
24
|
+
await x(), r.value && !a.value && (r.value.scrollTop = r.value.scrollHeight);
|
|
25
|
+
}, { immediate: !0 }), (t, i) => (C(), g("div", {
|
|
26
|
+
ref_key: "scrollEl",
|
|
27
|
+
ref: r,
|
|
28
|
+
class: "overflow-auto light-scrollbar",
|
|
29
|
+
onScroll: o
|
|
30
|
+
}, [y(n, {
|
|
31
|
+
markdown: e.markdown,
|
|
32
|
+
"is-dark": e.isDark,
|
|
33
|
+
class: S(e.innerClass)
|
|
34
|
+
}, null, 8, [
|
|
35
|
+
"markdown",
|
|
36
|
+
"is-dark",
|
|
37
|
+
"class"
|
|
38
|
+
])], 544));
|
|
39
|
+
}
|
|
40
|
+
}), M = {
|
|
41
|
+
key: 0,
|
|
42
|
+
class: "pk-chatbot-steps"
|
|
43
|
+
}, N = ["aria-expanded"], ne = { class: "pk-chatbot-steps__title" }, re = { class: "pk-chatbot-steps__meta" }, ie = {
|
|
44
|
+
key: 0,
|
|
45
|
+
class: "pk-chatbot-steps__list"
|
|
46
|
+
}, ae = { class: "pk-chatbot-steps__step-label" }, P = { class: "pk-chatbot-steps__step-summary" }, F = {
|
|
47
|
+
key: 0,
|
|
48
|
+
class: "pk-chatbot-steps__step-duration"
|
|
49
|
+
}, I = {
|
|
50
|
+
key: 0,
|
|
51
|
+
class: "pk-chatbot-steps__step-body"
|
|
52
|
+
}, L = {
|
|
53
|
+
key: 2,
|
|
54
|
+
class: "pk-chatbot-steps__step-payload pk-chatbot-steps__step-payload--error"
|
|
55
|
+
}, R = { class: "pk-chatbot-steps__step-payload-label" }, z = {
|
|
56
|
+
key: 0,
|
|
57
|
+
class: "pk-chatbot-steps__step-payload"
|
|
58
|
+
}, B = { class: "pk-chatbot-steps__step-payload-label" }, V = {
|
|
59
|
+
key: 1,
|
|
60
|
+
class: "pk-chatbot-steps__step-payload"
|
|
61
|
+
}, H = { class: "pk-chatbot-steps__step-payload-label" }, U = /* @__PURE__ */ b({
|
|
62
|
+
__name: "PkChatbotSteps",
|
|
63
|
+
props: {
|
|
64
|
+
message: {},
|
|
65
|
+
isStreaming: { type: Boolean },
|
|
66
|
+
isDark: { type: Boolean }
|
|
67
|
+
},
|
|
68
|
+
setup(b) {
|
|
69
|
+
let x = b, { t: U, n: W } = A({ useScope: "global" }), G = p(() => {
|
|
70
|
+
let n = x.message.parts, r = x.message.metadata?.partTimings, a = [];
|
|
71
|
+
for (let [s, c] of n.entries()) {
|
|
72
|
+
let u = t(c), d = e(c);
|
|
73
|
+
if (!u && !d) continue;
|
|
74
|
+
let f = "toolCallId" in c ? c.toolCallId : void 0;
|
|
75
|
+
a.push({
|
|
76
|
+
key: f ?? `${c.type}-${s}`,
|
|
77
|
+
part: c,
|
|
78
|
+
icon: o(c),
|
|
79
|
+
label: l(U, c),
|
|
80
|
+
summary: i(c),
|
|
81
|
+
durationMs: f ? r?.[f] : void 0,
|
|
82
|
+
isActive: !!x.isStreaming && s === n.length - 1,
|
|
83
|
+
text: u && c.text.trim() ? c.text : void 0,
|
|
84
|
+
toolInput: d && "input" in c ? c.input : void 0,
|
|
85
|
+
toolOutput: d && "output" in c ? c.output : void 0
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return a;
|
|
89
|
+
}), K = p(() => {
|
|
90
|
+
let { createdAt: e, completedAt: t } = x.message.metadata ?? {};
|
|
91
|
+
if (!(!e || !t)) return t - e;
|
|
92
|
+
}), q = (e) => c(W, e), J = (e) => r(e.part) === "output-error" && "errorText" in e.part && typeof e.part.errorText == "string" ? e.part.errorText : void 0, Y = (e) => e.text || J(e) ? !0 : s(e.part) ? e.toolInput !== void 0 || e.toolOutput !== void 0 : !!(a(e.toolInput) ?? a(e.toolOutput)), X = ee(/* @__PURE__ */ new Map()), Z = (e) => X.get(e.key) ?? e.isActive, oe = (e) => {
|
|
93
|
+
X.set(e.key, !Z(e));
|
|
94
|
+
}, Q = w(void 0), $ = p(() => Q.value ?? !!x.isStreaming);
|
|
95
|
+
return O(() => x.isStreaming, (e) => {
|
|
96
|
+
e && (Q.value = void 0, X.clear());
|
|
97
|
+
}), (e, t) => {
|
|
98
|
+
let r = d;
|
|
99
|
+
return G.value.length ? (C(), g("div", M, [_("button", {
|
|
100
|
+
type: "button",
|
|
101
|
+
class: "pk-chatbot-steps__header",
|
|
102
|
+
"aria-expanded": $.value,
|
|
103
|
+
onClick: t[0] ||= (e) => Q.value = !$.value
|
|
104
|
+
}, [
|
|
105
|
+
b.isStreaming ? (C(), m(r, {
|
|
106
|
+
key: 0,
|
|
107
|
+
name: "line-md:loading-loop",
|
|
108
|
+
class: "shrink-0"
|
|
109
|
+
})) : (C(), m(r, {
|
|
110
|
+
key: 1,
|
|
111
|
+
name: "ri:route-line",
|
|
112
|
+
class: "shrink-0"
|
|
113
|
+
})),
|
|
114
|
+
_("span", ne, E(D(U)("messagePart.steps")), 1),
|
|
115
|
+
_("span", re, [v(E(D(U)("messagePart.stepCount", G.value.length)) + " ", 1), q(K.value) ? (C(), g(f, { key: 0 }, [v(" · " + E(q(K.value)), 1)], 64)) : h("", !0)]),
|
|
116
|
+
y(r, {
|
|
117
|
+
name: $.value ? "ri:arrow-up-s-line" : "ri:arrow-down-s-line",
|
|
118
|
+
class: "pk-chatbot-steps__chevron shrink-0 ml-auto"
|
|
119
|
+
}, null, 8, ["name"])
|
|
120
|
+
], 8, N), $.value ? (C(), g("ol", ie, [(C(!0), g(f, null, te(G.value, (e) => (C(), g("li", {
|
|
121
|
+
key: e.key,
|
|
122
|
+
class: S(["pk-chatbot-steps__step", { "pk-chatbot-steps__step--active": e.isActive }])
|
|
123
|
+
}, [(C(), m(T(Y(e) ? "button" : "div"), {
|
|
124
|
+
type: Y(e) ? "button" : void 0,
|
|
125
|
+
class: "pk-chatbot-steps__step-header",
|
|
126
|
+
"aria-expanded": Y(e) ? Z(e) : void 0,
|
|
127
|
+
onClick: (t) => Y(e) ? oe(e) : void 0
|
|
128
|
+
}, {
|
|
129
|
+
default: k(() => [
|
|
130
|
+
_("span", { class: S(["pk-chatbot-steps-marker", { "pk-chatbot-steps-marker--active": e.isActive }]) }, [e.isActive ? (C(), m(r, {
|
|
131
|
+
key: 0,
|
|
132
|
+
name: "line-md:loading-loop"
|
|
133
|
+
})) : e.icon ? (C(), m(r, {
|
|
134
|
+
key: 1,
|
|
135
|
+
name: e.icon
|
|
136
|
+
}, null, 8, ["name"])) : h("", !0)], 2),
|
|
137
|
+
_("span", ae, E(e.label), 1),
|
|
138
|
+
_("span", P, E(e.summary), 1),
|
|
139
|
+
q(e.durationMs) ? (C(), g("span", F, E(q(e.durationMs)), 1)) : h("", !0),
|
|
140
|
+
Y(e) ? (C(), m(r, {
|
|
141
|
+
key: 1,
|
|
142
|
+
name: Z(e) ? "ri:arrow-up-s-line" : "ri:arrow-down-s-line",
|
|
143
|
+
class: "pk-chatbot-steps__chevron shrink-0 ml-auto"
|
|
144
|
+
}, null, 8, ["name"])) : h("", !0)
|
|
145
|
+
]),
|
|
146
|
+
_: 2
|
|
147
|
+
}, 1032, [
|
|
148
|
+
"type",
|
|
149
|
+
"aria-expanded",
|
|
150
|
+
"onClick"
|
|
151
|
+
])), Y(e) && Z(e) ? (C(), g("div", I, [e.text && e.isActive ? (C(), m(j, {
|
|
152
|
+
key: 0,
|
|
153
|
+
markdown: e.text,
|
|
154
|
+
"is-dark": b.isDark,
|
|
155
|
+
"inner-class": "wysiwyg",
|
|
156
|
+
class: "pk-chatbot-steps__step-content pk-chatbot-steps__step-content--scroll"
|
|
157
|
+
}, null, 8, ["markdown", "is-dark"])) : e.text ? (C(), m(n, {
|
|
158
|
+
key: 1,
|
|
159
|
+
markdown: e.text,
|
|
160
|
+
"is-dark": b.isDark,
|
|
161
|
+
class: "wysiwyg pk-chatbot-steps__step-content"
|
|
162
|
+
}, null, 8, ["markdown", "is-dark"])) : J(e) ? (C(), g("div", L, [_("span", R, E(D(U)("messagePart.stepError")), 1), _("pre", null, E(J(e)), 1)])) : D(s)(e.part) ? (C(), m(u, {
|
|
163
|
+
key: 3,
|
|
164
|
+
part: e.part
|
|
165
|
+
}, null, 8, ["part"])) : (C(), g(f, { key: 4 }, [D(a)(e.toolInput) ? (C(), g("div", z, [_("span", B, E(D(U)("messagePart.stepInput")), 1), _("pre", null, E(D(a)(e.toolInput)), 1)])) : h("", !0), D(a)(e.toolOutput) ? (C(), g("div", V, [_("span", H, E(D(U)("messagePart.stepOutput")), 1), _("pre", null, E(D(a)(e.toolOutput)), 1)])) : h("", !0)], 64))])) : h("", !0)], 2))), 128))])) : h("", !0)])) : h("", !0);
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
//#endregion
|
|
170
|
+
export { j as n, U as t };
|
|
171
|
+
|
|
172
|
+
//# sourceMappingURL=PkChatbotSteps-Dv9NkYER.js.map
|