Serg4451D commited on
Commit
5d2ac6e
·
verified ·
1 Parent(s): 81ff1b9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +162 -171
app.py CHANGED
@@ -1,21 +1,17 @@
1
  #!/usr/bin/env python3
2
  """
3
- multimodal gpt-oss 120b — Gradio app for Hugging Face Spaces
4
-
5
- Функции:
6
- - Загрузка собственной картинки (type="filepath")
7
- - Галерея примеров (клик -> подставляет в загрузчик)
8
- - Автогенерация "More Detailed Caption" через gradio_client Florence-2
9
- - Streaming ответов от openai/gpt-oss-120b (через NVIDIA integrate / OpenAI-compatible)
10
- - Кеширование подписи для одной и той же картинки
11
  """
12
 
13
  import os
14
  import traceback
15
- from typing import Any, Dict, List, Tuple, Optional
16
-
17
  import gradio as gr
18
- from gradio_client import Client, handle_file
19
  from openai import OpenAI
20
 
21
  # (опционально) локальный .env при локальном запуске
@@ -34,156 +30,56 @@ if not NV_API_KEY:
34
  "NV_API_KEY не задан. В Hugging Face Space зайди в Settings → Secrets и добавь NV_API_KEY."
35
  )
36
 
37
- # Florence-2 Gradio wrapper (публичный)
38
- FLORENCE_WRAPPER = "gokaygokay/Florence-2"
39
-
40
- # --------------------- Клиенты ---------------------
41
- florence = Client(FLORENCE_WRAPPER)
42
  llm = OpenAI(base_url=NV_BASE_URL, api_key=NV_API_KEY)
43
 
44
- # --------------------- Хелперы ---------------------
45
- def _normalize_florence_result(res: Any) -> str:
46
- """
47
- Нормализует результат predict от Florence-2: возвращает текстовую подпись.
48
- Подстраховываемся на разные форматы (строка, dict, list и т.д.).
49
- """
50
- try:
51
- if res is None:
52
- return ""
53
- if isinstance(res, str):
54
- return res
55
- # dict-like
56
- if isinstance(res, dict):
57
- # часто бывает ключ 'caption' или 'text' или 'generated_text'
58
- for k in ("caption", "text", "generated_text", "output", "result"):
59
- if k in res and isinstance(res[k], str):
60
- return res[k]
61
- # если есть nested fields, попробуем взять первое строковое значение
62
- for v in res.values():
63
- if isinstance(v, str):
64
- return v
65
- # fallback: str()
66
- return str(res)
67
- # list/tuple: join string elements
68
- if isinstance(res, (list, tuple)):
69
- pieces = [str(x) for x in res]
70
- return "\n".join(pieces)
71
- # other types: fallback to str
72
- return str(res)
73
- except Exception:
74
- return f"[Ошибка нормализации подписи: {traceback.format_exc()}]"
75
-
76
- def get_caption_for_image(image_path_or_url: str, safety_note: bool = False) -> str:
77
- """
78
- Запрос к Florence-2: task_prompt="More Detailed Caption".
79
- Принимает локальный путь или URL.
80
- """
81
- try:
82
- if not image_path_or_url:
83
- return ""
84
- # handle_file поддерживает URL и локальные пути
85
- res = florence.predict(
86
- image=handle_file(image_path_or_url),
87
- task_prompt="More Detailed Caption",
88
- text_input=None,
89
- model_id="microsoft/Florence-2-large",
90
- api_name="/process_image"
91
- )
92
- caption = _normalize_florence_result(res)
93
- return caption
94
- except Exception as e:
95
- # логируем в stdout (HF Spaces покажет лог)
96
- print("Ошибка Florence-2 predict:", e)
97
- traceback.print_exc()
98
- return f"[Ошибка при генерации подписи: {e}]"
99
 
100
  def _extract_text_from_stream_chunk(chunk: Any) -> str:
101
  """
102
  Универсально извлекает текстовые фрагменты из чанка стриминга LLM.
103
- Работает с разными формами chunk (объект SDK, dict, ...)
104
  """
105
  try:
106
- # объектный стиль: chunk.choices[0].delta.content
107
  if hasattr(chunk, "choices"):
108
  choices = getattr(chunk, "choices")
109
  if choices:
110
  c0 = choices[0]
111
  delta = getattr(c0, "delta", None)
112
  if delta is not None:
113
- # reasoning_content или content
114
  txt = getattr(delta, "reasoning_content", None) or getattr(delta, "content", None)
115
  if txt:
116
  return str(txt)
117
- # some SDK might put content in c0.get("text") etc.
118
  text_attr = getattr(c0, "text", None)
119
  if text_attr:
120
  return str(text_attr)
121
- # dict-style
122
  if isinstance(chunk, dict):
123
  choices = chunk.get("choices") or []
124
  if choices:
125
  delta = choices[0].get("delta") or {}
126
- # try common keys
127
  return str(delta.get("content") or delta.get("reasoning_content") or choices[0].get("text") or "")
128
  except Exception:
129
  pass
130
  return ""
131
 
132
- # --------------------- UI-логика ---------------------
133
- # Кеш подписи (чтобы не вызывать Florence снова для той же картинки)
134
- # Храним словарь: {"image_path": "...", "caption": "..."}
135
- # Будем использовать gr.State для хранения этого словаря в сессии
136
- def generate_and_cache_caption(image, cache: Optional[Dict[str, str]]):
137
- """
138
- Вызывается при изменении image_input или при клике по галерее.
139
- Возвращает (caption_text, new_cache_dict).
140
- """
141
- try:
142
- if not image:
143
- return "", {"image_path": None, "caption": None}
144
- # Готовим path/URL
145
- img_path = image if isinstance(image, str) else getattr(image, "name", None) or image
146
- # Проверка кеша
147
- if cache and cache.get("image_path") == img_path and cache.get("caption"):
148
- return cache.get("caption"), cache
149
- # Иначе генерируем подпись
150
- caption = get_caption_for_image(img_path)
151
- new_cache = {"image_path": img_path, "caption": caption}
152
- return caption, new_cache
153
- except Exception as e:
154
- print("generate_and_cache_caption exception:", e)
155
- traceback.print_exc()
156
- return f"[Ошибка генерации подписи: {e}]", {"image_path": None, "caption": None}
157
 
158
- def chat_stream(image, user_message, history, cache: Dict[str, str]):
159
  """
160
- Основной generator для кнопки Отправить / submit:
161
- - Автоматически использует кеш подписи (если есть), иначе генерирует новую
162
- - Возвращает по мере стриминга (history, caption) — соответствие outputs=[chatbot, raw_caption]
163
  """
164
  history = history or []
165
- # Проверки входа
166
  if not user_message:
167
- # ничего не делаем, просто возвращаем текущее состояние
168
- yield history, (cache.get("caption") if cache else "")
169
  return
170
 
171
  if not image:
172
- # если нет картинки — говорим пользователю
173
  history.append([user_message, "Пожалуйста, загрузите изображение или выберите из галереи."])
174
- yield history, (cache.get("caption") if cache else "")
175
  return
176
 
177
- # получить путь и подпись (используем кеш, если совпадает)
178
- img_path = image if isinstance(image, str) else getattr(image, "name", None) or image
179
- if cache and cache.get("image_path") == img_path and cache.get("caption"):
180
- caption = cache.get("caption")
181
- else:
182
- caption = get_caption_for_image(img_path)
183
- # обновляем кеш локально (не gr.State, а для текущего запроса)
184
- cache = {"image_path": img_path, "caption": caption}
185
 
186
- # система-промпт даём контекст и просим указывать степень уверенности
187
  system_prompt = (
188
  "You are 'multimodal gpt-oss 120b', a helpful multimodal assistant. "
189
  "Use the provided 'More Detailed Caption' as authoritative visual context. "
@@ -195,14 +91,14 @@ def chat_stream(image, user_message, history, cache: Dict[str, str]):
195
  "Be concise unless asked for details."
196
  )
197
 
198
- # добавляем пользовательский запрос в историю (пустой ответ пока)
199
- history.append([user_message, ""]) # assistant текст будет заполняться по мере стрима
200
- # первый yield чтобы UI сразу отобразил user's message и подпись
201
  yield history, caption
202
 
203
  assistant_accum = ""
204
  try:
205
- # Запускаем стриминг вызов
206
  stream = llm.chat.completions.create(
207
  model="openai/gpt-oss-120b",
208
  messages=[
@@ -220,15 +116,13 @@ def chat_stream(image, user_message, history, cache: Dict[str, str]):
220
  if not piece:
221
  continue
222
  assistant_accum += piece
223
- # обновляем последний элемент истории (assistant part)
224
  history[-1][1] = assistant_accum
225
  yield history, caption
226
 
227
  except Exception as e:
228
- # Ошибка стриминга: попробуем получить финальный ответ без стрима, либо показать ошибку
229
- print("Streaming error:", e)
230
  traceback.print_exc()
231
- # Пытаемся сделать не-стриминг вызов (fallback)
232
  try:
233
  resp = llm.chat.completions.create(
234
  model="openai/gpt-oss-120b",
@@ -241,9 +135,7 @@ def chat_stream(image, user_message, history, cache: Dict[str, str]):
241
  max_tokens=1024,
242
  stream=False,
243
  )
244
- # нормализуем возможный формат ответа
245
  final_text = ""
246
- # SDK может вернуть object-like resp.choices[0].message.content
247
  if hasattr(resp, "choices"):
248
  try:
249
  final_text = getattr(resp.choices[0].message, "content", "") or getattr(resp.choices[0], "text", "") or ""
@@ -264,10 +156,10 @@ def chat_stream(image, user_message, history, cache: Dict[str, str]):
264
  history[-1][1] = f"[Ошибка LLM: {e2}]"
265
  yield history, caption
266
 
267
- # финальный yield (гарантируем окончательное состояние)
268
  yield history, caption
269
 
270
- # --------------------- Примеры для галереи (список строк) ---------------------
 
271
  EXAMPLE_IMAGES = [
272
  "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
273
  "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png",
@@ -281,21 +173,108 @@ css = """
281
  #title { text-align: center; }
282
  """
283
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  with gr.Blocks(css=css, analytics_enabled=False) as demo:
285
- gr.Markdown("<h2 id='title'>🖼️ multimodal gpt-oss 120b — визуальный чат</h2>")
 
286
  with gr.Row():
287
  with gr.Column(scale=4):
288
- image_input = gr.Image(label="Загрузите картинку (файл / drag-n-drop / камера)", type="filepath")
289
- raw_caption = gr.Textbox(label="More Detailed Caption (Florence-2)", interactive=False, lines=6)
290
- user_input = gr.Textbox(label="Вопрос по изображению", placeholder="Например: Что происходит на фото?")
291
- send_btn = gr.Button("Отправить")
292
- clear_btn = gr.Button("Очистить чат")
293
- gr.Markdown("**Галерея примеров (клик — подставить в загрузчик и получить подпись)**")
294
- # Исправлено: убран .style(), добавлены параметры в конструктор
 
 
 
 
 
 
 
 
 
 
295
  gallery = gr.Gallery(
296
- value=EXAMPLE_IMAGES,
297
- label="Примеры",
298
- columns=4,
299
  rows=1,
300
  show_label=False,
301
  height="auto",
@@ -305,40 +284,52 @@ with gr.Blocks(css=css, analytics_enabled=False) as demo:
305
  with gr.Column(scale=6):
306
  chatbot = gr.Chatbot(label="Чат с моделью", height=640)
307
 
308
- # gr.State для кеша подписи
309
- caption_cache = gr.State(value={"image_path": None, "caption": None})
 
 
310
 
311
- # обработчик клика по галерее: сразу подставляет картинку, генерирует подпись и обновляет кеш
312
- def on_gallery_select(elem, cache):
313
- # elem может быть строкой (URL) или список/tuple в некоторых версиях
314
- img = None
315
- if isinstance(elem, (list, tuple)):
316
- img = elem[0] if elem else None
317
- else:
318
- img = elem
319
- caption, new_cache = generate_and_cache_caption(img, cache)
320
- # возвращаем (image_input value, raw_caption, new_cache)
321
- return img, caption, new_cache
322
-
323
- gallery.select(on_gallery_select, inputs=[gallery, caption_cache], outputs=[image_input, raw_caption, caption_cache])
324
 
325
- # обработчик изменения image_input (загрузка своей картинки)
326
- def on_image_change(image, cache):
327
- caption, new_cache = generate_and_cache_caption(image, cache)
328
- return caption, new_cache
 
 
 
329
 
330
- image_input.change(on_image_change, inputs=[image_input, caption_cache], outputs=[raw_caption, caption_cache])
 
 
 
 
 
331
 
332
- # отправка (кнопка или Enter) — стриминг LLM, outputs: (chatbot, raw_caption)
333
- send_btn.click(chat_stream, inputs=[image_input, user_input, chatbot, caption_cache], outputs=[chatbot, raw_caption])
334
- user_input.submit(chat_stream, inputs=[image_input, user_input, chatbot, caption_cache], outputs=[chatbot, raw_caption])
 
 
335
 
336
- # очистка: сбрасываем чат и кеш
337
  def clear_all():
338
- return [], {"image_path": None, "caption": None}, ""
339
 
340
- clear_btn.click(clear_all, inputs=None, outputs=[chatbot, caption_cache, raw_caption])
 
 
 
 
341
 
342
- # Запуск (в HF Spaces не указывать share=True)
343
  if __name__ == "__main__":
344
- demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
 
 
 
 
 
1
  #!/usr/bin/env python3
2
  """
3
+ multimodal gpt-oss 120b — Gradio app с Florence-2 в браузере (WebGPU)
4
+
5
+ Что изменилось:
6
+ - Подпись к изображению генерим на стороне пользователя (WebGPU) через Transformers.js.
7
+ - Сервер больше не грузит Florence/torch.
8
+ - LLM остаётся через NVIDIA Integrate (OpenAI-compatible), как и было.
 
 
9
  """
10
 
11
  import os
12
  import traceback
13
+ from typing import Any, Optional, List
 
14
  import gradio as gr
 
15
  from openai import OpenAI
16
 
17
  # (опционально) локальный .env при локальном запуске
 
30
  "NV_API_KEY не задан. В Hugging Face Space зайди в Settings → Secrets и добавь NV_API_KEY."
31
  )
32
 
33
+ # OpenAI клиент для LLM
 
 
 
 
34
  llm = OpenAI(base_url=NV_BASE_URL, api_key=NV_API_KEY)
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  def _extract_text_from_stream_chunk(chunk: Any) -> str:
38
  """
39
  Универсально извлекает текстовые фрагменты из чанка стриминга LLM.
 
40
  """
41
  try:
 
42
  if hasattr(chunk, "choices"):
43
  choices = getattr(chunk, "choices")
44
  if choices:
45
  c0 = choices[0]
46
  delta = getattr(c0, "delta", None)
47
  if delta is not None:
 
48
  txt = getattr(delta, "reasoning_content", None) or getattr(delta, "content", None)
49
  if txt:
50
  return str(txt)
 
51
  text_attr = getattr(c0, "text", None)
52
  if text_attr:
53
  return str(text_attr)
 
54
  if isinstance(chunk, dict):
55
  choices = chunk.get("choices") or []
56
  if choices:
57
  delta = choices[0].get("delta") or {}
 
58
  return str(delta.get("content") or delta.get("reasoning_content") or choices[0].get("text") or "")
59
  except Exception:
60
  pass
61
  return ""
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ def chat_stream(image, user_message: str, history: Optional[List[List[str]]], caption_text: str):
65
  """
66
+ Основной generator для стриминга ответов LLM.
67
+ Теперь принимает caption_text прямо из браузера (WebGPU).
 
68
  """
69
  history = history or []
70
+
71
  if not user_message:
72
+ yield history, (caption_text or "")
 
73
  return
74
 
75
  if not image:
 
76
  history.append([user_message, "Пожалуйста, загрузите изображение или выберите из галереи."])
77
+ yield history, (caption_text or "")
78
  return
79
 
80
+ caption = caption_text or ""
 
 
 
 
 
 
 
81
 
82
+ # Системный промпт с подписью
83
  system_prompt = (
84
  "You are 'multimodal gpt-oss 120b', a helpful multimodal assistant. "
85
  "Use the provided 'More Detailed Caption' as authoritative visual context. "
 
91
  "Be concise unless asked for details."
92
  )
93
 
94
+ # Добавляем сообщение пользователя
95
+ history.append([user_message, ""])
96
+ # Показать подпись справа от чата (как и ��аньше)
97
  yield history, caption
98
 
99
  assistant_accum = ""
100
  try:
101
+ # Стриминг от LLM
102
  stream = llm.chat.completions.create(
103
  model="openai/gpt-oss-120b",
104
  messages=[
 
116
  if not piece:
117
  continue
118
  assistant_accum += piece
 
119
  history[-1][1] = assistant_accum
120
  yield history, caption
121
 
122
  except Exception as e:
123
+ print(f"Streaming error: {e}")
 
124
  traceback.print_exc()
125
+ # Fallback на не-стриминг запрос
126
  try:
127
  resp = llm.chat.completions.create(
128
  model="openai/gpt-oss-120b",
 
135
  max_tokens=1024,
136
  stream=False,
137
  )
 
138
  final_text = ""
 
139
  if hasattr(resp, "choices"):
140
  try:
141
  final_text = getattr(resp.choices[0].message, "content", "") or getattr(resp.choices[0], "text", "") or ""
 
156
  history[-1][1] = f"[Ошибка LLM: {e2}]"
157
  yield history, caption
158
 
 
159
  yield history, caption
160
 
161
+
162
+ # --------------------- Примеры для галереи ---------------------
163
  EXAMPLE_IMAGES = [
164
  "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
165
  "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png",
 
173
  #title { text-align: center; }
174
  """
175
 
176
+ # JS-функция: делает caption в браузере через WebGPU (Transformers.js)
177
+ WEBGPU_CAPTION_JS = r"""
178
+ async (image, use_client) => {
179
+ try {
180
+ if (!use_client) return null;
181
+
182
+ if (!('gpu' in navigator)) {
183
+ return "[WebGPU недоступен в браузере. Chrome/Edge 113+ (на Linux — chrome://flags/#enable-unsafe-webgpu), Safari TP.]";
184
+ }
185
+
186
+ // Извлекаем источник изображения из значения Gradio Image
187
+ const toHTMLImage = async (imgVal) => {
188
+ if (!imgVal) throw new Error("Нет изображения");
189
+ let src = null;
190
+ if (typeof imgVal === 'string') {
191
+ src = imgVal;
192
+ } else if (imgVal?.image) {
193
+ src = imgVal.image;
194
+ } else if (imgVal?.data) {
195
+ src = imgVal.data;
196
+ }
197
+ if (!src) throw new Error("Не удалось прочитать изображение");
198
+ const im = new Image();
199
+ im.crossOrigin = 'anonymous';
200
+ im.src = src;
201
+ await im.decode();
202
+ return im;
203
+ };
204
+
205
+ // Подтягиваем Transformers.js
206
+ const { pipeline, env } = await import("https://cdn.jsdelivr.net/npm/@xenova/[email protected]");
207
+
208
+ // Предпочесть WebGPU
209
+ env.allowRemoteModels = true;
210
+ env.useBrowserCache = true; // кэш в IndexedDB
211
+ env.backends.onnx.backend = 'webgpu';
212
+
213
+ // Инициализация один раз
214
+ if (!window.__webgpu_captioner) {
215
+ const candidates = [
216
+ 'Xenova/Florence-2-large-ft',
217
+ 'Xenova/Florence-2-base-ft'
218
+ ];
219
+ let lastErr = null;
220
+ for (const model of candidates) {
221
+ try {
222
+ window.__webgpu_captioner = await pipeline(
223
+ 'image-to-text',
224
+ model,
225
+ { device: 'webgpu', dtype: 'fp16', quantized: true }
226
+ );
227
+ break;
228
+ } catch (e) {
229
+ lastErr = e;
230
+ console.warn('Failed to load', model, e);
231
+ }
232
+ }
233
+ if (!window.__webgpu_captioner) throw lastErr || new Error("Не удалось инициализировать captioner");
234
+ }
235
+
236
+ const imgEl = await toHTMLImage(image);
237
+
238
+ // Для Florence-2 более детальная подпись через специальный токен задачи
239
+ const out = await window.__webgpu_captioner(imgEl, { text: '<MORE_DETAILED_CAPTION>' });
240
+
241
+ const text = Array.isArray(out)
242
+ ? (out[0]?.generated_text ?? out[0]?.text ?? JSON.stringify(out[0]))
243
+ : (out?.generated_text ?? out?.text ?? String(out));
244
+
245
+ return text;
246
+ } catch (e) {
247
+ return `[WebGPU caption error: ${'message' in e ? e.message : e}]`;
248
+ }
249
+ }
250
+ """
251
+
252
  with gr.Blocks(css=css, analytics_enabled=False) as demo:
253
+ gr.Markdown("<h2 id='title'>🖼️ multimodal gpt-oss 120b — визуальный чат (Florence в браузере / WebGPU)</h2>")
254
+
255
  with gr.Row():
256
  with gr.Column(scale=4):
257
+ image_input = gr.Image(label="Загрузите картинку", type="filepath")
258
+ use_webgpu = gr.Checkbox(value=True, label="Генерировать подпись к изображению в браузере (WebGPU)")
259
+ raw_caption = gr.Textbox(
260
+ label="More Detailed Caption (WebGPU)",
261
+ interactive=True,
262
+ lines=6,
263
+ placeholder="Подпись появится тут (если включён WebGPU-капшенер)"
264
+ )
265
+ user_input = gr.Textbox(
266
+ label="Вопрос по изображению",
267
+ placeholder="Например: Что происходит на фото?"
268
+ )
269
+ with gr.Row():
270
+ send_btn = gr.Button("Отправить", variant="primary")
271
+ clear_btn = gr.Button("Очистить чат")
272
+
273
+ gr.Markdown("**Галерея примеров (клик — подставить в загрузчик, подпись посчитается в браузере)**")
274
  gallery = gr.Gallery(
275
+ value=EXAMPLE_IMAGES,
276
+ label="Примеры",
277
+ columns=4,
278
  rows=1,
279
  show_label=False,
280
  height="auto",
 
284
  with gr.Column(scale=6):
285
  chatbot = gr.Chatbot(label="Чат с моделью", height=640)
286
 
287
+ # Клик по галерее: просто подставить изображение и очистить подпись (капшенер сработает на change)
288
+ def on_gallery_select(evt: gr.SelectData):
289
+ img = EXAMPLE_IMAGES[evt.index]
290
+ return img, ""
291
 
292
+ gallery.select(
293
+ on_gallery_select,
294
+ inputs=None,
295
+ outputs=[image_input, raw_caption]
296
+ )
 
 
 
 
 
 
 
 
297
 
298
+ # Изменение картинки: считаем подпись на клиенте (WebGPU)
299
+ image_input.change(
300
+ None,
301
+ inputs=[image_input, use_webgpu],
302
+ outputs=[raw_caption],
303
+ js=WEBGPU_CAPTION_JS
304
+ )
305
 
306
+ # Отправка сообщения: берём caption прямо из текстбокса (не генерим на сервере)
307
+ send_btn.click(
308
+ chat_stream,
309
+ inputs=[image_input, user_input, chatbot, raw_caption],
310
+ outputs=[chatbot, raw_caption]
311
+ )
312
 
313
+ user_input.submit(
314
+ chat_stream,
315
+ inputs=[image_input, user_input, chatbot, raw_caption],
316
+ outputs=[chatbot, raw_caption]
317
+ )
318
 
319
+ # Очистка чата + подписи
320
  def clear_all():
321
+ return [], ""
322
 
323
+ clear_btn.click(
324
+ clear_all,
325
+ inputs=None,
326
+ outputs=[chatbot, raw_caption]
327
+ )
328
 
329
+ # Запуск
330
  if __name__ == "__main__":
331
+ demo.launch(
332
+ server_name="0.0.0.0",
333
+ server_port=int(os.environ.get("PORT", 7860)),
334
+ share=False
335
+ )