seawolf2357 commited on
Commit
a2b3420
ยท
verified ยท
1 Parent(s): 389962a

Create app-backup.py

Browse files
Files changed (1) hide show
  1. app-backup.py +499 -0
app-backup.py ADDED
@@ -0,0 +1,499 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+
3
+ import os
4
+ import re
5
+ import tempfile
6
+ from collections.abc import Iterator
7
+ from threading import Thread
8
+
9
+ import cv2
10
+ import gradio as gr
11
+ import spaces
12
+ import torch
13
+ from loguru import logger
14
+ from PIL import Image
15
+ from transformers import AutoProcessor, Gemma3ForConditionalGeneration, TextIteratorStreamer
16
+
17
+ # CSV/TXT ๋ถ„์„
18
+ import pandas as pd
19
+
20
+ # PDF ํ…์ŠคํŠธ ์ถ”์ถœ
21
+ import PyPDF2
22
+
23
+ MAX_CONTENT_CHARS = 8000 # ๋„ˆ๋ฌด ํฐ ํŒŒ์ผ์„ ๋ง‰๊ธฐ ์œ„ํ•ด ์ตœ๋Œ€ ํ‘œ์‹œ 8000์ž
24
+
25
+ model_id = os.getenv("MODEL_ID", "google/gemma-3-27b-it")
26
+ processor = AutoProcessor.from_pretrained(model_id, padding_side="left")
27
+ model = Gemma3ForConditionalGeneration.from_pretrained(
28
+ model_id,
29
+ device_map="auto",
30
+ torch_dtype=torch.bfloat16,
31
+ attn_implementation="eager"
32
+ )
33
+
34
+ MAX_NUM_IMAGES = int(os.getenv("MAX_NUM_IMAGES", "5"))
35
+
36
+
37
+ ##################################################
38
+ # CSV, TXT, PDF ๋ถ„์„ ํ•จ์ˆ˜
39
+ ##################################################
40
+ def analyze_csv_file(path: str) -> str:
41
+ """
42
+ CSV ํŒŒ์ผ์„ ์ „์ฒด ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜. ๋„ˆ๋ฌด ๊ธธ ๊ฒฝ์šฐ ์ผ๋ถ€๋งŒ ํ‘œ์‹œ.
43
+ """
44
+ try:
45
+ df = pd.read_csv(path)
46
+ df_str = df.to_string()
47
+ if len(df_str) > MAX_CONTENT_CHARS:
48
+ df_str = df_str[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
49
+ return f"**[CSV File: {os.path.basename(path)}]**\n\n{df_str}"
50
+ except Exception as e:
51
+ return f"Failed to read CSV ({os.path.basename(path)}): {str(e)}"
52
+
53
+
54
+ def analyze_txt_file(path: str) -> str:
55
+ """
56
+ TXT ํŒŒ์ผ ์ „๋ฌธ ์ฝ๊ธฐ. ๋„ˆ๋ฌด ๊ธธ๋ฉด ์ผ๋ถ€๋งŒ ํ‘œ์‹œ.
57
+ """
58
+ try:
59
+ with open(path, "r", encoding="utf-8") as f:
60
+ text = f.read()
61
+ if len(text) > MAX_CONTENT_CHARS:
62
+ text = text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
63
+ return f"**[TXT File: {os.path.basename(path)}]**\n\n{text}"
64
+ except Exception as e:
65
+ return f"Failed to read TXT ({os.path.basename(path)}): {str(e)}"
66
+
67
+
68
+ def pdf_to_markdown(pdf_path: str) -> str:
69
+ """
70
+ PDF โ†’ Markdown. ํŽ˜์ด์ง€๋ณ„๋กœ ๊ฐ„๋‹จํžˆ ํ…์ŠคํŠธ ์ถ”์ถœ.
71
+ """
72
+ text_chunks = []
73
+ try:
74
+ with open(pdf_path, "rb") as f:
75
+ reader = PyPDF2.PdfReader(f)
76
+ for page_num, page in enumerate(reader.pages, start=1):
77
+ page_text = page.extract_text() or ""
78
+ page_text = page_text.strip()
79
+ if page_text:
80
+ text_chunks.append(f"## Page {page_num}\n\n{page_text}\n")
81
+ except Exception as e:
82
+ return f"Failed to read PDF ({os.path.basename(pdf_path)}): {str(e)}"
83
+
84
+ full_text = "\n".join(text_chunks)
85
+ if len(full_text) > MAX_CONTENT_CHARS:
86
+ full_text = full_text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
87
+
88
+ return f"**[PDF File: {os.path.basename(pdf_path)}]**\n\n{full_text}"
89
+
90
+
91
+ ##################################################
92
+ # ์ด๋ฏธ์ง€/๋น„๋””์˜ค ์—…๋กœ๋“œ ์ œํ•œ ๊ฒ€์‚ฌ
93
+ ##################################################
94
+ def count_files_in_new_message(paths: list[str]) -> tuple[int, int]:
95
+ image_count = 0
96
+ video_count = 0
97
+ for path in paths:
98
+ if path.endswith(".mp4"):
99
+ video_count += 1
100
+ else:
101
+ image_count += 1
102
+ return image_count, video_count
103
+
104
+
105
+ def count_files_in_history(history: list[dict]) -> tuple[int, int]:
106
+ image_count = 0
107
+ video_count = 0
108
+ for item in history:
109
+ if item["role"] != "user" or isinstance(item["content"], str):
110
+ continue
111
+ if item["content"][0].endswith(".mp4"):
112
+ video_count += 1
113
+ else:
114
+ image_count += 1
115
+ return image_count, video_count
116
+
117
+
118
+ def validate_media_constraints(message: dict, history: list[dict]) -> bool:
119
+ """
120
+ - ๋น„๋””์˜ค 1๊ฐœ ์ดˆ๊ณผ ๋ถˆ๊ฐ€
121
+ - ๋น„๋””์˜ค์™€ ์ด๋ฏธ์ง€ ํ˜ผํ•ฉ ๋ถˆ๊ฐ€
122
+ - ์ด๋ฏธ์ง€ ๊ฐœ์ˆ˜ MAX_NUM_IMAGES ์ดˆ๊ณผ ๋ถˆ๊ฐ€
123
+ - <image> ํƒœ๊ทธ๊ฐ€ ์žˆ์œผ๋ฉด ํƒœ๊ทธ ์ˆ˜์™€ ์‹ค์ œ ์ด๋ฏธ์ง€ ์ˆ˜ ์ผ์น˜
124
+ - CSV, TXT, PDF ๋“ฑ์€ ์—ฌ๊ธฐ์„œ ์ œํ•œํ•˜์ง€ ์•Š์Œ
125
+ """
126
+ media_files = []
127
+ for f in message["files"]:
128
+ # ์ด๋ฏธ์ง€: png/jpg/jpeg/gif/webp
129
+ # ๋น„๋””์˜ค: mp4
130
+ # cf) PDF, CSV, TXT ๋“ฑ์€ ์ œ์™ธ
131
+ if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE) or f.endswith(".mp4"):
132
+ media_files.append(f)
133
+
134
+ new_image_count, new_video_count = count_files_in_new_message(media_files)
135
+ history_image_count, history_video_count = count_files_in_history(history)
136
+ image_count = history_image_count + new_image_count
137
+ video_count = history_video_count + new_video_count
138
+
139
+ if video_count > 1:
140
+ gr.Warning("Only one video is supported.")
141
+ return False
142
+ if video_count == 1:
143
+ if image_count > 0:
144
+ gr.Warning("Mixing images and videos is not allowed.")
145
+ return False
146
+ if "<image>" in message["text"]:
147
+ gr.Warning("Using <image> tags with video files is not supported.")
148
+ return False
149
+ if video_count == 0 and image_count > MAX_NUM_IMAGES:
150
+ gr.Warning(f"You can upload up to {MAX_NUM_IMAGES} images.")
151
+ return False
152
+ if "<image>" in message["text"] and message["text"].count("<image>") != new_image_count:
153
+ gr.Warning("The number of <image> tags in the text does not match the number of images.")
154
+ return False
155
+
156
+ return True
157
+
158
+
159
+ ##################################################
160
+ # ๋น„๋””์˜ค ์ฒ˜๋ฆฌ
161
+ ##################################################
162
+ def downsample_video(video_path: str) -> list[tuple[Image.Image, float]]:
163
+ vidcap = cv2.VideoCapture(video_path)
164
+ fps = vidcap.get(cv2.CAP_PROP_FPS)
165
+ total_frames = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
166
+
167
+ frame_interval = int(fps / 3)
168
+ frames = []
169
+
170
+ for i in range(0, total_frames, frame_interval):
171
+ vidcap.set(cv2.CAP_PROP_POS_FRAMES, i)
172
+ success, image = vidcap.read()
173
+ if success:
174
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
175
+ pil_image = Image.fromarray(image)
176
+ timestamp = round(i / fps, 2)
177
+ frames.append((pil_image, timestamp))
178
+
179
+ vidcap.release()
180
+ return frames
181
+
182
+
183
+ def process_video(video_path: str) -> list[dict]:
184
+ content = []
185
+ frames = downsample_video(video_path)
186
+ for frame in frames:
187
+ pil_image, timestamp = frame
188
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
189
+ pil_image.save(temp_file.name)
190
+ content.append({"type": "text", "text": f"Frame {timestamp}:"})
191
+ content.append({"type": "image", "url": temp_file.name})
192
+ logger.debug(f"{content=}")
193
+ return content
194
+
195
+
196
+ ##################################################
197
+ # interleaved <image> ์ฒ˜๋ฆฌ
198
+ ##################################################
199
+ def process_interleaved_images(message: dict) -> list[dict]:
200
+ parts = re.split(r"(<image>)", message["text"])
201
+ content = []
202
+ image_index = 0
203
+ for part in parts:
204
+ if part == "<image>":
205
+ content.append({"type": "image", "url": message["files"][image_index]})
206
+ image_index += 1
207
+ elif part.strip():
208
+ content.append({"type": "text", "text": part.strip()})
209
+ else:
210
+ # ๊ณต๋ฐฑ์ด๊ฑฐ๋‚˜ \n ๊ฐ™์€ ๊ฒฝ์šฐ
211
+ if isinstance(part, str) and part != "<image>":
212
+ content.append({"type": "text", "text": part})
213
+ return content
214
+
215
+
216
+ ##################################################
217
+ # PDF + CSV + TXT + ์ด๋ฏธ์ง€/๋น„๋””์˜ค
218
+ ##################################################
219
+ def process_new_user_message(message: dict) -> list[dict]:
220
+ if not message["files"]:
221
+ return [{"type": "text", "text": message["text"]}]
222
+
223
+ # 1) ํŒŒ์ผ ๋ถ„๋ฅ˜
224
+ video_files = [f for f in message["files"] if f.endswith(".mp4")]
225
+ image_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE)]
226
+ csv_files = [f for f in message["files"] if f.lower().endswith(".csv")]
227
+ txt_files = [f for f in message["files"] if f.lower().endswith(".txt")]
228
+ pdf_files = [f for f in message["files"] if f.lower().endswith(".pdf")]
229
+
230
+ # 2) ์‚ฌ์šฉ์ž ์›๋ณธ text ์ถ”๊ฐ€
231
+ content_list = [{"type": "text", "text": message["text"]}]
232
+
233
+ # 3) CSV
234
+ for csv_path in csv_files:
235
+ csv_analysis = analyze_csv_file(csv_path)
236
+ content_list.append({"type": "text", "text": csv_analysis})
237
+
238
+ # 4) TXT
239
+ for txt_path in txt_files:
240
+ txt_analysis = analyze_txt_file(txt_path)
241
+ content_list.append({"type": "text", "text": txt_analysis})
242
+
243
+ # 5) PDF
244
+ for pdf_path in pdf_files:
245
+ pdf_markdown = pdf_to_markdown(pdf_path)
246
+ content_list.append({"type": "text", "text": pdf_markdown})
247
+
248
+ # 6) ๋น„๋””์˜ค (ํ•œ ๊ฐœ๋งŒ ํ—ˆ์šฉ)
249
+ if video_files:
250
+ content_list += process_video(video_files[0])
251
+ return content_list
252
+
253
+ # 7) ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ
254
+ if "<image>" in message["text"]:
255
+ # interleaved
256
+ return process_interleaved_images(message)
257
+ else:
258
+ # ์ผ๋ฐ˜ ์—ฌ๋Ÿฌ ์žฅ
259
+ for img_path in image_files:
260
+ content_list.append({"type": "image", "url": img_path})
261
+
262
+ return content_list
263
+
264
+
265
+ ##################################################
266
+ # history -> LLM ๋ฉ”์‹œ์ง€ ๋ณ€ํ™˜
267
+ ##################################################
268
+ def process_history(history: list[dict]) -> list[dict]:
269
+ messages = []
270
+ current_user_content: list[dict] = []
271
+ for item in history:
272
+ if item["role"] == "assistant":
273
+ # user_content๊ฐ€ ์Œ“์—ฌ์žˆ๋‹ค๋ฉด user ๋ฉ”์‹œ์ง€๋กœ ์ €์žฅ
274
+ if current_user_content:
275
+ messages.append({"role": "user", "content": current_user_content})
276
+ current_user_content = []
277
+ # ๊ทธ ๋’ค item์€ assistant
278
+ messages.append({"role": "assistant", "content": [{"type": "text", "text": item["content"]}]})
279
+ else:
280
+ # user
281
+ content = item["content"]
282
+ if isinstance(content, str):
283
+ current_user_content.append({"type": "text", "text": content})
284
+ else:
285
+ # ์ด๋ฏธ์ง€๋‚˜ ๊ธฐํƒ€
286
+ current_user_content.append({"type": "image", "url": content[0]})
287
+ return messages
288
+
289
+
290
+ ##################################################
291
+ # ๋ฉ”์ธ ์ถ”๋ก  ํ•จ์ˆ˜
292
+ ##################################################
293
+ @spaces.GPU(duration=120)
294
+ def run(message: dict, history: list[dict], system_prompt: str = "", max_new_tokens: int = 512) -> Iterator[str]:
295
+ if not validate_media_constraints(message, history):
296
+ yield ""
297
+ return
298
+
299
+ messages = []
300
+ if system_prompt:
301
+ messages.append({"role": "system", "content": [{"type": "text", "text": system_prompt}]})
302
+ messages.extend(process_history(history))
303
+ messages.append({"role": "user", "content": process_new_user_message(message)})
304
+
305
+ inputs = processor.apply_chat_template(
306
+ messages,
307
+ add_generation_prompt=True,
308
+ tokenize=True,
309
+ return_dict=True,
310
+ return_tensors="pt",
311
+ ).to(device=model.device, dtype=torch.bfloat16)
312
+
313
+ streamer = TextIteratorStreamer(processor, timeout=30.0, skip_prompt=True, skip_special_tokens=True)
314
+ gen_kwargs = dict(
315
+ inputs,
316
+ streamer=streamer,
317
+ max_new_tokens=max_new_tokens,
318
+ )
319
+ t = Thread(target=model.generate, kwargs=gen_kwargs)
320
+ t.start()
321
+
322
+ output = ""
323
+ for new_text in streamer:
324
+ output += new_text
325
+ yield output
326
+
327
+
328
+ ##################################################
329
+ # ์˜ˆ์‹œ๋“ค (๊ธฐ์กด)
330
+ ##################################################
331
+ ##################################################
332
+ # ์˜ˆ์‹œ๋“ค (ํ•œ๊ธ€ํ™” ๋ฒ„์ „)
333
+ ##################################################
334
+ examples = [
335
+
336
+ [
337
+ {
338
+ "text": "PDF ํŒŒ์ผ ๋‚ด์šฉ์„ ์š”์•ฝ, ๋ถ„์„ํ•˜๋ผ.",
339
+ "files": ["assets/additional-examples/pdf.pdf"],
340
+ }
341
+ ],
342
+ [
343
+ {
344
+ "text": "CSV ํŒŒ์ผ ๋‚ด์šฉ์„ ์š”์•ฝ, ๋ถ„์„ํ•˜๋ผ",
345
+ "files": ["assets/additional-examples/sample-csv.csv"],
346
+ }
347
+ ],
348
+ [
349
+ {
350
+ "text": "๋™์ผํ•œ ๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„๋ฅผ ๊ทธ๋ฆฌ๋Š” matplotlib ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.",
351
+ "files": ["assets/additional-examples/barchart.png"],
352
+ }
353
+ ],
354
+ [
355
+ {
356
+ "text": "์ด ์˜์ƒ์—์„œ ์ด์ƒํ•œ ์ ์ด ๋ฌด์—‡์ธ๊ฐ€์š”?",
357
+ "files": ["assets/additional-examples/tmp.mp4"],
358
+ }
359
+ ],
360
+ [
361
+ {
362
+ "text": "์ด๋ฏธ ์ด ์˜์–‘์ œ๋ฅผ <image> ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ์ด ์ œํ’ˆ <image>์„ ์ƒˆ๋กœ ์‚ฌ๋ ค ํ•ฉ๋‹ˆ๋‹ค. ํ•จ๊ป˜ ์„ญ์ทจํ•  ๋•Œ ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ์ด ์žˆ์„๊นŒ์š”?",
363
+ "files": ["assets/additional-examples/pill1.png", "assets/additional-examples/pill2.png"],
364
+ }
365
+ ],
366
+ [
367
+ {
368
+ "text": "์ด๋ฏธ์ง€์˜ ์‹œ๊ฐ์  ์š”์†Œ์—์„œ ์˜๊ฐ์„ ๋ฐ›์•„ ์‹œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.",
369
+ "files": ["assets/sample-images/06-1.png", "assets/sample-images/06-2.png"],
370
+ }
371
+ ],
372
+ [
373
+ {
374
+ "text": "์ด๋ฏธ์ง€์˜ ์‹œ๊ฐ์  ์š”์†Œ๋ฅผ ํ† ๋Œ€๋กœ ์งง์€ ์•…๊ณก์„ ์ž‘๊ณกํ•ด์ฃผ์„ธ์š”.",
375
+ "files": [
376
+ "assets/sample-images/07-1.png",
377
+ "assets/sample-images/07-2.png",
378
+ "assets/sample-images/07-3.png",
379
+ "assets/sample-images/07-4.png",
380
+ ],
381
+ }
382
+ ],
383
+ [
384
+ {
385
+ "text": "์ด ์ง‘์—์„œ ๋ฌด์Šจ ์ผ์ด ์žˆ์—ˆ์„์ง€ ์งง์€ ์ด์•ผ๊ธฐ๋ฅผ ์ง€์–ด๋ณด์„ธ์š”.",
386
+ "files": ["assets/sample-images/08.png"],
387
+ }
388
+ ],
389
+ [
390
+ {
391
+ "text": "์ด๋ฏธ์ง€๋“ค์˜ ์ˆœ์„œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์งง์€ ์ด์•ผ๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ์„ธ์š”.",
392
+ "files": [
393
+ "assets/sample-images/09-1.png",
394
+ "assets/sample-images/09-2.png",
395
+ "assets/sample-images/09-3.png",
396
+ "assets/sample-images/09-4.png",
397
+ "assets/sample-images/09-5.png",
398
+ ],
399
+ }
400
+ ],
401
+ [
402
+ {
403
+ "text": "์ด ์„ธ๊ณ„์—์„œ ์‚ด๊ณ  ์žˆ์„ ์ƒ๋ฌผ๋“ค์„ ์ƒ์ƒํ•ด์„œ ๋ฌ˜์‚ฌํ•ด์ฃผ์„ธ์š”.",
404
+ "files": ["assets/sample-images/10.png"],
405
+ }
406
+ ],
407
+ [
408
+ {
409
+ "text": "์ด๋ฏธ์ง€์— ์ ํžŒ ํ…์ŠคํŠธ๋ฅผ ์ฝ์–ด์ฃผ์„ธ์š”.",
410
+ "files": ["assets/additional-examples/1.png"],
411
+ }
412
+ ],
413
+ [
414
+ {
415
+ "text": "์ด ํ‹ฐ์ผ“์€ ์–ธ์ œ ๋ฐœ๊ธ‰๋œ ๊ฒƒ์ด๊ณ , ๊ฐ€๊ฒฉ์€ ์–ผ๋งˆ์ธ๊ฐ€์š”?",
416
+ "files": ["assets/additional-examples/2.png"],
417
+ }
418
+ ],
419
+ [
420
+ {
421
+ "text": "์ด๋ฏธ์ง€์— ์žˆ๋Š” ํ…์ŠคํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ์ฝ์–ด์„œ ๋งˆํฌ๋‹ค์šด ํ˜•ํƒœ๋กœ ์ ์–ด์ฃผ์„ธ์š”.",
422
+ "files": ["assets/additional-examples/3.png"],
423
+ }
424
+ ],
425
+ [
426
+ {
427
+ "text": "์ด ์ ๋ถ„์„ ํ’€์–ด์ฃผ์„ธ์š”.",
428
+ "files": ["assets/additional-examples/4.png"],
429
+ }
430
+ ],
431
+ [
432
+ {
433
+ "text": "์ด ์ด๋ฏธ์ง€๋ฅผ ๊ฐ„๋‹จํžˆ ์บก์…˜์œผ๋กœ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.",
434
+ "files": ["assets/sample-images/01.png"],
435
+ }
436
+ ],
437
+ [
438
+ {
439
+ "text": "์ด ํ‘œ์ง€ํŒ์—๋Š” ๋ฌด์Šจ ๋ฌธ๊ตฌ๊ฐ€ ์ ํ˜€ ์žˆ๋‚˜์š”?",
440
+ "files": ["assets/sample-images/02.png"],
441
+ }
442
+ ],
443
+ [
444
+ {
445
+ "text": "๋‘ ์ด๋ฏธ์ง€๋ฅผ ๋น„๊ตํ•ด์„œ ๊ณตํ†ต์ ๊ณผ ์ฐจ์ด์ ์„ ๋งํ•ด์ฃผ์„ธ์š”.",
446
+ "files": ["assets/sample-images/03.png"],
447
+ }
448
+ ],
449
+ [
450
+ {
451
+ "text": "์ด๋ฏธ์ง€์— ๋ณด์ด๋Š” ๋ชจ๋“  ์‚ฌ๋ฌผ๊ณผ ๊ทธ ์ƒ‰์ƒ์„ ๋‚˜์—ดํ•ด์ฃผ์„ธ์š”.",
452
+ "files": ["assets/sample-images/04.png"],
453
+ }
454
+ ],
455
+ [
456
+ {
457
+ "text": "์žฅ๋ฉด์˜ ๋ถ„์œ„๊ธฐ๋ฅผ ๋ฌ˜์‚ฌํ•ด์ฃผ์„ธ์š”.",
458
+ "files": ["assets/sample-images/05.png"],
459
+ }
460
+ ],
461
+ ]
462
+
463
+
464
+
465
+ demo = gr.ChatInterface(
466
+ fn=run,
467
+ type="messages",
468
+ chatbot=gr.Chatbot(type="messages", scale=1, allow_tags=["image"]),
469
+ # .webp, .png, .jpg, .jpeg, .gif, .mp4, .csv, .txt, .pdf ๋ชจ๋‘ ํ—ˆ์šฉ
470
+ textbox=gr.MultimodalTextbox(
471
+ file_types=[
472
+ ".webp", ".png", ".jpg", ".jpeg", ".gif",
473
+ ".mp4", ".csv", ".txt", ".pdf"
474
+ ],
475
+ file_count="multiple",
476
+ autofocus=True
477
+ ),
478
+ multimodal=True,
479
+ additional_inputs=[
480
+ gr.Textbox(
481
+ label="System Prompt",
482
+ value=(
483
+ "You are a deeply thoughtful AI. Consider problems thoroughly and derive "
484
+ "correct solutions through systematic reasoning. Please answer in korean."
485
+ )
486
+ ),
487
+ gr.Slider(label="Max New Tokens", minimum=100, maximum=8000, step=50, value=2000),
488
+ ],
489
+ stop_btn=False,
490
+ title="Vidraft-Gemma-3-27B",
491
+ examples=examples,
492
+ run_examples_on_click=False,
493
+ cache_examples=False,
494
+ css_paths="style.css",
495
+ delete_cache=(1800, 1800),
496
+ )
497
+
498
+ if __name__ == "__main__":
499
+ demo.launch()