Files
artcraft/frontend/libs/api/src/lib/LocalApi.ts
T
BFlat 9f28c525ec Webapp/desktop: batch download video results, refresh credits after failed generations (#1801)
## Changes

**Batch download**
- Add multi-select mode to the create-video results feed (webapp +
desktop) with a "Select" toggle in the topbar, checkboxes on completed
items, and a floating download bar
- Webapp: "Download" bundles selected videos into a single .zip
client-side (new jszip dependency, concurrent fetches, progress and
partial-failure toasts)
- Desktop: "Download" prompts for a folder and saves each selected video
as its own file via native Tauri save, with progress toasts
- Library: add a Download (zip) button to the existing bulk selection
bar
- Shared `ui-generation-list` lib gains a selection store, select
toggle, action bar, and optional selection props on the grid/list views
(existing hosts unaffected)

**Credits refresh on failed generations**
- Webapp: dispatch a credits refresh when the task queue or generation
feed observes a newly failed job (immediately and again after 2.5s so
server-side refunds settle)
- Desktop: also refresh credits when the 5s task poll discovers a
failure that the push event missed, and listen for `credits-change` in
the TopBar

**UI fixes**
- Selection download bar always sits above the promptbox regardless of
its size
- Mobile Generate/History header no longer overlaps the gallery toggles
(flex layout instead of absolute positioning)
- Reference deck top-aligns in the expanded prompt box instead of
centering
- "Play videos with sound" setting now defaults to on
2026-07-23 19:54:12 +07:00

101 lines
2.9 KiB
TypeScript

import { download } from "@tauri-apps/plugin-upload";
import { downloadDir } from "@tauri-apps/api/path";
import { open, save } from "@tauri-apps/plugin-dialog";
const ASK_LOCATION_BEFORE_DOWNLOAD_KEY = "artcraft_ask_location_before_download";
export const getAskLocationBeforeDownload = (): boolean => {
if (typeof window === "undefined") return false;
try {
return window.localStorage.getItem(ASK_LOCATION_BEFORE_DOWNLOAD_KEY) === "true";
} catch {
return false;
}
};
export const setAskLocationBeforeDownload = (enabled: boolean): void => {
if (typeof window === "undefined") return;
try {
if (enabled) {
window.localStorage.setItem(ASK_LOCATION_BEFORE_DOWNLOAD_KEY, "true");
} else {
window.localStorage.removeItem(ASK_LOCATION_BEFORE_DOWNLOAD_KEY);
}
} catch {
// ignore storage failures
}
};
const deriveDownloadFilename = (url: string): string => {
try {
const urlObj = new URL(url);
const last = urlObj.pathname.split("/").pop();
if (last && last.length > 0) return last;
} catch {
// fall through
}
return "downloaded_file";
};
/**
* Prompts the user with a native save dialog if the
* "Ask location before download" setting is on.
*
* Returns:
* - the chosen absolute path when the user picked one
* - `null` when the user dismissed the dialog (caller should abort)
* - `undefined` when the toggle is off (caller should fall back to default)
*/
export const promptDownloadLocationIfNeeded = async (
url: string,
): Promise<string | null | undefined> => {
if (!getAskLocationBeforeDownload()) return undefined;
const filename = deriveDownloadFilename(url);
const chosen = await save({ defaultPath: filename });
return chosen ?? null;
};
/**
* Prompts the user to pick a directory (e.g. for batch downloads).
* Returns the chosen absolute path, or `null` when dismissed.
*/
export const pickDownloadDirectory = async (): Promise<string | null> => {
const chosen = await open({ directory: true, multiple: false });
return typeof chosen === "string" ? chosen : null;
};
/** Downloads `url` to an explicit absolute filesystem path. */
export const downloadUrlToPath = async (url: string, path: string) => {
await download(url, path);
};
export const downloadFileFromUrl = async (url: string) => {
console.log("GOT THE URL", url);
try {
const filename = deriveDownloadFilename(url);
let filePath: string;
const chosen = await promptDownloadLocationIfNeeded(url);
if (chosen === null) {
// User dismissed the picker.
return;
}
if (typeof chosen === "string") {
filePath = chosen;
} else {
const downloadsPath = await downloadDir();
filePath = `${downloadsPath}/${filename}`;
}
await download(url, filePath);
console.log(
`File downloaded and saved to ${filePath}`,
);
} catch (error) {
console.error("Error downloading file:", error);
throw error;
}
};