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
|