feat(attachments): optimize composer images
This commit is contained in:
parent
2ac71cd00d
commit
16161a2642
1 changed files with 51 additions and 5 deletions
|
|
@ -17,6 +17,10 @@ import {
|
|||
type ComposerDraftSnapshot,
|
||||
composerDraftStorage,
|
||||
} from '@renderer/services/composerDraftStorage';
|
||||
import {
|
||||
DEFAULT_AGENT_IMAGE_OPTIMIZATION_BUDGET,
|
||||
optimizeImageForAgent,
|
||||
} from '@features/agent-attachments/renderer';
|
||||
import {
|
||||
fileToAttachmentPayload,
|
||||
MAX_FILES,
|
||||
|
|
@ -105,6 +109,40 @@ function snapshotMatchesContent(
|
|||
);
|
||||
}
|
||||
|
||||
function imageOutputFilename(filename: string, mimeType: 'image/png' | 'image/jpeg'): string {
|
||||
const trimmed = filename.trim() || 'image';
|
||||
const withoutExtension = trimmed.replace(/\.[^.\\/]+$/, '') || 'image';
|
||||
return `${withoutExtension}.${mimeType === 'image/png' ? 'png' : 'jpg'}`;
|
||||
}
|
||||
|
||||
function blobToBase64(blob: Blob): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const dataUrl = typeof reader.result === 'string' ? reader.result : '';
|
||||
resolve(dataUrl.split(',')[1] ?? '');
|
||||
};
|
||||
reader.onerror = () => reject(new Error('Failed to read optimized image'));
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
async function fileToAgentAttachmentPayload(file: File): Promise<AttachmentPayload> {
|
||||
const category = categorizeFile(file);
|
||||
if (category !== 'image' || file.type === 'image/gif') {
|
||||
return fileToAttachmentPayload(file);
|
||||
}
|
||||
|
||||
const optimized = await optimizeImageForAgent({ file });
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
filename: imageOutputFilename(file.name, optimized.optimized.mimeType),
|
||||
mimeType: optimized.optimized.mimeType,
|
||||
size: optimized.optimized.sizeBytes,
|
||||
data: await blobToBase64(optimized.optimized.blob),
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hook
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -403,23 +441,23 @@ export function useComposerDraft(teamName: string): UseComposerDraftResult {
|
|||
|
||||
if (supported.length === 0) return;
|
||||
|
||||
let batchSize = 0;
|
||||
for (const file of supported) {
|
||||
const validation = validateAttachment(file);
|
||||
if (!validation.valid) {
|
||||
setAttachmentError(validation.error);
|
||||
return;
|
||||
}
|
||||
batchSize += file.size;
|
||||
}
|
||||
|
||||
const newPayloads: AttachmentPayload[] = [];
|
||||
for (const file of supported) {
|
||||
try {
|
||||
const payload = await fileToAttachmentPayload(file);
|
||||
const payload = await fileToAgentAttachmentPayload(file);
|
||||
newPayloads.push(payload);
|
||||
} catch {
|
||||
setAttachmentError(`Failed to read file: ${file.name}`);
|
||||
} catch (error) {
|
||||
const reason =
|
||||
error instanceof Error ? error.message : `Failed to read file: ${file.name}`;
|
||||
setAttachmentError(reason);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -430,10 +468,18 @@ export function useComposerDraft(teamName: string): UseComposerDraftResult {
|
|||
return;
|
||||
}
|
||||
const currentTotal = prev.reduce((sum, a) => sum + a.size, 0);
|
||||
const batchSize = newPayloads.reduce((sum, a) => sum + a.size, 0);
|
||||
if (currentTotal + batchSize > MAX_TOTAL_SIZE) {
|
||||
setAttachmentError('Total attachment size exceeds 20MB limit');
|
||||
return;
|
||||
}
|
||||
const optimizedImageBytes = [...prev, ...newPayloads]
|
||||
.filter((attachment) => attachment.mimeType.startsWith('image/'))
|
||||
.reduce((sum, attachment) => sum + attachment.size, 0);
|
||||
if (optimizedImageBytes > DEFAULT_AGENT_IMAGE_OPTIMIZATION_BUDGET.maxOutputBytesTotal) {
|
||||
setAttachmentError('Optimized image attachments exceed the safe runtime size limit');
|
||||
return;
|
||||
}
|
||||
|
||||
const next = [...prev, ...newPayloads];
|
||||
attachmentsRef.current = next;
|
||||
|
|
|
|||
Loading…
Reference in a new issue