Francisco Zanartu commited on
Commit
d7ffc69
·
1 Parent(s): 8aae565

feat: refactor document analysis and enhance Gradio interface for misinformation detection

Browse files
Files changed (1) hide show
  1. main.py +119 -119
main.py CHANGED
@@ -4,14 +4,26 @@ This is the minimal version for quick prototyping.
4
  """
5
 
6
  import logging
7
- import pprint
8
  from pathlib import Path
9
  import gradio as gr
10
- from src.utils.parser import MarkdownConverter
 
 
11
  from src.utils.chunking import get_base_chunks
 
 
 
 
 
 
12
  from src.api.apis import classify_text
13
  from src.api.rebuttal import RebuttalStructure
14
 
 
 
 
 
 
15
  logging.basicConfig(
16
  format="%(asctime)s %(levelname)s %(message)s",
17
  datefmt="%m/%d/%Y %I:%M:%S %p",
@@ -20,125 +32,113 @@ logging.basicConfig(
20
  logger = logging.getLogger(__name__)
21
 
22
 
23
- def analyze_document(pdf_file, url_text):
24
- """
25
- Simple analysis function that processes PDF or URL input.
26
-
27
- Returns formatted HTML with highlighted misinformation and rebuttals.
28
- """
29
- # Step 1: Convert to markdown
30
- md = MarkdownConverter()
31
-
32
- if pdf_file is not None:
33
- logger.info(f"📄 Processing PDF file: {pdf_file}")
34
- document = md.run(pdf_file)
35
- source = Path(pdf_file).name
36
- elif url_text and url_text.strip():
37
- logger.info(f"🔗 Processing URL: {url_text}")
38
- document = md.run(url_text)
39
- source = url_text
40
- else:
41
- return "<p style='color: red;'>❌ Please provide a PDF or URL</p>"
42
-
43
- # Step 2: Chunk the document
44
- chunks = get_base_chunks(document, chunk_size=1000, chunk_overlap=200)
45
- logger.info(f"✅ Created {len(chunks)} chunks")
46
- logger.info(f"✅ First chunk: {pprint.pformat(chunks[0], indent=4)}")
47
-
48
- # Step 3: Classify chunks
49
- # Handle both Document objects and dicts
50
- responses = []
51
- for chunk in chunks:
52
- # Extract text from Document object or dict
53
- if hasattr(chunk, "page_content"):
54
- # LangChain Document object
55
- chunk_text = chunk.page_content
56
- elif isinstance(chunk, dict):
57
- # Dictionary format
58
- chunk_text = chunk.get("metadata", {}).get("chunk") or chunk.get("text", "")
59
- else:
60
- # Fallback: try to convert to string
61
- chunk_text = str(chunk)
62
-
63
- if chunk_text:
64
- responses.append(classify_text(chunk_text))
65
-
66
- # Step 4: Keep only positive misinformation detections
67
- positive_responses = [r for r in responses if r.category != "0"]
68
-
69
- # If no misinformation found
70
- if not positive_responses:
71
- return f"""
72
- <div style='padding: 30px; background: #d4edda; border-radius: 8px; text-align: center;'>
73
- <h2>✅ No Misinformation Detected</h2>
74
- <p>Source: {source}</p>
75
- </div>
76
- """
77
-
78
- # Step 5: Generate rebuttals
79
  rebuttal_gen = RebuttalStructure()
80
- rebuttals = [rebuttal_gen.run(misinfo) for misinfo in positive_responses]
81
-
82
- # Step 6: Build output HTML
83
- output = f"""
84
- <div style='max-width: 900px; margin: 0 auto; padding: 20px; font-family: Arial, sans-serif;'>
85
- <h2>📋 Misinformation Analysis</h2>
86
- <p><strong>Source:</strong> {source}</p>
87
- <p><strong>Issues Found:</strong> {len(positive_responses)} out of {len(chunks)} sections</p>
88
- <hr>
89
- """
90
-
91
- # Map responses to chunks
92
- response_index = 0
93
- for i, chunk in enumerate(chunks):
94
- # Extract text from Document object or dict
95
- if hasattr(chunk, "page_content"):
96
- chunk_text = chunk.page_content
97
- elif isinstance(chunk, dict):
98
- chunk_text = chunk.get("metadata", {}).get("chunk") or chunk.get("text", "")
99
- else:
100
- chunk_text = str(chunk)
101
-
102
- # Check if this chunk has misinformation
103
- if i < len(responses) and responses[i].category != "0":
104
- # Highlighted section with rebuttal
105
- output += f"""
106
- <div style='background: #fff3cd; padding: 20px; margin: 20px 0; border-left: 4px solid #ff9800; border-radius: 4px;'>
107
- <p style='margin: 0; font-size: 16px;'>{chunk_text}</p>
108
- <div style='margin-top: 15px; padding: 15px; background: white; border-left: 3px solid #2196F3; border-radius: 4px;'>
109
- <strong style='color: #2196F3;'>🔍 Fact Check:</strong>
110
- <p style='margin: 5px 0 0 0;'>{rebuttals[response_index]}</p>
111
- </div>
112
- </div>
113
- """
114
- response_index += 1
115
- else:
116
- # Normal section
117
- output += f"""
118
- <div style='padding: 15px; margin: 15px 0; background: #f5f5f5; border-radius: 4px;'>
119
- <p style='margin: 0;'>{chunk_text}</p>
120
- </div>
121
- """
122
-
123
- output += "</div>"
124
- return output
125
-
126
-
127
- # Create simple Gradio interface
128
- demo = gr.Interface(
129
- fn=analyze_document,
130
- inputs=[
131
- gr.File(label="📄 Upload PDF (optional)", file_types=[".pdf"]),
132
- gr.Textbox(
133
- label="🔗 Or Enter URL (optional)",
134
- placeholder="https://example.com/article",
135
- ),
136
- ],
137
- outputs=gr.HTML(label="Analysis Results"),
138
- title="🔍 Misinformation Detector",
139
- description="Upload a PDF or enter a URL to detect and fact-check potential misinformation.",
140
- )
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  if __name__ == "__main__":
144
  demo.launch(
 
4
  """
5
 
6
  import logging
 
7
  from pathlib import Path
8
  import gradio as gr
9
+ from langchain_core.messages import HumanMessage
10
+ from src.llm.llms import google_llm
11
+ from src.utils.parser_utils import clean_markdown, encode_pdf_to_base64
12
  from src.utils.chunking import get_base_chunks
13
+ from src.utils.annotation_rendering import (
14
+ calculate_coverage,
15
+ create_end_markers,
16
+ highlight_text,
17
+ create_layout,
18
+ )
19
  from src.api.apis import classify_text
20
  from src.api.rebuttal import RebuttalStructure
21
 
22
+
23
+ transcription_prompt = Path("./src/prompts/md_transcript.md").read_text(
24
+ encoding="utf-8"
25
+ )
26
+
27
  logging.basicConfig(
28
  format="%(asctime)s %(levelname)s %(message)s",
29
  datefmt="%m/%d/%Y %I:%M:%S %p",
 
32
  logger = logging.getLogger(__name__)
33
 
34
 
35
+ def analyze_chunks(prev_state):
36
+ chunks = [c.copy() for c in prev_state["chunks"]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  rebuttal_gen = RebuttalStructure()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ for chunk in chunks:
40
+ resp = classify_text(chunk["text"])
41
+ chunk["CARDS_code"] = resp.category
42
+ chunk["CARDS_category"] = resp.description
43
+
44
+ if resp.category != "0":
45
+ chunk["has_misinformation"] = True
46
+ chunk["rebuttal"] = rebuttal_gen.run(chunk["text"])
47
+
48
+ return {
49
+ "raw_markdown": prev_state["raw_markdown"],
50
+ "chunks": chunks,
51
+ }
52
+
53
+
54
+ def render_document(state):
55
+ misleading = [c for c in state["chunks"] if c["has_misinformation"]]
56
+
57
+ coverage = calculate_coverage(misleading)
58
+ end_markers = create_end_markers(misleading)
59
+
60
+ annotated = highlight_text(state["raw_markdown"], coverage, end_markers)
61
+
62
+ return create_layout(annotated, misleading)
63
+
64
+
65
+ def transcribe_pdf(file_obj, prev_state):
66
+ if not file_obj:
67
+ yield "Please upload a PDF."
68
+ return
69
+
70
+ encoded_pdf = encode_pdf_to_base64(file_obj.name)
71
+
72
+ message = HumanMessage(
73
+ content=[
74
+ {"type": "text", "text": transcription_prompt},
75
+ {"type": "media", "mime_type": "application/pdf", "data": encoded_pdf},
76
+ ]
77
+ )
78
+
79
+ markdown = ""
80
+ for chunk in google_llm.stream([message]):
81
+ markdown += chunk.content
82
+ yield markdown, prev_state
83
+
84
+ cleaned = clean_markdown(markdown)
85
+
86
+ chunks = [
87
+ {
88
+ "id": i,
89
+ "text": c.page_content,
90
+ "start": c.metadata["start_index"],
91
+ "end": c.metadata["start_index"] + len(c.page_content),
92
+ "has_misinformation": False,
93
+ "CARDS_code": None,
94
+ "CARDS_category": None,
95
+ "rebuttal": None,
96
+ }
97
+ for i, c in enumerate(
98
+ get_base_chunks(cleaned, chunk_size=1000, chunk_overlap=200)
99
+ )
100
+ ]
101
+
102
+ new_state = {
103
+ "raw_markdown": cleaned,
104
+ "chunks": chunks,
105
+ }
106
+
107
+ yield cleaned, new_state
108
+
109
+
110
+ with gr.Blocks() as demo:
111
+
112
+ doc_state = gr.State(
113
+ {
114
+ "raw_markdown": "",
115
+ "chunks": [],
116
+ }
117
+ )
118
+
119
+ gr.Markdown("## Gemini Multimodal Chat (LangChain + Gradio)")
120
+
121
+ with gr.Row():
122
+ with gr.Row():
123
+ input_file = gr.File(label="Upload PDF file", file_types=[".pdf"])
124
+ submit_btn = gr.Button("Analyze", variant="primary")
125
+
126
+ with gr.Row():
127
+ output_text = gr.Markdown(label="Gemini's Response", line_breaks=True)
128
+
129
+ submit_btn.click(
130
+ fn=transcribe_pdf,
131
+ inputs=[input_file, doc_state],
132
+ outputs=[output_text, doc_state],
133
+ ).then(
134
+ fn=analyze_chunks,
135
+ inputs=doc_state,
136
+ outputs=doc_state,
137
+ ).then(
138
+ fn=render_document,
139
+ inputs=doc_state,
140
+ outputs=output_text,
141
+ )
142
 
143
  if __name__ == "__main__":
144
  demo.launch(