Francisco Magaña commited on
Commit
a51440e
·
unverified ·
2 Parent(s): f31d2d407659df

Merge pull request #1 from francisco-magana/franciscomaganacontact/nah-17-extract_text-pdf-pastedtxt-length-based-chunker

Browse files
Files changed (2) hide show
  1. app.py +33 -5
  2. content_pipeline.py +28 -6
app.py CHANGED
@@ -24,16 +24,39 @@ def start_session(file, pasted_text):
24
  text = pasted_text.strip() if pasted_text and pasted_text.strip() else cp.extract_text(file)
25
  if not text:
26
  return (None, gr.update(visible=True), gr.update(visible=False),
27
- "⚠️ No text found. Upload a PDF or paste some notes.", "", "")
28
- deck = cp.generate_deck(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  if not deck:
30
  return (None, gr.update(visible=True), gr.update(visible=False),
31
  "⚠️ Couldn't generate questions from that. Try different material.",
32
- "", "")
33
  session = le.init_session(deck)
34
  card = le.next_card(session)
35
  return (session, gr.update(visible=False), gr.update(visible=True),
36
- "", _progress(session), card["question"] if card else "")
 
37
 
38
 
39
  def submit_answer(session, user_answer):
@@ -111,9 +134,14 @@ with gr.Blocks(title="Recall — your AI study partner", theme=gr.themes.Soft())
111
  with gr.Group(visible=False) as recap_view:
112
  recap_md = gr.Markdown("")
113
 
 
 
 
 
114
  start_btn.click(
115
  start_session, [file_in, text_in],
116
- [session, upload_view, study_view, upload_msg, progress, question],
 
117
  )
118
  submit_btn.click(
119
  submit_answer, [session, answer_in],
 
24
  text = pasted_text.strip() if pasted_text and pasted_text.strip() else cp.extract_text(file)
25
  if not text:
26
  return (None, gr.update(visible=True), gr.update(visible=False),
27
+ "⚠️ No text found. Upload a PDF or paste some notes.", "", "",
28
+ gr.update(visible=False), None)
29
+ chunks = cp._chunk(text)
30
+ debug = {
31
+ "total_chars": len(text),
32
+ "extracted_text_preview": text[:800] + ("…" if len(text) > 800 else ""),
33
+ "chunk_count": len(chunks),
34
+ "chunks": [
35
+ {
36
+ "index": i,
37
+ "chars": len(c),
38
+ "tokens_approx": len(c) // 4,
39
+ "text": c,
40
+ }
41
+ for i, c in enumerate(chunks)
42
+ ],
43
+ }
44
+ try:
45
+ deck = cp.generate_deck(text)
46
+ except Exception as e:
47
+ return (None, gr.update(visible=True), gr.update(visible=False),
48
+ f"⚠️ generate_deck failed ({type(e).__name__}: {e}). "
49
+ "Extraction & chunks are shown below.",
50
+ "", "", gr.update(visible=True), debug)
51
  if not deck:
52
  return (None, gr.update(visible=True), gr.update(visible=False),
53
  "⚠️ Couldn't generate questions from that. Try different material.",
54
+ "", "", gr.update(visible=True), debug)
55
  session = le.init_session(deck)
56
  card = le.next_card(session)
57
  return (session, gr.update(visible=False), gr.update(visible=True),
58
+ "", _progress(session), card["question"] if card else "",
59
+ gr.update(visible=True), debug)
60
 
61
 
62
  def submit_answer(session, user_answer):
 
134
  with gr.Group(visible=False) as recap_view:
135
  recap_md = gr.Markdown("")
136
 
137
+ # Debug panel — visible after each upload so you can verify extraction & chunks
138
+ with gr.Accordion("🔍 Debug: extraction & chunks", open=False, visible=False) as debug_accordion:
139
+ debug_data = gr.JSON(label="Content pipeline output")
140
+
141
  start_btn.click(
142
  start_session, [file_in, text_in],
143
+ [session, upload_view, study_view, upload_msg, progress, question,
144
+ debug_accordion, debug_data],
145
  )
146
  submit_btn.click(
147
  submit_answer, [session, answer_in],
content_pipeline.py CHANGED
@@ -10,6 +10,7 @@ learning_engine depend on them.
10
  from __future__ import annotations
11
 
12
  import os
 
13
 
14
  import llm
15
  from schema import Card, new_card, validate_card
@@ -34,7 +35,7 @@ def extract_text(file) -> str:
34
  # TODO(Frank): pip install pypdf
35
  from pypdf import PdfReader
36
  reader = PdfReader(path)
37
- text = "\n".join((page.extract_text() or "") for page in reader.pages)
38
  else:
39
  with open(path, "r", encoding="utf-8", errors="ignore") as f:
40
  text = f.read()
@@ -85,10 +86,31 @@ def _normalize(text: str) -> str:
85
  return " ".join(text.split())
86
 
87
 
88
- def _chunk(text: str, size: int = 800) -> list[str]:
89
- """Simple length-based splitter. No vector DB — keep it boring."""
90
- words = text.split()
91
- return [" ".join(words[i:i + size]) for i in range(0, len(words), size)] or [text]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
 
94
  def _cards_from_chunk(chunk: str) -> list[Card]:
@@ -112,7 +134,7 @@ def _cards_from_chunk(chunk: str) -> list[Card]:
112
  question=str(item.get("question", "")).strip(),
113
  answer=str(item.get("answer", "")).strip(),
114
  topic=str(item.get("topic", "General")).strip() or "General",
115
- source_chunk=chunk[:300],
116
  difficulty=int(item.get("difficulty", 1) or 1),
117
  )
118
  if validate_card(card):
 
10
  from __future__ import annotations
11
 
12
  import os
13
+ import re
14
 
15
  import llm
16
  from schema import Card, new_card, validate_card
 
35
  # TODO(Frank): pip install pypdf
36
  from pypdf import PdfReader
37
  reader = PdfReader(path)
38
+ text = "\n\n".join((page.extract_text() or "") for page in reader.pages)
39
  else:
40
  with open(path, "r", encoding="utf-8", errors="ignore") as f:
41
  text = f.read()
 
86
  return " ".join(text.split())
87
 
88
 
89
+ def _chunk(text: str, size: int = 2500) -> list[str]:
90
+ """
91
+ Character-based splitter targeting ~500–800 tokens per chunk (1 token ≈ 4 chars,
92
+ so size=2500 ≈ 625 tokens). Breaks at sentence boundaries where possible.
93
+ """
94
+ # Split into sentences on . ! ? followed by whitespace or end-of-string.
95
+ sentences = re.split(r'(?<=[.!?])\s+', text.strip())
96
+ chunks: list[str] = []
97
+ current: list[str] = []
98
+ current_len = 0
99
+
100
+ for sentence in sentences:
101
+ slen = len(sentence)
102
+ if current and current_len + 1 + slen > size:
103
+ chunks.append(" ".join(current))
104
+ current = [sentence]
105
+ current_len = slen
106
+ else:
107
+ current.append(sentence)
108
+ current_len += (1 + slen) if current_len else slen
109
+
110
+ if current:
111
+ chunks.append(" ".join(current))
112
+
113
+ return chunks or [text]
114
 
115
 
116
  def _cards_from_chunk(chunk: str) -> list[Card]:
 
134
  question=str(item.get("question", "")).strip(),
135
  answer=str(item.get("answer", "")).strip(),
136
  topic=str(item.get("topic", "General")).strip() or "General",
137
+ source_chunk=chunk,
138
  difficulty=int(item.get("difficulty", 1) or 1),
139
  )
140
  if validate_card(card):