| import json |
| import streamlit as st |
|
|
| from corpus_loader import build_corpus |
| from dci_engine import search_corpus |
| from llm_planner import plan_search, synthesize_answer |
|
|
| st.set_page_config(page_title="DCI Agent", page_icon="π", layout="wide") |
| st.title("π DCI Agent") |
| st.caption("Przeszukuj dokumenty bez embeddingΓ³w i baz wektorowych.") |
|
|
| |
| with st.sidebar: |
| st.header("Konfiguracja") |
| api_key = st.text_input("OpenAI API Key", type="password", placeholder="sk-...") |
|
|
| col_btn, col_status = st.columns([1, 2]) |
| with col_btn: |
| fetch_clicked = st.button("Pobierz modele", disabled=not api_key) |
| if fetch_clicked: |
| try: |
| from openai import OpenAI as _OAI |
| _models = _OAI(api_key=api_key).models.list() |
| gpt_ids = sorted( |
| [m.id for m in _models if "gpt" in m.id and "realtime" not in m.id and "audio" not in m.id], |
| reverse=True, |
| ) |
| st.session_state["available_models"] = gpt_ids |
| with col_status: |
| st.success(f"{len(gpt_ids)} modeli") |
| except Exception as e: |
| with col_status: |
| st.error(str(e)) |
|
|
| _default_models = ["gpt-4o-mini", "gpt-4o"] |
| _model_list = st.session_state.get("available_models", _default_models) |
| model = st.selectbox("Model", _model_list) |
|
|
| st.header("Korpus dokumentΓ³w") |
| uploaded = st.file_uploader( |
| "Wgraj pliki (.txt / .md / .pdf)", |
| type=["txt", "md", "pdf"], |
| accept_multiple_files=True, |
| ) |
|
|
| if st.button("ZaΕaduj korpus", disabled=not uploaded): |
| import tempfile, os |
| paths = [] |
| for f in uploaded: |
| suffix = os.path.splitext(f.name)[1] |
| with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp: |
| tmp.write(f.read()) |
| paths.append((tmp.name, f.name)) |
|
|
| corpus = {} |
| for tmp_path, orig_name in paths: |
| from corpus_loader import load_file |
| try: |
| text = load_file(tmp_path) |
| corpus[orig_name] = text.splitlines() |
| except Exception as e: |
| st.warning(f"Nie moΕΌna wczytaΔ {orig_name}: {e}") |
| finally: |
| os.unlink(tmp_path) |
|
|
| st.session_state["corpus"] = corpus |
| total = sum(len(v) for v in corpus.values()) |
| st.success(f"ZaΕadowano {len(corpus)} plik(Γ³w), {total} linii.") |
|
|
| |
| corpus = st.session_state.get("corpus") |
| if corpus: |
| st.info(f"Korpus: {len(corpus)} plik(Γ³w) β " + ", ".join(corpus.keys())) |
|
|
| question = st.text_area("Pytanie", placeholder="Co chcesz wiedzieΔ z dokumentΓ³w?", height=80) |
|
|
| if st.button("π Szukaj", type="primary"): |
| if not api_key: |
| st.error("Podaj klucz OpenAI API w panelu bocznym.") |
| elif not corpus: |
| st.error("Najpierw zaΕaduj korpus w panelu bocznym.") |
| elif not question.strip(): |
| st.error("Wpisz pytanie.") |
| else: |
| from openai import OpenAI |
| client = OpenAI(api_key=api_key) |
|
|
| with st.status("Planowanie zapytaΕ...", expanded=True) as status: |
| plan = plan_search(question, list(corpus.keys()), client) |
| st.write("Plan:", plan.get("strategy", "")) |
|
|
| status.update(label="Przeszukiwanie korpusu...") |
| hits = search_corpus(corpus, plan.get("chain_queries", [])) |
| st.write(f"Znaleziono {len(hits)} trafieΕ.") |
|
|
| status.update(label="Synteza odpowiedzi...") |
| if hits: |
| answer = synthesize_answer(question, hits, client) |
| else: |
| answer = "Nie znaleziono pasujΔ
cych fragmentΓ³w w korpusie." |
| status.update(label="Gotowe", state="complete") |
|
|
| st.subheader("OdpowiedΕΊ") |
| st.markdown(answer) |
|
|
| tab1, tab2 = st.tabs(["Plan wyszukiwania", "Surowe trafienia"]) |
| with tab1: |
| st.json(plan) |
| with tab2: |
| if hits: |
| rows = [ |
| { |
| "plik": h["file"], |
| "linia": h["line_number"], |
| "treΕΔ": h["matched_line"].strip(), |
| "kontekst": " | ".join(l.strip() for l in h["context"]), |
| } |
| for h in hits |
| ] |
| st.dataframe(rows, use_container_width=True) |
| else: |
| st.write("Brak trafieΕ.") |
|
|