feat: full HEIF/HEIC support, content-aware resize performance fix, UI improvements

- Add bidirectional HEIF support: decode (input) and encode (output) via system heif-convert/heif-enc
- Add server-side WebP preview generation for non-browser-previewable formats (HEIC, TIFF)
- Fix content-aware resize failing on HEIF input (decode before passing to caire)
- Fix content-aware resize timeout on large images by downscaling to max 1200px and using JPEG intermediate
- Add HEIF as target format in convert tool
- Add loading spinner for HEIF preview decode in file store
- Fix file picker not accepting HEIF files (explicit .heic,.heif,.hif extensions)
- Extend frontend timeout for medium tools to 180s with 45s progress animation
- Redesign rotate controls with preset buttons and compact flip section
- Remove misleading savings percentage from convert tool
This commit is contained in:
Siddharth Kumar Sah
2026-04-11 23:27:44 +08:00
parent e0869477d4
commit 6f5283019b
18 changed files with 425 additions and 86 deletions
+7 -6
View File
@@ -7,6 +7,7 @@ import { useFileStore } from "@/stores/file-store";
interface ProcessResult {
jobId: string;
downloadUrl: string;
previewUrl?: string;
originalSize: number;
processedSize: number;
savedFileId?: string;
@@ -31,7 +32,7 @@ const AI_PYTHON_TOOLS = new Set<string>(PYTHON_SIDECAR_TOOLS);
// Tools that take a few seconds (not instant like Sharp, not minutes like AI).
// Uses a smoother progress: upload 0-40%, then a gradual fill during processing.
const MEDIUM_TOOLS = new Set(["content-aware-resize"]);
const MEDIUM_TOOLS = new Set(["content-aware-resize", "convert"]);
export function useToolProcessor(toolId: string) {
const {
@@ -141,8 +142,8 @@ export function useToolProcessor(toolId: string) {
const xhr = new XMLHttpRequest();
xhrRef.current = xhr;
// Timeout: 60s for fast/medium tools, 5 min for AI tools
xhr.timeout = isAiTool ? 300_000 : 60_000;
// Timeout: 60s for fast tools, 3 min for medium (seam carving), 5 min for AI
xhr.timeout = isAiTool ? 300_000 : isMediumTool ? 180_000 : 60_000;
// For AI tools: upload = 0-15%, processing = 15-100% (SSE-driven)
// For medium tools: upload = 0-40%, processing = 40-95% (gradual fill)
@@ -167,11 +168,11 @@ export function useToolProcessor(toolId: string) {
stage: isAiTool ? "Starting..." : "Processing...",
}));
// Medium tools: gradually fill from upload weight to 95% over ~15s
// Medium tools: gradually fill from upload weight to 95% over ~45s
if (isMediumTool) {
const start = UPLOAD_WEIGHT;
const target = 95;
const step = (target - start) / 30; // 30 ticks over ~15s
const step = (target - start) / 90; // 90 ticks over ~45s
processingTimerRef.current = setInterval(() => {
setProgress((prev) => {
if (prev.phase !== "processing") return prev;
@@ -194,7 +195,7 @@ export function useToolProcessor(toolId: string) {
try {
const result: ProcessResult = JSON.parse(xhr.responseText);
setJobId(result.jobId);
setProcessedUrl(result.downloadUrl);
setProcessedUrl(result.downloadUrl, result.previewUrl);
setSizes(result.originalSize, result.processedSize);
// Update serverFileId if a new version was saved
if (result.savedFileId) {