feat(TRUEREF-0022): complete iteration 0 — worker-thread indexing, parallel jobs, SSE progress
- Move IndexingPipeline.run() into Worker Threads via WorkerPool - Add dedicated embedding worker thread with single model instance - Add stage/stageDetail columns to indexing_jobs schema - Create ProgressBroadcaster for SSE channel management - Add SSE endpoints: GET /api/v1/jobs/:id/stream, GET /api/v1/jobs/stream - Replace UI polling with EventSource on repo detail and admin pages - Add concurrency settings UI and API endpoint - Build worker entries separately via esbuild
This commit is contained in:
@@ -66,12 +66,23 @@
|
||||
let saveError = $state<string | null>(null);
|
||||
let saveStatusTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
let concurrencyInput = $state<number>(0);
|
||||
let concurrencySaving = $state(false);
|
||||
let concurrencySaveStatus = $state<'idle' | 'ok' | 'error'>('idle');
|
||||
let concurrencySaveError = $state<string | null>(null);
|
||||
let concurrencySaveStatusTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
$effect(() => {
|
||||
concurrencyInput = data.indexingConcurrency;
|
||||
});
|
||||
|
||||
const currentSettings = $derived(settingsOverride ?? data.settings);
|
||||
const activeProfile = $derived(currentSettings.activeProfile);
|
||||
const activeConfigEntries = $derived(activeProfile?.configEntries ?? []);
|
||||
|
||||
onDestroy(() => {
|
||||
if (saveStatusTimer) clearTimeout(saveStatusTimer);
|
||||
if (concurrencySaveStatusTimer) clearTimeout(concurrencySaveStatusTimer);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -159,6 +170,38 @@
|
||||
void save();
|
||||
}
|
||||
|
||||
async function saveConcurrency() {
|
||||
concurrencySaving = true;
|
||||
concurrencySaveStatus = 'idle';
|
||||
concurrencySaveError = null;
|
||||
try {
|
||||
const res = await fetch('/api/v1/settings/indexing', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ concurrency: concurrencyInput })
|
||||
});
|
||||
if (res.ok) {
|
||||
const updated = await res.json();
|
||||
concurrencyInput = updated.concurrency;
|
||||
concurrencySaveStatus = 'ok';
|
||||
if (concurrencySaveStatusTimer) clearTimeout(concurrencySaveStatusTimer);
|
||||
concurrencySaveStatusTimer = setTimeout(() => {
|
||||
concurrencySaveStatus = 'idle';
|
||||
concurrencySaveStatusTimer = null;
|
||||
}, 3000);
|
||||
} else {
|
||||
const data = await res.json();
|
||||
concurrencySaveStatus = 'error';
|
||||
concurrencySaveError = data.error ?? 'Save failed';
|
||||
}
|
||||
} catch (e) {
|
||||
concurrencySaveStatus = 'error';
|
||||
concurrencySaveError = (e as Error).message;
|
||||
} finally {
|
||||
concurrencySaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getOpenAiProfile(settings: EmbeddingSettingsDto): EmbeddingProfileDto | null {
|
||||
return settings.profiles.find((profile) => profile.providerKind === 'openai-compatible') ?? null;
|
||||
}
|
||||
@@ -482,6 +525,45 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Indexing section -->
|
||||
<div class="space-y-3 rounded-lg border border-gray-200 bg-white p-4">
|
||||
<div>
|
||||
<label for="concurrency" class="block text-sm font-medium text-gray-700">
|
||||
Concurrent Workers
|
||||
</label>
|
||||
<p class="mt-0.5 text-xs text-gray-500">
|
||||
Number of parallel indexing workers. Range: 1 to 8.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<input
|
||||
id="concurrency"
|
||||
type="number"
|
||||
min="1"
|
||||
max="8"
|
||||
inputmode="numeric"
|
||||
bind:value={concurrencyInput}
|
||||
disabled={concurrencySaving}
|
||||
class="w-20 rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none disabled:opacity-50"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onclick={saveConcurrency}
|
||||
disabled={concurrencySaving}
|
||||
class="rounded-lg bg-blue-600 px-3 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{concurrencySaving ? 'Saving…' : 'Save'}
|
||||
</button>
|
||||
|
||||
{#if concurrencySaveStatus === 'ok'}
|
||||
<span class="text-sm text-green-600">✓ Saved</span>
|
||||
{:else if concurrencySaveStatus === 'error'}
|
||||
<span class="text-sm text-red-600">{concurrencySaveError}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save feedback banners -->
|
||||
{#if saveStatus === 'ok'}
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user