import streamlit as st
from datetime import datetime
import vault_logic as vl
import time
import json
import os
# --- CONFIGURATION ---
AUTO_LOCK_SECONDS = 150 # 2 Minutes
st.set_page_config(page_title="AI Vault", layout="wide")
# --- 0. FIRST TIME SETUP ---
if not vl.is_vault_initialized():
st.title("🔒 Initialize Your Secure Vault")
# 1. Primary Action: The PIN Box
st.info("👋 Welcome! To get started, please set your Master PIN below.")
with st.form("setup_form"):
new_pin = st.text_input("Create Master PIN", type="password", help="Minimum 4 digits")
conf_pin = st.text_input("Confirm Master PIN", type="password")
submit = st.form_submit_button("Setup Vault", type="primary")
# 2. Professional Spacing
st.write("")
st.write("")
st.divider()
# 3. Security & Recovery Information
if 'generated_recovery' not in st.session_state:
st.session_state.generated_recovery = vl.generate_recovery_key()
st.warning("⚠️ **CRITICAL: Save Your Recovery Key**")
st.caption("RECOVERY - KEY : This is the ONLY way to reset your vault access if you forget your PIN.")
st.code(st.session_state.generated_recovery, language=None)
# Important Security Note (Liability Waiver)
st.markdown("""
🛑 Important Security Notice:
This vault utilizes Zero-Knowledge AES-Encryption. Your PIN is never stored; it is only hashed.
While the Recovery Key can reset app access, it cannot decrypt existing secret data
locked with a different PIN.
The developer holds no responsibility for data loss resulting from forgotten credentials.
""", unsafe_allow_html=True)
if submit:
if new_pin == conf_pin and len(new_pin) >= 4:
vl.initialize_vault(new_pin, st.session_state.generated_recovery)
st.success("Vault Securely Initialized! Refreshing...")
time.sleep(1.5)
st.rerun()
else:
st.error("PINs must match and be at least 4 digits.")
st.stop()
# --- 1. SESSION STATE ---
if 'notes' not in st.session_state:
st.session_state.notes = vl.load_notes()
if 'edit_note_id' not in st.session_state:
st.session_state.edit_note_id = None
if 'vault_unlocked' not in st.session_state:
st.session_state.vault_unlocked = False
if 'master_pin' not in st.session_state:
st.session_state.master_pin = "" # We store the actual PIN in session only while unlocked
if 'temp_content' not in st.session_state:
st.session_state.temp_content = ""
if 'form_iteration' not in st.session_state:
st.session_state.form_iteration = 0
if 'last_activity' not in st.session_state:
st.session_state.last_activity = time.time()
if 'show_lock_alert' not in st.session_state:
st.session_state.show_lock_alert = False
# --- 2. AUTO-LOCK ENGINE ---
def update_activity():
st.session_state.last_activity = time.time()
st.session_state.show_lock_alert = False
if st.session_state.vault_unlocked:
elapsed_time = time.time() - st.session_state.last_activity
if elapsed_time > AUTO_LOCK_SECONDS:
st.session_state.vault_unlocked = False
st.session_state.edit_note_id = None
st.session_state.temp_content = ""
st.session_state.show_lock_alert = True
st.rerun()
# --- 3. CSS & ALERTS ---
st.markdown("""
""", unsafe_allow_html=True)
if st.session_state.show_lock_alert:
st.error("🚨 **THE VAULT IS AUTO LOCKED**")
# --- 4. SIDEBAR ---
with st.sidebar:
st.title("➕ Add/Edit Note")
c_title, c_content, c_secret = "", "", False
if st.session_state.edit_note_id:
n = next((x for x in st.session_state.notes if x['id'] == st.session_state.edit_note_id), None)
if n:
c_title = n['title']
c_content = vl.decrypt_data(n['content'], st.session_state.master_pin) if (n.get('secret') and st.session_state.vault_unlocked) else n['content']
c_secret = n.get('secret', False)
default_content = st.session_state.temp_content if st.session_state.temp_content else c_content
with st.form(key=f"note_form_{st.session_state.form_iteration}", clear_on_submit=False):
new_t = st.text_input("Title", value=c_title)
new_c = st.text_area("Content", value=default_content, height=150)
m_secret = st.checkbox("🤫 Mark as Secret", value=c_secret) if st.session_state.vault_unlocked else False
if st.form_submit_button("✨ AI Summarize"):
update_activity()
if new_c:
summary_result = vl.ai_summarize_text(new_c)
st.session_state.temp_content = summary_result
st.rerun()
if st.form_submit_button("Save", type="primary", use_container_width=True):
update_activity()
if new_t or new_c:
ts = datetime.now().strftime("%Y-%m-%d %H:%M")
final_content = vl.encrypt_data(new_c, st.session_state.master_pin) if m_secret else new_c
if st.session_state.edit_note_id:
for n in st.session_state.notes:
if n['id'] == st.session_state.edit_note_id:
n.update({"title": new_t, "content": final_content, "timestamp": ts, "secret": m_secret})
st.session_state.edit_note_id = None
else:
st.session_state.notes.insert(0, {"id": int(datetime.now().timestamp()), "title": new_t, "content": final_content, "timestamp": ts, "secret": m_secret})
vl.save_notes(st.session_state.notes)
st.session_state.temp_content = ""
st.session_state.form_iteration += 1
st.rerun()
st.divider()
st.write("### 🔐 Vault Security")
if not st.session_state.vault_unlocked:
pin_input = st.text_input("Enter Stealth PIN", type="password", key="pin_entry")
if pin_input:
if vl.verify_pin(pin_input):
update_activity()
st.session_state.vault_unlocked = True
st.session_state.master_pin = pin_input
st.rerun()
else: st.error("Incorrect PIN")
# NEW: Recovery Logic
with st.expander("Forgot PIN?"):
recovery_in = st.text_input("Enter Recovery Key")
if st.button("Reset Vault PIN"):
# Logic: If recovery key matches, delete config so they can start over
# Note: Old encrypted notes stay encrypted (this is the cost of security!)
if vl.verify_recovery_key(recovery_in): # You'll add this to vault_logic
os.remove("vault_config.json")
st.warning("Vault Identity Reset. Please refresh to set a new PIN.")
st.rerun()
else:
st.success("🔓 Vault is Open")
time_left = int(AUTO_LOCK_SECONDS - (time.time() - st.session_state.last_activity))
st.caption(f"Auto-locking in {max(0, time_left)}s")
if st.button("🔒 Close Vault", use_container_width=True):
st.session_state.vault_unlocked = False
st.session_state.master_pin = ""
st.rerun()
st.divider()
st.subheader("📊 AI Resources")
if os.path.exists("usage_stats.json"):
with open("usage_stats.json", "r") as f:
stats = json.load(f)
col1, col2 = st.columns(2)
col1.metric("Tokens", f"{int(stats['total_tokens'])}")
col2.metric("Cost", f"${stats['total_cost']:.5f}")
else:
st.info("No AI usage data yet.")
# --- 5. MAIN PAGE ---
st.title(vl.get_page_heading(st.session_state.vault_unlocked))
# --- 6. RAG CHAT INTERFACE ---
if st.session_state.vault_unlocked:
with st.expander("💬 Ask Your Vault (AI Search)", expanded=False):
user_query = st.text_input("Ask a question about your notes:", placeholder="e.g., What are my goals for 2026?")
if user_query:
update_activity()
# 1. Prepare Decrypted Context
decrypted_texts = []
for n in st.session_state.notes:
content = vl.decrypt_data(n['content'], st.session_state.master_pin) if n.get('secret') else n['content']
decrypted_texts.append(content)
# 2. RAG Logic
index, text_data = vl.create_vector_index([{"content": t} for t in decrypted_texts])
context_chunks = vl.query_vault(user_query, index, text_data)
# 3. AI Answer
context_str = "\n".join(context_chunks)
prompt = f"Using ONLY these notes:\n{context_str}\n\nAnswer this question: {user_query}"
with st.spinner("Searching Vault..."):
answer = vl.get_gemini_response(user_query, context_str)
st.info(f"**AI Answer:**\n{answer}")
# 4. Feedback System ---
st.write("---")
st.write("Was this answer helpful?")
f_col1, f_col2 = st.columns([1, 5])
if f_col1.button("✅ Yes", key="fb_yes"):
vl.log_feedback(user_query, answer, context_str, "Correct")
st.success("Thanks for the feedback!")
if f_col2.button("❌ No / Wrong", key="fb_no"):
vl.log_feedback(user_query, answer, context_str, "Wrong")
st.warning("Logged as a failure.")
st.divider()
search = st.text_input("🔍 Search...", placeholder="Filter notes...")
# --- 7. Display Grid ---
cols = st.columns(3)
filtered = vl.get_filtered_notes(st.session_state.notes, st.session_state.vault_unlocked, search)
for idx, note in enumerate(filtered):
with cols[idx % 3]:
with st.container(border=True):
display_content = vl.decrypt_data(note['content'], st.session_state.master_pin) if (note.get('secret') and st.session_state.vault_unlocked) else note['content']
st.subheader(f"🔒 {note['title']}" if note.get('secret') else note['title'])
st.write(display_content[:200] + "..." if len(display_content) > 200 else display_content)
st.caption(f"🕒 {note['timestamp']}")
eb, db = st.columns(2)
with eb:
if st.button("✏️ Edit", key=f"e_{note['id']}"):
update_activity()
st.session_state.edit_note_id = note['id']
st.rerun()
with db:
if st.button("🗑️ Delete", key=f"d_{note['id']}"):
st.session_state.notes = [x for x in st.session_state.notes if x['id'] != note['id']]
vl.save_notes(st.session_state.notes)
st.rerun()
st.divider()
p_col, d_col = st.columns(2)
with p_col:
pdf_data = vl.create_pdf(note['title'], display_content)
pdf_bytes = bytes(pdf_data) # This converts bytearray to standard bytes
st.download_button("📄 Download PDF", data=pdf_bytes, file_name=f"{note['title']}.pdf", key=f"pdf_{note['id']}", use_container_width=True)
with d_col:
docx_bytes = vl.create_docx(note['title'], display_content)
st.download_button("📝 Download DOCX", data=docx_bytes, file_name=f"{note['title']}.docx", key=f"docx_{note['id']}", use_container_width=True)