huijio commited on
Commit
8a406cf
Β·
verified Β·
1 Parent(s): 3662117

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -308
app.py CHANGED
@@ -2,218 +2,13 @@ import gradio as gr
2
  import requests
3
  from html import escape
4
  import re
5
- from typing import List, Dict
6
- import time
7
 
8
  # API configuration
9
  API_URL = "https://aham2api-3.onrender.com/v1/chat/completions"
10
  MODELS_URL = "https://aham2api-3.onrender.com/v1/models"
11
 
12
- # Modern CSS with dark/light mode support
13
- css = """
14
- :root {
15
- --primary: #6366f1;
16
- --primary-dark: #4f46e5;
17
- --bg-color: #ffffff;
18
- --text-color: #111827;
19
- --card-bg: #f9fafb;
20
- --border-color: #e5e7eb;
21
- --user-bubble: #e0e7ff;
22
- --bot-bubble: #f3f4f6;
23
- --latex-bg: #f8f9fa;
24
- --error-color: #ef4444;
25
- }
26
-
27
- .dark {
28
- --bg-color: #1f2937;
29
- --text-color: #f9fafb;
30
- --card-bg: #374151;
31
- --border-color: #4b5563;
32
- --user-bubble: #4338ca;
33
- --bot-bubble: #4b5563;
34
- --latex-bg: #1e293b;
35
- --error-color: #f87171;
36
- }
37
-
38
- .gradio-container {
39
- max-width: 1200px !important;
40
- margin: 0 auto;
41
- background: var(--bg-color) !important;
42
- color: var(--text-color) !important;
43
- font-family: 'Inter', system-ui, -apple-system, sans-serif;
44
- }
45
-
46
- .app-header {
47
- text-align: center;
48
- padding: 1rem 0;
49
- border-bottom: 1px solid var(--border-color);
50
- margin-bottom: 1.5rem;
51
- }
52
-
53
- .app-title {
54
- font-size: 1.8rem;
55
- font-weight: 700;
56
- margin: 0;
57
- color: var(--primary);
58
- }
59
-
60
- .app-subtitle {
61
- font-size: 0.9rem;
62
- opacity: 0.8;
63
- margin-top: 0.5rem;
64
- }
65
-
66
- .chat-container {
67
- display: flex;
68
- flex-direction: column;
69
- height: 75vh;
70
- gap: 0;
71
- background: var(--card-bg);
72
- border-radius: 12px;
73
- border: 1px solid var(--border-color);
74
- overflow: hidden;
75
- }
76
-
77
- .chatbot {
78
- flex-grow: 1;
79
- overflow-y: auto;
80
- padding: 1.5rem;
81
- scroll-behavior: smooth;
82
- }
83
-
84
- .chat-controls {
85
- padding: 1rem;
86
- background: var(--card-bg);
87
- border-top: 1px solid var(--border-color);
88
- display: flex;
89
- gap: 0.75rem;
90
- align-items: center;
91
- }
92
-
93
- .message {
94
- padding: 0.75rem 1rem;
95
- border-radius: 12px;
96
- margin-bottom: 0.75rem;
97
- max-width: 85%;
98
- line-height: 1.6;
99
- font-size: 1rem;
100
- animation: fadeIn 0.3s ease;
101
- }
102
-
103
- @keyframes fadeIn {
104
- from { opacity: 0; transform: translateY(10px); }
105
- to { opacity: 1; transform: translateY(0); }
106
- }
107
-
108
- .user-message {
109
- background: var(--user-bubble);
110
- margin-left: auto;
111
- border-bottom-right-radius: 4px;
112
- color: var(--text-color);
113
- }
114
-
115
- .bot-message {
116
- background: var(--bot-bubble);
117
- margin-right: auto;
118
- border-bottom-left-radius: 4px;
119
- color: var(--text-color);
120
- }
121
-
122
- .message-content {
123
- overflow-wrap: anywhere;
124
- }
125
-
126
- .latex-block {
127
- background: var(--latex-bg);
128
- padding: 1rem;
129
- margin: 1rem 0;
130
- border-radius: 8px;
131
- overflow-x: auto;
132
- font-family: 'CMU Serif', 'Latin Modern', serif;
133
- }
134
-
135
- .latex-inline {
136
- background: var(--latex-bg);
137
- padding: 0.2rem 0.4rem;
138
- border-radius: 4px;
139
- font-family: 'CMU Serif', 'Latin Modern', serif;
140
- }
141
-
142
- .model-selector {
143
- flex: 1;
144
- max-width: 300px;
145
- }
146
-
147
- .text-input {
148
- flex: 3;
149
- }
150
-
151
- .send-button {
152
- flex: 0.5;
153
- max-width: 100px;
154
- }
155
-
156
- .clear-button {
157
- flex: 0.5;
158
- max-width: 100px;
159
- }
160
-
161
- .toggle-container {
162
- display: flex;
163
- justify-content: flex-end;
164
- padding: 0.5rem 1.5rem;
165
- }
166
-
167
- .error-message {
168
- color: var(--error-color);
169
- font-size: 0.9rem;
170
- padding: 0.5rem 1rem;
171
- border-radius: 8px;
172
- background-color: rgba(239, 68, 68, 0.1);
173
- margin-top: 0.5rem;
174
- }
175
-
176
- .typing-indicator {
177
- display: inline-flex;
178
- align-items: center;
179
- gap: 0.25rem;
180
- padding: 0.5rem 1rem;
181
- background: var(--bot-bubble);
182
- border-radius: 12px;
183
- }
184
-
185
- .typing-dot {
186
- width: 8px;
187
- height: 8px;
188
- background: var(--text-color);
189
- border-radius: 50%;
190
- opacity: 0.4;
191
- animation: typingAnimation 1.4s infinite ease-in-out;
192
- }
193
-
194
- .typing-dot:nth-child(1) { animation-delay: 0s; }
195
- .typing-dot:nth-child(2) { animation-delay: 0.2s; }
196
- .typing-dot:nth-child(3) { animation-delay: 0.4s; }
197
-
198
- @keyframes typingAnimation {
199
- 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
200
- 30% { transform: translateY(-5px); opacity: 1; }
201
- }
202
-
203
- @media (max-width: 768px) {
204
- .gradio-container {
205
- padding: 0 1rem !important;
206
- }
207
- .chat-controls {
208
- flex-direction: column;
209
- }
210
- .model-selector, .text-input, .send-button, .clear-button {
211
- max-width: 100% !important;
212
- width: 100%;
213
- }
214
- }
215
- """
216
-
217
  def get_available_models() -> List[str]:
218
  try:
219
  response = requests.get(MODELS_URL, timeout=10)
@@ -223,7 +18,8 @@ def get_available_models() -> List[str]:
223
  except Exception:
224
  return ["samura-gpt-4o", "samura-claude-3-5-sonnet"]
225
 
226
- def chat_completion(messages: List[Dict], model: str) -> Dict:
 
227
  headers = {"Content-Type": "application/json"}
228
  data = {
229
  "model": model,
@@ -238,168 +34,129 @@ def chat_completion(messages: List[Dict], model: str) -> Dict:
238
  except Exception as e:
239
  return {"error": str(e)}
240
 
 
241
  def format_message(text: str) -> str:
242
  text = escape(text)
243
- # Enhanced LaTeX processing with proper HTML escaping
244
- text = re.sub(r'(?<!\\)\$(.*?)(?<!\\)\$', r'<span class="latex-inline">\(\1\)</span>', text)
245
- text = re.sub(r'(?<!\\)\$\$(.*?)(?<!\\)\$\$', r'<div class="latex-block">\[\1\]</div>', text, flags=re.DOTALL)
246
- # Handle code blocks
247
  text = re.sub(r'```(.*?)\n(.*?)```', r'<pre><code class="\1">\2</code></pre>', text, flags=re.DOTALL)
248
- # Handle bold and italic
249
  text = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', text)
250
  text = re.sub(r'\*(.*?)\*', r'<em>\1</em>', text)
251
  return text.replace("\n", "<br>")
252
 
 
253
  def create_typing_indicator():
254
- return {
255
- "role": "assistant",
256
- "content": """
257
- <div class="typing-indicator">
258
- <div class="typing-dot"></div>
259
- <div class="typing-dot"></div>
260
- <div class="typing-dot"></div>
261
- </div>
262
- """
263
- }
264
-
265
- def convert_to_openai_format(chat_history: List[List[str]]) -> List[Dict]:
266
- messages = []
267
- for turn in chat_history:
268
- if turn[0]: # User message
269
- clean_msg = re.sub('<[^<]+?>', '', turn[0]).replace("<br>", "\n")
270
- messages.append({"role": "user", "content": clean_msg})
271
- if turn[1] and "typing-indicator" not in turn[1]: # Bot message (not typing indicator)
272
- clean_msg = re.sub('<[^<]+?>', '', turn[1]).replace("<br>", "\n")
273
- messages.append({"role": "assistant", "content": clean_msg})
274
- return messages
275
-
276
- def respond(message: str, chat_history: List[List[str]], model: str):
277
  if not message.strip():
278
- yield chat_history, ""
279
 
280
- # Add user message
281
  user_message_html = format_message(message)
282
- chat_history.append([user_message_html, None])
283
 
284
- # Add typing indicator
285
- typing_msg = create_typing_indicator()
286
- chat_history.append([None, typing_msg["content"]])
287
  yield chat_history, ""
288
 
289
- # Prepare API messages
290
- messages = convert_to_openai_format(chat_history[:-1]) # Exclude typing indicator
 
 
 
 
 
 
291
  messages.append({"role": "user", "content": message})
292
 
293
- # Get response
294
  response = chat_completion(messages, model)
295
 
296
- # Remove typing indicator
297
  chat_history.pop()
298
 
299
  if "error" in response:
300
  error_message = f"<div class='error-message'>API Error: {response['error']}</div>"
301
- chat_history[-1][1] = error_message
302
  else:
303
  bot_message = response.get("choices", [{}])[0].get("message", {}).get("content", "No response")
304
- chat_history[-1][1] = format_message(bot_message)
305
 
306
- yield chat_history, ""
307
 
 
308
  def toggle_theme(theme: str):
309
  return gr.themes.Default() if theme == "light" else gr.themes.Dark()
310
 
311
- with gr.Blocks(css=css, theme=gr.themes.Default()) as app:
312
- # Header section
313
- with gr.Column(elem_classes=["app-header"]):
314
- gr.Markdown("""<h1 class="app-title">MathGenius AI</h1>""")
315
- gr.Markdown("""<p class="app-subtitle">Advanced mathematical reasoning with LaTeX support</p>""")
 
 
316
 
317
- # Theme toggle
318
- with gr.Row(elem_classes=["toggle-container"]):
319
  theme_toggle = gr.Radio(
320
  ["light", "dark"],
321
  value="light",
322
  label="Theme",
323
  interactive=True,
324
- show_label=False,
325
- elem_id="theme-toggle"
326
  )
327
 
328
- # Main chat area
329
- with gr.Column(elem_classes=["chat-container"]):
330
  chatbot = gr.Chatbot(
331
  elem_id="chatbot",
332
  show_label=False,
333
- avatar_images=(None, None),
334
- height="100%",
335
- render=False,
336
- layout="panel"
337
  )
338
 
339
- with gr.Row(elem_classes=["chat-controls"]):
340
  model_dropdown = gr.Dropdown(
341
  choices=get_available_models(),
342
  value="samura-gpt-4o",
343
  label="Model",
344
- interactive=True,
345
- elem_classes=["model-selector"],
346
- scale=1
347
  )
348
 
349
  msg = gr.Textbox(
350
- placeholder="Type your math question or equation... (Shift+Enter for new line)",
351
  show_label=False,
352
  lines=2,
353
- max_lines=5,
354
- elem_classes=["text-input"],
355
- container=False,
356
- scale=4
357
  )
358
 
359
- submit_btn = gr.Button(
360
- "Send",
361
- variant="primary",
362
- elem_classes=["send-button"],
363
- scale=1
364
- )
365
-
366
- clear_btn = gr.ClearButton(
367
- [msg, chatbot],
368
- value="Clear",
369
- elem_classes=["clear-button"],
370
- scale=1
371
- )
372
-
373
- # Event handlers
374
  msg.submit(
375
  respond,
376
- [msg, chatbot, model_dropdown],
377
- [chatbot, msg],
378
- queue=True
379
- ).then(
380
- None,
381
- None,
382
- None,
383
- _js="() => document.getElementById('chatbot').scrollTo(0, document.getElementById('chatbot').scrollHeight)"
384
  )
385
-
386
  submit_btn.click(
387
  respond,
388
- [msg, chatbot, model_dropdown],
389
- [chatbot, msg],
390
- queue=True
391
- ).then(
392
- None,
393
- None,
394
- None,
395
- _js="() => document.getElementById('chatbot').scrollTo(0, document.getElementById('chatbot').scrollHeight)"
396
  )
397
-
 
398
  theme_toggle.change(
 
 
 
 
 
399
  None,
400
- theme_toggle,
401
  None,
402
- _js="(theme) => document.body.className = theme"
403
  )
404
 
405
  if __name__ == "__main__":
 
2
  import requests
3
  from html import escape
4
  import re
5
+ from typing import List, Tuple
 
6
 
7
  # API configuration
8
  API_URL = "https://aham2api-3.onrender.com/v1/chat/completions"
9
  MODELS_URL = "https://aham2api-3.onrender.com/v1/models"
10
 
11
+ # Function to get available models
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  def get_available_models() -> List[str]:
13
  try:
14
  response = requests.get(MODELS_URL, timeout=10)
 
18
  except Exception:
19
  return ["samura-gpt-4o", "samura-claude-3-5-sonnet"]
20
 
21
+ # Function to handle chatbot responses
22
+ def chat_completion(messages: List[dict], model: str) -> dict:
23
  headers = {"Content-Type": "application/json"}
24
  data = {
25
  "model": model,
 
34
  except Exception as e:
35
  return {"error": str(e)}
36
 
37
+ # Function to format messages
38
  def format_message(text: str) -> str:
39
  text = escape(text)
40
+ text = re.sub(r'(?<!\\$(.*?)(?<!\\$', r'<span class="latex-inline">\1</span>', text)
41
+ text = re.sub(r'(?<!\\$\$(.*?)(?<!\\$\$', r'<div class="latex-block">\1</div>', text, flags=re.DOTALL)
 
 
42
  text = re.sub(r'```(.*?)\n(.*?)```', r'<pre><code class="\1">\2</code></pre>', text, flags=re.DOTALL)
 
43
  text = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', text)
44
  text = re.sub(r'\*(.*?)\*', r'<em>\1</em>', text)
45
  return text.replace("\n", "<br>")
46
 
47
+ # Typing indicator function
48
  def create_typing_indicator():
49
+ return """
50
+ <div class="typing-indicator">
51
+ <div class="typing-dot"></div>
52
+ <div class="typing-dot"></div>
53
+ <div class="typing-dot"></div>
54
+ </div>
55
+ """
56
+
57
+ # Main function to handle chat messages
58
+ def respond(message: str, chat_history: List[Tuple[str, str]], model: str, theme: str):
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  if not message.strip():
60
+ return chat_history, ""
61
 
 
62
  user_message_html = format_message(message)
63
+ chat_history.append((user_message_html, ""))
64
 
65
+ chat_history.append((None, create_typing_indicator()))
 
 
66
  yield chat_history, ""
67
 
68
+ messages = []
69
+ for user_msg, bot_msg in chat_history[:-2]:
70
+ if user_msg:
71
+ clean_msg = re.sub('<[^<]+?>', '', user_msg).replace("<br>", "\n")
72
+ messages.append({"role": "user", "content": clean_msg})
73
+ if bot_msg and "typing-indicator" not in bot_msg:
74
+ clean_msg = re.sub('<[^<]+?>', '', bot_msg).replace("<br>", "\n")
75
+ messages.append({"role": "assistant", "content": clean_msg})
76
  messages.append({"role": "user", "content": message})
77
 
 
78
  response = chat_completion(messages, model)
79
 
 
80
  chat_history.pop()
81
 
82
  if "error" in response:
83
  error_message = f"<div class='error-message'>API Error: {response['error']}</div>"
84
+ chat_history[-1] = (chat_history[-1][0], error_message)
85
  else:
86
  bot_message = response.get("choices", [{}])[0].get("message", {}).get("content", "No response")
87
+ chat_history[-1] = (chat_history[-1][0], format_message(bot_message))
88
 
89
+ return chat_history, ""
90
 
91
+ # Function to toggle theme
92
  def toggle_theme(theme: str):
93
  return gr.themes.Default() if theme == "light" else gr.themes.Dark()
94
 
95
+ # Gradio app layout
96
+ with gr.Blocks(theme=gr.themes.Default()) as app:
97
+ theme_state = gr.State("light")
98
+
99
+ with gr.Column():
100
+ gr.Markdown("<h1>MathGenius AI</h1>")
101
+ gr.Markdown("<p>Advanced mathematical reasoning with LaTeX support</p>")
102
 
103
+ with gr.Row():
 
104
  theme_toggle = gr.Radio(
105
  ["light", "dark"],
106
  value="light",
107
  label="Theme",
108
  interactive=True,
109
+ show_label=False
 
110
  )
111
 
112
+ with gr.Column():
 
113
  chatbot = gr.Chatbot(
114
  elem_id="chatbot",
115
  show_label=False,
116
+ type="messages", # FIX: Use recommended OpenAI-style message format
117
+ height="100%"
 
 
118
  )
119
 
120
+ with gr.Row():
121
  model_dropdown = gr.Dropdown(
122
  choices=get_available_models(),
123
  value="samura-gpt-4o",
124
  label="Model",
125
+ interactive=True
 
 
126
  )
127
 
128
  msg = gr.Textbox(
129
+ placeholder="Type your math question...",
130
  show_label=False,
131
  lines=2,
132
+ max_lines=5
 
 
 
133
  )
134
 
135
+ submit_btn = gr.Button("Send", variant="primary")
136
+ clear_btn = gr.ClearButton([msg, chatbot], value="Clear")
137
+
 
 
 
 
 
 
 
 
 
 
 
 
138
  msg.submit(
139
  respond,
140
+ [msg, chatbot, model_dropdown, theme_state],
141
+ [chatbot, msg]
 
 
 
 
 
 
142
  )
143
+
144
  submit_btn.click(
145
  respond,
146
+ [msg, chatbot, model_dropdown, theme_state],
147
+ [chatbot, msg]
 
 
 
 
 
 
148
  )
149
+
150
+ # FIX: Properly pass `inputs` as a list
151
  theme_toggle.change(
152
+ toggle_theme,
153
+ [theme_toggle], # Ensure inputs are in a list
154
+ None
155
+ ).then(
156
+ None,
157
  None,
 
158
  None,
159
+ js="(theme) => document.body.className = theme"
160
  )
161
 
162
  if __name__ == "__main__":