fantaxy commited on
Commit
5c52ea1
ยท
verified ยท
1 Parent(s): f997b8f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +495 -0
app.py ADDED
@@ -0,0 +1,495 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import spaces
3
+ import torch
4
+ from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
5
+ from threading import Thread
6
+ import re
7
+ import json
8
+ from datetime import datetime
9
+ import math
10
+ import os
11
+
12
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
13
+ # ๐Ÿ”ง ๋ชจ๋ธ ๋กœ๋”ฉ
14
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
15
+
16
+ MODEL_ID = "zai-org/GLM-4.7-Flash"
17
+
18
+ print(f"[Init] Loading tokenizer from {MODEL_ID}...")
19
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
20
+
21
+ print(f"[Init] Loading model from {MODEL_ID}...")
22
+ model = None # ์ง€์—ฐ ๋กœ๋”ฉ
23
+
24
+ def get_model():
25
+ global model
26
+ if model is None:
27
+ print("[Model] Loading model with bfloat16...")
28
+ model = AutoModelForCausalLM.from_pretrained(
29
+ MODEL_ID,
30
+ torch_dtype=torch.bfloat16,
31
+ device_map="auto",
32
+ trust_remote_code=True,
33
+ low_cpu_mem_usage=True,
34
+ )
35
+ print(f"[Model] Model loaded on {model.device}")
36
+ return model
37
+
38
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
39
+ # ๐Ÿ“„ ํŒŒ์ผ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
40
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
41
+
42
+ def extract_text_from_pdf(file_path: str) -> str:
43
+ """PDF ํŒŒ์ผ์—์„œ ํ…์ŠคํŠธ ์ถ”์ถœ"""
44
+ try:
45
+ import fitz # PyMuPDF
46
+ doc = fitz.open(file_path)
47
+ text_parts = []
48
+ for page_num, page in enumerate(doc, 1):
49
+ text = page.get_text()
50
+ if text.strip():
51
+ text_parts.append(f"[ํŽ˜์ด์ง€ {page_num}]\n{text}")
52
+ doc.close()
53
+ return "\n\n".join(text_parts) if text_parts else "[PDF์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค]"
54
+ except ImportError:
55
+ try:
56
+ from pypdf import PdfReader
57
+ reader = PdfReader(file_path)
58
+ text_parts = []
59
+ for page_num, page in enumerate(reader.pages, 1):
60
+ text = page.extract_text()
61
+ if text and text.strip():
62
+ text_parts.append(f"[ํŽ˜์ด์ง€ {page_num}]\n{text}")
63
+ return "\n\n".join(text_parts) if text_parts else "[PDF์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค]"
64
+ except Exception as e:
65
+ return f"[PDF ์ฝ๊ธฐ ์˜ค๋ฅ˜: {str(e)}]"
66
+ except Exception as e:
67
+ return f"[PDF ์ฝ๊ธฐ ์˜ค๋ฅ˜: {str(e)}]"
68
+
69
+ def extract_text_from_docx(file_path: str) -> str:
70
+ """DOCX ํŒŒ์ผ์—์„œ ํ…์ŠคํŠธ ์ถ”์ถœ"""
71
+ try:
72
+ from docx import Document
73
+ doc = Document(file_path)
74
+
75
+ text_parts = []
76
+
77
+ # ๋ฌธ๋‹จ ์ถ”์ถœ
78
+ for para in doc.paragraphs:
79
+ if para.text.strip():
80
+ text_parts.append(para.text)
81
+
82
+ # ํ…Œ์ด๋ธ” ์ถ”์ถœ
83
+ for table_idx, table in enumerate(doc.tables, 1):
84
+ table_text = [f"\n[ํ‘œ {table_idx}]"]
85
+ for row in table.rows:
86
+ row_text = " | ".join(cell.text.strip() for cell in row.cells)
87
+ if row_text.strip():
88
+ table_text.append(row_text)
89
+ if len(table_text) > 1:
90
+ text_parts.append("\n".join(table_text))
91
+
92
+ return "\n\n".join(text_parts) if text_parts else "[DOCX์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค]"
93
+ except Exception as e:
94
+ return f"[DOCX ์ฝ๊ธฐ ์˜ค๋ฅ˜: {str(e)}]"
95
+
96
+ def extract_text_from_txt(file_path: str) -> str:
97
+ """TXT ํŒŒ์ผ์—์„œ ํ…์ŠคํŠธ ์ถ”์ถœ"""
98
+ try:
99
+ encodings = ['utf-8', 'cp949', 'euc-kr', 'latin-1']
100
+ for encoding in encodings:
101
+ try:
102
+ with open(file_path, 'r', encoding=encoding) as f:
103
+ return f.read()
104
+ except UnicodeDecodeError:
105
+ continue
106
+ return "[ํ…์ŠคํŠธ ํŒŒ์ผ ์ธ์ฝ”๋”ฉ์„ ์ธ์‹ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค]"
107
+ except Exception as e:
108
+ return f"[TXT ์ฝ๊ธฐ ์˜ค๋ฅ˜: {str(e)}]"
109
+
110
+ def process_uploaded_file(file) -> tuple[str, str]:
111
+ """์—…๋กœ๋“œ๋œ ํŒŒ์ผ ์ฒ˜๋ฆฌ"""
112
+ if file is None:
113
+ return "", ""
114
+
115
+ file_path = file.name if hasattr(file, 'name') else str(file)
116
+ file_name = os.path.basename(file_path)
117
+ file_ext = os.path.splitext(file_name)[1].lower()
118
+
119
+ if file_ext == '.pdf':
120
+ content = extract_text_from_pdf(file_path)
121
+ elif file_ext == '.docx':
122
+ content = extract_text_from_docx(file_path)
123
+ elif file_ext in ['.txt', '.md', '.py', '.js', '.html', '.css', '.json', '.xml', '.csv']:
124
+ content = extract_text_from_txt(file_path)
125
+ else:
126
+ content = f"[์ง€์›ํ•˜์ง€ ์•Š๋Š” ํŒŒ์ผ ํ˜•์‹: {file_ext}]"
127
+
128
+ # ํ…์ŠคํŠธ ๊ธธ์ด ์ œํ•œ
129
+ max_chars = 50000
130
+ if len(content) > max_chars:
131
+ content = content[:max_chars] + f"\n\n... [ํ…์ŠคํŠธ๊ฐ€ {max_chars}์ž๋กœ ์ž˜๋ ธ์Šต๋‹ˆ๋‹ค. ์›๋ณธ: {len(content)}์ž]"
132
+
133
+ return file_name, content
134
+
135
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
136
+ # ๐Ÿ› ๏ธ Tool Definitions
137
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
138
+
139
+ def execute_tool(tool_name: str, arguments: dict) -> str:
140
+ """๋„๊ตฌ ์‹คํ–‰"""
141
+ try:
142
+ if tool_name == "calculator":
143
+ expr = arguments.get("expression", "")
144
+ allowed_names = {
145
+ "abs": abs, "round": round, "min": min, "max": max,
146
+ "sum": sum, "pow": pow, "sqrt": math.sqrt,
147
+ "sin": math.sin, "cos": math.cos, "tan": math.tan,
148
+ "log": math.log, "log10": math.log10, "exp": math.exp,
149
+ "pi": math.pi, "e": math.e,
150
+ "floor": math.floor, "ceil": math.ceil,
151
+ }
152
+ expr = re.sub(r'[^0-9+\-*/().a-zA-Z_ ]', '', expr)
153
+ result = eval(expr, {"__builtins__": {}}, allowed_names)
154
+ return f"๊ณ„์‚ฐ ๊ฒฐ๊ณผ: {expr} = {result}"
155
+
156
+ elif tool_name == "get_current_time":
157
+ tz = arguments.get("timezone", "UTC")
158
+ now = datetime.now()
159
+ return f"ํ˜„์žฌ ์‹œ๊ฐ„ ({tz}): {now.strftime('%Y-%m-%d %H:%M:%S')}"
160
+
161
+ elif tool_name == "unit_converter":
162
+ value = arguments.get("value", 0)
163
+ from_unit = arguments.get("from_unit", "").lower()
164
+ to_unit = arguments.get("to_unit", "").lower()
165
+
166
+ conversions = {
167
+ ("km", "m"): lambda x: x * 1000,
168
+ ("m", "km"): lambda x: x / 1000,
169
+ ("kg", "g"): lambda x: x * 1000,
170
+ ("g", "kg"): lambda x: x / 1000,
171
+ ("c", "f"): lambda x: x * 9/5 + 32,
172
+ ("f", "c"): lambda x: (x - 32) * 5/9,
173
+ ("km", "mile"): lambda x: x * 0.621371,
174
+ ("mile", "km"): lambda x: x * 1.60934,
175
+ ("kg", "lb"): lambda x: x * 2.20462,
176
+ ("lb", "kg"): lambda x: x * 0.453592,
177
+ }
178
+
179
+ key = (from_unit, to_unit)
180
+ if key in conversions:
181
+ result = conversions[key](value)
182
+ return f"๋ณ€ํ™˜ ๊ฒฐ๊ณผ: {value} {from_unit} = {result:.4f} {to_unit}"
183
+ else:
184
+ return f"์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋‹จ์œ„ ๋ณ€ํ™˜: {from_unit} -> {to_unit}"
185
+
186
+ elif tool_name == "code_executor":
187
+ code = arguments.get("code", "")
188
+ local_vars = {}
189
+ exec(code, {"__builtins__": {"print": print, "range": range, "len": len, "str": str, "int": int, "float": float, "list": list, "dict": dict}}, local_vars)
190
+ if "result" in local_vars:
191
+ return f"์‹คํ–‰ ๊ฒฐ๊ณผ: {local_vars['result']}"
192
+ return "์ฝ”๋“œ ์‹คํ–‰ ์™„๋ฃŒ"
193
+
194
+ else:
195
+ return f"์•Œ ์ˆ˜ ์—†๋Š” ๋„๊ตฌ: {tool_name}"
196
+
197
+ except Exception as e:
198
+ return f"๋„๊ตฌ ์‹คํ–‰ ์˜ค๋ฅ˜: {str(e)}"
199
+
200
+ def parse_tool_calls(response: str) -> list:
201
+ """์‘๋‹ต์—์„œ ๋„๊ตฌ ํ˜ธ์ถœ ํŒŒ์‹ฑ"""
202
+ tool_calls = []
203
+
204
+ patterns = [
205
+ r'<\|tool_call\|>(\{.*?\})<\|/tool_call\|>',
206
+ r'```json\s*(\{[^`]*"name"[^`]*\})\s*```',
207
+ r'\{"name":\s*"(\w+)",\s*"arguments":\s*(\{[^}]+\})\}',
208
+ ]
209
+
210
+ for pattern in patterns:
211
+ matches = re.findall(pattern, response, re.DOTALL)
212
+ for match in matches:
213
+ try:
214
+ if isinstance(match, tuple):
215
+ tool_call = {"name": match[0], "arguments": json.loads(match[1])}
216
+ else:
217
+ tool_call = json.loads(match)
218
+ tool_calls.append(tool_call)
219
+ except:
220
+ continue
221
+
222
+ return tool_calls
223
+
224
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
225
+ # ๐Ÿ’ฌ ์ŠคํŠธ๋ฆฌ๋ฐ ์ฑ„ํŒ… ํ•จ์ˆ˜
226
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
227
+
228
+ file_context = {"name": "", "content": ""}
229
+
230
+ @spaces.GPU(duration=120)
231
+ def chat_streaming(
232
+ message: str,
233
+ history: list,
234
+ system_prompt: str,
235
+ max_tokens: int,
236
+ temperature: float,
237
+ top_p: float,
238
+ enable_thinking: bool,
239
+ enable_tools: bool,
240
+ ):
241
+ """์ŠคํŠธ๋ฆฌ๋ฐ ์ฑ„ํŒ… ์ƒ์„ฑ"""
242
+ global file_context
243
+
244
+ if not message.strip():
245
+ yield history, ""
246
+ return
247
+
248
+ model = get_model()
249
+ messages = []
250
+
251
+ # ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ
252
+ sys_content = system_prompt if system_prompt.strip() else "You are a helpful AI assistant."
253
+
254
+ # ํŒŒ์ผ ์ปจํ…์ŠคํŠธ ์ถ”๊ฐ€
255
+ if file_context["content"]:
256
+ sys_content += f"\n\n[์—…๋กœ๋“œ๋œ ํŒŒ์ผ: {file_context['name']}]\n์•„๋ž˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์—…๋กœ๋“œํ•œ ํŒŒ์ผ์˜ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ์ด ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•˜์„ธ์š”.\n\n---\n{file_context['content']}\n---"
257
+
258
+ # Tool ์ •๋ณด ์ถ”๊ฐ€
259
+ if enable_tools:
260
+ tool_desc = """
261
+ You have access to the following tools:
262
+ 1. calculator: Perform mathematical calculations
263
+ 2. get_current_time: Get current date and time
264
+ 3. unit_converter: Convert between units
265
+ 4. code_executor: Execute Python code
266
+
267
+ To use a tool, respond with: {"name": "tool_name", "arguments": {...}}
268
+ """
269
+ sys_content += f"\n\n{tool_desc}"
270
+
271
+ messages.append({"role": "system", "content": sys_content})
272
+
273
+ # ํžˆ์Šคํ† ๋ฆฌ ์ถ”๊ฐ€
274
+ for h in history:
275
+ if h[0]:
276
+ messages.append({"role": "user", "content": h[0]})
277
+ if h[1]:
278
+ messages.append({"role": "assistant", "content": h[1]})
279
+
280
+ # ํ˜„์žฌ ๋ฉ”์‹œ์ง€
281
+ user_content = message
282
+ if enable_thinking:
283
+ user_content = f"<think>\nLet me think step by step.\n</think>\n\n{message}"
284
+
285
+ messages.append({"role": "user", "content": user_content})
286
+
287
+ # ํ† ํฌ๋‚˜์ด์ฆˆ
288
+ try:
289
+ inputs = tokenizer.apply_chat_template(
290
+ messages,
291
+ add_generation_prompt=True,
292
+ tokenize=True,
293
+ return_dict=True,
294
+ return_tensors="pt",
295
+ ).to(model.device)
296
+ except Exception as e:
297
+ yield history + [[message, f"ํ† ํฌ๋‚˜์ด์ฆˆ ์˜ค๋ฅ˜: {str(e)}"]], ""
298
+ return
299
+
300
+ # ์ŠคํŠธ๋ฆฌ๋จธ ์„ค์ •
301
+ streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
302
+
303
+ generation_kwargs = {
304
+ **inputs,
305
+ "streamer": streamer,
306
+ "max_new_tokens": max_tokens,
307
+ "temperature": temperature if temperature > 0 else 0.01,
308
+ "top_p": top_p,
309
+ "do_sample": temperature > 0,
310
+ "pad_token_id": tokenizer.pad_token_id or tokenizer.eos_token_id,
311
+ }
312
+
313
+ thread = Thread(target=model.generate, kwargs=generation_kwargs)
314
+ thread.start()
315
+
316
+ partial_response = ""
317
+ new_history = history + [[message, ""]]
318
+
319
+ for new_token in streamer:
320
+ partial_response += new_token
321
+ new_history[-1][1] = partial_response
322
+ yield new_history, ""
323
+
324
+ thread.join()
325
+
326
+ # Tool ํ˜ธ์ถœ ์ฒ˜๋ฆฌ
327
+ if enable_tools:
328
+ tool_calls = parse_tool_calls(partial_response)
329
+ if tool_calls:
330
+ tool_results = []
331
+ for tc in tool_calls:
332
+ result = execute_tool(tc.get("name", ""), tc.get("arguments", {}))
333
+ tool_results.append(result)
334
+
335
+ if tool_results:
336
+ final_response = partial_response + "\n\n๐Ÿ“Œ **๋„๊ตฌ ์‹คํ–‰ ๊ฒฐ๊ณผ:**\n" + "\n".join(tool_results)
337
+ new_history[-1][1] = final_response
338
+
339
+ yield new_history, ""
340
+
341
+ def handle_file_upload(file):
342
+ """ํŒŒ์ผ ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ"""
343
+ global file_context
344
+
345
+ if file is None:
346
+ file_context = {"name": "", "content": ""}
347
+ return "๐Ÿ“‚ ํŒŒ์ผ์ด ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
348
+
349
+ file_name, content = process_uploaded_file(file)
350
+
351
+ if content.startswith("[") and "์˜ค๋ฅ˜" in content:
352
+ file_context = {"name": "", "content": ""}
353
+ return f"โŒ {content}"
354
+
355
+ file_context = {"name": file_name, "content": content}
356
+
357
+ preview = content[:500] + "..." if len(content) > 500 else content
358
+ char_count = len(content)
359
+
360
+ return f"""โœ… **ํŒŒ์ผ ๋กœ๋“œ ์™„๋ฃŒ: {file_name}**
361
+ - ๋ฌธ์ž ์ˆ˜: {char_count:,}์ž
362
+ - ๋ฏธ๋ฆฌ๋ณด๊ธฐ:
363
+ ```
364
+ {preview}
365
+ ```"""
366
+
367
+ def clear_file():
368
+ """ํŒŒ์ผ ์ปจํ…์ŠคํŠธ ์ดˆ๊ธฐํ™”"""
369
+ global file_context
370
+ file_context = {"name": "", "content": ""}
371
+ return None, "๐Ÿ“‚ ํŒŒ์ผ์ด ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
372
+
373
+ def clear_chat():
374
+ """์ฑ„ํŒ… ์ดˆ๊ธฐํ™”"""
375
+ return [], ""
376
+
377
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
378
+ # ๐ŸŽจ Gradio UI
379
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
380
+
381
+ CUSTOM_CSS = """
382
+ .chatbot { height: 500px !important; }
383
+ .contain { max-width: 1200px !important; margin: auto !important; }
384
+ .title { text-align: center; margin-bottom: 1rem; }
385
+ .tool-badge {
386
+ display: inline-block;
387
+ padding: 4px 8px;
388
+ margin: 2px;
389
+ border-radius: 12px;
390
+ font-size: 12px;
391
+ background: #e0e7ff;
392
+ color: #3730a3;
393
+ }
394
+ """
395
+
396
+ with gr.Blocks(css=CUSTOM_CSS, title="GLM-4.7-Flash Chatbot") as demo:
397
+ gr.HTML("""
398
+ <div class="title">
399
+ <h1>๐Ÿค– GLM-4.7-Flash Chatbot</h1>
400
+ <p>30B-A3B MoE ๋ชจ๋ธ ๊ธฐ๋ฐ˜ ์ŠคํŠธ๋ฆฌ๋ฐ ์ฑ—๋ด‡ | ๋ฌธ์„œ ๋ถ„์„ | Tool Calling</p>
401
+ <div>
402
+ <span class="tool-badge">๐Ÿ“„ PDF</span>
403
+ <span class="tool-badge">๐Ÿ“ DOCX</span>
404
+ <span class="tool-badge">๐Ÿ“ƒ TXT</span>
405
+ <span class="tool-badge">๐Ÿงฎ ๊ณ„์‚ฐ๊ธฐ</span>
406
+ <span class="tool-badge">๐Ÿ• ์‹œ๊ฐ„์กฐํšŒ</span>
407
+ <span class="tool-badge">๐Ÿ“ ๋‹จ์œ„๋ณ€ํ™˜</span>
408
+ <span class="tool-badge">๐Ÿ ์ฝ”๋“œ์‹คํ–‰</span>
409
+ </div>
410
+ </div>
411
+ """)
412
+
413
+ with gr.Row():
414
+ with gr.Column(scale=3):
415
+ chatbot = gr.Chatbot(
416
+ label="๋Œ€ํ™”",
417
+ elem_classes=["chatbot"],
418
+ height=500,
419
+ show_copy_button=True,
420
+ )
421
+
422
+ with gr.Row():
423
+ message = gr.Textbox(
424
+ label="๋ฉ”์‹œ์ง€ ์ž…๋ ฅ",
425
+ placeholder="๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”... (Shift+Enter๋กœ ์ „์†ก)",
426
+ lines=3,
427
+ scale=4,
428
+ )
429
+ submit_btn = gr.Button("์ „์†ก ๐Ÿ“ค", variant="primary", scale=1)
430
+
431
+ with gr.Row():
432
+ clear_btn = gr.Button("๋Œ€ํ™” ์ดˆ๊ธฐํ™” ๐Ÿ—‘๏ธ")
433
+ stop_btn = gr.Button("์ƒ์„ฑ ์ค‘์ง€ โน๏ธ")
434
+
435
+ # ํŒŒ์ผ ์—…๋กœ๋“œ
436
+ with gr.Accordion("๐Ÿ“ ๋ฌธ์„œ ์—…๋กœ๋“œ (PDF / DOCX / TXT)", open=True):
437
+ file_upload = gr.File(
438
+ label="ํŒŒ์ผ ์„ ํƒ",
439
+ file_types=[".pdf", ".docx", ".txt", ".md", ".py", ".js", ".html", ".css", ".json", ".xml", ".csv"],
440
+ file_count="single",
441
+ )
442
+ file_status = gr.Markdown("๐Ÿ“‚ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๋ฉด ๋‚ด์šฉ์„ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")
443
+ clear_file_btn = gr.Button("๐Ÿ“‚ ํŒŒ์ผ ์ œ๊ฑฐ", size="sm")
444
+
445
+ with gr.Column(scale=1):
446
+ gr.Markdown("### โš™๏ธ ์„ค์ •")
447
+
448
+ system_prompt = gr.Textbox(
449
+ label="์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ",
450
+ value="You are a helpful AI assistant. Answer questions accurately and concisely in the same language as the user. You can analyze uploaded documents and use tools when needed.",
451
+ lines=4,
452
+ )
453
+
454
+ max_tokens = gr.Slider(64, 4096, value=1024, step=64, label="์ตœ๋Œ€ ํ† ํฐ ์ˆ˜")
455
+ temperature = gr.Slider(0, 2, value=0.7, step=0.1, label="Temperature")
456
+ top_p = gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="Top-P")
457
+
458
+ enable_thinking = gr.Checkbox(label="๐Ÿง  Thinking ๋ชจ๋“œ", value=False)
459
+ enable_tools = gr.Checkbox(label="๐Ÿ› ๏ธ Tool Calling ํ™œ์„ฑํ™”", value=True)
460
+
461
+ gr.Markdown("### ๐Ÿ“ ์˜ˆ์‹œ ์งˆ๋ฌธ")
462
+ examples = gr.Examples(
463
+ examples=[
464
+ ["์•ˆ๋…•ํ•˜์„ธ์š”! ์ž๊ธฐ์†Œ๊ฐœ ํ•ด์ฃผ์„ธ์š”."],
465
+ ["์—…๋กœ๋“œํ•œ ๋ฌธ์„œ๋ฅผ ์š”์•ฝํ•ด์ค˜"],
466
+ ["์ด ๋ฌธ์„œ์˜ ํ•ต์‹ฌ ๋‚ด์šฉ์ด ๋ญ์•ผ?"],
467
+ ["๋ฌธ์„œ์—์„œ ์ค‘์š”ํ•œ ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”์ถœํ•ด์ค˜"],
468
+ ["123 * 456 + 789๋ฅผ ๊ณ„์‚ฐํ•ด์ค˜"],
469
+ ["ํ˜„์žฌ ์‹œ๊ฐ„์ด ๋ช‡ ์‹œ์•ผ?"],
470
+ ["100 ํ‚ฌ๋กœ๋ฏธํ„ฐ๋Š” ๋ช‡ ๋งˆ์ผ์ด์•ผ?"],
471
+ ],
472
+ inputs=message,
473
+ )
474
+
475
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
476
+ submit_event = submit_btn.click(
477
+ fn=chat_streaming,
478
+ inputs=[message, chatbot, system_prompt, max_tokens, temperature, top_p, enable_thinking, enable_tools],
479
+ outputs=[chatbot, message],
480
+ )
481
+
482
+ message.submit(
483
+ fn=chat_streaming,
484
+ inputs=[message, chatbot, system_prompt, max_tokens, temperature, top_p, enable_thinking, enable_tools],
485
+ outputs=[chatbot, message],
486
+ )
487
+
488
+ clear_btn.click(fn=clear_chat, outputs=[chatbot, message])
489
+ stop_btn.click(fn=None, cancels=[submit_event])
490
+
491
+ file_upload.change(fn=handle_file_upload, inputs=[file_upload], outputs=[file_status])
492
+ clear_file_btn.click(fn=clear_file, outputs=[file_upload, file_status])
493
+
494
+ if __name__ == "__main__":
495
+ demo.queue().launch(server_name="0.0.0.0", server_port=7860, share=False)