Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion admin/slices/bridle/stores/bridle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,7 @@ export const useBridleStore = defineStore('bridle', {
for (const file of list) {
const mimeType = resolveMimeType(file)
const problem = !isAllowedMimeType(mimeType)
? `${file.name} is not a supported file type. Try an image, a PDF, or a text file.`
? `${file.name} is not a supported file type. Try an image, a PDF, an Office document, or a text file.`
: this._rejectReason(file, pendingBytes)

if (problem) {
Expand Down
27 changes: 26 additions & 1 deletion admin/slices/bridle/utils/attachment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ export const TEXT_MIME_TYPES = [
'application/json',
] as const

export const BINARY_MIME_TYPES = ['application/pdf'] as const
export const BINARY_MIME_TYPES = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel.sheet.macroEnabled.12',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
] as const

export const ALLOWED_MIME_TYPES: readonly string[] = [
...IMAGE_MIME_TYPES,
Expand All @@ -45,6 +54,13 @@ export const FILE_PICKER_ACCEPT = [
'.csv',
'.txt',
'.json',
'.doc',
'.docx',
'.xls',
'.xlsx',
'.xlsm',
'.ppt',
'.pptx',
].join(',')

/** Same extension fallback the server uses when `file.type` is blank. */
Expand All @@ -60,6 +76,15 @@ export const MIME_BY_EXTENSION: Readonly<Record<string, string>> = {
'.markdown': 'text/markdown',
'.csv': 'text/csv',
'.json': 'application/json',
'.doc': 'application/msword',
'.docx':
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.xls': 'application/vnd.ms-excel',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.xlsm': 'application/vnd.ms-excel.sheet.macroEnabled.12',
'.ppt': 'application/vnd.ms-powerpoint',
'.pptx':
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
}

export enum BridleAttachmentKinds {
Expand Down
33 changes: 31 additions & 2 deletions api/src/slices/bridle/domain/attachment.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,19 @@ export const TEXT_MIME_TYPES = [
'application/json',
] as const;

/** Accepted but not readable by the agent: delivered as a named reference. */
export const BINARY_MIME_TYPES = ['application/pdf'] as const;
/** Accepted but not readable by the agent: delivered as a named reference.
* Office formats are zip/OLE containers — extracting their text is a
* separate feature; until then they travel like PDFs, name and bytes only. */
export const BINARY_MIME_TYPES = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel.sheet.macroEnabled.12',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
] as const;

export const ALLOWED_MIME_TYPES: readonly string[] = [
...IMAGE_MIME_TYPES,
Expand All @@ -68,6 +79,15 @@ export const EXTENSION_BY_MIME: Readonly<Record<string, string>> = {
'text/markdown': '.md',
'text/csv': '.csv',
'application/json': '.json',
'application/msword': '.doc',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
'.docx',
'application/vnd.ms-excel': '.xls',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
'application/vnd.ms-excel.sheet.macroEnabled.12': '.xlsm',
'application/vnd.ms-powerpoint': '.ppt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
'.pptx',
};

/** Fallback when a file arrives with no usable MIME type but a known extension. */
Expand All @@ -83,4 +103,13 @@ export const MIME_BY_EXTENSION: Readonly<Record<string, string>> = {
'.markdown': 'text/markdown',
'.csv': 'text/csv',
'.json': 'application/json',
'.doc': 'application/msword',
'.docx':
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.xls': 'application/vnd.ms-excel',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.xlsm': 'application/vnd.ms-excel.sheet.macroEnabled.12',
'.ppt': 'application/vnd.ms-powerpoint',
'.pptx':
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
};
41 changes: 41 additions & 0 deletions api/src/slices/bridle/domain/attachment.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,47 @@ describe('BridleAttachmentService — upload validation', () => {
expect(result.readableByAgent).toBe(false);
});

it('accepts Office documents as binary references', async () => {
const { service } = makeService();
const cases = [
['macros.xlsm', 'application/vnd.ms-excel.sheet.macroEnabled.12'],
[
'sheet.xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
],
[
'letter.docx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
],
['deck.ppt', 'application/vnd.ms-powerpoint'],
] as const;

for (const [name, mimeType] of cases) {
const result = await service.upload({
agentId: AGENT,
name,
mimeType,
body: Buffer.from('PK'),
});
expect(result.kind).toBe(BridleAttachmentKinds.Binary);
expect(result.readableByAgent).toBe(false);
expect(result.mimeType).toBe(mimeType);
}
});

it('resolves an Office file by extension when the browser reports a blank type', async () => {
const { service } = makeService();
const result = await service.upload({
agentId: AGENT,
name: 'quarterly.xlsm',
mimeType: '',
body: Buffer.from('PK'),
});

expect(result.mimeType).toBe('application/vnd.ms-excel.sheet.macroEnabled.12');
expect(result.kind).toBe(BridleAttachmentKinds.Binary);
});

it('rejects a zero-byte file', async () => {
const { service } = makeService();
await expect(
Expand Down
2 changes: 1 addition & 1 deletion app/i18n.sync.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"chat.error_empty": "0b50d48fcbac",
"chat.error_size": "5dd5c28ddbbd",
"chat.error_total": "2a28704f7f5c",
"chat.error_type": "b5e7f88b3582",
"chat.error_type": "8c3cacb2300a",
"chat.input_hint": "8a8c17c82d18",
"chat.placeholder": "57ef13894e3b",
"chat.send": "f6f4688ff23d",
Expand Down
27 changes: 26 additions & 1 deletion app/slices/bridle/domain/attachment.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ export const TEXT_MIME_TYPES = [
'application/json',
] as const;

export const BINARY_MIME_TYPES = ['application/pdf'] as const;
export const BINARY_MIME_TYPES = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel.sheet.macroEnabled.12',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
] as const;

export const ALLOWED_MIME_TYPES: readonly string[] = [
...IMAGE_MIME_TYPES,
Expand All @@ -43,6 +52,13 @@ export const FILE_PICKER_ACCEPT = [
'.csv',
'.txt',
'.json',
'.doc',
'.docx',
'.xls',
'.xlsx',
'.xlsm',
'.ppt',
'.pptx',
].join(',');

/** Same extension fallback the server uses when `file.type` is blank. */
Expand All @@ -58,6 +74,15 @@ export const MIME_BY_EXTENSION: Readonly<Record<string, string>> = {
'.markdown': 'text/markdown',
'.csv': 'text/csv',
'.json': 'application/json',
'.doc': 'application/msword',
'.docx':
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.xls': 'application/vnd.ms-excel',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'.xlsm': 'application/vnd.ms-excel.sheet.macroEnabled.12',
'.ppt': 'application/vnd.ms-powerpoint',
'.pptx':
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
};

/** Human-readable byte size for chips and error copy. */
Expand Down
2 changes: 1 addition & 1 deletion app/slices/bridle/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"attachment_failed": "Couldn't upload {name}",
"attachment_unavailable": "This file is no longer available",
"attachment_not_readable": "The agent sees the name, not the contents",
"error_type": "{name} isn't a supported file type. Try an image, a PDF, or a text file.",
"error_type": "{name} isn't a supported file type. Try an image, a PDF, an Office document, or a text file.",
"error_size": "{name} is larger than {limit}",
"error_total": "Those files add up to more than {limit} in one message",
"error_empty": "{name} is empty"
Expand Down
2 changes: 1 addition & 1 deletion app/slices/bridle/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"attachment_failed": "Не удалось загрузить {name}",
"attachment_unavailable": "Этот файл больше недоступен",
"attachment_not_readable": "Агент видит имя файла, но не содержимое",
"error_type": "{name} — неподдерживаемый тип файла. Попробуйте изображение, PDF или текстовый файл.",
"error_type": "{name} — неподдерживаемый тип файла. Попробуйте изображение, PDF, документ Office или текстовый файл.",
"error_size": "{name} больше, чем {limit}",
"error_total": "Суммарный размер файлов в одном сообщении превышает {limit}",
"error_empty": "{name} пуст"
Expand Down
Loading