IAMTFRMZA commited on
Commit
a68bccd
Β·
verified Β·
1 Parent(s): 1d2d6d2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +151 -74
app.py CHANGED
@@ -1,93 +1,170 @@
1
  import streamlit as st
2
  import os
3
  import time
4
- import json
 
 
 
 
5
  from openai import OpenAI
6
 
7
- # App config
8
- st.set_page_config(page_title="πŸ—οΈ Forrestdale Technical Drawing Assistant", layout="wide")
9
- st.title("πŸ—οΈ Forrestdale Technical Drawing Assistant")
10
- st.caption("Ask about plans, drawings or components")
11
 
12
- # Load secrets
13
  OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
14
  ASSISTANT_ID = os.environ.get("ASSISTANT_ID")
15
- client = OpenAI(api_key=OPENAI_API_KEY)
16
 
17
  if not OPENAI_API_KEY or not ASSISTANT_ID:
18
- st.error("Missing secrets: OPENAI_API_KEY or ASSISTANT_ID")
19
  st.stop()
20
 
21
- # Session setup
 
 
22
  if "messages" not in st.session_state:
23
  st.session_state.messages = []
24
  if "thread_id" not in st.session_state:
25
  st.session_state.thread_id = None
 
 
 
 
 
 
26
 
27
- # Prompt input
28
- prompt = st.chat_input("Ask something like 'Show me all civil drawings' or 'Where are the switchboards?'")
29
- if prompt:
30
- st.session_state.messages.append({"role": "user", "content": prompt})
31
-
32
- # Chat + assistant processing
33
- if st.session_state.messages and st.session_state.messages[-1]["role"] == "user":
34
- if st.session_state.thread_id is None:
35
- thread = client.beta.threads.create()
36
- st.session_state.thread_id = thread.id
37
-
38
- client.beta.threads.messages.create(
39
- thread_id=st.session_state.thread_id,
40
- role="user",
41
- content=st.session_state.messages[-1]["content"]
42
- )
43
- run = client.beta.threads.runs.create(
44
- thread_id=st.session_state.thread_id,
45
- assistant_id=ASSISTANT_ID
46
- )
47
-
48
- with st.spinner("πŸ”Ž Querying assistant..."):
49
- while True:
50
- run_status = client.beta.threads.runs.retrieve(thread_id=st.session_state.thread_id, run_id=run.id)
51
- if run_status.status in ("completed", "failed", "cancelled"):
52
- break
53
- time.sleep(1)
54
-
55
- if run_status.status != "completed":
56
- st.error(f"Assistant failed: {run_status.status}")
57
- else:
58
- messages = client.beta.threads.messages.list(thread_id=st.session_state.thread_id)
59
- for message in reversed(messages.data):
60
- if message.role == "assistant":
61
- try:
62
- raw = message.content[0].text.value.strip()
63
- if raw.startswith("```json"):
64
- raw = raw.removeprefix("```json").removesuffix("```").strip()
65
- data = json.loads(raw)
66
- st.session_state.messages.append({"role": "assistant", "content": data})
67
- except Exception as e:
68
- st.error(f"⚠️ Could not parse assistant response as JSON.\n{e}")
69
- break
70
  st.rerun()
71
 
72
- # Card view rendering
73
- for msg in st.session_state.messages:
74
- if msg["role"] == "assistant":
75
- data = msg["content"]
76
- if isinstance(data, list) and len(data) > 0:
77
- st.markdown("### πŸ“‹ Results")
78
- cols = st.columns(4)
79
- for i, item in enumerate(data):
80
- with cols[i % 4]:
81
- st.image(item.get("images", [item.get("image")])[0], caption=item["drawing_number"], use_column_width=True)
82
- with st.expander("Details"):
83
- st.write(f"**Discipline:** {item['discipline']}")
84
- st.write(f"**Summary:** {item['summary']}")
85
- if "question" in item:
86
- st.write(f"**Question:** {item['question']}")
87
- for idx, img in enumerate(item.get("images", [])):
88
- st.image(img, caption=f"Page {idx+1}", use_column_width=True)
89
- else:
90
- st.warning("Assistant returned no matching drawings.")
91
- elif msg["role"] == "user":
92
- with st.chat_message("user"):
93
- st.markdown(msg["content"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
  import os
3
  import time
4
+ import re
5
+ import requests
6
+ from PIL import Image
7
+ from io import BytesIO
8
+ from urllib.parse import quote
9
  from openai import OpenAI
10
 
11
+ # ------------------ App Configuration ------------------
12
+ st.set_page_config(page_title="Schlaeger Forrestdale TechDocAIA", layout="wide", initial_sidebar_state="collapsed")
13
+ st.title("πŸ“„ Schlaeger Forrestdale Document Assistant")
14
+ st.caption("Explore City of Armadale construction documents using AI + OCR 🧠")
15
 
16
+ # ------------------ Load API Key and Assistant ID ------------------
17
  OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
18
  ASSISTANT_ID = os.environ.get("ASSISTANT_ID")
 
19
 
20
  if not OPENAI_API_KEY or not ASSISTANT_ID:
21
+ st.error("❌ Missing secrets. Please set both OPENAI_API_KEY and ASSISTANT_ID in Hugging Face Space secrets.")
22
  st.stop()
23
 
24
+ client = OpenAI(api_key=OPENAI_API_KEY)
25
+
26
+ # ------------------ Session State Initialization ------------------
27
  if "messages" not in st.session_state:
28
  st.session_state.messages = []
29
  if "thread_id" not in st.session_state:
30
  st.session_state.thread_id = None
31
+ if "image_url" not in st.session_state:
32
+ st.session_state.image_url = None
33
+ if "image_updated" not in st.session_state:
34
+ st.session_state.image_updated = False
35
+ if "pending_prompt" not in st.session_state:
36
+ st.session_state.pending_prompt = None
37
 
38
+ # ------------------ Sidebar ------------------
39
+ st.sidebar.header("ℹ️ Information")
40
+ if st.sidebar.button("🧹 Clear Chat"):
41
+ st.session_state.messages = []
42
+ st.session_state.thread_id = None
43
+ st.session_state.image_url = None
44
+ st.session_state.image_updated = False
45
+ st.session_state.pending_prompt = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  st.rerun()
47
 
48
+ show_image = st.sidebar.toggle("πŸ“‘ Show Page Image", value=True)
49
+
50
+ st.sidebar.subheader("πŸ“˜ Document Tools")
51
+ st.sidebar.markdown("Use the tools below to locate relevant clauses and actions:")
52
+
53
+ keyword = st.sidebar.text_input("Search by Keyword", placeholder="e.g. defects, WHS, delay")
54
+ if st.sidebar.button("πŸ”Ž Search Keyword") and keyword:
55
+ st.session_state.pending_prompt = f"Find clauses or references related to: {keyword}"
56
+
57
+ section_options = [
58
+ "Select a section...",
59
+ "1. Formal Instrument of Contract",
60
+ "2. Offer and Acceptance",
61
+ "3. Key Personnel",
62
+ "4. Contract Pricing",
63
+ "5. Specifications",
64
+ "6. WHS Policies",
65
+ "7. Penalties and Delays",
66
+ "8. Dispute Resolution",
67
+ "9. Principal Obligations"
68
+ ]
69
+ section_select = st.sidebar.selectbox("πŸ“„ Jump to Section", section_options)
70
+ if section_select != section_options[0]:
71
+ st.session_state.pending_prompt = f"Summarize or list key points from section: {section_select}"
72
+
73
+ actions = [
74
+ "Select an action...",
75
+ "List all contractual obligations",
76
+ "Summarize payment terms",
77
+ "List WHS responsibilities",
78
+ "Find delay-related penalties",
79
+ "Extract dispute resolution steps"
80
+ ]
81
+ action_select = st.sidebar.selectbox("βš™οΈ Common Contract Queries", actions)
82
+ if action_select != actions[0]:
83
+ st.session_state.pending_prompt = action_select
84
+
85
+ # ------------------ Layout: Chat + Image ------------------
86
+ chat_col, image_col = st.columns([2, 1])
87
+
88
+ # ------------------ Chat Interface ------------------
89
+ with chat_col:
90
+ st.markdown("### 🧠 Ask a Document-Specific Question")
91
+ user_prompt = st.chat_input("Example: What is the defects liability period?")
92
+
93
+ # Use pending prompt from sidebar if no new chat prompt
94
+ if user_prompt:
95
+ st.session_state.messages.append({"role": "user", "content": user_prompt})
96
+ elif st.session_state.pending_prompt:
97
+ st.session_state.messages.append({"role": "user", "content": st.session_state.pending_prompt})
98
+ st.session_state.pending_prompt = None
99
+
100
+ if st.session_state.messages and st.session_state.messages[-1]["role"] == "user":
101
+ try:
102
+ if st.session_state.thread_id is None:
103
+ thread = client.beta.threads.create()
104
+ st.session_state.thread_id = thread.id
105
+
106
+ client.beta.threads.messages.create(
107
+ thread_id=st.session_state.thread_id,
108
+ role="user",
109
+ content=st.session_state.messages[-1]["content"]
110
+ )
111
+
112
+ run = client.beta.threads.runs.create(
113
+ thread_id=st.session_state.thread_id,
114
+ assistant_id=ASSISTANT_ID
115
+ )
116
+
117
+ with st.spinner("πŸ€– Parsing and responding with referenced content..."):
118
+ while True:
119
+ run_status = client.beta.threads.runs.retrieve(
120
+ thread_id=st.session_state.thread_id,
121
+ run_id=run.id
122
+ )
123
+ if run_status.status in ("completed", "failed", "cancelled"):
124
+ break
125
+ time.sleep(1)
126
+
127
+ if run_status.status != "completed":
128
+ st.error(f"⚠️ Assistant failed: {run_status.status}")
129
+ else:
130
+ messages = client.beta.threads.messages.list(thread_id=st.session_state.thread_id)
131
+ for message in reversed(messages.data):
132
+ if message.role == "assistant":
133
+ assistant_reply = message.content[0].text.value
134
+ st.session_state.messages.append({"role": "assistant", "content": assistant_reply})
135
+
136
+ # Parse Document Reference and Page, then construct image URL with encoding
137
+ match = re.search(r'Document Reference:\s*(.*?),\s*Page\s*(\d+)', assistant_reply)
138
+ if match:
139
+ doc_name = match.group(1).strip()
140
+ page = int(match.group(2))
141
+ page_str = f"{page:04d}"
142
+ folder = quote(doc_name)
143
+ image_url = (
144
+ f"https://raw.githubusercontent.com/AndrewLORTech/c2ozschlaegerforrestdale/main/"
145
+ f"{folder}/{folder}_page_{page_str}.png"
146
+ )
147
+ st.session_state.image_url = image_url
148
+ st.session_state.image_updated = True
149
+ break
150
+
151
+ st.rerun()
152
+ except Exception as e:
153
+ st.error(f"❌ Error: {e}")
154
+
155
+ for msg in st.session_state.messages:
156
+ with st.chat_message(msg["role"]):
157
+ st.markdown(msg["content"], unsafe_allow_html=True)
158
+
159
+ # ------------------ Image Display ------------------
160
+ with image_col:
161
+ if show_image and st.session_state.image_url:
162
+ with st.spinner("Loading document preview..."):
163
+ try:
164
+ response = requests.get(st.session_state.image_url)
165
+ response.raise_for_status()
166
+ img = Image.open(BytesIO(response.content))
167
+ st.image(img, caption="πŸ“„ OCR Page Image", use_container_width=True)
168
+ st.session_state.image_updated = False
169
+ except Exception as e:
170
+ st.error(f"❗ Failed to load image: {e}")