Spaces:
Sleeping
Sleeping
init
Browse files- README.md +3 -3
- app/app.py +896 -9
- app/app/__init__.py +0 -3
- app/{app/app_device_routes.py โ app_device_routes.py} +0 -0
- app/{app/app_routes.py โ app_routes.py} +0 -0
- app/docs/cleanup_plan.md +0 -31
- app/docs/cleanup_summary.md +0 -44
- app/docs/project_plan.md +33 -64
- app/{app/init_retriever.py โ init_retriever.py} +0 -0
README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
---
|
2 |
-
title: RAG AgenticServer
|
3 |
emoji: ๐ฅ
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
sdk_version: 5.29.0
|
8 |
app_file: app.py
|
|
|
1 |
---
|
2 |
+
title: RAG AgenticServer
|
3 |
emoji: ๐ฅ
|
4 |
+
colorFrom: pink
|
5 |
+
colorTo: purple
|
6 |
sdk: gradio
|
7 |
sdk_version: 5.29.0
|
8 |
app_file: app.py
|
app/app.py
CHANGED
@@ -1,32 +1,919 @@
|
|
1 |
"""
|
2 |
-
RAG ๊ฒ์ ์ฑ๋ด
|
3 |
"""
|
4 |
|
5 |
import os
|
|
|
6 |
import logging
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
8 |
from dotenv import load_dotenv
|
|
|
9 |
|
10 |
# ๋ก๊ฑฐ ์ค์
|
11 |
logging.basicConfig(
|
12 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
13 |
-
level=logging.DEBUG
|
14 |
)
|
15 |
logger = logging.getLogger(__name__)
|
16 |
|
17 |
# ํ๊ฒฝ ๋ณ์ ๋ก๋
|
18 |
load_dotenv()
|
19 |
|
20 |
-
# ๋ก๊น
|
21 |
-
|
|
|
22 |
|
23 |
-
#
|
24 |
-
|
|
|
25 |
|
26 |
-
|
|
|
|
|
|
|
27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
if __name__ == '__main__':
|
29 |
logger.info("Flask ์ฑ์ ์ง์ ์คํํฉ๋๋ค (๊ฐ๋ฐ์ฉ ์๋ฒ).")
|
|
|
|
|
30 |
port = int(os.environ.get("PORT", 7860))
|
31 |
logger.info(f"์๋ฒ๋ฅผ http://0.0.0.0:{port} ์์ ์์ํฉ๋๋ค.")
|
32 |
-
|
|
|
|
|
|
|
|
1 |
"""
|
2 |
+
RAG ๊ฒ์ ์ฑ๋ด ์น ์ ํ๋ฆฌ์ผ์ด์
(์ธ์
์ค์ ์์ ์ ์ฉ ๋ฐ ์ค๋ณต ๋ผ์ฐํธ ๋ฑ๋ก ๋ฐฉ์ง)
|
3 |
"""
|
4 |
|
5 |
import os
|
6 |
+
import json
|
7 |
import logging
|
8 |
+
import tempfile
|
9 |
+
import threading
|
10 |
+
import datetime
|
11 |
+
from flask import Flask, request, jsonify, render_template, send_from_directory, session, redirect, url_for
|
12 |
+
from flask_cors import CORS
|
13 |
+
from werkzeug.utils import secure_filename
|
14 |
from dotenv import load_dotenv
|
15 |
+
from functools import wraps
|
16 |
|
17 |
# ๋ก๊ฑฐ ์ค์
|
18 |
logging.basicConfig(
|
19 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
20 |
+
level=logging.DEBUG # INFO์์ DEBUG๋ก ๋ณ๊ฒฝํ์ฌ ๋ ์์ธํ ๋ก๊ทธ ํ์ธ
|
21 |
)
|
22 |
logger = logging.getLogger(__name__)
|
23 |
|
24 |
# ํ๊ฒฝ ๋ณ์ ๋ก๋
|
25 |
load_dotenv()
|
26 |
|
27 |
+
# ํ๊ฒฝ ๋ณ์ ๋ก๋ ์ํ ํ์ธ ๋ฐ ๋ก๊น
|
28 |
+
ADMIN_USERNAME = os.getenv('ADMIN_USERNAME')
|
29 |
+
ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD')
|
30 |
|
31 |
+
# ์ฅ์น ์๋ฒ URL ํ๊ฒฝ ๋ณ์ ์ถ๊ฐ
|
32 |
+
DEVICE_SERVER_URL = os.getenv('DEVICE_SERVER_URL', 'http://localhost:5050')
|
33 |
+
logger.info(f"์ฅ์น ์๋ฒ URL: {DEVICE_SERVER_URL}")
|
34 |
|
35 |
+
logger.info(f"==== ํ๊ฒฝ ๋ณ์ ๋ก๋ ์ํ ====")
|
36 |
+
logger.info(f"ADMIN_USERNAME ์ค์ ์ฌ๋ถ: {ADMIN_USERNAME is not None}")
|
37 |
+
# ๋น๋ฐ๋ฒํธ๋ ๋ก๋ ์ฌ๋ถ๋ง ๊ธฐ๋ก (๋ณด์)
|
38 |
+
logger.info(f"ADMIN_PASSWORD ์ค์ ์ฌ๋ถ: {ADMIN_PASSWORD is not None}")
|
39 |
|
40 |
+
# ํ๊ฒฝ ๋ณ์๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ ์ค์ (๊ฐ๋ฐ์ฉ, ๋ฐฐํฌ ์ ํ๊ฒฝ ๋ณ์ ์ค์ ๊ถ์ฅ)
|
41 |
+
if not ADMIN_USERNAME:
|
42 |
+
ADMIN_USERNAME = 'admin'
|
43 |
+
logger.warning("ADMIN_USERNAME ํ๊ฒฝ๋ณ์๊ฐ ์์ด ๊ธฐ๋ณธ๊ฐ 'admin'์ผ๋ก ์ค์ ํฉ๋๋ค.")
|
44 |
+
|
45 |
+
if not ADMIN_PASSWORD:
|
46 |
+
ADMIN_PASSWORD = 'rag12345'
|
47 |
+
logger.warning("ADMIN_PASSWORD ํ๊ฒฝ๋ณ์๊ฐ ์์ด ๊ธฐ๋ณธ๊ฐ 'rag12345'๋ก ์ค์ ํฉ๋๋ค.")
|
48 |
+
class MockComponent: pass
|
49 |
+
|
50 |
+
# --- ๋ก์ปฌ ๋ชจ๋ ์ํฌํธ ---
|
51 |
+
# ์ค์ ๊ฒฝ๋ก์ ๋ง๊ฒ utils, retrieval ํด๋๊ฐ ์กด์ฌํด์ผ ํฉ๋๋ค.
|
52 |
+
try:
|
53 |
+
from utils.vito_stt import VitoSTT
|
54 |
+
from utils.llm_interface import LLMInterface
|
55 |
+
from utils.document_processor import DocumentProcessor
|
56 |
+
from retrieval.vector_retriever import VectorRetriever
|
57 |
+
from retrieval.reranker import ReRanker
|
58 |
+
# ์ฅ์น ๋ผ์ฐํธ ๋ฑ๋ก ํจ์ ์ํฌํธ
|
59 |
+
from app.app_device_routes import register_device_routes
|
60 |
+
except ImportError as e:
|
61 |
+
logger.error(f"๋ก์ปฌ ๋ชจ๋ ์ํฌํธ ์คํจ: {e}. utils, retrieval, app ํจํค์ง๊ฐ ์ฌ๋ฐ๋ฅธ ๊ฒฝ๋ก์ ์๋์ง ํ์ธํ์ธ์.")
|
62 |
+
# ๊ฐ๋ฐ/ํ
์คํธ๋ฅผ ์ํด ์์ ํด๋์ค/ํจ์ ์ ์ (์ค์ ์ฌ์ฉ ์ ์ ๊ฑฐ)
|
63 |
+
|
64 |
+
VitoSTT = LLMInterface = DocumentProcessor = VectorRetriever = ReRanker = MockComponent
|
65 |
+
def register_device_routes(*args, **kwargs):
|
66 |
+
logger.warning("Mock register_device_routes ํจ์ ํธ์ถ๋จ.")
|
67 |
+
pass
|
68 |
+
# --- ๋ก์ปฌ ๋ชจ๋ ์ํฌํธ ๋ ---
|
69 |
+
|
70 |
+
|
71 |
+
# Flask ์ฑ ์ด๊ธฐํ
|
72 |
+
app = Flask(__name__)
|
73 |
+
|
74 |
+
# CORS ์ค์ - ๋ชจ๋ ๋๋ฉ์ธ์์์ ์์ฒญ ํ์ฉ
|
75 |
+
CORS(app, supports_credentials=True)
|
76 |
+
|
77 |
+
# ์ธ์
์ค์ - ๊ณ ์ ๋ ์ํฌ๋ฆฟ ํค ์ฌ์ฉ (์ค์ ๋ฐฐํฌ ์ ํ๊ฒฝ ๋ณ์ ๋ฑ์ผ๋ก ๊ด๋ฆฌ ๊ถ์ฅ)
|
78 |
+
app.secret_key = os.getenv('FLASK_SECRET_KEY', 'rag_chatbot_fixed_secret_key_12345') # ํ๊ฒฝ ๋ณ์ ์ฐ์ ์ฌ์ฉ
|
79 |
+
|
80 |
+
# --- ์ธ์
์ฟ ํค ์ค์ ์์ (ํ๊น
ํ์ด์ค ํ๊ฒฝ ๊ณ ๋ ค) ---
|
81 |
+
# ํ๊น
ํ์ด์ค ์คํ์ด์ค๋ ์ผ๋ฐ์ ์ผ๋ก HTTPS๋ก ์๋น์ค๋๋ฏ๋ก Secure=True ์ค์
|
82 |
+
app.config['SESSION_COOKIE_SECURE'] = True
|
83 |
+
app.config['SESSION_COOKIE_HTTPONLY'] = True # JavaScript์์ ์ฟ ํค ์ ๊ทผ ๋ฐฉ์ง (๋ณด์ ๊ฐํ)
|
84 |
+
# SameSite='Lax'๊ฐ ๋๋ถ๋ถ์ ๊ฒฝ์ฐ์ ๋ ์์ ํ๊ณ ํธํ์ฑ์ด ์ข์.
|
85 |
+
# ๋ง์ฝ ์ฑ์ด ๋ค๋ฅธ ๋๋ฉ์ธ์ iframe ๋ด์์ ์คํ๋์ด์ผ ํ๋ค๋ฉด 'None'์ผ๋ก ์ค์ ํด์ผ ํจ.
|
86 |
+
# (๋จ, 'None'์ผ๋ก ์ค์ ์ ๋ฐ๋์ Secure=True์ฌ์ผ ํจ)
|
87 |
+
# ๋ก๊ทธ ๋ถ์ ๊ฒฐ๊ณผ iframe ํ๊ฒฝ์ผ๋ก ํ์ธ๋์ด 'None'์ผ๋ก ๋ณ๊ฒฝ
|
88 |
+
app.config['SESSION_COOKIE_SAMESITE'] = 'None' # <--- ์ด๋ ๊ฒ ๋ณ๊ฒฝํฉ๋๋ค.
|
89 |
+
app.config['SESSION_COOKIE_DOMAIN'] = None # ํน์ ๋๋ฉ์ธ ์ ํ ์์
|
90 |
+
app.config['SESSION_COOKIE_PATH'] = '/' # ์ฑ ์ ์ฒด ๊ฒฝ๋ก์ ์ฟ ํค ์ ์ฉ
|
91 |
+
app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=1) # ์ธ์
์ ํจ ์๊ฐ ์ฆ๊ฐ
|
92 |
+
# --- ์ธ์
์ฟ ํค ์ค์ ๋ ---
|
93 |
+
|
94 |
+
# ์ต๋ ํ์ผ ํฌ๊ธฐ ์ค์ (10MB)
|
95 |
+
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024
|
96 |
+
# ์ ํ๋ฆฌ์ผ์ด์
ํ์ผ ๊ธฐ์ค ์๋ ๊ฒฝ๋ก ์ค์
|
97 |
+
APP_ROOT = os.path.dirname(os.path.abspath(__file__))
|
98 |
+
app.config['UPLOAD_FOLDER'] = os.path.join(APP_ROOT, 'uploads')
|
99 |
+
app.config['DATA_FOLDER'] = os.path.join(APP_ROOT, '..', 'data')
|
100 |
+
app.config['INDEX_PATH'] = os.path.join(APP_ROOT, '..', 'data', 'index')
|
101 |
+
|
102 |
+
# ํ์ํ ํด๋ ์์ฑ
|
103 |
+
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
104 |
+
os.makedirs(app.config['DATA_FOLDER'], exist_ok=True)
|
105 |
+
os.makedirs(app.config['INDEX_PATH'], exist_ok=True)
|
106 |
+
|
107 |
+
# ํ์ฉ๋๋ ์ค๋์ค/๋ฌธ์ ํ์ผ ํ์ฅ์
|
108 |
+
ALLOWED_AUDIO_EXTENSIONS = {'mp3', 'wav', 'ogg', 'm4a'}
|
109 |
+
ALLOWED_DOC_EXTENSIONS = {'txt', 'md', 'pdf', 'docx', 'csv'}
|
110 |
+
|
111 |
+
# --- ์ ์ญ ๊ฐ์ฒด ์ด๊ธฐํ ---
|
112 |
+
try:
|
113 |
+
llm_interface = LLMInterface(default_llm="openai")
|
114 |
+
stt_client = VitoSTT()
|
115 |
+
except NameError:
|
116 |
+
logger.warning("LLM ๋๋ STT ์ธํฐํ์ด์ค ์ด๊ธฐํ ์คํจ. Mock ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํฉ๋๋ค.")
|
117 |
+
llm_interface = MockComponent()
|
118 |
+
stt_client = MockComponent()
|
119 |
+
|
120 |
+
base_retriever = None
|
121 |
+
retriever = None
|
122 |
+
app_ready = False # ์ฑ ์ด๊ธฐํ ์ํ ํ๋๊ทธ
|
123 |
+
DEVICE_ROUTES_REGISTERED = False # ์ฅ์น ๋ผ์ฐํธ ๋ฑ๋ก ์ํ ํ๋๊ทธ
|
124 |
+
# --- ์ ์ญ ๊ฐ์ฒด ์ด๊ธฐํ ๋ ---
|
125 |
+
|
126 |
+
|
127 |
+
# --- ์ธ์ฆ ๋ฐ์ฝ๋ ์ดํฐ (์์ ๋จ) ---
|
128 |
+
def login_required(f):
|
129 |
+
@wraps(f)
|
130 |
+
def decorated_function(*args, **kwargs):
|
131 |
+
logger.info(f"----------- ์ธ์ฆ ํ์ ํ์ด์ง ์ ๊ทผ ์๋: {request.path} -----------")
|
132 |
+
logger.info(f"ํ์ฌ ํ๋ผ์คํฌ ์ธ์
๊ฐ์ฒด: {session}")
|
133 |
+
logger.info(f"ํ์ฌ ์ธ์
์ํ: logged_in={session.get('logged_in', False)}, username={session.get('username', 'None')}")
|
134 |
+
# ๋ธ๋ผ์ฐ์ ๊ฐ ๋ณด๋ธ ์ค์ ์ฟ ํค ํ์ธ (๋๋ฒ๊น
์ฉ)
|
135 |
+
logger.info(f"์์ฒญ์ ์ธ์
์ฟ ํค ๊ฐ: {request.cookies.get('session', 'None')}")
|
136 |
+
|
137 |
+
# API ์์ฒญ์ด๊ณ ํด๋ผ์ด์ธํธ์์ ์ค๋ ๊ฒฝ์ฐ ์ธ์ฆ ๋ฌด์ (์์ ์กฐ์น)
|
138 |
+
# ---> ์ฃผ์: ์ด ๋ถ๋ถ์ ๋ณด์ ๊ฒํ ํ ์ค์ ํ๊ฒฝ์์๋ ์ ๊ฑฐํ๊ฑฐ๋ ๋ ์์ ํ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝํด์ผ ํ ์ ์์ต๋๋ค.
|
139 |
+
if request.path.startswith('/api/device/'):
|
140 |
+
logger.info(f"์ฅ์น API ์์ฒญ: {request.path} - ์ธ์ฆ ์ ์ธ (์ฃผ์: ์์ ์กฐ์น)")
|
141 |
+
return f(*args, **kwargs)
|
142 |
+
|
143 |
+
# Flask ์ธ์
์ 'logged_in' ํค๊ฐ ์๋์ง ์ง์ ํ์ธ
|
144 |
+
if 'logged_in' not in session:
|
145 |
+
logger.warning(f"ํ๋ผ์คํฌ ์ธ์
์ 'logged_in' ์์. ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
.")
|
146 |
+
# ์๋ ์ฟ ํค ํ์ธ ๋ก์ง ์ ๊ฑฐ๋จ
|
147 |
+
return redirect(url_for('login', next=request.url)) # ๋ก๊ทธ์ธ ํ ์๋ ํ์ด์ง๋ก ๋์๊ฐ๋๋ก next ํ๋ผ๋ฏธํฐ ์ถ๊ฐ
|
148 |
+
|
149 |
+
logger.info(f"์ธ์ฆ ์ฑ๊ณต: {session.get('username', 'unknown')} ์ฌ์ฉ์๊ฐ {request.path} ์ ๊ทผ")
|
150 |
+
return f(*args, **kwargs)
|
151 |
+
return decorated_function
|
152 |
+
# --- ์ธ์ฆ ๋ฐ์ฝ๋ ์ดํฐ ๋ ---
|
153 |
+
|
154 |
+
# --- ์ค๋ฅ ํธ๋ค๋ฌ ์ถ๊ฐ ---
|
155 |
+
@app.errorhandler(404)
|
156 |
+
def not_found(e):
|
157 |
+
# ํด๋ผ์ด์ธํธ๊ฐ JSON์ ๊ธฐ๋ํ๋ API ํธ์ถ์ธ ๊ฒฝ์ฐ JSON ์๋ต
|
158 |
+
if request.path.startswith('/api/'):
|
159 |
+
return jsonify({"success": False, "error": "์์ฒญํ API ์๋ํฌ์ธํธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."}), 404
|
160 |
+
# ์ผ๋ฐ ์น ํ์ด์ง ์์ฒญ์ธ ๊ฒฝ์ฐ HTML ์๋ต
|
161 |
+
return "ํ์ด์ง๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.", 404
|
162 |
+
|
163 |
+
@app.errorhandler(500)
|
164 |
+
def internal_error(e):
|
165 |
+
# ํด๋ผ์ด์ธํธ๊ฐ JSON์ ๊ธฐ๋ํ๋ API ํธ์ถ์ธ ๊ฒฝ์ฐ JSON ์๋ต
|
166 |
+
if request.path.startswith('/api/'):
|
167 |
+
return jsonify({"success": False, "error": "์๋ฒ ๋ด๋ถ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."}), 500
|
168 |
+
# ์ผ๋ฐ ์น ํ์ด์ง ์์ฒญ์ธ ๊ฒฝ์ฐ HTML ์๋ต
|
169 |
+
return "์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.", 500
|
170 |
+
# --- ์ค๋ฅ ํธ๋ค๋ฌ ๋ ---
|
171 |
+
|
172 |
+
# --- ์ฅ์น ๊ด๋ จ ๋ผ์ฐํธ ๋ฑ๋ก (์์ ๋จ: ์ค๋ณต ๋ฐฉ์ง) ---
|
173 |
+
# ์ ์ญ ํ๋๊ทธ๋ฅผ ์ฌ์ฉํ์ฌ ํ ๋ฒ๋ง ๋ฑ๋ก๋๋๋ก ํจ
|
174 |
+
if not DEVICE_ROUTES_REGISTERED:
|
175 |
+
try:
|
176 |
+
# ์ํฌํธ๋ register_device_routes ํจ์ ์ฌ์ฉ
|
177 |
+
# ์ธ์ฆ ๋ฐ์ฝ๋ ์ดํฐ(login_required)์ ์๋ฒ URL ์ ๋ฌ
|
178 |
+
register_device_routes(app, login_required, DEVICE_SERVER_URL)
|
179 |
+
DEVICE_ROUTES_REGISTERED = True # ๋ฑ๋ก ์ฑ๊ณต ์ ํ๋๊ทธ ์ค์
|
180 |
+
logger.info("์ฅ์น ๊ด๋ จ ๋ผ์ฐํธ ๋ฑ๋ก ์๋ฃ")
|
181 |
+
except NameError:
|
182 |
+
logger.error("register_device_routes ํจ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. app.app_device_routes ๋ชจ๋ ํ์ธ ํ์.")
|
183 |
+
except Exception as e:
|
184 |
+
logger.error(f"์ฅ์น ๊ด๋ จ ๋ผ์ฐํธ ๋ฑ๋ก ์คํจ: {e}", exc_info=True)
|
185 |
+
else:
|
186 |
+
logger.info("์ฅ์น ๊ด๋ จ ๋ผ์ฐํธ๊ฐ ์ด๋ฏธ ๋ฑ๋ก๋์ด ์์ด ๊ฑด๋<0xEB>๋๋๋ค.")
|
187 |
+
# --- ์ฅ์น ๊ด๋ จ ๋ผ์ฐํธ ๋ฑ๋ก ๋ ---
|
188 |
+
|
189 |
+
|
190 |
+
# --- ํฌํผ ํจ์ ---
|
191 |
+
def allowed_audio_file(filename):
|
192 |
+
"""ํ์ผ์ด ํ์ฉ๋ ์ค๋์ค ํ์ฅ์๋ฅผ ๊ฐ์ง๋์ง ํ์ธ"""
|
193 |
+
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_AUDIO_EXTENSIONS
|
194 |
+
|
195 |
+
def allowed_doc_file(filename):
|
196 |
+
"""ํ์ผ์ด ํ์ฉ๋ ๋ฌธ์ ํ์ฅ์๋ฅผ ๊ฐ์ง๋์ง ํ์ธ"""
|
197 |
+
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_DOC_EXTENSIONS
|
198 |
+
# --- ํฌํผ ํจ์ ๋ ---
|
199 |
+
|
200 |
+
|
201 |
+
# init_retriever ํจ์ ๋ด๋ถ์ ๋ก๊น
์ถ๊ฐ ์์
|
202 |
+
# --- ๊ฒ์๊ธฐ ์ด๊ธฐํ ๊ด๋ จ ํจ์ ---
|
203 |
+
def init_retriever():
|
204 |
+
"""๊ฒ์๊ธฐ ๊ฐ์ฒด ์ด๊ธฐํ ๋๋ ๋ก๋"""
|
205 |
+
global base_retriever, retriever
|
206 |
+
|
207 |
+
index_path = app.config['INDEX_PATH']
|
208 |
+
data_path = app.config['DATA_FOLDER'] # data_path ์ ์ ํ์ธ
|
209 |
+
logger.info("--- init_retriever ์์ ---")
|
210 |
+
|
211 |
+
# 1. ๊ธฐ๋ณธ ๊ฒ์๊ธฐ ๋ก๋ ๋๋ ์ด๊ธฐํ
|
212 |
+
if os.path.exists(os.path.join(index_path, "documents.json")):
|
213 |
+
try:
|
214 |
+
logger.info(f"์ธ๋ฑ์ค ๋ก๋ ์๋: {index_path}")
|
215 |
+
base_retriever = VectorRetriever.load(index_path)
|
216 |
+
logger.info(f"์ธ๋ฑ์ค ๋ก๋ ์ฑ๊ณต. ๋ฌธ์ {len(getattr(base_retriever, 'documents', []))}๊ฐ")
|
217 |
+
except Exception as e:
|
218 |
+
logger.error(f"์ธ๋ฑ์ค ๋ก๋ ์คํจ: {e}", exc_info=True)
|
219 |
+
logger.info("์ VectorRetriever ์ด๊ธฐํ ์๋...")
|
220 |
+
try:
|
221 |
+
base_retriever = VectorRetriever()
|
222 |
+
logger.info("์ VectorRetriever ์ด๊ธฐํ ์ฑ๊ณต.")
|
223 |
+
except Exception as e_init:
|
224 |
+
logger.error(f"์ VectorRetriever ์ด๊ธฐํ ์คํจ: {e_init}", exc_info=True)
|
225 |
+
base_retriever = None
|
226 |
+
else:
|
227 |
+
logger.info("์ธ๋ฑ์ค ํ์ผ ์์. ์ VectorRetriever ์ด๊ธฐํ ์๋...")
|
228 |
+
try:
|
229 |
+
base_retriever = VectorRetriever()
|
230 |
+
logger.info("์ VectorRetriever ์ด๊ธฐํ ์ฑ๊ณต.")
|
231 |
+
except Exception as e_init:
|
232 |
+
logger.error(f"์ VectorRetriever ์ด๊ธฐํ ์คํจ: {e_init}", exc_info=True)
|
233 |
+
base_retriever = None
|
234 |
+
|
235 |
+
if base_retriever is None:
|
236 |
+
logger.error("base_retriever ์ด๊ธฐํ/๋ก๋์ ์คํจํ์ฌ init_retriever ์ค๋จ.")
|
237 |
+
return None
|
238 |
+
|
239 |
+
# 2. ๋ฐ์ดํฐ ํด๋ ๋ฌธ์ ๋ก๋ (๊ธฐ๋ณธ ๊ฒ์๊ธฐ๊ฐ ๋น์ด์์ ๋)
|
240 |
+
needs_loading = (not hasattr(base_retriever, 'documents') or not getattr(base_retriever, 'documents', None)) # None ์ฒดํฌ ์ถ๊ฐ
|
241 |
+
if needs_loading and os.path.exists(data_path):
|
242 |
+
logger.info(f"๊ธฐ๋ณธ ๊ฒ์๊ธฐ๊ฐ ๋น์ด์์ด {data_path}์์ ๋ฌธ์ ๋ก๋ ์๋...")
|
243 |
+
try:
|
244 |
+
docs = DocumentProcessor.load_documents_from_directory(
|
245 |
+
directory=data_path,
|
246 |
+
extensions=[".txt", ".md", ".csv"],
|
247 |
+
recursive=True
|
248 |
+
)
|
249 |
+
logger.info(f"{len(docs)}๊ฐ ๋ฌธ์ ๋ก๋ ์ฑ๊ณต.")
|
250 |
+
if docs and hasattr(base_retriever, 'add_documents'):
|
251 |
+
logger.info("๊ฒ์๊ธฐ์ ๋ฌธ์ ์ถ๊ฐ ์๋...")
|
252 |
+
base_retriever.add_documents(docs)
|
253 |
+
logger.info("๋ฌธ์ ์ถ๊ฐ ์๋ฃ.")
|
254 |
+
|
255 |
+
if hasattr(base_retriever, 'save'):
|
256 |
+
logger.info(f"๊ฒ์๊ธฐ ์ํ ์ ์ฅ ์๋: {index_path}")
|
257 |
+
try:
|
258 |
+
base_retriever.save(index_path)
|
259 |
+
logger.info("์ธ๋ฑ์ค ์ ์ฅ ์๋ฃ.")
|
260 |
+
except Exception as e_save:
|
261 |
+
logger.error(f"์ธ๋ฑ์ค ์ ์ฅ ์คํจ: {e_save}", exc_info=True)
|
262 |
+
except Exception as e_load_add:
|
263 |
+
logger.error(f"DATA_FOLDER ๋ฌธ์ ๋ก๋/์ถ๊ฐ ์ค ์ค๋ฅ: {e_load_add}", exc_info=True)
|
264 |
+
|
265 |
+
# 3. ์ฌ์์ํ ๊ฒ์๊ธฐ ์ด๊ธฐํ
|
266 |
+
logger.info("์ฌ์์ํ ๊ฒ์๊ธฐ ์ด๊ธฐํ ์๋...")
|
267 |
+
try:
|
268 |
+
def custom_rerank_fn(query, results):
|
269 |
+
query_terms = set(query.lower().split())
|
270 |
+
for result in results:
|
271 |
+
if isinstance(result, dict) and "text" in result:
|
272 |
+
text = result["text"].lower()
|
273 |
+
term_freq = sum(1 for term in query_terms if term in text)
|
274 |
+
normalized_score = term_freq / (len(text.split()) + 1) * 10
|
275 |
+
result["rerank_score"] = result.get("score", 0) * 0.7 + normalized_score * 0.3
|
276 |
+
elif isinstance(result, dict):
|
277 |
+
result["rerank_score"] = result.get("score", 0)
|
278 |
+
results.sort(key=lambda x: x.get("rerank_score", 0) if isinstance(x, dict) else 0, reverse=True)
|
279 |
+
return results
|
280 |
+
|
281 |
+
# ReRanker ํด๋์ค ์ฌ์ฉ
|
282 |
+
retriever = ReRanker(
|
283 |
+
base_retriever=base_retriever,
|
284 |
+
rerank_fn=custom_rerank_fn,
|
285 |
+
rerank_field="text"
|
286 |
+
)
|
287 |
+
logger.info("์ฌ์์ํ ๊ฒ์๊ธฐ ์ด๊ธฐํ ์๋ฃ.")
|
288 |
+
except Exception as e_rerank:
|
289 |
+
logger.error(f"์ฌ์์ํ ๊ฒ์๊ธฐ ์ด๊ธฐํ ์คํจ: {e_rerank}", exc_info=True)
|
290 |
+
logger.warning("์ฌ์์ํ ์คํจ, ๊ธฐ๋ณธ ๊ฒ์๊ธฐ๋ฅผ retriever๋ก ์ฌ์ฉํฉ๋๋ค.")
|
291 |
+
retriever = base_retriever # fallback
|
292 |
+
|
293 |
+
logger.info("--- init_retriever ์ข
๋ฃ ---")
|
294 |
+
return retriever
|
295 |
+
|
296 |
+
def background_init():
|
297 |
+
"""๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ฒ์๊ธฐ ์ด๊ธฐํ ์ํ"""
|
298 |
+
global app_ready, retriever, base_retriever, llm_interface, stt_client
|
299 |
+
|
300 |
+
temp_app_ready = False # ์์ ์ํ ํ๋๊ทธ
|
301 |
+
try:
|
302 |
+
logger.info("๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๊ธฐํ ์์...")
|
303 |
+
|
304 |
+
# 1. LLM, STT ์ธํฐํ์ด์ค ์ด๊ธฐํ (ํ์ ์)
|
305 |
+
if llm_interface is None or isinstance(llm_interface, MockComponent):
|
306 |
+
if 'LLMInterface' in globals() and LLMInterface != MockComponent:
|
307 |
+
llm_interface = LLMInterface(default_llm="openai")
|
308 |
+
logger.info("LLM ์ธํฐํ์ด์ค ์ด๊ธฐํ ์๋ฃ.")
|
309 |
+
else:
|
310 |
+
logger.warning("LLMInterface ํด๋์ค ์์. Mock ์ฌ์ฉ.")
|
311 |
+
llm_interface = MockComponent() # Mock ๊ฐ์ฒด ๋ณด์ฅ
|
312 |
+
if stt_client is None or isinstance(stt_client, MockComponent):
|
313 |
+
if 'VitoSTT' in globals() and VitoSTT != MockComponent:
|
314 |
+
stt_client = VitoSTT()
|
315 |
+
logger.info("STT ํด๋ผ์ด์ธํธ ์ด๊ธฐํ ์๋ฃ.")
|
316 |
+
else:
|
317 |
+
logger.warning("VitoSTT ํด๋์ค ์์. Mock ์ฌ์ฉ.")
|
318 |
+
stt_client = MockComponent() # Mock ๊ฐ์ฒด ๋ณด์ฅ
|
319 |
+
|
320 |
+
|
321 |
+
# 2. ๊ฒ์๊ธฐ ์ด๊ธฐํ
|
322 |
+
if 'VectorRetriever' in globals() and VectorRetriever != MockComponent:
|
323 |
+
logger.info("์ค์ ๊ฒ์๊ธฐ ์ด๊ธฐํ ์๋...")
|
324 |
+
retriever = init_retriever()
|
325 |
+
if hasattr(retriever, 'base_retriever') and base_retriever is None:
|
326 |
+
base_retriever = retriever.base_retriever
|
327 |
+
elif base_retriever is None:
|
328 |
+
logger.warning("init_retriever ํ base_retriever๊ฐ ์ค์ ๋์ง ์์. ํ์ธ ํ์.")
|
329 |
+
if isinstance(retriever, VectorRetriever):
|
330 |
+
base_retriever = retriever
|
331 |
+
|
332 |
+
if retriever is not None and base_retriever is not None:
|
333 |
+
logger.info("๊ฒ์๊ธฐ (Retriever, Base Retriever) ์ด๊ธฐํ ์ฑ๊ณต")
|
334 |
+
temp_app_ready = True
|
335 |
+
else:
|
336 |
+
logger.error("๊ฒ์๊ธฐ ์ด๊ธฐํ ํ์๋ retriever ๋๋ base_retriever๊ฐ None์
๋๋ค.")
|
337 |
+
if base_retriever is None: base_retriever = MockComponent()
|
338 |
+
if retriever is None: retriever = MockComponent()
|
339 |
+
if not hasattr(retriever, 'search'): retriever.search = lambda query, **kwargs: []
|
340 |
+
if not hasattr(base_retriever, 'documents'): base_retriever.documents = []
|
341 |
+
temp_app_ready = True
|
342 |
+
|
343 |
+
else:
|
344 |
+
logger.warning("VectorRetriever ํด๋์ค ์์. Mock ๊ฒ์๊ธฐ ์ฌ์ฉ.")
|
345 |
+
base_retriever = MockComponent()
|
346 |
+
retriever = MockComponent()
|
347 |
+
if not hasattr(retriever, 'search'): retriever.search = lambda query, **kwargs: []
|
348 |
+
if not hasattr(base_retriever, 'documents'): base_retriever.documents = []
|
349 |
+
temp_app_ready = True
|
350 |
+
|
351 |
+
logger.info(f"๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๊ธฐํ ์๋ฃ. ์ต์ข
์ํ: {'Ready' if temp_app_ready else 'Not Ready (Error during init)'}")
|
352 |
+
|
353 |
+
except Exception as e:
|
354 |
+
logger.error(f"์ฑ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ด๊ธฐํ ์ค ์ฌ๊ฐํ ์ค๋ฅ ๋ฐ์: {e}", exc_info=True)
|
355 |
+
if base_retriever is None: base_retriever = MockComponent()
|
356 |
+
if retriever is None: retriever = MockComponent()
|
357 |
+
if not hasattr(retriever, 'search'): retriever.search = lambda query, **kwargs: []
|
358 |
+
if not hasattr(base_retriever, 'documents'): base_retriever.documents = []
|
359 |
+
temp_app_ready = True
|
360 |
+
logger.warning("์ด๊ธฐํ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง๋ง Mock ๊ฐ์ฒด๋ก ๋์ฒด ํ ์ฑ ์ฌ์ฉ ๊ฐ๋ฅ ์ํ๋ก ์ค์ .")
|
361 |
+
|
362 |
+
finally:
|
363 |
+
# ์ต์ข
์ ์ผ๋ก app_ready ์ํ ์
๋ฐ์ดํธ
|
364 |
+
app_ready = temp_app_ready
|
365 |
+
# ์ฅ์น ๋ผ์ฐํธ ๋ฑ๋ก ํธ์ถ์ ์ฌ๊ธฐ์ ์ ๊ฑฐ๋จ (๋ฉ์ธ ๋ ๋ฒจ์์ ์ฒ๋ฆฌ)
|
366 |
+
|
367 |
+
# ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋ ์์ ๋ถ๋ถ์ ๊ทธ๋๋ก ์ ์ง
|
368 |
+
init_thread = threading.Thread(target=background_init)
|
369 |
+
init_thread.daemon = True
|
370 |
+
init_thread.start()
|
371 |
+
|
372 |
+
|
373 |
+
# --- Flask ๋ผ์ฐํธ ์ ์ ---
|
374 |
+
|
375 |
+
@app.route('/login', methods=['GET', 'POST'])
|
376 |
+
def login():
|
377 |
+
error = None
|
378 |
+
next_url = request.args.get('next') # ๋ฆฌ๋๋ ์
ํ URL ๊ฐ์ ธ์ค๊ธฐ
|
379 |
+
logger.info(f"-------------- ๋ก๊ทธ์ธ ํ์ด์ง ์ ์ (Next: {next_url}) --------------")
|
380 |
+
logger.info(f"Method: {request.method}")
|
381 |
+
|
382 |
+
if request.method == 'POST':
|
383 |
+
logger.info("๋ก๊ทธ์ธ ์๋ ๋ฐ์")
|
384 |
+
username = request.form.get('username', '')
|
385 |
+
password = request.form.get('password', '')
|
386 |
+
logger.info(f"์
๋ ฅ๋ ์ฌ์ฉ์๋ช
: {username}")
|
387 |
+
logger.info(f"๋น๋ฐ๋ฒํธ ์
๋ ฅ ์ฌ๋ถ: {len(password) > 0}")
|
388 |
+
|
389 |
+
valid_username = ADMIN_USERNAME
|
390 |
+
valid_password = ADMIN_PASSWORD
|
391 |
+
logger.info(f"๊ฒ์ฆ์ฉ ์ฌ์ฉ์๋ช
: {valid_username}")
|
392 |
+
logger.info(f"๊ฒ์ฆ์ฉ ๋น๋ฐ๋ฒํธ ์กด์ฌ ์ฌ๋ถ: {valid_password is not None and len(valid_password) > 0}")
|
393 |
+
|
394 |
+
|
395 |
+
if username == valid_username and password == valid_password:
|
396 |
+
logger.info(f"๋ก๊ทธ์ธ ์ฑ๊ณต: {username}")
|
397 |
+
logger.debug(f"์ธ์
์ค์ ์ : {session}")
|
398 |
+
|
399 |
+
session.permanent = True
|
400 |
+
session['logged_in'] = True
|
401 |
+
session['username'] = username
|
402 |
+
session.modified = True
|
403 |
+
|
404 |
+
logger.info(f"์ธ์
์ค์ ํ: {session}")
|
405 |
+
logger.info("์ธ์
์ค์ ์๋ฃ, ๋ฆฌ๋๋ ์
์๋")
|
406 |
+
|
407 |
+
redirect_to = next_url or url_for('index')
|
408 |
+
logger.info(f"๋ฆฌ๋๋ ์
๋์: {redirect_to}")
|
409 |
+
response = redirect(redirect_to)
|
410 |
+
return response
|
411 |
+
else:
|
412 |
+
logger.warning("๋ก๊ทธ์ธ ์คํจ: ์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ ๋ถ์ผ์น")
|
413 |
+
if username != valid_username: logger.warning("์ฌ์ฉ์๋ช
๋ถ์ผ์น")
|
414 |
+
if password != valid_password: logger.warning("๋น๋ฐ๋ฒํธ ๋ถ์ผ์น")
|
415 |
+
error = '์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.'
|
416 |
+
else:
|
417 |
+
logger.info("๋ก๊ทธ์ธ ํ์ด์ง GET ์์ฒญ")
|
418 |
+
if 'logged_in' in session:
|
419 |
+
logger.info("์ด๋ฏธ ๋ก๊ทธ์ธ๋ ์ฌ์ฉ์, ๋ฉ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
")
|
420 |
+
return redirect(url_for('index'))
|
421 |
+
|
422 |
+
logger.info("---------- ๋ก๊ทธ์ธ ํ์ด์ง ๋ ๋๋ง ----------")
|
423 |
+
return render_template('login.html', error=error, next=next_url)
|
424 |
+
|
425 |
+
|
426 |
+
@app.route('/logout')
|
427 |
+
def logout():
|
428 |
+
logger.info("-------------- ๋ก๊ทธ์์ ์์ฒญ --------------")
|
429 |
+
logger.info(f"๋ก๊ทธ์์ ์ ์ธ์
์ํ: {session}")
|
430 |
+
|
431 |
+
if 'logged_in' in session:
|
432 |
+
username = session.get('username', 'unknown')
|
433 |
+
logger.info(f"์ฌ์ฉ์ {username} ๋ก๊ทธ์์ ์ฒ๋ฆฌ ์์")
|
434 |
+
session.pop('logged_in', None)
|
435 |
+
session.pop('username', None)
|
436 |
+
session.modified = True
|
437 |
+
logger.info(f"์ธ์
์ ๋ณด ์ญ์ ์๋ฃ. ํ์ฌ ์ธ์
: {session}")
|
438 |
+
else:
|
439 |
+
logger.warning("๋ก๊ทธ์ธ๋์ง ์์ ์ํ์์ ๋ก๊ทธ์์ ์๋")
|
440 |
+
|
441 |
+
logger.info("๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
")
|
442 |
+
response = redirect(url_for('login'))
|
443 |
+
return response
|
444 |
+
|
445 |
+
|
446 |
+
@app.route('/')
|
447 |
+
@login_required
|
448 |
+
def index():
|
449 |
+
"""๋ฉ์ธ ํ์ด์ง"""
|
450 |
+
global app_ready
|
451 |
+
|
452 |
+
current_time = datetime.datetime.now()
|
453 |
+
try:
|
454 |
+
start_time = datetime.datetime.fromtimestamp(os.path.getmtime(__file__))
|
455 |
+
time_diff = (current_time - start_time).total_seconds()
|
456 |
+
if not app_ready and time_diff > 30:
|
457 |
+
logger.warning(f"์ฑ์ด 30์ด ์ด์ ์ด๊ธฐํ ์ค ์ํ์
๋๋ค. ๊ฐ์ ๋ก ready ์ํ๋ก ๋ณ๊ฒฝํฉ๋๋ค.")
|
458 |
+
app_ready = True
|
459 |
+
except FileNotFoundError:
|
460 |
+
logger.warning("__file__ ๊ฒฝ๋ก๋ฅผ ์ฐพ์ ์ ์์ด ์๊ฐ ๋น๊ต ๋ก์ง์ ๊ฑด๋<0xEB>๋๋๋ค.")
|
461 |
+
if not app_ready: # ๊ธฐ๋ณธ ํ์์์ ๋์ ๊ฐ๋จํ ๋ก์ง ์ถ๊ฐ ๊ฐ๋ฅ
|
462 |
+
logger.warning("์ฑ ์ค๋น ์ํ ํ์ธ (์๊ฐ ๋น๊ต ๋ถ๊ฐ)")
|
463 |
+
# ํ์์ ๋ค๋ฅธ ์ค๋น ์ํ ํ์ธ ๋ก์ง ์ถ๊ฐ
|
464 |
+
pass # ์์๋ก ํต๊ณผ
|
465 |
+
|
466 |
+
if not app_ready:
|
467 |
+
logger.info("์ฑ์ด ์์ง ์ค๋น๋์ง ์์ ๋ก๋ฉ ํ์ด์ง ํ์")
|
468 |
+
return render_template('loading.html'), 503
|
469 |
+
|
470 |
+
logger.info("๋ฉ์ธ ํ์ด์ง ์์ฒญ")
|
471 |
+
return render_template('index.html')
|
472 |
+
|
473 |
+
|
474 |
+
@app.route('/api/status')
|
475 |
+
@login_required
|
476 |
+
def app_status():
|
477 |
+
"""์ฑ ์ด๊ธฐํ ์ํ ํ์ธ API"""
|
478 |
+
logger.info(f"์ฑ ์ํ ํ์ธ ์์ฒญ: {'Ready' if app_ready else 'Not Ready'}")
|
479 |
+
return jsonify({"ready": app_ready})
|
480 |
+
|
481 |
+
|
482 |
+
@app.route('/api/llm', methods=['GET', 'POST'])
|
483 |
+
@login_required
|
484 |
+
def llm_api():
|
485 |
+
"""์ฌ์ฉ ๊ฐ๋ฅํ LLM ๋ชฉ๋ก ๋ฐ ์ ํ API"""
|
486 |
+
global llm_interface
|
487 |
+
|
488 |
+
if not app_ready:
|
489 |
+
return jsonify({"error": "์ฑ์ด ์์ง ์ด๊ธฐํ ์ค์
๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์."}), 503
|
490 |
+
|
491 |
+
if request.method == 'GET':
|
492 |
+
logger.info("LLM ๋ชฉ๋ก ์์ฒญ")
|
493 |
+
try:
|
494 |
+
current_details = llm_interface.get_current_llm_details() if hasattr(llm_interface, 'get_current_llm_details') else {"id": "unknown", "name": "Unknown"}
|
495 |
+
supported_llms_dict = llm_interface.SUPPORTED_LLMS if hasattr(llm_interface, 'SUPPORTED_LLMS') else {}
|
496 |
+
supported_list = [{
|
497 |
+
"name": name, "id": id, "current": id == current_details.get("id")
|
498 |
+
} for name, id in supported_llms_dict.items()]
|
499 |
+
|
500 |
+
return jsonify({
|
501 |
+
"supported_llms": supported_list,
|
502 |
+
"current_llm": current_details
|
503 |
+
})
|
504 |
+
except Exception as e:
|
505 |
+
logger.error(f"LLM ์ ๋ณด ์กฐํ ์ค๋ฅ: {e}")
|
506 |
+
return jsonify({"error": "LLM ์ ๋ณด ์กฐํ ์ค ์ค๋ฅ ๋ฐ์"}), 500
|
507 |
+
|
508 |
+
elif request.method == 'POST':
|
509 |
+
data = request.get_json()
|
510 |
+
if not data or 'llm_id' not in data:
|
511 |
+
return jsonify({"error": "LLM ID๊ฐ ์ ๊ณต๋์ง ์์์ต๋๋ค."}), 400
|
512 |
+
|
513 |
+
llm_id = data['llm_id']
|
514 |
+
logger.info(f"LLM ๋ณ๊ฒฝ ์์ฒญ: {llm_id}")
|
515 |
+
|
516 |
+
try:
|
517 |
+
if not hasattr(llm_interface, 'set_llm') or not hasattr(llm_interface, 'llm_clients'):
|
518 |
+
raise NotImplementedError("LLM ์ธํฐํ์ด์ค์ ํ์ํ ๋ฉ์๋/์์ฑ ์์")
|
519 |
+
|
520 |
+
if llm_id not in llm_interface.llm_clients:
|
521 |
+
return jsonify({"error": f"์ง์๋์ง ์๋ LLM ID: {llm_id}"}), 400
|
522 |
+
|
523 |
+
success = llm_interface.set_llm(llm_id)
|
524 |
+
if success:
|
525 |
+
new_details = llm_interface.get_current_llm_details()
|
526 |
+
logger.info(f"LLM์ด '{new_details.get('name', llm_id)}'๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค.")
|
527 |
+
return jsonify({
|
528 |
+
"success": True,
|
529 |
+
"message": f"LLM์ด '{new_details.get('name', llm_id)}'๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค.",
|
530 |
+
"current_llm": new_details
|
531 |
+
})
|
532 |
+
else:
|
533 |
+
logger.error(f"LLM ๋ณ๊ฒฝ ์คํจ (ID: {llm_id})")
|
534 |
+
return jsonify({"error": "LLM ๋ณ๊ฒฝ ์ค ๋ด๋ถ ์ค๋ฅ ๋ฐ์"}), 500
|
535 |
+
except Exception as e:
|
536 |
+
logger.error(f"LLM ๋ณ๊ฒฝ ์ฒ๋ฆฌ ์ค ์ค๋ฅ: {e}", exc_info=True)
|
537 |
+
return jsonify({"error": f"LLM ๋ณ๊ฒฝ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}"}), 500
|
538 |
+
|
539 |
+
|
540 |
+
@app.route('/api/chat', methods=['POST'])
|
541 |
+
@login_required
|
542 |
+
def chat():
|
543 |
+
"""ํ
์คํธ ๊ธฐ๋ฐ ์ฑ๋ด API"""
|
544 |
+
global retriever
|
545 |
+
|
546 |
+
if not app_ready or retriever is None:
|
547 |
+
return jsonify({"error": "์ฑ/๊ฒ์๊ธฐ๊ฐ ์์ง ์ด๊ธฐํ ์ค์
๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์."}), 503
|
548 |
+
|
549 |
+
try:
|
550 |
+
data = request.get_json()
|
551 |
+
if not data or 'query' not in data:
|
552 |
+
return jsonify({"error": "์ฟผ๋ฆฌ๊ฐ ์ ๊ณต๋์ง ์์์ต๋๋ค."}), 400
|
553 |
+
|
554 |
+
query = data['query']
|
555 |
+
logger.info(f"ํ
์คํธ ์ฟผ๋ฆฌ ์์ : {query[:100]}...")
|
556 |
+
|
557 |
+
if not hasattr(retriever, 'search'):
|
558 |
+
raise NotImplementedError("Retriever์ search ๋ฉ์๋๊ฐ ์์ต๋๋ค.")
|
559 |
+
search_results = retriever.search(query, top_k=5, first_stage_k=6)
|
560 |
+
|
561 |
+
if not hasattr(DocumentProcessor, 'prepare_rag_context'):
|
562 |
+
raise NotImplementedError("DocumentProcessor์ prepare_rag_context ๋ฉ์๋๊ฐ ์์ต๋๋ค.")
|
563 |
+
context = DocumentProcessor.prepare_rag_context(search_results, field="text")
|
564 |
+
|
565 |
+
if not context:
|
566 |
+
logger.warning("๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ด ์ปจํ
์คํธ๋ฅผ ์์ฑํ์ง ๋ชปํจ.")
|
567 |
+
pass
|
568 |
+
|
569 |
+
llm_id = data.get('llm_id', None)
|
570 |
+
if not hasattr(llm_interface, 'rag_generate'):
|
571 |
+
raise NotImplementedError("LLMInterface์ rag_generate ๋ฉ์๋๊ฐ ์์ต๋๋ค.")
|
572 |
+
|
573 |
+
if not context:
|
574 |
+
answer = "์ฃ์กํฉ๋๋ค. ๊ด๋ จ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."
|
575 |
+
logger.info("์ปจํ
์คํธ ์์ด ๊ธฐ๋ณธ ์๋ต ์์ฑ")
|
576 |
+
else:
|
577 |
+
answer = llm_interface.rag_generate(query, context, llm_id=llm_id)
|
578 |
+
logger.info(f"LLM ์๋ต ์์ฑ ์๋ฃ (๊ธธ์ด: {len(answer)})")
|
579 |
+
|
580 |
+
|
581 |
+
sources = []
|
582 |
+
if search_results:
|
583 |
+
for result in search_results:
|
584 |
+
if not isinstance(result, dict):
|
585 |
+
logger.warning(f"์์์น ๋ชปํ ๊ฒ์ ๊ฒฐ๊ณผ ํ์: {type(result)}")
|
586 |
+
continue
|
587 |
+
|
588 |
+
if "source" in result:
|
589 |
+
source_info = {
|
590 |
+
"source": result.get("source", "Unknown"),
|
591 |
+
"score": result.get("rerank_score", result.get("score", 0))
|
592 |
+
}
|
593 |
+
|
594 |
+
if "text" in result and result.get("filetype") == "csv":
|
595 |
+
try:
|
596 |
+
text_lines = result["text"].strip().split('\n')
|
597 |
+
if text_lines:
|
598 |
+
first_line = text_lines[0].strip()
|
599 |
+
if ',' in first_line:
|
600 |
+
first_column = first_line.split(',')[0].strip()
|
601 |
+
source_info["id"] = first_column
|
602 |
+
logger.debug(f"CSV ์์ค ID ์ถ์ถ: {first_column} from {source_info['source']}")
|
603 |
+
except Exception as e:
|
604 |
+
logger.warning(f"CSV ์์ค ID ์ถ์ถ ์คํจ ({result.get('source')}): {e}")
|
605 |
+
|
606 |
+
sources.append(source_info)
|
607 |
+
|
608 |
+
response_data = {
|
609 |
+
"answer": answer,
|
610 |
+
"sources": sources,
|
611 |
+
"llm": llm_interface.get_current_llm_details() if hasattr(llm_interface, 'get_current_llm_details') else {}
|
612 |
+
}
|
613 |
+
return jsonify(response_data)
|
614 |
+
|
615 |
+
except Exception as e:
|
616 |
+
logger.error(f"์ฑํ
์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {e}", exc_info=True)
|
617 |
+
return jsonify({"error": f"์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}"}), 500
|
618 |
+
|
619 |
+
|
620 |
+
@app.route('/api/voice', methods=['POST'])
|
621 |
+
@login_required
|
622 |
+
def voice_chat():
|
623 |
+
"""์์ฑ ์ฑ API ์๋ํฌ์ธํธ"""
|
624 |
+
global retriever, stt_client
|
625 |
+
|
626 |
+
if not app_ready:
|
627 |
+
logger.warning("์ฑ ์ด๊ธฐํ๊ฐ ์๋ฃ๋์ง ์์์ง๋ง ์์ฑ API ์์ฒญ ์ฒ๋ฆฌ ์๋")
|
628 |
+
|
629 |
+
if retriever is None:
|
630 |
+
logger.error("retriever๊ฐ ์์ง ์ด๊ธฐํ๋์ง ์์์ต๋๋ค")
|
631 |
+
return jsonify({
|
632 |
+
"transcription": "(์์ฑ์ ํ
์คํธ๋ก ๋ณํํ์ง๋ง ๊ฒ์ ์์ง์ด ์์ง ์ค๋น๋์ง ์์์ต๋๋ค)",
|
633 |
+
"answer": "์ฃ์กํฉ๋๋ค. ๊ฒ์ ์์ง์ด ์์ง ์ด๊ธฐํ ์ค์
๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.",
|
634 |
+
"sources": []
|
635 |
+
})
|
636 |
+
if stt_client is None:
|
637 |
+
return jsonify({
|
638 |
+
"transcription": "(์์ฑ ์ธ์ ๊ธฐ๋ฅ์ด ์ค๋น ์ค์
๋๋ค)",
|
639 |
+
"answer": "์ฃ์กํฉ๋๋ค. ํ์ฌ ์์ฑ ์ธ์ ์๋น์ค๊ฐ ์ด๊ธฐํ ์ค์
๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.",
|
640 |
+
"sources": []
|
641 |
+
})
|
642 |
+
|
643 |
+
logger.info("์์ฑ ์ฑ ์์ฒญ ์์ ")
|
644 |
+
|
645 |
+
if 'audio' not in request.files:
|
646 |
+
logger.error("์ค๋์ค ํ์ผ์ด ์ ๊ณต๋์ง ์์")
|
647 |
+
return jsonify({"error": "์ค๋์ค ํ์ผ์ด ์ ๊ณต๋์ง ์์์ต๋๋ค."}), 400
|
648 |
+
|
649 |
+
audio_file = request.files['audio']
|
650 |
+
logger.info(f"์์ ๋ ์ค๋์ค ํ์ผ: {audio_file.filename} ({audio_file.content_type})")
|
651 |
+
|
652 |
+
try:
|
653 |
+
with tempfile.NamedTemporaryFile(delete=True) as temp_audio:
|
654 |
+
audio_file.save(temp_audio.name)
|
655 |
+
logger.info(f"์ค๋์ค ํ์ผ์ ์์ ์ ์ฅ: {temp_audio.name}")
|
656 |
+
|
657 |
+
if not hasattr(stt_client, 'transcribe_audio'):
|
658 |
+
raise NotImplementedError("STT ํด๋ผ์ด์ธํธ์ transcribe_audio ๋ฉ์๋๊ฐ ์์ต๋๋ค.")
|
659 |
+
|
660 |
+
with open(temp_audio.name, 'rb') as f_bytes:
|
661 |
+
audio_bytes = f_bytes.read()
|
662 |
+
stt_result = stt_client.transcribe_audio(audio_bytes, language="ko")
|
663 |
+
|
664 |
+
|
665 |
+
if not isinstance(stt_result, dict) or not stt_result.get("success"):
|
666 |
+
error_msg = stt_result.get("error", "์ ์ ์๋ STT ์ค๋ฅ") if isinstance(stt_result, dict) else "STT ๊ฒฐ๊ณผ ํ์ ์ค๋ฅ"
|
667 |
+
logger.error(f"์์ฑ์ธ์ ์คํจ: {error_msg}")
|
668 |
+
return jsonify({
|
669 |
+
"error": "์์ฑ์ธ์ ์คํจ",
|
670 |
+
"details": error_msg
|
671 |
+
}), 500
|
672 |
+
|
673 |
+
transcription = stt_result.get("text", "")
|
674 |
+
if not transcription:
|
675 |
+
logger.warning("์์ฑ์ธ์ ๊ฒฐ๊ณผ๊ฐ ๋น์ด์์ต๋๋ค.")
|
676 |
+
return jsonify({"error": "์์ฑ์์ ํ
์คํธ๋ฅผ ์ธ์ํ์ง ๋ชปํ์ต๋๋ค.", "transcription": ""}), 400
|
677 |
+
|
678 |
+
logger.info(f"์์ฑ์ธ์ ์ฑ๊ณต: {transcription[:50]}...")
|
679 |
+
if retriever is None:
|
680 |
+
logger.error("STT ์ฑ๊ณต ํ ๊ฒ์ ์๋ ์ค retriever๊ฐ None์")
|
681 |
+
return jsonify({
|
682 |
+
"transcription": transcription,
|
683 |
+
"answer": "์์ฑ์ ์ธ์ํ์ง๋ง, ํ์ฌ ๊ฒ์ ์์คํ
์ด ์ค๋น๋์ง ์์์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.",
|
684 |
+
"sources": []
|
685 |
+
})
|
686 |
+
|
687 |
+
search_results = retriever.search(transcription, top_k=5, first_stage_k=6)
|
688 |
+
context = DocumentProcessor.prepare_rag_context(search_results, field="text")
|
689 |
+
|
690 |
+
if not context:
|
691 |
+
logger.warning("์์ฑ ์ฟผ๋ฆฌ์ ๋ํ ๊ฒ์ ๊ฒฐ๊ณผ ์์.")
|
692 |
+
pass
|
693 |
+
|
694 |
+
llm_id = request.form.get('llm_id', None)
|
695 |
+
if not context:
|
696 |
+
answer = "์ฃ์กํฉ๋๋ค. ๊ด๋ จ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."
|
697 |
+
logger.info("์ปจํ
์คํธ ์์ด ๊ธฐ๋ณธ ์๋ต ์์ฑ")
|
698 |
+
else:
|
699 |
+
answer = llm_interface.rag_generate(transcription, context, llm_id=llm_id)
|
700 |
+
logger.info(f"LLM ์๋ต ์์ฑ ์๋ฃ (๊ธธ์ด: {len(answer)})")
|
701 |
+
|
702 |
+
enhanced_sources = []
|
703 |
+
if search_results:
|
704 |
+
for doc in search_results:
|
705 |
+
if not isinstance(doc, dict): continue
|
706 |
+
if "source" in doc:
|
707 |
+
source_info = {
|
708 |
+
"source": doc.get("source", "Unknown"),
|
709 |
+
"score": doc.get("rerank_score", doc.get("score", 0))
|
710 |
+
}
|
711 |
+
if "text" in doc and doc.get("filetype") == "csv":
|
712 |
+
try:
|
713 |
+
text_lines = doc["text"].strip().split('\n')
|
714 |
+
if text_lines:
|
715 |
+
first_line = text_lines[0].strip()
|
716 |
+
if ',' in first_line:
|
717 |
+
first_column = first_line.split(',')[0].strip()
|
718 |
+
source_info["id"] = first_column
|
719 |
+
except Exception as e:
|
720 |
+
logger.warning(f"[์์ฑ์ฑ] CSV ์์ค ID ์ถ์ถ ์คํจ ({doc.get('source')}): {e}")
|
721 |
+
enhanced_sources.append(source_info)
|
722 |
+
|
723 |
+
response_data = {
|
724 |
+
"transcription": transcription,
|
725 |
+
"answer": answer,
|
726 |
+
"sources": enhanced_sources,
|
727 |
+
"llm": llm_interface.get_current_llm_details() if hasattr(llm_interface, 'get_current_llm_details') else {}
|
728 |
+
}
|
729 |
+
return jsonify(response_data)
|
730 |
+
|
731 |
+
except Exception as e:
|
732 |
+
logger.error(f"์์ฑ ์ฑ ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {e}", exc_info=True)
|
733 |
+
return jsonify({
|
734 |
+
"error": "์์ฑ ์ฒ๋ฆฌ ์ค ๋ด๋ถ ์ค๋ฅ ๋ฐ์",
|
735 |
+
"details": str(e)
|
736 |
+
}), 500
|
737 |
+
|
738 |
+
|
739 |
+
@app.route('/api/upload', methods=['POST'])
|
740 |
+
@login_required
|
741 |
+
def upload_document():
|
742 |
+
"""์ง์๋ฒ ์ด์ค ๋ฌธ์ ์
๋ก๋ API"""
|
743 |
+
global base_retriever, retriever
|
744 |
+
|
745 |
+
if not app_ready or base_retriever is None:
|
746 |
+
return jsonify({"error": "์ฑ/๊ธฐ๋ณธ ๊ฒ์๊ธฐ๊ฐ ์์ง ์ด๊ธฐํ ์ค์
๋๋ค."}), 503
|
747 |
+
|
748 |
+
if 'document' not in request.files:
|
749 |
+
return jsonify({"error": "๋ฌธ์ ํ์ผ์ด ์ ๊ณต๋์ง ์์์ต๋๋ค."}), 400
|
750 |
+
|
751 |
+
doc_file = request.files['document']
|
752 |
+
if doc_file.filename == '':
|
753 |
+
return jsonify({"error": "์ ํ๋ ํ์ผ์ด ์์ต๋๋ค."}), 400
|
754 |
+
|
755 |
+
if not allowed_doc_file(doc_file.filename):
|
756 |
+
logger.error(f"ํ์ฉ๋์ง ์๋ ํ์ผ ํ์: {doc_file.filename}")
|
757 |
+
return jsonify({"error": f"ํ์ฉ๋์ง ์๋ ํ์ผ ํ์์
๋๋ค. ํ์ฉ: {', '.join(ALLOWED_DOC_EXTENSIONS)}"}), 400
|
758 |
+
|
759 |
+
try:
|
760 |
+
filename = secure_filename(doc_file.filename)
|
761 |
+
filepath = os.path.join(app.config['DATA_FOLDER'], filename)
|
762 |
+
doc_file.save(filepath)
|
763 |
+
logger.info(f"๋ฌธ์ ์ ์ฅ ์๋ฃ: {filepath}")
|
764 |
+
|
765 |
+
try:
|
766 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
767 |
+
content = f.read()
|
768 |
+
except UnicodeDecodeError:
|
769 |
+
logger.info(f"UTF-8 ๋์ฝ๋ฉ ์คํจ, CP949๋ก ์๋: {filename}")
|
770 |
+
try:
|
771 |
+
with open(filepath, 'r', encoding='cp949') as f:
|
772 |
+
content = f.read()
|
773 |
+
except Exception as e_cp949:
|
774 |
+
logger.error(f"CP949 ๋์ฝ๋ฉ ์คํจ ({filename}): {e_cp949}")
|
775 |
+
return jsonify({"error": "ํ์ผ ์ธ์ฝ๋ฉ์ ์ฝ์ ์ ์์ต๋๋ค (UTF-8, CP949 ์๋ ์คํจ)."}), 400
|
776 |
+
except Exception as e_read:
|
777 |
+
logger.error(f"ํ์ผ ์ฝ๊ธฐ ์ค๋ฅ ({filename}): {e_read}")
|
778 |
+
return jsonify({"error": f"ํ์ผ ์ฝ๊ธฐ ์ค ์ค๋ฅ ๋ฐ์: {str(e_read)}"}), 500
|
779 |
+
|
780 |
+
metadata = {
|
781 |
+
"source": filename, "filename": filename,
|
782 |
+
"filetype": filename.rsplit('.', 1)[1].lower(),
|
783 |
+
"filepath": filepath
|
784 |
+
}
|
785 |
+
file_ext = metadata["filetype"]
|
786 |
+
docs = []
|
787 |
+
|
788 |
+
if not hasattr(DocumentProcessor, 'csv_to_documents') or not hasattr(DocumentProcessor, 'text_to_documents'):
|
789 |
+
raise NotImplementedError("DocumentProcessor์ ํ์ํ ๋ฉ์๋ ์์")
|
790 |
+
|
791 |
+
if file_ext == 'csv':
|
792 |
+
logger.info(f"CSV ํ์ผ ์ฒ๋ฆฌ ์์: {filename}")
|
793 |
+
docs = DocumentProcessor.csv_to_documents(content, metadata)
|
794 |
+
else:
|
795 |
+
logger.info(f"์ผ๋ฐ ํ
์คํธ ๋ฌธ์ ์ฒ๋ฆฌ ์์: {filename}")
|
796 |
+
if file_ext in ['pdf', 'docx']:
|
797 |
+
logger.warning(f".{file_ext} ํ์ผ ์ฒ๋ฆฌ๋ ํ์ฌ ๊ตฌํ๋์ง ์์์ต๋๋ค. ํ
์คํธ ์ถ์ถ ๋ก์ง ์ถ๊ฐ ํ์.")
|
798 |
+
content = ""
|
799 |
+
|
800 |
+
if content:
|
801 |
+
docs = DocumentProcessor.text_to_documents(
|
802 |
+
content, metadata=metadata,
|
803 |
+
chunk_size=512, chunk_overlap=50
|
804 |
+
)
|
805 |
+
|
806 |
+
if docs:
|
807 |
+
if not hasattr(base_retriever, 'add_documents') or not hasattr(base_retriever, 'save'):
|
808 |
+
raise NotImplementedError("๊ธฐ๋ณธ ๊ฒ์๊ธฐ์ add_documents ๋๋ save ๋ฉ์๋ ์์")
|
809 |
+
|
810 |
+
logger.info(f"{len(docs)}๊ฐ ๋ฌธ์ ์ฒญํฌ๋ฅผ ๊ฒ์๊ธฐ์ ์ถ๊ฐํฉ๋๋ค...")
|
811 |
+
base_retriever.add_documents(docs)
|
812 |
+
|
813 |
+
logger.info(f"๊ฒ์๊ธฐ ์ํ๋ฅผ ์ ์ฅํฉ๋๋ค...")
|
814 |
+
index_path = app.config['INDEX_PATH']
|
815 |
+
try:
|
816 |
+
base_retriever.save(index_path)
|
817 |
+
logger.info("์ธ๋ฑ์ค ์ ์ฅ ์๋ฃ")
|
818 |
+
# ์ฌ์์ํ ๊ฒ์๊ธฐ ์
๋ฐ์ดํธ ๋ก์ง ํ์ ์ ์ถ๊ฐ
|
819 |
+
# ์: if retriever != base_retriever and hasattr(retriever, 'update_base_retriever'): retriever.update_base_retriever(base_retriever)
|
820 |
+
return jsonify({
|
821 |
+
"success": True,
|
822 |
+
"message": f"ํ์ผ '{filename}' ์
๋ก๋ ๋ฐ ์ฒ๋ฆฌ ์๋ฃ ({len(docs)}๊ฐ ์ฒญํฌ ์ถ๊ฐ)."
|
823 |
+
})
|
824 |
+
except Exception as e_save:
|
825 |
+
logger.error(f"์ธ๋ฑ์ค ์ ์ฅ ์ค ์ค๋ฅ ๋ฐ์: {e_save}")
|
826 |
+
return jsonify({"error": f"์ธ๋ฑ์ค ์ ์ฅ ์ค ์ค๋ฅ: {str(e_save)}"}), 500
|
827 |
+
else:
|
828 |
+
logger.warning(f"ํ์ผ '{filename}'์์ ์ฒ๋ฆฌํ ๋ด์ฉ์ด ์๊ฑฐ๋ ์ง์๋์ง ์๋ ํ์์
๋๋ค.")
|
829 |
+
return jsonify({
|
830 |
+
"warning": True,
|
831 |
+
"message": f"ํ์ผ '{filename}'์ด ์ ์ฅ๋์์ง๋ง ์ฒ๋ฆฌํ ๋ด์ฉ์ด ์์ต๋๋ค."
|
832 |
+
})
|
833 |
+
|
834 |
+
except Exception as e:
|
835 |
+
logger.error(f"ํ์ผ ์
๋ก๋ ๋๋ ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {e}", exc_info=True)
|
836 |
+
return jsonify({"error": f"ํ์ผ ์
๋ก๋ ์ค ์ค๋ฅ: {str(e)}"}), 500
|
837 |
+
|
838 |
+
|
839 |
+
@app.route('/api/documents', methods=['GET'])
|
840 |
+
@login_required
|
841 |
+
def list_documents():
|
842 |
+
"""์ง์๋ฒ ์ด์ค ๋ฌธ์ ๋ชฉ๋ก API"""
|
843 |
+
global base_retriever
|
844 |
+
|
845 |
+
if not app_ready or base_retriever is None:
|
846 |
+
return jsonify({"error": "์ฑ/๊ธฐ๋ณธ ๊ฒ์๊ธฐ๊ฐ ์์ง ์ด๊ธฐํ ์ค์
๋๋ค."}), 503
|
847 |
+
|
848 |
+
try:
|
849 |
+
sources = {}
|
850 |
+
total_chunks = 0
|
851 |
+
if hasattr(base_retriever, 'documents') and base_retriever.documents:
|
852 |
+
logger.info(f"์ด {len(base_retriever.documents)}๊ฐ ๋ฌธ์ ์ฒญํฌ์์ ์์ค ๋ชฉ๋ก ์์ฑ ์ค...")
|
853 |
+
for doc in base_retriever.documents:
|
854 |
+
if not isinstance(doc, dict): continue
|
855 |
+
|
856 |
+
source = doc.get("source", "unknown")
|
857 |
+
if source == "unknown" and "metadata" in doc and isinstance(doc["metadata"], dict):
|
858 |
+
source = doc["metadata"].get("source", "unknown")
|
859 |
+
|
860 |
+
if source != "unknown":
|
861 |
+
if source in sources:
|
862 |
+
sources[source]["chunks"] += 1
|
863 |
+
else:
|
864 |
+
filename = doc.get("filename", source)
|
865 |
+
filetype = doc.get("filetype", "unknown")
|
866 |
+
if "metadata" in doc and isinstance(doc["metadata"], dict):
|
867 |
+
filename = doc["metadata"].get("filename", filename)
|
868 |
+
filetype = doc["metadata"].get("filetype", filetype)
|
869 |
+
|
870 |
+
sources[source] = {
|
871 |
+
"filename": filename,
|
872 |
+
"chunks": 1,
|
873 |
+
"filetype": filetype
|
874 |
+
}
|
875 |
+
total_chunks += 1
|
876 |
+
else:
|
877 |
+
logger.info("๊ฒ์๊ธฐ์ ๋ฌธ์๊ฐ ์๊ฑฐ๋ documents ์์ฑ์ ์ฐพ์ ์ ์์ต๋๋ค.")
|
878 |
+
|
879 |
+
documents = [{"source": src, **info} for src, info in sources.items()]
|
880 |
+
documents.sort(key=lambda x: x["chunks"], reverse=True)
|
881 |
+
|
882 |
+
logger.info(f"๋ฌธ์ ๋ชฉ๋ก ์กฐํ ์๋ฃ: {len(documents)}๊ฐ ์์ค ํ์ผ, {total_chunks}๊ฐ ์ฒญํฌ")
|
883 |
+
return jsonify({
|
884 |
+
"documents": documents,
|
885 |
+
"total_documents": len(documents),
|
886 |
+
"total_chunks": total_chunks
|
887 |
+
})
|
888 |
+
|
889 |
+
except Exception as e:
|
890 |
+
logger.error(f"๋ฌธ์ ๋ชฉ๋ก ์กฐํ ์ค ์ค๋ฅ ๋ฐ์: {e}", exc_info=True)
|
891 |
+
return jsonify({"error": f"๋ฌธ์ ๋ชฉ๋ก ์กฐํ ์ค ์ค๋ฅ: {str(e)}"}), 500
|
892 |
+
|
893 |
+
|
894 |
+
# ์ ์ ํ์ผ ์๋น
|
895 |
+
@app.route('/static/<path:path>')
|
896 |
+
def send_static(path):
|
897 |
+
return send_from_directory('static', path)
|
898 |
+
|
899 |
+
|
900 |
+
# --- ์์ฒญ ์ฒ๋ฆฌ ํ
---
|
901 |
+
|
902 |
+
@app.after_request
|
903 |
+
def after_request_func(response):
|
904 |
+
"""๋ชจ๋ ์๋ต์ ๋ํด ํ์ฒ๋ฆฌ ์ํ"""
|
905 |
+
# logger.debug(f"[After Request] ์๋ต ํค๋: {response.headers}") # ๋๋ฒ๊น
์ Set-Cookie ํ์ธ
|
906 |
+
return response
|
907 |
+
|
908 |
+
|
909 |
+
# ์ฑ ์คํ (๋ก์ปฌ ํ
์คํธ์ฉ)
|
910 |
if __name__ == '__main__':
|
911 |
logger.info("Flask ์ฑ์ ์ง์ ์คํํฉ๋๋ค (๊ฐ๋ฐ์ฉ ์๋ฒ).")
|
912 |
+
# ๋๋ฒ๊ทธ ๋ชจ๋๋ ๊ฐ๋ฐ ์ค์๋ง True๋ก ์ค์ ํ๊ณ , ์ค์ ๋ฐฐํฌ ์์๋ False๋ก ์ค์ ํด์ผ ํฉ๋๋ค.
|
913 |
+
# host='0.0.0.0' ์ ๋ชจ๋ ๋คํธ์ํฌ ์ธํฐํ์ด์ค์์ ์ ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
|
914 |
port = int(os.environ.get("PORT", 7860))
|
915 |
logger.info(f"์๋ฒ๋ฅผ http://0.0.0.0:{port} ์์ ์์ํฉ๋๋ค.")
|
916 |
+
# debug=True ์ฌ์ฉ ์ werkzeug reloader๊ฐ ํ์ฑํ๋์ด ์ฝ๋๊ฐ ๋ณ๊ฒฝ๋ ๋ ์๋ฒ๊ฐ ์ฌ์์๋ ์ ์์ผ๋ฉฐ,
|
917 |
+
# ์ด ๊ณผ์ ์์ ์ ์ญ ์ด๊ธฐํ ์ฝ๋๊ฐ ๋ค์ ์คํ๋ ์ ์์ต๋๋ค.
|
918 |
+
# DEVICE_ROUTES_REGISTERED ํ๋๊ทธ๊ฐ ์ด๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
|
919 |
+
app.run(debug=True, host='0.0.0.0', port=port)
|
app/app/__init__.py
DELETED
@@ -1,3 +0,0 @@
|
|
1 |
-
"""
|
2 |
-
RAG ์ฑ๋ด ์ฑ ํจํค์ง
|
3 |
-
"""
|
|
|
|
|
|
|
|
app/{app/app_device_routes.py โ app_device_routes.py}
RENAMED
File without changes
|
app/{app/app_routes.py โ app_routes.py}
RENAMED
File without changes
|
app/docs/cleanup_plan.md
DELETED
@@ -1,31 +0,0 @@
|
|
1 |
-
# ํ๋ก์ ํธ ์ ๋ฆฌ ๊ณํ
|
2 |
-
|
3 |
-
## ์ ๊ฑฐํ ํ์ผ๋ค
|
4 |
-
1. `app.py` - `app_revised.py`๊ฐ ์ด ํ์ผ์ ๊ธฐ๋ฅ์ ๋์ฒดํ๊ณ ์์ผ๋ฉฐ, ๋ชจ๋ํ๊ฐ ๋ ์ ๋์ด ์์ต๋๋ค.
|
5 |
-
2. `app_part2.py` - ์ด ํ์ผ์ ๊ธฐ๋ฅ์ `init_retriever.py`๋ก ์ด๋ฏธ ๋ถ๋ฆฌ๋์ด ์์ต๋๋ค.
|
6 |
-
3. `app_part3.py` - ์ด ํ์ผ์ ๋ผ์ฐํธ ํจ์๋ค์ `app_routes.py`์ ํฌํจ๋์ด ์์ต๋๋ค.
|
7 |
-
|
8 |
-
## ๊ตฌ์กฐ ๊ฐ์
|
9 |
-
1. `app_revised.py` - ๋ฉ์ธ ์ ํ๋ฆฌ์ผ์ด์
ํ์ผ
|
10 |
-
- ์ฑ ์ด๊ธฐํ ๋ฐ ์คํ
|
11 |
-
- ๋ผ์ฐํธ ๋ฑ๋ก ํจ์ ํธ์ถ
|
12 |
-
|
13 |
-
2. `app_routes.py` - RAG ์ฑ๋ด ๊ธฐ๋ณธ ๋ผ์ฐํธ
|
14 |
-
- ๋ชจ๋ ๊ธฐ๋ณธ ๋ผ์ฐํธ (๋ก๊ทธ์ธ, ๊ฒ์, ๋ฌธ์ ๊ด๋ฆฌ ๋ฑ)
|
15 |
-
|
16 |
-
3. `app_device_routes.py` - ์ฅ์น ๊ด๋ฆฌ ๊ด๋ จ ๋ผ์ฐํธ
|
17 |
-
- LocalPCAgent ์ฐ๊ฒฐ ๋ฐ ์ ์ด ๊ด๋ จ API
|
18 |
-
|
19 |
-
4. `init_retriever.py` - ๊ฒ์๊ธฐ ์ด๊ธฐํ ํจ์
|
20 |
-
- ๊ฒ์๊ธฐ ๋ก๋, ์ด๊ธฐํ, ์๋ฒ ๋ฉ ๊ด๋ฆฌ
|
21 |
-
|
22 |
-
## ํ๋ฐํธ์๋ ํ์ผ ๊ฐ์ ๊ณํ
|
23 |
-
1. `templates/index.html` - ๊ธฐ์กด ํ์ผ์ ์ฅ์น ์ ์ด UI ์ถ๊ฐ
|
24 |
-
2. `static/js/device.js` - ์๋ก ์ถ๊ฐํ ์ฅ์น ์ ์ด JavaScript ํ์ผ
|
25 |
-
3. `static/css/device.css` - ์๋ก ์ถ๊ฐํ ์ฅ์น ์ ์ด ์คํ์ผ ํ์ผ (ํ์ ์)
|
26 |
-
|
27 |
-
## ์ ๋ฆฌ ์์
|
28 |
-
1. ๋ถํ์ํ ํ์ผ ์ ๊ฑฐ
|
29 |
-
2. ํ์ํ ๊ฒฝ์ฐ ํ์ผ ๊ฐ ์ฝ๋ ํตํฉ
|
30 |
-
3. ์๋ก์ด ํ๋ฐํธ์๋ ํ์ผ ์ถ๊ฐ
|
31 |
-
4. ์ ์ฒด ์์คํ
ํ
์คํธ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/docs/cleanup_summary.md
DELETED
@@ -1,44 +0,0 @@
|
|
1 |
-
# ํ๋ก์ ํธ ์ ๋ฆฌ ์์ฝ
|
2 |
-
|
3 |
-
## ๊ฐ์
|
4 |
-
RAG ์ฑ๋ด๊ณผ LocalPCAgent ํตํฉ ํ๋ก์ ํธ์ ์ฝ๋ ์ ๋ฆฌ ์์
์ ์๋ฃํ์ต๋๋ค. ๊ธฐ์กด ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋งํ๊ณ ๋ถํ์ํ ํ์ผ์ ์ ๊ฑฐํ์ฌ ํ๋ก์ ํธ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ ํ์ต๋๋ค.
|
5 |
-
|
6 |
-
## ์ํํ ์์
|
7 |
-
|
8 |
-
### 1. ์ฝ๋ ๊ตฌ์กฐ ๋ชจ๋ํ
|
9 |
-
- `app/` ๋๋ ํ ๋ฆฌ ์์ฑ ๋ฐ ๋ชจ๋ ํ์ผ ์ด๋:
|
10 |
-
- `app_device_routes.py` โ `app/app_device_routes.py`
|
11 |
-
- `app_routes.py` โ `app/app_routes.py`
|
12 |
-
- `init_retriever.py` โ `app/init_retriever.py`
|
13 |
-
- `app/__init__.py` ์ถ๊ฐํ์ฌ ํจํค์งํ
|
14 |
-
|
15 |
-
### 2. ํ์ผ ์ ๋ฆฌ
|
16 |
-
- ์ค๋ณต ํ์ผ ๋ฐ ๋ถํ์ํ ํ์ผ ์ ๋ฆฌ:
|
17 |
-
- `app_part2.py` ๋ฐ `app_part3.py` ์ ๊ฑฐ ์์ (์ด๋ฏธ ๋ค๋ฅธ ํ์ผ๋ก ํตํฉ๋จ)
|
18 |
-
- `app.py` ํ์ผ์ ๊ฐ์ํํ์ฌ `app_revised.py`๋ฅผ ํธ์ถํ๋ ๋ฐฉ์์ผ๋ก ๋ฆฌํฉํ ๋ง
|
19 |
-
|
20 |
-
### 3. ํ๋ฐํธ์๋ ํตํฉ ํ์ธ
|
21 |
-
- ์ฅ์น ์ ์ด UI ๊ตฌํ ํ์ธ:
|
22 |
-
- `templates/index.html`์ ์ฅ์น ์ ์ด UI ์น์
์ด๋ฏธ ๊ตฌํ๋จ
|
23 |
-
- `static/js/app-device.js`์ ์ฅ์น ์ ์ด JavaScript ๋ก์ง ๊ตฌํ๋จ
|
24 |
-
- `static/css/device-style.css`์ ์ฅ์น ์ ์ด ์คํ์ผ ๊ตฌํ๋จ
|
25 |
-
|
26 |
-
### 4. ํ๋ก์ ํธ ๋ฌธ์ํ
|
27 |
-
- `docs/project_plan.md` ์
๋ฐ์ดํธ: ์ต์ ํ๋ก์ ํธ ์ํ ๋ฐ์
|
28 |
-
- `docs/cleanup_plan.md` ์์ฑ: ์ ๋ฆฌ ์์
๊ณํ ๋ฌธ์ํ
|
29 |
-
- `docs/cleanup_summary.md` ์์ฑ: ์ ๋ฆฌ ์์
๊ฒฐ๊ณผ ์์ฝ
|
30 |
-
|
31 |
-
## ํ๋ก์ ํธ ํ์ฌ ์ํ
|
32 |
-
LocalPCAgent ์ ์ด ๊ธฐ๋ฅ์ด RAG ์ฑ๋ด ์น ์ธํฐํ์ด์ค์ ์ฑ๊ณต์ ์ผ๋ก ํตํฉ๋์์ต๋๋ค. UI๋ ํญ ๋ฐฉ์์ผ๋ก ๊ตฌํ๋์ด ์ฌ์ฉ์๊ฐ ์ฝ๊ฒ ๋ํ, ๋ฌธ์ ๊ด๋ฆฌ, ์ฅ์น ์ ์ด ๊ธฐ๋ฅ์ ์ ํํ ์ ์์ต๋๋ค.
|
33 |
-
|
34 |
-
์ฅ์น ์ ์ด ๊ธฐ๋ฅ ๊ตฌํ ์ํฉ:
|
35 |
-
1. **์๋ฒ ์ฐ๊ฒฐ**: ngrok URL ์
๋ ฅ ๋ฐ ์ฐ๊ฒฐ ๊ธฐ๋ฅ
|
36 |
-
2. **์ํ ํ์ธ**: ์ฅ์น ์๋ฒ ์ํ ํ์ธ ๊ธฐ๋ฅ
|
37 |
-
3. **ํ๋ก๊ทธ๋จ ๋ชฉ๋ก ์กฐํ**: ๋ฑ๋ก๋ ํ๋ก๊ทธ๋จ ๋ชฉ๋ก ํ์
|
38 |
-
4. **ํ๋ก๊ทธ๋จ ์คํ**: ์ ํํ ํ๋ก๊ทธ๋จ ์คํ ๊ธฐ๋ฅ
|
39 |
-
5. **์ฌ์ฉ์ ์ ์ ์คํ**: ์ฌ์ฉ์ ์ง์ ๋ช
๋ น์ด ์คํ ๊ธฐ๋ฅ
|
40 |
-
|
41 |
-
## ๋ค์ ๋จ๊ณ
|
42 |
-
1. **ํ
์คํธ**: ํตํฉ๋ ์์คํ
ํ
์คํธ ์งํ
|
43 |
-
2. **์ต์ ํ**: ํ์ ์ ์ถ๊ฐ ์ฝ๋ ์ต์ ํ
|
44 |
-
3. **๋ฐฐํฌ**: ๋ค์ํ ํ๊ฒฝ์์์ ๋ฐฐํฌ ํ
์คํธ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/docs/project_plan.md
CHANGED
@@ -1,75 +1,44 @@
|
|
1 |
# RAG ์ฑ๋ด + LocalPCAgent ํตํฉ ํ๋ก์ ํธ ๊ณํ
|
2 |
|
3 |
## ํ๋ก์ ํธ ๊ฐ์
|
4 |
-
|
|
|
5 |
|
6 |
## ์๋ฃ๋ ์์
|
7 |
-
- [x] ๊ธฐ๋ณธ Flask ๊ธฐ๋ฐ RAG ์ฑ๋ด ๋ฐฑ์๋ ๊ตฌํ
|
8 |
-
- [x] LocalPCAgent ์ ์ด๋ฅผ ์ํ API ์๋ํฌ์ธํธ ์ถ๊ฐ (`app_device_routes.py`)
|
9 |
-
- [x] Flask ๋ฐฑ์๋์ LocalPCAgent ๊ฐ ํต์ ๋ก์ง ๊ตฌํ
|
10 |
-
- [x] ์น UI์ LocalPCAgent ์ ์ด ํ๋ฉด ์ถ๊ฐ (HTML/CSS)
|
11 |
-
- [x] ์น UI์์ LocalPCAgent ์ ์ด ๊ธฐ๋ฅ์ ์ํ JavaScript ๋ก์ง ๊ตฌํ
|
12 |
-
- [x] ํ๋ก์ ํธ ์ฝ๋ ์ ๋ฆฌ ๋ฐ ์ต์ ํ
|
13 |
-
- ๋ถํ์ํ ํ์ผ ๋ฐ ์ฝ๋ ์ ๊ฑฐ
|
14 |
-
- ๋ชจ๋ํ๋ ์ฝ๋ ๊ตฌ์กฐ ๊ฐ์
|
15 |
|
16 |
-
|
17 |
-
|
18 |
-
-
|
19 |
-
-
|
20 |
-
-
|
21 |
-
-
|
22 |
-
|
23 |
-
|
24 |
-
-
|
25 |
-
- `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
|
27 |
-
|
28 |
-
-
|
29 |
-
- ๋ค์ํ ํ๊ฒฝ์์ ํ
์คํธ (๊ฐ๋ฐ ํ๊ฒฝ, ๋ฐฐํฌ ํ๊ฒฝ)
|
30 |
-
- ๋ค์ํ ์ฅ์น ์ฐ๊ฒฐ ์๋๋ฆฌ์ค ํ
์คํธ
|
31 |
|
32 |
-
|
33 |
-
- `app_revised.py`: ๋ฉ์ธ Flask ์ ํ๋ฆฌ์ผ์ด์
|
34 |
-
- `app.py`: ๋จ์ํ๋ ์ง์
์ (app_revised.py ํธ์ถ)
|
35 |
-
- `app/`: ์ ํ๋ฆฌ์ผ์ด์
๋ชจ๋
|
36 |
-
- `__init__.py`: ํจํค์ง ์ด๊ธฐํ
|
37 |
-
- `app_device_routes.py`: LocalPCAgent ์ ์ด API ๋ผ์ฐํธ
|
38 |
-
- `app_routes.py`: ๊ธฐ๋ณธ RAG ์ฑ๋ด API ๋ผ์ฐํธ
|
39 |
-
- `init_retriever.py`: RAG ๋ชจ๋ธ ์ด๊ธฐํ ์ฝ๋
|
40 |
-
- `static/`: ์ ์ ํ์ผ
|
41 |
-
- `css/`: ์คํ์ผ์ํธ
|
42 |
-
- `style.css`: ๊ธฐ๋ณธ ์คํ์ผ
|
43 |
-
- `device-style.css`: ์ฅ์น ์ ์ด ์คํ์ผ
|
44 |
-
- `js/`: JavaScript ํ์ผ
|
45 |
-
- `app.js`: ๋ฉ์ธ JavaScript
|
46 |
-
- `app-core.js`: ํต์ฌ ๊ธฐ๋ฅ
|
47 |
-
- `app-device.js`: ์ฅ์น ์ ์ด ๊ธฐ๋ฅ
|
48 |
-
- `app-docs.js`: ๋ฌธ์ ๊ด๋ฆฌ ๊ธฐ๋ฅ
|
49 |
-
- `app-llm.js`: LLM ๊ด๋ จ ๊ธฐ๋ฅ
|
50 |
-
- `templates/`: HTML ํ
ํ๋ฆฟ
|
51 |
-
- `index.html`: ๋ฉ์ธ ์ ํ๋ฆฌ์ผ์ด์
ํ์ด์ง
|
52 |
-
- `login.html`: ๋ก๊ทธ์ธ ํ์ด์ง
|
53 |
-
- `loading.html`: ๋ก๋ฉ ํ์ด์ง
|
54 |
-
- `docs/`: ํ๋ก์ ํธ ๋ฌธ์
|
55 |
-
- `project_plan.md`: ํ๋ก์ ํธ ๊ณํ ๋ฌธ์
|
56 |
-
- `cleanup_plan.md`: ์ฝ๋ ์ ๋ฆฌ ๊ณํ ๋ฌธ์
|
57 |
|
58 |
-
|
59 |
-
-
|
60 |
-
- `/api/device/status`: ์ฅ์น ์๋ฒ ์ํ ํ์ธ
|
61 |
-
- `/api/device/connect`: ์ฌ์ฉ์ ์ง์ URL ์ฐ๊ฒฐ
|
62 |
-
- `/api/device/list`: ์ฅ์น ๋ชฉ๋ก ์กฐํ
|
63 |
-
- `/api/device/programs`: ์คํ ๊ฐ๋ฅํ ํ๋ก๊ทธ๋จ ๋ชฉ๋ก ์กฐํ
|
64 |
-
- `/api/device/programs/<program_id>/execute`: ๋ฑ๋ก๋ ํ๋ก๊ทธ๋จ ์คํ
|
65 |
-
- `/api/device/execute-custom`: ์ฌ์ฉ์ ์ ์ ํ๋ก๊ทธ๋จ ์คํ
|
66 |
|
67 |
-
|
68 |
-
- ์ฅ์น ์ ์ด ํญ UI: `templates/index.html`์ ๊ตฌํ
|
69 |
-
- JavaScript ๋ก์ง: `static/js/app-device.js`์ ๊ตฌํ
|
70 |
-
- ์คํ์ผ: `static/css/device-style.css`์ ๊ตฌํ
|
71 |
|
72 |
-
|
73 |
-
-
|
74 |
-
-
|
75 |
-
-
|
|
|
1 |
# RAG ์ฑ๋ด + LocalPCAgent ํตํฉ ํ๋ก์ ํธ ๊ณํ
|
2 |
|
3 |
## ํ๋ก์ ํธ ๊ฐ์
|
4 |
+
|
5 |
+
์ด ํ๋ก์ ํธ๋ RAG ์ฑ๋ด ๋ฐฑ์๋(Flask ๊ธฐ๋ฐ)์ LocalPCAgent ์ ์ด ์ธํฐํ์ด์ค๋ฅผ ํตํฉํ๋ ๊ฒ์ ๋ชฉํ๋ก ํฉ๋๋ค. ์ฌ์ฉ์๋ RAG ์ฑ๋ด ์ธํฐํ์ด์ค ๋ด์์ LocalPCAgent๋ฅผ ํตํด ์๊ฒฉ์ผ๋ก PC๋ฅผ ์ ์ดํ ์ ์๊ฒ ๋ฉ๋๋ค.
|
6 |
|
7 |
## ์๋ฃ๋ ์์
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
+
### 1. ์ฅ์น ์๋ฒ ์ฐ๊ฒฐ ๊ธฐ๋ฅ ๊ฐ์ (2025-05-02)
|
10 |
+
|
11 |
+
- `app-device.js` ํ์ผ์์ `connectServer()` ํจ์ ์์ :
|
12 |
+
- ํ๊ฒฝ๋ณ์์ ์ ์ฅ๋ URL์ ์ฐ์ ์ ์ผ๋ก ์ฌ์ฉํ๋๋ก ์์
|
13 |
+
- ํ
์คํธ๋ฐ์ค์ ์
๋ ฅ๋ URL ์ฃผ์๋ ํ๊ฒฝ๋ณ์ URL ์ฐ๊ฒฐ ์คํจ ์ ๋ฐฑ์
์ผ๋ก ์ฌ์ฉ
|
14 |
+
- ์ฐ๊ฒฐ ์ํ ๋ฐ ์ค๋ฅ ๋ฉ์์ง ๊ฐ์
|
15 |
+
|
16 |
+
- `app_device_routes.py` ํ์ผ์ ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ:
|
17 |
+
- ์ฌ์ฉ์ ์ง์ URL ์ ์ฅ์ ์ํ `custom_device_url` ๋ณ์ ์ถ๊ฐ
|
18 |
+
- URL ๊ด๋ฆฌ๋ฅผ ์ํ `get_device_url()` ํจ์ ๊ตฌํ
|
19 |
+
- `/api/device/connect` ์๋ํฌ์ธํธ ์ถ๊ฐํ์ฌ ์ฌ์ฉ์ ์ง์ URL ์ค์ ๊ธฐ๋ฅ ๊ตฌํ
|
20 |
+
- ๋ชจ๋ API ์๋ํฌ์ธํธ์์ `get_device_url()` ํจ์๋ฅผ ์ฌ์ฉํ๋๋ก ์
๋ฐ์ดํธ
|
21 |
+
|
22 |
+
## ์์ ๋ ์์
|
23 |
+
|
24 |
+
### 1. ์ถ๊ฐ UI ๊ฐ์
|
25 |
+
|
26 |
+
- ์ฅ์น ์๋ฒ URL ์
๋ ฅ ํ๋์ ๊ธฐ๋ณธ ํ
์คํธ ์ถ๊ฐ (์: "ํ๊ฒฝ๋ณ์์ ์ ์ฅ๋ URL ์ฌ์ฉ, ๋๋ ์ง์ ์
๋ ฅ")
|
27 |
+
- ์ฐ๊ฒฐ ์ฑ๊ณต/์คํจ ์ UI ํผ๋๋ฐฑ ๊ฐ์
|
28 |
+
|
29 |
+
### 2. ์ค๋ฅ ์ฒ๋ฆฌ ๊ฐํ
|
30 |
|
31 |
+
- ์ค๋ฅ ๋ฉ์์ง ๊ฐ์ ๋ฐ ๋ ๊ตฌ์ฒด์ ์ธ ๊ฐ์ด๋ ์ ๊ณต
|
32 |
+
- ๋คํธ์ํฌ ์ค๋ฅ ๋ฐ์ ์ ์๋ ์ฌ์๋ ๊ธฐ๋ฅ
|
|
|
|
|
33 |
|
34 |
+
### 3. ํ
์คํธ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
|
36 |
+
- ํ๊ฒฝ๋ณ์ URL ๋ฐ ์ฌ์ฉ์ ์ง์ URL ์ ํ ํ
์คํธ
|
37 |
+
- ๋ค์ํ ์ค๋ฅ ์ํฉ ์๋ฎฌ๋ ์ด์
๋ฐ ๋ณต๊ตฌ ํ
์คํธ
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
|
39 |
+
## ๊ธฐ์ ์คํ
|
|
|
|
|
|
|
40 |
|
41 |
+
- ํ๋ก ํธ์๋: JavaScript, HTML, CSS
|
42 |
+
- ๋ฐฑ์๋: Flask (Python)
|
43 |
+
- ํต์ : RESTful API
|
44 |
+
- ์ฅ์น ์ ์ด: LocalPCAgent API
|
app/{app/init_retriever.py โ init_retriever.py}
RENAMED
File without changes
|