IAMTFRMZA commited on
Commit
209a87c
Β·
verified Β·
1 Parent(s): 717ae43

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -155
app.py CHANGED
@@ -1,170 +1,97 @@
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 DocAIA", 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}")
 
1
+ import gradio as gr
2
  import os
3
+ import json
 
 
 
 
 
4
  from openai import OpenAI
5
 
6
+ # ========== Config ==========
 
 
 
 
 
7
  OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
8
  ASSISTANT_ID = os.environ.get("ASSISTANT_ID")
 
 
 
 
 
9
  client = OpenAI(api_key=OPENAI_API_KEY)
10
 
11
+ # ========== Functions ==========
12
+ def query_assistant(message):
13
+ try:
14
+ thread = client.beta.threads.create()
15
+ client.beta.threads.messages.create(
16
+ thread_id=thread.id,
17
+ role="user",
18
+ content=message
19
+ )
20
+ run = client.beta.threads.runs.create(
21
+ thread_id=thread.id,
22
+ assistant_id=ASSISTANT_ID
23
+ )
24
+
25
+ # Poll run status
26
+ while True:
27
+ run_status = client.beta.threads.runs.retrieve(
28
+ thread_id=thread.id,
29
+ run_id=run.id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  )
31
+ if run_status.status in ("completed", "failed", "cancelled"):
32
+ break
33
+
34
+ if run_status.status != "completed":
35
+ return f"❌ Assistant failed: {run_status.status}", []
36
+
37
+ messages = client.beta.threads.messages.list(thread_id=thread.id)
38
+ for m in reversed(messages.data):
39
+ if m.role == "assistant":
40
+ try:
41
+ parsed = json.loads(m.content[0].text.value)
42
+ return None, parsed.get("results", [])
43
+ except Exception as e:
44
+ return f"⚠️ Failed to parse assistant response: {e}", []
45
+ return "⚠️ No assistant response found", []
46
+ except Exception as e:
47
+ return f"❌ Error: {e}", []
48
+
49
+ def render_cards(results):
50
+ cards = []
51
+ for r in results:
52
+ title = r.get("drawing_title", "Untitled")
53
+ summary = r.get("summary", "")
54
+ pages = r.get("pages", [])
55
+ image_elems = []
56
+ for page in pages:
57
+ url = page.get("public_image_url")
58
+ label = f"{title} – Page {page.get('page_number')}"
59
+ if url:
60
+ image_elems.append(gr.Image(value=url, label=label, show_label=True))
61
+
62
+ with gr.Column(scale=1):
63
+ cards.append(
64
+ gr.Group(
65
+ [
66
+ gr.Markdown(f"**{title}**\n\n**Summary:** {summary}"),
67
+ gr.Accordion("πŸ“‚ View Drawing Pages", open=False, children=image_elems)
68
+ ]
69
+ )
70
  )
71
+ return cards
72
 
73
+ def on_query_submit(prompt):
74
+ status, results = query_assistant(prompt)
75
+ if status:
76
+ return status, []
77
+ return "βœ… Found matching drawings", render_cards(results)
 
 
 
 
78
 
79
+ # ========== Interface ==========
80
+ with gr.Blocks(theme=gr.themes.Base(), title="Forrestdale Technical Drawing Assistant") as app:
81
+ gr.Markdown("""
82
+ # πŸ—οΈ Forrestdale Technical Drawing Assistant
83
+ Ask about plans, drawings or components (e.g. _Show me all electrical plans_)
84
+ """)
 
 
85
 
86
+ with gr.Row():
87
+ query_input = gr.Textbox(placeholder="e.g. Show me all civil drainage plans", scale=5)
88
+ query_button = gr.Button("πŸ” Search", scale=1)
 
 
 
 
 
 
 
 
 
 
 
89
 
90
+ status_output = gr.Markdown("", visible=True)
91
+ result_display = gr.Column()
 
92
 
93
+ query_button.click(fn=on_query_submit, inputs=query_input, outputs=[status_output, result_display])
 
 
94
 
95
+ # ========== Launch ==========
96
+ if __name__ == "__main__":
97
+ app.launch()