📦 Initialize Geulbeot structure and merge Prompts & test projects
This commit is contained in:
288
03. Code/geulbeot_10th/static/js/domain_selector.js
Normal file
288
03. Code/geulbeot_10th/static/js/domain_selector.js
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* domain_selector.js
|
||||
*
|
||||
* 도메인 지식 선택 시스템
|
||||
* - 폴더 설정 직후 자동으로 도메인 선택 모달 표시
|
||||
* - 체크박스로 도메인 선택 → 선택된 .txt 합쳐서 서버 전달
|
||||
* - "세부 분야별" 선택 시 하위 항목 펼침
|
||||
*/
|
||||
|
||||
// ===== 상태 =====
|
||||
let domainConfig = null; // 서버에서 로드한 config
|
||||
let selectedDomains = []; // 선택된 도메인 ID 배열
|
||||
let domainLoaded = false;
|
||||
|
||||
// ===== 초기화 =====
|
||||
async function loadDomainConfig() {
|
||||
try {
|
||||
const resp = await fetch('/api/domain-config');
|
||||
if (!resp.ok) throw new Error('도메인 설정 로드 실패');
|
||||
domainConfig = await resp.json();
|
||||
domainLoaded = true;
|
||||
console.log('[Domain] 설정 로드 완료:', domainConfig.categories.length, '카테고리');
|
||||
} catch (e) {
|
||||
console.error('[Domain] 설정 로드 실패:', e);
|
||||
// fallback: 빈 config
|
||||
domainConfig = { categories: [] };
|
||||
}
|
||||
}
|
||||
|
||||
// 페이지 로드 시 config 가져오기
|
||||
document.addEventListener('DOMContentLoaded', loadDomainConfig);
|
||||
|
||||
|
||||
// ===== 모달 열기/닫기 =====
|
||||
|
||||
function openDomainModal() {
|
||||
if (!domainConfig || !domainConfig.categories) {
|
||||
alert('도메인 설정을 불러오는 중입니다. 잠시 후 다시 시도해주세요.');
|
||||
return;
|
||||
}
|
||||
renderDomainModal();
|
||||
document.getElementById('domainModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeDomainModal() {
|
||||
document.getElementById('domainModal').style.display = 'none';
|
||||
}
|
||||
|
||||
|
||||
// ===== 모달 렌더링 =====
|
||||
|
||||
function renderDomainModal() {
|
||||
const container = document.getElementById('domainCategoryList');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
domainConfig.categories.forEach(cat => {
|
||||
const catDiv = document.createElement('div');
|
||||
catDiv.className = 'domain-category';
|
||||
catDiv.dataset.id = cat.id;
|
||||
|
||||
// 메인 체크박스 행
|
||||
const isChecked = selectedDomains.includes(cat.id);
|
||||
catDiv.innerHTML = `
|
||||
<label class="domain-cat-label ${isChecked ? 'checked' : ''}">
|
||||
<input type="checkbox" value="${cat.id}"
|
||||
${isChecked ? 'checked' : ''}
|
||||
onchange="toggleDomainCategory('${cat.id}', this.checked)">
|
||||
<span class="domain-cat-icon">${cat.icon}</span>
|
||||
<span class="domain-cat-text">
|
||||
<span class="domain-cat-name">${cat.label}</span>
|
||||
<span class="domain-cat-desc">${cat.description}</span>
|
||||
</span>
|
||||
${cat.children && cat.children.length > 0 ?
|
||||
`<span class="domain-cat-expand ${isChecked ? 'open' : ''}" id="expand_${cat.id}">▼</span>` : ''}
|
||||
</label>
|
||||
`;
|
||||
|
||||
// 하위 항목이 있으면 서브 패널 추가
|
||||
if (cat.children && cat.children.length > 0) {
|
||||
const subPanel = document.createElement('div');
|
||||
subPanel.className = 'domain-sub-panel';
|
||||
subPanel.id = `sub_${cat.id}`;
|
||||
subPanel.style.display = isChecked ? 'block' : 'none';
|
||||
|
||||
// 그룹별로 묶기
|
||||
const groups = {};
|
||||
cat.children.forEach(child => {
|
||||
const g = child.group || '기타';
|
||||
if (!groups[g]) groups[g] = [];
|
||||
groups[g].push(child);
|
||||
});
|
||||
|
||||
Object.entries(groups).forEach(([groupName, children]) => {
|
||||
const groupDiv = document.createElement('div');
|
||||
groupDiv.className = 'domain-sub-group';
|
||||
groupDiv.innerHTML = `<div class="domain-sub-group-label">${groupName}</div>`;
|
||||
|
||||
const chipsDiv = document.createElement('div');
|
||||
chipsDiv.className = 'domain-sub-chips';
|
||||
|
||||
children.forEach(child => {
|
||||
const childChecked = selectedDomains.includes(child.id);
|
||||
chipsDiv.innerHTML += `
|
||||
<label class="domain-chip ${childChecked ? 'selected' : ''}">
|
||||
<input type="checkbox" value="${child.id}"
|
||||
${childChecked ? 'checked' : ''}
|
||||
onchange="toggleDomainChild('${cat.id}', '${child.id}', this.checked)">
|
||||
<span>${child.label}</span>
|
||||
</label>
|
||||
`;
|
||||
});
|
||||
|
||||
groupDiv.appendChild(chipsDiv);
|
||||
subPanel.appendChild(groupDiv);
|
||||
});
|
||||
|
||||
catDiv.appendChild(subPanel);
|
||||
}
|
||||
|
||||
container.appendChild(catDiv);
|
||||
});
|
||||
|
||||
// 선택 요약 업데이트
|
||||
updateDomainSummary();
|
||||
}
|
||||
|
||||
|
||||
// ===== 선택 로직 =====
|
||||
|
||||
function toggleDomainCategory(catId, checked) {
|
||||
const cat = domainConfig.categories.find(c => c.id === catId);
|
||||
if (!cat) return;
|
||||
|
||||
if (checked) {
|
||||
// 카테고리 추가
|
||||
if (!selectedDomains.includes(catId)) {
|
||||
selectedDomains.push(catId);
|
||||
}
|
||||
|
||||
// 하위 항목이 있으면 서브 패널 펼침
|
||||
if (cat.children && cat.children.length > 0) {
|
||||
const subPanel = document.getElementById(`sub_${catId}`);
|
||||
if (subPanel) subPanel.style.display = 'block';
|
||||
const expand = document.getElementById(`expand_${catId}`);
|
||||
if (expand) expand.classList.add('open');
|
||||
}
|
||||
} else {
|
||||
// 카테고리 제거
|
||||
selectedDomains = selectedDomains.filter(d => d !== catId);
|
||||
|
||||
// 하위 항목도 전부 제거
|
||||
if (cat.children) {
|
||||
cat.children.forEach(child => {
|
||||
selectedDomains = selectedDomains.filter(d => d !== child.id);
|
||||
});
|
||||
const subPanel = document.getElementById(`sub_${catId}`);
|
||||
if (subPanel) subPanel.style.display = 'none';
|
||||
const expand = document.getElementById(`expand_${catId}`);
|
||||
if (expand) expand.classList.remove('open');
|
||||
}
|
||||
}
|
||||
|
||||
renderDomainModal();
|
||||
}
|
||||
|
||||
function toggleDomainChild(parentId, childId, checked) {
|
||||
if (checked) {
|
||||
if (!selectedDomains.includes(childId)) {
|
||||
selectedDomains.push(childId);
|
||||
}
|
||||
} else {
|
||||
selectedDomains = selectedDomains.filter(d => d !== childId);
|
||||
}
|
||||
|
||||
// 칩 UI 업데이트
|
||||
renderDomainModal();
|
||||
}
|
||||
|
||||
|
||||
// ===== 선택 요약 =====
|
||||
|
||||
function updateDomainSummary() {
|
||||
const summaryEl = document.getElementById('domainSummaryText');
|
||||
if (!summaryEl) return;
|
||||
|
||||
if (selectedDomains.length === 0) {
|
||||
summaryEl.textContent = '선택된 도메인이 없습니다. AI가 자동으로 분야를 판단합니다.';
|
||||
summaryEl.className = 'domain-summary-text empty';
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택된 항목 이름 수집
|
||||
const names = [];
|
||||
domainConfig.categories.forEach(cat => {
|
||||
if (selectedDomains.includes(cat.id)) {
|
||||
names.push(cat.label);
|
||||
}
|
||||
if (cat.children) {
|
||||
cat.children.forEach(child => {
|
||||
if (selectedDomains.includes(child.id)) {
|
||||
names.push(child.label);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
summaryEl.textContent = names.join(', ');
|
||||
summaryEl.className = 'domain-summary-text';
|
||||
}
|
||||
|
||||
|
||||
// ===== 확인 (서버에 전달) =====
|
||||
|
||||
async function submitDomainSelection() {
|
||||
if (selectedDomains.length === 0) {
|
||||
// 선택 없으면 step3 자동 분석 모드
|
||||
closeDomainModal();
|
||||
updateDomainDisplay('자동 분석 (AI 판단)');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/domain-combine', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ selected: selectedDomains })
|
||||
});
|
||||
|
||||
const data = await resp.json();
|
||||
|
||||
if (data.success) {
|
||||
closeDomainModal();
|
||||
|
||||
// 사이드바에 선택 결과 표시
|
||||
const names = data.selected_names || selectedDomains;
|
||||
updateDomainDisplay(names.join(', '));
|
||||
|
||||
console.log('[Domain] 도메인 지식 결합 완료:', data.combined_length, '자');
|
||||
} else {
|
||||
alert('도메인 지식 결합 실패: ' + (data.error || '알 수 없는 오류'));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Domain] 서버 전달 실패:', e);
|
||||
alert('서버 통신 오류가 발생했습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ===== 사이드바 표시 업데이트 =====
|
||||
|
||||
function updateDomainDisplay(text) {
|
||||
const el = document.getElementById('domainDisplayText');
|
||||
if (el) {
|
||||
el.textContent = text;
|
||||
el.className = 'domain-display-text' + (text.includes('자동') ? ' auto' : ' selected');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ===== 전체 선택/해제 =====
|
||||
|
||||
function selectAllDomains() {
|
||||
selectedDomains = [];
|
||||
domainConfig.categories.forEach(cat => {
|
||||
selectedDomains.push(cat.id);
|
||||
if (cat.children) {
|
||||
cat.children.forEach(child => selectedDomains.push(child.id));
|
||||
}
|
||||
});
|
||||
renderDomainModal();
|
||||
}
|
||||
|
||||
function clearAllDomains() {
|
||||
selectedDomains = [];
|
||||
renderDomainModal();
|
||||
}
|
||||
|
||||
|
||||
// ===== 폴더 설정 후 자동 호출 =====
|
||||
// 기존 submitFolder() 함수에서 성공 후 이 함수 호출
|
||||
function onFolderSetComplete() {
|
||||
// 약간의 딜레이 후 도메인 선택 모달 표시
|
||||
setTimeout(() => {
|
||||
openDomainModal();
|
||||
}, 500);
|
||||
}
|
||||
Reference in New Issue
Block a user