@8wave/ai-elements 0.114.0 → 0.115.0-beta.2

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 (20) hide show
  1. package/dist/_chunks/{PkToolRequestOAuthConnection-ChGMAKV7.js → PkToolRequestOAuthConnection-CgQePKKb.js} +3 -3
  2. package/dist/_chunks/PkToolRequestOAuthConnection-CgQePKKb.js.map +1 -0
  3. package/dist/ai-elements.es.js +2 -2
  4. package/dist-vue/PkChatbot.js +1 -1
  5. package/dist-vue/PkChatbotMessages.js +1 -1
  6. package/dist-vue/PkChatbotViewChat.js +1 -1
  7. package/dist-vue/_chunks/{PkChatbot-BMIs94QB.js → PkChatbot-CdtYJ1Lf.js} +2 -2
  8. package/dist-vue/_chunks/{PkChatbot-BMIs94QB.js.map → PkChatbot-CdtYJ1Lf.js.map} +1 -1
  9. package/dist-vue/_chunks/{PkChatbotMessages-Bj7mjhqN.js → PkChatbotMessages-R1EsTJpM.js} +3 -3
  10. package/dist-vue/_chunks/{PkChatbotMessages-Bj7mjhqN.js.map → PkChatbotMessages-R1EsTJpM.js.map} +1 -1
  11. package/dist-vue/_chunks/{PkChatbotViewChat-CQ5WcZs8.js → PkChatbotViewChat-CbqrHEC6.js} +3 -3
  12. package/dist-vue/_chunks/{PkChatbotViewChat-CQ5WcZs8.js.map → PkChatbotViewChat-CbqrHEC6.js.map} +1 -1
  13. package/dist-vue/_chunks/{PkToolRequestOAuthConnection-DMvCGPuX.js → PkToolRequestOAuthConnection-a2LegFoE.js} +3 -3
  14. package/dist-vue/_chunks/PkToolRequestOAuthConnection-a2LegFoE.js.map +1 -0
  15. package/dist-vue/_chunks/{PkToolShowDiagram-BziggKVa.js → PkToolShowDiagram-Dmtqu3th.js} +2 -2
  16. package/dist-vue/_chunks/{PkToolShowDiagram-BziggKVa.js.map → PkToolShowDiagram-Dmtqu3th.js.map} +1 -1
  17. package/dist-vue/index.js +3 -3
  18. package/package.json +1 -1
  19. package/dist/_chunks/PkToolRequestOAuthConnection-ChGMAKV7.js.map +0 -1
  20. package/dist-vue/_chunks/PkToolRequestOAuthConnection-DMvCGPuX.js.map +0 -1
@@ -93,7 +93,7 @@ var x = { class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-s
93
93
  }), (e, t) => {
94
94
  let n = b, i = y;
95
95
  return r(), m("div", x, [u("div", S, [h(n, {
96
- name: "ri:link-m",
96
+ name: "ri:link",
97
97
  class: "text-16 shrink-0"
98
98
  }), u("strong", C, a(I.value), 1)]), u("div", ee, [W.value ? (r(), m("div", w, [h(n, {
99
99
  name: "ri:check-line",
@@ -112,11 +112,11 @@ var x = { class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-s
112
112
  icon: "ri:external-link-line",
113
113
  label: s(N)("messagePart.requestOAuthConnectionButton", { name: I.value }),
114
114
  onClick: $
115
- }, null, 8, ["label"])], 64)) : (r(), m("div", k, [h(n, { name: "ri:link-m" }), u("span", A, a(s(N)("messagePart.requestOAuthConnectionInfo", { name: I.value })), 1)]))])]);
115
+ }, null, 8, ["label"])], 64)) : (r(), m("div", k, [h(n, { name: "ri:link" }), u("span", A, a(s(N)("messagePart.requestOAuthConnectionInfo", { name: I.value })), 1)]))])]);
116
116
  };
117
117
  }
118
118
  });
119
119
  //#endregion
120
120
  export { N as default };
121
121
 
122
- //# sourceMappingURL=PkToolRequestOAuthConnection-ChGMAKV7.js.map
122
+ //# sourceMappingURL=PkToolRequestOAuthConnection-CgQePKKb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PkToolRequestOAuthConnection-CgQePKKb.js","names":[],"sources":["../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue","../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -11345,7 +11345,7 @@ var Vp = ["datetime"], Hp = 6e4, Up = /* @__PURE__ */ C({
11345
11345
  //#region ../../packages/components/src/chat/toolComponentMap.ts
11346
11346
  k();
11347
11347
  var Wp = {
11348
- requestOAuthConnection: M(() => import("./_chunks/PkToolRequestOAuthConnection-ChGMAKV7.js")),
11348
+ requestOAuthConnection: M(() => import("./_chunks/PkToolRequestOAuthConnection-CgQePKKb.js")),
11349
11349
  showArtifact: M(() => import("./_chunks/PkToolShowArtifact-CHxeuKEU.js")),
11350
11350
  showCalendarEvent: M(() => import("./_chunks/PkToolShowCalendarEvent-D0jVbqWt.js")),
11351
11351
  showComparison: M(() => import("./_chunks/PkToolShowComparison-8d6fAhRp.js")),
@@ -13383,7 +13383,7 @@ var tg = { class: "pk-chatbot-view-chat__input-stage" }, ng = {
13383
13383
  },
13384
13384
  emits: ["show-info", "revise"],
13385
13385
  setup(e, { emit: t }) {
13386
- let n = M(() => import("./_chunks/PkToolShowForm-YJQPYfEe.js")), r = M(() => import("./_chunks/PkToolShowContactForm-DqVLVwqJ.js")), i = M(() => import("./_chunks/PkToolShowSuggestedReply-Dl4x_nEJ.js")), a = M(() => import("./_chunks/PkToolShowSources-VFoI0YS8.js")), o = M(() => import("./_chunks/PkToolRequestGeolocation-OF1PWhMw.js")), s = M(() => import("./_chunks/PkToolRequestOAuthConnection-ChGMAKV7.js")), c = M(() => import("./_chunks/PkToolShowLocation-D72rWN1o.js")), l = M(() => import("./_chunks/PkToolShowDiagram-CWsqmvB2.js")), u = e, d = t, f = qu(u.agentId), { agentInterface: p, agentFileUpload: m, agentVoice: h, actions: g, revisedAnswers: _, messages: v, chat: y, messageFeedbacks: b, feedbackDialogMessage: x, isFeedbackSubmitting: ee, isFeedbackSubmitted: C, feedbackSubmitError: te, isLeadSubmitted: w, isLoadingSubmitLead: T, submitLeadError: ne, input: E, inputMessagePlaceholder: re, isConversationBlocked: ie, baseUrl: oe, pendingAttachments: O, isAttachmentLimitReached: se, fileLimitError: k, apiClient: j, isDark: ce } = Nn(f), { handleSubmit: le, stopGeneration: ue, regenerate: L, onUpvote: de, onDownvote: B, onFeedback: fe, onFeedbackSubmit: pe, onLeadSubmit: me, startNewChat: he, addToolOutput: U, addToolApprovalResponse: ge, handleFileSelect: _e, clearFileLimitError: ve, refreshMessages: ye, appendMessage: be } = f, { t: xe } = Fe({ useScope: "global" }), Se = W(() => {
13386
+ let n = M(() => import("./_chunks/PkToolShowForm-YJQPYfEe.js")), r = M(() => import("./_chunks/PkToolShowContactForm-DqVLVwqJ.js")), i = M(() => import("./_chunks/PkToolShowSuggestedReply-Dl4x_nEJ.js")), a = M(() => import("./_chunks/PkToolShowSources-VFoI0YS8.js")), o = M(() => import("./_chunks/PkToolRequestGeolocation-OF1PWhMw.js")), s = M(() => import("./_chunks/PkToolRequestOAuthConnection-CgQePKKb.js")), c = M(() => import("./_chunks/PkToolShowLocation-D72rWN1o.js")), l = M(() => import("./_chunks/PkToolShowDiagram-CWsqmvB2.js")), u = e, d = t, f = qu(u.agentId), { agentInterface: p, agentFileUpload: m, agentVoice: h, actions: g, revisedAnswers: _, messages: v, chat: y, messageFeedbacks: b, feedbackDialogMessage: x, isFeedbackSubmitting: ee, isFeedbackSubmitted: C, feedbackSubmitError: te, isLeadSubmitted: w, isLoadingSubmitLead: T, submitLeadError: ne, input: E, inputMessagePlaceholder: re, isConversationBlocked: ie, baseUrl: oe, pendingAttachments: O, isAttachmentLimitReached: se, fileLimitError: k, apiClient: j, isDark: ce } = Nn(f), { handleSubmit: le, stopGeneration: ue, regenerate: L, onUpvote: de, onDownvote: B, onFeedback: fe, onFeedbackSubmit: pe, onLeadSubmit: me, startNewChat: he, addToolOutput: U, addToolApprovalResponse: ge, handleFileSelect: _e, clearFileLimitError: ve, refreshMessages: ye, appendMessage: be } = f, { t: xe } = Fe({ useScope: "global" }), Se = W(() => {
13387
13387
  let e = k.value;
13388
13388
  if (e) return e.reason === "per-chat" ? xe("message.fileLimitPerChat", { max: e.max }) : xe("message.fileLimitPerMessage", { max: e.max });
13389
13389
  }), Ce = qt(() => p.value?.dismissableNotice), we = W(() => v.value.some((e) => e.role === "user")), Te = W(() => u.modifier === "fullscreen" && !we.value), Ee = ad(p, () => u.userName), De = ae("chatViewEl"), Oe = async (e) => (await j.value.expandSourceContext(u.agentId, e.documentId, e.chunkIndex)).content, ke = async (e) => {
@@ -1,2 +1,2 @@
1
- import { t as e } from "./_chunks/PkChatbot-BMIs94QB.js";
1
+ import { t as e } from "./_chunks/PkChatbot-CdtYJ1Lf.js";
2
2
  export { e as default };
@@ -1,2 +1,2 @@
1
- import { t as e } from "./_chunks/PkChatbotMessages-Bj7mjhqN.js";
1
+ import { t as e } from "./_chunks/PkChatbotMessages-R1EsTJpM.js";
2
2
  export { e as default };
@@ -1,2 +1,2 @@
1
- import { t as e } from "./_chunks/PkChatbotViewChat-CQ5WcZs8.js";
1
+ import { t as e } from "./_chunks/PkChatbotViewChat-CbqrHEC6.js";
2
2
  export { e as default };
@@ -1,7 +1,7 @@
1
1
  import { n as e, t } from "./PkStreamingMarkdown-DYy79C1P.js";
2
2
  import { n } from "./useChatbotStore-CMo47fnS.js";
3
3
  import { t as ee } from "./PkChatbotViewConversations-CHpN4vhm.js";
4
- import { t as te } from "./PkChatbotViewChat-CQ5WcZs8.js";
4
+ import { t as te } from "./PkChatbotViewChat-CbqrHEC6.js";
5
5
  import { t as ne } from "./PkChatbotViewFiles-Bk9-K6Hw.js";
6
6
  import { t as r } from "./useLocalizedString-PfaKjIQY.js";
7
7
  import { S as i } from "./utils-B45A2ONa.js";
@@ -269,4 +269,4 @@ var be = { class: "pk-chatbot__header-start" }, xe = ["title"], Se = { key: 1 },
269
269
  //#endregion
270
270
  export { k as t };
271
271
 
272
- //# sourceMappingURL=PkChatbot-BMIs94QB.js.map
272
+ //# sourceMappingURL=PkChatbot-CdtYJ1Lf.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkChatbot-BMIs94QB.js","names":[],"sources":["../../../../packages/components/src/chat/PkChatbot.vue","../../../../packages/components/src/chat/PkChatbot.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import {\n computed,\n inject,\n toRef,\n useTemplateRef,\n watch,\n watchEffect,\n useSlots,\n } from 'vue'\n import { useElementSize } from '@vueuse/core'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type {\n AgentInterface,\n AgentModel,\n AgentFileUpload,\n AgentGatewayOptions,\n AgentVoiceCapability,\n ChatMessageActions,\n RevisedAnswer,\n UIChatMessage,\n AgentTools,\n } from 'models'\n import {\n useChatbotStore,\n useLocalizedString,\n provideShortUrlResolver,\n } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import PkChatbotViewChat from './PkChatbotViewChat.vue'\n import PkChatbotViewConversations from './PkChatbotViewConversations.vue'\n import PkChatbotViewFiles from './PkChatbotViewFiles.vue'\n import PkChatbotViewProfile from './PkChatbotViewProfile.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n import { resolveContrastColor } from './utils'\n\n // Captured in script setup to avoid TS7022 circular inference in template\n const slots = useSlots()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const props = defineProps<{\n baseUrl: string\n basePath?: string\n agentId: string\n agentType?: 'reasoning' | 'chat'\n userId?: string\n name?: string\n model?: string\n agentModel?: AgentModel\n agentTools?: AgentTools\n agentInterface?: AgentInterface\n actions?: ChatMessageActions[]\n revisedAnswers?: RevisedAnswer[]\n headers?: Record<string, string>\n systemTheme?: 'light' | 'dark'\n agentFileUpload?: AgentFileUpload\n /** Voice configuration from the agent settings: enables the talk button. */\n voice?: AgentVoiceCapability\n fallbackModels?: string[]\n gatewayOptions?: Partial<AgentGatewayOptions>\n externalContext?: Record<string, unknown>\n /** Layout modifier: `widget` (default) keeps the contained layout with header, `fullscreen` removes header/background for full-page usage (the page provides the chrome), `panel` keeps the header but slims it down for a docked side panel (the host provides background and border) */\n modifier?: 'widget' | 'fullscreen' | 'panel'\n /** Display name of the current user, used for the `{name}` placeholder in the fullscreen greeting */\n userName?: string\n /** Show the \"Files in this chat\" menu for hosts that don't provide the\n * profile context but whose user is authenticated (e.g. the backoffice\n * playground). The widget host relies on `canManageProfile` instead. */\n userFilesEnabled?: boolean\n }>()\n const emit = defineEmits<{\n 'message-update': [message: UIChatMessage]\n 'show-info': [message: UIChatMessage]\n revise: [message: UIChatMessage]\n error: [error: { type: string; title: string; status: number }]\n }>()\n\n const chatId = defineModel<string | undefined>('chatId', {\n type: String,\n default: undefined,\n })\n\n const store = useChatbotStore(props.agentId)\n\n // Resolve backend short-urls (`/api/r/<hash>`) back to their real target\n // for display in tool components. Batched + cached across the chat tree.\n provideShortUrlResolver((hashes) =>\n store.apiClient.resolveShortUrls(hashes),\n )\n\n const { messages, chat, localChatId, activeView, chatError, isDark } =\n storeToRefs(store)\n\n // Ref to store.agentId for watchers (avoids duplicate-key with agentId prop)\n const storeAgentId = toRef(store, 'agentId')\n\n const { startNewChat, stopGeneration, regenerate, navigate, sendMessage } =\n store\n\n // Optional profile-management context provided by the host widget.\n // Present only for authenticated, non-anonymous sessions.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canManageProfile = computed(\n () => profile?.canManageProfile.value ?? false,\n )\n // Anonymous users get a \"Sign in\" entry instead of the profile one,\n // provided the host filled the `view-auth` slot and the agent has at\n // least one auth method enabled.\n const canSignIn = computed(\n () =>\n !canManageProfile.value &&\n (profile?.canSignIn?.value ?? false) &&\n slots['view-auth'] !== undefined,\n )\n\n // Once the sign-in completes (the session becomes non-anonymous) leave\n // the auth view and return to the conversation.\n watch(canManageProfile, (canManage) => {\n if (canManage && activeView.value === 'auth') {\n navigate('chat')\n }\n })\n\n // Sync props → store reactively\n watchEffect(() => {\n store.baseUrl = props.baseUrl\n store.basePath = props.basePath\n store.name = props.name\n store.agentType = props.agentType\n store.userId = props.userId\n store.agentInterface = props.agentInterface\n store.agentModel = props.agentModel\n store.agentTools = props.agentTools\n store.headers = props.headers\n store.revisedAnswers = props.revisedAnswers\n store.model = props.model\n store.actionsInput = props.actions\n store.agentFileUpload = props.agentFileUpload\n store.agentVoice = props.voice\n store.fallbackModels = props.fallbackModels\n store.gatewayOptions = props.gatewayOptions\n store.externalContext = props.externalContext\n store.systemTheme = props.systemTheme\n store.hideWelcomeMessage = props.modifier === 'fullscreen'\n })\n\n // Sync chatId v-model ↔ store (bidirectional)\n // Parent → store: if the parent provides an external chatId, push it into\n // the store so the Chat instance picks it up.\n watch(\n () => chatId.value,\n (id) => {\n if (id && id !== store.localChatId) {\n store.localChatId = id\n }\n },\n { immediate: true },\n )\n // Store → parent: keep the v-model in sync whenever the store changes\n // the chatId internally (e.g. startNewChat).\n watch(localChatId, (id) => {\n chatId.value = id\n })\n\n // Emit structured errors from chat to the parent component\n watch(chatError, (error) => {\n if (!error) {\n return\n }\n try {\n const parsedError = JSON.parse(error.message) as {\n type: string\n title: string\n status: number\n }\n emit('error', parsedError)\n } catch {\n // ignore non-JSON errors\n }\n })\n\n // ui\n const isFullscreen = computed(() => props.modifier === 'fullscreen')\n const isPanel = computed(() => props.modifier === 'panel')\n\n /**\n * How much room the chat has, measured on the chat and not on the window.\n *\n * The same chat renders in a phone page, in a 408px widget, in a docked\n * panel and in a preview frame that the side panels squeeze to phone width\n * on a desktop screen, so the viewport says nothing about the space the\n * layout actually has. A CSS container query would read the same number\n * with no JavaScript, but `container-type` makes the element a containing\n * block for `position: fixed` descendants on every engine except Chrome\n * 129 and later, which displaces every dropdown in the chat: see\n * docs/agents/gotchas.md, \"The chat measures its own width, and not with a\n * container query\".\n *\n * The thresholds carry the modifiers, so the CSS stays declarative:\n * `--phone` has as much room as a phone page, `--tight` is too tight for\n * the agent chip beside the field, `--wide` has room for the outline rail\n * beside the reading column. Before the first measurement the width is 0\n * and no modifier applies, which is the roomy layout the base CSS writes.\n */\n const chatbotEl = useTemplateRef<HTMLDivElement>('chatbotEl')\n const { width: chatbotWidth } = useElementSize(chatbotEl)\n const isPhoneWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 576,\n )\n const isTightWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 640,\n )\n const isWideWidth = computed(() => chatbotWidth.value >= 848)\n const isReasoningAgent = computed(() => props.agentType === 'reasoning')\n const useColorsForAgentHeader = computed(\n () => props.agentInterface?.useColorsForAgentHeader,\n )\n // Panel mode promotes the everyday actions (new chat, history) from the\n // dropdown to direct header buttons: the dropdown then only remains for\n // the secondary entries (files, profile, sign-in) and disappears when\n // none of them applies.\n const showHeaderMenu = computed(\n () =>\n !isPanel.value ||\n canManageProfile.value ||\n canSignIn.value ||\n (!!props.userFilesEnabled && !!localChatId.value),\n )\n const themeClass = computed(() => [\n 'theme',\n `theme--${isDark.value ? 'dark' : 'light'}`,\n ])\n const mainColor = computed(() => props.agentInterface?.mainColor)\n const contrastColor = computed(() =>\n resolveContrastColor(props.agentInterface?.textColor, mainColor.value),\n )\n const footerMessage = useLocalizedString(\n () => props.agentInterface?.footerMessage,\n )\n const title = computed(() => {\n if (activeView.value === 'conversations') {\n return $t('label.conversations')\n }\n if (activeView.value === 'profile') {\n return $t('label.profile')\n }\n if (activeView.value === 'auth') {\n return $t('label.authentication')\n }\n if (activeView.value === 'files') {\n return $t('userFiles.chatFilesTitle')\n }\n return props.name\n })\n\n // state\n const isReady = computed(() => chat.value.status === 'ready')\n const activeMessage = computed(() => {\n return messages.value?.[messages.value.length - 1]\n })\n\n // expose\n defineExpose({\n startNewChat,\n regenerate,\n stopGeneration,\n sendMessage,\n })\n\n // load chat, conversations and feedbacks on agent or chat change\n watch(\n [storeAgentId, localChatId],\n async ([newAgentId, newChatId]) => {\n await store.initialize(newAgentId, newChatId)\n },\n {\n immediate: true,\n },\n )\n // load conversations when navigating to conversations view\n watch(activeView, (newView) => {\n if (newView === 'conversations') {\n store.loadConversations(storeAgentId.value)\n } else if (newView === 'files') {\n store.loadChatFiles()\n }\n })\n // emit message updates for up-to-date context in parent components\n watch(\n activeMessage,\n (message) => {\n if (message) {\n emit('message-update', message)\n }\n },\n {\n deep: true,\n immediate: true,\n },\n )\n</script>\n\n<template>\n <div\n ref=\"chatbotEl\"\n class=\"pk-chatbot\"\n :class=\"[\n themeClass,\n {\n 'pk-chatbot--fullscreen': isFullscreen,\n 'pk-chatbot--panel': isPanel,\n 'pk-chatbot--phone': isPhoneWidth,\n 'pk-chatbot--tight': isTightWidth,\n 'pk-chatbot--wide': isWideWidth,\n },\n ]\">\n <!-- #region title -->\n <div\n v-if=\"!isFullscreen\"\n class=\"pk-chatbot__header\"\n :style=\"\n useColorsForAgentHeader\n ? {\n backgroundColor: mainColor,\n }\n : undefined\n \">\n <div class=\"pk-chatbot__header-start\">\n <template v-if=\"activeView !== 'chat'\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:arrow-left-s-line',\n title: $t('action.goBack'),\n }\"\n @click=\"navigate('chat')\" />\n <VvIcon\n :name=\"\n activeView === 'conversations'\n ? 'ri:history-line'\n : activeView === 'files'\n ? 'ri:attachment-2'\n : 'ri:user-line'\n \"\n class=\"pk-chatbot__nav-icon\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \" />\n </template>\n <!-- Panel mode: no avatar, the docked panel identifies the\n agent with the title alone -->\n <PkAvatar\n v-else-if=\"!isPanel\"\n modifiers=\"surface ring shrink-0\"\n class=\"pk-chatbot__avatar\"\n :name\n :img-src=\"agentInterface?.logo\" />\n <strong\n class=\"pk-chatbot__title\"\n :title=\"title\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \">\n {{ title }}\n </strong>\n </div>\n <VvButtonGroup class=\"pk-chatbot__header-end\" modifiers=\"compact\">\n <slot\n name=\"header-actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n <template v-if=\"isPanel\">\n <VvButton\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:edit-box-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n <VvButton\n v-if=\"!isReasoningAgent\"\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:history-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.viewRecentChats')\"\n @click=\"navigate('conversations')\" />\n </template>\n <VvDropdown\n v-if=\"showHeaderMenu\"\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:more-2-fill',\n disabled: !isReady,\n title: $t('action.moreActions'),\n }\" />\n <template #items>\n <VvDropdownAction\n v-if=\"!isPanel\"\n @click=\"startNewChat()\">\n <VvIcon name=\"ri:edit-box-line\" />\n {{ $t('action.startNewChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"!isReasoningAgent && !isPanel\"\n @click=\"navigate('conversations')\">\n <VvIcon name=\"ri:history-line\" />\n {{ $t('action.viewRecentChats') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"\n (canManageProfile || userFilesEnabled) &&\n localChatId\n \"\n @click=\"navigate('files')\">\n <VvIcon name=\"ri:attachment-2\" />\n {{ $t('userFiles.chatFilesTitle') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"canManageProfile\"\n @click=\"navigate('profile')\">\n <VvIcon name=\"ri:user-line\" />\n {{ $t('label.profile') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-else-if=\"canSignIn\"\n @click=\"navigate('auth')\">\n <VvIcon name=\"ri:account-circle-line\" />\n {{ $t('action.signIn') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n <slot\n name=\"actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n </VvButtonGroup>\n </div>\n <!-- #endregion -->\n\n <div v-if=\"isReasoningAgent\">\n <div class=\"pk-chatbot__warning-body\">\n <VvAlert modifiers=\"callout\">\n <div class=\"pk-chatbot__warning-header\">\n <VvIcon\n name=\"ri:alert-line\"\n class=\"pk-chatbot__warning-icon\" />\n <strong class=\"pk-chatbot__warning-title\">\n {{ $t('message.warning') }}\n </strong>\n </div>\n <p class=\"pk-chatbot__warning-text\">\n {{ $t('message.reasoningAgent') }}\n </p>\n </VvAlert>\n </div>\n </div>\n\n <!-- #region views -->\n <PkChatbotViewChat\n v-if=\"activeView === 'chat'\"\n :agent-id\n :modifier\n :user-name\n @show-info=\"emit('show-info', $event)\"\n @revise=\"emit('revise', $event)\">\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewChat>\n <PkChatbotViewConversations\n v-else-if=\"activeView === 'conversations'\"\n :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewConversations>\n <PkChatbotViewFiles v-else-if=\"activeView === 'files'\" :agent-id />\n <div\n v-else-if=\"activeView === 'auth'\"\n class=\"pk-chatbot__view-auth flex-1 min-h-0 overflow-y-auto\">\n <!-- Sign-in form provided by the host widget (owns the auth client) -->\n <slot name=\"view-auth\" />\n </div>\n <PkChatbotViewProfile v-else :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewProfile>\n <!-- #endregion -->\n\n <!-- #region footer -->\n <div v-if=\"footerMessage\" class=\"pk-chatbot__footer\">\n <PkStreamingMarkdown :markdown=\"footerMessage\" class=\"wysiwyg\" />\n </div>\n <!-- #endregion -->\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot {\n display: flex;\n flex-direction: column;\n position: relative;\n height: 100%;\n background-color: var(--color-surface);\n\n &__header {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n justify-content: space-between;\n gap: var(--spacing-14);\n background-color: var(--color-surface-1);\n padding: var(--spacing-14);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__header-start {\n display: flex;\n align-items: center;\n gap: var(--spacing-sm);\n min-width: 0;\n }\n\n &__nav-icon {\n font-size: var(--text-18);\n }\n\n &__avatar {\n width: var(--spacing-24);\n height: var(--spacing-24);\n }\n\n &__title {\n font-size: var(--text-16);\n font-weight: var(--font-semibold);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__header-end {\n flex-shrink: 0;\n }\n\n &__warning-body {\n padding: var(--spacing-md);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__warning-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n margin-bottom: var(--spacing-4);\n }\n\n &__warning-icon {\n width: var(--spacing-16);\n height: var(--spacing-16);\n }\n\n &__warning-title {\n font-weight: 600;\n font-size: var(--text-14);\n }\n\n &__warning-text {\n font-size: var(--text-12);\n color: var(--color-word-3);\n }\n\n &__footer {\n padding: var(--spacing-md);\n border-top: 1px solid var(--color-surface-3);\n text-align: center;\n font-size: var(--text-12);\n color: var(--color-word-2);\n }\n\n // Modifier: fullscreen — layout full-page (Gemini-style): nessun\n // header interno, sfondo trasparente, input pill centrato.\n // Il chrome (sidebar, navigazione) è gestito dalla pagina ospite.\n &--fullscreen {\n // The dock, 60px. Declared rather than taken from the agent chip,\n // which the tight layout hides: the pill fell to 54px there and\n // the call bar that takes its place stayed at 60. There is no 60\n // in the spacing scale and a raw `60px` would stop following the\n // root font size, which the labels inside do follow.\n --pk-chat-fullscreen-dock-height: calc(\n var(--spacing-56) + var(--spacing-4)\n );\n // The pill, the notice that tucks into its top corners and the\n // call bar all round their ends on half of it.\n --pk-chat-dock-radius: calc(\n var(--pk-chat-fullscreen-dock-height) / 2\n );\n\n background-color: transparent;\n\n .pk-chatbot__footer {\n border-top: none;\n padding: 0 var(--spacing-md) var(--spacing-sm) var(--spacing-md);\n }\n\n // Reading column aligned with the input (Gemini-style). The\n // constraint lives on the inner wrapper so the scroll container\n // spans the page and the scrollbar sits at its edge.\n .pk-chatbot-messages {\n // Symmetric gutters: a visible scrollbar would otherwise\n // shrink the content box on one side only, shifting the\n // column off the input axis\n scrollbar-gutter: stable both-edges;\n // Dissolve the content at both edges (works on any page\n // background, dots included — a surface-colored fade band\n // would cut through the pattern instead)\n mask-image: linear-gradient(\n to bottom,\n transparent 0,\n #000 var(--spacing-40),\n #000 calc(100% - var(--spacing-40)),\n transparent 100%\n );\n // Resting clearance so the first/last lines sit inside the\n // fully opaque zone\n padding-block: var(--spacing-48);\n }\n\n .pk-chatbot-messages__wrapper {\n width: 100%;\n max-width: 768px;\n margin-inline: auto;\n }\n\n // Outline rail: wide chats only, measured on the chat and not on\n // the viewport, so a preview frame squeezed by side panels loses\n // it like a phone does. The rail sits at the edge of the scroll\n // area beside the reading column: below the `--wide` threshold\n // (768px column plus a 40px rail on each side) it would cover the\n // messages instead.\n .pk-chatbot-messages__outline {\n display: none;\n }\n\n &.pk-chatbot--wide .pk-chatbot-messages__outline {\n display: block;\n }\n\n .pk-chatbot-file-preview {\n background-color: var(--color-surface);\n }\n\n .pk-chatbot-input__notice {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-3);\n border-top: 1px solid var(--color-surface-2);\n padding: var(--spacing-10) var(--spacing-16);\n\n // As deep as the pill's corners, or the page shows through\n // where their curve leaves the side\n &::after {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-2);\n bottom: calc(-1 * var(--pk-chat-dock-radius));\n height: var(--pk-chat-dock-radius);\n left: -1px;\n right: -1px;\n }\n }\n\n .pk-chatbot-input {\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n\n &__form {\n // True semicircles at rest, concentric with the send\n // button, whose center sits 30px in. Not `rounded-full`:\n // once Shift+Enter grows the field the ends would swell\n // with it and cut into the first and last line.\n border-radius: var(--pk-chat-dock-radius);\n min-height: var(--pk-chat-fullscreen-dock-height);\n padding: var(--spacing-12) var(--spacing-16);\n background-color: var(--color-surface-1);\n border-color: var(--color-surface-2);\n box-shadow: none;\n transition:\n border-color var(--duration-200) var(--ease-in-out),\n box-shadow var(--duration-200) var(--ease-in-out);\n\n &:has(:focus-within) {\n border-color: var(--color-surface-4);\n box-shadow: var(--shadow-sm);\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n\n &__textarea {\n background-color: transparent;\n }\n }\n\n // Touch target ≥44px: estende l'hit area dei pulsanti\n // (icona 20px + padding 4px ≈ 28px) senza ingrandire l'icona.\n // Vale per l'input e per la barra della chiamata, che montano\n // lo stesso pulsante.\n .pk-chat-button {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--spacing-8));\n }\n }\n\n // A live call takes the place of the input, so it takes its shape,\n // its column and its height: full-bleed here would break the\n // reading axis, and a different height would make the dock jump\n // the moment the call starts.\n .pk-voice-session {\n --pk-chat-dock-height: var(--pk-chat-fullscreen-dock-height);\n --pk-voice-orb-size: var(--spacing-40);\n --pk-voice-orb-bars-height: var(--spacing-20);\n --pk-voice-session-surface: var(--color-surface-1);\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n padding-inline: var(--spacing-16);\n border-color: var(--color-surface-2);\n border-radius: var(--pk-chat-dock-radius);\n box-shadow: none;\n\n &__timer {\n font-size: var(--text-14);\n }\n }\n }\n\n // Modifier: panel — compact chrome for a docked side panel: the host\n // container owns background and border, the header slims down and\n // drops its own surface so the chat blends into the panel.\n &--panel {\n background-color: transparent;\n\n .pk-chatbot__header {\n background-color: transparent;\n\n // Fixed height so the panel header lines up with the host's\n // own chrome. Custom properties inherit through the shadow\n // root: an embedding host can override it to match its\n // header (the default matches the platform header, 50px).\n height: var(--pk-chatbot-panel-header-height, 50px);\n align-items: center;\n padding-block: 0;\n padding-inline: var(--spacing-10);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n .pk-chatbot__title {\n font-size: var(--text-14);\n }\n\n .pk-chatbot__footer {\n border-top: none;\n padding: var(--spacing-6) var(--spacing-md);\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import {\n computed,\n inject,\n toRef,\n useTemplateRef,\n watch,\n watchEffect,\n useSlots,\n } from 'vue'\n import { useElementSize } from '@vueuse/core'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type {\n AgentInterface,\n AgentModel,\n AgentFileUpload,\n AgentGatewayOptions,\n AgentVoiceCapability,\n ChatMessageActions,\n RevisedAnswer,\n UIChatMessage,\n AgentTools,\n } from 'models'\n import {\n useChatbotStore,\n useLocalizedString,\n provideShortUrlResolver,\n } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import PkChatbotViewChat from './PkChatbotViewChat.vue'\n import PkChatbotViewConversations from './PkChatbotViewConversations.vue'\n import PkChatbotViewFiles from './PkChatbotViewFiles.vue'\n import PkChatbotViewProfile from './PkChatbotViewProfile.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n import { resolveContrastColor } from './utils'\n\n // Captured in script setup to avoid TS7022 circular inference in template\n const slots = useSlots()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const props = defineProps<{\n baseUrl: string\n basePath?: string\n agentId: string\n agentType?: 'reasoning' | 'chat'\n userId?: string\n name?: string\n model?: string\n agentModel?: AgentModel\n agentTools?: AgentTools\n agentInterface?: AgentInterface\n actions?: ChatMessageActions[]\n revisedAnswers?: RevisedAnswer[]\n headers?: Record<string, string>\n systemTheme?: 'light' | 'dark'\n agentFileUpload?: AgentFileUpload\n /** Voice configuration from the agent settings: enables the talk button. */\n voice?: AgentVoiceCapability\n fallbackModels?: string[]\n gatewayOptions?: Partial<AgentGatewayOptions>\n externalContext?: Record<string, unknown>\n /** Layout modifier: `widget` (default) keeps the contained layout with header, `fullscreen` removes header/background for full-page usage (the page provides the chrome), `panel` keeps the header but slims it down for a docked side panel (the host provides background and border) */\n modifier?: 'widget' | 'fullscreen' | 'panel'\n /** Display name of the current user, used for the `{name}` placeholder in the fullscreen greeting */\n userName?: string\n /** Show the \"Files in this chat\" menu for hosts that don't provide the\n * profile context but whose user is authenticated (e.g. the backoffice\n * playground). The widget host relies on `canManageProfile` instead. */\n userFilesEnabled?: boolean\n }>()\n const emit = defineEmits<{\n 'message-update': [message: UIChatMessage]\n 'show-info': [message: UIChatMessage]\n revise: [message: UIChatMessage]\n error: [error: { type: string; title: string; status: number }]\n }>()\n\n const chatId = defineModel<string | undefined>('chatId', {\n type: String,\n default: undefined,\n })\n\n const store = useChatbotStore(props.agentId)\n\n // Resolve backend short-urls (`/api/r/<hash>`) back to their real target\n // for display in tool components. Batched + cached across the chat tree.\n provideShortUrlResolver((hashes) =>\n store.apiClient.resolveShortUrls(hashes),\n )\n\n const { messages, chat, localChatId, activeView, chatError, isDark } =\n storeToRefs(store)\n\n // Ref to store.agentId for watchers (avoids duplicate-key with agentId prop)\n const storeAgentId = toRef(store, 'agentId')\n\n const { startNewChat, stopGeneration, regenerate, navigate, sendMessage } =\n store\n\n // Optional profile-management context provided by the host widget.\n // Present only for authenticated, non-anonymous sessions.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canManageProfile = computed(\n () => profile?.canManageProfile.value ?? false,\n )\n // Anonymous users get a \"Sign in\" entry instead of the profile one,\n // provided the host filled the `view-auth` slot and the agent has at\n // least one auth method enabled.\n const canSignIn = computed(\n () =>\n !canManageProfile.value &&\n (profile?.canSignIn?.value ?? false) &&\n slots['view-auth'] !== undefined,\n )\n\n // Once the sign-in completes (the session becomes non-anonymous) leave\n // the auth view and return to the conversation.\n watch(canManageProfile, (canManage) => {\n if (canManage && activeView.value === 'auth') {\n navigate('chat')\n }\n })\n\n // Sync props → store reactively\n watchEffect(() => {\n store.baseUrl = props.baseUrl\n store.basePath = props.basePath\n store.name = props.name\n store.agentType = props.agentType\n store.userId = props.userId\n store.agentInterface = props.agentInterface\n store.agentModel = props.agentModel\n store.agentTools = props.agentTools\n store.headers = props.headers\n store.revisedAnswers = props.revisedAnswers\n store.model = props.model\n store.actionsInput = props.actions\n store.agentFileUpload = props.agentFileUpload\n store.agentVoice = props.voice\n store.fallbackModels = props.fallbackModels\n store.gatewayOptions = props.gatewayOptions\n store.externalContext = props.externalContext\n store.systemTheme = props.systemTheme\n store.hideWelcomeMessage = props.modifier === 'fullscreen'\n })\n\n // Sync chatId v-model ↔ store (bidirectional)\n // Parent → store: if the parent provides an external chatId, push it into\n // the store so the Chat instance picks it up.\n watch(\n () => chatId.value,\n (id) => {\n if (id && id !== store.localChatId) {\n store.localChatId = id\n }\n },\n { immediate: true },\n )\n // Store → parent: keep the v-model in sync whenever the store changes\n // the chatId internally (e.g. startNewChat).\n watch(localChatId, (id) => {\n chatId.value = id\n })\n\n // Emit structured errors from chat to the parent component\n watch(chatError, (error) => {\n if (!error) {\n return\n }\n try {\n const parsedError = JSON.parse(error.message) as {\n type: string\n title: string\n status: number\n }\n emit('error', parsedError)\n } catch {\n // ignore non-JSON errors\n }\n })\n\n // ui\n const isFullscreen = computed(() => props.modifier === 'fullscreen')\n const isPanel = computed(() => props.modifier === 'panel')\n\n /**\n * How much room the chat has, measured on the chat and not on the window.\n *\n * The same chat renders in a phone page, in a 408px widget, in a docked\n * panel and in a preview frame that the side panels squeeze to phone width\n * on a desktop screen, so the viewport says nothing about the space the\n * layout actually has. A CSS container query would read the same number\n * with no JavaScript, but `container-type` makes the element a containing\n * block for `position: fixed` descendants on every engine except Chrome\n * 129 and later, which displaces every dropdown in the chat: see\n * docs/agents/gotchas.md, \"The chat measures its own width, and not with a\n * container query\".\n *\n * The thresholds carry the modifiers, so the CSS stays declarative:\n * `--phone` has as much room as a phone page, `--tight` is too tight for\n * the agent chip beside the field, `--wide` has room for the outline rail\n * beside the reading column. Before the first measurement the width is 0\n * and no modifier applies, which is the roomy layout the base CSS writes.\n */\n const chatbotEl = useTemplateRef<HTMLDivElement>('chatbotEl')\n const { width: chatbotWidth } = useElementSize(chatbotEl)\n const isPhoneWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 576,\n )\n const isTightWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 640,\n )\n const isWideWidth = computed(() => chatbotWidth.value >= 848)\n const isReasoningAgent = computed(() => props.agentType === 'reasoning')\n const useColorsForAgentHeader = computed(\n () => props.agentInterface?.useColorsForAgentHeader,\n )\n // Panel mode promotes the everyday actions (new chat, history) from the\n // dropdown to direct header buttons: the dropdown then only remains for\n // the secondary entries (files, profile, sign-in) and disappears when\n // none of them applies.\n const showHeaderMenu = computed(\n () =>\n !isPanel.value ||\n canManageProfile.value ||\n canSignIn.value ||\n (!!props.userFilesEnabled && !!localChatId.value),\n )\n const themeClass = computed(() => [\n 'theme',\n `theme--${isDark.value ? 'dark' : 'light'}`,\n ])\n const mainColor = computed(() => props.agentInterface?.mainColor)\n const contrastColor = computed(() =>\n resolveContrastColor(props.agentInterface?.textColor, mainColor.value),\n )\n const footerMessage = useLocalizedString(\n () => props.agentInterface?.footerMessage,\n )\n const title = computed(() => {\n if (activeView.value === 'conversations') {\n return $t('label.conversations')\n }\n if (activeView.value === 'profile') {\n return $t('label.profile')\n }\n if (activeView.value === 'auth') {\n return $t('label.authentication')\n }\n if (activeView.value === 'files') {\n return $t('userFiles.chatFilesTitle')\n }\n return props.name\n })\n\n // state\n const isReady = computed(() => chat.value.status === 'ready')\n const activeMessage = computed(() => {\n return messages.value?.[messages.value.length - 1]\n })\n\n // expose\n defineExpose({\n startNewChat,\n regenerate,\n stopGeneration,\n sendMessage,\n })\n\n // load chat, conversations and feedbacks on agent or chat change\n watch(\n [storeAgentId, localChatId],\n async ([newAgentId, newChatId]) => {\n await store.initialize(newAgentId, newChatId)\n },\n {\n immediate: true,\n },\n )\n // load conversations when navigating to conversations view\n watch(activeView, (newView) => {\n if (newView === 'conversations') {\n store.loadConversations(storeAgentId.value)\n } else if (newView === 'files') {\n store.loadChatFiles()\n }\n })\n // emit message updates for up-to-date context in parent components\n watch(\n activeMessage,\n (message) => {\n if (message) {\n emit('message-update', message)\n }\n },\n {\n deep: true,\n immediate: true,\n },\n )\n</script>\n\n<template>\n <div\n ref=\"chatbotEl\"\n class=\"pk-chatbot\"\n :class=\"[\n themeClass,\n {\n 'pk-chatbot--fullscreen': isFullscreen,\n 'pk-chatbot--panel': isPanel,\n 'pk-chatbot--phone': isPhoneWidth,\n 'pk-chatbot--tight': isTightWidth,\n 'pk-chatbot--wide': isWideWidth,\n },\n ]\">\n <!-- #region title -->\n <div\n v-if=\"!isFullscreen\"\n class=\"pk-chatbot__header\"\n :style=\"\n useColorsForAgentHeader\n ? {\n backgroundColor: mainColor,\n }\n : undefined\n \">\n <div class=\"pk-chatbot__header-start\">\n <template v-if=\"activeView !== 'chat'\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:arrow-left-s-line',\n title: $t('action.goBack'),\n }\"\n @click=\"navigate('chat')\" />\n <VvIcon\n :name=\"\n activeView === 'conversations'\n ? 'ri:history-line'\n : activeView === 'files'\n ? 'ri:attachment-2'\n : 'ri:user-line'\n \"\n class=\"pk-chatbot__nav-icon\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \" />\n </template>\n <!-- Panel mode: no avatar, the docked panel identifies the\n agent with the title alone -->\n <PkAvatar\n v-else-if=\"!isPanel\"\n modifiers=\"surface ring shrink-0\"\n class=\"pk-chatbot__avatar\"\n :name\n :img-src=\"agentInterface?.logo\" />\n <strong\n class=\"pk-chatbot__title\"\n :title=\"title\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \">\n {{ title }}\n </strong>\n </div>\n <VvButtonGroup class=\"pk-chatbot__header-end\" modifiers=\"compact\">\n <slot\n name=\"header-actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n <template v-if=\"isPanel\">\n <VvButton\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:edit-box-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n <VvButton\n v-if=\"!isReasoningAgent\"\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:history-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.viewRecentChats')\"\n @click=\"navigate('conversations')\" />\n </template>\n <VvDropdown\n v-if=\"showHeaderMenu\"\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:more-2-fill',\n disabled: !isReady,\n title: $t('action.moreActions'),\n }\" />\n <template #items>\n <VvDropdownAction\n v-if=\"!isPanel\"\n @click=\"startNewChat()\">\n <VvIcon name=\"ri:edit-box-line\" />\n {{ $t('action.startNewChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"!isReasoningAgent && !isPanel\"\n @click=\"navigate('conversations')\">\n <VvIcon name=\"ri:history-line\" />\n {{ $t('action.viewRecentChats') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"\n (canManageProfile || userFilesEnabled) &&\n localChatId\n \"\n @click=\"navigate('files')\">\n <VvIcon name=\"ri:attachment-2\" />\n {{ $t('userFiles.chatFilesTitle') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"canManageProfile\"\n @click=\"navigate('profile')\">\n <VvIcon name=\"ri:user-line\" />\n {{ $t('label.profile') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-else-if=\"canSignIn\"\n @click=\"navigate('auth')\">\n <VvIcon name=\"ri:account-circle-line\" />\n {{ $t('action.signIn') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n <slot\n name=\"actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n </VvButtonGroup>\n </div>\n <!-- #endregion -->\n\n <div v-if=\"isReasoningAgent\">\n <div class=\"pk-chatbot__warning-body\">\n <VvAlert modifiers=\"callout\">\n <div class=\"pk-chatbot__warning-header\">\n <VvIcon\n name=\"ri:alert-line\"\n class=\"pk-chatbot__warning-icon\" />\n <strong class=\"pk-chatbot__warning-title\">\n {{ $t('message.warning') }}\n </strong>\n </div>\n <p class=\"pk-chatbot__warning-text\">\n {{ $t('message.reasoningAgent') }}\n </p>\n </VvAlert>\n </div>\n </div>\n\n <!-- #region views -->\n <PkChatbotViewChat\n v-if=\"activeView === 'chat'\"\n :agent-id\n :modifier\n :user-name\n @show-info=\"emit('show-info', $event)\"\n @revise=\"emit('revise', $event)\">\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewChat>\n <PkChatbotViewConversations\n v-else-if=\"activeView === 'conversations'\"\n :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewConversations>\n <PkChatbotViewFiles v-else-if=\"activeView === 'files'\" :agent-id />\n <div\n v-else-if=\"activeView === 'auth'\"\n class=\"pk-chatbot__view-auth flex-1 min-h-0 overflow-y-auto\">\n <!-- Sign-in form provided by the host widget (owns the auth client) -->\n <slot name=\"view-auth\" />\n </div>\n <PkChatbotViewProfile v-else :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewProfile>\n <!-- #endregion -->\n\n <!-- #region footer -->\n <div v-if=\"footerMessage\" class=\"pk-chatbot__footer\">\n <PkStreamingMarkdown :markdown=\"footerMessage\" class=\"wysiwyg\" />\n </div>\n <!-- #endregion -->\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot {\n display: flex;\n flex-direction: column;\n position: relative;\n height: 100%;\n background-color: var(--color-surface);\n\n &__header {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n justify-content: space-between;\n gap: var(--spacing-14);\n background-color: var(--color-surface-1);\n padding: var(--spacing-14);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__header-start {\n display: flex;\n align-items: center;\n gap: var(--spacing-sm);\n min-width: 0;\n }\n\n &__nav-icon {\n font-size: var(--text-18);\n }\n\n &__avatar {\n width: var(--spacing-24);\n height: var(--spacing-24);\n }\n\n &__title {\n font-size: var(--text-16);\n font-weight: var(--font-semibold);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__header-end {\n flex-shrink: 0;\n }\n\n &__warning-body {\n padding: var(--spacing-md);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__warning-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n margin-bottom: var(--spacing-4);\n }\n\n &__warning-icon {\n width: var(--spacing-16);\n height: var(--spacing-16);\n }\n\n &__warning-title {\n font-weight: 600;\n font-size: var(--text-14);\n }\n\n &__warning-text {\n font-size: var(--text-12);\n color: var(--color-word-3);\n }\n\n &__footer {\n padding: var(--spacing-md);\n border-top: 1px solid var(--color-surface-3);\n text-align: center;\n font-size: var(--text-12);\n color: var(--color-word-2);\n }\n\n // Modifier: fullscreen — layout full-page (Gemini-style): nessun\n // header interno, sfondo trasparente, input pill centrato.\n // Il chrome (sidebar, navigazione) è gestito dalla pagina ospite.\n &--fullscreen {\n // The dock, 60px. Declared rather than taken from the agent chip,\n // which the tight layout hides: the pill fell to 54px there and\n // the call bar that takes its place stayed at 60. There is no 60\n // in the spacing scale and a raw `60px` would stop following the\n // root font size, which the labels inside do follow.\n --pk-chat-fullscreen-dock-height: calc(\n var(--spacing-56) + var(--spacing-4)\n );\n // The pill, the notice that tucks into its top corners and the\n // call bar all round their ends on half of it.\n --pk-chat-dock-radius: calc(\n var(--pk-chat-fullscreen-dock-height) / 2\n );\n\n background-color: transparent;\n\n .pk-chatbot__footer {\n border-top: none;\n padding: 0 var(--spacing-md) var(--spacing-sm) var(--spacing-md);\n }\n\n // Reading column aligned with the input (Gemini-style). The\n // constraint lives on the inner wrapper so the scroll container\n // spans the page and the scrollbar sits at its edge.\n .pk-chatbot-messages {\n // Symmetric gutters: a visible scrollbar would otherwise\n // shrink the content box on one side only, shifting the\n // column off the input axis\n scrollbar-gutter: stable both-edges;\n // Dissolve the content at both edges (works on any page\n // background, dots included — a surface-colored fade band\n // would cut through the pattern instead)\n mask-image: linear-gradient(\n to bottom,\n transparent 0,\n #000 var(--spacing-40),\n #000 calc(100% - var(--spacing-40)),\n transparent 100%\n );\n // Resting clearance so the first/last lines sit inside the\n // fully opaque zone\n padding-block: var(--spacing-48);\n }\n\n .pk-chatbot-messages__wrapper {\n width: 100%;\n max-width: 768px;\n margin-inline: auto;\n }\n\n // Outline rail: wide chats only, measured on the chat and not on\n // the viewport, so a preview frame squeezed by side panels loses\n // it like a phone does. The rail sits at the edge of the scroll\n // area beside the reading column: below the `--wide` threshold\n // (768px column plus a 40px rail on each side) it would cover the\n // messages instead.\n .pk-chatbot-messages__outline {\n display: none;\n }\n\n &.pk-chatbot--wide .pk-chatbot-messages__outline {\n display: block;\n }\n\n .pk-chatbot-file-preview {\n background-color: var(--color-surface);\n }\n\n .pk-chatbot-input__notice {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-3);\n border-top: 1px solid var(--color-surface-2);\n padding: var(--spacing-10) var(--spacing-16);\n\n // As deep as the pill's corners, or the page shows through\n // where their curve leaves the side\n &::after {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-2);\n bottom: calc(-1 * var(--pk-chat-dock-radius));\n height: var(--pk-chat-dock-radius);\n left: -1px;\n right: -1px;\n }\n }\n\n .pk-chatbot-input {\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n\n &__form {\n // True semicircles at rest, concentric with the send\n // button, whose center sits 30px in. Not `rounded-full`:\n // once Shift+Enter grows the field the ends would swell\n // with it and cut into the first and last line.\n border-radius: var(--pk-chat-dock-radius);\n min-height: var(--pk-chat-fullscreen-dock-height);\n padding: var(--spacing-12) var(--spacing-16);\n background-color: var(--color-surface-1);\n border-color: var(--color-surface-2);\n box-shadow: none;\n transition:\n border-color var(--duration-200) var(--ease-in-out),\n box-shadow var(--duration-200) var(--ease-in-out);\n\n &:has(:focus-within) {\n border-color: var(--color-surface-4);\n box-shadow: var(--shadow-sm);\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n\n &__textarea {\n background-color: transparent;\n }\n }\n\n // Touch target ≥44px: estende l'hit area dei pulsanti\n // (icona 20px + padding 4px ≈ 28px) senza ingrandire l'icona.\n // Vale per l'input e per la barra della chiamata, che montano\n // lo stesso pulsante.\n .pk-chat-button {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--spacing-8));\n }\n }\n\n // A live call takes the place of the input, so it takes its shape,\n // its column and its height: full-bleed here would break the\n // reading axis, and a different height would make the dock jump\n // the moment the call starts.\n .pk-voice-session {\n --pk-chat-dock-height: var(--pk-chat-fullscreen-dock-height);\n --pk-voice-orb-size: var(--spacing-40);\n --pk-voice-orb-bars-height: var(--spacing-20);\n --pk-voice-session-surface: var(--color-surface-1);\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n padding-inline: var(--spacing-16);\n border-color: var(--color-surface-2);\n border-radius: var(--pk-chat-dock-radius);\n box-shadow: none;\n\n &__timer {\n font-size: var(--text-14);\n }\n }\n }\n\n // Modifier: panel — compact chrome for a docked side panel: the host\n // container owns background and border, the header slims down and\n // drops its own surface so the chat blends into the panel.\n &--panel {\n background-color: transparent;\n\n .pk-chatbot__header {\n background-color: transparent;\n\n // Fixed height so the panel header lines up with the host's\n // own chrome. Custom properties inherit through the shadow\n // root: an embedding host can override it to match its\n // header (the default matches the platform header, 50px).\n height: var(--pk-chatbot-panel-header-height, 50px);\n align-items: center;\n padding-block: 0;\n padding-inline: var(--spacing-10);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n .pk-chatbot__title {\n font-size: var(--text-14);\n }\n\n .pk-chatbot__footer {\n border-top: none;\n padding: var(--spacing-6) var(--spacing-md);\n }\n }\n }\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PkChatbot-CdtYJ1Lf.js","names":[],"sources":["../../../../packages/components/src/chat/PkChatbot.vue","../../../../packages/components/src/chat/PkChatbot.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import {\n computed,\n inject,\n toRef,\n useTemplateRef,\n watch,\n watchEffect,\n useSlots,\n } from 'vue'\n import { useElementSize } from '@vueuse/core'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type {\n AgentInterface,\n AgentModel,\n AgentFileUpload,\n AgentGatewayOptions,\n AgentVoiceCapability,\n ChatMessageActions,\n RevisedAnswer,\n UIChatMessage,\n AgentTools,\n } from 'models'\n import {\n useChatbotStore,\n useLocalizedString,\n provideShortUrlResolver,\n } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import PkChatbotViewChat from './PkChatbotViewChat.vue'\n import PkChatbotViewConversations from './PkChatbotViewConversations.vue'\n import PkChatbotViewFiles from './PkChatbotViewFiles.vue'\n import PkChatbotViewProfile from './PkChatbotViewProfile.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n import { resolveContrastColor } from './utils'\n\n // Captured in script setup to avoid TS7022 circular inference in template\n const slots = useSlots()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const props = defineProps<{\n baseUrl: string\n basePath?: string\n agentId: string\n agentType?: 'reasoning' | 'chat'\n userId?: string\n name?: string\n model?: string\n agentModel?: AgentModel\n agentTools?: AgentTools\n agentInterface?: AgentInterface\n actions?: ChatMessageActions[]\n revisedAnswers?: RevisedAnswer[]\n headers?: Record<string, string>\n systemTheme?: 'light' | 'dark'\n agentFileUpload?: AgentFileUpload\n /** Voice configuration from the agent settings: enables the talk button. */\n voice?: AgentVoiceCapability\n fallbackModels?: string[]\n gatewayOptions?: Partial<AgentGatewayOptions>\n externalContext?: Record<string, unknown>\n /** Layout modifier: `widget` (default) keeps the contained layout with header, `fullscreen` removes header/background for full-page usage (the page provides the chrome), `panel` keeps the header but slims it down for a docked side panel (the host provides background and border) */\n modifier?: 'widget' | 'fullscreen' | 'panel'\n /** Display name of the current user, used for the `{name}` placeholder in the fullscreen greeting */\n userName?: string\n /** Show the \"Files in this chat\" menu for hosts that don't provide the\n * profile context but whose user is authenticated (e.g. the backoffice\n * playground). The widget host relies on `canManageProfile` instead. */\n userFilesEnabled?: boolean\n }>()\n const emit = defineEmits<{\n 'message-update': [message: UIChatMessage]\n 'show-info': [message: UIChatMessage]\n revise: [message: UIChatMessage]\n error: [error: { type: string; title: string; status: number }]\n }>()\n\n const chatId = defineModel<string | undefined>('chatId', {\n type: String,\n default: undefined,\n })\n\n const store = useChatbotStore(props.agentId)\n\n // Resolve backend short-urls (`/api/r/<hash>`) back to their real target\n // for display in tool components. Batched + cached across the chat tree.\n provideShortUrlResolver((hashes) =>\n store.apiClient.resolveShortUrls(hashes),\n )\n\n const { messages, chat, localChatId, activeView, chatError, isDark } =\n storeToRefs(store)\n\n // Ref to store.agentId for watchers (avoids duplicate-key with agentId prop)\n const storeAgentId = toRef(store, 'agentId')\n\n const { startNewChat, stopGeneration, regenerate, navigate, sendMessage } =\n store\n\n // Optional profile-management context provided by the host widget.\n // Present only for authenticated, non-anonymous sessions.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canManageProfile = computed(\n () => profile?.canManageProfile.value ?? false,\n )\n // Anonymous users get a \"Sign in\" entry instead of the profile one,\n // provided the host filled the `view-auth` slot and the agent has at\n // least one auth method enabled.\n const canSignIn = computed(\n () =>\n !canManageProfile.value &&\n (profile?.canSignIn?.value ?? false) &&\n slots['view-auth'] !== undefined,\n )\n\n // Once the sign-in completes (the session becomes non-anonymous) leave\n // the auth view and return to the conversation.\n watch(canManageProfile, (canManage) => {\n if (canManage && activeView.value === 'auth') {\n navigate('chat')\n }\n })\n\n // Sync props → store reactively\n watchEffect(() => {\n store.baseUrl = props.baseUrl\n store.basePath = props.basePath\n store.name = props.name\n store.agentType = props.agentType\n store.userId = props.userId\n store.agentInterface = props.agentInterface\n store.agentModel = props.agentModel\n store.agentTools = props.agentTools\n store.headers = props.headers\n store.revisedAnswers = props.revisedAnswers\n store.model = props.model\n store.actionsInput = props.actions\n store.agentFileUpload = props.agentFileUpload\n store.agentVoice = props.voice\n store.fallbackModels = props.fallbackModels\n store.gatewayOptions = props.gatewayOptions\n store.externalContext = props.externalContext\n store.systemTheme = props.systemTheme\n store.hideWelcomeMessage = props.modifier === 'fullscreen'\n })\n\n // Sync chatId v-model ↔ store (bidirectional)\n // Parent → store: if the parent provides an external chatId, push it into\n // the store so the Chat instance picks it up.\n watch(\n () => chatId.value,\n (id) => {\n if (id && id !== store.localChatId) {\n store.localChatId = id\n }\n },\n { immediate: true },\n )\n // Store → parent: keep the v-model in sync whenever the store changes\n // the chatId internally (e.g. startNewChat).\n watch(localChatId, (id) => {\n chatId.value = id\n })\n\n // Emit structured errors from chat to the parent component\n watch(chatError, (error) => {\n if (!error) {\n return\n }\n try {\n const parsedError = JSON.parse(error.message) as {\n type: string\n title: string\n status: number\n }\n emit('error', parsedError)\n } catch {\n // ignore non-JSON errors\n }\n })\n\n // ui\n const isFullscreen = computed(() => props.modifier === 'fullscreen')\n const isPanel = computed(() => props.modifier === 'panel')\n\n /**\n * How much room the chat has, measured on the chat and not on the window.\n *\n * The same chat renders in a phone page, in a 408px widget, in a docked\n * panel and in a preview frame that the side panels squeeze to phone width\n * on a desktop screen, so the viewport says nothing about the space the\n * layout actually has. A CSS container query would read the same number\n * with no JavaScript, but `container-type` makes the element a containing\n * block for `position: fixed` descendants on every engine except Chrome\n * 129 and later, which displaces every dropdown in the chat: see\n * docs/agents/gotchas.md, \"The chat measures its own width, and not with a\n * container query\".\n *\n * The thresholds carry the modifiers, so the CSS stays declarative:\n * `--phone` has as much room as a phone page, `--tight` is too tight for\n * the agent chip beside the field, `--wide` has room for the outline rail\n * beside the reading column. Before the first measurement the width is 0\n * and no modifier applies, which is the roomy layout the base CSS writes.\n */\n const chatbotEl = useTemplateRef<HTMLDivElement>('chatbotEl')\n const { width: chatbotWidth } = useElementSize(chatbotEl)\n const isPhoneWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 576,\n )\n const isTightWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 640,\n )\n const isWideWidth = computed(() => chatbotWidth.value >= 848)\n const isReasoningAgent = computed(() => props.agentType === 'reasoning')\n const useColorsForAgentHeader = computed(\n () => props.agentInterface?.useColorsForAgentHeader,\n )\n // Panel mode promotes the everyday actions (new chat, history) from the\n // dropdown to direct header buttons: the dropdown then only remains for\n // the secondary entries (files, profile, sign-in) and disappears when\n // none of them applies.\n const showHeaderMenu = computed(\n () =>\n !isPanel.value ||\n canManageProfile.value ||\n canSignIn.value ||\n (!!props.userFilesEnabled && !!localChatId.value),\n )\n const themeClass = computed(() => [\n 'theme',\n `theme--${isDark.value ? 'dark' : 'light'}`,\n ])\n const mainColor = computed(() => props.agentInterface?.mainColor)\n const contrastColor = computed(() =>\n resolveContrastColor(props.agentInterface?.textColor, mainColor.value),\n )\n const footerMessage = useLocalizedString(\n () => props.agentInterface?.footerMessage,\n )\n const title = computed(() => {\n if (activeView.value === 'conversations') {\n return $t('label.conversations')\n }\n if (activeView.value === 'profile') {\n return $t('label.profile')\n }\n if (activeView.value === 'auth') {\n return $t('label.authentication')\n }\n if (activeView.value === 'files') {\n return $t('userFiles.chatFilesTitle')\n }\n return props.name\n })\n\n // state\n const isReady = computed(() => chat.value.status === 'ready')\n const activeMessage = computed(() => {\n return messages.value?.[messages.value.length - 1]\n })\n\n // expose\n defineExpose({\n startNewChat,\n regenerate,\n stopGeneration,\n sendMessage,\n })\n\n // load chat, conversations and feedbacks on agent or chat change\n watch(\n [storeAgentId, localChatId],\n async ([newAgentId, newChatId]) => {\n await store.initialize(newAgentId, newChatId)\n },\n {\n immediate: true,\n },\n )\n // load conversations when navigating to conversations view\n watch(activeView, (newView) => {\n if (newView === 'conversations') {\n store.loadConversations(storeAgentId.value)\n } else if (newView === 'files') {\n store.loadChatFiles()\n }\n })\n // emit message updates for up-to-date context in parent components\n watch(\n activeMessage,\n (message) => {\n if (message) {\n emit('message-update', message)\n }\n },\n {\n deep: true,\n immediate: true,\n },\n )\n</script>\n\n<template>\n <div\n ref=\"chatbotEl\"\n class=\"pk-chatbot\"\n :class=\"[\n themeClass,\n {\n 'pk-chatbot--fullscreen': isFullscreen,\n 'pk-chatbot--panel': isPanel,\n 'pk-chatbot--phone': isPhoneWidth,\n 'pk-chatbot--tight': isTightWidth,\n 'pk-chatbot--wide': isWideWidth,\n },\n ]\">\n <!-- #region title -->\n <div\n v-if=\"!isFullscreen\"\n class=\"pk-chatbot__header\"\n :style=\"\n useColorsForAgentHeader\n ? {\n backgroundColor: mainColor,\n }\n : undefined\n \">\n <div class=\"pk-chatbot__header-start\">\n <template v-if=\"activeView !== 'chat'\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:arrow-left-s-line',\n title: $t('action.goBack'),\n }\"\n @click=\"navigate('chat')\" />\n <VvIcon\n :name=\"\n activeView === 'conversations'\n ? 'ri:history-line'\n : activeView === 'files'\n ? 'ri:attachment-2'\n : 'ri:user-line'\n \"\n class=\"pk-chatbot__nav-icon\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \" />\n </template>\n <!-- Panel mode: no avatar, the docked panel identifies the\n agent with the title alone -->\n <PkAvatar\n v-else-if=\"!isPanel\"\n modifiers=\"surface ring shrink-0\"\n class=\"pk-chatbot__avatar\"\n :name\n :img-src=\"agentInterface?.logo\" />\n <strong\n class=\"pk-chatbot__title\"\n :title=\"title\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \">\n {{ title }}\n </strong>\n </div>\n <VvButtonGroup class=\"pk-chatbot__header-end\" modifiers=\"compact\">\n <slot\n name=\"header-actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n <template v-if=\"isPanel\">\n <VvButton\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:edit-box-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n <VvButton\n v-if=\"!isReasoningAgent\"\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:history-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.viewRecentChats')\"\n @click=\"navigate('conversations')\" />\n </template>\n <VvDropdown\n v-if=\"showHeaderMenu\"\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:more-2-fill',\n disabled: !isReady,\n title: $t('action.moreActions'),\n }\" />\n <template #items>\n <VvDropdownAction\n v-if=\"!isPanel\"\n @click=\"startNewChat()\">\n <VvIcon name=\"ri:edit-box-line\" />\n {{ $t('action.startNewChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"!isReasoningAgent && !isPanel\"\n @click=\"navigate('conversations')\">\n <VvIcon name=\"ri:history-line\" />\n {{ $t('action.viewRecentChats') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"\n (canManageProfile || userFilesEnabled) &&\n localChatId\n \"\n @click=\"navigate('files')\">\n <VvIcon name=\"ri:attachment-2\" />\n {{ $t('userFiles.chatFilesTitle') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"canManageProfile\"\n @click=\"navigate('profile')\">\n <VvIcon name=\"ri:user-line\" />\n {{ $t('label.profile') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-else-if=\"canSignIn\"\n @click=\"navigate('auth')\">\n <VvIcon name=\"ri:account-circle-line\" />\n {{ $t('action.signIn') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n <slot\n name=\"actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n </VvButtonGroup>\n </div>\n <!-- #endregion -->\n\n <div v-if=\"isReasoningAgent\">\n <div class=\"pk-chatbot__warning-body\">\n <VvAlert modifiers=\"callout\">\n <div class=\"pk-chatbot__warning-header\">\n <VvIcon\n name=\"ri:alert-line\"\n class=\"pk-chatbot__warning-icon\" />\n <strong class=\"pk-chatbot__warning-title\">\n {{ $t('message.warning') }}\n </strong>\n </div>\n <p class=\"pk-chatbot__warning-text\">\n {{ $t('message.reasoningAgent') }}\n </p>\n </VvAlert>\n </div>\n </div>\n\n <!-- #region views -->\n <PkChatbotViewChat\n v-if=\"activeView === 'chat'\"\n :agent-id\n :modifier\n :user-name\n @show-info=\"emit('show-info', $event)\"\n @revise=\"emit('revise', $event)\">\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewChat>\n <PkChatbotViewConversations\n v-else-if=\"activeView === 'conversations'\"\n :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewConversations>\n <PkChatbotViewFiles v-else-if=\"activeView === 'files'\" :agent-id />\n <div\n v-else-if=\"activeView === 'auth'\"\n class=\"pk-chatbot__view-auth flex-1 min-h-0 overflow-y-auto\">\n <!-- Sign-in form provided by the host widget (owns the auth client) -->\n <slot name=\"view-auth\" />\n </div>\n <PkChatbotViewProfile v-else :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewProfile>\n <!-- #endregion -->\n\n <!-- #region footer -->\n <div v-if=\"footerMessage\" class=\"pk-chatbot__footer\">\n <PkStreamingMarkdown :markdown=\"footerMessage\" class=\"wysiwyg\" />\n </div>\n <!-- #endregion -->\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot {\n display: flex;\n flex-direction: column;\n position: relative;\n height: 100%;\n background-color: var(--color-surface);\n\n &__header {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n justify-content: space-between;\n gap: var(--spacing-14);\n background-color: var(--color-surface-1);\n padding: var(--spacing-14);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__header-start {\n display: flex;\n align-items: center;\n gap: var(--spacing-sm);\n min-width: 0;\n }\n\n &__nav-icon {\n font-size: var(--text-18);\n }\n\n &__avatar {\n width: var(--spacing-24);\n height: var(--spacing-24);\n }\n\n &__title {\n font-size: var(--text-16);\n font-weight: var(--font-semibold);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__header-end {\n flex-shrink: 0;\n }\n\n &__warning-body {\n padding: var(--spacing-md);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__warning-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n margin-bottom: var(--spacing-4);\n }\n\n &__warning-icon {\n width: var(--spacing-16);\n height: var(--spacing-16);\n }\n\n &__warning-title {\n font-weight: 600;\n font-size: var(--text-14);\n }\n\n &__warning-text {\n font-size: var(--text-12);\n color: var(--color-word-3);\n }\n\n &__footer {\n padding: var(--spacing-md);\n border-top: 1px solid var(--color-surface-3);\n text-align: center;\n font-size: var(--text-12);\n color: var(--color-word-2);\n }\n\n // Modifier: fullscreen — layout full-page (Gemini-style): nessun\n // header interno, sfondo trasparente, input pill centrato.\n // Il chrome (sidebar, navigazione) è gestito dalla pagina ospite.\n &--fullscreen {\n // The dock, 60px. Declared rather than taken from the agent chip,\n // which the tight layout hides: the pill fell to 54px there and\n // the call bar that takes its place stayed at 60. There is no 60\n // in the spacing scale and a raw `60px` would stop following the\n // root font size, which the labels inside do follow.\n --pk-chat-fullscreen-dock-height: calc(\n var(--spacing-56) + var(--spacing-4)\n );\n // The pill, the notice that tucks into its top corners and the\n // call bar all round their ends on half of it.\n --pk-chat-dock-radius: calc(\n var(--pk-chat-fullscreen-dock-height) / 2\n );\n\n background-color: transparent;\n\n .pk-chatbot__footer {\n border-top: none;\n padding: 0 var(--spacing-md) var(--spacing-sm) var(--spacing-md);\n }\n\n // Reading column aligned with the input (Gemini-style). The\n // constraint lives on the inner wrapper so the scroll container\n // spans the page and the scrollbar sits at its edge.\n .pk-chatbot-messages {\n // Symmetric gutters: a visible scrollbar would otherwise\n // shrink the content box on one side only, shifting the\n // column off the input axis\n scrollbar-gutter: stable both-edges;\n // Dissolve the content at both edges (works on any page\n // background, dots included — a surface-colored fade band\n // would cut through the pattern instead)\n mask-image: linear-gradient(\n to bottom,\n transparent 0,\n #000 var(--spacing-40),\n #000 calc(100% - var(--spacing-40)),\n transparent 100%\n );\n // Resting clearance so the first/last lines sit inside the\n // fully opaque zone\n padding-block: var(--spacing-48);\n }\n\n .pk-chatbot-messages__wrapper {\n width: 100%;\n max-width: 768px;\n margin-inline: auto;\n }\n\n // Outline rail: wide chats only, measured on the chat and not on\n // the viewport, so a preview frame squeezed by side panels loses\n // it like a phone does. The rail sits at the edge of the scroll\n // area beside the reading column: below the `--wide` threshold\n // (768px column plus a 40px rail on each side) it would cover the\n // messages instead.\n .pk-chatbot-messages__outline {\n display: none;\n }\n\n &.pk-chatbot--wide .pk-chatbot-messages__outline {\n display: block;\n }\n\n .pk-chatbot-file-preview {\n background-color: var(--color-surface);\n }\n\n .pk-chatbot-input__notice {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-3);\n border-top: 1px solid var(--color-surface-2);\n padding: var(--spacing-10) var(--spacing-16);\n\n // As deep as the pill's corners, or the page shows through\n // where their curve leaves the side\n &::after {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-2);\n bottom: calc(-1 * var(--pk-chat-dock-radius));\n height: var(--pk-chat-dock-radius);\n left: -1px;\n right: -1px;\n }\n }\n\n .pk-chatbot-input {\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n\n &__form {\n // True semicircles at rest, concentric with the send\n // button, whose center sits 30px in. Not `rounded-full`:\n // once Shift+Enter grows the field the ends would swell\n // with it and cut into the first and last line.\n border-radius: var(--pk-chat-dock-radius);\n min-height: var(--pk-chat-fullscreen-dock-height);\n padding: var(--spacing-12) var(--spacing-16);\n background-color: var(--color-surface-1);\n border-color: var(--color-surface-2);\n box-shadow: none;\n transition:\n border-color var(--duration-200) var(--ease-in-out),\n box-shadow var(--duration-200) var(--ease-in-out);\n\n &:has(:focus-within) {\n border-color: var(--color-surface-4);\n box-shadow: var(--shadow-sm);\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n\n &__textarea {\n background-color: transparent;\n }\n }\n\n // Touch target ≥44px: estende l'hit area dei pulsanti\n // (icona 20px + padding 4px ≈ 28px) senza ingrandire l'icona.\n // Vale per l'input e per la barra della chiamata, che montano\n // lo stesso pulsante.\n .pk-chat-button {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--spacing-8));\n }\n }\n\n // A live call takes the place of the input, so it takes its shape,\n // its column and its height: full-bleed here would break the\n // reading axis, and a different height would make the dock jump\n // the moment the call starts.\n .pk-voice-session {\n --pk-chat-dock-height: var(--pk-chat-fullscreen-dock-height);\n --pk-voice-orb-size: var(--spacing-40);\n --pk-voice-orb-bars-height: var(--spacing-20);\n --pk-voice-session-surface: var(--color-surface-1);\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n padding-inline: var(--spacing-16);\n border-color: var(--color-surface-2);\n border-radius: var(--pk-chat-dock-radius);\n box-shadow: none;\n\n &__timer {\n font-size: var(--text-14);\n }\n }\n }\n\n // Modifier: panel — compact chrome for a docked side panel: the host\n // container owns background and border, the header slims down and\n // drops its own surface so the chat blends into the panel.\n &--panel {\n background-color: transparent;\n\n .pk-chatbot__header {\n background-color: transparent;\n\n // Fixed height so the panel header lines up with the host's\n // own chrome. Custom properties inherit through the shadow\n // root: an embedding host can override it to match its\n // header (the default matches the platform header, 50px).\n height: var(--pk-chatbot-panel-header-height, 50px);\n align-items: center;\n padding-block: 0;\n padding-inline: var(--spacing-10);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n .pk-chatbot__title {\n font-size: var(--text-14);\n }\n\n .pk-chatbot__footer {\n border-top: none;\n padding: var(--spacing-6) var(--spacing-md);\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import {\n computed,\n inject,\n toRef,\n useTemplateRef,\n watch,\n watchEffect,\n useSlots,\n } from 'vue'\n import { useElementSize } from '@vueuse/core'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type {\n AgentInterface,\n AgentModel,\n AgentFileUpload,\n AgentGatewayOptions,\n AgentVoiceCapability,\n ChatMessageActions,\n RevisedAnswer,\n UIChatMessage,\n AgentTools,\n } from 'models'\n import {\n useChatbotStore,\n useLocalizedString,\n provideShortUrlResolver,\n } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import PkChatbotViewChat from './PkChatbotViewChat.vue'\n import PkChatbotViewConversations from './PkChatbotViewConversations.vue'\n import PkChatbotViewFiles from './PkChatbotViewFiles.vue'\n import PkChatbotViewProfile from './PkChatbotViewProfile.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n import { resolveContrastColor } from './utils'\n\n // Captured in script setup to avoid TS7022 circular inference in template\n const slots = useSlots()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const props = defineProps<{\n baseUrl: string\n basePath?: string\n agentId: string\n agentType?: 'reasoning' | 'chat'\n userId?: string\n name?: string\n model?: string\n agentModel?: AgentModel\n agentTools?: AgentTools\n agentInterface?: AgentInterface\n actions?: ChatMessageActions[]\n revisedAnswers?: RevisedAnswer[]\n headers?: Record<string, string>\n systemTheme?: 'light' | 'dark'\n agentFileUpload?: AgentFileUpload\n /** Voice configuration from the agent settings: enables the talk button. */\n voice?: AgentVoiceCapability\n fallbackModels?: string[]\n gatewayOptions?: Partial<AgentGatewayOptions>\n externalContext?: Record<string, unknown>\n /** Layout modifier: `widget` (default) keeps the contained layout with header, `fullscreen` removes header/background for full-page usage (the page provides the chrome), `panel` keeps the header but slims it down for a docked side panel (the host provides background and border) */\n modifier?: 'widget' | 'fullscreen' | 'panel'\n /** Display name of the current user, used for the `{name}` placeholder in the fullscreen greeting */\n userName?: string\n /** Show the \"Files in this chat\" menu for hosts that don't provide the\n * profile context but whose user is authenticated (e.g. the backoffice\n * playground). The widget host relies on `canManageProfile` instead. */\n userFilesEnabled?: boolean\n }>()\n const emit = defineEmits<{\n 'message-update': [message: UIChatMessage]\n 'show-info': [message: UIChatMessage]\n revise: [message: UIChatMessage]\n error: [error: { type: string; title: string; status: number }]\n }>()\n\n const chatId = defineModel<string | undefined>('chatId', {\n type: String,\n default: undefined,\n })\n\n const store = useChatbotStore(props.agentId)\n\n // Resolve backend short-urls (`/api/r/<hash>`) back to their real target\n // for display in tool components. Batched + cached across the chat tree.\n provideShortUrlResolver((hashes) =>\n store.apiClient.resolveShortUrls(hashes),\n )\n\n const { messages, chat, localChatId, activeView, chatError, isDark } =\n storeToRefs(store)\n\n // Ref to store.agentId for watchers (avoids duplicate-key with agentId prop)\n const storeAgentId = toRef(store, 'agentId')\n\n const { startNewChat, stopGeneration, regenerate, navigate, sendMessage } =\n store\n\n // Optional profile-management context provided by the host widget.\n // Present only for authenticated, non-anonymous sessions.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canManageProfile = computed(\n () => profile?.canManageProfile.value ?? false,\n )\n // Anonymous users get a \"Sign in\" entry instead of the profile one,\n // provided the host filled the `view-auth` slot and the agent has at\n // least one auth method enabled.\n const canSignIn = computed(\n () =>\n !canManageProfile.value &&\n (profile?.canSignIn?.value ?? false) &&\n slots['view-auth'] !== undefined,\n )\n\n // Once the sign-in completes (the session becomes non-anonymous) leave\n // the auth view and return to the conversation.\n watch(canManageProfile, (canManage) => {\n if (canManage && activeView.value === 'auth') {\n navigate('chat')\n }\n })\n\n // Sync props → store reactively\n watchEffect(() => {\n store.baseUrl = props.baseUrl\n store.basePath = props.basePath\n store.name = props.name\n store.agentType = props.agentType\n store.userId = props.userId\n store.agentInterface = props.agentInterface\n store.agentModel = props.agentModel\n store.agentTools = props.agentTools\n store.headers = props.headers\n store.revisedAnswers = props.revisedAnswers\n store.model = props.model\n store.actionsInput = props.actions\n store.agentFileUpload = props.agentFileUpload\n store.agentVoice = props.voice\n store.fallbackModels = props.fallbackModels\n store.gatewayOptions = props.gatewayOptions\n store.externalContext = props.externalContext\n store.systemTheme = props.systemTheme\n store.hideWelcomeMessage = props.modifier === 'fullscreen'\n })\n\n // Sync chatId v-model ↔ store (bidirectional)\n // Parent → store: if the parent provides an external chatId, push it into\n // the store so the Chat instance picks it up.\n watch(\n () => chatId.value,\n (id) => {\n if (id && id !== store.localChatId) {\n store.localChatId = id\n }\n },\n { immediate: true },\n )\n // Store → parent: keep the v-model in sync whenever the store changes\n // the chatId internally (e.g. startNewChat).\n watch(localChatId, (id) => {\n chatId.value = id\n })\n\n // Emit structured errors from chat to the parent component\n watch(chatError, (error) => {\n if (!error) {\n return\n }\n try {\n const parsedError = JSON.parse(error.message) as {\n type: string\n title: string\n status: number\n }\n emit('error', parsedError)\n } catch {\n // ignore non-JSON errors\n }\n })\n\n // ui\n const isFullscreen = computed(() => props.modifier === 'fullscreen')\n const isPanel = computed(() => props.modifier === 'panel')\n\n /**\n * How much room the chat has, measured on the chat and not on the window.\n *\n * The same chat renders in a phone page, in a 408px widget, in a docked\n * panel and in a preview frame that the side panels squeeze to phone width\n * on a desktop screen, so the viewport says nothing about the space the\n * layout actually has. A CSS container query would read the same number\n * with no JavaScript, but `container-type` makes the element a containing\n * block for `position: fixed` descendants on every engine except Chrome\n * 129 and later, which displaces every dropdown in the chat: see\n * docs/agents/gotchas.md, \"The chat measures its own width, and not with a\n * container query\".\n *\n * The thresholds carry the modifiers, so the CSS stays declarative:\n * `--phone` has as much room as a phone page, `--tight` is too tight for\n * the agent chip beside the field, `--wide` has room for the outline rail\n * beside the reading column. Before the first measurement the width is 0\n * and no modifier applies, which is the roomy layout the base CSS writes.\n */\n const chatbotEl = useTemplateRef<HTMLDivElement>('chatbotEl')\n const { width: chatbotWidth } = useElementSize(chatbotEl)\n const isPhoneWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 576,\n )\n const isTightWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 640,\n )\n const isWideWidth = computed(() => chatbotWidth.value >= 848)\n const isReasoningAgent = computed(() => props.agentType === 'reasoning')\n const useColorsForAgentHeader = computed(\n () => props.agentInterface?.useColorsForAgentHeader,\n )\n // Panel mode promotes the everyday actions (new chat, history) from the\n // dropdown to direct header buttons: the dropdown then only remains for\n // the secondary entries (files, profile, sign-in) and disappears when\n // none of them applies.\n const showHeaderMenu = computed(\n () =>\n !isPanel.value ||\n canManageProfile.value ||\n canSignIn.value ||\n (!!props.userFilesEnabled && !!localChatId.value),\n )\n const themeClass = computed(() => [\n 'theme',\n `theme--${isDark.value ? 'dark' : 'light'}`,\n ])\n const mainColor = computed(() => props.agentInterface?.mainColor)\n const contrastColor = computed(() =>\n resolveContrastColor(props.agentInterface?.textColor, mainColor.value),\n )\n const footerMessage = useLocalizedString(\n () => props.agentInterface?.footerMessage,\n )\n const title = computed(() => {\n if (activeView.value === 'conversations') {\n return $t('label.conversations')\n }\n if (activeView.value === 'profile') {\n return $t('label.profile')\n }\n if (activeView.value === 'auth') {\n return $t('label.authentication')\n }\n if (activeView.value === 'files') {\n return $t('userFiles.chatFilesTitle')\n }\n return props.name\n })\n\n // state\n const isReady = computed(() => chat.value.status === 'ready')\n const activeMessage = computed(() => {\n return messages.value?.[messages.value.length - 1]\n })\n\n // expose\n defineExpose({\n startNewChat,\n regenerate,\n stopGeneration,\n sendMessage,\n })\n\n // load chat, conversations and feedbacks on agent or chat change\n watch(\n [storeAgentId, localChatId],\n async ([newAgentId, newChatId]) => {\n await store.initialize(newAgentId, newChatId)\n },\n {\n immediate: true,\n },\n )\n // load conversations when navigating to conversations view\n watch(activeView, (newView) => {\n if (newView === 'conversations') {\n store.loadConversations(storeAgentId.value)\n } else if (newView === 'files') {\n store.loadChatFiles()\n }\n })\n // emit message updates for up-to-date context in parent components\n watch(\n activeMessage,\n (message) => {\n if (message) {\n emit('message-update', message)\n }\n },\n {\n deep: true,\n immediate: true,\n },\n )\n</script>\n\n<template>\n <div\n ref=\"chatbotEl\"\n class=\"pk-chatbot\"\n :class=\"[\n themeClass,\n {\n 'pk-chatbot--fullscreen': isFullscreen,\n 'pk-chatbot--panel': isPanel,\n 'pk-chatbot--phone': isPhoneWidth,\n 'pk-chatbot--tight': isTightWidth,\n 'pk-chatbot--wide': isWideWidth,\n },\n ]\">\n <!-- #region title -->\n <div\n v-if=\"!isFullscreen\"\n class=\"pk-chatbot__header\"\n :style=\"\n useColorsForAgentHeader\n ? {\n backgroundColor: mainColor,\n }\n : undefined\n \">\n <div class=\"pk-chatbot__header-start\">\n <template v-if=\"activeView !== 'chat'\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:arrow-left-s-line',\n title: $t('action.goBack'),\n }\"\n @click=\"navigate('chat')\" />\n <VvIcon\n :name=\"\n activeView === 'conversations'\n ? 'ri:history-line'\n : activeView === 'files'\n ? 'ri:attachment-2'\n : 'ri:user-line'\n \"\n class=\"pk-chatbot__nav-icon\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \" />\n </template>\n <!-- Panel mode: no avatar, the docked panel identifies the\n agent with the title alone -->\n <PkAvatar\n v-else-if=\"!isPanel\"\n modifiers=\"surface ring shrink-0\"\n class=\"pk-chatbot__avatar\"\n :name\n :img-src=\"agentInterface?.logo\" />\n <strong\n class=\"pk-chatbot__title\"\n :title=\"title\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \">\n {{ title }}\n </strong>\n </div>\n <VvButtonGroup class=\"pk-chatbot__header-end\" modifiers=\"compact\">\n <slot\n name=\"header-actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n <template v-if=\"isPanel\">\n <VvButton\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:edit-box-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n <VvButton\n v-if=\"!isReasoningAgent\"\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:history-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.viewRecentChats')\"\n @click=\"navigate('conversations')\" />\n </template>\n <VvDropdown\n v-if=\"showHeaderMenu\"\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:more-2-fill',\n disabled: !isReady,\n title: $t('action.moreActions'),\n }\" />\n <template #items>\n <VvDropdownAction\n v-if=\"!isPanel\"\n @click=\"startNewChat()\">\n <VvIcon name=\"ri:edit-box-line\" />\n {{ $t('action.startNewChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"!isReasoningAgent && !isPanel\"\n @click=\"navigate('conversations')\">\n <VvIcon name=\"ri:history-line\" />\n {{ $t('action.viewRecentChats') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"\n (canManageProfile || userFilesEnabled) &&\n localChatId\n \"\n @click=\"navigate('files')\">\n <VvIcon name=\"ri:attachment-2\" />\n {{ $t('userFiles.chatFilesTitle') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"canManageProfile\"\n @click=\"navigate('profile')\">\n <VvIcon name=\"ri:user-line\" />\n {{ $t('label.profile') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-else-if=\"canSignIn\"\n @click=\"navigate('auth')\">\n <VvIcon name=\"ri:account-circle-line\" />\n {{ $t('action.signIn') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n <slot\n name=\"actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n </VvButtonGroup>\n </div>\n <!-- #endregion -->\n\n <div v-if=\"isReasoningAgent\">\n <div class=\"pk-chatbot__warning-body\">\n <VvAlert modifiers=\"callout\">\n <div class=\"pk-chatbot__warning-header\">\n <VvIcon\n name=\"ri:alert-line\"\n class=\"pk-chatbot__warning-icon\" />\n <strong class=\"pk-chatbot__warning-title\">\n {{ $t('message.warning') }}\n </strong>\n </div>\n <p class=\"pk-chatbot__warning-text\">\n {{ $t('message.reasoningAgent') }}\n </p>\n </VvAlert>\n </div>\n </div>\n\n <!-- #region views -->\n <PkChatbotViewChat\n v-if=\"activeView === 'chat'\"\n :agent-id\n :modifier\n :user-name\n @show-info=\"emit('show-info', $event)\"\n @revise=\"emit('revise', $event)\">\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewChat>\n <PkChatbotViewConversations\n v-else-if=\"activeView === 'conversations'\"\n :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewConversations>\n <PkChatbotViewFiles v-else-if=\"activeView === 'files'\" :agent-id />\n <div\n v-else-if=\"activeView === 'auth'\"\n class=\"pk-chatbot__view-auth flex-1 min-h-0 overflow-y-auto\">\n <!-- Sign-in form provided by the host widget (owns the auth client) -->\n <slot name=\"view-auth\" />\n </div>\n <PkChatbotViewProfile v-else :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewProfile>\n <!-- #endregion -->\n\n <!-- #region footer -->\n <div v-if=\"footerMessage\" class=\"pk-chatbot__footer\">\n <PkStreamingMarkdown :markdown=\"footerMessage\" class=\"wysiwyg\" />\n </div>\n <!-- #endregion -->\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot {\n display: flex;\n flex-direction: column;\n position: relative;\n height: 100%;\n background-color: var(--color-surface);\n\n &__header {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n justify-content: space-between;\n gap: var(--spacing-14);\n background-color: var(--color-surface-1);\n padding: var(--spacing-14);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__header-start {\n display: flex;\n align-items: center;\n gap: var(--spacing-sm);\n min-width: 0;\n }\n\n &__nav-icon {\n font-size: var(--text-18);\n }\n\n &__avatar {\n width: var(--spacing-24);\n height: var(--spacing-24);\n }\n\n &__title {\n font-size: var(--text-16);\n font-weight: var(--font-semibold);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__header-end {\n flex-shrink: 0;\n }\n\n &__warning-body {\n padding: var(--spacing-md);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__warning-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n margin-bottom: var(--spacing-4);\n }\n\n &__warning-icon {\n width: var(--spacing-16);\n height: var(--spacing-16);\n }\n\n &__warning-title {\n font-weight: 600;\n font-size: var(--text-14);\n }\n\n &__warning-text {\n font-size: var(--text-12);\n color: var(--color-word-3);\n }\n\n &__footer {\n padding: var(--spacing-md);\n border-top: 1px solid var(--color-surface-3);\n text-align: center;\n font-size: var(--text-12);\n color: var(--color-word-2);\n }\n\n // Modifier: fullscreen — layout full-page (Gemini-style): nessun\n // header interno, sfondo trasparente, input pill centrato.\n // Il chrome (sidebar, navigazione) è gestito dalla pagina ospite.\n &--fullscreen {\n // The dock, 60px. Declared rather than taken from the agent chip,\n // which the tight layout hides: the pill fell to 54px there and\n // the call bar that takes its place stayed at 60. There is no 60\n // in the spacing scale and a raw `60px` would stop following the\n // root font size, which the labels inside do follow.\n --pk-chat-fullscreen-dock-height: calc(\n var(--spacing-56) + var(--spacing-4)\n );\n // The pill, the notice that tucks into its top corners and the\n // call bar all round their ends on half of it.\n --pk-chat-dock-radius: calc(\n var(--pk-chat-fullscreen-dock-height) / 2\n );\n\n background-color: transparent;\n\n .pk-chatbot__footer {\n border-top: none;\n padding: 0 var(--spacing-md) var(--spacing-sm) var(--spacing-md);\n }\n\n // Reading column aligned with the input (Gemini-style). The\n // constraint lives on the inner wrapper so the scroll container\n // spans the page and the scrollbar sits at its edge.\n .pk-chatbot-messages {\n // Symmetric gutters: a visible scrollbar would otherwise\n // shrink the content box on one side only, shifting the\n // column off the input axis\n scrollbar-gutter: stable both-edges;\n // Dissolve the content at both edges (works on any page\n // background, dots included — a surface-colored fade band\n // would cut through the pattern instead)\n mask-image: linear-gradient(\n to bottom,\n transparent 0,\n #000 var(--spacing-40),\n #000 calc(100% - var(--spacing-40)),\n transparent 100%\n );\n // Resting clearance so the first/last lines sit inside the\n // fully opaque zone\n padding-block: var(--spacing-48);\n }\n\n .pk-chatbot-messages__wrapper {\n width: 100%;\n max-width: 768px;\n margin-inline: auto;\n }\n\n // Outline rail: wide chats only, measured on the chat and not on\n // the viewport, so a preview frame squeezed by side panels loses\n // it like a phone does. The rail sits at the edge of the scroll\n // area beside the reading column: below the `--wide` threshold\n // (768px column plus a 40px rail on each side) it would cover the\n // messages instead.\n .pk-chatbot-messages__outline {\n display: none;\n }\n\n &.pk-chatbot--wide .pk-chatbot-messages__outline {\n display: block;\n }\n\n .pk-chatbot-file-preview {\n background-color: var(--color-surface);\n }\n\n .pk-chatbot-input__notice {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-3);\n border-top: 1px solid var(--color-surface-2);\n padding: var(--spacing-10) var(--spacing-16);\n\n // As deep as the pill's corners, or the page shows through\n // where their curve leaves the side\n &::after {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-2);\n bottom: calc(-1 * var(--pk-chat-dock-radius));\n height: var(--pk-chat-dock-radius);\n left: -1px;\n right: -1px;\n }\n }\n\n .pk-chatbot-input {\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n\n &__form {\n // True semicircles at rest, concentric with the send\n // button, whose center sits 30px in. Not `rounded-full`:\n // once Shift+Enter grows the field the ends would swell\n // with it and cut into the first and last line.\n border-radius: var(--pk-chat-dock-radius);\n min-height: var(--pk-chat-fullscreen-dock-height);\n padding: var(--spacing-12) var(--spacing-16);\n background-color: var(--color-surface-1);\n border-color: var(--color-surface-2);\n box-shadow: none;\n transition:\n border-color var(--duration-200) var(--ease-in-out),\n box-shadow var(--duration-200) var(--ease-in-out);\n\n &:has(:focus-within) {\n border-color: var(--color-surface-4);\n box-shadow: var(--shadow-sm);\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n\n &__textarea {\n background-color: transparent;\n }\n }\n\n // Touch target ≥44px: estende l'hit area dei pulsanti\n // (icona 20px + padding 4px ≈ 28px) senza ingrandire l'icona.\n // Vale per l'input e per la barra della chiamata, che montano\n // lo stesso pulsante.\n .pk-chat-button {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--spacing-8));\n }\n }\n\n // A live call takes the place of the input, so it takes its shape,\n // its column and its height: full-bleed here would break the\n // reading axis, and a different height would make the dock jump\n // the moment the call starts.\n .pk-voice-session {\n --pk-chat-dock-height: var(--pk-chat-fullscreen-dock-height);\n --pk-voice-orb-size: var(--spacing-40);\n --pk-voice-orb-bars-height: var(--spacing-20);\n --pk-voice-session-surface: var(--color-surface-1);\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n padding-inline: var(--spacing-16);\n border-color: var(--color-surface-2);\n border-radius: var(--pk-chat-dock-radius);\n box-shadow: none;\n\n &__timer {\n font-size: var(--text-14);\n }\n }\n }\n\n // Modifier: panel — compact chrome for a docked side panel: the host\n // container owns background and border, the header slims down and\n // drops its own surface so the chat blends into the panel.\n &--panel {\n background-color: transparent;\n\n .pk-chatbot__header {\n background-color: transparent;\n\n // Fixed height so the panel header lines up with the host's\n // own chrome. Custom properties inherit through the shadow\n // root: an embedding host can override it to match its\n // header (the default matches the platform header, 50px).\n height: var(--pk-chatbot-panel-header-height, 50px);\n align-items: center;\n padding-block: 0;\n padding-inline: var(--spacing-10);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n .pk-chatbot__title {\n font-size: var(--text-14);\n }\n\n .pk-chatbot__footer {\n border-top: none;\n padding: var(--spacing-6) var(--spacing-md);\n }\n }\n }\n</style>\n"],"mappings":"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"}
@@ -15,12 +15,12 @@ import { Fragment as C, Transition as w, computed as T, createBlock as E, create
15
15
  import { useI18n as ve } from "vue-i18n";
16
16
  //#region ../../packages/utils/src/text.ts
17
17
  var U = (e) => e.normalize("NFD").replace(/[\u0300-\u036f]/g, ""), ye = (e) => e ? e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[-_]+/g, " ") : "", be = (e) => e ? U(e).replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").toLowerCase() : "", xe = (e) => e ? U(e).replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toLowerCase() : "", Se = (e) => e ? e.charAt(0).toUpperCase() + e.slice(1) : "", Ce = {
18
- requestOAuthConnection: j(() => import("./PkToolRequestOAuthConnection-DMvCGPuX.js")),
18
+ requestOAuthConnection: j(() => import("./PkToolRequestOAuthConnection-a2LegFoE.js")),
19
19
  showArtifact: j(() => import("./PkToolShowArtifact-DUOnFdfa.js").then((e) => e.n)),
20
20
  showCalendarEvent: j(() => import("./PkToolShowCalendarEvent-BsBdsbV0.js").then((e) => e.n)),
21
21
  showComparison: j(() => import("./PkToolShowComparison-DQs3E6-k.js").then((e) => e.n)),
22
22
  showContactForm: j(() => import("./PkToolShowContactForm-1TFKvG_s.js").then((e) => e.n)),
23
- showDiagram: j(() => import("./PkToolShowDiagram-BziggKVa.js")),
23
+ showDiagram: j(() => import("./PkToolShowDiagram-Dmtqu3th.js")),
24
24
  showEmail: j(() => import("./PkToolShowEmail-BDsBoETQ.js").then((e) => e.n)),
25
25
  showImageGallery: j(() => import("./PkToolShowImageGallery-DudQzBx4.js").then((e) => e.n)),
26
26
  showLocation: j(() => import("./PkToolShowLocation-Bv6sLN5P.js").then((e) => e.n)),
@@ -775,4 +775,4 @@ var ke = ["aria-busy"], Ae = {
775
775
  //#endregion
776
776
  export { xe as a, be as i, Se as n, ye as r, tt as t };
777
777
 
778
- //# sourceMappingURL=PkChatbotMessages-Bj7mjhqN.js.map
778
+ //# sourceMappingURL=PkChatbotMessages-R1EsTJpM.js.map