diff --git a/bitnet_tools/ui/app.js b/bitnet_tools/ui/app.js index 71ee814..28f9154 100644 --- a/bitnet_tools/ui/app.js +++ b/bitnet_tools/ui/app.js @@ -1,6 +1,8 @@ const csvFile = document.getElementById('csvFile'); const csvText = document.getElementById('csvText'); const question = document.getElementById('question'); +const intent = document.getElementById('intent'); +const intentActions = document.getElementById('intentActions'); const model = document.getElementById('model'); const analyzeBtn = document.getElementById('analyzeBtn'); const quickAnalyzeBtn = document.getElementById('quickAnalyzeBtn'); @@ -20,17 +22,83 @@ const dashboardCards = document.getElementById('dashboardCards'); const dashboardInsights = document.getElementById('dashboardInsights'); let latestPrompt = ''; +let currentMode = 'quick'; +const requestState = { question: '', intent: '', route: 'analyze' }; function setStatus(message) { if (statusBox) statusBox.textContent = message; } +function saveRequestState(route) { + requestState.question = question?.value || ''; + requestState.intent = intent?.value || ''; + requestState.route = route; +} + +function classifyIntent(intentText) { + const text = String(intentText || '').toLowerCase().trim(); + if (!text) return { route: 'analyze', reason: 'empty_intent' }; + + const hasMulti = /(멀티|여러|복수|비교|비교분석|multi)/.test(text); + const hasVisual = /(시각화|차트|그래프|plot|대시보드)/.test(text); + const hasAnalyze = /(분석|요약|인사이트|이상치|진단|핵심)/.test(text); + + if (hasMulti) return { route: 'multi', reason: 'keyword_multi' }; + if (hasVisual) return { route: 'visualize', reason: 'keyword_visualize' }; + if (hasAnalyze) return { route: 'analyze', reason: 'keyword_analyze' }; + return { route: 'unknown', reason: 'no_keyword_match' }; +} + +function renderIntentActions(actions = []) { + if (!intentActions) return; + intentActions.innerHTML = ''; + if (!actions.length) return; + + actions.forEach((item) => { + const btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'chip'; + btn.textContent = item.label; + btn.addEventListener('click', item.onClick); + intentActions.appendChild(btn); + }); +} + +function showFallbackIntentActions() { + renderIntentActions([ + { + label: '기본 분석 실행', + onClick: () => { + setMode('quick'); + if (quickAnalyzeBtn) quickAnalyzeBtn.focus(); + setStatus('추천 액션: 기본 분석 실행'); + }, + }, + { + label: '멀티 분석으로 전환', + onClick: () => { + setMode('advanced'); + if (multiCsvFiles) multiCsvFiles.focus(); + setStatus('추천 액션: 멀티 분석 파일을 선택하세요.'); + }, + }, + { + label: '시각화 안내 보기', + onClick: () => { + setMode('advanced'); + if (dashboardJson) dashboardJson.focus(); + setStatus('추천 액션: 멀티 분석 결과 JSON을 붙여넣고 대시보드를 렌더링하세요.'); + }, + }, + ]); +} + function renderModeGuide(mode) { if (!modeGuide) return; const steps = mode === 'quick' ? [ '1) CSV 파일을 선택하거나 CSV 텍스트를 붙여넣기', - '2) 요청 문장을 확인(칩 버튼으로 빠르게 선택 가능)', + '2) 질문(question)과 작업 요청(intent) 입력', '3) "바로 분석" 클릭 후 요약 결과 확인', ] : [ @@ -42,6 +110,7 @@ function renderModeGuide(mode) { } function setMode(mode) { + currentMode = mode; const advancedOnly = document.querySelectorAll('.advanced-only'); advancedOnly.forEach((el) => { el.style.display = mode === 'advanced' ? '' : 'none'; @@ -73,7 +142,7 @@ if (csvFile) { }); } -document.querySelectorAll('.chip').forEach((chip) => { +document.querySelectorAll('.chip[data-q]').forEach((chip) => { chip.addEventListener('click', () => { question.value = chip.dataset.q; if (quickAnalyzeBtn) quickAnalyzeBtn.focus(); @@ -114,8 +183,100 @@ async function runAnalyze() { setStatus('분석 완료'); } -if (analyzeBtn) analyzeBtn.addEventListener('click', runAnalyze); -if (quickAnalyzeBtn) quickAnalyzeBtn.addEventListener('click', runAnalyze); +async function runMultiAnalyze() { + const files = [...(multiCsvFiles?.files || [])]; + if (!files.length) { + dashboardInsights.textContent = '멀티 CSV 파일을 먼저 선택하세요.'; + setStatus('멀티 분석 중단: 파일 없음'); + return false; + } + + setStatus('멀티 분석 중...'); + dashboardInsights.textContent = '멀티 분석 중...'; + const payloadFiles = []; + for (const f of files) { + payloadFiles.push({ name: f.name, csv_text: await f.text() }); + } + + const res = await fetch('/api/multi-analyze', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + files: payloadFiles, + question: question.value, + group_column: groupColumn.value.trim(), + target_column: targetColumn.value.trim(), + }), + }); + const data = await res.json(); + if (!res.ok) { + dashboardInsights.textContent = data.error || 'error'; + setStatus(`멀티 분석 실패: ${data.error || 'error'}`); + return false; + } + + dashboardJson.value = JSON.stringify(data, null, 2); + const renderDashboardBtn = document.getElementById('renderDashboardBtn'); + if (renderDashboardBtn) renderDashboardBtn.click(); + setStatus('멀티 분석 완료'); + return true; +} + +async function runByIntent() { + renderIntentActions([]); + const intentResult = classifyIntent(intent?.value || ''); + saveRequestState(intentResult.route); + + if (intentResult.route === 'analyze') { + return runAnalyze(); + } + + if (intentResult.route === 'multi') { + setMode('advanced'); + const done = await runMultiAnalyze(); + if (!done) { + renderIntentActions([ + { + label: '멀티 파일 선택하기', + onClick: () => multiCsvFiles?.focus(), + }, + { + label: '기본 분석으로 진행', + onClick: () => { + setMode('quick'); + runAnalyze(); + }, + }, + ]); + setStatus('의도 라우팅: 멀티 비교 우선으로 판단했습니다. 파일 선택 후 다시 실행하세요.'); + } else { + setStatus('의도 라우팅: 멀티 비교 우선 작업을 완료했습니다.'); + } + return; + } + + if (intentResult.route === 'visualize') { + setMode('advanced'); + renderIntentActions([ + { + label: '대시보드 JSON 입력으로 이동', + onClick: () => dashboardJson?.focus(), + }, + { + label: '먼저 멀티 분석 실행', + onClick: () => multiAnalyzeBtn?.focus(), + }, + ]); + setStatus('의도 라우팅: 시각화 안내 우선으로 판단했습니다. 대시보드/멀티 분석 경로를 이용하세요.'); + return; + } + + showFallbackIntentActions(); + setStatus('의도 해석이 불명확합니다. 아래 추천 액션 중 하나를 선택하세요.'); +} + +if (analyzeBtn) analyzeBtn.addEventListener('click', runByIntent); +if (quickAnalyzeBtn) quickAnalyzeBtn.addEventListener('click', runByIntent); if (runBtn) { runBtn.addEventListener('click', async () => { @@ -181,42 +342,21 @@ if (renderDashboardBtn) { } if (multiAnalyzeBtn) { - multiAnalyzeBtn.addEventListener('click', async () => { - const files = [...(multiCsvFiles.files || [])]; - if (!files.length) { - dashboardInsights.textContent = '멀티 CSV 파일을 먼저 선택하세요.'; - setStatus('멀티 분석 중단: 파일 없음'); - return; - } - - setStatus('멀티 분석 중...'); - dashboardInsights.textContent = '멀티 분석 중...'; - const payloadFiles = []; - for (const f of files) { - payloadFiles.push({ name: f.name, csv_text: await f.text() }); - } + multiAnalyzeBtn.addEventListener('click', runMultiAnalyze); +} - const res = await fetch('/api/multi-analyze', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - files: payloadFiles, - question: question.value, - group_column: groupColumn.value.trim(), - target_column: targetColumn.value.trim(), - }), - }); - const data = await res.json(); - if (!res.ok) { - dashboardInsights.textContent = data.error || 'error'; - setStatus(`멀티 분석 실패: ${data.error || 'error'}`); +if (intent) { + intent.addEventListener('input', () => { + if (!intent.value.trim()) { + renderIntentActions([]); return; } - - dashboardJson.value = JSON.stringify(data, null, 2); - renderDashboardBtn.click(); - setStatus('멀티 분석 완료'); + const { route } = classifyIntent(intent.value); + if (route === 'analyze') setStatus('의도 라우팅 후보: 분석 우선'); + else if (route === 'multi') setStatus('의도 라우팅 후보: 멀티 비교 우선'); + else if (route === 'visualize') setStatus('의도 라우팅 후보: 시각화 안내 우선'); + else setStatus('의도 라우팅 후보를 찾지 못했습니다. 실행 시 추천 액션을 제공합니다.'); }); } -setMode('quick'); +setMode(currentMode); diff --git a/bitnet_tools/ui/index.html b/bitnet_tools/ui/index.html index bcfab0e..8d413d9 100644 --- a/bitnet_tools/ui/index.html +++ b/bitnet_tools/ui/index.html @@ -27,7 +27,7 @@

2) 입력

- +
@@ -35,6 +35,10 @@

2) 입력

+ + +
+
diff --git a/bitnet_tools/ui/styles.css b/bitnet_tools/ui/styles.css index 61007eb..b33c2c8 100644 --- a/bitnet_tools/ui/styles.css +++ b/bitnet_tools/ui/styles.css @@ -90,3 +90,9 @@ pre { color: var(--muted); } .quick-guide li { margin: 4px 0; } + + +.intent-actions { + margin: 8px 0 2px; + min-height: 36px; +}