Download app.py from Amdreas/StrojarAI: direct link, hf CLI and curl.
- Browser
- Download file 5.67 kB
-
https://huggingface.co/spaces/Amdreas/StrojarAI/resolve/main/app.py
- Command line
-
hf download hf://spaces/Amdreas/StrojarAI/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Amdreas/StrojarAI/resolve/main/app.py
5.67 kB
| python | |
| import os, io, re | |
| import streamlit as st | |
| from pdfminer.high_level import extract_text | |
| from sentence_transformers import SentenceTransformer | |
| from qdrant_client import QdrantClient | |
| from qdrant_client.http import models as qmodels | |
| import requests | |
| st.set_page_config(page_title="EDM & Fanuc – Hybrid RAG", layout="wide") | |
| st.title("🔧 EDM & Fanuc – Hybrid RAG (PDF + Web)") | |
| # Sidebar configs | |
| with st.sidebar: | |
| st.header("🔐 Secrets") | |
| QDRANT_URL = st.text_input("QDRANT_URL", value=os.getenv("QDRANT_URL", "")) | |
| QDRANT_API_KEY = st.text_input("QDRANT_API_KEY", value=os.getenv("QDRANT_API_KEY", ""), type="password") | |
| TAVILY_API_KEY = st.text_input("TAVILY_API_KEY (optional)", value=os.getenv("TAVILY_API_KEY", ""), type="password") | |
| st.header("⚙️ Settings") | |
| collection = st.text_input("Qdrant collection", value="docs") | |
| topic = st.selectbox("Topic tag", ["EDM","FANUC","general"], index=0) | |
| chunk_chars = st.slider("Chunk size (chars)", 800, 2500, 1500, 100) | |
| overlap = st.slider("Overlap (chars)", 0, 400, 200, 20) | |
| topk = st.slider("Top-K passages", 1, 8, 5, 1) | |
| # Cache models | |
| def get_embedder(): | |
| return SentenceTransformer("intfloat/multilingual-e5-small") | |
| def get_qdrant(url, key): | |
| return QdrantClient(url=url, api_key=key) if url else None | |
| # Helpers | |
| def chunk_text(text: str, size: int, ov: int): | |
| text = re.sub(r"\s+", " ", text) | |
| chunks, i = [], 0 | |
| step = max(1, size - ov) | |
| while i < len(text): | |
| chunk = text[i:i+size] | |
| if chunk.strip(): | |
| chunks.append(chunk) | |
| i += step | |
| return chunks | |
| def ensure_collection(client: QdrantClient, coll: str, dim: int = 384): | |
| try: | |
| client.get_collection(coll) | |
| except Exception: | |
| client.recreate_collection( | |
| collection_name=coll, | |
| vectors_config=qmodels.VectorParams(size=dim, distance=qmodels.Distance.COSINE) | |
| ) | |
| def embed_passages(texts): | |
| emb = get_embedder() | |
| return emb.encode([f"passage: {t}" for t in texts], normalize_embeddings=True).tolist() | |
| def embed_query(text: str): | |
| emb = get_embedder() | |
| return emb.encode([f"query: {text}"], normalize_embeddings=True)[0].tolist() | |
| def upsert_chunks(client, coll, chunks, topic): | |
| vectors = embed_passages(chunks) | |
| points = [] | |
| for c, v in zip(chunks, vectors): | |
| points.append(qmodels.PointStruct(vector=v, payload={"text": c, "topic": topic})) | |
| client.upsert(collection_name=coll, points=points) | |
| return len(points) | |
| def search_local(client, coll, query, topk, filt): | |
| qvec = embed_query(query) | |
| qfilter = None | |
| if filt: | |
| must = [qmodels.FieldCondition(key=k, match=qmodels.MatchValue(value=v)) for k,v in filt.items()] | |
| qfilter = qmodels.Filter(must=must) | |
| res = client.search(collection_name=coll, query_vector=qvec, limit=topk, query_filter=qfilter) | |
| hits = [{"score": r.score, "text": r.payload.get("text",""), "payload": r.payload} for r in res] | |
| return hits | |
| def web_search(query: str, n: int = 3): | |
| if not TAVILY_API_KEY: | |
| return [] | |
| r = requests.post("https://api.tavily.com/search", json={ | |
| "api_key": TAVILY_API_KEY, | |
| "query": query, | |
| "search_depth": "basic", | |
| "max_results": n | |
| }, timeout=30) | |
| data = r.json() | |
| out = [] | |
| for item in data.get("results", [])[:n]: | |
| text = (item.get("title","") + "\n" + item.get("content",""))[:1500] | |
| out.append({"url": item.get("url"), "text": text}) | |
| return out | |
| # Tabs | |
| ingest_tab, chat_tab = st.tabs(["📥 Ingest (PDF)", "💬 Chat"]) | |
| with ingest_tab: | |
| st.subheader("Nahraj PDF (EDM / Fanuc)") | |
| uploaded = st.file_uploader("Vyber PDF súbory", type=["pdf"], accept_multiple_files=True) | |
| if st.button("Indexovať do Qdrant"): | |
| if not (QDRANT_URL and QDRANT_API_KEY and uploaded): | |
| st.error("Vyplň Qdrant URL/API key a nahraj aspoň 1 PDF.") | |
| else: | |
| client = get_qdrant(QDRANT_URL, QDRANT_API_KEY) | |
| ensure_collection(client, collection) | |
| total_chunks = 0 | |
| with st.spinner("Spracúvam PDF…"): | |
| for f in uploaded: | |
| text = extract_text(io.BytesIO(f.getvalue())) | |
| chunks = chunk_text(text, chunk_chars, overlap) | |
| total_chunks += upsert_chunks(client, collection, chunks, topic) | |
| st.success(f"Hotovo. Uložených chunkov: {total_chunks}") | |
| with chat_tab: | |
| st.subheader("Otázka") | |
| q = st.text_area("Zadaj otázku (EDM/Fanuc)", height=100) | |
| do_web = st.checkbox("Povoliť web doplnenie (Tavily)", value=True) | |
| if st.button("Spýtať sa"): | |
| if not (QDRANT_URL and QDRANT_API_KEY and q): | |
| st.error("Vyplň Qdrant URL/API key a otázku.") | |
| else: | |
| client = get_qdrant(QDRANT_URL, QDRANT_API_KEY) | |
| filt = {"topic": topic} | |
| hits = search_local(client, collection, q, topk, filt) | |
| have_good_local = any(h.get("score",0) >= 0.35 for h in hits) | |
| web_snips = web_search(q, n=3) if (do_web and not have_good_local) else [] | |
| st.markdown("### Výsledky z PDF") | |
| for i,h in enumerate(hits): | |
| st.write(f"[{i+1}] (score {h['score']:.2f}) {h['text'][:500]}…") | |
| if web_snips: | |
| st.markdown("### Výsledky z webu") | |
| for j,w in enumerate(web_snips): | |
| st.write(f"[W{j+1}] {w['url']} — {w['text'][:300]}…") | |
| if not hits and not web_snips: | |
| st.warning("Nenašli sa žiadne relevantné zdroje.") |