Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/asad2204_studybuddy.json
T

1 line
10 KiB
JSON

{"repo": "asad2204/studybuddy", "files": {"app.py": {"c245d28e47e93165001863a16c72a1c0fd9a93e3": {"t": 1785674395.2875342, "n": 9570, "h": "3737f97094da47d9", "c": "# app.py\n# ---------------------------------------------------------\n# FINAL DEPLOYMENT VERSION\n# Features: Cloud Secrets, Redis Cloud, Conversational Memory\n# ---------------------------------------------------------\nimport streamlit as st\nimport time\nimport re\nimport os\nfrom langchain_groq import ChatGroq\nfrom langchain_core.prompts import ChatPromptTemplate\nfrom langchain_core.output_parsers import StrOutputParser\nfrom langchain_core.runnables import RunnablePassthrough\nfrom cag_engine import SemanticCache\nfrom rag_engine import process_textbook, embedding_function, expand_query, clear_database\nfrom router_engine import route_query \nfrom flashrank import Ranker, RerankRequest\n\n# --- 1. UI CONFIGURATION ---\nst.set_page_config(page_title=\"Study Buddy Pro\", layout=\"wide\", page_icon=\"🧠\")\n\nst.markdown(\"\"\"\n<style>\n .stApp { background-color: #0e1117; color: #ffffff; }\n [data-testid=\"stSidebar\"] { background-color: #161b22; border-right: 1px solid #30363d; }\n .stChatMessage[data-testid=\"stChatMessage\"]:nth-child(odd) { background: linear-gradient(135deg, #2b3137, #24292e); border: 1px solid #30363d; border-radius: 15px; padding: 15px; }\n .stChatMessage[data-testid=\"stChatMessage\"]:nth-child(even) { background: #1c2128; border: 1px solid #444c56; border-radius: 15px; padding: 15px; }\n .stButton button { background-color: transparent; color: #58a6ff; border: 1px solid #30363d; border-radius: 20px; padding: 5px 15px; font-size: 0.9rem; transition: all 0.3s ease; }\n .stButton button:hover { border-color: #58a6ff; background-color: rgba(88, 166, 255, 0.1); color: white; }\n</style>\n\"\"\", unsafe_allow_html=True)\n\nst.title(\"🧠 Study Buddy _Cloud_\")\n\n# --- 2. CLOUD SECRETS & SETUP ---\n# This ensures the app works for others without them needing your key.\ntry:\n GROQ_KEY = st.secrets[\"GROQ_API_KEY\"]\nexcept (FileNotFoundError, KeyError):\n # Fallback for local testing if secrets.toml isn't found\n GROQ_KEY = \"gsk_...\" # <--- PUT YOUR KEY HERE FOR LOCAL TESTING\n\n# Redis Logic: Try Cloud URL (Upstash), fail back to Localhost, fail back to None (RAM)\ntry:\n REDIS_URL = st.secrets[\"REDIS_URL\"]\nexcept (FileNotFoundError, KeyError):\n REDIS_URL = \"redis://localhost:6379\"\n\nif \"cag\" not in st.session_state:\n st.session_state.cag = SemanticCache(embedding_function, redis_url=REDIS_URL)\n\nif \"db_ready\" not in st.session_state: st.session_state.db_ready = False\nif \"reranker\" not in st.session_state: st.session_state.reranker = Ranker(model_name=\"ms-marco-MiniLM-L-12-v2\")\nif \"messages\" not in st.session_state: st.session_state.messages = []\nif \"trigger_query\" not in st.session_state: st.session_state.trigger_query = None\n\n# --- 3. SIDEBAR ---\nwith st.sidebar:\n st.header(\"⚙️ Knowledge Base\")\n uploaded_files = st.file_uploader(\"📂 Upload PDF(s)\", type=\"pdf\", accept_multiple_files=True)\n \n if uploaded_files:\n if st.button(\"🚀 Process Documents\"):\n with st.spinner(\"⚡ Merging & Indexing Knowledge Base...\"):\n saved_paths = []\n for i, up_file in enumerate(uploaded_files):\n path = f\"temp_{i}_{up_file.name}\"\n with open(path, \"wb\") as f: f.write(up_file.getbuffer())\n saved_paths.append(path)\n st.session_state.retriever = process_textbook(saved_paths)\n st.session_state.db_ready = True\n st.success(f\"✅ Indexed {len(saved_paths)} Files!\")\n\n st.markdown(\"---\")\n st.header(\"🗑️ Reset Zone\")\n if st.button(\"Clear Chat History\"):\n st.session_state.messages = []\n st.rerun()\n\n if st.button(\"⚠️ HARD RESET\"):\n with st.spinner(\"💥 Wiping System...\"):\n st.session_state.cag.clear()\n clear_database()\n st.session_state.db_ready = False\n st.session_state.messages = []\n st.session_state.retriever = None\n st.session_state.trigger_query = None\n st.success(\"System completely wiped.\")\n time.sleep(1)\n st.rerun()\n\n# --- 4. PROMPTS ---\nprompts = {\n \"coding example\": \"You are a Senior Software Engineer. Provide well-commented code in the context's language.\",\n \"debugging or error fixing\": \"You are a Code Troubleshooter. Analyze the context and suggest a step-by-step fix.\",\n \"summarization\": \"You are an Executive Assistant. Provide a high-level bullet point summary of key concepts.\",\n \"simplified explanation\": \"You are a Primary School Teacher. Explain using simple analogies and avoid jargon.\",\n \"theoretical explanation\": \"\"\"You are a Professor. Define terms strictly based on the text. Explain 'why' and 'how'.\"\"\",\n \"comparison or difference\": \"You are a Technical Analyst. Output a Markdown Table comparing the items.\",\n \"pros and cons analysis\": \"You are a Critical Reviewer. List Pros (Benefits) and Cons (Drawbacks).\",\n \"step by step solving\": \"You are a Lab Instructor. Break the answer into numbered steps (Step 1, Step 2...).\",\n \"quiz or exam practice\": \"You are an Exam Proctor. Generate 3 challenging multiple-choice questions with hidden answers.\",\n \"historical context\": \"You are a Tech Historian. Focus on dates, inventors, and evolution.\"\n}\n\nbase_instruction = \"\"\"\nInstructions:\n1. Answer based **ONLY** on the Context below.\n2. If the info is missing, say \"I cannot find information about [topic] in the document.\"\n3. **CRITICAL:** At the very end, generate 3 distinct follow-up questions.\n - Format exactly like this: ||Question 1||Question 2||Question 3||\n - **DO NOT** use placeholders like \"Q2\".\n\nContext: {context}\nQuestion: {question}\n\"\"\"\n\n# --- 5. RENDER HISTORY ---\nfor msg in st.session_state.messages:\n with st.chat_message(msg[\"role\"]): st.markdown(msg[\"content\"])\n\n# --- 6. INPUT LOGIC ---\nuser_input = st.chat_input(\"Ask about your documents...\")\n\nif st.session_state.trigger_query:\n question = st.session_state.trigger_query\n st.session_state.trigger_query = None\nelif user_input:\n question = user_input\nelse:\n question = None\n\n# --- 7. MAIN LOGIC ---\nif question:\n # --- CONTEXT REWRITER ---\n vague_phrases = [\"explain it\", \"explain that\", \"tell me more\", \"elaborate\", \"what does that mean\"]\n if any(phrase in question.lower() for phrase in vague_phrases) and len(st.session_state.messages) > 0:\n last_user_msg = next((m[\"content\"] for m in reversed(st.session_state.messages) if m[\"role\"] == \"user\"), None)\n if last_user_msg: search_query = f\"{question} related to: {last_user_msg}\"\n else: search_query = question\n else:\n search_query = question\n # ------------------------\n\n st.session_state.messages.append({\"role\": \"user\", \"content\": question})\n with st.chat_message(\"user\"): st.write(question)\n\n start_time = time.time()\n \n cached = st.session_state.cag.lookup(question)\n if cached:\n st.session_state.messages.append({\"role\": \"assistant\", \"content\": cached})\n with st.chat_message(\"assistant\"): st.markdown(cached + \" \\n\\n*(⚡ Memory Hit)*\")\n \n elif st.session_state.db_ready:\n with st.chat_message(\"assistant\"):\n intent = route_query(search_query)\n persona = prompts.get(intent, prompts[\"theoretical explanation\"])\n \n expanded_q = expand_query(search_query)\n docs = st.session_state.retriever.invoke(expanded_q)\n \n passages = [{\"id\": str(i), \"text\": d.page_content, \"meta\": d.metadata} for i, d in enumerate(docs)]\n rerank_req = RerankRequest(query=search_query, passages=passages)\n ranked = st.session_state.reranker.rerank(rerank_req)\n top_docs = [docs[int(r['id'])] for r in ranked[:3]]\n \n # Use the GROQ_KEY loaded from secrets/variable\n llm = ChatGroq(groq_api_key=GROQ_KEY, model_name=\"llama-3.1-8b-instant\", temperature=0.3)\n \n def format_docs(docs): return \"\\n\\n\".join(d.page_content for d in docs)\n prompt = ChatPromptTemplate.from_template(persona + base_instruction)\n chain = ({\"context\": lambda x: format_docs(top_docs), \"question\": RunnablePassthrough()} \n | prompt | llm | StrOutputParser())\n \n full_response = \"\"\n placeholder = st.empty()\n for chunk in chain.stream(question):\n full_response += chunk\n clean_view = re.sub(r\"\\|\\|.*?\\|\\|\", \"\", full_response)\n placeholder.markdown(clean_view + \"▌\")\n \n main_answer = re.sub(r\"\\|\\|.*?\\|\\|\", \"\", full_response).strip()\n placeholder.markdown(main_answer)\n \n st.session_state.messages.append({\"role\": \"assistant\", \"content\": main_answer})\n st.session_state.cag.update(question, main_answer)\n \n suggestions = re.findall(r\"\\|\\|(.*?)\\|\\|\", full_response)\n valid_suggestions = [s.strip() for s in suggestions if len(s.strip()) > 5 and not re.match(r\"^Q\\d$\", s.strip(), re.IGNORECASE)]\n \n if valid_suggestions:\n st.markdown(\"---\")\n cols = st.columns(len(valid_suggestions))\n def set_trigger(text): st.session_state.trigger_query = text\n for i, sug in enumerate(valid_suggestions):\n cols[i].button(sug, key=f\"sug_{int(time.time())}_{i}\", on_click=set_trigger, args=(sug,))\n\n st.caption(f\"⚡ Time: {time.time()-start_time:.2f}s | Intent: {intent}\")\n else:\n st.info(\"👈 Please upload a PDF to begin.\")"}}}}