Adoption commited on
Commit
bb86e0c
Β·
verified Β·
1 Parent(s): 1c2af68

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +202 -185
src/streamlit_app.py CHANGED
@@ -1,233 +1,250 @@
1
  import streamlit as st
2
  import time
3
- import os
4
 
5
- # --- 1. PAGE CONFIGURATION ---
 
 
6
  st.set_page_config(
7
- page_title="Voice of the Sign",
8
- page_icon="πŸ¦…",
9
- layout="wide",
10
  initial_sidebar_state="expanded"
11
  )
12
 
13
- # --- 2. BACKEND LOADING ---
 
 
14
  backend_loaded = False
15
  try:
16
  from app import get_rag_chain, search_archives
17
  backend_loaded = True
18
- except ImportError as e:
19
- st.error(f"❌ System Error: Could not load the Brain.\n\nDetails: {e}")
20
 
21
- # --- 3. HELPER: SMART LINK GENERATOR ---
22
- def generate_sermon_link(filename):
 
 
23
  """
24
- Converts '65_1128M_Gods_Only_Provided_Place.PDF'
25
- into 'https://churchages.net/en/sermon/branham/65-1128m-gods-only-provided-place'
 
26
  """
27
- if not filename: return "#"
28
-
29
- try:
30
- # 1. Remove file extension
31
- clean_name = filename.replace(".PDF", "").replace(".pdf", "")
32
-
33
- # 2. Replace underscores with dashes (ChurchAges format)
34
- clean_name = clean_name.replace("_", "-")
35
-
36
- # 3. Lowercase everything
37
- slug = clean_name.lower()
38
-
39
- # 4. Build URL
40
- return f"https://churchages.net/en/sermon/branham/{slug}"
41
- except:
42
  return "#"
43
 
44
- # --- 4. ADAPTIVE CSS ---
 
 
 
 
 
 
 
45
  st.markdown("""
46
  <style>
47
- /* FONTS & BASICS */
48
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Playfair+Display:ital,wght@0,600;1,600&display=swap');
49
- html, body, [class*="css"] { font-family: 'Inter', sans-serif; }
50
- h1, h2, h3 { font-family: 'Playfair Display', serif !important; }
51
-
52
- /* CHAT BUBBLES */
53
- div[data-testid="stChatMessage"][data-test-role="user"] {
54
- background-color: rgba(128, 128, 128, 0.1);
55
- border-radius: 20px 20px 5px 20px;
56
- }
57
- div[data-testid="stChatMessage"][data-test-role="assistant"] {
58
- background-color: rgba(212, 175, 55, 0.05);
59
- border-radius: 20px 20px 20px 5px;
60
- border-left: 4px solid #D4AF37;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
 
62
 
63
- /* QUOTE CARDS (SEARCH) */
64
  .quote-card {
65
- padding: 20px;
66
- margin-bottom: 15px;
67
- border-radius: 12px;
68
- border-left: 5px solid #D4AF37;
69
- transition: transform 0.2s;
70
- }
71
- .quote-meta {
72
- font-size: 0.9rem;
73
- margin-bottom: 8px;
74
- font-weight: 600;
75
- }
76
- .quote-meta a {
77
- text-decoration: none;
78
- color: #D4AF37 !important; /* Gold Link */
79
- }
80
- .quote-text {
81
- font-family: 'Playfair Display', serif;
82
- font-size: 1.1rem;
83
- line-height: 1.6;
84
- font-style: italic;
85
  }
 
86
 
87
- /* DARK/LIGHT ADAPTATION */
88
- @media (prefers-color-scheme: dark) {
89
- .quote-card { background-color: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); }
90
- .quote-text { color: #E0E0E0; }
91
- }
92
- @media (prefers-color-scheme: light) {
93
- .quote-card { background-color: #FFFFFF; border: 1px solid #E0E0E0; box-shadow: 0 4px 6px rgba(0,0,0,0.05); }
94
- .quote-text { color: #2C3E50; }
95
- }
96
-
97
- header {visibility: hidden;}
98
- #MainMenu {visibility: hidden;}
99
  </style>
100
  """, unsafe_allow_html=True)
101
 
102
- # --- 5. SESSION STATE ---
 
 
 
103
  if "chat_history" not in st.session_state:
104
  st.session_state.chat_history = []
105
 
106
- # --- 6. SIDEBAR ---
 
 
 
107
  with st.sidebar:
108
  st.title("πŸ¦… Controls")
109
- mode = st.radio("Mode:", ["πŸ—£οΈ Chat", "πŸ” Search"], index=0, label_visibility="collapsed")
110
  st.divider()
 
111
  if st.button("πŸ—‘οΈ Clear Screen", use_container_width=True):
112
- if mode.startswith("πŸ—£οΈ"):
113
- st.session_state.chat_history = []
114
  st.rerun()
115
 
116
- # --- 7. HEADER ---
117
- col1, col2 = st.columns([1, 15])
118
- with col1: st.markdown("# πŸ¦…")
 
 
 
 
119
  with col2:
120
- if mode.startswith("πŸ—£οΈ"):
121
- st.markdown("# The 7th Handle")
122
- else:
123
- st.markdown("# The Table")
124
  st.divider()
125
 
126
- if not backend_loaded: st.stop()
 
 
127
 
128
- # --- 8. LOAD SYSTEM ---
 
 
129
  @st.cache_resource(show_spinner=False)
130
- def load_system():
131
  return get_rag_chain()
132
 
133
- # --- 9. MAIN LOGIC ---
134
 
135
- # === A. CHAT MODE ===
 
 
136
  if mode.startswith("πŸ—£οΈ"):
137
- # Display History
138
- for message in st.session_state.chat_history:
139
- role = message["role"]
140
- with st.chat_message(role, avatar="πŸ‘€" if role == "user" else "πŸ¦…"):
141
- st.markdown(message["content"])
142
- if "sources" in message and message["sources"]:
 
143
  with st.expander("πŸ“š References"):
144
- for doc in message["sources"]:
145
- # Handle both string filenames and document objects
146
- if hasattr(doc, 'metadata'):
147
- fname = doc.metadata.get('source', 'Unknown')
148
- para = doc.metadata.get('paragraph', '')
149
- else:
150
- fname = str(doc)
151
- para = ""
152
-
153
- link = generate_sermon_link(fname)
154
- st.markdown(f"πŸ”— [{fname} (Para {para})]({link})")
155
-
156
- # Handle Input
157
- if prompt := st.chat_input("Ask a question..."):
158
- st.session_state.chat_history.append({"role": "user", "content": prompt})
159
- with st.chat_message("user", avatar="πŸ‘€"):
160
- st.markdown(prompt)
161
-
162
- with st.chat_message("assistant", avatar="πŸ¦…"):
163
- placeholder = st.empty()
164
- full_response = ""
165
- with st.spinner("Thinking..."):
166
- try:
167
- chain = load_system()
168
- response = chain.invoke({"question": prompt})
169
- result_text = response['result']
170
-
171
- for chunk in result_text.split():
172
- full_response += chunk + " "
173
- time.sleep(0.02)
174
- placeholder.markdown(full_response + "β–Œ")
175
- placeholder.markdown(full_response)
176
-
177
- # Process Sources
178
- source_docs = response.get('source_documents', [])
179
- unique_docs = {}
180
- for doc in source_docs:
181
- src = doc.metadata.get('source', 'Unknown')
182
- if src not in unique_docs:
183
- unique_docs[src] = doc
184
-
185
- final_sources = list(unique_docs.values())
186
-
187
- if final_sources:
188
- with st.expander("πŸ“š References"):
189
- for doc in final_sources:
190
- fname = doc.metadata.get('source', 'Unknown')
191
- para = doc.metadata.get('paragraph', '')
192
- link = generate_sermon_link(fname)
193
- st.markdown(f"πŸ”— [{fname} (Para {para})]({link})")
194
-
195
- st.session_state.chat_history.append({
196
- "role": "assistant",
197
- "content": full_response,
198
- "sources": final_sources
199
- })
200
- except Exception as e:
201
- st.error(f"Error: {e}")
202
-
203
- # === B. SEARCH MODE ===
204
  else:
205
- if prompt := st.chat_input("Search for a keyword..."):
206
- st.subheader(f"Results for: '{prompt}'")
207
-
 
 
208
  with st.spinner("Scanning archives..."):
209
- docs, debug_log = search_archives(prompt)
210
-
211
- with st.expander("πŸ› οΈ Debug Info", expanded=False):
212
- for line in debug_log: st.write(line)
213
-
214
- if not docs:
215
- st.info("No exact records found.")
216
- else:
217
- for doc in docs:
218
- src = doc.metadata.get('source', 'Tape')
219
- para = doc.metadata.get('paragraph', '')
220
- txt = doc.page_content.replace(prompt, f"**{prompt}**")
221
- link = generate_sermon_link(src)
222
-
223
- # Render Card with Link
224
- st.markdown(f"""
225
- <div class="quote-card">
226
- <div class="quote-meta">
227
- <a href="{link}" target="_blank">
228
- πŸ“Ό {src} (Para {para}) β†—
229
- </a>
230
- </div>
231
- <div class="quote-text">"{txt}"</div>
232
  </div>
233
- """, unsafe_allow_html=True)
 
 
1
  import streamlit as st
2
  import time
 
3
 
4
+ # ==============================
5
+ # PAGE CONFIG
6
+ # ==============================
7
  st.set_page_config(
8
+ page_title="Voice of the Sign",
9
+ page_icon="πŸ¦…",
10
+ layout="wide",
11
  initial_sidebar_state="expanded"
12
  )
13
 
14
+ # ==============================
15
+ # LOAD BACKEND
16
+ # ==============================
17
  backend_loaded = False
18
  try:
19
  from app import get_rag_chain, search_archives
20
  backend_loaded = True
21
+ except Exception as e:
22
+ st.error(f"❌ Backend failed to load:\n\n{e}")
23
 
24
+ # ==============================
25
+ # MESSAGEHUB LINK BUILDER
26
+ # ==============================
27
+ def messagehub_link(filename: str):
28
  """
29
+ Example:
30
+ 62-0909E In His Presence.pdf
31
+ β†’ https://www.messagehub.info/en/read.do?ref_num=62-0909E
32
  """
33
+ if not filename:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  return "#"
35
 
36
+ name = filename.replace(".pdf", "").replace(".PDF", "").strip()
37
+ code = name.split()[0] # first token is sermon code
38
+ return f"https://www.messagehub.info/en/read.do?ref_num={code}"
39
+
40
+
41
+ # ==============================
42
+ # STYLING
43
+ # ==============================
44
  st.markdown("""
45
  <style>
46
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Playfair+Display:wght@600&display=swap');
47
+
48
+ html, body, [class*="css"] {
49
+ font-family: 'Inter', sans-serif;
50
+ }
51
+
52
+ h1, h2, h3 {
53
+ font-family: 'Playfair Display', serif;
54
+ }
55
+
56
+ div[data-testid="stChatMessage"][data-test-role="user"] {
57
+ background-color: rgba(128,128,128,0.08);
58
+ border-radius: 20px 20px 5px 20px;
59
+ }
60
+
61
+ div[data-testid="stChatMessage"][data-test-role="assistant"] {
62
+ background-color: rgba(212,175,55,0.05);
63
+ border-left: 4px solid #D4AF37;
64
+ border-radius: 20px 20px 20px 5px;
65
+ }
66
+
67
+ /* Improve markdown spacing */
68
+ .markdown-text-container p {
69
+ margin-bottom: 0.9em;
70
+ line-height: 1.7;
71
+ }
72
+
73
+ .markdown-text-container ul {
74
+ margin-left: 1.2em;
75
+ }
76
+
77
+ .markdown-text-container h3 {
78
+ margin-top: 1.2em;
79
+ color: #D4AF37;
80
+ }
81
+
82
+ /* Reference cards */
83
+ .quote-card {
84
+ padding: 18px;
85
+ margin-bottom: 14px;
86
+ border-radius: 12px;
87
+ border-left: 5px solid #D4AF37;
88
+ }
89
+
90
+ .quote-meta {
91
+ font-weight: 600;
92
+ margin-bottom: 6px;
93
+ }
94
+
95
+ .quote-meta a {
96
+ color: #D4AF37;
97
+ text-decoration: none;
98
+ }
99
+
100
+ .quote-text {
101
+ font-family: 'Playfair Display', serif;
102
+ font-style: italic;
103
+ line-height: 1.6;
104
+ }
105
+
106
+ @media (prefers-color-scheme: dark) {
107
+ .quote-card {
108
+ background-color: rgba(255,255,255,0.05);
109
+ border: 1px solid rgba(255,255,255,0.08);
110
  }
111
+ }
112
 
113
+ @media (prefers-color-scheme: light) {
114
  .quote-card {
115
+ background: #ffffff;
116
+ border: 1px solid #e6e6e6;
117
+ box-shadow: 0 4px 6px rgba(0,0,0,0.05);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  }
119
+ }
120
 
121
+ header, #MainMenu { visibility: hidden; }
 
 
 
 
 
 
 
 
 
 
 
122
  </style>
123
  """, unsafe_allow_html=True)
124
 
125
+
126
+ # ==============================
127
+ # SESSION STATE
128
+ # ==============================
129
  if "chat_history" not in st.session_state:
130
  st.session_state.chat_history = []
131
 
132
+
133
+ # ==============================
134
+ # SIDEBAR
135
+ # ==============================
136
  with st.sidebar:
137
  st.title("πŸ¦… Controls")
138
+ mode = st.radio("Mode", ["πŸ—£οΈ Chat", "πŸ” Search"], index=0, label_visibility="collapsed")
139
  st.divider()
140
+
141
  if st.button("πŸ—‘οΈ Clear Screen", use_container_width=True):
142
+ st.session_state.chat_history = []
 
143
  st.rerun()
144
 
145
+
146
+ # ==============================
147
+ # HEADER
148
+ # ==============================
149
+ col1, col2 = st.columns([1, 14])
150
+ with col1:
151
+ st.markdown("# πŸ¦…")
152
  with col2:
153
+ st.markdown("# The 7th Handle" if mode.startswith("πŸ—£οΈ") else "# The Table")
154
+
 
 
155
  st.divider()
156
 
157
+ if not backend_loaded:
158
+ st.stop()
159
+
160
 
161
+ # ==============================
162
+ # LOAD RAG SYSTEM
163
+ # ==============================
164
  @st.cache_resource(show_spinner=False)
165
+ def load_chain():
166
  return get_rag_chain()
167
 
 
168
 
169
+ # =========================================================
170
+ # CHAT MODE
171
+ # =========================================================
172
  if mode.startswith("πŸ—£οΈ"):
173
+
174
+ # --- Render chat history ---
175
+ for msg in st.session_state.chat_history:
176
+ with st.chat_message(msg["role"], avatar="πŸ‘€" if msg["role"] == "user" else "πŸ¦…"):
177
+ st.markdown(msg["content"], unsafe_allow_html=False)
178
+
179
+ if msg.get("sources"):
180
  with st.expander("πŸ“š References"):
181
+ for doc in msg["sources"]:
182
+ src = doc.metadata.get("source", "")
183
+ para = doc.metadata.get("paragraph", "")
184
+ link = messagehub_link(src)
185
+ st.markdown(f"πŸ”— [{src} (Para {para})]({link})")
186
+
187
+ # --- Input ---
188
+ prompt = st.chat_input("Ask a question...")
189
+
190
+ if prompt:
191
+ # Save user message
192
+ st.session_state.chat_history.append({
193
+ "role": "user",
194
+ "content": prompt
195
+ })
196
+
197
+ with st.spinner("Searching the tapes..."):
198
+ chain = load_chain()
199
+ response = chain.invoke({"question": prompt})
200
+
201
+ answer_text = response.get("result", "")
202
+ sources = response.get("source_documents", [])
203
+
204
+ # Save assistant message (FULLY formed)
205
+ st.session_state.chat_history.append({
206
+ "role": "assistant",
207
+ "content": answer_text,
208
+ "sources": sources
209
+ })
210
+
211
+ # Force rerender so references appear immediately
212
+ st.rerun()
213
+
214
+
215
+ # =========================================================
216
+ # SEARCH MODE
217
+ # =========================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  else:
219
+ query = st.chat_input("Search for a keyword...")
220
+
221
+ if query:
222
+ st.subheader(f"Results for: β€œ{query}”")
223
+
224
  with st.spinner("Scanning archives..."):
225
+ docs, debug_log = search_archives(query)
226
+
227
+ with st.expander("πŸ›  Debug Info"):
228
+ for line in debug_log:
229
+ st.write(line)
230
+
231
+ if not docs:
232
+ st.info("No exact records found.")
233
+ else:
234
+ for doc in docs:
235
+ src = doc.metadata.get("source", "")
236
+ para = doc.metadata.get("paragraph", "")
237
+ link = messagehub_link(src)
238
+
239
+ st.markdown(f"""
240
+ <div class="quote-card">
241
+ <div class="quote-meta">
242
+ <a href="{link}" target="_blank">
243
+ πŸ“Ό {src} (Para {para})
244
+ </a>
245
+ </div>
246
+ <div class="quote-text">
247
+ "{doc.page_content}"
248
  </div>
249
+ </div>
250
+ """, unsafe_allow_html=True)