Image Validation: Ensuring Valid Images In Document Creation
Feature Request
To ensure data integrity and a seamless user experience, we need to implement robust image validation for all document creation and modification operations. This validation will ensure that all image links are valid, accessible, and non-empty before allowing any document operations to proceed. This is crucial for preventing broken images and ensuring visual consistency within our system. Hey guys, let's dive into why this is so important and how we're going to make it happen!
Current Behavior: The Problem with No Validation
Currently, our system lacks comprehensive image validation, which leads to several issues. Our AI can create documents with invalid image paths, resulting in broken image links and a frustrating user experience. We also face problems with documents being created with empty or null image fields, leading to missing avatars or tokens in the system. This lack of validation means that non-existent images are accepted without any checks, causing visual inconsistencies and display errors within FoundryVTT. Basically, it's a bit of a mess right now, and we need to clean it up!
The problems we're currently facing include documents with broken image links, empty image fields resulting in missing visuals, invalid paths causing display errors, and an overall poor user experience due to broken visuals. There's also no feedback mechanism in place when the AI suggests invalid images, which compounds the issue. It's like trying to build a house with faulty bricks – it just doesn't work.
Desired Behavior: Comprehensive Image Validation to the Rescue
Our goal is to implement a comprehensive image validation system that addresses all these issues. This system should validate that image paths exist and are accessible, rejecting any empty, null, or invalid image links. We need to verify that image file formats are supported and check file permissions and accessibility. Clear validation errors should be provided to the user, allowing for quick correction. This system should also integrate seamlessly with our AI retry mechanism (Issue #15), ensuring a smooth workflow even when issues arise. Imagine a system that not only flags errors but also helps you fix them – that's what we're aiming for!
The desired behavior includes validating image paths, rejecting invalid links, verifying supported file formats, checking file permissions, providing clear validation errors, and integrating with the AI retry mechanism. This will ensure that only valid images are used, leading to a more polished and professional user experience. It’s all about making things smoother and more reliable for everyone.
Implementation Strategy
To achieve our goals, we'll be implementing a multi-faceted approach that includes an Image Validation Service, integration with CRUD Operations, and enhancements to our AI Retry System. This strategy is designed to be robust, efficient, and user-friendly. Let's break down each component.
1. Image Validation Service
We'll start by creating a dedicated ImageValidator
class in a new file, scripts/core/image-validator.js
. This service will be responsible for all image validation tasks, ensuring a centralized and consistent approach. This is where the magic happens, folks!
export class ImageValidator {
constructor() {
this.supportedFormats = [
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.tif',
'.webp', '.svg', '.ico', '.apng', '.avif', '.jxl'
];
this.validationCache = new Map();
this.cacheTTL = 43200000; // 12 hours
}
/**
* Validates an image path for document use
* @param {string} imagePath - Path to validate
* @param {string} documentType - Type of document for context
* @returns {Promise<ValidationResult>}
*/
async validateImagePath(imagePath, documentType = 'unknown') {
try {
// Check cache first
const cached = this.getCachedValidation(imagePath);
if (cached !== null) return cached;
const result = await this.performImageValidation(imagePath, documentType);
// Cache result
this.setCachedValidation(imagePath, result);
return result;
} catch (error) {
return {
isValid: false,
error: error.message,
code: 'VALIDATION_ERROR'
};
}
}
async performImageValidation(imagePath, documentType) {
// 1. Check for empty/null paths
if (!imagePath || typeof imagePath !== 'string' || imagePath.trim() === '') {
return {
isValid: false,
error: 'Image path cannot be empty or null',
code: 'EMPTY_PATH',
suggestion: 'Provide a valid image path or use a default image'
};
}
// 2. Validate file extension
const formatResult = this.validateImageFormat(imagePath);
if (!formatResult.isValid) return formatResult;
// 3. Check if file exists and is accessible
const accessResult = await this.validateImageAccess(imagePath);
if (!accessResult.isValid) return accessResult;
// 4. Additional context-specific validation
const contextResult = this.validateImageContext(imagePath, documentType);
if (!contextResult.isValid) return contextResult;
return {
isValid: true,
path: imagePath,
format: this.getImageFormat(imagePath),
message: 'Image validation passed'
};
}
validateImageFormat(imagePath) {
const extension = this.getFileExtension(imagePath);
if (!extension) {
return {
isValid: false,
error: 'Image path has no file extension',
code: 'NO_EXTENSION',
suggestion: 'Add a valid image file extension (.png, .jpg, etc.)'
};
}
if (!this.supportedFormats.includes(extension)) {
return {
isValid: false,
error: `Unsupported image format: ${extension}`,
code: 'UNSUPPORTED_FORMAT',
supportedFormats: this.supportedFormats,
suggestion: `Use a supported format: ${this.supportedFormats.join(', ')}`
};
}
return { isValid: true };
}
async validateImageAccess(imagePath) {
try {
// Use FoundryVTT's FilePicker to check file existence
const exists = await this.checkFileExists(imagePath);
if (!exists) {
return {
isValid: false,
error: `Image file not found: ${imagePath}`,
code: 'FILE_NOT_FOUND',
suggestion: 'Verify the image path is correct and the file exists'
};
}
// Check if file is accessible (permissions)
const accessible = await this.checkFileAccessible(imagePath);
if (!accessible) {
return {
isValid: false,
error: `Image file not accessible: ${imagePath}`,
code: 'ACCESS_DENIED',
suggestion: 'Check file permissions and FoundryVTT access rights'
};
}
return { isValid: true };
} catch (error) {
return {
isValid: false,
error: `Cannot validate image access: ${error.message}`,
code: 'ACCESS_ERROR',
suggestion: 'Verify the image path and try again'
};
}
}
validateImageContext(imagePath, documentType) {
// Context-specific validation rules
const contextRules = {
'Actor': {
recommendedPaths: ['tokens/', 'actors/', 'characters/'],
preferredFormats: ['.png', '.jpg', '.webp']
},
'Item': {
recommendedPaths: ['items/', 'equipment/', 'icons/'],
preferredFormats: ['.png', '.jpg', '.svg']
},
'Scene': {
recommendedPaths: ['scenes/', 'maps/', 'backgrounds/'],
preferredFormats: ['.jpg', '.png', '.webp']
}
};
const rules = contextRules[documentType];
if (!rules) return { isValid: true }; // No specific rules
// Check if path follows recommended structure
const pathLower = imagePath.toLowerCase();
const inRecommendedPath = rules.recommendedPaths.some(path =>
pathLower.includes(path.toLowerCase())
);
const format = this.getFileExtension(imagePath);
const isPreferredFormat = rules.preferredFormats.includes(format);
// These are warnings, not failures
const warnings = [];
if (!inRecommendedPath) {
warnings.push(`Consider organizing ${documentType} images in: ${rules.recommendedPaths.join(', ')}`);
}
if (!isPreferredFormat) {
warnings.push(`Preferred formats for ${documentType}: ${rules.preferredFormats.join(', ')}`);
}
return {
isValid: true,
warnings: warnings.length > 0 ? warnings : undefined
};
}
async checkFileExists(imagePath) {
try {
// Try to browse the file's directory and check if file is listed
const pathParts = imagePath.split('/');
const fileName = pathParts.pop();
const dirPath = pathParts.join('/') || '.';
const browse = await FilePicker.browse('data', dirPath);
return browse.files.some(file => file.endsWith(fileName));
} catch (error) {
console.warn(`Cannot browse directory for ${imagePath}:`, error);
return false;
}
}
async checkFileAccessible(imagePath) {
try {
// Try to create a test image element to verify accessibility
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(true);
img.onerror = () => resolve(false);
img.src = imagePath;
// Timeout after 30 seconds
setTimeout(() => resolve(false), 30000);
});
} catch (error) {
return false;
}
}
getFileExtension(path) {
const parts = path.split('.');
return parts.length > 1 ? '.' + parts.pop().toLowerCase() : null;
}
getImageFormat(path) {
return this.getFileExtension(path);
}
getCachedValidation(imagePath) {
if (this.validationCache.has(imagePath)) {
const cached = this.validationCache.get(imagePath);
if (Date.now() - cached.timestamp < this.cacheTTL) {
return cached.result;
}
this.validationCache.delete(imagePath);
}
return null;
}
setCachedValidation(imagePath, result) {
this.validationCache.set(imagePath, {
result: result,
timestamp: Date.now()
});
}
clearValidationCache() {
this.validationCache.clear();
}
}
// Validation result interface
export class ValidationResult {
constructor(isValid, error = null, code = null, suggestion = null) {
this.isValid = isValid;
this.error = error;
this.code = code;
this.suggestion = suggestion;
}
}
This class includes methods to validate image paths, file extensions, and file accessibility. It also incorporates a caching mechanism to improve performance and reduce redundant file system checks. The validateImagePath
method is the core of this service, orchestrating the various validation steps. It first checks the cache for previous results, then performs the validation, and finally caches the result for future use. This approach ensures that we're not constantly re-validating the same images, which can save a lot of resources. Think of it as a bouncer for our images – only the valid ones get through!
2. Integration with CRUD Operations
Next, we'll integrate the ImageValidator
with our Generic CRUD Tools (scripts/core/generic-crud-tools.js
). This will ensure that image validation is performed during document creation and modification operations. This is where we put the validation service to work, making sure that every new or updated document has valid images. We're enhancing the GenericCRUDTools class to include image validation as a crucial step in the document lifecycle.
import { ImageValidator } from './image-validator.js';
export class GenericCRUDTools {
constructor(discoveryEngine) {
this.discoveryEngine = discoveryEngine;
this.imageValidator = new ImageValidator();
}
async createDocument(documentType, data) {
try {
// Validate images before document creation
await this.validateDocumentImages(data, documentType);
const { collection, subtype } = await this.discoveryEngine.normalizeDocumentType(documentType);
if (subtype) {
data.type = subtype;
}
const DocumentClass = CONFIG[collection]?.documentClass;
if (!DocumentClass) {
throw new Error(`No document class found for collection: ${collection}`);
}
const result = await DocumentClass.create(data);
ui.notifications.info(`Created ${collection}${subtype ? ` (${subtype})` : ''}: ${result.name}`);
return result;
} catch (error) {
ui.notifications.error(`Failed to create ${documentType}: ${error.message}`);
throw error;
}
}
async updateDocument(documentType, documentId, updateData) {
try {
// Validate images in update data
if (this.hasImageFields(updateData)) {
await this.validateDocumentImages(updateData, documentType);
}
// ... existing update logic
const result = await document.update(updateData);
return result;
} catch (error) {
ui.notifications.error(`Failed to update ${documentType}: ${error.message}`);
throw error;
}
}
async validateDocumentImages(data, documentType) {
const imageFields = this.extractImageFields(data);
for (const [fieldPath, imagePath] of imageFields) {
if (imagePath) { // Only validate non-empty paths
const validation = await this.imageValidator.validateImagePath(imagePath, documentType);
if (!validation.isValid) {
throw new ImageValidationError(
`Invalid image for field ${fieldPath}: ${validation.error}`,
{
field: fieldPath,
imagePath: imagePath,
validationResult: validation,
documentType: documentType
}
);
}
// Log warnings if present
if (validation.warnings) {
validation.warnings.forEach(warning => {
console.warn(`Simulacrum | Image warning for ${fieldPath}: ${warning}`);
});
}
}
}
}
extractImageFields(data, prefix = '') {
const imageFields = [];
for (const [key, value] of Object.entries(data)) {
const fieldPath = prefix ? `${prefix}.${key}` : key;
// Check if this is an image field
if (this.isImageField(key, value)) {
imageFields.push([fieldPath, value]);
}
// Recursively check nested objects
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
imageFields.push(...this.extractImageFields(value, fieldPath));
}
}
return imageFields;
}
isImageField(fieldName, value) {
if (typeof value !== 'string') return false;
// Common image field names
const imageFieldNames = ['img', 'image', 'avatar', 'token', 'background', 'icon', 'texture'];
if (imageFieldNames.includes(fieldName.toLowerCase())) return true;
// Check if value looks like an image path
if (value.match(/\.(jpe?g|png|gif|bmp|tiff?|webp|svg|ico|apng|avif|jxl)$/i)) return true;
return false;
}
hasImageFields(data) {
return this.extractImageFields(data).length > 0;
}
}
// Custom error class for image validation
export class ImageValidationError extends Error {
constructor(message, context) {
super(message);
this.name = 'ImageValidationError';
this.context = context;
}
}
This integration involves validating images before document creation and during updates. We've added methods to extract image fields from the data, check if a field is an image field, and handle image validation errors. The validateDocumentImages
method is the key here, ensuring that all images associated with a document are valid before any operations proceed. We've also included a custom error class, ImageValidationError
, to provide more context when things go wrong. This helps us (and the AI) understand exactly what the issue is and how to fix it.
3. Integration with AI Retry System (Issue #15)
To ensure a seamless experience with our AI, we'll integrate the image validation process with our AI Retry System. This integration will allow the AI to automatically retry document creation or modification with corrected image paths if validation fails. It's like having a safety net for the AI, ensuring that it can recover from image-related errors without human intervention. We're enhancing the validation error recovery mechanism to specifically handle image validation failures.
buildValidationErrorPrompt(context) {
let prompt = `VALIDATION ERROR CORRECTION NEEDED:\n\n`;
// Handle image validation errors specifically
if (context.error instanceof ImageValidationError) {
prompt += this.buildImageValidationPrompt(context);
} else {
prompt += this.buildGeneralValidationPrompt(context);
}
return prompt;
}
buildImageValidationPrompt(context) {
const imageContext = context.error.context;
return `
Your previous tool call failed IMAGE VALIDATION:
- Tool: ${context.tool}
- Document Type: ${context.documentType}
- Field: ${imageContext.field}
- Invalid Image Path: ${imageContext.imagePath}
- Error: ${imageContext.validationResult.error}
- Error Code: ${imageContext.validationResult.code}
SUGGESTED FIX: ${imageContext.validationResult.suggestion}
ORIGINAL DATA YOU PROVIDED:
${JSON.stringify(context.originalData, null, 2)}
IMAGE VALIDATION REQUIREMENTS:
1. Image paths must not be empty or null
2. Must use supported formats: .jpg, .jpeg, .png, .gif, .bmp, .webp, .svg, etc.
3. Image file must exist and be accessible
4. Path should be relative to FoundryVTT data directory
RECOMMENDATIONS:
- Use the list_images tool to find valid image paths
- For ${context.documentType} documents, consider images in: tokens/, actors/, items/, scenes/
- Verify the image exists before using it
- Use default images if no specific image is needed
Please provide corrected document data with a valid image path or remove the image field to use defaults.
Respond with corrected tool call parameters:
{
"corrected_data": {
// Your corrected document data with valid image path
},
"explanation": "Fixed image validation issue by [your fix]"
}
`;
}
This integration involves building a specific prompt for image validation errors, providing the AI with detailed information about the error and how to correct it. The prompt includes the tool used, document type, field, invalid image path, error details, and a suggested fix. It also provides the AI with image validation requirements and recommendations, such as using the list_images
tool to find valid images. This detailed feedback loop ensures that the AI can learn from its mistakes and improve over time. It’s like having a tutor for the AI, guiding it towards perfect image selection.
4. Integration with list_images Tool (Issue #16)
To further assist the AI in finding valid images, we'll enhance our AI System Prompt to include specific instructions on using the list_images
tool. This will encourage the AI to proactively search for valid images before attempting document creation or modification. This is about giving the AI the tools it needs to succeed, making it more efficient and effective.
IMAGE VALIDATION REQUIREMENTS:
When creating or updating documents with image fields, you MUST ensure:
1. Image paths are not empty or null
2. Images exist and are accessible
3. Use supported image formats
If you need to find valid images, use the list_images tool first:
- list_images({keyword: "*dragon*"}) to find dragon images
- list_images({keyword: "token_*"}) to find token images
- list_images({directories: ["user"], keyword: "*.png"}) for user PNG images
ALWAYS verify image paths before using them in document creation.
This enhancement adds clear requirements for image validation and provides examples of how to use the list_images
tool. It emphasizes the importance of verifying image paths before using them in document creation, helping the AI to avoid validation errors in the first place. It’s like giving the AI a map before sending it on a journey, ensuring that it knows where to go and how to get there.
Error Handling & User Experience
Effective error handling and a positive user experience are paramount. We'll ensure that validation error messages are clear, concise, and provide helpful suggestions for correction. This is all about making the system user-friendly, even when things go wrong. We want to guide users towards a solution, not leave them scratching their heads.
Validation Error Messages
We'll use clear and informative error messages to communicate validation failures to the user. These messages will include the field that caused the error, the specific issue, and a suggestion for how to fix it. It’s like having a friendly assistant that tells you exactly what’s wrong and how to make it right.
// Examples of clear error messages
"Invalid image for field 'img': Image file not found: tokens/invalid.png"
"Invalid image for field 'token': Image path cannot be empty or null"
"Invalid image for field 'avatar': Unsupported image format: .txt"
AI Integration Example
To illustrate how the image validation process will work with the AI, let’s consider an example scenario. This example shows how the AI can use the validation system to create documents with valid images, ensuring a smooth and error-free process. It’s like watching the AI in action, showcasing its ability to create beautiful and functional documents.
User: "Create a dragon NPC with a red dragon image"
AI: Uses list_images({keyword: "*red*dragon*"})
AI: Finds "tokens/red_dragon.png"
AI: Uses create_document with validated image path
System: Validates image exists and is accessible
Result: Document created successfully with valid image
Performance Considerations
To ensure optimal performance, we'll implement validation caching and asynchronous validation operations. This will minimize the impact of image validation on system performance, ensuring that things run smoothly and efficiently. We don’t want image validation to slow things down, so we’re taking steps to make it as fast as possible.
Validation Caching
We'll cache validation results for a period of 12 hours to avoid redundant file system checks. This will significantly reduce the number of times we need to validate the same images, improving overall performance. Think of it as a shortcut – once we know an image is valid, we remember it for a while.
- Cache validation results for 12 hours
- Avoid redundant file system checks
- Clear cache when files are modified
Async Validation
We'll use non-blocking validation operations to prevent delays. This will allow the system to continue processing other tasks while image validation is in progress. We'll also batch validate multiple images to further improve efficiency. And to protect against network issues, we'll implement a 30-second timeout for accessibility checks. It’s like having multiple workers handling different tasks simultaneously, ensuring that everything gets done quickly and efficiently.
- Non-blocking validation operations
- Batch validate multiple images
- 30-second timeout protection for network checks
Acceptance Criteria
To ensure that our image validation system meets our requirements, we've defined a set of acceptance criteria. These criteria serve as a checklist, ensuring that we've covered all the bases and delivered a high-quality solution. It’s like having a blueprint for success, guiding us towards a perfect outcome.
- [ ] All document creation validates image fields
- [ ] All document updates validate changed image fields
- [ ] Empty/null image paths are rejected with clear errors
- [ ] Non-existent image files are rejected
- [ ] Unsupported image formats are rejected
- [ ] Validation results are cached for 12 hours
- [ ] Integration with AI retry system (Issue #15)
- [ ] Clear error messages with suggestions
- [ ] Context-aware validation (different rules for Actors vs Items)
- [ ] Graceful handling of permission/access errors
- [ ] 30-second timeout for accessibility checks
Priority
This feature is of High priority, as it is critical for maintaining data integrity and user experience. Preventing broken images and providing clear feedback for correction is essential for a polished and professional system. This is a top priority, folks – we need to get this done and get it done right!
Labels
feature
- New validation systemimage-validation
- Image file validationdocument-integrity
- Data quality assuranceai-integration
- Works with AI retry systemuser-experience
- Clear error feedback
🤖 Generated with Claude Code