Update file_utils.py
Browse files- file_utils.py +43 -35
file_utils.py
CHANGED
|
@@ -15,7 +15,7 @@ SQLModel.metadata.create_all(engine)
|
|
| 15 |
openai_api_key = os.getenv("OPENAI_API_KEY")
|
| 16 |
client = OpenAI(api_key=openai_api_key)
|
| 17 |
|
| 18 |
-
# === DB
|
| 19 |
class StudyGuideEntry(SQLModel, table=True):
|
| 20 |
id: Optional[int] = Field(default=None, primary_key=True)
|
| 21 |
user: str
|
|
@@ -23,7 +23,35 @@ class StudyGuideEntry(SQLModel, table=True):
|
|
| 23 |
guide: str
|
| 24 |
timestamp: str
|
| 25 |
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
def save_study_guide(user, filename, guide):
|
| 28 |
session = Session(engine)
|
| 29 |
entry = StudyGuideEntry(
|
|
@@ -43,31 +71,14 @@ def fetch_study_guides(user):
|
|
| 43 |
session.close()
|
| 44 |
return results
|
| 45 |
|
| 46 |
-
# ===
|
| 47 |
def call_llm(prompt=None, system_message="You are a helpful assistant.", messages=None):
|
| 48 |
-
|
| 49 |
-
Call GPT-4 with either a single prompt or full message history.
|
| 50 |
-
Handles token limits and returns a single string response.
|
| 51 |
-
|
| 52 |
-
Args:
|
| 53 |
-
prompt (str): Optional one-turn prompt.
|
| 54 |
-
system_message (str): Optional system message.
|
| 55 |
-
messages (list): Optional list of chat messages (for multi-turn chat).
|
| 56 |
-
|
| 57 |
-
Returns:
|
| 58 |
-
str: The assistant's reply.
|
| 59 |
-
"""
|
| 60 |
-
max_tokens = 8192
|
| 61 |
-
max_safe_chars = 28000 # Roughly ~7000 tokens
|
| 62 |
-
|
| 63 |
if messages:
|
| 64 |
-
# Optional: truncate text-heavy messages
|
| 65 |
for msg in messages:
|
| 66 |
if len(msg["content"]) > max_safe_chars:
|
| 67 |
msg["content"] = msg["content"][:max_safe_chars] + "\n\n[Truncated due to token limit]"
|
| 68 |
-
|
| 69 |
else:
|
| 70 |
-
# One-shot prompt mode
|
| 71 |
if prompt and len(prompt) > max_safe_chars:
|
| 72 |
prompt = prompt[:max_safe_chars] + "\n\n[Truncated due to token limit]"
|
| 73 |
messages = [
|
|
@@ -80,48 +91,45 @@ def call_llm(prompt=None, system_message="You are a helpful assistant.", message
|
|
| 80 |
messages=messages,
|
| 81 |
temperature=0.7
|
| 82 |
)
|
| 83 |
-
|
| 84 |
return response.choices[0].message.content
|
| 85 |
|
| 86 |
-
|
| 87 |
# === File Parsing ===
|
| 88 |
def extract_text_from_file(file):
|
| 89 |
ext = os.path.splitext(file.name)[1].lower()
|
| 90 |
-
|
| 91 |
if ext == ".pdf":
|
| 92 |
with fitz.open(file.name) as doc:
|
| 93 |
return "\n".join([page.get_text() for page in doc])
|
| 94 |
-
|
| 95 |
elif ext in [".txt", ".md"]:
|
| 96 |
return file.read().decode("utf-8")
|
| 97 |
-
|
| 98 |
elif ext == ".pptx":
|
| 99 |
prs = Presentation(file.name)
|
| 100 |
return "\n".join([
|
| 101 |
shape.text for slide in prs.slides for shape in slide.shapes if hasattr(shape, "text")
|
| 102 |
])
|
| 103 |
-
|
| 104 |
else:
|
| 105 |
return "Unsupported file type."
|
| 106 |
|
| 107 |
# === GPT-Based Generators ===
|
| 108 |
-
|
| 109 |
def generate_summary(text):
|
| 110 |
prompt = f"Summarize the following document in 5–7 bullet points:\n\n{text}"
|
| 111 |
-
return call_llm(prompt)
|
| 112 |
|
| 113 |
def generate_flashcards(text):
|
| 114 |
-
prompt =
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
def generate_quiz(text):
|
| 118 |
prompt = (
|
| 119 |
-
"Generate 5 multiple choice questions
|
| 120 |
-
"Return
|
| 121 |
"- question (string)\n- options (list of strings)\n- answer (correct option string)\n\n"
|
| 122 |
f"{text}"
|
| 123 |
)
|
| 124 |
-
response = call_llm(prompt)
|
| 125 |
try:
|
| 126 |
return json.loads(response)
|
| 127 |
except:
|
|
@@ -129,4 +137,4 @@ def generate_quiz(text):
|
|
| 129 |
|
| 130 |
def answer_question(text, question):
|
| 131 |
prompt = f"Using only the document below, answer this question:\n\nDocument:\n{text}\n\nQuestion:\n{question}"
|
| 132 |
-
return call_llm(prompt)
|
|
|
|
| 15 |
openai_api_key = os.getenv("OPENAI_API_KEY")
|
| 16 |
client = OpenAI(api_key=openai_api_key)
|
| 17 |
|
| 18 |
+
# === DB Tables ===
|
| 19 |
class StudyGuideEntry(SQLModel, table=True):
|
| 20 |
id: Optional[int] = Field(default=None, primary_key=True)
|
| 21 |
user: str
|
|
|
|
| 23 |
guide: str
|
| 24 |
timestamp: str
|
| 25 |
|
| 26 |
+
class UserFileEntry(SQLModel, table=True):
|
| 27 |
+
id: Optional[int] = Field(default=None, primary_key=True)
|
| 28 |
+
user: str
|
| 29 |
+
filename: str
|
| 30 |
+
file_path: str
|
| 31 |
+
timestamp: str
|
| 32 |
+
|
| 33 |
+
SQLModel.metadata.create_all(engine)
|
| 34 |
+
|
| 35 |
+
# === File Persistence ===
|
| 36 |
+
def save_user_file(user, filename, file_path):
|
| 37 |
+
session = Session(engine)
|
| 38 |
+
entry = UserFileEntry(
|
| 39 |
+
user=user,
|
| 40 |
+
filename=filename,
|
| 41 |
+
file_path=file_path,
|
| 42 |
+
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M")
|
| 43 |
+
)
|
| 44 |
+
session.add(entry)
|
| 45 |
+
session.commit()
|
| 46 |
+
session.close()
|
| 47 |
+
|
| 48 |
+
def fetch_user_files(user):
|
| 49 |
+
session = Session(engine)
|
| 50 |
+
statement = select(UserFileEntry).where(UserFileEntry.user == user)
|
| 51 |
+
results = session.exec(statement).all()
|
| 52 |
+
session.close()
|
| 53 |
+
return results
|
| 54 |
+
|
| 55 |
def save_study_guide(user, filename, guide):
|
| 56 |
session = Session(engine)
|
| 57 |
entry = StudyGuideEntry(
|
|
|
|
| 71 |
session.close()
|
| 72 |
return results
|
| 73 |
|
| 74 |
+
# === LLM Core ===
|
| 75 |
def call_llm(prompt=None, system_message="You are a helpful assistant.", messages=None):
|
| 76 |
+
max_safe_chars = 28000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
if messages:
|
|
|
|
| 78 |
for msg in messages:
|
| 79 |
if len(msg["content"]) > max_safe_chars:
|
| 80 |
msg["content"] = msg["content"][:max_safe_chars] + "\n\n[Truncated due to token limit]"
|
|
|
|
| 81 |
else:
|
|
|
|
| 82 |
if prompt and len(prompt) > max_safe_chars:
|
| 83 |
prompt = prompt[:max_safe_chars] + "\n\n[Truncated due to token limit]"
|
| 84 |
messages = [
|
|
|
|
| 91 |
messages=messages,
|
| 92 |
temperature=0.7
|
| 93 |
)
|
|
|
|
| 94 |
return response.choices[0].message.content
|
| 95 |
|
|
|
|
| 96 |
# === File Parsing ===
|
| 97 |
def extract_text_from_file(file):
|
| 98 |
ext = os.path.splitext(file.name)[1].lower()
|
|
|
|
| 99 |
if ext == ".pdf":
|
| 100 |
with fitz.open(file.name) as doc:
|
| 101 |
return "\n".join([page.get_text() for page in doc])
|
|
|
|
| 102 |
elif ext in [".txt", ".md"]:
|
| 103 |
return file.read().decode("utf-8")
|
|
|
|
| 104 |
elif ext == ".pptx":
|
| 105 |
prs = Presentation(file.name)
|
| 106 |
return "\n".join([
|
| 107 |
shape.text for slide in prs.slides for shape in slide.shapes if hasattr(shape, "text")
|
| 108 |
])
|
|
|
|
| 109 |
else:
|
| 110 |
return "Unsupported file type."
|
| 111 |
|
| 112 |
# === GPT-Based Generators ===
|
|
|
|
| 113 |
def generate_summary(text):
|
| 114 |
prompt = f"Summarize the following document in 5–7 bullet points:\n\n{text}"
|
| 115 |
+
return call_llm(prompt=prompt)
|
| 116 |
|
| 117 |
def generate_flashcards(text):
|
| 118 |
+
prompt = (
|
| 119 |
+
"Generate 5 flashcards based on the document below.\n"
|
| 120 |
+
"Each flashcard should follow this format:\nQ: <question>\nA: <answer>\n\n"
|
| 121 |
+
f"{text}"
|
| 122 |
+
)
|
| 123 |
+
return call_llm(prompt=prompt)
|
| 124 |
|
| 125 |
def generate_quiz(text):
|
| 126 |
prompt = (
|
| 127 |
+
"Generate 5 multiple choice questions from the document below. "
|
| 128 |
+
"Return a JSON array where each item has:\n"
|
| 129 |
"- question (string)\n- options (list of strings)\n- answer (correct option string)\n\n"
|
| 130 |
f"{text}"
|
| 131 |
)
|
| 132 |
+
response = call_llm(prompt=prompt)
|
| 133 |
try:
|
| 134 |
return json.loads(response)
|
| 135 |
except:
|
|
|
|
| 137 |
|
| 138 |
def answer_question(text, question):
|
| 139 |
prompt = f"Using only the document below, answer this question:\n\nDocument:\n{text}\n\nQuestion:\n{question}"
|
| 140 |
+
return call_llm(prompt=prompt)
|