zelk12 commited on
Commit
c858e0c
·
verified ·
1 Parent(s): a75f249

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +668 -173
app.py CHANGED
@@ -1,7 +1,7 @@
1
  import os
2
  import gradio as gr
3
  from gradio import ChatMessage
4
- from typing import Iterator
5
  import google.generativeai as genai
6
  import time # Import time module for potential debugging/delay
7
 
@@ -10,14 +10,20 @@ print("add API key")
10
 
11
  # get Gemini API Key from the environ variable
12
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
 
 
 
 
13
  genai.configure(api_key=GEMINI_API_KEY)
14
 
 
15
  print("add API key complete ")
16
  print("add model")
17
 
18
- used_model = "gemma-3-27b-it"
19
-
20
- # we will be using the Gemini 2.0 Flash model with Thinking capabilities
 
21
  model = genai.GenerativeModel(used_model)
22
 
23
  print(f"add model {used_model} complete\n")
@@ -29,176 +35,624 @@ def format_chat_history(messages: list) -> list:
29
  """
30
  formatted_history = []
31
  for message in messages:
32
- #print(f"t1 {message}")
33
- # Skip thinking messages (messages with metadata)
34
- #if not (message.get("role") == "assistant" and "metadata" in message):
35
- # print(f"t2 {message}")
36
- # formatted_history.append({
37
- # "role": "user" if message.get("role") == "user" else "assistant",
38
- # "parts": [message.get("content", "")]
39
- # })
40
-
41
- #print(f"t2 {message}")
42
-
43
- if message.get("role") == "user" :
44
  formatted_history.append({
45
- "role": "user",
46
- "parts": [message.get("content", "")]
47
  })
48
- elif message.get("role") == "assistant" :
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  formatted_history.append({
50
- "role": "model",
51
- "parts": [message.get("content", "")]
52
  })
53
-
54
- #print(f"t3 {formatted_history}")
 
 
 
 
 
 
 
55
  print("return formatted history")
56
  return formatted_history
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  def stream_gemini_response(user_message: str, messages: list) -> Iterator[list]:
59
  print("start model response stream")
60
  """
61
- Streams thoughts and response with conversation history support for text input only.
 
62
  """
63
- if not user_message.strip(): # Robust check: if text message is empty or whitespace
64
- messages.append(ChatMessage(role="assistant", content="Please provide a non-empty text message. Empty input is not allowed.")) # More specific message
65
- yield messages
66
- print("Empty text message")
 
 
67
  return
 
68
 
69
  try:
70
  print(f"\n=== New Request (Text) ===")
71
- print(f"User message: {user_message}")
72
 
73
- # Format chat history for Gemini
74
- chat_history = format_chat_history(messages)
 
75
 
76
- #print(f"hist {chat_history}")
77
-
78
- # Initialize Gemini chat
79
  print("Chat parameter")
80
  chat = model.start_chat(history=chat_history)
81
  print("Start response")
 
 
 
82
  response = chat.send_message(user_message, stream=True)
83
 
84
- # Initialize buffers and flags
85
- thought_buffer = ""
86
  response_buffer = ""
87
- #thinking_complete = False
88
-
89
- # Add initial thinking message
90
- #messages.append(
91
- # ChatMessage(
92
- # role="assistant",
93
- # content="",
94
- # metadata={"title": "⚙️ Thinking: *The thoughts produced by the model are experimental"}
95
- # )
96
- #)
97
-
98
- messages.append(
99
- ChatMessage(
100
- role="assistant",
101
- content=response_buffer
102
- )
103
- )
104
- #print(f"mes {messages} \n\nhis {chat_history}")
105
-
106
- thinking_complete = True
107
 
 
108
  for chunk in response:
109
- print("chunk start")
110
- parts = chunk.candidates[0].content.parts
111
- print("parts")
112
- current_chunk = parts[0].text
113
- print("current_chunk")
114
-
115
- print(f"\n=========\nparts len: {len(parts)}\n\nparts: {parts}\n\ncurrent chunk: {current_chunk}\n=========\n")
116
-
117
- if len(parts) == 2 and not thinking_complete:
118
- # Complete thought and start response
119
- thought_buffer += current_chunk
120
- print(f"\n=== Complete Thought ===\n{thought_buffer}")
121
-
122
- messages[-1] = ChatMessage(
123
- role="assistant",
124
- content=thought_buffer,
125
- metadata={"title": "⚙️ Thinking: *The thoughts produced by the model are experimental"}
126
- )
127
- yield messages
128
-
129
- # Start response
130
- response_buffer = parts[1].text
131
- print(f"\n=== Starting Response ===\n{response_buffer}")
132
-
133
- messages.append(
134
- ChatMessage(
135
  role="assistant",
136
  content=response_buffer
137
  )
138
- )
139
- thinking_complete = True
140
-
141
- elif thinking_complete:
142
- # Stream response
143
- response_buffer += current_chunk
144
- print(f"\n=== Response Chunk ===\n{current_chunk}")
145
-
146
- messages[-1] = ChatMessage(
147
- role="assistant",
148
- content=response_buffer
149
- )
150
-
151
- else:
152
- # Stream thinking
153
- thought_buffer += current_chunk
154
- print(f"\n=== Thinking Chunk ===\n{current_chunk}")
155
-
156
- messages[-1] = ChatMessage(
157
- role="assistant",
158
- content=thought_buffer,
159
- metadata={"title": "⚙️ Thinking: *The thoughts produced by the model are experimental"}
160
- )
161
- #time.sleep(0.05) #Optional: Uncomment this line to add a slight delay for debugging/visualization of streaming. Remove for final version
162
- print("Response end")
163
- yield messages
164
 
165
  print(f"\n=== Final Response ===\n{response_buffer}")
 
 
 
 
 
 
 
 
166
 
 
167
  except Exception as e:
168
- print(f"\n=== Error ===\n{str(e)}")
169
- messages.append(
170
- ChatMessage(
171
- role="assistant",
172
- content=f"I apologize, but I encountered an error: {str(e)}"
173
- )
174
- )
 
 
175
  yield messages
 
 
176
 
177
- def user_message(msg: str, history: list) -> tuple[str, list]:
178
- """Adds user message to chat history"""
 
179
  history.append(ChatMessage(role="user", content=msg))
180
- return "", history
 
181
 
182
  # Create the Gradio interface
183
- with gr.Blocks(theme=gr.themes.Soft(primary_hue="teal", secondary_hue="slate", neutral_hue="neutral")) as demo: # Using Soft theme with adjusted hues for a refined look
184
  gr.Markdown("# Chat with " + used_model)
185
 
186
-
187
  gr.HTML("""<a href="https://visitorbadge.io/status?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2Fzelk12%2FGemini-2">
188
  <img src="https://api.visitorbadge.io/api/combined?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2Fzelk12%2FGemini-2&countColor=%23263759" />
189
  </a>""")
190
 
191
-
 
 
 
192
  chatbot = gr.Chatbot(
 
193
  type="messages",
194
- label=used_model + " Chatbot (Streaming Output)", #Label now indicates streaming
195
  render_markdown=True,
196
  scale=1,
197
- editable="all",
198
- avatar_images=(None,"https://lh3.googleusercontent.com/oxz0sUBF0iYoN4VvhqWTmux-cxfD1rxuYkuFEfm1SFaseXEsjjE4Je_C_V3UQPuJ87sImQK3HfQ3RXiaRnQetjaZbjJJUkiPL5jFJ1WRl5FKJZYibUA=w214-h214-n-nu")
 
 
199
  )
200
 
201
- with gr.Row(equal_height=True):
202
  input_box = gr.Textbox(
203
  lines=1,
204
  label="Chat Message",
@@ -206,16 +660,23 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="teal", secondary_hue="slate", n
206
  scale=4
207
  )
208
 
209
- with gr.Column(scale=1):
210
- submit_button = gr.Button("Submit", scale=1)
211
- clear_button = gr.Button("Clear Chat", scale=1)
 
 
 
 
 
212
 
213
- with gr.Row(equal_height=True):
214
- test_button = gr.Button("undo", scale=1)
215
- test1_button = gr.Button("redo", scale=1)
216
- test2_button = gr.Button("regenerate", scale=1)
217
 
218
- # Add example prompts - removed file upload examples. Kept text focused examples.
 
 
 
 
 
 
219
  example_prompts = [
220
  ["Write a short poem about the sunset."],
221
  ["Explain the theory of relativity in simple terms."],
@@ -227,70 +688,104 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="teal", secondary_hue="slate", n
227
  gr.Examples(
228
  examples=example_prompts,
229
  inputs=input_box,
230
- label="Examples: Try these prompts to see Gemini's thinking!",
231
- examples_per_page=5 # Adjust as needed
232
  )
233
 
234
- # Set up event handlers
235
- msg_store = gr.State("") # Store for preserving user message
236
-
 
237
  input_box.submit(
238
- lambda msg: (msg, msg, ""), # Store message and clear input
239
- inputs=[input_box],
240
- outputs=[msg_store, input_box, input_box],
241
- queue=False
242
  ).then(
243
- user_message, # Add user message to chat
244
- inputs=[msg_store, chatbot],
245
- outputs=[input_box, chatbot],
246
- queue=False
247
  ).then(
248
- stream_gemini_response, # Generate and stream response
249
- inputs=[msg_store, chatbot],
250
- outputs=chatbot
251
  )
252
 
 
253
  submit_button.click(
254
- lambda msg: (msg, msg, ""), # Store message and clear input
255
- inputs=[input_box],
256
- outputs=[msg_store, input_box, input_box],
257
- queue=False
258
  ).then(
259
- user_message, # Add user message to chat
260
- inputs=[msg_store, chatbot],
261
- outputs=[input_box, chatbot],
262
- queue=False
263
  ).then(
264
- stream_gemini_response, # Generate and stream response
265
  inputs=[msg_store, chatbot],
266
- outputs=chatbot
267
  )
268
 
 
269
  clear_button.click(
270
- lambda: ([], "", ""),
271
- outputs=[chatbot, input_box, msg_store],
272
- queue=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  )
 
274
 
275
- gr.Markdown( # Description moved to the bottom - updated for text-only
276
  """
277
- <br><br><br> <!-- Add some vertical space -->
278
  ---
279
  ### About this Chatbot
280
- **Try out the example prompts below to see Gemini in action!**
281
  **Key Features:**
282
- * Powered by Google's **Gemini 2.0 Flash** model.
283
- * Supports **conversation history** for multi-turn chats.
284
- * Uses **streaming** for a more interactive experience.
285
  **Instructions:**
286
- 1. Type your message in the input box below or select an example.
287
- 2. Press Enter or click Submit to send.
288
- 3. Observe the chatbot's "Thinking" process followed by the final response.
289
- 4. Use the "Clear Chat" button to start a new conversation.
 
290
  """
291
  )
292
 
293
 
294
  # Launch the interface
295
  if __name__ == "__main__":
296
- demo.launch(debug=True)
 
 
 
 
 
1
  import os
2
  import gradio as gr
3
  from gradio import ChatMessage
4
+ from typing import Iterator, List, Tuple, Optional # Добавили типы
5
  import google.generativeai as genai
6
  import time # Import time module for potential debugging/delay
7
 
 
10
 
11
  # get Gemini API Key from the environ variable
12
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
13
+ # --- Безопасность: Добавим проверку наличия ключа ---
14
+ if not GEMINI_API_KEY:
15
+ raise ValueError("GEMINI_API_KEY environment variable not set!")
16
+ # ----------------------------------------------------
17
  genai.configure(api_key=GEMINI_API_KEY)
18
 
19
+
20
  print("add API key complete ")
21
  print("add model")
22
 
23
+ # --- Используем более подходящую модель для чата, если Gemma IT недоступна или неоптимальна ---
24
+ # used_model = "gemma-3-27b-it" # Gemma может не поддерживать role='model' явно, Gemini Pro лучше для чата
25
+ used_model = "gemini-1.5-flash-latest" # Рекомендуется для чата с историей
26
+ # ------------------------------------------------------------------------------------------
27
  model = genai.GenerativeModel(used_model)
28
 
29
  print(f"add model {used_model} complete\n")
 
35
  """
36
  formatted_history = []
37
  for message in messages:
38
+ # Пропускаем пустые сообщения или сообщения без контента (на всякий случай)
39
+ content = message.get("content")
40
+ if not content:
41
+ continue
42
+
43
+ role = message.get("role")
44
+ if role == "user":
 
 
 
 
 
45
  formatted_history.append({
46
+ "role": "user",
47
+ "parts": [content]
48
  })
49
+ elif role == "assistant":
50
+ # Пропускаем "thinking" сообщения, если они есть (хотя мы их убрали)
51
+ if not message.get("metadata"):
52
+ formatted_history.append({
53
+ "role": "model", # Gemini API ожидает 'model' для ответов ассистента
54
+ "parts": [content]
55
+ })
56
+ # Игнорируем другие возможные роли или типы сообщений
57
+ print(f"Formatted history length: {len(formatted_history)}")
58
+ print("return formatted history")
59
+ return formatted_history
60
+
61
+ # --- Новая функция для Undo ---
62
+ def undo_last(history: List[ChatMessage]) -> Tuple[List[ChatMessage], List[ChatMessage]]:
63
+ """
64
+ Removes the last user message and the last assistant response.
65
+ Returns the updated history and the pair that was removed (for potential redo).
66
+ """
67
+ print("\nAttempting Undo")
68
+ undone_pair = []
69
+ if len(history) >= 2:
70
+ # Проверяем, что последние два сообщения - это user и assistant
71
+ if history[-2].role == "user" and history[-1].role == "assistant":
72
+ print("Found user/assistant pair to undo.")
73
+ undone_pair = history[-2:]
74
+ history = history[:-2]
75
+ else:
76
+ print("Last two messages are not a user/assistant pair. Cannot undo.")
77
+ else:
78
+ print("Not enough messages in history to undo.")
79
+ return history, undone_pair
80
+ # -----------------------------
81
+
82
+ # --- Новая функция для Redo ---
83
+ def redo_last(history: List[ChatMessage], undone_pair: List[ChatMessage]) -> Tuple[List[ChatMessage], List[ChatMessage]]:
84
+ """
85
+ Restores the last undone user/assistant pair.
86
+ Returns the updated history and clears the undone pair state.
87
+ """
88
+ print("\nAttempting Redo")
89
+ if undone_pair:
90
+ print("Found undone pair to redo.")
91
+ history.extend(undone_pair)
92
+ undone_pair = [] # Очищаем состояние после восстановления
93
+ else:
94
+ print("No undone pair available to redo.")
95
+ return history, undone_pair
96
+ # -----------------------------
97
+
98
+ # --- Новая функция для Regenerate ---
99
+ def regenerate_response(history: List[ChatMessage]) -> Tuple[List[ChatMessage], Optional[str], List[ChatMessage]]:
100
+ """
101
+ Removes the last assistant response and returns the history ending
102
+ with the last user message, and the content of that user message.
103
+ Also returns an empty list to clear the undone_state.
104
+ """
105
+ print("\nAttempting Regenerate")
106
+ last_user_message_content = None
107
+ history_for_regen = history[:] # Создаем копию
108
+
109
+ # Ищем последний user message с конца
110
+ last_user_index = -1
111
+ for i in range(len(history_for_regen) - 1, -1, -1):
112
+ if history_for_regen[i].role == "user":
113
+ last_user_index = i
114
+ break
115
+
116
+ if last_user_index != -1:
117
+ print(f"Found last user message at index {last_user_index}")
118
+ last_user_message_content = history_for_regen[last_user_index].content
119
+ # Обрезаем историю до этого user message включительно
120
+ history_for_regen = history_for_regen[:last_user_index + 1]
121
+ print("Prepared history for regeneration.")
122
+ else:
123
+ print("No user message found to regenerate from.")
124
+ history_for_regen = history # Возвращаем оригинал, если не нашли
125
+
126
+ # Регенерация инвалидирует предыдущее undo
127
+ return history_for_regen, last_user_message_content, [] # Возвращаем историю и контент пользователя, очищаем undone_state
128
+ # ----------------------------------
129
+
130
+
131
+ def stream_gemini_response(user_message: str, messages: list) -> Iterator[list]:
132
+ print("start model response stream")
133
+ """
134
+ Streams response with conversation history support for text input only.
135
+ (Убрали логику "Thinking" для упрощения и совместимости)
136
+ """
137
+ # --- Добавили проверку на None или пустую строку ---
138
+ if not user_message or not user_message.strip():
139
+ print("Empty or None user message received. Skipping API call.")
140
+ # Можно добавить сообщение об ошибке или просто ничего не делать
141
+ # messages.append(ChatMessage(role="assistant", content="Please provide a non-empty text message."))
142
+ yield messages # Просто возвращаем текущее состояние
143
+ return
144
+ # -----------------------------------------------------
145
+
146
+ try:
147
+ print(f"\n=== New Request (Text) ===")
148
+ print(f"User message: {user_message}") # Может быть None при регенерации, но мы проверили выше
149
+
150
+ # Форматируем историю *перед* добавлением нового ответа ассистента
151
+ # Важно: история для API должна содержать только завершенные user/model пары
152
+ chat_history = format_chat_history(messages) # messages здесь уже содержит user message, с которого регенерируем, или новый
153
+
154
+ print("Chat parameter")
155
+ chat = model.start_chat(history=chat_history)
156
+ print("Start response")
157
+ # Отправляем *только* последнее сообщение пользователя (которое уже есть в messages)
158
+ # Gemini API берет историю из start_chat, а последнее сообщение из send_message
159
+ # Важно: user_message здесь - это то, на что мы генерируем ответ
160
+ response = chat.send_message(user_message, stream=True)
161
+
162
+ response_buffer = ""
163
+ # Добавляем пустое сообщение ассистента, которое будем обновлять
164
+ messages.append(ChatMessage(role="assistant", content=""))
165
+ yield messages # Показываем пустой контейнер для ответа
166
+
167
+ print("Streaming response...")
168
+ for chunk in response:
169
+ # --- Обработка возможных ошибок в чанке ---
170
+ try:
171
+ # Проверяем наличие candidates и parts
172
+ if chunk.candidates and chunk.candidates[0].content and chunk.candidates[0].content.parts:
173
+ current_chunk_text = chunk.candidates[0].content.parts[0].text
174
+ # print(f"Chunk: '{current_chunk_text}'") # Отладка чанков
175
+ response_buffer += current_chunk_text
176
+ messages[-1] = ChatMessage(
177
+ role="assistant",
178
+ content=response_buffer
179
+ )
180
+ else:
181
+ # Обработка случая, когда структура чанка неожиданная
182
+ print(f"Warning: Unexpected chunk structure: {chunk}")
183
+ # Можно добавить запасной вариант или пропустить чанк
184
+ if hasattr(chunk, 'text'): # Иногда API возвращает просто text
185
+ response_buffer += chunk.text
186
+ messages[-1] = ChatMessage(role="assistant", content=response_buffer)
187
+
188
+ except (AttributeError, IndexError, ValueError) as chunk_err:
189
+ # Ловим ошибки доступа к атрибутам/индексам или другие проблемы чанка
190
+ print(f"Error processing chunk: {chunk_err}")
191
+ print(f"Problematic chunk data: {chunk}")
192
+ # Можно прервать или продолжить, добавив сообщение об ошибке
193
+ messages[-1] = ChatMessage(
194
+ role="assistant",
195
+ content=response_buffer + f"\n\n[Ошибка обработки части ответа: {chunk_err}]"
196
+ )
197
+ yield messages
198
+ return # Прерываем стриминг при ошибке в чанке
199
+ # -------------------------------------------
200
+
201
+ # time.sleep(0.05) # Раскомментируйте для замедления стриминга (отладка)
202
+ yield messages # Отправляем обновленное сообщение в интерфейс
203
+
204
+ print(f"\n=== Final Response ===\n{response_buffer}")
205
+ # Проверка на пустой финальный ответ (если модель ничего не вернула)
206
+ if not response_buffer.strip() and len(messages) > 0 and messages[-1].role == "assistant":
207
+ messages[-1] = ChatMessage(
208
+ role="assistant",
209
+ content="[Модель не дала ответа]"
210
+ )
211
+ yield messages
212
+
213
+
214
+ # --- Улучшенная обработка ошибок API ---
215
+ except Exception as e:
216
+ error_message = f"Произошла ошибка при обращении к Gemini API: {str(e)}"
217
+ print(f"\n=== Error ===\n{error_message}")
218
+ # Пытаемся добавить сообщение об ошибке в чат, если возможно
219
+ if messages and isinstance(messages[-1], ChatMessage) and messages[-1].role == "assistant" and messages[-1].content == "":
220
+ # Если последний элемент - пустое сообщение ассистента, заменяем его ошибкой
221
+ messages[-1] = ChatMessage(role="assistant", content=error_message)
222
+ else:
223
+ # Иначе добавляем новое сообщение с ошибкой
224
+ messages.append(ChatMessage(role="assistant", content=error_message))
225
+ yield messages
226
+ # -------------------------------------
227
+
228
+
229
+ def user_message(msg: str, history: list) -> tuple[str, list, list]:
230
+ """Adds user message to chat history and clears the undone state."""
231
+ print(f"\nUser message added: '{msg}'")
232
+ history.append(ChatMessage(role="user", content=msg))
233
+ # Новое сообщение пользователя инвалидирует предыдущее undo
234
+ return "", history, [] # Возвращаем пустую строку, историю и пустой undone_state
235
+
236
+ # Create the Gradio interface
237
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="teal", secondary_hue="slate", neutral_hue="neutral")) as demo:
238
+ gr.Markdown("# Chat with " + used_model)
239
+
240
+ gr.HTML("""<a href="https://visitorbadge.io/status?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2Fzelk12%2FGemini-2">
241
+ <img src="https://api.visitorbadge.io/api/combined?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2Fzelk12%2FGemini-2&countColor=%23263759" />
242
+ </a>""")
243
+
244
+ # --- Добавляем состояние для Undo/Redo ---
245
+ undone_state = gr.State([])
246
+ # --------------------------------------
247
+
248
+ chatbot = gr.Chatbot(
249
+ [], # Начинаем с пустого списка
250
+ type="messages",
251
+ label=used_model + " Chatbot (Streaming Output)",
252
+ render_markdown=True,
253
+ scale=1,
254
+ # editable="all", # editable="all" может мешать undo/redo, лучше отключить или user
255
+ editable=False,
256
+ avatar_images=(None,"https://lh3.googleusercontent.com/oxz0sUBF0iYoN4VvhqWTmux-cxfD1rxuYkuFEfm1SFaseXEsjjE4Je_C_V3UQPuJ87sImQK3HfQ3RXiaRnQetjaZbjJJUkiPL5jFJ1WRl5FKJZYibUA=w214-h214-n-nu"),
257
+ height=600 # Задаем высоту для лучшего вида
258
+ )
259
+
260
+ with gr.Row(equal_height=False): # equal_height=False может быть лучше для кнопок разной высоты
261
+ input_box = gr.Textbox(
262
+ lines=1,
263
+ label="Chat Message",
264
+ placeholder="Type your message here...",
265
+ scale=4
266
+ )
267
+
268
+ # --- Группируем кнопки управления ---
269
+ with gr.Column(scale=1, min_width=150): # Даем минимальную ширину колонке с кнопками
270
+ submit_button = gr.Button("Submit", scale=1, variant="primary") # Выделяем основную кнопку
271
+ with gr.Row():
272
+ undo_button = gr.Button("Undo", scale=1)
273
+ redo_button = gr.Button("Redo", scale=1)
274
+ regenerate_button = gr.Button("Regenerate", scale=1)
275
+ clear_button = gr.Button("Clear Chat", scale=1)
276
+
277
+
278
+ # --- Переименовали кнопки для ясности ---
279
+ # undo_button = test_button
280
+ # redo_button = test1_button
281
+ # regenerate_button = test2_button
282
+ # ----------------------------------------
283
+
284
+ # Add example prompts
285
+ example_prompts = [
286
+ ["Write a short poem about the sunset."],
287
+ ["Explain the theory of relativity in simple terms."],
288
+ ["If a train leaves Chicago at 6am traveling at 60mph, and another train leaves New York at 8am traveling at 80mph, at what time will they meet?"],
289
+ ["Summarize the plot of Hamlet."],
290
+ ["Write a haiku about a cat."]
291
+ ]
292
+
293
+ gr.Examples(
294
+ examples=example_prompts,
295
+ inputs=input_box,
296
+ label="Examples: Try these prompts!", # Убрали про thinking, т.к. убрали его вывод
297
+ examples_per_page=5
298
+ )
299
+
300
+ # --- Обновленные обработчики событий ---
301
+ msg_store = gr.State("") # Store for preserving user message during processing
302
+
303
+ # --- Обработчик для Enter в input_box ---
304
+ input_box.submit(
305
+ user_message, # 1. Добавить сообщение пользователя в историю, очистить undone_state
306
+ inputs=[input_box, chatbot],
307
+ outputs=[input_box, chatbot, undone_state] # input_box очищается первым возвращаемым значением ""
308
+ ).then(
309
+ lambda x: (x, x), # 2. Скопировать сообщение из history[-1].content в msg_store для stream_gemini_response
310
+ inputs=[chatbot], # Берем историю, где последнее сообщение - пользовательское
311
+ outputs=[msg_store, msg_store], # Копируем контент последнего сообщения (пользователя)
312
+ fn=lambda history: (history[-1].content, history[-1].content) if history and history[-1].role == "user" else (None, None)
313
+ ).then(
314
+ stream_gemini_response, # 3. Сгенерировать и стримить ответ
315
+ inputs=[msg_store, chatbot], # Используем сохраненное сообщение и обновленную историю
316
+ outputs=[chatbot]
317
+ )
318
+
319
+ # --- Обработчик для кнопки Submit ---
320
+ submit_button.click(
321
+ user_message, # 1. Добавить сообщение пользователя, очистить undone_state
322
+ inputs=[input_box, chatbot],
323
+ outputs=[input_box, chatbot, undone_state]
324
+ ).then(
325
+ lambda x: (x, x), # 2. Скопировать сообщение пользователя в msg_store
326
+ inputs=[chatbot],
327
+ outputs=[msg_store, msg_store],
328
+ fn=lambda history: (history[-1].content, history[-1].content) if history and history[-1].role == "user" else (None, None)
329
+ ).then(
330
+ stream_gemini_response, # 3. Сгенерировать ответ
331
+ inputs=[msg_store, chatbot],
332
+ outputs=[chatbot]
333
+ )
334
+
335
+ # --- Обработчик для кнопки Clear ---
336
+ clear_button.click(
337
+ lambda: ([], "", [], ""), # Очищаем chatbot, input_box, msg_store, undone_state
338
+ outputs=[chatbot, input_box, msg_store, undone_state],
339
+ queue=False # Очистка быстрая, очередь не нужна
340
+ )
341
+
342
+ # --- Обработчик для кнопки Undo ---
343
+ undo_button.click(
344
+ undo_last,
345
+ inputs=[chatbot],
346
+ outputs=[chatbot, undone_state], # Обновляем историю и сохраняем удаленную пару
347
+ queue=False # Действие быстрое
348
+ )
349
+
350
+ # --- Обработчик для кнопки Redo ---
351
+ redo_button.click(
352
+ redo_last,
353
+ inputs=[chatbot, undone_state], # Нужна история и сохраненная пара
354
+ outputs=[chatbot, undone_state], # Обновляем историю и очищаем сохраненную пару
355
+ queue=False # Действие быстрое
356
+ )
357
+
358
+ # --- Обработчик для кнопки Regenerate ---
359
+ regenerate_button.click(
360
+ regenerate_response, # 1. Подготовить историю и получить сообщение пользователя, очистить undone_state
361
+ inputs=[chatbot],
362
+ outputs=[chatbot, msg_store, undone_state] # Обновляем историю, помещаем user msg в msg_store, очищаем undone
363
+ ).then(
364
+ stream_gemini_response, # 2. Сгенерировать новый ответ на user msg из msg_store
365
+ inputs=[msg_store, chatbot], # Используем user msg из msg_store и подготовленную историю
366
+ outputs=[chatbot]
367
+ )
368
+ # -------------------------------------
369
+
370
+ gr.Markdown(
371
+ """
372
+ <br><br><br>
373
+ ---
374
+ ### About this Chatbot
375
+ **Try out the example prompts below!**
376
+ **Key Features:**
377
+ * Powered by Google's **""" + used_model + """** model.
378
+ * Supports **conversation history**, **undo**, **redo**, and **regenerate**.
379
+ * Uses **streaming** for responses.
380
+ **Instructions:**
381
+ 1. Type your message or select an example.
382
+ 2. Press Enter or click Submit.
383
+ 3. Use Undo/Redo to correct mistakes or explore alternatives.
384
+ 4. Use Regenerate to get a different response to your last message.
385
+ 5. Use Clear Chat to start over.
386
+ """
387
+ )
388
+
389
+
390
+ # Launch the interface
391
+ if __name__ == "__main__":
392
+ # --- Добавим возможность запускать на публичном URL через share=True ---
393
+ # demo.launch(debug=True) # Для локальной отладки
394
+ demo.launch(share=True) # Для получения публичной ссылки (если нужно)
395
+ # demo.launch() # Стандартный локальный запуск
396
+ # -----------------------------------------------------------------import os
397
+ import gradio as gr
398
+ from gradio import ChatMessage
399
+ from typing import Iterator, List, Tuple, Optional # Добавили типы
400
+ import google.generativeai as genai
401
+ import time # Import time module for potential debugging/delay
402
+
403
+ print("import library complete")
404
+ print("add API key")
405
+
406
+ # get Gemini API Key from the environ variable
407
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
408
+ # --- Безопасность: Добавим проверку наличия ключа ---
409
+ if not GEMINI_API_KEY:
410
+ raise ValueError("GEMINI_API_KEY environment variable not set!")
411
+ # ----------------------------------------------------
412
+ genai.configure(api_key=GEMINI_API_KEY)
413
+
414
+
415
+ print("add API key complete ")
416
+ print("add model")
417
+
418
+ # --- Используем более подходящую модель для чата, если Gemma IT недоступна или неоптимальна ---
419
+ # used_model = "gemma-3-27b-it" # Gemma может не поддерживать role='model' явно, Gemini Pro лучше для чата
420
+ used_model = "gemini-1.5-flash-latest" # Рекомендуется для чата с историей
421
+ # ------------------------------------------------------------------------------------------
422
+ model = genai.GenerativeModel(used_model)
423
+
424
+ print(f"add model {used_model} complete\n")
425
+
426
+ def format_chat_history(messages: list) -> list:
427
+ print("\nstart format history")
428
+ """
429
+ Formats the chat history into a structure Gemini can understand
430
+ """
431
+ formatted_history = []
432
+ for message in messages:
433
+ # Пропускаем пустые сообщения или сообщения без контента (на всякий случай)
434
+ content = message.get("content")
435
+ if not content:
436
+ continue
437
+
438
+ role = message.get("role")
439
+ if role == "user":
440
  formatted_history.append({
441
+ "role": "user",
442
+ "parts": [content]
443
  })
444
+ elif role == "assistant":
445
+ # Пропускаем "thinking" сообщения, если они есть (хотя мы их убрали)
446
+ if not message.get("metadata"):
447
+ formatted_history.append({
448
+ "role": "model", # Gemini API ожидает 'model' для ответов ассистента
449
+ "parts": [content]
450
+ })
451
+ # Игнорируем другие возможные роли или типы сообщений
452
+ print(f"Formatted history length: {len(formatted_history)}")
453
  print("return formatted history")
454
  return formatted_history
455
 
456
+ # --- Новая функция для Undo ---
457
+ def undo_last(history: List[ChatMessage]) -> Tuple[List[ChatMessage], List[ChatMessage]]:
458
+ """
459
+ Removes the last user message and the last assistant response.
460
+ Returns the updated history and the pair that was removed (for potential redo).
461
+ """
462
+ print("\nAttempting Undo")
463
+ undone_pair = []
464
+ if len(history) >= 2:
465
+ # Проверяем, что последние два сообщения - это user и assistant
466
+ if history[-2].role == "user" and history[-1].role == "assistant":
467
+ print("Found user/assistant pair to undo.")
468
+ undone_pair = history[-2:]
469
+ history = history[:-2]
470
+ else:
471
+ print("Last two messages are not a user/assistant pair. Cannot undo.")
472
+ else:
473
+ print("Not enough messages in history to undo.")
474
+ return history, undone_pair
475
+ # -----------------------------
476
+
477
+ # --- Новая функция для Redo ---
478
+ def redo_last(history: List[ChatMessage], undone_pair: List[ChatMessage]) -> Tuple[List[ChatMessage], List[ChatMessage]]:
479
+ """
480
+ Restores the last undone user/assistant pair.
481
+ Returns the updated history and clears the undone pair state.
482
+ """
483
+ print("\nAttempting Redo")
484
+ if undone_pair:
485
+ print("Found undone pair to redo.")
486
+ history.extend(undone_pair)
487
+ undone_pair = [] # Очищаем состояние после восстановления
488
+ else:
489
+ print("No undone pair available to redo.")
490
+ return history, undone_pair
491
+ # -----------------------------
492
+
493
+ # --- Новая функция для Regenerate ---
494
+ def regenerate_response(history: List[ChatMessage]) -> Tuple[List[ChatMessage], Optional[str], List[ChatMessage]]:
495
+ """
496
+ Removes the last assistant response and returns the history ending
497
+ with the last user message, and the content of that user message.
498
+ Also returns an empty list to clear the undone_state.
499
+ """
500
+ print("\nAttempting Regenerate")
501
+ last_user_message_content = None
502
+ history_for_regen = history[:] # Создаем копию
503
+
504
+ # Ищем последний user message с конца
505
+ last_user_index = -1
506
+ for i in range(len(history_for_regen) - 1, -1, -1):
507
+ if history_for_regen[i].role == "user":
508
+ last_user_index = i
509
+ break
510
+
511
+ if last_user_index != -1:
512
+ print(f"Found last user message at index {last_user_index}")
513
+ last_user_message_content = history_for_regen[last_user_index].content
514
+ # Обрезаем историю до этого user message включительно
515
+ history_for_regen = history_for_regen[:last_user_index + 1]
516
+ print("Prepared history for regeneration.")
517
+ else:
518
+ print("No user message found to regenerate from.")
519
+ history_for_regen = history # Возвращаем оригинал, если не нашли
520
+
521
+ # Регенерация инвалидирует предыдущее undo
522
+ return history_for_regen, last_user_message_content, [] # Возвращаем историю и контент пользователя, очищаем undone_state
523
+ # ----------------------------------
524
+
525
+
526
  def stream_gemini_response(user_message: str, messages: list) -> Iterator[list]:
527
  print("start model response stream")
528
  """
529
+ Streams response with conversation history support for text input only.
530
+ (Убрали логику "Thinking" для упрощения и совместимости)
531
  """
532
+ # --- Добавили проверку на None или пустую строку ---
533
+ if not user_message or not user_message.strip():
534
+ print("Empty or None user message received. Skipping API call.")
535
+ # Можно добавить сообщение об ошибке или просто ничего не делать
536
+ # messages.append(ChatMessage(role="assistant", content="Please provide a non-empty text message."))
537
+ yield messages # Просто возвращаем текущее состояние
538
  return
539
+ # -----------------------------------------------------
540
 
541
  try:
542
  print(f"\n=== New Request (Text) ===")
543
+ print(f"User message: {user_message}") # Может быть None при регенерации, но мы проверили выше
544
 
545
+ # Форматируем историю *перед* добавлением нового ответа ассистента
546
+ # Важно: история для API должна содержать только завершенные user/model пары
547
+ chat_history = format_chat_history(messages) # messages здесь уже содержит user message, с которого регенерируем, или новый
548
 
 
 
 
549
  print("Chat parameter")
550
  chat = model.start_chat(history=chat_history)
551
  print("Start response")
552
+ # Отправляем *только* последнее сообщение пользователя (которое уже есть в messages)
553
+ # Gemini API берет историю из start_chat, а последнее сообщение из send_message
554
+ # Важно: user_message здесь - это то, на что мы генерируем ответ
555
  response = chat.send_message(user_message, stream=True)
556
 
 
 
557
  response_buffer = ""
558
+ # Добавляем пустое сообщение ассистента, которое будем обновлять
559
+ messages.append(ChatMessage(role="assistant", content=""))
560
+ yield messages # Показываем пустой контейнер для ответа
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
 
562
+ print("Streaming response...")
563
  for chunk in response:
564
+ # --- Обработка возможных ошибок в чанке ---
565
+ try:
566
+ # Проверяем наличие candidates и parts
567
+ if chunk.candidates and chunk.candidates[0].content and chunk.candidates[0].content.parts:
568
+ current_chunk_text = chunk.candidates[0].content.parts[0].text
569
+ # print(f"Chunk: '{current_chunk_text}'") # Отладка чанков
570
+ response_buffer += current_chunk_text
571
+ messages[-1] = ChatMessage(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
  role="assistant",
573
  content=response_buffer
574
  )
575
+ else:
576
+ # Обработка случая, когда структура чанка неожиданная
577
+ print(f"Warning: Unexpected chunk structure: {chunk}")
578
+ # Можно добавить запасной вариант или пропустить чанк
579
+ if hasattr(chunk, 'text'): # Иногда API возвращает просто text
580
+ response_buffer += chunk.text
581
+ messages[-1] = ChatMessage(role="assistant", content=response_buffer)
582
+
583
+ except (AttributeError, IndexError, ValueError) as chunk_err:
584
+ # Ловим ошибки доступа к атрибутам/индексам или другие проблемы чанка
585
+ print(f"Error processing chunk: {chunk_err}")
586
+ print(f"Problematic chunk data: {chunk}")
587
+ # Можно прервать или продол��ить, добавив сообщение об ошибке
588
+ messages[-1] = ChatMessage(
589
+ role="assistant",
590
+ content=response_buffer + f"\n\n[Ошибка обработки части ответа: {chunk_err}]"
591
+ )
592
+ yield messages
593
+ return # Прерываем стриминг при ошибке в чанке
594
+ # -------------------------------------------
595
+
596
+ # time.sleep(0.05) # Раскомментируйте для замедления стриминга (отладка)
597
+ yield messages # Отправляем обновленное сообщение в интерфейс
 
 
 
598
 
599
  print(f"\n=== Final Response ===\n{response_buffer}")
600
+ # Проверка на пустой финальный ответ (если модель ничего не вернула)
601
+ if not response_buffer.strip() and len(messages) > 0 and messages[-1].role == "assistant":
602
+ messages[-1] = ChatMessage(
603
+ role="assistant",
604
+ content="[Модель не дала ответа]"
605
+ )
606
+ yield messages
607
+
608
 
609
+ # --- Улучшенная обработка ошибок API ---
610
  except Exception as e:
611
+ error_message = f"Произошла ошибка при обращении к Gemini API: {str(e)}"
612
+ print(f"\n=== Error ===\n{error_message}")
613
+ # Пытаемся добавить сообщение об ошибке в чат, если возможно
614
+ if messages and isinstance(messages[-1], ChatMessage) and messages[-1].role == "assistant" and messages[-1].content == "":
615
+ # Если последний элемент - пустое сообщение ассистента, заменяем его ошибкой
616
+ messages[-1] = ChatMessage(role="assistant", content=error_message)
617
+ else:
618
+ # Иначе добавляем новое сообщение с ошибкой
619
+ messages.append(ChatMessage(role="assistant", content=error_message))
620
  yield messages
621
+ # -------------------------------------
622
+
623
 
624
+ def user_message(msg: str, history: list) -> tuple[str, list, list]:
625
+ """Adds user message to chat history and clears the undone state."""
626
+ print(f"\nUser message added: '{msg}'")
627
  history.append(ChatMessage(role="user", content=msg))
628
+ # Новое сообщение пользователя инвалидирует предыдущее undo
629
+ return "", history, [] # Возвращаем пустую строку, историю и пустой undone_state
630
 
631
  # Create the Gradio interface
632
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="teal", secondary_hue="slate", neutral_hue="neutral")) as demo:
633
  gr.Markdown("# Chat with " + used_model)
634
 
 
635
  gr.HTML("""<a href="https://visitorbadge.io/status?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2Fzelk12%2FGemini-2">
636
  <img src="https://api.visitorbadge.io/api/combined?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2Fzelk12%2FGemini-2&countColor=%23263759" />
637
  </a>""")
638
 
639
+ # --- Добавляем состояние для Undo/Redo ---
640
+ undone_state = gr.State([])
641
+ # --------------------------------------
642
+
643
  chatbot = gr.Chatbot(
644
+ [], # Начинаем с пустого списка
645
  type="messages",
646
+ label=used_model + " Chatbot (Streaming Output)",
647
  render_markdown=True,
648
  scale=1,
649
+ # editable="all", # editable="all" может мешать undo/redo, лучше отключить или user
650
+ editable=False,
651
+ avatar_images=(None,"https://lh3.googleusercontent.com/oxz0sUBF0iYoN4VvhqWTmux-cxfD1rxuYkuFEfm1SFaseXEsjjE4Je_C_V3UQPuJ87sImQK3HfQ3RXiaRnQetjaZbjJJUkiPL5jFJ1WRl5FKJZYibUA=w214-h214-n-nu"),
652
+ height=600 # Задаем высоту для лучшего вида
653
  )
654
 
655
+ with gr.Row(equal_height=False): # equal_height=False может быть лучше для кнопок разной высоты
656
  input_box = gr.Textbox(
657
  lines=1,
658
  label="Chat Message",
 
660
  scale=4
661
  )
662
 
663
+ # --- Группируем кнопки управления ---
664
+ with gr.Column(scale=1, min_width=150): # Даем минимальную ширину колонке с кнопками
665
+ submit_button = gr.Button("Submit", scale=1, variant="primary") # Выделяем основную кнопку
666
+ with gr.Row():
667
+ undo_button = gr.Button("Undo", scale=1)
668
+ redo_button = gr.Button("Redo", scale=1)
669
+ regenerate_button = gr.Button("Regenerate", scale=1)
670
+ clear_button = gr.Button("Clear Chat", scale=1)
671
 
 
 
 
 
672
 
673
+ # --- Переименовали кнопки для ясности ---
674
+ # undo_button = test_button
675
+ # redo_button = test1_button
676
+ # regenerate_button = test2_button
677
+ # ----------------------------------------
678
+
679
+ # Add example prompts
680
  example_prompts = [
681
  ["Write a short poem about the sunset."],
682
  ["Explain the theory of relativity in simple terms."],
 
688
  gr.Examples(
689
  examples=example_prompts,
690
  inputs=input_box,
691
+ label="Examples: Try these prompts!", # Убрали про thinking, т.к. убрали его вывод
692
+ examples_per_page=5
693
  )
694
 
695
+ # --- Обновленные обработчики событий ---
696
+ msg_store = gr.State("") # Store for preserving user message during processing
697
+
698
+ # --- Обработчик для Enter в input_box ---
699
  input_box.submit(
700
+ user_message, # 1. Добавить сообщение пользователя в историю, очистить undone_state
701
+ inputs=[input_box, chatbot],
702
+ outputs=[input_box, chatbot, undone_state] # input_box очищается первым возвращаемым значением ""
 
703
  ).then(
704
+ lambda x: (x, x), # 2. Скопировать сообщение из history[-1].content в msg_store для stream_gemini_response
705
+ inputs=[chatbot], # Берем историю, где последнее сообщение - пользовательское
706
+ outputs=[msg_store, msg_store], # Копируем контент последнего сообщения (пользователя)
707
+ fn=lambda history: (history[-1].content, history[-1].content) if history and history[-1].role == "user" else (None, None)
708
  ).then(
709
+ stream_gemini_response, # 3. Сгенерировать и стримить ответ
710
+ inputs=[msg_store, chatbot], # Используем сохраненное сообщение и обновленную историю
711
+ outputs=[chatbot]
712
  )
713
 
714
+ # --- Обработчик для кнопки Submit ---
715
  submit_button.click(
716
+ user_message, # 1. Добавить сообщение пользователя, очистить undone_state
717
+ inputs=[input_box, chatbot],
718
+ outputs=[input_box, chatbot, undone_state]
 
719
  ).then(
720
+ lambda x: (x, x), # 2. Скопировать сообщение пользователя в msg_store
721
+ inputs=[chatbot],
722
+ outputs=[msg_store, msg_store],
723
+ fn=lambda history: (history[-1].content, history[-1].content) if history and history[-1].role == "user" else (None, None)
724
  ).then(
725
+ stream_gemini_response, # 3. Сгенер��ровать ответ
726
  inputs=[msg_store, chatbot],
727
+ outputs=[chatbot]
728
  )
729
 
730
+ # --- Обработчик для кнопки Clear ---
731
  clear_button.click(
732
+ lambda: ([], "", [], ""), # Очищаем chatbot, input_box, msg_store, undone_state
733
+ outputs=[chatbot, input_box, msg_store, undone_state],
734
+ queue=False # Очистка быстрая, очередь не нужна
735
+ )
736
+
737
+ # --- Обработчик для кнопки Undo ---
738
+ undo_button.click(
739
+ undo_last,
740
+ inputs=[chatbot],
741
+ outputs=[chatbot, undone_state], # Обновляем историю и сохраняем удаленную пару
742
+ queue=False # Действие быстрое
743
+ )
744
+
745
+ # --- Обработчик для кнопки Redo ---
746
+ redo_button.click(
747
+ redo_last,
748
+ inputs=[chatbot, undone_state], # Нужна история и сохраненная пара
749
+ outputs=[chatbot, undone_state], # Обновляем историю и очищаем сохраненную пару
750
+ queue=False # Действие быстрое
751
+ )
752
+
753
+ # --- Обработчик для кнопки Regenerate ---
754
+ regenerate_button.click(
755
+ regenerate_response, # 1. Подготовить историю и получить сообщение пользователя, очистить undone_state
756
+ inputs=[chatbot],
757
+ outputs=[chatbot, msg_store, undone_state] # Обновляем историю, помещаем user msg в msg_store, очищаем undone
758
+ ).then(
759
+ stream_gemini_response, # 2. Сгенерировать новый ответ на user msg из msg_store
760
+ inputs=[msg_store, chatbot], # Используем user msg из msg_store и подготовленную историю
761
+ outputs=[chatbot]
762
  )
763
+ # -------------------------------------
764
 
765
+ gr.Markdown(
766
  """
767
+ <br><br><br>
768
  ---
769
  ### About this Chatbot
770
+ **Try out the example prompts below!**
771
  **Key Features:**
772
+ * Powered by Google's **""" + used_model + """** model.
773
+ * Supports **conversation history**, **undo**, **redo**, and **regenerate**.
774
+ * Uses **streaming** for responses.
775
  **Instructions:**
776
+ 1. Type your message or select an example.
777
+ 2. Press Enter or click Submit.
778
+ 3. Use Undo/Redo to correct mistakes or explore alternatives.
779
+ 4. Use Regenerate to get a different response to your last message.
780
+ 5. Use Clear Chat to start over.
781
  """
782
  )
783
 
784
 
785
  # Launch the interface
786
  if __name__ == "__main__":
787
+ # --- Добавим возможность запускать на публичном URL через share=True ---
788
+ # demo.launch(debug=True) # Для локальной отладки
789
+ demo.launch(share=True) # Для получения публичной ссылки (если нужно)
790
+ # demo.launch() # Стандартный локальный запуск
791
+ # -----------------------------------------------------------------