LazyHuman10 commited on
Commit
2c111dc
·
1 Parent(s): defc54d

Add Plexi assistant onboarding and sidebar updates

Browse files
Files changed (3) hide show
  1. README.md +8 -0
  2. pages/Plexi-Assistant.py +77 -15
  3. utils.py +17 -6
README.md CHANGED
@@ -46,6 +46,14 @@ Chat with an AI that **only answers using actual study materials from Database**
46
  - Every answer includes **source citations** so you know exactly where the information came from
47
  - Bring your own API key (free tiers available from most providers)
48
 
 
 
 
 
 
 
 
 
49
  ### Contribute Materials
50
 
51
  Have notes that could help others? Submit them through a simple form — they'll be reviewed and added for everyone automatically.
 
46
  - Every answer includes **source citations** so you know exactly where the information came from
47
  - Bring your own API key (free tiers available from most providers)
48
 
49
+ ### Use Plexi Anywhere
50
+
51
+ Plexi is also available outside this app:
52
+
53
+ - ChatGPT GPT: [Plexi on ChatGPT](https://chatgpt.com/g/g-69caa671910481919ce71d19952e34e5-plexi)
54
+ - MCP Server: [https://plexi-mcp.vercel.app/](https://plexi-mcp.vercel.app/)
55
+ - MCP Endpoint: `https://plexi-mcp.vercel.app/api/mcp`
56
+
57
  ### Contribute Materials
58
 
59
  Have notes that could help others? Submit them through a simple form — they'll be reviewed and added for everyone automatically.
pages/Plexi-Assistant.py CHANGED
@@ -19,7 +19,8 @@ from utils import (
19
  load_subject_context,
20
  render_page_header,
21
  render_panel,
22
- render_sidebar,
 
23
  render_stat_cards,
24
  summarize_subject_catalog,
25
  )
@@ -100,6 +101,9 @@ PROVIDERS = {
100
  },
101
  }
102
  PROVIDER_NAMES = list(PROVIDERS.keys())
 
 
 
103
 
104
 
105
  def _matches_scope(node, semester: str, subject: str) -> bool:
@@ -114,6 +118,24 @@ def queue_prompt(prompt: str):
114
  st.rerun()
115
 
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  def local_retrieve(index, query: str, semester: str, subject: str, top_k: int = TOP_K):
118
  """Retrieve top-k relevant chunks scoped to the active semester + subject."""
119
  if index is None:
@@ -189,7 +211,7 @@ def _is_configured():
189
  )
190
 
191
 
192
- def render_onboarding():
193
  """Render the setup flow before chat becomes available."""
194
  render_page_header(
195
  "Plexi assistant",
@@ -244,7 +266,40 @@ def render_onboarding():
244
  placeholder="Paste your API key here",
245
  )
246
 
247
- can_start = bool(model_name and (not needs_key or api_key))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  if st.button(
249
  "Start Chatting",
250
  type="primary",
@@ -254,12 +309,15 @@ def render_onboarding():
254
  st.session_state.cfg_provider = provider_name
255
  st.session_state.cfg_base_url = base_url
256
  st.session_state.cfg_model = model_name
 
 
257
  if api_key:
258
  st.session_state.api_key = api_key
259
  st.session_state.pop("messages", None)
260
  st.rerun()
261
 
262
  with right_col:
 
263
  render_panel(
264
  "What Plexi does",
265
  "Keeps answers grounded in the currently loaded course materials instead of drifting into generic knowledge.",
@@ -288,10 +346,20 @@ def render_onboarding():
288
  )
289
 
290
 
291
- render_sidebar()
 
 
 
 
 
 
 
 
 
 
292
 
293
  if not _is_configured():
294
- render_onboarding()
295
  st.stop()
296
 
297
  provider_name = st.session_state.cfg_provider
@@ -303,16 +371,6 @@ rag_index, rag_error = fetch_rag_index()
303
  rag_active = rag_index is not None
304
  mode_label = "RAG retrieval" if rag_active else "Full-context fallback"
305
 
306
- try:
307
- manifest = get_manifest()
308
- except Exception as err:
309
- st.error(f"Failed to load materials catalog: {err}")
310
- st.stop()
311
-
312
- if not manifest:
313
- st.info("No study materials are available yet.")
314
- st.stop()
315
-
316
  with st.sidebar:
317
  st.markdown(
318
  '<div class="plexi-section-label">Study Scope</div>',
@@ -431,6 +489,8 @@ with st.sidebar:
431
  st.session_state.pop("messages", None)
432
  st.rerun()
433
 
 
 
434
  render_page_header(
435
  "Plexi assistant",
436
  f"Ask anything from {selected_subject}",
@@ -441,6 +501,8 @@ render_page_header(
441
  badges=[selected_semester, selected_subject, provider_name, mode_label],
442
  )
443
 
 
 
444
  render_stat_cards(
445
  [
446
  {
 
19
  load_subject_context,
20
  render_page_header,
21
  render_panel,
22
+ render_sidebar_footer,
23
+ render_sidebar_intro,
24
  render_stat_cards,
25
  summarize_subject_catalog,
26
  )
 
101
  },
102
  }
103
  PROVIDER_NAMES = list(PROVIDERS.keys())
104
+ PLEXI_GPT_URL = "https://chatgpt.com/g/g-69caa671910481919ce71d19952e34e5-plexi"
105
+ PLEXI_MCP_GUIDE_URL = "https://lazyhuman.notion.site/Setting-Up-Plexi-MCP-for-Claude-and-ChatGPT-336e3502f0918090b69fdbed148e8e55"
106
+ PLEXI_MCP_ENDPOINT = "https://plexi-mcp.vercel.app/api/mcp"
107
 
108
 
109
  def _matches_scope(node, semester: str, subject: str) -> bool:
 
118
  st.rerun()
119
 
120
 
121
+ def render_external_access():
122
+ """Render low-emphasis outbound access actions."""
123
+ st.markdown(
124
+ '<div class="plexi-section-label">Use Plexi Elsewhere</div>',
125
+ unsafe_allow_html=True,
126
+ )
127
+ st.caption(
128
+ "Open the GPT directly or use the MCP endpoint in any compatible client."
129
+ )
130
+ button_cols = st.columns([1, 1], gap="small")
131
+ with button_cols[0]:
132
+ st.link_button("Open Plexi GPT", PLEXI_GPT_URL, use_container_width=True)
133
+ with button_cols[1]:
134
+ st.link_button("Open MCP Guide", PLEXI_MCP_GUIDE_URL, use_container_width=True)
135
+ with st.expander("MCP endpoint", expanded=False):
136
+ st.code(PLEXI_MCP_ENDPOINT, language=None)
137
+
138
+
139
  def local_retrieve(index, query: str, semester: str, subject: str, top_k: int = TOP_K):
140
  """Retrieve top-k relevant chunks scoped to the active semester + subject."""
141
  if index is None:
 
211
  )
212
 
213
 
214
+ def render_onboarding(manifest):
215
  """Render the setup flow before chat becomes available."""
216
  render_page_header(
217
  "Plexi assistant",
 
266
  placeholder="Paste your API key here",
267
  )
268
 
269
+ semester_names = sorted(manifest.keys())
270
+ default_semester = st.session_state.get("asst_semester")
271
+ semester_index = (
272
+ semester_names.index(default_semester)
273
+ if default_semester in semester_names
274
+ else 0
275
+ )
276
+ selected_semester = st.selectbox(
277
+ "Semester",
278
+ semester_names,
279
+ index=semester_index,
280
+ key="ob_semester",
281
+ )
282
+
283
+ subject_names = sorted(manifest[selected_semester].keys())
284
+ default_subject = st.session_state.get("asst_subject")
285
+ subject_index = (
286
+ subject_names.index(default_subject)
287
+ if default_subject in subject_names
288
+ else 0
289
+ )
290
+ selected_subject = st.selectbox(
291
+ "Subject",
292
+ subject_names,
293
+ index=subject_index,
294
+ key="ob_subject",
295
+ )
296
+
297
+ can_start = bool(
298
+ model_name
299
+ and selected_semester
300
+ and selected_subject
301
+ and (not needs_key or api_key)
302
+ )
303
  if st.button(
304
  "Start Chatting",
305
  type="primary",
 
309
  st.session_state.cfg_provider = provider_name
310
  st.session_state.cfg_base_url = base_url
311
  st.session_state.cfg_model = model_name
312
+ st.session_state.asst_semester = selected_semester
313
+ st.session_state.asst_subject = selected_subject
314
  if api_key:
315
  st.session_state.api_key = api_key
316
  st.session_state.pop("messages", None)
317
  st.rerun()
318
 
319
  with right_col:
320
+ render_external_access()
321
  render_panel(
322
  "What Plexi does",
323
  "Keeps answers grounded in the currently loaded course materials instead of drifting into generic knowledge.",
 
346
  )
347
 
348
 
349
+ render_sidebar_intro()
350
+
351
+ try:
352
+ manifest = get_manifest()
353
+ except Exception as err:
354
+ st.error(f"Failed to load materials catalog: {err}")
355
+ st.stop()
356
+
357
+ if not manifest:
358
+ st.info("No study materials are available yet.")
359
+ st.stop()
360
 
361
  if not _is_configured():
362
+ render_onboarding(manifest)
363
  st.stop()
364
 
365
  provider_name = st.session_state.cfg_provider
 
371
  rag_active = rag_index is not None
372
  mode_label = "RAG retrieval" if rag_active else "Full-context fallback"
373
 
 
 
 
 
 
 
 
 
 
 
374
  with st.sidebar:
375
  st.markdown(
376
  '<div class="plexi-section-label">Study Scope</div>',
 
489
  st.session_state.pop("messages", None)
490
  st.rerun()
491
 
492
+ render_sidebar_footer()
493
+
494
  render_page_header(
495
  "Plexi assistant",
496
  f"Ask anything from {selected_subject}",
 
501
  badges=[selected_semester, selected_subject, provider_name, mode_label],
502
  )
503
 
504
+ render_external_access()
505
+
506
  render_stat_cards(
507
  [
508
  {
utils.py CHANGED
@@ -787,13 +787,9 @@ def get_mime_type(filename):
787
  return mime or "application/octet-stream"
788
 
789
 
790
- def render_sidebar():
791
- """Render the shared sidebar with branding and outbound links."""
792
  with st.sidebar:
793
- current_mode = get_theme_mode()
794
- widget_value = current_mode.capitalize()
795
- if st.session_state.get(THEME_MODE_WIDGET_KEY) != widget_value:
796
- st.session_state[THEME_MODE_WIDGET_KEY] = widget_value
797
  st.markdown(
798
  """
799
  <section class="plexi-sidecard">
@@ -807,6 +803,15 @@ def render_sidebar():
807
  """,
808
  unsafe_allow_html=True,
809
  )
 
 
 
 
 
 
 
 
 
810
  st.markdown(
811
  '<div class="plexi-section-label">Appearance</div>',
812
  unsafe_allow_html=True,
@@ -833,6 +838,12 @@ def render_sidebar():
833
  st.markdown('<div class="plexi-divider"></div>', unsafe_allow_html=True)
834
 
835
 
 
 
 
 
 
 
836
  def read_pdf_text(pdf_bytes):
837
  """Extract text from PDF bytes with error handling."""
838
  text = []
 
787
  return mime or "application/octet-stream"
788
 
789
 
790
+ def render_sidebar_intro():
791
+ """Render the shared sidebar intro card."""
792
  with st.sidebar:
 
 
 
 
793
  st.markdown(
794
  """
795
  <section class="plexi-sidecard">
 
803
  """,
804
  unsafe_allow_html=True,
805
  )
806
+
807
+
808
+ def render_sidebar_footer():
809
+ """Render shared appearance controls and outbound links at the end of the sidebar."""
810
+ with st.sidebar:
811
+ current_mode = get_theme_mode()
812
+ widget_value = current_mode.capitalize()
813
+ if st.session_state.get(THEME_MODE_WIDGET_KEY) != widget_value:
814
+ st.session_state[THEME_MODE_WIDGET_KEY] = widget_value
815
  st.markdown(
816
  '<div class="plexi-section-label">Appearance</div>',
817
  unsafe_allow_html=True,
 
838
  st.markdown('<div class="plexi-divider"></div>', unsafe_allow_html=True)
839
 
840
 
841
+ def render_sidebar():
842
+ """Render the shared sidebar for pages without extra sidebar sections."""
843
+ render_sidebar_intro()
844
+ render_sidebar_footer()
845
+
846
+
847
  def read_pdf_text(pdf_bytes):
848
  """Extract text from PDF bytes with error handling."""
849
  text = []