feat: frontend unlimited — raise all client-side caps and timeouts

- XHR timeouts: doubled across all tool categories (120s/300s/600s)
- Pipeline timeout: 180s → 600s
- Favicon/PDF/barcode timeouts raised to 300s
- Files page: fetch 200 per page
- Split grid: 20 → 100 max columns/rows
- Image viewer zoom: 25-300% → 10-1000%
- Collage zoom: 3x → 10x, pan: ±100 → ±200
- Passport photo: face adjust ±15% → ±30%, zoom 3x → 5x
This commit is contained in:
ashim-hq
2026-04-20 21:52:41 +08:00
parent ee8e9861a7
commit 7dd6023b8e
10 changed files with 21 additions and 21 deletions
@@ -27,7 +27,7 @@ interface ImageViewerProps {
imageWrapperStyle?: React.CSSProperties;
}
const ZOOM_STEPS = [25, 50, 75, 100, 125, 150, 200, 300];
const ZOOM_STEPS = [10, 25, 50, 75, 100, 150, 200, 300, 500, 1000];
const DEFAULT_ZOOM = 100;
export function ImageViewer({
@@ -72,7 +72,7 @@ function scanOneFile(
formData.append("settings", JSON.stringify({ tryHarder }));
const xhr = new XMLHttpRequest();
xhr.timeout = 60_000;
xhr.timeout = 300_000;
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) onUploadProgress((e.loaded / e.total) * 100);
@@ -349,8 +349,8 @@ function CollageCell({
if (first) memo = { panX: transform.panX, panY: transform.panY };
const rect = cellRef.current?.getBoundingClientRect();
if (!rect || !memo) return memo;
const panX = Math.max(-100, Math.min(100, memo.panX + (mx / rect.width) * 100));
const panY = Math.max(-100, Math.min(100, memo.panY + (my / rect.height) * 100));
const panX = Math.max(-200, Math.min(200, memo.panX + (mx / rect.width) * 100));
const panY = Math.max(-200, Math.min(200, memo.panY + (my / rect.height) * 100));
store.setCellTransform(cellIndex, { panX, panY });
return memo;
},
@@ -360,11 +360,11 @@ function CollageCell({
const bindPinch = usePinch(
({ offset: [scale] }) => {
if (!image || !isSelected) return;
const zoom = Math.max(1, Math.min(3, scale));
const zoom = Math.max(1, Math.min(10, scale));
store.setCellTransform(cellIndex, { zoom });
},
{
scaleBounds: { min: 1, max: 3 },
scaleBounds: { min: 1, max: 10 },
from: () => [transform.zoom, 0],
},
);
@@ -378,7 +378,7 @@ function CollageCell({
const handleWheel = (e: WheelEvent) => {
e.preventDefault();
const delta = e.deltaY > 0 ? -0.1 : 0.1;
const zoom = Math.max(1, Math.min(3, zoomRef.current + delta));
const zoom = Math.max(1, Math.min(10, zoomRef.current + delta));
store.setCellTransform(cellIndex, { zoom });
};
el.addEventListener("wheel", handleWheel, { passive: false });
@@ -555,7 +555,7 @@ function CollageCell({
<input
type="range"
min="1"
max="3"
max="10"
step="0.1"
value={transform.zoom}
onChange={handleZoomSlider}
@@ -73,7 +73,7 @@ export function FaviconSettings() {
const xhr = new XMLHttpRequest();
xhrRef.current = xhr;
xhr.responseType = "blob";
xhr.timeout = 180_000;
xhr.timeout = 300_000;
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
@@ -167,7 +167,7 @@ export function ImageToPdfSettings() {
const xhr = new XMLHttpRequest();
xhrRef.current = xhr;
xhr.timeout = 180_000;
xhr.timeout = 300_000;
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
@@ -1062,8 +1062,8 @@ export function PassportPhotoPreview() {
if (!dragStartRef.current) return;
const dx = (e.clientX - dragStartRef.current.x) * 0.001;
const dy = (e.clientY - dragStartRef.current.y) * 0.001;
setAdjustX(Math.max(-0.15, Math.min(0.15, dragStartRef.current.ax - dx)));
setAdjustY(Math.max(-0.15, Math.min(0.15, dragStartRef.current.ay - dy)));
setAdjustX(Math.max(-0.3, Math.min(0.3, dragStartRef.current.ax - dx)));
setAdjustY(Math.max(-0.3, Math.min(0.3, dragStartRef.current.ay - dy)));
}
function handleMouseUp() {
@@ -1083,7 +1083,7 @@ export function PassportPhotoPreview() {
const handleWheel = useCallback(
(e: React.WheelEvent<HTMLCanvasElement>) => {
e.preventDefault();
setZoom(Math.max(0.5, Math.min(3, zoom + (e.deltaY > 0 ? -0.1 : 0.1))));
setZoom(Math.max(0.5, Math.min(5, zoom + (e.deltaY > 0 ? -0.1 : 0.1))));
},
[zoom, setZoom],
);
@@ -1138,7 +1138,7 @@ export function PassportPhotoPreview() {
</span>
<button
type="button"
onClick={() => setZoom(Math.min(3, zoom + 0.25))}
onClick={() => setZoom(Math.min(5, zoom + 0.25))}
className="p-1.5 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
title="Zoom in"
>
+2 -2
View File
@@ -86,8 +86,8 @@ export function usePipelineProcessor() {
const xhr = new XMLHttpRequest();
xhrRef.current = xhr;
// Pipeline runs multiple steps sequentially, allow up to 3 minutes
xhr.timeout = 180_000;
// Pipeline runs multiple steps sequentially, allow up to 10 minutes
xhr.timeout = 600_000;
// Pipeline is always "medium" speed: upload = 0-40%, processing = 40-95%
const UPLOAD_WEIGHT = 40;
+2 -2
View File
@@ -153,8 +153,8 @@ export function useToolProcessor(toolId: string) {
const xhr = new XMLHttpRequest();
xhrRef.current = xhr;
// 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;
// Timeout: 2 min for fast tools, 5 min for medium (seam carving), 10 min for AI
xhr.timeout = isAiTool ? 600_000 : isMediumTool ? 300_000 : 120_000;
// For AI tools: upload = 0-15%, processing = 15-100% (SSE-driven)
// For medium tools: upload = 0-40%, processing = 40-95% (gradual fill)
+1 -1
View File
@@ -35,7 +35,7 @@ export const useFilesPageStore = create<FilesPageState>((set, get) => ({
set({ loading: true, error: null });
try {
const { searchQuery } = get();
const result = await apiListFiles({ search: searchQuery || undefined, limit: 100 });
const result = await apiListFiles({ search: searchQuery || undefined, limit: 200 });
set({ files: result.files, total: result.total, loading: false });
} catch (err) {
set({ error: err instanceof Error ? err.message : "Failed to load files", loading: false });
+2 -2
View File
@@ -68,8 +68,8 @@ export const useSplitStore = create<SplitState>((set, get) => ({
setMode: (mode) => set({ mode, tiles: [], zipBlobUrl: null, error: null }),
setColumns: (columns) =>
set({ columns: Math.max(1, Math.min(20, columns)), tiles: [], zipBlobUrl: null }),
setRows: (rows) => set({ rows: Math.max(1, Math.min(20, rows)), tiles: [], zipBlobUrl: null }),
set({ columns: Math.max(1, Math.min(100, columns)), tiles: [], zipBlobUrl: null }),
setRows: (rows) => set({ rows: Math.max(1, Math.min(100, rows)), tiles: [], zipBlobUrl: null }),
setTileWidth: (tileWidth) =>
set({ tileWidth: Math.max(10, tileWidth), tiles: [], zipBlobUrl: null }),
setTileHeight: (tileHeight) =>