shukdevdattaEX commited on
Commit
bd29ac8
Β·
verified Β·
1 Parent(s): 6391434

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +955 -0
app.py ADDED
@@ -0,0 +1,955 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
10
+ # Import Groq
11
+ from groq import Groq
12
+
13
+ class CreativeAgenticAI:
14
+ """
15
+ Creative Agentic AI Chat Tool using Groq's models with browser search and compound models
16
+ """
17
+
18
+ def __init__(self, api_key: str, model: str = "compound-beta"):
19
+ """
20
+ Initialize the Creative Agentic AI system.
21
+
22
+ Args:
23
+ api_key: Groq API key
24
+ model: Which Groq model to use
25
+ """
26
+ self.api_key = api_key
27
+ if not self.api_key:
28
+ raise ValueError("No API key provided")
29
+
30
+ self.client = Groq(api_key=self.api_key)
31
+ self.model = model
32
+ self.conversation_history = []
33
+
34
+ # Available models with their capabilities
35
+ self.available_models = {
36
+ "compound-beta": {"supports_web_search": True, "supports_browser_search": False},
37
+ "compound-beta-mini": {"supports_web_search": True, "supports_browser_search": False},
38
+ "openai/gpt-oss-20b": {"supports_web_search": False, "supports_browser_search": True},
39
+ "llama-3.3-70b-versatile": {"supports_web_search": False, "supports_browser_search": True},
40
+ "llama-3.1-70b-versatile": {"supports_web_search": False, "supports_browser_search": True},
41
+ "mixtral-8x7b-32768": {"supports_web_search": False, "supports_browser_search": True},
42
+ }
43
+
44
+ def chat(self, message: str,
45
+ include_domains: List[str] = None,
46
+ exclude_domains: List[str] = None,
47
+ system_prompt: str = None,
48
+ temperature: float = 0.7,
49
+ max_tokens: int = 1024,
50
+ search_type: str = "auto",
51
+ force_search: bool = False) -> Dict:
52
+ """
53
+ Send a message to the AI and get a response with flexible search options
54
+
55
+ Args:
56
+ message: User's message
57
+ include_domains: List of domains to include for web search
58
+ exclude_domains: List of domains to exclude from web search
59
+ system_prompt: Custom system prompt
60
+ temperature: Model temperature (0.0-2.0)
61
+ max_tokens: Maximum tokens in response
62
+ search_type: 'web_search', 'browser_search', 'auto', or 'none'
63
+ force_search: Force the AI to use search tools
64
+
65
+ Returns:
66
+ AI response with metadata
67
+ """
68
+ # Enhanced system prompt for better citation behavior
69
+ if not system_prompt:
70
+ citation_instruction = """
71
+ IMPORTANT: When you search the web and find information, you MUST:
72
+ 1. Always cite your sources with clickable links in this format: [Source Title](URL)
73
+ 2. Include multiple diverse sources when possible
74
+ 3. Show which specific websites you used for each claim
75
+ 4. At the end of your response, provide a "Sources Used" section with all the links
76
+ 5. Be transparent about which information comes from which source
77
+ """
78
+
79
+ domain_context = ""
80
+ if include_domains and self._supports_web_search():
81
+ 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."
82
+ elif exclude_domains and self._supports_web_search():
83
+ domain_context = f"\nAvoid searching these domains: {', '.join(exclude_domains)}. Search everywhere else on the web."
84
+
85
+ search_instruction = ""
86
+ if search_type == "browser_search":
87
+ search_instruction = "\nUse browser search tools to find the most current and relevant information from the web."
88
+ elif search_type == "web_search":
89
+ search_instruction = "\nUse web search capabilities to find relevant information."
90
+ elif force_search:
91
+ if self._supports_browser_search():
92
+ search_instruction = "\nYou MUST use search tools to find current information before responding."
93
+ elif self._supports_web_search():
94
+ search_instruction = "\nYou MUST use web search to find current information before responding."
95
+
96
+ system_prompt = f"""You are a creative and intelligent AI assistant with agentic capabilities.
97
+ You can search the web, analyze information, and provide comprehensive responses.
98
+ Be helpful, creative, and engaging while maintaining accuracy.
99
+
100
+ {citation_instruction}
101
+ {domain_context}
102
+ {search_instruction}
103
+
104
+ Your responses should be well-structured, informative, and properly cited with working links."""
105
+
106
+ # Build messages
107
+ messages = [{"role": "system", "content": system_prompt}]
108
+
109
+ # Add conversation history (last 10 exchanges)
110
+ messages.extend(self.conversation_history[-20:]) # Last 10 user-assistant pairs
111
+
112
+ # Add current message with context
113
+ enhanced_message = message
114
+ if include_domains or exclude_domains:
115
+ filter_context = []
116
+ if include_domains:
117
+ filter_context.append(f"ONLY search these domains: {', '.join(include_domains)}")
118
+ if exclude_domains:
119
+ filter_context.append(f"EXCLUDE these domains: {', '.join(exclude_domains)}")
120
+ enhanced_message += f"\n\n[Domain Filtering: {' | '.join(filter_context)}]"
121
+
122
+ messages.append({"role": "user", "content": enhanced_message})
123
+
124
+ # Set up API parameters
125
+ params = {
126
+ "messages": messages,
127
+ "model": self.model,
128
+ "temperature": temperature,
129
+ "max_completion_tokens": max_tokens if self._supports_browser_search() else None,
130
+ "max_tokens": max_tokens if not self._supports_browser_search() else None,
131
+ }
132
+
133
+ # Add domain filtering for compound models
134
+ if self._supports_web_search():
135
+ if include_domains and include_domains[0].strip():
136
+ params["include_domains"] = [domain.strip() for domain in include_domains if domain.strip()]
137
+ if exclude_domains and exclude_domains[0].strip():
138
+ params["exclude_domains"] = [domain.strip() for domain in exclude_domains if domain.strip()]
139
+
140
+ # Add tools based on search type and model capabilities
141
+ tools = []
142
+ tool_choice = None
143
+
144
+ if search_type == "browser_search" and self._supports_browser_search():
145
+ tools = [{"type": "browser_search"}]
146
+ tool_choice = "required" if force_search else "auto"
147
+ elif search_type == "auto":
148
+ if self._supports_browser_search():
149
+ tools = [{"type": "browser_search"}]
150
+ tool_choice = "required" if force_search else "auto"
151
+ elif force_search and self._supports_browser_search():
152
+ tools = [{"type": "browser_search"}]
153
+ tool_choice = "required"
154
+
155
+ if tools:
156
+ params["tools"] = tools
157
+ params["tool_choice"] = tool_choice
158
+
159
+ try:
160
+ # Make the API call
161
+ response = self.client.chat.completions.create(**params)
162
+ content = response.choices[0].message.content
163
+
164
+ # Extract tool usage information and enhance it
165
+ tool_info = self._extract_tool_info(response)
166
+
167
+ # Process content to enhance citations
168
+ processed_content = self._enhance_citations(content, tool_info)
169
+
170
+ # Add to conversation history
171
+ self.conversation_history.append({"role": "user", "content": message})
172
+ self.conversation_history.append({"role": "assistant", "content": processed_content})
173
+
174
+ # Create response object
175
+ response_data = {
176
+ "content": processed_content,
177
+ "timestamp": datetime.now().isoformat(),
178
+ "model": self.model,
179
+ "tool_usage": tool_info,
180
+ "search_type_used": search_type,
181
+ "parameters": {
182
+ "temperature": temperature,
183
+ "max_tokens": max_tokens,
184
+ "include_domains": include_domains,
185
+ "exclude_domains": exclude_domains,
186
+ "force_search": force_search
187
+ }
188
+ }
189
+
190
+ return response_data
191
+
192
+ except Exception as e:
193
+ error_msg = f"Error: {str(e)}"
194
+ self.conversation_history.append({"role": "user", "content": message})
195
+ self.conversation_history.append({"role": "assistant", "content": error_msg})
196
+
197
+ return {
198
+ "content": error_msg,
199
+ "timestamp": datetime.now().isoformat(),
200
+ "model": self.model,
201
+ "tool_usage": None,
202
+ "error": str(e)
203
+ }
204
+
205
+ def _supports_web_search(self) -> bool:
206
+ """Check if current model supports web search (compound models)"""
207
+ return self.available_models.get(self.model, {}).get("supports_web_search", False)
208
+
209
+ def _supports_browser_search(self) -> bool:
210
+ """Check if current model supports browser search tools"""
211
+ return self.available_models.get(self.model, {}).get("supports_browser_search", False)
212
+
213
+ def _extract_tool_info(self, response) -> Dict:
214
+ """Extract tool usage information in a JSON serializable format"""
215
+ tool_info = {
216
+ "tools_used": [],
217
+ "search_queries": [],
218
+ "sources_found": []
219
+ }
220
+
221
+ # Check for executed_tools attribute (compound models)
222
+ if hasattr(response.choices[0].message, 'executed_tools'):
223
+ tools = response.choices[0].message.executed_tools
224
+ if tools:
225
+ for tool in tools:
226
+ tool_dict = {
227
+ "tool_type": getattr(tool, "type", "unknown"),
228
+ "tool_name": getattr(tool, "name", "unknown"),
229
+ }
230
+
231
+ # Extract search queries and results
232
+ if hasattr(tool, "input"):
233
+ tool_input = str(tool.input)
234
+ tool_dict["input"] = tool_input
235
+ # Try to extract search query
236
+ if "search" in tool_dict["tool_name"].lower():
237
+ tool_info["search_queries"].append(tool_input)
238
+
239
+ if hasattr(tool, "output"):
240
+ tool_output = str(tool.output)
241
+ tool_dict["output"] = tool_output
242
+ # Try to extract URLs from output
243
+ urls = self._extract_urls(tool_output)
244
+ tool_info["sources_found"].extend(urls)
245
+
246
+ tool_info["tools_used"].append(tool_dict)
247
+
248
+ # Check for tool_calls attribute (browser search models)
249
+ if hasattr(response.choices[0].message, 'tool_calls') and response.choices[0].message.tool_calls:
250
+ for tool_call in response.choices[0].message.tool_calls:
251
+ tool_dict = {
252
+ "tool_type": tool_call.type if hasattr(tool_call, 'type') else "browser_search",
253
+ "tool_name": tool_call.function.name if hasattr(tool_call, 'function') else "browser_search",
254
+ "tool_id": tool_call.id if hasattr(tool_call, 'id') else None
255
+ }
256
+
257
+ if hasattr(tool_call, 'function') and hasattr(tool_call.function, 'arguments'):
258
+ try:
259
+ args = json.loads(tool_call.function.arguments) if isinstance(tool_call.function.arguments, str) else tool_call.function.arguments
260
+ tool_dict["arguments"] = args
261
+ if "query" in args:
262
+ tool_info["search_queries"].append(args["query"])
263
+ except:
264
+ tool_dict["arguments"] = str(tool_call.function.arguments)
265
+
266
+ tool_info["tools_used"].append(tool_dict)
267
+
268
+ return tool_info
269
+
270
+ def _extract_urls(self, text: str) -> List[str]:
271
+ """Extract URLs from text"""
272
+ url_pattern = r'https?://[^\s<>"]{2,}'
273
+ urls = re.findall(url_pattern, text)
274
+ return list(set(urls)) # Remove duplicates
275
+
276
+ def _enhance_citations(self, content: str, tool_info: Dict) -> str:
277
+ """Enhance content with better citation formatting"""
278
+ if not tool_info or not tool_info.get("sources_found"):
279
+ return content
280
+
281
+ # Add sources section if not already present
282
+ if "Sources Used:" not in content and "sources:" not in content.lower():
283
+ sources_section = "\n\n---\n\n### πŸ“š Sources Used:\n"
284
+ for i, url in enumerate(tool_info["sources_found"][:10], 1): # Limit to 10 sources
285
+ # Try to extract domain name for better formatting
286
+ domain = self._extract_domain(url)
287
+ sources_section += f"{i}. [{domain}]({url})\n"
288
+
289
+ content += sources_section
290
+
291
+ return content
292
+
293
+ def _extract_domain(self, url: str) -> str:
294
+ """Extract domain name from URL for display"""
295
+ try:
296
+ if url.startswith(('http://', 'https://')):
297
+ domain = url.split('/')[2]
298
+ # Remove www. prefix if present
299
+ if domain.startswith('www.'):
300
+ domain = domain[4:]
301
+ return domain
302
+ return url
303
+ except:
304
+ return url
305
+
306
+ def get_model_info(self) -> Dict:
307
+ """Get information about current model capabilities"""
308
+ return self.available_models.get(self.model, {})
309
+
310
+ def clear_history(self):
311
+ """Clear conversation history"""
312
+ self.conversation_history = []
313
+
314
+ def get_history_summary(self) -> str:
315
+ """Get a summary of conversation history"""
316
+ if not self.conversation_history:
317
+ return "No conversation history"
318
+
319
+ user_messages = [msg for msg in self.conversation_history if msg["role"] == "user"]
320
+ assistant_messages = [msg for msg in self.conversation_history if msg["role"] == "assistant"]
321
+
322
+ return f"Conversation: {len(user_messages)} user messages, {len(assistant_messages)} assistant responses"
323
+
324
+ # Global variables
325
+ ai_instance = None
326
+ api_key_status = "Not Set"
327
+
328
+ def validate_api_key(api_key: str, model: str) -> str:
329
+ """Validate Groq API key and initialize AI instance"""
330
+ global ai_instance, api_key_status
331
+
332
+ if not api_key or len(api_key.strip()) < 10:
333
+ api_key_status = "Invalid ❌"
334
+ return "❌ Please enter a valid API key (should be longer than 10 characters)"
335
+
336
+ try:
337
+ # Test the API key
338
+ client = Groq(api_key=api_key)
339
+ # Try a simple request to validate
340
+ test_response = client.chat.completions.create(
341
+ messages=[{"role": "user", "content": "Hello"}],
342
+ model=model,
343
+ max_completion_tokens=10 if model in ["openai/gpt-oss-20b", "llama-3.3-70b-versatile", "llama-3.1-70b-versatile", "mixtral-8x7b-32768"] else None,
344
+ max_tokens=10 if model in ["compound-beta", "compound-beta-mini"] else None
345
+ )
346
+
347
+ # Create AI instance
348
+ ai_instance = CreativeAgenticAI(api_key=api_key, model=model)
349
+ api_key_status = "Valid βœ…"
350
+
351
+ model_info = ai_instance.get_model_info()
352
+ capabilities = []
353
+ if model_info.get("supports_web_search"):
354
+ capabilities.append("🌐 Web Search with Domain Filtering")
355
+ if model_info.get("supports_browser_search"):
356
+ capabilities.append("πŸ” Browser Search Tools")
357
+
358
+ cap_text = " | ".join(capabilities) if capabilities else "πŸ’¬ Chat Only"
359
+
360
+ return f"βœ… API Key Valid! NeuroScope AI is ready.\n\n**Model:** {model}\n**Capabilities:** {cap_text}\n**Status:** Connected and ready for chat!"
361
+
362
+ except Exception as e:
363
+ api_key_status = "Invalid ❌"
364
+ ai_instance = None
365
+ return f"❌ Error validating API key: {str(e)}\n\nPlease check your API key and try again."
366
+
367
+ def update_model(model: str) -> str:
368
+ """Update the model selection"""
369
+ global ai_instance
370
+
371
+ if ai_instance:
372
+ ai_instance.model = model
373
+ model_info = ai_instance.get_model_info()
374
+ capabilities = []
375
+ if model_info.get("supports_web_search"):
376
+ capabilities.append("🌐 Web Search with Domain Filtering")
377
+ if model_info.get("supports_browser_search"):
378
+ capabilities.append("πŸ” Browser Search Tools")
379
+
380
+ cap_text = " | ".join(capabilities) if capabilities else "πŸ’¬ Chat Only"
381
+ return f"βœ… Model updated to: **{model}**\n**Capabilities:** {cap_text}"
382
+ else:
383
+ return "⚠️ Please set your API key first"
384
+
385
+ def get_search_options(model: str) -> gr.update:
386
+ """Get available search options based on model"""
387
+ if not ai_instance:
388
+ return gr.update(choices=["none"], value="none")
389
+
390
+ model_info = ai_instance.available_models.get(model, {})
391
+ options = ["none"]
392
+
393
+ if model_info.get("supports_web_search"):
394
+ options.extend(["web_search", "auto"])
395
+ if model_info.get("supports_browser_search"):
396
+ options.extend(["browser_search", "auto"])
397
+
398
+ # Remove duplicates while preserving order
399
+ options = list(dict.fromkeys(options))
400
+
401
+ default_value = "auto" if "auto" in options else "none"
402
+ return gr.update(choices=options, value=default_value)
403
+
404
+ def chat_with_ai(message: str,
405
+ include_domains: str,
406
+ exclude_domains: str,
407
+ system_prompt: str,
408
+ temperature: float,
409
+ max_tokens: int,
410
+ search_type: str,
411
+ force_search: bool,
412
+ history: List) -> tuple:
413
+ """Main chat function"""
414
+ global ai_instance
415
+
416
+ if not ai_instance:
417
+ error_msg = "⚠️ Please set your Groq API key first!"
418
+ history.append([message, error_msg])
419
+ return history, ""
420
+
421
+ if not message.strip():
422
+ return history, ""
423
+
424
+ # Process domain lists
425
+ include_list = [d.strip() for d in include_domains.split(",")] if include_domains.strip() else []
426
+ exclude_list = [d.strip() for d in exclude_domains.split(",")] if exclude_domains.strip() else []
427
+
428
+ try:
429
+ # Get AI response
430
+ response = ai_instance.chat(
431
+ message=message,
432
+ include_domains=include_list if include_list else None,
433
+ exclude_domains=exclude_list if exclude_list else None,
434
+ system_prompt=system_prompt if system_prompt.strip() else None,
435
+ temperature=temperature,
436
+ max_tokens=int(max_tokens),
437
+ search_type=search_type,
438
+ force_search=force_search
439
+ )
440
+
441
+ # Format response
442
+ ai_response = response["content"]
443
+
444
+ # Add enhanced tool usage info
445
+ if response.get("tool_usage"):
446
+ tool_info = response["tool_usage"]
447
+ tool_summary = []
448
+
449
+ if tool_info.get("search_queries"):
450
+ tool_summary.append(f"πŸ” Search queries: {len(tool_info['search_queries'])}")
451
+
452
+ if tool_info.get("sources_found"):
453
+ tool_summary.append(f"πŸ“„ Sources found: {len(tool_info['sources_found'])}")
454
+
455
+ if tool_info.get("tools_used"):
456
+ tool_types = [tool.get("tool_type", "unknown") for tool in tool_info["tools_used"]]
457
+ unique_types = list(set(tool_types))
458
+ tool_summary.append(f"πŸ”§ Tools used: {', '.join(unique_types)}")
459
+
460
+ if tool_summary:
461
+ ai_response += f"\n\n*{' | '.join(tool_summary)}*"
462
+
463
+ # Add search type info
464
+ search_info = []
465
+ if response.get("search_type_used") and response["search_type_used"] != "none":
466
+ search_info.append(f"πŸ” Search type: {response['search_type_used']}")
467
+
468
+ if force_search:
469
+ search_info.append("⚑ Forced search enabled")
470
+
471
+ # Add domain filtering info
472
+ if include_list or exclude_list:
473
+ filter_info = []
474
+ if include_list:
475
+ filter_info.append(f"βœ… Included domains: {', '.join(include_list)}")
476
+ if exclude_list:
477
+ filter_info.append(f"❌ Excluded domains: {', '.join(exclude_list)}")
478
+ search_info.extend(filter_info)
479
+
480
+ if search_info:
481
+ ai_response += f"\n\n*🌐 Search settings: {' | '.join(search_info)}*"
482
+
483
+ # Add to history
484
+ history.append([message, ai_response])
485
+
486
+ return history, ""
487
+
488
+ except Exception as e:
489
+ error_msg = f"❌ Error: {str(e)}"
490
+ history.append([message, error_msg])
491
+ return history, ""
492
+
493
+ def clear_chat_history():
494
+ """Clear the chat history"""
495
+ global ai_instance
496
+ if ai_instance:
497
+ ai_instance.clear_history()
498
+ return []
499
+
500
+ def create_gradio_app():
501
+ """Create the main Gradio application"""
502
+
503
+ # Custom CSS for better styling
504
+ css = """
505
+ .container {
506
+ max-width: 1200px;
507
+ margin: 0 auto;
508
+ }
509
+ .header {
510
+ text-align: center;
511
+ background: linear-gradient(to right, #00ff94, #00b4db);
512
+ color: white;
513
+ padding: 20px;
514
+ border-radius: 10px;
515
+ margin-bottom: 20px;
516
+ }
517
+ .status-box {
518
+ background-color: #f8f9fa;
519
+ border: 1px solid #dee2e6;
520
+ border-radius: 8px;
521
+ padding: 15px;
522
+ margin: 10px 0;
523
+ }
524
+ .example-box {
525
+ background-color: #e8f4fd;
526
+ border-left: 4px solid #007bff;
527
+ padding: 15px;
528
+ margin: 10px 0;
529
+ border-radius: 0 8px 8px 0;
530
+ }
531
+ .domain-info {
532
+ background-color: #fff3cd;
533
+ border: 1px solid #ffeaa7;
534
+ border-radius: 8px;
535
+ padding: 15px;
536
+ margin: 10px 0;
537
+ }
538
+ .citation-info {
539
+ background-color: #d1ecf1;
540
+ border: 1px solid #bee5eb;
541
+ border-radius: 8px;
542
+ padding: 15px;
543
+ margin: 10px 0;
544
+ }
545
+ .search-info {
546
+ background-color: #e2e3e5;
547
+ border: 1px solid #c6c8ca;
548
+ border-radius: 8px;
549
+ padding: 15px;
550
+ margin: 10px 0;
551
+ }
552
+ #neuroscope-accordion {
553
+ background: linear-gradient(to right, #00ff94, #00b4db);
554
+ border-radius: 8px;
555
+ }
556
+ """
557
+
558
+ with gr.Blocks(css=css, title="πŸ€– Creative Agentic AI Chat", theme=gr.themes.Ocean()) as app:
559
+
560
+ # Header
561
+ gr.HTML("""
562
+ <div class="header">
563
+ <h1>πŸ€– NeuroScope-AI Enhanced</h1>
564
+ <p>Powered by Groq's Models with Web Search, Browser Search & Agentic Capabilities</p>
565
+ </div>
566
+ """)
567
+
568
+ # NeuroScope AI Section
569
+ with gr.Group():
570
+ with gr.Accordion("πŸ€– NeuroScope AI Enhanced", open=False, elem_id="neuroscope-accordion"):
571
+ gr.Markdown("""
572
+ **Enhanced with Multiple Search Capabilities:**
573
+ - 🧠 **Intelligence** (Neuro): Advanced AI reasoning across multiple models
574
+ - πŸ” **Precision Search** (Scope): Domain filtering + Browser search tools
575
+ - πŸ€– **AI Capabilities** (AI): Agentic behavior with tool usage
576
+ - ⚑ **Dual Search**: Web search (compound models) + Browser search (other models)
577
+ - 🎯 **Model Flexibility**: Choose the right model for your task
578
+ """)
579
+
580
+ # IMPORTANT Section with Enhanced Search Info
581
+ with gr.Group():
582
+ with gr.Accordion("πŸ” IMPORTANT - Enhanced Search Capabilities!", open=True, elem_id="neuroscope-accordion"):
583
+ gr.Markdown("""
584
+ <div class="search-info">
585
+ <h3>πŸš€ NEW: Multiple Search Types Available!</h3>
586
+
587
+ <h4>🌐 Web Search Models (Compound Models)</h4>
588
+ <ul>
589
+ <li><strong>compound-beta:</strong> Most powerful with domain filtering</li>
590
+ <li><strong>compound-beta-mini:</strong> Faster with domain filtering</li>
591
+ <li><strong>Features:</strong> Include/exclude domains, autonomous web search</li>
592
+ </ul>
593
+
594
+ <h4>πŸ” Browser Search Models (Tool-based Models)</h4>
595
+ <ul>
596
+ <li><strong>openai/gpt-oss-20b:</strong> Fast browser search capabilities</li>
597
+ <li><strong>llama-3.3-70b-versatile:</strong> Advanced reasoning with search</li>
598
+ <li><strong>llama-3.1-70b-versatile:</strong> Reliable with search tools</li>
599
+ <li><strong>mixtral-8x7b-32768:</strong> Large context with search</li>
600
+ <li><strong>Features:</strong> Real-time browser search, current information</li>
601
+ </ul>
602
+ </div>
603
+
604
+ <div class="citation-info">
605
+ <h3>πŸ”— Enhanced Citation System</h3>
606
+ <p>All models now include:</p>
607
+ <ul>
608
+ <li><strong>Automatic Source Citations:</strong> Clickable links to sources</li>
609
+ <li><strong>Sources Used Section:</strong> Dedicated section showing all websites</li>
610
+ <li><strong>Search Type Indication:</strong> Shows which search method was used</li>
611
+ <li><strong>Tool Usage Display:</strong> Transparent about AI's research process</li>
612
+ </ul>
613
+ </div>
614
+ """)
615
+
616
+ # API Key and Model Selection Section
617
+ with gr.Row():
618
+ with gr.Column(scale=2):
619
+ api_key = gr.Textbox(
620
+ label="πŸ”‘ Groq API Key",
621
+ placeholder="Enter your Groq API key here...",
622
+ type="password",
623
+ info="Get your API key from: https://console.groq.com/"
624
+ )
625
+
626
+ # Advanced Settings
627
+ with gr.Accordion("βš™οΈ Advanced Settings", open=False, elem_id="neuroscope-accordion"):
628
+ with gr.Row():
629
+ temperature = gr.Slider(
630
+ minimum=0.0,
631
+ maximum=2.0,
632
+ value=0.7,
633
+ step=0.1,
634
+ label="🌑️ Temperature",
635
+ info="Higher = more creative, Lower = more focused"
636
+ )
637
+ max_tokens = gr.Slider(
638
+ minimum=100,
639
+ maximum=4000,
640
+ value=1024,
641
+ step=100,
642
+ label="πŸ“ Max Tokens",
643
+ info="Maximum length of response"
644
+ )
645
+
646
+ system_prompt = gr.Textbox(
647
+ label="🎭 Custom System Prompt",
648
+ placeholder="Override the default system prompt...",
649
+ lines=3,
650
+ info="Leave empty to use default creative assistant prompt with enhanced citations"
651
+ )
652
+
653
+ # Model Comparison Section
654
+ with gr.Accordion("πŸ“Š Model Comparison Guide", open=False, elem_id="neuroscope-accordion"):
655
+ gr.Markdown("""
656
+ ### πŸ” Choose Your Model Based on Task:
657
+
658
+ **For Academic Research & Domain-Specific Search:**
659
+ - `compound-beta` or `compound-beta-mini` with include domains (*.edu, arxiv.org)
660
+ - Best for: Research papers, academic sources, filtered searches
661
+
662
+ **For Current Events & Real-Time Information:**
663
+ - `openai/gpt-oss-20b` or `llama-3.3-70b-versatile` with browser search
664
+ - Best for: News, current events, real-time data
665
+
666
+ **For General Knowledge & Creative Tasks:**
667
+ - Any model with search type = "auto" or "none"
668
+ - Best for: Creative writing, general questions, analysis
669
+
670
+ **For Programming & Technical Documentation:**
671
+ - `llama-3.1-70b-versatile` with browser search, or compound models with tech domains
672
+ - Best for: Code help, documentation, technical guides
673
+ """)
674
+
675
+ # Domain Examples Section
676
+ with gr.Accordion("πŸ”— Common Domain Examples", open=False, elem_id="neuroscope-accordion"):
677
+ gr.Markdown("""
678
+ **Academic & Research:**
679
+ - `arxiv.org`, `*.edu`, `scholar.google.com`, `researchgate.net`, `pubmed.ncbi.nlm.nih.gov`
680
+
681
+ **Technology & Programming:**
682
+ - `github.com`, `stackoverflow.com`, `docs.python.org`, `developer.mozilla.org`, `medium.com`
683
+
684
+ **News & Media:**
685
+ - `reuters.com`, `bbc.com`, `npr.org`, `apnews.com`, `cnn.com`, `nytimes.com`
686
+
687
+ **Business & Finance:**
688
+ - `bloomberg.com`, `wsj.com`, `nasdaq.com`, `sec.gov`, `investopedia.com`
689
+
690
+ **Science & Medicine:**
691
+ - `nature.com`, `science.org`, `pubmed.ncbi.nlm.nih.gov`, `who.int`, `cdc.gov`
692
+
693
+ **Government & Official:**
694
+ - `*.gov`, `*.org`, `un.org`, `worldbank.org`, `imf.org`
695
+ """)
696
+
697
+ # How to Use Section
698
+ with gr.Accordion("πŸ“– How to Use This Enhanced App", open=False, elem_id="neuroscope-accordion"):
699
+ gr.Markdown("""
700
+ ### πŸš€ Getting Started
701
+ 1. **Enter your Groq API Key** - Get one from [console.groq.com](https://console.groq.com/)
702
+ 2. **Select a model** - Choose based on your search needs:
703
+ - **Compound models**: For web search with domain filtering
704
+ - **Tool-based models**: For browser search with real-time data
705
+ 3. **Configure search settings** - Choose search type and options
706
+ 4. **Click Connect** - Validate your key and connect to the AI
707
+ 5. **Start chatting!** - Type your message and get intelligent responses with citations
708
+
709
+ ### 🎯 Key Features
710
+ - **Dual Search Capabilities**: Web search + Browser search depending on model
711
+ - **Smart Citations**: Automatic source linking and citation formatting
712
+ - **Domain Filtering**: Control which websites the AI searches (compound models)
713
+ - **Real-time Search**: Get current information with browser search tools
714
+ - **Model Flexibility**: Choose the right model for your specific task
715
+ - **Enhanced Tool Visibility**: See exactly what search tools were used
716
+
717
+ ### πŸ’‘ Tips for Best Results
718
+
719
+ **For Research Tasks:**
720
+ - Use compound models with domain filtering
721
+ - Include academic domains (*.edu, arxiv.org) for scholarly sources
722
+ - Use "Force Search" for the most current information
723
+
724
+ **For Current Events:**
725
+ - Use tool-based models (openai/gpt-oss-20b, llama models)
726
+ - Set search type to "browser_search"
727
+ - Enable "Force Search" for real-time data
728
+
729
+ **For Creative Tasks:**
730
+ - Any model works well
731
+ - Set search type to "none" for purely creative responses
732
+ - Use higher temperature (0.8-1.0) for more creativity
733
+
734
+ **For Technical Questions:**
735
+ - Use llama-3.1-70b-versatile for programming
736
+ - Include tech domains (github.com, stackoverflow.com) with compound models
737
+ - Use browser search for latest documentation
738
+ """)
739
+
740
+ # Sample Examples Section
741
+ with gr.Accordion("🎯 Sample Examples to Test Enhanced Search", open=False, elem_id="neuroscope-accordion"):
742
+ gr.Markdown("""
743
+ <div class="example-box">
744
+ <h4>πŸ”¬ Research & Analysis (Test Different Models)</h4>
745
+
746
+ **Compound Model + Domain Filtering:**
747
+ - Query: "What are the latest breakthroughs in quantum computing?"
748
+ - Model: compound-beta
749
+ - Include domains: "arxiv.org, *.edu, nature.com"
750
+ - Search type: web_search
751
+
752
+ **Browser Search Model:**
753
+ - Same query with openai/gpt-oss-20b
754
+ - Search type: browser_search
755
+ - Force search: enabled
756
+
757
+ <h4>πŸ“° Current Events (Browser Search Excellence)</h4>
758
+
759
+ **Real-time News:**
760
+ - Query: "What happened in AI industry this week?"
761
+ - Model: llama-3.3-70b-versatile
762
+ - Search type: browser_search
763
+ - Force search: enabled
764
+
765
+ **Compare with Web Search:**
766
+ - Same query with compound-beta
767
+ - Include domains: "reuters.com, bbc.com, techcrunch.com"
768
+
769
+ <h4>πŸ’» Programming & Tech (Model Comparison)</h4>
770
+
771
+ **Technical Documentation:**
772
+ - Query: "How to implement OAuth 2.0 in Python Flask?"
773
+ - Try with both model types:
774
+ - compound-beta with "github.com, docs.python.org, stackoverflow.com"
775
+ - llama-3.1-70b-versatile with browser_search
776
+
777
+ <h4>🎨 Creative Tasks (No Search Needed)</h4>
778
+ - Query: "Write a short story about AI and humans working together"
779
+ - Any model with search_type: "none"
780
+ - Higher temperature (0.8-1.0)
781
+
782
+ <h4>πŸ“Š Business Analysis (Filtered vs Real-time)</h4>
783
+
784
+ **Financial Data (Real-time):**
785
+ - Query: "Current cryptocurrency market trends"
786
+ - Model: openai/gpt-oss-20b
787
+ - Search type: browser_search
788
+ - Force search: enabled
789
+
790
+ **Business Analysis (Filtered):**
791
+ - Query: "Cryptocurrency adoption in enterprise"
792
+ - Model: compound-beta
793
+ - Include domains: "bloomberg.com, wsj.com, harvard.edu"
794
+ </div>
795
+ """)
796
+
797
+ # Event handlers
798
+ send_btn.click(
799
+ fn=chat_with_ai,
800
+ inputs=[msg, include_domains, exclude_domains, system_prompt, temperature, max_tokens, search_type, force_search, chatbot],
801
+ outputs=[chatbot, msg]
802
+ )
803
+
804
+ msg.submit(
805
+ fn=chat_with_ai,
806
+ inputs=[msg, include_domains, exclude_domains, system_prompt, temperature, max_tokens, search_type, force_search, chatbot],
807
+ outputs=[chatbot, msg]
808
+ )
809
+
810
+ clear_btn.click(
811
+ fn=clear_chat_history,
812
+ outputs=[chatbot]
813
+ )
814
+
815
+ # Footer
816
+ with gr.Accordion("πŸš€ About This Enhanced NeuroScope AI", open=True, elem_id="neuroscope-accordion"):
817
+ gr.Markdown("""
818
+ **Enhanced Creative Agentic AI Chat Tool** with dual search capabilities:
819
+
820
+ ### πŸ†• **New in This Version:**
821
+ - πŸ” **Browser Search Integration**: Real-time search with tool-based models
822
+ - 🌐 **Dual Search System**: Web search (compound) + Browser search (tool-based)
823
+ - 🎯 **Model Flexibility**: 6 different models for different tasks
824
+ - ⚑ **Force Search Option**: Make AI search even for general questions
825
+ - πŸ”§ **Enhanced Tool Visibility**: See exactly what search tools were used
826
+ - πŸ“Š **Model Comparison Guide**: Choose the right model for your task
827
+
828
+ ### πŸ† **Core Features:**
829
+ - πŸ”— **Automatic Source Citations**: Every response includes clickable links to sources
830
+ - πŸ“š **Sources Used Section**: Dedicated section showing all websites referenced
831
+ - 🌐 **Smart Domain Filtering**: Control search scope (compound models)
832
+ - πŸ” **Real-time Browser Search**: Current information (tool-based models)
833
+ - πŸ’¬ **Conversational Memory**: Maintains context throughout the session
834
+ - βš™οΈ **Full Customization**: Adjust all parameters and prompts
835
+ - 🎨 **Creative & Analytical**: Optimized for both creative and research tasks
836
+
837
+ ### πŸ› οΈ **Technical Details:**
838
+ - **Compound Models**: compound-beta, compound-beta-mini (web search + domain filtering)
839
+ - **Tool-based Models**: openai/gpt-oss-20b, llama models, mixtral (browser search tools)
840
+ - **Automatic Search Type Detection**: AI chooses best search method
841
+ - **Enhanced Error Handling**: Robust error management and user feedback
842
+ - **Real-time Status Updates**: Live feedback on model capabilities and search settings
843
+ """)
844
+
845
+ return app
846
+
847
+ # Main execution
848
+ if __name__ == "__main__":
849
+ app = create_gradio_app()
850
+ app.launch(
851
+ share=True,
852
+ server_name="0.0.0.0",
853
+ server_port=7860
854
+ )
855
+ with gr.Column(scale=2):
856
+ model_selection = gr.Radio(
857
+ choices=[
858
+ "compound-beta",
859
+ "compound-beta-mini",
860
+ "openai/gpt-oss-20b",
861
+ "llama-3.3-70b-versatile",
862
+ "llama-3.1-70b-versatile",
863
+ "mixtral-8x7b-32768"
864
+ ],
865
+ label="🧠 Model Selection",
866
+ value="compound-beta",
867
+ info="Choose based on your search needs"
868
+ )
869
+ with gr.Column(scale=1):
870
+ connect_btn = gr.Button("πŸ”— Connect", variant="primary", size="lg")
871
+
872
+ # Status display
873
+ status_display = gr.Markdown("### πŸ“Š Status: Not connected", elem_classes=["status-box"])
874
+
875
+ # Connect button functionality
876
+ connect_btn.click(
877
+ fn=validate_api_key,
878
+ inputs=[api_key, model_selection],
879
+ outputs=[status_display]
880
+ )
881
+
882
+ model_selection.change(
883
+ fn=update_model,
884
+ inputs=[model_selection],
885
+ outputs=[status_display]
886
+ )
887
+
888
+ # Main Chat Interface
889
+ with gr.Tab("πŸ’¬ Chat"):
890
+ chatbot = gr.Chatbot(
891
+ label="Creative AI Assistant with Enhanced Search",
892
+ height=500,
893
+ show_label=True,
894
+ bubble_full_width=False,
895
+ show_copy_button=True
896
+ )
897
+
898
+ with gr.Row():
899
+ msg = gr.Textbox(
900
+ label="Your Message",
901
+ placeholder="Type your message here...",
902
+ lines=3
903
+ )
904
+ with gr.Column():
905
+ send_btn = gr.Button("πŸ“€ Send", variant="primary")
906
+ clear_btn = gr.Button("πŸ—‘οΈ Clear", variant="secondary")
907
+
908
+ # Search Settings
909
+ with gr.Accordion("πŸ” Search Settings", open=False, elem_id="neuroscope-accordion"):
910
+ with gr.Row():
911
+ search_type = gr.Radio(
912
+ choices=["auto", "web_search", "browser_search", "none"],
913
+ label="🎯 Search Type",
914
+ value="auto",
915
+ info="Choose search method (auto = model decides)"
916
+ )
917
+ force_search = gr.Checkbox(
918
+ label="⚑ Force Search",
919
+ value=False,
920
+ info="Force AI to search even for general questions"
921
+ )
922
+
923
+ # Update search options when model changes
924
+ model_selection.change(
925
+ fn=get_search_options,
926
+ inputs=[model_selection],
927
+ outputs=[search_type]
928
+ )
929
+
930
+ # Domain Filtering Section (only for web search models)
931
+ with gr.Accordion("🌐 Domain Filtering (Web Search Models Only)", open=False, elem_id="neuroscope-accordion"):
932
+ gr.Markdown("""
933
+ <div class="domain-info">
934
+ <h4>πŸ” Domain Filtering Guide</h4>
935
+ <p><strong>Note:</strong> Domain filtering only works with compound models (compound-beta, compound-beta-mini)</p>
936
+ <ul>
937
+ <li><strong>Include Domains:</strong> Only search these domains (comma-separated)</li>
938
+ <li><strong>Exclude Domains:</strong> Never search these domains (comma-separated)</li>
939
+ <li><strong>Examples:</strong> arxiv.org, *.edu, github.com, stackoverflow.com</li>
940
+ <li><strong>Wildcards:</strong> Use *.edu for all educational domains</li>
941
+ </ul>
942
+ </div>
943
+ """)
944
+
945
+ with gr.Row():
946
+ include_domains = gr.Textbox(
947
+ label="βœ… Include Domains (comma-separated)",
948
+ placeholder="arxiv.org, *.edu, github.com, stackoverflow.com",
949
+ info="Only search these domains (compound models only)"
950
+ )
951
+ exclude_domains = gr.Textbox(
952
+ label="❌ Exclude Domains (comma-separated)",
953
+ placeholder="wikipedia.org, reddit.com, twitter.com",
954
+ info="Never search these domains (compound models only)"
955
+ )