apaxray commited on
Commit
4f418db
Β·
verified Β·
1 Parent(s): 4d4f606

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +230 -371
app.py CHANGED
@@ -1,35 +1,54 @@
1
  import gradio as gr
2
  from huggingface_hub import InferenceClient
3
- import time
4
 
5
  MODELS = [
6
  {"name": "Qwen 2.5 Coder 32B", "id": "Qwen/Qwen2.5-Coder-32B-Instruct"},
7
- {"name": "Qwen 2.5 72B Instruct", "id": "Qwen/Qwen2.5-72B-Instruct"},
8
- {"name": "Llama 3.3 70B Instruct", "id": "meta-llama/Llama-3.3-70B-Instruct"},
9
- {"name": "Mixtral 8x7B Instruct", "id": "mistralai/Mixtral-8x7B-Instruct-v0.1"},
10
  {"name": "DeepSeek Coder V2", "id": "deepseek-ai/DeepSeek-Coder-V2-Instruct"},
11
  ]
12
 
13
- def stream_chat(message, history, model_name):
14
- model_id = next((m["id"] for m in MODELS if m["name"] == model_name), MODELS[0]["id"])
15
- client = InferenceClient(model_id)
 
16
 
17
- messages = []
18
- for h in history:
19
- if h[0]:
20
- messages.append({"role": "user", "content": h[0]})
21
- if h[1]:
22
- messages.append({"role": "assistant", "content": h[1]})
23
- messages.append({"role": "user", "content": message})
24
 
25
- response = ""
26
  try:
27
- for chunk in client.chat_completion(messages=messages, max_tokens=8192, stream=True, temperature=0.7):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  if chunk.choices[0].delta.content:
29
  response += chunk.choices[0].delta.content
30
  yield response
 
31
  except Exception as e:
32
- yield f"Error: {str(e)}"
 
 
 
 
 
 
33
 
34
  CSS = """
35
  @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap');
@@ -51,14 +70,13 @@ CSS = """
51
  --accent-hover: #79C0FF;
52
  --success: #3FB950;
53
  --error: #F85149;
54
- --shadow: rgba(0,0,0,0.4);
55
  }
56
 
57
  body {
58
  font-family: 'JetBrains Mono', monospace !important;
59
  background: var(--bg-primary) !important;
60
  color: var(--text-primary) !important;
61
- overflow-x: hidden;
62
  }
63
 
64
  .gradio-container {
@@ -68,7 +86,6 @@ body {
68
  background: var(--bg-primary) !important;
69
  }
70
 
71
- /* Header */
72
  #header {
73
  background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%);
74
  border-bottom: 2px solid var(--border);
@@ -76,7 +93,6 @@ body {
76
  position: sticky;
77
  top: 0;
78
  z-index: 100;
79
- backdrop-filter: blur(10px);
80
  }
81
 
82
  #header h1 {
@@ -100,40 +116,12 @@ body {
100
  text-transform: uppercase;
101
  }
102
 
103
- /* Main Layout */
104
- #main-wrapper {
105
- display: grid;
106
- grid-template-columns: 320px 1fr;
107
- min-height: calc(100vh - 100px);
108
- gap: 0;
109
- }
110
-
111
- /* Sidebar */
112
- #sidebar {
113
- background: var(--bg-secondary);
114
- border-right: 1px solid var(--border);
115
- padding: 32px 24px;
116
- overflow-y: auto;
117
- position: sticky;
118
- top: 100px;
119
- height: calc(100vh - 100px);
120
- }
121
-
122
- #sidebar::-webkit-scrollbar {
123
- width: 6px;
124
- }
125
-
126
- #sidebar::-webkit-scrollbar-track {
127
- background: transparent;
128
- }
129
-
130
- #sidebar::-webkit-scrollbar-thumb {
131
- background: var(--border);
132
- border-radius: 3px;
133
- }
134
-
135
  .sidebar-section {
136
- margin-bottom: 32px;
 
 
 
 
137
  }
138
 
139
  .sidebar-title {
@@ -145,47 +133,43 @@ body {
145
  margin-bottom: 12px;
146
  }
147
 
148
- .model-card {
149
- background: var(--bg-tertiary);
150
- border: 1px solid var(--border);
151
- border-radius: 8px;
152
- padding: 16px;
153
- margin-bottom: 12px;
154
- cursor: pointer;
155
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
156
- }
157
-
158
- .model-card:hover {
159
- border-color: var(--accent);
160
- transform: translateX(4px);
161
- box-shadow: 0 4px 12px var(--shadow);
162
- }
163
-
164
- .model-card.active {
165
- background: linear-gradient(135deg, var(--accent) 0%, var(--success) 100%);
166
- border-color: var(--success);
167
  }
168
 
169
- .model-card.active .model-name {
170
- color: var(--bg-primary);
171
- font-weight: 700;
172
  }
173
 
174
- .model-name {
175
- font-size: 13px;
176
- font-weight: 600;
177
- color: var(--text-primary);
178
- margin-bottom: 4px;
 
 
 
 
179
  }
180
 
181
- .model-info {
182
- font-size: 10px;
183
- color: var(--text-secondary);
184
- text-transform: uppercase;
185
- letter-spacing: 0.05em;
 
 
 
 
186
  }
187
 
188
- /* Stats */
189
  .stat-grid {
190
  display: grid;
191
  grid-template-columns: 1fr 1fr;
@@ -196,13 +180,13 @@ body {
196
  .stat-box {
197
  background: var(--bg-tertiary);
198
  border: 1px solid var(--border);
199
- border-radius: 6px;
200
  padding: 12px;
201
  text-align: center;
202
  }
203
 
204
  .stat-value {
205
- font-size: 18px;
206
  font-weight: 700;
207
  color: var(--success);
208
  margin-bottom: 4px;
@@ -215,125 +199,70 @@ body {
215
  letter-spacing: 0.1em;
216
  }
217
 
218
- /* Chat Area */
219
- #chat-area {
220
- background: var(--bg-primary);
221
- padding: 32px 48px;
222
- display: flex;
223
- flex-direction: column;
224
- gap: 32px;
225
- }
226
-
227
- #chatbot {
228
  background: transparent !important;
229
  border: none !important;
230
- min-height: 500px;
231
- padding: 0 !important;
232
  }
233
 
234
  .message {
235
- margin-bottom: 24px !important;
236
- animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
237
  }
238
 
239
- @keyframes slideUp {
240
- from {
241
- opacity: 0;
242
- transform: translateY(20px);
243
- }
244
- to {
245
- opacity: 1;
246
- transform: translateY(0);
247
- }
248
  }
249
 
250
- .message.user .message-content {
251
- background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%) !important;
252
- color: var(--bg-primary) !important;
253
- border-radius: 16px 16px 4px 16px !important;
254
- padding: 16px 20px !important;
255
- font-size: 14px !important;
256
- line-height: 1.6 !important;
257
- box-shadow: 0 4px 12px rgba(88, 166, 255, 0.2);
258
  }
259
 
260
- .message.bot .message-content {
261
- background: var(--bg-tertiary) !important;
262
- color: var(--text-primary) !important;
263
- border: 1px solid var(--border) !important;
264
- border-radius: 16px 16px 16px 4px !important;
265
  padding: 16px 20px !important;
 
266
  font-size: 14px !important;
267
  line-height: 1.6 !important;
 
268
  }
269
 
270
- .message-content code {
271
- background: rgba(255, 255, 255, 0.08) !important;
272
- padding: 2px 6px !important;
273
- border-radius: 4px !important;
274
- font-size: 13px !important;
275
- font-family: 'JetBrains Mono', monospace !important;
276
  }
277
 
278
- .message-content pre {
279
- background: var(--bg-secondary) !important;
 
280
  border: 1px solid var(--border) !important;
281
- border-radius: 8px !important;
282
- padding: 16px !important;
283
- overflow-x: auto !important;
284
- margin: 12px 0 !important;
285
- }
286
-
287
- .message-content pre code {
288
- background: none !important;
289
- padding: 0 !important;
290
- }
291
-
292
- /* Input Area */
293
- #input-wrapper {
294
- background: var(--bg-secondary);
295
- border: 2px solid var(--border);
296
- border-radius: 16px;
297
- padding: 8px;
298
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
299
- }
300
-
301
- #input-wrapper:focus-within {
302
- border-color: var(--accent);
303
- box-shadow: 0 0 0 4px rgba(88, 166, 255, 0.1);
304
  }
305
 
306
- #message-input {
307
- background: transparent !important;
308
- border: none !important;
309
  color: var(--text-primary) !important;
310
- font-size: 14px !important;
311
- padding: 16px 20px !important;
312
- resize: none !important;
313
  font-family: 'JetBrains Mono', monospace !important;
 
 
 
314
  }
315
 
316
- #message-input::placeholder {
317
- color: var(--text-secondary) !important;
 
318
  }
319
 
320
- /* Buttons */
321
- .btn {
 
322
  font-family: 'Space Grotesk', sans-serif !important;
323
  font-weight: 600 !important;
324
  text-transform: uppercase !important;
325
  letter-spacing: 0.05em !important;
326
- border-radius: 8px !important;
327
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
328
- cursor: pointer !important;
329
  border: none !important;
330
- }
331
-
332
- .btn-primary {
333
- background: linear-gradient(135deg, var(--accent) 0%, var(--success) 100%) !important;
334
- color: var(--bg-primary) !important;
335
  padding: 14px 28px !important;
336
- font-size: 13px !important;
 
337
  }
338
 
339
  .btn-primary:hover {
@@ -344,9 +273,14 @@ body {
344
  .btn-secondary {
345
  background: var(--bg-tertiary) !important;
346
  color: var(--text-primary) !important;
 
 
 
 
347
  border: 1px solid var(--border) !important;
348
  padding: 12px 24px !important;
349
- font-size: 12px !important;
 
350
  }
351
 
352
  .btn-secondary:hover {
@@ -354,120 +288,29 @@ body {
354
  border-color: var(--accent) !important;
355
  }
356
 
357
- /* Examples */
358
- .examples-grid {
359
- display: grid;
360
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
361
- gap: 16px;
362
- margin-top: 24px;
363
- }
364
-
365
- .example-card {
366
- background: var(--bg-tertiary);
367
- border: 1px solid var(--border);
368
- border-radius: 10px;
369
- padding: 16px 20px;
370
- cursor: pointer;
371
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
372
- font-size: 13px;
373
- color: var(--text-secondary);
374
- }
375
-
376
- .example-card:hover {
377
- background: var(--bg-secondary);
378
- border-color: var(--accent);
379
- color: var(--text-primary);
380
- transform: translateY(-3px);
381
- box-shadow: 0 6px 20px var(--shadow);
382
- }
383
-
384
- /* Action Bar */
385
- .action-bar {
386
- display: flex;
387
- gap: 12px;
388
- margin-top: 16px;
389
- }
390
-
391
- /* Loading State */
392
- .loading-indicator {
393
- display: inline-flex;
394
- gap: 4px;
395
- }
396
-
397
- .loading-dot {
398
- width: 6px;
399
- height: 6px;
400
- border-radius: 50%;
401
- background: var(--accent);
402
- animation: bounce 1.4s infinite ease-in-out;
403
- }
404
-
405
- .loading-dot:nth-child(1) {
406
- animation-delay: -0.32s;
407
- }
408
-
409
- .loading-dot:nth-child(2) {
410
- animation-delay: -0.16s;
411
- }
412
-
413
- @keyframes bounce {
414
- 0%, 80%, 100% {
415
- transform: scale(0);
416
- }
417
- 40% {
418
- transform: scale(1);
419
- }
420
- }
421
-
422
- /* Responsive */
423
- @media (max-width: 1024px) {
424
- #main-wrapper {
425
- grid-template-columns: 1fr;
426
- }
427
-
428
- #sidebar {
429
- position: relative;
430
- height: auto;
431
- border-right: none;
432
- border-bottom: 1px solid var(--border);
433
- }
434
-
435
- #header {
436
- padding: 20px 24px;
437
- }
438
-
439
- #chat-area {
440
- padding: 24px;
441
- }
442
- }
443
-
444
- /* Gradio Overrides */
445
- .gr-button {
446
  font-family: 'Space Grotesk', sans-serif !important;
 
 
 
 
 
447
  }
448
 
449
- .gr-input {
450
- font-family: 'JetBrains Mono', monospace !important;
451
- }
452
-
453
- .gr-box {
454
  background: var(--bg-tertiary) !important;
455
- border-color: var(--border) !important;
 
 
456
  }
457
 
458
- label {
459
- font-family: 'Space Grotesk', sans-serif !important;
460
- color: var(--text-secondary) !important;
461
- font-size: 11px !important;
462
- font-weight: 700 !important;
463
- text-transform: uppercase !important;
464
- letter-spacing: 0.1em !important;
465
  }
466
 
467
- /* Scrollbar */
468
  ::-webkit-scrollbar {
469
  width: 10px;
470
- height: 10px;
471
  }
472
 
473
  ::-webkit-scrollbar-track {
@@ -482,12 +325,6 @@ label {
482
  ::-webkit-scrollbar-thumb:hover {
483
  background: var(--text-secondary);
484
  }
485
-
486
- /* Selection */
487
- ::selection {
488
- background: var(--accent);
489
- color: var(--bg-primary);
490
- }
491
  """
492
 
493
  with gr.Blocks(css=CSS, theme=gr.themes.Base(), title="Neural Chat") as demo:
@@ -495,118 +332,133 @@ with gr.Blocks(css=CSS, theme=gr.themes.Base(), title="Neural Chat") as demo:
495
  gr.HTML("""
496
  <div id="header">
497
  <h1>⬑ NEURAL CHAT</h1>
498
- <div class="subtitle">Advanced AI Communication Interface</div>
499
  </div>
500
  """)
501
 
502
- with gr.Row(elem_id="main-wrapper"):
503
 
504
- with gr.Column(elem_id="sidebar", scale=1):
505
 
506
- gr.HTML('<div class="sidebar-title">MODEL SELECTION</div>')
507
-
508
- model_selector = gr.Dropdown(
509
- choices=[m["name"] for m in MODELS],
510
- value=MODELS[0]["name"],
511
- label="Active Model",
512
- interactive=True,
513
- elem_classes=["model-dropdown"]
514
- )
515
-
516
- gr.HTML('<div class="sidebar-title" style="margin-top: 24px;">SYSTEM STATUS</div>')
517
-
518
- gr.HTML("""
519
- <div class="stat-grid">
520
- <div class="stat-box">
521
- <div class="stat-value">● </div>
522
- <div class="stat-label">ONLINE</div>
523
- </div>
524
- <div class="stat-box">
525
- <div class="stat-value">32K</div>
526
- <div class="stat-label">CONTEXT</div>
527
- </div>
528
- <div class="stat-box">
529
- <div class="stat-value">~2.1s</div>
530
- <div class="stat-label">LATENCY</div>
531
  </div>
532
- <div class="stat-box">
533
- <div class="stat-value">99.8%</div>
534
- <div class="stat-label">UPTIME</div>
 
 
 
 
 
535
  </div>
536
- </div>
537
- """)
538
-
539
- gr.HTML('<div class="sidebar-title" style="margin-top: 24px;">QUICK ACTIONS</div>')
540
 
541
- clear_btn = gr.Button("⟳ NEW CONVERSATION", elem_classes=["btn", "btn-secondary"], size="sm")
542
- export_btn = gr.Button("↓ EXPORT CHAT", elem_classes=["btn", "btn-secondary"], size="sm")
 
 
 
 
 
 
 
543
 
544
- gr.HTML("""
545
- <div style="margin-top: 32px; padding-top: 24px; border-top: 1px solid var(--border);">
546
- <div style="font-size: 10px; color: var(--text-secondary); line-height: 1.6;">
547
- <strong>NEURAL CHAT v2.0</strong><br>
548
- Powered by Hugging Face<br>
549
- React + Python Architecture<br><br>
550
- All models are free and open-source
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
  </div>
552
- </div>
553
- """)
 
 
 
 
 
554
 
555
- with gr.Column(elem_id="chat-area", scale=3):
556
 
557
  chatbot = gr.Chatbot(
558
  value=[],
559
- elem_id="chatbot",
560
- height=550,
561
  show_label=False,
 
562
  avatar_images=(None, "⬑"),
563
- bubble_full_width=False,
564
- render_markdown=True,
565
- show_copy_button=True,
566
  )
567
 
568
- with gr.Group(elem_id="input-wrapper"):
569
- msg_input = gr.Textbox(
570
- placeholder="Enter your message here... (Shift+Enter for new line)",
571
- show_label=False,
572
- lines=1,
573
- max_lines=10,
574
- elem_id="message-input",
575
- container=False,
576
- )
577
 
578
- with gr.Row(elem_classes=["action-bar"]):
579
- send_btn = gr.Button("SEND MESSAGE", elem_classes=["btn", "btn-primary"], scale=2)
580
- retry_btn = gr.Button("⟲ RETRY", elem_classes=["btn", "btn-secondary"], scale=1)
581
- stop_btn = gr.Button("⏹ STOP", elem_classes=["btn", "btn-secondary"], scale=1)
582
 
583
- gr.HTML('<div class="sidebar-title" style="margin-top: 32px;">EXAMPLE PROMPTS</div>')
584
 
585
  gr.Examples(
586
  examples=[
587
- "Explain quantum entanglement in simple terms",
588
- "Write a Python script to analyze CSV data",
589
- "What are the key principles of clean code?",
590
- "Create a React component for a dropdown menu",
591
- "How does machine learning differ from deep learning?",
592
- "Explain the SOLID principles with code examples",
593
  ],
594
  inputs=msg_input,
595
- elem_classes=["examples-grid"]
596
  )
597
 
598
  def user_msg(message, history):
599
- return "", history + [[message, None]]
 
 
600
 
601
- def bot_msg(history, model):
602
- if not history or history[-1][1] is not None:
603
  return history
604
 
605
- user_message = history[-1][0]
606
- history[-1][1] = ""
607
 
608
- for response in stream_chat(user_message, history[:-1], model):
609
- history[-1][1] = response
610
  yield history
611
 
612
  msg_input.submit(
@@ -616,7 +468,7 @@ with gr.Blocks(css=CSS, theme=gr.themes.Base(), title="Neural Chat") as demo:
616
  queue=False
617
  ).then(
618
  bot_msg,
619
- [chatbot, model_selector],
620
  chatbot
621
  )
622
 
@@ -627,7 +479,7 @@ with gr.Blocks(css=CSS, theme=gr.themes.Base(), title="Neural Chat") as demo:
627
  queue=False
628
  ).then(
629
  bot_msg,
630
- [chatbot, model_selector],
631
  chatbot
632
  )
633
 
@@ -636,8 +488,15 @@ with gr.Blocks(css=CSS, theme=gr.themes.Base(), title="Neural Chat") as demo:
636
  def retry_last_msg(history):
637
  if not history:
638
  return history
639
- if history[-1][1] is not None:
640
- history[-1][1] = None
 
 
 
 
 
 
 
641
  return history
642
 
643
  retry_btn.click(
@@ -647,7 +506,7 @@ with gr.Blocks(css=CSS, theme=gr.themes.Base(), title="Neural Chat") as demo:
647
  queue=False
648
  ).then(
649
  bot_msg,
650
- [chatbot, model_selector],
651
  chatbot
652
  )
653
 
 
1
  import gradio as gr
2
  from huggingface_hub import InferenceClient
3
+ import os
4
 
5
  MODELS = [
6
  {"name": "Qwen 2.5 Coder 32B", "id": "Qwen/Qwen2.5-Coder-32B-Instruct"},
7
+ {"name": "Qwen 2.5 72B", "id": "Qwen/Qwen2.5-72B-Instruct"},
8
+ {"name": "Llama 3.3 70B", "id": "meta-llama/Llama-3.3-70B-Instruct"},
9
+ {"name": "Mixtral 8x7B", "id": "mistralai/Mixtral-8x7B-Instruct-v0.1"},
10
  {"name": "DeepSeek Coder V2", "id": "deepseek-ai/DeepSeek-Coder-V2-Instruct"},
11
  ]
12
 
13
+ def stream_chat(message, history, model_name, api_key):
14
+ if not api_key or api_key.strip() == "":
15
+ yield "⚠️ Please enter your Hugging Face API key in the sidebar first.\n\nGet your free API key at: https://huggingface.co/settings/tokens"
16
+ return
17
 
18
+ model_id = next((m["id"] for m in MODELS if m["name"] == model_name), MODELS[0]["id"])
 
 
 
 
 
 
19
 
 
20
  try:
21
+ client = InferenceClient(token=api_key.strip())
22
+
23
+ messages = []
24
+ for h in history:
25
+ if h["role"] == "user":
26
+ messages.append({"role": "user", "content": h["content"]})
27
+ elif h["role"] == "assistant":
28
+ messages.append({"role": "assistant", "content": h["content"]})
29
+
30
+ messages.append({"role": "user", "content": message})
31
+
32
+ response = ""
33
+ for chunk in client.chat_completion(
34
+ model=model_id,
35
+ messages=messages,
36
+ max_tokens=8192,
37
+ stream=True,
38
+ temperature=0.7
39
+ ):
40
  if chunk.choices[0].delta.content:
41
  response += chunk.choices[0].delta.content
42
  yield response
43
+
44
  except Exception as e:
45
+ error_msg = str(e)
46
+ if "401" in error_msg or "authorization" in error_msg.lower():
47
+ yield "❌ Invalid API Key. Please check your Hugging Face token.\n\nGet a new one at: https://huggingface.co/settings/tokens"
48
+ elif "429" in error_msg:
49
+ yield "⏳ Rate limit exceeded. Please wait a moment and try again."
50
+ else:
51
+ yield f"❌ Error: {error_msg}\n\nPlease try again or select a different model."
52
 
53
  CSS = """
54
  @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap');
 
70
  --accent-hover: #79C0FF;
71
  --success: #3FB950;
72
  --error: #F85149;
73
+ --warning: #F0883E;
74
  }
75
 
76
  body {
77
  font-family: 'JetBrains Mono', monospace !important;
78
  background: var(--bg-primary) !important;
79
  color: var(--text-primary) !important;
 
80
  }
81
 
82
  .gradio-container {
 
86
  background: var(--bg-primary) !important;
87
  }
88
 
 
89
  #header {
90
  background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%);
91
  border-bottom: 2px solid var(--border);
 
93
  position: sticky;
94
  top: 0;
95
  z-index: 100;
 
96
  }
97
 
98
  #header h1 {
 
116
  text-transform: uppercase;
117
  }
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  .sidebar-section {
120
+ background: var(--bg-secondary);
121
+ border: 1px solid var(--border);
122
+ border-radius: 12px;
123
+ padding: 20px;
124
+ margin-bottom: 20px;
125
  }
126
 
127
  .sidebar-title {
 
133
  margin-bottom: 12px;
134
  }
135
 
136
+ .api-key-input input {
137
+ background: var(--bg-tertiary) !important;
138
+ border: 1px solid var(--border) !important;
139
+ color: var(--text-primary) !important;
140
+ font-family: 'JetBrains Mono', monospace !important;
141
+ font-size: 12px !important;
142
+ padding: 12px !important;
143
+ border-radius: 8px !important;
 
 
 
 
 
 
 
 
 
 
 
144
  }
145
 
146
+ .api-key-input input:focus {
147
+ border-color: var(--accent) !important;
148
+ box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.1) !important;
149
  }
150
 
151
+ .security-note {
152
+ background: rgba(63, 185, 80, 0.1);
153
+ border: 1px solid var(--success);
154
+ border-radius: 8px;
155
+ padding: 12px;
156
+ font-size: 11px;
157
+ color: var(--success);
158
+ line-height: 1.6;
159
+ margin-top: 12px;
160
  }
161
 
162
+ .warning-note {
163
+ background: rgba(240, 136, 62, 0.1);
164
+ border: 1px solid var(--warning);
165
+ border-radius: 8px;
166
+ padding: 12px;
167
+ font-size: 11px;
168
+ color: var(--warning);
169
+ line-height: 1.6;
170
+ margin-top: 12px;
171
  }
172
 
 
173
  .stat-grid {
174
  display: grid;
175
  grid-template-columns: 1fr 1fr;
 
180
  .stat-box {
181
  background: var(--bg-tertiary);
182
  border: 1px solid var(--border);
183
+ border-radius: 8px;
184
  padding: 12px;
185
  text-align: center;
186
  }
187
 
188
  .stat-value {
189
+ font-size: 16px;
190
  font-weight: 700;
191
  color: var(--success);
192
  margin-bottom: 4px;
 
199
  letter-spacing: 0.1em;
200
  }
201
 
202
+ .chatbot-container {
 
 
 
 
 
 
 
 
 
203
  background: transparent !important;
204
  border: none !important;
 
 
205
  }
206
 
207
  .message {
208
+ margin-bottom: 16px !important;
 
209
  }
210
 
211
+ .user .message-row {
212
+ justify-content: flex-end !important;
 
 
 
 
 
 
 
213
  }
214
 
215
+ .bot .message-row {
216
+ justify-content: flex-start !important;
 
 
 
 
 
 
217
  }
218
 
219
+ .message-content {
 
 
 
 
220
  padding: 16px 20px !important;
221
+ border-radius: 16px !important;
222
  font-size: 14px !important;
223
  line-height: 1.6 !important;
224
+ max-width: 70% !important;
225
  }
226
 
227
+ .user .message-content {
228
+ background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%) !important;
229
+ color: var(--bg-primary) !important;
230
+ border-radius: 16px 16px 4px 16px !important;
 
 
231
  }
232
 
233
+ .bot .message-content {
234
+ background: var(--bg-tertiary) !important;
235
+ color: var(--text-primary) !important;
236
  border: 1px solid var(--border) !important;
237
+ border-radius: 16px 16px 16px 4px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  }
239
 
240
+ #message-input textarea {
241
+ background: var(--bg-tertiary) !important;
242
+ border: 2px solid var(--border) !important;
243
  color: var(--text-primary) !important;
 
 
 
244
  font-family: 'JetBrains Mono', monospace !important;
245
+ font-size: 14px !important;
246
+ padding: 16px !important;
247
+ border-radius: 12px !important;
248
  }
249
 
250
+ #message-input textarea:focus {
251
+ border-color: var(--accent) !important;
252
+ box-shadow: 0 0 0 4px rgba(88, 166, 255, 0.1) !important;
253
  }
254
 
255
+ .btn-primary {
256
+ background: linear-gradient(135deg, var(--accent) 0%, var(--success) 100%) !important;
257
+ color: var(--bg-primary) !important;
258
  font-family: 'Space Grotesk', sans-serif !important;
259
  font-weight: 600 !important;
260
  text-transform: uppercase !important;
261
  letter-spacing: 0.05em !important;
 
 
 
262
  border: none !important;
 
 
 
 
 
263
  padding: 14px 28px !important;
264
+ border-radius: 8px !important;
265
+ cursor: pointer !important;
266
  }
267
 
268
  .btn-primary:hover {
 
273
  .btn-secondary {
274
  background: var(--bg-tertiary) !important;
275
  color: var(--text-primary) !important;
276
+ font-family: 'Space Grotesk', sans-serif !important;
277
+ font-weight: 600 !important;
278
+ text-transform: uppercase !important;
279
+ letter-spacing: 0.05em !important;
280
  border: 1px solid var(--border) !important;
281
  padding: 12px 24px !important;
282
+ border-radius: 8px !important;
283
+ cursor: pointer !important;
284
  }
285
 
286
  .btn-secondary:hover {
 
288
  border-color: var(--accent) !important;
289
  }
290
 
291
+ label {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  font-family: 'Space Grotesk', sans-serif !important;
293
+ color: var(--text-secondary) !important;
294
+ font-size: 11px !important;
295
+ font-weight: 600 !important;
296
+ text-transform: uppercase !important;
297
+ letter-spacing: 0.05em !important;
298
  }
299
 
300
+ .gr-dropdown {
 
 
 
 
301
  background: var(--bg-tertiary) !important;
302
+ border: 1px solid var(--border) !important;
303
+ color: var(--text-primary) !important;
304
+ border-radius: 8px !important;
305
  }
306
 
307
+ ::selection {
308
+ background: var(--accent);
309
+ color: var(--bg-primary);
 
 
 
 
310
  }
311
 
 
312
  ::-webkit-scrollbar {
313
  width: 10px;
 
314
  }
315
 
316
  ::-webkit-scrollbar-track {
 
325
  ::-webkit-scrollbar-thumb:hover {
326
  background: var(--text-secondary);
327
  }
 
 
 
 
 
 
328
  """
329
 
330
  with gr.Blocks(css=CSS, theme=gr.themes.Base(), title="Neural Chat") as demo:
 
332
  gr.HTML("""
333
  <div id="header">
334
  <h1>⬑ NEURAL CHAT</h1>
335
+ <div class="subtitle">Secure AI Interface with Personal API Keys</div>
336
  </div>
337
  """)
338
 
339
+ with gr.Row():
340
 
341
+ with gr.Column(scale=1, min_width=320):
342
 
343
+ with gr.Group(elem_classes=["sidebar-section"]):
344
+ gr.HTML('<div class="sidebar-title">πŸ” API AUTHENTICATION</div>')
345
+
346
+ api_key_input = gr.Textbox(
347
+ label="Hugging Face API Key",
348
+ type="password",
349
+ placeholder="hf_...",
350
+ info="Your key is stored locally in your browser session",
351
+ elem_classes=["api-key-input"]
352
+ )
353
+
354
+ gr.HTML("""
355
+ <div class="security-note">
356
+ βœ“ Your API key is never stored on our servers<br>
357
+ βœ“ Each user uses their own key<br>
358
+ βœ“ Completely secure and private
 
 
 
 
 
 
 
 
 
359
  </div>
360
+ """)
361
+
362
+ gr.HTML("""
363
+ <div class="warning-note">
364
+ πŸ“Œ Get your free API key:<br>
365
+ <a href="https://huggingface.co/settings/tokens" target="_blank" style="color: #F0883E;">
366
+ https://huggingface.co/settings/tokens
367
+ </a>
368
  </div>
369
+ """)
 
 
 
370
 
371
+ with gr.Group(elem_classes=["sidebar-section"]):
372
+ gr.HTML('<div class="sidebar-title">πŸ€– MODEL SELECTION</div>')
373
+
374
+ model_selector = gr.Dropdown(
375
+ choices=[m["name"] for m in MODELS],
376
+ value=MODELS[0]["name"],
377
+ label="Active Model",
378
+ interactive=True,
379
+ )
380
 
381
+ with gr.Group(elem_classes=["sidebar-section"]):
382
+ gr.HTML('<div class="sidebar-title">πŸ“Š STATISTICS</div>')
383
+
384
+ gr.HTML("""
385
+ <div class="stat-grid">
386
+ <div class="stat-box">
387
+ <div class="stat-value">●</div>
388
+ <div class="stat-label">SECURE</div>
389
+ </div>
390
+ <div class="stat-box">
391
+ <div class="stat-value">32K</div>
392
+ <div class="stat-label">CONTEXT</div>
393
+ </div>
394
+ <div class="stat-box">
395
+ <div class="stat-value">FREE</div>
396
+ <div class="stat-label">TIER</div>
397
+ </div>
398
+ <div class="stat-box">
399
+ <div class="stat-value">5</div>
400
+ <div class="stat-label">MODELS</div>
401
+ </div>
402
  </div>
403
+ """)
404
+
405
+ with gr.Group(elem_classes=["sidebar-section"]):
406
+ gr.HTML('<div class="sidebar-title">⚑ ACTIONS</div>')
407
+
408
+ clear_btn = gr.Button("⟳ NEW CHAT", elem_classes=["btn-secondary"], size="sm")
409
+ retry_btn = gr.Button("πŸ”„ RETRY LAST", elem_classes=["btn-secondary"], size="sm")
410
 
411
+ with gr.Column(scale=3):
412
 
413
  chatbot = gr.Chatbot(
414
  value=[],
415
+ height=600,
 
416
  show_label=False,
417
+ type="messages",
418
  avatar_images=(None, "⬑"),
419
+ elem_classes=["chatbot-container"]
 
 
420
  )
421
 
422
+ msg_input = gr.Textbox(
423
+ placeholder="Type your message... (Shift+Enter for new line)",
424
+ show_label=False,
425
+ lines=3,
426
+ max_lines=10,
427
+ elem_id="message-input"
428
+ )
 
 
429
 
430
+ with gr.Row():
431
+ send_btn = gr.Button("SEND MESSAGE", elem_classes=["btn-primary"], scale=3)
432
+ stop_btn = gr.Button("⏹ STOP", elem_classes=["btn-secondary"], scale=1)
 
433
 
434
+ gr.HTML('<div class="sidebar-title" style="margin-top: 24px;">πŸ’‘ EXAMPLE PROMPTS</div>')
435
 
436
  gr.Examples(
437
  examples=[
438
+ "Explain quantum computing in simple terms",
439
+ "Write a Python function for fibonacci sequence",
440
+ "What are the SOLID principles with examples?",
441
+ "Create a React component for a modal dialog",
442
+ "Help me optimize this SQL query",
443
+ "Explain async/await in JavaScript",
444
  ],
445
  inputs=msg_input,
 
446
  )
447
 
448
  def user_msg(message, history):
449
+ if not message.strip():
450
+ return "", history
451
+ return "", history + [{"role": "user", "content": message}]
452
 
453
+ def bot_msg(history, model, api_key):
454
+ if not history or history[-1]["role"] != "user":
455
  return history
456
 
457
+ user_message = history[-1]["content"]
458
+ history.append({"role": "assistant", "content": ""})
459
 
460
+ for response in stream_chat(user_message, history[:-1], model, api_key):
461
+ history[-1]["content"] = response
462
  yield history
463
 
464
  msg_input.submit(
 
468
  queue=False
469
  ).then(
470
  bot_msg,
471
+ [chatbot, model_selector, api_key_input],
472
  chatbot
473
  )
474
 
 
479
  queue=False
480
  ).then(
481
  bot_msg,
482
+ [chatbot, model_selector, api_key_input],
483
  chatbot
484
  )
485
 
 
488
  def retry_last_msg(history):
489
  if not history:
490
  return history
491
+
492
+ if history[-1]["role"] == "assistant":
493
+ history = history[:-1]
494
+
495
+ if history and history[-1]["role"] == "user":
496
+ last_user_msg = history[-1]
497
+ history = history[:-1]
498
+ return history + [last_user_msg]
499
+
500
  return history
501
 
502
  retry_btn.click(
 
506
  queue=False
507
  ).then(
508
  bot_msg,
509
+ [chatbot, model_selector, api_key_input],
510
  chatbot
511
  )
512