카카오톡 인앱 WebView는 서브도메인 간 쿠키를 공유하지 않아 tkds에서 로그인 후 tkfb로 리다이렉트 시 인증이 풀리는 문제. - sso-relay.js: URL hash의 _sso= 토큰을 로컬 쿠키+localStorage로 설정 - gateway dashboard: 로그인 후 redirect URL에 #_sso=<token> 추가 - 전 서비스 HTML: core JS 직전에 sso-relay.js 로드 (81개 파일) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
288 lines
17 KiB
HTML
288 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AI 어시스턴트</title>
|
|
<link rel="preload" href="https://cdn.tailwindcss.com" as="script">
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<link rel="stylesheet" href="/static/css/tkqc-common.css?v=2026031401">
|
|
<link rel="stylesheet" href="/static/css/ai-assistant.css?v=2026031401">
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
<script src="/static/js/lib/purify.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<!-- 로딩 스크린 -->
|
|
<div id="loadingScreen" class="fixed inset-0 bg-white z-50 flex items-center justify-center">
|
|
<div class="text-center">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-purple-600 mx-auto mb-4"></div>
|
|
<p class="text-gray-600">AI 어시스턴트를 불러오는 중...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 메인 콘텐츠 -->
|
|
<div id="mainContent" class="min-h-screen">
|
|
<!-- 공통 헤더 -->
|
|
<div id="commonHeader"></div>
|
|
|
|
<main class="container mx-auto px-4 py-8 content-fade-in" style="padding-top: 72px;">
|
|
<!-- 페이지 헤더 -->
|
|
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-900 flex items-center">
|
|
<i class="fas fa-robot text-purple-500 mr-3"></i>
|
|
AI 어시스턴트
|
|
</h1>
|
|
<p class="text-gray-600 mt-1">AI 기반 부적합 분석, 검색, 질의응답을 한곳에서 사용하세요</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 1. 상태 카드 (3열 그리드) -->
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
<div class="status-card bg-white rounded-xl shadow-sm p-5 border-l-4 border-purple-500">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-gray-500">AI 서비스</p>
|
|
<p class="text-lg font-bold mt-1" id="aiStatusText">확인 중...</p>
|
|
</div>
|
|
<div id="aiStatusIcon" class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center">
|
|
<i class="fas fa-spinner fa-spin text-gray-400"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="status-card bg-white rounded-xl shadow-sm p-5 border-l-4 border-indigo-500">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-gray-500">임베딩 데이터</p>
|
|
<p class="text-lg font-bold mt-1" id="aiEmbeddingCount">-</p>
|
|
</div>
|
|
<div class="w-10 h-10 rounded-full bg-indigo-50 flex items-center justify-center">
|
|
<i class="fas fa-database text-indigo-500"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="status-card bg-white rounded-xl shadow-sm p-5 border-l-4 border-blue-500">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-gray-500">AI 모델</p>
|
|
<p class="text-lg font-bold mt-1" id="aiModelName">-</p>
|
|
</div>
|
|
<div class="w-10 h-10 rounded-full bg-blue-50 flex items-center justify-center">
|
|
<i class="fas fa-brain text-blue-500"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 2. AI Q&A (메인 — 채팅형) -->
|
|
<div class="section-card bg-white rounded-xl shadow-sm p-6 mb-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="flex items-center">
|
|
<i class="fas fa-comments text-purple-500 mr-2"></i>
|
|
<h2 class="text-lg font-semibold text-gray-800">AI Q&A</h2>
|
|
<span class="ml-2 text-xs bg-purple-100 text-purple-600 px-2 py-0.5 rounded-full">과거 사례 기반</span>
|
|
</div>
|
|
<button onclick="clearChat()" class="text-sm text-gray-400 hover:text-gray-600 transition-colors">
|
|
<i class="fas fa-trash-alt mr-1"></i>대화 초기화
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 채팅 히스토리 -->
|
|
<div id="chatContainer" class="chat-container bg-gray-50 rounded-lg p-4 mb-4 min-h-[200px]">
|
|
<div class="text-center text-gray-400 text-sm py-8" id="chatPlaceholder">
|
|
<i class="fas fa-robot text-4xl mb-3 text-gray-300"></i>
|
|
<p>부적합 관련 질문을 입력하세요.</p>
|
|
<p class="text-xs mt-1">과거 사례를 분석하여 답변합니다.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 빠른 질문 템플릿 -->
|
|
<div class="flex flex-wrap gap-2 mb-3">
|
|
<button onclick="setQuickQuestion('최근 가장 많이 발생하는 부적합 유형은?')"
|
|
class="quick-question-btn text-xs px-3 py-1.5 rounded-full bg-white text-gray-600">
|
|
<i class="fas fa-chart-pie mr-1 text-purple-400"></i>많이 발생하는 유형
|
|
</button>
|
|
<button onclick="setQuickQuestion('용접 불량의 주요 원인과 해결방법은?')"
|
|
class="quick-question-btn text-xs px-3 py-1.5 rounded-full bg-white text-gray-600">
|
|
<i class="fas fa-fire mr-1 text-orange-400"></i>용접 불량 원인
|
|
</button>
|
|
<button onclick="setQuickQuestion('자재 관련 부적합을 줄이려면 어떻게 해야 하나요?')"
|
|
class="quick-question-btn text-xs px-3 py-1.5 rounded-full bg-white text-gray-600">
|
|
<i class="fas fa-box mr-1 text-blue-400"></i>자재 부적합 개선
|
|
</button>
|
|
<button onclick="setQuickQuestion('반복적으로 발생하는 부적합 패턴이 있나요?')"
|
|
class="quick-question-btn text-xs px-3 py-1.5 rounded-full bg-white text-gray-600">
|
|
<i class="fas fa-redo mr-1 text-red-400"></i>반복 패턴 분석
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 입력 영역 -->
|
|
<div class="flex flex-col md:flex-row gap-2">
|
|
<div class="flex-1">
|
|
<textarea id="qaQuestion" rows="3"
|
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 resize-none text-sm"
|
|
placeholder="질문을 입력하세요... (Ctrl+Enter로 전송)"
|
|
onkeydown="if(event.ctrlKey && event.key==='Enter') submitQuestion()"></textarea>
|
|
</div>
|
|
<div class="flex flex-col gap-2 md:w-48">
|
|
<select id="qaProjectFilter" class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500">
|
|
<option value="">전체 프로젝트</option>
|
|
</select>
|
|
<button onclick="submitQuestion()"
|
|
class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors text-sm font-medium">
|
|
<i class="fas fa-paper-plane mr-1"></i>질문하기
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 3. 시맨틱 검색 -->
|
|
<div class="section-card bg-white rounded-xl shadow-sm p-6 mb-6">
|
|
<div class="flex items-center mb-4">
|
|
<i class="fas fa-search text-indigo-500 mr-2"></i>
|
|
<h2 class="text-lg font-semibold text-gray-800">시맨틱 검색</h2>
|
|
<span class="ml-2 text-xs bg-indigo-100 text-indigo-600 px-2 py-0.5 rounded-full">유사 부적합 찾기</span>
|
|
</div>
|
|
|
|
<div class="flex flex-col md:flex-row gap-2 mb-4">
|
|
<input type="text" id="searchQuery"
|
|
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm"
|
|
placeholder="부적합 내용을 자연어로 검색하세요... (예: 볼트 누락, 용접 불량)"
|
|
onkeydown="if(event.key==='Enter') executeSemanticSearch()">
|
|
<select id="searchProjectFilter" class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-indigo-500 md:w-40">
|
|
<option value="">전체 프로젝트</option>
|
|
</select>
|
|
<select id="searchCategoryFilter" class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-indigo-500 md:w-40">
|
|
<option value="">전체 카테고리</option>
|
|
<option value="civil">토목</option>
|
|
<option value="architecture">건축</option>
|
|
<option value="mechanical">기계</option>
|
|
<option value="electrical">전기</option>
|
|
<option value="piping">배관</option>
|
|
<option value="instrument">계장</option>
|
|
<option value="painting">도장</option>
|
|
<option value="insulation">보온</option>
|
|
<option value="fireproof">내화</option>
|
|
<option value="other">기타</option>
|
|
</select>
|
|
<select id="searchResultCount" class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-indigo-500 md:w-28">
|
|
<option value="5">5건</option>
|
|
<option value="10" selected>10건</option>
|
|
<option value="20">20건</option>
|
|
</select>
|
|
<button onclick="executeSemanticSearch()"
|
|
class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors text-sm whitespace-nowrap">
|
|
<i class="fas fa-search mr-1"></i>검색
|
|
</button>
|
|
</div>
|
|
|
|
<div id="searchLoading" class="hidden text-center py-4">
|
|
<i class="fas fa-spinner fa-spin text-indigo-500 mr-1"></i>
|
|
<span class="text-sm text-gray-500">AI 검색 중...</span>
|
|
</div>
|
|
<div id="searchResults" class="space-y-2">
|
|
<!-- 검색 결과 -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 4. 패턴 분석 -->
|
|
<div class="section-card bg-white rounded-xl shadow-sm p-6 mb-6">
|
|
<div class="flex items-center mb-4">
|
|
<i class="fas fa-chart-bar text-green-500 mr-2"></i>
|
|
<h2 class="text-lg font-semibold text-gray-800">패턴 분석</h2>
|
|
<span class="ml-2 text-xs bg-green-100 text-green-600 px-2 py-0.5 rounded-full">부적합 패턴 파악</span>
|
|
</div>
|
|
|
|
<div class="flex flex-col md:flex-row gap-2 mb-4">
|
|
<textarea id="patternInput" rows="2"
|
|
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 resize-none text-sm"
|
|
placeholder="분석할 부적합 내용을 입력하세요... (예: 배관 용접부 결함)"></textarea>
|
|
<button onclick="executePatternAnalysis()"
|
|
class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors text-sm whitespace-nowrap self-end">
|
|
<i class="fas fa-chart-bar mr-1"></i>패턴 분석
|
|
</button>
|
|
</div>
|
|
|
|
<div id="patternLoading" class="hidden text-center py-4">
|
|
<i class="fas fa-spinner fa-spin text-green-500 mr-1"></i>
|
|
<span class="text-sm text-gray-500">패턴 분석 중...</span>
|
|
</div>
|
|
<div id="patternResults" class="hidden">
|
|
<!-- 패턴 분석 결과 -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 5. AI 분류 테스트 -->
|
|
<div class="section-card bg-white rounded-xl shadow-sm p-6 mb-6">
|
|
<div class="flex items-center mb-4">
|
|
<i class="fas fa-tags text-amber-500 mr-2"></i>
|
|
<h2 class="text-lg font-semibold text-gray-800">AI 분류 테스트</h2>
|
|
<span class="ml-2 text-xs bg-amber-100 text-amber-600 px-2 py-0.5 rounded-full">기본 vs RAG 비교</span>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">부적합 설명</label>
|
|
<textarea id="classifyDescription" rows="3"
|
|
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-amber-500 resize-none text-sm"
|
|
placeholder="부적합 설명을 입력하세요..."></textarea>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">상세 내용 (선택)</label>
|
|
<textarea id="classifyDetail" rows="3"
|
|
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-amber-500 resize-none text-sm"
|
|
placeholder="상세 내용을 입력하세요..."></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-2 mb-4">
|
|
<button onclick="executeClassification(false)"
|
|
class="px-4 py-2 bg-amber-500 text-white rounded-lg hover:bg-amber-600 transition-colors text-sm">
|
|
<i class="fas fa-tag mr-1"></i>기본 분류
|
|
</button>
|
|
<button onclick="executeClassification(true)"
|
|
class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors text-sm">
|
|
<i class="fas fa-tags mr-1"></i>RAG 분류
|
|
</button>
|
|
</div>
|
|
|
|
<div id="classifyLoading" class="hidden text-center py-4">
|
|
<i class="fas fa-spinner fa-spin text-amber-500 mr-1"></i>
|
|
<span class="text-sm text-gray-500">AI 분류 중...</span>
|
|
</div>
|
|
<div id="classifyResults" class="hidden">
|
|
<!-- 분류 결과 -->
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- AI 이슈 상세 모달 -->
|
|
<div id="aiIssueModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center" onclick="if(event.target===this)this.classList.add('hidden')">
|
|
<div class="bg-white rounded-xl shadow-2xl w-full max-w-lg mx-4 max-h-[80vh] overflow-y-auto">
|
|
<div class="sticky top-0 bg-white border-b px-5 py-3 flex justify-between items-center rounded-t-xl">
|
|
<h3 id="aiIssueModalTitle" class="font-bold text-gray-800"></h3>
|
|
<button onclick="document.getElementById('aiIssueModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div id="aiIssueModalBody" class="p-5 text-sm text-gray-700 space-y-3"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 스크립트 -->
|
|
<script src="/static/js/core/permissions.js?v=2026031401"></script>
|
|
<script src="/static/js/components/common-header.js?v=2026031401"></script>
|
|
<script src="/static/js/core/page-manager.js?v=2026031401"></script>
|
|
<script src="/static/js/api.js?v=2026031401"></script>
|
|
<script src="/static/js/sso-relay.js?v=20260401"></script>
|
|
<script src="/static/js/core/auth-manager.js?v=2026031401"></script>
|
|
<script src="/static/js/utils/issue-helpers.js?v=2026031401"></script>
|
|
<script src="/static/js/utils/toast.js?v=2026031401"></script>
|
|
<script src="/static/js/components/mobile-bottom-nav.js?v=2026031401"></script>
|
|
<script src="/static/js/pages/ai-assistant.js?v=2026031401"></script>
|
|
</body>
|
|
</html>
|