jeongsoo commited on
Commit
0230ecf
Β·
2 Parent(s): 01a3606 342cb1b

Fix requirements.txt file

Browse files
Files changed (4) hide show
  1. Procfile +4 -0
  2. app/app.py +45 -22
  3. app/static/js/app.js +27 -0
  4. app/templates/loading.html +75 -0
Procfile CHANGED
@@ -1 +1,5 @@
 
1
  web: gunicorn -b 0.0.0.0:$PORT app:app
 
 
 
 
1
+ <<<<<<< HEAD
2
  web: gunicorn -b 0.0.0.0:$PORT app:app
3
+ =======
4
+ web: gunicorn app:app
5
+ >>>>>>> 342cb1bea06d143718684d40b8f294967c3bbcae
app/app.py CHANGED
@@ -6,6 +6,7 @@ import os
6
  import json
7
  import logging
8
  import tempfile
 
9
  from flask import Flask, request, jsonify, render_template, send_from_directory
10
  from werkzeug.utils import secure_filename
11
  from dotenv import load_dotenv
@@ -57,6 +58,9 @@ stt_client = VitoSTT()
57
  base_retriever = None
58
  retriever = None
59
 
 
 
 
60
  def allowed_audio_file(filename):
61
  """파일이 ν—ˆμš©λœ μ˜€λ””μ˜€ ν™•μž₯자λ₯Ό κ°€μ§€λŠ”μ§€ 확인"""
62
  return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_AUDIO_EXTENSIONS
@@ -148,19 +152,44 @@ def init_retriever():
148
 
149
  return retriever
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  @app.route('/')
152
  def index():
153
  """메인 νŽ˜μ΄μ§€"""
 
 
154
  return render_template('index.html')
155
 
 
 
 
 
 
156
  @app.route('/api/chat', methods=['POST'])
157
  def chat():
158
  """ν…μŠ€νŠΈ 기반 챗봇 API"""
159
- global retriever
160
 
161
- # 검색기가 μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ€ 경우 μ΄ˆκΈ°ν™”
162
- if retriever is None:
163
- retriever = init_retriever()
164
 
165
  try:
166
  data = request.get_json()
@@ -208,11 +237,11 @@ def chat():
208
  @app.route('/api/voice', methods=['POST'])
209
  def voice_chat():
210
  """μŒμ„± 기반 챗봇 API"""
211
- global retriever
212
 
213
- # 검색기가 μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ€ 경우 μ΄ˆκΈ°ν™”
214
- if retriever is None:
215
- retriever = init_retriever()
216
 
217
  try:
218
  # 파일이 μš”μ²­μ— ν¬ν•¨λ˜μ–΄ μžˆλŠ”μ§€ 확인
@@ -304,11 +333,11 @@ def voice_chat():
304
  @app.route('/api/upload', methods=['POST'])
305
  def upload_document():
306
  """μ§€μ‹λ² μ΄μŠ€ λ¬Έμ„œ μ—…λ‘œλ“œ API"""
307
- global base_retriever, retriever
308
 
309
- # 검색기가 μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ€ 경우 μ΄ˆκΈ°ν™”
310
- if retriever is None:
311
- retriever = init_retriever()
312
 
313
  try:
314
  # 파일이 μš”μ²­μ— ν¬ν•¨λ˜μ–΄ μžˆλŠ”μ§€ 확인
@@ -398,11 +427,11 @@ def upload_document():
398
  @app.route('/api/documents', methods=['GET'])
399
  def list_documents():
400
  """μ§€μ‹λ² μ΄μŠ€ λ¬Έμ„œ λͺ©λ‘ API"""
401
- global base_retriever, retriever
402
 
403
- # 검색기가 μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ€ 경우 μ΄ˆκΈ°ν™”
404
- if retriever is None:
405
- retriever = init_retriever()
406
 
407
  try:
408
  # λ¬Έμ„œ μ†ŒμŠ€ λͺ©λ‘ 생성
@@ -443,12 +472,6 @@ def list_documents():
443
  logger.error(f"λ¬Έμ„œ λͺ©λ‘ 쑰회 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
444
  return jsonify({"error": f"λ¬Έμ„œ λͺ©λ‘ 쑰회 쀑 였λ₯˜: {str(e)}"}), 500
445
 
446
- # μ•± μ‹œμž‘ μ‹œ 검색기 μ΄ˆκΈ°ν™”
447
- @app.before_first_request
448
- def before_first_request():
449
- global retriever
450
- retriever = init_retriever()
451
-
452
  # 정적 파일 μ„œλΉ™
453
  @app.route('/static/<path:path>')
454
  def send_static(path):
 
6
  import json
7
  import logging
8
  import tempfile
9
+ import threading
10
  from flask import Flask, request, jsonify, render_template, send_from_directory
11
  from werkzeug.utils import secure_filename
12
  from dotenv import load_dotenv
 
58
  base_retriever = None
59
  retriever = None
60
 
61
+ # μ•± μ΄ˆκΈ°ν™” μƒνƒœ
62
+ app_ready = False
63
+
64
  def allowed_audio_file(filename):
65
  """파일이 ν—ˆμš©λœ μ˜€λ””μ˜€ ν™•μž₯자λ₯Ό κ°€μ§€λŠ”μ§€ 확인"""
66
  return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_AUDIO_EXTENSIONS
 
152
 
153
  return retriever
154
 
155
+ # 비동기 μ΄ˆκΈ°ν™” ν•¨μˆ˜
156
+ def background_init():
157
+ """λ°±κ·ΈλΌμš΄λ“œμ—μ„œ 검색기 μ΄ˆκΈ°ν™” μˆ˜ν–‰"""
158
+ global app_ready, retriever
159
+ try:
160
+ logger.info("λ°±κ·ΈλΌμš΄λ“œ μ΄ˆκΈ°ν™” μ‹œμž‘")
161
+ retriever = init_retriever()
162
+ app_ready = True
163
+ logger.info("μ•± μ΄ˆκΈ°ν™” μ™„λ£Œ")
164
+ except Exception as e:
165
+ logger.error(f"μ•± μ΄ˆκΈ°ν™” 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
166
+ app_ready = False
167
+
168
+ # λ°±κ·ΈλΌμš΄λ“œ μŠ€λ ˆλ“œμ—μ„œ μ΄ˆκΈ°ν™” μ‹œμž‘
169
+ init_thread = threading.Thread(target=background_init)
170
+ init_thread.daemon = True
171
+ init_thread.start()
172
+
173
  @app.route('/')
174
  def index():
175
  """메인 νŽ˜μ΄μ§€"""
176
+ if not app_ready:
177
+ return render_template('loading.html')
178
  return render_template('index.html')
179
 
180
+ @app.route('/api/status')
181
+ def app_status():
182
+ """μ•± μ΄ˆκΈ°ν™” μƒνƒœ 확인 API"""
183
+ return jsonify({"ready": app_ready})
184
+
185
  @app.route('/api/chat', methods=['POST'])
186
  def chat():
187
  """ν…μŠ€νŠΈ 기반 챗봇 API"""
188
+ global retriever, app_ready
189
 
190
+ # μ•± μ€€λΉ„ μƒνƒœ 확인
191
+ if not app_ready:
192
+ return jsonify({"error": "앱이 아직 μ΄ˆκΈ°ν™” μ€‘μž…λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."}), 503
193
 
194
  try:
195
  data = request.get_json()
 
237
  @app.route('/api/voice', methods=['POST'])
238
  def voice_chat():
239
  """μŒμ„± 기반 챗봇 API"""
240
+ global retriever, app_ready
241
 
242
+ # μ•± μ€€λΉ„ μƒνƒœ 확인
243
+ if not app_ready:
244
+ return jsonify({"error": "앱이 아직 μ΄ˆκΈ°ν™” μ€‘μž…λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."}), 503
245
 
246
  try:
247
  # 파일이 μš”μ²­μ— ν¬ν•¨λ˜μ–΄ μžˆλŠ”μ§€ 확인
 
333
  @app.route('/api/upload', methods=['POST'])
334
  def upload_document():
335
  """μ§€μ‹λ² μ΄μŠ€ λ¬Έμ„œ μ—…λ‘œλ“œ API"""
336
+ global base_retriever, retriever, app_ready
337
 
338
+ # μ•± μ€€λΉ„ μƒνƒœ 확인
339
+ if not app_ready:
340
+ return jsonify({"error": "앱이 아직 μ΄ˆκΈ°ν™” μ€‘μž…λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."}), 503
341
 
342
  try:
343
  # 파일이 μš”μ²­μ— ν¬ν•¨λ˜μ–΄ μžˆλŠ”μ§€ 확인
 
427
  @app.route('/api/documents', methods=['GET'])
428
  def list_documents():
429
  """μ§€μ‹λ² μ΄μŠ€ λ¬Έμ„œ λͺ©λ‘ API"""
430
+ global base_retriever, retriever, app_ready
431
 
432
+ # μ•± μ€€λΉ„ μƒνƒœ 확인
433
+ if not app_ready:
434
+ return jsonify({"error": "앱이 아직 μ΄ˆκΈ°ν™” μ€‘μž…λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."}), 503
435
 
436
  try:
437
  # λ¬Έμ„œ μ†ŒμŠ€ λͺ©λ‘ 생성
 
472
  logger.error(f"λ¬Έμ„œ λͺ©λ‘ 쑰회 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
473
  return jsonify({"error": f"λ¬Έμ„œ λͺ©λ‘ 쑰회 쀑 였λ₯˜: {str(e)}"}), 500
474
 
 
 
 
 
 
 
475
  # 정적 파일 μ„œλΉ™
476
  @app.route('/static/<path:path>')
477
  def send_static(path):
app/static/js/app.js CHANGED
@@ -28,8 +28,35 @@ let mediaRecorder = null;
28
  let audioChunks = [];
29
  let isRecording = false;
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  // νŽ˜μ΄μ§€ λ‘œλ“œ μ‹œ μ΄ˆκΈ°ν™”
32
  document.addEventListener('DOMContentLoaded', () => {
 
 
 
 
 
 
 
 
 
 
 
 
33
  // νƒ­ μ „ν™˜ 이벀트 λ¦¬μŠ€λ„ˆ
34
  chatTab.addEventListener('click', () => {
35
  switchTab('chat');
 
28
  let audioChunks = [];
29
  let isRecording = false;
30
 
31
+ // μ•± μ΄ˆκΈ°ν™” μƒνƒœ 확인 ν•¨μˆ˜
32
+ async function checkAppStatus() {
33
+ try {
34
+ const response = await fetch('/api/status');
35
+ if (!response.ok) {
36
+ return false;
37
+ }
38
+ const data = await response.json();
39
+ return data.ready;
40
+ } catch (error) {
41
+ console.error('Status check failed:', error);
42
+ return false;
43
+ }
44
+ }
45
+
46
  // νŽ˜μ΄μ§€ λ‘œλ“œ μ‹œ μ΄ˆκΈ°ν™”
47
  document.addEventListener('DOMContentLoaded', () => {
48
+ // μ•± μƒνƒœ 확인 (λ‘œλ”© νŽ˜μ΄μ§€κ°€ μ•„λ‹Œ κ²½μš°μ—λ§Œ)
49
+ if (window.location.pathname === '/' && !document.getElementById('app-loading-indicator')) {
50
+ // μ•± μƒνƒœ 주기적으둜 확인
51
+ const statusInterval = setInterval(async () => {
52
+ const isReady = await checkAppStatus();
53
+ if (isReady) {
54
+ clearInterval(statusInterval);
55
+ console.log('앱이 μ€€λΉ„λ˜μ—ˆμŠ΅λ‹ˆλ‹€.');
56
+ }
57
+ }, 5000);
58
+ }
59
+
60
  // νƒ­ μ „ν™˜ 이벀트 λ¦¬μŠ€λ„ˆ
61
  chatTab.addEventListener('click', () => {
62
  switchTab('chat');
app/templates/loading.html ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="refresh" content="5"> <!-- 5μ΄ˆλ§ˆλ‹€ μƒˆλ‘œκ³ μΉ¨ -->
7
+ <title>RAG 검색 챗봇 - μ΄ˆκΈ°ν™” 쀑</title>
8
+ <style>
9
+ body {
10
+ font-family: 'Pretendard', 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
11
+ line-height: 1.6;
12
+ color: #333;
13
+ background-color: #f8f9fa;
14
+ text-align: center;
15
+ padding-top: 100px;
16
+ margin: 0;
17
+ }
18
+ .container {
19
+ max-width: 800px;
20
+ margin: 0 auto;
21
+ padding: 20px;
22
+ }
23
+ h1 {
24
+ color: #4a6da7;
25
+ margin-bottom: 30px;
26
+ }
27
+ .loader {
28
+ border: 16px solid #f3f3f3;
29
+ border-top: 16px solid #4a6da7;
30
+ border-radius: 50%;
31
+ width: 80px;
32
+ height: 80px;
33
+ animation: spin 2s linear infinite;
34
+ margin: 0 auto 30px;
35
+ }
36
+ @keyframes spin {
37
+ 0% { transform: rotate(0deg); }
38
+ 100% { transform: rotate(360deg); }
39
+ }
40
+ p {
41
+ font-size: 18px;
42
+ margin-bottom: 20px;
43
+ }
44
+ .info {
45
+ background-color: #e7f0ff;
46
+ border-radius: 8px;
47
+ padding: 20px;
48
+ margin-top: 30px;
49
+ text-align: left;
50
+ }
51
+ .info h2 {
52
+ color: #4a6da7;
53
+ margin-top: 0;
54
+ }
55
+ </style>
56
+ </head>
57
+ <body>
58
+ <div class="container">
59
+ <h1>RAG 검색 챗봇 μ΄ˆκΈ°ν™” 쀑...</h1>
60
+ <div class="loader"></div>
61
+ <p>첫 μ‹€ν–‰ μ‹œ 데이터 쀀비에 μ‹œκ°„μ΄ μ†Œμš”λ©λ‹ˆλ‹€. μž μ‹œλ§Œ κΈ°λ‹€λ €μ£Όμ„Έμš”.</p>
62
+ <p>νŽ˜μ΄μ§€λŠ” 5μ΄ˆλ§ˆλ‹€ μžλ™μœΌλ‘œ μƒˆλ‘œκ³ μΉ¨λ©λ‹ˆλ‹€.</p>
63
+
64
+ <div class="info">
65
+ <h2>μ΄ˆκΈ°ν™” μž‘μ—…</h2>
66
+ <ul>
67
+ <li>벑터 인덱슀 생성 쀑...</li>
68
+ <li>λ¬Έμ„œ 처리 및 μž„λ² λ”© 생성 쀑...</li>
69
+ <li>검색 μ—”μ§„ μ€€λΉ„ 쀑...</li>
70
+ </ul>
71
+ <p>이 과정은 μ΅œλŒ€ 1-2뢄이 μ†Œμš”λ  수 μžˆμŠ΅λ‹ˆλ‹€.</p>
72
+ </div>
73
+ </div>
74
+ </body>
75
+ </html>