feat(aurora-smart): add dual-stack orchestration with policy, audit, and UI toggle

This commit is contained in:
Apple
2026-03-01 06:21:17 -08:00
parent 5b4c4f92ba
commit 1ea4464838
2 changed files with 918 additions and 5 deletions

View File

@@ -836,6 +836,35 @@
</div>
</details>
<div style="margin-top:10px; border:1px solid rgba(255,255,255,0.08); border-radius:8px; padding:8px; background:var(--bg2);">
<div class="aurora-note" style="margin-top:0;">Smart Orchestrator (Dual Stack)</div>
<label class="aurora-checkline" style="margin-top:5px;">
<input type="checkbox" id="auroraSmartEnabled" checked>
Auto (Aurora + Kling when needed)
</label>
<div class="aurora-export-grid" style="margin-top:8px;">
<label>Strategy
<select id="auroraSmartStrategy">
<option value="auto" selected>Auto</option>
<option value="local_only">Local only</option>
<option value="local_then_kling">Local then Kling</option>
</select>
</label>
<label>Budget
<select id="auroraSmartBudget">
<option value="low">Low</option>
<option value="normal" selected>Normal</option>
<option value="high">High</option>
</select>
</label>
</div>
<label class="aurora-checkline" style="margin-top:6px;">
<input type="checkbox" id="auroraSmartPreferQuality" checked>
Prefer quality over speed
</label>
<div id="auroraSmartHint" class="aurora-note">policy: standby</div>
</div>
<div style="display:flex; gap:8px; margin-top:12px;">
<button id="auroraAnalyzeBtn" class="btn btn-ghost" onclick="auroraAnalyze()" disabled>🔍 Аналіз</button>
<button id="auroraAudioProcessBtn" class="btn btn-ghost" style="display:none;" onclick="auroraStartAudio()">🎧 Audio process</button>
@@ -847,6 +876,8 @@
<div class="aurora-card">
<div class="aurora-title">Job Status</div>
<div class="aurora-kv"><span class="k">Job ID</span><span class="v" id="auroraJobId"></span></div>
<div class="aurora-kv"><span class="k">Smart Run</span><span class="v" id="auroraSmartRunId"></span></div>
<div class="aurora-kv"><span class="k">Smart Policy</span><span class="v" id="auroraSmartPolicy"></span></div>
<div class="aurora-kv"><span class="k">Статус</span><span class="v" id="auroraJobStatus">idle</span></div>
<div class="aurora-kv"><span class="k">Етап</span><span class="v" id="auroraJobStage"></span></div>
<div class="aurora-kv"><span class="k">Черга</span><span class="v" id="auroraQueuePos"></span></div>
@@ -2018,6 +2049,8 @@ let currentAudio = null;
let auroraMode = 'tactical';
let auroraSelectedFile = null;
let auroraJobId = null;
let auroraSmartRunId = null;
let auroraSmartStatusCache = null;
let auroraPollTimer = null;
let auroraResultCache = null;
let auroraAnalysisCache = null;
@@ -2035,8 +2068,10 @@ let auroraChatBusy = false;
let auroraFolderPath = null;
const AURORA_MAX_TRANSIENT_ERRORS = 12;
const AURORA_ACTIVE_JOB_KEY = 'aurora_active_job_id';
const AURORA_SMART_RUN_KEY = 'aurora_smart_run_id';
const AURORA_TIMING_CACHE_PREFIX = 'aurora_timing_cache_v1:';
try { auroraJobId = localStorage.getItem(AURORA_ACTIVE_JOB_KEY) || null; } catch (_) {}
try { auroraSmartRunId = localStorage.getItem(AURORA_SMART_RUN_KEY) || null; } catch (_) {}
let mediaTabBootstrapped = false;
let aistalkTabBootstrapped = false;
let aistalkRunId = null;
@@ -2214,6 +2249,38 @@ function auroraPersistActiveJob() {
} catch (_) {}
}
function auroraPersistSmartRun() {
try {
if (auroraSmartRunId) localStorage.setItem(AURORA_SMART_RUN_KEY, auroraSmartRunId);
else localStorage.removeItem(AURORA_SMART_RUN_KEY);
} catch (_) {}
}
function auroraSetSmartRunId(runId) {
const normalized = String(runId || '').trim();
auroraSmartRunId = normalized || null;
const el = document.getElementById('auroraSmartRunId');
if (el) el.textContent = auroraSmartRunId || '—';
auroraPersistSmartRun();
}
function auroraSetSmartPolicyText(text) {
const el = document.getElementById('auroraSmartPolicy');
const hint = document.getElementById('auroraSmartHint');
const msg = String(text || '').trim() || '—';
if (el) el.textContent = msg;
if (hint) hint.textContent = `policy: ${msg}`;
}
function auroraSmartConfig() {
return {
enabled: Boolean(document.getElementById('auroraSmartEnabled')?.checked),
strategy: document.getElementById('auroraSmartStrategy')?.value || 'auto',
budget_tier: document.getElementById('auroraSmartBudget')?.value || 'normal',
prefer_quality: Boolean(document.getElementById('auroraSmartPreferQuality')?.checked),
};
}
function auroraTimingCacheKey(jobId) {
const id = String(jobId || '').trim();
return id ? `${AURORA_TIMING_CACHE_PREFIX}${id}` : '';
@@ -2301,6 +2368,9 @@ function auroraSetSelectedFile(file) {
auroraSuggestedPriority = 'balanced';
auroraSuggestedExport = {};
auroraPresetMode = 'balanced';
auroraSetSmartRunId(null);
auroraSmartStatusCache = null;
auroraSetSmartPolicyText('standby');
auroraResetAnalysisControls();
auroraUpdateQueuePosition(null);
auroraUpdateStorage(null);
@@ -2474,6 +2544,9 @@ function auroraSelectJob(jobId) {
const id = String(jobId || '').trim();
if (!id) return;
auroraSetActiveJobId(id);
auroraSetSmartRunId(null);
auroraSmartStatusCache = null;
auroraSetSmartPolicyText('manual open');
auroraStatusCache = null;
auroraResultCache = null;
auroraPollErrorCount = 0;
@@ -2491,6 +2564,9 @@ function auroraSelectJob(jobId) {
function auroraClearActiveJob() {
auroraStopPolling();
auroraSetActiveJobId(null);
auroraSetSmartRunId(null);
auroraSmartStatusCache = null;
auroraSetSmartPolicyText('—');
auroraStatusCache = null;
auroraResultCache = null;
auroraLastProgress = 0;
@@ -3165,6 +3241,9 @@ async function auroraReprocess(options) {
}
const data = await r.json();
auroraSetActiveJobId(data.job_id);
auroraSetSmartRunId(null);
auroraSmartStatusCache = null;
auroraSetSmartPolicyText('audio local');
auroraStatusCache = null;
auroraResultCache = null;
auroraPollErrorCount = 0;
@@ -3301,6 +3380,9 @@ async function auroraStartAudio() {
}
const data = await r.json();
auroraSetActiveJobId(data.job_id);
auroraSetSmartRunId(null);
auroraSmartStatusCache = null;
auroraSetSmartPolicyText('reprocess local');
auroraStatusCache = null;
auroraResultCache = null;
auroraPollErrorCount = 0;
@@ -3331,6 +3413,29 @@ function auroraStopPolling() {
auroraPollInFlight = false;
}
async function auroraPollSmartStatus({ quiet = true } = {}) {
if (!auroraSmartRunId) return null;
try {
const r = await fetch(`${API}/api/aurora/process-smart/${encodeURIComponent(auroraSmartRunId)}`);
if (r.status === 404) {
auroraSetSmartRunId(null);
auroraSmartStatusCache = null;
auroraSetSmartPolicyText('—');
return null;
}
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const smart = await r.json();
auroraSmartStatusCache = smart;
const strategy = smart?.policy?.strategy || 'auto';
const phase = smart?.phase || smart?.status || '—';
auroraSetSmartPolicyText(`${strategy} · ${phase}`);
return smart;
} catch (e) {
if (!quiet) console.warn('aurora smart status error:', e);
return auroraSmartStatusCache;
}
}
async function auroraPollStatus() {
if (!auroraJobId || auroraPollInFlight) return;
auroraPollInFlight = true;
@@ -3346,6 +3451,7 @@ async function auroraPollStatus() {
}
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const st = await r.json();
const smart = await auroraPollSmartStatus({ quiet: true });
auroraStatusCache = st;
auroraPollErrorCount = 0;
const cachedTiming = auroraGetPersistedTiming(auroraJobId) || {};
@@ -3369,10 +3475,24 @@ async function auroraPollStatus() {
const reBtn = document.getElementById('auroraReprocessBtn');
if (reBtn) reBtn.disabled = !(st.status === 'completed' || st.status === 'failed' || st.status === 'cancelled');
if (st.status === 'completed') {
const smartActive = smart && !['completed', 'failed', 'cancelled'].includes(String(smart.status || '').toLowerCase());
if (smartActive) {
if (!auroraResultCache) {
await auroraLoadResult(auroraJobId);
}
const kStat = smart?.kling?.status ? ` · Kling ${smart.kling.status}` : '';
auroraSetProgress(99, 'processing', `smart orchestration (${smart.phase || 'running'}${kStat})`);
const cancelBtn = document.getElementById('auroraCancelBtn');
if (cancelBtn) cancelBtn.style.display = 'none';
return;
}
auroraStopPolling();
await auroraLoadResult(auroraJobId);
const cancelBtn = document.getElementById('auroraCancelBtn');
if (cancelBtn) cancelBtn.style.display = 'none';
if (smart && smart.selected_stack) {
auroraChatAdd('assistant', `Smart run завершено. Selected stack: ${smart.selected_stack}.`);
}
await auroraRefreshJobs();
} else if (st.status === 'failed' || st.status === 'cancelled') {
auroraStopPolling();
@@ -3406,20 +3526,29 @@ async function auroraStart() {
return;
}
const analysisControls = auroraCollectAnalysisControls();
const smartCfg = auroraSmartConfig();
const fd = new FormData();
fd.append('file', auroraSelectedFile);
fd.append('mode', auroraMode);
fd.append('priority', analysisControls.priority || auroraSuggestedPriority || 'balanced');
const uiExport = auroraCollectExportOptions();
const analysisExport = auroraBuildAnalysisExportHints(analysisControls);
fd.append('export_options', JSON.stringify({ ...auroraSuggestedExport, ...uiExport, ...analysisExport }));
const mergedExport = { ...auroraSuggestedExport, ...uiExport, ...analysisExport };
fd.append('export_options', JSON.stringify(mergedExport));
if (smartCfg.enabled) {
fd.append('strategy', smartCfg.strategy || 'auto');
fd.append('prefer_quality', smartCfg.prefer_quality ? 'true' : 'false');
fd.append('budget_tier', smartCfg.budget_tier || 'normal');
fd.append('learning_enabled', 'true');
}
_auroraLogLines = [];
const startBtn = document.getElementById('auroraStartBtn');
const quickStartBtn = document.getElementById('auroraStartFromAnalysisBtn');
if (startBtn) startBtn.disabled = true;
if (quickStartBtn) quickStartBtn.disabled = true;
try {
const r = await fetch(`${API}/api/aurora/upload`, {
const endpoint = smartCfg.enabled ? '/api/aurora/process-smart' : '/api/aurora/upload';
const r = await fetch(`${API}${endpoint}`, {
method: 'POST',
body: fd,
});
@@ -3428,7 +3557,23 @@ async function auroraStart() {
throw new Error(body || `HTTP ${r.status}`);
}
const data = await r.json();
auroraSetActiveJobId(data.job_id);
const localJobId = data.local_job_id || data.job_id;
if (!localJobId) {
throw new Error('job_id missing in response');
}
auroraSetActiveJobId(localJobId);
if (smartCfg.enabled) {
auroraSetSmartRunId(data.smart_run_id || null);
const policyStrategy = data?.policy?.strategy || smartCfg.strategy || 'auto';
const policyScore = Number(data?.policy?.score);
const scoreTxt = Number.isFinite(policyScore) ? ` (${policyScore.toFixed(2)})` : '';
auroraSetSmartPolicyText(`${policyStrategy}${scoreTxt}`);
auroraChatAdd('assistant', `Smart run ${data.smart_run_id || '—'}: strategy=${policyStrategy}`);
} else {
auroraSetSmartRunId(null);
auroraSmartStatusCache = null;
auroraSetSmartPolicyText('manual local');
}
auroraStatusCache = null;
auroraResultCache = null;
auroraPollErrorCount = 0;
@@ -3441,7 +3586,7 @@ async function auroraStart() {
document.getElementById('auroraResultCard').style.display = 'none';
const reBtn = document.getElementById('auroraReprocessBtn');
if (reBtn) reBtn.disabled = true;
auroraSetProgress(1, 'processing', 'dispatching');
auroraSetProgress(1, 'processing', smartCfg.enabled ? 'dispatching smart orchestration' : 'dispatching');
const cancelBtn = document.getElementById('auroraCancelBtn');
if (cancelBtn) cancelBtn.style.display = 'inline-block';
auroraStopPolling();
@@ -3449,7 +3594,7 @@ async function auroraStart() {
await auroraPollStatus();
await auroraRefreshJobs();
} catch (e) {
alert(`Aurora upload error: ${e.message || e}`);
alert(`Aurora start error: ${e.message || e}`);
auroraSetProgress(0, 'failed', 'upload_error');
} finally {
if (startBtn) startBtn.disabled = !auroraSelectedFile;
@@ -3805,6 +3950,10 @@ function auroraInitTab() {
auroraBindDropzone();
auroraRefreshHealth();
auroraUpdatePriorityLabel();
auroraSetSmartRunId(auroraSmartRunId);
if (!auroraSmartRunId) {
auroraSetSmartPolicyText('standby');
}
const quickStartBtn = document.getElementById('auroraStartFromAnalysisBtn');
if (quickStartBtn) quickStartBtn.disabled = !auroraSelectedFile;
if (!auroraTabBootstrapped) {
@@ -3827,6 +3976,15 @@ function auroraInitTab() {
}
auroraUpdateQueuePosition((auroraStatusCache || {}).queue_position || null);
auroraUpdateStorage((auroraStatusCache || {}).storage || null);
if (auroraSmartRunId) {
auroraPollSmartStatus({ quiet: true }).then((smart) => {
if (!smart || typeof smart !== 'object') return;
const localJob = smart?.local?.job_id || null;
if (!auroraJobId && localJob) {
auroraSetActiveJobId(localJob);
}
}).catch(() => {});
}
auroraRefreshJobs();
if (auroraJobId && !auroraPollTimer) {
auroraSetProgress(Math.max(1, auroraLastProgress || 0), 'processing', 'restoring previous job...');