@8wave/ai-elements 0.117.0-beta.12 → 0.117.0-beta.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{Agent-BxyaZeuL.js → Agent-CFkAjuTy.js} +204 -168
- package/dist/_chunks/Agent-CFkAjuTy.js.map +1 -0
- package/dist/_chunks/{PkStreamingMarkdown-B8mvCNou.js → PkStreamingMarkdown-BoVR7gtl.js} +670 -644
- package/dist/_chunks/PkStreamingMarkdown-BoVR7gtl.js.map +1 -0
- package/dist/_chunks/{PkToolEditSpreadsheet-Bp-ES8B0.js → PkToolEditSpreadsheet-BlfsM7Qp.js} +2 -2
- package/dist/_chunks/{PkToolEditSpreadsheet-Bp-ES8B0.js.map → PkToolEditSpreadsheet-BlfsM7Qp.js.map} +1 -1
- package/dist/_chunks/{PkToolRequestGeolocation-DcPMMyJH.js → PkToolRequestGeolocation-DdQ91rpB.js} +2 -2
- package/dist/_chunks/{PkToolRequestGeolocation-DcPMMyJH.js.map → PkToolRequestGeolocation-DdQ91rpB.js.map} +1 -1
- package/dist/_chunks/{PkToolRequestOAuthConnection-Cc2qViGX.js → PkToolRequestOAuthConnection-yuTNMEFj.js} +2 -2
- package/dist/_chunks/{PkToolRequestOAuthConnection-Cc2qViGX.js.map → PkToolRequestOAuthConnection-yuTNMEFj.js.map} +1 -1
- package/dist/_chunks/{PkToolShowArtifact-CcwAlOFM.js → PkToolShowArtifact-DaTlDsDo.js} +2 -2
- package/dist/_chunks/{PkToolShowArtifact-CcwAlOFM.js.map → PkToolShowArtifact-DaTlDsDo.js.map} +1 -1
- package/dist/_chunks/{PkToolShowContactForm-DByjGK_F.js → PkToolShowContactForm-Dxh6bEZt.js} +2 -2
- package/dist/_chunks/{PkToolShowContactForm-DByjGK_F.js.map → PkToolShowContactForm-Dxh6bEZt.js.map} +1 -1
- package/dist/_chunks/{PkToolShowForm-CGrjJ-9k.js → PkToolShowForm-D1VzwMmq.js} +3 -3
- package/dist/_chunks/{PkToolShowForm-CGrjJ-9k.js.map → PkToolShowForm-D1VzwMmq.js.map} +1 -1
- package/dist/_chunks/{PkToolShowImageGallery-C3ZaW1jQ.js → PkToolShowImageGallery-Cs8FxK9V.js} +2 -2
- package/dist/_chunks/{PkToolShowImageGallery-C3ZaW1jQ.js.map → PkToolShowImageGallery-Cs8FxK9V.js.map} +1 -1
- package/dist/_chunks/{PkToolShowProductList-BAHoB6mi.js → PkToolShowProductList-B4fxqFaf.js} +2 -2
- package/dist/_chunks/{PkToolShowProductList-BAHoB6mi.js.map → PkToolShowProductList-B4fxqFaf.js.map} +1 -1
- package/dist/_chunks/{PkToolShowSources-BgnumOzQ.js → PkToolShowSources-DNiiBz9b.js} +3 -3
- package/dist/_chunks/{PkToolShowSources-BgnumOzQ.js.map → PkToolShowSources-DNiiBz9b.js.map} +1 -1
- package/dist/_chunks/{PkToolShowSuggestedReply-CpixDpsJ.js → PkToolShowSuggestedReply-Bn1w6HdI.js} +2 -2
- package/dist/_chunks/{PkToolShowSuggestedReply-CpixDpsJ.js.map → PkToolShowSuggestedReply-Bn1w6HdI.js.map} +1 -1
- package/dist/_chunks/{PkToolShowVideo-94-l_bBW.js → PkToolShowVideo-Be5bKhUf.js} +2 -2
- package/dist/_chunks/{PkToolShowVideo-94-l_bBW.js.map → PkToolShowVideo-Be5bKhUf.js.map} +1 -1
- package/dist/_chunks/{PkToolShowWebPages-q1gLOgDl.js → PkToolShowWebPages-Bs251x0h.js} +2 -2
- package/dist/_chunks/{PkToolShowWebPages-q1gLOgDl.js.map → PkToolShowWebPages-Bs251x0h.js.map} +1 -1
- package/dist/_chunks/{chatImagePolicy-D4kQJejM.js → chatImagePolicy-BACuW8x3.js} +2 -2
- package/dist/_chunks/{chatImagePolicy-D4kQJejM.js.map → chatImagePolicy-BACuW8x3.js.map} +1 -1
- package/dist/_chunks/{markdown-DCYDJNwF.js → markdown-Bk1clNA6.js} +13 -13
- package/dist/_chunks/{markdown-DCYDJNwF.js.map → markdown-Bk1clNA6.js.map} +1 -1
- package/dist/ai-elements.es.js +1992 -1880
- package/dist/ai-elements.es.js.map +1 -1
- package/dist-vue/PkChatbot.js +1 -1
- package/dist-vue/PkChatbotAudioTranscript.d.ts +2 -0
- package/dist-vue/PkChatbotAudioTranscript.js +1 -0
- package/dist-vue/PkChatbotAuth.js +1 -1
- package/dist-vue/PkChatbotEmptyState.js +1 -1
- package/dist-vue/PkChatbotInput.js +1 -1
- package/dist-vue/PkChatbotMessages.js +1 -1
- package/dist-vue/PkChatbotStepPayload.js +1 -1
- package/dist-vue/PkChatbotStepToolDetail.js +1 -1
- package/dist-vue/PkChatbotSteps.js +1 -1
- package/dist-vue/PkChatbotViewChat.js +1 -1
- package/dist-vue/PkChatbotViewConversations.js +1 -1
- package/dist-vue/PkChatbotViewFiles.js +1 -1
- package/dist-vue/PkChatbotViewProfile.js +1 -1
- package/dist-vue/_chunks/{Agent-CSXbmC44.js → Agent-CNJ3b9Je.js} +2 -2
- package/dist-vue/_chunks/{Agent-CSXbmC44.js.map → Agent-CNJ3b9Je.js.map} +1 -1
- package/dist-vue/_chunks/{Document-Ol8nGgij.js → Document-Bp3YJuQQ.js} +11 -11
- package/dist-vue/_chunks/{Document-Ol8nGgij.js.map → Document-Bp3YJuQQ.js.map} +1 -1
- package/dist-vue/_chunks/{Media-Cv7ou2tP.js → Media-C6Xsb1Lm.js} +77 -27
- package/dist-vue/_chunks/Media-C6Xsb1Lm.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbot-BmpFnkde.js → PkChatbot-BEnC7_hV.js} +8 -8
- package/dist-vue/_chunks/{PkChatbot-BmpFnkde.js.map → PkChatbot-BEnC7_hV.js.map} +1 -1
- package/dist-vue/_chunks/PkChatbotAudioTranscript-BUxBHanA.js +80 -0
- package/dist-vue/_chunks/PkChatbotAudioTranscript-BUxBHanA.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotAuth-Dolfz0pa.js → PkChatbotAuth-C0qfDOGh.js} +8 -8
- package/dist-vue/_chunks/{PkChatbotAuth-Dolfz0pa.js.map → PkChatbotAuth-C0qfDOGh.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotEmptyState-BaiGfaAD.js → PkChatbotEmptyState-DjmF3dcI.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotEmptyState-BaiGfaAD.js.map → PkChatbotEmptyState-DjmF3dcI.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotInput-BjfW_bML.js → PkChatbotInput-B8hKYdD9.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotInput-BjfW_bML.js.map → PkChatbotInput-B8hKYdD9.js.map} +1 -1
- package/dist-vue/_chunks/PkChatbotMessages-DnoohZxu.js +861 -0
- package/dist-vue/_chunks/PkChatbotMessages-DnoohZxu.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotStepPayload-CGAdiGR5.js → PkChatbotStepPayload-BGYwwXaT.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotStepPayload-CGAdiGR5.js.map → PkChatbotStepPayload-BGYwwXaT.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotStepToolDetail-DQsmXnKT.js → PkChatbotStepToolDetail-BH07XycE.js} +3 -3
- package/dist-vue/_chunks/{PkChatbotStepToolDetail-DQsmXnKT.js.map → PkChatbotStepToolDetail-BH07XycE.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotSteps-DzajeHKc.js → PkChatbotSteps-qyUOh42z.js} +6 -6
- package/dist-vue/_chunks/{PkChatbotSteps-DzajeHKc.js.map → PkChatbotSteps-qyUOh42z.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewChat-DZbQQNQn.js → PkChatbotViewChat-BM51ovrp.js} +7 -7
- package/dist-vue/_chunks/{PkChatbotViewChat-DZbQQNQn.js.map → PkChatbotViewChat-BM51ovrp.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewConversations-BXtTZ3kb.js → PkChatbotViewConversations-ClJLe1bm.js} +3 -3
- package/dist-vue/_chunks/{PkChatbotViewConversations-BXtTZ3kb.js.map → PkChatbotViewConversations-ClJLe1bm.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewFiles--wM5SVAl.js → PkChatbotViewFiles-Do6tpo9O.js} +3 -3
- package/dist-vue/_chunks/{PkChatbotViewFiles--wM5SVAl.js.map → PkChatbotViewFiles-Do6tpo9O.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewProfile-B8T0AQC8.js → PkChatbotViewProfile-590b78yi.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewProfile-B8T0AQC8.js.map → PkChatbotViewProfile-590b78yi.js.map} +1 -1
- package/dist-vue/_chunks/{PkStreamingMarkdown-D6y6g6vP.js → PkStreamingMarkdown-NWO3WTw9.js} +3 -3
- package/dist-vue/_chunks/{PkStreamingMarkdown-D6y6g6vP.js.map → PkStreamingMarkdown-NWO3WTw9.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolEditSpreadsheet-BkjEQag2.js → PkToolEditSpreadsheet-BnMSzlCi.js} +2 -2
- package/dist-vue/_chunks/{PkToolEditSpreadsheet-BkjEQag2.js.map → PkToolEditSpreadsheet-BnMSzlCi.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolRequestGeolocation-BWWUGKHJ.js → PkToolRequestGeolocation-B2UNj6dx.js} +2 -2
- package/dist-vue/_chunks/{PkToolRequestGeolocation-BWWUGKHJ.js.map → PkToolRequestGeolocation-B2UNj6dx.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolRequestOAuthConnection-DvvLZ8yi.js → PkToolRequestOAuthConnection-B262JqNh.js} +2 -2
- package/dist-vue/_chunks/{PkToolRequestOAuthConnection-DvvLZ8yi.js.map → PkToolRequestOAuthConnection-B262JqNh.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowArtifact-CP-dG3-Y.js → PkToolShowArtifact-Dr0NHXDG.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowArtifact-CP-dG3-Y.js.map → PkToolShowArtifact-Dr0NHXDG.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowContactForm-BK7B-Qzr.js → PkToolShowContactForm-BDm1w3UK.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowContactForm-BK7B-Qzr.js.map → PkToolShowContactForm-BDm1w3UK.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowDiagram-9THUQfbg.js → PkToolShowDiagram-DzE-X06t.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowDiagram-9THUQfbg.js.map → PkToolShowDiagram-DzE-X06t.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowForm-CjN-_-1j.js → PkToolShowForm-cKgWwvPD.js} +4 -4
- package/dist-vue/_chunks/{PkToolShowForm-CjN-_-1j.js.map → PkToolShowForm-cKgWwvPD.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowImageGallery-8cmWigYT.js → PkToolShowImageGallery-DsAQhyOq.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowImageGallery-8cmWigYT.js.map → PkToolShowImageGallery-DsAQhyOq.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowProductList-BxsK6LXl.js → PkToolShowProductList-OwGaM5qj.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowProductList-BxsK6LXl.js.map → PkToolShowProductList-OwGaM5qj.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowSources-BfWNsyLw.js → PkToolShowSources-BVOqH-ZV.js} +3 -3
- package/dist-vue/_chunks/{PkToolShowSources-BfWNsyLw.js.map → PkToolShowSources-BVOqH-ZV.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowSuggestedReply-_bH3Jikn.js → PkToolShowSuggestedReply-CF31zmck.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowSuggestedReply-_bH3Jikn.js.map → PkToolShowSuggestedReply-CF31zmck.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowVideo-2_EdyT_U.js → PkToolShowVideo-BAqSAfdq.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowVideo-2_EdyT_U.js.map → PkToolShowVideo-BAqSAfdq.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowWebPages-B0glenu5.js → PkToolShowWebPages-BeMJqIkW.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowWebPages-B0glenu5.js.map → PkToolShowWebPages-BeMJqIkW.js.map} +1 -1
- package/dist-vue/_chunks/{createChatbotApiClient-DgtSqixO.js → createChatbotApiClient-DmqgH9Ul.js} +3 -3
- package/dist-vue/_chunks/{createChatbotApiClient-DgtSqixO.js.map → createChatbotApiClient-DmqgH9Ul.js.map} +1 -1
- package/dist-vue/_chunks/message-BOiF6Bt5.js +59 -0
- package/dist-vue/_chunks/message-BOiF6Bt5.js.map +1 -0
- package/dist-vue/_chunks/{useChatbotStore-A33jT5Cp.js → useChatbotStore-CiF_gfIk.js} +656 -653
- package/dist-vue/_chunks/useChatbotStore-CiF_gfIk.js.map +1 -0
- package/dist-vue/_chunks/{utils-ByN6GORv.js → utils-DxuGvgWg.js} +37 -37
- package/dist-vue/_chunks/{utils-ByN6GORv.js.map → utils-DxuGvgWg.js.map} +1 -1
- package/dist-vue/api.js +1 -1
- package/dist-vue/composables.js +15 -5
- package/dist-vue/composables.js.map +1 -1
- package/dist-vue/index.js +4754 -4749
- package/dist-vue/index.js.map +1 -1
- package/dist-vue/locales.js +52 -40
- package/dist-vue/packages/components/src/chat/PkChatbotAudioTranscript.d.ts +13 -0
- package/dist-vue/packages/components/src/chat/PkChatbotMessages.d.ts +2 -2
- package/dist-vue/packages/composable/src/chatbot/useChatbotStore.d.ts +1 -1
- package/dist-vue/packages/models/src/schema/Agent.d.ts +4 -4
- package/dist-vue/packages/models/src/schema/Media.d.ts +21 -0
- package/dist-vue/packages/models/src/schema/Tabular.d.ts +1 -1
- package/dist-vue/style.css +1 -1
- package/package.json +1 -1
- package/dist/_chunks/Agent-BxyaZeuL.js.map +0 -1
- package/dist/_chunks/PkStreamingMarkdown-B8mvCNou.js.map +0 -1
- package/dist-vue/_chunks/Media-Cv7ou2tP.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotMessages-CztL_AMB.js +0 -837
- package/dist-vue/_chunks/PkChatbotMessages-CztL_AMB.js.map +0 -1
- package/dist-vue/_chunks/message-RohfrdWS.js +0 -33
- package/dist-vue/_chunks/message-RohfrdWS.js.map +0 -1
- package/dist-vue/_chunks/useChatbotStore-A33jT5Cp.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Media-C6Xsb1Lm.js","names":["core._coercedDate","schemas.ZodDate","z.object","z.enum","z.string","z.number","z.literal","z.url","z.discriminatedUnion","z.record","z.array","z.custom","_coercedDate","number","record","custom","literal","array","_enum","ZodDate","discriminatedUnion","object","url","string","date","params","MIME_TYPE_REGEX","SUPPORTED_AUDIO_TYPES","AUDIO_EXTENSIONS","OTHER_AUDIO_EXTENSIONS","bareMimeType","mimeType","isAudioFile","extension","getExtensionFromFilename","name","StoragePolicy","\"DEFAULT\"","\"COLD\"","\"HOT\"","\"ARCHIVE\"","AzureBlobStorageHandleSchema","S3StorageHandleSchema","StorageHandleSchema","MediaSourceSchema","ext","MediaSchema","MediaType","\"picture\"","\"video\"","\"audio\"","\"office\"","\"archive\"","\"binary\"","\"executable\"","PictureRawOptionsSchema","VideoRawOptionsSchema","AudioRawOptionsSchema","OfficeRawOptionsSchema","ArchiveRawOptionsSchema","BinaryRawOptionsSchema","ExecutableRawOptionsSchema","lastDot","filename"],"sources":["../../../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/coerce.js","../../../../packages/models/src/schema/Media.ts"],"sourcesContent":["import * as core from \"../core/index.js\";\nimport * as schemas from \"./schemas.js\";\nexport function string(params) {\n return core._coercedString(schemas.ZodString, params);\n}\nexport function number(params) {\n return core._coercedNumber(schemas.ZodNumber, params);\n}\nexport function boolean(params) {\n return core._coercedBoolean(schemas.ZodBoolean, params);\n}\nexport function bigint(params) {\n return core._coercedBigint(schemas.ZodBigInt, params);\n}\nexport function date(params) {\n return core._coercedDate(schemas.ZodDate, params);\n}\n","import { z } from 'zod'\n\n// ===== Constants =====\n\nexport const MAX_FILE_SIZE = 100 * 1024 * 1024 // 100MB\n\n/**\n * The largest audio that is transcribed (spec `audio-file-transcription.md`,\n * D2): about seven hours of m4a at 32 kbps, ten minutes of CD quality wav.\n * The SDK sends it as base64 inside a JSON body, so a job holds about four\n * times this in memory. The upload alert\n * (`problemReasons.file-upload-validation.audio-too-large`) states it in MB.\n */\nexport const MAX_AUDIO_FILE_SIZE = MAX_FILE_SIZE\n\nexport const MIME_TYPE_REGEX = /^[a-z]+\\/[a-z0-9\\-+.]+$/i\n\nexport const SUPPORTED_IMAGE_TYPES = [\n 'image/jpeg',\n 'image/png',\n 'image/gif',\n 'image/webp',\n 'image/svg+xml',\n] as const\n\nexport const SUPPORTED_VIDEO_TYPES = [\n 'video/mp4',\n 'video/webm',\n 'video/ogg',\n] as const\n\n/**\n * The audio a transcription model reads (spec `audio-file-transcription.md`,\n * D2), by every type a browser or a drive declares for it: an m4a arrives as\n * `audio/mp4` or `audio/x-m4a` depending on the system, a wav as any of three.\n * `audio/webm`, what a browser records, waits for a test.\n */\nexport const SUPPORTED_AUDIO_TYPES = [\n 'audio/mpeg',\n 'audio/mp4',\n 'audio/x-m4a',\n 'audio/aac',\n 'audio/wav',\n 'audio/x-wav',\n 'audio/wave',\n 'audio/ogg',\n 'audio/flac',\n 'audio/x-flac',\n] as const\n\n/** The extensions of {@link SUPPORTED_AUDIO_TYPES}, for a file whose type says nothing. */\nconst AUDIO_EXTENSIONS = new Set([\n '.mp3',\n '.m4a',\n '.aac',\n '.wav',\n '.ogg',\n '.oga',\n '.opus',\n '.flac',\n])\n\n/** Audio no transcription reads, still to keep away from a model. */\nconst OTHER_AUDIO_EXTENSIONS = new Set([\n '.amr',\n '.3ga',\n '.aif',\n '.aiff',\n '.caf',\n '.m4b',\n '.mka',\n '.weba',\n '.wma',\n])\n\n/** A MIME type without its parameters: `audio/ogg; codecs=opus` is `audio/ogg`. */\nconst bareMimeType = (mimeType?: string): string =>\n mimeType?.split(';')[0]?.trim().toLowerCase() ?? ''\n\nconst hasAudioExtension = (name = ''): boolean =>\n AUDIO_EXTENSIONS.has(getExtensionFromFilename(name))\n\n/**\n * Whether a file is audio of any kind, read or not: what a channel that does\n * not transcribe has to keep away from the model, since most models refuse an\n * audio part and the whole turn with it.\n */\nexport const isAudioFile = (name?: string, mimeType?: string): boolean => {\n const extension = getExtensionFromFilename(name ?? '')\n return (\n bareMimeType(mimeType).startsWith('audio/') ||\n AUDIO_EXTENSIONS.has(extension) ||\n OTHER_AUDIO_EXTENSIONS.has(extension)\n )\n}\n\n/**\n * Whether a file is audio the transcription reads: by its type, else by its\n * extension when the type says nothing (none, `application/octet-stream`) or\n * is another system's name for an audio of ours (`audio/mp3`,\n * `audio/vnd.wave`, `audio/opus`): the model reads the bytes, not the type.\n */\nexport const isTranscribableAudio = (\n name?: string,\n mimeType?: string,\n): boolean => {\n const bare = bareMimeType(mimeType)\n if ((SUPPORTED_AUDIO_TYPES as readonly string[]).includes(bare)) {\n return true\n }\n return (\n (bare === '' ||\n bare === 'application/octet-stream' ||\n bare.startsWith('audio/')) &&\n hasAudioExtension(name)\n )\n}\n\n/**\n * What a chat turn made of an audio the user attached (spec\n * `audio-file-transcription.md`, D4), kept on its file part\n * (`providerMetadata['8bot'].transcript`) and on its `chat_attachments` row:\n * the transcript in paragraphs opened by their time, or why there is none.\n * `off` is an organization that does not use the platform gateway, where\n * nothing is transcribed and nothing is charged.\n */\nexport const ChatAudioTranscriptSchema = z.object({\n status: z.enum(['ready', 'no-speech', 'failed', 'off']),\n text: z.string().optional(),\n /** ISO 639-1, when the model detected it. */\n language: z.string().optional(),\n durationSeconds: z.number().optional(),\n model: z.string().optional(),\n})\nexport type ChatAudioTranscript = z.infer<typeof ChatAudioTranscriptSchema>\n\n// ===== Enums =====\n\nexport enum StoragePolicy {\n DEFAULT = 'default',\n COLD = 'cold',\n HOT = 'hot',\n ARCHIVE = 'archive',\n}\n\nexport enum PreviewStatus {\n Pending = 'pending',\n Done = 'done',\n Failed = 'failed',\n}\n\n// ===== Storage Handle Schemas =====\n\nconst AzureBlobStorageHandleSchema = z.object({\n type: z.literal('azure-blob'),\n blobUri: z.url(),\n containerName: z.string().min(1),\n pathRelativeToContainer: z.string().min(1),\n})\n\nconst S3StorageHandleSchema = z.object({\n type: z.literal('s3'),\n bucket: z.string().min(1),\n key: z.string().min(1),\n region: z.string().min(1),\n})\n\nconst StorageHandleSchema = z.discriminatedUnion('type', [\n AzureBlobStorageHandleSchema,\n S3StorageHandleSchema,\n])\n\n// ===== Media Upload Schema =====\n\n/**\n * Media upload information for preparing a file upload\n */\nexport const MediaUploadSchema = z.object({\n /** Media type category (picture, video, audio, etc.) */\n type: z.string().min(1),\n /** Original filename */\n name: z.string().min(1),\n /** MIME type (e.g., 'image/jpeg') */\n mimeType: z.string().regex(MIME_TYPE_REGEX),\n /** File size in bytes (must be positive) */\n bytesCount: z.number().positive(),\n /** File extension with dot (e.g., '.jpg') */\n extension: z\n .string()\n .regex(/^\\.[a-zA-Z0-9]+$/)\n .overwrite((ext) => ext.toLowerCase()),\n /** Optional custom stored filename */\n storedFileName: z.string().optional(),\n /** Additional metadata key-value pairs */\n metadata: z.record(z.string(), z.string()).default({}),\n /** Storage tags for categorization */\n tags: z.record(z.string(), z.string()).default({}),\n})\nexport type MediaUpload = z.infer<typeof MediaUploadSchema>\n\n// ===== Media Source Schema =====\n\n/**\n * Complete media source information including storage details\n */\nexport const MediaSourceSchema = MediaUploadSchema.extend({\n /** Storage backend configuration */\n storageHandle: StorageHandleSchema,\n /** Storage tier/policy */\n storagePolicy: z.enum(StoragePolicy),\n /** Optional signature data for verification */\n signData: z.string().optional(),\n /** Actual stored filename (required) */\n storedFileName: z.string().min(1),\n})\nexport type MediaSource = z.infer<typeof MediaSourceSchema>\n\n// ===== Media Schema =====\n\n/**\n * Complete media object with source and optional variants\n */\nexport const MediaSchema = z.object({\n /** Display name for the media */\n name: z.string().min(1),\n /** Primary media source */\n src: MediaSourceSchema,\n /** Optional additional sources (e.g., different resolutions) */\n srcSet: z.array(MediaSourceSchema).default([]),\n /** Type-specific metadata (dimensions, duration, etc.) */\n rawOptions: z.custom<RawOptions>().optional(),\n})\nexport type Media = z.infer<typeof MediaSchema>\n\n// ===== Upload Handle Schema =====\n\n/**\n * Upload handle containing SAS URI and metadata for client-side upload\n */\nexport const MediaUploadHandleSchema = z.object({\n /** Media source information */\n src: MediaSourceSchema,\n /** Upload credentials and metadata */\n uploadHandle: z.object({\n /** Shared Access Signature URI for upload */\n uploadSasuri: z.url(),\n /** Metadata to attach to the blob */\n metadata: z.record(z.string(), z.string()).default({}),\n /** Tags to attach to the blob */\n tags: z.record(z.string(), z.string()).default({}),\n }),\n})\nexport type MediaUploadHandle = z.infer<typeof MediaUploadHandleSchema>\n\n// ===== Download Handle Schema =====\n\n/**\n * Download handle containing SAS URIs for different access patterns\n */\nexport const MediaDownloadHandleSchema = z.object({\n /** Media source information */\n src: MediaSourceSchema,\n /** Download credentials and URIs */\n downloadHandle: z.object({\n /** SAS URI for file download */\n downloadSasuri: z.url(),\n /** SAS URI for inline viewing */\n viewSasuri: z.url(),\n /** Optional SAS URI for preview thumbnail */\n previewSasuri: z.url().optional(),\n /** Expiration timestamp for the SAS URIs */\n expiresOn: z.string().datetime(),\n /** Storage backend type */\n type: z.string(),\n }),\n})\nexport type MediaDownloadHandle = z.infer<typeof MediaDownloadHandleSchema>\n\n// ===== Media Types =====\n\nexport enum MediaType {\n picture = 'picture',\n video = 'video',\n audio = 'audio',\n office = 'office',\n archive = 'archive',\n binary = 'binary',\n executable = 'executable',\n}\n\n// ===== Raw Options Schemas =====\n\nconst PictureRawOptionsSchema = z.object({\n type: z.literal(MediaType.picture),\n width: z.number().positive(),\n height: z.number().positive(),\n blurhash: z.string().optional(),\n})\n\nconst VideoRawOptionsSchema = z.object({\n type: z.literal(MediaType.video),\n width: z.number().positive(),\n height: z.number().positive(),\n duration: z.number().nonnegative(),\n})\n\nconst AudioRawOptionsSchema = z.object({\n type: z.literal(MediaType.audio),\n duration: z.number().nonnegative(),\n})\n\nconst OfficeRawOptionsSchema = z.object({\n type: z.literal(MediaType.office),\n})\n\nconst ArchiveRawOptionsSchema = z.object({\n type: z.literal(MediaType.archive),\n})\n\nconst BinaryRawOptionsSchema = z.object({\n type: z.literal(MediaType.binary),\n})\n\nconst ExecutableRawOptionsSchema = z.object({\n type: z.literal(MediaType.executable),\n})\n\nexport const RawOptionsSchema = z.discriminatedUnion('type', [\n PictureRawOptionsSchema,\n VideoRawOptionsSchema,\n AudioRawOptionsSchema,\n OfficeRawOptionsSchema,\n ArchiveRawOptionsSchema,\n BinaryRawOptionsSchema,\n ExecutableRawOptionsSchema,\n])\n\n// ===== Raw Options Types =====\n\n// ===== Raw Options Types =====\n\nexport type PictureRawOptions = z.infer<typeof PictureRawOptionsSchema>\nexport type VideoRawOptions = z.infer<typeof VideoRawOptionsSchema>\nexport type AudioRawOptions = z.infer<typeof AudioRawOptionsSchema>\nexport type OfficeRawOptions = z.infer<typeof OfficeRawOptionsSchema>\nexport type ArchiveRawOptions = z.infer<typeof ArchiveRawOptionsSchema>\nexport type BinaryRawOptions = z.infer<typeof BinaryRawOptionsSchema>\nexport type ExecutableRawOptions = z.infer<typeof ExecutableRawOptionsSchema>\n\nexport type RawOptions = z.infer<typeof RawOptionsSchema>\n\n// ===== Uploaded File Type =====\n\nexport type UploadedFile<Source = undefined> = Source extends undefined\n ? {\n name: string\n size: number\n type: string\n url: string\n lastModified?: number\n }\n : {\n name: string\n size: number\n type: string\n url: string\n lastModified?: number\n source: Source\n }\n\n// ===== Helper Functions =====\n\n/**\n * Check if a MIME type is a supported image format\n */\nexport const isSupportedImageType = (mimeType: string): boolean => {\n return mimeType.startsWith('image/')\n}\n\n/**\n * Check if a MIME type supports preview generation\n */\nexport const isSupportedPreviewType = (mimeType: string): boolean => {\n return mimeType.startsWith('image/') || mimeType === 'application/pdf'\n}\n\n/**\n * Determine MediaType from MIME type\n */\nexport const getMediaTypeFromMime = (mimeType: string): MediaType => {\n if (mimeType.startsWith('image/')) {\n return MediaType.picture\n }\n if (mimeType.startsWith('video/')) {\n return MediaType.video\n }\n if (mimeType.startsWith('audio/')) {\n return MediaType.audio\n }\n if (\n mimeType.startsWith('application/pdf') ||\n mimeType.startsWith('application/msword') ||\n mimeType.startsWith('application/vnd.openxmlformats') ||\n mimeType.startsWith('application/vnd.ms-')\n ) {\n return MediaType.office\n }\n if (\n mimeType === 'application/zip' ||\n mimeType === 'application/x-rar-compressed' ||\n mimeType === 'application/x-7z-compressed'\n ) {\n return MediaType.archive\n }\n if (\n mimeType.startsWith('application/x-') &&\n mimeType.includes('executable')\n ) {\n return MediaType.executable\n }\n return MediaType.binary\n}\n\n/**\n * Extract file extension from filename\n */\nexport const getExtensionFromFilename = (filename: string): string => {\n const lastDot = filename.lastIndexOf('.')\n return lastDot === -1 ? '' : filename.slice(lastDot).toLowerCase()\n}\n\n/**\n * Format bytes to human-readable string\n */\nexport const formatBytes = (bytes: number, decimals = 2): string => {\n if (bytes === 0) {\n return '0 Bytes'\n }\n const k = 1024\n const dm = decimals < 0 ? 0 : decimals\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`\n}\n\n// ===== Type Guards =====\n\nexport const isPictureOptions = (\n options: RawOptions,\n): options is PictureRawOptions => {\n return options.type === MediaType.picture\n}\n\nexport const isVideoOptions = (\n options: RawOptions,\n): options is VideoRawOptions => {\n return options.type === MediaType.video\n}\n\nexport const isAudioOptions = (\n options: RawOptions,\n): options is AudioRawOptions => {\n return options.type === MediaType.audio\n}\n\nexport const isOfficeOptions = (\n options: RawOptions,\n): options is OfficeRawOptions => {\n return options.type === MediaType.office\n}\n\nexport const isArchiveOptions = (\n options: RawOptions,\n): options is ArchiveRawOptions => {\n return options.type === MediaType.archive\n}\n\nexport const isBinaryOptions = (\n options: RawOptions,\n): options is BinaryRawOptions => {\n return options.type === MediaType.binary\n}\n\nexport const isExecutableOptions = (\n options: RawOptions,\n): options is ExecutableRawOptions => {\n return options.type === MediaType.executable\n}\n"],"ignoreList":[0],"mappings":"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"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { n as e, t } from "./PkStreamingMarkdown-
|
|
2
|
-
import { n } from "./useChatbotStore-
|
|
3
|
-
import { t as ee } from "./PkChatbotViewConversations-
|
|
4
|
-
import { t as te } from "./PkChatbotViewChat-
|
|
1
|
+
import { n as e, t } from "./PkStreamingMarkdown-NWO3WTw9.js";
|
|
2
|
+
import { n } from "./useChatbotStore-CiF_gfIk.js";
|
|
3
|
+
import { t as ee } from "./PkChatbotViewConversations-ClJLe1bm.js";
|
|
4
|
+
import { t as te } from "./PkChatbotViewChat-BM51ovrp.js";
|
|
5
5
|
import { t as r } from "./useShortUrlResolver-DI8ycJiO.js";
|
|
6
|
-
import { t as ne } from "./PkChatbotViewFiles
|
|
6
|
+
import { t as ne } from "./PkChatbotViewFiles-Do6tpo9O.js";
|
|
7
7
|
import { t as i } from "./useLocalizedString-PfaKjIQY.js";
|
|
8
|
-
import { w as a } from "./utils-
|
|
8
|
+
import { w as a } from "./utils-DxuGvgWg.js";
|
|
9
9
|
import { t as re } from "./PkAvatar-ZgCsoO-p.js";
|
|
10
10
|
import { t as o } from "./chatbotProfileContext-CgnFhNPn.js";
|
|
11
|
-
import { t as ie } from "./PkChatbotViewProfile-
|
|
11
|
+
import { t as ie } from "./PkChatbotViewProfile-590b78yi.js";
|
|
12
12
|
import { VvAlert as ae, VvButton as oe, VvButtonGroup as se, VvDropdown as ce, VvDropdownAction as le, VvIcon as ue } from "@volverjs/ui-vue/components";
|
|
13
13
|
import { Fragment as s, computed as c, createBlock as l, createCommentVNode as u, createElementBlock as d, createElementVNode as f, createSlots as p, createTextVNode as m, createVNode as h, defineComponent as g, guardReactiveProps as _, inject as v, mergeModels as y, mergeProps as b, normalizeClass as de, normalizeProps as x, normalizeStyle as S, openBlock as C, renderList as w, renderSlot as T, toDisplayString as E, toRef as fe, unref as D, useModel as pe, useSlots as me, useTemplateRef as he, watch as O, watchEffect as ge, withCtx as k } from "vue";
|
|
14
14
|
import { useI18n as _e } from "vue-i18n";
|
|
@@ -272,4 +272,4 @@ var be = { class: "pk-chatbot__header-start" }, xe = ["title"], Se = { key: 1 },
|
|
|
272
272
|
//#endregion
|
|
273
273
|
export { A as t };
|
|
274
274
|
|
|
275
|
-
//# sourceMappingURL=PkChatbot-
|
|
275
|
+
//# sourceMappingURL=PkChatbot-BEnC7_hV.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PkChatbot-BmpFnkde.js","names":["_useModel","__unplugin_components_0","__unplugin_components_1","__unplugin_components_2","__unplugin_components_3","__unplugin_components_4","__unplugin_components_5","_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createElementVNode","_unref","_Fragment","_createVNode","_mergeProps","_createBlock","PkAvatar","_createCommentVNode","_toDisplayString","_withCtx","_renderSlot","_normalizeProps","_guardReactiveProps","_createTextVNode","PkChatbotViewChat","_createSlots","_renderList","PkChatbotViewConversations","PkChatbotViewFiles","PkChatbotViewProfile","PkStreamingMarkdown","_sfc_main","provideChatImagePolicy","PkChatbotViewConversations_default","PkChatbotViewChat_default","provideShortUrlResolver","PkChatbotViewFiles_default","useLocalizedString","resolveContrastColor","PkAvatar_default","ChatbotProfileContextKey","PkChatbotViewProfile_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","PkChatbot_default","defineComponent","\"chatId\"","\"chatIdModifiers\"","__expose","__emit","slots","useSlots","$t","useI18n","props","__props","emit","chatId","useModel","store","useChatbotStore","hashes","storeToRefs","storeAgentId","toRef","profile","inject","canManageProfile","computed","canSignIn","watch","canManage","activeView","navigate","watchEffect","id","localChatId","chatError","error","parsedError","isFullscreen","isPanel","chatbotEl","useTemplateRef","chatbotWidth","useElementSize","isPhoneWidth","isTightWidth","isWideWidth","isReasoningAgent","useColorsForAgentHeader","showHeaderMenu","themeClass","isDark","mainColor","contrastColor","footerMessage","title","isReady","chat","activeMessage","messages","newAgentId","newChatId","newView","message","_component_VvButton","VvButton","_component_VvIcon","VvIcon","_component_VvDropdownAction","VvDropdownAction","_component_VvDropdown","VvDropdown","_component_VvButtonGroup","VvButtonGroup","_component_VvAlert","VvAlert","openBlock","createElementBlock","normalizeClass","createCommentVNode","normalizeStyle","createElementVNode","unref","createBlock","Fragment","createVNode","mergeProps","_cache","toDisplayString","withCtx","renderSlot","_ctx","normalizeProps","guardReactiveProps","startNewChat","stopGeneration","createTextVNode","$event","createSlots","renderList","slotName","slotData","PkStreamingMarkdown_default"],"sources":["../../../../packages/components/src/chat/PkChatbot.vue","../../../../packages/components/src/chat/PkChatbot.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import {\n computed,\n inject,\n toRef,\n useTemplateRef,\n watch,\n watchEffect,\n useSlots,\n } from 'vue'\n import { useElementSize } from '@vueuse/core'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type {\n AgentInterface,\n AgentModel,\n AgentFileUpload,\n AgentGatewayOptions,\n AgentVoiceCapability,\n ChatMessageActions,\n RevisedAnswer,\n UIChatMessage,\n AgentTools,\n } from 'models'\n import {\n useChatbotStore,\n useLocalizedString,\n provideShortUrlResolver,\n } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import PkChatbotViewChat from './PkChatbotViewChat.vue'\n import PkChatbotViewConversations from './PkChatbotViewConversations.vue'\n import PkChatbotViewFiles from './PkChatbotViewFiles.vue'\n import PkChatbotViewProfile from './PkChatbotViewProfile.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n import { provideChatImagePolicy } from './chatImagePolicy'\n import { resolveContrastColor } from './utils'\n\n // Captured in script setup to avoid TS7022 circular inference in template\n const slots = useSlots()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const props = defineProps<{\n baseUrl: string\n basePath?: string\n agentId: string\n agentType?: 'reasoning' | 'chat'\n userId?: string\n name?: string\n model?: string\n agentModel?: AgentModel\n agentTools?: AgentTools\n agentInterface?: AgentInterface\n actions?: ChatMessageActions[]\n revisedAnswers?: RevisedAnswer[]\n headers?: Record<string, string>\n systemTheme?: 'light' | 'dark'\n agentFileUpload?: AgentFileUpload\n /** Voice configuration from the agent settings: enables the talk button. */\n voice?: AgentVoiceCapability\n fallbackModels?: string[]\n gatewayOptions?: Partial<AgentGatewayOptions>\n externalContext?: Record<string, unknown>\n /** Layout modifier: `widget` (default) keeps the contained layout with header, `fullscreen` removes header/background for full-page usage (the page provides the chrome), `panel` keeps the header but slims it down for a docked side panel (the host provides background and border) */\n modifier?: 'widget' | 'fullscreen' | 'panel'\n /** Display name of the current user, used for the `{name}` placeholder in the fullscreen greeting */\n userName?: string\n /** Show the \"Files in this chat\" menu for hosts that don't provide the\n * profile context but whose user is authenticated (e.g. the backoffice\n * playground). The widget host relies on `canManageProfile` instead. */\n userFilesEnabled?: boolean\n }>()\n const emit = defineEmits<{\n 'message-update': [message: UIChatMessage]\n 'show-info': [message: UIChatMessage]\n revise: [message: UIChatMessage]\n error: [error: { type: string; title: string; status: number }]\n }>()\n\n const chatId = defineModel<string | undefined>('chatId', {\n type: String,\n default: undefined,\n })\n\n const store = useChatbotStore(props.agentId)\n\n // Resolve backend short-urls (`/api/r/<hash>`) back to their real target\n // for display in tool components. Batched + cached across the chat tree.\n provideShortUrlResolver((hashes) =>\n store.apiClient.resolveShortUrls(hashes),\n )\n\n const { messages, chat, localChatId, activeView, chatError, isDark } =\n storeToRefs(store)\n\n // Ref to store.agentId for watchers (avoids duplicate-key with agentId prop)\n const storeAgentId = toRef(store, 'agentId')\n\n const { startNewChat, stopGeneration, regenerate, navigate, sendMessage } =\n store\n\n // Images in the answers and the tool cards load by themselves only from\n // the backend this chat talks to and from the hosts the agent lists.\n provideChatImagePolicy(\n () => props.agentInterface?.imageHosts,\n () => props.baseUrl,\n )\n\n // Optional profile-management context provided by the host widget.\n // Present only for authenticated, non-anonymous sessions.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canManageProfile = computed(\n () => profile?.canManageProfile.value ?? false,\n )\n // Anonymous users get a \"Sign in\" entry instead of the profile one,\n // provided the host filled the `view-auth` slot and the agent has at\n // least one auth method enabled.\n const canSignIn = computed(\n () =>\n !canManageProfile.value &&\n (profile?.canSignIn?.value ?? false) &&\n slots['view-auth'] !== undefined,\n )\n\n // Once the sign-in completes (the session becomes non-anonymous) leave\n // the auth view and return to the conversation.\n watch(canManageProfile, (canManage) => {\n if (canManage && activeView.value === 'auth') {\n navigate('chat')\n }\n })\n\n // Sync props → store reactively\n watchEffect(() => {\n store.baseUrl = props.baseUrl\n store.basePath = props.basePath\n store.name = props.name\n store.agentType = props.agentType\n store.userId = props.userId\n store.agentInterface = props.agentInterface\n store.agentModel = props.agentModel\n store.agentTools = props.agentTools\n store.headers = props.headers\n store.revisedAnswers = props.revisedAnswers\n store.model = props.model\n store.actionsInput = props.actions\n store.agentFileUpload = props.agentFileUpload\n store.agentVoice = props.voice\n store.fallbackModels = props.fallbackModels\n store.gatewayOptions = props.gatewayOptions\n store.externalContext = props.externalContext\n store.systemTheme = props.systemTheme\n store.hideWelcomeMessage = props.modifier === 'fullscreen'\n })\n\n // Sync chatId v-model ↔ store (bidirectional)\n // Parent → store: if the parent provides an external chatId, push it into\n // the store so the Chat instance picks it up.\n watch(\n () => chatId.value,\n (id) => {\n if (id && id !== store.localChatId) {\n store.localChatId = id\n }\n },\n { immediate: true },\n )\n // Store → parent: keep the v-model in sync whenever the store changes\n // the chatId internally (e.g. startNewChat).\n watch(localChatId, (id) => {\n chatId.value = id\n })\n\n // Emit structured errors from chat to the parent component\n watch(chatError, (error) => {\n if (!error) {\n return\n }\n try {\n const parsedError = JSON.parse(error.message) as {\n type: string\n title: string\n status: number\n }\n emit('error', parsedError)\n } catch {\n // ignore non-JSON errors\n }\n })\n\n // ui\n const isFullscreen = computed(() => props.modifier === 'fullscreen')\n const isPanel = computed(() => props.modifier === 'panel')\n\n /**\n * How much room the chat has, measured on the chat and not on the window.\n *\n * The same chat renders in a phone page, in a 408px widget, in a docked\n * panel and in a preview frame that the side panels squeeze to phone width\n * on a desktop screen, so the viewport says nothing about the space the\n * layout actually has. A CSS container query would read the same number\n * with no JavaScript, but `container-type` makes the element a containing\n * block for `position: fixed` descendants on every engine except Chrome\n * 129 and later, which displaces every dropdown in the chat: see\n * docs/agents/gotchas.md, \"The chat measures its own width, and not with a\n * container query\".\n *\n * The thresholds carry the modifiers, so the CSS stays declarative:\n * `--phone` has as much room as a phone page, `--tight` is too tight for\n * the agent chip beside the field, `--wide` has room for the outline rail\n * beside the reading column. Before the first measurement the width is 0\n * and no modifier applies, which is the roomy layout the base CSS writes.\n */\n const chatbotEl = useTemplateRef<HTMLDivElement>('chatbotEl')\n const { width: chatbotWidth } = useElementSize(chatbotEl)\n const isPhoneWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 576,\n )\n const isTightWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 640,\n )\n const isWideWidth = computed(() => chatbotWidth.value >= 848)\n const isReasoningAgent = computed(() => props.agentType === 'reasoning')\n const useColorsForAgentHeader = computed(\n () => props.agentInterface?.useColorsForAgentHeader,\n )\n // Panel mode promotes the everyday actions (new chat, history) from the\n // dropdown to direct header buttons: the dropdown then only remains for\n // the secondary entries (files, profile, sign-in) and disappears when\n // none of them applies.\n const showHeaderMenu = computed(\n () =>\n !isPanel.value ||\n canManageProfile.value ||\n canSignIn.value ||\n (!!props.userFilesEnabled && !!localChatId.value),\n )\n const themeClass = computed(() => [\n 'theme',\n `theme--${isDark.value ? 'dark' : 'light'}`,\n ])\n const mainColor = computed(() => props.agentInterface?.mainColor)\n const contrastColor = computed(() =>\n resolveContrastColor(props.agentInterface?.textColor, mainColor.value),\n )\n const footerMessage = useLocalizedString(\n () => props.agentInterface?.footerMessage,\n )\n const title = computed(() => {\n if (activeView.value === 'conversations') {\n return $t('label.conversations')\n }\n if (activeView.value === 'profile') {\n return $t('label.profile')\n }\n if (activeView.value === 'auth') {\n return $t('label.authentication')\n }\n if (activeView.value === 'files') {\n return $t('userFiles.chatFilesTitle')\n }\n return props.name\n })\n\n // state\n const isReady = computed(() => chat.value.status === 'ready')\n const activeMessage = computed(() => {\n return messages.value?.[messages.value.length - 1]\n })\n\n // expose\n defineExpose({\n startNewChat,\n regenerate,\n stopGeneration,\n sendMessage,\n })\n\n // load chat, conversations and feedbacks on agent or chat change\n watch(\n [storeAgentId, localChatId],\n async ([newAgentId, newChatId]) => {\n await store.initialize(newAgentId, newChatId)\n },\n {\n immediate: true,\n },\n )\n // load conversations when navigating to conversations view\n watch(activeView, (newView) => {\n if (newView === 'conversations') {\n store.loadConversations(storeAgentId.value)\n } else if (newView === 'files') {\n store.loadChatFiles()\n }\n })\n // emit message updates for up-to-date context in parent components\n watch(\n activeMessage,\n (message) => {\n if (message) {\n emit('message-update', message)\n }\n },\n {\n deep: true,\n immediate: true,\n },\n )\n</script>\n\n<template>\n <div\n ref=\"chatbotEl\"\n class=\"pk-chatbot\"\n :class=\"[\n themeClass,\n {\n 'pk-chatbot--fullscreen': isFullscreen,\n 'pk-chatbot--panel': isPanel,\n 'pk-chatbot--phone': isPhoneWidth,\n 'pk-chatbot--tight': isTightWidth,\n 'pk-chatbot--wide': isWideWidth,\n },\n ]\">\n <!-- #region title -->\n <div\n v-if=\"!isFullscreen\"\n class=\"pk-chatbot__header\"\n :style=\"\n useColorsForAgentHeader\n ? {\n backgroundColor: mainColor,\n }\n : undefined\n \">\n <div class=\"pk-chatbot__header-start\">\n <template v-if=\"activeView !== 'chat'\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:arrow-left-s-line',\n title: $t('action.goBack'),\n }\"\n @click=\"navigate('chat')\" />\n <VvIcon\n :name=\"\n activeView === 'conversations'\n ? 'ri:history-line'\n : activeView === 'files'\n ? 'ri:attachment-2'\n : 'ri:user-line'\n \"\n class=\"pk-chatbot__nav-icon\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \" />\n </template>\n <!-- Panel mode: no avatar, the docked panel identifies the\n agent with the title alone -->\n <PkAvatar\n v-else-if=\"!isPanel\"\n modifiers=\"surface ring shrink-0\"\n class=\"pk-chatbot__avatar\"\n :name\n :img-src=\"agentInterface?.logo\" />\n <strong\n class=\"pk-chatbot__title\"\n :title=\"title\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \">\n {{ title }}\n </strong>\n </div>\n <VvButtonGroup class=\"pk-chatbot__header-end\" modifiers=\"compact\">\n <slot\n name=\"header-actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n <template v-if=\"isPanel\">\n <VvButton\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:edit-box-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n <VvButton\n v-if=\"!isReasoningAgent\"\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:history-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.viewRecentChats')\"\n @click=\"navigate('conversations')\" />\n </template>\n <VvDropdown\n v-if=\"showHeaderMenu\"\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:more-2-fill',\n disabled: !isReady,\n title: $t('action.moreActions'),\n }\" />\n <template #items>\n <VvDropdownAction\n v-if=\"!isPanel\"\n @click=\"startNewChat()\">\n <VvIcon name=\"ri:edit-box-line\" />\n {{ $t('action.startNewChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"!isReasoningAgent && !isPanel\"\n @click=\"navigate('conversations')\">\n <VvIcon name=\"ri:history-line\" />\n {{ $t('action.viewRecentChats') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"\n (canManageProfile || userFilesEnabled) &&\n localChatId\n \"\n @click=\"navigate('files')\">\n <VvIcon name=\"ri:attachment-2\" />\n {{ $t('userFiles.chatFilesTitle') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"canManageProfile\"\n @click=\"navigate('profile')\">\n <VvIcon name=\"ri:user-line\" />\n {{ $t('label.profile') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-else-if=\"canSignIn\"\n @click=\"navigate('auth')\">\n <VvIcon name=\"ri:account-circle-line\" />\n {{ $t('action.signIn') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n <slot\n name=\"actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n </VvButtonGroup>\n </div>\n <!-- #endregion -->\n\n <div v-if=\"isReasoningAgent\">\n <div class=\"pk-chatbot__warning-body\">\n <VvAlert modifiers=\"callout\">\n <div class=\"pk-chatbot__warning-header\">\n <VvIcon\n name=\"ri:alert-line\"\n class=\"pk-chatbot__warning-icon\" />\n <strong class=\"pk-chatbot__warning-title\">\n {{ $t('message.warning') }}\n </strong>\n </div>\n <p class=\"pk-chatbot__warning-text\">\n {{ $t('message.reasoningAgent') }}\n </p>\n </VvAlert>\n </div>\n </div>\n\n <!-- #region views -->\n <PkChatbotViewChat\n v-if=\"activeView === 'chat'\"\n :agent-id\n :modifier\n :user-name\n @show-info=\"emit('show-info', $event)\"\n @revise=\"emit('revise', $event)\">\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewChat>\n <PkChatbotViewConversations\n v-else-if=\"activeView === 'conversations'\"\n :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewConversations>\n <PkChatbotViewFiles v-else-if=\"activeView === 'files'\" :agent-id />\n <div\n v-else-if=\"activeView === 'auth'\"\n class=\"pk-chatbot__view-auth flex-1 min-h-0 overflow-y-auto\">\n <!-- Sign-in form provided by the host widget (owns the auth client) -->\n <slot name=\"view-auth\" />\n </div>\n <PkChatbotViewProfile v-else :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewProfile>\n <!-- #endregion -->\n\n <!-- #region footer -->\n <div v-if=\"footerMessage\" class=\"pk-chatbot__footer\">\n <PkStreamingMarkdown :markdown=\"footerMessage\" class=\"wysiwyg\" />\n </div>\n <!-- #endregion -->\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot {\n display: flex;\n flex-direction: column;\n position: relative;\n height: 100%;\n background-color: var(--color-surface);\n\n &__header {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n justify-content: space-between;\n gap: var(--spacing-14);\n background-color: var(--color-surface-1);\n padding: var(--spacing-14);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__header-start {\n display: flex;\n align-items: center;\n gap: var(--spacing-sm);\n min-width: 0;\n }\n\n &__nav-icon {\n font-size: var(--text-18);\n }\n\n &__avatar {\n width: var(--spacing-24);\n height: var(--spacing-24);\n }\n\n &__title {\n font-size: var(--text-16);\n font-weight: var(--font-semibold);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__header-end {\n flex-shrink: 0;\n }\n\n &__warning-body {\n padding: var(--spacing-md);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__warning-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n margin-bottom: var(--spacing-4);\n }\n\n &__warning-icon {\n width: var(--spacing-16);\n height: var(--spacing-16);\n }\n\n &__warning-title {\n font-weight: 600;\n font-size: var(--text-14);\n }\n\n &__warning-text {\n font-size: var(--text-12);\n color: var(--color-word-3);\n }\n\n &__footer {\n padding: var(--spacing-md);\n border-top: 1px solid var(--color-surface-3);\n text-align: center;\n font-size: var(--text-12);\n color: var(--color-word-2);\n }\n\n // Modifier: fullscreen — layout full-page (Gemini-style): nessun\n // header interno, sfondo trasparente, input pill centrato.\n // Il chrome (sidebar, navigazione) è gestito dalla pagina ospite.\n &--fullscreen {\n // The dock, 60px. Declared rather than taken from the agent chip,\n // which the tight layout hides: the pill fell to 54px there and\n // the call bar that takes its place stayed at 60. There is no 60\n // in the spacing scale and a raw `60px` would stop following the\n // root font size, which the labels inside do follow.\n --pk-chat-fullscreen-dock-height: calc(\n var(--spacing-56) + var(--spacing-4)\n );\n // The pill, the notice that tucks into its top corners and the\n // call bar all round their ends on half of it.\n --pk-chat-dock-radius: calc(\n var(--pk-chat-fullscreen-dock-height) / 2\n );\n\n background-color: transparent;\n\n .pk-chatbot__footer {\n border-top: none;\n padding: 0 var(--spacing-md) var(--spacing-sm) var(--spacing-md);\n }\n\n // Reading column aligned with the input (Gemini-style). The\n // constraint lives on the inner wrapper so the scroll container\n // spans the page and the scrollbar sits at its edge.\n .pk-chatbot-messages {\n // Symmetric gutters: a visible scrollbar would otherwise\n // shrink the content box on one side only, shifting the\n // column off the input axis\n scrollbar-gutter: stable both-edges;\n // Dissolve the content at both edges (works on any page\n // background, dots included — a surface-colored fade band\n // would cut through the pattern instead)\n mask-image: linear-gradient(\n to bottom,\n transparent 0,\n #000 var(--spacing-40),\n #000 calc(100% - var(--spacing-40)),\n transparent 100%\n );\n // Resting clearance so the first/last lines sit inside the\n // fully opaque zone\n padding-block: var(--spacing-48);\n }\n\n .pk-chatbot-messages__wrapper {\n width: 100%;\n max-width: 768px;\n margin-inline: auto;\n }\n\n // Outline rail: wide chats only, measured on the chat and not on\n // the viewport, so a preview frame squeezed by side panels loses\n // it like a phone does. The rail sits at the edge of the scroll\n // area beside the reading column: below the `--wide` threshold\n // (768px column plus a 40px rail on each side) it would cover the\n // messages instead.\n .pk-chatbot-messages__outline {\n display: none;\n }\n\n &.pk-chatbot--wide .pk-chatbot-messages__outline {\n display: block;\n }\n\n .pk-chatbot-file-preview {\n background-color: var(--color-surface);\n }\n\n .pk-chatbot-input__notice {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-3);\n border-top: 1px solid var(--color-surface-2);\n padding: var(--spacing-10) var(--spacing-16);\n\n // As deep as the pill's corners, or the page shows through\n // where their curve leaves the side\n &::after {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-2);\n bottom: calc(-1 * var(--pk-chat-dock-radius));\n height: var(--pk-chat-dock-radius);\n left: -1px;\n right: -1px;\n }\n }\n\n .pk-chatbot-input {\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n\n &__form {\n // True semicircles at rest, concentric with the send\n // button, whose center sits 30px in. Not `rounded-full`:\n // once Shift+Enter grows the field the ends would swell\n // with it and cut into the first and last line.\n border-radius: var(--pk-chat-dock-radius);\n min-height: var(--pk-chat-fullscreen-dock-height);\n padding: var(--spacing-12) var(--spacing-16);\n background-color: var(--color-surface-1);\n border-color: var(--color-surface-2);\n box-shadow: none;\n transition:\n border-color var(--duration-200) var(--ease-in-out),\n box-shadow var(--duration-200) var(--ease-in-out);\n\n &:has(:focus-within) {\n border-color: var(--color-surface-4);\n box-shadow: var(--shadow-sm);\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n\n &__textarea {\n background-color: transparent;\n }\n }\n\n // Touch target ≥44px: estende l'hit area dei pulsanti\n // (icona 20px + padding 4px ≈ 28px) senza ingrandire l'icona.\n // Vale per l'input e per la barra della chiamata, che montano\n // lo stesso pulsante.\n .pk-chat-button {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--spacing-8));\n }\n }\n\n // A live call takes the place of the input, so it takes its shape,\n // its column and its height: full-bleed here would break the\n // reading axis, and a different height would make the dock jump\n // the moment the call starts.\n .pk-voice-session {\n --pk-chat-dock-height: var(--pk-chat-fullscreen-dock-height);\n --pk-voice-orb-size: var(--spacing-40);\n --pk-voice-orb-bars-height: var(--spacing-20);\n --pk-voice-session-surface: var(--color-surface-1);\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n padding-inline: var(--spacing-16);\n border-color: var(--color-surface-2);\n border-radius: var(--pk-chat-dock-radius);\n box-shadow: none;\n\n &__timer {\n font-size: var(--text-14);\n }\n }\n }\n\n // Modifier: panel — compact chrome for a docked side panel: the host\n // container owns background and border, the header slims down and\n // drops its own surface so the chat blends into the panel.\n &--panel {\n background-color: transparent;\n\n .pk-chatbot__header {\n background-color: transparent;\n\n // Fixed height so the panel header lines up with the host's\n // own chrome. Custom properties inherit through the shadow\n // root: an embedding host can override it to match its\n // header (the default matches the platform header, 50px).\n height: var(--pk-chatbot-panel-header-height, 50px);\n align-items: center;\n padding-block: 0;\n padding-inline: var(--spacing-10);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n .pk-chatbot__title {\n font-size: var(--text-14);\n }\n\n .pk-chatbot__footer {\n border-top: none;\n padding: var(--spacing-6) var(--spacing-md);\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import {\n computed,\n inject,\n toRef,\n useTemplateRef,\n watch,\n watchEffect,\n useSlots,\n } from 'vue'\n import { useElementSize } from '@vueuse/core'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type {\n AgentInterface,\n AgentModel,\n AgentFileUpload,\n AgentGatewayOptions,\n AgentVoiceCapability,\n ChatMessageActions,\n RevisedAnswer,\n UIChatMessage,\n AgentTools,\n } from 'models'\n import {\n useChatbotStore,\n useLocalizedString,\n provideShortUrlResolver,\n } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import PkChatbotViewChat from './PkChatbotViewChat.vue'\n import PkChatbotViewConversations from './PkChatbotViewConversations.vue'\n import PkChatbotViewFiles from './PkChatbotViewFiles.vue'\n import PkChatbotViewProfile from './PkChatbotViewProfile.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n import { provideChatImagePolicy } from './chatImagePolicy'\n import { resolveContrastColor } from './utils'\n\n // Captured in script setup to avoid TS7022 circular inference in template\n const slots = useSlots()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const props = defineProps<{\n baseUrl: string\n basePath?: string\n agentId: string\n agentType?: 'reasoning' | 'chat'\n userId?: string\n name?: string\n model?: string\n agentModel?: AgentModel\n agentTools?: AgentTools\n agentInterface?: AgentInterface\n actions?: ChatMessageActions[]\n revisedAnswers?: RevisedAnswer[]\n headers?: Record<string, string>\n systemTheme?: 'light' | 'dark'\n agentFileUpload?: AgentFileUpload\n /** Voice configuration from the agent settings: enables the talk button. */\n voice?: AgentVoiceCapability\n fallbackModels?: string[]\n gatewayOptions?: Partial<AgentGatewayOptions>\n externalContext?: Record<string, unknown>\n /** Layout modifier: `widget` (default) keeps the contained layout with header, `fullscreen` removes header/background for full-page usage (the page provides the chrome), `panel` keeps the header but slims it down for a docked side panel (the host provides background and border) */\n modifier?: 'widget' | 'fullscreen' | 'panel'\n /** Display name of the current user, used for the `{name}` placeholder in the fullscreen greeting */\n userName?: string\n /** Show the \"Files in this chat\" menu for hosts that don't provide the\n * profile context but whose user is authenticated (e.g. the backoffice\n * playground). The widget host relies on `canManageProfile` instead. */\n userFilesEnabled?: boolean\n }>()\n const emit = defineEmits<{\n 'message-update': [message: UIChatMessage]\n 'show-info': [message: UIChatMessage]\n revise: [message: UIChatMessage]\n error: [error: { type: string; title: string; status: number }]\n }>()\n\n const chatId = defineModel<string | undefined>('chatId', {\n type: String,\n default: undefined,\n })\n\n const store = useChatbotStore(props.agentId)\n\n // Resolve backend short-urls (`/api/r/<hash>`) back to their real target\n // for display in tool components. Batched + cached across the chat tree.\n provideShortUrlResolver((hashes) =>\n store.apiClient.resolveShortUrls(hashes),\n )\n\n const { messages, chat, localChatId, activeView, chatError, isDark } =\n storeToRefs(store)\n\n // Ref to store.agentId for watchers (avoids duplicate-key with agentId prop)\n const storeAgentId = toRef(store, 'agentId')\n\n const { startNewChat, stopGeneration, regenerate, navigate, sendMessage } =\n store\n\n // Images in the answers and the tool cards load by themselves only from\n // the backend this chat talks to and from the hosts the agent lists.\n provideChatImagePolicy(\n () => props.agentInterface?.imageHosts,\n () => props.baseUrl,\n )\n\n // Optional profile-management context provided by the host widget.\n // Present only for authenticated, non-anonymous sessions.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canManageProfile = computed(\n () => profile?.canManageProfile.value ?? false,\n )\n // Anonymous users get a \"Sign in\" entry instead of the profile one,\n // provided the host filled the `view-auth` slot and the agent has at\n // least one auth method enabled.\n const canSignIn = computed(\n () =>\n !canManageProfile.value &&\n (profile?.canSignIn?.value ?? false) &&\n slots['view-auth'] !== undefined,\n )\n\n // Once the sign-in completes (the session becomes non-anonymous) leave\n // the auth view and return to the conversation.\n watch(canManageProfile, (canManage) => {\n if (canManage && activeView.value === 'auth') {\n navigate('chat')\n }\n })\n\n // Sync props → store reactively\n watchEffect(() => {\n store.baseUrl = props.baseUrl\n store.basePath = props.basePath\n store.name = props.name\n store.agentType = props.agentType\n store.userId = props.userId\n store.agentInterface = props.agentInterface\n store.agentModel = props.agentModel\n store.agentTools = props.agentTools\n store.headers = props.headers\n store.revisedAnswers = props.revisedAnswers\n store.model = props.model\n store.actionsInput = props.actions\n store.agentFileUpload = props.agentFileUpload\n store.agentVoice = props.voice\n store.fallbackModels = props.fallbackModels\n store.gatewayOptions = props.gatewayOptions\n store.externalContext = props.externalContext\n store.systemTheme = props.systemTheme\n store.hideWelcomeMessage = props.modifier === 'fullscreen'\n })\n\n // Sync chatId v-model ↔ store (bidirectional)\n // Parent → store: if the parent provides an external chatId, push it into\n // the store so the Chat instance picks it up.\n watch(\n () => chatId.value,\n (id) => {\n if (id && id !== store.localChatId) {\n store.localChatId = id\n }\n },\n { immediate: true },\n )\n // Store → parent: keep the v-model in sync whenever the store changes\n // the chatId internally (e.g. startNewChat).\n watch(localChatId, (id) => {\n chatId.value = id\n })\n\n // Emit structured errors from chat to the parent component\n watch(chatError, (error) => {\n if (!error) {\n return\n }\n try {\n const parsedError = JSON.parse(error.message) as {\n type: string\n title: string\n status: number\n }\n emit('error', parsedError)\n } catch {\n // ignore non-JSON errors\n }\n })\n\n // ui\n const isFullscreen = computed(() => props.modifier === 'fullscreen')\n const isPanel = computed(() => props.modifier === 'panel')\n\n /**\n * How much room the chat has, measured on the chat and not on the window.\n *\n * The same chat renders in a phone page, in a 408px widget, in a docked\n * panel and in a preview frame that the side panels squeeze to phone width\n * on a desktop screen, so the viewport says nothing about the space the\n * layout actually has. A CSS container query would read the same number\n * with no JavaScript, but `container-type` makes the element a containing\n * block for `position: fixed` descendants on every engine except Chrome\n * 129 and later, which displaces every dropdown in the chat: see\n * docs/agents/gotchas.md, \"The chat measures its own width, and not with a\n * container query\".\n *\n * The thresholds carry the modifiers, so the CSS stays declarative:\n * `--phone` has as much room as a phone page, `--tight` is too tight for\n * the agent chip beside the field, `--wide` has room for the outline rail\n * beside the reading column. Before the first measurement the width is 0\n * and no modifier applies, which is the roomy layout the base CSS writes.\n */\n const chatbotEl = useTemplateRef<HTMLDivElement>('chatbotEl')\n const { width: chatbotWidth } = useElementSize(chatbotEl)\n const isPhoneWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 576,\n )\n const isTightWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 640,\n )\n const isWideWidth = computed(() => chatbotWidth.value >= 848)\n const isReasoningAgent = computed(() => props.agentType === 'reasoning')\n const useColorsForAgentHeader = computed(\n () => props.agentInterface?.useColorsForAgentHeader,\n )\n // Panel mode promotes the everyday actions (new chat, history) from the\n // dropdown to direct header buttons: the dropdown then only remains for\n // the secondary entries (files, profile, sign-in) and disappears when\n // none of them applies.\n const showHeaderMenu = computed(\n () =>\n !isPanel.value ||\n canManageProfile.value ||\n canSignIn.value ||\n (!!props.userFilesEnabled && !!localChatId.value),\n )\n const themeClass = computed(() => [\n 'theme',\n `theme--${isDark.value ? 'dark' : 'light'}`,\n ])\n const mainColor = computed(() => props.agentInterface?.mainColor)\n const contrastColor = computed(() =>\n resolveContrastColor(props.agentInterface?.textColor, mainColor.value),\n )\n const footerMessage = useLocalizedString(\n () => props.agentInterface?.footerMessage,\n )\n const title = computed(() => {\n if (activeView.value === 'conversations') {\n return $t('label.conversations')\n }\n if (activeView.value === 'profile') {\n return $t('label.profile')\n }\n if (activeView.value === 'auth') {\n return $t('label.authentication')\n }\n if (activeView.value === 'files') {\n return $t('userFiles.chatFilesTitle')\n }\n return props.name\n })\n\n // state\n const isReady = computed(() => chat.value.status === 'ready')\n const activeMessage = computed(() => {\n return messages.value?.[messages.value.length - 1]\n })\n\n // expose\n defineExpose({\n startNewChat,\n regenerate,\n stopGeneration,\n sendMessage,\n })\n\n // load chat, conversations and feedbacks on agent or chat change\n watch(\n [storeAgentId, localChatId],\n async ([newAgentId, newChatId]) => {\n await store.initialize(newAgentId, newChatId)\n },\n {\n immediate: true,\n },\n )\n // load conversations when navigating to conversations view\n watch(activeView, (newView) => {\n if (newView === 'conversations') {\n store.loadConversations(storeAgentId.value)\n } else if (newView === 'files') {\n store.loadChatFiles()\n }\n })\n // emit message updates for up-to-date context in parent components\n watch(\n activeMessage,\n (message) => {\n if (message) {\n emit('message-update', message)\n }\n },\n {\n deep: true,\n immediate: true,\n },\n )\n</script>\n\n<template>\n <div\n ref=\"chatbotEl\"\n class=\"pk-chatbot\"\n :class=\"[\n themeClass,\n {\n 'pk-chatbot--fullscreen': isFullscreen,\n 'pk-chatbot--panel': isPanel,\n 'pk-chatbot--phone': isPhoneWidth,\n 'pk-chatbot--tight': isTightWidth,\n 'pk-chatbot--wide': isWideWidth,\n },\n ]\">\n <!-- #region title -->\n <div\n v-if=\"!isFullscreen\"\n class=\"pk-chatbot__header\"\n :style=\"\n useColorsForAgentHeader\n ? {\n backgroundColor: mainColor,\n }\n : undefined\n \">\n <div class=\"pk-chatbot__header-start\">\n <template v-if=\"activeView !== 'chat'\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:arrow-left-s-line',\n title: $t('action.goBack'),\n }\"\n @click=\"navigate('chat')\" />\n <VvIcon\n :name=\"\n activeView === 'conversations'\n ? 'ri:history-line'\n : activeView === 'files'\n ? 'ri:attachment-2'\n : 'ri:user-line'\n \"\n class=\"pk-chatbot__nav-icon\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \" />\n </template>\n <!-- Panel mode: no avatar, the docked panel identifies the\n agent with the title alone -->\n <PkAvatar\n v-else-if=\"!isPanel\"\n modifiers=\"surface ring shrink-0\"\n class=\"pk-chatbot__avatar\"\n :name\n :img-src=\"agentInterface?.logo\" />\n <strong\n class=\"pk-chatbot__title\"\n :title=\"title\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \">\n {{ title }}\n </strong>\n </div>\n <VvButtonGroup class=\"pk-chatbot__header-end\" modifiers=\"compact\">\n <slot\n name=\"header-actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n <template v-if=\"isPanel\">\n <VvButton\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:edit-box-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n <VvButton\n v-if=\"!isReasoningAgent\"\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:history-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.viewRecentChats')\"\n @click=\"navigate('conversations')\" />\n </template>\n <VvDropdown\n v-if=\"showHeaderMenu\"\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:more-2-fill',\n disabled: !isReady,\n title: $t('action.moreActions'),\n }\" />\n <template #items>\n <VvDropdownAction\n v-if=\"!isPanel\"\n @click=\"startNewChat()\">\n <VvIcon name=\"ri:edit-box-line\" />\n {{ $t('action.startNewChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"!isReasoningAgent && !isPanel\"\n @click=\"navigate('conversations')\">\n <VvIcon name=\"ri:history-line\" />\n {{ $t('action.viewRecentChats') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"\n (canManageProfile || userFilesEnabled) &&\n localChatId\n \"\n @click=\"navigate('files')\">\n <VvIcon name=\"ri:attachment-2\" />\n {{ $t('userFiles.chatFilesTitle') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"canManageProfile\"\n @click=\"navigate('profile')\">\n <VvIcon name=\"ri:user-line\" />\n {{ $t('label.profile') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-else-if=\"canSignIn\"\n @click=\"navigate('auth')\">\n <VvIcon name=\"ri:account-circle-line\" />\n {{ $t('action.signIn') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n <slot\n name=\"actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n </VvButtonGroup>\n </div>\n <!-- #endregion -->\n\n <div v-if=\"isReasoningAgent\">\n <div class=\"pk-chatbot__warning-body\">\n <VvAlert modifiers=\"callout\">\n <div class=\"pk-chatbot__warning-header\">\n <VvIcon\n name=\"ri:alert-line\"\n class=\"pk-chatbot__warning-icon\" />\n <strong class=\"pk-chatbot__warning-title\">\n {{ $t('message.warning') }}\n </strong>\n </div>\n <p class=\"pk-chatbot__warning-text\">\n {{ $t('message.reasoningAgent') }}\n </p>\n </VvAlert>\n </div>\n </div>\n\n <!-- #region views -->\n <PkChatbotViewChat\n v-if=\"activeView === 'chat'\"\n :agent-id\n :modifier\n :user-name\n @show-info=\"emit('show-info', $event)\"\n @revise=\"emit('revise', $event)\">\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewChat>\n <PkChatbotViewConversations\n v-else-if=\"activeView === 'conversations'\"\n :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewConversations>\n <PkChatbotViewFiles v-else-if=\"activeView === 'files'\" :agent-id />\n <div\n v-else-if=\"activeView === 'auth'\"\n class=\"pk-chatbot__view-auth flex-1 min-h-0 overflow-y-auto\">\n <!-- Sign-in form provided by the host widget (owns the auth client) -->\n <slot name=\"view-auth\" />\n </div>\n <PkChatbotViewProfile v-else :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewProfile>\n <!-- #endregion -->\n\n <!-- #region footer -->\n <div v-if=\"footerMessage\" class=\"pk-chatbot__footer\">\n <PkStreamingMarkdown :markdown=\"footerMessage\" class=\"wysiwyg\" />\n </div>\n <!-- #endregion -->\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot {\n display: flex;\n flex-direction: column;\n position: relative;\n height: 100%;\n background-color: var(--color-surface);\n\n &__header {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n justify-content: space-between;\n gap: var(--spacing-14);\n background-color: var(--color-surface-1);\n padding: var(--spacing-14);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__header-start {\n display: flex;\n align-items: center;\n gap: var(--spacing-sm);\n min-width: 0;\n }\n\n &__nav-icon {\n font-size: var(--text-18);\n }\n\n &__avatar {\n width: var(--spacing-24);\n height: var(--spacing-24);\n }\n\n &__title {\n font-size: var(--text-16);\n font-weight: var(--font-semibold);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__header-end {\n flex-shrink: 0;\n }\n\n &__warning-body {\n padding: var(--spacing-md);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__warning-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n margin-bottom: var(--spacing-4);\n }\n\n &__warning-icon {\n width: var(--spacing-16);\n height: var(--spacing-16);\n }\n\n &__warning-title {\n font-weight: 600;\n font-size: var(--text-14);\n }\n\n &__warning-text {\n font-size: var(--text-12);\n color: var(--color-word-3);\n }\n\n &__footer {\n padding: var(--spacing-md);\n border-top: 1px solid var(--color-surface-3);\n text-align: center;\n font-size: var(--text-12);\n color: var(--color-word-2);\n }\n\n // Modifier: fullscreen — layout full-page (Gemini-style): nessun\n // header interno, sfondo trasparente, input pill centrato.\n // Il chrome (sidebar, navigazione) è gestito dalla pagina ospite.\n &--fullscreen {\n // The dock, 60px. Declared rather than taken from the agent chip,\n // which the tight layout hides: the pill fell to 54px there and\n // the call bar that takes its place stayed at 60. There is no 60\n // in the spacing scale and a raw `60px` would stop following the\n // root font size, which the labels inside do follow.\n --pk-chat-fullscreen-dock-height: calc(\n var(--spacing-56) + var(--spacing-4)\n );\n // The pill, the notice that tucks into its top corners and the\n // call bar all round their ends on half of it.\n --pk-chat-dock-radius: calc(\n var(--pk-chat-fullscreen-dock-height) / 2\n );\n\n background-color: transparent;\n\n .pk-chatbot__footer {\n border-top: none;\n padding: 0 var(--spacing-md) var(--spacing-sm) var(--spacing-md);\n }\n\n // Reading column aligned with the input (Gemini-style). The\n // constraint lives on the inner wrapper so the scroll container\n // spans the page and the scrollbar sits at its edge.\n .pk-chatbot-messages {\n // Symmetric gutters: a visible scrollbar would otherwise\n // shrink the content box on one side only, shifting the\n // column off the input axis\n scrollbar-gutter: stable both-edges;\n // Dissolve the content at both edges (works on any page\n // background, dots included — a surface-colored fade band\n // would cut through the pattern instead)\n mask-image: linear-gradient(\n to bottom,\n transparent 0,\n #000 var(--spacing-40),\n #000 calc(100% - var(--spacing-40)),\n transparent 100%\n );\n // Resting clearance so the first/last lines sit inside the\n // fully opaque zone\n padding-block: var(--spacing-48);\n }\n\n .pk-chatbot-messages__wrapper {\n width: 100%;\n max-width: 768px;\n margin-inline: auto;\n }\n\n // Outline rail: wide chats only, measured on the chat and not on\n // the viewport, so a preview frame squeezed by side panels loses\n // it like a phone does. The rail sits at the edge of the scroll\n // area beside the reading column: below the `--wide` threshold\n // (768px column plus a 40px rail on each side) it would cover the\n // messages instead.\n .pk-chatbot-messages__outline {\n display: none;\n }\n\n &.pk-chatbot--wide .pk-chatbot-messages__outline {\n display: block;\n }\n\n .pk-chatbot-file-preview {\n background-color: var(--color-surface);\n }\n\n .pk-chatbot-input__notice {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-3);\n border-top: 1px solid var(--color-surface-2);\n padding: var(--spacing-10) var(--spacing-16);\n\n // As deep as the pill's corners, or the page shows through\n // where their curve leaves the side\n &::after {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-2);\n bottom: calc(-1 * var(--pk-chat-dock-radius));\n height: var(--pk-chat-dock-radius);\n left: -1px;\n right: -1px;\n }\n }\n\n .pk-chatbot-input {\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n\n &__form {\n // True semicircles at rest, concentric with the send\n // button, whose center sits 30px in. Not `rounded-full`:\n // once Shift+Enter grows the field the ends would swell\n // with it and cut into the first and last line.\n border-radius: var(--pk-chat-dock-radius);\n min-height: var(--pk-chat-fullscreen-dock-height);\n padding: var(--spacing-12) var(--spacing-16);\n background-color: var(--color-surface-1);\n border-color: var(--color-surface-2);\n box-shadow: none;\n transition:\n border-color var(--duration-200) var(--ease-in-out),\n box-shadow var(--duration-200) var(--ease-in-out);\n\n &:has(:focus-within) {\n border-color: var(--color-surface-4);\n box-shadow: var(--shadow-sm);\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n\n &__textarea {\n background-color: transparent;\n }\n }\n\n // Touch target ≥44px: estende l'hit area dei pulsanti\n // (icona 20px + padding 4px ≈ 28px) senza ingrandire l'icona.\n // Vale per l'input e per la barra della chiamata, che montano\n // lo stesso pulsante.\n .pk-chat-button {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--spacing-8));\n }\n }\n\n // A live call takes the place of the input, so it takes its shape,\n // its column and its height: full-bleed here would break the\n // reading axis, and a different height would make the dock jump\n // the moment the call starts.\n .pk-voice-session {\n --pk-chat-dock-height: var(--pk-chat-fullscreen-dock-height);\n --pk-voice-orb-size: var(--spacing-40);\n --pk-voice-orb-bars-height: var(--spacing-20);\n --pk-voice-session-surface: var(--color-surface-1);\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n padding-inline: var(--spacing-16);\n border-color: var(--color-surface-2);\n border-radius: var(--pk-chat-dock-radius);\n box-shadow: none;\n\n &__timer {\n font-size: var(--text-14);\n }\n }\n }\n\n // Modifier: panel — compact chrome for a docked side panel: the host\n // container owns background and border, the header slims down and\n // drops its own surface so the chat blends into the panel.\n &--panel {\n background-color: transparent;\n\n .pk-chatbot__header {\n background-color: transparent;\n\n // Fixed height so the panel header lines up with the host's\n // own chrome. Custom properties inherit through the shadow\n // root: an embedding host can override it to match its\n // header (the default matches the platform header, 50px).\n height: var(--pk-chatbot-panel-header-height, 50px);\n align-items: center;\n padding-block: 0;\n padding-inline: var(--spacing-10);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n .pk-chatbot__title {\n font-size: var(--text-14);\n }\n\n .pk-chatbot__footer {\n border-top: none;\n padding: var(--spacing-6) var(--spacing-md);\n }\n }\n }\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PkChatbot-BEnC7_hV.js","names":["_useModel","__unplugin_components_0","__unplugin_components_1","__unplugin_components_2","__unplugin_components_3","__unplugin_components_4","__unplugin_components_5","_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createElementVNode","_unref","_Fragment","_createVNode","_mergeProps","_createBlock","PkAvatar","_createCommentVNode","_toDisplayString","_withCtx","_renderSlot","_normalizeProps","_guardReactiveProps","_createTextVNode","PkChatbotViewChat","_createSlots","_renderList","PkChatbotViewConversations","PkChatbotViewFiles","PkChatbotViewProfile","PkStreamingMarkdown","_sfc_main","provideChatImagePolicy","PkChatbotViewConversations_default","PkChatbotViewChat_default","provideShortUrlResolver","PkChatbotViewFiles_default","useLocalizedString","resolveContrastColor","PkAvatar_default","ChatbotProfileContextKey","PkChatbotViewProfile_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","PkChatbot_default","defineComponent","\"chatId\"","\"chatIdModifiers\"","__expose","__emit","slots","useSlots","$t","useI18n","props","__props","emit","chatId","useModel","store","useChatbotStore","hashes","storeToRefs","storeAgentId","toRef","profile","inject","canManageProfile","computed","canSignIn","watch","canManage","activeView","navigate","watchEffect","id","localChatId","chatError","error","parsedError","isFullscreen","isPanel","chatbotEl","useTemplateRef","chatbotWidth","useElementSize","isPhoneWidth","isTightWidth","isWideWidth","isReasoningAgent","useColorsForAgentHeader","showHeaderMenu","themeClass","isDark","mainColor","contrastColor","footerMessage","title","isReady","chat","activeMessage","messages","newAgentId","newChatId","newView","message","_component_VvButton","VvButton","_component_VvIcon","VvIcon","_component_VvDropdownAction","VvDropdownAction","_component_VvDropdown","VvDropdown","_component_VvButtonGroup","VvButtonGroup","_component_VvAlert","VvAlert","openBlock","createElementBlock","normalizeClass","createCommentVNode","normalizeStyle","createElementVNode","unref","createBlock","Fragment","createVNode","mergeProps","_cache","toDisplayString","withCtx","renderSlot","_ctx","normalizeProps","guardReactiveProps","startNewChat","stopGeneration","createTextVNode","$event","createSlots","renderList","slotName","slotData","PkStreamingMarkdown_default"],"sources":["../../../../packages/components/src/chat/PkChatbot.vue","../../../../packages/components/src/chat/PkChatbot.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import {\n computed,\n inject,\n toRef,\n useTemplateRef,\n watch,\n watchEffect,\n useSlots,\n } from 'vue'\n import { useElementSize } from '@vueuse/core'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type {\n AgentInterface,\n AgentModel,\n AgentFileUpload,\n AgentGatewayOptions,\n AgentVoiceCapability,\n ChatMessageActions,\n RevisedAnswer,\n UIChatMessage,\n AgentTools,\n } from 'models'\n import {\n useChatbotStore,\n useLocalizedString,\n provideShortUrlResolver,\n } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import PkChatbotViewChat from './PkChatbotViewChat.vue'\n import PkChatbotViewConversations from './PkChatbotViewConversations.vue'\n import PkChatbotViewFiles from './PkChatbotViewFiles.vue'\n import PkChatbotViewProfile from './PkChatbotViewProfile.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n import { provideChatImagePolicy } from './chatImagePolicy'\n import { resolveContrastColor } from './utils'\n\n // Captured in script setup to avoid TS7022 circular inference in template\n const slots = useSlots()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const props = defineProps<{\n baseUrl: string\n basePath?: string\n agentId: string\n agentType?: 'reasoning' | 'chat'\n userId?: string\n name?: string\n model?: string\n agentModel?: AgentModel\n agentTools?: AgentTools\n agentInterface?: AgentInterface\n actions?: ChatMessageActions[]\n revisedAnswers?: RevisedAnswer[]\n headers?: Record<string, string>\n systemTheme?: 'light' | 'dark'\n agentFileUpload?: AgentFileUpload\n /** Voice configuration from the agent settings: enables the talk button. */\n voice?: AgentVoiceCapability\n fallbackModels?: string[]\n gatewayOptions?: Partial<AgentGatewayOptions>\n externalContext?: Record<string, unknown>\n /** Layout modifier: `widget` (default) keeps the contained layout with header, `fullscreen` removes header/background for full-page usage (the page provides the chrome), `panel` keeps the header but slims it down for a docked side panel (the host provides background and border) */\n modifier?: 'widget' | 'fullscreen' | 'panel'\n /** Display name of the current user, used for the `{name}` placeholder in the fullscreen greeting */\n userName?: string\n /** Show the \"Files in this chat\" menu for hosts that don't provide the\n * profile context but whose user is authenticated (e.g. the backoffice\n * playground). The widget host relies on `canManageProfile` instead. */\n userFilesEnabled?: boolean\n }>()\n const emit = defineEmits<{\n 'message-update': [message: UIChatMessage]\n 'show-info': [message: UIChatMessage]\n revise: [message: UIChatMessage]\n error: [error: { type: string; title: string; status: number }]\n }>()\n\n const chatId = defineModel<string | undefined>('chatId', {\n type: String,\n default: undefined,\n })\n\n const store = useChatbotStore(props.agentId)\n\n // Resolve backend short-urls (`/api/r/<hash>`) back to their real target\n // for display in tool components. Batched + cached across the chat tree.\n provideShortUrlResolver((hashes) =>\n store.apiClient.resolveShortUrls(hashes),\n )\n\n const { messages, chat, localChatId, activeView, chatError, isDark } =\n storeToRefs(store)\n\n // Ref to store.agentId for watchers (avoids duplicate-key with agentId prop)\n const storeAgentId = toRef(store, 'agentId')\n\n const { startNewChat, stopGeneration, regenerate, navigate, sendMessage } =\n store\n\n // Images in the answers and the tool cards load by themselves only from\n // the backend this chat talks to and from the hosts the agent lists.\n provideChatImagePolicy(\n () => props.agentInterface?.imageHosts,\n () => props.baseUrl,\n )\n\n // Optional profile-management context provided by the host widget.\n // Present only for authenticated, non-anonymous sessions.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canManageProfile = computed(\n () => profile?.canManageProfile.value ?? false,\n )\n // Anonymous users get a \"Sign in\" entry instead of the profile one,\n // provided the host filled the `view-auth` slot and the agent has at\n // least one auth method enabled.\n const canSignIn = computed(\n () =>\n !canManageProfile.value &&\n (profile?.canSignIn?.value ?? false) &&\n slots['view-auth'] !== undefined,\n )\n\n // Once the sign-in completes (the session becomes non-anonymous) leave\n // the auth view and return to the conversation.\n watch(canManageProfile, (canManage) => {\n if (canManage && activeView.value === 'auth') {\n navigate('chat')\n }\n })\n\n // Sync props → store reactively\n watchEffect(() => {\n store.baseUrl = props.baseUrl\n store.basePath = props.basePath\n store.name = props.name\n store.agentType = props.agentType\n store.userId = props.userId\n store.agentInterface = props.agentInterface\n store.agentModel = props.agentModel\n store.agentTools = props.agentTools\n store.headers = props.headers\n store.revisedAnswers = props.revisedAnswers\n store.model = props.model\n store.actionsInput = props.actions\n store.agentFileUpload = props.agentFileUpload\n store.agentVoice = props.voice\n store.fallbackModels = props.fallbackModels\n store.gatewayOptions = props.gatewayOptions\n store.externalContext = props.externalContext\n store.systemTheme = props.systemTheme\n store.hideWelcomeMessage = props.modifier === 'fullscreen'\n })\n\n // Sync chatId v-model ↔ store (bidirectional)\n // Parent → store: if the parent provides an external chatId, push it into\n // the store so the Chat instance picks it up.\n watch(\n () => chatId.value,\n (id) => {\n if (id && id !== store.localChatId) {\n store.localChatId = id\n }\n },\n { immediate: true },\n )\n // Store → parent: keep the v-model in sync whenever the store changes\n // the chatId internally (e.g. startNewChat).\n watch(localChatId, (id) => {\n chatId.value = id\n })\n\n // Emit structured errors from chat to the parent component\n watch(chatError, (error) => {\n if (!error) {\n return\n }\n try {\n const parsedError = JSON.parse(error.message) as {\n type: string\n title: string\n status: number\n }\n emit('error', parsedError)\n } catch {\n // ignore non-JSON errors\n }\n })\n\n // ui\n const isFullscreen = computed(() => props.modifier === 'fullscreen')\n const isPanel = computed(() => props.modifier === 'panel')\n\n /**\n * How much room the chat has, measured on the chat and not on the window.\n *\n * The same chat renders in a phone page, in a 408px widget, in a docked\n * panel and in a preview frame that the side panels squeeze to phone width\n * on a desktop screen, so the viewport says nothing about the space the\n * layout actually has. A CSS container query would read the same number\n * with no JavaScript, but `container-type` makes the element a containing\n * block for `position: fixed` descendants on every engine except Chrome\n * 129 and later, which displaces every dropdown in the chat: see\n * docs/agents/gotchas.md, \"The chat measures its own width, and not with a\n * container query\".\n *\n * The thresholds carry the modifiers, so the CSS stays declarative:\n * `--phone` has as much room as a phone page, `--tight` is too tight for\n * the agent chip beside the field, `--wide` has room for the outline rail\n * beside the reading column. Before the first measurement the width is 0\n * and no modifier applies, which is the roomy layout the base CSS writes.\n */\n const chatbotEl = useTemplateRef<HTMLDivElement>('chatbotEl')\n const { width: chatbotWidth } = useElementSize(chatbotEl)\n const isPhoneWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 576,\n )\n const isTightWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 640,\n )\n const isWideWidth = computed(() => chatbotWidth.value >= 848)\n const isReasoningAgent = computed(() => props.agentType === 'reasoning')\n const useColorsForAgentHeader = computed(\n () => props.agentInterface?.useColorsForAgentHeader,\n )\n // Panel mode promotes the everyday actions (new chat, history) from the\n // dropdown to direct header buttons: the dropdown then only remains for\n // the secondary entries (files, profile, sign-in) and disappears when\n // none of them applies.\n const showHeaderMenu = computed(\n () =>\n !isPanel.value ||\n canManageProfile.value ||\n canSignIn.value ||\n (!!props.userFilesEnabled && !!localChatId.value),\n )\n const themeClass = computed(() => [\n 'theme',\n `theme--${isDark.value ? 'dark' : 'light'}`,\n ])\n const mainColor = computed(() => props.agentInterface?.mainColor)\n const contrastColor = computed(() =>\n resolveContrastColor(props.agentInterface?.textColor, mainColor.value),\n )\n const footerMessage = useLocalizedString(\n () => props.agentInterface?.footerMessage,\n )\n const title = computed(() => {\n if (activeView.value === 'conversations') {\n return $t('label.conversations')\n }\n if (activeView.value === 'profile') {\n return $t('label.profile')\n }\n if (activeView.value === 'auth') {\n return $t('label.authentication')\n }\n if (activeView.value === 'files') {\n return $t('userFiles.chatFilesTitle')\n }\n return props.name\n })\n\n // state\n const isReady = computed(() => chat.value.status === 'ready')\n const activeMessage = computed(() => {\n return messages.value?.[messages.value.length - 1]\n })\n\n // expose\n defineExpose({\n startNewChat,\n regenerate,\n stopGeneration,\n sendMessage,\n })\n\n // load chat, conversations and feedbacks on agent or chat change\n watch(\n [storeAgentId, localChatId],\n async ([newAgentId, newChatId]) => {\n await store.initialize(newAgentId, newChatId)\n },\n {\n immediate: true,\n },\n )\n // load conversations when navigating to conversations view\n watch(activeView, (newView) => {\n if (newView === 'conversations') {\n store.loadConversations(storeAgentId.value)\n } else if (newView === 'files') {\n store.loadChatFiles()\n }\n })\n // emit message updates for up-to-date context in parent components\n watch(\n activeMessage,\n (message) => {\n if (message) {\n emit('message-update', message)\n }\n },\n {\n deep: true,\n immediate: true,\n },\n )\n</script>\n\n<template>\n <div\n ref=\"chatbotEl\"\n class=\"pk-chatbot\"\n :class=\"[\n themeClass,\n {\n 'pk-chatbot--fullscreen': isFullscreen,\n 'pk-chatbot--panel': isPanel,\n 'pk-chatbot--phone': isPhoneWidth,\n 'pk-chatbot--tight': isTightWidth,\n 'pk-chatbot--wide': isWideWidth,\n },\n ]\">\n <!-- #region title -->\n <div\n v-if=\"!isFullscreen\"\n class=\"pk-chatbot__header\"\n :style=\"\n useColorsForAgentHeader\n ? {\n backgroundColor: mainColor,\n }\n : undefined\n \">\n <div class=\"pk-chatbot__header-start\">\n <template v-if=\"activeView !== 'chat'\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:arrow-left-s-line',\n title: $t('action.goBack'),\n }\"\n @click=\"navigate('chat')\" />\n <VvIcon\n :name=\"\n activeView === 'conversations'\n ? 'ri:history-line'\n : activeView === 'files'\n ? 'ri:attachment-2'\n : 'ri:user-line'\n \"\n class=\"pk-chatbot__nav-icon\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \" />\n </template>\n <!-- Panel mode: no avatar, the docked panel identifies the\n agent with the title alone -->\n <PkAvatar\n v-else-if=\"!isPanel\"\n modifiers=\"surface ring shrink-0\"\n class=\"pk-chatbot__avatar\"\n :name\n :img-src=\"agentInterface?.logo\" />\n <strong\n class=\"pk-chatbot__title\"\n :title=\"title\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \">\n {{ title }}\n </strong>\n </div>\n <VvButtonGroup class=\"pk-chatbot__header-end\" modifiers=\"compact\">\n <slot\n name=\"header-actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n <template v-if=\"isPanel\">\n <VvButton\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:edit-box-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n <VvButton\n v-if=\"!isReasoningAgent\"\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:history-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.viewRecentChats')\"\n @click=\"navigate('conversations')\" />\n </template>\n <VvDropdown\n v-if=\"showHeaderMenu\"\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:more-2-fill',\n disabled: !isReady,\n title: $t('action.moreActions'),\n }\" />\n <template #items>\n <VvDropdownAction\n v-if=\"!isPanel\"\n @click=\"startNewChat()\">\n <VvIcon name=\"ri:edit-box-line\" />\n {{ $t('action.startNewChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"!isReasoningAgent && !isPanel\"\n @click=\"navigate('conversations')\">\n <VvIcon name=\"ri:history-line\" />\n {{ $t('action.viewRecentChats') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"\n (canManageProfile || userFilesEnabled) &&\n localChatId\n \"\n @click=\"navigate('files')\">\n <VvIcon name=\"ri:attachment-2\" />\n {{ $t('userFiles.chatFilesTitle') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"canManageProfile\"\n @click=\"navigate('profile')\">\n <VvIcon name=\"ri:user-line\" />\n {{ $t('label.profile') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-else-if=\"canSignIn\"\n @click=\"navigate('auth')\">\n <VvIcon name=\"ri:account-circle-line\" />\n {{ $t('action.signIn') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n <slot\n name=\"actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n </VvButtonGroup>\n </div>\n <!-- #endregion -->\n\n <div v-if=\"isReasoningAgent\">\n <div class=\"pk-chatbot__warning-body\">\n <VvAlert modifiers=\"callout\">\n <div class=\"pk-chatbot__warning-header\">\n <VvIcon\n name=\"ri:alert-line\"\n class=\"pk-chatbot__warning-icon\" />\n <strong class=\"pk-chatbot__warning-title\">\n {{ $t('message.warning') }}\n </strong>\n </div>\n <p class=\"pk-chatbot__warning-text\">\n {{ $t('message.reasoningAgent') }}\n </p>\n </VvAlert>\n </div>\n </div>\n\n <!-- #region views -->\n <PkChatbotViewChat\n v-if=\"activeView === 'chat'\"\n :agent-id\n :modifier\n :user-name\n @show-info=\"emit('show-info', $event)\"\n @revise=\"emit('revise', $event)\">\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewChat>\n <PkChatbotViewConversations\n v-else-if=\"activeView === 'conversations'\"\n :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewConversations>\n <PkChatbotViewFiles v-else-if=\"activeView === 'files'\" :agent-id />\n <div\n v-else-if=\"activeView === 'auth'\"\n class=\"pk-chatbot__view-auth flex-1 min-h-0 overflow-y-auto\">\n <!-- Sign-in form provided by the host widget (owns the auth client) -->\n <slot name=\"view-auth\" />\n </div>\n <PkChatbotViewProfile v-else :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewProfile>\n <!-- #endregion -->\n\n <!-- #region footer -->\n <div v-if=\"footerMessage\" class=\"pk-chatbot__footer\">\n <PkStreamingMarkdown :markdown=\"footerMessage\" class=\"wysiwyg\" />\n </div>\n <!-- #endregion -->\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot {\n display: flex;\n flex-direction: column;\n position: relative;\n height: 100%;\n background-color: var(--color-surface);\n\n &__header {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n justify-content: space-between;\n gap: var(--spacing-14);\n background-color: var(--color-surface-1);\n padding: var(--spacing-14);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__header-start {\n display: flex;\n align-items: center;\n gap: var(--spacing-sm);\n min-width: 0;\n }\n\n &__nav-icon {\n font-size: var(--text-18);\n }\n\n &__avatar {\n width: var(--spacing-24);\n height: var(--spacing-24);\n }\n\n &__title {\n font-size: var(--text-16);\n font-weight: var(--font-semibold);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__header-end {\n flex-shrink: 0;\n }\n\n &__warning-body {\n padding: var(--spacing-md);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__warning-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n margin-bottom: var(--spacing-4);\n }\n\n &__warning-icon {\n width: var(--spacing-16);\n height: var(--spacing-16);\n }\n\n &__warning-title {\n font-weight: 600;\n font-size: var(--text-14);\n }\n\n &__warning-text {\n font-size: var(--text-12);\n color: var(--color-word-3);\n }\n\n &__footer {\n padding: var(--spacing-md);\n border-top: 1px solid var(--color-surface-3);\n text-align: center;\n font-size: var(--text-12);\n color: var(--color-word-2);\n }\n\n // Modifier: fullscreen — layout full-page (Gemini-style): nessun\n // header interno, sfondo trasparente, input pill centrato.\n // Il chrome (sidebar, navigazione) è gestito dalla pagina ospite.\n &--fullscreen {\n // The dock, 60px. Declared rather than taken from the agent chip,\n // which the tight layout hides: the pill fell to 54px there and\n // the call bar that takes its place stayed at 60. There is no 60\n // in the spacing scale and a raw `60px` would stop following the\n // root font size, which the labels inside do follow.\n --pk-chat-fullscreen-dock-height: calc(\n var(--spacing-56) + var(--spacing-4)\n );\n // The pill, the notice that tucks into its top corners and the\n // call bar all round their ends on half of it.\n --pk-chat-dock-radius: calc(\n var(--pk-chat-fullscreen-dock-height) / 2\n );\n\n background-color: transparent;\n\n .pk-chatbot__footer {\n border-top: none;\n padding: 0 var(--spacing-md) var(--spacing-sm) var(--spacing-md);\n }\n\n // Reading column aligned with the input (Gemini-style). The\n // constraint lives on the inner wrapper so the scroll container\n // spans the page and the scrollbar sits at its edge.\n .pk-chatbot-messages {\n // Symmetric gutters: a visible scrollbar would otherwise\n // shrink the content box on one side only, shifting the\n // column off the input axis\n scrollbar-gutter: stable both-edges;\n // Dissolve the content at both edges (works on any page\n // background, dots included — a surface-colored fade band\n // would cut through the pattern instead)\n mask-image: linear-gradient(\n to bottom,\n transparent 0,\n #000 var(--spacing-40),\n #000 calc(100% - var(--spacing-40)),\n transparent 100%\n );\n // Resting clearance so the first/last lines sit inside the\n // fully opaque zone\n padding-block: var(--spacing-48);\n }\n\n .pk-chatbot-messages__wrapper {\n width: 100%;\n max-width: 768px;\n margin-inline: auto;\n }\n\n // Outline rail: wide chats only, measured on the chat and not on\n // the viewport, so a preview frame squeezed by side panels loses\n // it like a phone does. The rail sits at the edge of the scroll\n // area beside the reading column: below the `--wide` threshold\n // (768px column plus a 40px rail on each side) it would cover the\n // messages instead.\n .pk-chatbot-messages__outline {\n display: none;\n }\n\n &.pk-chatbot--wide .pk-chatbot-messages__outline {\n display: block;\n }\n\n .pk-chatbot-file-preview {\n background-color: var(--color-surface);\n }\n\n .pk-chatbot-input__notice {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-3);\n border-top: 1px solid var(--color-surface-2);\n padding: var(--spacing-10) var(--spacing-16);\n\n // As deep as the pill's corners, or the page shows through\n // where their curve leaves the side\n &::after {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-2);\n bottom: calc(-1 * var(--pk-chat-dock-radius));\n height: var(--pk-chat-dock-radius);\n left: -1px;\n right: -1px;\n }\n }\n\n .pk-chatbot-input {\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n\n &__form {\n // True semicircles at rest, concentric with the send\n // button, whose center sits 30px in. Not `rounded-full`:\n // once Shift+Enter grows the field the ends would swell\n // with it and cut into the first and last line.\n border-radius: var(--pk-chat-dock-radius);\n min-height: var(--pk-chat-fullscreen-dock-height);\n padding: var(--spacing-12) var(--spacing-16);\n background-color: var(--color-surface-1);\n border-color: var(--color-surface-2);\n box-shadow: none;\n transition:\n border-color var(--duration-200) var(--ease-in-out),\n box-shadow var(--duration-200) var(--ease-in-out);\n\n &:has(:focus-within) {\n border-color: var(--color-surface-4);\n box-shadow: var(--shadow-sm);\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n\n &__textarea {\n background-color: transparent;\n }\n }\n\n // Touch target ≥44px: estende l'hit area dei pulsanti\n // (icona 20px + padding 4px ≈ 28px) senza ingrandire l'icona.\n // Vale per l'input e per la barra della chiamata, che montano\n // lo stesso pulsante.\n .pk-chat-button {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--spacing-8));\n }\n }\n\n // A live call takes the place of the input, so it takes its shape,\n // its column and its height: full-bleed here would break the\n // reading axis, and a different height would make the dock jump\n // the moment the call starts.\n .pk-voice-session {\n --pk-chat-dock-height: var(--pk-chat-fullscreen-dock-height);\n --pk-voice-orb-size: var(--spacing-40);\n --pk-voice-orb-bars-height: var(--spacing-20);\n --pk-voice-session-surface: var(--color-surface-1);\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n padding-inline: var(--spacing-16);\n border-color: var(--color-surface-2);\n border-radius: var(--pk-chat-dock-radius);\n box-shadow: none;\n\n &__timer {\n font-size: var(--text-14);\n }\n }\n }\n\n // Modifier: panel — compact chrome for a docked side panel: the host\n // container owns background and border, the header slims down and\n // drops its own surface so the chat blends into the panel.\n &--panel {\n background-color: transparent;\n\n .pk-chatbot__header {\n background-color: transparent;\n\n // Fixed height so the panel header lines up with the host's\n // own chrome. Custom properties inherit through the shadow\n // root: an embedding host can override it to match its\n // header (the default matches the platform header, 50px).\n height: var(--pk-chatbot-panel-header-height, 50px);\n align-items: center;\n padding-block: 0;\n padding-inline: var(--spacing-10);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n .pk-chatbot__title {\n font-size: var(--text-14);\n }\n\n .pk-chatbot__footer {\n border-top: none;\n padding: var(--spacing-6) var(--spacing-md);\n }\n }\n }\n</style>\n","<script setup lang=\"ts\">\n import {\n computed,\n inject,\n toRef,\n useTemplateRef,\n watch,\n watchEffect,\n useSlots,\n } from 'vue'\n import { useElementSize } from '@vueuse/core'\n import { storeToRefs } from 'pinia'\n import { useI18n } from 'vue-i18n'\n import type {\n AgentInterface,\n AgentModel,\n AgentFileUpload,\n AgentGatewayOptions,\n AgentVoiceCapability,\n ChatMessageActions,\n RevisedAnswer,\n UIChatMessage,\n AgentTools,\n } from 'models'\n import {\n useChatbotStore,\n useLocalizedString,\n provideShortUrlResolver,\n } from 'composables'\n import PkAvatar from './PkAvatar.vue'\n import PkChatbotViewChat from './PkChatbotViewChat.vue'\n import PkChatbotViewConversations from './PkChatbotViewConversations.vue'\n import PkChatbotViewFiles from './PkChatbotViewFiles.vue'\n import PkChatbotViewProfile from './PkChatbotViewProfile.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import { ChatbotProfileContextKey } from './chatbotProfileContext'\n import { provideChatImagePolicy } from './chatImagePolicy'\n import { resolveContrastColor } from './utils'\n\n // Captured in script setup to avoid TS7022 circular inference in template\n const slots = useSlots()\n\n const { t: $t } = useI18n({\n useScope: 'global',\n })\n\n const props = defineProps<{\n baseUrl: string\n basePath?: string\n agentId: string\n agentType?: 'reasoning' | 'chat'\n userId?: string\n name?: string\n model?: string\n agentModel?: AgentModel\n agentTools?: AgentTools\n agentInterface?: AgentInterface\n actions?: ChatMessageActions[]\n revisedAnswers?: RevisedAnswer[]\n headers?: Record<string, string>\n systemTheme?: 'light' | 'dark'\n agentFileUpload?: AgentFileUpload\n /** Voice configuration from the agent settings: enables the talk button. */\n voice?: AgentVoiceCapability\n fallbackModels?: string[]\n gatewayOptions?: Partial<AgentGatewayOptions>\n externalContext?: Record<string, unknown>\n /** Layout modifier: `widget` (default) keeps the contained layout with header, `fullscreen` removes header/background for full-page usage (the page provides the chrome), `panel` keeps the header but slims it down for a docked side panel (the host provides background and border) */\n modifier?: 'widget' | 'fullscreen' | 'panel'\n /** Display name of the current user, used for the `{name}` placeholder in the fullscreen greeting */\n userName?: string\n /** Show the \"Files in this chat\" menu for hosts that don't provide the\n * profile context but whose user is authenticated (e.g. the backoffice\n * playground). The widget host relies on `canManageProfile` instead. */\n userFilesEnabled?: boolean\n }>()\n const emit = defineEmits<{\n 'message-update': [message: UIChatMessage]\n 'show-info': [message: UIChatMessage]\n revise: [message: UIChatMessage]\n error: [error: { type: string; title: string; status: number }]\n }>()\n\n const chatId = defineModel<string | undefined>('chatId', {\n type: String,\n default: undefined,\n })\n\n const store = useChatbotStore(props.agentId)\n\n // Resolve backend short-urls (`/api/r/<hash>`) back to their real target\n // for display in tool components. Batched + cached across the chat tree.\n provideShortUrlResolver((hashes) =>\n store.apiClient.resolveShortUrls(hashes),\n )\n\n const { messages, chat, localChatId, activeView, chatError, isDark } =\n storeToRefs(store)\n\n // Ref to store.agentId for watchers (avoids duplicate-key with agentId prop)\n const storeAgentId = toRef(store, 'agentId')\n\n const { startNewChat, stopGeneration, regenerate, navigate, sendMessage } =\n store\n\n // Images in the answers and the tool cards load by themselves only from\n // the backend this chat talks to and from the hosts the agent lists.\n provideChatImagePolicy(\n () => props.agentInterface?.imageHosts,\n () => props.baseUrl,\n )\n\n // Optional profile-management context provided by the host widget.\n // Present only for authenticated, non-anonymous sessions.\n const profile = inject(ChatbotProfileContextKey, undefined)\n const canManageProfile = computed(\n () => profile?.canManageProfile.value ?? false,\n )\n // Anonymous users get a \"Sign in\" entry instead of the profile one,\n // provided the host filled the `view-auth` slot and the agent has at\n // least one auth method enabled.\n const canSignIn = computed(\n () =>\n !canManageProfile.value &&\n (profile?.canSignIn?.value ?? false) &&\n slots['view-auth'] !== undefined,\n )\n\n // Once the sign-in completes (the session becomes non-anonymous) leave\n // the auth view and return to the conversation.\n watch(canManageProfile, (canManage) => {\n if (canManage && activeView.value === 'auth') {\n navigate('chat')\n }\n })\n\n // Sync props → store reactively\n watchEffect(() => {\n store.baseUrl = props.baseUrl\n store.basePath = props.basePath\n store.name = props.name\n store.agentType = props.agentType\n store.userId = props.userId\n store.agentInterface = props.agentInterface\n store.agentModel = props.agentModel\n store.agentTools = props.agentTools\n store.headers = props.headers\n store.revisedAnswers = props.revisedAnswers\n store.model = props.model\n store.actionsInput = props.actions\n store.agentFileUpload = props.agentFileUpload\n store.agentVoice = props.voice\n store.fallbackModels = props.fallbackModels\n store.gatewayOptions = props.gatewayOptions\n store.externalContext = props.externalContext\n store.systemTheme = props.systemTheme\n store.hideWelcomeMessage = props.modifier === 'fullscreen'\n })\n\n // Sync chatId v-model ↔ store (bidirectional)\n // Parent → store: if the parent provides an external chatId, push it into\n // the store so the Chat instance picks it up.\n watch(\n () => chatId.value,\n (id) => {\n if (id && id !== store.localChatId) {\n store.localChatId = id\n }\n },\n { immediate: true },\n )\n // Store → parent: keep the v-model in sync whenever the store changes\n // the chatId internally (e.g. startNewChat).\n watch(localChatId, (id) => {\n chatId.value = id\n })\n\n // Emit structured errors from chat to the parent component\n watch(chatError, (error) => {\n if (!error) {\n return\n }\n try {\n const parsedError = JSON.parse(error.message) as {\n type: string\n title: string\n status: number\n }\n emit('error', parsedError)\n } catch {\n // ignore non-JSON errors\n }\n })\n\n // ui\n const isFullscreen = computed(() => props.modifier === 'fullscreen')\n const isPanel = computed(() => props.modifier === 'panel')\n\n /**\n * How much room the chat has, measured on the chat and not on the window.\n *\n * The same chat renders in a phone page, in a 408px widget, in a docked\n * panel and in a preview frame that the side panels squeeze to phone width\n * on a desktop screen, so the viewport says nothing about the space the\n * layout actually has. A CSS container query would read the same number\n * with no JavaScript, but `container-type` makes the element a containing\n * block for `position: fixed` descendants on every engine except Chrome\n * 129 and later, which displaces every dropdown in the chat: see\n * docs/agents/gotchas.md, \"The chat measures its own width, and not with a\n * container query\".\n *\n * The thresholds carry the modifiers, so the CSS stays declarative:\n * `--phone` has as much room as a phone page, `--tight` is too tight for\n * the agent chip beside the field, `--wide` has room for the outline rail\n * beside the reading column. Before the first measurement the width is 0\n * and no modifier applies, which is the roomy layout the base CSS writes.\n */\n const chatbotEl = useTemplateRef<HTMLDivElement>('chatbotEl')\n const { width: chatbotWidth } = useElementSize(chatbotEl)\n const isPhoneWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 576,\n )\n const isTightWidth = computed(\n () => chatbotWidth.value > 0 && chatbotWidth.value < 640,\n )\n const isWideWidth = computed(() => chatbotWidth.value >= 848)\n const isReasoningAgent = computed(() => props.agentType === 'reasoning')\n const useColorsForAgentHeader = computed(\n () => props.agentInterface?.useColorsForAgentHeader,\n )\n // Panel mode promotes the everyday actions (new chat, history) from the\n // dropdown to direct header buttons: the dropdown then only remains for\n // the secondary entries (files, profile, sign-in) and disappears when\n // none of them applies.\n const showHeaderMenu = computed(\n () =>\n !isPanel.value ||\n canManageProfile.value ||\n canSignIn.value ||\n (!!props.userFilesEnabled && !!localChatId.value),\n )\n const themeClass = computed(() => [\n 'theme',\n `theme--${isDark.value ? 'dark' : 'light'}`,\n ])\n const mainColor = computed(() => props.agentInterface?.mainColor)\n const contrastColor = computed(() =>\n resolveContrastColor(props.agentInterface?.textColor, mainColor.value),\n )\n const footerMessage = useLocalizedString(\n () => props.agentInterface?.footerMessage,\n )\n const title = computed(() => {\n if (activeView.value === 'conversations') {\n return $t('label.conversations')\n }\n if (activeView.value === 'profile') {\n return $t('label.profile')\n }\n if (activeView.value === 'auth') {\n return $t('label.authentication')\n }\n if (activeView.value === 'files') {\n return $t('userFiles.chatFilesTitle')\n }\n return props.name\n })\n\n // state\n const isReady = computed(() => chat.value.status === 'ready')\n const activeMessage = computed(() => {\n return messages.value?.[messages.value.length - 1]\n })\n\n // expose\n defineExpose({\n startNewChat,\n regenerate,\n stopGeneration,\n sendMessage,\n })\n\n // load chat, conversations and feedbacks on agent or chat change\n watch(\n [storeAgentId, localChatId],\n async ([newAgentId, newChatId]) => {\n await store.initialize(newAgentId, newChatId)\n },\n {\n immediate: true,\n },\n )\n // load conversations when navigating to conversations view\n watch(activeView, (newView) => {\n if (newView === 'conversations') {\n store.loadConversations(storeAgentId.value)\n } else if (newView === 'files') {\n store.loadChatFiles()\n }\n })\n // emit message updates for up-to-date context in parent components\n watch(\n activeMessage,\n (message) => {\n if (message) {\n emit('message-update', message)\n }\n },\n {\n deep: true,\n immediate: true,\n },\n )\n</script>\n\n<template>\n <div\n ref=\"chatbotEl\"\n class=\"pk-chatbot\"\n :class=\"[\n themeClass,\n {\n 'pk-chatbot--fullscreen': isFullscreen,\n 'pk-chatbot--panel': isPanel,\n 'pk-chatbot--phone': isPhoneWidth,\n 'pk-chatbot--tight': isTightWidth,\n 'pk-chatbot--wide': isWideWidth,\n },\n ]\">\n <!-- #region title -->\n <div\n v-if=\"!isFullscreen\"\n class=\"pk-chatbot__header\"\n :style=\"\n useColorsForAgentHeader\n ? {\n backgroundColor: mainColor,\n }\n : undefined\n \">\n <div class=\"pk-chatbot__header-start\">\n <template v-if=\"activeView !== 'chat'\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:arrow-left-s-line',\n title: $t('action.goBack'),\n }\"\n @click=\"navigate('chat')\" />\n <VvIcon\n :name=\"\n activeView === 'conversations'\n ? 'ri:history-line'\n : activeView === 'files'\n ? 'ri:attachment-2'\n : 'ri:user-line'\n \"\n class=\"pk-chatbot__nav-icon\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \" />\n </template>\n <!-- Panel mode: no avatar, the docked panel identifies the\n agent with the title alone -->\n <PkAvatar\n v-else-if=\"!isPanel\"\n modifiers=\"surface ring shrink-0\"\n class=\"pk-chatbot__avatar\"\n :name\n :img-src=\"agentInterface?.logo\" />\n <strong\n class=\"pk-chatbot__title\"\n :title=\"title\"\n :style=\"\n useColorsForAgentHeader\n ? {\n color: contrastColor,\n }\n : undefined\n \">\n {{ title }}\n </strong>\n </div>\n <VvButtonGroup class=\"pk-chatbot__header-end\" modifiers=\"compact\">\n <slot\n name=\"header-actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n <template v-if=\"isPanel\">\n <VvButton\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:edit-box-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.startNewChat')\"\n @click=\"startNewChat()\" />\n <VvButton\n v-if=\"!isReasoningAgent\"\n :modifiers=\"\n useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet'\n \"\n icon=\"ri:history-line\"\n :disabled=\"!isReady\"\n :title=\"$t('action.viewRecentChats')\"\n @click=\"navigate('conversations')\" />\n </template>\n <VvDropdown\n v-if=\"showHeaderMenu\"\n v-bind=\"{\n placement: 'bottom-end',\n modifiers: 'menu',\n flip: true,\n offset: 3,\n strategy: 'fixed',\n }\">\n <VvButton\n v-bind=\"{\n modifiers: useColorsForAgentHeader\n ? `action-quiet-${contrastColor}`\n : 'action-quiet',\n icon: 'ri:more-2-fill',\n disabled: !isReady,\n title: $t('action.moreActions'),\n }\" />\n <template #items>\n <VvDropdownAction\n v-if=\"!isPanel\"\n @click=\"startNewChat()\">\n <VvIcon name=\"ri:edit-box-line\" />\n {{ $t('action.startNewChat') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"!isReasoningAgent && !isPanel\"\n @click=\"navigate('conversations')\">\n <VvIcon name=\"ri:history-line\" />\n {{ $t('action.viewRecentChats') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"\n (canManageProfile || userFilesEnabled) &&\n localChatId\n \"\n @click=\"navigate('files')\">\n <VvIcon name=\"ri:attachment-2\" />\n {{ $t('userFiles.chatFilesTitle') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-if=\"canManageProfile\"\n @click=\"navigate('profile')\">\n <VvIcon name=\"ri:user-line\" />\n {{ $t('label.profile') }}\n </VvDropdownAction>\n <VvDropdownAction\n v-else-if=\"canSignIn\"\n @click=\"navigate('auth')\">\n <VvIcon name=\"ri:account-circle-line\" />\n {{ $t('action.signIn') }}\n </VvDropdownAction>\n </template>\n </VvDropdown>\n <slot\n name=\"actions\"\n v-bind=\"{\n mainColor,\n useColorsForAgentHeader,\n contrastColor,\n startNewChat,\n stopGeneration,\n }\" />\n </VvButtonGroup>\n </div>\n <!-- #endregion -->\n\n <div v-if=\"isReasoningAgent\">\n <div class=\"pk-chatbot__warning-body\">\n <VvAlert modifiers=\"callout\">\n <div class=\"pk-chatbot__warning-header\">\n <VvIcon\n name=\"ri:alert-line\"\n class=\"pk-chatbot__warning-icon\" />\n <strong class=\"pk-chatbot__warning-title\">\n {{ $t('message.warning') }}\n </strong>\n </div>\n <p class=\"pk-chatbot__warning-text\">\n {{ $t('message.reasoningAgent') }}\n </p>\n </VvAlert>\n </div>\n </div>\n\n <!-- #region views -->\n <PkChatbotViewChat\n v-if=\"activeView === 'chat'\"\n :agent-id\n :modifier\n :user-name\n @show-info=\"emit('show-info', $event)\"\n @revise=\"emit('revise', $event)\">\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewChat>\n <PkChatbotViewConversations\n v-else-if=\"activeView === 'conversations'\"\n :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewConversations>\n <PkChatbotViewFiles v-else-if=\"activeView === 'files'\" :agent-id />\n <div\n v-else-if=\"activeView === 'auth'\"\n class=\"pk-chatbot__view-auth flex-1 min-h-0 overflow-y-auto\">\n <!-- Sign-in form provided by the host widget (owns the auth client) -->\n <slot name=\"view-auth\" />\n </div>\n <PkChatbotViewProfile v-else :agent-id>\n <template\n v-for=\"slotName in Object.keys(slots)\"\n #[slotName]=\"slotData\"\n ><slot :name=\"slotName\" v-bind=\"slotData\"\n /></template>\n </PkChatbotViewProfile>\n <!-- #endregion -->\n\n <!-- #region footer -->\n <div v-if=\"footerMessage\" class=\"pk-chatbot__footer\">\n <PkStreamingMarkdown :markdown=\"footerMessage\" class=\"wysiwyg\" />\n </div>\n <!-- #endregion -->\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot {\n display: flex;\n flex-direction: column;\n position: relative;\n height: 100%;\n background-color: var(--color-surface);\n\n &__header {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n justify-content: space-between;\n gap: var(--spacing-14);\n background-color: var(--color-surface-1);\n padding: var(--spacing-14);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__header-start {\n display: flex;\n align-items: center;\n gap: var(--spacing-sm);\n min-width: 0;\n }\n\n &__nav-icon {\n font-size: var(--text-18);\n }\n\n &__avatar {\n width: var(--spacing-24);\n height: var(--spacing-24);\n }\n\n &__title {\n font-size: var(--text-16);\n font-weight: var(--font-semibold);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n &__header-end {\n flex-shrink: 0;\n }\n\n &__warning-body {\n padding: var(--spacing-md);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n &__warning-header {\n display: flex;\n align-items: center;\n gap: var(--spacing-4);\n margin-bottom: var(--spacing-4);\n }\n\n &__warning-icon {\n width: var(--spacing-16);\n height: var(--spacing-16);\n }\n\n &__warning-title {\n font-weight: 600;\n font-size: var(--text-14);\n }\n\n &__warning-text {\n font-size: var(--text-12);\n color: var(--color-word-3);\n }\n\n &__footer {\n padding: var(--spacing-md);\n border-top: 1px solid var(--color-surface-3);\n text-align: center;\n font-size: var(--text-12);\n color: var(--color-word-2);\n }\n\n // Modifier: fullscreen — layout full-page (Gemini-style): nessun\n // header interno, sfondo trasparente, input pill centrato.\n // Il chrome (sidebar, navigazione) è gestito dalla pagina ospite.\n &--fullscreen {\n // The dock, 60px. Declared rather than taken from the agent chip,\n // which the tight layout hides: the pill fell to 54px there and\n // the call bar that takes its place stayed at 60. There is no 60\n // in the spacing scale and a raw `60px` would stop following the\n // root font size, which the labels inside do follow.\n --pk-chat-fullscreen-dock-height: calc(\n var(--spacing-56) + var(--spacing-4)\n );\n // The pill, the notice that tucks into its top corners and the\n // call bar all round their ends on half of it.\n --pk-chat-dock-radius: calc(\n var(--pk-chat-fullscreen-dock-height) / 2\n );\n\n background-color: transparent;\n\n .pk-chatbot__footer {\n border-top: none;\n padding: 0 var(--spacing-md) var(--spacing-sm) var(--spacing-md);\n }\n\n // Reading column aligned with the input (Gemini-style). The\n // constraint lives on the inner wrapper so the scroll container\n // spans the page and the scrollbar sits at its edge.\n .pk-chatbot-messages {\n // Symmetric gutters: a visible scrollbar would otherwise\n // shrink the content box on one side only, shifting the\n // column off the input axis\n scrollbar-gutter: stable both-edges;\n // Dissolve the content at both edges (works on any page\n // background, dots included — a surface-colored fade band\n // would cut through the pattern instead)\n mask-image: linear-gradient(\n to bottom,\n transparent 0,\n #000 var(--spacing-40),\n #000 calc(100% - var(--spacing-40)),\n transparent 100%\n );\n // Resting clearance so the first/last lines sit inside the\n // fully opaque zone\n padding-block: var(--spacing-48);\n }\n\n .pk-chatbot-messages__wrapper {\n width: 100%;\n max-width: 768px;\n margin-inline: auto;\n }\n\n // Outline rail: wide chats only, measured on the chat and not on\n // the viewport, so a preview frame squeezed by side panels loses\n // it like a phone does. The rail sits at the edge of the scroll\n // area beside the reading column: below the `--wide` threshold\n // (768px column plus a 40px rail on each side) it would cover the\n // messages instead.\n .pk-chatbot-messages__outline {\n display: none;\n }\n\n &.pk-chatbot--wide .pk-chatbot-messages__outline {\n display: block;\n }\n\n .pk-chatbot-file-preview {\n background-color: var(--color-surface);\n }\n\n .pk-chatbot-input__notice {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-3);\n border-top: 1px solid var(--color-surface-2);\n padding: var(--spacing-10) var(--spacing-16);\n\n // As deep as the pill's corners, or the page shows through\n // where their curve leaves the side\n &::after {\n background-color: var(--color-surface);\n border-inline: 1px solid var(--color-surface-2);\n bottom: calc(-1 * var(--pk-chat-dock-radius));\n height: var(--pk-chat-dock-radius);\n left: -1px;\n right: -1px;\n }\n }\n\n .pk-chatbot-input {\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n\n &__form {\n // True semicircles at rest, concentric with the send\n // button, whose center sits 30px in. Not `rounded-full`:\n // once Shift+Enter grows the field the ends would swell\n // with it and cut into the first and last line.\n border-radius: var(--pk-chat-dock-radius);\n min-height: var(--pk-chat-fullscreen-dock-height);\n padding: var(--spacing-12) var(--spacing-16);\n background-color: var(--color-surface-1);\n border-color: var(--color-surface-2);\n box-shadow: none;\n transition:\n border-color var(--duration-200) var(--ease-in-out),\n box-shadow var(--duration-200) var(--ease-in-out);\n\n &:has(:focus-within) {\n border-color: var(--color-surface-4);\n box-shadow: var(--shadow-sm);\n }\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n\n &__textarea {\n background-color: transparent;\n }\n }\n\n // Touch target ≥44px: estende l'hit area dei pulsanti\n // (icona 20px + padding 4px ≈ 28px) senza ingrandire l'icona.\n // Vale per l'input e per la barra della chiamata, che montano\n // lo stesso pulsante.\n .pk-chat-button {\n position: relative;\n\n &::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--spacing-8));\n }\n }\n\n // A live call takes the place of the input, so it takes its shape,\n // its column and its height: full-bleed here would break the\n // reading axis, and a different height would make the dock jump\n // the moment the call starts.\n .pk-voice-session {\n --pk-chat-dock-height: var(--pk-chat-fullscreen-dock-height);\n --pk-voice-orb-size: var(--spacing-40);\n --pk-voice-orb-bars-height: var(--spacing-20);\n --pk-voice-session-surface: var(--color-surface-1);\n width: calc(100% - var(--spacing-sm) * 2);\n max-width: 768px;\n margin-inline: auto;\n padding-inline: var(--spacing-16);\n border-color: var(--color-surface-2);\n border-radius: var(--pk-chat-dock-radius);\n box-shadow: none;\n\n &__timer {\n font-size: var(--text-14);\n }\n }\n }\n\n // Modifier: panel — compact chrome for a docked side panel: the host\n // container owns background and border, the header slims down and\n // drops its own surface so the chat blends into the panel.\n &--panel {\n background-color: transparent;\n\n .pk-chatbot__header {\n background-color: transparent;\n\n // Fixed height so the panel header lines up with the host's\n // own chrome. Custom properties inherit through the shadow\n // root: an embedding host can override it to match its\n // header (the default matches the platform header, 50px).\n height: var(--pk-chatbot-panel-header-height, 50px);\n align-items: center;\n padding-block: 0;\n padding-inline: var(--spacing-10);\n border-bottom: 1px solid var(--color-surface-3);\n }\n\n .pk-chatbot__title {\n font-size: var(--text-14);\n }\n\n .pk-chatbot__footer {\n border-top: none;\n padding: var(--spacing-6) var(--spacing-md);\n }\n }\n }\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import "./src-DCfvejyT.js";
|
|
2
|
+
import { VvIcon as e } from "@volverjs/ui-vue/components";
|
|
3
|
+
import { computed as t, createCommentVNode as n, createElementBlock as r, createElementVNode as i, createVNode as a, defineComponent as o, openBlock as s, ref as c, toDisplayString as l, unref as u } from "vue";
|
|
4
|
+
import { useI18n as d } from "vue-i18n";
|
|
5
|
+
//#region ../../packages/utils/src/text-metrics.ts
|
|
6
|
+
function f(e) {
|
|
7
|
+
let t = Math.max(0, Math.round(e)), n = Math.floor(t / 3600), r = String(Math.floor(t % 3600 / 60)).padStart(n ? 2 : 1, "0"), i = String(t % 60).padStart(2, "0");
|
|
8
|
+
return n ? `${n}:${r}:${i}` : `${r}:${i}`;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region ../../packages/components/src/chat/PkChatbotAudioTranscript.vue?vue&type=script&setup=true&lang.ts
|
|
12
|
+
var p = { class: "pk-chatbot-audio" }, m = ["src"], h = {
|
|
13
|
+
key: 0,
|
|
14
|
+
class: "pk-chatbot-audio__box",
|
|
15
|
+
role: "status"
|
|
16
|
+
}, g = { class: "pk-chatbot-audio__header" }, _ = { class: "pk-chatbot-audio__title" }, v = {
|
|
17
|
+
key: 1,
|
|
18
|
+
class: "pk-chatbot-audio__box"
|
|
19
|
+
}, y = ["aria-expanded"], b = { class: "pk-chatbot-audio__title" }, x = {
|
|
20
|
+
key: 0,
|
|
21
|
+
class: "pk-chatbot-audio__meta"
|
|
22
|
+
}, S = {
|
|
23
|
+
key: 0,
|
|
24
|
+
class: "pk-chatbot-audio__body light-scrollbar"
|
|
25
|
+
}, C = {
|
|
26
|
+
key: 2,
|
|
27
|
+
class: "pk-chatbot-audio__box"
|
|
28
|
+
}, w = { class: "pk-chatbot-audio__header" }, T = /* @__PURE__ */ o({
|
|
29
|
+
__name: "PkChatbotAudioTranscript",
|
|
30
|
+
props: {
|
|
31
|
+
url: {},
|
|
32
|
+
transcript: {},
|
|
33
|
+
pending: { type: Boolean }
|
|
34
|
+
},
|
|
35
|
+
setup(o) {
|
|
36
|
+
let T = o, { t: E } = d({ useScope: "global" }), D = c(!0), O = t(() => T.transcript?.status === "ready" ? T.transcript.text?.trim() : void 0), k = t(() => T.transcript?.durationSeconds ? f(T.transcript.durationSeconds) : void 0), A = t(() => {
|
|
37
|
+
switch (T.transcript?.status) {
|
|
38
|
+
case "no-speech": return E("messagePart.audioTranscriptNoSpeech");
|
|
39
|
+
case "failed": return E("messagePart.audioTranscriptFailed");
|
|
40
|
+
case "off": return E("messagePart.audioTranscriptOff");
|
|
41
|
+
default: return;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return (t, c) => {
|
|
45
|
+
let d = e;
|
|
46
|
+
return s(), r("div", p, [i("audio", {
|
|
47
|
+
class: "pk-chatbot-audio__player",
|
|
48
|
+
src: o.url,
|
|
49
|
+
controls: "",
|
|
50
|
+
preload: "none"
|
|
51
|
+
}, null, 8, m), o.pending && !o.transcript ? (s(), r("div", h, [i("div", g, [a(d, {
|
|
52
|
+
name: "loading-loop",
|
|
53
|
+
class: "shrink-0"
|
|
54
|
+
}), i("span", _, l(u(E)("messagePart.audioTranscribing")), 1)])])) : O.value ? (s(), r("div", v, [i("button", {
|
|
55
|
+
type: "button",
|
|
56
|
+
class: "pk-chatbot-audio__header pk-chatbot-audio__header--toggle",
|
|
57
|
+
"aria-expanded": D.value,
|
|
58
|
+
onClick: c[0] ||= (e) => D.value = !D.value
|
|
59
|
+
}, [
|
|
60
|
+
a(d, {
|
|
61
|
+
name: "ri:file-text-line",
|
|
62
|
+
class: "shrink-0"
|
|
63
|
+
}),
|
|
64
|
+
i("span", b, l(u(E)("messagePart.audioTranscript")), 1),
|
|
65
|
+
k.value ? (s(), r("span", x, l(k.value), 1)) : n("", !0),
|
|
66
|
+
a(d, {
|
|
67
|
+
name: D.value ? "ri:arrow-up-s-line" : "ri:arrow-down-s-line",
|
|
68
|
+
class: "pk-chatbot-audio__chevron shrink-0 ml-auto"
|
|
69
|
+
}, null, 8, ["name"])
|
|
70
|
+
], 8, y), D.value ? (s(), r("div", S, l(O.value), 1)) : n("", !0)])) : A.value ? (s(), r("div", C, [i("div", w, [a(d, {
|
|
71
|
+
name: "ri:file-text-line",
|
|
72
|
+
class: "shrink-0"
|
|
73
|
+
}), i("span", null, l(A.value), 1)])])) : n("", !0)]);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
//#endregion
|
|
78
|
+
export { T as t };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=PkChatbotAudioTranscript-BUxBHanA.js.map
|