Adoption commited on
Commit
fc2b2d7
Β·
verified Β·
1 Parent(s): 3bcf1f7

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +178 -171
src/streamlit_app.py CHANGED
@@ -2,205 +2,210 @@ import streamlit as st
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="πŸ¦…",
9
- layout="centered",
10
  initial_sidebar_state="expanded"
11
  )
12
 
13
- # --- 2. IMPORTS & ERROR HANDLING ---
 
 
14
  try:
15
- from app import get_rag_chain, search_archives, build_index_on_server, check_files, raw_text_search
 
16
  except ImportError as e:
17
- st.error(f"❌ CRITICAL ERROR: Could not load the Brain.\n\nDetails: {e}")
18
- st.stop()
19
 
20
- # --- 3. PROFESSIONAL "EAGLE & SCROLL" THEME ---
21
  st.markdown("""
22
  <style>
23
- /* GLOBAL FONTS */
24
- @import url('https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;1,300&family=Lato:wght@400;700&display=swap');
25
-
26
- html, body, [class*="css"] {
27
- font-family: 'Lato', sans-serif;
 
28
  }
29
-
30
- /* HEADERS */
31
  h1, h2, h3 {
32
- font-family: 'Merriweather', serif;
33
  color: #2C3E50;
34
  }
 
 
 
 
 
35
 
36
  /* CHAT BUBBLES - USER */
37
- .stChatMessage[data-testid="stChatMessage"]:nth-child(odd) {
38
- background-color: #E8F4F8; /* Soft Blue-Grey */
39
- border-left: 4px solid #3498DB;
40
- color: #2C3E50 !important;
 
41
  }
42
 
43
- /* CHAT BUBBLES - ASSISTANT (BRANHAM) */
44
- .stChatMessage[data-testid="stChatMessage"]:nth-child(even) {
45
- background-color: #FDF5E6; /* Old Lace / Vintage Paper */
46
- border-left: 4px solid #8B5E3C; /* Saddle Brown */
47
- color: #2C3E50 !important;
 
 
48
  }
49
 
50
- /* TEXT COLOR OVERRIDE (For Dark Mode Compatibility) */
51
- div[data-testid="stChatMessage"] p,
52
- div[data-testid="stChatMessage"] div {
53
- color: #2C3E50 !important; /* Dark Slate for readability */
54
- font-size: 1.05em;
55
- line-height: 1.6;
56
  }
57
 
58
- /* CITATION BOXES */
59
- .source-box {
60
  background-color: #FFFFFF;
61
- border: 1px solid #D1D1D1;
 
 
62
  border-radius: 8px;
63
- padding: 12px;
64
- margin-top: 10px;
65
- font-family: 'Merriweather', serif;
66
- font-size: 0.9em;
67
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
 
68
  }
69
- .source-title {
70
- color: #8B5E3C;
71
- font-weight: bold;
72
- border-bottom: 1px solid #EEE;
73
- padding-bottom: 5px;
74
- margin-bottom: 5px;
 
 
75
  }
76
- .source-text {
77
- font-style: italic;
78
- color: #555;
 
 
79
  }
80
 
81
  /* SIDEBAR STYLING */
82
  section[data-testid="stSidebar"] {
83
- background-color: #F8F9FA;
 
84
  }
85
 
86
- /* REMOVE DEFAULT BRANDING */
87
- #MainMenu {visibility: hidden;}
88
- footer {visibility: hidden;}
89
-
 
90
  </style>
91
  """, unsafe_allow_html=True)
92
 
93
- # --- 4. SIDEBAR DASHBOARD ---
 
 
 
 
94
  with st.sidebar:
95
- st.image("https://img.icons8.com/ios-filled/100/8B5E3C/eagle.png", width=60)
96
- st.title("Voice of the Sign")
97
- st.markdown("---")
98
 
99
- # Mode Selector
100
  mode = st.radio(
101
- "Select Operation Mode:",
102
- ["πŸ—£οΈ Chat with Brother Branham", "πŸ” Quote Search Only"],
103
- captions=["AI Persona (1965 Era)", "Exact Text Search"]
 
104
  )
105
 
106
  st.markdown("---")
107
 
108
- # Admin Panel (Collapsible)
109
- with st.expander("πŸ› οΈ Admin & Diagnostics"):
110
- st.caption("Server Status")
111
- file_count = check_files()
112
- st.info(f"πŸ“š Index Size: {file_count} Sermons")
113
-
114
- if st.button("πŸ”„ Rebuild Index (Admin Only)"):
115
- with st.status("Rebuilding Knowledge Base...", expanded=True) as status:
116
- st.write("Extracting Text...")
117
- res = build_index_on_server()
118
- st.write(res)
119
- status.update(label="Index Rebuilt!", state="complete", expanded=False)
120
- time.sleep(1)
121
- st.rerun()
122
-
123
- st.caption("Keyword Check")
124
- test_word = st.text_input("Search Raw Text", placeholder="e.g. Coleman")
125
- if test_word:
126
- hits = raw_text_search(test_word)
127
- if hits:
128
- st.success(f"Found in: {hits[0]}...")
129
- else:
130
- st.error("Not found in raw text.")
131
-
132
- if st.button("🧹 Clear Chat History", type="primary", use_container_width=True):
133
  st.session_state.messages = []
134
  st.rerun()
135
-
136
- # --- 5. MAIN CONTENT AREA ---
137
-
138
- # Initialize History
139
- if "messages" not in st.session_state:
140
- st.session_state.messages = []
141
-
142
- # --- MODE A: CHAT (PERSISTENT HISTORY) ---
143
- if mode == "πŸ—£οΈ Chat with Brother Branham":
144
- st.markdown("<h1 style='text-align: center; color: #8B5E3C;'>πŸ¦… THE 7TH HANDLE</h1>", unsafe_allow_html=True)
145
- st.markdown("<p style='text-align: center; color: #7F8C8D;'><i>Interactive Doctrinal Archive β€’ Speaking as the Prophet (1965)</i></p>", unsafe_allow_html=True)
146
- st.divider()
147
-
148
- # 1. DISPLAY EXISTING HISTORY
149
- # We loop through history FIRST so it stays on screen when new input arrives
150
- if not st.session_state.messages:
151
- with st.chat_message("assistant", avatar="πŸ“–"):
152
- st.markdown("God bless you. I am here to search the Message for you. What is on your heart?")
153
-
154
- for msg in st.session_state.messages:
155
- with st.chat_message(msg["role"], avatar="πŸ“–" if msg["role"] == "assistant" else "πŸ‘€"):
156
- st.markdown(msg["content"])
157
- # Render Sources nicely if they exist
158
- if "sources" in msg and msg["sources"]:
159
- with st.expander("πŸ“œ View Sermon References"):
160
- for src in msg["sources"]:
161
- st.markdown(f"**πŸ“Ό {src}**")
162
-
163
- # 2. HANDLE NEW INPUT
164
- if prompt := st.chat_input("Ask a question on doctrine..."):
165
 
166
- # A. Append User Message
167
- st.session_state.messages.append({"role": "user", "content": prompt})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  with st.chat_message("user", avatar="πŸ‘€"):
169
- st.markdown(prompt)
170
-
171
- # B. Generate & Append Assistant Message
172
- with st.chat_message("assistant", avatar="πŸ“–"):
173
- with st.spinner("Searching the storehouse..."):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  try:
175
- # Run the Brain
176
- chain = get_rag_chain()
177
- if not chain:
178
- st.error("⚠️ System Index is missing. Please Rebuild Index in Sidebar.")
179
- st.stop()
180
-
181
  response = chain.invoke({"question": prompt})
182
- answer_text = response['result']
183
 
184
- # Extract Sources
185
- raw_docs = response.get('source_documents', [])
186
- unique_sources = list(set([d.metadata.get('source', 'Unknown') for d in raw_docs]))
187
-
188
- # Simulate Typing Effect
189
- message_placeholder = st.empty()
190
- full_response = ""
191
- for chunk in answer_text.split():
192
  full_response += chunk + " "
193
- time.sleep(0.04)
194
  message_placeholder.markdown(full_response + "β–Œ")
195
  message_placeholder.markdown(full_response)
196
-
197
- # Show Sources
198
  if unique_sources:
199
- with st.expander("πŸ“œ View Sermon References"):
200
- for src in unique_sources:
201
- st.markdown(f"- {src}")
202
-
203
- # Save to State (So it persists next run!)
204
  st.session_state.messages.append({
205
  "role": "assistant",
206
  "content": full_response,
@@ -208,31 +213,33 @@ if mode == "πŸ—£οΈ Chat with Brother Branham":
208
  })
209
 
210
  except Exception as e:
211
- st.error(f"Something went wrong: {e}")
212
 
213
- # --- MODE B: SEARCH (EPHEMERAL) ---
214
- else:
215
- st.markdown("<h1 style='text-align: center; color: #2F4F4F;'>πŸ” SERMON SEARCH</h1>", unsafe_allow_html=True)
216
- st.caption("Search for exact quotes without the AI Persona.")
217
-
218
- search_query = st.chat_input("Enter a keyword or phrase...")
219
-
220
- if search_query:
221
- st.markdown(f"### Results for: *'{search_query}'*")
222
- with st.spinner("Scanning tapes..."):
223
- docs = search_archives(search_query)
224
 
225
- if not docs:
226
- st.warning("No results found.")
 
227
 
228
- for doc in docs:
229
- source = doc.metadata.get('source', 'Unknown')
230
- text = doc.page_content
231
-
232
- # Render as a nice card
233
- st.markdown(f"""
234
- <div class="source-box">
235
- <div class="source-title">πŸ“Ό {source}</div>
236
- <div class="source-text">"...{text}..."</div>
237
- </div>
238
- """, unsafe_allow_html=True)
 
 
 
 
 
2
  import time
3
  import os
4
 
5
+ # --- 1. PAGE CONFIGURATION ---
6
  st.set_page_config(
7
+ page_title="The Voice of the Sign",
8
  page_icon="πŸ¦…",
9
+ layout="wide",
10
  initial_sidebar_state="expanded"
11
  )
12
 
13
+ # --- 2. BACKEND LOADING ---
14
+ # We try to import the brain. If it fails, we show a pretty error.
15
+ backend_loaded = False
16
  try:
17
+ from app import get_rag_chain, search_archives
18
+ backend_loaded = True
19
  except ImportError as e:
20
+ st.error(f"❌ System Error: Could not load the Brain.\n\nDetails: {e}")
 
21
 
22
+ # --- 3. CUSTOM CSS (The "Appealing" Look) ---
23
  st.markdown("""
24
  <style>
25
+ /* IMPORT FONTS */
26
+ @import url('https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;1,300&family=Inter:wght@300;400;600&display=swap');
27
+
28
+ /* GLOBAL THEME */
29
+ .stApp {
30
+ background-color: #FDFBF7; /* Parchment White */
31
  }
32
+
 
33
  h1, h2, h3 {
34
+ font-family: 'Merriweather', serif !important;
35
  color: #2C3E50;
36
  }
37
+
38
+ p, div, label {
39
+ font-family: 'Inter', sans-serif;
40
+ color: #34495E;
41
+ }
42
 
43
  /* CHAT BUBBLES - USER */
44
+ div[data-testid="stChatMessage"][data-test-role="user"] {
45
+ background-color: #EAECEE;
46
+ border-radius: 20px 20px 5px 20px;
47
+ padding: 15px;
48
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
49
  }
50
 
51
+ /* CHAT BUBBLES - ASSISTANT */
52
+ div[data-testid="stChatMessage"][data-test-role="assistant"] {
53
+ background-color: #FFFFFF;
54
+ border: 1px solid #D4AF37; /* Gold Border */
55
+ border-radius: 20px 20px 20px 5px;
56
+ padding: 20px;
57
+ box-shadow: 0 4px 10px rgba(0,0,0,0.05);
58
  }
59
 
60
+ /* AVATARS */
61
+ .stChatMessage .stChatMessageAvatar {
62
+ background-color: #2C3E50 !important; /* Navy Blue */
63
+ color: white !important;
 
 
64
  }
65
 
66
+ /* QUOTE CARDS (SEARCH MODE) */
67
+ .quote-card {
68
  background-color: #FFFFFF;
69
+ border-left: 6px solid #D4AF37; /* Gold Accent */
70
+ padding: 20px;
71
+ margin-bottom: 20px;
72
  border-radius: 8px;
73
+ box-shadow: 0 4px 6px rgba(0,0,0,0.05);
74
+ transition: transform 0.2s;
75
+ }
76
+ .quote-card:hover {
77
+ transform: translateY(-2px);
78
+ box-shadow: 0 8px 15px rgba(0,0,0,0.1);
79
  }
80
+ .quote-meta {
81
+ font-family: 'Inter', sans-serif;
82
+ font-size: 0.85rem;
83
+ color: #7F8C8D;
84
+ text-transform: uppercase;
85
+ letter-spacing: 1px;
86
+ margin-bottom: 8px;
87
+ font-weight: 600;
88
  }
89
+ .quote-text {
90
+ font-family: 'Merriweather', serif;
91
+ font-size: 1.05rem;
92
+ line-height: 1.6;
93
+ color: #2C3E50;
94
  }
95
 
96
  /* SIDEBAR STYLING */
97
  section[data-testid="stSidebar"] {
98
+ background-color: #F4F6F7;
99
+ border-right: 1px solid #E5E7E9;
100
  }
101
 
102
+ /* INPUT BOX */
103
+ .stChatInput textarea {
104
+ border-radius: 15px !important;
105
+ border: 1px solid #BDC3C7 !important;
106
+ }
107
  </style>
108
  """, unsafe_allow_html=True)
109
 
110
+ # --- 4. SESSION STATE INITIALIZATION (Fixes the "Clearing" Issue) ---
111
+ if "messages" not in st.session_state:
112
+ st.session_state.messages = []
113
+
114
+ # --- 5. SIDEBAR CONTROLS ---
115
  with st.sidebar:
116
+ st.markdown("### πŸ¦… Controls")
 
 
117
 
 
118
  mode = st.radio(
119
+ "Select Mode:",
120
+ ["πŸ—£οΈ Chat with Bro. Branham", "πŸ” Search Quotes Only"],
121
+ captions=["Ask theological questions.", "Find exact paragraphs."],
122
+ index=0
123
  )
124
 
125
  st.markdown("---")
126
 
127
+ # CLEAR BUTTON (Resets Memory)
128
+ if st.button("πŸ—‘οΈ Clear Conversation", use_container_width=True):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  st.session_state.messages = []
130
  st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
+ st.markdown("---")
133
+ st.info("πŸ’‘ **Tip:** Ask specific questions like *'What is the Third Pull?'* or search names like *'Coleman'*.")
134
+
135
+ # --- 6. MAIN HEADER ---
136
+ col1, col2 = st.columns([1, 6])
137
+ with col1:
138
+ st.markdown("# πŸ¦…") # Simple Icon
139
+ with col2:
140
+ if mode.startswith("πŸ—£οΈ"):
141
+ st.markdown("# The 7th Handle\n*Interactive AI Archive*")
142
+ else:
143
+ st.markdown("# The Table\n*Direct Quote Search*")
144
+
145
+ st.divider()
146
+
147
+ if not backend_loaded: st.stop()
148
+
149
+ # --- 7. LOAD SYSTEM (Cached) ---
150
+ @st.cache_resource(show_spinner=False)
151
+ def load_system():
152
+ return get_rag_chain()
153
+
154
+ # --- 8. DISPLAY CHAT HISTORY (Persistent) ---
155
+ # This block ensures old messages stay on screen
156
+ for message in st.session_state.messages:
157
+ # We differentiate between "Search Results" and "Chat Messages" visually
158
+ if message["role"] == "user":
159
  with st.chat_message("user", avatar="πŸ‘€"):
160
+ st.markdown(message["content"])
161
+ elif message["role"] == "assistant":
162
+ with st.chat_message("assistant", avatar="πŸ¦…"):
163
+ st.markdown(message["content"])
164
+ # If sources exist, show them in a dropdown
165
+ if "sources" in message and message["sources"]:
166
+ with st.expander("πŸ“š View Sermon References"):
167
+ for src in message["sources"]:
168
+ st.markdown(f"- *{src}*")
169
+
170
+ # --- 9. INPUT HANDLER ---
171
+ prompt = st.chat_input("Ask a question or search...")
172
+
173
+ if prompt:
174
+ # 9a. SHOW USER MESSAGE IMMEDIATELY
175
+ st.session_state.messages.append({"role": "user", "content": prompt})
176
+ with st.chat_message("user", avatar="πŸ‘€"):
177
+ st.markdown(prompt)
178
+
179
+ # 9b. PROCESS BASED ON MODE
180
+
181
+ # === CHAT MODE ===
182
+ if mode.startswith("πŸ—£οΈ"):
183
+ with st.chat_message("assistant", avatar="πŸ¦…"):
184
+ message_placeholder = st.empty()
185
+ full_response = ""
186
+
187
+ with st.spinner("Searching the archives..."):
188
  try:
189
+ chain = load_system()
 
 
 
 
 
190
  response = chain.invoke({"question": prompt})
 
191
 
192
+ result_text = response['result']
193
+ source_docs = response.get('source_documents', [])
194
+ unique_sources = list(set([doc.metadata.get('source', 'Unknown Tape') for doc in source_docs]))
195
+
196
+ # TYPEWRITER EFFECT
197
+ for chunk in result_text.split():
 
 
198
  full_response += chunk + " "
199
+ time.sleep(0.02) # Subtle typing speed
200
  message_placeholder.markdown(full_response + "β–Œ")
201
  message_placeholder.markdown(full_response)
202
+
203
+ # SHOW SOURCES
204
  if unique_sources:
205
+ with st.expander("πŸ“š Sermon References"):
206
+ for src in unique_sources: st.markdown(f"- *{src}*")
207
+
208
+ # SAVE TO HISTORY
 
209
  st.session_state.messages.append({
210
  "role": "assistant",
211
  "content": full_response,
 
213
  })
214
 
215
  except Exception as e:
216
+ st.error(f"Error: {e}")
217
 
218
+ # === SEARCH MODE ===
219
+ else:
220
+ # For Search Mode, we don't save to chat history the same way.
221
+ # We just display the results clearly.
222
+ st.markdown(f"### πŸ”Ž Results for: *'{prompt}'*")
223
+
224
+ with st.spinner("Scanning local and cloud libraries..."):
225
+ docs, debug_log = search_archives(prompt)
 
 
 
226
 
227
+ # DIAGNOSTICS (Collapsible)
228
+ with st.expander("πŸ› οΈ View System Diagnostics", expanded=False):
229
+ for line in debug_log: st.write(line)
230
 
231
+ if not docs:
232
+ st.warning("No relevant records found.")
233
+ else:
234
+ for doc in docs:
235
+ src = doc.metadata.get('source', 'Tape')
236
+ para = doc.metadata.get('paragraph', '')
237
+ # Highlight the keyword
238
+ txt = doc.page_content.replace(prompt, f"**{prompt}**")
239
+
240
+ st.markdown(f"""
241
+ <div class="quote-card">
242
+ <div class="quote-meta">πŸ“Ό {src} {para}</div>
243
+ <div class="quote-text">"{txt}"</div>
244
+ </div>
245
+ """, unsafe_allow_html=True)