Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 177 additions & 37 deletions bitnet_tools/ui/app.js
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -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('추천 액션: 기본 분석 실행');
Comment thread
rad1092 marked this conversation as resolved.
},
},
{
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) "바로 분석" 클릭 후 요약 결과 확인',
]
: [
Expand All @@ -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';
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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);
6 changes: 5 additions & 1 deletion bitnet_tools/ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@ <h2>2) 입력</h2>
<input id="csvFile" type="file" accept=".csv,text/csv" />
<textarea id="csvText" rows="8" placeholder="또는 CSV 내용을 직접 붙여넣기"></textarea>

<label>요청</label>
<label>질문(question)</label>
<div class="chips">
<button class="chip" data-q="핵심 인사이트 3개와 근거를 알려줘">인사이트</button>
<button class="chip" data-q="이상치 의심 포인트와 추가 확인 항목을 알려줘">이상치</button>
<button class="chip" data-q="실행 가능한 다음 액션 5개를 우선순위로 제안해줘">다음행동</button>
</div>
<textarea id="question" rows="3">핵심 인사이트 3개와 근거를 알려줘</textarea>

<label>작업 요청(intent)</label>
<textarea id="intent" rows="2" placeholder="예: 파일 먼저 분석하고 이상치만 보여줘"></textarea>
<div id="intentActions" class="actions intent-actions" aria-live="polite"></div>

<div class="actions">
<button id="quickAnalyzeBtn">바로 분석</button>
<button id="analyzeBtn" class="advanced-only">분석</button>
Expand Down
6 changes: 6 additions & 0 deletions bitnet_tools/ui/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,9 @@ pre {
color: var(--muted);
}
.quick-guide li { margin: 4px 0; }


.intent-actions {
margin: 8px 0 2px;
min-height: 36px;
}