ProCreations commited on
Commit
12a8757
Β·
verified Β·
1 Parent(s): 4eec27e

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +700 -0
app.py ADDED
@@ -0,0 +1,700 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Just search - A Smart Search Agent using Menlo/Lucy-128k
4
+ Part of the Just, AKA Simple series
5
+ Built with Gradio, DuckDuckGo Search, and Hugging Face Transformers
6
+ """
7
+
8
+ import gradio as gr
9
+ import torch
10
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
11
+ from duckduckgo_search import DDGS
12
+ import json
13
+ import re
14
+ import time
15
+ from typing import List, Dict, Tuple
16
+ import spaces
17
+
18
+ # Initialize the model and tokenizer globally for efficiency
19
+ MODEL_NAME = "Menlo/Lucy-128k"
20
+ tokenizer = None
21
+ model = None
22
+ search_pipeline = None
23
+
24
+ def initialize_model():
25
+ """Initialize the Menlo/Lucy-128k model and tokenizer"""
26
+ global tokenizer, model, search_pipeline
27
+ try:
28
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
29
+ if tokenizer.pad_token is None:
30
+ tokenizer.pad_token = tokenizer.eos_token
31
+
32
+ model = AutoModelForCausalLM.from_pretrained(
33
+ MODEL_NAME,
34
+ torch_dtype=torch.float16,
35
+ device_map="auto",
36
+ trust_remote_code=True,
37
+ max_length=131072, # 128k context length
38
+ rope_scaling={"type": "linear", "factor": 1.0} # Enable extended context
39
+ )
40
+ search_pipeline = pipeline(
41
+ "text-generation",
42
+ model=model,
43
+ tokenizer=tokenizer,
44
+ torch_dtype=torch.float16,
45
+ device_map="auto",
46
+ max_new_tokens=16384, # 16k max output
47
+ temperature=0.3,
48
+ do_sample=True,
49
+ pad_token_id=tokenizer.eos_token_id
50
+ )
51
+ return True
52
+ except Exception as e:
53
+ print(f"Error initializing model: {e}")
54
+ return False
55
+
56
+ def extract_thinking_and_response(text: str) -> Tuple[str, str]:
57
+ """Extract thinking process and clean response from AI output"""
58
+ thinking = ""
59
+ response = text
60
+
61
+ # Multiple patterns for thinking extraction
62
+ patterns = [
63
+ (r'<think>(.*?)</think>', 1),
64
+ (r'<thinking>(.*?)</thinking>', 1),
65
+ (r'(Let me think about.*?)(?=\n\n|\n[A-Z]|$)', 1), # Catch untagged thinking
66
+ ]
67
+
68
+ for pattern, group_idx in patterns:
69
+ thinking_match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
70
+ if thinking_match:
71
+ thinking = thinking_match.group(group_idx).strip()
72
+ response = re.sub(pattern, '', text, flags=re.DOTALL | re.IGNORECASE)
73
+ break
74
+
75
+ # If no thinking found but text looks like reasoning, extract it
76
+ if not thinking and ('let me think' in text.lower() or 'i need to consider' in text.lower()):
77
+ lines = text.split('\n')
78
+ thinking_lines = []
79
+ response_lines = []
80
+ in_thinking = False
81
+
82
+ for line in lines:
83
+ lower_line = line.lower().strip()
84
+ if any(phrase in lower_line for phrase in ['let me think', 'i need to consider', 'first,', 'the user is asking']):
85
+ in_thinking = True
86
+ thinking_lines.append(line)
87
+ elif in_thinking and (line.strip().startswith(('β€’', '-', '1.', '2.', '3.')) or len(line.strip()) < 5):
88
+ in_thinking = False
89
+ response_lines.append(line)
90
+ elif in_thinking:
91
+ thinking_lines.append(line)
92
+ else:
93
+ response_lines.append(line)
94
+
95
+ if thinking_lines:
96
+ thinking = '\n'.join(thinking_lines).strip()
97
+ response = '\n'.join(response_lines).strip()
98
+
99
+ # Clean up the response
100
+ response = re.sub(r'^(Assistant:|AI:|Response:|Answer:)\s*', '', response.strip())
101
+ response = re.sub(r'\[INST\].*?\[\/INST\]', '', response, flags=re.DOTALL)
102
+ response = re.sub(r'<\|.*?\|>', '', response)
103
+
104
+ # Remove any remaining thinking artifacts from response
105
+ response = re.sub(r'Let me think.*?(?=\n\n|\n[A-Z]|$)', '', response, flags=re.DOTALL | re.IGNORECASE)
106
+ response = re.sub(r'I need to consider.*?(?=\n\n|\n[A-Z]|$)', '', response, flags=re.DOTALL | re.IGNORECASE)
107
+
108
+ return thinking.strip(), response.strip()
109
+
110
+ def clean_response(text: str) -> str:
111
+ """Clean up the AI response to extract just the relevant content"""
112
+ _, response = extract_thinking_and_response(text)
113
+ return response
114
+
115
+ @spaces.GPU
116
+ def generate_search_queries(user_query: str) -> Tuple[List[str], str]:
117
+ """Generate multiple search queries based on user input using AI"""
118
+ prompt = f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
119
+ You are an expert search query strategist. Your task is to generate diverse, effective search queries that will find the most comprehensive information to answer the user's question.
120
+
121
+ **Your Approach:**
122
+ 1. Analyze the user's question to identify key concepts, entities, and intent
123
+ 2. Consider different angles: current news, technical details, background context, expert opinions
124
+ 3. Use varied terminology: formal terms, common language, industry jargon, synonyms
125
+ 4. Target different types of sources: news sites, academic papers, official documents, forums
126
+
127
+ **Query Requirements:**
128
+ - Generate exactly 4 distinct search queries
129
+ - Each query should be 3-8 words long
130
+ - Optimize for search engine effectiveness
131
+ - Cover different aspects or perspectives of the topic
132
+ - Use specific, relevant keywords
133
+
134
+ **Examples:**
135
+ User: "What is the current status of artificial intelligence regulation?"
136
+ Queries:
137
+ AI regulation 2024 legislation
138
+ artificial intelligence policy updates
139
+ government AI rules current
140
+ machine learning regulation news
141
+
142
+ User: "How does climate change affect coral reefs?"
143
+ Queries:
144
+ climate change coral reef impact
145
+ ocean warming coral bleaching
146
+ coral reef ecosystem changes
147
+ marine biodiversity climate effects
148
+
149
+ <|eot_id|><|start_header_id|>user<|end_header_id|>
150
+ User question: {user_query}
151
+
152
+ Generate 4 strategic search queries:
153
+ <|eot_id|><|start_header_id|>assistant<|end_header_id|>"""
154
+
155
+ try:
156
+ response = search_pipeline(prompt, max_new_tokens=150, temperature=0.1)
157
+ generated_text = response[0]['generated_text']
158
+
159
+ # Extract assistant's response
160
+ assistant_response = generated_text.split('<|start_header_id|>assistant<|end_header_id|>')[-1]
161
+ thinking, cleaned_response = extract_thinking_and_response(assistant_response)
162
+
163
+ # Split and clean queries
164
+ lines = [line.strip() for line in cleaned_response.split('\n') if line.strip()]
165
+
166
+ # Filter to get actual search queries (remove meta-commentary)
167
+ queries = []
168
+ for line in lines:
169
+ # Skip lines that look like explanations or meta-commentary
170
+ if any(skip_word in line.lower() for skip_word in [
171
+ 'user', 'question', 'query', 'search', 'generate', 'here are',
172
+ 'these are', 'i will', 'let me', 'first', 'second', 'third', 'fourth',
173
+ 'based on', 'the user', 'example'
174
+ ]):
175
+ continue
176
+
177
+ # Skip lines with too many words (likely explanations)
178
+ if len(line.split()) > 8:
179
+ continue
180
+
181
+ # Skip numbered/bulleted lines
182
+ line_clean = re.sub(r'^\d+[\.\)]\s*', '', line)
183
+ line_clean = re.sub(r'^[\-\*\β€’]\s*', '', line_clean)
184
+ line_clean = line_clean.strip('"\'')
185
+
186
+ if len(line_clean) > 3 and len(line_clean.split()) >= 2:
187
+ queries.append(line_clean)
188
+
189
+ # If we didn't get good queries, fall back to simple variations
190
+ if len(queries) < 2:
191
+ queries = [
192
+ user_query,
193
+ f"{user_query} 2024",
194
+ f"{user_query} news",
195
+ f"{user_query} latest"
196
+ ]
197
+
198
+ return queries[:4], thinking
199
+
200
+ except Exception as e:
201
+ print(f"Error generating queries: {e}")
202
+ # Fallback to simple query variations
203
+ return [user_query, f"{user_query} 2024", f"{user_query} news", f"{user_query} latest"], ""
204
+
205
+ def search_web(queries: List[str]) -> List[Dict]:
206
+ """Search the web using DuckDuckGo with multiple queries"""
207
+ all_results = []
208
+ ddgs = DDGS()
209
+
210
+ for query in queries:
211
+ try:
212
+ results = ddgs.text(query, max_results=5, region='wt-wt', safesearch='moderate')
213
+ for result in results:
214
+ result['search_query'] = query
215
+ all_results.append(result)
216
+ time.sleep(0.5) # Rate limiting
217
+ except Exception as e:
218
+ print(f"Error searching for '{query}': {e}")
219
+ continue
220
+
221
+ # Remove duplicates based on URL
222
+ seen_urls = set()
223
+ unique_results = []
224
+ for result in all_results:
225
+ if result['href'] not in seen_urls:
226
+ seen_urls.add(result['href'])
227
+ unique_results.append(result)
228
+
229
+ return unique_results[:15] # Return max 15 results
230
+
231
+ @spaces.GPU
232
+ def filter_relevant_results(user_query: str, generated_queries: List[str], search_results: List[Dict]) -> Tuple[List[Dict], str]:
233
+ """Use AI to filter and rank search results by relevance"""
234
+ if not search_results:
235
+ return [], ""
236
+
237
+ # Prepare results summary for AI
238
+ results_text = ""
239
+ for i, result in enumerate(search_results[:15]): # Increased limit for better coverage
240
+ results_text += f"{i+1}. Title: {result.get('title', 'No title')}\n"
241
+ results_text += f" URL: {result.get('href', 'No URL')}\n"
242
+ results_text += f" Snippet: {result.get('body', 'No description')[:300]}...\n"
243
+ results_text += f" Search Query: {result.get('search_query', 'Unknown')}\n\n"
244
+
245
+ queries_text = "\n".join(f"β€’ {q}" for q in generated_queries)
246
+
247
+ prompt = f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
248
+ You are an expert information analyst specializing in search result evaluation. Your mission is to identify the highest-quality, most relevant sources that will enable a comprehensive answer to the user's question.
249
+
250
+ **Your Analysis Framework:**
251
+
252
+ **1. Relevance Assessment (40% weight):**
253
+ - How directly does the content address the user's specific question?
254
+ - Does it contain factual information needed for the answer?
255
+ - Is it focused on the core topic or just tangentially related?
256
+
257
+ **2. Source Quality & Authority (25% weight):**
258
+ - Is this from a credible, authoritative source?
259
+ - Does the source have expertise in this domain?
260
+ - Is it from official organizations, established media, academic institutions, or verified experts?
261
+
262
+ **3. Information Completeness (20% weight):**
263
+ - Does the source provide comprehensive coverage of the topic?
264
+ - Are there specific details, data, or insights that add value?
265
+ - Does it cover multiple aspects of the question?
266
+
267
+ **4. Recency & Timeliness (10% weight):**
268
+ - Is the information current and up-to-date?
269
+ - For time-sensitive topics, prioritize recent sources
270
+ - For established facts, older authoritative sources are acceptable
271
+
272
+ **5. Strategic Value (5% weight):**
273
+ - Does this complement other selected sources well?
274
+ - Does it provide unique perspectives or fill information gaps?
275
+
276
+ **Task Instructions:**
277
+ 1. Carefully analyze each search result against these criteria
278
+ 2. Consider how the results work together to provide comprehensive coverage
279
+ 3. Select exactly 5 results that will enable the best possible answer
280
+ 4. Prioritize quality over quantity - better to have fewer excellent sources
281
+
282
+ **Output Format:** Return only the numbers of your selected results, comma-separated (e.g., "1, 3, 7, 12, 14")
283
+
284
+ <|eot_id|><|start_header_id|>user<|end_header_id|>
285
+ **Original User Question:** {user_query}
286
+
287
+ **Context - Generated Search Queries:**
288
+ {queries_text}
289
+
290
+ **Search Results for Analysis:**
291
+ {results_text}
292
+
293
+ **Your Selection (5 most valuable results):**
294
+ <|eot_id|><|start_header_id|>assistant<|end_header_id|>"""
295
+
296
+ try:
297
+ response = search_pipeline(prompt, max_new_tokens=300, temperature=0.1)
298
+ generated_text = response[0]['generated_text']
299
+
300
+ # Extract assistant's response
301
+ assistant_response = generated_text.split('<|start_header_id|>assistant<|end_header_id|>')[-1]
302
+ thinking, cleaned_response = extract_thinking_and_response(assistant_response)
303
+
304
+ # Extract numbers
305
+ numbers = re.findall(r'\d+', cleaned_response)
306
+ selected_indices = [int(n) - 1 for n in numbers if int(n) <= len(search_results)]
307
+
308
+ return [search_results[i] for i in selected_indices if 0 <= i < len(search_results)][:5], thinking
309
+ except Exception as e:
310
+ print(f"Error filtering results: {e}")
311
+ return search_results[:5], "" # Fallback to first 5 results
312
+
313
+ @spaces.GPU
314
+ def generate_final_answer(user_query: str, generated_queries: List[str], all_search_results: List[Dict], selected_results: List[Dict]) -> Tuple[str, str]:
315
+ """Generate final answer based on complete search context"""
316
+ if not selected_results:
317
+ return "I couldn't find relevant information to answer your question. Please try rephrasing your query.", ""
318
+
319
+ # Prepare context from selected results
320
+ selected_context = ""
321
+ for i, result in enumerate(selected_results):
322
+ selected_context += f"**Source {i+1}:** {result.get('title', 'Unknown')}\n"
323
+ selected_context += f"**Content:** {result.get('body', 'No content available')}\n"
324
+ selected_context += f"**URL:** {result.get('href', 'No URL')}\n"
325
+ selected_context += f"**Found via query:** {result.get('search_query', 'Unknown')}\n\n"
326
+
327
+ # Summary of the search process
328
+ queries_text = "\n".join(f"β€’ {q}" for q in generated_queries)
329
+ process_summary = f"""
330
+ **Search Process Summary:**
331
+ - Generated {len(generated_queries)} targeted search queries
332
+ - Found {len(all_search_results)} total search results
333
+ - Filtered down to {len(selected_results)} most relevant sources
334
+ """
335
+
336
+ prompt = f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
337
+ You are a world-class research synthesist and expert communicator. You have access to comprehensive search intelligence and must craft the definitive answer to the user's question.
338
+
339
+ **Your Complete Context:**
340
+ - Original user question and intent
341
+ - Strategic search queries designed to find comprehensive information
342
+ - Curated high-quality sources selected for maximum relevance and authority
343
+ - Full visibility into the research methodology used
344
+
345
+ **Answer Quality Standards:**
346
+
347
+ 🎯 **Precision & Relevance (25%)**
348
+ - Address the user's exact question directly and completely
349
+ - Stay focused on their specific information needs
350
+ - Avoid tangential information that doesn't serve the core query
351
+
352
+ πŸ“š **Source Integration & Synthesis (25%)**
353
+ - Weave information from multiple sources into a cohesive narrative
354
+ - Identify patterns, agreements, and contradictions across sources
355
+ - Present a unified understanding rather than separate source summaries
356
+
357
+ πŸ” **Accuracy & Verification (20%)**
358
+ - Use only information explicitly stated in the provided sources
359
+ - Clearly attribute claims to specific sources with citations
360
+ - Acknowledge when information is limited or when sources conflict
361
+
362
+ πŸ“– **Structure & Clarity (15%)**
363
+ - Organize information logically with clear flow
364
+ - Use headings, bullet points, or sections when helpful
365
+ - Write in clear, accessible language appropriate for the topic
366
+
367
+ 🌟 **Completeness & Context (10%)**
368
+ - Provide sufficient background context for understanding
369
+ - Address multiple dimensions of the question when relevant
370
+ - Explain significance and implications of the findings
371
+
372
+ ⚑ **Transparency & Limitations (5%)**
373
+ - Be honest about gaps in available information
374
+ - Note if search results don't fully address certain aspects
375
+ - Distinguish between established facts and emerging information
376
+
377
+ **Citation Format:**
378
+ - When referencing specific information: [Source Title](URL)
379
+ - For direct quotes: "Quote text" - [Source Title](URL)
380
+ - Include a "Sources" section at the end with all referenced URLs
381
+
382
+ **Response Structure:**
383
+ 1. **Direct Answer** - Lead with a clear, concise response to the user's question
384
+ 2. **Detailed Analysis** - Comprehensive exploration with evidence and citations
385
+ 3. **Key Insights** - Important takeaways or implications
386
+ 4. **Sources** - List of referenced URLs for further reading
387
+
388
+ <|eot_id|><|start_header_id|>user<|end_header_id|>
389
+ **Original User Question:** {user_query}
390
+
391
+ **Research Intelligence:**
392
+ {queries_text}
393
+
394
+ {process_summary}
395
+
396
+ **Curated Source Material:**
397
+ {selected_context}
398
+
399
+ **Task:** Provide the definitive, well-sourced answer to this question using your complete research context.
400
+
401
+ <|eot_id|><|start_header_id|>assistant<|end_header_id|>"""
402
+
403
+ try:
404
+ response = search_pipeline(prompt, max_new_tokens=12288, temperature=0.2) # Even higher for comprehensive answers
405
+ generated_text = response[0]['generated_text']
406
+
407
+ # Extract assistant's response
408
+ assistant_response = generated_text.split('<|start_header_id|>assistant<|end_header_id|>')[-1]
409
+ thinking, answer = extract_thinking_and_response(assistant_response)
410
+
411
+ return answer, thinking
412
+ except Exception as e:
413
+ print(f"Error generating final answer: {e}")
414
+ return "I encountered an error while processing the search results. Please try again.", ""
415
+
416
+ def search_agent_workflow(user_query: str, progress=gr.Progress()) -> Tuple[str, str, str]:
417
+ """Main workflow that orchestrates the search agent"""
418
+ if not user_query.strip():
419
+ return "Please enter a search query.", "", ""
420
+
421
+ progress(0.1, desc="Initializing...")
422
+ all_thinking = []
423
+
424
+ # Step 1: Generate search queries
425
+ progress(0.2, desc="Generating search queries...")
426
+ queries, thinking1 = generate_search_queries(user_query)
427
+ if thinking1:
428
+ all_thinking.append(f"**Query Generation:**\n{thinking1}")
429
+ queries_text = "Generated queries:\n" + "\n".join(f"β€’ {q}" for q in queries)
430
+
431
+ # Step 2: Search the web
432
+ progress(0.4, desc="Searching the web...")
433
+ search_results = search_web(queries)
434
+
435
+ if not search_results:
436
+ return "No search results found. Please try a different query.", queries_text, "\n\n".join(all_thinking)
437
+
438
+ # Step 3: Filter relevant results
439
+ progress(0.6, desc="Filtering relevant results...")
440
+ relevant_results, thinking2 = filter_relevant_results(user_query, queries, search_results)
441
+ if thinking2:
442
+ all_thinking.append(f"**Result Filtering:**\n{thinking2}")
443
+
444
+ # Step 4: Generate final answer
445
+ progress(0.8, desc="Generating comprehensive answer...")
446
+ final_answer, thinking3 = generate_final_answer(user_query, queries, search_results, relevant_results)
447
+ if thinking3:
448
+ all_thinking.append(f"**Answer Generation:**\n{thinking3}")
449
+
450
+ progress(1.0, desc="Complete!")
451
+
452
+ # Prepare debug info
453
+ debug_info = f"{queries_text}\n\nSelected {len(relevant_results)} relevant sources:\n"
454
+ for i, result in enumerate(relevant_results):
455
+ debug_info += f"{i+1}. {result.get('title', 'No title')} - {result.get('href', 'No URL')}\n"
456
+
457
+ thinking_display = "\n\n".join(all_thinking) if all_thinking else "No thinking process recorded."
458
+
459
+ return final_answer, debug_info, thinking_display
460
+
461
+ # Custom CSS for dark blue theme and mobile responsiveness
462
+ custom_css = """
463
+ /* Dark blue theme */
464
+ :root {
465
+ --primary-bg: #0a1628;
466
+ --secondary-bg: #1e3a5f;
467
+ --accent-bg: #2563eb;
468
+ --text-primary: #f8fafc;
469
+ --text-secondary: #cbd5e1;
470
+ --border-color: #334155;
471
+ --input-bg: #1e293b;
472
+ --button-bg: #3b82f6;
473
+ --button-hover: #2563eb;
474
+ }
475
+
476
+ /* Global styles */
477
+ .gradio-container {
478
+ background: linear-gradient(135deg, var(--primary-bg) 0%, var(--secondary-bg) 100%) !important;
479
+ color: var(--text-primary) !important;
480
+ font-family: 'Inter', 'Segoe UI', system-ui, sans-serif !important;
481
+ }
482
+
483
+ /* Mobile responsiveness */
484
+ @media (max-width: 768px) {
485
+ .gradio-container {
486
+ padding: 10px !important;
487
+ }
488
+
489
+ .gr-form {
490
+ gap: 15px !important;
491
+ }
492
+
493
+ .gr-button {
494
+ font-size: 16px !important;
495
+ padding: 12px 20px !important;
496
+ }
497
+ }
498
+
499
+ /* Input styling */
500
+ .gr-textbox textarea, .gr-textbox input {
501
+ background: var(--input-bg) !important;
502
+ border: 1px solid var(--border-color) !important;
503
+ color: var(--text-primary) !important;
504
+ border-radius: 8px !important;
505
+ }
506
+
507
+ /* Button styling */
508
+ .gr-button {
509
+ background: linear-gradient(135deg, var(--button-bg) 0%, var(--accent-bg) 100%) !important;
510
+ color: white !important;
511
+ border: none !important;
512
+ border-radius: 8px !important;
513
+ font-weight: 600 !important;
514
+ transition: all 0.3s ease !important;
515
+ }
516
+
517
+ .gr-button:hover {
518
+ background: linear-gradient(135deg, var(--button-hover) 0%, var(--button-bg) 100%) !important;
519
+ transform: translateY(-1px) !important;
520
+ box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
521
+ }
522
+
523
+ /* Output styling */
524
+ .gr-markdown, .gr-textbox {
525
+ background: var(--input-bg) !important;
526
+ border: 1px solid var(--border-color) !important;
527
+ border-radius: 8px !important;
528
+ color: var(--text-primary) !important;
529
+ }
530
+
531
+ /* Header styling */
532
+ .gr-markdown h1 {
533
+ color: var(--accent-bg) !important;
534
+ text-align: center !important;
535
+ margin-bottom: 20px !important;
536
+ font-size: 2.5rem !important;
537
+ font-weight: 700 !important;
538
+ }
539
+
540
+ /* Thinking section styling */
541
+ #thinking-output {
542
+ background: var(--secondary-bg) !important;
543
+ border: 1px solid var(--border-color) !important;
544
+ border-radius: 8px !important;
545
+ padding: 15px !important;
546
+ font-family: 'Fira Code', 'Monaco', monospace !important;
547
+ font-size: 0.9rem !important;
548
+ line-height: 1.4 !important;
549
+ }
550
+
551
+ /* Loading animation */
552
+ .gr-loading {
553
+ background: var(--secondary-bg) !important;
554
+ border-radius: 8px !important;
555
+ }
556
+
557
+ /* Scrollbar styling */
558
+ ::-webkit-scrollbar {
559
+ width: 8px;
560
+ }
561
+
562
+ ::-webkit-scrollbar-track {
563
+ background: var(--primary-bg);
564
+ }
565
+
566
+ ::-webkit-scrollbar-thumb {
567
+ background: var(--accent-bg);
568
+ border-radius: 4px;
569
+ }
570
+
571
+ ::-webkit-scrollbar-thumb:hover {
572
+ background: var(--button-hover);
573
+ }
574
+ """
575
+
576
+ def create_interface():
577
+ """Create the Gradio interface"""
578
+ with gr.Blocks(
579
+ theme=gr.themes.Base(
580
+ primary_hue="blue",
581
+ secondary_hue="slate",
582
+ neutral_hue="slate",
583
+ text_size="lg",
584
+ spacing_size="lg",
585
+ radius_size="md"
586
+ ),
587
+ css=custom_css,
588
+ title="Just search - AI Search Agent",
589
+ head="<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
590
+ ) as interface:
591
+
592
+ gr.Markdown("# πŸ” Just search", elem_id="header")
593
+ gr.Markdown(
594
+ "*Part of the Just, AKA Simple series*\n\n"
595
+ "**Intelligent search agent powered by Menlo/Lucy-128k**\n\n"
596
+ "Ask any question and get comprehensive answers from the web.",
597
+ elem_id="description"
598
+ )
599
+
600
+ with gr.Row():
601
+ with gr.Column(scale=4):
602
+ query_input = gr.Textbox(
603
+ label="Your Question",
604
+ placeholder="Ask me anything... (e.g., 'What are the latest developments in AI?')",
605
+ lines=2,
606
+ elem_id="query-input"
607
+ )
608
+ with gr.Column(scale=1):
609
+ search_btn = gr.Button(
610
+ "πŸ”Ž Search",
611
+ variant="primary",
612
+ size="lg",
613
+ elem_id="search-button"
614
+ )
615
+
616
+ with gr.Row():
617
+ answer_output = gr.Markdown(
618
+ label="Answer",
619
+ elem_id="answer-output",
620
+ height=400
621
+ )
622
+
623
+ with gr.Accordion("πŸ€” AI Thinking Process", open=False):
624
+ thinking_output = gr.Markdown(
625
+ label="Model's Chain of Thought",
626
+ elem_id="thinking-output",
627
+ height=300
628
+ )
629
+
630
+ with gr.Accordion("πŸ”§ Debug Info", open=False):
631
+ debug_output = gr.Textbox(
632
+ label="Search Process Details",
633
+ lines=8,
634
+ elem_id="debug-output"
635
+ )
636
+
637
+ # Event handlers
638
+ search_btn.click(
639
+ fn=search_agent_workflow,
640
+ inputs=[query_input],
641
+ outputs=[answer_output, debug_output, thinking_output],
642
+ show_progress=True
643
+ )
644
+
645
+ query_input.submit(
646
+ fn=search_agent_workflow,
647
+ inputs=[query_input],
648
+ outputs=[answer_output, debug_output, thinking_output],
649
+ show_progress=True
650
+ )
651
+
652
+ # Example queries
653
+ gr.Examples(
654
+ examples=[
655
+ ["What are the latest breakthroughs in quantum computing?"],
656
+ ["How does climate change affect ocean currents?"],
657
+ ["What are the best practices for sustainable agriculture?"],
658
+ ["Explain the recent developments in renewable energy technology"],
659
+ ["What are the health benefits of the Mediterranean diet?"]
660
+ ],
661
+ inputs=query_input,
662
+ outputs=[answer_output, debug_output, thinking_output],
663
+ fn=search_agent_workflow,
664
+ cache_examples=False
665
+ )
666
+
667
+ gr.Markdown(
668
+ "---\n**Note:** This search agent generates multiple queries, searches the web, "
669
+ "filters results for relevance, and provides comprehensive answers. "
670
+ "Results are sourced from DuckDuckGo search."
671
+ )
672
+
673
+ return interface
674
+
675
+ def main():
676
+ """Main function to initialize and launch the app"""
677
+ print("πŸš€ Initializing Just search...")
678
+
679
+ # Initialize the model
680
+ if not initialize_model():
681
+ print("❌ Failed to initialize model. Please check your setup.")
682
+ return
683
+
684
+ print("βœ… Model initialized successfully!")
685
+ print("🌐 Creating interface...")
686
+
687
+ # Create and launch the interface
688
+ interface = create_interface()
689
+
690
+ print("πŸŽ‰ Just search is ready!")
691
+ interface.launch(
692
+ server_name="0.0.0.0",
693
+ server_port=7860,
694
+ share=True,
695
+ show_error=True,
696
+ debug=True
697
+ )
698
+
699
+ if __name__ == "__main__":
700
+ main()