seawolf2357 commited on
Commit
ff48136
·
verified ·
1 Parent(s): 78bdfe0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +218 -32
app.py CHANGED
@@ -135,6 +135,30 @@ footer, .footer, .gradio-container footer, .built-with, [class*="footer"], .grad
135
  box-shadow: 2px 2px 0 #1F2937;
136
  }
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  .gr-panel, .gr-box, .gr-form, .block, .gr-group {
139
  background: #FFF !important;
140
  border: 3px solid #1F2937 !important;
@@ -225,6 +249,58 @@ textarea:focus, input[type="text"]:focus {
225
  font-size: 1.5rem;
226
  }
227
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  label, .gr-input-label, .gr-block-label {
229
  color: #1F2937 !important;
230
  font-family: 'Comic Neue', cursive !important;
@@ -799,14 +875,14 @@ def convert_hwp_to_markdown(input_path: str) -> tuple:
799
  # ============== LLM API ==============
800
  def call_groq_api_stream(messages: List[Dict], api_key: str) -> Generator[str, None, None]:
801
  if not api_key:
802
- yield "❌ Groq API 키가 설정되지 않았습니다."
803
  return
804
  try:
805
  response = requests.post(
806
  "https://api.groq.com/openai/v1/chat/completions",
807
  headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
808
  json={
809
- "model": "meta-llama/llama-4-scout-17b-16e-instruct",
810
  "messages": messages,
811
  "temperature": 0.7,
812
  "max_tokens": 8192,
@@ -833,7 +909,7 @@ def call_groq_api_stream(messages: List[Dict], api_key: str) -> Generator[str, N
833
 
834
  def call_fireworks_api_stream(messages: List[Dict], image_base64: str, mime_type: str, api_key: str) -> Generator[str, None, None]:
835
  if not api_key:
836
- yield "❌ Fireworks API 키가 설정되지 않았습니다."
837
  return
838
  try:
839
  formatted_messages = [{"role": m["role"], "content": m["content"]} for m in messages[:-1]]
@@ -849,7 +925,7 @@ def call_fireworks_api_stream(messages: List[Dict], image_base64: str, mime_type
849
  headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
850
  json={
851
  "model": "accounts/fireworks/models/qwen3-vl-235b-a22b-thinking",
852
- "max_tokens": 4096,
853
  "temperature": 0.6,
854
  "messages": formatted_messages,
855
  "stream": True
@@ -885,25 +961,28 @@ def process_file(file_path: str) -> tuple:
885
  if is_hwp_file(file_path) or is_hwpx_file(file_path):
886
  text, error = extract_text_from_hwp_or_hwpx(file_path)
887
  if text and len(text.strip()) > 20:
888
- return "text", f"[📄 한글 문서: {filename}]\n\n{text}", None
 
 
889
  return "error", f"한글 문서 추출 실패: {error}", None
890
 
891
  if is_pdf_file(file_path):
892
  text = extract_text_from_pdf(file_path)
893
  if text:
894
- return "text", f"[📑 PDF 문서: {filename}]\n\n{text}", None
 
895
  return "error", "PDF 추출 실패", None
896
 
897
  if is_text_file(file_path):
898
  text = extract_text_from_txt(file_path)
899
  if text:
900
- return "text", f"[📝 텍스트 파일: {filename}]\n\n{text}", None
901
  return "error", "텍스트 읽기 실패", None
902
 
903
  return "unsupported", f"지원하지 않는 형식: {filename}", None
904
 
905
  def chat_response(message: str, history: List[Dict], file: Optional[str],
906
- session_id: str, groq_key: str, fireworks_key: str) -> Generator[tuple, None, None]:
907
  if history is None:
908
  history = []
909
  if not message.strip() and not file:
@@ -914,10 +993,12 @@ def chat_response(message: str, history: List[Dict], file: Optional[str],
914
 
915
  file_type, file_content, file_mime = None, None, None
916
  file_info = None
 
917
 
918
  if file:
 
919
  file_type, file_content, file_mime = process_file(file)
920
- file_info = json.dumps({"type": file_type, "filename": os.path.basename(file)})
921
 
922
  if file_type == "error":
923
  history = history + [
@@ -934,41 +1015,104 @@ def chat_response(message: str, history: List[Dict], file: Optional[str],
934
  yield history, session_id
935
  return
936
 
 
937
  user_msg = message
938
  if file:
939
- filename = os.path.basename(file)
940
  user_msg = f"📎 {filename}\n\n{message}" if message else f"📎 {filename}"
941
 
942
  history = history + [{"role": "user", "content": user_msg}, {"role": "assistant", "content": ""}]
943
  yield history, session_id
944
 
 
945
  db_messages = get_session_messages(session_id, limit=10)
946
- api_messages = [{
947
- "role": "system",
948
- "content": "당신은 도움이 되는 AI 어시스턴트입니다. 한국어로 자연스럽게 대화하며, 파일이 첨부되면 내용을 상세히 분석하여 답변합니다. 문서의 핵심 내용을 파악하고, 사용자의 질문에 정확하게 답변하세요."
949
- }]
950
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
  for m in db_messages:
952
  api_messages.append({"role": m["role"], "content": m["content"]})
953
 
954
- current_content = message or ""
955
  if file_type == "text" and file_content:
956
- current_content = f"{file_content}\n\n사용자 질문: {message}" if message else f"{file_content}\n\n위 문서 내용을 요약해주세요."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
957
 
958
  api_messages.append({"role": "user", "content": current_content})
959
 
 
 
 
 
 
 
 
 
 
960
  full_response = ""
961
  if file_type == "image":
962
- for chunk in call_fireworks_api_stream(api_messages, file_content, file_mime, fireworks_key):
963
  full_response += chunk
964
  history[-1] = {"role": "assistant", "content": full_response}
965
  yield history, session_id
966
  else:
967
- for chunk in call_groq_api_stream(api_messages, groq_key):
968
  full_response += chunk
969
  history[-1] = {"role": "assistant", "content": full_response}
970
  yield history, session_id
971
 
 
972
  save_message(session_id, "user", current_content, file_info)
973
  save_message(session_id, "assistant", full_response)
974
 
@@ -1056,7 +1200,7 @@ def convert_hwp(file, output_format, progress=gr.Progress()):
1056
  f.write(text)
1057
  ext = ".txt"
1058
 
1059
- elif output_format == "Markdown":
1060
  text, error = convert_hwp_to_markdown(input_path)
1061
  if text:
1062
  output_path = os.path.join(tmp_dir, "output.md")
@@ -1146,6 +1290,14 @@ with gr.Blocks(title="HWP AI 어시스턴트", css=COMIC_CSS, delete_cache=(60,
1146
  </div>
1147
  """)
1148
 
 
 
 
 
 
 
 
 
1149
  session_state = gr.State("")
1150
 
1151
  with gr.Tabs():
@@ -1167,12 +1319,6 @@ with gr.Blocks(title="HWP AI 어시스턴트", css=COMIC_CSS, delete_cache=(60,
1167
 
1168
  with gr.Row():
1169
  with gr.Column(scale=1):
1170
- gr.HTML('<div class="info-box">⚙️ <b>설정</b></div>')
1171
-
1172
- with gr.Accordion("🔑 API 키 설정", open=True):
1173
- groq_key = gr.Textbox(label="Groq API Key", type="password", value=GROQ_API_KEY, placeholder="gsk_...")
1174
- fireworks_key = gr.Textbox(label="Fireworks API Key", type="password", value=FIREWORKS_API_KEY, placeholder="fw_...")
1175
-
1176
  gr.HTML("""
1177
  <div class="info-box">
1178
  📁 <b>지원 파일 형식</b><br><br>
@@ -1220,6 +1366,45 @@ with gr.Blocks(title="HWP AI 어시스턴트", css=COMIC_CSS, delete_cache=(60,
1220
  </div>
1221
  """)
1222
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1223
  with gr.Row():
1224
  with gr.Column():
1225
  gr.HTML('<div class="info-box">📤 <b>파일 업로드</b></div>')
@@ -1229,8 +1414,8 @@ with gr.Blocks(title="HWP AI 어시스턴트", css=COMIC_CSS, delete_cache=(60,
1229
  elem_classes=["upload-box"]
1230
  )
1231
  format_select = gr.Radio(
1232
- ["HTML", "ODT (OpenDocument)", "TXT (텍스트)", "Markdown", "XML"],
1233
- value="TXT (텍스트)",
1234
  label="📋 변환 형식"
1235
  )
1236
  convert_btn = gr.Button("🔄 변환하기", variant="primary", size="lg")
@@ -1245,7 +1430,7 @@ with gr.Blocks(title="HWP AI 어시스턴트", css=COMIC_CSS, delete_cache=(60,
1245
 
1246
  gr.HTML("""
1247
  <div class="info-box">
1248
- ℹ️ <b>안내</b>: HWPX 파일은 TXT, Markdown, XML 변환만 지원됩니다.
1249
  </div>
1250
  """)
1251
 
@@ -1255,20 +1440,21 @@ with gr.Blocks(title="HWP AI 어시스턴트", css=COMIC_CSS, delete_cache=(60,
1255
  <p style="font-family:'Bangers',cursive;font-size:1.8rem;letter-spacing:2px">📄 HWP AI 어시스턴트 🤖</p>
1256
  <p>AI가 HWP 파일을 읽고, 보고, 말하며, 생각하고 기억합니다!</p>
1257
  <p>📖 READ • 👁️ SEE • 💬 SPEAK • 🧠 THINK • 💾 MEMORY</p>
 
1258
  <p style="margin-top:10px"><a href="https://www.humangen.ai" target="_blank" style="color:#FACC15;text-decoration:none;font-weight:bold;">🏠 www.humangen.ai</a></p>
1259
  </div>
1260
  """)
1261
 
1262
  # ============== 이벤트 핸들러 ==============
1263
- def on_submit(msg, hist, f, sid, gk, fk):
1264
  if hist is None:
1265
  hist = []
1266
- for r in chat_response(msg, hist, f, sid, gk, fk):
1267
  yield r[0], r[1], "", None
1268
 
1269
- submit_btn.click(on_submit, [msg_input, chatbot, file_upload, session_state, groq_key, fireworks_key],
1270
  [chatbot, session_state, msg_input, file_upload])
1271
- msg_input.submit(on_submit, [msg_input, chatbot, file_upload, session_state, groq_key, fireworks_key],
1272
  [chatbot, session_state, msg_input, file_upload])
1273
 
1274
  new_btn.click(lambda: ([], create_session(), None, ""), outputs=[chatbot, session_state, file_upload, msg_input])
 
135
  box-shadow: 2px 2px 0 #1F2937;
136
  }
137
 
138
+ /* 무료 서비스 안내 박스 */
139
+ .free-service-notice {
140
+ text-align: center;
141
+ padding: 10px 15px;
142
+ background: linear-gradient(135deg, #FEE2E2 0%, #FECACA 100%);
143
+ border: 3px solid #1F2937;
144
+ border-radius: 8px;
145
+ margin: 10px 0;
146
+ box-shadow: 4px 4px 0 #1F2937;
147
+ font-family: 'Comic Neue', cursive;
148
+ font-weight: 700;
149
+ color: #991B1B;
150
+ }
151
+
152
+ .free-service-notice a {
153
+ color: #1D4ED8;
154
+ text-decoration: none;
155
+ font-weight: 700;
156
+ }
157
+
158
+ .free-service-notice a:hover {
159
+ text-decoration: underline;
160
+ }
161
+
162
  .gr-panel, .gr-box, .gr-form, .block, .gr-group {
163
  background: #FFF !important;
164
  border: 3px solid #1F2937 !important;
 
249
  font-size: 1.5rem;
250
  }
251
 
252
+ /* Markdown 강조 박스 */
253
+ .markdown-highlight-box {
254
+ background: linear-gradient(135deg, #EC4899 0%, #F472B6 100%) !important;
255
+ border: 4px solid #1F2937 !important;
256
+ border-radius: 12px !important;
257
+ padding: 20px !important;
258
+ margin: 15px 0 !important;
259
+ box-shadow: 6px 6px 0 #1F2937 !important;
260
+ animation: pulse-glow 2s ease-in-out infinite;
261
+ }
262
+
263
+ @keyframes pulse-glow {
264
+ 0%, 100% { box-shadow: 6px 6px 0 #1F2937; }
265
+ 50% { box-shadow: 8px 8px 0 #1F2937, 0 0 20px rgba(236, 72, 153, 0.5); }
266
+ }
267
+
268
+ .markdown-title {
269
+ font-family: 'Bangers', cursive !important;
270
+ font-size: 2rem !important;
271
+ color: #FFF !important;
272
+ text-shadow: 3px 3px 0 #1F2937 !important;
273
+ letter-spacing: 2px !important;
274
+ margin-bottom: 15px !important;
275
+ text-align: center !important;
276
+ }
277
+
278
+ .markdown-benefits {
279
+ display: grid;
280
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
281
+ gap: 12px;
282
+ margin-top: 10px;
283
+ }
284
+
285
+ .markdown-benefit-item {
286
+ background: rgba(255,255,255,0.95) !important;
287
+ border: 3px solid #1F2937 !important;
288
+ border-radius: 8px !important;
289
+ padding: 12px !important;
290
+ box-shadow: 3px 3px 0 #1F2937 !important;
291
+ font-family: 'Comic Neue', cursive !important;
292
+ font-weight: 700 !important;
293
+ font-size: 0.95rem !important;
294
+ color: #1F2937 !important;
295
+ text-align: center !important;
296
+ }
297
+
298
+ .markdown-benefit-icon {
299
+ font-size: 1.8rem !important;
300
+ display: block !important;
301
+ margin-bottom: 5px !important;
302
+ }
303
+
304
  label, .gr-input-label, .gr-block-label {
305
  color: #1F2937 !important;
306
  font-family: 'Comic Neue', cursive !important;
 
875
  # ============== LLM API ==============
876
  def call_groq_api_stream(messages: List[Dict], api_key: str) -> Generator[str, None, None]:
877
  if not api_key:
878
+ yield "❌ Groq API 키가 설정되지 않았습니다. 환경변수 GROQ_API_KEY를 설정해주세요."
879
  return
880
  try:
881
  response = requests.post(
882
  "https://api.groq.com/openai/v1/chat/completions",
883
  headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
884
  json={
885
+ "model": "mopenai/gpt-oss-120b",
886
  "messages": messages,
887
  "temperature": 0.7,
888
  "max_tokens": 8192,
 
909
 
910
  def call_fireworks_api_stream(messages: List[Dict], image_base64: str, mime_type: str, api_key: str) -> Generator[str, None, None]:
911
  if not api_key:
912
+ yield "❌ Fireworks API 키가 설정되지 않았습니다. 환경변수 FIREWORKS_API_KEY를 설정해주세요."
913
  return
914
  try:
915
  formatted_messages = [{"role": m["role"], "content": m["content"]} for m in messages[:-1]]
 
925
  headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
926
  json={
927
  "model": "accounts/fireworks/models/qwen3-vl-235b-a22b-thinking",
928
+ "max_tokens": 8000,
929
  "temperature": 0.6,
930
  "messages": formatted_messages,
931
  "stream": True
 
961
  if is_hwp_file(file_path) or is_hwpx_file(file_path):
962
  text, error = extract_text_from_hwp_or_hwpx(file_path)
963
  if text and len(text.strip()) > 20:
964
+ print(f"📄 [문서 내용 추출 완료] {len(text)} 글자")
965
+ print(f"📄 [문서 미리보기] {text[:500]}...")
966
+ return "text", text, None
967
  return "error", f"한글 문서 추출 실패: {error}", None
968
 
969
  if is_pdf_file(file_path):
970
  text = extract_text_from_pdf(file_path)
971
  if text:
972
+ print(f"📄 [PDF 내용 추출 완료] {len(text)} 글자")
973
+ return "text", text, None
974
  return "error", "PDF 추출 실패", None
975
 
976
  if is_text_file(file_path):
977
  text = extract_text_from_txt(file_path)
978
  if text:
979
+ return "text", text, None
980
  return "error", "텍스트 읽기 실패", None
981
 
982
  return "unsupported", f"지원하지 않는 형식: {filename}", None
983
 
984
  def chat_response(message: str, history: List[Dict], file: Optional[str],
985
+ session_id: str) -> Generator[tuple, None, None]:
986
  if history is None:
987
  history = []
988
  if not message.strip() and not file:
 
993
 
994
  file_type, file_content, file_mime = None, None, None
995
  file_info = None
996
+ filename = None
997
 
998
  if file:
999
+ filename = os.path.basename(file)
1000
  file_type, file_content, file_mime = process_file(file)
1001
+ file_info = json.dumps({"type": file_type, "filename": filename})
1002
 
1003
  if file_type == "error":
1004
  history = history + [
 
1015
  yield history, session_id
1016
  return
1017
 
1018
+ # 사용자 메시지 표시
1019
  user_msg = message
1020
  if file:
 
1021
  user_msg = f"📎 {filename}\n\n{message}" if message else f"📎 {filename}"
1022
 
1023
  history = history + [{"role": "user", "content": user_msg}, {"role": "assistant", "content": ""}]
1024
  yield history, session_id
1025
 
1026
+ # 이전 대화 불러오기
1027
  db_messages = get_session_messages(session_id, limit=10)
 
 
 
 
1028
 
1029
+ # 시스템 프롬프트 - 문서 분석 강화
1030
+ system_prompt = """당신은 문서 분석 전문 AI 어시스턴트입니다.
1031
+
1032
+ ## 핵심 역할
1033
+ - 사용자가 업로드한 문서의 내용을 **정확하게 분석**하고 **구체적으로 답변**합니다.
1034
+ - 문서에 있는 **실제 내용**을 기반으로만 답변합니다.
1035
+ - 문서에 없는 내용은 추측하지 않습니다.
1036
+
1037
+ ## 문서 분석 방법
1038
+ 1. **문서가 제공되면**: 문서 전체 내용을 꼼꼼히 읽고 핵심 정보를 파악합니다.
1039
+ 2. **요약 요청 시**: 문서의 주제, 목적, 핵심 내용, 주요 항목을 구조화하여 요약합니다.
1040
+ 3. **질문 응답 시**: 문서에서 관련 내용을 찾아 **직접 인용하거나 구체적으로 설명**합니다.
1041
+
1042
+ ## 답변 형식
1043
+ - 한국어로 자연스럽고 명확하게 답변합니다.
1044
+ - 문서 내용을 인용할 때는 구체적으로 언급합니다.
1045
+ - 긴 문서는 섹션별로 나누어 정리합니다.
1046
+
1047
+ ## 주의사항
1048
+ - 문서에 **실제로 있는 내용만** 답변에 포함합니다.
1049
+ - 불확실한 내용은 "문서에서 확인되지 않습니다"라고 명시합니다."""
1050
+
1051
+ api_messages = [{"role": "system", "content": system_prompt}]
1052
+
1053
+ # 이전 대화 추가
1054
  for m in db_messages:
1055
  api_messages.append({"role": m["role"], "content": m["content"]})
1056
 
1057
+ # 현재 메시지 구성 - 문서 내용을 명확하게 구분
1058
  if file_type == "text" and file_content:
1059
+ if message:
1060
+ current_content = f"""## 📄 업로드된 문서 내용 ({filename})
1061
+
1062
+ 다음은 사용자가 업로드한 문서의 전체 내용입니다:
1063
+
1064
+ ---
1065
+ {file_content}
1066
+ ---
1067
+
1068
+ ## 💬 사용자 질문
1069
+ {message}
1070
+
1071
+ 위 문서 내용을 바탕으로 사용자의 질문에 **구체적이고 정확하게** 답변해주세요."""
1072
+ else:
1073
+ current_content = f"""## 📄 업로드된 문서 내용 ({filename})
1074
+
1075
+ 다음은 사용자가 업로드한 문서의 전체 내용입니다:
1076
+
1077
+ ---
1078
+ {file_content}
1079
+ ---
1080
+
1081
+ ## 📋 요청사항
1082
+ 위 문서의 내용을 다음 형식으로 **상세하게 요약**해주세요:
1083
+
1084
+ 1. **문서 제목/주제**: 문서가 다루는 주요 주제
1085
+ 2. **문서 목적**: 이 문서의 작성 목적
1086
+ 3. **핵심 내용**: 가장 중요한 내용 3-5���지
1087
+ 4. **세부 항목**: 문서에 포함된 주요 섹션이나 항목
1088
+ 5. **결론/요약**: 문서의 핵심 메시지"""
1089
+ else:
1090
+ current_content = message or ""
1091
 
1092
  api_messages.append({"role": "user", "content": current_content})
1093
 
1094
+ # 디버그 로그
1095
+ print(f"\n🤖 [API 요청]")
1096
+ print(f" - 메시지 수: {len(api_messages)}")
1097
+ print(f" - 파일 타입: {file_type}")
1098
+ print(f" - 문서 길이: {len(file_content) if file_content else 0} 글자")
1099
+ if file_content:
1100
+ print(f" - 문서 미리보기: {file_content[:200]}...")
1101
+
1102
+ # 응답 생성
1103
  full_response = ""
1104
  if file_type == "image":
1105
+ for chunk in call_fireworks_api_stream(api_messages, file_content, file_mime, FIREWORKS_API_KEY):
1106
  full_response += chunk
1107
  history[-1] = {"role": "assistant", "content": full_response}
1108
  yield history, session_id
1109
  else:
1110
+ for chunk in call_groq_api_stream(api_messages, GROQ_API_KEY):
1111
  full_response += chunk
1112
  history[-1] = {"role": "assistant", "content": full_response}
1113
  yield history, session_id
1114
 
1115
+ # DB 저장
1116
  save_message(session_id, "user", current_content, file_info)
1117
  save_message(session_id, "assistant", full_response)
1118
 
 
1200
  f.write(text)
1201
  ext = ".txt"
1202
 
1203
+ elif output_format == "⭐ MARKDOWN (추천)":
1204
  text, error = convert_hwp_to_markdown(input_path)
1205
  if text:
1206
  output_path = os.path.join(tmp_dir, "output.md")
 
1290
  </div>
1291
  """)
1292
 
1293
+ # 무료 서비스 안내
1294
+ gr.HTML("""
1295
+ <div class="free-service-notice">
1296
+ 🆓 본 서비스는 <b>무료 버전</b>으로 일부 기능에 제약이 있습니다.<br>
1297
+ 📧 문의: <a href="mailto:arxivgpt@gmail.com">arxivgpt@gmail.com</a>
1298
+ </div>
1299
+ """)
1300
+
1301
  session_state = gr.State("")
1302
 
1303
  with gr.Tabs():
 
1319
 
1320
  with gr.Row():
1321
  with gr.Column(scale=1):
 
 
 
 
 
 
1322
  gr.HTML("""
1323
  <div class="info-box">
1324
  📁 <b>지원 파일 형식</b><br><br>
 
1366
  </div>
1367
  """)
1368
 
1369
+ # Markdown 강조 박스
1370
+ gr.HTML("""
1371
+ <div class="markdown-highlight-box">
1372
+ <div class="markdown-title">⭐ MARKDOWN 변환 추천! ⭐</div>
1373
+ <div class="markdown-benefits">
1374
+ <div class="markdown-benefit-item">
1375
+ <span class="markdown-benefit-icon">🤖</span>
1376
+ <b>AI/LLM 최적화</b><br>
1377
+ ChatGPT, Claude 등 AI에 바로 입력 가능
1378
+ </div>
1379
+ <div class="markdown-benefit-item">
1380
+ <span class="markdown-benefit-icon">📝</span>
1381
+ <b>범용 포맷</b><br>
1382
+ GitHub, Notion, 블로그 등 어디서나 사용
1383
+ </div>
1384
+ <div class="markdown-benefit-item">
1385
+ <span class="markdown-benefit-icon">🔍</span>
1386
+ <b>구조 유지</b><br>
1387
+ 제목, 목록, 표 등 문서 구조 보존
1388
+ </div>
1389
+ <div class="markdown-benefit-item">
1390
+ <span class="markdown-benefit-icon">⚡</span>
1391
+ <b>가볍고 빠름</b><br>
1392
+ 용량이 작고 처리 속도 빠름
1393
+ </div>
1394
+ <div class="markdown-benefit-item">
1395
+ <span class="markdown-benefit-icon">🔄</span>
1396
+ <b>변환 용이</b><br>
1397
+ HTML, PDF, Word 등으로 재변환 가능
1398
+ </div>
1399
+ <div class="markdown-benefit-item">
1400
+ <span class="markdown-benefit-icon">✏️</span>
1401
+ <b>편집 간편</b><br>
1402
+ 메모장으로도 바로 수정 가능
1403
+ </div>
1404
+ </div>
1405
+ </div>
1406
+ """)
1407
+
1408
  with gr.Row():
1409
  with gr.Column():
1410
  gr.HTML('<div class="info-box">📤 <b>파일 업로드</b></div>')
 
1414
  elem_classes=["upload-box"]
1415
  )
1416
  format_select = gr.Radio(
1417
+ [" MARKDOWN (추천)", "TXT (텍스트)", "HTML", "ODT (OpenDocument)", "XML"],
1418
+ value=" MARKDOWN (추천)",
1419
  label="📋 변환 형식"
1420
  )
1421
  convert_btn = gr.Button("🔄 변환하기", variant="primary", size="lg")
 
1430
 
1431
  gr.HTML("""
1432
  <div class="info-box">
1433
+ ℹ️ <b>안내</b>: HWPX 파일은 MARKDOWN, TXT, XML 변환만 지원됩니다.
1434
  </div>
1435
  """)
1436
 
 
1440
  <p style="font-family:'Bangers',cursive;font-size:1.8rem;letter-spacing:2px">📄 HWP AI 어시스턴트 🤖</p>
1441
  <p>AI가 HWP 파일을 읽고, 보고, 말하며, 생각하고 기억합니다!</p>
1442
  <p>📖 READ • 👁️ SEE • 💬 SPEAK • 🧠 THINK • 💾 MEMORY</p>
1443
+ <p style="margin-top:8px;font-size:0.9rem;">🆓 무료 서비스 (일부 기능 제한) | 📧 arxivgpt@gmail.com</p>
1444
  <p style="margin-top:10px"><a href="https://www.humangen.ai" target="_blank" style="color:#FACC15;text-decoration:none;font-weight:bold;">🏠 www.humangen.ai</a></p>
1445
  </div>
1446
  """)
1447
 
1448
  # ============== 이벤트 핸들러 ==============
1449
+ def on_submit(msg, hist, f, sid):
1450
  if hist is None:
1451
  hist = []
1452
+ for r in chat_response(msg, hist, f, sid):
1453
  yield r[0], r[1], "", None
1454
 
1455
+ submit_btn.click(on_submit, [msg_input, chatbot, file_upload, session_state],
1456
  [chatbot, session_state, msg_input, file_upload])
1457
+ msg_input.submit(on_submit, [msg_input, chatbot, file_upload, session_state],
1458
  [chatbot, session_state, msg_input, file_upload])
1459
 
1460
  new_btn.click(lambda: ([], create_session(), None, ""), outputs=[chatbot, session_state, file_upload, msg_input])