Adoption commited on
Commit
222bd04
Β·
verified Β·
1 Parent(s): ca31038

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +144 -90
src/streamlit_app.py CHANGED
@@ -2,14 +2,7 @@ import streamlit as st
2
  import time
3
  import os
4
 
5
- # Import the backend logic from app.py
6
- try:
7
- from app import get_rag_chain, search_archives
8
- except ImportError as e:
9
- st.error(f"CRITICAL ERROR: Could not import app.py. Details: {e}")
10
- st.stop()
11
-
12
- # --- PAGE CONFIGURATION ---
13
  st.set_page_config(
14
  page_title="Voice of the Sign",
15
  page_icon="πŸ¦…",
@@ -17,141 +10,210 @@ st.set_page_config(
17
  initial_sidebar_state="expanded"
18
  )
19
 
20
- # --- CUSTOM CSS STYLING ---
 
 
 
 
 
 
 
 
21
  st.markdown("""
22
  <style>
23
- /* Chat Bubble Styling */
24
- .stChatMessage {
25
- border-radius: 12px;
26
- border: 1px solid #E0E0E0;
27
- padding: 10px;
 
28
  }
29
 
30
- /* Force text color to black inside chat bubbles, even in Dark Mode */
31
- div[data-testid="stChatMessage"] {
32
- color: #31333F !important; /* Dark Grey Text */
33
  }
34
-
35
- div[data-testid="stChatMessage"] p,
36
- div[data-testid="stChatMessage"] div {
37
- color: #31333F !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
39
 
40
- div[data-testid="stChatMessage"]:nth-child(even) {
41
- background-color: #FFFFFF;
42
- border-left: 5px solid #8B5E3C;
 
43
  }
44
 
45
- div[data-testid="stChatMessage"]:nth-child(odd) {
46
- background-color: #F8F9FA;
 
 
 
 
 
 
47
  }
48
 
49
- /* Quote Card Styling */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  .quote-card {
51
- background-color: #f9f9f9;
52
- border-left: 4px solid #8B5E3C;
53
- padding: 15px;
54
- margin-bottom: 10px;
55
- border-radius: 5px;
56
- box-shadow: 0 1px 2px rgba(0,0,0,0.1);
 
 
 
 
 
57
  }
58
  .quote-meta {
59
- color: #8B5E3C;
60
- font-weight: bold;
61
- font-size: 0.9em;
62
- margin-bottom: 5px;
 
 
 
63
  }
64
  .quote-text {
65
- font-family: "Georgia", serif;
66
- font-style: italic;
 
67
  color: #333;
 
68
  }
69
-
70
- /* Hide Branding */
71
  #MainMenu {visibility: hidden;}
72
  footer {visibility: hidden;}
 
73
  </style>
74
  """, unsafe_allow_html=True)
75
 
76
- # --- SIDEBAR ---
77
  with st.sidebar:
78
- st.title("πŸ¦… Voice of the Sign")
 
79
 
80
- # --- MODE TOGGLE ---
81
- st.markdown("### βš™οΈ Settings")
82
  mode = st.radio(
83
- "Select Mode:",
84
  ["πŸ—£οΈ Chat with Bro. Branham", "πŸ” Search Quotes Only"],
85
- help="Chat uses AI to speak like Branham. Search returns exact text from the sermons."
86
  )
87
 
 
 
88
  st.divider()
89
- if st.button("Clear History", use_container_width=True):
90
  st.session_state.messages = []
91
  st.rerun()
92
 
93
- # --- INITIALIZE CHAT HISTORY ---
94
  if "messages" not in st.session_state:
95
  st.session_state.messages = []
96
 
97
- # --- LOAD RESOURCES (Cached) ---
98
  @st.cache_resource(show_spinner=False)
99
  def load_system():
100
- # Calling this initializes the retriever cache in app.py
101
- return get_rag_chain()
 
102
 
103
- # --- DISPLAY PAGE HEADER ---
 
104
  if mode == "πŸ—£οΈ Chat with Bro. Branham":
105
- st.title("πŸ¦… THE 7TH HANDLE")
106
- st.caption("Interactive Archive β€’ AI Persona Mode")
 
 
 
 
107
  else:
108
- st.title("πŸ” SERMON SEARCH")
109
- st.caption("Direct Archive Search β€’ Quote Mode")
 
 
 
 
110
 
111
- st.divider()
 
 
 
112
 
113
- # --- DISPLAY HISTORY ---
114
  if mode == "πŸ—£οΈ Chat with Bro. Branham":
115
  if not st.session_state.messages:
116
- # Use markdown directly for the welcome message to ensure styling applies
117
  with st.chat_message("assistant", avatar="πŸ“–"):
118
- st.markdown("God bless you. I am here to search the tapes for you. What is on your heart?")
119
 
120
  for msg in st.session_state.messages:
 
 
121
  with st.chat_message(msg["role"], avatar="πŸ“–" if msg["role"] == "assistant" else "πŸ‘€"):
122
  st.markdown(msg["content"])
123
- # Display Sources if available
124
  if "sources" in msg and msg["sources"]:
125
  with st.expander("πŸ“š Sermon References"):
126
  for src in msg["sources"]:
127
  st.markdown(f"- *{src}*")
128
 
129
- # --- USER INPUT HANDLING ---
130
- placeholder_text = "Ask a question..." if mode == "πŸ—£οΈ Chat with Bro. Branham" else "Search for a quote (e.g., 'Third Pull')..."
131
 
132
  if prompt := st.chat_input(placeholder_text):
133
 
134
- # ==========================
135
- # MODE A: CHAT (AI PERSONA)
136
- # ==========================
137
  if mode == "πŸ—£οΈ Chat with Bro. Branham":
138
- # 1. Add User Message
139
  st.session_state.messages.append({"role": "user", "content": prompt})
140
  with st.chat_message("user", avatar="πŸ‘€"):
141
  st.markdown(prompt)
142
 
143
- # 2. Generate Response
144
  with st.chat_message("assistant", avatar="πŸ“–"):
145
- with st.spinner("Searching the archives..."):
146
  try:
147
- # Load Chain (Lazy Load)
148
  chain = load_system()
 
149
 
150
- response = chain.invoke({"query": prompt})
151
- result_text = response['result']
152
  source_docs = response.get('source_documents', [])
153
 
154
- # Format Sources
155
  formatted_sources = []
156
  for doc in source_docs:
157
  filename = doc.metadata.get('source', 'Unknown')
@@ -159,7 +221,7 @@ if prompt := st.chat_input(placeholder_text):
159
  formatted_sources.append(f"{filename} {paragraph}")
160
  unique_sources = list(dict.fromkeys(formatted_sources))
161
 
162
- # Typing Effect
163
  placeholder = st.empty()
164
  full_response = ""
165
  for chunk in result_text.split():
@@ -168,14 +230,12 @@ if prompt := st.chat_input(placeholder_text):
168
  placeholder.markdown(full_response + "β–Œ")
169
  placeholder.markdown(full_response)
170
 
171
- # Save to History
172
  st.session_state.messages.append({
173
  "role": "assistant",
174
  "content": full_response,
175
  "sources": unique_sources
176
  })
177
 
178
- # Show Sources
179
  if unique_sources:
180
  with st.expander("πŸ“š Sermon References"):
181
  for src in unique_sources:
@@ -184,31 +244,25 @@ if prompt := st.chat_input(placeholder_text):
184
  except Exception as e:
185
  st.error(f"An error occurred: {e}")
186
 
187
- # ==========================
188
- # MODE B: QUOTE SEARCH
189
- # ==========================
190
  else:
191
- st.subheader(f"Results for: '{prompt}'")
192
-
193
- with st.spinner("Scanning tapes..."):
194
  try:
195
- load_system()
196
  docs = search_archives(prompt)
197
-
198
  if not docs:
199
- st.warning("No results found in the archives.")
200
 
201
  for doc in docs:
202
  filename = doc.metadata.get('source', 'Unknown Tape')
203
- paragraph = doc.metadata.get('paragraph', 'Unknown Para')
204
  content = doc.page_content
205
 
206
  st.markdown(f"""
207
  <div class="quote-card">
208
- <div class="quote-meta">πŸ“Ό {filename} (Para {paragraph})</div>
209
  <div class="quote-text">"{content}"</div>
210
  </div>
211
  """, unsafe_allow_html=True)
212
-
213
  except Exception as e:
214
- st.error(f"Search failed: {e}")
 
2
  import time
3
  import os
4
 
5
+ # --- 1. PAGE CONFIGURATION (MUST BE FIRST) ---
 
 
 
 
 
 
 
6
  st.set_page_config(
7
  page_title="Voice of the Sign",
8
  page_icon="πŸ¦…",
 
10
  initial_sidebar_state="expanded"
11
  )
12
 
13
+ # --- 2. ERROR HANDLING IMPORTS ---
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"❌ CRITICAL SYSTEM ERROR: Could not load the Brain.\n\nDetails: {e}")
20
+
21
+ # --- 3. PROFESSIONAL CSS STYLING ---
22
  st.markdown("""
23
  <style>
24
+ /* IMPORT FONTS */
25
+ @import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&family=Inter:wght@300;400;600&display=swap');
26
+
27
+ /* GLOBAL TEXT STYLES */
28
+ html, body, [class*="css"] {
29
+ font-family: 'Inter', sans-serif;
30
  }
31
 
32
+ h1, h2, h3 {
33
+ font-family: 'Merriweather', serif !important;
34
+ color: #2C3E50;
35
  }
36
+
37
+ /* CUSTOM HERO HEADER */
38
+ .hero-container {
39
+ text-align: center;
40
+ padding: 40px 20px;
41
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
42
+ border-radius: 15px;
43
+ margin-bottom: 30px;
44
+ box-shadow: 0 4px 6px rgba(0,0,0,0.05);
45
+ }
46
+ .hero-title {
47
+ font-size: 2.5em;
48
+ font-weight: 700;
49
+ color: #1a1a1a;
50
+ margin: 0;
51
+ }
52
+ .hero-subtitle {
53
+ font-size: 1.1em;
54
+ color: #555;
55
+ font-style: italic;
56
+ margin-top: 10px;
57
  }
58
 
59
+ /* CHAT BUBBLES */
60
+ .stChatMessage {
61
+ background-color: transparent !important;
62
+ border: none !important;
63
  }
64
 
65
+ /* User Message Bubble */
66
+ div[data-testid="stChatMessage"][data-test-role="user"] {
67
+ background-color: #E8F0FE !important; /* Soft Blue */
68
+ border-radius: 15px 15px 0px 15px !important;
69
+ padding: 20px !important;
70
+ margin-bottom: 15px;
71
+ box-shadow: 0 1px 2px rgba(0,0,0,0.05);
72
+ border: 1px solid #D0E1FD;
73
  }
74
 
75
+ /* AI Message Bubble */
76
+ div[data-testid="stChatMessage"][data-test-role="assistant"] {
77
+ background-color: #FFFFFF !important;
78
+ border-radius: 15px 15px 15px 0px !important;
79
+ padding: 20px !important;
80
+ margin-bottom: 15px;
81
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
82
+ border: 1px solid #F0F0F0;
83
+ }
84
+
85
+ /* TEXT COLOR FIX FOR DARK MODE */
86
+ div[data-testid="stChatMessage"] p,
87
+ div[data-testid="stChatMessage"] div {
88
+ color: #1a1a1a !important; /* Always dark text */
89
+ }
90
+
91
+ /* QUOTE CARDS (Search Mode) */
92
  .quote-card {
93
+ background-color: white;
94
+ border-left: 5px solid #D4AF37; /* Gold Border */
95
+ padding: 20px;
96
+ margin-bottom: 15px;
97
+ border-radius: 8px;
98
+ box-shadow: 0 3px 6px rgba(0,0,0,0.1);
99
+ transition: transform 0.2s;
100
+ }
101
+ .quote-card:hover {
102
+ transform: translateY(-2px);
103
+ box-shadow: 0 5px 10px rgba(0,0,0,0.15);
104
  }
105
  .quote-meta {
106
+ font-family: 'Inter', sans-serif;
107
+ font-size: 0.85em;
108
+ text-transform: uppercase;
109
+ letter-spacing: 1px;
110
+ color: #D4AF37; /* Gold */
111
+ font-weight: 700;
112
+ margin-bottom: 8px;
113
  }
114
  .quote-text {
115
+ font-family: 'Merriweather', serif;
116
+ font-size: 1.05em;
117
+ line-height: 1.6;
118
  color: #333;
119
+ font-style: italic;
120
  }
121
+
122
+ /* HIDE STREAMLIT BRANDING */
123
  #MainMenu {visibility: hidden;}
124
  footer {visibility: hidden;}
125
+ header {visibility: hidden;}
126
  </style>
127
  """, unsafe_allow_html=True)
128
 
129
+ # --- 4. SIDEBAR ---
130
  with st.sidebar:
131
+ st.markdown("<h2 style='text-align: center; color: #2C3E50;'>πŸ¦… Settings</h2>", unsafe_allow_html=True)
132
+ st.divider()
133
 
 
 
134
  mode = st.radio(
135
+ "Choose Experience:",
136
  ["πŸ—£οΈ Chat with Bro. Branham", "πŸ” Search Quotes Only"],
137
+ captions=["AI Persona asking & answering", "Direct text search from the tapes"]
138
  )
139
 
140
+ st.info("πŸ’‘ **Tip:** Use 'Chat' for deep questions and 'Search' for finding specific quotes.")
141
+
142
  st.divider()
143
+ if st.button("πŸ—‘οΈ Start New Conversation", use_container_width=True):
144
  st.session_state.messages = []
145
  st.rerun()
146
 
147
+ # --- 5. INITIALIZE STATE ---
148
  if "messages" not in st.session_state:
149
  st.session_state.messages = []
150
 
151
+ # --- 6. LOAD BACKEND ---
152
  @st.cache_resource(show_spinner=False)
153
  def load_system():
154
+ if backend_loaded:
155
+ return get_rag_chain()
156
+ return None
157
 
158
+ # --- 7. MAIN HEADER ---
159
+ # Custom HTML Hero Section
160
  if mode == "πŸ—£οΈ Chat with Bro. Branham":
161
+ st.markdown("""
162
+ <div class="hero-container">
163
+ <div class="hero-title">πŸ¦… The 7th Handle</div>
164
+ <div class="hero-subtitle">Interactive AI Archive β€’ "He that has an ear, let him hear."</div>
165
+ </div>
166
+ """, unsafe_allow_html=True)
167
  else:
168
+ st.markdown("""
169
+ <div class="hero-container">
170
+ <div class="hero-title">πŸ” The Table</div>
171
+ <div class="hero-subtitle">Direct Reference Search β€’ "Spoken Word is the Original Seed"</div>
172
+ </div>
173
+ """, unsafe_allow_html=True)
174
 
175
+ # Check backend status
176
+ if not backend_loaded:
177
+ st.warning("⚠️ System is offline. Please check logs.")
178
+ st.stop()
179
 
180
+ # --- 8. CHAT INTERFACE ---
181
  if mode == "πŸ—£οΈ Chat with Bro. Branham":
182
  if not st.session_state.messages:
 
183
  with st.chat_message("assistant", avatar="πŸ“–"):
184
+ st.markdown("God bless you, friend. I am here to search the tapes for you. What is on your heart today?")
185
 
186
  for msg in st.session_state.messages:
187
+ # We manually assign the role for CSS targeting
188
+ role_class = "user" if msg["role"] == "user" else "assistant"
189
  with st.chat_message(msg["role"], avatar="πŸ“–" if msg["role"] == "assistant" else "πŸ‘€"):
190
  st.markdown(msg["content"])
 
191
  if "sources" in msg and msg["sources"]:
192
  with st.expander("πŸ“š Sermon References"):
193
  for src in msg["sources"]:
194
  st.markdown(f"- *{src}*")
195
 
196
+ # --- 9. INPUT & LOGIC ---
197
+ placeholder_text = "Ask a difficult question..." if mode == "πŸ—£οΈ Chat with Bro. Branham" else "Search for a specific quote..."
198
 
199
  if prompt := st.chat_input(placeholder_text):
200
 
201
+ # === MODE A: CHAT ===
 
 
202
  if mode == "πŸ—£οΈ Chat with Bro. Branham":
 
203
  st.session_state.messages.append({"role": "user", "content": prompt})
204
  with st.chat_message("user", avatar="πŸ‘€"):
205
  st.markdown(prompt)
206
 
 
207
  with st.chat_message("assistant", avatar="πŸ“–"):
208
+ with st.spinner("Searching the mysteries..."):
209
  try:
 
210
  chain = load_system()
211
+ response = chain.invoke({"question": prompt})
212
 
213
+ result_text = response['answer']
 
214
  source_docs = response.get('source_documents', [])
215
 
216
+ # Deduplicate Sources
217
  formatted_sources = []
218
  for doc in source_docs:
219
  filename = doc.metadata.get('source', 'Unknown')
 
221
  formatted_sources.append(f"{filename} {paragraph}")
222
  unique_sources = list(dict.fromkeys(formatted_sources))
223
 
224
+ # Stream Response
225
  placeholder = st.empty()
226
  full_response = ""
227
  for chunk in result_text.split():
 
230
  placeholder.markdown(full_response + "β–Œ")
231
  placeholder.markdown(full_response)
232
 
 
233
  st.session_state.messages.append({
234
  "role": "assistant",
235
  "content": full_response,
236
  "sources": unique_sources
237
  })
238
 
 
239
  if unique_sources:
240
  with st.expander("πŸ“š Sermon References"):
241
  for src in unique_sources:
 
244
  except Exception as e:
245
  st.error(f"An error occurred: {e}")
246
 
247
+ # === MODE B: SEARCH ===
 
 
248
  else:
249
+ st.markdown(f"### πŸ”Ž Results for: *'{prompt}'*")
250
+ with st.spinner("Scanning the tapes..."):
 
251
  try:
 
252
  docs = search_archives(prompt)
 
253
  if not docs:
254
+ st.warning("No results found.")
255
 
256
  for doc in docs:
257
  filename = doc.metadata.get('source', 'Unknown Tape')
258
+ paragraph = doc.metadata.get('paragraph', '')
259
  content = doc.page_content
260
 
261
  st.markdown(f"""
262
  <div class="quote-card">
263
+ <div class="quote-meta">πŸ“Ό {filename} {paragraph}</div>
264
  <div class="quote-text">"{content}"</div>
265
  </div>
266
  """, unsafe_allow_html=True)
 
267
  except Exception as e:
268
+ st.error(f"Search failed: {e}")