feat: dismissible setup banners on Home page (13 contextual prompts)

This commit is contained in:
pyr0ball 2026-02-25 09:53:34 -08:00
parent 7fa3aa3848
commit 9439246383
2 changed files with 76 additions and 0 deletions

View file

@ -8,6 +8,7 @@ import sys
from pathlib import Path from pathlib import Path
import streamlit as st import streamlit as st
import yaml
sys.path.insert(0, str(Path(__file__).parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent))
@ -24,6 +25,35 @@ from scripts.task_runner import submit_task
init_db(DEFAULT_DB) init_db(DEFAULT_DB)
_SETUP_BANNERS = [
{"key": "connect_cloud", "text": "Connect a cloud service for resume/cover letter storage",
"link_label": "Settings → Integrations"},
{"key": "setup_email", "text": "Set up email sync to catch recruiter outreach",
"link_label": "Settings → Email"},
{"key": "setup_email_labels", "text": "Set up email label filters for auto-classification",
"link_label": "Settings → Email (label guide)"},
{"key": "tune_mission", "text": "Tune your mission preferences for better cover letters",
"link_label": "Settings → My Profile"},
{"key": "configure_keywords", "text": "Configure keywords and blocklist for smarter search",
"link_label": "Settings → Search"},
{"key": "upload_corpus", "text": "Upload your cover letter corpus for voice fine-tuning",
"link_label": "Settings → Fine-Tune"},
{"key": "configure_linkedin", "text": "Configure LinkedIn Easy Apply automation",
"link_label": "Settings → AIHawk"},
{"key": "setup_searxng", "text": "Set up company research with SearXNG",
"link_label": "Settings → Services"},
{"key": "target_companies", "text": "Build a target company list for focused outreach",
"link_label": "Settings → Search"},
{"key": "setup_notifications", "text": "Set up notifications for stage changes",
"link_label": "Settings → Integrations"},
{"key": "tune_model", "text": "Tune a custom cover letter model on your writing",
"link_label": "Settings → Fine-Tune"},
{"key": "review_training", "text": "Review and curate training data for model tuning",
"link_label": "Settings → Fine-Tune"},
{"key": "setup_calendar", "text": "Set up calendar sync to track interview dates",
"link_label": "Settings → Integrations"},
]
def _dismissible(key: str, status: str, msg: str) -> None: def _dismissible(key: str, status: str, msg: str) -> None:
"""Render a dismissible success/error message. key must be unique per task result.""" """Render a dismissible success/error message. key must be unique per task result."""
@ -479,3 +509,24 @@ with st.expander("⚠️ Danger Zone", expanded=False):
if c2.button("Cancel ", use_container_width=True): if c2.button("Cancel ", use_container_width=True):
st.session_state.pop("confirm_purge", None) st.session_state.pop("confirm_purge", None)
st.rerun() st.rerun()
# ── Setup banners ─────────────────────────────────────────────────────────────
if _profile and _profile.wizard_complete:
_dismissed = set(_profile.dismissed_banners)
_pending_banners = [b for b in _SETUP_BANNERS if b["key"] not in _dismissed]
if _pending_banners:
st.divider()
st.markdown("#### Finish setting up Peregrine")
for banner in _pending_banners:
_bcol, _bdismiss = st.columns([10, 1])
with _bcol:
st.info(f"💡 {banner['text']} → _{banner['link_label']}_")
with _bdismiss:
st.write("")
if st.button("", key=f"dismiss_banner_{banner['key']}", help="Dismiss"):
_data = yaml.safe_load(_USER_YAML.read_text()) if _USER_YAML.exists() else {}
_data.setdefault("dismissed_banners", [])
if banner["key"] not in _data["dismissed_banners"]:
_data["dismissed_banners"].append(banner["key"])
_USER_YAML.write_text(yaml.dump(_data, default_flow_style=False, allow_unicode=True))
st.rerun()

View file

@ -0,0 +1,25 @@
import sys
from pathlib import Path
import yaml
sys.path.insert(0, str(Path(__file__).parent.parent))
def test_banner_config_is_complete():
"""All banner keys are strings and all have link destinations."""
from app.Home import _SETUP_BANNERS
for b in _SETUP_BANNERS:
assert "key" in b
assert "text" in b
assert "link_label" in b
def test_banner_dismissed_persists(tmp_path):
"""Dismissing a banner writes to dismissed_banners in user.yaml."""
p = tmp_path / "user.yaml"
p.write_text("name: T\nemail: t@t.com\ncareer_summary: x\nwizard_complete: true\n")
data = yaml.safe_load(p.read_text()) or {}
data.setdefault("dismissed_banners", [])
data["dismissed_banners"].append("connect_cloud")
p.write_text(yaml.dump(data))
reloaded = yaml.safe_load(p.read_text())
assert "connect_cloud" in reloaded["dismissed_banners"]