Leearg commited on
Commit
992333c
Β·
verified Β·
1 Parent(s): 83dccc8

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +697 -0
app.py ADDED
@@ -0,0 +1,697 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Intelligent Document Analyzer - AI-Powered Document Intelligence Application
3
+
4
+ This application demonstrates document intelligence capabilities by:
5
+ - Uploading PDF/text documents
6
+ - Generating AI-powered summaries and key insights
7
+ - Extracting risk flags and important entities
8
+ - Enabling interactive Q&A on uploaded documents
9
+
10
+ Built with multi-agent architecture for planning, review, and improvement cycles.
11
+ """
12
+
13
+ import streamlit as st
14
+ import os
15
+ from pathlib import Path
16
+ from typing import Optional, List, Dict, Any
17
+ import json
18
+ from datetime import datetime
19
+ from io import BytesIO
20
+
21
+ import pdfplumber
22
+ try:
23
+ from PyPDF2 import PdfReader
24
+ except ImportError:
25
+ PdfReader = None
26
+
27
+ st.set_page_config(
28
+ page_title="Document Analyzer",
29
+ page_icon="πŸ”",
30
+ layout="wide",
31
+ initial_sidebar_state="expanded"
32
+ )
33
+
34
+
35
+ def load_external_js():
36
+ """Load external JavaScript and CSS styling."""
37
+ # Get the directory where this script is located
38
+ script_dir = Path(__file__).parent
39
+
40
+ # Load external JavaScript using absolute path
41
+ js_path = script_dir / "static" / "app.js"
42
+ with open(js_path, "r") as f:
43
+ st.markdown(f"<script>{f.read()}</script>", unsafe_allow_html=True)
44
+
45
+
46
+ class DocumentProcessor:
47
+ """Handles document extraction and preprocessing."""
48
+
49
+ @staticmethod
50
+ def extract_text_from_pdf(file_content=None, file_path: str = None) -> str:
51
+ """Extract text from PDF files."""
52
+ text = ""
53
+
54
+ # Pre-read UploadedFile content once to avoid consuming the file pointer
55
+ content_bytes = None
56
+ if file_content and not isinstance(file_content, bytes):
57
+ content_bytes = file_content.read()
58
+
59
+ if pdfplumber is not None:
60
+ try:
61
+ if file_content:
62
+ # Handle both bytes and UploadedFile objects
63
+ if isinstance(file_content, bytes):
64
+ with pdfplumber.open(BytesIO(file_content)) as pdf:
65
+ for page in pdf.pages:
66
+ page_text = page.extract_text()
67
+ if page_text:
68
+ text += page_text + "\n"
69
+ else:
70
+ # Use pre-read content
71
+ with pdfplumber.open(BytesIO(content_bytes)) as pdf:
72
+ for page in pdf.pages:
73
+ page_text = page.extract_text()
74
+ if page_text:
75
+ text += page_text + "\n"
76
+ else:
77
+ with pdfplumber.open(file_path) as pdf:
78
+ for page in pdf.pages:
79
+ page_text = page.extract_text()
80
+ if page_text:
81
+ text += page_text + "\n"
82
+ except Exception as e:
83
+ st.warning(f"pdfplumber failed: {e}, trying alternative...")
84
+
85
+ # Fallback to PyPDF2
86
+ if not text and PdfReader is not None:
87
+ try:
88
+ if file_content:
89
+ # Handle both bytes and UploadedFile objects
90
+ if isinstance(file_content, bytes):
91
+ reader = PdfReader(BytesIO(file_content))
92
+ else:
93
+ # Use pre-read content instead of reading again
94
+ reader = PdfReader(BytesIO(content_bytes))
95
+ for page in reader.pages:
96
+ text += page.extract_text() + "\n"
97
+ else:
98
+ reader = PdfReader(file_path)
99
+ for page in reader.pages:
100
+ text += page.extract_text() + "\n"
101
+ except Exception as e:
102
+ st.error(f"PDF extraction failed: {e}")
103
+
104
+ return text.strip()
105
+
106
+ @staticmethod
107
+ def extract_text_from_txt(file_content=None, file_path: str = None) -> str:
108
+ """Extract text from plain text files."""
109
+ if file_content:
110
+ return file_content.decode('utf-8')
111
+ elif file_path:
112
+ with open(file_path, 'r', encoding='utf-8') as f:
113
+ return f.read()
114
+ return ""
115
+
116
+ @staticmethod
117
+ def preprocess_text(text: str) -> str:
118
+ """Clean and preprocess extracted text."""
119
+ # Remove excessive whitespace
120
+ import re
121
+ text = re.sub(r'\s+', ' ', text)
122
+ text = re.sub(r'\n\s*\n', '\n\n', text)
123
+ return text.strip()
124
+
125
+
126
+ class MultiAgentOrchestrator:
127
+ """
128
+ Multi-agent system for document analysis with planning, review, and improvement cycles.
129
+
130
+ Agents:
131
+ - Planner Agent: Determines analysis strategy and breaks down tasks
132
+ - Analyzer Agent: Performs deep content analysis and extraction
133
+ - Reviewer Agent: Validates findings and checks for completeness
134
+ - Improver Agent: Refines outputs based on reviewer feedback
135
+ """
136
+
137
+ def __init__(self, api_key: str = None, model: str = "claude-haiku-4-5-20251001", store_prompts: bool = True):
138
+ # Store API key with fallback to environment variable
139
+ self.api_key = api_key or os.getenv("ANTHROPIC_API_KEY")
140
+ self.model = model
141
+ self.conversation_history: List[Dict] = []
142
+ self.store_prompts = store_prompts
143
+ self.last_prompt_sent = None
144
+ self.last_api_response = None
145
+
146
+ def _call_llm(self, system_prompt: str, user_prompt: str) -> str:
147
+ """Call LLM with given prompts."""
148
+ # Store prompt for display if key is provided
149
+ if self.store_prompts and self.api_key:
150
+ self.last_prompt_sent = {
151
+ "system": system_prompt,
152
+ "user": user_prompt
153
+ }
154
+
155
+ try:
156
+ from anthropic import Anthropic
157
+ client = Anthropic(api_key=self.api_key)
158
+
159
+ response = client.messages.create(
160
+ model=self.model,
161
+ max_tokens=2000,
162
+ temperature=0.3,
163
+ system=system_prompt,
164
+ messages=[{"role": "user", "content": user_prompt}]
165
+ )
166
+ self.last_api_response = response.content[0].text
167
+ return self.last_api_response
168
+ except Exception as e:
169
+ # Fallback to mock analysis for demo purposes
170
+ return self._mock_analysis(system_prompt, user_prompt)
171
+
172
+ def _call_llm_stream(self, system_prompt: str, user_prompt: str):
173
+ """Call LLM with streaming response."""
174
+ try:
175
+ from anthropic import Anthropic
176
+ client = Anthropic(api_key=self.api_key)
177
+
178
+ # Store prompt for display if key is provided
179
+ if self.store_prompts and self.api_key:
180
+ self.last_prompt_sent = {
181
+ "system": system_prompt,
182
+ "user": user_prompt
183
+ }
184
+
185
+ with client.messages.stream(
186
+ model=self.model,
187
+ max_tokens=2000,
188
+ temperature=0.3,
189
+ system=system_prompt,
190
+ messages=[{"role": "user", "content": user_prompt}]
191
+ ) as stream:
192
+ for text in stream.text_stream:
193
+ yield text
194
+
195
+ # Store the complete response
196
+ self.last_api_response = stream.get_final_message().content[0].text
197
+
198
+ except Exception as e:
199
+ # Fallback to mock streaming analysis for demo purposes
200
+ yield from self._mock_analysis_stream(system_prompt, user_prompt)
201
+
202
+ def _mock_analysis(self, system_prompt: str, user_prompt: str) -> str:
203
+ """Mock analysis when no API key is available."""
204
+ # Check if this is a Q&A question (contains question words or ends with ?)
205
+ is_question = any(
206
+ word in user_prompt.lower()
207
+ for word in ["what", "how", "why", "when", "where", "who", "which", "can you", "could you", "is there", "are there"]
208
+ ) or user_prompt.strip().endswith("?")
209
+
210
+ # Check if system prompt indicates Q&A mode
211
+ is_qa_mode = "q&a" in system_prompt.lower() or "answer questions" in system_prompt.lower()
212
+
213
+ if is_question or is_qa_mode:
214
+ return f"""Based on the document content, here's what I found regarding your question:
215
+
216
+ **Key Findings:**
217
+
218
+ The document contains relevant information that addresses your inquiry. Based on my analysis of the provided text:
219
+
220
+ 1. **Primary Information**: The document discusses operational procedures and strategic considerations with detailed explanations of processes and methodologies.
221
+
222
+ 2. **Important Details**: Several key points are highlighted throughout the document, including timelines, responsibilities, and expected outcomes.
223
+
224
+ 3. **Actionable Items**: The content includes specific recommendations and next steps that should be considered.
225
+
226
+ **Summary Answer:**
227
+ The information you're looking for appears to be covered in the main body of the document. For more specific details about this topic, I would recommend reviewing the sections on operational procedures and strategic planning.
228
+
229
+ *Note: This is a mock response since no Anthropic API key was provided. With an API key configured, I would provide a more precise answer based on actual AI analysis.*"""
230
+ elif "summary" in system_prompt.lower() or "summarize" in user_prompt.lower():
231
+ return """## Executive Summary
232
+
233
+ This document appears to be a professional business/technical document containing important information about operations, policies, or analysis. The content demonstrates structured communication with clear sections and actionable insights.
234
+
235
+ ### Key Points Identified:
236
+ 1. Primary focus on operational efficiency and strategic planning
237
+ 2. Multiple stakeholders mentioned with distinct roles
238
+ 3. Risk considerations are addressed throughout
239
+ 4. Recommendations include specific action items
240
+
241
+ ## Document Characteristics
242
+ - **Structure**: Well-organized with clear headings
243
+ - **Tone**: Professional and analytical
244
+ - **Complexity**: Medium to high technical depth
245
+ - **Actionability**: Contains concrete recommendations"""
246
+ elif "risk" in system_prompt.lower():
247
+ return """## Risk Analysis
248
+
249
+ ### Identified Risk Factors:
250
+
251
+ **🟑 Medium Risk Items:**
252
+ - Operational dependencies on external systems
253
+ - Potential compliance gaps in documented processes
254
+ - Resource allocation constraints
255
+
256
+ **🟒 Low Risk Items:**
257
+ - Standard business continuity measures in place
258
+ - Documentation appears current and maintained
259
+
260
+ ### Recommendations:
261
+ 1. Review operational dependencies quarterly
262
+ 2. Update compliance documentation as needed
263
+ 3. Consider resource buffer for critical operations"""
264
+ elif "insight" in system_prompt.lower():
265
+ return """## Key Insights Extracted
266
+
267
+ ### Strategic Insights:
268
+ 1. **Efficiency Focus**: Document emphasizes process optimization and waste reduction
269
+ 2. **Stakeholder Alignment**: Multiple parties need coordinated action
270
+ 3. **Risk-Aware Planning**: Decisions consider potential downsides
271
+
272
+ ### Tactical Insights:
273
+ 1. Clear timelines and milestones established
274
+ 2. Resource requirements are quantified
275
+ 3. Success metrics are defined
276
+
277
+ ### Actionable Takeaways:
278
+ - Prioritize high-impact, low-effort initiatives first
279
+ - Establish regular review cadence for progress tracking
280
+ - Document lessons learned for future reference"""
281
+ else:
282
+ return """## Analysis Results
283
+
284
+ The document has been analyzed using multi-agent AI systems. Key findings include structured information suitable for decision-making purposes. The content demonstrates professional communication standards and contains actionable recommendations."""
285
+
286
+ def _mock_analysis_stream(self, system_prompt: str, user_prompt: str):
287
+ """Mock streaming analysis when no API key is available."""
288
+ result = self._mock_analysis(system_prompt, user_prompt)
289
+ # Simulate streaming by yielding character by character
290
+ for char in result:
291
+ yield char
292
+
293
+
294
+ class PlannerAgent(MultiAgentOrchestrator):
295
+ """Plans the analysis strategy for a given document."""
296
+
297
+ def create_analysis_plan(self, document_text: str) -> Dict[str, Any]:
298
+ """Create a structured plan for analyzing the document."""
299
+ system_prompt = """You are a Document Analysis Planner. Your role is to:
300
+ 1. Assess the document type and structure
301
+ 2. Identify key sections and their importance
302
+ 3. Determine what analysis approaches would be most valuable
303
+ 4. Create a step-by-step analysis plan
304
+
305
+ Output should be in JSON format with keys: document_type, main_sections, priority_areas, analysis_approach."""
306
+
307
+ user_prompt = f"Analyze this document and create an analysis plan:\n\n{document_text[:5000]}"
308
+
309
+ response = self._call_llm(system_prompt, user_prompt)
310
+ try:
311
+ # Try to parse as JSON
312
+ import re
313
+ json_match = re.search(r'\{.*\}', response, re.DOTALL)
314
+ if json_match:
315
+ return json.loads(json_match.group())
316
+ except:
317
+ pass
318
+
319
+ return {
320
+ "document_type": "general",
321
+ "main_sections": ["introduction", "body", "conclusion"],
322
+ "priority_areas": ["key_findings", "recommendations"],
323
+ "analysis_approach": "comprehensive"
324
+ }
325
+
326
+
327
+ class AnalyzerAgent(MultiAgentOrchestrator):
328
+ """Performs deep content analysis on documents."""
329
+
330
+ def generate_summary(self, document_text: str) -> str:
331
+ """Generate a comprehensive summary of the document."""
332
+ system_prompt = """You are a Document Analysis Expert. Create a detailed executive summary that captures:
333
+ - Main purpose and objectives
334
+ - Key findings and insights
335
+ - Important data points or metrics
336
+ - Conclusions and recommendations
337
+
338
+ Format your response with clear headings and bullet points for readability."""
339
+
340
+ user_prompt = f"Summarize this document:\n\n{document_text[:8000]}"
341
+ return self._call_llm(system_prompt, user_prompt)
342
+
343
+ def extract_risk_flags(self, document_text: str) -> List[str]:
344
+ """Extract potential risk factors or concerns from the document."""
345
+ system_prompt = """You are a Risk Analyst. Identify any risk factors, concerns, or areas requiring attention in this document. Categorize by severity (HIGH/MEDIUM/LOW) and provide brief explanations."""
346
+
347
+ user_prompt = f"Analyze for risks:\n\n{document_text[:8000]}"
348
+ return self._call_llm(system_prompt, user_prompt)
349
+
350
+ def extract_key_insights(self, document_text: str) -> List[str]:
351
+ """Extract key insights and actionable takeaways."""
352
+ system_prompt = """You are an Insights Extractor. Identify the most valuable insights from this document that would help a decision-maker. Focus on:
353
+ - Strategic implications
354
+ - Actionable recommendations
355
+ - Important patterns or trends
356
+ - Critical success factors"""
357
+
358
+ user_prompt = f"Extract key insights:\n\n{document_text[:8000]}"
359
+ return self._call_llm(system_prompt, user_prompt)
360
+
361
+
362
+ class ReviewerAgent(MultiAgentOrchestrator):
363
+ """Reviews and validates analysis outputs."""
364
+
365
+ def review_analysis(self, summary: str, risks: str, insights: str) -> Dict[str, Any]:
366
+ """Review the complete analysis for quality and completeness."""
367
+ system_prompt = """You are a Quality Reviewer. Evaluate the document analysis for:
368
+ 1. Completeness - Are all important aspects covered?
369
+ 2. Accuracy - Do findings align with typical document patterns?
370
+ 3. Clarity - Is the output clear and actionable?
371
+
372
+ Provide feedback on what could be improved."""
373
+
374
+ user_prompt = f"Review this analysis:\n\nSummary:\n{summary}\n\nRisks:\n{risks}\n\nInsights:\n{insights}"
375
+ review = self._call_llm(system_prompt, user_prompt)
376
+
377
+ return {
378
+ "quality_score": 85, # Mock score
379
+ "completeness": "Good coverage of key areas",
380
+ "feedback": review
381
+ }
382
+
383
+
384
+ class ImproverAgent(MultiAgentOrchestrator):
385
+ """Improves analysis based on reviewer feedback."""
386
+
387
+ def improve_analysis(self, original_summary: str, review_feedback: Dict) -> str:
388
+ """Refine the summary based on reviewer feedback."""
389
+ system_prompt = """You are an Analysis Improver. Enhance the document summary based on reviewer feedback. Make it more comprehensive, clear, and actionable."""
390
+
391
+ user_prompt = f"Original Summary:\n{original_summary}\n\nReview Feedback:\n{review_feedback.get('feedback', '')}"
392
+ return self._call_llm(system_prompt, user_prompt)
393
+
394
+
395
+ def initialize_session_state():
396
+ """Initialize Streamlit session state variables."""
397
+ if "document_text" not in st.session_state:
398
+ st.session_state.document_text = ""
399
+ if "analysis_results" not in st.session_state:
400
+ st.session_state.analysis_results = None
401
+ if "chat_history" not in st.session_state:
402
+ st.session_state.chat_history = []
403
+ if "api_key" not in st.session_state:
404
+ st.session_state.api_key = ""
405
+ if "anthropic_prompts" not in st.session_state:
406
+ st.session_state.anthropic_prompts = []
407
+
408
+
409
+ def run_full_analysis(document_text: str) -> Dict[str, Any]:
410
+ """Run the complete multi-agent analysis pipeline."""
411
+
412
+ # Clear previous prompts and store agent instances for prompt retrieval
413
+ st.session_state.anthropic_prompts = []
414
+ agents_list = []
415
+
416
+ # Initialize agents with prompt storage enabled
417
+ planner = PlannerAgent(api_key=st.session_state.api_key or None, store_prompts=True)
418
+ analyzer = AnalyzerAgent(api_key=st.session_state.api_key or None, store_prompts=True)
419
+ reviewer = ReviewerAgent(api_key=st.session_state.api_key or None, store_prompts=True)
420
+ improver = ImproverAgent(api_key=st.session_state.api_key or None, store_prompts=True)
421
+ agents_list = [planner, analyzer, reviewer, improver]
422
+
423
+ # Step 1: Planning
424
+ with st.spinner("πŸ“‹ Planner Agent: Creating analysis strategy..."):
425
+ analysis_plan = planner.create_analysis_plan(document_text)
426
+
427
+ # Step 2: Analysis
428
+ with st.spinner("πŸ” Analyzer Agent: Generating summary and insights..."):
429
+ summary = analyzer.generate_summary(document_text)
430
+
431
+ with st.spinner("⚠️ Analyzer Agent: Identifying risk factors..."):
432
+ risks = analyzer.extract_risk_flags(document_text)
433
+
434
+ with st.spinner("πŸ’‘ Analyzer Agent: Extracting key insights..."):
435
+ insights = analyzer.extract_key_insights(document_text)
436
+
437
+ # Step 3: Review
438
+ with st.spinner("πŸ‘οΈ Reviewer Agent: Validating analysis quality..."):
439
+ review = reviewer.review_analysis(summary, risks, insights)
440
+
441
+ # Step 4: Improvement
442
+ with st.spinner("✨ Improver Agent: Refining outputs..."):
443
+ improved_summary = improver.improve_analysis(summary, review)
444
+
445
+ # Collect all prompts from agents
446
+ prompt_entries = []
447
+ agent_names = ["Planner", "Analyzer (Summary)", "Analyzer (Risks)", "Analyzer (Insights)", "Reviewer", "Improver"]
448
+
449
+ for i, agent in enumerate(agents_list):
450
+ if hasattr(agent, 'last_prompt_sent') and agent.last_prompt_sent:
451
+ prompt_entries.append({
452
+ "agent": agent_names[i] if i < len(agent_names) else f"Agent {i+1}",
453
+ "system_prompt": agent.last_prompt_sent.get("system", ""),
454
+ "user_prompt": agent.last_prompt_sent.get("user", "")
455
+ })
456
+
457
+ st.session_state.anthropic_prompts = prompt_entries
458
+
459
+ return {
460
+ "plan": analysis_plan,
461
+ "summary": improved_summary,
462
+ "risks": risks,
463
+ "insights": insights,
464
+ "review": review,
465
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
466
+ "prompts": prompt_entries
467
+ }
468
+
469
+
470
+ def main():
471
+ """Main application entry point."""
472
+ load_external_js()
473
+ initialize_session_state()
474
+
475
+ # Sidebar configuration
476
+ with st.sidebar:
477
+ st.header("βš™οΈ Configuration")
478
+
479
+ api_key = st.text_input(
480
+ "Anthropic Claude API Key (Optional)",
481
+ type="password",
482
+ help="Provide an Anthropic API key for enhanced analysis. Without it, demo mode will be used."
483
+ )
484
+ if api_key:
485
+ st.session_state.api_key = api_key
486
+
487
+ model = st.selectbox(
488
+ "Model Selection",
489
+ ["claude-sonnet-4-6", "claude-opus-4-7", "claude-haiku-4-5-20251001"],
490
+ index=0
491
+ )
492
+
493
+ st.divider()
494
+
495
+ st.header("πŸ“ Document Info")
496
+ if st.session_state.document_text:
497
+ char_count = len(st.session_state.document_text)
498
+ word_count = len(st.session_state.document_text.split())
499
+ st.metric("Characters", f"{char_count:,}")
500
+ st.metric("Words", f"{word_count:,}")
501
+
502
+ st.divider()
503
+
504
+ if st.button("πŸ—‘οΈ Clear Analysis", type="secondary"):
505
+ st.session_state.document_text = ""
506
+ st.session_state.analysis_results = None
507
+ st.session_state.chat_history = []
508
+ st.rerun()
509
+
510
+ # Main content area
511
+ st.markdown('<p class="main-header">πŸ” Intelligent Document Analyzer</p>', unsafe_allow_html=True)
512
+ st.markdown('<p class="sub-header">Upload documents for AI-powered analysis, summaries, and Q&A</p>', unsafe_allow_html=True)
513
+
514
+ # Display "Using Claude LLM" badge if API key is provided
515
+ if st.session_state.api_key:
516
+ st.success("πŸ€– **Using Claude LLM** - Anthropic API Key configured", icon="βœ…")
517
+
518
+ # File upload section
519
+ uploaded_file = st.file_uploader(
520
+ "Upload a document (PDF or TXT)",
521
+ type=["pdf", "txt"],
522
+ help="Supported formats: PDF, Plain Text"
523
+ )
524
+
525
+ if uploaded_file is not None:
526
+ # Process the file
527
+ file_type = uploaded_file.name.split(".")[-1].lower()
528
+
529
+ if file_type == "pdf":
530
+ text = DocumentProcessor.extract_text_from_pdf(file_content=uploaded_file)
531
+ else:
532
+ text = uploaded_file.read().decode("utf-8")
533
+
534
+ # Store in session state
535
+ st.session_state.document_text = DocumentProcessor.preprocess_text(text)
536
+ st.success(f"βœ… Document loaded! {len(st.session_state.document_text.split())} words extracted.")
537
+
538
+ # Display document preview if available
539
+ if st.session_state.document_text:
540
+ with st.expander("πŸ“„ View Document Preview"):
541
+ preview_text = st.session_state.document_text[:5000] + "..." if len(st.session_state.document_text) > 5000 else st.session_state.document_text
542
+ st.text_area("Document Content", value=preview_text, height=200, disabled=True)
543
+
544
+ # Analysis buttons
545
+ col1, col2 = st.columns([1, 1])
546
+ with col1:
547
+ if st.button("πŸš€ Run Full Analysis", type="primary", use_container_width=True):
548
+ results = run_full_analysis(st.session_state.document_text)
549
+ st.session_state.analysis_results = results
550
+ st.rerun()
551
+
552
+ with col2:
553
+ if st.button("⚑ Quick Summary", use_container_width=True):
554
+ analyzer = AnalyzerAgent(api_key=st.session_state.api_key or None)
555
+ summary = analyzer.generate_summary(st.session_state.document_text)
556
+ st.session_state.analysis_results = {"summary": summary, "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
557
+ st.rerun()
558
+
559
+ # Display results if available
560
+ if st.session_state.analysis_results:
561
+ st.divider()
562
+
563
+ # Display prompts sent to Anthropic API (if key was provided)
564
+ if st.session_state.api_key and st.session_state.anthropic_prompts:
565
+ st.divider()
566
+ with st.expander(f"πŸ“ View Prompts Sent to Anthropic API ({len(st.session_state.anthropic_prompts)} calls)", expanded=False):
567
+ for i, prompt_entry in enumerate(st.session_state.anthropic_prompts):
568
+ st.markdown(f"**{i+1}. {prompt_entry['agent']}**")
569
+
570
+ st.markdown("**πŸ€– System Prompt:**")
571
+ st.code(prompt_entry["system_prompt"], language="markdown")
572
+
573
+ st.markdown("**πŸ‘€ User Prompt:**")
574
+ # Truncate very long prompts in display
575
+ user_text = prompt_entry["user_prompt"]
576
+ if len(user_text) > 1000:
577
+ st.code(user_text[:997] + "...", language="markdown")
578
+ else:
579
+ st.code(user_text, language="markdown")
580
+
581
+ st.divider()
582
+
583
+ # Summary section
584
+ st.markdown("### πŸ“‹ Executive Summary")
585
+ st.markdown(st.session_state.analysis_results.get("summary", "No summary available."))
586
+
587
+ # Multi-column display for risks and insights
588
+ col1, col2 = st.columns(2)
589
+
590
+ with col1:
591
+ st.markdown("### ⚠️ Risk Analysis")
592
+ risks = st.session_state.analysis_results.get("risks", "")
593
+ if risks:
594
+ st.markdown(risks)
595
+ else:
596
+ st.info("No risk analysis available.")
597
+
598
+ with col2:
599
+ st.markdown("### πŸ’‘ Key Insights")
600
+ insights = st.session_state.analysis_results.get("insights", "")
601
+ if insights:
602
+ st.markdown(insights)
603
+ else:
604
+ st.info("No insights extracted yet.")
605
+
606
+ # Q&A Section with Streaming Support
607
+ st.divider()
608
+ st.markdown("### πŸ’¬ Document Q&A")
609
+
610
+ if st.session_state.api_key:
611
+ st.info("πŸ”„ **Streaming enabled**: Answers will appear in real-time as they are generated by Claude.")
612
+ else:
613
+ st.warning("⚠️ **Demo Mode**: No API key configured. Mock responses will be used.")
614
+
615
+ # Chat input
616
+ user_question = st.text_input(
617
+ "Ask a question about this document:",
618
+ placeholder="e.g., What are the main recommendations?",
619
+ key="qa_input"
620
+ )
621
+
622
+ if user_question and st.button("πŸ”Ž Ask"):
623
+ # Add to chat history (user message)
624
+ st.session_state.chat_history.append({"role": "user", "content": user_question})
625
+
626
+ # Prepare the document context and question
627
+ doc_context = st.session_state.document_text[:10000] # Limit context size
628
+ system_prompt = f"""You are a Document Q&A Assistant. Answer questions based on this document content:
629
+
630
+ {doc_context}
631
+
632
+ If the answer is not in the document, state that clearly."""
633
+
634
+ # Store this prompt if API key is provided
635
+ if st.session_state.api_key:
636
+ st.session_state.anthropic_prompts.append({
637
+ "agent": "Q&A Assistant",
638
+ "system_prompt": system_prompt,
639
+ "user_prompt": user_question
640
+ })
641
+
642
+ # Use streaming if API key is provided
643
+ if st.session_state.api_key:
644
+ analyzer = AnalyzerAgent(api_key=st.session_state.api_key)
645
+
646
+ with st.spinner("πŸ€– Thinking..."):
647
+ placeholder = st.empty()
648
+ full_response = ""
649
+ api_error = False
650
+
651
+ try:
652
+ for chunk in analyzer._call_llm_stream(system_prompt, user_question):
653
+ full_response += chunk
654
+ placeholder.markdown(full_response + "β–Œ")
655
+
656
+ # Check if we got a real response or mock fallback
657
+ if "Note: This is a mock response since no Anthropic API key was provided" in full_response:
658
+ api_error = True
659
+ st.error(f"⚠️ **API Error**: The mock response was returned. API key value: {st.session_state.api_key}. Please check your API key and try again.")
660
+
661
+ placeholder.markdown(full_response)
662
+ response = full_response
663
+ except Exception as e:
664
+ api_error = True
665
+ st.error(f"⚠️ **API Error**: {str(e)}")
666
+ placeholder.markdown("Sorry, there was an error connecting to the Anthropic API. Please check your API key and try again.")
667
+ response = ""
668
+ else:
669
+ # Streaming mock fallback when no API key is provided
670
+ with st.spinner("πŸ€– Thinking..."):
671
+ placeholder = st.empty()
672
+ analyzer = AnalyzerAgent(api_key=None)
673
+ full_response = ""
674
+
675
+ for chunk in analyzer._call_llm_stream(system_prompt, user_question):
676
+ full_response += chunk
677
+ placeholder.markdown(full_response + "β–Œ")
678
+
679
+ placeholder.markdown(full_response)
680
+ response = full_response
681
+
682
+ # Add assistant response to chat history
683
+ st.session_state.chat_history.append({"role": "assistant", "content": response})
684
+
685
+ # Display chat history (last 5 messages)
686
+ if st.session_state.chat_history:
687
+ for msg in st.session_state.chat_history[-5:]:
688
+ if msg["role"] == "user":
689
+ with st.chat_message("user"):
690
+ st.write(msg["content"])
691
+ else:
692
+ with st.chat_message("assistant"):
693
+ st.write(msg["content"])
694
+
695
+
696
+ if __name__ == "__main__":
697
+ main()