import base64 import html import uuid import streamlit as st from ingestion.qdrant_store import ensure_history_collection, ensure_jobs_collection, get_client from rag.pipeline import query st.set_page_config( page_title="JobRAG — Job Search Assistant", page_icon=":material/work:", layout="wide", initial_sidebar_state="expanded", ) CUSTOM_CSS = """ """ def _svg_b64(path: str, replacements: dict | None = None) -> str: with open(path, "rb") as f: data = f.read() for old, new in (replacements or {}).items(): data = data.replace(old.encode(), new.encode()) return base64.b64encode(data).decode() _SEND_B64 = _svg_b64("send-svgrepo-com.svg", {'stroke="#000000"': 'stroke="#ffffff"'}) _SIDEBAR_B64 = _svg_b64("side-list-svgrepo-com.svg") _ICON_CSS_RAW = """""" ICON_CSS = _ICON_CSS_RAW.replace("__SEND__", _SEND_B64).replace("__SIDEBAR__", _SIDEBAR_B64) SUGGESTIONS = [ "Which companies are actively hiring right now?", "What skills are most in demand?", "Are there any remote software engineer roles?", "What seniority levels are available?", "Which jobs require Python or Go?", "What are the most common job requirements?", ] @st.cache_resource(show_spinner="Connecting to knowledge base…") def _init_db(): client = get_client() ensure_jobs_collection(client) ensure_history_collection(client) return client def _render_sources(sources: list) -> None: if not sources: return st.markdown( f'

Sources · {len(sources)}

', unsafe_allow_html=True, ) for job in sources: title = html.escape(job.get("title") or "Untitled role") company = html.escape(job.get("company") or "Unknown company") score = html.escape(str(job.get("score", "—"))) location = html.escape(job.get("location") or "") seniority = html.escape(job.get("seniority_level") or "") employment = html.escape(job.get("employment_type") or "") description = job.get("description") or "No description available." desc_preview = html.escape(description[:400] + ("…" if len(description) > 400 else "")) tags_html = "" if location: tags_html += f'{location}' if seniority: tags_html += f'{seniority}' if employment: tags_html += f'{employment}' tags_html += f'{score}' st.markdown( f"""

{title}

{company}

{tags_html}

{desc_preview}

""", unsafe_allow_html=True, ) if job.get("job_url"): st.link_button("View listing →", job["job_url"], use_container_width=False) def _render_sidebar() -> None: with st.sidebar: st.markdown( '

JobRAG

' '

Semantic search over live job listings.

', unsafe_allow_html=True, ) st.markdown('

Dataset

', unsafe_allow_html=True) st.markdown( """

Software Engineer · Berlin

Germany-focused listings refreshed on a scheduled ingestion pipeline.

""", unsafe_allow_html=True, ) st.markdown('

Session

', unsafe_allow_html=True) st.markdown( f'
{st.session_state.session_id[:20]}…
', unsafe_allow_html=True, ) st.markdown('
', unsafe_allow_html=True) if st.button("New session", use_container_width=True, type="primary"): st.session_state.session_id = str(uuid.uuid4()) st.session_state.messages = [] st.rerun() st.markdown("
", unsafe_allow_html=True) st.markdown('

Stack

', unsafe_allow_html=True) st.markdown( """
Vector store Qdrant Cloud Embeddings qwen3-embedding:8b Chat model gemma3:27b
""", unsafe_allow_html=True, ) def _render_hero() -> None: st.markdown( """

Find the right role, faster.

Ask natural-language questions about Software Engineer openings in Berlin. Every answer is grounded in retrieved listings.

Berlin, Germany Software Engineer RAG-powered
""", unsafe_allow_html=True, ) def _render_suggestions() -> None: st.markdown('

Try asking

', unsafe_allow_html=True) cols = st.columns(2) for i, suggestion in enumerate(SUGGESTIONS): with cols[i % 2]: if st.button(suggestion, use_container_width=True, key=f"suggest_{i}"): st.session_state._prefill = suggestion st.rerun() st.markdown(CUSTOM_CSS, unsafe_allow_html=True) st.markdown(ICON_CSS, unsafe_allow_html=True) try: _init_db() except Exception as e: st.error(f"Could not connect to the knowledge base: {e}") st.stop() if "session_id" not in st.session_state: st.session_state.session_id = str(uuid.uuid4()) if "user_id" not in st.session_state: st.session_state.user_id = str(uuid.uuid4()) if "messages" not in st.session_state: st.session_state.messages = [] _render_sidebar() _render_hero() if not st.session_state.messages: _render_suggestions() for msg in st.session_state.messages: with st.chat_message(msg["role"]): st.markdown(msg["content"]) if msg["role"] == "assistant" and msg.get("sources"): _render_sources(msg["sources"]) prefill = st.session_state.pop("_prefill", None) with st.form("chat_form", clear_on_submit=True): col_text, col_btn = st.columns([15, 1]) with col_text: user_text = st.text_area( "", placeholder="Ask about roles, skills, companies, or requirements…", label_visibility="collapsed", height=68, ) with col_btn: submitted = st.form_submit_button("↑", use_container_width=False) user_input = prefill or (user_text.strip() if submitted and user_text.strip() else None) if user_input: st.session_state.messages.append({"role": "user", "content": user_input, "sources": None}) with st.chat_message("user"): st.markdown(user_input) with st.chat_message("assistant"): try: stream, sources = query( user_message=user_input, user_id=st.session_state.user_id, session_id=st.session_state.session_id, ) response_text = st.write_stream(stream) _render_sources(sources) except Exception as e: response_text = f"Something went wrong: {e}" sources = [] st.error(response_text) st.session_state.messages.append({ "role": "assistant", "content": response_text, "sources": sources, }) st.rerun()