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
36 changes: 34 additions & 2 deletions bitnet_tools/ui/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const UI = {
schemaMappings: document.getElementById('schemaMappings'),
prompt: document.getElementById('prompt'),
answer: document.getElementById('answer'),
rawJson: document.getElementById('rawJson'),
codeDetails: document.getElementById('codeDetails'),
analyzeAssist: document.getElementById('analyzeAssist'),
confidenceBadge: document.getElementById('confidenceBadge'),
switchToCsvBtn: document.getElementById('switchToCsvBtn'),
Expand Down Expand Up @@ -95,6 +97,36 @@ const CONFIDENCE_THRESHOLD_DEFAULT = 0.7;
const CANDIDATE_PREVIEW_ROWS = 5;



const CODE_REQUEST_PATTERN = /코드도\s*보여줘/;

function shouldShowCodeBlock() {
return CODE_REQUEST_PATTERN.test(String(UI.question?.value || ''));
}

function formatCoreSummary(summary) {
if (!summary) return '요약 결과가 없습니다.';
if (typeof summary === 'string') return summary;
if (Array.isArray(summary)) return summary.slice(0, 3).map((item, idx) => `${idx + 1}. ${item}`).join('\n');
const lines = [];
const insights = Array.isArray(summary.insights) ? summary.insights : [];
if (insights.length) lines.push(...insights.slice(0, 3).map((item, idx) => `${idx + 1}. ${item}`));
if (!lines.length) {
Object.entries(summary).slice(0, 3).forEach(([key, value]) => {
if (Array.isArray(value)) lines.push(`${key}: ${value.slice(0, 3).join(', ')}`);
else if (value && typeof value === 'object') lines.push(`${key}: ${JSON.stringify(value)}`);
else lines.push(`${key}: ${value}`);
});
}
return lines.length ? lines.join('\n') : '핵심 결과를 추출하지 못했습니다.';
}

function renderPrimaryResult(data) {
if (UI.summary) UI.summary.textContent = formatCoreSummary(data?.summary);
if (UI.prompt) UI.prompt.textContent = data?.prompt || '';
if (UI.rawJson) UI.rawJson.textContent = JSON.stringify(data || {}, null, 2);
if (UI.codeDetails) UI.codeDetails.hidden = !shouldShowCodeBlock();
}
function getInputTypeForFile(file) {
const selected = UI.inputType?.value || 'auto';
if (selected !== 'auto') return selected;
Expand Down Expand Up @@ -736,10 +768,9 @@ async function runAnalyzeFromPreprocessed(result, fallbackQuestion = '') {
};
const data = await postJson('/api/analyze', body, '분석');
appState.latestPrompt = data.prompt;
UI.summary.textContent = JSON.stringify(data.summary, null, 2);
renderPrimaryResult(data);
renderSchemaMappings(data);
renderAnalyzeAssist(data);
if (UI.prompt) UI.prompt.textContent = data.prompt;
if (UI.answer) UI.answer.textContent = '';
setStatus(STATUS.analyzeDone);
}
Expand Down Expand Up @@ -905,6 +936,7 @@ async function runAnalyze() {
resetAnalyzeAssist();
setStatus(STATUS.analyzing);
UI.summary.textContent = STATUS.analyzing;
if (UI.codeDetails) UI.codeDetails.hidden = true;
if (UI.schemaMappings) UI.schemaMappings.textContent = '자동 매핑 결과를 계산 중입니다...';
toggleBusy(true);
try {
Expand Down
240 changes: 74 additions & 166 deletions bitnet_tools/ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,194 +9,102 @@
<body>
<main class="container">
<h1>BitNet CSV Analyzer</h1>
<p class="sub">파일 입력 → 분석 실행 → 결과 확인 순서로 바로 시작하세요.</p>
<p class="sub">입력 1개 → 실행 → 결과 카드 확인</p>

<section class="panel">
<h2>1) 작업 모드</h2>
<section class="panel simple-panel">
<h2>요청 입력</h2>
<textarea id="question" rows="3" placeholder="예: 핵심 인사이트 3개 요약해줘"></textarea>
<div class="actions">
<button class="mode-btn active" data-mode="quick" id="modeQuickBtn">빠른 시작</button>
<button class="mode-btn" data-mode="advanced" id="modeAdvancedBtn">고급 모드</button>
<button id="quickAnalyzeBtn">실행</button>
</div>
<p class="sub">빠른 시작은 핵심 분석에 집중하고, 고급 모드는 멀티 분석/모델 실행 옵션을 제공합니다.</p>
<ol id="modeGuide" class="quick-guide" aria-live="polite"></ol>
<input id="model" placeholder="bitnet:latest" class="advanced-only" />
<button id="runBtn" class="advanced-only">BitNet 실행</button>
<textarea id="intent" class="advanced-only" rows="2" placeholder="intent"></textarea>
<div id="intentActions" class="actions intent-actions" aria-live="polite"></div>
<button id="analyzeBtn" class="advanced-only">분석</button>
</section>

<section class="panel">
<h2>2) 입력</h2>
<h2>결과 카드</h2>
<div class="card result-card">
<strong>핵심 결과</strong>
<pre id="summary" aria-live="polite">실행하면 핵심 결과가 여기에 표시됩니다.</pre>
</div>

<details id="codeDetails" hidden>
<summary>코드 보기</summary>
<pre id="prompt"></pre>
</details>

<details>
<summary>고급 디버그 / 원본 JSON</summary>
<pre id="schemaMappings">자동 매핑 결과가 여기에 표시됩니다.</pre>
<pre id="rawJson"></pre>
<pre id="answer"></pre>
<pre id="statusBox" aria-live="polite">대기 중</pre>
<div id="errorUser" class="error-user" aria-live="polite"></div>
<details id="errorDetails" class="error-details">
<summary>상세 오류 보기</summary>
<pre id="errorDetailText"></pre>
</details>
</details>
</section>

<details class="panel">
<summary>입력 데이터(파일/CSV)</summary>
<label>파일 타입</label>
<select id="inputType">
<option value="auto">자동 감지</option>
<option value="csv">CSV</option>
<option value="excel">Excel (.xlsx/.xls)</option>
<option value="document">문서 (.pdf/.docx/.pptx)</option>
</select>

<label>CSV/Excel 파일</label>
<input id="csvFile" type="file" accept=".csv,text/csv,.xlsx,.xls,.pdf,.docx,.pptx,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.presentationml.presentation" />
<label>Excel 시트 / 문서 테이블(선택)</label>
<div class="row">
<select id="sheetSelect">
<option value="">기본 시트(첫 번째)</option>
</select>
<select id="sheetSelect"><option value="">기본 시트(첫 번째)</option></select>
<button id="refreshSheetsBtn" type="button">목록 새로고침</button>
</div>
<textarea id="csvText" rows="8" placeholder="또는 CSV 내용을 직접 붙여넣기"></textarea>
</details>

<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>
<div class="actions">
<button id="showVizOptionsBtn" type="button">시각화 옵션 보기</button>
</div>
<pre id="vizRecommendation" aria-live="polite">추천 시각화 옵션이 여기에 표시됩니다.</pre>

<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>
</div>
</section>

<section class="panel advanced-only" id="advancedModelPanel">
<h2>고급: 모델 실행</h2>
<label>BitNet 모델 태그</label>
<input id="model" placeholder="bitnet:latest" />
<div class="actions">
<button id="runBtn">BitNet 실행</button>
</div>
</section>

<section class="panel">
<h2>3) 실행 상태</h2>
<pre id="statusBox" aria-live="polite">대기 중</pre>
<div class="actions">
<button id="retryPreprocessBtn" type="button" class="advanced-only" disabled>전처리 실패 재시도</button>
</div>
<pre id="preprocessStatus" aria-live="polite">입력 전처리 대기 중</pre>
<div id="errorUser" class="error-user" aria-live="polite"></div>
<details id="errorDetails" class="error-details">
<summary>상세 오류 보기</summary>
<pre id="errorDetailText"></pre>
</details>
</section>


<section class="panel">
<h2>Geo 의심 케이스 추출</h2>
<p class="sub">지도 렌더링 없이 의심 케이스 파일(CSV/JSON)을 생성합니다.</p>
<div class="row">
<div>
<label>위도 컬럼명</label>
<input id="geoLatCol" placeholder="예: lat" />
</div>
<div>
<label>경도 컬럼명</label>
<input id="geoLonCol" placeholder="예: lon" />
</div>
<div>
<label>거리 임계값(km)</label>
<input id="geoThreshold" type="number" min="0" step="0.1" value="25" />
</div>
</div>
<div class="actions">
<button id="geoExtractBtn" type="button">의심 케이스 추출 다운로드</button>
</div>
<pre id="geoResult" aria-live="polite">Geo 추출 대기 중</pre>
</section>

<section class="panel">
<h2>4) 결과</h2>
<div id="analyzeAssist" class="analyze-assist" aria-live="polite" hidden>
<div class="actions">
<span id="confidenceBadge" class="chip" hidden>신뢰도 낮음</span>
<button id="switchToCsvBtn" type="button" hidden>CSV 업로드로 전환</button>
</div>
<p id="analyzeRecommendation" class="sub"></p>
<div id="candidateTableWrap" hidden>
<label for="candidateTableSelect">추출 후보 테이블 선택</label>
<select id="candidateTableSelect"></select>
<pre id="candidateTablePreview">후보 테이블 미리보기(상위 5행)</pre>
</div>
</div>
<h3>스키마 자동 매핑</h3>
<pre id="schemaMappings">자동 매핑 결과가 여기에 표시됩니다.</pre>
<h3>데이터 요약</h3>
<pre id="summary"></pre>
</section>

<section class="panel advanced-only">
<h3>생성 프롬프트</h3>
<div class="actions"><button id="copyPrompt">복사</button></div>
<pre id="prompt"></pre>
</section>

<section class="panel advanced-only">
<h3>BitNet 응답</h3>
<pre id="answer"></pre>
</section>

<section class="panel advanced-only" id="multiPanel">
<h2>고급: 멀티 CSV/Excel 분석</h2>
<input id="multiCsvFiles" type="file" multiple accept=".csv,text/csv,.xlsx,.xls,.pdf,.docx,.pptx,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.presentationml.presentation" />
<div class="row">
<div>
<label>그룹 컬럼(선택)</label>
<input id="groupColumn" placeholder="예: 시도명" />
</div>
<div>
<label>타깃 컬럼(선택)</label>
<input id="targetColumn" placeholder="예: 세차유형" />
</div>
</div>
<div class="actions">
<button id="multiAnalyzeBtn">멀티 분석 실행</button>
<button id="startChartsJobBtn">차트 비동기 생성 시작</button>
<button id="retryChartsJobBtn" disabled>차트 실패 재시도</button>
</div>
<pre id="chartsJobStatus">차트 작업 대기 중</pre>
</section>

<section class="panel advanced-only" id="dashboardPanel">
<h2>고급: 멀티 분석 대시보드(JSON)</h2>
<textarea id="dashboardJson" rows="10" placeholder='{"file_count":2,...}'></textarea>
<div class="actions">
<button id="renderDashboardBtn">대시보드 렌더링</button>
</div>
<div id="dashboardCards" class="cards"></div>

<h3>인사이트 필터</h3>
<div class="row">
<div>
<label>파일</label>
<select id="filterFile"></select>
</div>
<div>
<label>컬럼명</label>
<input id="filterColumn" placeholder="예: amount" />
</div>
<div>
<label>유형</label>
<select id="filterType"></select>
</div>
</div>

<h3>인사이트 리스트</h3>
<div id="insightList" class="insight-list"></div>

<h3>이유 후보 보기</h3>
<pre id="reasonCandidates">이유 후보가 없습니다.</pre>

<h3>드릴다운(근거 데이터)</h3>
<pre id="insightDrilldown">인사이트를 선택하면 근거 데이터가 표시됩니다.</pre>

<section class="advanced-only" hidden>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove permanent hidden flag from advanced controls section

The new <section class="advanced-only" hidden> makes all advanced controls permanently non-rendered, because setMode in app.js only toggles style.display for .advanced-only elements and never clears the hidden attribute. In this commit, that hidden section now contains both mode-toggle buttons and the multi/dashboard/geo inputs, so users cannot enter advanced mode or supply inputs required by multi/visualize flows from the UI.

Useful? React with 👍 / 👎.

<button class="mode-btn active" data-mode="quick" id="modeQuickBtn">빠른 시작</button>
<button class="mode-btn" data-mode="advanced" id="modeAdvancedBtn">고급 모드</button>
<ol id="modeGuide" class="quick-guide" aria-live="polite"></ol>
<button id="showVizOptionsBtn" type="button">시각화 옵션 보기</button>
<pre id="vizRecommendation"></pre>
<button id="copyPrompt">복사</button>
<button id="switchToCsvBtn" type="button">CSV 업로드로 전환</button>
<span id="confidenceBadge" class="chip">신뢰도 낮음</span>
<p id="analyzeRecommendation" class="sub"></p>
<div id="analyzeAssist"></div>
<div id="candidateTableWrap"><select id="candidateTableSelect"></select><pre id="candidateTablePreview"></pre></div>
<input id="multiCsvFiles" type="file" multiple />
<input id="groupColumn" />
<input id="targetColumn" />
<button id="multiAnalyzeBtn">멀티 분석 실행</button>
<button id="startChartsJobBtn">차트 비동기 생성 시작</button>
<button id="retryChartsJobBtn">차트 실패 재시도</button>
<pre id="chartsJobStatus"></pre>
<button id="retryPreprocessBtn">전처리 실패 재시도</button>
<pre id="preprocessStatus"></pre>
<textarea id="dashboardJson"></textarea>
<button id="renderDashboardBtn">대시보드 렌더링</button>
<div id="dashboardCards"></div>
<select id="filterFile"></select>
<input id="filterColumn" />
<select id="filterType"></select>
<div id="insightList"></div>
<pre id="reasonCandidates"></pre>
<pre id="insightDrilldown"></pre>
<pre id="dashboardInsights"></pre>
<input id="geoLatCol" />
<input id="geoLonCol" />
<input id="geoThreshold" type="number" value="25" />
<button id="geoExtractBtn" type="button">의심 케이스 추출 다운로드</button>
<pre id="geoResult"></pre>
</section>
</main>
<script src="/app.js"></script>
Expand Down
14 changes: 14 additions & 0 deletions bitnet_tools/ui/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,17 @@ select {
border-radius: 8px;
padding: 10px;
}

.simple-panel textarea#question {
font-size: 16px;
}
.result-card {
max-width: 720px;
margin: 0 auto;
}
.result-card pre {
min-height: 140px;
}
.container {
max-width: 840px;
}