Process pages on tool completion, only mark as leaf when history doesn't branch

This commit is contained in:
Connor Yoh 2025-09-11 13:01:02 +01:00
parent e585f67183
commit 0f1db3621f
3 changed files with 93 additions and 60 deletions

View File

@ -10,6 +10,7 @@ import {
createFileId, createFileId,
createQuickKey, createQuickKey,
createStirlingFile, createStirlingFile,
ProcessedFileMetadata,
} from '../../types/fileContext'; } from '../../types/fileContext';
import { FileId } from '../../types/file'; import { FileId } from '../../types/file';
import { generateThumbnailWithMetadata } from '../../utils/thumbnailUtils'; import { generateThumbnailWithMetadata } from '../../utils/thumbnailUtils';
@ -70,19 +71,43 @@ export function createProcessedFile(pageCount: number, thumbnail?: string) {
}; };
} }
/**
* Generate fresh ProcessedFileMetadata for a file
* Used when tools process files to ensure metadata matches actual file content
*/
export async function generateProcessedFileMetadata(file: File): Promise<ProcessedFileMetadata | undefined> {
// Only generate metadata for PDF files
if (!file.type.startsWith('application/pdf')) {
return undefined;
}
try {
const result = await generateThumbnailWithMetadata(file);
return createProcessedFile(result.pageCount, result.thumbnail);
} catch (error) {
if (DEBUG) console.warn(`📄 Failed to generate processedFileMetadata for ${file.name}:`, error);
}
return undefined;
}
/** /**
* Create a child StirlingFileStub from a parent stub with proper history management. * Create a child StirlingFileStub from a parent stub with proper history management.
* Used when a tool processes an existing file to create a new version with incremented history. * Used when a tool processes an existing file to create a new version with incremented history.
* *
* @param parentStub - The parent StirlingFileStub to create a child from * @param parentStub - The parent StirlingFileStub to create a child from
* @param operation - Tool operation information (toolName, timestamp) * @param operation - Tool operation information (toolName, timestamp)
* @param resultingFile - The processed File object
* @param thumbnail - Optional thumbnail for the child
* @param processedFileMetadata - Optional fresh metadata for the processed file
* @returns New child StirlingFileStub with proper version history * @returns New child StirlingFileStub with proper version history
*/ */
export function createChildStub( export function createChildStub(
parentStub: StirlingFileStub, parentStub: StirlingFileStub,
operation: { toolName: string; timestamp: number }, operation: { toolName: string; timestamp: number },
resultingFile: File, resultingFile: File,
thumbnail?: string thumbnail?: string,
processedFileMetadata?: ProcessedFileMetadata
): StirlingFileStub { ): StirlingFileStub {
const newFileId = createFileId(); const newFileId = createFileId();
@ -96,10 +121,12 @@ export function createChildStub(
// Determine original file ID (root of the version chain) // Determine original file ID (root of the version chain)
const originalFileId = parentStub.originalFileId || parentStub.id; const originalFileId = parentStub.originalFileId || parentStub.id;
// Update the child stub's name to match the processed file // Copy parent metadata but exclude processedFile to prevent stale data
const { processedFile: _processedFile, ...parentMetadata } = parentStub;
return { return {
// Copy all parent metadata // Copy parent metadata (excluding processedFile)
...parentStub, ...parentMetadata,
// Update identity and version info // Update identity and version info
id: newFileId, id: newFileId,
@ -113,10 +140,10 @@ export function createChildStub(
size: resultingFile.size, size: resultingFile.size,
type: resultingFile.type, type: resultingFile.type,
lastModified: resultingFile.lastModified, lastModified: resultingFile.lastModified,
thumbnailUrl: thumbnail thumbnailUrl: thumbnail,
// Preserve thumbnails and processing metadata from parent // Set fresh processedFile metadata (no inheritance from parent)
// These will be updated if the child has new thumbnails, but fallback to parent processedFile: processedFileMetadata
}; };
} }
@ -170,36 +197,29 @@ export async function addFiles(
const fileId = createFileId(); const fileId = createFileId();
filesRef.current.set(fileId, file); filesRef.current.set(fileId, file);
// Generate thumbnail and page count immediately // Generate processedFile metadata using centralized function
let thumbnail: string | undefined; const processedFileMetadata = await generateProcessedFileMetadata(file);
let pageCount: number = 1;
// Route based on file type - PDFs through full metadata pipeline, non-PDFs through simple path // Extract thumbnail for non-PDF files or use from processedFile for PDFs
if (file.type.startsWith('application/pdf')) { let thumbnail: string | undefined;
try { if (processedFileMetadata) {
if (DEBUG) console.log(`📄 Generating PDF metadata for ${file.name}`); // PDF file - use thumbnail from processedFile metadata
const result = await generateThumbnailWithMetadata(file); thumbnail = processedFileMetadata.thumbnailUrl;
thumbnail = result.thumbnail; if (DEBUG) console.log(`📄 Generated PDF metadata for ${file.name}: ${processedFileMetadata.totalPages} pages, thumbnail: SUCCESS`);
pageCount = result.pageCount; } else if (!file.type.startsWith('application/pdf')) {
if (DEBUG) console.log(`📄 Generated PDF metadata for ${file.name}: ${pageCount} pages, thumbnail: SUCCESS`); // Non-PDF files: simple thumbnail generation, no processedFile metadata
} catch (error) {
if (DEBUG) console.warn(`📄 Failed to generate PDF metadata for ${file.name}:`, error);
}
} else {
// Non-PDF files: simple thumbnail generation, no page count
try { try {
if (DEBUG) console.log(`📄 Generating simple thumbnail for non-PDF file ${file.name}`); if (DEBUG) console.log(`📄 Generating simple thumbnail for non-PDF file ${file.name}`);
const { generateThumbnailForFile } = await import('../../utils/thumbnailUtils'); const { generateThumbnailForFile } = await import('../../utils/thumbnailUtils');
thumbnail = await generateThumbnailForFile(file); thumbnail = await generateThumbnailForFile(file);
pageCount = 0; // Non-PDFs have no page count
if (DEBUG) console.log(`📄 Generated simple thumbnail for ${file.name}: no page count, thumbnail: SUCCESS`); if (DEBUG) console.log(`📄 Generated simple thumbnail for ${file.name}: no page count, thumbnail: SUCCESS`);
} catch (error) { } catch (error) {
if (DEBUG) console.warn(`📄 Failed to generate simple thumbnail for ${file.name}:`, error); if (DEBUG) console.warn(`📄 Failed to generate simple thumbnail for ${file.name}:`, error);
} }
} }
// Create new filestub with immediate thumbnail and page metadata // Create new filestub with processedFile metadata
const fileStub = createNewStirlingFileStub(file, fileId, thumbnail); const fileStub = createNewStirlingFileStub(file, fileId, thumbnail, processedFileMetadata);
if (thumbnail) { if (thumbnail) {
// Track blob URLs for cleanup (images return blob URLs that need revocation) // Track blob URLs for cleanup (images return blob URLs that need revocation)
if (thumbnail.startsWith('blob:')) { if (thumbnail.startsWith('blob:')) {
@ -212,12 +232,6 @@ export async function addFiles(
fileStub.insertAfterPageId = options.insertAfterPageId; fileStub.insertAfterPageId = options.insertAfterPageId;
} }
// Create initial processedFile metadata with page count
if (pageCount > 0) {
fileStub.processedFile = createProcessedFile(pageCount, thumbnail);
if (DEBUG) console.log(`📄 addFiles(raw): Created initial processedFile metadata for ${file.name} with ${pageCount} pages`);
}
existingQuickKeys.add(quickKey); existingQuickKeys.add(quickKey);
stirlingFileStubs.push(fileStub); stirlingFileStubs.push(fileStub);
@ -289,6 +303,7 @@ export async function consumeFiles(
} }
// Mark input files as processed in storage (no longer leaf nodes) // Mark input files as processed in storage (no longer leaf nodes)
if(!outputStirlingFileStubs.reduce((areAllV1, stub) => areAllV1 && (stub.versionNumber == 1), true)) {
await Promise.all( await Promise.all(
inputFileIds.map(async (fileId) => { inputFileIds.map(async (fileId) => {
try { try {
@ -299,6 +314,7 @@ export async function consumeFiles(
} }
}) })
); );
}
// Save output files directly to fileStorage with complete metadata // Save output files directly to fileStorage with complete metadata
for (let i = 0; i < outputStirlingFiles.length; i++) { for (let i = 0; i < outputStirlingFiles.length; i++) {
@ -500,15 +516,16 @@ export async function addStirlingFileStubs(
if (needsProcessing) { if (needsProcessing) {
if (DEBUG) console.log(`📄 addStirlingFileStubs: Regenerating processedFile for ${record.name}`); if (DEBUG) console.log(`📄 addStirlingFileStubs: Regenerating processedFile for ${record.name}`);
try {
// Generate basic processedFile structure with page count // Use centralized metadata generation function
const result = await generateThumbnailWithMetadata(stirlingFile); const processedFileMetadata = await generateProcessedFileMetadata(stirlingFile);
record.processedFile = createProcessedFile(result.pageCount, result.thumbnail); if (processedFileMetadata) {
record.thumbnailUrl = result.thumbnail; // Update thumbnail if needed record.processedFile = processedFileMetadata;
if (DEBUG) console.log(`📄 addStirlingFileStubs: Regenerated processedFile for ${record.name} with ${result.pageCount} pages`); record.thumbnailUrl = processedFileMetadata.thumbnailUrl; // Update thumbnail if needed
} catch (error) { if (DEBUG) console.log(`📄 addStirlingFileStubs: Regenerated processedFile for ${record.name} with ${processedFileMetadata.totalPages} pages`);
if (DEBUG) console.warn(`📄 addStirlingFileStubs: Failed to regenerate processedFile for ${record.name}:`, error); } else {
// Ensure we have at least basic structure // Fallback for files that couldn't be processed
if (DEBUG) console.warn(`📄 addStirlingFileStubs: Failed to regenerate processedFile for ${record.name}`);
if (!record.processedFile) { if (!record.processedFile) {
record.processedFile = createProcessedFile(1); // Fallback to 1 page record.processedFile = createProcessedFile(1); // Fallback to 1 page
} }

View File

@ -8,7 +8,7 @@ import { useToolResources } from './useToolResources';
import { extractErrorMessage } from '../../../utils/toolErrorHandler'; import { extractErrorMessage } from '../../../utils/toolErrorHandler';
import { StirlingFile, extractFiles, FileId, StirlingFileStub, createStirlingFile, createNewStirlingFileStub } from '../../../types/fileContext'; import { StirlingFile, extractFiles, FileId, StirlingFileStub, createStirlingFile, createNewStirlingFileStub } from '../../../types/fileContext';
import { ResponseHandler } from '../../../utils/toolResponseProcessor'; import { ResponseHandler } from '../../../utils/toolResponseProcessor';
import { createChildStub } from '../../../contexts/file/fileActions'; import { createChildStub, generateProcessedFileMetadata } from '../../../contexts/file/fileActions';
// Re-export for backwards compatibility // Re-export for backwards compatibility
export type { ProcessingProgress, ResponseHandler }; export type { ProcessingProgress, ResponseHandler };
@ -272,11 +272,26 @@ export const useToolOperation = <TParams>(
toolName: config.operationType, toolName: config.operationType,
timestamp: Date.now() timestamp: Date.now()
}; };
console.log("tool complete inputs ")
const outputStirlingFileStubs = processedFiles.length != inputStirlingFileStubs.length // Generate fresh processedFileMetadata for all processed files to ensure accuracy
? processedFiles.map((file, index) => createNewStirlingFileStub(file, undefined, thumbnails[index])) actions.setStatus('Generating metadata for processed files...');
const processedFileMetadataArray = await Promise.all(
processedFiles.map(file => generateProcessedFileMetadata(file))
);
const shouldBranchHistory = processedFiles.length != inputStirlingFileStubs.length;
// Create output stubs with fresh metadata (no inheritance of stale processedFile data)
const outputStirlingFileStubs = shouldBranchHistory
? processedFiles.map((file, index) =>
createNewStirlingFileStub(file, undefined, thumbnails[index], processedFileMetadataArray[index])
)
: processedFiles.map((resultingFile, index) => : processedFiles.map((resultingFile, index) =>
createChildStub(inputStirlingFileStubs[index], newToolOperation, resultingFile, thumbnails[index]) createChildStub(
inputStirlingFileStubs[index],
newToolOperation,
resultingFile,
thumbnails[index],
processedFileMetadataArray[index]
)
); );
// Create StirlingFile objects from processed files and child stubs // Create StirlingFile objects from processed files and child stubs

View File

@ -159,8 +159,8 @@ export function isFileObject(obj: any): obj is File | StirlingFile {
export function createNewStirlingFileStub( export function createNewStirlingFileStub(
file: File, file: File,
id?: FileId, id?: FileId,
thumbnail?: string thumbnail?: string,
processedFileMetadata?: ProcessedFileMetadata
): StirlingFileStub { ): StirlingFileStub {
const fileId = id || createFileId(); const fileId = id || createFileId();
return { return {
@ -173,7 +173,8 @@ export function createNewStirlingFileStub(
createdAt: Date.now(), createdAt: Date.now(),
isLeaf: true, // New files are leaf nodes by default isLeaf: true, // New files are leaf nodes by default
versionNumber: 1, // New files start at version 1 versionNumber: 1, // New files start at version 1
thumbnailUrl: thumbnail thumbnailUrl: thumbnail,
processedFile: processedFileMetadata
}; };
} }