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

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +176 -127
src/streamlit_app.py CHANGED
@@ -2,7 +2,7 @@ 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="πŸ¦…",
@@ -10,180 +10,229 @@ st.set_page_config(
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"❌ CRITICAL SYSTEM ERROR: Could not load the Brain.\n\nDetails: {e}")
 
20
 
21
- # --- 3. CSS STYLING ---
22
  st.markdown("""
23
  <style>
24
- @import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&family=Inter:wght@300;400;600&display=swap');
25
- html, body, [class*="css"] { font-family: 'Inter', sans-serif; }
26
- h1, h2, h3 { font-family: 'Merriweather', serif !important; }
27
- #MainMenu {visibility: hidden;}
28
- footer {visibility: hidden;}
29
- header {visibility: hidden;}
30
-
31
- /* CHAT BUBBLES */
32
- .stChatMessage { background-color: transparent !important; border: none !important; }
33
- div[data-testid="stChatMessage"][data-test-role="user"] {
34
- background-color: #E8F0FE;
35
- border: 1px solid #D0E1FD;
36
- border-radius: 15px 15px 0px 15px;
37
- padding: 20px;
38
- color: #1a1a1a;
39
  }
40
- div[data-testid="stChatMessage"][data-test-role="assistant"] {
41
- background-color: #FFFFFF;
42
- border: 1px solid #E0E0E0;
43
- border-radius: 15px 15px 15px 0px;
44
- padding: 20px;
45
- color: #1a1a1a;
46
  }
47
- @media (prefers-color-scheme: dark) {
48
- div[data-testid="stChatMessage"][data-test-role="user"] {
49
- background-color: #2b313e !important;
50
- border: 1px solid #3b4252 !important;
51
- color: #FFFFFF !important;
52
- }
53
- div[data-testid="stChatMessage"][data-test-role="assistant"] {
54
- background-color: #262730 !important;
55
- border: 1px solid #3b4252 !important;
56
- color: #FFFFFF !important;
57
- }
58
  }
59
- /* QUOTE CARDS */
60
- .quote-card {
61
- background-color: #FFFFFF !important;
62
- border-left: 5px solid #D4AF37;
63
- padding: 20px;
64
- margin-bottom: 15px;
65
- border-radius: 8px;
66
  }
67
- .quote-text {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  font-family: 'Merriweather', serif;
69
- font-style: italic;
70
- color: #1a1a1a !important;
71
  }
72
- .quote-meta {
 
73
  font-weight: bold;
74
- color: #D4AF37 !important;
75
- margin-bottom: 8px;
 
 
 
 
 
 
 
 
 
 
76
  }
 
 
 
 
 
77
  </style>
78
  """, unsafe_allow_html=True)
79
 
80
- # --- 4. NAVIGATION (Main Page) ---
81
- col1, col2 = st.columns([3, 1])
82
- with col1:
 
 
 
 
83
  mode = st.radio(
84
- "Select Mode:",
85
- ["πŸ—£οΈ Chat with the Message", "πŸ” Search Quotes Only"],
86
- horizontal=True,
87
- label_visibility="collapsed"
88
  )
89
- with col2:
90
- if st.button("πŸ”„ Reset", use_container_width=True):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  st.session_state.messages = []
92
  st.rerun()
93
 
94
- st.divider()
95
-
96
- # --- 5. STATE & LOAD ---
97
- if "messages" not in st.session_state: st.session_state.messages = []
98
 
99
- @st.cache_resource(show_spinner=False)
100
- def load_system():
101
- if backend_loaded: return get_rag_chain()
102
- return None
103
 
104
- # --- 6. HEADER ---
105
- if mode.startswith("πŸ—£οΈ"):
106
- st.markdown("<h2 style='text-align: center;'>πŸ¦… The 7th Handle</h2>", unsafe_allow_html=True)
107
- st.caption("Interactive AI Archive β€’ Ask any question")
108
- else:
109
- st.markdown("<h2 style='text-align: center;'>πŸ” The Table</h2>", unsafe_allow_html=True)
110
- st.caption("Direct Quote Search β€’ Search the tapes")
111
 
112
- if not backend_loaded: st.stop()
 
 
 
 
113
 
114
- # --- 7. MAIN LOGIC ---
115
- placeholder = "Ask a question..." if mode.startswith("πŸ—£οΈ") else "Search for a quote (e.g., 'Coleman')..."
 
 
 
 
 
 
116
 
117
- if prompt := st.chat_input(placeholder):
118
-
119
- # === CHAT MODE ===
120
- if mode.startswith("πŸ—£οΈ"):
121
- st.session_state.messages.append({"role": "user", "content": prompt})
122
- with st.chat_message("user", avatar="πŸ‘€"): st.markdown(prompt)
123
 
 
 
 
 
 
 
124
  with st.chat_message("assistant", avatar="πŸ“–"):
125
- with st.spinner("Searching..."):
126
  try:
127
- chain = load_system()
 
 
 
 
 
128
  response = chain.invoke({"question": prompt})
 
129
 
130
- result_text = response['result']
131
- source_docs = response.get('source_documents', [])
132
-
133
- # Format Sources
134
- unique_sources = list(set([doc.metadata.get('source', 'Unknown') for doc in source_docs]))
135
-
136
- # Stream Output
137
- placeholder_box = st.empty()
138
  full_response = ""
139
- for chunk in result_text.split():
140
  full_response += chunk + " "
141
  time.sleep(0.04)
142
- placeholder_box.markdown(full_response + "β–Œ")
143
- placeholder_box.markdown(full_response)
144
-
 
 
 
 
 
 
 
145
  st.session_state.messages.append({
146
  "role": "assistant",
147
  "content": full_response,
148
  "sources": unique_sources
149
  })
150
 
151
- if unique_sources:
152
- with st.expander("πŸ“š Sermon References"):
153
- for src in unique_sources: st.markdown(f"- *{src}*")
154
-
155
  except Exception as e:
156
- st.error(f"Error: {e}")
157
 
158
- # === SEARCH MODE (The Table) ===
159
- else:
160
- st.markdown(f"### πŸ”Ž Results for: *'{prompt}'*")
161
-
162
- with st.spinner("Scanning archives..."):
163
- # UNPACKING THE TUPLE (This fixes your error)
164
- docs, debug_log = search_archives(prompt)
 
 
 
 
165
 
166
- # 1. SHOW DIAGNOSTICS
167
- with st.expander("πŸ› οΈ System Diagnostics", expanded=False):
168
- if debug_log:
169
- for line in debug_log:
170
- st.write(line)
171
- else:
172
- st.write("No diagnostic info available.")
173
-
174
- # 2. SHOW RESULTS
175
  if not docs:
176
  st.warning("No results found.")
177
 
178
  for doc in docs:
179
- # Safe Metadata Access
180
- src = doc.metadata.get('source', 'Tape')
181
- para = doc.metadata.get('paragraph', '')
182
- txt = doc.page_content
183
 
 
184
  st.markdown(f"""
185
- <div class="quote-card">
186
- <div class="quote-meta">πŸ“Ό {src} {para}</div>
187
- <div class="quote-text">"{txt}"</div>
188
  </div>
189
  """, unsafe_allow_html=True)
 
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. 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,
207
  "sources": unique_sources
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)