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 = `

AI dang tinh toan...

`; } 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)