@8wave/ai-elements 0.100.0 → 0.101.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 (51) hide show
  1. package/dist/ai-elements.es.js +1590 -1556
  2. package/dist/ai-elements.es.js.map +1 -1
  3. package/dist-vue/PkChatbot.js +1 -1
  4. package/dist-vue/PkChatbotViewChat.js +1 -1
  5. package/dist-vue/PkChatbotViewConversations.js +1 -1
  6. package/dist-vue/PkChatbotViewFiles.js +1 -1
  7. package/dist-vue/PkChatbotViewProfile.js +1 -1
  8. package/dist-vue/_chunks/PkChatbot-OT6Vj9xn.js +264 -0
  9. package/dist-vue/_chunks/PkChatbot-OT6Vj9xn.js.map +1 -0
  10. package/dist-vue/_chunks/PkChatbotMessages-Cj974POl.js.map +1 -1
  11. package/dist-vue/_chunks/{PkChatbotViewChat-glcxrv5f.js → PkChatbotViewChat-BhwV8BzI.js} +2 -2
  12. package/dist-vue/_chunks/PkChatbotViewChat-BhwV8BzI.js.map +1 -0
  13. package/dist-vue/_chunks/{PkChatbotViewConversations-CNZuX3Rl.js → PkChatbotViewConversations-EuLSiakV.js} +3 -3
  14. package/dist-vue/_chunks/{PkChatbotViewConversations-CNZuX3Rl.js.map → PkChatbotViewConversations-EuLSiakV.js.map} +1 -1
  15. package/dist-vue/_chunks/{PkChatbotViewFiles-CyBqTzjY.js → PkChatbotViewFiles-CIr-0xpK.js} +3 -3
  16. package/dist-vue/_chunks/{PkChatbotViewFiles-CyBqTzjY.js.map → PkChatbotViewFiles-CIr-0xpK.js.map} +1 -1
  17. package/dist-vue/_chunks/{PkChatbotViewProfile-BCPCO6mp.js → PkChatbotViewProfile-CIO0mBFM.js} +2 -2
  18. package/dist-vue/_chunks/{PkChatbotViewProfile-BCPCO6mp.js.map → PkChatbotViewProfile-CIO0mBFM.js.map} +1 -1
  19. package/dist-vue/_chunks/{createChatbotApiClient-BGkXm2NB.js → createChatbotApiClient-2iaBCziJ.js} +362 -346
  20. package/dist-vue/_chunks/createChatbotApiClient-2iaBCziJ.js.map +1 -0
  21. package/dist-vue/_chunks/{useChatbotStore-D9b5Fz-e.js → useChatbotStore-BK1xv_i6.js} +2 -2
  22. package/dist-vue/_chunks/{useChatbotStore-D9b5Fz-e.js.map → useChatbotStore-BK1xv_i6.js.map} +1 -1
  23. package/dist-vue/_chunks/{useDialogConfirm-DaoekZdk.js → useDialogConfirm-CMTIKoWn.js} +12 -11
  24. package/dist-vue/_chunks/useDialogConfirm-CMTIKoWn.js.map +1 -0
  25. package/dist-vue/api.js +1 -1
  26. package/dist-vue/apps/web-component/src/components/EmbeddedChatWidget.ce.d.ts +3 -3
  27. package/dist-vue/apps/web-component/src/lib.d.ts +3 -1
  28. package/dist-vue/composables.js +2 -2
  29. package/dist-vue/index.js +2875 -2861
  30. package/dist-vue/index.js.map +1 -1
  31. package/dist-vue/locales.js +2 -0
  32. package/dist-vue/packages/auth/src/index.d.ts +4 -4
  33. package/dist-vue/packages/components/src/chat/PkChatbot.d.ts +9 -9
  34. package/dist-vue/packages/components/src/chat/PkChatbotViewChat.d.ts +2 -2
  35. package/dist-vue/packages/composable/src/useDialog.d.ts +2 -0
  36. package/dist-vue/packages/models/src/schema/Agent.d.ts +114 -5
  37. package/dist-vue/packages/models/src/schema/Chat.d.ts +7 -0
  38. package/dist-vue/packages/models/src/schema/OpenAiCompat.d.ts +192 -0
  39. package/dist-vue/packages/models/src/schema/OrganizationExternalAuth.d.ts +7 -0
  40. package/dist-vue/packages/models/src/schema/ReasoningChat.d.ts +6 -0
  41. package/dist-vue/packages/models/src/schema/SubAgent.d.ts +6 -0
  42. package/dist-vue/packages/models/src/schema/TimingLog.d.ts +153 -0
  43. package/dist-vue/packages/models/src/schema/UserOverview.d.ts +1 -1
  44. package/dist-vue/packages/models/src/schema/index.d.ts +2 -0
  45. package/dist-vue/style.css +1 -1
  46. package/package.json +1 -1
  47. package/dist-vue/_chunks/PkChatbot-bWzP1psN.js +0 -236
  48. package/dist-vue/_chunks/PkChatbot-bWzP1psN.js.map +0 -1
  49. package/dist-vue/_chunks/PkChatbotViewChat-glcxrv5f.js.map +0 -1
  50. package/dist-vue/_chunks/createChatbotApiClient-BGkXm2NB.js.map +0 -1
  51. package/dist-vue/_chunks/useDialogConfirm-DaoekZdk.js.map +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@8wave/ai-elements",
3
3
  "private": false,
4
- "version": "0.100.0",
4
+ "version": "0.101.0-beta.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite --port 8082 --open --host",
@@ -1,236 +0,0 @@
1
- import { t as e } from "./PkStreamingMarkdown-D1HqC0AG.js";
2
- import { t } from "./useChatbotStore-D9b5Fz-e.js";
3
- import { t as n } from "./PkChatbotViewConversations-CNZuX3Rl.js";
4
- import { t as ee } from "./PkChatbotViewChat-glcxrv5f.js";
5
- import { t as r } from "./useShortUrlResolver-DI8ycJiO.js";
6
- import { t as te } from "./PkChatbotViewFiles-CyBqTzjY.js";
7
- import { t as i } from "./useLocalizedString-PfaKjIQY.js";
8
- import { t as ne } from "./PkAvatar-paEJByEA.js";
9
- import { h as a } from "./utils-Cru8TJxe.js";
10
- import { t as o } from "./chatbotProfileContext-CgnFhNPn.js";
11
- import { t as re } from "./PkChatbotViewProfile-BCPCO6mp.js";
12
- import { VvAlert as ie, VvButton as ae, VvButtonGroup as oe, VvDropdown as se, VvDropdownAction as ce, VvIcon as le } from "@volverjs/ui-vue/components";
13
- import { Fragment as ue, computed as s, createBlock as c, createCommentVNode as l, createElementBlock as u, createElementVNode as d, createSlots as f, createTextVNode as p, createVNode as m, defineComponent as h, guardReactiveProps as g, inject as _, mergeModels as v, mergeProps as de, normalizeClass as fe, normalizeProps as y, normalizeStyle as b, openBlock as x, renderList as S, renderSlot as C, toDisplayString as w, toRef as pe, unref as T, useModel as me, useSlots as he, watch as E, watchEffect as ge, withCtx as D } from "vue";
14
- import { useI18n as _e } from "vue-i18n";
15
- import { storeToRefs as ve } from "pinia";
16
- //#region ../../packages/components/src/chat/PkChatbot.vue?vue&type=script&setup=true&lang.ts
17
- var ye = { class: "pk-chatbot__header-start" }, be = ["title"], xe = { key: 1 }, Se = { class: "pk-chatbot__warning-body" }, Ce = { class: "pk-chatbot__warning-header" }, we = { class: "pk-chatbot__warning-title" }, O = { class: "pk-chatbot__warning-text" }, k = {
18
- key: 5,
19
- class: "pk-chatbot__view-auth flex-1 min-h-0 overflow-y-auto"
20
- }, Te = {
21
- key: 7,
22
- class: "pk-chatbot__footer"
23
- }, A = /* @__PURE__ */ h({
24
- __name: "PkChatbot",
25
- props: /*@__PURE__*/ v({
26
- baseUrl: {},
27
- basePath: {},
28
- agentId: {},
29
- agentType: {},
30
- userId: {},
31
- name: {},
32
- model: {},
33
- agentModel: {},
34
- agentTools: {},
35
- agentInterface: {},
36
- actions: {},
37
- revisedAnswers: {},
38
- headers: {},
39
- systemTheme: {},
40
- agentFileUpload: {},
41
- fallbackModels: {},
42
- gatewayOptions: {},
43
- externalContext: {},
44
- modifier: {},
45
- userName: {},
46
- userFilesEnabled: { type: Boolean }
47
- }, {
48
- chatId: {
49
- type: String,
50
- default: void 0
51
- },
52
- chatIdModifiers: {}
53
- }),
54
- emits: /*@__PURE__*/ v([
55
- "message-update",
56
- "show-info",
57
- "revise",
58
- "error"
59
- ], ["update:chatId"]),
60
- setup(h, { expose: v, emit: A }) {
61
- let j = he(), { t: M } = _e({ useScope: "global" }), N = h, P = A, F = me(h, "chatId"), I = t(N.agentId);
62
- r((e) => I.apiClient.resolveShortUrls(e));
63
- let { messages: L, chat: Ee, localChatId: R, activeView: z, chatError: De, isDark: Oe } = ve(I), B = pe(I, "agentId"), { startNewChat: V, stopGeneration: H, regenerate: ke, navigate: U, sendMessage: Ae } = I, W = _(o, void 0), G = s(() => W?.canManageProfile.value ?? !1), je = s(() => !G.value && (W?.canSignIn?.value ?? !1) && j["view-auth"] !== void 0);
64
- E(G, (e) => {
65
- e && z.value === "auth" && U("chat");
66
- }), ge(() => {
67
- I.baseUrl = N.baseUrl, I.basePath = N.basePath, I.name = N.name, I.agentType = N.agentType, I.userId = N.userId, I.agentInterface = N.agentInterface, I.agentModel = N.agentModel, I.agentTools = N.agentTools, I.headers = N.headers, I.revisedAnswers = N.revisedAnswers, I.model = N.model, I.actionsInput = N.actions, I.agentFileUpload = N.agentFileUpload, I.fallbackModels = N.fallbackModels, I.gatewayOptions = N.gatewayOptions, I.externalContext = N.externalContext, I.systemTheme = N.systemTheme, I.hideWelcomeMessage = N.modifier === "fullscreen";
68
- }), E(() => F.value, (e) => {
69
- e && e !== I.localChatId && (I.localChatId = e);
70
- }, { immediate: !0 }), E(R, (e) => {
71
- F.value = e;
72
- }), E(De, (e) => {
73
- if (e) try {
74
- let t = JSON.parse(e.message);
75
- P("error", t);
76
- } catch {}
77
- });
78
- let K = s(() => N.modifier === "fullscreen"), q = s(() => N.agentType === "reasoning"), J = s(() => N.agentInterface?.useColorsForAgentHeader), Y = s(() => ["theme", `theme--${Oe.value ? "dark" : "light"}`]), X = s(() => N.agentInterface?.mainColor), Z = s(() => a(N.agentInterface?.textColor, X.value)), Q = i(() => N.agentInterface?.footerMessage), $ = s(() => z.value === "conversations" ? M("label.conversations") : z.value === "profile" ? M("label.profile") : z.value === "auth" ? M("label.authentication") : z.value === "files" ? M("userFiles.chatFilesTitle") : N.name), Me = s(() => Ee.value.status === "ready"), Ne = s(() => L.value?.[L.value.length - 1]);
79
- return v({
80
- startNewChat: V,
81
- regenerate: ke,
82
- stopGeneration: H,
83
- sendMessage: Ae
84
- }), E([B, R], async ([e, t]) => {
85
- await I.initialize(e, t);
86
- }, { immediate: !0 }), E(z, (e) => {
87
- e === "conversations" ? I.loadConversations(B.value) : e === "files" && I.loadChatFiles();
88
- }), E(Ne, (e) => {
89
- e && P("message-update", e);
90
- }, {
91
- deep: !0,
92
- immediate: !0
93
- }), (t, r) => {
94
- let i = ae, a = le, o = ce, s = se, _ = oe, v = ie;
95
- return x(), u("div", { class: fe(["pk-chatbot", [Y.value, { "pk-chatbot--fullscreen": K.value }]]) }, [
96
- K.value ? l("", !0) : (x(), u("div", {
97
- key: 0,
98
- class: "pk-chatbot__header",
99
- style: b(J.value ? { backgroundColor: X.value } : void 0)
100
- }, [d("div", ye, [T(z) === "chat" ? (x(), c(ne, {
101
- key: 1,
102
- modifiers: "surface ring shrink-0",
103
- class: "pk-chatbot__avatar",
104
- name: h.name,
105
- "img-src": h.agentInterface?.logo
106
- }, null, 8, ["name", "img-src"])) : (x(), u(ue, { key: 0 }, [m(i, de({
107
- modifiers: J.value ? `action-quiet-${Z.value}` : "action-quiet",
108
- icon: "ri:arrow-left-s-line",
109
- title: T(M)("action.goBack")
110
- }, { onClick: r[0] ||= (e) => T(U)("chat") }), null, 16), m(a, {
111
- name: T(z) === "conversations" ? "ri:history-line" : T(z) === "files" ? "ri:attachment-2" : "ri:user-line",
112
- class: "pk-chatbot__nav-icon",
113
- style: b(J.value ? { color: Z.value } : void 0)
114
- }, null, 8, ["name", "style"])], 64)), d("strong", {
115
- class: "pk-chatbot__title",
116
- title: $.value,
117
- style: b(J.value ? { color: Z.value } : void 0)
118
- }, w($.value), 13, be)]), m(_, {
119
- class: "pk-chatbot__header-end",
120
- modifiers: "compact"
121
- }, {
122
- default: D(() => [
123
- C(t.$slots, "header-actions", y(g({
124
- mainColor: X.value,
125
- useColorsForAgentHeader: J.value,
126
- contrastColor: Z.value,
127
- startNewChat: T(V),
128
- stopGeneration: T(H)
129
- }))),
130
- m(s, y(g({
131
- placement: "bottom-end",
132
- modifiers: "menu",
133
- flip: !0,
134
- offset: 3,
135
- strategy: "fixed"
136
- })), {
137
- items: D(() => [
138
- m(o, { onClick: r[1] ||= (e) => T(V)() }, {
139
- default: D(() => [m(a, { name: "ri:edit-box-line" }), p(" " + w(T(M)("action.startNewChat")), 1)]),
140
- _: 1
141
- }),
142
- q.value ? l("", !0) : (x(), c(o, {
143
- key: 0,
144
- onClick: r[2] ||= (e) => T(U)("conversations")
145
- }, {
146
- default: D(() => [m(a, { name: "ri:history-line" }), p(" " + w(T(M)("action.viewRecentChats")), 1)]),
147
- _: 1
148
- })),
149
- (G.value || h.userFilesEnabled) && T(R) ? (x(), c(o, {
150
- key: 1,
151
- onClick: r[3] ||= (e) => T(U)("files")
152
- }, {
153
- default: D(() => [m(a, { name: "ri:attachment-2" }), p(" " + w(T(M)("userFiles.chatFilesTitle")), 1)]),
154
- _: 1
155
- })) : l("", !0),
156
- G.value ? (x(), c(o, {
157
- key: 2,
158
- onClick: r[4] ||= (e) => T(U)("profile")
159
- }, {
160
- default: D(() => [m(a, { name: "ri:user-line" }), p(" " + w(T(M)("label.profile")), 1)]),
161
- _: 1
162
- })) : je.value ? (x(), c(o, {
163
- key: 3,
164
- onClick: r[5] ||= (e) => T(U)("auth")
165
- }, {
166
- default: D(() => [m(a, { name: "ri:account-circle-line" }), p(" " + w(T(M)("action.signIn")), 1)]),
167
- _: 1
168
- })) : l("", !0)
169
- ]),
170
- default: D(() => [m(i, y(g({
171
- modifiers: J.value ? `action-quiet-${Z.value}` : "action-quiet",
172
- icon: "ri:more-2-fill",
173
- disabled: !Me.value,
174
- title: T(M)("action.moreActions")
175
- })), null, 16)]),
176
- _: 1
177
- }, 16),
178
- C(t.$slots, "actions", y(g({
179
- mainColor: X.value,
180
- useColorsForAgentHeader: J.value,
181
- contrastColor: Z.value,
182
- startNewChat: T(V),
183
- stopGeneration: T(H)
184
- })))
185
- ]),
186
- _: 3
187
- })], 4)),
188
- q.value ? (x(), u("div", xe, [d("div", Se, [m(v, { modifiers: "callout" }, {
189
- default: D(() => [d("div", Ce, [m(a, {
190
- name: "ri:alert-line",
191
- class: "pk-chatbot__warning-icon"
192
- }), d("strong", we, w(T(M)("message.warning")), 1)]), d("p", O, w(T(M)("message.reasoningAgent")), 1)]),
193
- _: 1
194
- })])])) : l("", !0),
195
- T(z) === "chat" ? (x(), c(ee, {
196
- key: 2,
197
- "agent-id": h.agentId,
198
- modifier: h.modifier,
199
- "user-name": h.userName,
200
- onShowInfo: r[6] ||= (e) => P("show-info", e),
201
- onRevise: r[7] ||= (e) => P("revise", e)
202
- }, f({ _: 2 }, [S(Object.keys(T(j)), (e) => ({
203
- name: e,
204
- fn: D((n) => [C(t.$slots, e, y(g(n)))])
205
- }))]), 1032, [
206
- "agent-id",
207
- "modifier",
208
- "user-name"
209
- ])) : T(z) === "conversations" ? (x(), c(n, {
210
- key: 3,
211
- "agent-id": h.agentId
212
- }, f({ _: 2 }, [S(Object.keys(T(j)), (e) => ({
213
- name: e,
214
- fn: D((n) => [C(t.$slots, e, y(g(n)))])
215
- }))]), 1032, ["agent-id"])) : T(z) === "files" ? (x(), c(te, {
216
- key: 4,
217
- "agent-id": h.agentId
218
- }, null, 8, ["agent-id"])) : T(z) === "auth" ? (x(), u("div", k, [C(t.$slots, "view-auth")])) : (x(), c(re, {
219
- key: 6,
220
- "agent-id": h.agentId
221
- }, f({ _: 2 }, [S(Object.keys(T(j)), (e) => ({
222
- name: e,
223
- fn: D((n) => [C(t.$slots, e, y(g(n)))])
224
- }))]), 1032, ["agent-id"])),
225
- T(Q) ? (x(), u("div", Te, [m(e, {
226
- markdown: T(Q),
227
- class: "wysiwyg"
228
- }, null, 8, ["markdown"])])) : l("", !0)
229
- ], 2);
230
- };
231
- }
232
- });
233
- //#endregion
234
- export { A as t };
235
-
236
- //# sourceMappingURL=PkChatbot-bWzP1psN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PkChatbot-bWzP1psN.js","names":[],"sources":["../../../../packages/components/src/chat/PkChatbot.vue","../../../../packages/components/src/chat/PkChatbot.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, inject, toRef, watch, watchEffect, useSlots } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type {\n AgentInterface,\n AgentModel,\n AgentFileUpload,\n AgentGatewayOptions,\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 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) */\n modifier?: 'widget' | 'fullscreen'\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.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 isReasoningAgent = computed(() => props.agentType === 'reasoning')\n const useColorsForAgentHeader = computed(\n () => props.agentInterface?.useColorsForAgentHeader,\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 class=\"pk-chatbot\"\n :class=\"[themeClass, { 'pk-chatbot--fullscreen': isFullscreen }]\">\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 <PkAvatar\n v-else\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 <VvDropdown\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 @click=\"startNewChat()\">\n <VvIcon name=\"ri:edit-box-line\" />\n {{ $t('action.startNewChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"!isReasoningAgent\"\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-4);\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-4);\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-4);\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 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 .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 &::after {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-2);\n bottom: calc(-1 * var(--spacing-20));\n height: var(--spacing-20);\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 border-radius: var(--rounded-xxxl); // pill shape\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 // Touch target ≥44px: estende l'hit area dei pulsanti\n // (icona 20px + padding 4px ≈ 28px) senza ingrandire l'icona\n &__button {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--spacing-8));\n }\n }\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { computed, inject, toRef, watch, watchEffect, useSlots } from 'vue'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type {\n AgentInterface,\n AgentModel,\n AgentFileUpload,\n AgentGatewayOptions,\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 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) */\n modifier?: 'widget' | 'fullscreen'\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.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 isReasoningAgent = computed(() => props.agentType === 'reasoning')\n const useColorsForAgentHeader = computed(\n () => props.agentInterface?.useColorsForAgentHeader,\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 class=\"pk-chatbot\"\n :class=\"[themeClass, { 'pk-chatbot--fullscreen': isFullscreen }]\">\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 <PkAvatar\n v-else\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 <VvDropdown\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 @click=\"startNewChat()\">\n <VvIcon name=\"ri:edit-box-line\" />\n {{ $t('action.startNewChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"!isReasoningAgent\"\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-4);\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-4);\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-4);\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 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 .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 &::after {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-2);\n bottom: calc(-1 * var(--spacing-20));\n height: var(--spacing-20);\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 border-radius: var(--rounded-xxxl); // pill shape\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 // Touch target ≥44px: estende l'hit area dei pulsanti\n // (icona 20px + padding 4px ≈ 28px) senza ingrandire l'icona\n &__button {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--spacing-8));\n }\n }\n }\n }\n }\n</style>\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"PkChatbotViewChat-glcxrv5f.js","names":[],"sources":["../../../../packages/composable/src/chatbot/useGreeting.ts","../../../../packages/components/src/chat/PkAuroraCanvas.vue","../../../../packages/components/src/chat/PkAuroraCanvas.vue","../../../../packages/components/src/chat/PkChatbotViewChat.vue","../../../../packages/components/src/chat/PkChatbotViewChat.vue"],"sourcesContent":["import { computed, toValue } from 'vue'\nimport type { ComputedRef, MaybeRefOrGetter } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport type { AgentInterface } from 'models'\n\n/**\n * Resolves the greeting shown in the fullscreen empty state.\n *\n * v1 behavior (docs/specs/public-chat-page.md): uses the first entry of the\n * `agentInterface.greeting` pool and supports only the `{name}` placeholder.\n * Fallback chain: `greeting[0]` → `initialMessage` → default welcome message.\n * Pool rotation and `{timeGreeting}` are planned follow-ups.\n */\nexport function useGreeting(\n agentInterface: MaybeRefOrGetter<AgentInterface | undefined>,\n userName: MaybeRefOrGetter<string | undefined>,\n): ComputedRef<string> {\n const { locale, t, te } = useI18n({ useScope: 'global' })\n\n return computed(() => {\n const ui = toValue(agentInterface)\n const template =\n ui?.greeting?.[0]?.[locale.value] ||\n ui?.initialMessage?.[locale.value] ||\n (te('message.defaultAgentWelcome')\n ? t('message.defaultAgentWelcome')\n : '')\n return resolveNamePlaceholder(template, toValue(userName))\n })\n}\n\nfunction resolveNamePlaceholder(template: string, name?: string): string {\n const result = name\n ? template.replace(/\\{name\\}/g, name)\n : // No name available: drop the placeholder along with any\n // adjacent punctuation (\", {name}!\" → \"\")\n template.replace(/[,\\s]*\\{name\\}[!?]?/g, '')\n return result.trim()\n}\n","<script setup lang=\"ts\">\n import {\n onBeforeUnmount,\n onMounted,\n onUnmounted,\n ref,\n useTemplateRef,\n } from 'vue'\n\n /**\n * Ambient animated \"aurora\" rendered with a raw WebGL fragment shader\n * (simplex noise blending two tints of the inherited `color`). No\n * dependencies; falls back to CSS blobs when WebGL is unavailable.\n * The accent comes from the CSS cascade: set `color` on the host.\n */\n\n const props = defineProps<{\n /** CSS selector (within the positioned parent) of the element the glow radiates from; defaults to the positioned parent box itself */\n sourceSelector?: string\n }>()\n\n const canvasEl = useTemplateRef<HTMLCanvasElement>('canvasEl')\n const failed = ref(false)\n\n const VERTEX_SHADER = `\nattribute vec2 a_position;\nvoid main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n}`\n\n // snoise: 2D simplex noise by Ian McEwan / Stefan Gustavson (MIT)\n const FRAGMENT_SHADER = `\nprecision mediump float;\nuniform vec2 u_resolution;\nuniform float u_time;\nuniform vec3 u_color;\nuniform vec2 u_center;\nuniform vec2 u_half;\nuniform float u_spread;\n\nvec3 permute(vec3 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); }\n\nfloat snoise(vec2 v) {\n const vec4 C = vec4(0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439);\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0));\n vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)), 0.0);\n m = m * m;\n m = m * m;\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nvoid main() {\n vec2 px = gl_FragCoord.xy;\n vec2 rel = px - u_center;\n\n // Ignition: the ellipse lights up expanding from the center on mount\n float intro = smoothstep(0.0, 1.4, u_time);\n\n // Breathing: size and brightness oscillate gently but visibly\n float breath = 1.0\n + 0.12 * sin(u_time * 0.55)\n + 0.06 * snoise(vec2(u_time * 0.1, 3.7));\n float pulse = 1.0 + 0.2 * sin(u_time * 0.55 + 1.3);\n\n // Keep it simple (Gemini-style): one huge soft elliptical glow\n // centered on the pill\n vec2 radii = vec2(u_half.x * 0.95, u_half.y * 6.5)\n * u_spread\n * breath\n * mix(0.15, 1.0, intro);\n float r = length(rel / max(radii, vec2(1.0)));\n float glow = exp(-r * r * 2.2);\n\n // Never cut hard against the canvas bounds\n vec2 m = min(px, u_resolution - px);\n glow *= smoothstep(0.0, 32.0, min(m.x, m.y));\n\n // Dither: breaks the 8-bit banding rings of the very soft gradient\n // (no CSS blur on the canvas, the noise must fully mask the bands)\n glow = max(glow + (snoise(px * 0.9) - 0.5) * 0.03, 0.0);\n\n float alpha = glow * 0.2 * pulse * intro;\n\n // Premultiplied alpha (default canvas compositing)\n gl_FragColor = vec4(u_color * alpha, alpha);\n}`\n\n let gl: WebGLRenderingContext | null = null\n let rafId = 0\n let resizeObserver: ResizeObserver | null = null\n let timeLocation: WebGLUniformLocation | null = null\n let resolutionLocation: WebGLUniformLocation | null = null\n let centerLocation: WebGLUniformLocation | null = null\n let halfLocation: WebGLUniformLocation | null = null\n let spreadLocation: WebGLUniformLocation | null = null\n const startTime = performance.now()\n\n const compile = (type: number, source: string): WebGLShader | null => {\n if (!gl) {\n return null\n }\n const shader = gl.createShader(type)\n if (!shader) {\n return null\n }\n gl.shaderSource(shader, source)\n gl.compileShader(shader)\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n return null\n }\n return shader\n }\n\n const parseInheritedColor = (): [number, number, number] | null => {\n if (!canvasEl.value) {\n return null\n }\n const raw = getComputedStyle(canvasEl.value).color\n const match = raw.match(/rgba?\\(([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/)\n if (!match) {\n return null\n }\n return [\n Number(match[1]) / 255,\n Number(match[2]) / 255,\n Number(match[3]) / 255,\n ]\n }\n\n const resize = () => {\n if (!gl || !canvasEl.value) {\n return\n }\n // The glow is blurred anyway: cap the DPR to keep the GPU cost low\n const dpr = Math.min(window.devicePixelRatio || 1, 1.5)\n const { clientWidth, clientHeight } = canvasEl.value\n canvasEl.value.width = Math.max(1, Math.round(clientWidth * dpr))\n canvasEl.value.height = Math.max(1, Math.round(clientHeight * dpr))\n gl.viewport(0, 0, canvasEl.value.width, canvasEl.value.height)\n\n // The glow radiates from the source element (by selector within the\n // positioned parent, or the parent box itself): measure it into\n // shader pixel coordinates (gl origin is bottom-left)\n const host = canvasEl.value.parentElement?.offsetParent\n const source =\n (props.sourceSelector\n ? host?.querySelector(props.sourceSelector)\n : host) ?? host\n const canvasRect = canvasEl.value.getBoundingClientRect()\n const sourceRect = source?.getBoundingClientRect()\n if (sourceRect && canvasRect.width > 0) {\n const scaleX = canvasEl.value.width / canvasRect.width\n const scaleY = canvasEl.value.height / canvasRect.height\n const centerX =\n (sourceRect.left + sourceRect.width / 2 - canvasRect.left) *\n scaleX\n const centerY =\n (canvasRect.bottom - (sourceRect.top + sourceRect.height / 2)) *\n scaleY\n const halfWidth = (sourceRect.width / 2) * scaleX\n const halfHeight = (sourceRect.height / 2) * scaleY\n gl.uniform2f(centerLocation, centerX, centerY)\n gl.uniform2f(halfLocation, halfWidth, halfHeight)\n // Optional responsive boost of the glow size (e.g. mobile)\n const spread = Number.parseFloat(\n getComputedStyle(canvasEl.value).getPropertyValue(\n '--aurora-spread',\n ),\n )\n gl.uniform1f(spreadLocation, Number.isNaN(spread) ? 1 : spread)\n }\n\n // Redraw before the next paint: setting the canvas size reallocates\n // the drawing buffer, and presenting it undrawn can flash as an\n // uninitialized (white) frame. With reduced motion (no loop) this\n // also keeps the settled static frame across resizes.\n drawFrame(prefersReducedMotion() ? 10 : undefined)\n }\n\n const prefersReducedMotion = () =>\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n\n const drawFrame = (timeSeconds?: number) => {\n if (!gl || !canvasEl.value) {\n return\n }\n gl.uniform1f(\n timeLocation,\n timeSeconds ?? (performance.now() - startTime) / 1000,\n )\n gl.uniform2f(\n resolutionLocation,\n canvasEl.value.width,\n canvasEl.value.height,\n )\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)\n }\n\n const loop = () => {\n drawFrame()\n rafId = requestAnimationFrame(loop)\n }\n\n onMounted(() => {\n const canvas = canvasEl.value\n gl = canvas?.getContext('webgl', { alpha: true }) ?? null\n const color = parseInheritedColor()\n if (!canvas || !gl || !color) {\n failed.value = true\n return\n }\n\n const vertex = compile(gl.VERTEX_SHADER, VERTEX_SHADER)\n const fragment = compile(gl.FRAGMENT_SHADER, FRAGMENT_SHADER)\n const program = gl.createProgram()\n if (!vertex || !fragment || !program) {\n failed.value = true\n return\n }\n gl.attachShader(program, vertex)\n gl.attachShader(program, fragment)\n gl.linkProgram(program)\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n failed.value = true\n return\n }\n gl.useProgram(program)\n\n // Fullscreen quad\n const buffer = gl.createBuffer()\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),\n gl.STATIC_DRAW,\n )\n const positionLocation = gl.getAttribLocation(program, 'a_position')\n gl.enableVertexAttribArray(positionLocation)\n gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0)\n\n timeLocation = gl.getUniformLocation(program, 'u_time')\n resolutionLocation = gl.getUniformLocation(program, 'u_resolution')\n centerLocation = gl.getUniformLocation(program, 'u_center')\n halfLocation = gl.getUniformLocation(program, 'u_half')\n spreadLocation = gl.getUniformLocation(program, 'u_spread')\n gl.uniform3f(gl.getUniformLocation(program, 'u_color'), ...color)\n\n resizeObserver = new ResizeObserver(resize)\n resizeObserver.observe(canvas)\n resize()\n\n if (prefersReducedMotion()) {\n // Static frame: the color stays, the motion goes\n drawFrame(10)\n return\n }\n loop()\n })\n\n onBeforeUnmount(() => {\n cancelAnimationFrame(rafId)\n resizeObserver?.disconnect()\n })\n\n // Release the context (Chrome caps live WebGL contexts per page) only\n // after the compositor committed the removal: dropping the backing\n // buffer while the last frame is still on screen paints an undefined\n // (white) frame — a visible flash on the dark theme\n onUnmounted(() => {\n const context = gl\n gl = null\n setTimeout(() => {\n context?.getExtension('WEBGL_lose_context')?.loseContext()\n }, 150)\n })\n</script>\n\n<template>\n <div class=\"pk-aurora\" aria-hidden=\"true\">\n <canvas v-if=\"!failed\" ref=\"canvasEl\" class=\"pk-aurora__canvas\" />\n <div v-else class=\"pk-aurora__fallback\" />\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-aurora {\n position: absolute;\n inset: 0;\n // The accent resolved by the canvas (and the CSS fallback)\n color: var(--chat-page-accent, var(--color-brand));\n pointer-events: none;\n\n &__canvas {\n width: 100%;\n height: 100%;\n // No CSS filter on purpose: a filtered accelerated canvas gets\n // its own compositor surface, which Chrome can present\n // uninitialized (a white flash) when the layer is created or\n // torn down. The glow is smoothed in the shader instead.\n }\n\n // CSS blobs when WebGL is unavailable\n &__fallback {\n position: absolute;\n inset: 0;\n background:\n radial-gradient(\n 45% 65% at 32% 38%,\n color-mix(in srgb, currentColor 26%, transparent),\n transparent 70%\n ),\n radial-gradient(\n 40% 55% at 68% 62%,\n color-mix(in srgb, currentColor 16%, transparent),\n transparent 70%\n );\n filter: blur(48px);\n animation: pk-aurora-drift 14s var(--ease-in-out) infinite alternate;\n\n @media (prefers-reduced-motion: reduce) {\n animation: none;\n }\n }\n }\n\n @keyframes pk-aurora-drift {\n from {\n transform: rotate(-10deg) scale(1);\n }\n\n to {\n transform: rotate(10deg) scale(1.2);\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import {\n onBeforeUnmount,\n onMounted,\n onUnmounted,\n ref,\n useTemplateRef,\n } from 'vue'\n\n /**\n * Ambient animated \"aurora\" rendered with a raw WebGL fragment shader\n * (simplex noise blending two tints of the inherited `color`). No\n * dependencies; falls back to CSS blobs when WebGL is unavailable.\n * The accent comes from the CSS cascade: set `color` on the host.\n */\n\n const props = defineProps<{\n /** CSS selector (within the positioned parent) of the element the glow radiates from; defaults to the positioned parent box itself */\n sourceSelector?: string\n }>()\n\n const canvasEl = useTemplateRef<HTMLCanvasElement>('canvasEl')\n const failed = ref(false)\n\n const VERTEX_SHADER = `\nattribute vec2 a_position;\nvoid main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n}`\n\n // snoise: 2D simplex noise by Ian McEwan / Stefan Gustavson (MIT)\n const FRAGMENT_SHADER = `\nprecision mediump float;\nuniform vec2 u_resolution;\nuniform float u_time;\nuniform vec3 u_color;\nuniform vec2 u_center;\nuniform vec2 u_half;\nuniform float u_spread;\n\nvec3 permute(vec3 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); }\n\nfloat snoise(vec2 v) {\n const vec4 C = vec4(0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439);\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0));\n vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)), 0.0);\n m = m * m;\n m = m * m;\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nvoid main() {\n vec2 px = gl_FragCoord.xy;\n vec2 rel = px - u_center;\n\n // Ignition: the ellipse lights up expanding from the center on mount\n float intro = smoothstep(0.0, 1.4, u_time);\n\n // Breathing: size and brightness oscillate gently but visibly\n float breath = 1.0\n + 0.12 * sin(u_time * 0.55)\n + 0.06 * snoise(vec2(u_time * 0.1, 3.7));\n float pulse = 1.0 + 0.2 * sin(u_time * 0.55 + 1.3);\n\n // Keep it simple (Gemini-style): one huge soft elliptical glow\n // centered on the pill\n vec2 radii = vec2(u_half.x * 0.95, u_half.y * 6.5)\n * u_spread\n * breath\n * mix(0.15, 1.0, intro);\n float r = length(rel / max(radii, vec2(1.0)));\n float glow = exp(-r * r * 2.2);\n\n // Never cut hard against the canvas bounds\n vec2 m = min(px, u_resolution - px);\n glow *= smoothstep(0.0, 32.0, min(m.x, m.y));\n\n // Dither: breaks the 8-bit banding rings of the very soft gradient\n // (no CSS blur on the canvas, the noise must fully mask the bands)\n glow = max(glow + (snoise(px * 0.9) - 0.5) * 0.03, 0.0);\n\n float alpha = glow * 0.2 * pulse * intro;\n\n // Premultiplied alpha (default canvas compositing)\n gl_FragColor = vec4(u_color * alpha, alpha);\n}`\n\n let gl: WebGLRenderingContext | null = null\n let rafId = 0\n let resizeObserver: ResizeObserver | null = null\n let timeLocation: WebGLUniformLocation | null = null\n let resolutionLocation: WebGLUniformLocation | null = null\n let centerLocation: WebGLUniformLocation | null = null\n let halfLocation: WebGLUniformLocation | null = null\n let spreadLocation: WebGLUniformLocation | null = null\n const startTime = performance.now()\n\n const compile = (type: number, source: string): WebGLShader | null => {\n if (!gl) {\n return null\n }\n const shader = gl.createShader(type)\n if (!shader) {\n return null\n }\n gl.shaderSource(shader, source)\n gl.compileShader(shader)\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n return null\n }\n return shader\n }\n\n const parseInheritedColor = (): [number, number, number] | null => {\n if (!canvasEl.value) {\n return null\n }\n const raw = getComputedStyle(canvasEl.value).color\n const match = raw.match(/rgba?\\(([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/)\n if (!match) {\n return null\n }\n return [\n Number(match[1]) / 255,\n Number(match[2]) / 255,\n Number(match[3]) / 255,\n ]\n }\n\n const resize = () => {\n if (!gl || !canvasEl.value) {\n return\n }\n // The glow is blurred anyway: cap the DPR to keep the GPU cost low\n const dpr = Math.min(window.devicePixelRatio || 1, 1.5)\n const { clientWidth, clientHeight } = canvasEl.value\n canvasEl.value.width = Math.max(1, Math.round(clientWidth * dpr))\n canvasEl.value.height = Math.max(1, Math.round(clientHeight * dpr))\n gl.viewport(0, 0, canvasEl.value.width, canvasEl.value.height)\n\n // The glow radiates from the source element (by selector within the\n // positioned parent, or the parent box itself): measure it into\n // shader pixel coordinates (gl origin is bottom-left)\n const host = canvasEl.value.parentElement?.offsetParent\n const source =\n (props.sourceSelector\n ? host?.querySelector(props.sourceSelector)\n : host) ?? host\n const canvasRect = canvasEl.value.getBoundingClientRect()\n const sourceRect = source?.getBoundingClientRect()\n if (sourceRect && canvasRect.width > 0) {\n const scaleX = canvasEl.value.width / canvasRect.width\n const scaleY = canvasEl.value.height / canvasRect.height\n const centerX =\n (sourceRect.left + sourceRect.width / 2 - canvasRect.left) *\n scaleX\n const centerY =\n (canvasRect.bottom - (sourceRect.top + sourceRect.height / 2)) *\n scaleY\n const halfWidth = (sourceRect.width / 2) * scaleX\n const halfHeight = (sourceRect.height / 2) * scaleY\n gl.uniform2f(centerLocation, centerX, centerY)\n gl.uniform2f(halfLocation, halfWidth, halfHeight)\n // Optional responsive boost of the glow size (e.g. mobile)\n const spread = Number.parseFloat(\n getComputedStyle(canvasEl.value).getPropertyValue(\n '--aurora-spread',\n ),\n )\n gl.uniform1f(spreadLocation, Number.isNaN(spread) ? 1 : spread)\n }\n\n // Redraw before the next paint: setting the canvas size reallocates\n // the drawing buffer, and presenting it undrawn can flash as an\n // uninitialized (white) frame. With reduced motion (no loop) this\n // also keeps the settled static frame across resizes.\n drawFrame(prefersReducedMotion() ? 10 : undefined)\n }\n\n const prefersReducedMotion = () =>\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n\n const drawFrame = (timeSeconds?: number) => {\n if (!gl || !canvasEl.value) {\n return\n }\n gl.uniform1f(\n timeLocation,\n timeSeconds ?? (performance.now() - startTime) / 1000,\n )\n gl.uniform2f(\n resolutionLocation,\n canvasEl.value.width,\n canvasEl.value.height,\n )\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)\n }\n\n const loop = () => {\n drawFrame()\n rafId = requestAnimationFrame(loop)\n }\n\n onMounted(() => {\n const canvas = canvasEl.value\n gl = canvas?.getContext('webgl', { alpha: true }) ?? null\n const color = parseInheritedColor()\n if (!canvas || !gl || !color) {\n failed.value = true\n return\n }\n\n const vertex = compile(gl.VERTEX_SHADER, VERTEX_SHADER)\n const fragment = compile(gl.FRAGMENT_SHADER, FRAGMENT_SHADER)\n const program = gl.createProgram()\n if (!vertex || !fragment || !program) {\n failed.value = true\n return\n }\n gl.attachShader(program, vertex)\n gl.attachShader(program, fragment)\n gl.linkProgram(program)\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n failed.value = true\n return\n }\n gl.useProgram(program)\n\n // Fullscreen quad\n const buffer = gl.createBuffer()\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),\n gl.STATIC_DRAW,\n )\n const positionLocation = gl.getAttribLocation(program, 'a_position')\n gl.enableVertexAttribArray(positionLocation)\n gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0)\n\n timeLocation = gl.getUniformLocation(program, 'u_time')\n resolutionLocation = gl.getUniformLocation(program, 'u_resolution')\n centerLocation = gl.getUniformLocation(program, 'u_center')\n halfLocation = gl.getUniformLocation(program, 'u_half')\n spreadLocation = gl.getUniformLocation(program, 'u_spread')\n gl.uniform3f(gl.getUniformLocation(program, 'u_color'), ...color)\n\n resizeObserver = new ResizeObserver(resize)\n resizeObserver.observe(canvas)\n resize()\n\n if (prefersReducedMotion()) {\n // Static frame: the color stays, the motion goes\n drawFrame(10)\n return\n }\n loop()\n })\n\n onBeforeUnmount(() => {\n cancelAnimationFrame(rafId)\n resizeObserver?.disconnect()\n })\n\n // Release the context (Chrome caps live WebGL contexts per page) only\n // after the compositor committed the removal: dropping the backing\n // buffer while the last frame is still on screen paints an undefined\n // (white) frame — a visible flash on the dark theme\n onUnmounted(() => {\n const context = gl\n gl = null\n setTimeout(() => {\n context?.getExtension('WEBGL_lose_context')?.loseContext()\n }, 150)\n })\n</script>\n\n<template>\n <div class=\"pk-aurora\" aria-hidden=\"true\">\n <canvas v-if=\"!failed\" ref=\"canvasEl\" class=\"pk-aurora__canvas\" />\n <div v-else class=\"pk-aurora__fallback\" />\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-aurora {\n position: absolute;\n inset: 0;\n // The accent resolved by the canvas (and the CSS fallback)\n color: var(--chat-page-accent, var(--color-brand));\n pointer-events: none;\n\n &__canvas {\n width: 100%;\n height: 100%;\n // No CSS filter on purpose: a filtered accelerated canvas gets\n // its own compositor surface, which Chrome can present\n // uninitialized (a white flash) when the layer is created or\n // torn down. The glow is smoothed in the shader instead.\n }\n\n // CSS blobs when WebGL is unavailable\n &__fallback {\n position: absolute;\n inset: 0;\n background:\n radial-gradient(\n 45% 65% at 32% 38%,\n color-mix(in srgb, currentColor 26%, transparent),\n transparent 70%\n ),\n radial-gradient(\n 40% 55% at 68% 62%,\n color-mix(in srgb, currentColor 16%, transparent),\n transparent 70%\n );\n filter: blur(48px);\n animation: pk-aurora-drift 14s var(--ease-in-out) infinite alternate;\n\n @media (prefers-reduced-motion: reduce) {\n animation: none;\n }\n }\n }\n\n @keyframes pk-aurora-drift {\n from {\n transform: rotate(-10deg) scale(1);\n }\n\n to {\n transform: rotate(10deg) scale(1.2);\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { computed, useTemplateRef, defineAsyncComponent } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { useDropZone } from '@vueuse/core'\n import { storeToRefs } from 'pinia'\n import PkAuroraCanvas from './PkAuroraCanvas.vue'\n import PkAvatar from './PkAvatar.vue'\n import PkChatbotMessages from './PkChatbotMessages.vue'\n import PkChatbotInput from './PkChatbotInput.vue'\n import PkChatbotEmptyState from './PkChatbotEmptyState.vue'\n import {\n useChatbotStore,\n useLocalizedString,\n useGreeting,\n } from 'composables'\n import type { UIChatMessage } from 'models'\n\n const PkToolShowForm = defineAsyncComponent(\n () => import('./PkToolShowForm.vue'),\n )\n const PkToolShowContactForm = defineAsyncComponent(\n () => import('./PkToolShowContactForm.vue'),\n )\n const PkToolShowSuggestedReply = defineAsyncComponent(\n () => import('./PkToolShowSuggestedReply.vue'),\n )\n const PkToolShowSources = defineAsyncComponent(\n () => import('./PkToolShowSources.vue'),\n )\n const PkToolRequestGeolocation = defineAsyncComponent(\n () => import('./PkToolRequestGeolocation.vue'),\n )\n const PkToolRequestOAuthConnection = defineAsyncComponent(\n () => import('./PkToolRequestOAuthConnection.vue'),\n )\n const PkToolShowLocation = defineAsyncComponent(\n () => import('./PkToolShowLocation.vue'),\n )\n const PkToolShowDiagram = defineAsyncComponent(\n () => import('./PkToolShowDiagram.vue'),\n )\n\n const props = defineProps<{\n agentId: string\n /** Mirrors the PkChatbot `modifier` prop: `fullscreen` shows a centered empty state (greeting) instead of the welcome message */\n modifier?: 'widget' | 'fullscreen'\n /** Display name of the current user, used for the `{name}` greeting placeholder */\n userName?: string\n }>()\n\n const emit = defineEmits<{\n 'show-info': [message: UIChatMessage]\n revise: [message: UIChatMessage]\n }>()\n\n const store = useChatbotStore(props.agentId)\n\n const {\n agentInterface,\n agentFileUpload,\n actions,\n revisedAnswers,\n messages,\n chat,\n messageFeedbacks,\n feedbackDialogMessage,\n isFeedbackSubmitting,\n isFeedbackSubmitted,\n feedbackSubmitError,\n isLeadSubmitted,\n isLoadingSubmitLead,\n submitLeadError,\n input,\n inputMessagePlaceholder,\n isConversationBlocked,\n baseUrl,\n pendingAttachments,\n isAttachmentLimitReached,\n fileLimitError,\n apiClient,\n isDark,\n } = storeToRefs(store)\n\n const {\n handleSubmit: storeHandleSubmit,\n stopGeneration,\n regenerate,\n onUpvote,\n onDownvote,\n onFeedback,\n onFeedbackSubmit,\n onLeadSubmit,\n startNewChat,\n addToolOutput,\n addToolApprovalResponse,\n handleFileSelect,\n clearFileLimitError,\n } = store\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const attachmentNotice = computed(() => {\n const err = fileLimitError.value\n if (!err) {\n return undefined\n }\n return err.reason === 'per-chat'\n ? $t('message.fileLimitPerChat', { max: err.max })\n : $t('message.fileLimitPerMessage', { max: err.max })\n })\n\n const dismissableNotice = useLocalizedString(\n () => agentInterface.value?.dismissableNotice,\n )\n\n // Fullscreen empty state: shown until the user sends the first message.\n // The synthetic welcome message is suppressed by the store in fullscreen\n // (`hideWelcomeMessage`), the greeting takes its place.\n const hasUserMessages = computed(() =>\n messages.value.some((message) => message.role === 'user'),\n )\n const showEmptyState = computed(\n () => props.modifier === 'fullscreen' && !hasUserMessages.value,\n )\n const greeting = useGreeting(agentInterface, () => props.userName)\n\n const chatViewEl = useTemplateRef<HTMLDivElement>('chatViewEl')\n\n const handleExpandSourceContext = async (payload: {\n documentId: string\n chunkIndex: number\n }) => {\n const result = await apiClient.value.expandSourceContext(\n props.agentId,\n payload.documentId,\n payload.chunkIndex,\n )\n return result.content\n }\n\n const handleDownloadSource = async (documentId: string) => {\n const result = await apiClient.value.downloadSourceDocument(\n props.agentId,\n documentId,\n )\n window.open(result.downloadUrl, '_blank')\n }\n\n const handleFileDrop = (files: File[] | null) => {\n if (!agentFileUpload.value?.enabled || !files) {\n return\n }\n for (const file of files) {\n handleFileSelect(file)\n }\n }\n\n const { isOverDropZone } = useDropZone(chatViewEl, {\n dataTypes: computed(\n () => agentFileUpload.value?.allowedMimeTypes ?? [],\n ),\n onDrop: handleFileDrop,\n })\n</script>\n\n<template>\n <div\n ref=\"chatViewEl\"\n class=\"pk-chatbot-view-chat\"\n :class=\"{\n 'pk-chatbot-view-chat--dragover':\n isOverDropZone && agentFileUpload?.enabled,\n 'pk-chatbot-view-chat--empty': showEmptyState,\n }\">\n <!-- #region empty state (fullscreen) / messages — crossfade out-in -->\n <Transition name=\"pk-chatbot-view-chat-fade\" mode=\"out-in\">\n <PkChatbotEmptyState\n v-if=\"showEmptyState\"\n :greeting=\"greeting\"\n :logo=\"agentInterface?.logo\"\n :name=\"store.name\" />\n <PkChatbotMessages\n v-else\n class=\"flex flex-col flex-1 min-h-0 p-md overflow-y-auto\"\n interactive\n :messages=\"messages\"\n :status=\"chat.status\"\n :error=\"chat.error\"\n :main-color=\"agentInterface?.mainColor\"\n :text-color=\"agentInterface?.textColor\"\n :revised-answers=\"revisedAnswers\"\n :actions=\"actions\"\n :message-feedbacks=\"messageFeedbacks\"\n :feedback-message-id=\"feedbackDialogMessage?.id\"\n :feedback-loading=\"isFeedbackSubmitting\"\n :feedback-submitted=\"isFeedbackSubmitted\"\n :feedback-error=\"feedbackSubmitError\"\n :show-extended-steps=\"agentInterface?.showExtendedSteps\"\n :is-dark=\"isDark\"\n :show-scroll-to-bottom=\"modifier === 'fullscreen'\"\n @feedback-submit=\"onFeedbackSubmit($event)\"\n @feedback-close=\"feedbackDialogMessage = undefined\"\n @regenerate=\"regenerate\"\n @auto-retry=\"regenerate\"\n @reset-chat=\"startNewChat\"\n @show-info=\"emit('show-info', $event)\"\n @revise=\"emit('revise', $event)\"\n @upvote=\"onUpvote\"\n @downvote=\"onDownvote\"\n @feedback=\"onFeedback\"\n @approval-respond=\"addToolApprovalResponse\"\n @confirm-respond=\"\n ({ toolCallId, confirmed }) =>\n addToolOutput({\n tool: 'requestConfirm',\n toolCallId,\n output: confirmed ? 'confirmed' : 'cancelled',\n })\n \">\n <template #tool-showContactForm=\"{ part }\">\n <PkToolShowContactForm\n :part\n :readonly=\"!baseUrl\"\n :submitted=\"isLeadSubmitted\"\n :loading=\"isLoadingSubmitLead\"\n :error=\"submitLeadError\"\n :privacy-policy-notice=\"\n agentInterface?.privacyPolicyNotice\n \"\n @submit=\"onLeadSubmit\" />\n </template>\n <template #tool-showSuggestedReply=\"{ part }\">\n <PkToolShowSuggestedReply\n :part\n @select=\"\n ($event) => {\n input = $event\n storeHandleSubmit()\n }\n \" />\n </template>\n <template #tool-showSources=\"{ part }\">\n <PkToolShowSources\n :part\n :on-expand-context=\"handleExpandSourceContext\"\n :on-download=\"handleDownloadSource\" />\n </template>\n <template #tool-showForm=\"{ part }\">\n <transition mode=\"out-in\">\n <PkToolShowForm\n :part\n @select=\"\n addToolOutput({\n tool: 'showForm',\n toolCallId: (part as any).toolCallId,\n output: $event,\n })\n \" />\n </transition>\n </template>\n <template #tool-showMultipleChoice=\"{ part }\">\n <transition mode=\"out-in\">\n <PkToolShowForm\n :part\n allow-custom-answer\n @select=\"\n addToolOutput({\n tool: 'showMultipleChoice',\n toolCallId: (part as any).toolCallId,\n output: $event,\n })\n \" />\n </transition>\n </template>\n <template #tool-requestOAuthConnection=\"{ part }\">\n <PkToolRequestOAuthConnection\n :part\n :resolve-connection=\"\n (serverName: string) =>\n apiClient.getOAuthAuthorizeUrl(\n props.agentId,\n serverName,\n )\n \"\n @connected=\"\n addToolOutput({\n tool: 'requestOAuthConnection',\n toolCallId: (part as any).toolCallId,\n output: {\n connected: true,\n mcpServerId: $event,\n },\n })\n \" />\n </template>\n <template #tool-requestGeolocation=\"{ part }\">\n <PkToolRequestGeolocation\n :part\n :reverse-geocode=\"\n (lat: number, lon: number) =>\n apiClient.reverseGeocode(lat, lon)\n \"\n @result=\"\n addToolOutput({\n tool: 'requestGeolocation',\n toolCallId: (part as any).toolCallId,\n output: $event,\n })\n \" />\n </template>\n <template #tool-showLocation=\"{ part }\">\n <PkToolShowLocation\n :part\n :is-dark\n :main-color=\"agentInterface?.mainColor\"\n :forward-geocode=\"\n (query: string, lang?: string) =>\n apiClient.forwardGeocode(query, lang)\n \" />\n </template>\n <template #tool-showDiagram=\"{ part }\">\n <PkToolShowDiagram :part :is-dark />\n </template>\n </PkChatbotMessages>\n </Transition>\n <!-- #endregion -->\n\n <!-- #region input -->\n <div class=\"pk-chatbot-view-chat__input-stage\">\n <!-- Animated accent glow radiating from the input pill -->\n <Transition name=\"pk-chatbot-view-chat-fade\">\n <PkAuroraCanvas\n v-if=\"showEmptyState\"\n class=\"pk-chatbot-view-chat__aurora\"\n source-selector=\".pk-chatbot-input__form\" />\n </Transition>\n <div\n v-if=\"isConversationBlocked\"\n class=\"p-md border-t border-surface-3 text-center text-12 text-danger-darken-2 bg-surface-danger\">\n {{ $t('message.chatErrorConversationBlocked') }}\n </div>\n <PkChatbotInput\n v-else\n v-model=\"input\"\n v-model:pending-attachments=\"pendingAttachments\"\n :placeholder=\"inputMessagePlaceholder\"\n :dismissable-notice=\"\n dismissableNotice && chat.messages.length <= 1\n ? dismissableNotice\n : undefined\n \"\n :status=\"chat.status\"\n :max-message-length=\"agentInterface?.maxMessageLength\"\n :file-upload=\"agentFileUpload\"\n :attachment-limit-reached=\"isAttachmentLimitReached\"\n :attachment-notice=\"attachmentNotice\"\n @stop-generation=\"stopGeneration\"\n @submit=\"storeHandleSubmit\"\n @file-select=\"handleFileSelect\"\n @dismiss-attachment-notice=\"clearFileLimitError\">\n <!-- Fullscreen has no header: the agent identity lives in the\n input pill -->\n <template v-if=\"modifier === 'fullscreen'\" #prepend>\n <span class=\"pk-chatbot-view-chat__agent-chip\">\n <PkAvatar\n modifiers=\"surface\"\n class=\"pk-chatbot-view-chat__agent-chip-avatar\"\n :img-src=\"agentInterface?.logo\"\n :name=\"store.name\" />\n <strong\n v-if=\"store.name\"\n class=\"pk-chatbot-view-chat__agent-chip-name\">\n {{ store.name }}\n </strong>\n </span>\n </template>\n </PkChatbotInput>\n </div>\n <!-- #endregion -->\n <Transition>\n <div\n v-if=\"isOverDropZone && agentFileUpload?.enabled\"\n class=\"pk-chatbot-view-chat__drop-overlay\">\n <VvIcon\n name=\"ri:upload-cloud-2-line\"\n class=\"pk-chatbot-view-chat__drop-overlay-icon\" />\n <span>{{ $t('action.dropFile') }}</span>\n </div>\n </Transition>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-view-chat {\n position: relative;\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n\n // Empty state (fullscreen): center the greeting + input block\n // vertically in the available space\n &--empty {\n justify-content: center;\n gap: var(--spacing-md);\n\n // On phones the input docks to the bottom edge instead\n // (Gemini-like), with the greeting centered in the space above\n @include media-breakpoint-down('xs', $breakpoints) {\n justify-content: flex-start;\n\n .pk-chatbot-empty-state {\n flex: 1;\n justify-content: center;\n }\n }\n }\n\n // Anchors the glow to the input pill\n &__input-stage {\n position: relative;\n }\n\n // Radiates outwards from the pill, biased upwards (the pill itself\n // paints above, being later in the DOM)\n &__input-stage &__aurora {\n inset: calc(-1 * var(--spacing-224)) calc(-1 * var(--spacing-96))\n calc(-1 * var(--spacing-160));\n\n // With the input docked at the bottom the glow becomes a wide\n // dome filling the lower part of the screen (Gemini-like)\n @include media-breakpoint-down('xs', $breakpoints) {\n --aurora-spread: 1.6;\n\n // Tall enough that the glow tail never meets the canvas\n // edge fade (it would draw a visible horizontal band)\n inset: calc(-1 * var(--spacing-384) - var(--spacing-128))\n calc(-1 * var(--spacing-96)) calc(-1 * var(--spacing-160));\n }\n }\n\n // Agent identity inside the input pill (fullscreen has no header)\n &__agent-chip {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-6);\n flex-shrink: 0;\n padding: var(--spacing-4) var(--spacing-10) var(--spacing-4)\n var(--spacing-4);\n border: 1px solid var(--color-surface-3);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface);\n\n // Don't crowd the textarea on small screens\n @include media-breakpoint-down('md', $breakpoints) {\n display: none;\n }\n }\n\n &__agent-chip-avatar {\n width: var(--spacing-24);\n height: var(--spacing-24);\n font-size: var(--text-10);\n border-radius: var(--rounded-full);\n }\n\n &__agent-chip-name {\n font-size: var(--text-12);\n font-weight: var(--font-semibold);\n color: var(--color-word-2);\n white-space: nowrap;\n }\n\n // Crossfade empty state ↔ messages: exit faster than enter so the\n // switch never feels like a snap\n &-fade-enter-active {\n transition: opacity 250ms var(--ease-out);\n }\n\n &-fade-leave-active {\n transition: opacity 150ms var(--ease-out);\n }\n\n &-fade-enter-from,\n &-fade-leave-to {\n opacity: 0;\n }\n\n @media (prefers-reduced-motion: reduce) {\n &-fade-enter-active,\n &-fade-leave-active {\n transition: none;\n }\n }\n\n &__drop-overlay {\n position: absolute;\n inset: var(--spacing-sm) var(--spacing-sm) var(--spacing-sm)\n var(--spacing-sm);\n z-index: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-xs);\n background-color: color-mix(\n in srgb,\n var(--color-surface) 85%,\n transparent\n );\n border-radius: var(--rounded-xl);\n border: var(--spacing-2) dashed var(--color-surface-5);\n pointer-events: none;\n color: var(--color-word-3);\n\n &-icon {\n font-size: var(--spacing-32);\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import { computed, useTemplateRef, defineAsyncComponent } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { useDropZone } from '@vueuse/core'\n import { storeToRefs } from 'pinia'\n import PkAuroraCanvas from './PkAuroraCanvas.vue'\n import PkAvatar from './PkAvatar.vue'\n import PkChatbotMessages from './PkChatbotMessages.vue'\n import PkChatbotInput from './PkChatbotInput.vue'\n import PkChatbotEmptyState from './PkChatbotEmptyState.vue'\n import {\n useChatbotStore,\n useLocalizedString,\n useGreeting,\n } from 'composables'\n import type { UIChatMessage } from 'models'\n\n const PkToolShowForm = defineAsyncComponent(\n () => import('./PkToolShowForm.vue'),\n )\n const PkToolShowContactForm = defineAsyncComponent(\n () => import('./PkToolShowContactForm.vue'),\n )\n const PkToolShowSuggestedReply = defineAsyncComponent(\n () => import('./PkToolShowSuggestedReply.vue'),\n )\n const PkToolShowSources = defineAsyncComponent(\n () => import('./PkToolShowSources.vue'),\n )\n const PkToolRequestGeolocation = defineAsyncComponent(\n () => import('./PkToolRequestGeolocation.vue'),\n )\n const PkToolRequestOAuthConnection = defineAsyncComponent(\n () => import('./PkToolRequestOAuthConnection.vue'),\n )\n const PkToolShowLocation = defineAsyncComponent(\n () => import('./PkToolShowLocation.vue'),\n )\n const PkToolShowDiagram = defineAsyncComponent(\n () => import('./PkToolShowDiagram.vue'),\n )\n\n const props = defineProps<{\n agentId: string\n /** Mirrors the PkChatbot `modifier` prop: `fullscreen` shows a centered empty state (greeting) instead of the welcome message */\n modifier?: 'widget' | 'fullscreen'\n /** Display name of the current user, used for the `{name}` greeting placeholder */\n userName?: string\n }>()\n\n const emit = defineEmits<{\n 'show-info': [message: UIChatMessage]\n revise: [message: UIChatMessage]\n }>()\n\n const store = useChatbotStore(props.agentId)\n\n const {\n agentInterface,\n agentFileUpload,\n actions,\n revisedAnswers,\n messages,\n chat,\n messageFeedbacks,\n feedbackDialogMessage,\n isFeedbackSubmitting,\n isFeedbackSubmitted,\n feedbackSubmitError,\n isLeadSubmitted,\n isLoadingSubmitLead,\n submitLeadError,\n input,\n inputMessagePlaceholder,\n isConversationBlocked,\n baseUrl,\n pendingAttachments,\n isAttachmentLimitReached,\n fileLimitError,\n apiClient,\n isDark,\n } = storeToRefs(store)\n\n const {\n handleSubmit: storeHandleSubmit,\n stopGeneration,\n regenerate,\n onUpvote,\n onDownvote,\n onFeedback,\n onFeedbackSubmit,\n onLeadSubmit,\n startNewChat,\n addToolOutput,\n addToolApprovalResponse,\n handleFileSelect,\n clearFileLimitError,\n } = store\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const attachmentNotice = computed(() => {\n const err = fileLimitError.value\n if (!err) {\n return undefined\n }\n return err.reason === 'per-chat'\n ? $t('message.fileLimitPerChat', { max: err.max })\n : $t('message.fileLimitPerMessage', { max: err.max })\n })\n\n const dismissableNotice = useLocalizedString(\n () => agentInterface.value?.dismissableNotice,\n )\n\n // Fullscreen empty state: shown until the user sends the first message.\n // The synthetic welcome message is suppressed by the store in fullscreen\n // (`hideWelcomeMessage`), the greeting takes its place.\n const hasUserMessages = computed(() =>\n messages.value.some((message) => message.role === 'user'),\n )\n const showEmptyState = computed(\n () => props.modifier === 'fullscreen' && !hasUserMessages.value,\n )\n const greeting = useGreeting(agentInterface, () => props.userName)\n\n const chatViewEl = useTemplateRef<HTMLDivElement>('chatViewEl')\n\n const handleExpandSourceContext = async (payload: {\n documentId: string\n chunkIndex: number\n }) => {\n const result = await apiClient.value.expandSourceContext(\n props.agentId,\n payload.documentId,\n payload.chunkIndex,\n )\n return result.content\n }\n\n const handleDownloadSource = async (documentId: string) => {\n const result = await apiClient.value.downloadSourceDocument(\n props.agentId,\n documentId,\n )\n window.open(result.downloadUrl, '_blank')\n }\n\n const handleFileDrop = (files: File[] | null) => {\n if (!agentFileUpload.value?.enabled || !files) {\n return\n }\n for (const file of files) {\n handleFileSelect(file)\n }\n }\n\n const { isOverDropZone } = useDropZone(chatViewEl, {\n dataTypes: computed(\n () => agentFileUpload.value?.allowedMimeTypes ?? [],\n ),\n onDrop: handleFileDrop,\n })\n</script>\n\n<template>\n <div\n ref=\"chatViewEl\"\n class=\"pk-chatbot-view-chat\"\n :class=\"{\n 'pk-chatbot-view-chat--dragover':\n isOverDropZone && agentFileUpload?.enabled,\n 'pk-chatbot-view-chat--empty': showEmptyState,\n }\">\n <!-- #region empty state (fullscreen) / messages — crossfade out-in -->\n <Transition name=\"pk-chatbot-view-chat-fade\" mode=\"out-in\">\n <PkChatbotEmptyState\n v-if=\"showEmptyState\"\n :greeting=\"greeting\"\n :logo=\"agentInterface?.logo\"\n :name=\"store.name\" />\n <PkChatbotMessages\n v-else\n class=\"flex flex-col flex-1 min-h-0 p-md overflow-y-auto\"\n interactive\n :messages=\"messages\"\n :status=\"chat.status\"\n :error=\"chat.error\"\n :main-color=\"agentInterface?.mainColor\"\n :text-color=\"agentInterface?.textColor\"\n :revised-answers=\"revisedAnswers\"\n :actions=\"actions\"\n :message-feedbacks=\"messageFeedbacks\"\n :feedback-message-id=\"feedbackDialogMessage?.id\"\n :feedback-loading=\"isFeedbackSubmitting\"\n :feedback-submitted=\"isFeedbackSubmitted\"\n :feedback-error=\"feedbackSubmitError\"\n :show-extended-steps=\"agentInterface?.showExtendedSteps\"\n :is-dark=\"isDark\"\n :show-scroll-to-bottom=\"modifier === 'fullscreen'\"\n @feedback-submit=\"onFeedbackSubmit($event)\"\n @feedback-close=\"feedbackDialogMessage = undefined\"\n @regenerate=\"regenerate\"\n @auto-retry=\"regenerate\"\n @reset-chat=\"startNewChat\"\n @show-info=\"emit('show-info', $event)\"\n @revise=\"emit('revise', $event)\"\n @upvote=\"onUpvote\"\n @downvote=\"onDownvote\"\n @feedback=\"onFeedback\"\n @approval-respond=\"addToolApprovalResponse\"\n @confirm-respond=\"\n ({ toolCallId, confirmed }) =>\n addToolOutput({\n tool: 'requestConfirm',\n toolCallId,\n output: confirmed ? 'confirmed' : 'cancelled',\n })\n \">\n <template #tool-showContactForm=\"{ part }\">\n <PkToolShowContactForm\n :part\n :readonly=\"!baseUrl\"\n :submitted=\"isLeadSubmitted\"\n :loading=\"isLoadingSubmitLead\"\n :error=\"submitLeadError\"\n :privacy-policy-notice=\"\n agentInterface?.privacyPolicyNotice\n \"\n @submit=\"onLeadSubmit\" />\n </template>\n <template #tool-showSuggestedReply=\"{ part }\">\n <PkToolShowSuggestedReply\n :part\n @select=\"\n ($event) => {\n input = $event\n storeHandleSubmit()\n }\n \" />\n </template>\n <template #tool-showSources=\"{ part }\">\n <PkToolShowSources\n :part\n :on-expand-context=\"handleExpandSourceContext\"\n :on-download=\"handleDownloadSource\" />\n </template>\n <template #tool-showForm=\"{ part }\">\n <transition mode=\"out-in\">\n <PkToolShowForm\n :part\n @select=\"\n addToolOutput({\n tool: 'showForm',\n toolCallId: (part as any).toolCallId,\n output: $event,\n })\n \" />\n </transition>\n </template>\n <template #tool-showMultipleChoice=\"{ part }\">\n <transition mode=\"out-in\">\n <PkToolShowForm\n :part\n allow-custom-answer\n @select=\"\n addToolOutput({\n tool: 'showMultipleChoice',\n toolCallId: (part as any).toolCallId,\n output: $event,\n })\n \" />\n </transition>\n </template>\n <template #tool-requestOAuthConnection=\"{ part }\">\n <PkToolRequestOAuthConnection\n :part\n :resolve-connection=\"\n (serverName: string) =>\n apiClient.getOAuthAuthorizeUrl(\n props.agentId,\n serverName,\n )\n \"\n @connected=\"\n addToolOutput({\n tool: 'requestOAuthConnection',\n toolCallId: (part as any).toolCallId,\n output: {\n connected: true,\n mcpServerId: $event,\n },\n })\n \" />\n </template>\n <template #tool-requestGeolocation=\"{ part }\">\n <PkToolRequestGeolocation\n :part\n :reverse-geocode=\"\n (lat: number, lon: number) =>\n apiClient.reverseGeocode(lat, lon)\n \"\n @result=\"\n addToolOutput({\n tool: 'requestGeolocation',\n toolCallId: (part as any).toolCallId,\n output: $event,\n })\n \" />\n </template>\n <template #tool-showLocation=\"{ part }\">\n <PkToolShowLocation\n :part\n :is-dark\n :main-color=\"agentInterface?.mainColor\"\n :forward-geocode=\"\n (query: string, lang?: string) =>\n apiClient.forwardGeocode(query, lang)\n \" />\n </template>\n <template #tool-showDiagram=\"{ part }\">\n <PkToolShowDiagram :part :is-dark />\n </template>\n </PkChatbotMessages>\n </Transition>\n <!-- #endregion -->\n\n <!-- #region input -->\n <div class=\"pk-chatbot-view-chat__input-stage\">\n <!-- Animated accent glow radiating from the input pill -->\n <Transition name=\"pk-chatbot-view-chat-fade\">\n <PkAuroraCanvas\n v-if=\"showEmptyState\"\n class=\"pk-chatbot-view-chat__aurora\"\n source-selector=\".pk-chatbot-input__form\" />\n </Transition>\n <div\n v-if=\"isConversationBlocked\"\n class=\"p-md border-t border-surface-3 text-center text-12 text-danger-darken-2 bg-surface-danger\">\n {{ $t('message.chatErrorConversationBlocked') }}\n </div>\n <PkChatbotInput\n v-else\n v-model=\"input\"\n v-model:pending-attachments=\"pendingAttachments\"\n :placeholder=\"inputMessagePlaceholder\"\n :dismissable-notice=\"\n dismissableNotice && chat.messages.length <= 1\n ? dismissableNotice\n : undefined\n \"\n :status=\"chat.status\"\n :max-message-length=\"agentInterface?.maxMessageLength\"\n :file-upload=\"agentFileUpload\"\n :attachment-limit-reached=\"isAttachmentLimitReached\"\n :attachment-notice=\"attachmentNotice\"\n @stop-generation=\"stopGeneration\"\n @submit=\"storeHandleSubmit\"\n @file-select=\"handleFileSelect\"\n @dismiss-attachment-notice=\"clearFileLimitError\">\n <!-- Fullscreen has no header: the agent identity lives in the\n input pill -->\n <template v-if=\"modifier === 'fullscreen'\" #prepend>\n <span class=\"pk-chatbot-view-chat__agent-chip\">\n <PkAvatar\n modifiers=\"surface\"\n class=\"pk-chatbot-view-chat__agent-chip-avatar\"\n :img-src=\"agentInterface?.logo\"\n :name=\"store.name\" />\n <strong\n v-if=\"store.name\"\n class=\"pk-chatbot-view-chat__agent-chip-name\">\n {{ store.name }}\n </strong>\n </span>\n </template>\n </PkChatbotInput>\n </div>\n <!-- #endregion -->\n <Transition>\n <div\n v-if=\"isOverDropZone && agentFileUpload?.enabled\"\n class=\"pk-chatbot-view-chat__drop-overlay\">\n <VvIcon\n name=\"ri:upload-cloud-2-line\"\n class=\"pk-chatbot-view-chat__drop-overlay-icon\" />\n <span>{{ $t('action.dropFile') }}</span>\n </div>\n </Transition>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-view-chat {\n position: relative;\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n\n // Empty state (fullscreen): center the greeting + input block\n // vertically in the available space\n &--empty {\n justify-content: center;\n gap: var(--spacing-md);\n\n // On phones the input docks to the bottom edge instead\n // (Gemini-like), with the greeting centered in the space above\n @include media-breakpoint-down('xs', $breakpoints) {\n justify-content: flex-start;\n\n .pk-chatbot-empty-state {\n flex: 1;\n justify-content: center;\n }\n }\n }\n\n // Anchors the glow to the input pill\n &__input-stage {\n position: relative;\n }\n\n // Radiates outwards from the pill, biased upwards (the pill itself\n // paints above, being later in the DOM)\n &__input-stage &__aurora {\n inset: calc(-1 * var(--spacing-224)) calc(-1 * var(--spacing-96))\n calc(-1 * var(--spacing-160));\n\n // With the input docked at the bottom the glow becomes a wide\n // dome filling the lower part of the screen (Gemini-like)\n @include media-breakpoint-down('xs', $breakpoints) {\n --aurora-spread: 1.6;\n\n // Tall enough that the glow tail never meets the canvas\n // edge fade (it would draw a visible horizontal band)\n inset: calc(-1 * var(--spacing-384) - var(--spacing-128))\n calc(-1 * var(--spacing-96)) calc(-1 * var(--spacing-160));\n }\n }\n\n // Agent identity inside the input pill (fullscreen has no header)\n &__agent-chip {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-6);\n flex-shrink: 0;\n padding: var(--spacing-4) var(--spacing-10) var(--spacing-4)\n var(--spacing-4);\n border: 1px solid var(--color-surface-3);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface);\n\n // Don't crowd the textarea on small screens\n @include media-breakpoint-down('md', $breakpoints) {\n display: none;\n }\n }\n\n &__agent-chip-avatar {\n width: var(--spacing-24);\n height: var(--spacing-24);\n font-size: var(--text-10);\n border-radius: var(--rounded-full);\n }\n\n &__agent-chip-name {\n font-size: var(--text-12);\n font-weight: var(--font-semibold);\n color: var(--color-word-2);\n white-space: nowrap;\n }\n\n // Crossfade empty state ↔ messages: exit faster than enter so the\n // switch never feels like a snap\n &-fade-enter-active {\n transition: opacity 250ms var(--ease-out);\n }\n\n &-fade-leave-active {\n transition: opacity 150ms var(--ease-out);\n }\n\n &-fade-enter-from,\n &-fade-leave-to {\n opacity: 0;\n }\n\n @media (prefers-reduced-motion: reduce) {\n &-fade-enter-active,\n &-fade-leave-active {\n transition: none;\n }\n }\n\n &__drop-overlay {\n position: absolute;\n inset: var(--spacing-sm) var(--spacing-sm) var(--spacing-sm)\n var(--spacing-sm);\n z-index: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-xs);\n background-color: color-mix(\n in srgb,\n var(--color-surface) 85%,\n transparent\n );\n border-radius: var(--rounded-xl);\n border: var(--spacing-2) dashed var(--color-surface-5);\n pointer-events: none;\n color: var(--color-word-3);\n\n &-icon {\n font-size: var(--spacing-32);\n }\n }\n }\n</style>\n"],"mappings":"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"}