perf(import): adjustable concurrency and skip redundant SWF downloads
Raise batch concurrency (furni 3->12, clone 10->12) with a Speed control next to Translate. Skip the SWF download when a .nitro bundle already exists on disk (color variants share the base nitro), and stop flagging that as a failed download.
This commit is contained in:
1 parent
641b6b8cb8
commit
2a708b01b1
5 files changed
+42
-5
No files matched your search
@@ -43,7 +43,7 @@ export const POST = withAdmin(
|
||||
async (request, ctx) => {
|
||||
const body = await request.json();
|
||||
const rawItems: BatchItem[] = body.items || [];
|
||||
const concurrency = Math.min(Math.max(body.concurrency || 1, 1), 3);
|
||||
const concurrency = Math.min(Math.max(body.concurrency || 3, 1), 12);
|
||||
const sourceId: string | undefined = body.sourceId;
|
||||
|
||||
if (rawItems.length === 0) return apiError("No items to import", 400);
|
||||
|
||||
@@ -269,6 +269,8 @@ export function StudioClient({
|
||||
null,
|
||||
);
|
||||
const batchAbortRef = useRef<AbortController | null>(null);
|
||||
// How many items the batch endpoints process in parallel.
|
||||
const [concurrency, setConcurrency] = useState(4);
|
||||
|
||||
// Nitro editor
|
||||
const [editingClassname, setEditingClassname] = useState<string | null>(null);
|
||||
@@ -834,7 +836,7 @@ export function StudioClient({
|
||||
revision: i.revision,
|
||||
category: i.category,
|
||||
})),
|
||||
concurrency: 3,
|
||||
concurrency,
|
||||
translate,
|
||||
langs:
|
||||
translate && translateLangs.length > 0 ? translateLangs : undefined,
|
||||
@@ -979,7 +981,7 @@ export function StudioClient({
|
||||
body: JSON.stringify({
|
||||
sourceId: activeSource,
|
||||
classnames: failed,
|
||||
concurrency: 10,
|
||||
concurrency,
|
||||
final: true,
|
||||
}),
|
||||
signal: abort.signal,
|
||||
@@ -1561,6 +1563,33 @@ export function StudioClient({
|
||||
</summary>
|
||||
<div className="flex max-h-48 flex-wrap items-center gap-3 overflow-auto py-3">
|
||||
{" "}
|
||||
{/* ── Import speed (parallel items per batch) ─────────── */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-medium text-[var(--admin-text-muted)]">
|
||||
Speed
|
||||
</span>
|
||||
<Select
|
||||
value={String(concurrency)}
|
||||
onValueChange={(value) =>
|
||||
setConcurrency(Math.max(1, Number(value)))
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
aria-label="Import speed"
|
||||
className="h-8 w-24 text-xs"
|
||||
>
|
||||
<SelectValue>{`${concurrency} parallel`}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">1 (gentle)</SelectItem>
|
||||
<SelectItem value="2">2</SelectItem>
|
||||
<SelectItem value="4">4 (recommended)</SelectItem>
|
||||
<SelectItem value="6">6</SelectItem>
|
||||
<SelectItem value="8">8</SelectItem>
|
||||
<SelectItem value="12">12 (turbo)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{/* ── Translate control (per-import) ─────────────── */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-8 items-center gap-1.5 rounded-md border border-[var(--admin-border)] px-2.5">
|
||||
|
||||
@@ -152,6 +152,7 @@ describe("completion of existing furniture", () => {
|
||||
|
||||
it("imports recycler_kintsugiB with an allocated ID throughout SQL and furnidata", async () => {
|
||||
vi.clearAllMocks();
|
||||
mocks.nitroExists = false;
|
||||
const selections = [[], [{ itemName: "recycler_kintsugi" }]];
|
||||
mocks.select.mockImplementation(() => ({
|
||||
from: () => ({ where: () => ({ limit: async () => selections.shift() }) }),
|
||||
|
||||
@@ -711,7 +711,13 @@ export async function importSingleFurni(params: {
|
||||
iconFailure = detail;
|
||||
warnings.push(`Icon download failed: ${detail}`);
|
||||
}),
|
||||
params.providedNitro || (params.preserveAssets && existsSync(nitroPath))
|
||||
// Skip the SWF download entirely when a .nitro bundle already exists on
|
||||
// disk — this happens for colour variants that share the base classname's
|
||||
// nitro file. The SWF is only used for Nitro conversion (not needed) and
|
||||
// icon extraction (the nitro bundle is used as a fallback for that below).
|
||||
existsSync(/*turbopackIgnore: true*/ nitroPath) ||
|
||||
params.providedNitro ||
|
||||
(params.preserveAssets && existsSync(nitroPath))
|
||||
? Promise.resolve(false)
|
||||
: (preservingExisting || params.preserveAssets) && existsSync(swfPath)
|
||||
? Promise.resolve(true)
|
||||
@@ -766,6 +772,7 @@ export async function importSingleFurni(params: {
|
||||
!swfOk &&
|
||||
!nitroDownloadOk &&
|
||||
!params.providedNitro &&
|
||||
!existsSync(/*turbopackIgnore: true*/ nitroPath) &&
|
||||
!(params.preserveAssets && existsSync(nitroPath))
|
||||
)
|
||||
warnings.push(`SWF download failed: ${swfFailure || "unknown cause"}`);
|
||||
|
||||
@@ -33,7 +33,7 @@ export interface RunSseBatchOptions<T> {
|
||||
*/
|
||||
export function runSseBatch<T>(opts: RunSseBatchOptions<T>): Response {
|
||||
const { items, labelOf, worker, signal, flush } = opts;
|
||||
const concurrency = Math.min(Math.max(opts.concurrency || 3, 1), 10);
|
||||
const concurrency = Math.min(Math.max(opts.concurrency || 3, 1), 12);
|
||||
const encoder = new TextEncoder();
|
||||
const ac = new AbortController();
|
||||
|
||||
|
||||
Reference in new issue
Block a user