@8wave/ai-elements 0.95.0 → 0.97.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{Document-BUoM2Gd0.js → Document-njJL_3oo.js} +2 -2
- package/dist/_chunks/Document-njJL_3oo.js.map +1 -0
- package/dist/_chunks/{PkToolShowArtifact-CX4u80Vm.js → PkToolShowArtifact-BHGuiacA.js} +2 -2
- package/dist/_chunks/PkToolShowArtifact-BHGuiacA.js.map +1 -0
- package/dist/_chunks/{PkToolShowSources-B5_-IXvz.js → PkToolShowSources-Bg0QAcmF.js} +2 -2
- package/dist/_chunks/{PkToolShowSources-B5_-IXvz.js.map → PkToolShowSources-Bg0QAcmF.js.map} +1 -1
- package/dist/_chunks/PkToolShowVideo-DoLoI8_N.js +121 -0
- package/dist/_chunks/PkToolShowVideo-DoLoI8_N.js.map +1 -0
- package/dist/ai-elements.es.js +22 -13
- package/dist/ai-elements.es.js.map +1 -1
- package/dist-vue/PkChatbot.js +1 -1
- package/dist-vue/PkChatbotMessages.js +1 -1
- package/dist-vue/PkChatbotStepToolDetail.js +1 -1
- package/dist-vue/PkChatbotSteps.js +1 -1
- package/dist-vue/PkChatbotViewChat.js +1 -1
- package/dist-vue/PkChatbotViewConversations.js +1 -1
- package/dist-vue/PkChatbotViewFiles.js +1 -1
- package/dist-vue/PkChatbotViewProfile.js +1 -1
- package/dist-vue/_chunks/{Document--p4dmaUY.js → Document-q67u4D3u.js} +2 -2
- package/dist-vue/_chunks/{Document--p4dmaUY.js.map → Document-q67u4D3u.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbot-DH5-1our.js → PkChatbot-C_eZTz6p.js} +7 -7
- package/dist-vue/_chunks/{PkChatbot-DH5-1our.js.map → PkChatbot-C_eZTz6p.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotMessages-kRAjAYUH.js → PkChatbotMessages-Cbn8okze.js} +6 -5
- package/dist-vue/_chunks/PkChatbotMessages-Cbn8okze.js.map +1 -0
- package/dist-vue/_chunks/{PkChatbotStepToolDetail-Csp_kQG0.js → PkChatbotStepToolDetail-ldThv0t5.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotStepToolDetail-Csp_kQG0.js.map → PkChatbotStepToolDetail-ldThv0t5.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotSteps-DVQxL0Ti.js → PkChatbotSteps-BhqYCNWy.js} +3 -3
- package/dist-vue/_chunks/{PkChatbotSteps-DVQxL0Ti.js.map → PkChatbotSteps-BhqYCNWy.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewChat-CvUQ6zSI.js → PkChatbotViewChat-DhoY_8n8.js} +4 -4
- package/dist-vue/_chunks/{PkChatbotViewChat-CvUQ6zSI.js.map → PkChatbotViewChat-DhoY_8n8.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewConversations-DbnJaJw_.js → PkChatbotViewConversations-DS_k_sT_.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewConversations-DbnJaJw_.js.map → PkChatbotViewConversations-DS_k_sT_.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewFiles-B_HZ-Gjh.js → PkChatbotViewFiles-7dp8BlYW.js} +3 -3
- package/dist-vue/_chunks/{PkChatbotViewFiles-B_HZ-Gjh.js.map → PkChatbotViewFiles-7dp8BlYW.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewProfile-BglHN7F5.js → PkChatbotViewProfile-BdFkddeC.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewProfile-BglHN7F5.js.map → PkChatbotViewProfile-BdFkddeC.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowArtifact-BCc4qcf0.js → PkToolShowArtifact-B2ilUNT0.js} +2 -2
- package/dist-vue/_chunks/PkToolShowArtifact-B2ilUNT0.js.map +1 -0
- package/dist-vue/_chunks/{PkToolShowSources-BxKpqH6r.js → PkToolShowSources-BF2-VGSg.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowSources-BxKpqH6r.js.map → PkToolShowSources-BF2-VGSg.js.map} +1 -1
- package/dist-vue/_chunks/PkToolShowVideo-Ox8rFd7j.js +122 -0
- package/dist-vue/_chunks/PkToolShowVideo-Ox8rFd7j.js.map +1 -0
- package/dist-vue/_chunks/{constants-D4_mnkiX.js → constants-DtnBK4PW.js} +2 -2
- package/dist-vue/_chunks/{constants-D4_mnkiX.js.map → constants-DtnBK4PW.js.map} +1 -1
- package/dist-vue/_chunks/{createChatbotApiClient-C7cQhOuG.js → createChatbotApiClient-DvuGurzU.js} +5 -4
- package/dist-vue/_chunks/createChatbotApiClient-DvuGurzU.js.map +1 -0
- package/dist-vue/_chunks/{useChatbotStore-D6IdQhl-.js → useChatbotStore-C7VyUKXU.js} +2 -2
- package/dist-vue/_chunks/{useChatbotStore-D6IdQhl-.js.map → useChatbotStore-C7VyUKXU.js.map} +1 -1
- package/dist-vue/_chunks/{utils-CVZaf4tf.js → utils-BMa8jwJS.js} +3 -3
- package/dist-vue/_chunks/{utils-CVZaf4tf.js.map → utils-BMa8jwJS.js.map} +1 -1
- package/dist-vue/api.js +1 -1
- package/dist-vue/composables.js +3 -3
- package/dist-vue/index.js +17 -14
- package/dist-vue/index.js.map +1 -1
- package/dist-vue/locales.js +4 -0
- package/dist-vue/packages/components/src/chat/PkToolShowVideo.d.ts +7 -0
- package/dist-vue/packages/components/src/chat/constants.d.ts +1 -1
- package/dist-vue/packages/models/src/schema/Agent.d.ts +1 -0
- package/dist-vue/packages/models/src/schema/constants.d.ts +2 -0
- package/dist-vue/style.css +1 -1
- package/package.json +1 -1
- package/dist/_chunks/Document-BUoM2Gd0.js.map +0 -1
- package/dist/_chunks/PkToolShowArtifact-CX4u80Vm.js.map +0 -1
- package/dist-vue/_chunks/PkChatbotMessages-kRAjAYUH.js.map +0 -1
- package/dist-vue/_chunks/PkToolShowArtifact-BCc4qcf0.js.map +0 -1
- package/dist-vue/_chunks/createChatbotApiClient-C7cQhOuG.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Document-BUoM2Gd0.js","names":["core._coercedDate","schemas.ZodDate"],"sources":["../../../../packages/utils/src/markdown.ts","../../../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/coerce.js","../../../../packages/models/src/utils.ts","../../../../packages/models/src/schema/constants.ts","../../../../packages/models/src/schema/Media.ts","../../../../packages/models/src/schema/Header.ts","../../../../packages/models/src/schema/Document.ts"],"sourcesContent":["export const stripMarkdown = (text?: string | null) => {\n if (!text) {\n return undefined\n }\n return text\n .replace(/!\\[.*?\\]\\(.*?\\)/g, '') // images\n .replace(/\\[([^\\]]+)\\]\\(.*?\\)/g, '$1') // links\n .replace(/`{1,3}[^`]*`{1,3}/g, '') // inline code / code blocks\n .replace(/#{1,6}\\s/g, '') // headings\n .replace(/(\\*{1,3}|_{1,3})(.*?)\\1/g, '$2') // bold / italic\n .replace(/~~(.*?)~~/g, '$1') // strikethrough\n .replace(/^\\s*[-*+>]\\s+/gm, '') // lists and blockquotes\n .replace(/\\n+/g, ' ') // newlines\n .trim()\n}\n","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 * as z from 'zod'\n\n/**\n * Bidirectional wrapper for decode-time normalizations.\n *\n * Response serialization must never hit a unidirectional transform\n * (z.preprocess/.transform throw ZodEncodeError under zod encode), so\n * normalizing schemas are expressed as codecs: `decode` runs the\n * normalization (output validity is enforced by the output schema at\n * runtime), while `encode` is the identity for already-normalized data.\n */\nexport const decodeOnly = <Input extends z.ZodType, Output extends z.ZodType>(\n input: Input,\n output: Output,\n decode: (value: z.output<Input>) => z.input<Output>,\n) =>\n z.codec(input, output, {\n // Identity: already-normalized data is serialized unchanged. The\n // input schema re-validates at runtime, so the cast is type-only.\n encode: (value) => value as z.output<Input>,\n decode,\n })\n\nconst MAX_SCHEMA_SIZE_BYTES = 50 * 1024 // 50 KB\n\n/** Reusable Zod field for JSON Schema objects with a 50 KB size limit. */\nexport const jsonSchemaField = z\n .record(z.string(), z.unknown())\n .refine((val) => JSON.stringify(val).length <= MAX_SCHEMA_SIZE_BYTES, {\n message: `Schema must not exceed ${MAX_SCHEMA_SIZE_BYTES / 1024} KB`,\n })\n\nexport type QueryStringFilter<T extends string = string> = `filter[${T}]`\nexport type Querystring = {\n 'page[number]'?: number | string\n 'page[size]'?: number | string\n sort?: string[] | string\n [filterKeys: QueryStringFilter]: unknown\n [otherKeys: string]: unknown\n}\n\nexport const makeSortEnum = <const T extends string>(input: T) => {\n return [`${input}`, `-${input}`] as [T, `-${T}`]\n}\n\nexport const makeSortEnums = <const T extends string>(input: readonly T[]) => {\n return input.flatMap((i) => makeSortEnum(i))\n}\n\nexport const zodQs = {\n pagination: (defaultNumber = 1, defaultSize = 10) => {\n return {\n 'page[number]': z\n .number()\n .or(z.string())\n .transform((v) => Number(v))\n .default(defaultNumber),\n 'page[size]': z\n .number()\n .or(z.string())\n .transform((v) => Number(v))\n .default(defaultSize),\n }\n },\n filter: <const T extends string>(input: T) => {\n return { [`filter[${input}]`]: z.string().optional() } as Record<\n `filter[${T}]`,\n z.ZodOptional<z.ZodString | z.ZodArray<z.ZodString>>\n >\n },\n include: <const T extends string>(input: T) => {\n return { [`include[${input}]`]: z.string().optional() } as Record<\n `include[${T}]`,\n z.ZodOptional<z.ZodArray<z.ZodString> | z.ZodString>\n >\n },\n range: <const T extends string>(input: T) => {\n return {\n [`from[${input}]`]: z.string().optional(),\n [`to[${input}]`]: z.string().optional(),\n } as Record<`from[${T}]` | `to[${T}]`, z.ZodOptional<z.ZodString>>\n },\n has: <const T extends string>(input: T) => {\n return { [`has[${input}]`]: z.boolean().optional() } as Record<\n `has[${T}]`,\n z.ZodOptional<z.ZodBoolean>\n >\n },\n fullText: () => {\n return { [`filter[fullText]`]: z.string().optional() }\n },\n ids: () => {\n return { ids: z.union([z.string(), z.array(z.string())]).optional() }\n },\n deleted: () => {\n return { 'filter[isDeleted]': z.boolean().default(false) }\n },\n filters: <const T extends string>(input: readonly T[]) => {\n return input\n .map((i) => zodQs.filter(i))\n .reduce((a, b) => ({ ...a, ...b }))\n },\n sort: <const S extends string>(sort: S[]) => {\n const sortEnums = makeSortEnums(sort) as [S, ...`-${S}`[]]\n return {\n // @ts-expect-error: zod does not support tuple types\n sort: z.enum(sortEnums).optional().default(sortEnums[0]),\n }\n },\n}\n","export enum LanguageModel {\n // OpenAI\n textEmbedding3Small = 'openai/text-embedding-3-small',\n gpt5Nano = 'openai/gpt-5-nano',\n gpt5Mini = 'openai/gpt-5-mini',\n gpt51Thinking = 'openai/gpt-5.1-thinking',\n gpt51Instant = 'openai/gpt-5.1-instant',\n gpt52 = 'openai/gpt-5.2',\n gpt52Chat = 'openai/gpt-5.2-chat',\n gpt54nano = 'openai/gpt-5.4-nano',\n gpt54mini = 'openai/gpt-5.4-mini',\n gpt54 = 'openai/gpt-5.4',\n gpt54Pro = 'openai/gpt-5.4-pro',\n gpt55 = 'openai/gpt-5.5',\n gpt55Pro = 'openai/gpt-5.5-pro',\n gpt56Luna = 'openai/gpt-5.6-luna',\n gpt56Terra = 'openai/gpt-5.6-terra',\n gpt56Sol = 'openai/gpt-5.6-sol',\n // Google Gemini\n geminiEmbedding2 = 'google/gemini-embedding-2',\n gemini25Flash = 'google/gemini-2.5-flash',\n gemini25FlashLite = 'google/gemini-2.5-flash-lite',\n gemini25Pro = 'google/gemini-2.5-pro',\n gemini3Flash = 'google/gemini-3-flash',\n gemini3ProPreview = 'google/gemini-3-pro-preview',\n gemini31FlashLitePreview = 'google/gemini-3.1-flash-lite-preview',\n gemini31ProPreview = 'google/gemini-3.1-pro-preview',\n gemini31FlashLite = 'google/gemini-3.1-flash-lite',\n gemini35Flash = 'google/gemini-3.5-flash',\n // Anthropic Claude\n claudeHaiku45 = 'anthropic/claude-haiku-4.5',\n claudeSonnet45 = 'anthropic/claude-sonnet-4.5',\n claudeSonnet46 = 'anthropic/claude-sonnet-4.6',\n claudeSonnet5 = 'anthropic/claude-sonnet-5',\n claudeOpus46 = 'anthropic/claude-opus-4.6',\n claudeOpus47 = 'anthropic/claude-opus-4.7',\n claudeOpus48 = 'anthropic/claude-opus-4.8',\n claudeFable5 = 'anthropic/claude-fable-5',\n // Deepseek\n deepseek32 = 'deepseek/deepseek-v3.2',\n deepseek32Thinking = 'deepseek/deepseek-v3.2-thinking',\n deepseek4pro = 'deepseek/deepseek-v4-pro',\n deepseek4Flash = 'deepseek/deepseek-v4-flash',\n // Alibaba\n alibabaQwen36Plus = 'alibaba/qwen3.6-plus',\n alibabaQwen37Max = 'alibaba/qwen3.7-max',\n alibabaQwen37Plus = 'alibaba/qwen3.7-plus',\n // xAI\n xaiGrok43 = 'xai/grok-4.3',\n // z.ai\n zaiGlm52 = 'zai/glm-5.2',\n zaiGlm52Fast = 'zai/glm-5.2-fast',\n // Meta\n metaMuseSpark11 = 'meta/muse-spark-1.1',\n}\n\nexport const EMBED_MODEL = LanguageModel.geminiEmbedding2\n\nexport enum LanguageModelBrand {\n openai = 'openai',\n google = 'google',\n anthropic = 'anthropic',\n deepseek = 'deepseek',\n xai = 'xai',\n zai = 'zai',\n meta = 'meta',\n alibaba = 'alibaba',\n}\n\n/**\n * Supported languages for documents and revised answers\n */\nexport enum SupportedLanguage {\n it = 'it',\n en = 'en',\n es = 'es',\n fr = 'fr',\n de = 'de',\n}\n\n/**\n * Language display names (i18n keys)\n */\nexport const LANGUAGE_NAMES: Record<SupportedLanguage, string> = {\n [SupportedLanguage.it]: 'language.italian',\n [SupportedLanguage.en]: 'language.english',\n [SupportedLanguage.es]: 'language.spanish',\n [SupportedLanguage.fr]: 'language.french',\n [SupportedLanguage.de]: 'language.german',\n}\n\n/**\n * Content extraction strategies for URL scraping\n */\nexport enum ExtractionStrategy {\n /** Use Mozilla Readability for content extraction (fast, free, no API calls) */\n READABILITY = 'readability',\n /** Use AI-based extraction (better for complex layouts, requires API calls) */\n AI = 'ai',\n}\n\n/**\n * Browser engine options for URL scraping\n */\nexport enum BrowserEngine {\n /** Use simple HTTP fetch (fast, lightweight, no JavaScript support) */\n FETCH = 'fetch',\n /** Use Puppeteer with headless Chrome (full browser, JavaScript support) */\n PUPPETEER = 'puppeteer',\n /** Delegate browser rendering to an external pdf-image-generator service */\n REMOTE = 'remote',\n}\n","import { z } from 'zod'\n\n// ===== Constants =====\n\nexport const MAX_FILE_SIZE = 100 * 1024 * 1024 // 100MB\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_DOCUMENT_TYPES = [\n 'application/pdf',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.ms-excel',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'text/plain',\n 'text/markdown',\n] as const\n\nexport const SUPPORTED_VIDEO_TYPES = [\n 'video/mp4',\n 'video/webm',\n 'video/ogg',\n] as const\n\nexport const SUPPORTED_AUDIO_TYPES = [\n 'audio/mpeg',\n 'audio/wav',\n 'audio/ogg',\n] as const\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/')) return MediaType.picture\n if (mimeType.startsWith('video/')) return MediaType.video\n if (mimeType.startsWith('audio/')) return MediaType.audio\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) return '0 Bytes'\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","import * as z from 'zod'\nimport { decodeOnly } from '../utils'\n\nexport const HeaderSchema = z.object({\n key: z.string().optional(),\n value: z.string().optional(),\n})\n\nexport const HeadesSchema = decodeOnly(\n z.array(HeaderSchema),\n z.array(z.object({ key: z.string(), value: z.string() })),\n (headers) =>\n headers?.filter(\n (header) => header.key?.trim() && header.value?.trim(),\n ) as Array<{ key: string; value: string }>,\n)\n\nexport type Header = z.infer<typeof HeaderSchema>\nexport type Headers = z.infer<typeof HeadesSchema>\n","import * as z from 'zod'\nimport { zodQs, decodeOnly } from '../utils'\nimport {\n SupportedLanguage,\n ExtractionStrategy,\n BrowserEngine,\n} from './constants'\nimport { MediaSchema } from './Media'\nimport { HeadesSchema } from './Header'\n\n/**\n * Processing status for document indexing and embedding generation\n */\nexport enum IndexStatus {\n Pending = 'pending',\n Processing = 'processing',\n Success = 'success',\n Failed = 'failed',\n}\n\n// ===== Document Source Types =====\n\n/**\n * Conversion options for document processing\n * Used when creating or reprocessing documents\n */\nexport const ConversionOptionsSchema = z.object({\n extractionStrategy: z.enum(ExtractionStrategy).optional(),\n browserEngine: z\n .enum(BrowserEngine)\n .optional()\n .default(BrowserEngine.FETCH),\n headers: HeadesSchema.nullish(),\n aiDescriptionModel: z.string().optional(),\n aiDescriptionPrompt: z.string().max(5000).optional(),\n language: z.string().optional(),\n forceOcr: z.boolean().default(false),\n pages: z\n .string()\n .max(100)\n .regex(/^\\d+(\\s*-\\s*\\d+)?(\\s*,\\s*\\d+(\\s*-\\s*\\d+)?)*$/)\n .optional(),\n})\n\nexport type ConversionOptions = z.infer<typeof ConversionOptionsSchema>\n\n/**\n * Metadata structure for document.metadata column\n * Consolidates all metadata from converters and source info\n */\nexport const DocumentMetadataSchema = z\n .object({\n // === Source Info (URL documents only) ===\n source: z\n .object({\n url: z.string(),\n lastScrapedAt: z.string(),\n })\n .optional(),\n\n // === Media Info (File documents only) ===\n media: MediaSchema.optional(),\n\n // === Common Fields (all document types) ===\n title: z.string().optional(),\n author: z.string().optional(),\n description: z.string().optional(),\n summary: z.string().optional(),\n keywords: z.array(z.string()).optional(),\n\n // === PDF Specific ===\n subject: z.string().optional(),\n creator: z.string().optional(), // Original application\n producer: z.string().optional(), // PDF generator software\n creationDate: z.string().optional(),\n modificationDate: z.string().optional(),\n pageCount: decodeOnly(\n z.union([z.number(), z.array(z.unknown())]),\n z.number(),\n (val) => (Array.isArray(val) ? val.length : val),\n ).optional(),\n encrypted: z.boolean().optional(),\n\n // === DOCX Specific ===\n characterCount: z.number().optional(),\n lineCount: z.number().optional(),\n\n // === URL Specific ===\n siteName: z.string().optional(),\n canonicalUrl: z.url().optional(),\n imageUrl: z.url().optional(),\n favicon: z.url().optional(),\n type: z.string().optional(), // og:type (article, website, etc.)\n locale: z.string().optional(),\n tags: z.array(z.string()).optional(),\n publishedTime: z.string().optional(),\n modifiedTime: z.string().optional(),\n\n // === Metrics (all document types) ===\n wordCount: z.number().optional(),\n paragraphCount: z.number().optional(),\n readingTime: z.number().optional(), // in minutes\n\n // === OCR (PDF documents) ===\n ocrUsed: z.boolean().optional(),\n ocrProvider: z.string().optional(),\n ocrModel: z.string().optional(),\n ocrPages: z.string().optional(),\n ocrCost: z.number().optional(),\n ocrError: z.string().optional(),\n\n // === RAG / Duplicates / Sync Info ===\n chunksCount: z.number().optional(),\n redundantChunksCount: z.number().optional(),\n hasRedundantChunks: z.boolean().optional(),\n qdrantSynced: z.boolean().optional(),\n\n // === Conversion Options (for reprocessing) ===\n conversionOptions: ConversionOptionsSchema.optional(),\n })\n .loose() // Allow additional custom fields\n\nexport type DocumentMetadata = z.infer<typeof DocumentMetadataSchema>\n\n/**\n * Document type enum\n *\n * - 'file': Document created from uploaded file (PDF, DOCX)\n * - 'url': Document created from web scraping\n * - 'revised-answer': Document created from revised AI answer\n * - 'plain-text': Document created from plain text input\n */\nexport enum DocumentType {\n File = 'file',\n Url = 'url',\n RevisedAnswer = 'revised-answer',\n PlainText = 'plain-text',\n}\n\n// ===== Schemas =====\n\n/**\n * Full document schema from database\n * Extended with computed field chunksCount\n */\nconst BaseDocumentSchema = z.object({\n id: z.string(),\n externalId: z.string().nullable(),\n name: z.string(),\n content: z.string(),\n type: z.string(),\n language: z.string(),\n metadata: z.record(z.string(), z.unknown()),\n indexStatus: z.string(),\n processedAt: z.coerce.date().nullable(),\n createdAt: z.coerce.date(),\n createdBy: z.string(),\n updatedAt: z.coerce.date(),\n updatedBy: z.string(),\n deleted: z.boolean(),\n deletedAt: z.coerce.date().nullable(),\n deletedBy: z.string().nullable(),\n organizationId: z.string(),\n priority: z.number().int().min(1).max(5).default(3),\n})\n\nexport const DocumentSchema = BaseDocumentSchema.extend({\n type: z.enum(DocumentType),\n indexStatus: z.enum(IndexStatus),\n metadata: DocumentMetadataSchema,\n // Add computed field for chunks count (populated by service layer)\n chunksCount: z.number().optional(),\n // Computed fields for redundancy tracking\n redundantChunksCount: z.number().optional(),\n hasRedundantChunks: z.boolean().optional(),\n // Qdrant sync status\n qdrantSynced: z.boolean().optional(),\n // Agent IDs associated with this document (populated in single-item reads)\n agentIds: z.array(z.string()).optional(),\n // Folder ID for this document within an agent context (populated in agent-scoped reads)\n folderId: z.string().nullable().optional(),\n // Full folder path string e.g. \"Parent / Child\" (populated in list reads when agentId is set)\n folderPath: z.string().nullable().optional(),\n})\n\n/**\n * Query parameters for listing documents with pagination, sorting, and filters\n */\nexport const DocumentQuerystringSchema = z.object({\n ...zodQs.pagination(),\n ...zodQs.sort(['id', 'updatedAt', 'createdAt', 'name']),\n ...zodQs.filters(['type', 'language', 'agentId', 'indexStatus']),\n ...zodQs.fullText(),\n ...zodQs.ids(),\n 'filter[similarity]': z.string().optional(),\n // UUID = documents in that folder; 'null' = documents at root (no folder); absent = all documents\n 'filter[folderId]': z.string().optional(),\n 'filter[hasRedundantChunks]': z.string().optional(),\n})\n\n/**\n * Document creation schema for POST /documents endpoint\n *\n * Supports three types:\n * - 'file': Requires media object with uploaded file info\n * - 'url': Requires url string for web scraping\n * - 'revised-answer': Requires content (created from revisedAnswer service)\n */\nexport const DocumentCreateDtoSchema = z\n .object({\n type: z.enum(DocumentType).default(DocumentType.File),\n name: z.string().min(1).max(255).optional(),\n language: z.enum(SupportedLanguage).optional(),\n organizationId: z.string(),\n agentIds: z.array(z.string()).min(1, 'At least one agent is required'),\n folderId: z.uuid().nullable().optional(),\n media: MediaSchema.optional(),\n url: z.url().optional(),\n content: z.string().optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n extractionStrategy: z.enum(ExtractionStrategy).optional(),\n browserEngine: z.enum(BrowserEngine).optional(),\n headers: HeadesSchema.nullish().default([]),\n aiDescriptionModel: z.string().optional(),\n aiDescriptionPrompt: z.string().max(5000).optional(),\n forceOcr: z.boolean().default(false),\n pages: z\n .string()\n .max(100)\n .regex(/^\\d+(\\s*-\\s*\\d+)?(\\s*,\\s*\\d+(\\s*-\\s*\\d+)?)*$/)\n .optional(),\n priority: z.number().int().min(1).max(5).default(3).optional(),\n })\n .superRefine((data, ctx) => {\n if (data.type === DocumentType.File && !data.media) {\n ctx.addIssue({\n code: 'custom',\n params: {\n i18n: 'errors.required',\n },\n path: ['media'],\n })\n return\n }\n if (data.type === DocumentType.Url && !data.url) {\n ctx.addIssue({\n code: 'custom',\n params: {\n i18n: 'errors.required',\n },\n path: ['url'],\n })\n }\n if (\n data.type === DocumentType.PlainText &&\n !(data.name || data.language)\n ) {\n if (!data.name) {\n ctx.addIssue({\n code: 'custom',\n params: {\n i18n: 'errors.required',\n },\n path: ['name'],\n })\n }\n if (!data.language) {\n ctx.addIssue({\n code: 'custom',\n params: {\n i18n: 'errors.required',\n },\n path: ['language'],\n })\n }\n }\n })\n\n/**\n * Document update schema for PUT /documents/:id endpoint\n *\n * All fields are optional, but at least one must be provided\n */\nexport const DocumentUpdateDtoSchema = z.object({\n id: z.string(),\n organizationId: z.string(),\n content: z.string().optional(),\n name: z.string().min(1).max(255).optional(),\n language: z.enum(SupportedLanguage).optional(),\n priority: z.number().int().min(1).max(5).optional(),\n agentIds: z.array(z.string()).min(1).optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n})\n\n// ===== Type Exports =====\n\nexport type Document = z.infer<typeof DocumentSchema>\nexport type DocumentQuerystring = z.infer<typeof DocumentQuerystringSchema>\nexport type DocumentCreateDto = z.infer<typeof DocumentCreateDtoSchema>\nexport type DocumentUpdateDto = z.infer<typeof DocumentUpdateDtoSchema>\n\n// ===== Helper Functions =====\n\nexport const getStatusColor = (status: IndexStatus) => {\n switch (status) {\n case IndexStatus.Success:\n return 'success'\n case IndexStatus.Failed:\n return 'danger'\n case IndexStatus.Processing:\n return 'warning'\n default:\n return 'info'\n }\n}\n\nexport const getStatusIcon = (status: IndexStatus) => {\n switch (status) {\n case IndexStatus.Success:\n return 'ri:check-line'\n case IndexStatus.Failed:\n return 'ri:error-warning-line'\n case IndexStatus.Processing:\n return 'ri:hourglass-line'\n default:\n return 'ri:time-line'\n }\n}\n\nexport const getTypeIcon = (type: DocumentType) => {\n switch (type) {\n case DocumentType.File:\n return 'ri:file-line'\n case DocumentType.Url:\n return 'ri:link'\n case DocumentType.RevisedAnswer:\n return 'ri:feedback-line'\n default:\n return 'ri:input-method-line'\n }\n}\n\nexport const getFileIcon = (extension: string) => {\n const ext = (\n extension.startsWith('.') ? extension.slice(1) : extension\n ).toLowerCase()\n switch (ext) {\n case 'pdf':\n return 'ri:file-pdf-2-line'\n case 'doc':\n case 'docx':\n return 'ri:file-word-line'\n case 'xls':\n case 'xlsx':\n return 'ri:file-excel-line'\n case 'ppt':\n case 'pptx':\n return 'ri:file-ppt-line'\n case 'txt':\n return 'ri:file-text-line'\n case 'mp3':\n case 'wav':\n case 'ogg':\n return 'ri:file-music-line'\n case 'mp4':\n case 'avi':\n case 'mkv':\n return 'ri:file-video-line'\n case 'jpg':\n case 'jpeg':\n case 'png':\n case 'gif':\n case 'bmp':\n case 'svg':\n return 'ri:file-image-line'\n case 'zip':\n case 'rar':\n case '7z':\n return 'ri:file-zip-line'\n default:\n return 'ri:file-line'\n }\n}\n\n/** Whether a MIME type is an image. */\nexport const isImageMime = (mimeType?: string | null): boolean =>\n (mimeType ?? '').startsWith('image/')\n\n/** Whether a file MIME type can be previewed inline in the browser (image or\n * PDF) instead of being forced as a download. Shared by every file-list surface\n * so the preview/download decision stays consistent. */\nexport const isInlineViewable = (mimeType?: string | null): boolean =>\n isImageMime(mimeType) || mimeType === 'application/pdf'\n\n/** File-type identity for list rows and previews: icon, accent color\n * (CSS value driving glyph and tile tint) and a short human kind label. */\nexport interface FileKind {\n icon: string\n accent: string\n label: string\n}\n\n/** File-type identity derived from a MIME type. Accents match the chat\n * attachment cards (PkChatbotFilePreview) so files read the same everywhere. */\nexport const fileKindForMime = (mimeType?: string | null): FileKind => {\n const mime = mimeType ?? ''\n if (isImageMime(mime)) {\n return { icon: 'ri:image-line', accent: '#ec4899', label: 'Image' }\n }\n if (mime === 'application/pdf') {\n return { icon: 'ri:file-pdf-2-line', accent: '#e2483d', label: 'PDF' }\n }\n if (mime === 'application/msword' || mime.includes('wordprocessingml')) {\n return { icon: 'ri:file-word-line', accent: '#2b7de9', label: 'Word' }\n }\n if (mime === 'application/vnd.ms-excel' || mime.includes('spreadsheetml')) {\n return {\n icon: 'ri:file-excel-line',\n accent: '#1f9d57',\n label: 'Spreadsheet',\n }\n }\n if (\n mime === 'application/vnd.ms-powerpoint' ||\n mime.includes('presentationml')\n ) {\n return {\n icon: 'ri:file-ppt-line',\n accent: '#d98324',\n label: 'Presentation',\n }\n }\n if (mime.startsWith('audio/')) {\n return { icon: 'ri:file-music-line', accent: '#8b5cf6', label: 'Audio' }\n }\n if (mime.startsWith('video/')) {\n return { icon: 'ri:file-video-line', accent: '#6366f1', label: 'Video' }\n }\n if (\n mime === 'application/zip' ||\n mime === 'application/x-rar-compressed' ||\n mime === 'application/x-7z-compressed'\n ) {\n return { icon: 'ri:file-zip-line', accent: '#c08a1e', label: 'Archive' }\n }\n if (mime.startsWith('text/')) {\n return { icon: 'ri:file-text-line', accent: '#64748b', label: 'Text' }\n }\n return { icon: 'ri:file-line', accent: '#64748b', label: 'File' }\n}\n\n/** Icon for a file-list row, derived from its MIME type. */\nexport const fileIconForMime = (mimeType?: string | null): string =>\n fileKindForMime(mimeType).icon\n"],"x_google_ignoreList":[1],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PkToolShowArtifact-CX4u80Vm.js","names":["$attrs","$t"],"sources":["../../../../packages/components/src/chat/PkCode.vue","../../../../packages/components/src/chat/PkCode.vue","../../../../packages/components/src/chat/PkToolShowArtifact.vue","../../../../packages/components/src/chat/PkToolShowArtifact.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, ref, watch } from 'vue'\n\n defineOptions({\n inheritAttrs: false,\n })\n\n const props = defineProps<{\n code: unknown\n language: string\n line?: boolean\n }>()\n\n const highlightedHtml = ref('')\n\n const hasCode = computed(() => {\n if (typeof props.code === 'string') {\n return props.code\n }\n return JSON.stringify(props.code, null, 2)\n })\n\n async function highlight() {\n const hljs = (await import('highlight.js/lib/common')).default\n try {\n const result = props.language\n ? hljs.highlight(hasCode.value, { language: props.language })\n : hljs.highlightAuto(hasCode.value)\n highlightedHtml.value = result.value\n } catch {\n highlightedHtml.value = hasCode.value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n }\n }\n\n watch(hasCode, highlight, { immediate: true })\n</script>\n\n<template>\n <pre\n :class=\"\n $attrs.class\n ? $attrs.class\n : 'font-mono leading-snug text-smaller bg-surface-1'\n \">\n <code :class=\"['hljs', line ? 'whitespace-pre-line' : 'whitespace-pre', language]\" v-html=\"highlightedHtml\"></code>\n </pre>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, watch } from 'vue'\n\n defineOptions({\n inheritAttrs: false,\n })\n\n const props = defineProps<{\n code: unknown\n language: string\n line?: boolean\n }>()\n\n const highlightedHtml = ref('')\n\n const hasCode = computed(() => {\n if (typeof props.code === 'string') {\n return props.code\n }\n return JSON.stringify(props.code, null, 2)\n })\n\n async function highlight() {\n const hljs = (await import('highlight.js/lib/common')).default\n try {\n const result = props.language\n ? hljs.highlight(hasCode.value, { language: props.language })\n : hljs.highlightAuto(hasCode.value)\n highlightedHtml.value = result.value\n } catch {\n highlightedHtml.value = hasCode.value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n }\n }\n\n watch(hasCode, highlight, { immediate: true })\n</script>\n\n<template>\n <pre\n :class=\"\n $attrs.class\n ? $attrs.class\n : 'font-mono leading-snug text-smaller bg-surface-1'\n \">\n <code :class=\"['hljs', line ? 'whitespace-pre-line' : 'whitespace-pre', language]\" v-html=\"highlightedHtml\"></code>\n </pre>\n</template>\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { useClipboard } from '@vueuse/core'\n import PkCode from './PkCode.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n\n const { copy, copied } = useClipboard()\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n title: string\n content: string\n artifactType: string\n language?: string\n }\n }\n return part\n })\n\n const artifactTypeLabel = computed(() => {\n const labels: Record<string, string> = {\n code: 'Code',\n document: 'Document',\n markdown: 'Markdown',\n json: 'JSON',\n xml: 'XML',\n csv: 'CSV',\n html: 'HTML',\n yaml: 'YAML',\n sql: 'SQL',\n text: 'Text',\n }\n if (!toolPart.value.input?.artifactType) {\n return 'Artifact'\n }\n return labels[toolPart.value.input.artifactType] || 'Artifact'\n })\n\n const languageLabel = computed(() => {\n if (!toolPart.value.input?.language) {\n return ''\n }\n return ` (${toolPart.value.input.language})`\n })\n\n const isCodeType = computed(() => {\n return (\n toolPart.value.input?.artifactType === 'code' ||\n toolPart.value.input?.artifactType === 'json' ||\n toolPart.value.input?.artifactType === 'xml' ||\n toolPart.value.input?.artifactType === 'yaml' ||\n toolPart.value.input?.artifactType === 'html' ||\n toolPart.value.input?.artifactType === 'sql'\n )\n })\n\n const isMarkdownType = computed(() => {\n return toolPart.value.input?.artifactType === 'markdown'\n })\n\n const codeLanguage = computed(() => {\n if (toolPart.value.input?.language) {\n return toolPart.value.input.language\n }\n // Map artifact types to highlight.js languages\n const languageMap: Record<string, string> = {\n json: 'json',\n xml: 'xml',\n yaml: 'yaml',\n html: 'html',\n sql: 'sql',\n }\n if (!toolPart.value.input?.artifactType) {\n return 'plaintext'\n }\n return languageMap[toolPart.value.input.artifactType] || 'plaintext'\n })\n</script>\n\n<template>\n <div class=\"border border-surface-3 rounded-xl w-full overflow-hidden\">\n <div\n class=\"px-sm py-6 font-bold bg-surface-1 text-12 border-b border-surface-3 text-word-3 flex justify-between items-center\">\n <div class=\"flex flex-col items-start gap-4\">\n <span>{{ toolPart?.input?.title }}</span>\n <span class=\"text-10 opacity-60 font-normal\">\n {{ artifactTypeLabel }}{{ languageLabel }}\n </span>\n </div>\n <VvButton\n :icon=\"copied ? 'ri:check-line' : 'ri:file-copy-line'\"\n :label=\"copied ? $t('action.copied') : $t('action.copy')\"\n modifiers=\"action-quiet-small\"\n :title=\"$t('action.copy')\"\n class=\"shrink-0\"\n @click=\"copy(toolPart?.input?.content || '')\" />\n </div>\n <div class=\"px-sm py-10\">\n <PkCode\n v-if=\"isCodeType\"\n :code=\"toolPart?.input?.content\"\n :language=\"codeLanguage\" />\n <PkStreamingMarkdown\n v-else-if=\"isMarkdownType\"\n :markdown=\"toolPart?.input?.content\"\n class=\"wysiwyg text-word-2\" />\n <pre\n v-else\n class=\"text-word-2 whitespace-pre-wrap break-words\"\n :class=\"{\n italic:\n toolPart?.input?.artifactType === 'document' ||\n toolPart?.input?.artifactType === 'text',\n }\"\n >{{ toolPart?.input?.content }}</pre>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { useClipboard } from '@vueuse/core'\n import PkCode from './PkCode.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n\n const { copy, copied } = useClipboard()\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n title: string\n content: string\n artifactType: string\n language?: string\n }\n }\n return part\n })\n\n const artifactTypeLabel = computed(() => {\n const labels: Record<string, string> = {\n code: 'Code',\n document: 'Document',\n markdown: 'Markdown',\n json: 'JSON',\n xml: 'XML',\n csv: 'CSV',\n html: 'HTML',\n yaml: 'YAML',\n sql: 'SQL',\n text: 'Text',\n }\n if (!toolPart.value.input?.artifactType) {\n return 'Artifact'\n }\n return labels[toolPart.value.input.artifactType] || 'Artifact'\n })\n\n const languageLabel = computed(() => {\n if (!toolPart.value.input?.language) {\n return ''\n }\n return ` (${toolPart.value.input.language})`\n })\n\n const isCodeType = computed(() => {\n return (\n toolPart.value.input?.artifactType === 'code' ||\n toolPart.value.input?.artifactType === 'json' ||\n toolPart.value.input?.artifactType === 'xml' ||\n toolPart.value.input?.artifactType === 'yaml' ||\n toolPart.value.input?.artifactType === 'html' ||\n toolPart.value.input?.artifactType === 'sql'\n )\n })\n\n const isMarkdownType = computed(() => {\n return toolPart.value.input?.artifactType === 'markdown'\n })\n\n const codeLanguage = computed(() => {\n if (toolPart.value.input?.language) {\n return toolPart.value.input.language\n }\n // Map artifact types to highlight.js languages\n const languageMap: Record<string, string> = {\n json: 'json',\n xml: 'xml',\n yaml: 'yaml',\n html: 'html',\n sql: 'sql',\n }\n if (!toolPart.value.input?.artifactType) {\n return 'plaintext'\n }\n return languageMap[toolPart.value.input.artifactType] || 'plaintext'\n })\n</script>\n\n<template>\n <div class=\"border border-surface-3 rounded-xl w-full overflow-hidden\">\n <div\n class=\"px-sm py-6 font-bold bg-surface-1 text-12 border-b border-surface-3 text-word-3 flex justify-between items-center\">\n <div class=\"flex flex-col items-start gap-4\">\n <span>{{ toolPart?.input?.title }}</span>\n <span class=\"text-10 opacity-60 font-normal\">\n {{ artifactTypeLabel }}{{ languageLabel }}\n </span>\n </div>\n <VvButton\n :icon=\"copied ? 'ri:check-line' : 'ri:file-copy-line'\"\n :label=\"copied ? $t('action.copied') : $t('action.copy')\"\n modifiers=\"action-quiet-small\"\n :title=\"$t('action.copy')\"\n class=\"shrink-0\"\n @click=\"copy(toolPart?.input?.content || '')\" />\n </div>\n <div class=\"px-sm py-10\">\n <PkCode\n v-if=\"isCodeType\"\n :code=\"toolPart?.input?.content\"\n :language=\"codeLanguage\" />\n <PkStreamingMarkdown\n v-else-if=\"isMarkdownType\"\n :markdown=\"toolPart?.input?.content\"\n class=\"wysiwyg text-word-2\" />\n <pre\n v-else\n class=\"text-word-2 whitespace-pre-wrap break-words\"\n :class=\"{\n italic:\n toolPart?.input?.artifactType === 'document' ||\n toolPart?.input?.artifactType === 'text',\n }\"\n >{{ toolPart?.input?.content }}</pre>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PkChatbotMessages-kRAjAYUH.js","names":["$emit"],"sources":["../../../../packages/components/src/chat/toolComponentMap.ts","../../../../packages/components/src/chat/useChatScroll.ts","../../../../packages/components/src/chat/PkChatbotMessages.vue","../../../../packages/components/src/chat/PkChatbotMessages.vue"],"sourcesContent":["import { defineAsyncComponent } from 'vue'\n\n/**\n * Maps tool `part.type` to the corresponding component for auto-rendering.\n * Only includes simple tools that accept only a `:part` prop.\n * Interactive tools (requestConfirm, showContactForm, showSuggestedReply,\n * showSources, showMultipleChoice) must be wired explicitly in the parent\n * with their required callbacks/events.\n */\nexport const toolComponentMap: Record<\n string,\n ReturnType<typeof defineAsyncComponent>\n> = {\n requestConfirm: defineAsyncComponent(\n () => import('./PkToolRequestConfirm.vue'),\n ),\n requestOAuthConnection: defineAsyncComponent(\n () => import('./PkToolRequestOAuthConnection.vue'),\n ),\n showArtifact: defineAsyncComponent(\n () => import('./PkToolShowArtifact.vue'),\n ),\n showCalendarEvent: defineAsyncComponent(\n () => import('./PkToolShowCalendarEvent.vue'),\n ),\n showComparison: defineAsyncComponent(\n () => import('./PkToolShowComparison.vue'),\n ),\n showContactForm: defineAsyncComponent(\n () => import('./PkToolShowContactForm.vue'),\n ),\n showDiagram: defineAsyncComponent(() => import('./PkToolShowDiagram.vue')),\n showEmail: defineAsyncComponent(() => import('./PkToolShowEmail.vue')),\n showImageGallery: defineAsyncComponent(\n () => import('./PkToolShowImageGallery.vue'),\n ),\n showLocation: defineAsyncComponent(\n () => import('./PkToolShowLocation.vue'),\n ),\n showMessage: defineAsyncComponent(() => import('./PkToolShowMessage.vue')),\n showForm: defineAsyncComponent(() => import('./PkToolShowForm.vue')),\n showMultipleChoice: defineAsyncComponent(\n () => import('./PkToolShowForm.vue'),\n ),\n showProductList: defineAsyncComponent(\n () => import('./PkToolShowProductList.vue'),\n ),\n showQrCode: defineAsyncComponent(() => import('./PkToolShowQrCode.vue')),\n showSources: defineAsyncComponent(() => import('./PkToolShowSources.vue')),\n showSuggestedReply: defineAsyncComponent(\n () => import('./PkToolShowSuggestedReply.vue'),\n ),\n showWeather: defineAsyncComponent(() => import('./PkToolShowWeather.vue')),\n showWebPages: defineAsyncComponent(\n () => import('./PkToolShowWebPages.vue'),\n ),\n}\n","import type { Ref, ShallowRef } from 'vue'\nimport { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'\n\nconst NEAR_BOTTOM_THRESHOLD = 30\nconst SMOOTH_SCROLL_DURATION = 500\n\ninterface UseChatScrollOptions {\n scrollEl:\n | Readonly<ShallowRef<HTMLDivElement | null>>\n | Ref<HTMLDivElement | undefined>\n status: () => string | undefined\n messagesLength: () => number | undefined\n lastMessageRole: () => string | undefined\n /**\n * Element whose height tracks the content height, watched for late\n * growth (lazy tool outputs). Defaults to the scroll element's first\n * child.\n */\n contentEl?: () => HTMLElement | null\n onScrollUp?: () => void\n onScrollDown?: () => void\n}\n\nexport function useChatScroll(options: UseChatScrollOptions) {\n const {\n scrollEl,\n status,\n messagesLength,\n lastMessageRole,\n contentEl,\n onScrollUp,\n onScrollDown,\n } = options\n\n const userScrolledUp = ref(false)\n const isAtBottom = ref(true)\n let isAutoScrolling = false\n let mutationObserver: MutationObserver | null = null\n let resizeObserver: ResizeObserver | null = null\n let initMutationObserver: MutationObserver | null = null\n let contentResizeObserver: ResizeObserver | null = null\n let lastScrollTop = 0\n\n // --- Core scroll ---\n\n const scrollToBottom = (behavior: ScrollBehavior = 'smooth') => {\n if (!scrollEl.value || userScrolledUp.value) {\n return\n }\n isAutoScrolling = true\n scrollEl.value.scrollTo({\n top: scrollEl.value.scrollHeight,\n behavior,\n })\n const unlockDelay = behavior === 'instant' ? 0 : SMOOTH_SCROLL_DURATION\n setTimeout(() => {\n isAutoScrolling = false\n }, unlockDelay)\n }\n\n // --- User scroll detection ---\n // Intent to scroll away comes from input events (wheel/touch): unlike\n // scroll deltas they are never produced by programmatic scrolls, layout\n // shifts or elastic overscroll bounces, so the auto-scroll never fights\n // the user nor disables itself spuriously.\n\n const onWheel = (event: WheelEvent) => {\n if (event.deltaY < 0) {\n userScrolledUp.value = true\n }\n }\n\n let lastTouchY = 0\n const onTouchStart = (event: TouchEvent) => {\n lastTouchY = event.touches[0]?.clientY ?? 0\n }\n const onTouchMove = (event: TouchEvent) => {\n const y = event.touches[0]?.clientY ?? 0\n // Finger moving down drags the content up\n if (y > lastTouchY) {\n userScrolledUp.value = true\n }\n lastTouchY = y\n }\n\n const handleScroll = () => {\n if (!scrollEl.value) {\n return\n }\n const {\n scrollTop: currentScrollTop,\n scrollHeight,\n clientHeight,\n } = scrollEl.value\n isAtBottom.value =\n scrollHeight - clientHeight - currentScrollTop <\n NEAR_BOTTOM_THRESHOLD\n if (isAutoScrolling) {\n lastScrollTop = currentScrollTop\n return\n }\n\n if (currentScrollTop < lastScrollTop) {\n onScrollUp?.()\n }\n\n if (currentScrollTop > lastScrollTop) {\n // Back at the bottom: re-enable auto-scroll\n if (isAtBottom.value) {\n userScrolledUp.value = false\n }\n onScrollDown?.()\n }\n\n lastScrollTop = currentScrollTop\n }\n\n // --- Streaming auto-follow ---\n // Tracks the growing content towards the bottom, never above the\n // current position. While the answer fits the space reserved by the\n // last-message spacer in PkChatbotMessages the content bottom does not\n // move, so the freshly sent user message stays anchored at the top;\n // once the answer overflows, the follow keeps the tail in view until\n // the user scrolls away (wheel/touch sets `userScrolledUp`).\n const autoFollow = () => {\n if (isAutoScrolling || userScrolledUp.value || !scrollEl.value) {\n return\n }\n const el = scrollEl.value\n const bottom = el.scrollHeight - el.clientHeight\n if (bottom <= el.scrollTop) {\n return\n }\n isAtBottom.value = true\n isAutoScrolling = true\n el.scrollTop = bottom\n requestAnimationFrame(() => {\n isAutoScrolling = false\n })\n }\n\n const startMutationScroll = () => {\n if (mutationObserver || !scrollEl.value) {\n return\n }\n mutationObserver = new MutationObserver(autoFollow)\n mutationObserver.observe(scrollEl.value, {\n childList: true,\n subtree: true,\n characterData: true,\n })\n }\n\n const stopMutationScroll = () => {\n mutationObserver?.disconnect()\n mutationObserver = null\n }\n\n // --- Status watcher ---\n\n watch(status, (newStatus, oldStatus) => {\n if (newStatus === 'streaming' || newStatus === 'submitted') {\n startMutationScroll()\n return\n }\n if (newStatus === 'ready' && oldStatus === 'streaming') {\n // Keep observer active to catch footer buttons rendering\n return\n }\n stopMutationScroll()\n })\n\n // --- New message watcher ---\n\n watch(messagesLength, async () => {\n if (!messagesLength()) {\n return\n }\n if (lastMessageRole() === 'assistant') {\n return\n }\n // A user send ends the initial-load phase: on a chat whose content\n // first overflows while the answer streams in, the initial observers\n // would otherwise slam the scroll to the bottom past the anchor\n cleanupInitialObservers()\n userScrolledUp.value = false\n isAutoScrolling = true\n await nextTick()\n scrollToBottom()\n })\n\n // --- Initial scroll ---\n\n let initialScrollTimer: ReturnType<typeof setTimeout> | null = null\n const INITIAL_SCROLL_SETTLE_MS = 300\n\n const tryInitialScroll = () => {\n if (!scrollEl.value) {\n return\n }\n if (scrollEl.value.scrollHeight <= scrollEl.value.clientHeight) {\n return\n }\n scrollEl.value.scrollTop = scrollEl.value.scrollHeight\n\n // Reset the settle timer on each mutation — disconnect only after stability\n if (initialScrollTimer) {\n clearTimeout(initialScrollTimer)\n }\n initialScrollTimer = setTimeout(\n cleanupInitialObservers,\n INITIAL_SCROLL_SETTLE_MS,\n )\n }\n\n const cleanupInitialObservers = () => {\n resizeObserver?.disconnect()\n resizeObserver = null\n initMutationObserver?.disconnect()\n initMutationObserver = null\n if (initialScrollTimer) {\n clearTimeout(initialScrollTimer)\n initialScrollTimer = null\n }\n }\n\n onMounted(() => {\n if (!scrollEl.value) {\n return\n }\n scrollEl.value.addEventListener('wheel', onWheel, { passive: true })\n scrollEl.value.addEventListener('touchstart', onTouchStart, {\n passive: true,\n })\n scrollEl.value.addEventListener('touchmove', onTouchMove, {\n passive: true,\n })\n\n // Mounted mid-send (e.g. fullscreen first message: the messages view\n // replaces the empty state after the message is already in) is not a\n // history load: skip the initial bottom-jump and follow the stream\n // from the anchored position instead.\n const isSendInFlight =\n status() === 'submitted' || status() === 'streaming'\n if (isSendInFlight) {\n startMutationScroll()\n } else {\n resizeObserver = new ResizeObserver(tryInitialScroll)\n resizeObserver.observe(scrollEl.value)\n\n initMutationObserver = new MutationObserver(tryInitialScroll)\n initMutationObserver.observe(scrollEl.value, {\n childList: true,\n subtree: true,\n })\n }\n\n // Late content growth: tool outputs render lazily (async chunks,\n // dynamic imports: diagrams, images...) and can change the content\n // height long after the initial scroll settled. The wrapper height\n // tracks the content height (the scroll element itself never\n // resizes with it).\n const wrapper = contentEl?.() ?? scrollEl.value.firstElementChild\n if (wrapper) {\n contentResizeObserver = new ResizeObserver(autoFollow)\n contentResizeObserver.observe(wrapper)\n }\n\n if (!isSendInFlight) {\n tryInitialScroll()\n }\n })\n\n onBeforeUnmount(() => {\n scrollEl.value?.removeEventListener('wheel', onWheel)\n scrollEl.value?.removeEventListener('touchstart', onTouchStart)\n scrollEl.value?.removeEventListener('touchmove', onTouchMove)\n contentResizeObserver?.disconnect()\n contentResizeObserver = null\n stopMutationScroll()\n cleanupInitialObservers()\n })\n\n return {\n handleScroll,\n scrollToBottom,\n userScrolledUp,\n isAtBottom,\n }\n}\n","<script lang=\"ts\" setup>\n import type {\n ChatMessageActions,\n MessageFeedback,\n RevisedAnswer,\n UIChatMessage,\n } from 'models'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkChatbotError from './PkChatbotError.vue'\n import PkChatbotFeedbackForm from './PkChatbotFeedbackForm.vue'\n import PkChatbotMessageFeedback from './PkChatbotMessageFeedback.vue'\n import PkChatbotFilePreview from './PkChatbotFilePreview.vue'\n import PkRelativeTime from '../PkRelativeTime.vue'\n import { useI18n } from 'vue-i18n'\n import {\n useTemplateRef,\n ref,\n watch,\n computed,\n nextTick,\n useSlots,\n } from 'vue'\n import { toolComponentMap } from './toolComponentMap'\n import PkChatbotSteps from './PkChatbotSteps.vue'\n import {\n resolveContrastColor,\n getPartState,\n isTextPart,\n isFilePart,\n isToolPart,\n isReasoningPart,\n isStreamingPart,\n getPartIcon,\n getToolPartLabel,\n mergeConsecutiveTextParts,\n formatDuration,\n } from './utils'\n import { getFilePartMedia, getToolPartName, toKebabCase } from 'utils'\n import PkStreamingMarkdownAutoscroll from './PkStreamingMarkdownAutoscroll.vue'\n import PkChatbotOutlineRail from './PkChatbotOutlineRail.vue'\n import { useChatScroll } from './useChatScroll'\n\n const props = defineProps<{\n status?: 'submitted' | 'streaming' | 'ready' | 'error'\n messages?: UIChatMessage[]\n error?: Error\n actions?: ChatMessageActions[]\n mainColor?: string\n textColor?: 'auto' | 'white' | 'black'\n revisedAnswers?: RevisedAnswer[]\n messageFeedbacks?: MessageFeedback[]\n /**\n * Render the vote/feedback action buttons as read-only state indicators\n * (e.g. admin chat-logs view): icons reflect the user's feedback but the\n * buttons are not interactive.\n */\n readonlyFeedback?: boolean\n disableHeightAdjustment?: boolean\n showMessageDateTime?: boolean\n showMessageTokensCount?: boolean\n showAllMessageParts?: boolean\n showExtendedSteps?: boolean\n isDark?: boolean\n /** Show a floating \"back to bottom\" button when the user scrolls up (fullscreen layout) */\n showScrollToBottom?: boolean\n // TODO: move feedback in a separate component to avoid passing these props\n feedbackMessageId?: string\n feedbackLoading?: boolean\n feedbackSubmitted?: boolean\n feedbackError?: string\n }>()\n\n const emit = defineEmits<{\n (e: 'show-info', message: UIChatMessage): void\n (e: 'regenerate'): void\n (e: 'revise', message: UIChatMessage): void\n (e: 'upvote', message: UIChatMessage): void\n (e: 'downvote', message: UIChatMessage): void\n (e: 'feedback', message: UIChatMessage): void\n (e: 'feedback-submit', comment: string): void\n (e: 'feedback-close'): void\n (e: 'scroll-up'): void\n (e: 'scroll-down'): void\n (e: 'auto-retry'): void\n (e: 'reset-chat'): void\n }>()\n\n const {\n t: $t,\n d: $d,\n n: $n,\n } = useI18n({\n useScope: 'global',\n })\n\n const slots = useSlots()\n\n const scrollEl = useTemplateRef<HTMLDivElement>('scrollEl')\n const wrapperEl = useTemplateRef<HTMLDivElement>('wrapperEl')\n const contrastColor = computed(() =>\n resolveContrastColor(props.textColor, props.mainColor),\n )\n\n // --- Scroll ---\n\n const messagesElRefs = useTemplateRef<HTMLDivElement[]>('messagesEl')\n\n const { handleScroll, scrollToBottom, userScrolledUp, isAtBottom } =\n useChatScroll({\n scrollEl,\n status: () => props.status,\n messagesLength: () => props.messages?.length,\n lastMessageRole: () =>\n props.messages?.[props.messages.length - 1]?.role,\n contentEl: () => wrapperEl.value,\n onScrollUp: () => emit('scroll-up'),\n onScrollDown: () => emit('scroll-down'),\n })\n\n const onScrollToBottomClick = () => {\n // scrollToBottom() is a no-op while userScrolledUp is set\n userScrolledUp.value = false\n scrollToBottom()\n }\n\n // --- Conversation outline (fullscreen, desktop) ---\n\n const outlineItems = computed(() =>\n (props.messages ?? [])\n .filter((message) => message.role === 'user')\n .map((message) => ({\n id: message.id,\n preview:\n message.parts.find(isTextPart)?.text.trim().slice(0, 120) ||\n '…',\n })),\n )\n const activeOutlineId = ref<string>()\n\n // The rail entries map 1:1 onto the rendered user messages\n const getUserMessageEls = () =>\n scrollEl.value?.querySelectorAll('.pk-chatbot-message--user') ?? []\n\n // Scroll-spy: active is the last user message above the middle of the\n // viewport (the exchange currently being read)\n const updateActiveOutline = () => {\n const scroller = scrollEl.value\n if (!scroller || !props.showScrollToBottom) {\n return\n }\n const line =\n scroller.getBoundingClientRect().top + scroller.clientHeight / 2\n let active = outlineItems.value[0]?.id\n getUserMessageEls().forEach((el, index) => {\n if (el.getBoundingClientRect().top <= line) {\n active = outlineItems.value[index]?.id\n }\n })\n activeOutlineId.value = active\n }\n\n watch(outlineItems, async () => {\n await nextTick()\n updateActiveOutline()\n })\n\n const onScroll = () => {\n handleScroll()\n updateActiveOutline()\n }\n\n const scrollToUserMessage = (id: string) => {\n const scroller = scrollEl.value\n const index = outlineItems.value.findIndex((item) => item.id === id)\n const messageEl = getUserMessageEls()[index]\n if (!scroller || !messageEl) {\n return\n }\n // Intentional jump away from the live tail: the wheel/touch intent\n // detection cannot see programmatic scrolls, pause the auto-follow\n // explicitly (reaching the bottom again re-enables it)\n userScrolledUp.value = true\n // Same resting line as a freshly sent message: top of the content area\n scroller.scrollTo({\n top:\n scroller.scrollTop +\n messageEl.getBoundingClientRect().top -\n scroller.getBoundingClientRect().top -\n Number.parseFloat(getComputedStyle(scroller).paddingTop),\n behavior: 'smooth',\n })\n }\n\n // --- Height adjustment ---\n\n const height = ref<number>(0)\n\n // Recalculate when a message is added or the conversation changes (the\n // store recreates `messages` on every streamed token, so watching the\n // array reference would re-run this on each character). The height only\n // depends on the last user message height, which is stable while\n // streaming.\n watch(\n () => [\n props.messages?.length,\n props.messages?.[props.messages.length - 1]?.id,\n ],\n async () => {\n const messages = props.messages\n if (!messages?.length || props.disableHeightAdjustment) {\n return\n }\n // The spacer only serves the in-flight exchange, letting the\n // fresh user message anchor at the top while the answer streams.\n // On plain loads (e.g. opening an old conversation) it would\n // just leave a large void after the last message.\n const isExchangeInFlight =\n messages[messages.length - 1].role === 'user' ||\n props.status === 'submitted' ||\n props.status === 'streaming'\n if (!isExchangeInFlight) {\n return\n }\n await nextTick()\n if (!scrollEl.value) {\n return\n }\n // Spacer sized so the freshly sent user message rests at the top\n // of the content area once scrolled to bottom: subtract both\n // paddings (clientHeight includes them) so any extra top\n // clearance (e.g. the fullscreen overlay header) is respected,\n // and the wrapper row gap separating the message from the spacer\n const { paddingTop, paddingBottom } = getComputedStyle(\n scrollEl.value,\n )\n const scrollElHeight =\n scrollEl.value.clientHeight -\n Number.parseFloat(paddingTop) -\n Number.parseFloat(paddingBottom)\n const wrapperGap = wrapperEl.value\n ? Number.parseFloat(getComputedStyle(wrapperEl.value).rowGap) ||\n 0\n : 0\n const lastUserMessageIndex = messages\n .map((message) => message.role)\n .lastIndexOf('user')\n const lastUserMessageHeight =\n messagesElRefs.value?.[lastUserMessageIndex]?.clientHeight ?? 0\n const newHeight =\n scrollElHeight - lastUserMessageHeight - wrapperGap\n if (newHeight > 0) {\n height.value = newHeight\n }\n },\n )\n\n // Release the spacer once the exchange is over: it would otherwise\n // leave a large empty area after the last message. The min-height\n // transition settles the layout smoothly (the browser clamps the\n // scroll progressively when the viewport sat inside the removed area).\n watch(\n () => props.status,\n (status) => {\n if (status === 'ready' || status === 'error') {\n height.value = 0\n }\n },\n )\n\n const isRevised = (messageId: string) => {\n return props.revisedAnswers?.some((r) => r.messageId === messageId)\n }\n\n const getMessageFeedback = (messageId: string) =>\n props.messageFeedbacks?.find((f) => f.messageId === messageId)\n\n // Interactive vote/feedback buttons. In read-only mode (admin chat-logs)\n // the vote is shown on the message itself (coloured bubble border + an\n // \"active\" thumb in the footer) and the comment, if any, below it.\n const isFeedbackButtonVisible = (\n kind: 'upvote' | 'downvote' | 'feedback',\n index: number,\n ) => !props.readonlyFeedback && isActionButtonVisible(kind, index)\n\n // Read-only feedback for a message (drives the reaction chip anchored to\n // the bubble). undefined when not in read-only mode.\n const readonlyFeedbackOf = (messageId: string) =>\n props.readonlyFeedback ? getMessageFeedback(messageId) : undefined\n const feedbackVote = (messageId: string) =>\n readonlyFeedbackOf(messageId)?.vote\n // The real reaction glyph, shown ONLY for unmapped reactions (no vote):\n // 👍/❤️/👎 keep the classic thumb icon, while e.g. WhatsApp \"😍\" shows the\n // actual emoji. Platforms that send a type word (Teams \"like\") have a vote\n // and therefore use the icon too.\n const feedbackEmoji = (messageId: string) => {\n const feedback = readonlyFeedbackOf(messageId)\n if (feedback?.vote) {\n return undefined\n }\n const raw = feedback?.metadata?.reactionEmoji\n return typeof raw === 'string' && /\\p{Extended_Pictographic}/u.test(raw)\n ? raw\n : undefined\n }\n const hasReaction = (messageId: string) =>\n Boolean(feedbackVote(messageId) || feedbackEmoji(messageId))\n const reactionToneClass = (messageId: string) => {\n // A real emoji carries its own colour → neutral pill. Only the\n // thumb-icon fallback uses the sentiment colour.\n if (feedbackEmoji(messageId)) {\n return 'pk-chatbot-message__reaction--neutral'\n }\n const vote = feedbackVote(messageId)\n if (vote === 'upvote') {\n return 'pk-chatbot-message__reaction--positive'\n }\n if (vote === 'downvote') {\n return 'pk-chatbot-message__reaction--negative'\n }\n return 'pk-chatbot-message__reaction--neutral'\n }\n const reactionTitle = (messageId: string) => {\n const vote = feedbackVote(messageId)\n if (vote === 'upvote') {\n return $t('hint.userFeedbackPositive')\n }\n if (vote === 'downvote') {\n return $t('hint.userFeedbackNegative')\n }\n return $t('hint.userFeedbackReaction')\n }\n\n // Index of the last text part: the reaction chip anchors to that bubble so\n // it renders once even when the message has several text parts.\n const lastTextPartIndex = (message: UIChatMessage) => {\n const parts = getMergedParts(message)\n for (let index = parts.length - 1; index >= 0; index -= 1) {\n if (isTextPart(parts[index])) {\n return index\n }\n }\n return -1\n }\n\n const activeMessage = computed(() => {\n return props.messages?.[props.messages.length - 1]\n })\n const activeMessageLastPart = computed(() => {\n const active = activeMessage.value\n if (!active) {\n return null\n }\n return active.parts[active.parts.length - 1]\n })\n\n const mergedPartsMap = computed(() => {\n const map = new Map<string, UIChatMessage['parts']>()\n for (const message of props.messages ?? []) {\n if (message.role === 'assistant') {\n map.set(message.id, mergeConsecutiveTextParts(message.parts))\n }\n }\n return map\n })\n const getMergedParts = (message: UIChatMessage) => {\n return mergedPartsMap.value.get(message.id) ?? message.parts\n }\n const isLoading = computed(() => {\n return props.status === 'submitted' || props.status === 'streaming'\n })\n const isError = computed(() => props.status === 'error')\n\n const activeMessageLastPartLabel = computed(() => {\n const part = activeMessageLastPart.value\n return getToolPartLabel($t, part)\n })\n const activeMessageLastPartIcon = computed(() => {\n const part = activeMessageLastPart.value\n return getPartIcon(part)\n })\n\n const getMessageIndexById = (messageId: string) => {\n return props.messages?.findIndex((m) => m.id === messageId)\n }\n const isLastMessage = (index?: number) => {\n if (index === undefined || !props.messages) {\n return false\n }\n return index === props.messages.length - 1\n }\n const isMessageRegenerateButtonVisible = (index: number) => {\n return (\n isLastMessage(index) &&\n props.status === 'ready' &&\n !!props.actions?.includes('regenerate')\n )\n }\n // On the last message the actions appear only once the answer is ready;\n // on previous messages they are always available.\n const isActionButtonVisible = (\n action: ChatMessageActions,\n index: number,\n ) => {\n return (\n (!isLastMessage(index) || props.status === 'ready') &&\n !!props.actions?.includes(action)\n )\n }\n const isLastTextPart = (message: UIChatMessage) => {\n const lastPart = message.parts[message.parts.length - 1]\n return isTextPart(lastPart)\n }\n const messageHasSteps = (message: UIChatMessage) =>\n message.parts.some((part) => isReasoningPart(part) || isToolPart(part))\n // The extended steps timeline replaces the compact activity indicator as\n // soon as the message has at least one step to show.\n const showStepsTimeline = (message: UIChatMessage) =>\n Boolean(props.showExtendedSteps) &&\n isAssistant(message) &&\n messageHasSteps(message)\n const isAssistant = (message: UIChatMessage) => message.role === 'assistant'\n const isUser = (message: UIChatMessage) => message.role === 'user'\n const getPreviousAssistantMessage = (index: number) => {\n return props.messages\n ?.slice(0, index)\n .reverse()\n .find((message) => isAssistant(message))\n }\n const getNextAssistantMessage = (index: number) => {\n return props.messages\n ?.slice(index + 1)\n .find((message) => isAssistant(message))\n }\n // Returns the date to show in the day divider before the message at\n // `index`, or undefined when the surrounding assistant messages belong\n // to the same (local) day.\n const getDividerDate = (index: number) => {\n const previousCreatedAt =\n getPreviousAssistantMessage(index)?.metadata?.createdAt\n const nextCreatedAt =\n getNextAssistantMessage(index)?.metadata?.createdAt\n if (!previousCreatedAt || !nextCreatedAt) {\n return undefined\n }\n const previousDate = new Date(previousCreatedAt)\n const nextDate = new Date(nextCreatedAt)\n return previousDate.toDateString() !== nextDate.toDateString()\n ? nextDate\n : undefined\n }\n const showMessageFooter = (message: UIChatMessage, index: number) => {\n if (index === 0 || message.role !== 'assistant') {\n return false\n }\n return Boolean(\n props.actions?.length ||\n (props.showMessageDateTime &&\n message.metadata?.createdAt !== undefined) ||\n message.metadata?.completedAt !== undefined ||\n (props.showMessageTokensCount &&\n message.metadata?.totalTokens !== undefined),\n )\n }\n // Hover-only metadata (time + duration), shown when the fixed variant\n // (showMessageDateTime) is off.\n const formatMessageTime = (createdAt: number) => {\n const date = new Date(createdAt)\n const isToday = date.toDateString() === new Date().toDateString()\n return $d(date, isToday ? 'time' : 'date-time')\n }\n const getMessageDuration = (message: UIChatMessage) => {\n const { createdAt, completedAt } = message.metadata ?? {}\n if (!createdAt || !completedAt) {\n return undefined\n }\n return formatDuration($n, completedAt - createdAt)\n }\n // feedback message auto scroll\n const feedbackMessageEl =\n useTemplateRef<InstanceType<typeof PkChatbotFeedbackForm>[]>(\n 'feedbackMessageEl',\n )\n watch(\n () => props.feedbackMessageId,\n async () => {\n await nextTick()\n if (!props.feedbackMessageId) {\n return\n }\n const el = feedbackMessageEl.value?.[0]?.$el\n if (!el) {\n return\n }\n if (isLastMessage(getMessageIndexById(props.feedbackMessageId))) {\n scrollToBottom()\n return\n }\n el.scrollIntoView({ behavior: 'smooth', block: 'center' })\n },\n )\n</script>\n\n<template>\n <div\n ref=\"scrollEl\"\n class=\"pk-chatbot-messages\"\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n :aria-busy=\"isLoading\"\n :style=\"{\n '--chatbot-main-color': mainColor,\n '--chatbot-contrast-color': contrastColor,\n }\"\n @scroll=\"onScroll\">\n <!-- First in flow (content top): its absolutely-positioned children\n must never inflate the scrollable overflow past the real content -->\n <div\n v-if=\"showScrollToBottom && outlineItems.length > 1\"\n class=\"pk-chatbot-messages__outline\">\n <PkChatbotOutlineRail\n :items=\"outlineItems\"\n :active-id=\"activeOutlineId\"\n @select=\"scrollToUserMessage\" />\n </div>\n <div ref=\"wrapperEl\" class=\"pk-chatbot-messages__wrapper\">\n <template v-for=\"(message, index) in messages\" :key=\"message.id\">\n <div\n v-if=\"isUser(message) && getDividerDate(index)\"\n class=\"pk-chatbot-divider\">\n <span class=\"pk-chatbot-divider__label\">\n {{ $d(getDividerDate(index)!, 'short') }}\n </span>\n </div>\n <div\n v-if=\"\n message.parts.length ||\n (isLastMessage(index) &&\n isLoading &&\n isAssistant(message))\n \"\n ref=\"messagesEl\"\n class=\"pk-chatbot-message\"\n :class=\"[\n `pk-chatbot-message--${message.role}`,\n {\n 'pk-chatbot-message--loading':\n isLoading &&\n isLastMessage(index) &&\n isAssistant(message),\n },\n ]\"\n :style=\"{\n minHeight:\n isLastMessage(index) &&\n isAssistant(message) &&\n !isError\n ? `${height}px`\n : undefined,\n }\">\n <transition appear name=\"pk-chatbot-part\">\n <PkChatbotSteps\n v-if=\"showStepsTimeline(message)\"\n :message\n :is-streaming=\"isLoading && isLastMessage(index)\"\n :is-dark />\n </transition>\n <template\n v-for=\"(part, partIndex) in getMergedParts(message)\"\n :key=\"partIndex\">\n <transition\n v-if=\"isTextPart(part) && part.text.trim()\"\n appear\n name=\"pk-chatbot-part\">\n <div class=\"pk-chatbot-message__text\">\n <slot\n v-bind=\"{\n message,\n part,\n index,\n isLoading,\n }\"\n name=\"text\">\n <PkStreamingMarkdown\n v-if=\"isAssistant(message)\"\n class=\"wysiwyg\"\n :markdown=\"part.text\"\n :is-dark\n :loading=\"\n isLastMessage(index) &&\n status === 'streaming'\n \" />\n <template v-else>\n {{ part.text }}\n </template>\n </slot>\n <!-- Read-only reaction chip overlapping the\n bubble corner, like a messaging app. Hidden\n when a comment is shown: the callout below\n then carries the reaction glyph instead. -->\n <span\n v-if=\"\n hasReaction(message.id) &&\n !getMessageFeedback(message.id)\n ?.comment &&\n partIndex === lastTextPartIndex(message)\n \"\n class=\"pk-chatbot-message__reaction\"\n :class=\"reactionToneClass(message.id)\"\n :title=\"reactionTitle(message.id)\">\n <span\n v-if=\"feedbackEmoji(message.id)\"\n class=\"pk-chatbot-message__reaction-emoji\">\n {{ feedbackEmoji(message.id) }}\n </span>\n <VvIcon\n v-else\n :name=\"\n feedbackVote(message.id) ===\n 'downvote'\n ? 'ri:thumb-down-fill'\n : 'ri:thumb-up-fill'\n \" />\n </span>\n </div>\n </transition>\n <transition\n v-else-if=\"isFilePart(part)\"\n appear\n name=\"pk-chatbot-part\">\n <PkChatbotFilePreview\n :media-type=\"part.mediaType\"\n :url=\"part.url\"\n :filename=\"part.filename\"\n :size=\"getFilePartMedia(part)?.bytesCount\" />\n </transition>\n <transition\n v-else-if=\"\n isToolPart(part) &&\n !isStreamingPart(part) &&\n (toolComponentMap[getToolPartName(part)] ||\n slots[part.type] ||\n showAllMessageParts)\n \"\n appear\n name=\"pk-chatbot-part\">\n <component\n :is=\"toolComponentMap[getToolPartName(part)]\"\n v-if=\"\n !slots[part.type] &&\n toolComponentMap[getToolPartName(part)]\n \"\n :key=\"`component-${partIndex}-${getPartState(part)}`\"\n :part />\n <slot\n v-else\n :name=\"part.type\"\n v-bind=\"{\n message,\n part,\n index,\n isLoading,\n }\">\n <div\n v-if=\"showAllMessageParts\"\n class=\"pk-chatbot-message__loading-info\">\n <div class=\"flex gap-8 items-center\">\n <VvIcon\n v-if=\"getPartIcon(part)\"\n :name=\"getPartIcon(part)!\"\n class=\"shrink-0\" />\n <code class=\"font-mono rounded text-12\">\n {{ toKebabCase(part.type) }}\n </code>\n </div>\n </div>\n </slot>\n </transition>\n <!-- Read-only comment: anchored right after the message\n text (same bubble as the reaction chip) so the\n feedback stays attached to the message body. -->\n <transition mode=\"out-in\">\n <PkChatbotMessageFeedback\n v-if=\"\n partIndex === lastTextPartIndex(message) &&\n readonlyFeedback &&\n getMessageFeedback(message.id)?.comment\n \"\n :feedback=\"getMessageFeedback(message.id)!\" />\n </transition>\n </template>\n <transition name=\"fade-in\" mode=\"out-in\">\n <div\n v-if=\"\n isLoading &&\n isLastMessage(index) &&\n !isLastTextPart(message) &&\n isAssistant(message) &&\n !showStepsTimeline(message)\n \"\n :key=\"`loading-info-${message.id}`\"\n class=\"pk-chatbot-message__loading-info\">\n <div class=\"flex gap-8 items-center\">\n <VvIcon\n name=\"line-md:loading-loop\"\n class=\"shrink-0\" />\n <transition mode=\"out-in\">\n <VvIcon\n v-if=\"activeMessageLastPartIcon\"\n :key=\"activeMessageLastPartIcon\"\n class=\"shrink-0\"\n :name=\"activeMessageLastPartIcon\" />\n </transition>\n <transition mode=\"out-in\">\n <span\n v-if=\"activeMessageLastPartLabel\"\n :key=\"activeMessageLastPartLabel\"\n class=\"text-12\">\n {{ activeMessageLastPartLabel }}\n </span>\n </transition>\n </div>\n <transition name=\"fade-in\" mode=\"out-in\">\n <PkStreamingMarkdownAutoscroll\n v-if=\"\n activeMessageLastPart &&\n 'text' in activeMessageLastPart &&\n activeMessageLastPart.text.trim()\n \"\n :markdown=\"activeMessageLastPart.text\"\n :is-dark\n inner-class=\"wysiwyg\"\n class=\"border border-surface-4 rounded p-4 mt-8 bg-surface-1 max-h-64 text-12 w-full\" />\n </transition>\n </div>\n </transition>\n <transition mode=\"out-in\">\n <div\n v-if=\"showMessageFooter(message, index)\"\n class=\"pk-chatbot-message__footer\">\n <VvButtonGroup\n modifiers=\"compact\"\n class=\"mr-auto shrink-0\">\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isMessageRegenerateButtonVisible(\n index,\n )\n \"\n icon=\"ri:reset-right-line\"\n modifiers=\"action-quiet-small\"\n :title=\"$t('action.regenerate')\"\n :aria-label=\"$t('action.regenerate')\"\n @click.stop=\"$emit('regenerate')\" />\n </transition>\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isActionButtonVisible(\n 'show-info',\n index,\n )\n \"\n icon=\"ri:information-line\"\n :title=\"$t('action.getMoreInfo')\"\n :aria-label=\"$t('action.getMoreInfo')\"\n modifiers=\"action-quiet-small\"\n @click.stop=\"\n $emit('show-info', message)\n \" />\n </transition>\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isActionButtonVisible(\n 'revise',\n index,\n )\n \"\n :icon=\"\n isRevised(message.id)\n ? 'ri:file-edit-fill'\n : 'ri:file-edit-line'\n \"\n :title=\"\n isRevised(message.id)\n ? $t('action.editRevise')\n : $t('action.createRevise')\n \"\n :aria-label=\"\n isRevised(message.id)\n ? $t('action.editRevise')\n : $t('action.createRevise')\n \"\n modifiers=\"action-quiet-small\"\n :class=\"{\n 'text-brand': isRevised(message.id),\n }\"\n @click.stop=\"\n $emit('revise', message)\n \" />\n </transition>\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isFeedbackButtonVisible(\n 'upvote',\n index,\n )\n \"\n :icon=\"\n getMessageFeedback(message.id)\n ?.vote === 'upvote'\n ? 'ri:thumb-up-fill'\n : 'ri:thumb-up-line'\n \"\n :title=\"$t('action.upvote')\"\n :aria-label=\"$t('action.upvote')\"\n modifiers=\"action-quiet-small\"\n @click.stop=\"\n $emit('upvote', message)\n \" />\n </transition>\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isFeedbackButtonVisible(\n 'downvote',\n index,\n )\n \"\n :icon=\"\n getMessageFeedback(message.id)\n ?.vote === 'downvote'\n ? 'ri:thumb-down-fill'\n : 'ri:thumb-down-line'\n \"\n :title=\"$t('action.downvote')\"\n :aria-label=\"$t('action.downvote')\"\n modifiers=\"action-quiet-small\"\n @click.stop=\"\n $emit('downvote', message)\n \" />\n </transition>\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isFeedbackButtonVisible(\n 'feedback',\n index,\n )\n \"\n :icon=\"\n getMessageFeedback(message.id)\n ?.comment\n ? 'ri:feedback-fill'\n : 'ri:feedback-line'\n \"\n :title=\"$t('action.feedback')\"\n :aria-label=\"$t('action.feedback')\"\n modifiers=\"action-quiet-small\"\n @click.stop=\"\n $emit('feedback', message)\n \" />\n </transition>\n </VvButtonGroup>\n <div\n v-if=\"\n !showMessageDateTime &&\n message.metadata?.createdAt &&\n message.metadata?.completedAt\n \"\n class=\"pk-chatbot-message__hover-meta\">\n <time\n :datetime=\"\n new Date(\n message.metadata.createdAt,\n ).toISOString()\n \"\n :title=\"\n $d(\n new Date(\n message.metadata.createdAt,\n ),\n 'date-time',\n )\n \"\n class=\"flex items-center gap-4 min-w-0\">\n <VvIcon\n name=\"ri:time-line\"\n class=\"shrink-0\" />\n <span class=\"truncate\">{{\n formatMessageTime(\n message.metadata.createdAt,\n )\n }}</span>\n </time>\n <span\n v-if=\"getMessageDuration(message)\"\n class=\"flex items-center gap-4 min-w-0\">\n <VvIcon\n name=\"ri:hourglass-line\"\n class=\"shrink-0\" />\n <span class=\"truncate\">{{\n getMessageDuration(message)\n }}</span>\n </span>\n </div>\n <span\n v-if=\"\n showMessageTokensCount &&\n message.metadata?.totalTokens\n \"\n class=\"flex items-center gap-4 text-12 text-word-3\">\n <VvIcon\n name=\"ri:ai-generate-2-line\"\n class=\"shrink-0\" />\n <span class=\"truncate\"\n >{{\n $n(\n message.metadata.totalTokens,\n 'integer',\n )\n }}\n {{ $t('label.tokens') }}\n </span>\n </span>\n <time\n v-if=\"\n showMessageDateTime &&\n message.metadata?.createdAt\n \"\n :datetime=\"\n new Date(\n message.metadata?.createdAt,\n ).toISOString()\n \"\n class=\"flex items-center gap-4 text-12 text-word-3\">\n <VvIcon name=\"ri:time-line\" class=\"shrink-0\" />\n <span class=\"truncate\">{{\n $d(\n new Date(message.metadata?.createdAt),\n 'date-time',\n )\n }}</span>\n </time>\n <div\n v-if=\"\n showMessageDateTime &&\n message?.metadata?.completedAt &&\n message?.metadata?.createdAt\n \"\n class=\"flex items-center gap-4 text-12 text-word-3\">\n <VvIcon\n name=\"ri:hourglass-line\"\n class=\"shrink-0\" />\n <PkRelativeTime\n class=\"truncate\"\n :date=\"message.metadata?.createdAt\"\n :end-date=\"message.metadata?.completedAt\" />\n </div>\n </div>\n </transition>\n <transition mode=\"out-in\">\n <PkChatbotFeedbackForm\n v-if=\"message.id === feedbackMessageId\"\n ref=\"feedbackMessageEl\"\n :loading=\"feedbackLoading\"\n :submitted=\"feedbackSubmitted\"\n :error=\"feedbackError\"\n @submit=\"$emit('feedback-submit', $event)\"\n @close=\"$emit('feedback-close')\" />\n </transition>\n </div>\n </template>\n <transition appear name=\"pk-chatbot-part\">\n <PkChatbotError\n v-if=\"isError\"\n :error\n @retry=\"$emit('auto-retry')\"\n @reset=\"$emit('reset-chat')\" />\n </transition>\n <div\n v-if=\"activeMessage?.role === 'user' || isError\"\n class=\"pk-chatbot-messages__spacer\"\n :style=\"{ minHeight: `${height}px` }\"></div>\n </div>\n <Transition name=\"pk-chatbot-messages-fab\">\n <button\n v-if=\"showScrollToBottom && (userScrolledUp || !isAtBottom)\"\n type=\"button\"\n class=\"pk-chatbot-messages__scroll-to-bottom\"\n :title=\"$t('action.scrollToBottom')\"\n :aria-label=\"$t('action.scrollToBottom')\"\n @click=\"onScrollToBottomClick\">\n <VvIcon name=\"ri:arrow-down-line\" />\n </button>\n </Transition>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-messages {\n overflow-y: auto;\n flex: 1;\n min-width: 0;\n font-size: var(--text-14);\n\n scrollbar-width: thin;\n scrollbar-color: var(--color-word-5) var(--color-surface-1);\n\n &__wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-16);\n min-width: 0;\n }\n\n // Conversation outline rail: sticky shell pinned to the vertical\n // middle of the scroll area, at the right edge. Zero-sized and\n // first in flow so neither it nor its absolutely-positioned pieces\n // inflate scrollHeight (desktop only, like ChatGPT)\n &__outline {\n display: none;\n position: sticky;\n top: 50%;\n z-index: 2;\n flex: none;\n align-self: flex-end;\n height: 0;\n\n @include media-breakpoint-up('md', $breakpoints) {\n display: block;\n }\n\n .pk-chatbot-outline {\n position: absolute;\n top: 0;\n right: 0;\n transform: translateY(-50%);\n }\n }\n\n // Placeholder while the answer has not started yet: settles like\n // the message spacer when released\n &__spacer {\n transition: min-height 300ms var(--ease-out);\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n\n // Floating \"back to bottom\" button, sticky inside the scroll area\n &__scroll-to-bottom {\n position: sticky;\n bottom: var(--spacing-8);\n z-index: 2;\n display: flex;\n align-items: center;\n justify-content: center;\n // The scroll container is a flex column: without this the\n // overflowing content shrinks the button, ignoring its height\n flex: none;\n width: var(--spacing-36);\n height: var(--spacing-36);\n // Net-zero layout contribution: mounting/unmounting the button\n // must not change scrollHeight, or reaching the bottom (which\n // hides it) would clamp scrollTop and cause a visible jump\n margin-block-start: calc(-1 * var(--spacing-36));\n margin-inline: auto;\n border: 1px solid var(--color-surface-3);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface-1);\n box-shadow: var(--shadow-md);\n color: var(--color-word-1);\n cursor: pointer;\n transition: var(--transition-colors);\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n .pk-chatbot-messages-fab-enter-active,\n .pk-chatbot-messages-fab-leave-active {\n transition: opacity 150ms var(--ease-out);\n }\n\n .pk-chatbot-messages-fab-enter-from,\n .pk-chatbot-messages-fab-leave-to {\n opacity: 0;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .pk-chatbot-messages-fab-enter-active,\n .pk-chatbot-messages-fab-leave-active {\n transition: none;\n }\n }\n\n .pk-chatbot-divider {\n position: relative;\n border-bottom: 1px solid var(--color-surface-3);\n\n &__label {\n position: absolute;\n padding-inline: var(--spacing-8);\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background-color: var(--color-surface);\n color: var(--color-word-4);\n font-size: var(--text-12);\n }\n }\n\n .pk-chatbot-message {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-sm);\n min-width: 0;\n align-items: flex-start;\n overflow: hidden;\n max-width: 100%;\n // The anchor spacer (inline min-height on the last assistant\n // message) settles smoothly when released at the end of the stream\n transition: min-height 300ms var(--ease-out);\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n\n &__text {\n position: relative;\n color: var(--color-word-2);\n background-color: var(--color-surface);\n line-height: var(--leading-normal);\n border-width: var(--spacing-px);\n border-style: solid;\n border-color: var(--color-surface-3);\n padding: var(--spacing-sm);\n border-radius: var(--rounded-xl) var(--rounded-xl) var(--rounded-xl)\n 0;\n }\n\n // Read-only feedback: a circular vote chip that overlaps the bubble's\n // bottom-right corner (messaging-app style). White ring lifts it off\n // the bubble; solid accent makes the rating unmistakable.\n &__reaction {\n position: absolute;\n right: var(--spacing-8);\n bottom: 0;\n transform: translateY(50%);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--spacing-24);\n height: var(--spacing-24);\n border-radius: var(--rounded-full, 9999px);\n color: var(--color-white, #fff);\n font-size: var(--text-12);\n box-shadow: var(--shadow-md);\n\n &--positive {\n background-color: var(--color-success);\n }\n\n &--negative {\n background-color: var(--color-danger);\n }\n\n // Real emoji (e.g. WhatsApp): the glyph carries its own colour, so\n // the chip stays a neutral surface pill.\n &--neutral {\n background-color: var(--color-surface-1);\n }\n }\n\n &__reaction-emoji {\n // Smaller than the chip so the glyph doesn't touch the ring.\n font-size: var(--text-12);\n line-height: 1;\n }\n\n &__loading-info {\n line-height: var(--leading-normal);\n border-width: var(--spacing-px);\n border-style: solid;\n border-color: var(--color-surface-3);\n padding: var(--spacing-sm);\n border-radius: var(--rounded-xl) var(--rounded-xl) var(--rounded-xl)\n 0;\n font-size: var(--text-12);\n color: var(--color-word-3);\n }\n\n &__footer {\n width: 100%;\n display: flex;\n gap: var(--spacing-8);\n align-items: center;\n }\n\n &__hover-meta {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: var(--spacing-8);\n font-size: var(--text-12);\n color: var(--color-word-3);\n white-space: nowrap;\n font-variant-numeric: tabular-nums;\n opacity: 0;\n transition: opacity 0.2s ease;\n }\n\n &:hover &__hover-meta,\n &:focus-within &__hover-meta {\n opacity: 1;\n }\n\n // Hover doesn't exist on touch devices: keep the metadata visible\n @media (hover: none) {\n &__hover-meta {\n opacity: 1;\n }\n }\n\n &--user {\n align-items: flex-end;\n\n .pk-chatbot-message__text {\n background-color: var(\n --chatbot-main-color,\n var(--color-surface-1)\n );\n border-color: var(--chatbot-main-color, var(--color-surface-4));\n color: var(--chatbot-contrast-color, var(--color-word-1));\n border-radius: var(--rounded-xl) var(--rounded-xl) 0\n var(--rounded-xl);\n }\n }\n\n &--assistant {\n width: 100%;\n gap: var(--spacing-16);\n\n .pk-chatbot-message__text {\n width: 100%;\n }\n }\n\n &--system {\n align-items: flex-end;\n\n .pk-chatbot-message__text {\n display: flex;\n gap: var(--spacing-8);\n font-size: var(--text-12);\n padding: var(--spacing-8) var(--spacing-sm);\n align-items: center;\n border-color: var(--chatbot-main-color, var(--color-surface-4));\n color: var(--chatbot-main-color, var(--color-word-1));\n border-radius: var(--rounded-xl) var(--rounded-xl) 0\n var(--rounded-xl);\n }\n }\n }\n\n .pk-chatbot-part-enter-active {\n transition:\n opacity 0.25s ease,\n transform 0.25s ease;\n }\n\n .pk-chatbot-part-enter-from {\n opacity: 0;\n transform: translateY(8px);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .pk-chatbot-part-enter-active {\n transition: none;\n }\n\n .pk-chatbot-part-enter-from {\n transform: none;\n }\n }\n</style>\n","<script lang=\"ts\" setup>\n import type {\n ChatMessageActions,\n MessageFeedback,\n RevisedAnswer,\n UIChatMessage,\n } from 'models'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n import PkChatbotError from './PkChatbotError.vue'\n import PkChatbotFeedbackForm from './PkChatbotFeedbackForm.vue'\n import PkChatbotMessageFeedback from './PkChatbotMessageFeedback.vue'\n import PkChatbotFilePreview from './PkChatbotFilePreview.vue'\n import PkRelativeTime from '../PkRelativeTime.vue'\n import { useI18n } from 'vue-i18n'\n import {\n useTemplateRef,\n ref,\n watch,\n computed,\n nextTick,\n useSlots,\n } from 'vue'\n import { toolComponentMap } from './toolComponentMap'\n import PkChatbotSteps from './PkChatbotSteps.vue'\n import {\n resolveContrastColor,\n getPartState,\n isTextPart,\n isFilePart,\n isToolPart,\n isReasoningPart,\n isStreamingPart,\n getPartIcon,\n getToolPartLabel,\n mergeConsecutiveTextParts,\n formatDuration,\n } from './utils'\n import { getFilePartMedia, getToolPartName, toKebabCase } from 'utils'\n import PkStreamingMarkdownAutoscroll from './PkStreamingMarkdownAutoscroll.vue'\n import PkChatbotOutlineRail from './PkChatbotOutlineRail.vue'\n import { useChatScroll } from './useChatScroll'\n\n const props = defineProps<{\n status?: 'submitted' | 'streaming' | 'ready' | 'error'\n messages?: UIChatMessage[]\n error?: Error\n actions?: ChatMessageActions[]\n mainColor?: string\n textColor?: 'auto' | 'white' | 'black'\n revisedAnswers?: RevisedAnswer[]\n messageFeedbacks?: MessageFeedback[]\n /**\n * Render the vote/feedback action buttons as read-only state indicators\n * (e.g. admin chat-logs view): icons reflect the user's feedback but the\n * buttons are not interactive.\n */\n readonlyFeedback?: boolean\n disableHeightAdjustment?: boolean\n showMessageDateTime?: boolean\n showMessageTokensCount?: boolean\n showAllMessageParts?: boolean\n showExtendedSteps?: boolean\n isDark?: boolean\n /** Show a floating \"back to bottom\" button when the user scrolls up (fullscreen layout) */\n showScrollToBottom?: boolean\n // TODO: move feedback in a separate component to avoid passing these props\n feedbackMessageId?: string\n feedbackLoading?: boolean\n feedbackSubmitted?: boolean\n feedbackError?: string\n }>()\n\n const emit = defineEmits<{\n (e: 'show-info', message: UIChatMessage): void\n (e: 'regenerate'): void\n (e: 'revise', message: UIChatMessage): void\n (e: 'upvote', message: UIChatMessage): void\n (e: 'downvote', message: UIChatMessage): void\n (e: 'feedback', message: UIChatMessage): void\n (e: 'feedback-submit', comment: string): void\n (e: 'feedback-close'): void\n (e: 'scroll-up'): void\n (e: 'scroll-down'): void\n (e: 'auto-retry'): void\n (e: 'reset-chat'): void\n }>()\n\n const {\n t: $t,\n d: $d,\n n: $n,\n } = useI18n({\n useScope: 'global',\n })\n\n const slots = useSlots()\n\n const scrollEl = useTemplateRef<HTMLDivElement>('scrollEl')\n const wrapperEl = useTemplateRef<HTMLDivElement>('wrapperEl')\n const contrastColor = computed(() =>\n resolveContrastColor(props.textColor, props.mainColor),\n )\n\n // --- Scroll ---\n\n const messagesElRefs = useTemplateRef<HTMLDivElement[]>('messagesEl')\n\n const { handleScroll, scrollToBottom, userScrolledUp, isAtBottom } =\n useChatScroll({\n scrollEl,\n status: () => props.status,\n messagesLength: () => props.messages?.length,\n lastMessageRole: () =>\n props.messages?.[props.messages.length - 1]?.role,\n contentEl: () => wrapperEl.value,\n onScrollUp: () => emit('scroll-up'),\n onScrollDown: () => emit('scroll-down'),\n })\n\n const onScrollToBottomClick = () => {\n // scrollToBottom() is a no-op while userScrolledUp is set\n userScrolledUp.value = false\n scrollToBottom()\n }\n\n // --- Conversation outline (fullscreen, desktop) ---\n\n const outlineItems = computed(() =>\n (props.messages ?? [])\n .filter((message) => message.role === 'user')\n .map((message) => ({\n id: message.id,\n preview:\n message.parts.find(isTextPart)?.text.trim().slice(0, 120) ||\n '…',\n })),\n )\n const activeOutlineId = ref<string>()\n\n // The rail entries map 1:1 onto the rendered user messages\n const getUserMessageEls = () =>\n scrollEl.value?.querySelectorAll('.pk-chatbot-message--user') ?? []\n\n // Scroll-spy: active is the last user message above the middle of the\n // viewport (the exchange currently being read)\n const updateActiveOutline = () => {\n const scroller = scrollEl.value\n if (!scroller || !props.showScrollToBottom) {\n return\n }\n const line =\n scroller.getBoundingClientRect().top + scroller.clientHeight / 2\n let active = outlineItems.value[0]?.id\n getUserMessageEls().forEach((el, index) => {\n if (el.getBoundingClientRect().top <= line) {\n active = outlineItems.value[index]?.id\n }\n })\n activeOutlineId.value = active\n }\n\n watch(outlineItems, async () => {\n await nextTick()\n updateActiveOutline()\n })\n\n const onScroll = () => {\n handleScroll()\n updateActiveOutline()\n }\n\n const scrollToUserMessage = (id: string) => {\n const scroller = scrollEl.value\n const index = outlineItems.value.findIndex((item) => item.id === id)\n const messageEl = getUserMessageEls()[index]\n if (!scroller || !messageEl) {\n return\n }\n // Intentional jump away from the live tail: the wheel/touch intent\n // detection cannot see programmatic scrolls, pause the auto-follow\n // explicitly (reaching the bottom again re-enables it)\n userScrolledUp.value = true\n // Same resting line as a freshly sent message: top of the content area\n scroller.scrollTo({\n top:\n scroller.scrollTop +\n messageEl.getBoundingClientRect().top -\n scroller.getBoundingClientRect().top -\n Number.parseFloat(getComputedStyle(scroller).paddingTop),\n behavior: 'smooth',\n })\n }\n\n // --- Height adjustment ---\n\n const height = ref<number>(0)\n\n // Recalculate when a message is added or the conversation changes (the\n // store recreates `messages` on every streamed token, so watching the\n // array reference would re-run this on each character). The height only\n // depends on the last user message height, which is stable while\n // streaming.\n watch(\n () => [\n props.messages?.length,\n props.messages?.[props.messages.length - 1]?.id,\n ],\n async () => {\n const messages = props.messages\n if (!messages?.length || props.disableHeightAdjustment) {\n return\n }\n // The spacer only serves the in-flight exchange, letting the\n // fresh user message anchor at the top while the answer streams.\n // On plain loads (e.g. opening an old conversation) it would\n // just leave a large void after the last message.\n const isExchangeInFlight =\n messages[messages.length - 1].role === 'user' ||\n props.status === 'submitted' ||\n props.status === 'streaming'\n if (!isExchangeInFlight) {\n return\n }\n await nextTick()\n if (!scrollEl.value) {\n return\n }\n // Spacer sized so the freshly sent user message rests at the top\n // of the content area once scrolled to bottom: subtract both\n // paddings (clientHeight includes them) so any extra top\n // clearance (e.g. the fullscreen overlay header) is respected,\n // and the wrapper row gap separating the message from the spacer\n const { paddingTop, paddingBottom } = getComputedStyle(\n scrollEl.value,\n )\n const scrollElHeight =\n scrollEl.value.clientHeight -\n Number.parseFloat(paddingTop) -\n Number.parseFloat(paddingBottom)\n const wrapperGap = wrapperEl.value\n ? Number.parseFloat(getComputedStyle(wrapperEl.value).rowGap) ||\n 0\n : 0\n const lastUserMessageIndex = messages\n .map((message) => message.role)\n .lastIndexOf('user')\n const lastUserMessageHeight =\n messagesElRefs.value?.[lastUserMessageIndex]?.clientHeight ?? 0\n const newHeight =\n scrollElHeight - lastUserMessageHeight - wrapperGap\n if (newHeight > 0) {\n height.value = newHeight\n }\n },\n )\n\n // Release the spacer once the exchange is over: it would otherwise\n // leave a large empty area after the last message. The min-height\n // transition settles the layout smoothly (the browser clamps the\n // scroll progressively when the viewport sat inside the removed area).\n watch(\n () => props.status,\n (status) => {\n if (status === 'ready' || status === 'error') {\n height.value = 0\n }\n },\n )\n\n const isRevised = (messageId: string) => {\n return props.revisedAnswers?.some((r) => r.messageId === messageId)\n }\n\n const getMessageFeedback = (messageId: string) =>\n props.messageFeedbacks?.find((f) => f.messageId === messageId)\n\n // Interactive vote/feedback buttons. In read-only mode (admin chat-logs)\n // the vote is shown on the message itself (coloured bubble border + an\n // \"active\" thumb in the footer) and the comment, if any, below it.\n const isFeedbackButtonVisible = (\n kind: 'upvote' | 'downvote' | 'feedback',\n index: number,\n ) => !props.readonlyFeedback && isActionButtonVisible(kind, index)\n\n // Read-only feedback for a message (drives the reaction chip anchored to\n // the bubble). undefined when not in read-only mode.\n const readonlyFeedbackOf = (messageId: string) =>\n props.readonlyFeedback ? getMessageFeedback(messageId) : undefined\n const feedbackVote = (messageId: string) =>\n readonlyFeedbackOf(messageId)?.vote\n // The real reaction glyph, shown ONLY for unmapped reactions (no vote):\n // 👍/❤️/👎 keep the classic thumb icon, while e.g. WhatsApp \"😍\" shows the\n // actual emoji. Platforms that send a type word (Teams \"like\") have a vote\n // and therefore use the icon too.\n const feedbackEmoji = (messageId: string) => {\n const feedback = readonlyFeedbackOf(messageId)\n if (feedback?.vote) {\n return undefined\n }\n const raw = feedback?.metadata?.reactionEmoji\n return typeof raw === 'string' && /\\p{Extended_Pictographic}/u.test(raw)\n ? raw\n : undefined\n }\n const hasReaction = (messageId: string) =>\n Boolean(feedbackVote(messageId) || feedbackEmoji(messageId))\n const reactionToneClass = (messageId: string) => {\n // A real emoji carries its own colour → neutral pill. Only the\n // thumb-icon fallback uses the sentiment colour.\n if (feedbackEmoji(messageId)) {\n return 'pk-chatbot-message__reaction--neutral'\n }\n const vote = feedbackVote(messageId)\n if (vote === 'upvote') {\n return 'pk-chatbot-message__reaction--positive'\n }\n if (vote === 'downvote') {\n return 'pk-chatbot-message__reaction--negative'\n }\n return 'pk-chatbot-message__reaction--neutral'\n }\n const reactionTitle = (messageId: string) => {\n const vote = feedbackVote(messageId)\n if (vote === 'upvote') {\n return $t('hint.userFeedbackPositive')\n }\n if (vote === 'downvote') {\n return $t('hint.userFeedbackNegative')\n }\n return $t('hint.userFeedbackReaction')\n }\n\n // Index of the last text part: the reaction chip anchors to that bubble so\n // it renders once even when the message has several text parts.\n const lastTextPartIndex = (message: UIChatMessage) => {\n const parts = getMergedParts(message)\n for (let index = parts.length - 1; index >= 0; index -= 1) {\n if (isTextPart(parts[index])) {\n return index\n }\n }\n return -1\n }\n\n const activeMessage = computed(() => {\n return props.messages?.[props.messages.length - 1]\n })\n const activeMessageLastPart = computed(() => {\n const active = activeMessage.value\n if (!active) {\n return null\n }\n return active.parts[active.parts.length - 1]\n })\n\n const mergedPartsMap = computed(() => {\n const map = new Map<string, UIChatMessage['parts']>()\n for (const message of props.messages ?? []) {\n if (message.role === 'assistant') {\n map.set(message.id, mergeConsecutiveTextParts(message.parts))\n }\n }\n return map\n })\n const getMergedParts = (message: UIChatMessage) => {\n return mergedPartsMap.value.get(message.id) ?? message.parts\n }\n const isLoading = computed(() => {\n return props.status === 'submitted' || props.status === 'streaming'\n })\n const isError = computed(() => props.status === 'error')\n\n const activeMessageLastPartLabel = computed(() => {\n const part = activeMessageLastPart.value\n return getToolPartLabel($t, part)\n })\n const activeMessageLastPartIcon = computed(() => {\n const part = activeMessageLastPart.value\n return getPartIcon(part)\n })\n\n const getMessageIndexById = (messageId: string) => {\n return props.messages?.findIndex((m) => m.id === messageId)\n }\n const isLastMessage = (index?: number) => {\n if (index === undefined || !props.messages) {\n return false\n }\n return index === props.messages.length - 1\n }\n const isMessageRegenerateButtonVisible = (index: number) => {\n return (\n isLastMessage(index) &&\n props.status === 'ready' &&\n !!props.actions?.includes('regenerate')\n )\n }\n // On the last message the actions appear only once the answer is ready;\n // on previous messages they are always available.\n const isActionButtonVisible = (\n action: ChatMessageActions,\n index: number,\n ) => {\n return (\n (!isLastMessage(index) || props.status === 'ready') &&\n !!props.actions?.includes(action)\n )\n }\n const isLastTextPart = (message: UIChatMessage) => {\n const lastPart = message.parts[message.parts.length - 1]\n return isTextPart(lastPart)\n }\n const messageHasSteps = (message: UIChatMessage) =>\n message.parts.some((part) => isReasoningPart(part) || isToolPart(part))\n // The extended steps timeline replaces the compact activity indicator as\n // soon as the message has at least one step to show.\n const showStepsTimeline = (message: UIChatMessage) =>\n Boolean(props.showExtendedSteps) &&\n isAssistant(message) &&\n messageHasSteps(message)\n const isAssistant = (message: UIChatMessage) => message.role === 'assistant'\n const isUser = (message: UIChatMessage) => message.role === 'user'\n const getPreviousAssistantMessage = (index: number) => {\n return props.messages\n ?.slice(0, index)\n .reverse()\n .find((message) => isAssistant(message))\n }\n const getNextAssistantMessage = (index: number) => {\n return props.messages\n ?.slice(index + 1)\n .find((message) => isAssistant(message))\n }\n // Returns the date to show in the day divider before the message at\n // `index`, or undefined when the surrounding assistant messages belong\n // to the same (local) day.\n const getDividerDate = (index: number) => {\n const previousCreatedAt =\n getPreviousAssistantMessage(index)?.metadata?.createdAt\n const nextCreatedAt =\n getNextAssistantMessage(index)?.metadata?.createdAt\n if (!previousCreatedAt || !nextCreatedAt) {\n return undefined\n }\n const previousDate = new Date(previousCreatedAt)\n const nextDate = new Date(nextCreatedAt)\n return previousDate.toDateString() !== nextDate.toDateString()\n ? nextDate\n : undefined\n }\n const showMessageFooter = (message: UIChatMessage, index: number) => {\n if (index === 0 || message.role !== 'assistant') {\n return false\n }\n return Boolean(\n props.actions?.length ||\n (props.showMessageDateTime &&\n message.metadata?.createdAt !== undefined) ||\n message.metadata?.completedAt !== undefined ||\n (props.showMessageTokensCount &&\n message.metadata?.totalTokens !== undefined),\n )\n }\n // Hover-only metadata (time + duration), shown when the fixed variant\n // (showMessageDateTime) is off.\n const formatMessageTime = (createdAt: number) => {\n const date = new Date(createdAt)\n const isToday = date.toDateString() === new Date().toDateString()\n return $d(date, isToday ? 'time' : 'date-time')\n }\n const getMessageDuration = (message: UIChatMessage) => {\n const { createdAt, completedAt } = message.metadata ?? {}\n if (!createdAt || !completedAt) {\n return undefined\n }\n return formatDuration($n, completedAt - createdAt)\n }\n // feedback message auto scroll\n const feedbackMessageEl =\n useTemplateRef<InstanceType<typeof PkChatbotFeedbackForm>[]>(\n 'feedbackMessageEl',\n )\n watch(\n () => props.feedbackMessageId,\n async () => {\n await nextTick()\n if (!props.feedbackMessageId) {\n return\n }\n const el = feedbackMessageEl.value?.[0]?.$el\n if (!el) {\n return\n }\n if (isLastMessage(getMessageIndexById(props.feedbackMessageId))) {\n scrollToBottom()\n return\n }\n el.scrollIntoView({ behavior: 'smooth', block: 'center' })\n },\n )\n</script>\n\n<template>\n <div\n ref=\"scrollEl\"\n class=\"pk-chatbot-messages\"\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n :aria-busy=\"isLoading\"\n :style=\"{\n '--chatbot-main-color': mainColor,\n '--chatbot-contrast-color': contrastColor,\n }\"\n @scroll=\"onScroll\">\n <!-- First in flow (content top): its absolutely-positioned children\n must never inflate the scrollable overflow past the real content -->\n <div\n v-if=\"showScrollToBottom && outlineItems.length > 1\"\n class=\"pk-chatbot-messages__outline\">\n <PkChatbotOutlineRail\n :items=\"outlineItems\"\n :active-id=\"activeOutlineId\"\n @select=\"scrollToUserMessage\" />\n </div>\n <div ref=\"wrapperEl\" class=\"pk-chatbot-messages__wrapper\">\n <template v-for=\"(message, index) in messages\" :key=\"message.id\">\n <div\n v-if=\"isUser(message) && getDividerDate(index)\"\n class=\"pk-chatbot-divider\">\n <span class=\"pk-chatbot-divider__label\">\n {{ $d(getDividerDate(index)!, 'short') }}\n </span>\n </div>\n <div\n v-if=\"\n message.parts.length ||\n (isLastMessage(index) &&\n isLoading &&\n isAssistant(message))\n \"\n ref=\"messagesEl\"\n class=\"pk-chatbot-message\"\n :class=\"[\n `pk-chatbot-message--${message.role}`,\n {\n 'pk-chatbot-message--loading':\n isLoading &&\n isLastMessage(index) &&\n isAssistant(message),\n },\n ]\"\n :style=\"{\n minHeight:\n isLastMessage(index) &&\n isAssistant(message) &&\n !isError\n ? `${height}px`\n : undefined,\n }\">\n <transition appear name=\"pk-chatbot-part\">\n <PkChatbotSteps\n v-if=\"showStepsTimeline(message)\"\n :message\n :is-streaming=\"isLoading && isLastMessage(index)\"\n :is-dark />\n </transition>\n <template\n v-for=\"(part, partIndex) in getMergedParts(message)\"\n :key=\"partIndex\">\n <transition\n v-if=\"isTextPart(part) && part.text.trim()\"\n appear\n name=\"pk-chatbot-part\">\n <div class=\"pk-chatbot-message__text\">\n <slot\n v-bind=\"{\n message,\n part,\n index,\n isLoading,\n }\"\n name=\"text\">\n <PkStreamingMarkdown\n v-if=\"isAssistant(message)\"\n class=\"wysiwyg\"\n :markdown=\"part.text\"\n :is-dark\n :loading=\"\n isLastMessage(index) &&\n status === 'streaming'\n \" />\n <template v-else>\n {{ part.text }}\n </template>\n </slot>\n <!-- Read-only reaction chip overlapping the\n bubble corner, like a messaging app. Hidden\n when a comment is shown: the callout below\n then carries the reaction glyph instead. -->\n <span\n v-if=\"\n hasReaction(message.id) &&\n !getMessageFeedback(message.id)\n ?.comment &&\n partIndex === lastTextPartIndex(message)\n \"\n class=\"pk-chatbot-message__reaction\"\n :class=\"reactionToneClass(message.id)\"\n :title=\"reactionTitle(message.id)\">\n <span\n v-if=\"feedbackEmoji(message.id)\"\n class=\"pk-chatbot-message__reaction-emoji\">\n {{ feedbackEmoji(message.id) }}\n </span>\n <VvIcon\n v-else\n :name=\"\n feedbackVote(message.id) ===\n 'downvote'\n ? 'ri:thumb-down-fill'\n : 'ri:thumb-up-fill'\n \" />\n </span>\n </div>\n </transition>\n <transition\n v-else-if=\"isFilePart(part)\"\n appear\n name=\"pk-chatbot-part\">\n <PkChatbotFilePreview\n :media-type=\"part.mediaType\"\n :url=\"part.url\"\n :filename=\"part.filename\"\n :size=\"getFilePartMedia(part)?.bytesCount\" />\n </transition>\n <transition\n v-else-if=\"\n isToolPart(part) &&\n !isStreamingPart(part) &&\n (toolComponentMap[getToolPartName(part)] ||\n slots[part.type] ||\n showAllMessageParts)\n \"\n appear\n name=\"pk-chatbot-part\">\n <component\n :is=\"toolComponentMap[getToolPartName(part)]\"\n v-if=\"\n !slots[part.type] &&\n toolComponentMap[getToolPartName(part)]\n \"\n :key=\"`component-${partIndex}-${getPartState(part)}`\"\n :part />\n <slot\n v-else\n :name=\"part.type\"\n v-bind=\"{\n message,\n part,\n index,\n isLoading,\n }\">\n <div\n v-if=\"showAllMessageParts\"\n class=\"pk-chatbot-message__loading-info\">\n <div class=\"flex gap-8 items-center\">\n <VvIcon\n v-if=\"getPartIcon(part)\"\n :name=\"getPartIcon(part)!\"\n class=\"shrink-0\" />\n <code class=\"font-mono rounded text-12\">\n {{ toKebabCase(part.type) }}\n </code>\n </div>\n </div>\n </slot>\n </transition>\n <!-- Read-only comment: anchored right after the message\n text (same bubble as the reaction chip) so the\n feedback stays attached to the message body. -->\n <transition mode=\"out-in\">\n <PkChatbotMessageFeedback\n v-if=\"\n partIndex === lastTextPartIndex(message) &&\n readonlyFeedback &&\n getMessageFeedback(message.id)?.comment\n \"\n :feedback=\"getMessageFeedback(message.id)!\" />\n </transition>\n </template>\n <transition name=\"fade-in\" mode=\"out-in\">\n <div\n v-if=\"\n isLoading &&\n isLastMessage(index) &&\n !isLastTextPart(message) &&\n isAssistant(message) &&\n !showStepsTimeline(message)\n \"\n :key=\"`loading-info-${message.id}`\"\n class=\"pk-chatbot-message__loading-info\">\n <div class=\"flex gap-8 items-center\">\n <VvIcon\n name=\"line-md:loading-loop\"\n class=\"shrink-0\" />\n <transition mode=\"out-in\">\n <VvIcon\n v-if=\"activeMessageLastPartIcon\"\n :key=\"activeMessageLastPartIcon\"\n class=\"shrink-0\"\n :name=\"activeMessageLastPartIcon\" />\n </transition>\n <transition mode=\"out-in\">\n <span\n v-if=\"activeMessageLastPartLabel\"\n :key=\"activeMessageLastPartLabel\"\n class=\"text-12\">\n {{ activeMessageLastPartLabel }}\n </span>\n </transition>\n </div>\n <transition name=\"fade-in\" mode=\"out-in\">\n <PkStreamingMarkdownAutoscroll\n v-if=\"\n activeMessageLastPart &&\n 'text' in activeMessageLastPart &&\n activeMessageLastPart.text.trim()\n \"\n :markdown=\"activeMessageLastPart.text\"\n :is-dark\n inner-class=\"wysiwyg\"\n class=\"border border-surface-4 rounded p-4 mt-8 bg-surface-1 max-h-64 text-12 w-full\" />\n </transition>\n </div>\n </transition>\n <transition mode=\"out-in\">\n <div\n v-if=\"showMessageFooter(message, index)\"\n class=\"pk-chatbot-message__footer\">\n <VvButtonGroup\n modifiers=\"compact\"\n class=\"mr-auto shrink-0\">\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isMessageRegenerateButtonVisible(\n index,\n )\n \"\n icon=\"ri:reset-right-line\"\n modifiers=\"action-quiet-small\"\n :title=\"$t('action.regenerate')\"\n :aria-label=\"$t('action.regenerate')\"\n @click.stop=\"$emit('regenerate')\" />\n </transition>\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isActionButtonVisible(\n 'show-info',\n index,\n )\n \"\n icon=\"ri:information-line\"\n :title=\"$t('action.getMoreInfo')\"\n :aria-label=\"$t('action.getMoreInfo')\"\n modifiers=\"action-quiet-small\"\n @click.stop=\"\n $emit('show-info', message)\n \" />\n </transition>\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isActionButtonVisible(\n 'revise',\n index,\n )\n \"\n :icon=\"\n isRevised(message.id)\n ? 'ri:file-edit-fill'\n : 'ri:file-edit-line'\n \"\n :title=\"\n isRevised(message.id)\n ? $t('action.editRevise')\n : $t('action.createRevise')\n \"\n :aria-label=\"\n isRevised(message.id)\n ? $t('action.editRevise')\n : $t('action.createRevise')\n \"\n modifiers=\"action-quiet-small\"\n :class=\"{\n 'text-brand': isRevised(message.id),\n }\"\n @click.stop=\"\n $emit('revise', message)\n \" />\n </transition>\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isFeedbackButtonVisible(\n 'upvote',\n index,\n )\n \"\n :icon=\"\n getMessageFeedback(message.id)\n ?.vote === 'upvote'\n ? 'ri:thumb-up-fill'\n : 'ri:thumb-up-line'\n \"\n :title=\"$t('action.upvote')\"\n :aria-label=\"$t('action.upvote')\"\n modifiers=\"action-quiet-small\"\n @click.stop=\"\n $emit('upvote', message)\n \" />\n </transition>\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isFeedbackButtonVisible(\n 'downvote',\n index,\n )\n \"\n :icon=\"\n getMessageFeedback(message.id)\n ?.vote === 'downvote'\n ? 'ri:thumb-down-fill'\n : 'ri:thumb-down-line'\n \"\n :title=\"$t('action.downvote')\"\n :aria-label=\"$t('action.downvote')\"\n modifiers=\"action-quiet-small\"\n @click.stop=\"\n $emit('downvote', message)\n \" />\n </transition>\n <transition mode=\"out-in\">\n <VvButton\n v-if=\"\n isFeedbackButtonVisible(\n 'feedback',\n index,\n )\n \"\n :icon=\"\n getMessageFeedback(message.id)\n ?.comment\n ? 'ri:feedback-fill'\n : 'ri:feedback-line'\n \"\n :title=\"$t('action.feedback')\"\n :aria-label=\"$t('action.feedback')\"\n modifiers=\"action-quiet-small\"\n @click.stop=\"\n $emit('feedback', message)\n \" />\n </transition>\n </VvButtonGroup>\n <div\n v-if=\"\n !showMessageDateTime &&\n message.metadata?.createdAt &&\n message.metadata?.completedAt\n \"\n class=\"pk-chatbot-message__hover-meta\">\n <time\n :datetime=\"\n new Date(\n message.metadata.createdAt,\n ).toISOString()\n \"\n :title=\"\n $d(\n new Date(\n message.metadata.createdAt,\n ),\n 'date-time',\n )\n \"\n class=\"flex items-center gap-4 min-w-0\">\n <VvIcon\n name=\"ri:time-line\"\n class=\"shrink-0\" />\n <span class=\"truncate\">{{\n formatMessageTime(\n message.metadata.createdAt,\n )\n }}</span>\n </time>\n <span\n v-if=\"getMessageDuration(message)\"\n class=\"flex items-center gap-4 min-w-0\">\n <VvIcon\n name=\"ri:hourglass-line\"\n class=\"shrink-0\" />\n <span class=\"truncate\">{{\n getMessageDuration(message)\n }}</span>\n </span>\n </div>\n <span\n v-if=\"\n showMessageTokensCount &&\n message.metadata?.totalTokens\n \"\n class=\"flex items-center gap-4 text-12 text-word-3\">\n <VvIcon\n name=\"ri:ai-generate-2-line\"\n class=\"shrink-0\" />\n <span class=\"truncate\"\n >{{\n $n(\n message.metadata.totalTokens,\n 'integer',\n )\n }}\n {{ $t('label.tokens') }}\n </span>\n </span>\n <time\n v-if=\"\n showMessageDateTime &&\n message.metadata?.createdAt\n \"\n :datetime=\"\n new Date(\n message.metadata?.createdAt,\n ).toISOString()\n \"\n class=\"flex items-center gap-4 text-12 text-word-3\">\n <VvIcon name=\"ri:time-line\" class=\"shrink-0\" />\n <span class=\"truncate\">{{\n $d(\n new Date(message.metadata?.createdAt),\n 'date-time',\n )\n }}</span>\n </time>\n <div\n v-if=\"\n showMessageDateTime &&\n message?.metadata?.completedAt &&\n message?.metadata?.createdAt\n \"\n class=\"flex items-center gap-4 text-12 text-word-3\">\n <VvIcon\n name=\"ri:hourglass-line\"\n class=\"shrink-0\" />\n <PkRelativeTime\n class=\"truncate\"\n :date=\"message.metadata?.createdAt\"\n :end-date=\"message.metadata?.completedAt\" />\n </div>\n </div>\n </transition>\n <transition mode=\"out-in\">\n <PkChatbotFeedbackForm\n v-if=\"message.id === feedbackMessageId\"\n ref=\"feedbackMessageEl\"\n :loading=\"feedbackLoading\"\n :submitted=\"feedbackSubmitted\"\n :error=\"feedbackError\"\n @submit=\"$emit('feedback-submit', $event)\"\n @close=\"$emit('feedback-close')\" />\n </transition>\n </div>\n </template>\n <transition appear name=\"pk-chatbot-part\">\n <PkChatbotError\n v-if=\"isError\"\n :error\n @retry=\"$emit('auto-retry')\"\n @reset=\"$emit('reset-chat')\" />\n </transition>\n <div\n v-if=\"activeMessage?.role === 'user' || isError\"\n class=\"pk-chatbot-messages__spacer\"\n :style=\"{ minHeight: `${height}px` }\"></div>\n </div>\n <Transition name=\"pk-chatbot-messages-fab\">\n <button\n v-if=\"showScrollToBottom && (userScrolledUp || !isAtBottom)\"\n type=\"button\"\n class=\"pk-chatbot-messages__scroll-to-bottom\"\n :title=\"$t('action.scrollToBottom')\"\n :aria-label=\"$t('action.scrollToBottom')\"\n @click=\"onScrollToBottomClick\">\n <VvIcon name=\"ri:arrow-down-line\" />\n </button>\n </Transition>\n </div>\n</template>\n\n<style lang=\"scss\">\n .pk-chatbot-messages {\n overflow-y: auto;\n flex: 1;\n min-width: 0;\n font-size: var(--text-14);\n\n scrollbar-width: thin;\n scrollbar-color: var(--color-word-5) var(--color-surface-1);\n\n &__wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-16);\n min-width: 0;\n }\n\n // Conversation outline rail: sticky shell pinned to the vertical\n // middle of the scroll area, at the right edge. Zero-sized and\n // first in flow so neither it nor its absolutely-positioned pieces\n // inflate scrollHeight (desktop only, like ChatGPT)\n &__outline {\n display: none;\n position: sticky;\n top: 50%;\n z-index: 2;\n flex: none;\n align-self: flex-end;\n height: 0;\n\n @include media-breakpoint-up('md', $breakpoints) {\n display: block;\n }\n\n .pk-chatbot-outline {\n position: absolute;\n top: 0;\n right: 0;\n transform: translateY(-50%);\n }\n }\n\n // Placeholder while the answer has not started yet: settles like\n // the message spacer when released\n &__spacer {\n transition: min-height 300ms var(--ease-out);\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n\n // Floating \"back to bottom\" button, sticky inside the scroll area\n &__scroll-to-bottom {\n position: sticky;\n bottom: var(--spacing-8);\n z-index: 2;\n display: flex;\n align-items: center;\n justify-content: center;\n // The scroll container is a flex column: without this the\n // overflowing content shrinks the button, ignoring its height\n flex: none;\n width: var(--spacing-36);\n height: var(--spacing-36);\n // Net-zero layout contribution: mounting/unmounting the button\n // must not change scrollHeight, or reaching the bottom (which\n // hides it) would clamp scrollTop and cause a visible jump\n margin-block-start: calc(-1 * var(--spacing-36));\n margin-inline: auto;\n border: 1px solid var(--color-surface-3);\n border-radius: var(--rounded-full);\n background-color: var(--color-surface-1);\n box-shadow: var(--shadow-md);\n color: var(--color-word-1);\n cursor: pointer;\n transition: var(--transition-colors);\n\n &:hover {\n background-color: var(--color-surface-2);\n }\n }\n }\n\n .pk-chatbot-messages-fab-enter-active,\n .pk-chatbot-messages-fab-leave-active {\n transition: opacity 150ms var(--ease-out);\n }\n\n .pk-chatbot-messages-fab-enter-from,\n .pk-chatbot-messages-fab-leave-to {\n opacity: 0;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .pk-chatbot-messages-fab-enter-active,\n .pk-chatbot-messages-fab-leave-active {\n transition: none;\n }\n }\n\n .pk-chatbot-divider {\n position: relative;\n border-bottom: 1px solid var(--color-surface-3);\n\n &__label {\n position: absolute;\n padding-inline: var(--spacing-8);\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background-color: var(--color-surface);\n color: var(--color-word-4);\n font-size: var(--text-12);\n }\n }\n\n .pk-chatbot-message {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-sm);\n min-width: 0;\n align-items: flex-start;\n overflow: hidden;\n max-width: 100%;\n // The anchor spacer (inline min-height on the last assistant\n // message) settles smoothly when released at the end of the stream\n transition: min-height 300ms var(--ease-out);\n\n @media (prefers-reduced-motion: reduce) {\n transition: none;\n }\n\n &__text {\n position: relative;\n color: var(--color-word-2);\n background-color: var(--color-surface);\n line-height: var(--leading-normal);\n border-width: var(--spacing-px);\n border-style: solid;\n border-color: var(--color-surface-3);\n padding: var(--spacing-sm);\n border-radius: var(--rounded-xl) var(--rounded-xl) var(--rounded-xl)\n 0;\n }\n\n // Read-only feedback: a circular vote chip that overlaps the bubble's\n // bottom-right corner (messaging-app style). White ring lifts it off\n // the bubble; solid accent makes the rating unmistakable.\n &__reaction {\n position: absolute;\n right: var(--spacing-8);\n bottom: 0;\n transform: translateY(50%);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--spacing-24);\n height: var(--spacing-24);\n border-radius: var(--rounded-full, 9999px);\n color: var(--color-white, #fff);\n font-size: var(--text-12);\n box-shadow: var(--shadow-md);\n\n &--positive {\n background-color: var(--color-success);\n }\n\n &--negative {\n background-color: var(--color-danger);\n }\n\n // Real emoji (e.g. WhatsApp): the glyph carries its own colour, so\n // the chip stays a neutral surface pill.\n &--neutral {\n background-color: var(--color-surface-1);\n }\n }\n\n &__reaction-emoji {\n // Smaller than the chip so the glyph doesn't touch the ring.\n font-size: var(--text-12);\n line-height: 1;\n }\n\n &__loading-info {\n line-height: var(--leading-normal);\n border-width: var(--spacing-px);\n border-style: solid;\n border-color: var(--color-surface-3);\n padding: var(--spacing-sm);\n border-radius: var(--rounded-xl) var(--rounded-xl) var(--rounded-xl)\n 0;\n font-size: var(--text-12);\n color: var(--color-word-3);\n }\n\n &__footer {\n width: 100%;\n display: flex;\n gap: var(--spacing-8);\n align-items: center;\n }\n\n &__hover-meta {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: var(--spacing-8);\n font-size: var(--text-12);\n color: var(--color-word-3);\n white-space: nowrap;\n font-variant-numeric: tabular-nums;\n opacity: 0;\n transition: opacity 0.2s ease;\n }\n\n &:hover &__hover-meta,\n &:focus-within &__hover-meta {\n opacity: 1;\n }\n\n // Hover doesn't exist on touch devices: keep the metadata visible\n @media (hover: none) {\n &__hover-meta {\n opacity: 1;\n }\n }\n\n &--user {\n align-items: flex-end;\n\n .pk-chatbot-message__text {\n background-color: var(\n --chatbot-main-color,\n var(--color-surface-1)\n );\n border-color: var(--chatbot-main-color, var(--color-surface-4));\n color: var(--chatbot-contrast-color, var(--color-word-1));\n border-radius: var(--rounded-xl) var(--rounded-xl) 0\n var(--rounded-xl);\n }\n }\n\n &--assistant {\n width: 100%;\n gap: var(--spacing-16);\n\n .pk-chatbot-message__text {\n width: 100%;\n }\n }\n\n &--system {\n align-items: flex-end;\n\n .pk-chatbot-message__text {\n display: flex;\n gap: var(--spacing-8);\n font-size: var(--text-12);\n padding: var(--spacing-8) var(--spacing-sm);\n align-items: center;\n border-color: var(--chatbot-main-color, var(--color-surface-4));\n color: var(--chatbot-main-color, var(--color-word-1));\n border-radius: var(--rounded-xl) var(--rounded-xl) 0\n var(--rounded-xl);\n }\n }\n }\n\n .pk-chatbot-part-enter-active {\n transition:\n opacity 0.25s ease,\n transform 0.25s ease;\n }\n\n .pk-chatbot-part-enter-from {\n opacity: 0;\n transform: translateY(8px);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .pk-chatbot-part-enter-active {\n transition: none;\n }\n\n .pk-chatbot-part-enter-from {\n transform: none;\n }\n }\n</style>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PkToolShowArtifact-BCc4qcf0.js","names":["$attrs","$t"],"sources":["../../../../packages/components/src/chat/PkCode.vue","../../../../packages/components/src/chat/PkCode.vue","../../../../packages/components/src/chat/PkToolShowArtifact.vue","../../../../packages/components/src/chat/PkToolShowArtifact.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, ref, watch } from 'vue'\n\n defineOptions({\n inheritAttrs: false,\n })\n\n const props = defineProps<{\n code: unknown\n language: string\n line?: boolean\n }>()\n\n const highlightedHtml = ref('')\n\n const hasCode = computed(() => {\n if (typeof props.code === 'string') {\n return props.code\n }\n return JSON.stringify(props.code, null, 2)\n })\n\n async function highlight() {\n const hljs = (await import('highlight.js/lib/common')).default\n try {\n const result = props.language\n ? hljs.highlight(hasCode.value, { language: props.language })\n : hljs.highlightAuto(hasCode.value)\n highlightedHtml.value = result.value\n } catch {\n highlightedHtml.value = hasCode.value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n }\n }\n\n watch(hasCode, highlight, { immediate: true })\n</script>\n\n<template>\n <pre\n :class=\"\n $attrs.class\n ? $attrs.class\n : 'font-mono leading-snug text-smaller bg-surface-1'\n \">\n <code :class=\"['hljs', line ? 'whitespace-pre-line' : 'whitespace-pre', language]\" v-html=\"highlightedHtml\"></code>\n </pre>\n</template>\n","<script setup lang=\"ts\">\n import { computed, ref, watch } from 'vue'\n\n defineOptions({\n inheritAttrs: false,\n })\n\n const props = defineProps<{\n code: unknown\n language: string\n line?: boolean\n }>()\n\n const highlightedHtml = ref('')\n\n const hasCode = computed(() => {\n if (typeof props.code === 'string') {\n return props.code\n }\n return JSON.stringify(props.code, null, 2)\n })\n\n async function highlight() {\n const hljs = (await import('highlight.js/lib/common')).default\n try {\n const result = props.language\n ? hljs.highlight(hasCode.value, { language: props.language })\n : hljs.highlightAuto(hasCode.value)\n highlightedHtml.value = result.value\n } catch {\n highlightedHtml.value = hasCode.value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n }\n }\n\n watch(hasCode, highlight, { immediate: true })\n</script>\n\n<template>\n <pre\n :class=\"\n $attrs.class\n ? $attrs.class\n : 'font-mono leading-snug text-smaller bg-surface-1'\n \">\n <code :class=\"['hljs', line ? 'whitespace-pre-line' : 'whitespace-pre', language]\" v-html=\"highlightedHtml\"></code>\n </pre>\n</template>\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { useClipboard } from '@vueuse/core'\n import PkCode from './PkCode.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n\n const { copy, copied } = useClipboard()\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n title: string\n content: string\n artifactType: string\n language?: string\n }\n }\n return part\n })\n\n const artifactTypeLabel = computed(() => {\n const labels: Record<string, string> = {\n code: 'Code',\n document: 'Document',\n markdown: 'Markdown',\n json: 'JSON',\n xml: 'XML',\n csv: 'CSV',\n html: 'HTML',\n yaml: 'YAML',\n sql: 'SQL',\n text: 'Text',\n }\n if (!toolPart.value.input?.artifactType) {\n return 'Artifact'\n }\n return labels[toolPart.value.input.artifactType] || 'Artifact'\n })\n\n const languageLabel = computed(() => {\n if (!toolPart.value.input?.language) {\n return ''\n }\n return ` (${toolPart.value.input.language})`\n })\n\n const isCodeType = computed(() => {\n return (\n toolPart.value.input?.artifactType === 'code' ||\n toolPart.value.input?.artifactType === 'json' ||\n toolPart.value.input?.artifactType === 'xml' ||\n toolPart.value.input?.artifactType === 'yaml' ||\n toolPart.value.input?.artifactType === 'html' ||\n toolPart.value.input?.artifactType === 'sql'\n )\n })\n\n const isMarkdownType = computed(() => {\n return toolPart.value.input?.artifactType === 'markdown'\n })\n\n const codeLanguage = computed(() => {\n if (toolPart.value.input?.language) {\n return toolPart.value.input.language\n }\n // Map artifact types to highlight.js languages\n const languageMap: Record<string, string> = {\n json: 'json',\n xml: 'xml',\n yaml: 'yaml',\n html: 'html',\n sql: 'sql',\n }\n if (!toolPart.value.input?.artifactType) {\n return 'plaintext'\n }\n return languageMap[toolPart.value.input.artifactType] || 'plaintext'\n })\n</script>\n\n<template>\n <div class=\"border border-surface-3 rounded-xl w-full overflow-hidden\">\n <div\n class=\"px-sm py-6 font-bold bg-surface-1 text-12 border-b border-surface-3 text-word-3 flex justify-between items-center\">\n <div class=\"flex flex-col items-start gap-4\">\n <span>{{ toolPart?.input?.title }}</span>\n <span class=\"text-10 opacity-60 font-normal\">\n {{ artifactTypeLabel }}{{ languageLabel }}\n </span>\n </div>\n <VvButton\n :icon=\"copied ? 'ri:check-line' : 'ri:file-copy-line'\"\n :label=\"copied ? $t('action.copied') : $t('action.copy')\"\n modifiers=\"action-quiet-small\"\n :title=\"$t('action.copy')\"\n class=\"shrink-0\"\n @click=\"copy(toolPart?.input?.content || '')\" />\n </div>\n <div class=\"px-sm py-10\">\n <PkCode\n v-if=\"isCodeType\"\n :code=\"toolPart?.input?.content\"\n :language=\"codeLanguage\" />\n <PkStreamingMarkdown\n v-else-if=\"isMarkdownType\"\n :markdown=\"toolPart?.input?.content\"\n class=\"wysiwyg text-word-2\" />\n <pre\n v-else\n class=\"text-word-2 whitespace-pre-wrap break-words\"\n :class=\"{\n italic:\n toolPart?.input?.artifactType === 'document' ||\n toolPart?.input?.artifactType === 'text',\n }\"\n >{{ toolPart?.input?.content }}</pre>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed } from 'vue'\n import { useClipboard } from '@vueuse/core'\n import PkCode from './PkCode.vue'\n import PkStreamingMarkdown from './PkStreamingMarkdown.vue'\n\n const { copy, copied } = useClipboard()\n\n const props = defineProps<{\n part: unknown\n }>()\n\n const toolPart = computed(() => {\n const part = props.part as {\n input?: {\n title: string\n content: string\n artifactType: string\n language?: string\n }\n }\n return part\n })\n\n const artifactTypeLabel = computed(() => {\n const labels: Record<string, string> = {\n code: 'Code',\n document: 'Document',\n markdown: 'Markdown',\n json: 'JSON',\n xml: 'XML',\n csv: 'CSV',\n html: 'HTML',\n yaml: 'YAML',\n sql: 'SQL',\n text: 'Text',\n }\n if (!toolPart.value.input?.artifactType) {\n return 'Artifact'\n }\n return labels[toolPart.value.input.artifactType] || 'Artifact'\n })\n\n const languageLabel = computed(() => {\n if (!toolPart.value.input?.language) {\n return ''\n }\n return ` (${toolPart.value.input.language})`\n })\n\n const isCodeType = computed(() => {\n return (\n toolPart.value.input?.artifactType === 'code' ||\n toolPart.value.input?.artifactType === 'json' ||\n toolPart.value.input?.artifactType === 'xml' ||\n toolPart.value.input?.artifactType === 'yaml' ||\n toolPart.value.input?.artifactType === 'html' ||\n toolPart.value.input?.artifactType === 'sql'\n )\n })\n\n const isMarkdownType = computed(() => {\n return toolPart.value.input?.artifactType === 'markdown'\n })\n\n const codeLanguage = computed(() => {\n if (toolPart.value.input?.language) {\n return toolPart.value.input.language\n }\n // Map artifact types to highlight.js languages\n const languageMap: Record<string, string> = {\n json: 'json',\n xml: 'xml',\n yaml: 'yaml',\n html: 'html',\n sql: 'sql',\n }\n if (!toolPart.value.input?.artifactType) {\n return 'plaintext'\n }\n return languageMap[toolPart.value.input.artifactType] || 'plaintext'\n })\n</script>\n\n<template>\n <div class=\"border border-surface-3 rounded-xl w-full overflow-hidden\">\n <div\n class=\"px-sm py-6 font-bold bg-surface-1 text-12 border-b border-surface-3 text-word-3 flex justify-between items-center\">\n <div class=\"flex flex-col items-start gap-4\">\n <span>{{ toolPart?.input?.title }}</span>\n <span class=\"text-10 opacity-60 font-normal\">\n {{ artifactTypeLabel }}{{ languageLabel }}\n </span>\n </div>\n <VvButton\n :icon=\"copied ? 'ri:check-line' : 'ri:file-copy-line'\"\n :label=\"copied ? $t('action.copied') : $t('action.copy')\"\n modifiers=\"action-quiet-small\"\n :title=\"$t('action.copy')\"\n class=\"shrink-0\"\n @click=\"copy(toolPart?.input?.content || '')\" />\n </div>\n <div class=\"px-sm py-10\">\n <PkCode\n v-if=\"isCodeType\"\n :code=\"toolPart?.input?.content\"\n :language=\"codeLanguage\" />\n <PkStreamingMarkdown\n v-else-if=\"isMarkdownType\"\n :markdown=\"toolPart?.input?.content\"\n class=\"wysiwyg text-word-2\" />\n <pre\n v-else\n class=\"text-word-2 whitespace-pre-wrap break-words\"\n :class=\"{\n italic:\n toolPart?.input?.artifactType === 'document' ||\n toolPart?.input?.artifactType === 'text',\n }\"\n >{{ toolPart?.input?.content }}</pre>\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;EAOI,IAAM,IAAQ,GAMR,IAAkB,EAAI,EAAE,GAExB,IAAU,QACR,OAAO,EAAM,QAAS,WACf,EAAM,OAEV,KAAK,UAAU,EAAM,MAAM,MAAM,CAAC,CAC5C;EAED,eAAe,IAAY;GACvB,IAAM,KAAQ,MAAM,OAAO,2BAAA,CAA4B;GACvD,IAAI;IACA,IAAM,IAAS,EAAM,WACf,EAAK,UAAU,EAAQ,OAAO,EAAE,UAAU,EAAM,SAAS,CAAC,IAC1D,EAAK,cAAc,EAAQ,KAAK;IACtC,EAAgB,QAAQ,EAAO;GACnC,QAAQ;IACJ,EAAgB,QAAQ,EAAQ,MAC3B,QAAQ,MAAM,OAAO,CAAA,CACrB,QAAQ,MAAM,MAAM,CAAA,CACpB,QAAQ,MAAM,MAAM;GAC7B;EACJ;SAEA,EAAM,GAAS,GAAW,EAAE,WAAW,GAAK,CAAC,mBAI7C,EAOM,OAAA,EAND,OAAK,EAAeA,EAAAA,OAAO,QAAwBA,EAAAA,OAAO,QAAA,kDAAA,EAAA,GAAA;cAIzD,YACF,EAAA;GAAA,EAAmH,QAAA;IAA5G,OAAK,EAAA;KAAA;KAAW,EAAA,OAAI,wBAAA;KAA6C,EAAA;IAAQ,CAAA;IAAG,WAAQ,EAAA;;cAAwB,UACvH,EAAA;;;;;;;EE1CA,IAAM,EAAE,SAAM,cAAW,EAAa,GAEhC,IAAQ,GAIR,IAAW,QACA,EAAM,IAStB,GAEK,IAAoB,QAajB,EAAS,MAAM,OAAO,gBAGpB;GAdH,MAAM;GACN,UAAU;GACV,UAAU;GACV,MAAM;GACN,KAAK;GACL,KAAK;GACL,MAAM;GACN,MAAM;GACN,KAAK;GACL,MAAM;EAKH,EAAO,EAAS,MAAM,MAAM,iBAFxB,UAGd,GAEK,IAAgB,QACb,EAAS,MAAM,OAAO,WAGpB,KAAK,EAAS,MAAM,MAAM,SAAS,KAF/B,EAGd,GAEK,IAAa,QAEX,EAAS,MAAM,OAAO,iBAAiB,UACvC,EAAS,MAAM,OAAO,iBAAiB,UACvC,EAAS,MAAM,OAAO,iBAAiB,SACvC,EAAS,MAAM,OAAO,iBAAiB,UACvC,EAAS,MAAM,OAAO,iBAAiB,UACvC,EAAS,MAAM,OAAO,iBAAiB,KAE9C,GAEK,IAAiB,QACZ,EAAS,MAAM,OAAO,iBAAiB,UACjD,GAEK,IAAe,QACb,EAAS,MAAM,OAAO,WACf,EAAS,MAAM,MAAM,WAU3B,EAAS,MAAM,OAAO,gBAGpB;GATH,MAAM;GACN,KAAK;GACL,MAAM;GACN,MAAM;GACN,KAAK;EAKF,EAAY,EAAS,MAAM,MAAM,iBAF7B,WAGd;;;eAID,EAoCM,OApCN,GAoCM,CAnCF,EAeM,OAfN,GAeM,CAbF,EAKM,OALN,GAKM,CAJF,EAAyC,QAAA,MAAA,EAAhC,EAAA,OAAU,OAAO,KAAK,GAAA,CAAA,GAC/B,EAEO,QAFP,GAEO,EADA,EAAA,KAAiB,IAAA,EAAM,EAAA,KAAa,GAAA,CAAA,CAAA,CAAA,GAG/C,EAMoD,GAAA;IAL/C,MAAM,EAAA,CAAA,IAAM,kBAAA;IACZ,OAAO,EAAA,CAAA,IAASC,EAAAA,GAAE,eAAA,IAAoBA,EAAAA,GAAE,aAAA;IACzC,WAAU;IACT,OAAOA,EAAAA,GAAE,aAAA;IACV,OAAM;IACL,SAAK,AAAA,EAAA,QAAA,MAAE,EAAA,CAAA,CAAI,CAAC,EAAA,OAAU,OAAO,WAAO,EAAA;;;;;SAE7C,EAkBM,OAlBN,GAkBM,CAhBQ,EAAA,SAAA,EAAA,GADV,EAG+B,GAAA;;IAD1B,MAAM,EAAA,OAAU,OAAO;IACvB,UAAU,EAAA;wCAEA,EAAA,SAAA,EAAA,GADf,EAGkC,GAAA;;IAD7B,UAAU,EAAA,OAAU,OAAO;IAC5B,OAAM;sCACV,EAQyC,OAAA;;IANrC,OAAK,EAAA,CAAC,+CAA6C,EAAA,QACW,EAAA,OAAU,OAAO,iBAAY,cAA2C,EAAA,OAAU,OAAO,iBAAY,OAAA,CAAA,CAAA;QAK/J,EAAA,OAAU,OAAO,OAAO,GAAA,CAAA,EAAA,CAAA,CAAA,CAAA"}
|