Spaces:
Sleeping
Sleeping
Update routes.py
Browse files
routes.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
|
2 |
-
|
3 |
import logging
|
4 |
from flask import Blueprint, render_template, request, jsonify, send_from_directory
|
5 |
from pathlib import Path
|
@@ -23,7 +21,7 @@ main_bp = Blueprint('main', __name__)
|
|
23 |
# --- Serve the cache directory as a zip file ---
|
24 |
@main_bp.route('/download_cache')
|
25 |
def download_cache_zip():
|
26 |
-
"""
|
27 |
zip_filename = "radexplain-cache.zip"
|
28 |
# Create the zip file in a temporary directory
|
29 |
# Using /tmp is common in containerized environments
|
@@ -33,30 +31,31 @@ def download_cache_zip():
|
|
33 |
|
34 |
# Ensure the cache directory exists before trying to zip it
|
35 |
if not os.path.isdir(cache_directory):
|
36 |
-
logger.error(f"
|
37 |
-
return jsonify({"error": f"
|
38 |
|
39 |
try:
|
40 |
-
logger.info(f"
|
41 |
shutil.make_archive(
|
42 |
zip_base_path, # This is the base name, shutil adds the .zip extension
|
43 |
"zip",
|
44 |
cache_directory, # This is the root directory to archive
|
45 |
)
|
46 |
-
logger.info("
|
47 |
# Send the file and then clean it up
|
48 |
return send_from_directory(temp_dir, zip_filename, as_attachment=True)
|
49 |
except Exception as e:
|
50 |
-
logger.error(f"
|
51 |
-
return jsonify({"error": f"
|
|
|
52 |
@main_bp.route('/')
|
53 |
def index():
|
54 |
-
"""
|
55 |
# The backend now only provides the list of available reports.
|
56 |
# The frontend will be responsible for selecting a report,
|
57 |
# fetching its details (text, image path), and managing the current state.
|
58 |
if not config.AVAILABLE_REPORTS:
|
59 |
-
logger.warning("
|
60 |
|
61 |
return render_template(
|
62 |
'index.html',
|
@@ -65,12 +64,12 @@ def index():
|
|
65 |
|
66 |
@main_bp.route('/get_report_details/<report_name>')
|
67 |
def get_report_details(report_name):
|
68 |
-
"""
|
69 |
selected_report_info = next((item for item in config.AVAILABLE_REPORTS if item['name'] == report_name), None)
|
70 |
|
71 |
if not selected_report_info:
|
72 |
-
logger.error(f"
|
73 |
-
return jsonify({"error": f"
|
74 |
|
75 |
report_file = selected_report_info.get('report_file')
|
76 |
image_file = selected_report_info.get('image_file')
|
@@ -83,47 +82,45 @@ def get_report_details(report_name):
|
|
83 |
try:
|
84 |
report_text_content = actual_server_report_path.read_text(encoding='utf-8').strip()
|
85 |
except Exception as e:
|
86 |
-
logger.error(f"
|
87 |
-
return jsonify({"error": "
|
88 |
# If report_file was empty, report_text_content remains "".
|
89 |
|
90 |
image_type_from_config = selected_report_info.get('image_type')
|
91 |
-
display_image_type = '
|
92 |
|
93 |
return jsonify({"text": report_text_content, "image_file": image_file, "image_type": display_image_type})
|
94 |
|
95 |
-
|
96 |
-
|
97 |
@main_bp.route('/explain', methods=['POST'])
|
98 |
def explain_sentence():
|
99 |
-
"""
|
100 |
if not llm_is_initialized():
|
101 |
-
logger.error("LLM
|
102 |
-
return jsonify({"error": "LLM
|
103 |
|
104 |
data = request.get_json()
|
105 |
if not data or 'sentence' not in data or 'report_name' not in data:
|
106 |
-
logger.warning("
|
107 |
-
return jsonify({"error": "
|
108 |
|
109 |
selected_sentence = data['sentence']
|
110 |
report_name = data['report_name']
|
111 |
-
logger.info(f"
|
112 |
|
113 |
# --- Find the selected report info ---
|
114 |
selected_report_info = next((item for item in config.AVAILABLE_REPORTS if item['name'] == report_name), None)
|
115 |
|
116 |
if not selected_report_info:
|
117 |
-
logger.error(f"
|
118 |
-
return jsonify({"error": f"
|
119 |
|
120 |
image_file = selected_report_info.get('image_file')
|
121 |
report_file = selected_report_info.get('report_file')
|
122 |
image_type = selected_report_info.get('image_type')
|
123 |
|
124 |
if not image_file:
|
125 |
-
logger.error(f"
|
126 |
-
return jsonify({"error": f"
|
127 |
|
128 |
# Construct absolute server paths using BASE_DIR as image_file and report_file include "static/"
|
129 |
server_image_path = config.BASE_DIR / image_file
|
@@ -131,15 +128,15 @@ def explain_sentence():
|
|
131 |
|
132 |
# --- Prepare Base64 Image for API ---
|
133 |
if not server_image_path.is_file():
|
134 |
-
logger.error(f"
|
135 |
-
return jsonify({"error": f"
|
136 |
|
137 |
base64_image_data_url = utils.image_to_base64_data_url(str(server_image_path))
|
138 |
if not base64_image_data_url:
|
139 |
-
logger.error("
|
140 |
-
return jsonify({"error": "
|
141 |
|
142 |
-
logger.info("
|
143 |
|
144 |
full_report_text = ""
|
145 |
if report_file: # Only attempt to read if a report file is configured
|
@@ -147,25 +144,28 @@ def explain_sentence():
|
|
147 |
try:
|
148 |
full_report_text = server_report_path.read_text(encoding='utf-8')
|
149 |
except FileNotFoundError:
|
150 |
-
logger.error(f"
|
151 |
-
return jsonify({"error": f"
|
152 |
except Exception as e:
|
153 |
-
logger.error(f"
|
154 |
-
return jsonify({"error": "
|
155 |
else: # If report_file is not configured (e.g. empty string from selected_report_info)
|
156 |
-
logger.info(f"
|
157 |
|
|
|
|
|
|
|
158 |
system_prompt = (
|
159 |
-
"
|
160 |
-
f"
|
161 |
-
"
|
162 |
-
"
|
163 |
-
f"{f'
|
164 |
-
"
|
165 |
"===\n"
|
166 |
-
f"
|
167 |
)
|
168 |
-
user_prompt_text = f"
|
169 |
|
170 |
messages_for_api = [
|
171 |
{"role": "system", "content": system_prompt},
|
@@ -180,11 +180,11 @@ def explain_sentence():
|
|
180 |
cache_key = f"explain::{report_name}::{selected_sentence}"
|
181 |
cached_result = cache.get(cache_key)
|
182 |
if cached_result:
|
183 |
-
logger.info("
|
184 |
return jsonify({"explanation": cached_result})
|
185 |
|
186 |
try:
|
187 |
-
logger.info("
|
188 |
response = make_chat_completion_request(
|
189 |
model="tgi",
|
190 |
messages=messages_for_api,
|
@@ -197,7 +197,7 @@ def explain_sentence():
|
|
197 |
frequency_penalty=None,
|
198 |
presence_penalty=None
|
199 |
)
|
200 |
-
logger.info("
|
201 |
|
202 |
explanation_parts = []
|
203 |
for line in response.iter_lines():
|
@@ -212,7 +212,7 @@ def explain_sentence():
|
|
212 |
if chunk.get("choices") and chunk["choices"][0].get("delta") and chunk["choices"][0]["delta"].get("content"):
|
213 |
explanation_parts.append(chunk["choices"][0]["delta"]["content"])
|
214 |
except json.JSONDecodeError:
|
215 |
-
logger.warning(f"
|
216 |
# Depending on API, might need to handle partial JSON or other errors
|
217 |
elif decoded_line.strip() == "[DONE]": # Some APIs might send [DONE] without "data: "
|
218 |
break
|
@@ -221,10 +221,10 @@ def explain_sentence():
|
|
221 |
if explanation:
|
222 |
cache.set(cache_key, explanation, expire=None)
|
223 |
|
224 |
-
logger.info("
|
225 |
-
return jsonify({"explanation": explanation or "
|
226 |
except requests.exceptions.RequestException as e:
|
227 |
-
logger.error(f"
|
228 |
-
user_error_message = ("
|
229 |
-
"
|
230 |
-
return jsonify({"error": user_error_message}), 500
|
|
|
|
|
|
|
1 |
import logging
|
2 |
from flask import Blueprint, render_template, request, jsonify, send_from_directory
|
3 |
from pathlib import Path
|
|
|
21 |
# --- Serve the cache directory as a zip file ---
|
22 |
@main_bp.route('/download_cache')
|
23 |
def download_cache_zip():
|
24 |
+
"""μΊμ λλ ν 리λ₯Ό μμΆνμ¬ λ€μ΄λ‘λλ₯Ό μν΄ μ 곡ν©λλ€."""
|
25 |
zip_filename = "radexplain-cache.zip"
|
26 |
# Create the zip file in a temporary directory
|
27 |
# Using /tmp is common in containerized environments
|
|
|
31 |
|
32 |
# Ensure the cache directory exists before trying to zip it
|
33 |
if not os.path.isdir(cache_directory):
|
34 |
+
logger.error(f"μΊμ λλ ν 리λ₯Ό μ°Ύμ μ μμ΅λλ€: {cache_directory}")
|
35 |
+
return jsonify({"error": f"μλ²μμ μΊμ λλ ν 리λ₯Ό μ°Ύμ μ μμ΅λλ€: {cache_directory}"}), 500
|
36 |
|
37 |
try:
|
38 |
+
logger.info(f"μΊμ λλ ν 리μ μμΆ νμΌ μμ± μ€: {cache_directory} -> {zip_filepath}")
|
39 |
shutil.make_archive(
|
40 |
zip_base_path, # This is the base name, shutil adds the .zip extension
|
41 |
"zip",
|
42 |
cache_directory, # This is the root directory to archive
|
43 |
)
|
44 |
+
logger.info("μμΆ νμΌμ΄ μ±κ³΅μ μΌλ‘ μμ±λμμ΅λλ€.")
|
45 |
# Send the file and then clean it up
|
46 |
return send_from_directory(temp_dir, zip_filename, as_attachment=True)
|
47 |
except Exception as e:
|
48 |
+
logger.error(f"μΊμ λλ ν 리 μμΆ νμΌ μμ± λλ μ μ‘ μ€ μ€λ₯ λ°μ: {e}", exc_info=True)
|
49 |
+
return jsonify({"error": f"μμΆ νμΌ μμ± λλ μ μ‘ μ€ μ€λ₯ λ°μ: {e}"}), 500
|
50 |
+
|
51 |
@main_bp.route('/')
|
52 |
def index():
|
53 |
+
"""λ©μΈ HTML νμ΄μ§λ₯Ό μ 곡ν©λλ€."""
|
54 |
# The backend now only provides the list of available reports.
|
55 |
# The frontend will be responsible for selecting a report,
|
56 |
# fetching its details (text, image path), and managing the current state.
|
57 |
if not config.AVAILABLE_REPORTS:
|
58 |
+
logger.warning("μ€μ μμ λ³΄κ³ μλ₯Ό μ°Ύμ μ μμ΅λλ€. AVAILABLE_REPORTSκ° λΉμ΄μμ΅λλ€.")
|
59 |
|
60 |
return render_template(
|
61 |
'index.html',
|
|
|
64 |
|
65 |
@main_bp.route('/get_report_details/<report_name>')
|
66 |
def get_report_details(report_name):
|
67 |
+
"""μ£Όμ΄μ§ λ³΄κ³ μ μ΄λ¦μ λν ν
μ€νΈ λ΄μ©κ³Ό μ΄λ―Έμ§ κ²½λ‘λ₯Ό κ°μ Έμ΅λλ€."""
|
68 |
selected_report_info = next((item for item in config.AVAILABLE_REPORTS if item['name'] == report_name), None)
|
69 |
|
70 |
if not selected_report_info:
|
71 |
+
logger.error(f"μμΈ μ 보λ₯Ό κ°μ Έμ¬ λ λ³΄κ³ μ '{report_name}'μ(λ₯Ό) μ°Ύμ μ μμ΅λλ€.")
|
72 |
+
return jsonify({"error": f"λ³΄κ³ μ '{report_name}'μ(λ₯Ό) μ°Ύμ μ μμ΅λλ€."}), 404
|
73 |
|
74 |
report_file = selected_report_info.get('report_file')
|
75 |
image_file = selected_report_info.get('image_file')
|
|
|
82 |
try:
|
83 |
report_text_content = actual_server_report_path.read_text(encoding='utf-8').strip()
|
84 |
except Exception as e:
|
85 |
+
logger.error(f"λ³΄κ³ μ '{report_name}'μ νμΌ {actual_server_report_path} μ½κΈ° μ€λ₯: {e}", exc_info=True)
|
86 |
+
return jsonify({"error": "λ³΄κ³ μ νμΌμ μ½λ μ€ μ€λ₯κ° λ°μνμ΅λλ€."}), 500
|
87 |
# If report_file was empty, report_text_content remains "".
|
88 |
|
89 |
image_type_from_config = selected_report_info.get('image_type')
|
90 |
+
display_image_type = 'νλΆ X-λ μ΄' if image_type_from_config == 'CXR' else ('CT' if image_type_from_config == 'CT' else 'μλ£ μμ')
|
91 |
|
92 |
return jsonify({"text": report_text_content, "image_file": image_file, "image_type": display_image_type})
|
93 |
|
|
|
|
|
94 |
@main_bp.route('/explain', methods=['POST'])
|
95 |
def explain_sentence():
|
96 |
+
"""base64λ‘ μΈμ½λ©λ μ΄λ―Έμ§μ ν¨κ» LLM APIλ₯Ό μ¬μ©νμ¬ μ€λͺ
μμ²μ μ²λ¦¬ν©λλ€."""
|
97 |
if not llm_is_initialized():
|
98 |
+
logger.error("LLM ν΄λΌμ΄μΈνΈ(REST API)κ° μ΄κΈ°νλμ§ μμμ΅λλ€. μμ²μ μ²λ¦¬ν μ μμ΅λλ€.")
|
99 |
+
return jsonify({"error": "LLM ν΄λΌμ΄μΈνΈ(REST API)κ° μ΄κΈ°νλμ§ μμμ΅λλ€. API ν€μ λ² μ΄μ€ URLμ νμΈνμΈμ."}), 500
|
100 |
|
101 |
data = request.get_json()
|
102 |
if not data or 'sentence' not in data or 'report_name' not in data:
|
103 |
+
logger.warning("μμ² νμ΄λ‘λμ 'sentence' λλ 'report_name'μ΄ λλ½λμμ΅λλ€.")
|
104 |
+
return jsonify({"error": "μμ²μ 'sentence' λλ 'report_name'μ΄ λλ½λμμ΅λλ€"}), 400
|
105 |
|
106 |
selected_sentence = data['sentence']
|
107 |
report_name = data['report_name']
|
108 |
+
logger.info(f"μ€λͺ
μμ² μμ : '{selected_sentence}' (λ³΄κ³ μ: '{report_name}')")
|
109 |
|
110 |
# --- Find the selected report info ---
|
111 |
selected_report_info = next((item for item in config.AVAILABLE_REPORTS if item['name'] == report_name), None)
|
112 |
|
113 |
if not selected_report_info:
|
114 |
+
logger.error(f"μ¬μ© κ°λ₯ν λ³΄κ³ μμμ '{report_name}'μ(λ₯Ό) μ°Ύμ μ μμ΅λλ€.")
|
115 |
+
return jsonify({"error": f"λ³΄κ³ μ '{report_name}'μ(λ₯Ό) μ°Ύμ μ μμ΅λλ€."}), 404
|
116 |
|
117 |
image_file = selected_report_info.get('image_file')
|
118 |
report_file = selected_report_info.get('report_file')
|
119 |
image_type = selected_report_info.get('image_type')
|
120 |
|
121 |
if not image_file:
|
122 |
+
logger.error(f"λ³΄κ³ μ '{report_name}'μ μ€μ μμ μ΄λ―Έμ§ λλ λ³΄κ³ μ νμΌ κ²½λ‘(static κΈ°μ€ μλκ²½λ‘)κ° λλ½λμμ΅λλ€.")
|
123 |
+
return jsonify({"error": f"λ³΄κ³ μ '{report_name}'μ νμΌ μ€μ μ΄ λλ½λμμ΅λλ€."}), 500
|
124 |
|
125 |
# Construct absolute server paths using BASE_DIR as image_file and report_file include "static/"
|
126 |
server_image_path = config.BASE_DIR / image_file
|
|
|
128 |
|
129 |
# --- Prepare Base64 Image for API ---
|
130 |
if not server_image_path.is_file():
|
131 |
+
logger.error(f"μ΄λ―Έμ§ νμΌμ μ°Ύμ μ μμ΅λλ€: {server_image_path}")
|
132 |
+
return jsonify({"error": f"λ³΄κ³ μ '{report_name}'μ μ΄λ―Έμ§ νμΌμ μλ²μμ μ°Ύμ μ μμ΅λλ€."}), 500
|
133 |
|
134 |
base64_image_data_url = utils.image_to_base64_data_url(str(server_image_path))
|
135 |
if not base64_image_data_url:
|
136 |
+
logger.error("μ΄λ―Έμ§λ₯Ό base64λ‘ μΈμ½λ©νλλ° μ€ν¨νμ΅λλ€.")
|
137 |
+
return jsonify({"error": "API μμ²μ μν μ΄λ―Έμ§ μΈμ½λ©μ μ€ν¨νμ΅λλ€"}), 500
|
138 |
|
139 |
+
logger.info("APIλ₯Ό μν base64 λ°μ΄ν° URLλ‘ μ΄λ―Έμ§κ° μ±κ³΅μ μΌλ‘ μΈμ½λ©λμμ΅λλ€.")
|
140 |
|
141 |
full_report_text = ""
|
142 |
if report_file: # Only attempt to read if a report file is configured
|
|
|
144 |
try:
|
145 |
full_report_text = server_report_path.read_text(encoding='utf-8')
|
146 |
except FileNotFoundError:
|
147 |
+
logger.error(f"λ³΄κ³ μ νμΌμ μ°Ύμ μ μμ΅λλ€: {server_report_path}")
|
148 |
+
return jsonify({"error": f"λ³΄κ³ μ '{report_name}'μ νμΌμ μλ²μμ μ°Ύμ μ μμ΅λλ€."}), 500
|
149 |
except Exception as e:
|
150 |
+
logger.error(f"λ³΄κ³ μ νμΌ {server_report_path} μ½κΈ° μ€λ₯: {e}", exc_info=True)
|
151 |
+
return jsonify({"error": "λ³΄κ³ μ νμΌμ μ½λ μ€ μ€λ₯κ° λ°μνμ΅λλ€."}), 500
|
152 |
else: # If report_file is not configured (e.g. empty string from selected_report_info)
|
153 |
+
logger.info(f"λ³΄κ³ μ '{report_name}'μ λν λ³΄κ³ μ νμΌμ΄ μ€μ λμ§ μμμ΅λλ€. μμ€ν
ν둬ννΈμ μ 체 λ³΄κ³ μ ν
μ€νΈ μμ΄ μ§νν©λλ€.")
|
154 |
|
155 |
+
# μ΄λ―Έμ§ νμ
μ λ°λ₯Έ νκΈ νμ
|
156 |
+
image_type_korean = 'νλΆ X-λ μ΄' if image_type == 'CXR' else ('CT' if image_type == 'CT' else 'μλ£ μμ')
|
157 |
+
|
158 |
system_prompt = (
|
159 |
+
"λΉμ μ μΌλ°μΈμ λμμΌλ‘ μ€λͺ
νλ μμμμ
λλ€. "
|
160 |
+
f"νμ΅ μ€μΈ μ¬μ©μκ° λ°©μ¬μ λ³΄κ³ μμ ν λ¬Έμ₯μ μ 곡νκ³ ν¨κ» μ 곡λ {image_type_korean} μμμ λ³΄κ³ μμ΅λλ€. "
|
161 |
+
"λΉμ μ μ무λ μ 곡λ λ¬Έμ₯λ§μ μλ―Έλ₯Ό κ°λ¨νκ³ λͺ
νν μ©μ΄λ‘ μ€λͺ
νλ κ²μ
λλ€. μ λ¬Έ μ©μ΄μ μ½μ΄λ₯Ό μ€λͺ
νμΈμ. κ°κ²°νκ² μ μ§νμΈμ. "
|
162 |
+
"λ¬Έμ₯μ μλ―Έλ₯Ό μ§μ μ μΌλ‘ μ€λͺ
νμΈμ. 'λ€' λλ 'μκ² μ΅λλ€'μ κ°μ λμ
문ꡬλ₯Ό μ¬μ©νμ§ λ§κ³ , λ¬Έμ₯ μ체λ λ³΄κ³ μ μ체λ₯Ό μΈκΈνμ§ λ§μΈμ(μ: 'μ΄ λ¬Έμ₯μ...'). "
|
163 |
+
f"{f'μ€μν μ μ, μ¬μ©μκ° {image_type_korean} μμμ λ³΄κ³ μμΌλ―λ‘, ν΄λΉλλ κ²½μ° μ€λͺ
μ μ΄ν΄νκΈ° μν΄ μμμ μ΄λ λΆλΆμ λ΄μΌ νλμ§ μλ΄λ₯Ό μ 곡νμΈμ. ' if image_type != 'CT' else ''}"
|
164 |
+
"μ¬μ©μκ° λͺ
μμ μΌλ‘ μ 곡νμ§ μμ λ³΄κ³ μμ λ€λ₯Έ λΆλΆμ΄λ λ¬Έμ₯μ λν΄μλ λ
Όμνμ§ λ§μΈμ. ν
μ€νΈμ μλ μ¬μ€λ§μ κ³ μνμΈμ. μΆλ‘ νμ§ λ§μΈμ. \n"
|
165 |
"===\n"
|
166 |
+
f"μ°Έκ³ λ₯Ό μν μ 체 λ³΄κ³ μ:\n{full_report_text}"
|
167 |
)
|
168 |
+
user_prompt_text = f"λ°©μ¬μ λ³΄κ³ μμ μ΄ λ¬Έμ₯μ μ€λͺ
ν΄μ£ΌμΈμ: '{selected_sentence}'"
|
169 |
|
170 |
messages_for_api = [
|
171 |
{"role": "system", "content": system_prompt},
|
|
|
180 |
cache_key = f"explain::{report_name}::{selected_sentence}"
|
181 |
cached_result = cache.get(cache_key)
|
182 |
if cached_result:
|
183 |
+
logger.info("μΊμλ μ€λͺ
μ λ°νν©λλ€.")
|
184 |
return jsonify({"explanation": cached_result})
|
185 |
|
186 |
try:
|
187 |
+
logger.info("base64 μ΄λ―Έμ§μ ν¨κ» LLM API(REST)λ‘ μμ²μ μ μ‘ μ€...")
|
188 |
response = make_chat_completion_request(
|
189 |
model="tgi",
|
190 |
messages=messages_for_api,
|
|
|
197 |
frequency_penalty=None,
|
198 |
presence_penalty=None
|
199 |
)
|
200 |
+
logger.info("LLM API(REST)λ‘λΆν° μλ΅ μ€νΈλ¦Όμ μμ νμ΅λλ€.")
|
201 |
|
202 |
explanation_parts = []
|
203 |
for line in response.iter_lines():
|
|
|
212 |
if chunk.get("choices") and chunk["choices"][0].get("delta") and chunk["choices"][0]["delta"].get("content"):
|
213 |
explanation_parts.append(chunk["choices"][0]["delta"]["content"])
|
214 |
except json.JSONDecodeError:
|
215 |
+
logger.warning(f"μ€νΈλ¦Ό μ²ν¬μμ JSONμ λμ½λ©ν μ μμ΅λλ€: {json_data_str}")
|
216 |
# Depending on API, might need to handle partial JSON or other errors
|
217 |
elif decoded_line.strip() == "[DONE]": # Some APIs might send [DONE] without "data: "
|
218 |
break
|
|
|
221 |
if explanation:
|
222 |
cache.set(cache_key, explanation, expire=None)
|
223 |
|
224 |
+
logger.info("μ€λͺ
μ΄ μ±κ³΅μ μΌλ‘ μμ±λμμ΅λλ€." if explanation else "APIλ‘λΆν° λΉ μ€λͺ
μ λ°μμ΅λλ€.")
|
225 |
+
return jsonify({"explanation": explanation or "APIλ‘λΆν° μ€λͺ
λ΄μ©μ λ°μ§ λͺ»νμ΅λλ€."})
|
226 |
except requests.exceptions.RequestException as e:
|
227 |
+
logger.error(f"LLM API(REST) νΈμΆ μ€ μ€λ₯ λ°μ: {e}", exc_info=True)
|
228 |
+
user_error_message = ("μ€λͺ
μμ±μ μ€ν¨νμ΅λλ€. μλΉμ€κ° μΌμμ μΌλ‘ μ¬μ©ν μ μμΌλ©° "
|
229 |
+
"νμ¬ μμ μ€μΌ μ μμ΅λλ€. μ μ ν λ€μ μλν΄ μ£ΌμΈμ.")
|
230 |
+
return jsonify({"error": user_error_message}), 500
|