Shinhati2023 commited on
Commit
db7ea30
Β·
verified Β·
1 Parent(s): ccd28f1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -307
app.py CHANGED
@@ -1,7 +1,6 @@
1
  """
2
  Groq AI Studio Pro - Glassmorphism Edition with Auto-Fallback
3
- A complete, copy-paste ready tool for Groq API with intelligent model fallback
4
- Features: Glass UI, Mobile-Responsive, Auto Model Switching, GitHub Integration
5
  """
6
 
7
  import os
@@ -11,31 +10,25 @@ import requests
11
  import gradio as gr
12
  from datetime import datetime
13
  from typing import Optional, List, Dict, Any, Tuple
14
- from functools import lru_cache
15
 
16
  # ==================== CONFIGURATION ====================
17
- # Set these environment variables:
18
- # GROQ_API_KEY - Your Groq API key
19
- # GITHUB_TOKEN - Your GitHub Personal Access Token
20
- # GITHUB_REPO - Your repo (format: username/repo-name)
21
-
22
  GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
23
  GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "")
24
  GITHUB_REPO = os.getenv("GITHUB_REPO", "")
 
25
 
26
  # Fallback chain - if selected model fails, try these in order
27
  MODEL_FALLBACK_CHAIN = [
28
- "llama-3.3-70b-versatile", # Primary workhorse
29
- "meta-llama/llama-4-scout-17b-16e-instruct", # Llama 4 Scout
30
- "meta-llama/llama-4-maverick-17b-128e-instruct", # Llama 4 Maverick
31
- "llama-3.1-8b-instant", # Fast fallback
32
- "openai/gpt-oss-120b", # GPT-OSS large
33
- "openai/gpt-oss-20b", # GPT-OSS small
34
- "qwen/qwen3-32b", # Qwen 3
35
- "mixtral-8x7b-32768", # Legacy fallback
36
  ]
37
 
38
- # Default models shown in UI (will be updated dynamically)
39
  DEFAULT_MODELS = [
40
  "llama-3.3-70b-versatile",
41
  "meta-llama/llama-4-scout-17b-16e-instruct",
@@ -47,16 +40,10 @@ DEFAULT_MODELS = [
47
  "qwen/qwen3-32b",
48
  "gemma2-9b-it",
49
  "mixtral-8x7b-32768",
50
- "whisper-large-v3-turbo",
51
- "groq/compound",
52
  ]
53
 
54
- # ==================== HELPER: CONVERT HISTORY TO CHATBOT FORMAT ====================
55
  def to_chatbot_messages(history: List[List[str]]) -> List[Dict[str, str]]:
56
- """
57
- Convert internal history format (list of [user, assistant]) to Gradio Chatbot format.
58
- Returns a list of dicts with 'role' and 'content'.
59
- """
60
  messages = []
61
  for user_msg, assistant_msg in history:
62
  if user_msg:
@@ -74,48 +61,29 @@ class GroqClient:
74
  "Authorization": f"Bearer {api_key}",
75
  "Content-Type": "application/json"
76
  }
77
- self.available_models = []
78
 
79
  def fetch_models(self) -> List[str]:
80
- """Fetch available models from Groq API"""
81
  url = f"{self.base_url}/models"
82
  try:
83
  response = requests.get(url, headers=self.headers, timeout=10)
84
  if response.status_code == 200:
85
  data = response.json()
86
- models = [m["id"] for m in data.get("data", [])]
87
- self.available_models = models
88
- return models
89
  except Exception as e:
90
  print(f"Error fetching models: {e}")
91
  return []
92
 
93
- def chat_completion(
94
- self,
95
- messages: List[Dict[str, str]],
96
- model: str,
97
- temperature: float = 0.7,
98
- max_tokens: int = 4096,
99
- stream: bool = False,
100
- retry_with_fallback: bool = True
101
- ) -> Tuple[Dict[str, Any], str]:
102
- """
103
- Send chat completion request with automatic fallback
104
- Returns: (response_dict, model_used)
105
- """
106
  url = f"{self.base_url}/chat/completions"
107
-
108
- # Determine which models to try
109
  models_to_try = [model]
110
  if retry_with_fallback and model in MODEL_FALLBACK_CHAIN:
111
- # Add remaining fallback chain after the selected model
112
  idx = MODEL_FALLBACK_CHAIN.index(model)
113
  models_to_try.extend(MODEL_FALLBACK_CHAIN[idx+1:])
114
  elif retry_with_fallback:
115
  models_to_try.extend(MODEL_FALLBACK_CHAIN)
116
 
117
  last_error = None
118
-
119
  for try_model in models_to_try:
120
  payload = {
121
  "model": try_model,
@@ -124,31 +92,20 @@ class GroqClient:
124
  "max_completion_tokens": max_tokens,
125
  "stream": stream
126
  }
127
-
128
  try:
129
  response = requests.post(url, headers=self.headers, json=payload, timeout=60)
130
-
131
  if response.status_code == 200:
132
  return response.json(), try_model
133
-
134
- # Handle specific errors
135
  error_data = response.json() if response.text else {}
136
  error_msg = error_data.get("error", {}).get("message", "")
137
-
138
- # If model not found, try next
139
  if "model_not_found" in error_msg or "does not exist" in error_msg:
140
  print(f"Model {try_model} not available, trying fallback...")
141
  last_error = f"{try_model}: {error_msg}"
142
  continue
143
-
144
- # For other errors, return immediately
145
  return {"error": error_msg or f"HTTP {response.status_code}", "detail": response.text}, try_model
146
-
147
  except requests.exceptions.RequestException as e:
148
  last_error = str(e)
149
  continue
150
-
151
- # All models failed
152
  return {"error": f"All models failed. Last error: {last_error}"}, model
153
 
154
  # ==================== GITHUB INTEGRATION ====================
@@ -162,30 +119,17 @@ class GitHubIntegration:
162
  "Accept": "application/vnd.github.v3+json"
163
  }
164
 
165
- def commit_file(
166
- self,
167
- content: str,
168
- filename: str,
169
- path: str = "groq-studio-sessions",
170
- message: Optional[str] = None
171
- ) -> Dict[str, str]:
172
- """Commit a file to GitHub repository"""
173
  if not self.token or not self.repo:
174
  return {"status": "error", "message": "GitHub credentials not configured"}
175
-
176
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
177
  full_filename = f"{timestamp}_{filename}"
178
  file_path = f"{path}/{full_filename}"
179
-
180
- content_bytes = content.encode('utf-8')
181
- content_b64 = base64.b64encode(content_bytes).decode('utf-8')
182
-
183
  check_url = f"{self.base_url}/repos/{self.repo}/contents/{file_path}"
184
-
185
  try:
186
  check_resp = requests.get(check_url, headers=self.headers)
187
  sha = check_resp.json().get("sha") if check_resp.status_code == 200 else None
188
-
189
  data = {
190
  "message": message or f"Update from Groq Studio - {timestamp}",
191
  "content": content_b64,
@@ -193,104 +137,90 @@ class GitHubIntegration:
193
  }
194
  if sha:
195
  data["sha"] = sha
196
-
197
  resp = requests.put(check_url, headers=self.headers, json=data)
198
  resp.raise_for_status()
199
-
200
  result = resp.json()
201
- return {
202
- "status": "success",
203
- "url": result["content"]["html_url"],
204
- "sha": result["content"]["sha"],
205
- "path": file_path
206
- }
207
  except Exception as e:
208
  return {"status": "error", "message": str(e)}
209
 
210
- def create_gist(self, content: str, description: str = "Groq Studio Export", public: bool = False) -> Dict[str, str]:
211
- """Create a GitHub Gist"""
212
  if not self.token:
213
  return {"status": "error", "message": "GitHub token not configured"}
214
-
215
  url = f"{self.base_url}/gists"
216
  data = {
217
  "description": description,
218
  "public": public,
219
- "files": {
220
- "groq_session.md": {
221
- "content": content
222
- }
223
- }
224
  }
225
-
226
  try:
227
  resp = requests.post(url, headers=self.headers, json=data)
228
  resp.raise_for_status()
229
  result = resp.json()
230
- return {
231
- "status": "success",
232
- "url": result["html_url"],
233
- "id": result["id"]
234
- }
235
  except Exception as e:
236
  return {"status": "error", "message": str(e)}
237
 
238
  # ==================== UI FUNCTIONS ====================
239
  def refresh_models(api_key: str) -> gr.update:
240
- """Refresh available models from API"""
241
- key = api_key or GROQ_API_KEY
242
- if not key:
243
  return gr.update(choices=DEFAULT_MODELS, value=DEFAULT_MODELS[0])
244
-
245
- client = GroqClient(key)
246
  models = client.fetch_models()
247
-
248
  if models:
249
- # Prioritize common models at top
250
  priority = ["llama-3.3-70b-versatile", "meta-llama/llama-4", "gpt-oss", "qwen", "llama-3.1"]
251
  sorted_models = []
252
  for p in priority:
253
  sorted_models.extend([m for m in models if p in m and m not in sorted_models])
254
  sorted_models.extend([m for m in models if m not in sorted_models])
255
  return gr.update(choices=sorted_models, value=sorted_models[0])
256
-
257
  return gr.update(choices=DEFAULT_MODELS, value=DEFAULT_MODELS[0])
258
 
 
 
 
 
259
  def process_chat(
260
  message: str,
261
- history: List[List[str]], # internal format: list of [user, assistant]
262
  model: str,
263
  temperature: float,
264
  max_tokens: int,
265
  system_prompt: str,
266
- api_key: str,
 
267
  auto_fallback: bool
268
  ) -> tuple:
269
- """Process chat message with Groq API and auto-fallback"""
270
- if not api_key and not GROQ_API_KEY:
271
- new_history = history + [[message, "❌ Error: Please provide a Groq API Key in settings or environment variable."]]
272
- return to_chatbot_messages(new_history), "", "⚠️ No API Key"
 
 
 
 
 
 
 
 
 
273
 
274
- key = api_key or GROQ_API_KEY
275
- client = GroqClient(key)
276
 
277
  # Build messages for API
278
  api_messages = []
279
  if system_prompt:
280
  api_messages.append({"role": "system", "content": system_prompt})
281
-
282
  for human, assistant in history:
283
  api_messages.append({"role": "user", "content": human})
284
  if assistant:
285
  api_messages.append({"role": "assistant", "content": assistant})
286
-
287
  api_messages.append({"role": "user", "content": message})
288
 
289
- # Show typing indicator: update chatbot with temporary assistant message
290
  temp_history = history + [[message, "⏳ Thinking..."]]
291
  yield to_chatbot_messages(temp_history), "", f"πŸ”„ Using {model}..."
292
 
293
- # Get response with fallback
294
  response, model_used = client.chat_completion(
295
  messages=api_messages,
296
  model=model,
@@ -299,7 +229,6 @@ def process_chat(
299
  retry_with_fallback=auto_fallback
300
  )
301
 
302
- # Update internal history
303
  if "error" in response:
304
  error_msg = f"❌ Error: {response['error']}"
305
  if "detail" in response:
@@ -310,11 +239,9 @@ def process_chat(
310
  try:
311
  content = response["choices"][0]["message"]["content"]
312
  usage = response.get("usage", {})
313
-
314
  fallback_notice = ""
315
  if model_used != model:
316
  fallback_notice = f" (⚠️ Fallback from {model})"
317
-
318
  info = f"\n\n---\n*Model: {model_used}{fallback_notice} | Tokens: {usage.get('total_tokens', 'N/A')}*"
319
  history.append([message, content + info])
320
  status = f"βœ… Success with {model_used}" + (" (fallback)" if model_used != model else "")
@@ -322,83 +249,41 @@ def process_chat(
322
  history.append([message, f"❌ Error parsing response: {str(e)}"])
323
  status = f"❌ Parse Error"
324
 
325
- # Convert to chatbot format and yield final result
326
  yield to_chatbot_messages(history), "", status
327
 
328
- def export_to_github(
329
- history: List[List[str]],
330
- filename: str,
331
- commit_msg: str,
332
- github_token: str,
333
- github_repo: str
334
- ) -> str:
335
- """Export chat history to GitHub"""
336
  token = github_token or GITHUB_TOKEN
337
  repo = github_repo or GITHUB_REPO
338
-
339
  if not token or not repo:
340
  return "❌ Error: GitHub token and repo not configured."
341
-
342
  content = format_conversation(history)
343
  gh = GitHubIntegration(token, repo)
344
  result = gh.commit_file(content, filename or "session.md", message=commit_msg)
 
345
 
346
- if result["status"] == "success":
347
- return f"βœ… Committed successfully!\n\nπŸ”— [View on GitHub]({result['url']})"
348
- else:
349
- return f"❌ Error: {result['message']}"
350
-
351
- def create_gist_export(
352
- history: List[List[str]],
353
- description: str,
354
- github_token: str
355
- ) -> str:
356
- """Export as GitHub Gist"""
357
  token = github_token or GITHUB_TOKEN
358
-
359
  if not token:
360
  return "❌ Error: GitHub token not configured."
361
-
362
  content = format_conversation(history)
363
  gh = GitHubIntegration(token, "")
364
  result = gh.create_gist(content, description)
 
365
 
366
- if result["status"] == "success":
367
- return f"βœ… Gist created!\n\nπŸ”— [View Gist]({result['url']})"
368
- else:
369
- return f"❌ Error: {result['message']}"
370
-
371
- def format_conversation(history: List[List[str]]) -> str:
372
- """Format chat history as markdown"""
373
  content = "# Groq Studio Session\n\n"
374
- content += f"**Date:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
375
- content += "## Conversation\n\n"
376
-
377
  for i, (human, assistant) in enumerate(history, 1):
378
  if not assistant or assistant == "⏳ Thinking...":
379
  continue
380
- content += f"### Turn {i}\n\n"
381
- content += f"**User:**\n{human}\n\n"
382
- content += f"**Assistant:**\n{assistant}\n\n---\n\n"
383
-
384
  return content
385
 
386
  def clear_chat():
387
- """Clear chat history"""
388
- return [], [], "πŸ”„ Ready" # returns (empty chatbot messages, empty internal history, status)
389
 
390
- def debug_request(
391
- message: str,
392
- history: List[List[str]],
393
- model: str,
394
- temperature: float,
395
- max_tokens: int,
396
- system_prompt: str,
397
- api_key: str
398
- ) -> str:
399
- """Generate debug/curl command"""
400
  key = api_key or GROQ_API_KEY
401
-
402
  messages = []
403
  if system_prompt:
404
  messages.append({"role": "system", "content": system_prompt})
@@ -407,41 +292,22 @@ def debug_request(
407
  if assistant:
408
  messages.append({"role": "assistant", "content": assistant})
409
  messages.append({"role": "user", "content": message})
410
-
411
  payload = {
412
  "model": model,
413
  "messages": messages,
414
  "temperature": temperature,
415
  "max_completion_tokens": max_tokens
416
  }
417
-
418
  curl_cmd = f"""curl -X POST https://api.groq.com/openai/v1/chat/completions \\
419
- -H "Authorization: Bearer {key[:10]}...{key[-4:] if len(key) > 14 else ''}" \\
420
  -H "Content-Type: application/json" \\
421
- -d '{json.dumps(payload, indent=2)}'"""
422
-
423
- python_code = f"""import requests
424
- import json
425
- import os
426
-
427
- url = "https://api.groq.com/openai/v1/chat/completions"
428
- headers = {{
429
- "Authorization": f"Bearer {{os.getenv('GROQ_API_KEY')}}",
430
- "Content-Type": "application/json"
431
- }}
432
-
433
- payload = {json.dumps(payload, indent=2)}
434
-
435
- response = requests.post(url, headers=headers, json=payload)
436
- print(f"Status: {{response.status_code}}")
437
- print(f"Response: {{response.text}}")"""
438
-
439
- return f"## cURL Command\n```bash\n{curl_cmd}\n```\n\n## Python Code\n```python\n{python_code}\n```"
440
 
441
  # ==================== CUSTOM CSS ====================
442
  CUSTOM_CSS = """
 
443
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
444
-
445
  :root {
446
  --glass-bg: rgba(20, 20, 30, 0.6);
447
  --glass-border: rgba(255, 255, 255, 0.1);
@@ -450,7 +316,6 @@ CUSTOM_CSS = """
450
  --text-primary: #ffffff;
451
  --gradient-1: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
452
  }
453
-
454
  body {
455
  font-family: 'Inter', sans-serif !important;
456
  background: linear-gradient(-45deg, #0f0c29, #302b63, #24243e, #1a1a2e) !important;
@@ -458,29 +323,24 @@ body {
458
  animation: gradientBG 15s ease infinite !important;
459
  min-height: 100vh;
460
  }
461
-
462
  @keyframes gradientBG {
463
  0% { background-position: 0% 50%; }
464
  50% { background-position: 100% 50%; }
465
  100% { background-position: 0% 50%; }
466
  }
467
-
468
  .glass-panel {
469
  background: var(--glass-bg) !important;
470
  backdrop-filter: blur(20px) !important;
471
- -webkit-backdrop-filter: blur(20px) !important;
472
  border: 1px solid var(--glass-border) !important;
473
  border-radius: 24px !important;
474
  box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37) !important;
475
  }
476
-
477
  .chatbot {
478
  background: rgba(0, 0, 0, 0.3) !important;
479
  border-radius: 20px !important;
480
  border: 1px solid var(--glass-border) !important;
481
  height: 600px !important;
482
  }
483
-
484
  .chatbot .message {
485
  background: var(--glass-bg) !important;
486
  border: 1px solid var(--glass-border) !important;
@@ -489,17 +349,14 @@ body {
489
  padding: 16px !important;
490
  backdrop-filter: blur(10px) !important;
491
  }
492
-
493
  .chatbot .message.user {
494
  background: linear-gradient(135deg, rgba(102, 126, 234, 0.3), rgba(118, 75, 162, 0.3)) !important;
495
  border-left: 4px solid #667eea !important;
496
  }
497
-
498
  .chatbot .message.bot {
499
  background: linear-gradient(135deg, rgba(78, 205, 196, 0.2), rgba(68, 160, 141, 0.2)) !important;
500
  border-left: 4px solid #4ecdc4 !important;
501
  }
502
-
503
  input, textarea, select {
504
  background: rgba(0, 0, 0, 0.4) !important;
505
  border: 1px solid var(--glass-border) !important;
@@ -507,7 +364,6 @@ input, textarea, select {
507
  color: var(--text-primary) !important;
508
  padding: 12px 16px !important;
509
  }
510
-
511
  button {
512
  background: var(--gradient-1) !important;
513
  border: none !important;
@@ -517,12 +373,10 @@ button {
517
  padding: 12px 24px !important;
518
  transition: all 0.3s ease !important;
519
  }
520
-
521
  button:hover {
522
  transform: translateY(-2px) !important;
523
  box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4) !important;
524
  }
525
-
526
  .status-bar {
527
  background: rgba(0, 0, 0, 0.5) !important;
528
  border-radius: 8px !important;
@@ -531,7 +385,6 @@ button:hover {
531
  color: #4ecdc4 !important;
532
  border: 1px solid var(--glass-border) !important;
533
  }
534
-
535
  @media (max-width: 768px) {
536
  .glass-panel { border-radius: 16px !important; margin: 8px !important; }
537
  .chatbot { height: 400px !important; }
@@ -542,115 +395,68 @@ button:hover {
542
  # ==================== GRADIO INTERFACE ====================
543
  def create_interface():
544
  with gr.Blocks(title="Groq AI Studio Pro") as demo:
 
 
 
 
 
 
 
 
 
 
545
 
546
- # Header
547
- with gr.Row():
548
- with gr.Column():
549
- gr.HTML("""
550
- <div class="glass-panel" style="text-align: center; padding: 20px; margin-bottom: 20px;">
551
- <h1 style="background: linear-gradient(135deg, #fff 0%, #a8edea 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 700; margin: 0;">
552
- ⚑ Groq AI Studio Pro
553
- </h1>
554
- <p style="color: #b0b0b0; margin-top: 8px;">
555
- <span style="color: #4ecdc4;">●</span> Auto-Fallback Enabled | Glass UI | GitHub Integration
556
- </p>
557
- </div>
558
- """)
559
-
560
- # State (internal history as list of lists)
561
  state_history = gr.State([])
562
 
563
  with gr.Row():
564
- # Left Panel - Chat
565
  with gr.Column(scale=3):
566
  with gr.Group(elem_classes="glass-panel"):
567
- # FIX: Removed bubble_full_width, show_copy_button, and type parameters
568
- chatbot = gr.Chatbot(
569
- label="Conversation",
570
- elem_classes="chatbot"
571
- )
572
-
573
  with gr.Row():
574
- msg_input = gr.Textbox(
575
- placeholder="Type your message here... (Shift+Enter for new line)",
576
- label="",
577
- scale=4,
578
- show_label=False,
579
- lines=1
580
- )
581
  send_btn = gr.Button("Send ➀", scale=1, variant="primary")
582
-
583
  with gr.Row():
584
  clear_btn = gr.Button("πŸ—‘οΈ Clear", variant="secondary")
585
  debug_btn = gr.Button("πŸ”§ Debug", variant="secondary")
586
  export_btn = gr.Button("πŸ“€ Export", variant="secondary")
 
587
 
588
- # Status bar
589
- status_text = gr.Textbox(
590
- value="πŸ”„ Ready",
591
- label="",
592
- interactive=False,
593
- elem_classes="status-bar"
594
- )
595
-
596
- # Right Panel - Settings
597
  with gr.Column(scale=1):
598
  with gr.Group(elem_classes="glass-panel"):
599
- gr.Markdown("### βš™οΈ Configuration")
 
 
 
 
 
 
 
 
 
 
 
 
600
 
601
- with gr.Accordion("πŸ”‘ API Settings", open=False):
602
- api_key_input = gr.Textbox(
603
- label="Groq API Key",
604
- placeholder="gsk_...",
605
- type="password",
606
- value=GROQ_API_KEY
607
- )
608
-
609
- github_token_input = gr.Textbox(
610
- label="GitHub Token",
611
- placeholder="ghp_...",
612
- type="password",
613
- value=GITHUB_TOKEN
614
- )
615
-
616
- github_repo_input = gr.Textbox(
617
- label="GitHub Repo",
618
- placeholder="username/repo",
619
- value=GITHUB_REPO
620
- )
621
 
622
  with gr.Row():
623
- model_dropdown = gr.Dropdown(
624
- choices=DEFAULT_MODELS,
625
- value=DEFAULT_MODELS[0],
626
- label="Model"
627
- )
628
  refresh_btn = gr.Button("πŸ”„", size="sm")
629
-
630
- auto_fallback = gr.Checkbox(
631
- label="Auto-Fallback if Model Fails",
632
- value=True,
633
- info="Automatically try backup models"
634
- )
635
-
636
- system_prompt = gr.Textbox(
637
- label="System Prompt",
638
- placeholder="You are a helpful assistant...",
639
- lines=3,
640
- value="You are a helpful, creative AI assistant powered by Groq. Respond concisely but thoroughly."
641
- )
642
-
643
  with gr.Row():
644
  temperature = gr.Slider(0, 2, value=0.7, label="Temperature")
645
  max_tokens = gr.Slider(100, 8192, value=4096, label="Max Tokens")
646
 
647
- # Debug Modal
648
  with gr.Row(visible=False) as debug_row:
649
  with gr.Column():
650
  debug_output = gr.Code(label="Debug Request", language="python")
651
  close_debug = gr.Button("Close")
652
-
653
- # Export Modal
654
  with gr.Row(visible=False) as export_row:
655
  with gr.Column():
656
  gr.Markdown("### Export to GitHub")
@@ -662,64 +468,52 @@ def create_interface():
662
  export_result = gr.Markdown()
663
  close_export = gr.Button("Close")
664
 
665
- # Event Handlers
666
- refresh_btn.click(
667
- refresh_models,
668
- [api_key_input],
669
- [model_dropdown]
670
- )
671
 
672
  send_event = msg_input.submit(
673
  process_chat,
674
- [msg_input, state_history, model_dropdown, temperature, max_tokens, system_prompt, api_key_input, auto_fallback],
 
675
  [chatbot, msg_input, status_text]
676
  )
677
-
678
  send_btn.click(
679
  process_chat,
680
- [msg_input, state_history, model_dropdown, temperature, max_tokens, system_prompt, api_key_input, auto_fallback],
 
681
  [chatbot, msg_input, status_text]
682
  )
683
 
684
- clear_btn.click(
685
- clear_chat,
686
- outputs=[chatbot, state_history, status_text]
687
- )
688
 
689
  debug_btn.click(
690
  debug_request,
691
- [msg_input, state_history, model_dropdown, temperature, max_tokens, system_prompt, api_key_input],
692
  [debug_output]
693
  ).then(lambda: gr.update(visible=True), outputs=[debug_row])
694
-
695
  close_debug.click(lambda: gr.update(visible=False), outputs=[debug_row])
696
 
697
  export_btn.click(lambda: gr.update(visible=True), outputs=[export_row])
698
  close_export.click(lambda: gr.update(visible=False), outputs=[export_row])
699
-
700
  commit_btn.click(
701
  export_to_github,
702
  [state_history, export_filename, export_message, github_token_input, github_repo_input],
703
  [export_result]
704
  )
705
-
706
  gist_btn.click(
707
  create_gist_export,
708
  [state_history, export_message, github_token_input],
709
  [export_result]
710
  )
711
 
712
- # Footer
713
  gr.HTML("""
714
  <div style="text-align: center; padding: 20px; color: #666; font-size: 12px;">
715
- <p>Powered by Groq API | Auto-Fallback: Llama 3.3 β†’ Llama 4 β†’ GPT-OSS β†’ Qwen |
716
- <a href="https://console.groq.com" target="_blank" style="color: #4ecdc4;">Get API Key</a></p>
717
  </div>
718
  """)
719
 
720
  return demo
721
 
722
- # Launch
723
  if __name__ == "__main__":
724
  demo = create_interface()
725
  demo.launch(server_name="0.0.0.0", server_port=7860, css=CUSTOM_CSS)
 
1
  """
2
  Groq AI Studio Pro - Glassmorphism Edition with Auto-Fallback
3
+ Now with password protection to secure your API key.
 
4
  """
5
 
6
  import os
 
10
  import gradio as gr
11
  from datetime import datetime
12
  from typing import Optional, List, Dict, Any, Tuple
 
13
 
14
  # ==================== CONFIGURATION ====================
 
 
 
 
 
15
  GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
16
  GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "")
17
  GITHUB_REPO = os.getenv("GITHUB_REPO", "")
18
+ ACCESS_PASSWORD = os.getenv("ACCESS_PASSWORD", "") # Secret password
19
 
20
  # Fallback chain - if selected model fails, try these in order
21
  MODEL_FALLBACK_CHAIN = [
22
+ "llama-3.3-70b-versatile",
23
+ "meta-llama/llama-4-scout-17b-16e-instruct",
24
+ "meta-llama/llama-4-maverick-17b-128e-instruct",
25
+ "llama-3.1-8b-instant",
26
+ "openai/gpt-oss-120b",
27
+ "openai/gpt-oss-20b",
28
+ "qwen/qwen3-32b",
29
+ "mixtral-8x7b-32768",
30
  ]
31
 
 
32
  DEFAULT_MODELS = [
33
  "llama-3.3-70b-versatile",
34
  "meta-llama/llama-4-scout-17b-16e-instruct",
 
40
  "qwen/qwen3-32b",
41
  "gemma2-9b-it",
42
  "mixtral-8x7b-32768",
 
 
43
  ]
44
 
45
+ # ==================== HELPER: CONVERT HISTORY ====================
46
  def to_chatbot_messages(history: List[List[str]]) -> List[Dict[str, str]]:
 
 
 
 
47
  messages = []
48
  for user_msg, assistant_msg in history:
49
  if user_msg:
 
61
  "Authorization": f"Bearer {api_key}",
62
  "Content-Type": "application/json"
63
  }
 
64
 
65
  def fetch_models(self) -> List[str]:
 
66
  url = f"{self.base_url}/models"
67
  try:
68
  response = requests.get(url, headers=self.headers, timeout=10)
69
  if response.status_code == 200:
70
  data = response.json()
71
+ return [m["id"] for m in data.get("data", [])]
 
 
72
  except Exception as e:
73
  print(f"Error fetching models: {e}")
74
  return []
75
 
76
+ def chat_completion(self, messages, model, temperature=0.7, max_tokens=4096,
77
+ stream=False, retry_with_fallback=True):
 
 
 
 
 
 
 
 
 
 
 
78
  url = f"{self.base_url}/chat/completions"
 
 
79
  models_to_try = [model]
80
  if retry_with_fallback and model in MODEL_FALLBACK_CHAIN:
 
81
  idx = MODEL_FALLBACK_CHAIN.index(model)
82
  models_to_try.extend(MODEL_FALLBACK_CHAIN[idx+1:])
83
  elif retry_with_fallback:
84
  models_to_try.extend(MODEL_FALLBACK_CHAIN)
85
 
86
  last_error = None
 
87
  for try_model in models_to_try:
88
  payload = {
89
  "model": try_model,
 
92
  "max_completion_tokens": max_tokens,
93
  "stream": stream
94
  }
 
95
  try:
96
  response = requests.post(url, headers=self.headers, json=payload, timeout=60)
 
97
  if response.status_code == 200:
98
  return response.json(), try_model
 
 
99
  error_data = response.json() if response.text else {}
100
  error_msg = error_data.get("error", {}).get("message", "")
 
 
101
  if "model_not_found" in error_msg or "does not exist" in error_msg:
102
  print(f"Model {try_model} not available, trying fallback...")
103
  last_error = f"{try_model}: {error_msg}"
104
  continue
 
 
105
  return {"error": error_msg or f"HTTP {response.status_code}", "detail": response.text}, try_model
 
106
  except requests.exceptions.RequestException as e:
107
  last_error = str(e)
108
  continue
 
 
109
  return {"error": f"All models failed. Last error: {last_error}"}, model
110
 
111
  # ==================== GITHUB INTEGRATION ====================
 
119
  "Accept": "application/vnd.github.v3+json"
120
  }
121
 
122
+ def commit_file(self, content, filename, path="groq-studio-sessions", message=None):
 
 
 
 
 
 
 
123
  if not self.token or not self.repo:
124
  return {"status": "error", "message": "GitHub credentials not configured"}
 
125
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
126
  full_filename = f"{timestamp}_{filename}"
127
  file_path = f"{path}/{full_filename}"
128
+ content_b64 = base64.b64encode(content.encode()).decode()
 
 
 
129
  check_url = f"{self.base_url}/repos/{self.repo}/contents/{file_path}"
 
130
  try:
131
  check_resp = requests.get(check_url, headers=self.headers)
132
  sha = check_resp.json().get("sha") if check_resp.status_code == 200 else None
 
133
  data = {
134
  "message": message or f"Update from Groq Studio - {timestamp}",
135
  "content": content_b64,
 
137
  }
138
  if sha:
139
  data["sha"] = sha
 
140
  resp = requests.put(check_url, headers=self.headers, json=data)
141
  resp.raise_for_status()
 
142
  result = resp.json()
143
+ return {"status": "success", "url": result["content"]["html_url"]}
 
 
 
 
 
144
  except Exception as e:
145
  return {"status": "error", "message": str(e)}
146
 
147
+ def create_gist(self, content, description="Groq Studio Export", public=False):
 
148
  if not self.token:
149
  return {"status": "error", "message": "GitHub token not configured"}
 
150
  url = f"{self.base_url}/gists"
151
  data = {
152
  "description": description,
153
  "public": public,
154
+ "files": {"groq_session.md": {"content": content}}
 
 
 
 
155
  }
 
156
  try:
157
  resp = requests.post(url, headers=self.headers, json=data)
158
  resp.raise_for_status()
159
  result = resp.json()
160
+ return {"status": "success", "url": result["html_url"]}
 
 
 
 
161
  except Exception as e:
162
  return {"status": "error", "message": str(e)}
163
 
164
  # ==================== UI FUNCTIONS ====================
165
  def refresh_models(api_key: str) -> gr.update:
166
+ if not api_key:
 
 
167
  return gr.update(choices=DEFAULT_MODELS, value=DEFAULT_MODELS[0])
168
+ client = GroqClient(api_key)
 
169
  models = client.fetch_models()
 
170
  if models:
 
171
  priority = ["llama-3.3-70b-versatile", "meta-llama/llama-4", "gpt-oss", "qwen", "llama-3.1"]
172
  sorted_models = []
173
  for p in priority:
174
  sorted_models.extend([m for m in models if p in m and m not in sorted_models])
175
  sorted_models.extend([m for m in models if m not in sorted_models])
176
  return gr.update(choices=sorted_models, value=sorted_models[0])
 
177
  return gr.update(choices=DEFAULT_MODELS, value=DEFAULT_MODELS[0])
178
 
179
+ def check_password(password: str) -> bool:
180
+ """Return True if password matches ACCESS_PASSWORD."""
181
+ return ACCESS_PASSWORD and password == ACCESS_PASSWORD
182
+
183
  def process_chat(
184
  message: str,
185
+ history: List[List[str]],
186
  model: str,
187
  temperature: float,
188
  max_tokens: int,
189
  system_prompt: str,
190
+ access_password: str, # new: password input
191
+ manual_api_key: str, # new: manual API key input
192
  auto_fallback: bool
193
  ) -> tuple:
194
+ # Determine which API key to use
195
+ if check_password(access_password):
196
+ # Correct password: use host's key
197
+ api_key = GROQ_API_KEY
198
+ if not api_key:
199
+ yield to_chatbot_messages(history + [[message, "❌ Host API key not configured."]]), "", "❌ No host key"
200
+ return
201
+ else:
202
+ # No or wrong password: user must provide their own key
203
+ api_key = manual_api_key
204
+ if not api_key:
205
+ yield to_chatbot_messages(history + [[message, "❌ Please enter your own Groq API Key or provide the correct access password."]]), "", "❌ No API Key"
206
+ return
207
 
208
+ client = GroqClient(api_key)
 
209
 
210
  # Build messages for API
211
  api_messages = []
212
  if system_prompt:
213
  api_messages.append({"role": "system", "content": system_prompt})
 
214
  for human, assistant in history:
215
  api_messages.append({"role": "user", "content": human})
216
  if assistant:
217
  api_messages.append({"role": "assistant", "content": assistant})
 
218
  api_messages.append({"role": "user", "content": message})
219
 
220
+ # Show typing indicator
221
  temp_history = history + [[message, "⏳ Thinking..."]]
222
  yield to_chatbot_messages(temp_history), "", f"πŸ”„ Using {model}..."
223
 
 
224
  response, model_used = client.chat_completion(
225
  messages=api_messages,
226
  model=model,
 
229
  retry_with_fallback=auto_fallback
230
  )
231
 
 
232
  if "error" in response:
233
  error_msg = f"❌ Error: {response['error']}"
234
  if "detail" in response:
 
239
  try:
240
  content = response["choices"][0]["message"]["content"]
241
  usage = response.get("usage", {})
 
242
  fallback_notice = ""
243
  if model_used != model:
244
  fallback_notice = f" (⚠️ Fallback from {model})"
 
245
  info = f"\n\n---\n*Model: {model_used}{fallback_notice} | Tokens: {usage.get('total_tokens', 'N/A')}*"
246
  history.append([message, content + info])
247
  status = f"βœ… Success with {model_used}" + (" (fallback)" if model_used != model else "")
 
249
  history.append([message, f"❌ Error parsing response: {str(e)}"])
250
  status = f"❌ Parse Error"
251
 
 
252
  yield to_chatbot_messages(history), "", status
253
 
254
+ def export_to_github(history, filename, commit_msg, github_token, github_repo):
 
 
 
 
 
 
 
255
  token = github_token or GITHUB_TOKEN
256
  repo = github_repo or GITHUB_REPO
 
257
  if not token or not repo:
258
  return "❌ Error: GitHub token and repo not configured."
 
259
  content = format_conversation(history)
260
  gh = GitHubIntegration(token, repo)
261
  result = gh.commit_file(content, filename or "session.md", message=commit_msg)
262
+ return f"βœ… Committed! [View]({result['url']})" if result["status"] == "success" else f"❌ {result['message']}"
263
 
264
+ def create_gist_export(history, description, github_token):
 
 
 
 
 
 
 
 
 
 
265
  token = github_token or GITHUB_TOKEN
 
266
  if not token:
267
  return "❌ Error: GitHub token not configured."
 
268
  content = format_conversation(history)
269
  gh = GitHubIntegration(token, "")
270
  result = gh.create_gist(content, description)
271
+ return f"βœ… Gist created! [View]({result['url']})" if result["status"] == "success" else f"❌ {result['message']}"
272
 
273
+ def format_conversation(history):
 
 
 
 
 
 
274
  content = "# Groq Studio Session\n\n"
275
+ content += f"**Date:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n## Conversation\n\n"
 
 
276
  for i, (human, assistant) in enumerate(history, 1):
277
  if not assistant or assistant == "⏳ Thinking...":
278
  continue
279
+ content += f"### Turn {i}\n\n**User:**\n{human}\n\n**Assistant:**\n{assistant}\n\n---\n\n"
 
 
 
280
  return content
281
 
282
  def clear_chat():
283
+ return [], [], "πŸ”„ Ready"
 
284
 
285
+ def debug_request(message, history, model, temperature, max_tokens, system_prompt, api_key):
 
 
 
 
 
 
 
 
 
286
  key = api_key or GROQ_API_KEY
 
287
  messages = []
288
  if system_prompt:
289
  messages.append({"role": "system", "content": system_prompt})
 
292
  if assistant:
293
  messages.append({"role": "assistant", "content": assistant})
294
  messages.append({"role": "user", "content": message})
 
295
  payload = {
296
  "model": model,
297
  "messages": messages,
298
  "temperature": temperature,
299
  "max_completion_tokens": max_tokens
300
  }
 
301
  curl_cmd = f"""curl -X POST https://api.groq.com/openai/v1/chat/completions \\
302
+ -H "Authorization: Bearer {key[:10]}..." \\
303
  -H "Content-Type: application/json" \\
304
+ -d '{json.dumps(payload)}'"""
305
+ return f"## cURL\n```bash\n{curl_cmd}\n```"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
 
307
  # ==================== CUSTOM CSS ====================
308
  CUSTOM_CSS = """
309
+ /* Your existing glassmorphism CSS – unchanged */
310
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
 
311
  :root {
312
  --glass-bg: rgba(20, 20, 30, 0.6);
313
  --glass-border: rgba(255, 255, 255, 0.1);
 
316
  --text-primary: #ffffff;
317
  --gradient-1: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
318
  }
 
319
  body {
320
  font-family: 'Inter', sans-serif !important;
321
  background: linear-gradient(-45deg, #0f0c29, #302b63, #24243e, #1a1a2e) !important;
 
323
  animation: gradientBG 15s ease infinite !important;
324
  min-height: 100vh;
325
  }
 
326
  @keyframes gradientBG {
327
  0% { background-position: 0% 50%; }
328
  50% { background-position: 100% 50%; }
329
  100% { background-position: 0% 50%; }
330
  }
 
331
  .glass-panel {
332
  background: var(--glass-bg) !important;
333
  backdrop-filter: blur(20px) !important;
 
334
  border: 1px solid var(--glass-border) !important;
335
  border-radius: 24px !important;
336
  box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37) !important;
337
  }
 
338
  .chatbot {
339
  background: rgba(0, 0, 0, 0.3) !important;
340
  border-radius: 20px !important;
341
  border: 1px solid var(--glass-border) !important;
342
  height: 600px !important;
343
  }
 
344
  .chatbot .message {
345
  background: var(--glass-bg) !important;
346
  border: 1px solid var(--glass-border) !important;
 
349
  padding: 16px !important;
350
  backdrop-filter: blur(10px) !important;
351
  }
 
352
  .chatbot .message.user {
353
  background: linear-gradient(135deg, rgba(102, 126, 234, 0.3), rgba(118, 75, 162, 0.3)) !important;
354
  border-left: 4px solid #667eea !important;
355
  }
 
356
  .chatbot .message.bot {
357
  background: linear-gradient(135deg, rgba(78, 205, 196, 0.2), rgba(68, 160, 141, 0.2)) !important;
358
  border-left: 4px solid #4ecdc4 !important;
359
  }
 
360
  input, textarea, select {
361
  background: rgba(0, 0, 0, 0.4) !important;
362
  border: 1px solid var(--glass-border) !important;
 
364
  color: var(--text-primary) !important;
365
  padding: 12px 16px !important;
366
  }
 
367
  button {
368
  background: var(--gradient-1) !important;
369
  border: none !important;
 
373
  padding: 12px 24px !important;
374
  transition: all 0.3s ease !important;
375
  }
 
376
  button:hover {
377
  transform: translateY(-2px) !important;
378
  box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4) !important;
379
  }
 
380
  .status-bar {
381
  background: rgba(0, 0, 0, 0.5) !important;
382
  border-radius: 8px !important;
 
385
  color: #4ecdc4 !important;
386
  border: 1px solid var(--glass-border) !important;
387
  }
 
388
  @media (max-width: 768px) {
389
  .glass-panel { border-radius: 16px !important; margin: 8px !important; }
390
  .chatbot { height: 400px !important; }
 
395
  # ==================== GRADIO INTERFACE ====================
396
  def create_interface():
397
  with gr.Blocks(title="Groq AI Studio Pro") as demo:
398
+ gr.HTML("""
399
+ <div class="glass-panel" style="text-align: center; padding: 20px; margin-bottom: 20px;">
400
+ <h1 style="background: linear-gradient(135deg, #fff 0%, #a8edea 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 700; margin: 0;">
401
+ ⚑ Groq AI Studio Pro
402
+ </h1>
403
+ <p style="color: #b0b0b0; margin-top: 8px;">
404
+ <span style="color: #4ecdc4;">●</span> πŸ”’ Password Protected | Auto-Fallback | GitHub Integration
405
+ </p>
406
+ </div>
407
+ """)
408
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  state_history = gr.State([])
410
 
411
  with gr.Row():
 
412
  with gr.Column(scale=3):
413
  with gr.Group(elem_classes="glass-panel"):
414
+ chatbot = gr.Chatbot(label="Conversation", elem_classes="chatbot")
 
 
 
 
 
415
  with gr.Row():
416
+ msg_input = gr.Textbox(placeholder="Type your message...", scale=4, show_label=False, lines=1)
 
 
 
 
 
 
417
  send_btn = gr.Button("Send ➀", scale=1, variant="primary")
 
418
  with gr.Row():
419
  clear_btn = gr.Button("πŸ—‘οΈ Clear", variant="secondary")
420
  debug_btn = gr.Button("πŸ”§ Debug", variant="secondary")
421
  export_btn = gr.Button("πŸ“€ Export", variant="secondary")
422
+ status_text = gr.Textbox(value="πŸ”„ Ready", interactive=False, elem_classes="status-bar")
423
 
 
 
 
 
 
 
 
 
 
424
  with gr.Column(scale=1):
425
  with gr.Group(elem_classes="glass-panel"):
426
+ gr.Markdown("### πŸ” Access & API Keys")
427
+ # Password field
428
+ access_password = gr.Textbox(
429
+ label="Access Password",
430
+ placeholder="Enter password to use host API key",
431
+ type="password"
432
+ )
433
+ # Manual API key (only needed if password wrong or missing)
434
+ manual_api_key = gr.Textbox(
435
+ label="Your Groq API Key (if no password)",
436
+ placeholder="gsk_...",
437
+ type="password"
438
+ )
439
 
440
+ gr.Markdown("### βš™οΈ Configuration")
441
+ with gr.Accordion("πŸ”‘ GitHub Settings", open=False):
442
+ github_token_input = gr.Textbox(label="GitHub Token", type="password", value=GITHUB_TOKEN)
443
+ github_repo_input = gr.Textbox(label="GitHub Repo", value=GITHUB_REPO)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
 
445
  with gr.Row():
446
+ model_dropdown = gr.Dropdown(choices=DEFAULT_MODELS, value=DEFAULT_MODELS[0], label="Model")
 
 
 
 
447
  refresh_btn = gr.Button("πŸ”„", size="sm")
448
+ auto_fallback = gr.Checkbox(label="Auto-Fallback", value=True)
449
+ system_prompt = gr.Textbox(label="System Prompt", lines=3,
450
+ value="You are a helpful AI assistant powered by Groq.")
 
 
 
 
 
 
 
 
 
 
 
451
  with gr.Row():
452
  temperature = gr.Slider(0, 2, value=0.7, label="Temperature")
453
  max_tokens = gr.Slider(100, 8192, value=4096, label="Max Tokens")
454
 
455
+ # Debug & Export modals (simplified for brevity, same as before)
456
  with gr.Row(visible=False) as debug_row:
457
  with gr.Column():
458
  debug_output = gr.Code(label="Debug Request", language="python")
459
  close_debug = gr.Button("Close")
 
 
460
  with gr.Row(visible=False) as export_row:
461
  with gr.Column():
462
  gr.Markdown("### Export to GitHub")
 
468
  export_result = gr.Markdown()
469
  close_export = gr.Button("Close")
470
 
471
+ # Event handlers
472
+ refresh_btn.click(refresh_models, [manual_api_key], [model_dropdown])
 
 
 
 
473
 
474
  send_event = msg_input.submit(
475
  process_chat,
476
+ [msg_input, state_history, model_dropdown, temperature, max_tokens, system_prompt,
477
+ access_password, manual_api_key, auto_fallback],
478
  [chatbot, msg_input, status_text]
479
  )
 
480
  send_btn.click(
481
  process_chat,
482
+ [msg_input, state_history, model_dropdown, temperature, max_tokens, system_prompt,
483
+ access_password, manual_api_key, auto_fallback],
484
  [chatbot, msg_input, status_text]
485
  )
486
 
487
+ clear_btn.click(clear_chat, outputs=[chatbot, state_history, status_text])
 
 
 
488
 
489
  debug_btn.click(
490
  debug_request,
491
+ [msg_input, state_history, model_dropdown, temperature, max_tokens, system_prompt, manual_api_key],
492
  [debug_output]
493
  ).then(lambda: gr.update(visible=True), outputs=[debug_row])
 
494
  close_debug.click(lambda: gr.update(visible=False), outputs=[debug_row])
495
 
496
  export_btn.click(lambda: gr.update(visible=True), outputs=[export_row])
497
  close_export.click(lambda: gr.update(visible=False), outputs=[export_row])
 
498
  commit_btn.click(
499
  export_to_github,
500
  [state_history, export_filename, export_message, github_token_input, github_repo_input],
501
  [export_result]
502
  )
 
503
  gist_btn.click(
504
  create_gist_export,
505
  [state_history, export_message, github_token_input],
506
  [export_result]
507
  )
508
 
 
509
  gr.HTML("""
510
  <div style="text-align: center; padding: 20px; color: #666; font-size: 12px;">
511
+ <p>πŸ”’ Password protection enabled. Use the correct password to access the host API key.</p>
 
512
  </div>
513
  """)
514
 
515
  return demo
516
 
 
517
  if __name__ == "__main__":
518
  demo = create_interface()
519
  demo.launch(server_name="0.0.0.0", server_port=7860, css=CUSTOM_CSS)