feat: AI 서비스 및 AI 어시스턴트 전용 페이지 추가

- ai-service: Ollama 기반 AI 서비스 (분류, 시맨틱 검색, RAG Q&A, 패턴 분석)
- AI 어시스턴트 페이지: 채팅형 Q&A, 시맨틱 검색, 패턴 분석, 분류 테스트
- 권한 시스템에 ai_assistant 페이지 등록 (기본 비활성)
- 기존 페이지에 AI 기능 통합 (대시보드, 수신함, 관리함)
- docker-compose, gateway, nginx 설정 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-06 09:38:30 +09:00
parent d385ce7ac1
commit b3012b8320
44 changed files with 2914 additions and 53 deletions

View File

@@ -879,14 +879,81 @@ function showError(message) {
alert(message);
}
// API 스크립트 동적 로딩
const script = document.createElement('script');
script.src = '/static/js/api.js?v=20260213';
script.onload = function() {
console.log('API 스크립트 로드 완료 (issues-inbox.html)');
initializeInbox();
};
script.onerror = function() {
console.error('API 스크립트 로드 실패');
};
document.head.appendChild(script);
// AI 분류 추천
async function aiClassifyCurrentIssue() {
if (!currentIssueId || typeof AiAPI === 'undefined') return;
const issue = issues.find(i => i.id === currentIssueId);
if (!issue) return;
const btn = document.getElementById('aiClassifyBtn');
const loading = document.getElementById('aiClassifyLoading');
const result = document.getElementById('aiClassifyResult');
if (btn) btn.disabled = true;
if (loading) loading.classList.remove('hidden');
if (result) result.classList.add('hidden');
// RAG 강화 분류 사용 (과거 사례 참고)
const classifyFn = AiAPI.classifyWithRAG || AiAPI.classifyIssue;
const data = await classifyFn(
issue.description || issue.final_description || '',
issue.detail_notes || ''
);
if (loading) loading.classList.add('hidden');
if (btn) btn.disabled = false;
if (!data.available) {
if (result) {
result.innerHTML = '<p class="text-xs text-red-500">AI 서비스를 사용할 수 없습니다</p>';
result.classList.remove('hidden');
}
return;
}
const categoryMap = {
'material_missing': '자재 누락',
'design_error': '설계 오류',
'incoming_defect': '반입 불량',
'inspection_miss': '검사 누락',
};
const deptMap = {
'production': '생산',
'quality': '품질',
'purchasing': '구매',
'design': '설계',
'sales': '영업',
};
const cat = data.category || '';
const dept = data.responsible_department || '';
const severity = data.severity || '';
const summary = data.summary || '';
const confidence = data.category_confidence ? Math.round(data.category_confidence * 100) : '';
result.innerHTML = `
<div class="space-y-1">
<p><strong>분류:</strong> ${categoryMap[cat] || cat} ${confidence ? `(${confidence}%)` : ''}</p>
<p><strong>부서:</strong> ${deptMap[dept] || dept}</p>
<p><strong>심각도:</strong> ${severity}</p>
${summary ? `<p><strong>요약:</strong> ${summary}</p>` : ''}
<button onclick="applyAiClassification('${cat}')"
class="mt-2 px-3 py-1 bg-purple-600 text-white text-xs rounded hover:bg-purple-700">
<i class="fas fa-check mr-1"></i>적용
</button>
</div>
`;
result.classList.remove('hidden');
}
function applyAiClassification(category) {
const reviewCategory = document.getElementById('reviewCategory');
if (reviewCategory && category) {
reviewCategory.value = category;
}
if (window.showToast) {
window.showToast('AI 추천이 적용되었습니다', 'success');
}
}
// 초기화 (api.js는 HTML에서 로드됨)
initializeInbox();