feat: 챗봇 신고 페이지 AI 백엔드 추가 및 기타 개선

- ai-service: 챗봇 분석/요약 엔드포인트 추가 (chatbot.py, chatbot_service.py)
- tkreport: 챗봇 신고 페이지 (chat-report.html/js/css), nginx ai-api 프록시
- tkreport: 이미지 업로드 서비스 개선, M-Project 연동 신고자 정보 전달
- system1: TBM 작업보고서 UI 개선
- TKQC: 관리함/수신함 기능 개선

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-09 14:11:00 +09:00
parent 5aeda43605
commit d42380ff63
16 changed files with 1522 additions and 59 deletions

View File

@@ -19,6 +19,7 @@
<!-- 공통 스타일 및 페이지 전용 스타일 -->
<link rel="stylesheet" href="/static/css/tkqc-common.css?v=20260213">
<link rel="stylesheet" href="/static/css/issues-management.css?v=20260213">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
@@ -193,7 +194,7 @@
<span class="text-xs text-gray-500">과거 사례 분석 중...</span>
</div>
<div id="aiSuggestResult" class="hidden mt-2 bg-indigo-50 border border-indigo-200 rounded-lg p-3">
<div id="aiSuggestContent" class="text-sm text-gray-700 whitespace-pre-line"></div>
<div id="aiSuggestContent" class="text-sm text-gray-700 prose prose-sm max-w-none"></div>
<div id="aiSuggestSources" class="mt-2 text-xs text-indigo-500"></div>
</div>
</div>

View File

@@ -8,20 +8,13 @@ let projects = [];
let filteredIssues = [];
// 한국 시간(KST) 유틸리티 함수
function getKSTDate(date) {
const utcDate = new Date(date);
// UTC + 9시간 = KST
return new Date(utcDate.getTime() + (9 * 60 * 60 * 1000));
}
// DB에 KST로 저장된 naive datetime을 그대로 표시
function formatKSTDate(date) {
const kstDate = getKSTDate(date);
return kstDate.toLocaleDateString('ko-KR', { timeZone: 'Asia/Seoul' });
return new Date(date).toLocaleDateString('ko-KR', { timeZone: 'Asia/Seoul' });
}
function formatKSTTime(date) {
const kstDate = getKSTDate(date);
return kstDate.toLocaleTimeString('ko-KR', {
return new Date(date).toLocaleTimeString('ko-KR', {
timeZone: 'Asia/Seoul',
hour: '2-digit',
minute: '2-digit'
@@ -30,8 +23,8 @@ function formatKSTTime(date) {
function getKSTToday() {
const now = new Date();
const kstNow = getKSTDate(now);
return new Date(kstNow.getFullYear(), kstNow.getMonth(), kstNow.getDate());
const kst = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Seoul' }));
return new Date(kst.getFullYear(), kst.getMonth(), kst.getDate());
}
// 애니메이션 함수들
@@ -365,7 +358,7 @@ async function loadStatistics() {
// 금일 신규: 오늘 올라온 목록 숫자 (확인된 것 포함) - KST 기준
const todayNewCount = issues.filter(issue => {
const reportDate = getKSTDate(new Date(issue.report_date));
const reportDate = new Date(issue.report_date);
const reportDateOnly = new Date(reportDate.getFullYear(), reportDate.getMonth(), reportDate.getDate());
return reportDateOnly >= todayStart;
}).length;
@@ -389,7 +382,7 @@ async function loadStatistics() {
// 미해결: 오늘꺼 제외한 남아있는 것들 - KST 기준
const unresolvedCount = issues.filter(issue => {
const reportDate = getKSTDate(new Date(issue.report_date));
const reportDate = new Date(issue.report_date);
const reportDateOnly = new Date(reportDate.getFullYear(), reportDate.getMonth(), reportDate.getDate());
return reportDateOnly < todayStart;
}).length;
@@ -852,9 +845,9 @@ async function confirmStatus() {
// issue-helpers.js에서 제공됨
function getTimeAgo(date) {
const now = getKSTDate(new Date());
const kstDate = getKSTDate(date);
const diffMs = now - kstDate;
const now = new Date();
const target = new Date(date);
const diffMs = now - target;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);

View File

@@ -461,7 +461,7 @@ function createInProgressRow(issue, project) {
<i class="fas fa-lightbulb mr-2"></i>AI 해결방안 제안 (과거 사례 기반)
</button>
<div id="aiSuggestResult_${issue.id}" class="hidden mt-2 p-3 bg-purple-50 border border-purple-200 rounded-lg">
<p id="aiSuggestContent_${issue.id}" class="text-sm text-gray-800 whitespace-pre-wrap"></p>
<div id="aiSuggestContent_${issue.id}" class="text-sm text-gray-800 prose prose-sm max-w-none"></div>
<p id="aiSuggestSources_${issue.id}" class="text-xs text-purple-600 mt-2"></p>
<button onclick="applyAiSuggestion(${issue.id})" class="mt-2 text-xs px-2 py-1 bg-purple-600 text-white rounded hover:bg-purple-700 transition-colors">
<i class="fas fa-paste mr-1"></i>해결방안에 적용
@@ -985,7 +985,15 @@ async function aiSuggestSolution() {
return;
}
if (content) content.textContent = data.suggestion || '';
if (content) {
const raw = data.suggestion || '';
content.dataset.raw = raw;
if (typeof marked !== 'undefined') {
content.innerHTML = marked.parse(raw);
} else {
content.textContent = raw;
}
}
if (sources && data.referenced_issues) {
const refs = data.referenced_issues
.filter(r => r.has_solution)
@@ -1017,7 +1025,15 @@ async function aiSuggestSolutionInline(issueId) {
return;
}
if (content) content.textContent = data.suggestion || '';
if (content) {
const raw = data.suggestion || '';
content.dataset.raw = raw;
if (typeof marked !== 'undefined') {
content.innerHTML = marked.parse(raw);
} else {
content.textContent = raw;
}
}
if (sources && data.referenced_issues) {
const refs = data.referenced_issues
.filter(r => r.has_solution)
@@ -1033,7 +1049,7 @@ function applyAiSuggestion(issueId) {
const content = document.getElementById(`aiSuggestContent_${issueId}`);
const textarea = document.getElementById(`management_comment_${issueId}`);
if (content && textarea) {
textarea.value = content.textContent;
textarea.value = content.dataset.raw || content.textContent;
textarea.focus();
}
}