IAMTFRMZA commited on
Commit
0e1b8a9
Β·
verified Β·
1 Parent(s): 373b6fa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +119 -117
app.py CHANGED
@@ -1,117 +1,119 @@
1
- import streamlit as st
2
- import requests
3
-
4
- # ----------------- Streamlit Setup ----------------- #
5
- st.set_page_config(page_title="Lease Agreement Extractor (OCR + AI)", layout="centered")
6
- st.title("πŸ“„ Lease Agreement Extractor (OCR + AI)")
7
-
8
- # ----------------- Assistant & API Config ----------------- #
9
- OPENAI_ASSISTANT_ID = "asst_xBnNfiyWmVa4iF3CgXwJnmBt" # Replace with your assistant ID
10
- OPENAI_API_KEY = "YOUR_OPENAI_API_KEY" # Replace with your OpenAI key
11
-
12
- HEADERS = {
13
- "Authorization": f"Bearer {OPENAI_API_KEY}",
14
- "Content-Type": "application/json"
15
- }
16
-
17
- # ----------------- Upload Interface ----------------- #
18
- uploaded_file = st.file_uploader("Upload a lease agreement (PDF, PNG, JPG)", type=["pdf", "png", "jpg", "jpeg"])
19
-
20
- def upload_file_to_openai(file_bytes, filename):
21
- files = {
22
- "file": (filename, file_bytes, "application/octet-stream")
23
- }
24
- response = requests.post("https://api.openai.com/v1/files", headers={"Authorization": f"Bearer {OPENAI_API_KEY}"}, files=files)
25
- return response.json().get("id")
26
-
27
- def create_thread_and_run(file_id):
28
- # Create thread
29
- thread_res = requests.post("https://api.openai.com/v1/threads", headers=HEADERS, json={})
30
- thread_data = thread_res.json()
31
- thread_id = thread_data.get("id")
32
-
33
- # Run assistant with structured lease extraction prompt
34
- run_payload = {
35
- "assistant_id": OPENAI_ASSISTANT_ID,
36
- "instructions": """
37
- You are an AI assistant designed to extract structured lease data from any uploaded agreement, even if it is scanned. Your responsibilities are:
38
-
39
- - Accept PDF or image-based lease documents.
40
- - Use OCR (Optical Character Recognition) if the document is non-selectable or scanned.
41
- - Parse and extract key structured data fields from lease agreements.
42
- - Output results in JSON format following a fixed schema.
43
-
44
- OCR & Extraction Rules:
45
- 1. If text cannot be extracted normally, perform OCR using image-to-text on each page.
46
- 2. Preserve paragraph structure, detect headers, signature blocks, tables, and dates.
47
-
48
- Expected JSON Output:
49
- {
50
- "document_title": "Lease Agreement Title or File Name",
51
- "parties": {
52
- "lessor": "Name of Lessor",
53
- "lessee": "Name of Lessee"
54
- },
55
- "property_description": "Detailed description of leased property",
56
- "term": {
57
- "start_date": "YYYY-MM-DD",
58
- "end_date": "YYYY-MM-DD",
59
- "renewal_options": "Yes/No or Clause Text"
60
- },
61
- "financials": {
62
- "monthly_rent": "Amount",
63
- "deposit": "Amount",
64
- "escalation_clause": "Text if present"
65
- },
66
- "obligations": {
67
- "maintenance": "Lessor/Lessee",
68
- "insurance": "Lessor/Lessee",
69
- "subletting": "Allowed/Not allowed"
70
- },
71
- "signatures": [
72
- {
73
- "party": "Party Name",
74
- "signed_by": "Full Name",
75
- "date": "YYYY-MM-DD",
76
- "position": "Title if listed"
77
- }
78
- ],
79
- "raw_text_per_page": {
80
- "page_1": "Full OCR text of page 1",
81
- "page_2": "Full OCR text of page 2"
82
- }
83
- }
84
-
85
- πŸ“Œ Behaviors:
86
- - Automatically detect if OCR is needed.
87
- - Normalize dates to YYYY-MM-DD.
88
- - Use "Not Found" or null where info is missing.
89
- - Return JSON only.
90
- - If OCR text is low-confidence, include: "low_confidence": true
91
- """,
92
- "file_ids": [file_id]
93
- }
94
-
95
- run_res = requests.post(f"https://api.openai.com/v1/threads/{thread_id}/runs", headers=HEADERS, json=run_payload)
96
- run_data = run_res.json()
97
- run_id = run_data.get("id")
98
-
99
- return thread_id, run_id
100
-
101
- # ----------------- Main UI Flow ----------------- #
102
- if uploaded_file is not None:
103
- st.success("πŸ“„ File uploaded successfully.")
104
-
105
- if st.button("πŸš€ Run Lease Extraction"):
106
- with st.spinner("Uploading and invoking assistant..."):
107
- file_id = upload_file_to_openai(uploaded_file.getvalue(), uploaded_file.name)
108
- if file_id:
109
- thread_id, run_id = create_thread_and_run(file_id)
110
- st.success("βœ… Assistant run started!")
111
- st.code(f"Thread ID: {thread_id}", language="text")
112
- st.code(f"Run ID: {run_id}", language="text")
113
- st.markdown("πŸ“Œ You can retrieve results via OpenAI API using these IDs.")
114
- else:
115
- st.error("❌ File upload to OpenAI failed.")
116
- else:
117
- st.info("Please upload a lease agreement file to begin.")
 
 
 
1
+ import streamlit as st
2
+ from openai import OpenAI
3
+ import time
4
+ import os
5
+ import uuid
6
+ import firebase_admin
7
+ from firebase_admin import credentials, firestore
8
+
9
+ # πŸ” Firebase setup
10
+ if not firebase_admin._apps:
11
+ cred = credentials.Certificate("firebase-service-account.json")
12
+ firebase_admin.initialize_app(cred)
13
+
14
+ db = firestore.client()
15
+
16
+ # πŸ” OpenAI setup
17
+ openai_key = os.getenv("openai_key")
18
+ assistant_id = os.getenv("ASSISTANT_ID")
19
+ client = OpenAI(api_key=openai_key)
20
+
21
+ # 🌐 Streamlit Config
22
+ st.set_page_config(page_title="LeaseTracker AI Assistant", layout="wide")
23
+
24
+ # 🎯 Session + User ID
25
+ if "user_id" not in st.session_state:
26
+ st.session_state["user_id"] = str(uuid.uuid4())
27
+ user_id = st.session_state["user_id"]
28
+
29
+ # πŸ–ΌοΈ LeaseTracker Branding + Styling
30
+ st.markdown("""
31
+ <style>
32
+ .block-container {padding-top: 1rem; padding-bottom: 0rem;}
33
+ header {visibility: hidden;}
34
+ .stChatMessage { max-width: 85%; border-radius: 12px; padding: 8px; margin-bottom: 10px; }
35
+ .stChatMessage[data-testid="stChatMessage-user"] { background: #f0f0f0; color: #000000; }
36
+ .stChatMessage[data-testid="stChatMessage-assistant"] { background: #e3f2fd; color: #000000; }
37
+ .lt-logo { vertical-align: middle; }
38
+ </style>
39
+ """, unsafe_allow_html=True)
40
+
41
+ st.markdown("""
42
+ <div style='text-align: center; margin-top: 20px; margin-bottom: -10px;'>
43
+ <span style='display: inline-flex; align-items: center; gap: 8px;'>
44
+ <img src='https://leasetracker.co.za/wp-content/uploads/2020/10/Lease-Tracker-Logo.png' width='100' class='lt-logo'/>
45
+ <span style='font-size: 12px; color: gray;'>Powered by LeaseTracker</span>
46
+ </span>
47
+ </div>
48
+ """, unsafe_allow_html=True)
49
+
50
+ # πŸ” Get or create a thread ID
51
+ def get_or_create_thread_id():
52
+ doc_ref = db.collection("users").document(user_id)
53
+ doc = doc_ref.get()
54
+ if doc.exists:
55
+ return doc.to_dict()["thread_id"]
56
+ else:
57
+ thread = client.beta.threads.create()
58
+ doc_ref.set({"thread_id": thread.id, "created_at": firestore.SERVER_TIMESTAMP})
59
+ return thread.id
60
+
61
+ # πŸ’Ύ Save a message
62
+ def save_message(role, content):
63
+ db.collection("users").document(user_id).collection("messages").add({
64
+ "role": role,
65
+ "content": content,
66
+ "timestamp": firestore.SERVER_TIMESTAMP
67
+ })
68
+
69
+ # πŸ’¬ Display chat history
70
+ def display_chat_history():
71
+ messages = db.collection("users").document(user_id).collection("messages").order_by("timestamp").stream()
72
+ assistant_icon_html = "<img src='https://leasetracker.co.za/wp-content/uploads/2020/10/Lease-Tracker-Logo.png' width='20' style='vertical-align:middle;'/>"
73
+ for msg in list(messages)[::-1]:
74
+ data = msg.to_dict()
75
+ if data["role"] == "user":
76
+ st.markdown(f"<div class='stChatMessage' data-testid='stChatMessage-user'>πŸ‘€ <strong>You:</strong> {data['content']}</div>", unsafe_allow_html=True)
77
+ else:
78
+ st.markdown(f"<div class='stChatMessage' data-testid='stChatMessage-assistant'>{assistant_icon_html} <strong>LeaseTracker Assistant:</strong> {data['content']}</div>", unsafe_allow_html=True)
79
+
80
+ # πŸš€ Main Chat UI
81
+ input_col, clear_col = st.columns([9, 1])
82
+ with input_col:
83
+ user_input = st.chat_input("Type your message here...")
84
+
85
+ with clear_col:
86
+ if st.button("πŸ—‘οΈ", key="clear-chat", help="Clear Chat"):
87
+ try:
88
+ user_doc_ref = db.collection("users").document(user_id)
89
+ for msg in user_doc_ref.collection("messages").stream():
90
+ msg.reference.delete()
91
+ user_doc_ref.delete()
92
+ st.session_state.clear()
93
+ st.rerun()
94
+ except Exception as e:
95
+ st.error(f"Failed to clear chat: {e}")
96
+
97
+ thread_id = get_or_create_thread_id()
98
+ display_chat_history()
99
+
100
+ if user_input:
101
+ # Send user message to OpenAI thread
102
+ client.beta.threads.messages.create(thread_id=thread_id, role="user", content=user_input)
103
+ save_message("user", user_input)
104
+
105
+ with st.spinner("Thinking and typing... πŸ’­"):
106
+ run = client.beta.threads.runs.create(thread_id=thread_id, assistant_id=assistant_id)
107
+ while True:
108
+ run_status = client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)
109
+ if run_status.status == "completed":
110
+ break
111
+ time.sleep(1)
112
+
113
+ messages_response = client.beta.threads.messages.list(thread_id=thread_id)
114
+ latest_response = sorted(messages_response.data, key=lambda x: x.created_at)[-1]
115
+ assistant_message = latest_response.content[0].text.value
116
+
117
+ save_message("assistant", assistant_message)
118
+ time.sleep(0.5)
119
+ st.rerun()