import re
path = r'd:\Python\Kronos_Platform_V1\frontend\index.html'
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
# 1. Add fetchPaneAI function (or modify fetchAIAnalysis)
# Actually, let's just append fetchPaneAI function before fetchAIAnalysis
# and update the UI binding.
new_fetch_code = """
// --- MULTI-PANE AI FETCH ---
async function fetchPaneAI(pane, options = {}) {
const symbol = pane.symbol;
const interval = pane.interval;
const horizon = pane.horizon || 24;
const requestKey = `${symbol}|${interval}|${horizon}`;
if (!options.force && pane.analysisRequestPromise && pane.analysisRequestKey === requestKey) {
return pane.analysisRequestPromise;
}
if (pane.analysisRetryTimer) {
clearTimeout(pane.analysisRetryTimer);
pane.analysisRetryTimer = null;
}
if (pane.analysisFetchController) {
pane.analysisFetchController.abort();
}
const controller = new AbortController();
pane.analysisFetchController = controller;
pane.analysisRequestKey = requestKey;
if (pane.paneId === Workspace.activePaneId && !pane.lastAnalysis?.payload) {
const panel = document.getElementById('analysisPanel');
panel.innerHTML = `
`;
}
const requestPromise = (async () => {
try {
const fData = await DataCoordinator.fetchForecast(symbol, interval, horizon, controller.signal);
if (pane.symbol !== symbol || pane.interval !== interval) return null;
pane.lastAnalysis = { payload: fData, symbol, interval };
const hasForecast = Array.isArray(fData.forecast) && fData.forecast.length > 0;
if (hasForecast && pane.lastCandleData) {
const forecastPoints = fData.forecast;
const anchorPoint = { time: pane.lastCandleData.time, value: pane.lastCandleData.close };
const futurePoints = forecastPoints.filter(d => d && d.time !== undefined && d.p50 !== undefined && d.time !== pane.lastCandleData.time).map(d => ({ time: d.time, value: d.p50 }));
const p50 = [anchorPoint, ...futurePoints];
const p10 = [anchorPoint, ...forecastPoints.filter(d => d && d.time !== undefined && d.p10 !== undefined && d.time !== pane.lastCandleData.time).map(d => ({ time: d.time, value: d.p10 }))];
const p90 = [anchorPoint, ...forecastPoints.filter(d => d && d.time !== undefined && d.p90 !== undefined && d.time !== pane.lastCandleData.time).map(d => ({ time: d.time, value: d.p90 }))];
if (pane.forecastSeries) {
pane.forecastSeries.p50.setData([]);
pane.forecastSeries.p10.setData(p10);
pane.forecastSeries.p90.setData(p90);
// For segments we need a helper since it's complex, or just ignore segments per-pane to keep it fast
// Actually we can reuse buildForecastSegmentSeries but pass the pane
buildPaneForecastSegments(pane, p50);
}
}
if (pane.paneId === Workspace.activePaneId) {
renderAnalysisPanel(symbol, interval, fData);
renderCompactGauges(symbol, interval, fData);
updateDashboardScale();
} else {
// For non-active panes, render gauges into their mini container
renderPaneGauges(pane, fData);
}
return fData;
} catch (e) {
if (e.name === 'AbortError') return null;
console.error(`[Pane ${pane.paneId}] AI Error:`, e);
return null;
} finally {
if (pane.analysisFetchController === controller) pane.analysisFetchController = null;
}
})();
pane.analysisRequestPromise = requestPromise;
try {
return await requestPromise;
} finally {
if (pane.analysisRequestPromise === requestPromise) {
pane.analysisRequestPromise = null;
pane.analysisRequestKey = null;
}
}
}
function buildPaneForecastSegments(pane, p50) {
if (!pane.forecastSeries || !pane.forecastSeries.segments) return;
const sGroup = pane.forecastSeries.segments;
sGroup.forEach(s => s.setData([]));
if (p50.length < 2) return;
for (let i = 0; i < p50.length - 1; i++) {
if (i >= sGroup.length) {
const ns = pane.chartInstance.addLineSeries({
color: 'rgba(34,211,238,0.8)', lineWidth: 2, lineStyle: 0,
crosshairMarkerVisible: false, lastValueVisible: false, priceLineVisible: false
});
sGroup.push(ns);
}
const pA = p50[i], pB = p50[i+1];
const clr = pB.value >= pA.value ? 'rgba(34,211,238,0.8)' : 'rgba(251,113,133,0.8)';
sGroup[i].applyOptions({ color: clr });
sGroup[i].setData([pA, pB]);
}
}
function renderPaneGauges(pane, payload) {
if (!pane.gaugesEl) return;
if (!payload || !payload.analysis) {
pane.gaugesEl.innerHTML = '';
return;
}
// Build mini gauges
const a = payload.analysis;
const tScore = typeof a.trend_score === 'number' ? a.trend_score : 50;
const sScore = typeof a.strength_score === 'number' ? a.strength_score : 50;
const vScore = typeof a.volatility_score === 'number' ? a.volatility_score : 50;
const cT = tScore > 60 ? '#22d3ee' : (tScore < 40 ? '#fb7185' : '#94a3b8');
const cS = sScore > 60 ? '#818cf8' : (sScore < 40 ? '#fb7185' : '#94a3b8');
const cV = vScore > 60 ? '#fb923c' : (vScore < 40 ? '#2dd4bf' : '#94a3b8');
pane.gaugesEl.innerHTML = `
${tScore > 50 ? '↑' : '↓'}
`;
}
// Replace the active pane listener
Workspace._onActivePaneChange = (newPaneId) => {
const pane = Workspace.getPane(newPaneId);
if (!pane) return;
// Sync toolbar
if (currentSymbol !== pane.symbol || timeframeSelect.value !== pane.interval) {
currentSymbol = pane.symbol;
searchInput.value = pane.symbol;
timeframeSelect.value = pane.interval;
if (window.initSymbolDetails) initSymbolDetails();
}
// Update AI Panel
if (pane.lastAnalysis?.payload) {
renderAnalysisPanel(pane.symbol, pane.interval, pane.lastAnalysis.payload);
renderCompactGauges(pane.symbol, pane.interval, pane.lastAnalysis.payload);
updateDashboardScale();
} else {
document.getElementById('analysisPanel').innerHTML = '';
document.getElementById('chartGauges').innerHTML = '';
fetchPaneAI(pane);
}
};
// Hook into loadPaneData
"""
with open('_ai_refactor.txt', 'w') as f:
f.write(new_fetch_code)