Spaces:
Running
Running
| 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 = `<div class="dash-loading"><div class="loader-ring"></div><p>AI dang tinh toan...</p></div>`; | |
| } | |
| 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 = ` | |
| <div style="width:20px;height:20px;border-radius:50%;border:2px solid ${cT};display:flex;align-items:center;justify-content:center;background:var(--bg-depth);"> | |
| <span style="font-size:8px;font-weight:bold;color:${cT}">${tScore > 50 ? '↑' : '↓'}</span> | |
| </div> | |
| `; | |
| } | |
| // 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) | |