@8wave/ai-elements 0.104.0 → 0.105.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.
Files changed (125) hide show
  1. package/dist/_chunks/{Agent-DG4nPG72.js → Agent-VhugusRf.js} +2 -2
  2. package/dist/_chunks/Agent-VhugusRf.js.map +1 -0
  3. package/dist/_chunks/{Document-DXkvvAnt.js → Document-DjNrISX1.js} +2 -2
  4. package/dist/_chunks/{Document-DXkvvAnt.js.map → Document-DjNrISX1.js.map} +1 -1
  5. package/dist/_chunks/{Media-3Mo4NRj7.js → Media-hjgDyEMr.js} +2 -2
  6. package/dist/_chunks/Media-hjgDyEMr.js.map +1 -0
  7. package/dist/_chunks/{PkStreamingMarkdown-YKd9F3xU.js → PkStreamingMarkdown-B4eMb97I.js} +2 -2
  8. package/dist/_chunks/{PkStreamingMarkdown-YKd9F3xU.js.map → PkStreamingMarkdown-B4eMb97I.js.map} +1 -1
  9. package/dist/_chunks/{PkToolRequestGeolocation-sf6LpUN_.js → PkToolRequestGeolocation-Bp-D2nDb.js} +3 -3
  10. package/{dist-vue/_chunks/PkToolRequestGeolocation-BdX5DzEN.js.map → dist/_chunks/PkToolRequestGeolocation-Bp-D2nDb.js.map} +1 -1
  11. package/dist/_chunks/{PkToolRequestOAuthConnection-BB8mlQit.js → PkToolRequestOAuthConnection-CjjyDj9A.js} +3 -3
  12. package/dist/_chunks/{PkToolRequestOAuthConnection-BB8mlQit.js.map → PkToolRequestOAuthConnection-CjjyDj9A.js.map} +1 -1
  13. package/dist/_chunks/{PkToolShowArtifact-54saXXCe.js → PkToolShowArtifact-Dl8f7Q7q.js} +2 -2
  14. package/dist/_chunks/{PkToolShowArtifact-54saXXCe.js.map → PkToolShowArtifact-Dl8f7Q7q.js.map} +1 -1
  15. package/dist/_chunks/{PkToolShowCalendarEvent-B_HPVUDk.js → PkToolShowCalendarEvent-B_QmZrlZ.js} +2 -2
  16. package/dist/_chunks/{PkToolShowCalendarEvent-B_HPVUDk.js.map → PkToolShowCalendarEvent-B_QmZrlZ.js.map} +1 -1
  17. package/dist/_chunks/{PkToolShowContactForm-BtcNCixq.js → PkToolShowContactForm-CXE0485W.js} +14 -14
  18. package/dist/_chunks/{PkToolShowContactForm-BtcNCixq.js.map → PkToolShowContactForm-CXE0485W.js.map} +1 -1
  19. package/dist/_chunks/{PkToolShowDiagram-cA64c2_N.js → PkToolShowDiagram-D-TyCkCB.js} +2 -2
  20. package/dist/_chunks/{PkToolShowDiagram-cA64c2_N.js.map → PkToolShowDiagram-D-TyCkCB.js.map} +1 -1
  21. package/dist/_chunks/{PkToolShowForm-DdBAG0Qs.js → PkToolShowForm-BzaibKAz.js} +101 -115
  22. package/dist/_chunks/PkToolShowForm-BzaibKAz.js.map +1 -0
  23. package/dist/_chunks/{PkToolShowSources-0qudS-bu.js → PkToolShowSources-CoTIoupP.js} +3 -3
  24. package/dist/_chunks/{PkToolShowSources-0qudS-bu.js.map → PkToolShowSources-CoTIoupP.js.map} +1 -1
  25. package/dist/_chunks/{PkToolShowSuggestedReply-B2IB6ioR.js → PkToolShowSuggestedReply-DA2Bi7k4.js} +2 -2
  26. package/dist/_chunks/{PkToolShowSuggestedReply-B2IB6ioR.js.map → PkToolShowSuggestedReply-DA2Bi7k4.js.map} +1 -1
  27. package/dist/_chunks/{PkToolShowWeather-Bl3Yoscl.js → PkToolShowWeather-ChDHtOLz.js} +2 -2
  28. package/dist/_chunks/{PkToolShowWeather-Bl3Yoscl.js.map → PkToolShowWeather-ChDHtOLz.js.map} +1 -1
  29. package/dist/_chunks/{browser-DuzNfJq0.js → browser-BXy4OnXA.js} +804 -779
  30. package/dist/_chunks/browser-BXy4OnXA.js.map +1 -0
  31. package/dist/_chunks/index.es-CnpsLhp_.js +1006 -0
  32. package/dist/_chunks/index.es-CnpsLhp_.js.map +1 -0
  33. package/dist/_chunks/utils-DtJdKlnF.js +185 -0
  34. package/dist/_chunks/utils-DtJdKlnF.js.map +1 -0
  35. package/dist/_chunks/{vue-i18n-_ouwlDGE.js → vue-i18n-C3J36GV4.js} +2 -2
  36. package/dist/_chunks/{vue-i18n-_ouwlDGE.js.map → vue-i18n-C3J36GV4.js.map} +1 -1
  37. package/dist/_chunks/{web-DnPxEADy.js → web-CzR5jWfC.js} +46 -45
  38. package/dist/_chunks/{web-DnPxEADy.js.map → web-CzR5jWfC.js.map} +1 -1
  39. package/dist/ai-elements.es.js +2100 -2159
  40. package/dist/ai-elements.es.js.map +1 -1
  41. package/dist-vue/PkChatbot.js +1 -1
  42. package/dist-vue/PkChatbotMessages.js +1 -1
  43. package/dist-vue/PkChatbotStepPayload.js +1 -1
  44. package/dist-vue/PkChatbotStepToolDetail.js +1 -1
  45. package/dist-vue/PkChatbotSteps.js +1 -1
  46. package/dist-vue/PkChatbotViewChat.js +1 -1
  47. package/dist-vue/PkChatbotViewConversations.js +1 -1
  48. package/dist-vue/PkChatbotViewFiles.js +1 -1
  49. package/dist-vue/PkChatbotViewProfile.js +1 -1
  50. package/dist-vue/_chunks/{Agent-gCuDMxHN.js → Agent-CpIgGdU4.js} +2 -2
  51. package/dist-vue/_chunks/Agent-CpIgGdU4.js.map +1 -0
  52. package/dist-vue/_chunks/{Document-iH61Cw06.js → Document-CiaGPYTR.js} +2 -2
  53. package/dist-vue/_chunks/{Document-iH61Cw06.js.map → Document-CiaGPYTR.js.map} +1 -1
  54. package/dist-vue/_chunks/{PkChatbot-Bg9oercD.js → PkChatbot-xmjKbljL.js} +7 -7
  55. package/dist-vue/_chunks/{PkChatbot-Bg9oercD.js.map → PkChatbot-xmjKbljL.js.map} +1 -1
  56. package/dist-vue/_chunks/{PkChatbotMessages-DUQxuYaG.js → PkChatbotMessages-tHQ2JQrb.js} +75 -75
  57. package/dist-vue/_chunks/{PkChatbotMessages-DUQxuYaG.js.map → PkChatbotMessages-tHQ2JQrb.js.map} +1 -1
  58. package/dist-vue/_chunks/{PkChatbotStepPayload-hCmy4XG9.js → PkChatbotStepPayload-BtsnkJIV.js} +2 -2
  59. package/dist-vue/_chunks/{PkChatbotStepPayload-hCmy4XG9.js.map → PkChatbotStepPayload-BtsnkJIV.js.map} +1 -1
  60. package/dist-vue/_chunks/{PkChatbotStepToolDetail-B0WU0HcD.js → PkChatbotStepToolDetail-CCa95eBO.js} +116 -101
  61. package/dist-vue/_chunks/PkChatbotStepToolDetail-CCa95eBO.js.map +1 -0
  62. package/dist-vue/_chunks/PkChatbotSteps-CPBzs7Gi.js +180 -0
  63. package/dist-vue/_chunks/PkChatbotSteps-CPBzs7Gi.js.map +1 -0
  64. package/dist-vue/_chunks/{PkChatbotViewChat-YwjWNoqf.js → PkChatbotViewChat-CdiPjQLq.js} +6 -6
  65. package/dist-vue/_chunks/{PkChatbotViewChat-YwjWNoqf.js.map → PkChatbotViewChat-CdiPjQLq.js.map} +1 -1
  66. package/dist-vue/_chunks/{PkChatbotViewConversations-vo57eSwy.js → PkChatbotViewConversations-BP-NJvlK.js} +2 -2
  67. package/dist-vue/_chunks/{PkChatbotViewConversations-vo57eSwy.js.map → PkChatbotViewConversations-BP-NJvlK.js.map} +1 -1
  68. package/dist-vue/_chunks/{PkChatbotViewFiles-DDPugn7F.js → PkChatbotViewFiles-DRUVmxU5.js} +3 -3
  69. package/dist-vue/_chunks/{PkChatbotViewFiles-DDPugn7F.js.map → PkChatbotViewFiles-DRUVmxU5.js.map} +1 -1
  70. package/dist-vue/_chunks/{PkChatbotViewProfile-Dww5KI21.js → PkChatbotViewProfile-ywvIs6N5.js} +2 -2
  71. package/dist-vue/_chunks/{PkChatbotViewProfile-Dww5KI21.js.map → PkChatbotViewProfile-ywvIs6N5.js.map} +1 -1
  72. package/dist-vue/_chunks/{PkToolRequestGeolocation-BdX5DzEN.js → PkToolRequestGeolocation-BdIyXoPQ.js} +2 -2
  73. package/{dist/_chunks/PkToolRequestGeolocation-sf6LpUN_.js.map → dist-vue/_chunks/PkToolRequestGeolocation-BdIyXoPQ.js.map} +1 -1
  74. package/dist-vue/_chunks/{PkToolRequestOAuthConnection-BUmnRgMi.js → PkToolRequestOAuthConnection-Dnch7xAB.js} +2 -2
  75. package/dist-vue/_chunks/{PkToolRequestOAuthConnection-BUmnRgMi.js.map → PkToolRequestOAuthConnection-Dnch7xAB.js.map} +1 -1
  76. package/dist-vue/_chunks/{PkToolShowDiagram-YMo-iVWr.js → PkToolShowDiagram-DgLxNNp7.js} +2 -2
  77. package/dist-vue/_chunks/{PkToolShowDiagram-YMo-iVWr.js.map → PkToolShowDiagram-DgLxNNp7.js.map} +1 -1
  78. package/dist-vue/_chunks/{PkToolShowForm-D2n2rA6J.js → PkToolShowForm-6eLi6Wr0.js} +103 -117
  79. package/dist-vue/_chunks/PkToolShowForm-6eLi6Wr0.js.map +1 -0
  80. package/dist-vue/_chunks/{PkToolShowSources-DUAsrrzC.js → PkToolShowSources-DTJgvDiB.js} +2 -2
  81. package/dist-vue/_chunks/{PkToolShowSources-DUAsrrzC.js.map → PkToolShowSources-DTJgvDiB.js.map} +1 -1
  82. package/dist-vue/_chunks/{PkToolShowSuggestedReply-Dm28RIJF.js → PkToolShowSuggestedReply-D9RY2h6Z.js} +2 -2
  83. package/dist-vue/_chunks/{PkToolShowSuggestedReply-Dm28RIJF.js.map → PkToolShowSuggestedReply-D9RY2h6Z.js.map} +1 -1
  84. package/dist-vue/_chunks/{createChatbotApiClient-CAX-3-oK.js → createChatbotApiClient-12eL7hul.js} +2 -2
  85. package/dist-vue/_chunks/{createChatbotApiClient-CAX-3-oK.js.map → createChatbotApiClient-12eL7hul.js.map} +1 -1
  86. package/dist-vue/_chunks/{useChatbotStore-Rj18hAUf.js → useChatbotStore-Chtzi5jc.js} +1086 -1016
  87. package/dist-vue/_chunks/useChatbotStore-Chtzi5jc.js.map +1 -0
  88. package/dist-vue/_chunks/{utils-B0jsOUqd.js → utils-DKNWwMTQ.js} +46 -23
  89. package/dist-vue/_chunks/utils-DKNWwMTQ.js.map +1 -0
  90. package/dist-vue/_chunks/{web-DnPxEADy.js → web-CzR5jWfC.js} +46 -45
  91. package/dist-vue/_chunks/{web-DnPxEADy.js.map → web-CzR5jWfC.js.map} +1 -1
  92. package/dist-vue/api.js +1 -1
  93. package/dist-vue/composables.js +3 -3
  94. package/dist-vue/index.js +259 -259
  95. package/dist-vue/index.js.map +1 -1
  96. package/dist-vue/locales.js +10 -4
  97. package/dist-vue/packages/components/src/PkEditorRepeater.d.ts +1 -1
  98. package/dist-vue/packages/components/src/chat/constants.d.ts +12 -0
  99. package/dist-vue/packages/components/src/chat/utils.d.ts +37 -0
  100. package/dist-vue/packages/models/src/schema/Agent.d.ts +24 -16
  101. package/dist-vue/packages/models/src/schema/AgentChatStatistics.d.ts +1 -1
  102. package/dist-vue/packages/models/src/schema/Chat.d.ts +9 -9
  103. package/dist-vue/packages/models/src/schema/Document.d.ts +6 -6
  104. package/dist-vue/packages/models/src/schema/DocumentFolder.d.ts +0 -1
  105. package/dist-vue/packages/models/src/schema/McpServer.d.ts +2 -2
  106. package/dist-vue/packages/models/src/schema/Member.d.ts +2 -2
  107. package/dist-vue/packages/models/src/schema/Organization.d.ts +4 -4
  108. package/dist-vue/packages/models/src/schema/ReasoningChat.d.ts +29 -29
  109. package/dist-vue/packages/models/src/schema/SubAgent.d.ts +20 -20
  110. package/dist-vue/packages/models/src/schema/User.d.ts +6 -6
  111. package/dist-vue/packages/models/src/schema/constants.d.ts +2 -0
  112. package/package.json +10 -5
  113. package/dist/_chunks/Agent-DG4nPG72.js.map +0 -1
  114. package/dist/_chunks/Media-3Mo4NRj7.js.map +0 -1
  115. package/dist/_chunks/PkToolShowForm-DdBAG0Qs.js.map +0 -1
  116. package/dist/_chunks/browser-DuzNfJq0.js.map +0 -1
  117. package/dist/_chunks/index.es-DXx8DNKf.js +0 -2986
  118. package/dist/_chunks/index.es-DXx8DNKf.js.map +0 -1
  119. package/dist-vue/_chunks/Agent-gCuDMxHN.js.map +0 -1
  120. package/dist-vue/_chunks/PkChatbotStepToolDetail-B0WU0HcD.js.map +0 -1
  121. package/dist-vue/_chunks/PkChatbotSteps-BrvTsAeZ.js +0 -177
  122. package/dist-vue/_chunks/PkChatbotSteps-BrvTsAeZ.js.map +0 -1
  123. package/dist-vue/_chunks/PkToolShowForm-D2n2rA6J.js.map +0 -1
  124. package/dist-vue/_chunks/useChatbotStore-Rj18hAUf.js.map +0 -1
  125. package/dist-vue/_chunks/utils-B0jsOUqd.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PkChatbotStepToolDetail-CCa95eBO.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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"}
@@ -0,0 +1,180 @@
1
+ import { t as e } from "./PkStreamingMarkdown-DWU_rlti.js";
2
+ import { c as t, o as n } from "./message-RohfrdWS.js";
3
+ import { c as r, d as i, f as a, o, p as ee, s, u as c } from "./utils-DKNWwMTQ.js";
4
+ import { t as l } from "./PkChatbotStepPayload-BtsnkJIV.js";
5
+ import { t as u } from "./PkChatbotStepToolDetail-CCa95eBO.js";
6
+ import { VvIcon as d } from "@volverjs/ui-vue/components";
7
+ 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 w, ref as T, renderList as E, resolveDynamicComponent as D, toDisplayString as O, unref as k, watch as A, withCtx as j } from "vue";
8
+ import { useI18n as te } from "vue-i18n";
9
+ //#endregion
10
+ //#region ../../packages/components/src/chat/PkStreamingMarkdownAutoscroll.vue
11
+ var M = /* @__PURE__ */ b({
12
+ __name: "PkStreamingMarkdownAutoscroll",
13
+ props: {
14
+ markdown: {},
15
+ innerClass: {},
16
+ isDark: { type: Boolean }
17
+ },
18
+ setup(t) {
19
+ let n = t, r = T(), i = T(0), a = T(!1), o = () => {
20
+ if (!r.value) return;
21
+ let e = r.value.scrollTop;
22
+ 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;
23
+ };
24
+ return A(() => n.markdown, async () => {
25
+ await x(), r.value && !a.value && (r.value.scrollTop = r.value.scrollHeight);
26
+ }, { immediate: !0 }), (n, i) => (C(), g("div", {
27
+ ref_key: "scrollEl",
28
+ ref: r,
29
+ class: "overflow-auto light-scrollbar",
30
+ onScroll: o
31
+ }, [y(e, {
32
+ markdown: t.markdown,
33
+ "is-dark": t.isDark,
34
+ class: S(t.innerClass)
35
+ }, null, 8, [
36
+ "markdown",
37
+ "is-dark",
38
+ "class"
39
+ ])], 544));
40
+ }
41
+ }), ne = {
42
+ key: 0,
43
+ class: "pk-chatbot-steps"
44
+ }, N = ["aria-expanded"], P = { class: "pk-chatbot-steps__title" }, F = { class: "pk-chatbot-steps__meta" }, I = {
45
+ key: 0,
46
+ class: "pk-chatbot-steps__list"
47
+ }, L = { class: "pk-chatbot-steps__step-label" }, R = { class: "pk-chatbot-steps__step-summary" }, z = {
48
+ key: 0,
49
+ class: "pk-chatbot-steps__step-duration"
50
+ }, B = {
51
+ key: 0,
52
+ class: "pk-chatbot-steps__step-body"
53
+ }, V = {
54
+ key: 1,
55
+ class: "pk-chatbot-steps__step-content pk-chatbot-steps__step-content--scroll light-scrollbar"
56
+ }, H = {
57
+ key: 2,
58
+ class: "pk-chatbot-steps__step-payload pk-chatbot-steps__step-payload--error"
59
+ }, U = { class: "pk-chatbot-steps__step-payload-label" }, W = /* @__PURE__ */ b({
60
+ __name: "PkChatbotSteps",
61
+ props: {
62
+ message: {},
63
+ isStreaming: { type: Boolean },
64
+ isDark: { type: Boolean }
65
+ },
66
+ setup(b) {
67
+ let x = b, { t: W, n: G } = te({ useScope: "global" }), K = (e) => r(e) === "output-error" && "errorText" in e && typeof e.errorText == "string" ? e.errorText : void 0, q = p(() => {
68
+ let e = x.message.parts, r = x.message.metadata?.partTimings, o = [];
69
+ for (let [l, u] of e.entries()) {
70
+ let d = n(u), f = t(u);
71
+ if (!d && !f) continue;
72
+ let p = "toolCallId" in u ? u.toolCallId : void 0, m = d && u.text.trim() ? u.text : void 0, h = K(u), g = ee(u), _ = f && "input" in u ? u.input : void 0, v = f && "output" in u ? u.output : void 0;
73
+ o.push({
74
+ key: p ?? `${u.type}-${l}`,
75
+ part: u,
76
+ icon: s(u),
77
+ label: c(W, u),
78
+ summary: i(u),
79
+ durationMs: p ? r?.[p] : void 0,
80
+ isActive: !!x.isStreaming && l === e.length - 1,
81
+ text: m,
82
+ error: h,
83
+ hasRichDetail: g,
84
+ hasBody: !!(m || h || (g ? _ !== void 0 || v !== void 0 : a(_) || a(v))),
85
+ toolInput: _,
86
+ toolOutput: v
87
+ });
88
+ }
89
+ return o;
90
+ }), J = p(() => {
91
+ let { createdAt: e, completedAt: t } = x.message.metadata ?? {};
92
+ if (!(!e || !t)) return t - e;
93
+ }), Y = (e) => o(G, e), X = w(/* @__PURE__ */ new Map()), Z = (e) => X.get(e.key) ?? !1, re = (e) => {
94
+ X.set(e.key, !Z(e));
95
+ }, Q = T(void 0), $ = p(() => Q.value ?? !!x.isStreaming);
96
+ return A(() => x.isStreaming, (e) => {
97
+ e && (Q.value = void 0, X.clear());
98
+ }), (t, n) => {
99
+ let r = d;
100
+ return q.value.length ? (C(), g("div", ne, [_("button", {
101
+ type: "button",
102
+ class: "pk-chatbot-steps__header",
103
+ "aria-expanded": $.value,
104
+ onClick: n[0] ||= (e) => Q.value = !$.value
105
+ }, [
106
+ b.isStreaming ? (C(), m(r, {
107
+ key: 0,
108
+ name: "line-md:loading-loop",
109
+ class: "shrink-0"
110
+ })) : (C(), m(r, {
111
+ key: 1,
112
+ name: "ri:route-line",
113
+ class: "shrink-0"
114
+ })),
115
+ _("span", P, O(k(W)("messagePart.steps")), 1),
116
+ _("span", F, [v(O(k(W)("messagePart.stepCount", q.value.length)) + " ", 1), Y(J.value) ? (C(), g(f, { key: 0 }, [v(" · " + O(Y(J.value)), 1)], 64)) : h("", !0)]),
117
+ y(r, {
118
+ name: $.value ? "ri:arrow-up-s-line" : "ri:arrow-down-s-line",
119
+ class: "pk-chatbot-steps__chevron shrink-0 ml-auto"
120
+ }, null, 8, ["name"])
121
+ ], 8, N), $.value ? (C(), g("ol", I, [(C(!0), g(f, null, E(q.value, (t) => (C(), g("li", {
122
+ key: t.key,
123
+ class: S(["pk-chatbot-steps__step", { "pk-chatbot-steps__step--active": t.isActive }])
124
+ }, [(C(), m(D(t.hasBody ? "button" : "div"), {
125
+ type: t.hasBody ? "button" : void 0,
126
+ class: "pk-chatbot-steps__step-header",
127
+ "aria-expanded": t.hasBody ? Z(t) : void 0,
128
+ onClick: (e) => t.hasBody ? re(t) : void 0
129
+ }, {
130
+ default: j(() => [
131
+ _("span", { class: S(["pk-chatbot-steps-marker", { "pk-chatbot-steps-marker--active": t.isActive }]) }, [t.isActive ? (C(), m(r, {
132
+ key: 0,
133
+ name: "line-md:loading-loop"
134
+ })) : t.icon ? (C(), m(r, {
135
+ key: 1,
136
+ name: t.icon
137
+ }, null, 8, ["name"])) : h("", !0)], 2),
138
+ _("span", L, O(t.label), 1),
139
+ _("span", R, O(t.summary), 1),
140
+ Y(t.durationMs) ? (C(), g("span", z, O(Y(t.durationMs)), 1)) : h("", !0),
141
+ t.hasBody ? (C(), m(r, {
142
+ key: 1,
143
+ name: Z(t) ? "ri:arrow-up-s-line" : "ri:arrow-down-s-line",
144
+ class: "pk-chatbot-steps__chevron shrink-0 ml-auto"
145
+ }, null, 8, ["name"])) : h("", !0)
146
+ ]),
147
+ _: 2
148
+ }, 1032, [
149
+ "type",
150
+ "aria-expanded",
151
+ "onClick"
152
+ ])), t.hasBody && Z(t) ? (C(), g("div", B, [t.text && t.isActive ? (C(), m(M, {
153
+ key: 0,
154
+ markdown: t.text,
155
+ "is-dark": b.isDark,
156
+ "inner-class": "wysiwyg",
157
+ class: "pk-chatbot-steps__step-content pk-chatbot-steps__step-content--scroll"
158
+ }, null, 8, ["markdown", "is-dark"])) : t.text ? (C(), g("div", V, [y(e, {
159
+ markdown: t.text,
160
+ "is-dark": b.isDark,
161
+ class: "wysiwyg"
162
+ }, null, 8, ["markdown", "is-dark"])])) : t.error ? (C(), g("div", H, [_("span", U, O(k(W)("messagePart.stepError")), 1), _("pre", null, O(t.error), 1)])) : t.hasRichDetail ? (C(), m(u, {
163
+ key: 3,
164
+ part: t.part
165
+ }, null, 8, ["part"])) : (C(), g(f, { key: 4 }, [k(a)(t.toolInput) ? (C(), m(l, {
166
+ key: 0,
167
+ label: k(W)("messagePart.stepInput"),
168
+ value: t.toolInput
169
+ }, null, 8, ["label", "value"])) : h("", !0), k(a)(t.toolOutput) ? (C(), m(l, {
170
+ key: 1,
171
+ label: k(W)("messagePart.stepOutput"),
172
+ value: t.toolOutput
173
+ }, null, 8, ["label", "value"])) : h("", !0)], 64))])) : h("", !0)], 2))), 128))])) : h("", !0)])) : h("", !0);
174
+ };
175
+ }
176
+ });
177
+ //#endregion
178
+ export { M as n, W as t };
179
+
180
+ //# sourceMappingURL=PkChatbotSteps-CPBzs7Gi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PkChatbotSteps-CPBzs7Gi.js","names":[],"sources":["../../../../packages/components/src/chat/PkStreamingMarkdownAutoscroll.vue","../../../../packages/components/src/chat/PkStreamingMarkdownAutoscroll.vue","../../../../packages/components/src/chat/PkChatbotSteps.vue","../../../../packages/components/src/chat/PkChatbotSteps.vue"],"sourcesContent":["<script lang=\"ts\" setup>\n import { nextTick, ref, watch } from 'vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n\n const props = defineProps<{\n markdown?: string\n innerClass?: string\n isDark?: boolean\n }>()\n\n const scrollEl = ref<HTMLDivElement>()\n const lastScrollTop = ref<number>(0)\n const stopAutoScroll = ref<boolean>(false)\n\n const handleScroll = () => {\n if (!scrollEl.value) {\n return\n }\n const currentScrollTop = scrollEl.value.scrollTop\n if (currentScrollTop < lastScrollTop.value) {\n const delta = lastScrollTop.value - currentScrollTop\n if (delta < 50) {\n stopAutoScroll.value = true\n }\n }\n if (currentScrollTop > lastScrollTop.value) {\n if (\n currentScrollTop ===\n scrollEl.value.scrollHeight - scrollEl.value.clientHeight\n ) {\n stopAutoScroll.value = false\n }\n }\n lastScrollTop.value = currentScrollTop\n }\n\n watch(\n () => props.markdown,\n async () => {\n await nextTick()\n if (scrollEl.value && !stopAutoScroll.value) {\n scrollEl.value.scrollTop = scrollEl.value.scrollHeight\n }\n },\n { immediate: true },\n )\n</script>\n\n<template>\n <div\n ref=\"scrollEl\"\n class=\"overflow-auto light-scrollbar\"\n @scroll=\"handleScroll\">\n <PkStreamingMarkdown :markdown :is-dark :class=\"innerClass\" />\n </div>\n</template>\n","<script lang=\"ts\" setup>\n import { nextTick, ref, watch } from 'vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n\n const props = defineProps<{\n markdown?: string\n innerClass?: string\n isDark?: boolean\n }>()\n\n const scrollEl = ref<HTMLDivElement>()\n const lastScrollTop = ref<number>(0)\n const stopAutoScroll = ref<boolean>(false)\n\n const handleScroll = () => {\n if (!scrollEl.value) {\n return\n }\n const currentScrollTop = scrollEl.value.scrollTop\n if (currentScrollTop < lastScrollTop.value) {\n const delta = lastScrollTop.value - currentScrollTop\n if (delta < 50) {\n stopAutoScroll.value = true\n }\n }\n if (currentScrollTop > lastScrollTop.value) {\n if (\n currentScrollTop ===\n scrollEl.value.scrollHeight - scrollEl.value.clientHeight\n ) {\n stopAutoScroll.value = false\n }\n }\n lastScrollTop.value = currentScrollTop\n }\n\n watch(\n () => props.markdown,\n async () => {\n await nextTick()\n if (scrollEl.value && !stopAutoScroll.value) {\n scrollEl.value.scrollTop = scrollEl.value.scrollHeight\n }\n },\n { immediate: true },\n )\n</script>\n\n<template>\n <div\n ref=\"scrollEl\"\n class=\"overflow-auto light-scrollbar\"\n @scroll=\"handleScroll\">\n <PkStreamingMarkdown :markdown :is-dark :class=\"innerClass\" />\n </div>\n</template>\n","<script lang=\"ts\" setup>\n import { computed, reactive, ref, watch } from 'vue'\n import { useI18n } from 'vue-i18n'\n import type { UIChatMessage } from 'models'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkStreamingMarkdownAutoscroll from './PkStreamingMarkdownAutoscroll.vue'\n import PkChatbotStepToolDetail from './PkChatbotStepToolDetail.vue'\n import PkChatbotStepPayload from './PkChatbotStepPayload.vue'\n import {\n isReasoningPart,\n isToolPart,\n getPartIcon,\n getPartState,\n getToolPartLabel,\n getToolPartSummary,\n hasRichToolDetail,\n hasPayload,\n formatDuration as formatDurationWith,\n } from './utils'\n\n const props = defineProps<{\n message: UIChatMessage\n isStreaming?: boolean\n isDark?: boolean\n }>()\n\n const { t: $t, n: $n } = useI18n({\n useScope: 'global',\n })\n\n type MessagePart = UIChatMessage['parts'][number]\n\n interface Step {\n key: string\n part: MessagePart\n icon?: string\n label?: string\n summary?: string\n durationMs?: number\n isActive: boolean\n text?: string\n error?: string\n hasRichDetail: boolean\n hasBody: boolean\n toolInput?: unknown\n toolOutput?: unknown\n }\n\n const getStepError = (part: MessagePart) =>\n getPartState(part) === 'output-error' &&\n 'errorText' in part &&\n typeof part.errorText === 'string'\n ? part.errorText\n : undefined\n\n // What the step header needs is decided here, once per recomputation.\n // `hasBody` was a function the template called six times per step, and\n // `steps` recomputes at every delta of the stream: on a tool answering\n // hundreds of kilobytes, where the answer comes from `hasPayload` and\n // `hasPayload` serializes the whole payload to find out, that is the same\n // JSON.stringify six times for one boolean.\n //\n // The two payload blocks keep asking for themselves, because they render\n // only inside an expanded step: deciding here whether to show the output\n // would serialize it for every step the reader never opens, which is all\n // of them by default.\n const steps = computed<Step[]>(() => {\n const parts = props.message.parts\n const partTimings = props.message.metadata?.partTimings\n const result: Step[] = []\n for (const [index, part] of parts.entries()) {\n const isReasoning = isReasoningPart(part)\n const isTool = isToolPart(part)\n if (!isReasoning && !isTool) {\n continue\n }\n const toolCallId =\n 'toolCallId' in part ? (part.toolCallId as string) : undefined\n const text = isReasoning && part.text.trim() ? part.text : undefined\n const error = getStepError(part)\n const hasRichDetail = hasRichToolDetail(part)\n const toolInput = isTool && 'input' in part ? part.input : undefined\n const toolOutput =\n isTool && 'output' in part ? part.output : undefined\n result.push({\n key: toolCallId ?? `${part.type}-${index}`,\n part,\n icon: getPartIcon(part),\n label: getToolPartLabel($t, part),\n summary: getToolPartSummary(part),\n durationMs: toolCallId ? partTimings?.[toolCallId] : undefined,\n isActive:\n Boolean(props.isStreaming) && index === parts.length - 1,\n text,\n error,\n hasRichDetail,\n // The `||` short-circuits as the old function did: a tool\n // with an input never serializes its output to answer this.\n hasBody: Boolean(\n text ||\n error ||\n (hasRichDetail\n ? toolInput !== undefined || toolOutput !== undefined\n : hasPayload(toolInput) || hasPayload(toolOutput)),\n ),\n toolInput,\n toolOutput,\n })\n }\n return result\n })\n\n const totalDurationMs = computed(() => {\n const { createdAt, completedAt } = props.message.metadata ?? {}\n if (!createdAt || !completedAt) {\n return undefined\n }\n return completedAt - createdAt\n })\n\n const formatDuration = (ms?: number) => formatDurationWith($n, ms)\n\n // --- Expand / collapse ---\n // Every step starts closed, the running one included: the timeline says\n // what the answer is doing, the body is opened only when asked for. The\n // reasoning of the active step still follows the stream once opened.\n const manualSteps = reactive(new Map<string, boolean>())\n const isStepExpanded = (step: Step) => manualSteps.get(step.key) ?? false\n const toggleStep = (step: Step) => {\n manualSteps.set(step.key, !isStepExpanded(step))\n }\n\n // The group is open while streaming and collapses to its header once the\n // answer is complete, unless the user toggled it manually.\n const manualGroup = ref<boolean | undefined>(undefined)\n const isGroupExpanded = computed(\n () => manualGroup.value ?? Boolean(props.isStreaming),\n )\n // Reset the manual override when a new stream starts so the timeline\n // opens again on regenerate.\n watch(\n () => props.isStreaming,\n (streaming) => {\n if (streaming) {\n manualGroup.value = undefined\n manualSteps.clear()\n }\n },\n )\n</script>\n\n<template>\n <div v-if=\"steps.length\" class=\"pk-chatbot-steps\">\n <button\n type=\"button\"\n class=\"pk-chatbot-steps__header\"\n :aria-expanded=\"isGroupExpanded\"\n @click=\"manualGroup = !isGroupExpanded\">\n <VvIcon\n v-if=\"isStreaming\"\n name=\"line-md:loading-loop\"\n class=\"shrink-0\" />\n <VvIcon v-else name=\"ri:route-line\" class=\"shrink-0\" />\n <span class=\"pk-chatbot-steps__title\">\n {{ $t('messagePart.steps') }}\n </span>\n <span class=\"pk-chatbot-steps__meta\">\n {{ $t('messagePart.stepCount', steps.length) }}\n <template v-if=\"formatDuration(totalDurationMs)\">\n · {{ formatDuration(totalDurationMs) }}\n </template>\n </span>\n <VvIcon\n :name=\"\n isGroupExpanded\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"pk-chatbot-steps__chevron shrink-0 ml-auto\" />\n </button>\n <ol v-if=\"isGroupExpanded\" class=\"pk-chatbot-steps__list\">\n <li\n v-for=\"step in steps\"\n :key=\"step.key\"\n class=\"pk-chatbot-steps__step\"\n :class=\"{\n 'pk-chatbot-steps__step--active': step.isActive,\n }\">\n <component\n :is=\"step.hasBody ? 'button' : 'div'\"\n :type=\"step.hasBody ? 'button' : undefined\"\n class=\"pk-chatbot-steps__step-header\"\n :aria-expanded=\"\n step.hasBody ? isStepExpanded(step) : undefined\n \"\n @click=\"step.hasBody ? toggleStep(step) : undefined\">\n <span\n class=\"pk-chatbot-steps-marker\"\n :class=\"{\n 'pk-chatbot-steps-marker--active': step.isActive,\n }\">\n <VvIcon\n v-if=\"step.isActive\"\n name=\"line-md:loading-loop\" />\n <VvIcon v-else-if=\"step.icon\" :name=\"step.icon\" />\n </span>\n <span class=\"pk-chatbot-steps__step-label\">\n {{ step.label }}\n </span>\n <span class=\"pk-chatbot-steps__step-summary\">\n {{ step.summary }}\n </span>\n <span\n v-if=\"formatDuration(step.durationMs)\"\n class=\"pk-chatbot-steps__step-duration\">\n {{ formatDuration(step.durationMs) }}\n </span>\n <VvIcon\n v-if=\"step.hasBody\"\n :name=\"\n isStepExpanded(step)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"pk-chatbot-steps__chevron shrink-0 ml-auto\" />\n </component>\n <div\n v-if=\"step.hasBody && isStepExpanded(step)\"\n class=\"pk-chatbot-steps__step-body\">\n <PkStreamingMarkdownAutoscroll\n v-if=\"step.text && step.isActive\"\n :markdown=\"step.text\"\n :is-dark\n inner-class=\"wysiwyg\"\n class=\"pk-chatbot-steps__step-content pk-chatbot-steps__step-content--scroll\" />\n <div\n v-else-if=\"step.text\"\n class=\"pk-chatbot-steps__step-content pk-chatbot-steps__step-content--scroll light-scrollbar\">\n <PkStreamingMarkdown\n :markdown=\"step.text\"\n :is-dark\n class=\"wysiwyg\" />\n </div>\n <div\n v-else-if=\"step.error\"\n class=\"pk-chatbot-steps__step-payload pk-chatbot-steps__step-payload--error\">\n <span class=\"pk-chatbot-steps__step-payload-label\">\n {{ $t('messagePart.stepError') }}\n </span>\n <pre>{{ step.error }}</pre>\n </div>\n <PkChatbotStepToolDetail\n v-else-if=\"step.hasRichDetail\"\n :part=\"step.part\" />\n <template v-else>\n <PkChatbotStepPayload\n v-if=\"hasPayload(step.toolInput)\"\n :label=\"$t('messagePart.stepInput')\"\n :value=\"step.toolInput\" />\n <PkChatbotStepPayload\n v-if=\"hasPayload(step.toolOutput)\"\n :label=\"$t('messagePart.stepOutput')\"\n :value=\"step.toolOutput\" />\n </template>\n </div>\n </li>\n </ol>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-steps {\n width: 100%;\n border: var(--spacing-px) solid var(--color-surface-3);\n border-radius: var(--rounded-lg);\n background-color: var(--color-surface-1);\n font-size: var(--text-12);\n color: var(--color-word-3);\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-40);\n padding: var(--spacing-8) var(--spacing-sm);\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 &__chevron {\n font-size: var(--text-16);\n }\n\n &__title {\n font-weight: 600;\n color: var(--color-word-2);\n flex-grow: 1;\n }\n\n &__meta {\n color: var(--color-word-4);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n\n &__list {\n background-color: var(--color-surface);\n border-top: var(--spacing-px) solid var(--color-surface-3);\n padding: var(--spacing-8) var(--spacing-sm);\n display: flex;\n flex-direction: column;\n }\n\n &__step {\n position: relative;\n\n // Vertical connector between step markers\n &:not(:last-child)::before {\n content: '';\n position: absolute;\n left: var(--spacing-10);\n top: var(--spacing-28);\n bottom: calc(var(--spacing-8) * -1);\n width: var(--spacing-px);\n background-color: var(--color-surface-4);\n }\n }\n\n &__step-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n width: calc(100% + var(--spacing-16));\n min-height: var(--spacing-36);\n margin-inline: calc(var(--spacing-8) * -1);\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n\n &:is(button) {\n cursor: pointer;\n border-radius: var(--rounded);\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n &-marker {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--spacing-20);\n height: var(--spacing-20);\n flex-shrink: 0;\n position: relative;\n z-index: 1;\n background-color: var(--color-surface-1);\n border-radius: var(--rounded-full);\n border: var(--spacing-px) solid var(--color-surface-4);\n\n &--active {\n background-color: var(\n --chatbot-main-color,\n var(--color-surface-4)\n );\n border-color: transparent;\n color: var(--chatbot-contrast-color, var(--color-word-1));\n }\n }\n\n &__step-label {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-word-2);\n }\n\n &__step-summary {\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-4);\n }\n\n &__step-duration {\n color: var(--color-word-4);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n\n &__step--active &__step-label {\n color: var(--color-word-1);\n }\n\n &__step-body {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-4);\n margin-left: var(--spacing-28);\n margin-block: var(--spacing-4) var(--spacing-8);\n }\n\n &__step-content {\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded);\n padding: var(--spacing-8);\n background-color: var(--color-surface-1);\n\n // The finished reasoning keeps the cap the streaming one had:\n // `.wysiwyg` hides its own overflow, so the scroll belongs to the\n // wrapper, exactly as in the autoscroll variant.\n &--scroll {\n max-height: var(--spacing-256);\n overflow: auto;\n }\n }\n\n &__step-payload {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2);\n\n pre {\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded);\n padding: var(--spacing-8);\n font-size: var(--text-10);\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 background-color: var(--color-surface-1);\n }\n }\n\n &__step-payload-label {\n font-weight: 600;\n color: var(--color-word-3);\n text-transform: uppercase;\n font-size: var(--text-10);\n letter-spacing: 0.05em;\n }\n\n &__step-payload--error {\n .pk-chatbot-steps__step-payload-label,\n pre {\n color: var(--color-danger);\n }\n\n pre {\n border-color: var(--color-danger-lighten-5);\n }\n }\n }\n</style>\n","<script lang=\"ts\" setup>\n import { computed, reactive, ref, watch } from 'vue'\n import { useI18n } from 'vue-i18n'\n import type { UIChatMessage } from 'models'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkStreamingMarkdownAutoscroll from './PkStreamingMarkdownAutoscroll.vue'\n import PkChatbotStepToolDetail from './PkChatbotStepToolDetail.vue'\n import PkChatbotStepPayload from './PkChatbotStepPayload.vue'\n import {\n isReasoningPart,\n isToolPart,\n getPartIcon,\n getPartState,\n getToolPartLabel,\n getToolPartSummary,\n hasRichToolDetail,\n hasPayload,\n formatDuration as formatDurationWith,\n } from './utils'\n\n const props = defineProps<{\n message: UIChatMessage\n isStreaming?: boolean\n isDark?: boolean\n }>()\n\n const { t: $t, n: $n } = useI18n({\n useScope: 'global',\n })\n\n type MessagePart = UIChatMessage['parts'][number]\n\n interface Step {\n key: string\n part: MessagePart\n icon?: string\n label?: string\n summary?: string\n durationMs?: number\n isActive: boolean\n text?: string\n error?: string\n hasRichDetail: boolean\n hasBody: boolean\n toolInput?: unknown\n toolOutput?: unknown\n }\n\n const getStepError = (part: MessagePart) =>\n getPartState(part) === 'output-error' &&\n 'errorText' in part &&\n typeof part.errorText === 'string'\n ? part.errorText\n : undefined\n\n // What the step header needs is decided here, once per recomputation.\n // `hasBody` was a function the template called six times per step, and\n // `steps` recomputes at every delta of the stream: on a tool answering\n // hundreds of kilobytes, where the answer comes from `hasPayload` and\n // `hasPayload` serializes the whole payload to find out, that is the same\n // JSON.stringify six times for one boolean.\n //\n // The two payload blocks keep asking for themselves, because they render\n // only inside an expanded step: deciding here whether to show the output\n // would serialize it for every step the reader never opens, which is all\n // of them by default.\n const steps = computed<Step[]>(() => {\n const parts = props.message.parts\n const partTimings = props.message.metadata?.partTimings\n const result: Step[] = []\n for (const [index, part] of parts.entries()) {\n const isReasoning = isReasoningPart(part)\n const isTool = isToolPart(part)\n if (!isReasoning && !isTool) {\n continue\n }\n const toolCallId =\n 'toolCallId' in part ? (part.toolCallId as string) : undefined\n const text = isReasoning && part.text.trim() ? part.text : undefined\n const error = getStepError(part)\n const hasRichDetail = hasRichToolDetail(part)\n const toolInput = isTool && 'input' in part ? part.input : undefined\n const toolOutput =\n isTool && 'output' in part ? part.output : undefined\n result.push({\n key: toolCallId ?? `${part.type}-${index}`,\n part,\n icon: getPartIcon(part),\n label: getToolPartLabel($t, part),\n summary: getToolPartSummary(part),\n durationMs: toolCallId ? partTimings?.[toolCallId] : undefined,\n isActive:\n Boolean(props.isStreaming) && index === parts.length - 1,\n text,\n error,\n hasRichDetail,\n // The `||` short-circuits as the old function did: a tool\n // with an input never serializes its output to answer this.\n hasBody: Boolean(\n text ||\n error ||\n (hasRichDetail\n ? toolInput !== undefined || toolOutput !== undefined\n : hasPayload(toolInput) || hasPayload(toolOutput)),\n ),\n toolInput,\n toolOutput,\n })\n }\n return result\n })\n\n const totalDurationMs = computed(() => {\n const { createdAt, completedAt } = props.message.metadata ?? {}\n if (!createdAt || !completedAt) {\n return undefined\n }\n return completedAt - createdAt\n })\n\n const formatDuration = (ms?: number) => formatDurationWith($n, ms)\n\n // --- Expand / collapse ---\n // Every step starts closed, the running one included: the timeline says\n // what the answer is doing, the body is opened only when asked for. The\n // reasoning of the active step still follows the stream once opened.\n const manualSteps = reactive(new Map<string, boolean>())\n const isStepExpanded = (step: Step) => manualSteps.get(step.key) ?? false\n const toggleStep = (step: Step) => {\n manualSteps.set(step.key, !isStepExpanded(step))\n }\n\n // The group is open while streaming and collapses to its header once the\n // answer is complete, unless the user toggled it manually.\n const manualGroup = ref<boolean | undefined>(undefined)\n const isGroupExpanded = computed(\n () => manualGroup.value ?? Boolean(props.isStreaming),\n )\n // Reset the manual override when a new stream starts so the timeline\n // opens again on regenerate.\n watch(\n () => props.isStreaming,\n (streaming) => {\n if (streaming) {\n manualGroup.value = undefined\n manualSteps.clear()\n }\n },\n )\n</script>\n\n<template>\n <div v-if=\"steps.length\" class=\"pk-chatbot-steps\">\n <button\n type=\"button\"\n class=\"pk-chatbot-steps__header\"\n :aria-expanded=\"isGroupExpanded\"\n @click=\"manualGroup = !isGroupExpanded\">\n <VvIcon\n v-if=\"isStreaming\"\n name=\"line-md:loading-loop\"\n class=\"shrink-0\" />\n <VvIcon v-else name=\"ri:route-line\" class=\"shrink-0\" />\n <span class=\"pk-chatbot-steps__title\">\n {{ $t('messagePart.steps') }}\n </span>\n <span class=\"pk-chatbot-steps__meta\">\n {{ $t('messagePart.stepCount', steps.length) }}\n <template v-if=\"formatDuration(totalDurationMs)\">\n · {{ formatDuration(totalDurationMs) }}\n </template>\n </span>\n <VvIcon\n :name=\"\n isGroupExpanded\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"pk-chatbot-steps__chevron shrink-0 ml-auto\" />\n </button>\n <ol v-if=\"isGroupExpanded\" class=\"pk-chatbot-steps__list\">\n <li\n v-for=\"step in steps\"\n :key=\"step.key\"\n class=\"pk-chatbot-steps__step\"\n :class=\"{\n 'pk-chatbot-steps__step--active': step.isActive,\n }\">\n <component\n :is=\"step.hasBody ? 'button' : 'div'\"\n :type=\"step.hasBody ? 'button' : undefined\"\n class=\"pk-chatbot-steps__step-header\"\n :aria-expanded=\"\n step.hasBody ? isStepExpanded(step) : undefined\n \"\n @click=\"step.hasBody ? toggleStep(step) : undefined\">\n <span\n class=\"pk-chatbot-steps-marker\"\n :class=\"{\n 'pk-chatbot-steps-marker--active': step.isActive,\n }\">\n <VvIcon\n v-if=\"step.isActive\"\n name=\"line-md:loading-loop\" />\n <VvIcon v-else-if=\"step.icon\" :name=\"step.icon\" />\n </span>\n <span class=\"pk-chatbot-steps__step-label\">\n {{ step.label }}\n </span>\n <span class=\"pk-chatbot-steps__step-summary\">\n {{ step.summary }}\n </span>\n <span\n v-if=\"formatDuration(step.durationMs)\"\n class=\"pk-chatbot-steps__step-duration\">\n {{ formatDuration(step.durationMs) }}\n </span>\n <VvIcon\n v-if=\"step.hasBody\"\n :name=\"\n isStepExpanded(step)\n ? 'ri:arrow-up-s-line'\n : 'ri:arrow-down-s-line'\n \"\n class=\"pk-chatbot-steps__chevron shrink-0 ml-auto\" />\n </component>\n <div\n v-if=\"step.hasBody && isStepExpanded(step)\"\n class=\"pk-chatbot-steps__step-body\">\n <PkStreamingMarkdownAutoscroll\n v-if=\"step.text && step.isActive\"\n :markdown=\"step.text\"\n :is-dark\n inner-class=\"wysiwyg\"\n class=\"pk-chatbot-steps__step-content pk-chatbot-steps__step-content--scroll\" />\n <div\n v-else-if=\"step.text\"\n class=\"pk-chatbot-steps__step-content pk-chatbot-steps__step-content--scroll light-scrollbar\">\n <PkStreamingMarkdown\n :markdown=\"step.text\"\n :is-dark\n class=\"wysiwyg\" />\n </div>\n <div\n v-else-if=\"step.error\"\n class=\"pk-chatbot-steps__step-payload pk-chatbot-steps__step-payload--error\">\n <span class=\"pk-chatbot-steps__step-payload-label\">\n {{ $t('messagePart.stepError') }}\n </span>\n <pre>{{ step.error }}</pre>\n </div>\n <PkChatbotStepToolDetail\n v-else-if=\"step.hasRichDetail\"\n :part=\"step.part\" />\n <template v-else>\n <PkChatbotStepPayload\n v-if=\"hasPayload(step.toolInput)\"\n :label=\"$t('messagePart.stepInput')\"\n :value=\"step.toolInput\" />\n <PkChatbotStepPayload\n v-if=\"hasPayload(step.toolOutput)\"\n :label=\"$t('messagePart.stepOutput')\"\n :value=\"step.toolOutput\" />\n </template>\n </div>\n </li>\n </ol>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-steps {\n width: 100%;\n border: var(--spacing-px) solid var(--color-surface-3);\n border-radius: var(--rounded-lg);\n background-color: var(--color-surface-1);\n font-size: var(--text-12);\n color: var(--color-word-3);\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-40);\n padding: var(--spacing-8) var(--spacing-sm);\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 &__chevron {\n font-size: var(--text-16);\n }\n\n &__title {\n font-weight: 600;\n color: var(--color-word-2);\n flex-grow: 1;\n }\n\n &__meta {\n color: var(--color-word-4);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n\n &__list {\n background-color: var(--color-surface);\n border-top: var(--spacing-px) solid var(--color-surface-3);\n padding: var(--spacing-8) var(--spacing-sm);\n display: flex;\n flex-direction: column;\n }\n\n &__step {\n position: relative;\n\n // Vertical connector between step markers\n &:not(:last-child)::before {\n content: '';\n position: absolute;\n left: var(--spacing-10);\n top: var(--spacing-28);\n bottom: calc(var(--spacing-8) * -1);\n width: var(--spacing-px);\n background-color: var(--color-surface-4);\n }\n }\n\n &__step-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-8);\n width: calc(100% + var(--spacing-16));\n min-height: var(--spacing-36);\n margin-inline: calc(var(--spacing-8) * -1);\n padding: var(--spacing-4) var(--spacing-8);\n text-align: left;\n\n &:is(button) {\n cursor: pointer;\n border-radius: var(--rounded);\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n &-marker {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--spacing-20);\n height: var(--spacing-20);\n flex-shrink: 0;\n position: relative;\n z-index: 1;\n background-color: var(--color-surface-1);\n border-radius: var(--rounded-full);\n border: var(--spacing-px) solid var(--color-surface-4);\n\n &--active {\n background-color: var(\n --chatbot-main-color,\n var(--color-surface-4)\n );\n border-color: transparent;\n color: var(--chatbot-contrast-color, var(--color-word-1));\n }\n }\n\n &__step-label {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-word-2);\n }\n\n &__step-summary {\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-4);\n }\n\n &__step-duration {\n color: var(--color-word-4);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n\n &__step--active &__step-label {\n color: var(--color-word-1);\n }\n\n &__step-body {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-4);\n margin-left: var(--spacing-28);\n margin-block: var(--spacing-4) var(--spacing-8);\n }\n\n &__step-content {\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded);\n padding: var(--spacing-8);\n background-color: var(--color-surface-1);\n\n // The finished reasoning keeps the cap the streaming one had:\n // `.wysiwyg` hides its own overflow, so the scroll belongs to the\n // wrapper, exactly as in the autoscroll variant.\n &--scroll {\n max-height: var(--spacing-256);\n overflow: auto;\n }\n }\n\n &__step-payload {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-2);\n\n pre {\n border: var(--spacing-px) solid var(--color-surface-4);\n border-radius: var(--rounded);\n padding: var(--spacing-8);\n font-size: var(--text-10);\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 background-color: var(--color-surface-1);\n }\n }\n\n &__step-payload-label {\n font-weight: 600;\n color: var(--color-word-3);\n text-transform: uppercase;\n font-size: var(--text-10);\n letter-spacing: 0.05em;\n }\n\n &__step-payload--error {\n .pk-chatbot-steps__step-payload-label,\n pre {\n color: var(--color-danger);\n }\n\n pre {\n border-color: var(--color-danger-lighten-5);\n }\n }\n }\n</style>\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
- import { a as e, n as t, t as n } from "./useChatbotStore-Rj18hAUf.js";
1
+ import { a as e, n as t, t as n } from "./useChatbotStore-Chtzi5jc.js";
2
2
  import { t as r } from "./src-C2w8W_tC.js";
3
- import { t as i } from "./PkChatbotMessages-DUQxuYaG.js";
3
+ import { t as i } from "./PkChatbotMessages-tHQ2JQrb.js";
4
4
  import { t as a } from "./useLocalizedString-PfaKjIQY.js";
5
5
  import { t as o } from "./PkAvatar-paEJByEA.js";
6
- import { r as s } from "./utils-B0jsOUqd.js";
6
+ import { r as s } from "./utils-DKNWwMTQ.js";
7
7
  import { n as c, t as l } from "./PkChatbotInput-Cnqm3mnZ.js";
8
8
  import { t as u } from "./PkChatbotEmptyState-j7ubd6rk.js";
9
9
  import { VvIcon as d } from "@volverjs/ui-vue/components";
@@ -288,7 +288,7 @@ var P = {
288
288
  a.value = void 0, o.value = [], i.value = "listening", c.value = void 0, n.value = "connecting", d += 1;
289
289
  let r = d, s = () => r === d && !f, u;
290
290
  try {
291
- let r = import("./web-DnPxEADy.js");
291
+ let r = import("./web-CzR5jWfC.js");
292
292
  r.catch(() => void 0);
293
293
  let d = await e.fetchGrant();
294
294
  if (!s()) {
@@ -408,7 +408,7 @@ var P = {
408
408
  },
409
409
  emits: ["show-info", "revise"],
410
410
  setup(r, { emit: c }) {
411
- let f = S(() => import("./PkToolShowForm-D2n2rA6J.js").then((e) => e.n)), b = S(() => import("./PkToolShowContactForm-DaDVmo24.js").then((e) => e.n)), C = S(() => import("./PkToolShowSuggestedReply-Dm28RIJF.js").then((e) => e.n)), ee = S(() => import("./PkToolShowSources-DUAsrrzC.js").then((e) => e.n)), ne = S(() => import("./PkToolRequestGeolocation-BdX5DzEN.js")), re = S(() => import("./PkToolRequestOAuthConnection-BUmnRgMi.js")), ie = S(() => import("./PkToolShowLocation-BHHR5EC5.js").then((e) => e.n)), ae = S(() => import("./PkToolShowDiagram-YMo-iVWr.js")), O = r, A = c, N = t(O.agentId), { agentInterface: P, agentFileUpload: F, agentVoice: fe, actions: pe, revisedAnswers: I, messages: he, chat: L, messageFeedbacks: ge, feedbackDialogMessage: _e, isFeedbackSubmitting: ve, isFeedbackSubmitted: ye, feedbackSubmitError: be, isLeadSubmitted: xe, isLoadingSubmitLead: Ne, submitLeadError: Pe, input: R, inputMessagePlaceholder: Fe, isConversationBlocked: Ie, baseUrl: Le, pendingAttachments: Re, isAttachmentLimitReached: ze, fileLimitError: Be, apiClient: z, isDark: Ve } = ue(N), { handleSubmit: He, stopGeneration: Ue, regenerate: We, onUpvote: Ge, onDownvote: Ke, onFeedback: qe, onFeedbackSubmit: Je, onLeadSubmit: Ye, startNewChat: Xe, addToolOutput: B, addToolApprovalResponse: Ze, handleFileSelect: Qe, clearFileLimitError: $e, refreshMessages: et, appendMessage: tt } = N, { t: V } = ce({ useScope: "global" }), nt = m(() => {
411
+ let f = S(() => import("./PkToolShowForm-6eLi6Wr0.js").then((e) => e.n)), b = S(() => import("./PkToolShowContactForm-DaDVmo24.js").then((e) => e.n)), C = S(() => import("./PkToolShowSuggestedReply-D9RY2h6Z.js").then((e) => e.n)), ee = S(() => import("./PkToolShowSources-DTJgvDiB.js").then((e) => e.n)), ne = S(() => import("./PkToolRequestGeolocation-BdIyXoPQ.js")), re = S(() => import("./PkToolRequestOAuthConnection-Dnch7xAB.js")), ie = S(() => import("./PkToolShowLocation-BHHR5EC5.js").then((e) => e.n)), ae = S(() => import("./PkToolShowDiagram-DgLxNNp7.js")), O = r, A = c, N = t(O.agentId), { agentInterface: P, agentFileUpload: F, agentVoice: fe, actions: pe, revisedAnswers: I, messages: he, chat: L, messageFeedbacks: ge, feedbackDialogMessage: _e, isFeedbackSubmitting: ve, isFeedbackSubmitted: ye, feedbackSubmitError: be, isLeadSubmitted: xe, isLoadingSubmitLead: Ne, submitLeadError: Pe, input: R, inputMessagePlaceholder: Fe, isConversationBlocked: Ie, baseUrl: Le, pendingAttachments: Re, isAttachmentLimitReached: ze, fileLimitError: Be, apiClient: z, isDark: Ve } = ue(N), { handleSubmit: He, stopGeneration: Ue, regenerate: We, onUpvote: Ge, onDownvote: Ke, onFeedback: qe, onFeedbackSubmit: Je, onLeadSubmit: Ye, startNewChat: Xe, addToolOutput: B, addToolApprovalResponse: Ze, handleFileSelect: Qe, clearFileLimitError: $e, refreshMessages: et, appendMessage: tt } = N, { t: V } = ce({ useScope: "global" }), nt = m(() => {
412
412
  let e = Be.value;
413
413
  if (e) return e.reason === "per-chat" ? V("message.fileLimitPerChat", { max: e.max }) : V("message.fileLimitPerMessage", { max: e.max });
414
414
  }), rt = a(() => P.value?.dismissableNotice), it = m(() => he.value.some((e) => e.role === "user")), at = m(() => O.modifier === "fullscreen" && !it.value), ot = de(P, () => O.userName), st = oe("chatViewEl"), ct = async (e) => (await z.value.expandSourceContext(O.agentId, e.documentId, e.chunkIndex)).content, lt = async (e) => {
@@ -799,4 +799,4 @@ var P = {
799
799
  //#endregion
800
800
  export { me as i, Se as n, L as r, Ne as t };
801
801
 
802
- //# sourceMappingURL=PkChatbotViewChat-YwjWNoqf.js.map
802
+ //# sourceMappingURL=PkChatbotViewChat-CdiPjQLq.js.map