Adoption commited on
Commit
1cf4f73
Β·
verified Β·
1 Parent(s): f7aed17

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +215 -103
src/streamlit_app.py CHANGED
@@ -2,120 +2,232 @@ import streamlit as st
2
  import time
3
  import os
4
 
5
- # Import backend logic
 
 
 
 
 
 
 
 
 
6
  try:
7
- from app import get_rag_chain
 
8
  except ImportError as e:
9
- st.error(f"CRITICAL ERROR: {e}")
10
- st.stop()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
- # --- PAGE CONFIG ---
13
- st.set_page_config(page_title="Voice of the Sign", page_icon="πŸ¦…", layout="centered")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- # --- CSS STYLING (Dark Mode Fix) ---
16
- st.markdown("""
17
- <style>
18
- /* Force chat bubbles to look correct regardless of user theme */
19
- div[data-testid="stChatMessage"] {
20
- background-color: #ffffff !important;
21
- border: 1px solid #e0e0e0;
22
- color: #000000 !important;
23
  }
24
- /* Ensure text inside bubbles is black */
25
- div[data-testid="stChatMessage"] p,
26
- div[data-testid="stChatMessage"] li {
27
- color: #000000 !important;
28
  }
29
- /* Hide default footer */
30
- footer {visibility: hidden;}
31
- </style>
 
32
  """, unsafe_allow_html=True)
33
 
34
- # --- INITIALIZE CHAT ---
35
- if "messages" not in st.session_state:
36
- st.session_state.messages = [{"role": "assistant", "content": "God bless you. What is on your heart?"}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- # --- LOAD BRAIN ---
39
  @st.cache_resource(show_spinner=False)
40
- def load_chain():
41
  return get_rag_chain()
42
 
43
- if "chain" not in st.session_state:
44
- with st.spinner("Connecting to the Message..."):
45
- try:
46
- st.session_state.chain = load_chain()
47
- except Exception as e:
48
- st.error(f"Connection Failed: {e}")
49
- st.stop()
50
-
51
- chain = st.session_state.chain
52
-
53
- # --- DISPLAY CHAT HISTORY ---
54
- st.title("πŸ¦… The Message AI")
55
- st.caption("Interactive Archive β€’ Powered by Pinecone & Gemini")
56
- st.divider()
57
-
58
- for msg in st.session_state.messages:
59
- with st.chat_message(msg["role"], avatar="πŸ“–" if msg["role"] == "assistant" else "πŸ‘€"):
60
- st.markdown(msg["content"])
61
- if "sources" in msg:
62
- with st.expander("πŸ“š References"):
63
- for src in msg["sources"]:
64
- st.markdown(f"- {src}")
65
-
66
- # --- INPUT HANDLING ---
67
- if prompt := st.chat_input("Ask a question..."):
68
- st.session_state.messages.append({"role": "user", "content": prompt})
69
- with st.chat_message("user", avatar="πŸ‘€"):
70
- st.markdown(prompt)
71
-
72
- with st.chat_message("assistant", avatar="πŸ“–"):
73
- with st.spinner("Searching..."):
74
- try:
75
- response = chain.invoke({"query": prompt})
76
- result = response['result']
77
-
78
- # --- SMART REFERENCE FORMATTER ---
79
- # This reads the metadata you uploaded to Pinecone
80
- sources = []
81
- for doc in response.get('source_documents', []):
82
- meta = doc.metadata
83
 
84
- # Get fields (defaults if missing)
85
- source_file = meta.get('source', 'Unknown')
86
- para = meta.get('paragraph', 'Intro')
87
- title = meta.get('title', 'Unknown Title')
88
- date = meta.get('date', '')
89
-
90
- # Logic: Avoid repeating if Title IS the Date
91
- if title == date:
92
- display_str = f"**{source_file}** | Para {para}"
93
- elif title != "Unknown Title":
94
- display_str = f"**{title}** ({date}) | Para {para}"
95
- else:
96
- display_str = f"**{source_file}** | Para {para}"
97
 
98
- sources.append(display_str)
99
-
100
- # Remove duplicates
101
- unique_sources = list(dict.fromkeys(sources))
102
-
103
- # Display Response
104
- st.markdown(result)
105
-
106
- # Save to History
107
- st.session_state.messages.append({
108
- "role": "assistant",
109
- "content": result,
110
- "sources": unique_sources
111
- })
112
-
113
- if unique_sources:
114
- with st.expander("πŸ“š References"):
115
- for src in unique_sources:
116
- st.markdown(f"- {src}")
117
-
118
- except Exception as e:
119
- st.error(f"Error: {e}")
120
-
121
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)