From 6c46fd12526595005e279a3270c1e9d4dda798fc Mon Sep 17 00:00:00 2001 From: snapydziuba Date: Fri, 14 Aug 2026 06:22:01 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=84=20feat:=20accept=20PowerPoint=20te?= =?UTF-8?q?mplate=20MIME=20type=20(#14761)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 +- client/src/data-provider/Files/sharepoint.ts | 1 + client/src/utils/__tests__/artifacts.test.ts | 5 ++++ .../__tests__/getViableUploadOptions.spec.ts | 8 +++++++ client/src/utils/artifacts.ts | 3 +++ packages/api/src/agents/handlers.ts | 1 + packages/api/src/files/code/classify.spec.ts | 20 +++++++--------- packages/api/src/files/code/classify.ts | 13 +++++----- packages/api/src/files/code/extract.spec.ts | 22 +++++++++++++---- packages/api/src/files/code/extract.ts | 8 +++---- packages/api/src/files/documents/html.spec.ts | 14 +++++++---- packages/api/src/files/documents/html.ts | 12 ++++++---- .../data-provider/src/file-config.spec.ts | 24 +++++++++++++++++-- packages/data-provider/src/file-config.ts | 10 ++++++-- 14 files changed, 102 insertions(+), 41 deletions(-) diff --git a/.env.example b/.env.example index 75c52ae5f7..c87c160dfe 100644 --- a/.env.example +++ b/.env.example @@ -991,7 +991,7 @@ HELP_AND_FAQ_URL=https://librechat.ai # such as the below example of 250 mib # CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES=262144000 -# Max size (bytes) of a code-execution artifact (docx/xlsx/csv/pptx/text/pdf) rendered as an +# Max size (bytes) of a code-execution artifact (docx/xlsx/csv/pptx/potx/text/pdf) rendered as an # inline preview. Larger files fall back to download-only. Default: 2 MB (2097152). Note the # rendered HTML is independently capped at 512 KB, so very rich files may still skip preview. # FILE_PREVIEW_MAX_EXTRACT_BYTES=2097152 diff --git a/client/src/data-provider/Files/sharepoint.ts b/client/src/data-provider/Files/sharepoint.ts index 6a9dfb9a35..596448c1b8 100644 --- a/client/src/data-provider/Files/sharepoint.ts +++ b/client/src/data-provider/Files/sharepoint.ts @@ -204,6 +204,7 @@ function getMimeTypeFromFileName(fileName: string): string { xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ppt: 'application/vnd.ms-powerpoint', pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + potx: 'application/vnd.openxmlformats-officedocument.presentationml.template', txt: 'text/plain', csv: 'text/csv', diff --git a/client/src/utils/__tests__/artifacts.test.ts b/client/src/utils/__tests__/artifacts.test.ts index b8439008d5..f4444a5d38 100644 --- a/client/src/utils/__tests__/artifacts.test.ts +++ b/client/src/utils/__tests__/artifacts.test.ts @@ -66,6 +66,7 @@ describe('detectArtifactTypeFromFile', () => { ['legacy.xls', TOOL_ARTIFACT_TYPES.SPREADSHEET], ['sheet.ods', TOOL_ARTIFACT_TYPES.SPREADSHEET], ['slides.pptx', TOOL_ARTIFACT_TYPES.PRESENTATION], + ['template.potx', TOOL_ARTIFACT_TYPES.PRESENTATION], ])('classifies %s by extension', (filename, expected) => { /* Office types require `textFormat: 'html'` to route to their HTML * preview buckets — the security gate added for Codex P1 review on @@ -155,6 +156,10 @@ describe('detectArtifactTypeFromFile', () => { 'application/vnd.openxmlformats-officedocument.presentationml.presentation', TOOL_ARTIFACT_TYPES.PRESENTATION, ], + [ + 'application/vnd.openxmlformats-officedocument.presentationml.template', + TOOL_ARTIFACT_TYPES.PRESENTATION, + ], ])('routes office MIME %s to its preview bucket when extension is missing', (mime, expected) => { /* `textFormat: 'html'` is required so the security gate (Codex P1 on * PR #12934) lets routing proceed to the office HTML bucket — without diff --git a/client/src/utils/__tests__/getViableUploadOptions.spec.ts b/client/src/utils/__tests__/getViableUploadOptions.spec.ts index 5c1a7ff74e..261ea70e40 100644 --- a/client/src/utils/__tests__/getViableUploadOptions.spec.ts +++ b/client/src/utils/__tests__/getViableUploadOptions.spec.ts @@ -3,6 +3,7 @@ import type { FileConfig } from 'librechat-data-provider'; import { getViableUploadOptions, type UploadOptionContext } from '../files'; const XLSX = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; +const POTX = 'application/vnd.openxmlformats-officedocument.presentationml.template'; /** context accepts plain text + csv (text), pdf + xlsx (ocr); nothing else */ const fileConfig = { @@ -49,6 +50,13 @@ describe('getViableUploadOptions', () => { ]); }); + it('routes a PowerPoint template to file search and code, not the provider', () => { + expect(getViableUploadOptions([file(POTX, 'brand-template.potx')], baseCtx())).toEqual([ + EToolResources.file_search, + EToolResources.execute_code, + ]); + }); + it('offers every destination for a PDF', () => { expect(getViableUploadOptions([file('application/pdf', 'doc.pdf')], baseCtx())).toEqual([ undefined, diff --git a/client/src/utils/artifacts.ts b/client/src/utils/artifacts.ts index 0d5601a324..50531867bd 100644 --- a/client/src/utils/artifacts.ts +++ b/client/src/utils/artifacts.ts @@ -590,6 +590,7 @@ const EXTENSION_TO_TOOL_ARTIFACT_TYPE: Record = { xls: TOOL_ARTIFACT_TYPES.SPREADSHEET, ods: TOOL_ARTIFACT_TYPES.SPREADSHEET, pptx: TOOL_ARTIFACT_TYPES.PRESENTATION, + potx: TOOL_ARTIFACT_TYPES.PRESENTATION, }; /* Append every entry in `CODE_EXTENSION_TO_LANGUAGE` to the routing map @@ -667,6 +668,8 @@ const MIME_TO_TOOL_ARTIFACT_TYPE: Record = { 'text/comma-separated-values': TOOL_ARTIFACT_TYPES.SPREADSHEET, 'application/vnd.openxmlformats-officedocument.presentationml.presentation': TOOL_ARTIFACT_TYPES.PRESENTATION, + 'application/vnd.openxmlformats-officedocument.presentationml.template': + TOOL_ARTIFACT_TYPES.PRESENTATION, // Note: bare `text/plain` is NOT mapped here. The extension map handles // `.txt` explicitly; routing every unrecognized-extension `text/plain` // file (extensionless scripts, .env, etc.) through the panel would be a diff --git a/packages/api/src/agents/handlers.ts b/packages/api/src/agents/handlers.ts index 1cca993bd4..04ae2b978b 100644 --- a/packages/api/src/agents/handlers.ts +++ b/packages/api/src/agents/handlers.ts @@ -1211,6 +1211,7 @@ const BINARY_EXTENSIONS_NEVER_READABLE = new Set([ '.xlsx', '.ppt', '.pptx', + '.potx', '.odt', '.ods', '.odp', diff --git a/packages/api/src/files/code/classify.spec.ts b/packages/api/src/files/code/classify.spec.ts index 7e713427a0..a800361dd9 100644 --- a/packages/api/src/files/code/classify.spec.ts +++ b/packages/api/src/files/code/classify.spec.ts @@ -79,18 +79,14 @@ describe('classifyCodeArtifact', () => { }); }); - describe('pptx', () => { - it('classifies .pptx by extension', () => { - expect(classifyCodeArtifact('slides.pptx', 'application/octet-stream')).toBe('pptx'); - }); - - it('classifies pptx by mime', () => { - expect( - classifyCodeArtifact( - 'unknown', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - ), - ).toBe('pptx'); + describe('presentation', () => { + it.each([ + ['slides.pptx', 'application/octet-stream'], + ['template.potx', 'application/octet-stream'], + ['unknown', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'], + ['unknown', 'application/vnd.openxmlformats-officedocument.presentationml.template'], + ])('classifies %s with MIME %s as a presentation', (name, mime) => { + expect(classifyCodeArtifact(name, mime)).toBe('presentation'); }); }); diff --git a/packages/api/src/files/code/classify.ts b/packages/api/src/files/code/classify.ts index d4b782553d..844da878c6 100644 --- a/packages/api/src/files/code/classify.ts +++ b/packages/api/src/files/code/classify.ts @@ -1,6 +1,6 @@ import { excelMimeTypes } from 'librechat-data-provider'; -export type CodeArtifactCategory = 'utf8-text' | 'document' | 'pptx' | 'other'; +export type CodeArtifactCategory = 'utf8-text' | 'document' | 'presentation' | 'other'; const UTF8_TEXT_EXTENSIONS = new Set([ // plaintext / data @@ -119,9 +119,10 @@ const UTF8_TEXT_MIME_EXACT = new Set([ const DOCX_MIME = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; const ODT_MIME = 'application/vnd.oasis.opendocument.text'; const PPTX_MIME = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; +const POTX_MIME = 'application/vnd.openxmlformats-officedocument.presentationml.template'; const DOCUMENT_EXTENSIONS = new Set(['docx', 'odt', 'xlsx', 'xls', 'ods']); -const PPTX_EXTENSIONS = new Set(['pptx']); +const PRESENTATION_EXTENSIONS = new Set(['pptx', 'potx']); const extensionOf = (name: string): string => { const dot = name.lastIndexOf('.'); @@ -176,8 +177,8 @@ export function classifyCodeArtifact(name: string, mimeType: string): CodeArtifa if (ext && DOCUMENT_EXTENSIONS.has(ext)) { return 'document'; } - if (ext && PPTX_EXTENSIONS.has(ext)) { - return 'pptx'; + if (ext && PRESENTATION_EXTENSIONS.has(ext)) { + return 'presentation'; } const bare = bareNameOf(name); if (bare && UTF8_TEXT_EXTENSIONS.has(bare)) { @@ -189,8 +190,8 @@ export function classifyCodeArtifact(name: string, mimeType: string): CodeArtifa if (isDocumentMime(mimeType)) { return 'document'; } - if (mimeType === PPTX_MIME) { - return 'pptx'; + if (mimeType === PPTX_MIME || mimeType === POTX_MIME) { + return 'presentation'; } return 'other'; } diff --git a/packages/api/src/files/code/extract.spec.ts b/packages/api/src/files/code/extract.spec.ts index e6a987ca1d..a9d51add6b 100644 --- a/packages/api/src/files/code/extract.spec.ts +++ b/packages/api/src/files/code/extract.spec.ts @@ -203,13 +203,13 @@ describe('extractCodeArtifactText', () => { }); describe('skipped categories', () => { - it('returns null for pptx when HTML rendering also fails', async () => { + it('returns null for presentations when HTML rendering also fails', async () => { mockOfficeHtml.mockResolvedValueOnce(null); const text = await extractCodeArtifactText( Buffer.from('PK'), 'slides.pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'pptx', + 'presentation', ); expect(text).toBeNull(); }); @@ -249,11 +249,23 @@ describe('extractCodeArtifactText', () => { Buffer.from('PK'), 'deck.pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'pptx', + 'presentation', ); expect(text).toContain('slides'); }); + it('returns the HTML rendering for a potx when the producer succeeds', async () => { + const html = 'template'; + mockOfficeHtml.mockResolvedValueOnce(html); + const text = await extractCodeArtifactText( + Buffer.from('PK'), + 'template.potx', + 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'presentation', + ); + expect(text).toBe(html); + }); + it('returns the HTML rendering for csv (overriding utf8-text raw output)', async () => { mockOfficeHtml.mockResolvedValueOnce('
a
'); const text = await extractCodeArtifactText( @@ -422,9 +434,9 @@ describe('extractCodeArtifactText', () => { ['deck', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'], ])('routes extensionless office files by MIME alone (%s, %s)', async (name, mime) => { mockOfficeHtml.mockResolvedValueOnce('x'); - let category: 'pptx' | 'utf8-text' | 'document' = 'document'; + let category: 'presentation' | 'utf8-text' | 'document' = 'document'; if (mime.includes('presentation')) { - category = 'pptx'; + category = 'presentation'; } else if (mime.startsWith('text/')) { category = 'utf8-text'; } diff --git a/packages/api/src/files/code/extract.ts b/packages/api/src/files/code/extract.ts index 7cef9b19ae..d0231e7724 100644 --- a/packages/api/src/files/code/extract.ts +++ b/packages/api/src/files/code/extract.ts @@ -260,13 +260,13 @@ const renderOfficeHtml = async ( * rendering. Returns `null` for binary, oversized, or unsupported files; the * caller should fall back to the standard download UI in that case. * - * Office types (docx, xlsx/xls/ods, csv, pptx) are rendered as sanitized + * Office types (docx, xlsx/xls/ods, csv, pptx/potx) are rendered as sanitized * HTML by the producers in `~/files/documents/html`. The frontend feeds the * HTML into the Sandpack `static` template via `index.html`. CSV is special- * cased here — its category is `utf8-text` (raw CSV is text), but we want * the styled-table preview when the file extension says CSV. * - * - office (docx/xlsx/xls/ods/csv/pptx): sanitized HTML preview + * - office (docx/xlsx/xls/ods/csv/pptx/potx): sanitized HTML preview * - utf8-text: decodes the buffer (with a binary safety net) * - document: dispatches to the existing PDF/ODT parser * - other: returns null (binary file, no inline preview) @@ -318,8 +318,8 @@ export async function extractCodeArtifactText( * the markdown viewer with proper escaping). Plain text is safe. */ return await extractDocument(buffer, name, mimeType); } - /* category === 'pptx' that didn't go through the office HTML path - * (shouldn't happen — pptx ext is in OFFICE_HTML_EXTENSIONS — but + /* category === 'presentation' that didn't go through the office HTML path + * (shouldn't happen — presentation extensions use the office HTML path — but * defended in depth). */ return null; } catch (error) { diff --git a/packages/api/src/files/documents/html.spec.ts b/packages/api/src/files/documents/html.spec.ts index 6725b577be..e320abddf9 100644 --- a/packages/api/src/files/documents/html.spec.ts +++ b/packages/api/src/files/documents/html.spec.ts @@ -729,11 +729,17 @@ describe('Office HTML producers', () => { ['noext', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'spreadsheet'], ['noext', 'application/vnd.ms-excel', 'spreadsheet'], ['noext', 'application/vnd.oasis.opendocument.spreadsheet', 'spreadsheet'], - ['deck.pptx', '', 'pptx'], + ['deck.pptx', '', 'presentation'], + ['template.potx', '', 'presentation'], [ 'noext', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'pptx', + 'presentation', + ], + [ + 'noext', + 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'presentation', ], ])('classifies (%s, %s) as %s', (name, mime, expected) => { expect(officeHtmlBucket(name, mime)).toBe(expected); @@ -760,7 +766,7 @@ describe('Office HTML producers', () => { * documented "extension wins" precedence is enforced by checking * extensions exhaustively before any MIME pattern fires. */ it.each([ - ['deck.pptx', 'text/csv', 'pptx'], + ['deck.pptx', 'text/csv', 'presentation'], ['workbook.xlsx', 'text/csv', 'spreadsheet'], [ 'legacy.xls', @@ -838,7 +844,7 @@ describe('Office HTML producers', () => { [ 'deck', 'application/vnd.openxmlformats-officedocument.presentationml.presentation; foo=bar', - 'pptx', + 'presentation', ], ])('strips MIME parameters before matching: (%s, %s) → %s', (name, mime, expected) => { expect(officeHtmlBucket(name, mime)).toBe(expected); diff --git a/packages/api/src/files/documents/html.ts b/packages/api/src/files/documents/html.ts index 96e41d9107..2ee0ff163b 100644 --- a/packages/api/src/files/documents/html.ts +++ b/packages/api/src/files/documents/html.ts @@ -1501,6 +1501,7 @@ export const _internal: { const DOCX_MIME = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; const PPTX_MIME = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; +const POTX_MIME = 'application/vnd.openxmlformats-officedocument.presentationml.template'; const ODS_MIME = 'application/vnd.oasis.opendocument.spreadsheet'; const CSV_MIME_PATTERN = /^(text\/csv|application\/csv|text\/comma-separated-values)$/i; @@ -1541,7 +1542,7 @@ function baseMime(mime: string): string { * `text/csv`), which then routed to the SPREADSHEET bucket on the * client expecting full HTML and got raw text instead. */ -export type OfficeHtmlBucket = 'docx' | 'spreadsheet' | 'csv' | 'pptx'; +export type OfficeHtmlBucket = 'docx' | 'spreadsheet' | 'csv' | 'presentation'; const OFFICE_EXTENSIONS: Record = { docx: 'docx', @@ -1549,7 +1550,8 @@ const OFFICE_EXTENSIONS: Record = { xlsx: 'spreadsheet', xls: 'spreadsheet', ods: 'spreadsheet', - pptx: 'pptx', + pptx: 'presentation', + potx: 'presentation', }; /** @@ -1608,8 +1610,8 @@ export function officeHtmlBucket(name: string, mimeType: string): OfficeHtmlBuck if (excelMimeTypes.test(normalized) || normalized === ODS_MIME) { return 'spreadsheet'; } - if (normalized === PPTX_MIME) { - return 'pptx'; + if (normalized === PPTX_MIME || normalized === POTX_MIME) { + return 'presentation'; } return null; } @@ -1632,7 +1634,7 @@ export async function bufferToOfficeHtml( return csvToHtml(buffer); case 'spreadsheet': return excelSheetToHtml(buffer); - case 'pptx': + case 'presentation': return pptxToHtml(buffer); default: return null; diff --git a/packages/data-provider/src/file-config.spec.ts b/packages/data-provider/src/file-config.spec.ts index f295d5ecca..e7f895b6b1 100644 --- a/packages/data-provider/src/file-config.spec.ts +++ b/packages/data-provider/src/file-config.spec.ts @@ -98,12 +98,16 @@ describe('inferMimeType', () => { expect(inferMimeType('deck.pptx', '')).toBe( 'application/vnd.openxmlformats-officedocument.presentationml.presentation', ); + expect(inferMimeType('template.potx', '')).toBe( + 'application/vnd.openxmlformats-officedocument.presentationml.template', + ); }); it('produces Office types accepted by checkType after inference', () => { expect(baseFileConfig.checkType(inferMimeType('report.docx', ''))).toBe(true); expect(baseFileConfig.checkType(inferMimeType('sheet.xlsx', ''))).toBe(true); expect(baseFileConfig.checkType(inferMimeType('legacy.doc', ''))).toBe(true); + expect(baseFileConfig.checkType(inferMimeType('template.potx', ''))).toBe(true); }); }); @@ -170,7 +174,8 @@ describe('defaultOCRMimeTypes', () => { 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation', 'application/vnd.oasis.opendocument.graphics', - ])('matches ODF type for OCR: %s', (mimeType) => { + 'application/vnd.openxmlformats-officedocument.presentationml.template', + ])('matches configured OCR type: %s', (mimeType) => { expect(checkOCRType(mimeType)).toBe(true); }); }); @@ -184,7 +189,8 @@ describe('supportedMimeTypes', () => { 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation', 'application/vnd.oasis.opendocument.graphics', - ])('ODF type flows through supportedMimeTypes: %s', (mimeType) => { + 'application/vnd.openxmlformats-officedocument.presentationml.template', + ])('document type flows through supportedMimeTypes: %s', (mimeType) => { expect(checkSupported(mimeType)).toBe(true); }); @@ -1491,6 +1497,20 @@ describe('getConfiguredMimeAccept', () => { expect(accept.has('video/*')).toBe(false); }); + it('translates a PowerPoint template allowlist to the template extension and MIME', () => { + const templateMime = 'application/vnd.openxmlformats-officedocument.presentationml.template'; + const accept = toSet( + getConfiguredMimeAccept( + convertStringsToRegex([ + '^application/vnd\\.openxmlformats-officedocument\\.presentationml\\.template$', + ]), + IMAGE_DOC, + ), + ); + + expect(accept).toEqual(new Set(['.potx', templateMime])); + }); + it('emits image/* for an image-only allowlist', () => { const accept = toSet(getConfiguredMimeAccept([/^image\/(jpeg|png)$/], IMAGE_DOC)); expect(accept.has('image/*')).toBe(true); diff --git a/packages/data-provider/src/file-config.ts b/packages/data-provider/src/file-config.ts index c6985367a3..9a0983bf90 100644 --- a/packages/data-provider/src/file-config.ts +++ b/packages/data-provider/src/file-config.ts @@ -39,6 +39,7 @@ export const fullMimeTypesList = [ 'application/pdf', 'text/x-php', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/vnd.openxmlformats-officedocument.presentationml.template', 'text/x-python', 'text/x-script.python', 'text/x-ruby', @@ -106,6 +107,7 @@ export const codeInterpreterMimeTypesList = [ 'application/pdf', 'text/x-php', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/vnd.openxmlformats-officedocument.presentationml.template', 'text/x-python', 'text/x-script.python', 'text/x-ruby', @@ -139,6 +141,7 @@ export const retrievalMimeTypesList = [ 'application/pdf', 'text/x-php', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/vnd.openxmlformats-officedocument.presentationml.template', 'text/x-python', 'text/x-script.python', 'text/x-ruby', @@ -217,7 +220,7 @@ export const textMimeTypes = /^(text\/(x-c|x-csharp|tab-separated-values|x-c\+\+|x-h|x-java|html|markdown|x-php|x-python|x-script\.python|x-ruby|x-tex|plain|css|vtt|javascript|csv|xml|calendar))$/; export const applicationMimeTypes = - /^(application\/(epub\+zip|csv|json|msword|pdf|x-tar|x-sh|x-zip-compressed|typescript|sql|yaml|x-parquet|vnd\.apache\.parquet|vnd\.coffeescript|vnd\.openxmlformats-officedocument\.(wordprocessingml\.document|presentationml\.presentation|spreadsheetml\.sheet)|vnd\.oasis\.opendocument\.(text|spreadsheet|presentation|graphics)|xml|zip))$/; + /^(application\/(epub\+zip|csv|json|msword|pdf|x-tar|x-sh|x-zip-compressed|typescript|sql|yaml|x-parquet|vnd\.apache\.parquet|vnd\.coffeescript|vnd\.openxmlformats-officedocument\.(wordprocessingml\.document|presentationml\.(presentation|template)|spreadsheetml\.sheet)|vnd\.oasis\.opendocument\.(text|spreadsheet|presentation|graphics)|xml|zip))$/; export const imageMimeTypes = /^image\/(jpeg|gif|png|webp|heic|heif)$/; @@ -231,6 +234,7 @@ export const defaultOCRMimeTypes = [ excelMimeTypes, /^application\/pdf$/, /^application\/vnd\.openxmlformats-officedocument\.(wordprocessingml\.document|presentationml\.presentation)$/, + /^application\/vnd\.openxmlformats-officedocument\.presentationml\.template$/, /^application\/vnd\.ms-(word|powerpoint)$/, /^application\/epub\+zip$/, /^application\/vnd\.oasis\.opendocument\.(text|spreadsheet|presentation|graphics)$/, @@ -394,6 +398,7 @@ export const codeTypeMapping: { [key: string]: string } = { xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx - Excel ppt: 'application/vnd.ms-powerpoint', // .ppt - PowerPoint (legacy) pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx - PowerPoint + potx: 'application/vnd.openxmlformats-officedocument.presentationml.template', // .potx - PowerPoint template ics: 'text/calendar', // .ics - iCalendar ical: 'text/calendar', // .ical - iCalendar ifb: 'text/calendar', // .ifb - iCalendar free/busy @@ -431,7 +436,7 @@ export function inferMimeType(fileName: string, currentType: string): string { export const retrievalMimeTypes = [ /^(text\/(x-c|x-c\+\+|x-h|html|x-java|markdown|x-php|x-python|x-script\.python|x-ruby|x-tex|plain|vtt|xml))$/, - /^(application\/(json|pdf|vnd\.openxmlformats-officedocument\.(wordprocessingml\.document|presentationml\.presentation)))$/, + /^(application\/(json|pdf|vnd\.openxmlformats-officedocument\.(wordprocessingml\.document|presentationml\.(presentation|template))))$/, ]; export const megabyte = 1024 * 1024; @@ -682,6 +687,7 @@ const documentMimeExtensions: ReadonlyArray