bissal's picture
Add separate web chat endpoint (/api/web/chat) and disable Kakao webhook
0078332
# app.py - μˆ˜μ •λœ μ™„μ „ν•œ Flask μ• ν”Œλ¦¬μΌ€μ΄μ…˜
from flask import Flask, request, jsonify
import logging
import threading
from datetime import datetime
import os
# Flask μ•± 생성 (κ°€μž₯ λ¨Όμ €!)
app = Flask(__name__)
# λ‘œκΉ… μ„€μ •
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# RAG μ‹œμŠ€ν…œ import μΆ”κ°€
try:
from rag_system import search_tax_law, is_rag_available
logger.info("βœ… RAG μ‹œμŠ€ν…œ λ‘œλ“œ μ™„λ£Œ")
RAG_SYSTEM_AVAILABLE = True
except ImportError as e:
logger.warning(f"⚠️ RAG μ‹œμŠ€ν…œ λ‘œλ“œ μ‹€νŒ¨: {e}")
RAG_SYSTEM_AVAILABLE = False
# 톡합 μ‹œμŠ€ν…œ import (app 생성 ν›„)
try:
from tax_consultant import (
handle_tax_consultation_start,
handle_tax_survey_response,
is_tax_question,
tax_consultant
)
logger.info("βœ… 톡합 취득세 상담 μ‹œμŠ€ν…œ λ‘œλ“œ μ™„λ£Œ")
INTEGRATED_SYSTEM_AVAILABLE = True
except ImportError as e:
logger.warning(f"⚠️ 톡합 μ‹œμŠ€ν…œ λ‘œλ“œ μ‹€νŒ¨: {e}")
INTEGRATED_SYSTEM_AVAILABLE = False
# κΈ°μ‘΄ AI ν•¨μˆ˜ (폴백용)
def generate_tax_advice(question):
"""AI 응닡 생성 ν•¨μˆ˜ (RAG μ‹œμŠ€ν…œ ν™œμš©)"""
try:
# 1μˆœμœ„: RAG μ‹œμŠ€ν…œ μ‚¬μš©
if RAG_SYSTEM_AVAILABLE and is_rag_available():
logger.info("🧠 RAG μ‹œμŠ€ν…œμœΌλ‘œ λ‹΅λ³€ 생성")
return search_tax_law(question)
# 2μˆœμœ„: κΈ°μ‘΄ AI 둜직
logger.info("πŸ€– κΈ°μ‘΄ AI μ‹œμŠ€ν…œμœΌλ‘œ λ‹΅λ³€ 생성")
return f"취득세 상담: {question}에 λŒ€ν•œ 뢄석을 μ™„λ£Œν–ˆμŠ΅λ‹ˆλ‹€."
except Exception as e:
logger.error(f"AI 응닡 생성 였λ₯˜: {e}")
return "μ£„μ†‘ν•©λ‹ˆλ‹€. 뢄석 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."
@app.route('/api/web/chat', methods=['POST'])
def web_chat():
"""μ›Ή μΈν„°νŽ˜μ΄μŠ€ μ „μš© μ±„νŒ… μ—”λ“œν¬μΈνŠΈ"""
try:
data = request.get_json()
user_message = data.get('message', '').strip()
if not user_message:
return jsonify({"error": "λ©”μ‹œμ§€κ°€ λΉ„μ–΄μžˆμŠ΅λ‹ˆλ‹€"}), 400
logger.info(f"πŸ’¬ μ›Ή μ±„νŒ… μš”μ²­: {user_message}")
# RAG μ‹œμŠ€ν…œμœΌλ‘œ 응닡 생성
if RAG_SYSTEM_AVAILABLE and is_rag_available():
try:
logger.info("πŸ“š RAG μ‹œμŠ€ν…œμœΌλ‘œ λ‹΅λ³€ 생성")
response = search_tax_law(user_message)
return jsonify({"response": response})
except Exception as e:
logger.error(f"❌ RAG μ‹œμŠ€ν…œ 였λ₯˜: {e}")
# 폴백 응닡
return jsonify({"response": generate_tax_advice(user_message)})
else:
# RAG 없을 λ•Œ κΈ°λ³Έ 응닡
response = generate_tax_advice(user_message)
return jsonify({"response": response})
except Exception as e:
logger.error(f"❌ μ›Ή μ±„νŒ… 였λ₯˜: {e}")
return jsonify({"error": str(e)}), 500
# μΉ΄μΉ΄μ˜€ν†‘ 웹훅은 λ‚˜μ€‘μ— μ‚¬μš©ν•  수 μžˆλ„λ‘ μœ μ§€ν•˜λ˜ λΉ„ν™œμ„±ν™”
# @app.route('/api/kakao/webhook', methods=['POST']) # μ£Όμ„μ²˜λ¦¬λ‘œ λΉ„ν™œμ„±ν™”
@app.route('/api/chat', methods=['POST'])
def kakao_webhook_disabled():
"""μΉ΄μΉ΄μ˜€ν†‘ μ›Ήν›… - ν˜„μž¬ λΉ„ν™œμ„±ν™”λ¨"""
logger.warning("⚠️ μΉ΄μΉ΄μ˜€ν†‘ 웹훅이 ν˜ΈμΆœλ˜μ—ˆμ§€λ§Œ ν˜„μž¬ λΉ„ν™œμ„±ν™” μƒνƒœμž…λ‹ˆλ‹€")
return jsonify({"message": "μΉ΄μΉ΄μ˜€ν†‘ 웹훅은 ν˜„μž¬ λΉ„ν™œμ„±ν™”λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€"}), 503
# μ›λž˜ μΉ΄μΉ΄μ˜€ν†‘ μ›Ήν›… μ½”λ“œλŠ” μ•„λž˜μ— 보쑴
def kakao_webhook_original():
"""μΉ΄μΉ΄μ˜€ν†‘ μ˜€ν”ˆλΉŒλ” μ›Ήν›… 처리 - 톡합 슀마트 μ‹œμŠ€ν…œ + RAG (ν˜„μž¬ λΉ„ν™œμ„±ν™”)"""
try:
data = request.get_json()
user_utterance = data.get('userRequest', {}).get('utterance', '')
user_id = data.get('userRequest', {}).get('user', {}).get('id', 'anonymous')
session_id = f"session_{user_id}"
logger.info(f"πŸ“¨ μΉ΄μΉ΄μ˜€ν†‘ - User: {user_id[:8]}***, Msg: {user_utterance}")
if not user_utterance.strip():
raise ValueError("빈 λ©”μ‹œμ§€")
# 🧠 톡합 μ‹œμŠ€ν…œμ΄ μ‚¬μš© κ°€λŠ₯ν•œ 경우 (μ΅œμš°μ„ )
if INTEGRATED_SYSTEM_AVAILABLE and is_tax_question(user_utterance):
session = tax_consultant.session_manager.get_session(session_id)
if session and session.get('current_step', 0) > 0:
# πŸ“‹ μ„€λ¬Έ μ§„ν–‰ 쀑
logger.info(f"πŸ”„ μ„€λ¬Έ 응닡 처리: 단계 {session['current_step']}")
try:
survey_response = handle_tax_survey_response(user_utterance, session_id)
logger.info(f"βœ… μ„€λ¬Έ 응닡 처리 μ™„λ£Œ")
return survey_response
except Exception as e:
logger.error(f"❌ μ„€λ¬Έ 처리 였λ₯˜: {e}")
return create_error_response("μ„€λ¬Έ 처리 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.")
else:
# πŸ†• μƒˆλ‘œμš΄ 상담 μ‹œμž‘
logger.info(f"πŸ†• μƒˆλ‘œμš΄ 취득세 상담 μ‹œμž‘")
try:
analysis_result = tax_consultant.analyzer.analyze_comprehensive_question(user_utterance)
missing_conditions = analysis_result.get('missing_conditions', [])
confidence_score = analysis_result.get('confidence_score', 0)
if not missing_conditions and confidence_score >= 75:
# ⚑ λͺ¨λ“  정보 μ™„λΉ„ - μ¦‰μ‹œ 계산
logger.info(f"⚑ μ¦‰μ‹œ 계산 κ°€λŠ₯ (신뒰도: {confidence_score}%)")
consultation_response = handle_tax_consultation_start(user_utterance, session_id, user_id)
return consultation_response
else:
# πŸ“‹ μ„€λ¬Έ ν•„μš” - 뢄석과 ν•¨κ»˜ μ§„ν–‰
logger.info(f"πŸ“‹ μ„€λ¬Έ ν•„μš” (λΆ€μ‘±: {len(missing_conditions)}개)")
consultation_response = handle_tax_consultation_start(user_utterance, session_id, user_id)
# λ°±κ·ΈλΌμš΄λ“œμ—μ„œ RAG μ‹œμŠ€ν…œμœΌλ‘œ μΆ”κ°€ 정보 제곡
def generate_rag_enhancement():
try:
if RAG_SYSTEM_AVAILABLE and is_rag_available():
logger.info(f"πŸ“š RAG μ‹œμŠ€ν…œμœΌλ‘œ 법령 검색 μ‹œμž‘")
rag_result = search_tax_law(user_utterance)
logger.info(f"πŸ“š RAG 검색 μ™„λ£Œ: {len(rag_result)}자")
# μ—¬κΈ°μ„œ μΆ”κ°€ 정보λ₯Ό μ„Έμ…˜μ— μ €μž₯ν•˜κ±°λ‚˜ 별도 처리 κ°€λŠ₯
except Exception as e:
logger.error(f"❌ RAG λ°±κ·ΈλΌμš΄λ“œ 처리 μ‹€νŒ¨: {e}")
threading.Thread(target=generate_rag_enhancement, daemon=True).start()
return consultation_response
except Exception as e:
logger.error(f"❌ 상담 μ‹œμž‘ 였λ₯˜: {e}")
# 폴백: RAG μ‹œμŠ€ν…œ λ˜λŠ” κΈ°μ‘΄ λ°©μ‹μœΌλ‘œ 처리
is_web = 'message' in data
return handle_fallback_response(user_utterance, is_web_request=is_web)
# πŸ”„ 폴백 처리 (RAG μ‹œμŠ€ν…œ μš°μ„ )
else:
is_web = 'message' in data
return handle_fallback_response(user_utterance, is_web_request=is_web)
except Exception as e:
logger.error(f"❌ μ±„νŒ… 였λ₯˜: {e}")
# μ›Ή μΈν„°νŽ˜μ΄μŠ€μš© 응닡
if 'message' in data:
return jsonify({"response": f"μ£„μ†‘ν•©λ‹ˆλ‹€. 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}"}), 500
# μΉ΄μΉ΄μ˜€ν†‘μš© 응닡
else:
return create_error_response("μ£„μ†‘ν•©λ‹ˆλ‹€. μΌμ‹œμ μΈ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.")
def handle_fallback_response(user_utterance, is_web_request=False):
"""폴백 응닡 처리 (RAG μ‹œμŠ€ν…œ μš°μ„ )"""
# 취득세 κ΄€λ ¨ ν‚€μ›Œλ“œ 확인
tax_keywords = ['취득세', '뢀동산', 'μ§‘', 'μ•„νŒŒνŠΈ', '주택', 'μ„ΈκΈˆ', 'λ§€λ§€', '증여', '상속']
is_tax_related = any(keyword in user_utterance for keyword in tax_keywords)
if is_tax_related:
# 🧠 RAG μ‹œμŠ€ν…œ μš°μ„  μ‹œλ„
if RAG_SYSTEM_AVAILABLE and is_rag_available():
try:
logger.info("πŸ“š RAG μ‹œμŠ€ν…œμœΌλ‘œ 법령 기반 λ‹΅λ³€ 생성")
rag_response = search_tax_law(user_utterance)
# μ›Ή μΈν„°νŽ˜μ΄μŠ€μš© 응닡
if is_web_request:
return jsonify({"response": rag_response})
# RAG 응닡을 μΉ΄μΉ΄μ˜€ν†‘ ν˜•μ‹μœΌλ‘œ ν¬λ§·νŒ…
formatted_response = f"""🏠 **취득세 법령 상담**
πŸ“ **질문**: {user_utterance}
πŸ“š **법령 기반 λ‹΅λ³€**:
{rag_response}
πŸ’‘ **μΆ”κ°€ 문의**κ°€ μžˆμœΌμ‹œλ©΄ μ–Έμ œλ“  λ§μ”€ν•΄μ£Όμ„Έμš”!"""
return create_simple_response(formatted_response)
except Exception as e:
logger.error(f"❌ RAG μ‹œμŠ€ν…œ 였λ₯˜: {e}")
# RAG μ‹€νŒ¨μ‹œ κΈ°μ‘΄ λ°©μ‹μœΌλ‘œ 폴백
# κΈ°μ‘΄ 방식 (RAG μ‹œμŠ€ν…œμ΄ μ—†κ±°λ‚˜ μ‹€νŒ¨ν•œ 경우)
quick_response = f"""πŸ€– 취득세 상담을 μ‹œμž‘ν•©λ‹ˆλ‹€!
πŸ“ 질문 λ‚΄μš©: "{user_utterance}"
πŸ”„ μ •ν™•ν•œ 계산을 μœ„ν•΄ 뢄석 μ€‘μž…λ‹ˆλ‹€...
μž μ‹œλ§Œ κΈ°λ‹€λ €μ£Όμ„Έμš”! (μ•½ 10-15초 μ†Œμš”)
πŸ’‘ **미리 μ•Œλ €λ“œλ¦¬λŠ” 정보:**
- μ·¨λ“μ„ΈλŠ” μ§€μ—­κ³Ό κΈˆμ•‘μ— 따라 닀름
- μ‘°μ •λŒ€μƒμ§€μ—­μ€ μΆ”κ°€ 쀑과세 적용
- λ‹€μ£ΌνƒμžλŠ” 별도 μ„Έμœ¨ 적용
μ •ν™•ν•œ 계산 κ²°κ³Όλ₯Ό κ³§ μ•Œλ €λ“œλ¦¬κ² μŠ΅λ‹ˆλ‹€! ✨"""
# λ°±κ·ΈλΌμš΄λ“œμ—μ„œ μ‹€μ œ AI 응닡 생성
def generate_delayed_response():
try:
ai_response = generate_tax_advice(user_utterance)
logger.info(f"πŸ“€ μ§€μ—° 응닡 생성 μ™„λ£Œ: {len(ai_response)}자")
except Exception as e:
logger.error(f"❌ μ§€μ—° 응닡 생성 μ‹€νŒ¨: {e}")
threading.Thread(target=generate_delayed_response, daemon=True).start()
return create_simple_response(quick_response)
else:
# 일반 인사말
return create_simple_response(
"μ•ˆλ…•ν•˜μ„Έμš”! 🏠 **뢀동산 취득세 μ „λ¬Έ 상담** μ±—λ΄‡μž…λ‹ˆλ‹€.\n\n"
"취득세 κ΄€λ ¨ μ§ˆλ¬Έμ„ ν•΄μ£Όμ‹œλ©΄ μ •ν™•ν•œ 계산과 상담을 λ„μ™€λ“œλ¦½λ‹ˆλ‹€!\n\n"
"πŸ’‘ **μ˜ˆμ‹œ:**\n"
"β€’ '강남ꡬ μ•„νŒŒνŠΈ 10μ–΅ 2μ£Όνƒμž 취득세'\n"
"β€’ 'λΆ€λͺ¨λ‹˜μ΄ μžλ…€μ—κ²Œ 5μ–΅ μ§‘ 증여 μ„ΈκΈˆ'\n"
"β€’ 'μ„œμ΄ˆκ΅¬ 15μ–΅ 주택 λ§€λ§€ 취득세 계산'"
)
def create_simple_response(text: str) -> dict:
"""λ‹¨μˆœ ν…μŠ€νŠΈ 응닡 생성"""
return jsonify({
"version": "2.0",
"template": {
"outputs": [
{
"simpleText": {
"text": text
}
}
]
}
})
def create_error_response(error_message: str) -> tuple:
"""μ—λŸ¬ 응닡 생성"""
return jsonify({
"version": "2.0",
"template": {
"outputs": [
{
"simpleText": {
"text": error_message
}
}
]
}
}), 500
@app.route('/health', methods=['GET'])
def health_check():
"""μ‹œμŠ€ν…œ μƒνƒœ 확인"""
try:
status_info = {
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"service": "Smart Tax Consultant",
"integrated_system": INTEGRATED_SYSTEM_AVAILABLE,
"rag_system": RAG_SYSTEM_AVAILABLE and is_rag_available() if RAG_SYSTEM_AVAILABLE else False
}
if INTEGRATED_SYSTEM_AVAILABLE:
active_sessions = len(tax_consultant.session_manager.active_sessions)
status_info.update({
"active_sessions": active_sessions,
"features": {
"intelligent_analysis": "enabled",
"adaptive_survey": "enabled",
"gift_tax_special": "enabled",
"rag_law_search": "enabled" if RAG_SYSTEM_AVAILABLE else "disabled",
"timeout_prevention": "enabled"
}
})
else:
status_info.update({
"active_sessions": 0,
"features": {
"fallback_mode": "enabled",
"rag_law_search": "enabled" if RAG_SYSTEM_AVAILABLE else "disabled",
"timeout_prevention": "enabled"
}
})
return jsonify(status_info)
except Exception as e:
return jsonify({
"status": "unhealthy",
"error": str(e),
"timestamp": datetime.now().isoformat()
}), 500
# RAG μ‹œμŠ€ν…œ μ „μš© μ—”λ“œν¬μΈνŠΈ μΆ”κ°€
@app.route('/api/rag/search', methods=['POST'])
def rag_search():
"""RAG μ‹œμŠ€ν…œ μ „μš© 법령 검색"""
try:
if not RAG_SYSTEM_AVAILABLE:
return jsonify({"error": "RAG μ‹œμŠ€ν…œμ„ μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€"}), 503
data = request.get_json()
question = data.get('question', '')
if not question.strip():
return jsonify({"error": "질문이 λΉ„μ–΄μžˆμŠ΅λ‹ˆλ‹€"}), 400
result = search_tax_law(question)
return jsonify({
"status": "success",
"question": question,
"answer": result,
"timestamp": datetime.now().isoformat()
})
except Exception as e:
return jsonify({
"status": "error",
"error": str(e),
"timestamp": datetime.now().isoformat()
}), 500
@app.route('/cleanup', methods=['POST'])
def manual_cleanup():
"""μˆ˜λ™ μ„Έμ…˜ 정리"""
try:
cleanup_results = {}
if INTEGRATED_SYSTEM_AVAILABLE:
tax_consultant.session_manager.cleanup_old_sessions()
active_count = len(tax_consultant.session_manager.active_sessions)
cleanup_results['integrated_system'] = {
"status": "cleaned",
"active_sessions": active_count
}
else:
cleanup_results['integrated_system'] = {
"status": "not_available",
"active_sessions": 0
}
return jsonify({
"status": "success",
"message": "μ‹œμŠ€ν…œ 정리 μ™„λ£Œ",
"details": cleanup_results
})
except Exception as e:
return jsonify({
"status": "error",
"message": str(e)
}), 500
@app.route('/', methods=['GET'])
def root():
"""μ›Ή μΈν„°νŽ˜μ΄μŠ€ 제곡"""
html_content = '''<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>πŸ›οΈ 취득세 AI 상담봇</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 800px; margin: 0 auto; background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; }
.chat-container { border: 1px solid #ddd; border-radius: 8px; height: 400px; overflow-y: auto; padding: 15px; background: #fafafa; margin-bottom: 20px; }
.message { margin: 10px 0; padding: 10px; border-radius: 8px; }
.user-message { background: #007bff; color: white; margin-left: 20%; text-align: right; }
.bot-message { background: #e9ecef; color: #333; margin-right: 20%; }
.input-container { display: flex; gap: 10px; }
#questionInput { flex: 1; padding: 12px; border: 1px solid #ddd; border-radius: 5px; font-size: 16px; }
#sendButton { padding: 12px 24px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; }
#sendButton:hover { background: #0056b3; }
#sendButton:disabled { background: #ccc; cursor: not-allowed; }
.status { text-align: center; color: #666; margin: 10px 0; font-size: 14px; }
.samples { margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 5px; }
.samples h3 { margin-top: 0; color: #495057; }
.sample-btn { background: #28a745; color: white; border: none; padding: 8px 16px; margin: 5px; border-radius: 5px; cursor: pointer; font-size: 14px; }
.sample-btn:hover { background: #218838; }
</style>
</head>
<body>
<div class="container">
<h1>πŸ›οΈ 취득세 AI 상담봇</h1>
<div class="status">
βœ… RAG μ‹œμŠ€ν…œ: ''' + ('ν™œμ„±ν™”' if RAG_SYSTEM_AVAILABLE else 'λΉ„ν™œμ„±ν™”') + '''
| πŸ–₯️ ν™˜κ²½: ν—ˆκΉ…νŽ˜μ΄μŠ€ 슀페이슀 GPU
</div>
<div class="chat-container" id="chatContainer">
<div class="message bot-message">
μ•ˆλ…•ν•˜μ„Έμš”! μ €λŠ” 취득세 AI μƒλ‹΄λ΄‡μž…λ‹ˆλ‹€. 🏠<br>
취득세 κ΄€λ ¨ κΆκΈˆν•œ 점을 자유둭게 μ§ˆλ¬Έν•΄μ£Όμ„Έμš”!
</div>
</div>
<div class="input-container">
<input type="text" id="questionInput" placeholder="취득세 κ΄€λ ¨ μ§ˆλ¬Έμ„ μž…λ ₯ν•˜μ„Έμš”..."
onkeypress="if(event.key==='Enter') sendMessage()">
<button id="sendButton" onclick="sendMessage()">μ§ˆλ¬Έν•˜κΈ°</button>
</div>
<div class="samples">
<h3>πŸ’‘ μƒ˜ν”Œ μ§ˆλ¬Έλ“€ (ν΄λ¦­ν•˜λ©΄ μžλ™ μž…λ ₯)</h3>
<button class="sample-btn" onclick="setSample('μ·¨λ“μ„Έμœ¨μ΄ μ–Όλ§ˆμΈκ°€μš”?')">μ·¨λ“μ„Έμœ¨</button>
<button class="sample-btn" onclick="setSample('1μ„ΈλŒ€ 1μ£Όνƒμž 감면 ν˜œνƒμ€?')">1μ„ΈλŒ€1주택 감면</button>
<button class="sample-btn" onclick="setSample('μ‹ ν˜ΌλΆ€λΆ€ 취득세 νŠΉλ‘€λŠ”?')">μ‹ ν˜ΌλΆ€λΆ€ νŠΉλ‘€</button>
<button class="sample-btn" onclick="setSample('농지 취득세 계산방법은?')">농지 취득세</button>
<button class="sample-btn" onclick="setSample('λ‹€μ£Όνƒμž μ€‘κ³Όμ„Έμœ¨μ€?')">μ€‘κ³Όμ„Έμœ¨</button>
</div>
</div>
<script>
function addMessage(message, isUser) {
const chatContainer = document.getElementById('chatContainer');
const messageDiv = document.createElement('div');
messageDiv.className = 'message ' + (isUser ? 'user-message' : 'bot-message');
messageDiv.innerHTML = message.replace(/\\n/g, '<br>');
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function setSample(question) {
document.getElementById('questionInput').value = question;
}
async function sendMessage() {
const input = document.getElementById('questionInput');
const button = document.getElementById('sendButton');
const question = input.value.trim();
if (!question) return;
addMessage(question, true);
input.value = '';
button.disabled = true;
button.textContent = 'λ‹΅λ³€ 생성 쀑...';
try {
const response = await fetch('/api/web/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: question })
});
const data = await response.json();
if (data.response) {
addMessage(data.response, false);
} else {
addMessage('μ£„μ†‘ν•©λ‹ˆλ‹€. 응닡을 생성할 수 μ—†μŠ΅λ‹ˆλ‹€.', false);
}
} catch (error) {
addMessage('였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: ' + error.message, false);
}
button.disabled = false;
button.textContent = 'μ§ˆλ¬Έν•˜κΈ°';
}
</script>
</body>
</html>'''
return html_content
@app.route('/api/info', methods=['GET'])
def api_info():
"""API 정보 및 μƒνƒœ 제곡"""
return jsonify({
"service": "Smart Tax Consultant with RAG",
"version": "2.0",
"systems": {
"integrated_system": INTEGRATED_SYSTEM_AVAILABLE,
"rag_system": RAG_SYSTEM_AVAILABLE and is_rag_available() if RAG_SYSTEM_AVAILABLE else False
},
"endpoints": {
"web_chat": "/api/web/chat",
"kakao_webhook": "/api/chat (disabled)",
"rag_search": "/api/rag/search",
"health": "/health",
"cleanup": "/cleanup"
}
})
if __name__ == '__main__':
logger.info("πŸš€ 슀마트 취득세 상담 μ‹œμŠ€ν…œ + RAG μ‹œμž‘")
logger.info(f"πŸ“Š 톡합 μ‹œμŠ€ν…œ: {'μ‚¬μš© κ°€λŠ₯' if INTEGRATED_SYSTEM_AVAILABLE else '폴백 λͺ¨λ“œ'}")
logger.info(f"πŸ“š RAG μ‹œμŠ€ν…œ: {'μ‚¬μš© κ°€λŠ₯' if RAG_SYSTEM_AVAILABLE else 'μ‚¬μš© λΆˆκ°€'}")
# ν™˜κ²½λ³€μˆ˜μ—μ„œ 포트 읽기 (HuggingFace Spaces ν˜Έν™˜)
port = int(os.getenv('PORT', 7860))
app.run(host='0.0.0.0', port=port, debug=False)