File size: 5,006 Bytes
c35855b | 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 | import os
import re
import pdfplumber
from flask import Blueprint, request, jsonify
from werkzeug.utils import secure_filename
from assistant.storage import (
generate_doc_id,
save_document,
load_document,
save_metadata,
get_all_docs,
)
assistant_bp = Blueprint('assistant_bp', __name__)
UPLOAD_FOLDER = './uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
FALLBACK = "This information is not available in your uploaded notes."
STOP_WORDS = {
"what", "is", "a", "an", "the", "of", "to", "in", "and", "why",
"how", "who", "when", "where", "are", "do", "does", "did", "for",
"on", "it", "this", "that", "tell", "me", "about", "define",
"explain", "give", "meaning", "can", "you", "please", "with",
"was", "will", "has", "have", "had", "be", "been", "by", "or",
"at", "from", "as", "its", "my", "your"
}
# ---------------------------------------------------------------------------
# POST /upload_notes
# ---------------------------------------------------------------------------
@assistant_bp.route('/upload_notes', methods=['POST'])
def upload_notes():
if 'file' not in request.files:
return jsonify({"error": "No file part in request"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
filename = secure_filename(file.filename)
filepath = os.path.join(UPLOAD_FOLDER, filename)
file.save(filepath)
try:
text = ""
with pdfplumber.open(filepath) as pdf:
for page in pdf.pages:
extracted = page.extract_text()
if extracted:
text += extracted + "\n"
if not text.strip():
return jsonify({"error": "Could not extract any text from PDF"}), 422
doc_id = generate_doc_id()
save_document(doc_id, text)
save_metadata(doc_id, filename)
return jsonify({"doc_id": doc_id, "message": "Saved successfully"}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
finally:
if os.path.exists(filepath):
os.remove(filepath)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _extract_keywords(question: str) -> list:
words = re.findall(r'\b\w+\b', question.lower())
keywords = [w for w in words if w not in STOP_WORDS and len(w) > 1]
return keywords if keywords else words
# ---------------------------------------------------------------------------
# POST /ask
# ---------------------------------------------------------------------------
@assistant_bp.route('/ask', methods=['POST'])
def ask_question():
data = request.get_json()
if not data:
return jsonify({"error": "Request body must be JSON"}), 400
if 'doc_id' not in data or 'question' not in data:
return jsonify({"error": "Missing 'doc_id' or 'question'"}), 400
doc_id = data['doc_id']
question = data['question'].strip()
if not question:
return jsonify({"answer": "Please provide a non-empty question."}), 200
# STEP 1 β Load document
text = load_document(doc_id)
if text is None:
return jsonify({"error": f"Document '{doc_id}' not found"}), 404
# STEP 2 β Extract Keywords
keywords = _extract_keywords(question)
# STEP 3 β Match context with line tracking (Pure RAG)
lines = text.split("\n")
matched_data = []
for i, line in enumerate(lines):
line_lower = line.lower()
for kw in keywords:
if kw in line_lower:
matched_data.append({
"line": i + 1,
"text": line.strip()
})
break
if len(matched_data) >= 3: # Keep it concise for direct RAG
break
# Build context string
context = " ".join([item["text"] for item in matched_data])
# STEP 4 β Guard: check context length
if len(context.strip()) < 30:
return jsonify({
"answer": FALLBACK,
"source": None
}), 200
# STEP 5 β Determine source metadata (first match)
first_match = matched_data[0]
# Find the column index of the first keyword in the first matched line
column_index = first_match["text"].lower().find(keywords[0]) if keywords else 0
# STEP 6 β Return the raw extracted context natively as the answer
answer = f"According to your notes: {context}"
return jsonify({
"answer": answer,
"source": {
"line": first_match["line"],
"column": column_index
}
}), 200
# ---------------------------------------------------------------------------
# GET /documents
# ---------------------------------------------------------------------------
@assistant_bp.route('/documents', methods=['GET'])
def list_documents():
return jsonify(get_all_docs()), 200
|