Uploading many docs (or a folder) previously spawned an ingestion subprocess per file all at once, thrashing the GPU/CPU. Add a configurable concurrency limit and a folder picker. - ragIndexConcurrency setting (default 1) in the chat runtime store, persisted like the other RAG scalar settings; exposed as a 'Parallel indexing' slider (1-8) at the bottom of the sidebar Retrieval section. - New rag-index-queue.ts semaphore: each document upload acquires a slot before it starts and releases it once its ingestion job finishes (complete / error / already-indexed), so bulk uploads drain at the configured rate. Wired into both composer upload paths (use-thread-doc-uploads + shared-composer). - Folder upload: a second 'Attach a folder' button on the RAG attach control uses a webkitdirectory input; every compatible file is routed through the same queue. Multi-file select already worked (the input has 'multiple' and loops addDoc). - Content-hash dedup (shipped earlier) means re-scanning a folder skips already-indexed files. Not build/UI verified here (no bun); needs bun typecheck + a browser check of bulk/folder upload draining at the set concurrency.
41 lines
1.3 KiB
TypeScript
41 lines
1.3 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-only
|
|
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
|
|
import { useChatRuntimeStore } from "../stores/chat-runtime-store";
|
|
|
|
/** Bounds how many documents index in parallel. Each RAG upload acquires a
|
|
* slot before it starts and releases it once its ingestion job finishes
|
|
* (complete/error/already-indexed), so bulk/folder uploads drain at the
|
|
* user-configured `ragIndexConcurrency` rate instead of spawning a
|
|
* subprocess per file all at once. Module-scoped singleton — shared across
|
|
* both composer surfaces. */
|
|
|
|
let active = 0;
|
|
const waiters: Array<() => void> = [];
|
|
|
|
function limit(): number {
|
|
const n = useChatRuntimeStore.getState().ragIndexConcurrency;
|
|
return Math.max(1, Number.isFinite(n) ? Math.round(n) : 1);
|
|
}
|
|
|
|
function admitWaiters(): void {
|
|
while (waiters.length > 0 && active < limit()) {
|
|
active += 1;
|
|
const next = waiters.shift();
|
|
next?.();
|
|
}
|
|
}
|
|
|
|
/** Resolves once a slot is free (immediately if under the limit). */
|
|
export function acquireIndexSlot(): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
waiters.push(resolve);
|
|
admitWaiters();
|
|
});
|
|
}
|
|
|
|
/** Release a previously-acquired slot and admit the next waiter. */
|
|
export function releaseIndexSlot(): void {
|
|
active = Math.max(0, active - 1);
|
|
admitWaiters();
|
|
}
|