diff --git a/app/pages/2_Settings.py b/app/pages/2_Settings.py index 0275932..60b955e 100644 --- a/app/pages/2_Settings.py +++ b/app/pages/2_Settings.py @@ -77,10 +77,104 @@ Return ONLY valid JSON in this exact format: pass return {"suggested_titles": [], "suggested_excludes": []} -tab_search, tab_llm, tab_notion, tab_services, tab_resume, tab_email, tab_skills = st.tabs( - ["πŸ”Ž Search", "πŸ€– LLM Backends", "πŸ“š Notion", "πŸ”Œ Services", "πŸ“ Resume Profile", "πŸ“§ Email", "🏷️ Skills"] +tab_profile, tab_search, tab_llm, tab_notion, tab_services, tab_resume, tab_email, tab_skills = st.tabs( + ["πŸ‘€ My Profile", "πŸ”Ž Search", "πŸ€– LLM Backends", "πŸ“š Notion", + "πŸ”Œ Services", "πŸ“ Resume Profile", "πŸ“§ Email", "🏷️ Skills"] ) +USER_CFG = CONFIG_DIR / "user.yaml" + +with tab_profile: + from scripts.user_profile import UserProfile as _UP, _DEFAULTS as _UP_DEFAULTS + import yaml as _yaml_up + + st.caption("Your identity and service configuration. Saved values drive all LLM prompts, PDF headers, and service connections.") + + _u = _yaml_up.safe_load(USER_CFG.read_text()) or {} if USER_CFG.exists() else {} + _svc = {**_UP_DEFAULTS["services"], **_u.get("services", {})} + + with st.expander("πŸ‘€ Identity", expanded=True): + c1, c2 = st.columns(2) + u_name = c1.text_input("Full Name", _u.get("name", "")) + u_email = c1.text_input("Email", _u.get("email", "")) + u_phone = c2.text_input("Phone", _u.get("phone", "")) + u_linkedin = c2.text_input("LinkedIn URL", _u.get("linkedin", "")) + u_summary = st.text_area("Career Summary (used in LLM prompts)", + _u.get("career_summary", ""), height=100) + + with st.expander("πŸ”’ Sensitive Employers (NDA)"): + st.caption("Companies listed here appear as 'previous employer (NDA)' in research briefs.") + nda_list = list(_u.get("nda_companies", [])) + if nda_list: + nda_cols = st.columns(len(nda_list)) + _to_remove = None + for i, company in enumerate(nda_list): + if nda_cols[i].button(f"Γ— {company}", key=f"rm_nda_{company}"): + _to_remove = company + if _to_remove: + nda_list.remove(_to_remove) + nc, nb = st.columns([4, 1]) + new_nda = nc.text_input("Add employer", key="new_nda", + label_visibility="collapsed", placeholder="Employer name…") + if nb.button("οΌ‹ Add", key="add_nda") and new_nda.strip(): + nda_list.append(new_nda.strip()) + + with st.expander("πŸ“ File Paths"): + u_docs = st.text_input("Documents directory", _u.get("docs_dir", "~/Documents/JobSearch")) + u_ollama = st.text_input("Ollama models directory", _u.get("ollama_models_dir", "~/models/ollama")) + u_vllm = st.text_input("vLLM models directory", _u.get("vllm_models_dir", "~/models/vllm")) + + with st.expander("βš™οΈ Inference Profile"): + _profiles = ["remote", "cpu", "single-gpu", "dual-gpu"] + u_inf_profile = st.selectbox("Active profile", _profiles, + index=_profiles.index(_u.get("inference_profile", "remote"))) + + with st.expander("πŸ”Œ Service Ports & Hosts"): + st.caption("Advanced β€” change only if services run on non-default ports or remote hosts.") + sc1, sc2, sc3 = st.columns(3) + with sc1: + st.markdown("**Ollama**") + svc_ollama_host = st.text_input("Host", _svc["ollama_host"], key="svc_ollama_host") + svc_ollama_port = st.number_input("Port", value=_svc["ollama_port"], step=1, key="svc_ollama_port") + svc_ollama_ssl = st.checkbox("SSL", _svc["ollama_ssl"], key="svc_ollama_ssl") + svc_ollama_verify = st.checkbox("Verify cert", _svc["ollama_ssl_verify"], key="svc_ollama_verify") + with sc2: + st.markdown("**vLLM**") + svc_vllm_host = st.text_input("Host", _svc["vllm_host"], key="svc_vllm_host") + svc_vllm_port = st.number_input("Port", value=_svc["vllm_port"], step=1, key="svc_vllm_port") + svc_vllm_ssl = st.checkbox("SSL", _svc["vllm_ssl"], key="svc_vllm_ssl") + svc_vllm_verify = st.checkbox("Verify cert", _svc["vllm_ssl_verify"], key="svc_vllm_verify") + with sc3: + st.markdown("**SearXNG**") + svc_sxng_host = st.text_input("Host", _svc["searxng_host"], key="svc_sxng_host") + svc_sxng_port = st.number_input("Port", value=_svc["searxng_port"], step=1, key="svc_sxng_port") + svc_sxng_ssl = st.checkbox("SSL", _svc["searxng_ssl"], key="svc_sxng_ssl") + svc_sxng_verify = st.checkbox("Verify cert", _svc["searxng_ssl_verify"], key="svc_sxng_verify") + + if st.button("πŸ’Ύ Save Profile", type="primary", key="save_user_profile"): + new_data = { + "name": u_name, "email": u_email, "phone": u_phone, + "linkedin": u_linkedin, "career_summary": u_summary, + "nda_companies": nda_list, + "docs_dir": u_docs, "ollama_models_dir": u_ollama, "vllm_models_dir": u_vllm, + "inference_profile": u_inf_profile, + "services": { + "streamlit_port": _svc["streamlit_port"], + "ollama_host": svc_ollama_host, "ollama_port": int(svc_ollama_port), + "ollama_ssl": svc_ollama_ssl, "ollama_ssl_verify": svc_ollama_verify, + "vllm_host": svc_vllm_host, "vllm_port": int(svc_vllm_port), + "vllm_ssl": svc_vllm_ssl, "vllm_ssl_verify": svc_vllm_verify, + "searxng_host": svc_sxng_host, "searxng_port": int(svc_sxng_port), + "searxng_ssl": svc_sxng_ssl, "searxng_ssl_verify": svc_sxng_verify, + } + } + save_yaml(USER_CFG, new_data) + # Reload from disk so URL generation uses saved values + from scripts.generate_llm_config import apply_service_urls as _apply_urls + _apply_urls(_UP(USER_CFG), LLM_CFG) + st.success("Profile saved and service URLs updated.") + st.rerun() + # ── Search tab ─────────────────────────────────────────────────────────────── with tab_search: cfg = load_yaml(SEARCH_CFG) @@ -364,21 +458,12 @@ with tab_llm: for n in new_order )) - col_save_llm, col_sync_llm = st.columns(2) - if col_save_llm.button("πŸ’Ύ Save LLM settings", type="primary"): + if st.button("πŸ’Ύ Save LLM settings", type="primary"): save_yaml(LLM_CFG, {**cfg, "backends": updated_backends, "fallback_order": new_order}) st.session_state.pop("_llm_order", None) st.session_state.pop("_llm_order_cfg_key", None) st.success("LLM settings saved!") - if col_sync_llm.button("πŸ”„ Sync URLs from Profile", help="Regenerate backend base_url values from your service host/port settings in user.yaml"): - if _profile is not None: - from scripts.generate_llm_config import apply_service_urls as _apply_urls - _apply_urls(_profile, LLM_CFG) - st.success("Profile saved and service URLs updated.") - else: - st.warning("No user profile found β€” configure it in the My Profile tab first.") - # ── Notion tab ──────────────────────────────────────────────────────────────── with tab_notion: cfg = load_yaml(NOTION_CFG) if NOTION_CFG.exists() else {}