fix(cloud): use per-user config dir for wizard gate; redirect on invalid session
- app.py: wizard gate now reads get_config_dir()/user.yaml instead of hardcoded repo-level config/ — fixes perpetual onboarding loop in cloud mode where per-user wizard_complete was never seen - app.py: page title corrected to "Peregrine" - cloud_session.py: add get_config_dir() returning per-user config path in cloud mode, repo config/ locally - cloud_session.py: replace st.error() with JS redirect on missing/invalid session token so users land on login page instead of error screen - Home.py, 4_Apply.py, migrate.py: remove remaining AIHawk UI references
This commit is contained in:
parent
f3617abb6b
commit
9603d591a3
5 changed files with 30 additions and 11 deletions
|
|
@ -69,7 +69,7 @@ _SETUP_BANNERS = [
|
|||
{"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"},
|
||||
"link_label": "Settings → Integrations"},
|
||||
{"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",
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ IS_DEMO = os.environ.get("DEMO_MODE", "").lower() in ("1", "true", "yes")
|
|||
import streamlit as st
|
||||
from scripts.db import DEFAULT_DB, init_db, get_active_tasks
|
||||
from app.feedback import inject_feedback_button
|
||||
from app.cloud_session import resolve_session, get_db_path
|
||||
from app.cloud_session import resolve_session, get_db_path, get_config_dir
|
||||
import sqlite3
|
||||
|
||||
st.set_page_config(
|
||||
page_title="Job Seeker",
|
||||
page_title="Peregrine",
|
||||
page_icon="💼",
|
||||
layout="wide",
|
||||
)
|
||||
|
|
@ -80,7 +80,7 @@ except Exception:
|
|||
|
||||
# ── First-run wizard gate ───────────────────────────────────────────────────────
|
||||
from scripts.user_profile import UserProfile as _UserProfile
|
||||
_USER_YAML = Path(__file__).parent.parent / "config" / "user.yaml"
|
||||
_USER_YAML = get_config_dir() / "user.yaml"
|
||||
|
||||
_show_wizard = not IS_DEMO and (
|
||||
not _UserProfile.exists(_USER_YAML)
|
||||
|
|
|
|||
|
|
@ -112,13 +112,19 @@ def resolve_session(app: str = "peregrine") -> None:
|
|||
cookie_header = st.context.headers.get("x-cf-session", "")
|
||||
session_jwt = _extract_session_token(cookie_header)
|
||||
if not session_jwt:
|
||||
st.error("Session token missing. Please log in at circuitforge.tech.")
|
||||
st.components.v1.html(
|
||||
'<script>window.top.location.href = "https://circuitforge.tech/login";</script>',
|
||||
height=0,
|
||||
)
|
||||
st.stop()
|
||||
|
||||
try:
|
||||
user_id = validate_session_jwt(session_jwt)
|
||||
except Exception as exc:
|
||||
st.error(f"Invalid session — please log in again. ({exc})")
|
||||
except Exception:
|
||||
st.components.v1.html(
|
||||
'<script>window.top.location.href = "https://circuitforge.tech/login";</script>',
|
||||
height=0,
|
||||
)
|
||||
st.stop()
|
||||
|
||||
user_path = _user_data_path(user_id, app)
|
||||
|
|
@ -141,6 +147,19 @@ def get_db_path() -> Path:
|
|||
return st.session_state.get("db_path", DEFAULT_DB)
|
||||
|
||||
|
||||
def get_config_dir() -> Path:
|
||||
"""
|
||||
Return the config directory for this session.
|
||||
Cloud: per-user path (<data_root>/<user_id>/peregrine/config/) so each
|
||||
user's YAML files (user.yaml, plain_text_resume.yaml, etc.) are
|
||||
isolated and never shared across tenants.
|
||||
Local: repo-level config/ directory.
|
||||
"""
|
||||
if CLOUD_MODE and st.session_state.get("db_path"):
|
||||
return Path(st.session_state["db_path"]).parent / "config"
|
||||
return Path(__file__).parent.parent.parent / "config"
|
||||
|
||||
|
||||
def get_cloud_tier() -> str:
|
||||
"""
|
||||
Return the current user's cloud tier.
|
||||
|
|
|
|||
|
|
@ -389,7 +389,7 @@ with col_tools:
|
|||
|
||||
st.markdown("---")
|
||||
else:
|
||||
st.warning("Resume YAML not found — check that AIHawk is cloned.")
|
||||
st.warning("Resume profile not found — complete setup or upload a resume in Settings → Resume Profile.")
|
||||
|
||||
# ── Application Q&A ───────────────────────────────────────────────────────
|
||||
with st.expander("💬 Answer Application Questions"):
|
||||
|
|
|
|||
|
|
@ -83,10 +83,10 @@ def _extract_career_summary(source: Path) -> str:
|
|||
|
||||
|
||||
def _extract_personal_info(source: Path) -> dict:
|
||||
"""Extract personal info from aihawk resume yaml."""
|
||||
"""Extract personal info from resume yaml."""
|
||||
resume = source / "config" / "plain_text_resume.yaml"
|
||||
if not resume.exists():
|
||||
resume = source / "aihawk" / "data_folder" / "plain_text_resume.yaml"
|
||||
resume = source / "aihawk" / "data_folder" / "plain_text_resume.yaml" # legacy path
|
||||
if not resume.exists():
|
||||
return {}
|
||||
data = _load_yaml(resume)
|
||||
|
|
@ -196,7 +196,7 @@ def _copy_configs(source: Path, dest: Path, apply: bool) -> None:
|
|||
|
||||
|
||||
def _copy_aihawk_resume(source: Path, dest: Path, apply: bool) -> None:
|
||||
print("\n── Copying AIHawk resume profile")
|
||||
print("\n── Copying resume profile")
|
||||
src = source / "config" / "plain_text_resume.yaml"
|
||||
if not src.exists():
|
||||
src = source / "aihawk" / "data_folder" / "plain_text_resume.yaml"
|
||||
|
|
|
|||
Loading…
Reference in a new issue