File size: 10,550 Bytes
cf40b67
ed0c3c5
12a0d68
cf40b67
 
835fc41
379919c
ed0c3c5
12a0d68
b17a402
ed0c3c5
12a0d68
 
cf40b67
 
ed0c3c5
12a0d68
 
 
 
 
 
 
 
cf40b67
60c475d
cf40b67
12a0d68
60c475d
12a0d68
 
60c475d
12a0d68
835fc41
12a0d68
60c475d
12a0d68
cf40b67
12a0d68
 
60c475d
12a0d68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60c475d
12a0d68
60c475d
12a0d68
cf40b67
12a0d68
 
60c475d
cf40b67
 
 
60c475d
12a0d68
 
cf40b67
 
 
 
 
60c475d
ed0c3c5
12a0d68
60c475d
 
379919c
ed0c3c5
 
60c475d
 
ed0c3c5
60c475d
 
379919c
6c1f2d1
379919c
 
 
60c475d
12a0d68
cf40b67
60c475d
 
 
 
 
cf40b67
12a0d68
60c475d
12a0d68
cf40b67
60c475d
cf40b67
12a0d68
60c475d
12a0d68
 
 
cf40b67
12a0d68
 
cf40b67
60c475d
12a0d68
 
 
 
60c475d
 
12a0d68
60c475d
 
12a0d68
 
 
cf40b67
 
60c475d
cf40b67
12a0d68
 
 
 
cf40b67
12a0d68
cf40b67
12a0d68
60c475d
12a0d68
cf40b67
 
60c475d
 
12a0d68
 
 
 
 
cf40b67
12a0d68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60c475d
 
 
12a0d68
 
 
 
 
 
 
 
 
ed0c3c5
12a0d68
ed0c3c5
12a0d68
ed0c3c5
12a0d68
 
 
 
60c475d
12a0d68
60c475d
12a0d68
 
 
cf40b67
12a0d68
835fc41
cf40b67
 
ed0c3c5
 
60c475d
 
 
 
 
12a0d68
60c475d
 
 
12a0d68
60c475d
 
 
 
 
 
 
 
 
379919c
60c475d
379919c
60c475d
 
ed0c3c5
 
 
cf40b67
 
60c475d
 
379919c
60c475d
 
 
 
cf40b67
60c475d
cf40b67
60c475d
 
 
 
 
 
 
cf40b67
 
60c475d
cf40b67
60c475d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
import gradio as gr
import spaces  # Required for ZeroGPU
from transformers import pipeline, AutoTokenizer
from duckduckgo_search import DDGS
from datetime import datetime

# Initialize a lightweight text generation model on CPU
generator = pipeline("text-generation", model="distilgpt2", device=-1)  # -1 ensures CPU by default
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")  # For better token handling

# Web search function (CPU-based)
def get_web_results(query: str, max_results: int = 5) -> list:
    """Fetch web results synchronously for Zero GPU compatibility, focusing on high-quality sources."""
    try:
        with DDGS() as ddgs:
            results = list(ddgs.text(query, max_results=max_results))
            # Filter for high-quality, relevant results (e.g., educational, authoritative sites)
            filtered_results = [
                {"title": r.get("title", "No Title"), "snippet": r["body"], "url": r["href"]}
                for r in results
                if any(domain in r["href"] for domain in ["geeksforgeeks.org", "realpython.com", "coursera.org", "udemy.com", "stackexchange.com"])
                or "edu" in r["href"]
            ]
            return filtered_results if filtered_results else results  # Fall back to all results if no high-quality ones found
    except Exception as e:
        return [{"title": "Error", "snippet": f"Failed to fetch results: {str(e)}", "url": "#"}]

# Format prompt for the AI model to generate high-quality, structured answers
def format_prompt(query: str, web_results: list) -> str:
    """Create a detailed prompt with web context to guide the model toward high-quality answers."""
    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S IST")
    context = "\n".join([f"- {r['title']}: {r['snippet']}" for r in web_results])
    return f"""Current Time: {current_time}
Query: {query}
Web Context (High-Quality Sources):
{context}
Provide a detailed, step-by-step answer in markdown format with clear headings (e.g., #, ##), bullet points, and citations [1], [2], etc. Ensure the answer is structured, relevant, and visually appealing, addressing the user's intent comprehensively. If the query is informational (e.g., 'what,' 'how,' 'why'), offer in-depth insights, examples, and practical advice. If no high-quality answer is possible, state, 'I couldn’t find sufficient high-quality information to provide a detailed answer, but here’s what I found:' followed by a summary of web results."""

# GPU-decorated answer generation for high-quality output
@spaces.GPU(duration=180)  # Increased duration for more detailed generation
def generate_answer(prompt: str) -> str:
    """Generate a detailed, high-quality research answer using GPU."""
    # Tokenize and truncate prompt to fit within limits
    tokenized_prompt = tokenizer(prompt, truncation=True, max_length=200, return_tensors="pt")
    input_ids = tokenized_prompt["input_ids"]
    
    # Generate response with more tokens and better sampling for quality
    response = generator(
        prompt,
        max_new_tokens=400,  # Increased for more detailed output
        num_return_sequences=1,
        truncation=True,
        do_sample=True,
        temperature=0.7,  # Controlled randomness for coherent, detailed output
        top_p=0.9,  # Focus on top probabilities for quality
        top_k=50  # Limit to top 50 tokens for better coherence
    )[0]["generated_text"]
    
    answer_start = response.find("Provide a detailed") + len("Provide a detailed, step-by-step answer in markdown format with clear headings (e.g., #, ##), bullet points, and citations [1], [2], etc. Ensure the answer is structured, relevant, and visually appealing, addressing the user's intent comprehensively. If the query is informational (e.g., 'what,' 'how,' 'why'), offer in-depth insights, examples, and practical advice. If no high-quality answer is possible, state, 'I couldn’t find sufficient high-quality information to provide a detailed answer, but here’s what I found:' followed by a summary of web results.")
    return response[answer_start:].strip() if answer_start > -1 else "I couldn’t find sufficient high-quality information to provide a detailed answer, but here’s what I found:\n\n" + "\n".join([f"- {r['title']}: {r['snippet']}" for r in get_web_results(query, max_results=3)])

# Format sources for display with enhanced styling
def format_sources(web_results: list) -> str:
    """Create a styled HTML list of sources with better visuals for high-quality presentation."""
    if not web_results:
        return "<div class='no-sources'>No sources available</div>"
    
    sources_html = "<div class='sources-list'>"
    for i, res in enumerate(web_results, 1):
        sources_html += f"""
        <div class='source-item'>
            <span class='source-number'>[{i}]</span>
            <a href='{res['url']}' target='_blank' class='source-link'>{res['title']}</a>
            <p class='source-snippet'>{res['snippet'][:150]}...</p>
        </div>
        """
    sources_html += "</div>"
    return sources_html

# Main processing function
def process_deep_research(query: str, history: list):
    """Handle the deep research process for any query with high-quality output."""
    if not history:
        history = []
    
    # Fetch web results (CPU)
    web_results = get_web_results(query)
    sources_html = format_sources(web_results)

    # Generate answer (GPU via @spaces.GPU)
    prompt = format_prompt(query, web_results)
    answer = generate_answer(prompt)
    
    # Convert history to messages format (role/content)
    new_history = history + [{"role": "user", "content": query}, {"role": "assistant", "content": answer}]
    
    return answer, sources_html, new_history

# Enhanced CSS for a polished, visually appealing UI
css = """
body {
    font-family: 'Arial', sans-serif;
    background: #1a1a1a;
    color: #ffffff;
}
.gradio-container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
}
.header {
    text-align: center;
    padding: 20px;
    background: linear-gradient(135deg, #2c3e50, #3498db);
    border-radius: 12px;
    margin-bottom: 20px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.header h1 { font-size: 2.5em; margin: 0; color: #ffffff; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); }
.header p { color: #bdc3c7; font-size: 1.1em; }
.search-box {
    background: #2c2c2c;
    padding: 15px;
    border-radius: 12px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    margin-bottom: 20px;
}
.search-box input {
    background: #3a3a3e !important;
    color: #ffffff !important;
    border: none !important;
    border-radius: 8px !important;
    padding: 10px;
    font-size: 1em;
}
.search-box button {
    background: #3498db !important;
    border: none !important;
    border-radius: 8px !important;
    padding: 10px 20px;
    font-size: 1em;
    transition: background 0.3s;
}
.search-box button:hover { background: #2980b9 !important; }
.results-container {
    margin-top: 20px;
    display: flex;
    gap: 20px;
}
.answer-box {
    flex: 2;
    background: #2c2c2c;
    padding: 20px;
    border-radius: 12px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    overflow-y: auto;
    max-height: 600px;
}
.answer-box .markdown {
    color: #ecf0f1;
    line-height: 1.6;
}
.answer-box .markdown h1 {
    color: #ffffff;
    border-bottom: 2px solid #3498db;
    padding-bottom: 10px;
}
.answer-box .markdown h2 {
    color: #a8b5c3;
    margin-top: 20px;
}
.answer-box .markdown ul {
    list-style-type: none;
    padding-left: 20px;
}
.answer-box .markdown ul li::before {
    content: "•";
    color: #3498db;
    display: inline-block;
    width: 1em;
    margin-left: -1em;
}
.answer-box .markdown a { color: #60a5fa; text-decoration: underline; }
.sources-list {
    flex: 1;
    background: #2c2c2c;
    padding: 15px;
    border-radius: 12px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    max-height: 600px;
    overflow-y: auto;
}
.no-sources { color: #a8a9ab; font-style: italic; }
.source-item {
    margin-bottom: 15px;
    padding: 10px;
    background: #3a3a3e;
    border-radius: 8px;
    transition: background 0.2s;
}
.source-item:hover { background: #4a4b4e; }
.source-number { color: #3498db; font-weight: bold; margin-right: 10px; }
.source-link { color: #60a5fa; font-weight: 500; display: block; margin-bottom: 5px; }
.source-snippet { color: #e5e7eb; font-size: 0.9em; line-height: 1.4; }
.history-box {
    margin-top: 20px;
    background: #2c2c2c;
    padding: 15px;
    border-radius: 12px;
    max-height: 300px;
    overflow-y: auto;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
"""

# Gradio app setup with Blocks
with gr.Blocks(title="Deep Research Engine - ZeroGPU", css=css) as demo:
    history_state = gr.State([])

    # Header
    with gr.Column(elem_classes="header"):
        gr.Markdown("# Deep Research Engine")
        gr.Markdown("Your gateway to in-depth, high-quality research for any query with real-time web insights.")

    # Search input and button
    with gr.Row(elem_classes="search-box"):
        search_input = gr.Textbox(label="", placeholder="Ask anything (e.g., 'What are the latest AI trends in 2025?')", lines=2)
        search_btn = gr.Button("Research", variant="primary")

    # Results layout
    with gr.Row(elem_classes="results-container"):
        with gr.Column():
            answer_output = gr.Markdown(label="Research Findings", elem_classes="answer-box")
        with gr.Column():
            sources_output = gr.HTML(label="Sources", elem_classes="sources-list")

    # Chat history (using messages format)
    with gr.Row():
        history_display = gr.Chatbot(label="History", elem_classes="history-box", type="messages")

    # Event handling
    def handle_search(query, history):
        answer, sources, new_history = process_deep_research(query, history)
        return answer, sources, new_history

    search_btn.click(
        fn=handle_search,
        inputs=[search_input, history_state],
        outputs=[answer_output, sources_output, history_display]
    ).then(
        fn=lambda x: x,
        inputs=[history_display],
        outputs=[history_state]
    )

    search_input.submit(
        fn=handle_search,
        inputs=[search_input, history_state],
        outputs=[answer_output, sources_output, history_display]
    ).then(
        fn=lambda x: x,
        inputs=[history_display],
        outputs=[history_state]
    )

# Launch the app
if __name__ == "__main__":
    demo.launch()