ego commited on
Commit
7bafc8f
·
1 Parent(s): 5acbfd6

without comment

Browse files
.streamlit/config.toml CHANGED
@@ -1,5 +1,7 @@
1
- [server]
2
- headless = true
3
-
4
- [browser]
5
- gatherUsageStats = false
 
 
 
1
+ [theme]
2
+ base="light"
3
+ primaryColor="#4A6D8C"
4
+ backgroundColor="#ffffff"
5
+ secondaryBackgroundColor="#f0f2f6"
6
+ textColor="#31333F"
7
+ font="sans serif"
DEPLOY_INSTRUCTIONS.md DELETED
@@ -1,45 +0,0 @@
1
- # Deploying to Hugging Face Spaces
2
-
3
- ## Prerequisites
4
- - A Hugging Face account.
5
- - A Google API Key.
6
-
7
- ## Steps
8
-
9
- 1. **Create a New Space**
10
- - Go to [Hugging Face Spaces](https://huggingface.co/spaces).
11
- - Click **"Create new Space"**.
12
- - Enter a name (e.g., `academic-assistant`).
13
- - **Select SDK**: Choose **Docker**. (This is critical because we need system-level dependencies like `graphviz` and specific Python versions).
14
- - Choose "Public" or "Private".
15
- - Click **"Create Space"**.
16
-
17
- 2. **Upload Files**
18
- - You can upload files directly via the browser or use Git.
19
- - **Files to Upload**:
20
- - `Dockerfile`
21
- - `requirements.txt`
22
- - `app.py`
23
- - `prompts.py`
24
- - `core/` (The entire directory)
25
- - `.streamlit/config.toml` (Optional, prevents welcome screen)
26
-
27
- *Note: Do NOT upload `.env` or your API keys directly in the files.*
28
-
29
- 3. **Configure Secrets (Environment Variables)**
30
- - In your Space settings, go to the **"Settings"** tab.
31
- - Scroll down to **"Variables and secrets"**.
32
- - Click **"New secret"**.
33
- - **Name**: `GOOGLE_API_KEY`
34
- - **Value**: Your actual Google API Key (starting with `AIza...`).
35
- - Click **Save**.
36
-
37
- 4. **Build & Run**
38
- - Once files are uploaded, Hugging Face will automatically start building the Docker image.
39
- - You can watch the "Build" logs.
40
- - Once "Running", your app will be live!
41
-
42
- ## Troubleshooting
43
- - **Build Error: "graphviz not found"**: Ensure your `Dockerfile` includes `RUN apt-get update && apt-get install -y graphviz`. (Already included).
44
- - **ModuleNotFoundError**: Ensure all packages (like `google-genai`) are in `requirements.txt`. (I just updated this for you).
45
- - **Runtime Error "Credentials"**: Ensure you added the `GOOGLE_API_KEY` in the Spaces Settings/Secrets, NOT just the `.env` file locally.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
__pycache__/prompts.cpython-312.pyc CHANGED
Binary files a/__pycache__/prompts.cpython-312.pyc and b/__pycache__/prompts.cpython-312.pyc differ
 
app.py CHANGED
@@ -3,54 +3,105 @@ import os
3
  import time
4
  from dotenv import load_dotenv
5
 
6
- # Load env
7
  load_dotenv()
8
 
9
- from core.pdf_processer import PDFProcessor
10
  from core.graph import RAGAgent
11
  from core.podcast import PodcastGenerator
12
  from core.visualizer import KnowledgeGraphGenerator
13
  from core.map_reduce import MapReduceSummarizer
14
 
15
- # Configuration
16
  st.set_page_config(
17
- page_title="Academic Learning Assistant",
18
  page_icon="🎓",
19
  layout="wide",
20
  initial_sidebar_state="collapsed"
21
  )
22
 
23
- # Custom CSS for "Fancy" Look
24
  st.markdown("""
25
  <style>
26
  .main { background-color: #f8f9fa; }
27
 
28
  /* Typography */
29
- h1, h2, h3, h4 { font-family: 'Helvetica Neue', 'Inter', sans-serif; color: #333; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  /* Card Style */
32
  .stCard {
33
- background-color: white;
34
  padding: 24px;
35
- border-radius: 12px;
36
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
37
  margin-bottom: 20px;
38
- border: 1px solid #e1e4e8;
 
 
 
 
 
39
  }
40
 
41
- /* Button Styling */
42
- .stButton>button {
 
 
 
43
  border-radius: 20px;
44
- font-weight: 600;
 
 
 
 
45
  }
46
 
47
- /* Navigation Button */
48
- .nav-btn>button {
49
- width: 100%;
50
- background-color: #4CAF50;
51
- color: white;
52
- font-size: 18px;
53
- padding: 12px;
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
55
  </style>
56
  """, unsafe_allow_html=True)
@@ -61,7 +112,7 @@ if "page" not in st.session_state:
61
  if "agent" not in st.session_state:
62
  st.session_state.agent = None
63
  if "pdf_processor" not in st.session_state:
64
- st.session_state.pdf_processor = PDFProcessor()
65
  if "messages" not in st.session_state:
66
  st.session_state.messages = []
67
  if "full_text" not in st.session_state:
@@ -81,13 +132,10 @@ def switch_page(page_name):
81
  st.session_state.page = page_name
82
  st.rerun()
83
 
84
- # --- PAGES ---
85
  def show_home():
86
- # Hero Section
87
- st.markdown("<h1 class='hero-title'>🎓 Academic Learning Assistant</h1>", unsafe_allow_html=True)
88
- st.markdown("<p class='hero-subtitle'>Next-Gen Multi-Agent System for Research & Study</p>", unsafe_allow_html=True)
89
 
90
- # Call to Action Button (Centered)
91
  col_cta1, col_cta2, col_cta3 = st.columns([1, 1, 1])
92
  with col_cta2:
93
  if st.button("🚀 Launch Application", type="primary", width='stretch'):
@@ -95,18 +143,17 @@ def show_home():
95
 
96
  st.markdown("---")
97
 
98
- # --- Section 1 & 2: Motivation & Problem (2-Column Layout) ---
99
  col1, col2 = st.columns(2, gap="large")
100
 
101
  with col1:
102
  st.markdown("<div class='section-header'>🌟 1. Motivation</div>", unsafe_allow_html=True)
103
  st.markdown("""
104
  <div class="stCard">
105
- <p>In the era of information explosion, students and researchers are overwhelmed by the sheer volume of academic literature.</p>
106
  <ul>
107
- <li><b>Information Overload:</b> Time-consuming to synthesize connections across lengthy PDFs.</li>
108
- <li><b>Tool Limitations:</b> Basic search lacks semantics; generic LLMs hallucinate citations.</li>
109
- <li><b>Multi-Modal Need:</b> Learners need text, audio (podcasts), and visual structures (graphs).</li>
110
  </ul>
111
  </div>
112
  """, unsafe_allow_html=True)
@@ -115,102 +162,103 @@ def show_home():
115
  st.markdown("<div class='section-header'>❓ 2. Problem Definition</div>", unsafe_allow_html=True)
116
  st.markdown("""
117
  <div class="stCard">
118
- <p>Inefficiency and unreliability in synthesizing technical knowledge from unstructured documents.</p>
119
  <ul>
120
- <li><b>Reliability:</b> How to ensure answers are grounded and free from hallucinations?</li>
121
- <li><b>Complexity:</b> How to grasp structural relationships in dense papers?</li>
122
- <li><b>Accessibility:</b> How to make dry documents consumable on the go?</li>
123
  </ul>
124
  </div>
125
  """, unsafe_allow_html=True)
126
 
127
- # --- Section 3: Solution Approach (3-Column Feature Grid) ---
128
- st.markdown("<div class='section-header'>💡 3. Solution Approach: Multi-Agent System</div>", unsafe_allow_html=True)
129
 
130
- c1, c2, c3 = st.columns(3, gap="medium")
131
 
132
  with c1:
133
  st.markdown("""
134
- <div class="stCard">
135
- <span class="feature-badge">The Researcher</span>
136
- <h3>Reflective RAG Agent</h3>
137
- <p>Utilizes a cyclic <b>Agentic Workflow</b>. It retrieves, answers, and performs a <b>Self-Reflection</b> step. If quality is poor, it automatically rewrites the query.</p>
138
  </div>
139
  """, unsafe_allow_html=True)
140
-
141
  with c2:
142
  st.markdown("""
143
- <div class="stCard">
144
- <span class="feature-badge">The Producer</span>
145
- <h3>Podcast Agent</h3>
146
- <p>Employs <b>Map-Reduce</b> for long docs. Summarizes chunks, synthesizes a briefing, and uses <b>Gemini Native Audio</b> for natural multi-speaker dialogue.</p>
147
  </div>
148
  """, unsafe_allow_html=True)
149
 
150
  with c3:
151
  st.markdown("""
152
- <div class="stCard">
153
- <span class="feature-badge">The Analyst</span>
154
- <h3>Visualizer Agent</h3>
155
- <p>Analyzes text to extract entities and relationships, generating <b>Graphviz DOT</b> code to render interactive Knowledge Graphs for structural insight.</p>
 
 
 
 
 
 
 
 
 
156
  </div>
157
  """, unsafe_allow_html=True)
158
 
159
- # --- Section 4: System Architecture (Clean Layered View) ---
160
  st.markdown("<div class='section-header'>🏗️ 4. System Architecture</div>", unsafe_allow_html=True)
161
 
162
  st.markdown("""
163
  <div class="stCard">
164
  <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; text-align: center;">
165
  <div>
166
- <h4>💻 Frontend</h4>
167
- <p style="font-size: 0.9rem; color: #666;">Streamlit UI<br>Card Interface<br>Multi-file Upload</p>
168
  </div>
169
  <div style="border-left: 1px solid #eee;">
170
- <h4>🧠 Orchestration</h4>
171
- <p style="font-size: 0.9rem; color: #666;">LangGraph<br>State Management<br>Cyclic Flows</p>
172
  </div>
173
  <div style="border-left: 1px solid #eee;">
174
- <h4>💾 Data Layer</h4>
175
- <p style="font-size: 0.9rem; color: #666;">ChromaDB<br>In-Memory Vector Store<br>HF Spaces Ready</p>
176
  </div>
177
  <div style="border-left: 1px solid #eee;">
178
- <h4>🤖 Model Layer</h4>
179
- <p style="font-size: 0.9rem; color: #666;">Google Gemini 2.5<br>(Native Audio/Text)<br>NVIDIA NIM (Optional)</p>
180
  </div>
181
  </div>
182
  </div>
183
  """, unsafe_allow_html=True)
184
 
185
- # --- Section 5: Implementation Details (Interactive Tabs) ---
186
  st.markdown("<div class='section-header'>⚙️ 5. Implementation Details</div>", unsafe_allow_html=True)
187
 
188
- tab_rag, tab_pod, tab_vis = st.tabs(["🔄 RAG Loop", "🎙️ Map-Reduce Podcast", "🕸Knowledge Graph"])
189
 
190
  with tab_rag:
191
- st.info("**Core Logic:** `core/graph.py`")
192
  st.markdown("""
193
- 1. **Retrieve:** Fetches document chunks based on query.
194
- 2. **Generate:** Drafts answer with strict citations `[Source: paper.pdf]`.
195
- 3. **Reflect:** LLM acts as "grader" for relevance/hallucination.
196
- - **Pass:** Returns final answer.
197
- - **Fail:** Triggers **Query Rewrite** and re-enters Retrieve loop (Max 2 iters).
198
  """)
199
 
200
- with tab_pod:
201
- st.info("**Core Logic:** `core/podcast.py`")
202
  st.markdown("""
203
- 1. **Map:** Iterates through all PDF chunks to generate concise summaries.
204
- 2. **Reduce:** Aggregates summaries into a structured "Episode Briefing".
205
- 3. **Audio Synthesis:** Sends briefing to **Gemini 2.5 Flash** with prompt to "generate AUDIO directly" (Host & Expert personas).
206
  """)
207
 
208
- with tab_vis:
209
- st.info("**Core Logic:** `core/visualizer.py`")
210
  st.markdown("""
211
- 1. **Extraction:** LLM extracts `Subject -> Predicate -> Object` triplets.
212
- 2. **Formatting:** Converts relationships into valid **Graphviz DOT** syntax.
213
- 3. **Rendering:** Streamlit renders the DOT code into a visual chart.
214
  """)
215
 
216
  st.markdown("<br><br>", unsafe_allow_html=True)
@@ -221,18 +269,16 @@ def ensure_deep_summary():
221
 
222
  if not st.session_state.deep_summary:
223
  if st.session_state.full_text:
224
- with st.spinner("Analyzing Document (Map-Reduce Deep Summary)..."):
225
  mr = MapReduceSummarizer()
226
  st.session_state.deep_summary = mr.generate_deep_summary(st.session_state.full_text)
227
  return st.session_state.deep_summary
228
 
229
- # Dialog Helper (handles version differences)
230
  if hasattr(st, "dialog"):
231
  dialog_decorator = st.dialog
232
  elif hasattr(st, "experimental_dialog"):
233
  dialog_decorator = st.experimental_dialog
234
  else:
235
- # No dialog support, fallback to a simple function
236
  def dialog_decorator(*args, **kwargs):
237
  def decorator(func):
238
  return func
@@ -247,7 +293,6 @@ def view_summary_dialog(text):
247
  @dialog_decorator("Knowledge Graph Visualization", width="large")
248
  def view_graph_dialog(dot_code):
249
  st.graphviz_chart(dot_code, width="stretch")
250
- st.caption("Right-click -> 'Open Image in New Tab' to zoom/download.")
251
 
252
  def show_app():
253
  # Sidebar: Clean, just for upload and nav
@@ -275,7 +320,6 @@ def show_app():
275
  for f in new_files:
276
  st.session_state.processed_files.add(f.name)
277
 
278
- # Set success message and reset uploader to clear the list
279
  st.session_state.upload_status = f"Successfully indexed ~{total_tokens:,} tokens from {len(new_files)} new file(s)."
280
  st.session_state.uploader_key += 1
281
  st.rerun()
@@ -284,7 +328,7 @@ def show_app():
284
  st.success("Analysis Ready")
285
 
286
  if st.button("🔄 Reset / Clear All", type="primary"):
287
- st.session_state.pdf_processor = PDFProcessor()
288
  st.session_state.agent = None
289
  st.session_state.messages = []
290
  st.session_state.full_text = ""
@@ -295,13 +339,11 @@ def show_app():
295
  st.rerun()
296
 
297
 
298
- # Layout: Chat (Left/Center) | Tools (Right)
299
  col_chat, col_tools = st.columns([3, 1.3])
300
 
301
  with col_chat:
302
  st.subheader("💬 Chat")
303
 
304
- # Chat History
305
  for msg in st.session_state.messages:
306
  with st.chat_message(msg["role"]):
307
  if "thoughts" in msg and msg["thoughts"]:
@@ -310,7 +352,6 @@ def show_app():
310
  st.write(log)
311
  st.markdown(msg["content"])
312
 
313
- # User Input
314
  if prompt := st.chat_input("Ask about the document..."):
315
  st.session_state.messages.append({"role": "user", "content": prompt})
316
  with st.chat_message("user"):
@@ -318,7 +359,6 @@ def show_app():
318
 
319
  with st.chat_message("assistant"):
320
  if st.session_state.agent:
321
- # Container for intermediate thought process
322
  with st.status("Agent Reasoning...", expanded=True) as status:
323
  thoughts = []
324
 
@@ -346,7 +386,6 @@ def show_app():
346
 
347
  response = result["generation"]
348
 
349
- # Show debug steps comfortably (Optional redundant info, maybe keep for final stats)
350
  with st.expander("📊 Final Stats", expanded=False):
351
  st.write(f"**Reflected:** {result.get('reflection_score')} | **Total Iter:** {result.get('iterations')}")
352
 
@@ -378,7 +417,6 @@ def show_app():
378
  st.session_state.deep_summary = None
379
  st.rerun()
380
 
381
- # Podcast Tool
382
  with st.expander("🎧 Podcast", expanded=False):
383
  if not st.session_state.podcast_audio:
384
  if st.button("Generate Audio"):
@@ -399,20 +437,18 @@ def show_app():
399
  st.session_state.podcast_audio = None
400
  st.rerun()
401
 
402
- # Knowledge Graph Tool
403
  with st.expander("🕸️ Knowledge Graph", expanded=False):
404
  if not st.session_state.graph_dot:
405
  if st.button("Generate Graph"):
406
  summary_text = ensure_deep_summary()
407
  with st.spinner("Building Graph structure..."):
408
  kg_gen = KnowledgeGraphGenerator()
409
- # Generate and clean dot code
410
  raw_dot = kg_gen.generate_graph(summary_text)
411
  st.session_state.graph_dot = raw_dot
412
  st.rerun()
413
  else:
414
  st.success("Graph Ready!")
415
- if st.button("👁️ View Graph High-Res", type="primary", width='stretch'):
416
  view_graph_dialog(st.session_state.graph_dot)
417
 
418
  if st.button("🔄 Regenerate Graph"):
 
3
  import time
4
  from dotenv import load_dotenv
5
 
 
6
  load_dotenv()
7
 
8
+ from core.retriever import Retriever
9
  from core.graph import RAGAgent
10
  from core.podcast import PodcastGenerator
11
  from core.visualizer import KnowledgeGraphGenerator
12
  from core.map_reduce import MapReduceSummarizer
13
 
 
14
  st.set_page_config(
15
+ page_title="AI Knowledge Assistant",
16
  page_icon="🎓",
17
  layout="wide",
18
  initial_sidebar_state="collapsed"
19
  )
20
 
 
21
  st.markdown("""
22
  <style>
23
  .main { background-color: #f8f9fa; }
24
 
25
  /* Typography */
26
+ h1, h2, h3, h4 { font-family: 'Helvetica Neue', 'Inter', sans-serif; color: #385A7C; }
27
+ p, li { color: #424242; line-height: 1.6; }
28
+
29
+ /* Hero Section */
30
+ .hero-title {
31
+ font-size: 3.5rem;
32
+ font-weight: 800;
33
+ color: #385A7C;
34
+ text-align: center;
35
+ margin-bottom: 0.5rem;
36
+ background: -webkit-linear-gradient(#4A6D8C, #385A7C);
37
+ -webkit-background-clip: text;
38
+ -webkit-text-fill-color: transparent;
39
+ }
40
+ .hero-subtitle {
41
+ font-size: 1.5rem;
42
+ color: #607d8b;
43
+ text-align: center;
44
+ margin-bottom: 2rem;
45
+ }
46
+
47
+ /* Section Headers */
48
+ .section-header {
49
+ font-size: 1.8rem;
50
+ font-weight: 700;
51
+ color: #385A7C;
52
+ margin-top: 2rem;
53
+ margin-bottom: 1rem;
54
+ border-left: 5px solid #4A6D8C;
55
+ padding-left: 15px;
56
+ }
57
 
58
  /* Card Style */
59
  .stCard {
60
+ background-color: #ffffff;
61
  padding: 24px;
62
+ border-radius: 16px;
63
+ box-shadow: 0 8px 20px rgba(56, 90, 124, 0.05);
64
  margin-bottom: 20px;
65
+ border: 1px solid #e1e8ed;
66
+ transition: transform 0.3s ease;
67
+ }
68
+ .stCard:hover {
69
+ transform: translateY(-5px);
70
+ box-shadow: 0 12px 30px rgba(56, 90, 124, 0.1);
71
  }
72
 
73
+ /* Feature Badge */
74
+ .feature-badge {
75
+ background-color: #eef2f6;
76
+ color: #4A6D8C;
77
+ padding: 4px 12px;
78
  border-radius: 20px;
79
+ font-size: 0.8rem;
80
+ font-weight: 700;
81
+ text-transform: uppercase;
82
+ margin-bottom: 10px;
83
+ display: inline-block;
84
  }
85
 
86
+ /* Button Styling */
87
+ div.stButton > button {
88
+ border-radius: 30px !important;
89
+ padding: 10px 25px !important;
90
+ background-color: #4A6D8C !important;
91
+ color: white !important;
92
+ border: none !important;
93
+ box-shadow: 0 4px 12px rgba(74, 109, 140, 0.2) !important;
94
+ font-size: 1rem !important;
95
+ font-weight: 700 !important;
96
+ }
97
+ div.stButton > button:hover {
98
+ background-color: #385A7C !important;
99
+ color: white !important;
100
+ box-shadow: 0 6px 18px rgba(74, 109, 140, 0.3) !important;
101
+ }
102
+ /* Force white text for button labels */
103
+ div.stButton > button p {
104
+ color: white !important;
105
  }
106
  </style>
107
  """, unsafe_allow_html=True)
 
112
  if "agent" not in st.session_state:
113
  st.session_state.agent = None
114
  if "pdf_processor" not in st.session_state:
115
+ st.session_state.pdf_processor = Retriever()
116
  if "messages" not in st.session_state:
117
  st.session_state.messages = []
118
  if "full_text" not in st.session_state:
 
132
  st.session_state.page = page_name
133
  st.rerun()
134
 
 
135
  def show_home():
136
+ st.markdown("<h1 class='hero-title'>🎓 AI Knowledge Assistant</h1>", unsafe_allow_html=True)
137
+ st.markdown("<p class='hero-subtitle'>Transforming Complex Documents into Dynamic Multi-Modal Insights</p>", unsafe_allow_html=True)
 
138
 
 
139
  col_cta1, col_cta2, col_cta3 = st.columns([1, 1, 1])
140
  with col_cta2:
141
  if st.button("🚀 Launch Application", type="primary", width='stretch'):
 
143
 
144
  st.markdown("---")
145
 
 
146
  col1, col2 = st.columns(2, gap="large")
147
 
148
  with col1:
149
  st.markdown("<div class='section-header'>🌟 1. Motivation</div>", unsafe_allow_html=True)
150
  st.markdown("""
151
  <div class="stCard">
152
+ <p>In the digital age, we are overwhelmed by the sheer volume of information and complex documents.</p>
153
  <ul>
154
+ <li><b>Information Overload:</b> Time-consuming to synthesize connections across lengthy files.</li>
155
+ <li><b>Tool Limitations:</b> Basic search lacks semantics; generic LLMs may lose context.</li>
156
+ <li><b>Multi-Modal Need:</b> Integrated text, audio (podcasts), and visual structures (graphs).</li>
157
  </ul>
158
  </div>
159
  """, unsafe_allow_html=True)
 
162
  st.markdown("<div class='section-header'>❓ 2. Problem Definition</div>", unsafe_allow_html=True)
163
  st.markdown("""
164
  <div class="stCard">
165
+ <p>Difficulty in reliably distilling and structuring insights from dense, unstructured data.</p>
166
  <ul>
167
+ <li><b>Reliability:</b> Ensuring answers are grounded in the source material.</li>
168
+ <li><b>Complexity:</b> Grasping underlying structural relationships in technical content.</li>
169
+ <li><b>Accessibility:</b> Making specialized documents consumable and structured.</li>
170
  </ul>
171
  </div>
172
  """, unsafe_allow_html=True)
173
 
174
+ st.markdown("<div class='section-header'>💡 3. Versatile Multi-Agent Suite</div>", unsafe_allow_html=True)
 
175
 
176
+ c1, c2, c3, c4 = st.columns(4, gap="small")
177
 
178
  with c1:
179
  st.markdown("""
180
+ <div class="stCard" style="min-height: 240px;">
181
+ <span class="feature-badge">Conversational</span>
182
+ <h4>Reflective RAG</h4>
183
+ <p style="font-size: 0.9rem;">A <b>LangGraph</b> state machine that retrieves and self-corrects via reasoning loops for grounded Q&A.</p>
184
  </div>
185
  """, unsafe_allow_html=True)
186
+
187
  with c2:
188
  st.markdown("""
189
+ <div class="stCard" style="min-height: 240px;">
190
+ <span class="feature-badge">Synthesis</span>
191
+ <h4>Deep Summary</h4>
192
+ <p style="font-size: 0.9rem;">Utilizes <b>Map-Reduce</b> logic to distill long documents into high-density atomic facts and briefings.</p>
193
  </div>
194
  """, unsafe_allow_html=True)
195
 
196
  with c3:
197
  st.markdown("""
198
+ <div class="stCard" style="min-height: 240px;">
199
+ <span class="feature-badge">Audio</span>
200
+ <h4>AI Podcast</h4>
201
+ <p style="font-size: 0.9rem;">Transforms facts into natural multi-speaker dialogue using <b>Gemini Native Audio</b> technology.</p>
202
+ </div>
203
+ """, unsafe_allow_html=True)
204
+
205
+ with c4:
206
+ st.markdown("""
207
+ <div class="stCard" style="min-height: 240px;">
208
+ <span class="feature-badge">Visual</span>
209
+ <h4>Knowledge Graph</h4>
210
+ <p style="font-size: 0.9rem;">Maps relationships from summaries into hierarchical, interactive <b>DOT visuals</b> for structural insight.</p>
211
  </div>
212
  """, unsafe_allow_html=True)
213
 
 
214
  st.markdown("<div class='section-header'>🏗️ 4. System Architecture</div>", unsafe_allow_html=True)
215
 
216
  st.markdown("""
217
  <div class="stCard">
218
  <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; text-align: center;">
219
  <div>
220
+ <h4>🎨 Frontend</h4>
221
+ <p style="font-size: 0.85rem; color: #666;">Streamlit Dashboard<br>Responsive UI Components<br>Multi-modal Displays</p>
222
  </div>
223
  <div style="border-left: 1px solid #eee;">
224
+ <h4>🧠 Brain</h4>
225
+ <p style="font-size: 0.85rem; color: #666;">LangChain / LangGraph<br>Agentic Workflows<br>Task Orchestration</p>
226
  </div>
227
  <div style="border-left: 1px solid #eee;">
228
+ <h4>💾 Data</h4>
229
+ <p style="font-size: 0.85rem; color: #666;">ChromaDB Vector Store<br>Persistent Metadata<br>Hierarchical Retrieval</p>
230
  </div>
231
  <div style="border-left: 1px solid #eee;">
232
+ <h4>🧬 Models</h4>
233
+ <p style="font-size: 0.85rem; color: #666;">NVIDIA Nemotron-3 (Reasoning)<br>Google Embedding-001 (Vector)<br>Gemini 2.5 Flash (Audio/TTS)</p>
234
  </div>
235
  </div>
236
  </div>
237
  """, unsafe_allow_html=True)
238
 
 
239
  st.markdown("<div class='section-header'>⚙️ 5. Implementation Details</div>", unsafe_allow_html=True)
240
 
241
+ tab_rag, tab_sum, tab_others = st.tabs(["💻Reflective RAG", "📄 Smart Summary", "🛠Tools & Visuals"])
242
 
243
  with tab_rag:
244
+ st.info("**Cyclic State Machine**")
245
  st.markdown("""
246
+ - Executes a reasoning loop: **Retrieve Draft Grade Rewrite**.
247
+ - Powered by **LangGraph** to ensure answers are strictly evidence-based.
 
 
 
248
  """)
249
 
250
+ with tab_sum:
251
+ st.info("**Map-Reduce Pipeline**")
252
  st.markdown("""
253
+ - Seamlessly handles ultra-long documents by chunking and parallel summarizing.
254
+ - Provides the analytical foundation for deep-dive tools.
 
255
  """)
256
 
257
+ with tab_others:
258
+ st.info("**Multi-Modal Outputs**")
259
  st.markdown("""
260
+ - **Podcast:** Multi-speaker audio briefings using Gemini TTS.
261
+ - **Knowledge Graph:** Structural relationship mapping via DOT syntax.
 
262
  """)
263
 
264
  st.markdown("<br><br>", unsafe_allow_html=True)
 
269
 
270
  if not st.session_state.deep_summary:
271
  if st.session_state.full_text:
272
+ with st.spinner("Analyzing Document (Deep Summary)..."):
273
  mr = MapReduceSummarizer()
274
  st.session_state.deep_summary = mr.generate_deep_summary(st.session_state.full_text)
275
  return st.session_state.deep_summary
276
 
 
277
  if hasattr(st, "dialog"):
278
  dialog_decorator = st.dialog
279
  elif hasattr(st, "experimental_dialog"):
280
  dialog_decorator = st.experimental_dialog
281
  else:
 
282
  def dialog_decorator(*args, **kwargs):
283
  def decorator(func):
284
  return func
 
293
  @dialog_decorator("Knowledge Graph Visualization", width="large")
294
  def view_graph_dialog(dot_code):
295
  st.graphviz_chart(dot_code, width="stretch")
 
296
 
297
  def show_app():
298
  # Sidebar: Clean, just for upload and nav
 
320
  for f in new_files:
321
  st.session_state.processed_files.add(f.name)
322
 
 
323
  st.session_state.upload_status = f"Successfully indexed ~{total_tokens:,} tokens from {len(new_files)} new file(s)."
324
  st.session_state.uploader_key += 1
325
  st.rerun()
 
328
  st.success("Analysis Ready")
329
 
330
  if st.button("🔄 Reset / Clear All", type="primary"):
331
+ st.session_state.pdf_processor = Retriever()
332
  st.session_state.agent = None
333
  st.session_state.messages = []
334
  st.session_state.full_text = ""
 
339
  st.rerun()
340
 
341
 
 
342
  col_chat, col_tools = st.columns([3, 1.3])
343
 
344
  with col_chat:
345
  st.subheader("💬 Chat")
346
 
 
347
  for msg in st.session_state.messages:
348
  with st.chat_message(msg["role"]):
349
  if "thoughts" in msg and msg["thoughts"]:
 
352
  st.write(log)
353
  st.markdown(msg["content"])
354
 
 
355
  if prompt := st.chat_input("Ask about the document..."):
356
  st.session_state.messages.append({"role": "user", "content": prompt})
357
  with st.chat_message("user"):
 
359
 
360
  with st.chat_message("assistant"):
361
  if st.session_state.agent:
 
362
  with st.status("Agent Reasoning...", expanded=True) as status:
363
  thoughts = []
364
 
 
386
 
387
  response = result["generation"]
388
 
 
389
  with st.expander("📊 Final Stats", expanded=False):
390
  st.write(f"**Reflected:** {result.get('reflection_score')} | **Total Iter:** {result.get('iterations')}")
391
 
 
417
  st.session_state.deep_summary = None
418
  st.rerun()
419
 
 
420
  with st.expander("🎧 Podcast", expanded=False):
421
  if not st.session_state.podcast_audio:
422
  if st.button("Generate Audio"):
 
437
  st.session_state.podcast_audio = None
438
  st.rerun()
439
 
 
440
  with st.expander("🕸️ Knowledge Graph", expanded=False):
441
  if not st.session_state.graph_dot:
442
  if st.button("Generate Graph"):
443
  summary_text = ensure_deep_summary()
444
  with st.spinner("Building Graph structure..."):
445
  kg_gen = KnowledgeGraphGenerator()
 
446
  raw_dot = kg_gen.generate_graph(summary_text)
447
  st.session_state.graph_dot = raw_dot
448
  st.rerun()
449
  else:
450
  st.success("Graph Ready!")
451
+ if st.button("👁️ View Knowledge Graph", type="primary", width='stretch'):
452
  view_graph_dialog(st.session_state.graph_dot)
453
 
454
  if st.button("🔄 Regenerate Graph"):
core/__pycache__/pdf_processer.cpython-312.pyc CHANGED
Binary files a/core/__pycache__/pdf_processer.cpython-312.pyc and b/core/__pycache__/pdf_processer.cpython-312.pyc differ
 
core/__pycache__/podcast.cpython-312.pyc CHANGED
Binary files a/core/__pycache__/podcast.cpython-312.pyc and b/core/__pycache__/podcast.cpython-312.pyc differ
 
core/__pycache__/visualizer.cpython-312.pyc CHANGED
Binary files a/core/__pycache__/visualizer.cpython-312.pyc and b/core/__pycache__/visualizer.cpython-312.pyc differ
 
core/graph.py CHANGED
@@ -28,7 +28,6 @@ class RAGAgent:
28
  question = state["question"]
29
  docs = state["documents"]
30
 
31
- # Format context with explicit source numbering for citation
32
  context = "\n\n".join([f"[Document: {doc.metadata.get('filename', 'Unknown')} | Page: {doc.metadata.get('page', 0) + 2}] {doc.page_content}" for doc in docs])
33
 
34
  chain = RAG_PROMPT | self.llm | StrOutputParser()
@@ -40,7 +39,6 @@ class RAGAgent:
40
  generation = state["generation"]
41
  docs = state["documents"]
42
 
43
- # Format context so the reflector can check for grounding
44
  context = "\n\n".join([f"[Source: {doc.metadata.get('filename', 'Unknown')}] {doc.page_content}" for doc in docs])
45
 
46
  chain = REFLECTION_PROMPT | self.llm | StrOutputParser()
@@ -50,7 +48,6 @@ class RAGAgent:
50
  "generation": generation
51
  })
52
 
53
- # Normalize score
54
  normalized_score = "yes" if "yes" in score.lower() else "no"
55
  return {"reflection_score": normalized_score}
56
 
@@ -80,13 +77,12 @@ class RAGAgent:
80
  def build_graph(self):
81
  workflow = StateGraph(GraphState)
82
 
83
- # Define Nodes
84
  workflow.add_node("retrieve", self.retrieve)
85
  workflow.add_node("generate", self.generate)
86
  workflow.add_node("reflect", self.reflect)
87
  workflow.add_node("rewrite_query", self.rewrite_query)
88
 
89
- # Build Edges
90
  workflow.set_entry_point("retrieve")
91
  workflow.add_edge("retrieve", "generate")
92
  workflow.add_edge("generate", "reflect")
@@ -112,21 +108,15 @@ class RAGAgent:
112
  }
113
 
114
  final_state = inputs
115
- # Stream allowing for intermediate updates
116
  for output in self.app.stream(inputs):
117
  for key, value in output.items():
118
- # Update our tracking of final state (simplistic merge)
119
  final_state.update(value)
120
-
121
- # 'key' is the node name (e.g., 'retrieve', 'generate')
122
- # 'final_state' is the cumulative state
123
  if callback:
124
  callback(key, final_state)
125
 
126
  return final_state
127
 
128
  def get_graph_image(self, file_path: str = None):
129
- """Returns the PNG binary of the graph structure. Optionally saves to file."""
130
  img_bytes = self.app.get_graph().draw_mermaid_png()
131
  if file_path:
132
  with open(file_path, "wb") as f:
 
28
  question = state["question"]
29
  docs = state["documents"]
30
 
 
31
  context = "\n\n".join([f"[Document: {doc.metadata.get('filename', 'Unknown')} | Page: {doc.metadata.get('page', 0) + 2}] {doc.page_content}" for doc in docs])
32
 
33
  chain = RAG_PROMPT | self.llm | StrOutputParser()
 
39
  generation = state["generation"]
40
  docs = state["documents"]
41
 
 
42
  context = "\n\n".join([f"[Source: {doc.metadata.get('filename', 'Unknown')}] {doc.page_content}" for doc in docs])
43
 
44
  chain = REFLECTION_PROMPT | self.llm | StrOutputParser()
 
48
  "generation": generation
49
  })
50
 
 
51
  normalized_score = "yes" if "yes" in score.lower() else "no"
52
  return {"reflection_score": normalized_score}
53
 
 
77
  def build_graph(self):
78
  workflow = StateGraph(GraphState)
79
 
80
+
81
  workflow.add_node("retrieve", self.retrieve)
82
  workflow.add_node("generate", self.generate)
83
  workflow.add_node("reflect", self.reflect)
84
  workflow.add_node("rewrite_query", self.rewrite_query)
85
 
 
86
  workflow.set_entry_point("retrieve")
87
  workflow.add_edge("retrieve", "generate")
88
  workflow.add_edge("generate", "reflect")
 
108
  }
109
 
110
  final_state = inputs
 
111
  for output in self.app.stream(inputs):
112
  for key, value in output.items():
 
113
  final_state.update(value)
 
 
 
114
  if callback:
115
  callback(key, final_state)
116
 
117
  return final_state
118
 
119
  def get_graph_image(self, file_path: str = None):
 
120
  img_bytes = self.app.get_graph().draw_mermaid_png()
121
  if file_path:
122
  with open(file_path, "wb") as f:
core/models.py CHANGED
@@ -5,12 +5,7 @@ from langchain_google_genai import GoogleGenerativeAIEmbeddings, ChatGoogleGener
5
  from google import genai
6
 
7
  def get_llm(model_name: str = "nvidia/nemotron-3-nano-30b-a3b"):
8
- """
9
- Returns a configured ChatGoogleGenerativeAI instance.
10
- Uses st.secrets or environment variable for API Key.
11
- """
12
  api_key = os.getenv("NV_API_KEY")
13
- # Streamlit Cloud deployment support
14
  if not api_key and "NV_API_KEY" in st.secrets:
15
  api_key = st.secrets["NV_API_KEY"]
16
 
@@ -27,9 +22,6 @@ def get_llm(model_name: str = "nvidia/nemotron-3-nano-30b-a3b"):
27
  )
28
 
29
  def get_embeddings():
30
- """
31
- Returns GoogleGenerativeAIEmbeddings.
32
- """
33
  api_key = os.getenv("GOOGLE_API_KEY")
34
  if not api_key and "GOOGLE_API_KEY" in st.secrets:
35
  api_key = st.secrets["GOOGLE_API_KEY"]
@@ -43,10 +35,6 @@ from google.genai import types
43
 
44
 
45
  def generate_podcast_audio(script_text: str):
46
- """
47
- Calls Gemini TTS with multi-speaker configuration.
48
- Returns raw audio data.
49
- """
50
  api_key = os.getenv("GOOGLE_API_KEY")
51
  if not api_key and "GOOGLE_API_KEY" in st.secrets:
52
  api_key = st.secrets["GOOGLE_API_KEY"]
 
5
  from google import genai
6
 
7
  def get_llm(model_name: str = "nvidia/nemotron-3-nano-30b-a3b"):
 
 
 
 
8
  api_key = os.getenv("NV_API_KEY")
 
9
  if not api_key and "NV_API_KEY" in st.secrets:
10
  api_key = st.secrets["NV_API_KEY"]
11
 
 
22
  )
23
 
24
  def get_embeddings():
 
 
 
25
  api_key = os.getenv("GOOGLE_API_KEY")
26
  if not api_key and "GOOGLE_API_KEY" in st.secrets:
27
  api_key = st.secrets["GOOGLE_API_KEY"]
 
35
 
36
 
37
  def generate_podcast_audio(script_text: str):
 
 
 
 
38
  api_key = os.getenv("GOOGLE_API_KEY")
39
  if not api_key and "GOOGLE_API_KEY" in st.secrets:
40
  api_key = st.secrets["GOOGLE_API_KEY"]
core/podcast.py CHANGED
@@ -20,15 +20,10 @@ class PodcastGenerator:
20
  return chain.invoke({"text": briefing_text})
21
 
22
  def generate_audio_file(self, script_text):
23
- """
24
- Uses centralized Gemini TTS logic.
25
- """
26
  try:
27
  data = generate_podcast_audio(script_text)
28
 
29
  if data:
30
- # Use NamedTemporaryFile to get a unique name, then close it immediately
31
- # so wave.open can re-open it for writing.
32
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
33
  tmp_path = tmp.name
34
 
@@ -41,7 +36,6 @@ class PodcastGenerator:
41
  print(f"Gemini Official TTS failed: {e}. Falling back to gTTS.")
42
  try:
43
  from gtts import gTTS
44
- # Strip names for gTTS single voice
45
  tts_text = script_text.replace("Alex:", "").replace("Jamie:", "")
46
  tts = gTTS(tts_text, lang='en')
47
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as fp:
 
20
  return chain.invoke({"text": briefing_text})
21
 
22
  def generate_audio_file(self, script_text):
 
 
 
23
  try:
24
  data = generate_podcast_audio(script_text)
25
 
26
  if data:
 
 
27
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
28
  tmp_path = tmp.name
29
 
 
36
  print(f"Gemini Official TTS failed: {e}. Falling back to gTTS.")
37
  try:
38
  from gtts import gTTS
 
39
  tts_text = script_text.replace("Alex:", "").replace("Jamie:", "")
40
  tts = gTTS(tts_text, lang='en')
41
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as fp:
core/{pdf_processer.py → retriever.py} RENAMED
@@ -7,7 +7,7 @@ import tempfile
7
 
8
  import tiktoken
9
 
10
- class PDFProcessor:
11
  def __init__(self):
12
  self.embeddings = get_embeddings()
13
  self.vector_store = None
@@ -18,14 +18,9 @@ class PDFProcessor:
18
  return len(self.encoding.encode(text))
19
 
20
  def process_pdf(self, uploaded_files):
21
- """
22
- Reads uploaded PDF file(s), splits text, and initializes VectorStore.
23
- Returns the total token count of newly added text.
24
- """
25
  if not uploaded_files:
26
  return 0
27
 
28
- # Ensure input is a list (handle single file case just in case)
29
  if not isinstance(uploaded_files, list):
30
  uploaded_files = [uploaded_files]
31
 
@@ -33,7 +28,6 @@ class PDFProcessor:
33
  cumulative_text = ""
34
 
35
  for uploaded_file in uploaded_files:
36
- # Save uploaded file typically to temp because PyPDFLoader needs a path
37
  with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
38
  tmp_file.write(uploaded_file.read())
39
  tmp_path = tmp_file.name
@@ -61,12 +55,11 @@ class PDFProcessor:
61
  )
62
  splits = text_splitter.split_documents(new_documents)
63
 
64
- # Initialize Chroma (In-memory)
65
  if self.vector_store is None:
66
  self.vector_store = Chroma.from_documents(
67
  documents=splits,
68
  embedding=self.embeddings,
69
- collection_name="academic_papers"
70
  )
71
  else:
72
  self.vector_store.add_documents(splits)
 
7
 
8
  import tiktoken
9
 
10
+ class Retriever:
11
  def __init__(self):
12
  self.embeddings = get_embeddings()
13
  self.vector_store = None
 
18
  return len(self.encoding.encode(text))
19
 
20
  def process_pdf(self, uploaded_files):
 
 
 
 
21
  if not uploaded_files:
22
  return 0
23
 
 
24
  if not isinstance(uploaded_files, list):
25
  uploaded_files = [uploaded_files]
26
 
 
28
  cumulative_text = ""
29
 
30
  for uploaded_file in uploaded_files:
 
31
  with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
32
  tmp_file.write(uploaded_file.read())
33
  tmp_path = tmp_file.name
 
55
  )
56
  splits = text_splitter.split_documents(new_documents)
57
 
 
58
  if self.vector_store is None:
59
  self.vector_store = Chroma.from_documents(
60
  documents=splits,
61
  embedding=self.embeddings,
62
+ collection_name="knowledge_base"
63
  )
64
  else:
65
  self.vector_store.add_documents(splits)
core/visualizer.py CHANGED
@@ -1,8 +1,5 @@
1
  from core.models import get_llm
2
- from langchain_core.prompts import PromptTemplate
3
  from langchain_core.output_parsers import StrOutputParser
4
- import graphviz
5
- import re
6
  from prompts import GRAPH_PROMPT
7
 
8
  class KnowledgeGraphGenerator:
@@ -10,15 +7,12 @@ class KnowledgeGraphGenerator:
10
  self.llm = get_llm()
11
 
12
  def generate_graph(self, text):
13
- # Text is now the "Deep Summary", so no need to truncate.
14
- input_text = text
15
-
16
  chain = GRAPH_PROMPT | self.llm | StrOutputParser()
17
- dot_code = chain.invoke({"text": input_text})
18
 
19
- # Cleanup markdown if present
 
20
  dot_code = dot_code.replace("```dot", "").replace("```", "").strip()
21
  if "digraph" not in dot_code:
22
  dot_code = f'digraph G {{ {dot_code} }}'
23
-
24
  return dot_code
 
1
  from core.models import get_llm
 
2
  from langchain_core.output_parsers import StrOutputParser
 
 
3
  from prompts import GRAPH_PROMPT
4
 
5
  class KnowledgeGraphGenerator:
 
7
  self.llm = get_llm()
8
 
9
  def generate_graph(self, text):
 
 
 
10
  chain = GRAPH_PROMPT | self.llm | StrOutputParser()
 
11
 
12
+ dot_code = chain.invoke({"text": text})
13
+
14
  dot_code = dot_code.replace("```dot", "").replace("```", "").strip()
15
  if "digraph" not in dot_code:
16
  dot_code = f'digraph G {{ {dot_code} }}'
17
+
18
  return dot_code
prompts.py CHANGED
@@ -1,11 +1,11 @@
1
  from langchain_core.prompts import ChatPromptTemplate
2
 
3
  # RAG Generation Prompt
4
- RAG_SYSTEM = """You are a research assistant. Answer the user's question based strictly on the provided context.
5
- If the context does not contain the answer, say "I cannot answer this based on the document."
6
 
7
  Requirements:
8
- 1. Use academic tone.
9
  2. **In-text Citations:** Use Unicode Superscript Numbers (¹, ², ³, ⁴, ⁵, ⁶, ⁷, ⁸, ⁹, ¹⁰) strictly. Place them immediately after the punctuation or relevant phrase.
10
  - Do NOT use `[^1]` (Markdown footnotes) or `[1]` (Brackets).
11
  - Example: ...at compile time¹.
@@ -135,7 +135,7 @@ PODCAST_AUDIO_PROMPT = ChatPromptTemplate.from_messages([
135
 
136
 
137
  # Knowledge Graph Prompt
138
- GRAPH_SYSTEM = """You are an expert at visualizing complex knowledge information.
139
  Your goal is to extract a DEEP hierarchical structure and key relationships from the provided text and represent them as a CLEAN, multi-level Knowledge Graph using DOT syntax.
140
 
141
  CRITICAL INSTRUCTIONS:
 
1
  from langchain_core.prompts import ChatPromptTemplate
2
 
3
  # RAG Generation Prompt
4
+ RAG_SYSTEM = """You are a professional research and learning assistant. Answer the user's question based strictly on the provided context.
5
+ If the context does not contain the answer, say "I cannot answer this based on the provided material."
6
 
7
  Requirements:
8
+ 1. Use a professional and objective tone.
9
  2. **In-text Citations:** Use Unicode Superscript Numbers (¹, ², ³, ⁴, ⁵, ⁶, ⁷, ⁸, ⁹, ¹⁰) strictly. Place them immediately after the punctuation or relevant phrase.
10
  - Do NOT use `[^1]` (Markdown footnotes) or `[1]` (Brackets).
11
  - Example: ...at compile time¹.
 
135
 
136
 
137
  # Knowledge Graph Prompt
138
+ GRAPH_SYSTEM = """You are an expert at visualizing complex information.
139
  Your goal is to extract a DEEP hierarchical structure and key relationships from the provided text and represent them as a CLEAN, multi-level Knowledge Graph using DOT syntax.
140
 
141
  CRITICAL INSTRUCTIONS: