shukdevdattaEX commited on
Commit
1aa9138
·
verified ·
1 Parent(s): 8b9d003

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +30 -1015
app.py CHANGED
@@ -1,1027 +1,42 @@
 
 
1
  import os
2
- import json
3
- import time
4
- import gradio as gr
5
- from datetime import datetime
6
- from typing import List, Dict, Any, Optional, Union
7
- import threading
8
- import re
9
- import aiohttp
10
- import asyncio
11
 
12
- # Import Groq
13
- from groq import Groq
14
 
15
- class CreativeAgenticAI:
16
- """
17
- Creative Agentic AI Chat Tool using multiple providers (Groq and Chutes)
18
- """
19
-
20
- def __init__(self, groq_api_key: str = None, chutes_api_key: str = None, provider: str = "groq", model: str = None):
21
- """
22
- Initialize the Creative Agentic AI system.
23
-
24
- Args:
25
- groq_api_key: Groq API key
26
- chutes_api_key: Chutes API key
27
- provider: Which provider to use ('groq' or 'chutes')
28
- model: Which model to use
29
- """
30
- self.groq_api_key = groq_api_key
31
- self.chutes_api_key = chutes_api_key
32
- self.provider = provider
33
- self.conversation_history = []
34
-
35
- # Initialize clients based on provider
36
- if provider == "groq" and groq_api_key:
37
- if not groq_api_key:
38
- raise ValueError("No Groq API key provided")
39
- self.groq_client = Groq(api_key=groq_api_key)
40
- self.model = model or "compound-beta"
41
- elif provider == "chutes" and chutes_api_key:
42
- if not chutes_api_key:
43
- raise ValueError("No Chutes API key provided")
44
- self.model = model or "openai/gpt-oss-20b"
45
- else:
46
- raise ValueError(f"Invalid provider or missing API key for {provider}")
47
-
48
- async def _chutes_chat_async(self, messages: List[Dict], temperature: float = 0.7, max_tokens: int = 1024) -> str:
49
- """
50
- Async method for Chutes API chat
51
- """
52
- headers = {
53
- "Authorization": f"Bearer {self.chutes_api_key}",
54
- "Content-Type": "application/json"
55
- }
56
-
57
- body = {
58
- "model": self.model,
59
- "messages": messages,
60
- "stream": False, # Set to False for simpler handling
61
- "max_tokens": max_tokens,
62
- "temperature": temperature
63
- }
64
-
65
- async with aiohttp.ClientSession() as session:
66
- async with session.post(
67
- "https://llm.chutes.ai/v1/chat/completions",
68
- headers=headers,
69
- json=body
70
- ) as response:
71
- if response.status == 200:
72
- result = await response.json()
73
- return result['choices'][0]['message']['content']
74
- else:
75
- error_text = await response.text()
76
- raise Exception(f"Chutes API error: {response.status} - {error_text}")
77
 
78
- def _chutes_chat_sync(self, messages: List[Dict], temperature: float = 0.7, max_tokens: int = 1024) -> str:
79
- """
80
- Synchronous wrapper for Chutes API chat
81
- """
82
- try:
83
- loop = asyncio.get_event_loop()
84
- except RuntimeError:
85
- loop = asyncio.new_event_loop()
86
- asyncio.set_event_loop(loop)
87
-
88
- return loop.run_until_complete(
89
- self._chutes_chat_async(messages, temperature, max_tokens)
90
- )
91
-
92
- def chat(self, message: str,
93
- include_domains: List[str] = None,
94
- exclude_domains: List[str] = None,
95
- system_prompt: str = None,
96
- temperature: float = 0.7,
97
- max_tokens: int = 1024) -> Dict:
98
- """
99
- Send a message to the AI and get a response
100
-
101
- Args:
102
- message: User's message
103
- include_domains: List of domains to include for web search (Groq only)
104
- exclude_domains: List of domains to exclude from web search (Groq only)
105
- system_prompt: Custom system prompt
106
- temperature: Model temperature (0.0-2.0)
107
- max_tokens: Maximum tokens in response
108
-
109
- Returns:
110
- AI response with metadata
111
- """
112
- # Enhanced system prompt for better citation behavior
113
- if not system_prompt:
114
- if self.provider == "groq":
115
- citation_instruction = """
116
- IMPORTANT: When you search the web and find information, you MUST:
117
- 1. Always cite your sources with clickable links in this format: [Source Title](URL)
118
- 2. Include multiple diverse sources when possible
119
- 3. Show which specific websites you used for each claim
120
- 4. At the end of your response, provide a "Sources Used" section with all the links
121
- 5. Be transparent about which information comes from which source
122
- """
123
-
124
- domain_context = ""
125
- if include_domains:
126
- domain_context = f"\nYou are restricted to searching ONLY these domains: {', '.join(include_domains)}. Make sure to find and cite sources specifically from these domains."
127
- elif exclude_domains:
128
- domain_context = f"\nAvoid searching these domains: {', '.join(exclude_domains)}. Search everywhere else on the web."
129
-
130
- system_prompt = f"""You are a creative and intelligent AI assistant with agentic capabilities.
131
- You can search the web, analyze information, and provide comprehensive responses.
132
- Be helpful, creative, and engaging while maintaining accuracy.
133
-
134
- {citation_instruction}
135
- {domain_context}
136
-
137
- Your responses should be well-structured, informative, and properly cited with working links."""
138
- else:
139
- # Simpler system prompt for Chutes (no web search capabilities)
140
- system_prompt = """You are a creative and intelligent AI assistant.
141
- Be helpful, creative, and engaging while maintaining accuracy.
142
- Your responses should be well-structured, informative, and comprehensive."""
143
-
144
- # Build messages
145
- messages = [{"role": "system", "content": system_prompt}]
146
-
147
- # Add conversation history (last 10 exchanges)
148
- messages.extend(self.conversation_history[-20:]) # Last 10 user-assistant pairs
149
-
150
- # Add current message with domain filtering context (Groq only)
151
- enhanced_message = message
152
- if self.provider == "groq" and (include_domains or exclude_domains):
153
- filter_context = []
154
- if include_domains:
155
- filter_context.append(f"ONLY search these domains: {', '.join(include_domains)}")
156
- if exclude_domains:
157
- filter_context.append(f"EXCLUDE these domains: {', '.join(exclude_domains)}")
158
- enhanced_message += f"\n\n[Domain Filtering: {' | '.join(filter_context)}]"
159
-
160
- messages.append({"role": "user", "content": enhanced_message})
161
-
162
- try:
163
- if self.provider == "groq":
164
- return self._handle_groq_chat(messages, include_domains, exclude_domains, temperature, max_tokens, message)
165
- elif self.provider == "chutes":
166
- return self._handle_chutes_chat(messages, temperature, max_tokens, message)
167
- else:
168
- raise ValueError(f"Unknown provider: {self.provider}")
169
-
170
- except Exception as e:
171
- error_msg = f"Error: {str(e)}"
172
- self.conversation_history.append({"role": "user", "content": message})
173
- self.conversation_history.append({"role": "assistant", "content": error_msg})
174
-
175
- return {
176
- "content": error_msg,
177
- "timestamp": datetime.now().isoformat(),
178
- "model": self.model,
179
- "provider": self.provider,
180
- "tool_usage": None,
181
- "error": str(e)
182
- }
183
 
184
- def _handle_groq_chat(self, messages: List[Dict], include_domains: List[str], exclude_domains: List[str],
185
- temperature: float, max_tokens: int, original_message: str) -> Dict:
186
- """Handle Groq API chat"""
187
- # Set up API parameters
188
- params = {
189
- "messages": messages,
190
- "model": self.model,
191
- "temperature": temperature,
192
- "max_tokens": max_tokens
193
- }
194
-
195
- # Add domain filtering if specified
196
- if include_domains and include_domains[0].strip():
197
- params["include_domains"] = [domain.strip() for domain in include_domains if domain.strip()]
198
- if exclude_domains and exclude_domains[0].strip():
199
- params["exclude_domains"] = [domain.strip() for domain in exclude_domains if domain.strip()]
200
-
201
- # Make the API call
202
- response = self.groq_client.chat.completions.create(**params)
203
- content = response.choices[0].message.content
204
-
205
- # Extract tool usage information and enhance it
206
- tool_info = self._extract_tool_info(response)
207
-
208
- # Process content to enhance citations
209
- processed_content = self._enhance_citations(content, tool_info)
210
-
211
- # Add to conversation history
212
- self.conversation_history.append({"role": "user", "content": original_message})
213
- self.conversation_history.append({"role": "assistant", "content": processed_content})
214
-
215
- # Create response object
216
- return {
217
- "content": processed_content,
218
- "timestamp": datetime.now().isoformat(),
219
- "model": self.model,
220
- "provider": "groq",
221
- "tool_usage": tool_info,
222
- "parameters": {
223
- "temperature": temperature,
224
- "max_tokens": max_tokens,
225
- "include_domains": include_domains,
226
- "exclude_domains": exclude_domains
227
- }
228
- }
229
 
230
- def _handle_chutes_chat(self, messages: List[Dict], temperature: float, max_tokens: int, original_message: str) -> Dict:
231
- """Handle Chutes API chat"""
232
- content = self._chutes_chat_sync(messages, temperature, max_tokens)
233
-
234
- # Add to conversation history
235
- self.conversation_history.append({"role": "user", "content": original_message})
236
- self.conversation_history.append({"role": "assistant", "content": content})
237
-
238
- # Create response object
239
- return {
240
- "content": content,
241
- "timestamp": datetime.now().isoformat(),
242
- "model": self.model,
243
- "provider": "chutes",
244
- "tool_usage": None, # Chutes doesn't have tool usage info
245
- "parameters": {
246
- "temperature": temperature,
247
- "max_tokens": max_tokens
248
- }
249
- }
250
-
251
- def _extract_tool_info(self, response) -> Dict:
252
- """Extract tool usage information in a JSON serializable format (Groq only)"""
253
- tool_info = {
254
- "tools_used": [],
255
- "search_queries": [],
256
- "sources_found": []
257
- }
258
-
259
- if hasattr(response.choices[0].message, 'executed_tools'):
260
- tools = response.choices[0].message.executed_tools
261
- if tools:
262
- for tool in tools:
263
- tool_dict = {
264
- "tool_type": getattr(tool, "type", "unknown"),
265
- "tool_name": getattr(tool, "name", "unknown"),
266
- }
267
-
268
- # Extract search queries and results
269
- if hasattr(tool, "input"):
270
- tool_input = str(tool.input)
271
- tool_dict["input"] = tool_input
272
- # Try to extract search query
273
- if "search" in tool_dict["tool_name"].lower():
274
- tool_info["search_queries"].append(tool_input)
275
-
276
- if hasattr(tool, "output"):
277
- tool_output = str(tool.output)
278
- tool_dict["output"] = tool_output
279
- # Try to extract URLs from output
280
- urls = self._extract_urls(tool_output)
281
- tool_info["sources_found"].extend(urls)
282
-
283
- tool_info["tools_used"].append(tool_dict)
284
-
285
- return tool_info
286
-
287
- def _extract_urls(self, text: str) -> List[str]:
288
- """Extract URLs from text"""
289
- url_pattern = r'https?://[^\s<>"]{2,}'
290
- urls = re.findall(url_pattern, text)
291
- return list(set(urls)) # Remove duplicates
292
-
293
- def _enhance_citations(self, content: str, tool_info: Dict) -> str:
294
- """Enhance content with better citation formatting (Groq only)"""
295
- if not tool_info or not tool_info.get("sources_found"):
296
- return content
297
-
298
- # Add sources section if not already present
299
- if "Sources Used:" not in content and "sources:" not in content.lower():
300
- sources_section = "\n\n---\n\n### 📚 Sources Used:\n"
301
- for i, url in enumerate(tool_info["sources_found"][:10], 1): # Limit to 10 sources
302
- # Try to extract domain name for better formatting
303
- domain = self._extract_domain(url)
304
- sources_section += f"{i}. [{domain}]({url})\n"
305
-
306
- content += sources_section
307
-
308
- return content
309
-
310
- def _extract_domain(self, url: str) -> str:
311
- """Extract domain name from URL for display"""
312
- try:
313
- if url.startswith(('http://', 'https://')):
314
- domain = url.split('/')[2]
315
- # Remove www. prefix if present
316
- if domain.startswith('www.'):
317
- domain = domain[4:]
318
- return domain
319
- return url
320
- except:
321
- return url
322
-
323
- def clear_history(self):
324
- """Clear conversation history"""
325
- self.conversation_history = []
326
-
327
- def get_history_summary(self) -> str:
328
- """Get a summary of conversation history"""
329
- if not self.conversation_history:
330
- return "No conversation history"
331
-
332
- user_messages = [msg for msg in self.conversation_history if msg["role"] == "user"]
333
- assistant_messages = [msg for msg in self.conversation_history if msg["role"] == "assistant"]
334
-
335
- return f"Conversation: {len(user_messages)} user messages, {len(assistant_messages)} assistant responses"
336
 
337
- # Global variables
338
- ai_instance = None
339
- current_provider = "groq"
340
- api_key_status = {"groq": "Not Set", "chutes": "Not Set"}
341
 
342
- def validate_api_keys(groq_api_key: str, chutes_api_key: str, provider: str, model: str) -> str:
343
- """Validate API keys and initialize AI instance"""
344
- global ai_instance, current_provider, api_key_status
345
-
346
- current_provider = provider
347
-
348
- if provider == "groq":
349
- if not groq_api_key or len(groq_api_key.strip()) < 10:
350
- api_key_status["groq"] = "Invalid ❌"
351
- return "❌ Please enter a valid Groq API key (should be longer than 10 characters)"
352
-
353
- try:
354
- # Test the Groq API key
355
- client = Groq(api_key=groq_api_key)
356
- test_response = client.chat.completions.create(
357
- messages=[{"role": "user", "content": "Hello"}],
358
- model=model,
359
- max_tokens=10
360
- )
361
-
362
- # Create AI instance
363
- ai_instance = CreativeAgenticAI(groq_api_key=groq_api_key, provider="groq", model=model)
364
- api_key_status["groq"] = "Valid ✅"
365
-
366
- return f"✅ Groq API Key Valid! Creative Agentic AI is ready.\n\n**Provider:** Groq\n**Model:** {model}\n**Status:** Connected with web search capabilities!"
367
-
368
- except Exception as e:
369
- api_key_status["groq"] = "Invalid ❌"
370
- ai_instance = None
371
- return f"❌ Error validating Groq API key: {str(e)}\n\nPlease check your API key and try again."
372
-
373
- elif provider == "chutes":
374
- if not chutes_api_key or len(chutes_api_key.strip()) < 10:
375
- api_key_status["chutes"] = "Invalid ❌"
376
- return "❌ Please enter a valid Chutes API key (should be longer than 10 characters)"
377
-
378
- try:
379
- # Test the Chutes API key with a simple request
380
- test_ai = CreativeAgenticAI(chutes_api_key=chutes_api_key, provider="chutes", model=model)
381
- test_response = test_ai._chutes_chat_sync(
382
- [{"role": "user", "content": "Hello"}],
383
- temperature=0.7,
384
- max_tokens=10
385
- )
386
-
387
- # Create AI instance
388
- ai_instance = CreativeAgenticAI(chutes_api_key=chutes_api_key, provider="chutes", model=model)
389
- api_key_status["chutes"] = "Valid ✅"
390
-
391
- return f"✅ Chutes API Key Valid! Creative AI is ready.\n\n**Provider:** Chutes\n**Model:** {model}\n**Status:** Connected (text generation focused)!"
392
-
393
- except Exception as e:
394
- api_key_status["chutes"] = "Invalid ❌"
395
- ai_instance = None
396
- return f"❌ Error validating Chutes API key: {str(e)}\n\nPlease check your API key and try again."
397
 
398
- def get_available_models(provider: str) -> List[str]:
399
- """Get available models for the selected provider"""
400
- if provider == "groq":
401
- return ["compound-beta", "compound-beta-mini"]
402
- elif provider == "chutes":
403
- return ["openai/gpt-oss-20b", "zai-org/GLM-4.5-Air", "Qwen/Qwen3-8B"]
404
- return []
405
 
406
- def update_model_choices(provider: str):
407
- """Update model choices based on provider selection"""
408
- models = get_available_models(provider)
409
- return gr.Radio(choices=models, value=models[0] if models else None, label=f"🧠 {provider.title()} Models")
 
410
 
411
- def chat_with_ai(message: str,
412
- include_domains: str,
413
- exclude_domains: str,
414
- system_prompt: str,
415
- temperature: float,
416
- max_tokens: int,
417
- history: List) -> tuple:
418
- """Main chat function"""
419
- global ai_instance, current_provider
420
-
421
- if not ai_instance:
422
- error_msg = f"⚠️ Please set your {current_provider.title()} API key first!"
423
- history.append([message, error_msg])
424
- return history, ""
425
-
426
- if not message.strip():
427
- return history, ""
428
-
429
- # Process domain lists (only for Groq)
430
- include_list = None
431
- exclude_list = None
432
- if current_provider == "groq":
433
- include_list = [d.strip() for d in include_domains.split(",")] if include_domains.strip() else []
434
- exclude_list = [d.strip() for d in exclude_domains.split(",")] if exclude_domains.strip() else []
435
-
436
- try:
437
- # Get AI response
438
- response = ai_instance.chat(
439
- message=message,
440
- include_domains=include_list if include_list else None,
441
- exclude_domains=exclude_list if exclude_list else None,
442
- system_prompt=system_prompt if system_prompt.strip() else None,
443
- temperature=temperature,
444
- max_tokens=int(max_tokens)
445
- )
446
-
447
- # Format response
448
- ai_response = response["content"]
449
-
450
- # Add enhanced tool usage info (Groq only)
451
- if response.get("tool_usage") and current_provider == "groq":
452
- tool_info = response["tool_usage"]
453
- tool_summary = []
454
-
455
- if tool_info.get("search_queries"):
456
- tool_summary.append(f"🔍 Search queries: {len(tool_info['search_queries'])}")
457
-
458
- if tool_info.get("sources_found"):
459
- tool_summary.append(f"📄 Sources found: {len(tool_info['sources_found'])}")
460
-
461
- if tool_info.get("tools_used"):
462
- tool_summary.append(f"🔧 Tools used: {len(tool_info['tools_used'])}")
463
-
464
- if tool_summary:
465
- ai_response += f"\n\n*{' | '.join(tool_summary)}*"
466
-
467
- # Add domain filtering info (Groq only)
468
- if current_provider == "groq" and (include_list or exclude_list):
469
- filter_info = []
470
- if include_list:
471
- filter_info.append(f"✅ Included domains: {', '.join(include_list)}")
472
- if exclude_list:
473
- filter_info.append(f"❌ Excluded domains: {', '.join(exclude_list)}")
474
-
475
- ai_response += f"\n\n*🌐 Domain filtering applied: {' | '.join(filter_info)}*"
476
-
477
- # Add provider info
478
- ai_response += f"\n\n*🤖 Powered by: {current_provider.title()} ({response.get('model', 'unknown')})*"
479
-
480
- # Add to history
481
- history.append([message, ai_response])
482
-
483
- return history, ""
484
-
485
- except Exception as e:
486
- error_msg = f"❌ Error: {str(e)}"
487
- history.append([message, error_msg])
488
- return history, ""
489
-
490
- def clear_chat_history():
491
- """Clear the chat history"""
492
- global ai_instance
493
- if ai_instance:
494
- ai_instance.clear_history()
495
- return []
496
-
497
- def create_gradio_app():
498
- """Create the main Gradio application"""
499
-
500
- # Custom CSS for better styling
501
- css = """
502
- .container {
503
- max-width: 1200px;
504
- margin: 0 auto;
505
- }
506
- .header {
507
- text-align: center;
508
- background: linear-gradient(to right, #00ff94, #00b4db);
509
- color: white;
510
- padding: 20px;
511
- border-radius: 10px;
512
- margin-bottom: 20px;
513
- }
514
- .status-box {
515
- background-color: #f8f9fa;
516
- border: 1px solid #dee2e6;
517
- border-radius: 8px;
518
- padding: 15px;
519
- margin: 10px 0;
520
- }
521
- .example-box {
522
- background-color: #e8f4fd;
523
- border-left: 4px solid #007bff;
524
- padding: 15px;
525
- margin: 10px 0;
526
- border-radius: 0 8px 8px 0;
527
- }
528
- .domain-info {
529
- background-color: #fff3cd;
530
- border: 1px solid #ffeaa7;
531
- border-radius: 8px;
532
- padding: 15px;
533
- margin: 10px 0;
534
- }
535
- .citation-info {
536
- background-color: #d1ecf1;
537
- border: 1px solid #bee5eb;
538
- border-radius: 8px;
539
- padding: 15px;
540
- margin: 10px 0;
541
- }
542
- .provider-info {
543
- background-color: #f8d7da;
544
- border: 1px solid #f5c6cb;
545
- border-radius: 8px;
546
- padding: 15px;
547
- margin: 10px 0;
548
- }
549
- #neuroscope-accordion {
550
- background: linear-gradient(to right, #00ff94, #00b4db);
551
- border-radius: 8px;
552
- }
553
- #neuroscope-accordion2 {
554
- background: linear-gradient(to right, #00ff94, #00b4db);
555
- border-radius: 8px;
556
- margin-top: 10px;
557
- }
558
- """
559
-
560
- with gr.Blocks(css=css, title="🤖 Multi-Provider Creative Agentic AI Chat", theme=gr.themes.Ocean()) as app:
561
-
562
- # Header
563
- gr.HTML("""
564
- <div class="header">
565
- <h1>🤖 NeuroScope-AI Enhanced</h1>
566
- <p>Multi-Provider AI Chat Tool - Powered by Groq's Compound Models & Chutes API</p>
567
- </div>
568
- """)
569
-
570
- # Provider Selection
571
- with gr.Group():
572
- with gr.Accordion("🤖 Multi-Provider NeuroScope AI", open=False, elem_id="neuroscope-accordion"):
573
- gr.Markdown("""
574
- **Enhanced with Multiple AI Providers:**
575
- - 🧠 Intelligence (Neuro) - Now supports Groq & Chutes
576
- - 🔍 Advanced capabilities (Scope) - Web search with Groq, powerful text generation with Chutes
577
- - 🤖 AI capabilities (AI) - Multiple model options
578
- - ⚡ Precision & Speed (Scope) - Choose the best provider for your needs
579
- """)
580
-
581
- # Provider and API Key Section
582
- with gr.Row():
583
- with gr.Column():
584
- provider_selection = gr.Radio(
585
- choices=["groq", "chutes"],
586
- label="🏢 AI Provider",
587
- value="groq",
588
- info="Choose your AI provider"
589
- )
590
-
591
- # API Key inputs
592
- groq_api_key = gr.Textbox(
593
- label="🔑 Groq API Key",
594
- placeholder="Enter your Groq API key here...",
595
- type="password",
596
- info="Get your API key from: https://console.groq.com/",
597
- visible=True
598
- )
599
-
600
- chutes_api_key = gr.Textbox(
601
- label="🔑 Chutes API Key",
602
- placeholder="Enter your Chutes API key here...",
603
- type="password",
604
- info="Get your API key from: https://chutes.ai/",
605
- visible=False
606
- )
607
-
608
- with gr.Column():
609
- model_selection = gr.Radio(
610
- choices=get_available_models("groq"),
611
- label="🧠 Groq Models",
612
- value="compound-beta",
613
- info="compound-beta: More powerful | compound-beta-mini: Faster"
614
- )
615
-
616
- connect_btn = gr.Button("🔗 Connect", variant="primary", size="lg")
617
-
618
- # Status display
619
- status_display = gr.Markdown("### 📊 Status: Not connected", elem_classes=["status-box"])
620
-
621
- # Provider Information
622
- with gr.Group():
623
- with gr.Accordion("🏢 Provider Comparison", open=False, elem_id="neuroscope-accordion"):
624
- gr.Markdown("""
625
- <div class="provider-info">
626
- <h3>🆚 Groq vs Chutes Comparison</h3>
627
-
628
- **🚀 Groq (Compound Models)**
629
- - ✅ **Web Search Capabilities** - Can search the internet and cite sources
630
- - ✅ **Agentic Tools** - Advanced tool usage and autonomous web browsing
631
- - ✅ **Domain Filtering** - Control which websites to search
632
- - ✅ **Citation System** - Automatic source linking and references
633
- - ⚡ **Ultra-fast inference** - Groq's hardware acceleration
634
- - 🧠 **Models**: compound-beta, compound-beta-mini
635
-
636
- **🎯 Chutes**
637
- - ✅ **Multiple Model Access** - Various open-source and commercial models
638
- - ✅ **Cost-effective** - Competitive pricing
639
- - ✅ **High-quality text generation** - Excellent for creative writing and analysis
640
- - ⚡ **Good performance** - Reliable and fast responses
641
- - 🧠 **Models**: GPT-OSS-20B, Llama 3.1, Claude 3 Sonnet, and more
642
- - ❌ **No web search** - Relies on training data only
643
-
644
- **💡 Use Groq when you need:**
645
- - Real-time information and web search
646
- - Research with source citations
647
- - Domain-specific searches
648
- - Agentic AI capabilities
649
-
650
- **💡 Use Chutes when you need:**
651
- - Pure text generation and analysis
652
- - Creative writing tasks
653
- - Cost-effective AI access
654
- - Variety of model options
655
- </div>
656
- """)
657
-
658
- # Update UI based on provider selection
659
- def update_provider_ui(provider):
660
- groq_visible = provider == "groq"
661
- chutes_visible = provider == "chutes"
662
- models = get_available_models(provider)
663
-
664
- return (
665
- gr.update(visible=groq_visible), # groq_api_key
666
- gr.update(visible=chutes_visible), # chutes_api_key
667
- gr.update(choices=models, value=models[0] if models else None,
668
- label=f"🧠 {provider.title()} Models"), # model_selection
669
- gr.update(visible=groq_visible), # domain filtering sections
670
- gr.update(visible=groq_visible), # include domains
671
- gr.update(visible=groq_visible) # exclude domains
672
- )
673
-
674
- provider_selection.change(
675
- fn=update_provider_ui,
676
- inputs=[provider_selection],
677
- outputs=[groq_api_key, chutes_api_key, model_selection] # We'll add domain filtering updates later
678
- )
679
-
680
- # Connect button functionality
681
- connect_btn.click(
682
- fn=validate_api_keys,
683
- inputs=[groq_api_key, chutes_api_key, provider_selection, model_selection],
684
- outputs=[status_display]
685
- )
686
-
687
- # Main Chat Interface
688
- with gr.Tab("💬 Chat"):
689
- chatbot = gr.Chatbot(
690
- label="Multi-Provider Creative AI Assistant",
691
- height=500,
692
- show_label=True,
693
- bubble_full_width=False,
694
- show_copy_button=True
695
- )
696
-
697
- with gr.Row():
698
- msg = gr.Textbox(
699
- label="Your Message",
700
- placeholder="Type your message here...",
701
- lines=3
702
- )
703
- with gr.Column():
704
- send_btn = gr.Button("📤 Send", variant="primary")
705
- clear_btn = gr.Button("🗑️ Clear", variant="secondary")
706
-
707
- # Advanced Settings
708
- with gr.Accordion("⚙️ Advanced Settings", open=False, elem_id="neuroscope-accordion"):
709
- with gr.Row():
710
- temperature = gr.Slider(
711
- minimum=0.0,
712
- maximum=2.0,
713
- value=0.7,
714
- step=0.1,
715
- label="🌡️ Temperature",
716
- info="Higher = more creative, Lower = more focused"
717
- )
718
- max_tokens = gr.Slider(
719
- minimum=100,
720
- maximum=4000,
721
- value=1024,
722
- step=100,
723
- label="📝 Max Tokens",
724
- info="Maximum length of response"
725
- )
726
-
727
- system_prompt = gr.Textbox(
728
- label="🎭 Custom System Prompt",
729
- placeholder="Override the default system prompt...",
730
- lines=2,
731
- info="Leave empty to use provider-optimized default prompt"
732
- )
733
-
734
- # Domain Filtering Section (Groq only)
735
- with gr.Group():
736
- with gr.Accordion("🌐 Domain Filtering (Groq Web Search Only)", open=False, elem_id="neuroscope-accordion"):
737
- gr.Markdown("""
738
- <div class="domain-info">
739
- <h4>🔍 Domain Filtering Guide (Groq Only)</h4>
740
- <p><strong>Note:</strong> Domain filtering only works with Groq's compound models that have web search capabilities.</p>
741
- <p>Control which websites the AI can search when answering your questions:</p>
742
- <ul>
743
- <li><strong>Include Domains:</strong> Only search these domains (comma-separated)</li>
744
- <li><strong>Exclude Domains:</strong> Never search these domains (comma-separated)</li>
745
- <li><strong>Examples:</strong> arxiv.org, *.edu, github.com, stackoverflow.com</li>
746
- <li><strong>Wildcards:</strong> Use *.edu for all educational domains</li>
747
- </ul>
748
- <p><strong>New:</strong> Domain filtering status will be shown in responses!</p>
749
- </div>
750
- """)
751
-
752
- with gr.Row():
753
- include_domains = gr.Textbox(
754
- label="✅ Include Domains (comma-separated)",
755
- placeholder="arxiv.org, *.edu, github.com, stackoverflow.com",
756
- info="Only search these domains"
757
- )
758
- exclude_domains = gr.Textbox(
759
- label="❌ Exclude Domains (comma-separated)",
760
- placeholder="wikipedia.org, reddit.com, twitter.com",
761
- info="Never search these domains"
762
- )
763
-
764
- with gr.Accordion("🔗 Common Domain Examples", open=False, elem_id="neuroscope-accordion2"):
765
- gr.Markdown("""
766
- **Academic & Research:**
767
- - `arxiv.org`, `*.edu`, `scholar.google.com`, `researchgate.net`
768
-
769
- **Technology & Programming:**
770
- - `github.com`, `stackoverflow.com`, `docs.python.org`, `developer.mozilla.org`
771
-
772
- **News & Media:**
773
- - `reuters.com`, `bbc.com`, `npr.org`, `apnews.com`
774
-
775
- **Business & Finance:**
776
- - `bloomberg.com`, `wsj.com`, `nasdaq.com`, `sec.gov`
777
-
778
- **Science & Medicine:**
779
- - `nature.com`, `science.org`, `pubmed.ncbi.nlm.nih.gov`, `who.int`
780
- """)
781
-
782
- # Update provider UI function with domain filtering
783
- def update_provider_ui_complete(provider):
784
- groq_visible = provider == "groq"
785
- chutes_visible = provider == "chutes"
786
- models = get_available_models(provider)
787
-
788
- return (
789
- gr.update(visible=groq_visible), # groq_api_key
790
- gr.update(visible=chutes_visible), # chutes_api_key
791
- gr.update(choices=models, value=models[0] if models else None,
792
- label=f"🧠 {provider.title()} Models"), # model_selection
793
- gr.update(visible=groq_visible), # domain_group
794
- )
795
-
796
- provider_selection.change(
797
- fn=update_provider_ui_complete,
798
- inputs=[provider_selection],
799
- outputs=[groq_api_key, chutes_api_key, model_selection]
800
- )
801
-
802
- # IMPORTANT Section with Citation Info
803
- with gr.Group():
804
- with gr.Accordion("📚 IMPORTANT - Citations & Multi-Provider Features!", open=False, elem_id="neuroscope-accordion"):
805
- gr.Markdown("""
806
- <div class="citation-info">
807
- <h3>🆕 Multi-Provider Enhancement</h3>
808
- <p>This enhanced version now supports both Groq and Chutes AI providers:</p>
809
- <ul>
810
- <li><strong>🚀 Groq Integration:</strong> Agentic AI with web search, citations, and tool usage</li>
811
- <li><strong>🎯 Chutes Integration:</strong> Multiple AI models for text generation and analysis</li>
812
- <li><strong>🔄 Easy Switching:</strong> Switch between providers based on your needs</li>
813
- <li><strong>📊 Provider Comparison:</strong> Clear information about each provider's strengths</li>
814
- </ul>
815
-
816
- <h3>🔗 Groq Enhanced Citation System</h3>
817
- <p>When using Groq, you get:</p>
818
- <ul>
819
- <li><strong>Automatic Source Citations:</strong> All responses include clickable links to sources</li>
820
- <li><strong>Sources Used Section:</strong> Dedicated section showing all websites referenced</li>
821
- <li><strong>Domain Filtering Verification:</strong> Clear indication when domain filtering is applied</li>
822
- <li><strong>Search Query Tracking:</strong> Shows what queries were made to find information</li>
823
- </ul>
824
-
825
- <h3>🎯 Chutes Model Access</h3>
826
- <p>When using Chutes, you get access to:</p>
827
- <ul>
828
- <li><strong>Multiple Models:</strong> GPT-OSS-20B, Llama 3.1, Claude 3 Sonnet</li>
829
- <li><strong>Cost-Effective:</strong> Competitive pricing for high-quality AI</li>
830
- <li><strong>Specialized Tasks:</strong> Optimized for creative writing and analysis</li>
831
- <li><strong>Reliable Performance:</strong> Consistent and fast responses</li>
832
- </ul>
833
- </div>
834
-
835
- ### 🔍 **Web Search Behavior (Groq Only)**
836
-
837
- **No Domains Specified:**
838
- - AI operates with **unrestricted web search capabilities**.
839
- - Compound models autonomously search the **entire internet** for the most relevant and up-to-date information.
840
- - AI has complete freedom to use its **agentic tools** and browse **any website** it finds useful.
841
-
842
- **Include Domains Specified (e.g., `arxiv.org`, `*.edu`):**
843
- - AI is restricted to search **only the specified domains**.
844
- - Acts as a **strict whitelist**, making the AI **laser-focused** on your chosen sources.
845
- - Ensures information is sourced from **preferred or authoritative domains** (e.g., academic or research-focused).
846
-
847
- **Exclude Domains Specified (e.g., `wikipedia.org`, `reddit.com`):**
848
- - AI searches the entire web **except the listed domains**.
849
- - Useful for **filtering out unreliable or unwanted sources**.
850
- - Allows broad search with **targeted exclusions**.
851
-
852
- **Both Include and Exclude Domains Specified:**
853
- - **Only the include domains** are used for searching.
854
- - **Exclude list is ignored** because the include list already restricts search scope.
855
- - Guarantees AI pulls content **exclusively from whitelisted domains**, regardless of the excluded ones.
856
-
857
- ---
858
-
859
- ### 🎭 **Custom System Prompt Feature**
860
-
861
- Allows complete override of the AI's **default personality and behavior** for both providers.
862
- You can redefine the AI to act as:
863
- - A **professional business consultant**
864
- - A **coding mentor**
865
- - A **creative writer**
866
- - A **specific character or persona**
867
- - Provides full control to **reshape the AI's tone, expertise, and conversational style** with a single prompt.
868
- """)
869
-
870
- # How to Use Section
871
- with gr.Accordion("📖 How to Use This Enhanced Multi-Provider App", open=False, elem_id="neuroscope-accordion"):
872
- gr.Markdown("""
873
- ### 🚀 Getting Started
874
- 1. **Choose your AI Provider** - Select between Groq (web search + agentic) or Chutes (text generation)
875
- 2. **Enter your API Key** -
876
- - Groq: Get one from [console.groq.com](https://console.groq.com/)
877
- - Chutes: Get one from [chutes.ai](https://chutes.ai/)
878
- 3. **Select a model** - Choose from provider-specific model options
879
- 4. **Click Connect** - Validate your key and connect to the AI
880
- 5. **Start chatting!** - Type your message and get intelligent responses
881
-
882
- ### 🎯 Key Features
883
- **🚀 Groq Features:**
884
- - **Agentic AI**: The AI can use tools and search the web autonomously
885
- - **Smart Citations**: Automatic source linking and citation formatting
886
- - **Domain Filtering**: Control which websites the AI searches
887
- - **Ultra-fast**: Groq's hardware-accelerated inference
888
-
889
- **🎯 Chutes Features:**
890
- - **Multiple Models**: Access to various open-source and commercial models
891
- - **Cost-Effective**: Competitive pricing for AI access
892
- - **High Quality**: Excellent text generation and analysis
893
- - **Model Variety**: Choose the best model for your specific task
894
-
895
- **🔄 Universal Features:**
896
- - **Memory**: Maintains conversation context throughout the session
897
- - **Customizable**: Adjust temperature, tokens, and system prompts
898
- - **Provider Switching**: Easy switching between AI providers
899
-
900
- ### 💡 Tips for Best Results
901
- **For Groq:**
902
- - Be specific in your questions for better web search results
903
- - Use domain filtering for specialized research
904
- - Check the "Sources Used" section for all references
905
- - Try different domain combinations to see varied results
906
-
907
- **For Chutes:**
908
- - Experiment with different models for different tasks
909
- - Use higher temperatures for creative tasks
910
- - Leverage the variety of available models (GPT, Llama, Claude)
911
- - Perfect for tasks that don't require real-time information
912
-
913
- **General:**
914
- - Adjust temperature: higher for creativity, lower for precision
915
- - Try different system prompts for different conversation styles
916
- - Use the provider that best fits your current task
917
- """)
918
-
919
- # Sample Examples Section
920
- with gr.Accordion("🎯 Sample Examples to Test Both Providers", open=False, elem_id="neuroscope-accordion"):
921
- gr.Markdown("""
922
- <div class="example-box">
923
- <h4>🆚 Provider Comparison Examples</h4>
924
- <p>Try the same prompts with both providers to see the difference:</p>
925
-
926
- <h4>🔬 Research & Analysis</h4>
927
- <ul>
928
- <li><strong>Groq (with web search):</strong> "What are the latest breakthroughs in quantum computing in 2024?"</li>
929
- <li><strong>Chutes (knowledge-based):</strong> "Explain the fundamental principles of quantum computing"</li>
930
- <li><strong>Groq with domains:</strong> Same question with "arxiv.org, *.edu" in include domains</li>
931
- </ul>
932
-
933
- <h4>💻 Programming & Tech</h4>
934
- <ul>
935
- <li><strong>Groq:</strong> "What are the current best practices for React 18 in 2024?"</li>
936
- <li><strong>Chutes:</strong> "Write a comprehensive React component with hooks and best practices"</li>
937
- <li><strong>Groq filtered:</strong> Same with "github.com, stackoverflow.com" included</li>
938
- </ul>
939
-
940
- <h4>🎨 Creative Tasks (Great for Chutes)</h4>
941
- <ul>
942
- <li>"Write a short story about AI and humans working together"</li>
943
- <li>"Create a marketing plan for a sustainable fashion brand"</li>
944
- <li>"Generate ideas for a mobile app that helps with mental health"</li>
945
- <li>"Write a poem about the beauty of code"</li>
946
- </ul>
947
-
948
- <h4>📊 Business & Analysis</h4>
949
- <ul>
950
- <li><strong>Groq:</strong> "What are the current trends in cryptocurrency markets?"</li>
951
- <li><strong>Chutes:</strong> "Analyze the pros and cons of different investment strategies"</li>
952
- <li><strong>Groq filtered:</strong> Crypto question with "bloomberg.com, wsj.com" included</li>
953
- </ul>
954
-
955
- <h4>🧠 Model-Specific Testing (Chutes)</h4>
956
- <ul>
957
- <li><strong>GPT-OSS-20B:</strong> "Explain complex scientific concepts in simple terms"</li>
958
- <li><strong>Llama 3.1:</strong> "Help me debug this Python code and explain the solution"</li>
959
- <li><strong>Claude 3 Sonnet:</strong> "Analyze this business scenario and provide strategic recommendations"</li>
960
- </ul>
961
- </div>
962
- """)
963
-
964
- # Event handlers
965
- send_btn.click(
966
- fn=chat_with_ai,
967
- inputs=[msg, include_domains, exclude_domains, system_prompt, temperature, max_tokens, chatbot],
968
- outputs=[chatbot, msg]
969
- )
970
-
971
- msg.submit(
972
- fn=chat_with_ai,
973
- inputs=[msg, include_domains, exclude_domains, system_prompt, temperature, max_tokens, chatbot],
974
- outputs=[chatbot, msg]
975
- )
976
-
977
- clear_btn.click(
978
- fn=clear_chat_history,
979
- outputs=[chatbot]
980
- )
981
-
982
- # Footer
983
- with gr.Accordion("🚀 About This Enhanced Multi-Provider Tool", open=True, elem_id="neuroscope-accordion"):
984
- gr.Markdown("""
985
- **Enhanced Multi-Provider Creative Agentic AI Chat Tool** with dual API support:
986
-
987
- **🆕 New Multi-Provider Features:**
988
- - 🚀 **Groq Integration**: Agentic AI with web search, citations, and tool usage
989
- - 🎯 **Chutes Integration**: Multiple AI models for diverse text generation tasks
990
- - 🔄 **Provider Switching**: Easy switching between different AI providers
991
- - 📊 **Provider Comparison**: Clear information about each provider's strengths
992
- - 🧠 **Multiple Models**: Access to various AI models through both providers
993
-
994
- **🚀 Groq Features:**
995
- - 🔗 **Automatic Source Citations**: Every response includes clickable links to sources
996
- - 📚 **Sources Used Section**: Dedicated section showing all websites referenced
997
- - 🌐 **Domain Filtering Verification**: Clear indication when filtering is applied
998
- - 🔍 **Search Query Tracking**: Shows what queries were made
999
- - ⚡ **Enhanced Tool Usage Display**: Better visibility into AI's research process
1000
- - 🔍 Web search with domain filtering
1001
- - 🧠 Advanced AI reasoning with tool usage
1002
-
1003
- **🎯 Chutes Features:**
1004
- - 🤖 **Multiple AI Models**: GPT-OSS-20B, Llama 3.1, Claude 3 Sonnet
1005
- - 💰 **Cost-Effective**: Competitive pricing for AI access
1006
- - 🎨 **Creative Excellence**: Optimized for writing and analysis tasks
1007
- - ⚡ **Reliable Performance**: Consistent and fast responses
1008
-
1009
- **🔄 Universal Features:**
1010
- - 💬 Conversational memory and context
1011
- - ⚙️ Customizable parameters and prompts
1012
- - 🎨 Creative and analytical capabilities
1013
- - 🌟 Enhanced user interface with provider-specific optimizations
1014
-
1015
- **💡 Choose Your Provider:**
1016
- - **Use Groq** when you need real-time information, web search, and citations
1017
- - **Use Chutes** when you need pure text generation, creative writing, or cost-effective AI access
1018
- """)
1019
-
1020
- return app
1021
-
1022
- # Main execution
1023
  if __name__ == "__main__":
1024
- app = create_gradio_app()
1025
- app.launch(
1026
- share=True
1027
- )
 
1
+ from Crypto.Cipher import AES
2
+ from Crypto.Protocol.KDF import PBKDF2
3
  import os
4
+ import tempfile
5
+ from dotenv import load_dotenv
 
 
 
 
 
 
 
6
 
7
+ load_dotenv() # Load all environment variables
 
8
 
9
+ def unpad(data):
10
+ return data[:-data[-1]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ def decrypt_and_run():
13
+ # Get password from Hugging Face Secrets environment variable
14
+ password = os.getenv("PASSWORD")
15
+ if not password:
16
+ raise ValueError("PASSWORD secret not found in environment variables")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
+ password = password.encode()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ with open("code.enc", "rb") as f:
21
+ encrypted = f.read()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
+ salt = encrypted[:16]
24
+ iv = encrypted[16:32]
25
+ ciphertext = encrypted[32:]
 
26
 
27
+ key = PBKDF2(password, salt, dkLen=32, count=1000000)
28
+ cipher = AES.new(key, AES.MODE_CBC, iv)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ plaintext = unpad(cipher.decrypt(ciphertext))
 
 
 
 
 
 
31
 
32
+ with tempfile.NamedTemporaryFile(suffix=".py", delete=False, mode='wb') as tmp:
33
+ tmp.write(plaintext)
34
+ tmp.flush()
35
+ print(f"[INFO] Running decrypted code from {tmp.name}")
36
+ os.system(f"python {tmp.name}")
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  if __name__ == "__main__":
39
+ decrypt_and_run()
40
+
41
+ # This script decrypts the encrypted code and runs it.
42
+ # Ensure you have the PASSWORD secret set in your Hugging Face Secrets