// file: kb_app/frontend/ts/demo_pipeline.ts import * as bootstrap from "bootstrap"; import "simplebar"; import ResizeObserver from "resize-observer-polyfill"; import { invoke } from "@tauri-apps/api/core"; import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing"; import { KbDemoPipelineInspectRequest } from './bindings/KbDemoPipelineInspectRequest.ts'; import { KbDemoPipelineInspectPayload } from './bindings/KbDemoPipelineInspectPayload.ts'; import { KbDemoPipelineInspectTokenRequest } from './bindings/KbDemoPipelineInspectTokenRequest.ts'; import { KbDemoPipelineInspectPairRequest } from './bindings/KbDemoPipelineInspectPairRequest.ts'; import { KbDemoPipelineInspectPoolRequest } from './bindings/KbDemoPipelineInspectPoolRequest.ts'; import { KbDemoPipelineBackfillTokenRequest } from './bindings/KbDemoPipelineBackfillTokenRequest.ts'; import { KbDemoPipelineBackfillTokenPayload } from './bindings/KbDemoPipelineBackfillTokenPayload.ts'; import { KbDemoPipelineBackfillPoolRequest } from './bindings/KbDemoPipelineBackfillPoolRequest.ts'; import { KbDemoPipelineBackfillPoolPayload } from './bindings/KbDemoPipelineBackfillPoolPayload.ts'; import * as echarts from "echarts"; (window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap; (window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; interface DemoPipelinePairCandle { id: number | null; pair_id: number; timeframe_seconds: number; bucket_start_unix: number; bucket_end_unix: number; open_price_quote_per_base: number; high_price_quote_per_base: number; low_price_quote_per_base: number; close_price_quote_per_base: number; trade_count: number; buy_count: number; sell_count: number; base_volume_raw: string | null; quote_volume_raw: string | null; first_trade_signature: string | null; last_trade_signature: string | null; created_at: string; updated_at: string; } interface DemoPipelinePairCandleGroup { pairId: number; timeframeSeconds: number; candles: DemoPipelinePairCandle[]; } function parsePairCandleGroups(rawJson: string): DemoPipelinePairCandleGroup[] { if (rawJson.trim() === "") { return []; } try { const parsed = JSON.parse(rawJson) as unknown; if (!Array.isArray(parsed)) { return []; } const groups: DemoPipelinePairCandleGroup[] = []; for (const value of parsed) { if (typeof value !== "object" || value === null) { continue; } const maybeGroup = value as { pairId?: unknown; timeframeSeconds?: unknown; candles?: unknown; }; if ( typeof maybeGroup.pairId !== "number" || typeof maybeGroup.timeframeSeconds !== "number" || !Array.isArray(maybeGroup.candles) ) { continue; } groups.push({ pairId: maybeGroup.pairId, timeframeSeconds: maybeGroup.timeframeSeconds, candles: maybeGroup.candles as DemoPipelinePairCandle[], }); } return groups; } catch { return []; } } function formatTimeframeLabel(timeframeSeconds: number): string { if (timeframeSeconds % 3600 === 0) { return `${timeframeSeconds / 3600}h`; } if (timeframeSeconds % 60 === 0) { return `${timeframeSeconds / 60}m`; } return `${timeframeSeconds}s`; } function parseRawVolume(text: string | null, fallback: number): number { if (text === null || text.trim() === "") { return fallback; } const parsed = Number.parseFloat(text); if (Number.isNaN(parsed)) { return fallback; } return parsed; } function setEmptyCandlesChart( chart: echarts.ECharts, chartMeta: HTMLElement, message: string, ): void { chartMeta.textContent = message; chart.setOption({ animation: false, title: { text: message, left: "center", top: "middle", textStyle: { fontSize: 14, fontWeight: "normal", }, }, tooltip: {}, xAxis: { show: false, type: "category", data: [] }, yAxis: { show: false, type: "value" }, series: [], }, true); } function refreshCandlesSelectors( groups: DemoPipelinePairCandleGroup[], pairSelect: HTMLSelectElement, timeframeSelect: HTMLSelectElement, ): void { const currentPairValue = pairSelect.value; const currentTimeframeValue = timeframeSelect.value; const uniquePairs = Array.from(new Set(groups.map((group) => group.pairId))).sort((left, right) => left - right); pairSelect.innerHTML = ""; if (uniquePairs.length === 0) { const option = document.createElement("option"); option.value = ""; option.textContent = "Aucune"; pairSelect.appendChild(option); timeframeSelect.innerHTML = ""; const tfOption = document.createElement("option"); tfOption.value = ""; tfOption.textContent = "Aucun"; timeframeSelect.appendChild(tfOption); return; } for (const pairId of uniquePairs) { const option = document.createElement("option"); option.value = String(pairId); option.textContent = `Pair #${pairId}`; if (option.value === currentPairValue) { option.selected = true; } pairSelect.appendChild(option); } if (pairSelect.value === "" && uniquePairs.length > 0) { pairSelect.value = String(uniquePairs[0]); } const selectedPairId = Number.parseInt(pairSelect.value, 10); const pairGroups = groups .filter((group) => group.pairId === selectedPairId) .sort((left, right) => left.timeframeSeconds - right.timeframeSeconds); timeframeSelect.innerHTML = ""; if (pairGroups.length === 0) { const option = document.createElement("option"); option.value = ""; option.textContent = "Aucun"; timeframeSelect.appendChild(option); return; } for (const group of pairGroups) { const option = document.createElement("option"); option.value = String(group.timeframeSeconds); option.textContent = formatTimeframeLabel(group.timeframeSeconds); if (option.value === currentTimeframeValue) { option.selected = true; } timeframeSelect.appendChild(option); } if (timeframeSelect.value === "" && pairGroups.length > 0) { timeframeSelect.value = String(pairGroups[0].timeframeSeconds); } } function renderSelectedCandlesChart( chart: echarts.ECharts, chartMeta: HTMLElement, groups: DemoPipelinePairCandleGroup[], pairSelect: HTMLSelectElement, timeframeSelect: HTMLSelectElement, ): void { if (groups.length === 0) { setEmptyCandlesChart(chart, chartMeta, "Aucune candle disponible."); return; } const selectedPairId = Number.parseInt(pairSelect.value, 10); const selectedTimeframe = Number.parseInt(timeframeSelect.value, 10); if (Number.isNaN(selectedPairId) || Number.isNaN(selectedTimeframe)) { setEmptyCandlesChart(chart, chartMeta, "Sélection de paire/timeframe invalide."); return; } const group = groups.find( (value) => value.pairId === selectedPairId && value.timeframeSeconds === selectedTimeframe, ); if (!group || group.candles.length === 0) { setEmptyCandlesChart( chart, chartMeta, `Aucune candle pour la pair #${selectedPairId} en ${formatTimeframeLabel(selectedTimeframe)}.`, ); return; } const candles = [...group.candles].sort( (left, right) => left.bucket_start_unix - right.bucket_start_unix, ); const categoryData = candles.map((candle) => new Date(candle.bucket_start_unix * 1000).toLocaleString("fr-CH", { hour12: false, year: "2-digit", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }), ); const ohlcData = candles.map((candle) => [ candle.open_price_quote_per_base, candle.close_price_quote_per_base, candle.low_price_quote_per_base, candle.high_price_quote_per_base, ]); const volumeData = candles.map((candle) => parseRawVolume(candle.quote_volume_raw, candle.trade_count), ); chartMeta.textContent = `Pair #${selectedPairId} • ${formatTimeframeLabel(selectedTimeframe)} • ${candles.length} candles`; chart.setOption( { animation: false, legend: { data: ["OHLC", "Volume"], top: 0, }, tooltip: { trigger: "axis", axisPointer: { type: "cross", }, }, axisPointer: { link: [{ xAxisIndex: "all" }], }, grid: [ { left: 60, right: 24, top: 40, height: "58%" }, { left: 60, right: 24, top: "74%", height: "16%" }, ], xAxis: [ { type: "category", data: categoryData, boundaryGap: true, axisLine: { onZero: false }, splitLine: { show: false }, min: "dataMin", max: "dataMax", }, { type: "category", gridIndex: 1, data: categoryData, boundaryGap: true, axisLine: { onZero: false }, axisTick: { show: false }, splitLine: { show: false }, axisLabel: { show: false }, min: "dataMin", max: "dataMax", }, ], yAxis: [ { scale: true, splitArea: { show: false }, }, { gridIndex: 1, scale: true, splitNumber: 2, }, ], dataZoom: [ { type: "inside", xAxisIndex: [0, 1], start: 0, end: 100, }, { show: true, type: "slider", xAxisIndex: [0, 1], bottom: 6, start: 0, end: 100, }, ], series: [ { name: "OHLC", type: "candlestick", data: ohlcData, }, { name: "Volume", type: "bar", xAxisIndex: 1, yAxisIndex: 1, data: volumeData, }, ], }, true, ); } function applyInspectionPayload( payload: KbDemoPipelineInspectPayload, summaryTextarea: HTMLTextAreaElement, transactionTextarea: HTMLTextAreaElement, decodedEventsTextarea: HTMLTextAreaElement, poolsTextarea: HTMLTextAreaElement, pairsTextarea: HTMLTextAreaElement, launchAttributionsTextarea: HTMLTextAreaElement, poolOriginsTextarea: HTMLTextAreaElement, walletsTextarea: HTMLTextAreaElement, tradeEventsTextarea: HTMLTextAreaElement, pairMetricsTextarea: HTMLTextAreaElement, pairCandlesTextarea: HTMLTextAreaElement, pairAnalyticSignalsTextarea: HTMLTextAreaElement, chart: echarts.ECharts, chartMeta: HTMLElement, pairSelect: HTMLSelectElement, timeframeSelect: HTMLSelectElement, ): void { summaryTextarea.value = payload.summaryJson; transactionTextarea.value = payload.transactionJson; decodedEventsTextarea.value = payload.decodedEventsJson; poolsTextarea.value = payload.poolsJson; pairsTextarea.value = payload.pairsJson; launchAttributionsTextarea.value = payload.launchAttributionsJson; poolOriginsTextarea.value = payload.poolOriginsJson; walletsTextarea.value = payload.walletsJson; tradeEventsTextarea.value = payload.tradeEventsJson; pairMetricsTextarea.value = payload.pairMetricsJson; pairCandlesTextarea.value = payload.pairCandlesJson; pairAnalyticSignalsTextarea.value = payload.pairAnalyticSignalsJson; const groups = parsePairCandleGroups(payload.pairCandlesJson); refreshCandlesSelectors(groups, pairSelect, timeframeSelect); renderSelectedCandlesChart(chart, chartMeta, groups, pairSelect, timeframeSelect); } function appendLogLine(textarea: HTMLTextAreaElement, line: string): void { const now = new Date(); const timestamp = now.toLocaleTimeString("fr-CH", { hour12: false }); const lines = textarea.value === "" ? [] : textarea.value.split("\n"); lines.push(`[${timestamp}] ${line}`); const maxLines = 300; textarea.value = lines.slice(-maxLines).join("\n"); textarea.scrollTop = textarea.scrollHeight; } function clearInspection( backfillTextarea: HTMLTextAreaElement, summaryTextarea: HTMLTextAreaElement, transactionTextarea: HTMLTextAreaElement, decodedEventsTextarea: HTMLTextAreaElement, poolsTextarea: HTMLTextAreaElement, pairsTextarea: HTMLTextAreaElement, launchAttributionsTextarea: HTMLTextAreaElement, poolOriginsTextarea: HTMLTextAreaElement, walletsTextarea: HTMLTextAreaElement, tradeEventsTextarea: HTMLTextAreaElement, pairMetricsTextarea: HTMLTextAreaElement, pairCandlesTextarea: HTMLTextAreaElement, pairAnalyticSignalsTextarea: HTMLTextAreaElement, ): void { backfillTextarea.value = ""; summaryTextarea.value = ""; transactionTextarea.value = ""; decodedEventsTextarea.value = ""; poolsTextarea.value = ""; pairsTextarea.value = ""; launchAttributionsTextarea.value = ""; poolOriginsTextarea.value = ""; walletsTextarea.value = ""; tradeEventsTextarea.value = ""; pairMetricsTextarea.value = ""; pairCandlesTextarea.value = ""; pairAnalyticSignalsTextarea.value = ""; } function readCustomTimeframeSeconds( input: HTMLInputElement, logTextarea: HTMLTextAreaElement, ): bigint | null | undefined { const customTimeframeText = input.value.trim(); if (customTimeframeText === "") { return null; } const parsed = Number.parseInt(customTimeframeText, 10); if (Number.isNaN(parsed) || parsed <= 0) { appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`); return undefined; } return BigInt(parsed); } function readPositiveIntegerInput( input: HTMLInputElement, logTextarea: HTMLTextAreaElement, label: string, ): number | undefined { const text = input.value.trim(); if (text === "") { appendLogLine(logTextarea, `[ui] ${label} is required`); return undefined; } const parsed = Number.parseInt(text, 10); if (Number.isNaN(parsed) || parsed <= 0) { appendLogLine(logTextarea, `[ui] invalid ${label} '${text}'`); return undefined; } return parsed; } document.addEventListener("DOMContentLoaded", async () => { void takeoverConsole(); debug("demo_pipeline window loaded"); const sidebarToggle = document.querySelector('#sidebarToggle'); if (sidebarToggle) { // restaurer l’état depuis localStorage if (localStorage.getItem('sidebar-toggle') === 'true') { document.body.classList.add('sidenav-toggled'); } sidebarToggle.addEventListener('click', (event) => { event.preventDefault(); document.body.classList.toggle('sidenav-toggled'); localStorage.setItem('sidebar-toggle', document.body.classList.contains('sidenav-toggled') ? 'true' : 'false'); }); } const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); const toastElList = document.querySelectorAll('.toast'); Array.from(toastElList).map(toastEl => new bootstrap.Toast(toastEl)); const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]'); Array.from(popoverTriggerList).map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl)); const gobackto = location.pathname + location.search; document.querySelectorAll('a[data-setlang]').forEach((a) => { const href = a.getAttribute("href"); if (!href) return; // pas de href => on ignore const url = new URL(href, location.origin); url.searchParams.set("gobackto", gobackto); // conserve une URL relative (path + query) a.setAttribute("href", url.pathname + "?" + url.searchParams.toString()); }); const signatureInput = document.querySelector("#demoPipelineSignatureInput"); const customTimeframeInput = document.querySelector("#demoPipelineCustomTimeframeInput"); const inspectButton = document.querySelector("#demoPipelineInspectButton"); const clearButton = document.querySelector("#demoPipelineClearButton"); const clearLogButton = document.querySelector("#demoPipelineClearLogButton"); const summaryTextarea = document.querySelector("#demoPipelineSummaryTextarea"); const transactionTextarea = document.querySelector("#demoPipelineTransactionTextarea"); const decodedEventsTextarea = document.querySelector("#demoPipelineDecodedEventsTextarea"); const poolsTextarea = document.querySelector("#demoPipelinePoolsTextarea"); const pairsTextarea = document.querySelector("#demoPipelinePairsTextarea"); const launchAttributionsTextarea = document.querySelector("#demoPipelineLaunchAttributionsTextarea"); const poolOriginsTextarea = document.querySelector("#demoPipelinePoolOriginsTextarea"); const walletsTextarea = document.querySelector("#demoPipelineWalletsTextarea"); const tradeEventsTextarea = document.querySelector("#demoPipelineTradeEventsTextarea"); const pairMetricsTextarea = document.querySelector("#demoPipelinePairMetricsTextarea"); const pairCandlesTextarea = document.querySelector("#demoPipelinePairCandlesTextarea"); const pairAnalyticSignalsTextarea = document.querySelector("#demoPipelinePairAnalyticSignalsTextarea"); const logTextarea = document.querySelector("#demoPipelineLogTextarea"); const tokenMintInput = document.querySelector("#demoPipelineTokenMintInput"); const inspectTokenButton = document.querySelector("#demoPipelineInspectTokenButton"); const pairIdInput = document.querySelector("#demoPipelinePairIdInput"); const inspectPairButton = document.querySelector("#demoPipelineInspectPairButton"); const poolAddressInput = document.querySelector("#demoPipelinePoolAddressInput"); const inspectPoolButton = document.querySelector("#demoPipelineInspectPoolButton"); const backfillTokenMintInput = document.querySelector("#demoPipelineBackfillTokenMintInput"); const backfillHttpRoleInput = document.querySelector("#demoPipelineBackfillHttpRoleInput"); const backfillMintLimitInput = document.querySelector("#demoPipelineBackfillMintLimitInput"); const backfillPoolLimitInput = document.querySelector("#demoPipelineBackfillPoolLimitInput"); const backfillTokenButton = document.querySelector("#demoPipelineBackfillTokenButton"); const backfillTextarea = document.querySelector("#demoPipelineBackfillTextarea"); const chartPairSelect = document.querySelector("#demoPipelineChartPairSelect"); const chartTimeframeSelect = document.querySelector("#demoPipelineChartTimeframeSelect"); const candlesChartElement = document.querySelector("#demoPipelineCandlesChart"); const candlesChartMeta = document.querySelector("#demoPipelineCandlesChartMeta"); const backfillPoolAddressInput = document.querySelector("#demoPipelineBackfillPoolAddressInput"); const backfillPoolOnlyLimitInput = document.querySelector("#demoPipelineBackfillPoolOnlyLimitInput"); const backfillPoolButton = document.querySelector("#demoPipelineBackfillPoolButton"); if ( !chartPairSelect || !chartTimeframeSelect || !candlesChartElement || !candlesChartMeta || !backfillTokenMintInput || !backfillHttpRoleInput || !backfillMintLimitInput || !backfillPoolLimitInput || !backfillTokenButton || !backfillTextarea || !backfillPoolAddressInput || !backfillPoolOnlyLimitInput || !backfillPoolButton || !pairIdInput || !inspectPairButton || !poolAddressInput || !inspectPoolButton || !tokenMintInput || !inspectTokenButton || !signatureInput || !customTimeframeInput || !inspectButton || !clearButton || !clearLogButton || !summaryTextarea || !transactionTextarea || !decodedEventsTextarea || !poolsTextarea || !pairsTextarea || !launchAttributionsTextarea || !poolOriginsTextarea || !walletsTextarea || !tradeEventsTextarea || !pairMetricsTextarea || !pairCandlesTextarea || !pairAnalyticSignalsTextarea || !logTextarea ) { console.error("demo_pipeline DOM is incomplete"); return; } const candlesChart = echarts.init(candlesChartElement); setEmptyCandlesChart(candlesChart, candlesChartMeta, "Aucune candle disponible."); window.addEventListener("resize", () => { candlesChart.resize(); }); chartPairSelect.addEventListener("change", () => { const groups = parsePairCandleGroups(pairCandlesTextarea.value); refreshCandlesSelectors(groups, chartPairSelect, chartTimeframeSelect); renderSelectedCandlesChart( candlesChart, candlesChartMeta, groups, chartPairSelect, chartTimeframeSelect, ); }); chartTimeframeSelect.addEventListener("change", () => { const groups = parsePairCandleGroups(pairCandlesTextarea.value); renderSelectedCandlesChart( candlesChart, candlesChartMeta, groups, chartPairSelect, chartTimeframeSelect, ); }); clearButton.addEventListener("click", () => { clearInspection( backfillTextarea, summaryTextarea, transactionTextarea, decodedEventsTextarea, poolsTextarea, pairsTextarea, launchAttributionsTextarea, poolOriginsTextarea, walletsTextarea, tradeEventsTextarea, pairMetricsTextarea, pairCandlesTextarea, pairAnalyticSignalsTextarea, ); backfillTokenMintInput.value = ""; backfillHttpRoleInput.value = "history_backfill"; backfillMintLimitInput.value = "50"; backfillPoolLimitInput.value = "50"; signatureInput.value = ""; customTimeframeInput.value = ""; tokenMintInput.value = ""; pairIdInput.value = ""; poolAddressInput.value = ""; backfillPoolAddressInput.value = ""; backfillPoolOnlyLimitInput.value = "50"; chartPairSelect.innerHTML = ``; chartTimeframeSelect.innerHTML = ``; setEmptyCandlesChart(candlesChart, candlesChartMeta, "Aucune candle disponible."); appendLogLine(logTextarea, "[ui] inspection state cleared"); }); backfillPoolButton.addEventListener("click", async () => { const poolAddress = backfillPoolAddressInput.value.trim(); if (poolAddress === "") { appendLogLine(logTextarea, "[ui] backfill pool address is required"); return; } const poolSignatureLimit = readPositiveIntegerInput( backfillPoolOnlyLimitInput, logTextarea, "poolSignatureLimit", ); if (poolSignatureLimit === undefined) { return; } const httpRoleText = backfillHttpRoleInput.value.trim(); const httpRole = httpRoleText === "" ? null : httpRoleText; appendLogLine( logTextarea, `[ui] launching pool backfill for '${poolAddress}' with role '${httpRole ?? "history_backfill"}' (pool=${poolSignatureLimit})`, ); const request: KbDemoPipelineBackfillPoolRequest = { poolAddress, httpRole, poolSignatureLimit, }; try { const payload = await invoke( "demo_pipeline_backfill_pool_address", { request }, ); backfillTextarea.value = payload.backfillJson; appendLogLine( logTextarea, `[ui] pool backfill completed for '${payload.poolAddress}' with role '${payload.httpRole}'`, ); if (!payload.poolPersistedAfterBackfill) { appendLogLine( logTextarea, `[ui] backfill completed but pool '${payload.poolAddress}' is still absent from persisted pool objects; automatic pool inspection skipped`, ); return; } const inspectRequest: KbDemoPipelineInspectPoolRequest = { poolAddress: payload.poolAddress, customTimeframeSeconds: null, }; try { const inspectPayload = await invoke( "demo_pipeline_inspect_pool_address", { request: inspectRequest }, ); applyInspectionPayload( inspectPayload, summaryTextarea, transactionTextarea, decodedEventsTextarea, poolsTextarea, pairsTextarea, launchAttributionsTextarea, poolOriginsTextarea, walletsTextarea, tradeEventsTextarea, pairMetricsTextarea, pairCandlesTextarea, pairAnalyticSignalsTextarea, candlesChart, candlesChartMeta, chartPairSelect, chartTimeframeSelect, ); appendLogLine( logTextarea, `[ui] pool inspection refreshed after backfill for '${payload.poolAddress}'`, ); } catch (error) { appendLogLine( logTextarea, `[ui] backfill completed but automatic pool inspection failed for '${payload.poolAddress}': ${String(error)}`, ); } } catch (error) { appendLogLine(logTextarea, `[ui] pool backfill error: ${String(error)}`); } }); clearLogButton.addEventListener("click", () => { logTextarea.value = ""; }); inspectButton.addEventListener("click", async () => { const signature = signatureInput.value.trim(); if (signature === "") { appendLogLine(logTextarea, "[ui] signature is required"); return; } let customTimeframeSeconds: bigint | null = null; const customTimeframeText = customTimeframeInput.value.trim(); if (customTimeframeText !== "") { const parsed = Number.parseInt(customTimeframeText, 10); if (Number.isNaN(parsed) || parsed <= 0) { appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`); return; } customTimeframeSeconds = BigInt(parsed); } appendLogLine( logTextarea, `[ui] inspecting signature '${signature}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`, ); const request: KbDemoPipelineInspectRequest = { signature, customTimeframeSeconds, }; try { const payload = await invoke("demo_pipeline_inspect_signature", { request }); applyInspectionPayload( payload, summaryTextarea, transactionTextarea, decodedEventsTextarea, poolsTextarea, pairsTextarea, launchAttributionsTextarea, poolOriginsTextarea, walletsTextarea, tradeEventsTextarea, pairMetricsTextarea, pairCandlesTextarea, pairAnalyticSignalsTextarea, candlesChart, candlesChartMeta, chartPairSelect, chartTimeframeSelect, ); appendLogLine(logTextarea, `[ui] inspection completed for '${payload.signature}'`); } catch (error) { appendLogLine(logTextarea, `[ui] inspect error: ${String(error)}`); } }); inspectTokenButton.addEventListener("click", async () => { const tokenMint = tokenMintInput.value.trim(); if (tokenMint === "") { appendLogLine(logTextarea, "[ui] token mint is required"); return; } let customTimeframeSeconds: bigint | null = null; const customTimeframeText = customTimeframeInput.value.trim(); if (customTimeframeText !== "") { const parsed = Number.parseInt(customTimeframeText, 10); if (Number.isNaN(parsed) || parsed <= 0) { appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`); return; } customTimeframeSeconds = BigInt(parsed); } appendLogLine( logTextarea, `[ui] inspecting token mint '${tokenMint}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`, ); const request: KbDemoPipelineInspectTokenRequest = { tokenMint, customTimeframeSeconds, }; try { const payload = await invoke("demo_pipeline_inspect_token_mint", { request }); applyInspectionPayload( payload, summaryTextarea, transactionTextarea, decodedEventsTextarea, poolsTextarea, pairsTextarea, launchAttributionsTextarea, poolOriginsTextarea, walletsTextarea, tradeEventsTextarea, pairMetricsTextarea, pairCandlesTextarea, pairAnalyticSignalsTextarea, candlesChart, candlesChartMeta, chartPairSelect, chartTimeframeSelect, ); appendLogLine(logTextarea, `[ui] token inspection completed for '${payload.signature}'`); } catch (error) { appendLogLine(logTextarea, `[ui] token inspect error: ${String(error)}`); } }); inspectPairButton.addEventListener("click", async () => { const pairIdText = pairIdInput.value.trim(); if (pairIdText === "") { appendLogLine(logTextarea, "[ui] pair id is required"); return; } const parsedPairId = Number.parseInt(pairIdText, 10); if (Number.isNaN(parsedPairId) || parsedPairId <= 0) { appendLogLine(logTextarea, `[ui] invalid pair id '${pairIdText}'`); return; } const customTimeframeSeconds = readCustomTimeframeSeconds(customTimeframeInput, logTextarea); if (customTimeframeSeconds === undefined) { return; } appendLogLine( logTextarea, `[ui] inspecting pair id '${parsedPairId}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`, ); const request: KbDemoPipelineInspectPairRequest = { pairId: BigInt(parsedPairId), customTimeframeSeconds, }; try { const payload = await invoke("demo_pipeline_inspect_pair_id", { request }); applyInspectionPayload( payload, summaryTextarea, transactionTextarea, decodedEventsTextarea, poolsTextarea, pairsTextarea, launchAttributionsTextarea, poolOriginsTextarea, walletsTextarea, tradeEventsTextarea, pairMetricsTextarea, pairCandlesTextarea, pairAnalyticSignalsTextarea, candlesChart, candlesChartMeta, chartPairSelect, chartTimeframeSelect, ); appendLogLine(logTextarea, `[ui] pair inspection completed for '${payload.signature}'`); } catch (error) { appendLogLine(logTextarea, `[ui] pair inspect error: ${String(error)}`); } }); inspectPoolButton.addEventListener("click", async () => { const poolAddress = poolAddressInput.value.trim(); if (poolAddress === "") { appendLogLine(logTextarea, "[ui] pool address is required"); return; } const customTimeframeSeconds = readCustomTimeframeSeconds(customTimeframeInput, logTextarea); if (customTimeframeSeconds === undefined) { return; } appendLogLine( logTextarea, `[ui] inspecting pool '${poolAddress}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`, ); const request: KbDemoPipelineInspectPoolRequest = { poolAddress, customTimeframeSeconds, }; try { const payload = await invoke("demo_pipeline_inspect_pool_address", { request }); applyInspectionPayload( payload, summaryTextarea, transactionTextarea, decodedEventsTextarea, poolsTextarea, pairsTextarea, launchAttributionsTextarea, poolOriginsTextarea, walletsTextarea, tradeEventsTextarea, pairMetricsTextarea, pairCandlesTextarea, pairAnalyticSignalsTextarea, candlesChart, candlesChartMeta, chartPairSelect, chartTimeframeSelect, ); appendLogLine(logTextarea, `[ui] pool inspection completed for '${payload.signature}'`); } catch (error) { appendLogLine(logTextarea, `[ui] pool inspect error: ${String(error)}`); } }); backfillTokenButton.addEventListener("click", async () => { const tokenMint = backfillTokenMintInput.value.trim(); if (tokenMint === "") { appendLogLine(logTextarea, "[ui] backfill token mint is required"); return; } const mintSignatureLimit = readPositiveIntegerInput( backfillMintLimitInput, logTextarea, "mintSignatureLimit", ); if (mintSignatureLimit === undefined) { return; } const poolSignatureLimit = readPositiveIntegerInput( backfillPoolLimitInput, logTextarea, "poolSignatureLimit", ); if (poolSignatureLimit === undefined) { return; } const httpRoleText = backfillHttpRoleInput.value.trim(); const httpRole = httpRoleText === "" ? null : httpRoleText; appendLogLine( logTextarea, `[ui] launching token backfill for '${tokenMint}' with role '${httpRole ?? "history_backfill"}' (mint=${mintSignatureLimit}, pool=${poolSignatureLimit})`, ); const request: KbDemoPipelineBackfillTokenRequest = { tokenMint, httpRole, mintSignatureLimit, poolSignatureLimit, }; try { const payload = await invoke( "demo_pipeline_backfill_token_mint", { request }, ); backfillTextarea.value = payload.backfillJson; appendLogLine( logTextarea, `[ui] token backfill completed for '${payload.tokenMint}' with role '${payload.httpRole}'`, ); if (!payload.tokenPersistedAfterBackfill) { appendLogLine( logTextarea, `[ui] backfill completed but token '${payload.tokenMint}' is still absent from persisted token objects; automatic token inspection skipped`, ); return; } const inspectRequest: KbDemoPipelineInspectTokenRequest = { tokenMint: payload.tokenMint, customTimeframeSeconds: null, }; try { const inspectPayload = await invoke( "demo_pipeline_inspect_token_mint", { request: inspectRequest }, ); summaryTextarea.value = inspectPayload.summaryJson; transactionTextarea.value = inspectPayload.transactionJson; decodedEventsTextarea.value = inspectPayload.decodedEventsJson; poolsTextarea.value = inspectPayload.poolsJson; pairsTextarea.value = inspectPayload.pairsJson; launchAttributionsTextarea.value = inspectPayload.launchAttributionsJson; poolOriginsTextarea.value = inspectPayload.poolOriginsJson; walletsTextarea.value = inspectPayload.walletsJson; tradeEventsTextarea.value = inspectPayload.tradeEventsJson; pairMetricsTextarea.value = inspectPayload.pairMetricsJson; pairCandlesTextarea.value = inspectPayload.pairCandlesJson; pairAnalyticSignalsTextarea.value = inspectPayload.pairAnalyticSignalsJson; appendLogLine( logTextarea, `[ui] token inspection refreshed after backfill for '${payload.tokenMint}'`, ); } catch (error) { appendLogLine( logTextarea, `[ui] backfill completed but automatic token inspection failed for '${payload.tokenMint}': ${String(error)}`, ); } } catch (error) { appendLogLine(logTextarea, `[ui] token backfill error: ${String(error)}`); } }); });