ginipick commited on
Commit
5f32e07
ยท
verified ยท
1 Parent(s): 4e15f38

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -57
app.py CHANGED
@@ -8,7 +8,7 @@ from gradio_client import Client
8
  import markdown
9
  import tempfile
10
  import base64
11
- from weasyprint import HTML
12
 
13
  # ๋กœ๊น… ์„ค์ •
14
  logging.basicConfig(
@@ -25,6 +25,9 @@ IMAGE_API_URL = "http://211.233.58.201:7896"
25
  # ์ตœ๋Œ€ ํ† ํฐ ์ˆ˜ ์„ค์ • (Claude-3 Sonnet์˜ ์ตœ๋Œ€ ํ† ํฐ ์ˆ˜)
26
  MAX_TOKENS = 7999
27
 
 
 
 
28
  def get_system_prompt():
29
  return """
30
  ๋‹น์‹ ์€ ์ „๋ฌธ ๋ธ”๋กœ๊ทธ ์ž‘์„ฑ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ธ”๋กœ๊ทธ ๊ธ€ ์ž‘์„ฑ ์š”์ฒญ์— ๋Œ€ํ•ด ๋‹ค์Œ์˜ 8๋‹จ๊ณ„ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ฒ ์ €ํžˆ ๋”ฐ๋ฅด๋˜, ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๋งค๋ ฅ์ ์ธ ๊ธ€์ด ๋˜๋„๋ก ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:
@@ -141,31 +144,113 @@ def convert_md_to_html(md_text, title="Ginigen Blog"):
141
  """
142
  return html_doc
143
 
144
- # ๋งˆํฌ๋‹ค์šด์„ PDF๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜
145
- def convert_md_to_pdf(md_text, title="Ginigen Blog"):
146
- html_content = convert_md_to_html(md_text, title)
147
-
148
- # ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ
149
- with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp:
150
- tmp_path = tmp.name
151
-
152
- # HTML์„ PDF๋กœ ๋ณ€ํ™˜
153
- HTML(string=html_content).write_pdf(tmp_path)
154
-
155
- # ์ƒ์„ฑ๋œ PDF ์ฝ๊ธฐ
156
- with open(tmp_path, 'rb') as f:
157
- pdf_data = f.read()
158
-
159
- # ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
160
- os.unlink(tmp_path)
161
-
162
- return pdf_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
- # ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ์ƒ์„ฑ ํ—ฌํผ ํ•จ์ˆ˜
165
- def create_download_link(bin_data, download_filename, link_text):
166
- b64 = base64.b64encode(bin_data).decode()
167
- href = f'<a href="data:application/octet-stream;base64,{b64}" download="{download_filename}">{link_text}</a>'
168
- return href
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
  def chatbot_interface():
171
  st.title("Ginigen Blog")
@@ -186,6 +271,10 @@ def chatbot_interface():
186
  if "generate_image" not in st.session_state:
187
  st.session_state.generate_image = False
188
 
 
 
 
 
189
  # ์ด๋ฏธ์ง€ API ์ƒํƒœ
190
  if "image_api_status" not in st.session_state:
191
  st.session_state.image_api_status = test_image_api_connection()
@@ -199,6 +288,9 @@ def chatbot_interface():
199
  # ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ† ๊ธ€
200
  st.session_state.generate_image = st.sidebar.toggle("๋ธ”๋กœ๊ทธ ๊ธ€ ์ž‘์„ฑ ํ›„ ์ด๋ฏธ์ง€ ์ž๋™ ์ƒ์„ฑ", value=st.session_state.generate_image)
201
 
 
 
 
202
  # ์ด๋ฏธ์ง€ API ์ƒํƒœ ํ‘œ์‹œ
203
  st.sidebar.text(st.session_state.image_api_status)
204
 
@@ -246,7 +338,7 @@ def chatbot_interface():
246
  if latest_blog:
247
  st.sidebar.subheader("์ตœ๊ทผ ๋ธ”๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ")
248
 
249
- col1, col2, col3 = st.sidebar.columns(3)
250
 
251
  # ๋งˆํฌ๋‹ค์šด์œผ๋กœ ๋‹ค์šด๋กœ๋“œ
252
  with col1:
@@ -266,20 +358,6 @@ def chatbot_interface():
266
  file_name=f"{latest_blog_title}.html",
267
  mime="text/html"
268
  )
269
-
270
- # PDF๋กœ ๋‹ค์šด๋กœ๋“œ
271
- with col3:
272
- try:
273
- pdf_data = convert_md_to_pdf(latest_blog, latest_blog_title)
274
- st.download_button(
275
- label="PDF",
276
- data=pdf_data,
277
- file_name=f"{latest_blog_title}.pdf",
278
- mime="application/pdf"
279
- )
280
- except Exception as e:
281
- st.error(f"PDF ์ƒ์„ฑ ์˜ค๋ฅ˜: {e}")
282
- logging.error(f"PDF ์ƒ์„ฑ ์˜ค๋ฅ˜: {e}")
283
 
284
  # ๋Œ€ํ™” ๊ธฐ๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
285
  uploaded_file = st.sidebar.file_uploader("๋Œ€ํ™” ๊ธฐ๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ", type=['json'])
@@ -320,10 +398,23 @@ def chatbot_interface():
320
  message_placeholder = st.empty()
321
  full_response = ""
322
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  # API ํ˜ธ์ถœ
324
  with client.messages.stream(
325
  max_tokens=MAX_TOKENS,
326
- system=get_system_prompt(),
327
  messages=[{"role": m["role"], "content": m["content"]} for m in st.session_state.messages],
328
  model=st.session_state["ai_model"]
329
  ) as stream:
@@ -373,7 +464,7 @@ def chatbot_interface():
373
 
374
  # ๋ธ”๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ํ‘œ์‹œ (์‘๋‹ต ๋ฐ”๋กœ ์•„๋ž˜์—)
375
  st.subheader("์ด ๋ธ”๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ:")
376
- col1, col2, col3 = st.columns(3)
377
 
378
  with col1:
379
  st.download_button(
@@ -391,19 +482,6 @@ def chatbot_interface():
391
  file_name=f"{prompt[:30]}.html",
392
  mime="text/html"
393
  )
394
-
395
- with col3:
396
- try:
397
- pdf_data = convert_md_to_pdf(full_response, prompt[:30])
398
- st.download_button(
399
- label="PDF๋กœ ์ €์žฅ",
400
- data=pdf_data,
401
- file_name=f"{prompt[:30]}.pdf",
402
- mime="application/pdf"
403
- )
404
- except Exception as e:
405
- st.error(f"PDF ์ƒ์„ฑ ์˜ค๋ฅ˜: {e}")
406
- logging.error(f"PDF ์ƒ์„ฑ ์˜ค๋ฅ˜: {e}")
407
 
408
  # ์ž๋™ ์ €์žฅ ๊ธฐ๋Šฅ
409
  if st.session_state.auto_save:
@@ -414,7 +492,11 @@ def chatbot_interface():
414
  save_msg = {"role": msg["role"], "content": msg["content"]}
415
  save_messages.append(save_msg)
416
 
417
- with open('chat_history_auto_save.json', 'w', encoding='utf-8') as f:
 
 
 
 
418
  json.dump(save_messages, f, ensure_ascii=False, indent=4)
419
  except Exception as e:
420
  st.sidebar.error(f"์ž๋™ ์ €์žฅ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
@@ -446,7 +528,6 @@ if __name__ == "__main__":
446
  f.write("gradio-client>=1.8.0\n")
447
  f.write("requests>=2.32.3\n")
448
  f.write("markdown>=3.5.1\n")
449
- f.write("weasyprint>=60.2\n")
450
  f.write("pillow>=10.1.0\n")
451
 
452
  main()
 
8
  import markdown
9
  import tempfile
10
  import base64
11
+ from datetime import datetime
12
 
13
  # ๋กœ๊น… ์„ค์ •
14
  logging.basicConfig(
 
25
  # ์ตœ๋Œ€ ํ† ํฐ ์ˆ˜ ์„ค์ • (Claude-3 Sonnet์˜ ์ตœ๋Œ€ ํ† ํฐ ์ˆ˜)
26
  MAX_TOKENS = 7999
27
 
28
+ # SerpHouse API Key ์„ค์ •
29
+ SERPHOUSE_API_KEY = os.environ.get("SERPHOUSE_API_KEY", "")
30
+
31
  def get_system_prompt():
32
  return """
33
  ๋‹น์‹ ์€ ์ „๋ฌธ ๋ธ”๋กœ๊ทธ ์ž‘์„ฑ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ธ”๋กœ๊ทธ ๊ธ€ ์ž‘์„ฑ ์š”์ฒญ์— ๋Œ€ํ•ด ๋‹ค์Œ์˜ 8๋‹จ๊ณ„ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ฒ ์ €ํžˆ ๋”ฐ๋ฅด๋˜, ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๋งค๋ ฅ์ ์ธ ๊ธ€์ด ๋˜๋„๋ก ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:
 
144
  """
145
  return html_doc
146
 
147
+ # ์›น ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ ์ถ”์ถœ ํ•จ์ˆ˜
148
+ def extract_keywords(text: str, top_k: int = 5) -> str:
149
+ """
150
+ 1) ํ•œ๊ธ€(๊ฐ€-ํžฃ), ์˜์–ด(a-zA-Z), ์ˆซ์ž(0-9), ๊ณต๋ฐฑ๋งŒ ๋‚จ๊น€
151
+ 2) ๊ณต๋ฐฑ ๊ธฐ์ค€ ํ† ํฐ ๋ถ„๋ฆฌ
152
+ 3) ์ตœ๋Œ€ top_k๊ฐœ๋งŒ
153
+ """
154
+ import re
155
+ text = re.sub(r"[^a-zA-Z0-9๊ฐ€-ํžฃ\s]", "", text)
156
+ tokens = text.split()
157
+ key_tokens = tokens[:top_k]
158
+ return " ".join(key_tokens)
159
+
160
+ # ์›น ๊ฒ€์ƒ‰ ํ•จ์ˆ˜
161
+ def do_web_search(query: str) -> str:
162
+ """
163
+ ์ƒ์œ„ 20๊ฐœ 'organic' ๊ฒฐ๊ณผ item ์ „์ฒด(์ œ๋ชฉ, link, snippet ๋“ฑ)๋ฅผ
164
+ JSON ๋ฌธ์ž์—ด ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜
165
+ """
166
+ try:
167
+ url = "https://api.serphouse.com/serp/live"
168
+
169
+ # ๊ธฐ๋ณธ GET ๋ฐฉ์‹์œผ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ„์†Œํ™”ํ•˜๊ณ  ๊ฒฐ๊ณผ ์ˆ˜๋ฅผ 20๊ฐœ๋กœ ์ œํ•œ
170
+ params = {
171
+ "q": query,
172
+ "domain": "google.com",
173
+ "serp_type": "web", # ๊ธฐ๋ณธ ์›น ๊ฒ€์ƒ‰
174
+ "device": "desktop",
175
+ "lang": "en",
176
+ "num": "20" # ์ตœ๋Œ€ 20๊ฐœ ๊ฒฐ๊ณผ๋งŒ ์š”์ฒญ
177
+ }
178
+
179
+ headers = {
180
+ "Authorization": f"Bearer {SERPHOUSE_API_KEY}"
181
+ }
182
+
183
+ logging.info(f"SerpHouse API ํ˜ธ์ถœ ์ค‘... ๊ฒ€์ƒ‰์–ด: {query}")
184
+ logging.info(f"์š”์ฒญ URL: {url} - ํŒŒ๋ผ๋ฏธํ„ฐ: {params}")
185
+
186
+ # GET ์š”์ฒญ ์ˆ˜ํ–‰
187
+ response = requests.get(url, headers=headers, params=params, timeout=60)
188
+ response.raise_for_status()
189
+
190
+ logging.info(f"SerpHouse API ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")
191
+ data = response.json()
192
+
193
+ # ๋‹ค์–‘ํ•œ ์‘๋‹ต ๊ตฌ์กฐ ์ฒ˜๋ฆฌ
194
+ results = data.get("results", {})
195
+ organic = None
196
+
197
+ # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 1
198
+ if isinstance(results, dict) and "organic" in results:
199
+ organic = results["organic"]
200
+
201
+ # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 2 (์ค‘์ฒฉ๋œ results)
202
+ elif isinstance(results, dict) and "results" in results:
203
+ if isinstance(results["results"], dict) and "organic" in results["results"]:
204
+ organic = results["results"]["organic"]
205
+
206
+ # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 3 (์ตœ์ƒ์œ„ organic)
207
+ elif "organic" in data:
208
+ organic = data["organic"]
209
+
210
+ if not organic:
211
+ logging.warning("์‘๋‹ต์—์„œ organic ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
212
+ logging.debug(f"์‘๋‹ต ๊ตฌ์กฐ: {list(data.keys())}")
213
+ if isinstance(results, dict):
214
+ logging.debug(f"results ๊ตฌ์กฐ: {list(results.keys())}")
215
+ return "No web search results found or unexpected API response structure."
216
 
217
+ # ๊ฒฐ๊ณผ ์ˆ˜ ์ œํ•œ ๋ฐ ์ปจํ…์ŠคํŠธ ๊ธธ์ด ์ตœ์ ํ™”
218
+ max_results = min(20, len(organic))
219
+ limited_organic = organic[:max_results]
220
+
221
+ # ๊ฒฐ๊ณผ ํ˜•์‹ ๊ฐœ์„  - ๋งˆํฌ๋‹ค์šด ํ˜•์‹์œผ๋กœ ์ถœ๋ ฅํ•˜์—ฌ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ
222
+ summary_lines = []
223
+ for idx, item in enumerate(limited_organic, start=1):
224
+ title = item.get("title", "No title")
225
+ link = item.get("link", "#")
226
+ snippet = item.get("snippet", "No description")
227
+ displayed_link = item.get("displayed_link", link)
228
+
229
+ # ๋งˆํฌ๋‹ค์šด ํ˜•์‹ (๋งํฌ ํด๋ฆญ ๊ฐ€๋Šฅ)
230
+ summary_lines.append(
231
+ f"### Result {idx}: {title}\n\n"
232
+ f"{snippet}\n\n"
233
+ f"**์ถœ์ฒ˜**: [{displayed_link}]({link})\n\n"
234
+ f"---\n"
235
+ )
236
+
237
+ # ๋ชจ๋ธ์—๊ฒŒ ๋ช…ํ™•ํ•œ ์ง€์นจ ์ถ”๊ฐ€
238
+ instructions = """
239
+ # ์›น ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ
240
+ ์•„๋ž˜๋Š” ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•  ๋•Œ ์ด ์ •๋ณด๋ฅผ ํ™œ์šฉํ•˜์„ธ์š”:
241
+ 1. ๊ฐ ๊ฒฐ๊ณผ์˜ ์ œ๋ชฉ, ๋‚ด์šฉ, ์ถœ์ฒ˜ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”
242
+ 2. ๋‹ต๋ณ€์— ๊ด€๋ จ ์ •๋ณด์˜ ์ถœ์ฒ˜๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ธ์šฉํ•˜์„ธ์š” (์˜ˆ: "X ์ถœ์ฒ˜์— ๋”ฐ๋ฅด๋ฉด...")
243
+ 3. ์‘๋‹ต์— ์‹ค์ œ ์ถœ์ฒ˜ ๋งํฌ๋ฅผ ํฌํ•จํ•˜์„ธ์š”
244
+ 4. ์—ฌ๋Ÿฌ ์ถœ์ฒ˜์˜ ์ •๋ณด๋ฅผ ์ข…ํ•ฉํ•˜์—ฌ ๋‹ต๋ณ€ํ•˜์„ธ์š”
245
+ """
246
+
247
+ search_results = instructions + "\n".join(summary_lines)
248
+ logging.info(f"๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ {len(limited_organic)}๊ฐœ ์ฒ˜๋ฆฌ ์™„๋ฃŒ")
249
+ return search_results
250
+
251
+ except Exception as e:
252
+ logging.error(f"Web search failed: {e}")
253
+ return f"Web search failed: {str(e)}"
254
 
255
  def chatbot_interface():
256
  st.title("Ginigen Blog")
 
271
  if "generate_image" not in st.session_state:
272
  st.session_state.generate_image = False
273
 
274
+ # ์›น ๊ฒ€์ƒ‰ ํ† ๊ธ€
275
+ if "use_web_search" not in st.session_state:
276
+ st.session_state.use_web_search = False
277
+
278
  # ์ด๋ฏธ์ง€ API ์ƒํƒœ
279
  if "image_api_status" not in st.session_state:
280
  st.session_state.image_api_status = test_image_api_connection()
 
288
  # ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ† ๊ธ€
289
  st.session_state.generate_image = st.sidebar.toggle("๋ธ”๋กœ๊ทธ ๊ธ€ ์ž‘์„ฑ ํ›„ ์ด๋ฏธ์ง€ ์ž๋™ ์ƒ์„ฑ", value=st.session_state.generate_image)
290
 
291
+ # ์›น ๊ฒ€์ƒ‰ ํ† ๊ธ€
292
+ st.session_state.use_web_search = st.sidebar.toggle("์ฃผ์ œ ์›น ๊ฒ€์ƒ‰ ๋ฐ ๋ถ„์„", value=st.session_state.use_web_search)
293
+
294
  # ์ด๋ฏธ์ง€ API ์ƒํƒœ ํ‘œ์‹œ
295
  st.sidebar.text(st.session_state.image_api_status)
296
 
 
338
  if latest_blog:
339
  st.sidebar.subheader("์ตœ๊ทผ ๋ธ”๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ")
340
 
341
+ col1, col2 = st.sidebar.columns(2)
342
 
343
  # ๋งˆํฌ๋‹ค์šด์œผ๋กœ ๋‹ค์šด๋กœ๋“œ
344
  with col1:
 
358
  file_name=f"{latest_blog_title}.html",
359
  mime="text/html"
360
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
 
362
  # ๋Œ€ํ™” ๊ธฐ๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
363
  uploaded_file = st.sidebar.file_uploader("๋Œ€ํ™” ๊ธฐ๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ", type=['json'])
 
398
  message_placeholder = st.empty()
399
  full_response = ""
400
 
401
+ # ์›น ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰ (์›น ๊ฒ€์ƒ‰ ์˜ต์…˜์ด ์ผœ์ ธ ์žˆ์„ ๊ฒฝ์šฐ)
402
+ system_prompt = get_system_prompt()
403
+ if st.session_state.use_web_search:
404
+ with st.spinner("์›น์—์„œ ๊ด€๋ จ ์ •๋ณด๋ฅผ ๊ฒ€์ƒ‰ ์ค‘..."):
405
+ search_query = extract_keywords(prompt, top_k=5)
406
+ search_results = do_web_search(search_query)
407
+ if "search failed" not in search_results.lower():
408
+ # ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ถ”๊ฐ€
409
+ system_prompt += f"\n\n๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n{search_results}\n"
410
+ st.success(f"๊ฒ€์ƒ‰ ์™„๋ฃŒ: '{search_query}'์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ–ˆ์Šต๋‹ˆ๋‹ค.")
411
+ else:
412
+ st.error("์›น ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.")
413
+
414
  # API ํ˜ธ์ถœ
415
  with client.messages.stream(
416
  max_tokens=MAX_TOKENS,
417
+ system=system_prompt,
418
  messages=[{"role": m["role"], "content": m["content"]} for m in st.session_state.messages],
419
  model=st.session_state["ai_model"]
420
  ) as stream:
 
464
 
465
  # ๋ธ”๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ํ‘œ์‹œ (์‘๋‹ต ๋ฐ”๋กœ ์•„๋ž˜์—)
466
  st.subheader("์ด ๋ธ”๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ:")
467
+ col1, col2 = st.columns(2)
468
 
469
  with col1:
470
  st.download_button(
 
482
  file_name=f"{prompt[:30]}.html",
483
  mime="text/html"
484
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
485
 
486
  # ์ž๋™ ์ €์žฅ ๊ธฐ๋Šฅ
487
  if st.session_state.auto_save:
 
492
  save_msg = {"role": msg["role"], "content": msg["content"]}
493
  save_messages.append(save_msg)
494
 
495
+ # ํ˜„์žฌ ์‹œ๊ฐ„์„ ํฌํ•จํ•œ ํŒŒ์ผ๋ช… ์ƒ์„ฑ
496
+ current_time = datetime.now().strftime("%Y%m%d_%H%M%S")
497
+ filename = f'chat_history_auto_save_{current_time}.json'
498
+
499
+ with open(filename, 'w', encoding='utf-8') as f:
500
  json.dump(save_messages, f, ensure_ascii=False, indent=4)
501
  except Exception as e:
502
  st.sidebar.error(f"์ž๋™ ์ €์žฅ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
 
528
  f.write("gradio-client>=1.8.0\n")
529
  f.write("requests>=2.32.3\n")
530
  f.write("markdown>=3.5.1\n")
 
531
  f.write("pillow>=10.1.0\n")
532
 
533
  main()