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:
@@ -930,13 +930,100 @@ async function openIssueDetailModal(issueId) {
|
||||
|
||||
// 모달 표시
|
||||
document.getElementById('issueDetailModal').classList.remove('hidden');
|
||||
|
||||
// AI 유사 부적합 자동 로드
|
||||
const aiPanel = document.getElementById('aiSimilarPanel');
|
||||
if (aiPanel) {
|
||||
aiPanel.classList.remove('hidden');
|
||||
loadSimilarIssues();
|
||||
}
|
||||
}
|
||||
|
||||
function closeIssueDetailModal() {
|
||||
document.getElementById('issueDetailModal').classList.add('hidden');
|
||||
const aiPanel = document.getElementById('aiSimilarPanel');
|
||||
if (aiPanel) aiPanel.classList.add('hidden');
|
||||
// RAG 결과 초기화
|
||||
const suggestResult = document.getElementById('aiSuggestResult');
|
||||
if (suggestResult) suggestResult.classList.add('hidden');
|
||||
currentModalIssueId = null;
|
||||
}
|
||||
|
||||
// RAG: AI 해결방안 제안
|
||||
async function aiSuggestSolution() {
|
||||
if (!currentModalIssueId || typeof AiAPI === 'undefined') return;
|
||||
const btn = document.getElementById('aiSuggestSolutionBtn');
|
||||
const loading = document.getElementById('aiSuggestLoading');
|
||||
const result = document.getElementById('aiSuggestResult');
|
||||
const content = document.getElementById('aiSuggestContent');
|
||||
const sources = document.getElementById('aiSuggestSources');
|
||||
|
||||
if (btn) btn.disabled = true;
|
||||
if (loading) loading.classList.remove('hidden');
|
||||
if (result) result.classList.add('hidden');
|
||||
|
||||
const data = await AiAPI.suggestSolution(currentModalIssueId);
|
||||
|
||||
if (loading) loading.classList.add('hidden');
|
||||
if (btn) btn.disabled = false;
|
||||
|
||||
if (!data.available) {
|
||||
if (content) content.textContent = 'AI 서비스를 사용할 수 없습니다';
|
||||
if (result) result.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
if (content) content.textContent = data.suggestion || '';
|
||||
if (sources && data.referenced_issues) {
|
||||
const refs = data.referenced_issues
|
||||
.filter(r => r.has_solution)
|
||||
.map(r => `No.${r.id}(${r.similarity}%)`)
|
||||
.join(', ');
|
||||
sources.textContent = refs ? `참고 사례: ${refs}` : '';
|
||||
}
|
||||
if (result) result.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// AI 유사 부적합 검색
|
||||
async function loadSimilarIssues() {
|
||||
if (!currentModalIssueId || typeof AiAPI === 'undefined') return;
|
||||
const loading = document.getElementById('aiSimilarLoading');
|
||||
const results = document.getElementById('aiSimilarResults');
|
||||
const empty = document.getElementById('aiSimilarEmpty');
|
||||
if (loading) loading.classList.remove('hidden');
|
||||
if (results) results.innerHTML = '';
|
||||
if (empty) empty.classList.add('hidden');
|
||||
|
||||
const data = await AiAPI.getSimilarIssues(currentModalIssueId, 5);
|
||||
if (loading) loading.classList.add('hidden');
|
||||
|
||||
if (!data.available || !data.results || data.results.length === 0) {
|
||||
if (empty) empty.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
results.innerHTML = data.results.map(r => {
|
||||
const meta = r.metadata || {};
|
||||
const similarity = Math.round((r.similarity || 0) * 100);
|
||||
const issueId = meta.issue_id || r.id.replace('issue_', '');
|
||||
const doc = (r.document || '').substring(0, 80);
|
||||
const cat = meta.category || '';
|
||||
return `
|
||||
<div class="bg-purple-50 border border-purple-100 rounded-lg p-3 cursor-pointer hover:bg-purple-100 transition-colors"
|
||||
onclick="openIssueDetailModal(${issueId})"
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-xs font-medium text-purple-700">No.${issueId}</span>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full ${similarity >= 70 ? 'bg-purple-200 text-purple-800' : 'bg-gray-200 text-gray-600'}">
|
||||
${similarity}% 유사
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-600 line-clamp-2">${doc}...</p>
|
||||
${cat ? `<span class="text-xs text-purple-500 mt-1 inline-block">${cat}</span>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function createModalContent(issue, project) {
|
||||
return `
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
@@ -1186,17 +1273,8 @@ function getPriorityBadge(priority) {
|
||||
return `<span class="badge ${p.class}">${p.text}</span>`;
|
||||
}
|
||||
|
||||
// API 스크립트 동적 로딩
|
||||
const script = document.createElement('script');
|
||||
script.src = '/static/js/api.js?v=20260213';
|
||||
script.onload = function() {
|
||||
console.log('✅ API 스크립트 로드 완료 (issues-management.js)');
|
||||
initializeManagement();
|
||||
};
|
||||
script.onerror = function() {
|
||||
console.error('❌ API 스크립트 로드 실패');
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
// 초기화 (api.js는 HTML에서 로드됨)
|
||||
initializeManagement();
|
||||
|
||||
// 추가 정보 모달 관련 함수들
|
||||
let selectedIssueId = null;
|
||||
|
||||
Reference in New Issue
Block a user