IAMTFRMZA commited on
Commit
d09f114
Β·
verified Β·
1 Parent(s): 3d9fd27

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +201 -62
app.py CHANGED
@@ -1,95 +1,234 @@
1
  import streamlit as st
2
  import os
3
  import time
4
- import json
5
  import re
 
 
 
 
 
6
  from openai import OpenAI
7
 
8
- # Basic config
9
- st.set_page_config(page_title="Forrestdale Drawing Viewer", layout="wide")
10
- st.title("πŸ“ Forrestdale Technical Drawing Assistant")
 
11
 
 
12
  OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
13
- ASSISTANT_ID = "asst_DjvuWBc7tCvMbAhY7n1em4BZ"
14
-
15
  if not OPENAI_API_KEY:
16
- st.error("❌ Missing OPENAI_API_KEY.")
17
  st.stop()
18
 
19
  client = OpenAI(api_key=OPENAI_API_KEY)
20
 
21
- if "tech_thread_id" not in st.session_state:
22
- thread = client.beta.threads.create()
23
- st.session_state.tech_thread_id = thread.id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- if "tech_messages" not in st.session_state:
26
- st.session_state.tech_messages = []
 
 
 
 
27
 
28
- prompt = st.chat_input("Ask about plans, drawings or components (e.g. Show me all electrical plans)")
29
- if prompt:
30
- st.session_state.tech_messages.append({"role": "user", "content": prompt})
 
 
 
 
 
 
 
 
 
31
 
32
- for msg in st.session_state.tech_messages:
33
- with st.chat_message(msg["role"]):
34
- st.markdown(msg["content"])
35
 
36
- # Fetch assistant reply
37
- if st.session_state.tech_messages and st.session_state.tech_messages[-1]["role"] == "user":
38
- with st.spinner("⏳ Fetching results from assistant..."):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  try:
 
 
 
 
40
  client.beta.threads.messages.create(
41
  thread_id=st.session_state.tech_thread_id,
42
  role="user",
43
  content=st.session_state.tech_messages[-1]["content"]
44
  )
45
-
46
  run = client.beta.threads.runs.create(
47
  thread_id=st.session_state.tech_thread_id,
48
  assistant_id=ASSISTANT_ID
49
  )
50
 
51
- while True:
52
- run_status = client.beta.threads.runs.retrieve(
53
- thread_id=st.session_state.tech_thread_id,
54
- run_id=run.id
55
- )
56
- if run_status.status in ["completed", "failed", "cancelled"]:
57
- break
58
- time.sleep(1)
 
59
 
60
- if run_status.status != "completed":
61
- st.error("⚠️ Assistant run failed.")
62
- else:
63
  messages = client.beta.threads.messages.list(thread_id=st.session_state.tech_thread_id)
64
- for message in reversed(messages.data):
65
- if message.role == "assistant":
66
- reply_content = message.content[0].text.value.strip()
67
- st.session_state.tech_messages.append({"role": "assistant", "content": reply_content})
68
-
69
  try:
70
- match = re.search(r"```json\s*(.*?)```", reply_content, re.DOTALL)
71
- json_str = match.group(1) if match else reply_content
72
- results = json.loads(json_str)
73
-
74
- if isinstance(results, list):
75
- cols = st.columns(4)
76
- for idx, item in enumerate(results):
77
- with cols[idx % 4]:
78
- with st.container(border=True):
79
- st.markdown(f"### πŸ“ {item.get('drawing_number')} ({item.get('discipline')})", help=item.get("summary"))
80
- st.caption(item.get("summary"))
81
-
82
- with st.expander("πŸ“‚ View Drawing Details"):
83
- if item.get("question"):
84
- st.markdown(f"**Question Match:** {item.get('question')}")
85
- if item.get("image"):
86
- st.image(item.get("image"), caption=item.get("drawing_number"))
87
- elif item.get("images"):
88
- for i, img in enumerate(item["images"]):
89
- if img.startswith("http"):
90
- st.image(img, caption=f"{item['drawing_number']} – Page {i+1}")
91
- except Exception as parse_error:
92
- st.warning("🟑 Could not parse assistant response as JSON.")
93
  break
 
 
 
94
  except Exception as e:
95
- st.error(f"❌ Error occurred: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
  import os
3
  import time
 
4
  import re
5
+ import requests
6
+ import json
7
+ from PIL import Image
8
+ from io import BytesIO
9
+ from urllib.parse import quote
10
  from openai import OpenAI
11
 
12
+ # ------------------ App Configuration ------------------
13
+ st.set_page_config(page_title="Schlaeger Forrestdale DocAIA", layout="wide", initial_sidebar_state="collapsed")
14
+ st.title("πŸ“„ Schlaeger Forrestdale Document Assistant")
15
+ st.caption("Explore City of Armadale construction documents using AI + OCR 🧠")
16
 
17
+ # ------------------ Load API Key ------------------
18
  OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
 
 
19
  if not OPENAI_API_KEY:
20
+ st.error("❌ Missing OPENAI_API_KEY in Hugging Face Space secrets.")
21
  st.stop()
22
 
23
  client = OpenAI(api_key=OPENAI_API_KEY)
24
 
25
+ # ------------------ Tabs ------------------
26
+ tabs = st.tabs(["πŸ“œ Contract", "πŸ“ Technical"])
27
+
28
+ # ================== CONTRACT TAB ==================
29
+ with tabs[0]:
30
+ ASSISTANT_ID = "asst_KsQRedoJUnEeStzfox1o06lO"
31
+ if "contract_messages" not in st.session_state:
32
+ st.session_state.contract_messages = []
33
+ if "contract_thread_id" not in st.session_state:
34
+ st.session_state.contract_thread_id = None
35
+ if "image_url" not in st.session_state:
36
+ st.session_state.image_url = None
37
+
38
+ st.sidebar.header("πŸ“˜ Contract Tools")
39
+ if st.sidebar.button("🧹 Clear Chat", key="clear_contract"):
40
+ st.session_state.contract_messages = []
41
+ st.session_state.contract_thread_id = None
42
+ st.session_state.image_url = None
43
+ st.rerun()
44
+
45
+ show_image = st.sidebar.toggle("πŸ“‘ Show Page Image", value=True, key="show_image_toggle")
46
+
47
+ keyword = st.sidebar.text_input("Search by Keyword", key="kw")
48
+ if st.sidebar.button("πŸ”Ž Search", key="kw_btn") and keyword:
49
+ st.session_state.contract_messages.append({"role": "user", "content": f"Find clauses or references related to: {keyword}"})
50
+
51
+ section = st.sidebar.selectbox("πŸ“„ Jump to Section", [
52
+ "Select a section...",
53
+ "1. Formal Instrument of Contract",
54
+ "2. Offer and Acceptance",
55
+ "3. Key Personnel",
56
+ "4. Contract Pricing",
57
+ "5. Specifications",
58
+ "6. WHS Policies",
59
+ "7. Penalties and Delays",
60
+ "8. Dispute Resolution",
61
+ "9. Principal Obligations"
62
+ ])
63
+ if section != "Select a section...":
64
+ st.session_state.contract_messages.append({"role": "user", "content": f"Summarize or list key points from section: {section}"})
65
+
66
+ action = st.sidebar.selectbox("βš™οΈ Common Queries", [
67
+ "Select an action...",
68
+ "List all contractual obligations",
69
+ "Summarize payment terms",
70
+ "List WHS responsibilities",
71
+ "Find delay-related penalties",
72
+ "Extract dispute resolution steps"
73
+ ])
74
+ if action != "Select an action...":
75
+ st.session_state.contract_messages.append({"role": "user", "content": action})
76
+
77
+ chat_col, img_col = st.columns([2, 1])
78
+ with chat_col:
79
+ st.markdown("### 🧠 Ask Contract Document Question")
80
+ user_input = st.chat_input("Ask something about the contract")
81
+ if user_input:
82
+ st.session_state.contract_messages.append({"role": "user", "content": user_input})
83
+
84
+ if st.session_state.contract_messages and st.session_state.contract_messages[-1]["role"] == "user":
85
+ try:
86
+ if st.session_state.contract_thread_id is None:
87
+ thread = client.beta.threads.create()
88
+ st.session_state.contract_thread_id = thread.id
89
+
90
+ client.beta.threads.messages.create(
91
+ thread_id=st.session_state.contract_thread_id,
92
+ role="user",
93
+ content=st.session_state.contract_messages[-1]["content"]
94
+ )
95
+ run = client.beta.threads.runs.create(
96
+ thread_id=st.session_state.contract_thread_id,
97
+ assistant_id=ASSISTANT_ID
98
+ )
99
+
100
+ with st.spinner("πŸ€– Analyzing contract..."):
101
+ while True:
102
+ status = client.beta.threads.runs.retrieve(
103
+ thread_id=st.session_state.contract_thread_id,
104
+ run_id=run.id
105
+ )
106
+ if status.status in ("completed", "failed", "cancelled"):
107
+ break
108
+ time.sleep(1)
109
 
110
+ if status.status == "completed":
111
+ messages = client.beta.threads.messages.list(thread_id=st.session_state.contract_thread_id)
112
+ for m in reversed(messages.data):
113
+ if m.role == "assistant":
114
+ content = m.content[0].text.value
115
+ st.session_state.contract_messages.append({"role": "assistant", "content": content})
116
 
117
+ match = re.search(r'Document Reference:\s*(.*?),\s*Page\s*(\d+)', content)
118
+ if match:
119
+ doc, page = match.group(1).strip(), int(match.group(2))
120
+ page_str = f"{page:04d}"
121
+ folder = quote(doc)
122
+ st.session_state.image_url = f"https://raw.githubusercontent.com/AndrewLORTech/c2ozschlaegerforrestdale/main/{folder}/{folder}_page_{page_str}.png"
123
+ break
124
+ else:
125
+ st.error("❌ Assistant failed.")
126
+ st.rerun()
127
+ except Exception as e:
128
+ st.error(f"❌ Error: {e}")
129
 
130
+ for msg in st.session_state.contract_messages:
131
+ with st.chat_message(msg["role"]):
132
+ st.markdown(msg["content"], unsafe_allow_html=True)
133
 
134
+ with img_col:
135
+ if show_image and st.session_state.image_url:
136
+ with st.spinner("Loading image..."):
137
+ try:
138
+ res = requests.get(st.session_state.image_url)
139
+ img = Image.open(BytesIO(res.content))
140
+ st.image(img, caption="πŸ“„ OCR Page Image", use_container_width=True)
141
+ except:
142
+ st.error("⚠️ Failed to load image.")
143
+
144
+ # ================== TECHNICAL TAB ==================
145
+ with tabs[1]:
146
+ ASSISTANT_ID = "asst_DjvuWBc7tCvMbAhY7n1em4BZ"
147
+ if "tech_messages" not in st.session_state:
148
+ st.session_state.tech_messages = []
149
+ if "tech_thread_id" not in st.session_state:
150
+ st.session_state.tech_thread_id = None
151
+ if "results" not in st.session_state:
152
+ st.session_state.results = []
153
+ if "lightbox_url" not in st.session_state:
154
+ st.session_state.lightbox_url = None
155
+
156
+ prompt = st.chat_input("Ask about plans, drawings or components")
157
+ if prompt:
158
+ st.session_state.tech_messages.append({"role": "user", "content": prompt})
159
+
160
+ if st.session_state.tech_messages and st.session_state.tech_messages[-1]["role"] == "user":
161
  try:
162
+ if st.session_state.tech_thread_id is None:
163
+ thread = client.beta.threads.create()
164
+ st.session_state.tech_thread_id = thread.id
165
+
166
  client.beta.threads.messages.create(
167
  thread_id=st.session_state.tech_thread_id,
168
  role="user",
169
  content=st.session_state.tech_messages[-1]["content"]
170
  )
 
171
  run = client.beta.threads.runs.create(
172
  thread_id=st.session_state.tech_thread_id,
173
  assistant_id=ASSISTANT_ID
174
  )
175
 
176
+ with st.spinner("πŸ€– Querying technical documents..."):
177
+ while True:
178
+ status = client.beta.threads.runs.retrieve(
179
+ thread_id=st.session_state.tech_thread_id,
180
+ run_id=run.id
181
+ )
182
+ if status.status in ("completed", "failed", "cancelled"):
183
+ break
184
+ time.sleep(1)
185
 
186
+ if status.status == "completed":
 
 
187
  messages = client.beta.threads.messages.list(thread_id=st.session_state.tech_thread_id)
188
+ for m in reversed(messages.data):
189
+ if m.role == "assistant":
190
+ content = m.content[0].text.value
191
+ st.session_state.tech_messages.append({"role": "assistant", "content": content})
 
192
  try:
193
+ json_data = json.loads(content.strip("`json "))
194
+ st.session_state.results = json_data
195
+ except:
196
+ st.session_state.results = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  break
198
+ else:
199
+ st.error("⚠️ Assistant failed to complete.")
200
+ st.rerun()
201
  except Exception as e:
202
+ st.error(f"❌ Error: {e}")
203
+
204
+ if st.session_state.results:
205
+ disciplines = sorted(set(d.get("discipline", "") for d in st.session_state.results))
206
+ selected = st.selectbox("🌍 Filter by discipline", ["All"] + disciplines)
207
+ page_size = 8
208
+ page_num = st.number_input("Page", min_value=1, step=1, value=1)
209
+
210
+ filtered = [r for r in st.session_state.results if selected == "All" or r.get("discipline") == selected]
211
+ paged = filtered[(page_num - 1) * page_size : page_num * page_size]
212
+
213
+ st.markdown("---")
214
+ st.subheader("πŸ“‚ Drawing Results")
215
+ cols = st.columns(4)
216
+ for i, item in enumerate(paged):
217
+ with cols[i % 4]:
218
+ st.markdown(f"**{item['drawing_number']}**")
219
+ st.markdown(f"_Discipline: {item['discipline']}_")
220
+ st.caption(item.get("summary", ""))
221
+ for url in item.get("images", [])[:1]:
222
+ if st.button("πŸ–ΌοΈ View Image", key=f"view_{i}"):
223
+ st.session_state.lightbox_url = url
224
+
225
+ if st.session_state.lightbox_url:
226
+ st.markdown("---")
227
+ st.image(st.session_state.lightbox_url, use_column_width=True, caption="πŸ” Enlarged Preview")
228
+ if st.button("❌ Close Viewer"):
229
+ st.session_state.lightbox_url = None
230
+ st.rerun()
231
+ else:
232
+ for msg in st.session_state.tech_messages:
233
+ with st.chat_message(msg["role"]):
234
+ st.markdown(msg["content"], unsafe_allow_html=True)