Spaces:
Running
Running
| import os | |
| from groq import Groq | |
| import gradio as gr | |
| import pypdf,numpy as np | |
| from docx import Document | |
| from pptx import Presentation | |
| from sentence_transformers import SentenceTransformer | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| from reportlab.platypus import SimpleDocTemplate,Paragraph,Spacer,Table,TableStyle | |
| from reportlab.lib.styles import getSampleStyleSheet | |
| from reportlab.lib.pagesizes import A4 | |
| from reportlab.lib.units import inch | |
| from reportlab.lib import colors | |
| from io import BytesIO | |
| os.environ["HF_HOME"] = "/tmp/huggingface" | |
| client = Groq(api_key=os.environ.get("GROQ_API_KEY")) | |
| embed = SentenceTransformer("all-MiniLM-L6-v2","cpu") | |
| MODELS = ["llama-3.3-70b-versatile","gpt-oss-120b","gpt-oss-20b","llama-3.1-8b-instant"] | |
| ECB_RENTE = "**ECB depositorente 2026**\nHuidig: **2,00 %** (feb 2026)\nVerwachting: geen verandering in 2026 (85–90 %)\nStabiel tot eind 2026 / begin 2027\nEerste verhoging waarschijnlijk 2027" | |
| ECB_INFLATIE = "**Eurozone inflatie 2026**\nECB staff: **1,9 %** headline\nSPF/Reuters: **1,8–1,9 %**\nCore: **2,0–2,2 %**\nLanger termijn: stabiel ~2,0 %" | |
| def read(f): | |
| t="" | |
| for x in f or []: | |
| try: | |
| e=os.path.splitext(x.name)[1].lower() | |
| if e==".pdf":t+="".join(p.extract_text()or""for p in pypdf.PdfReader(x.name).pages) | |
| elif e==".docx":t+="\n".join(p.text for p in Document(x.name).paragraphs) | |
| elif e==".pptx":t+="\n".join(sh.text for s in Presentation(x.name).slides for sh in s.shapes if hasattr(sh,"text")) | |
| elif e in(".txt",".md"):t+=open(x.name,"r",encoding="utf-8",errors="ignore").read() | |
| except:pass | |
| return t.strip() | |
| def analyse(q,f,l,m): | |
| if not q.strip()or not f:return "Vraag + bestanden verplicht.",None,gr.update(visible=False) | |
| if not client.api_key:return "GROQ_API_KEY ontbreekt.",None,gr.update(visible=False) | |
| txt=read(f) | |
| if len(txt)<50:return "Te weinig tekst.",None,gr.update(visible=False) | |
| ch=[txt[i:i+600].strip()for i in range(0,len(txt),450)if len(txt[i:i+600].strip())>30] | |
| if not ch:return "Geen bruikbare tekst.",None,gr.update(visible=False) | |
| sc=cosine_similarity(embed.encode([q]),embed.encode(ch)).flatten() | |
| idx=np.argsort(sc)[-3:][::-1] | |
| ctx="\n\n".join(ch[i]for i in idx) | |
| msgs=[{"role":"system","content":f"Strategische analist. Antwoord alleen in {l}."}, | |
| {"role":"user","content":f"Context:\n{ctx}\n\nVraag: {q}"}] | |
| s=client.chat.completions.create(model=m,messages=msgs,temperature=0.65,max_tokens=4500,stream=True) | |
| u=m | |
| if isinstance(s,str): | |
| for fb in[m for m in MODELS if m!=m]: | |
| s=client.chat.completions.create(model=fb,messages=msgs,temperature=0.65,max_tokens=4500,stream=True) | |
| if not isinstance(s,str):u=fb;break | |
| else:return f"Alle modellen faalden.\n\n{s}",None,gr.update(visible=False) | |
| a="" | |
| for c in s:a+=c.choices[0].delta.content or "" | |
| pdf=BytesIO() | |
| try: | |
| doc=SimpleDocTemplate(pdf,pagesize=A4) | |
| st=getSampleStyleSheet() | |
| d=[["Match","Tekst"]]+[[f"{sc[i]:.1%}",Paragraph(ch[i].replace("\n","<br/>"),st["BodyText"])]for i in idx] | |
| t=Table(d,colWidths=[0.6*inch,6.7*inch]) | |
| t.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,0),colors.HexColor("#004080")), | |
| ("GRID",(0,0),(-1,-1),0.3,colors.grey), | |
| ("VALIGN",(0,0),(-1,-1),"TOP"), | |
| ("FONTSIZE",(0,0),(-1,-1),6.5)])) | |
| story=[Paragraph("RAPPORT",st["Title"]),Spacer(1,0.08*inch), | |
| Paragraph(f"{q} • {u}",st["Heading2"]),Spacer(1,0.08*inch), | |
| Paragraph(a.replace("\n","<br/>"),st["Normal"]),Spacer(1,0.15*inch),t] | |
| doc.build(story) | |
| pdf.seek(0) | |
| return f"<div style='white-space:pre-wrap;line-height:1.7;font-size:17px;color:#00f0ff;text-shadow:0 0 10px #00f0ff;user-select:text;'>{a.replace('\n','<br>')}</div>", \ | |
| {"value": pdf.getvalue(), "orig_name": "rapport.pdf", "mime_type": "application/pdf"}, \ | |
| gr.update(visible=True) | |
| except Exception as e: | |
| print("PDF-fout:",e) | |
| return f"<div style='white-space:pre-wrap;line-height:1.7;font-size:17px;color:#00f0ff;text-shadow:0 0 10px #00f0ff;user-select:text;'>{a.replace('\n','<br>')}</div>", None, gr.update(visible=False) | |
| css=""" | |
| .gradio-container{background:#001122;color:#00f0ff;font-family:system-ui,sans-serif;max-width:1400px;margin:1.5rem auto;border:1px solid #00408088;border-radius:12px;} | |
| h1,h2,h3{color:#00f0ff !important;text-shadow:0 0 16px #00f0ffcc;font-weight:700;} | |
| .gr-markdown,.gr-markdown *{color:#00f0ff !important;text-shadow:0 0 14px #00f0ffaa !important;} | |
| .gr-file-label,.gr-file-description,.gr-label,.gr-input-label,.gr-dropdown-label,.gr-file-upload-text{color:#00f0ff !important;text-shadow:0 0 10px #00f0ff;font-weight:bold;} | |
| .gr-dropdown,.gr-dropdown *{color:#00f0ff !important;} | |
| button{background:linear-gradient(135deg,#00aaff,#004080) !important;color:#000 !important;font-weight:600;border:none;border-radius:8px;box-shadow:0 4px 15px #004080aa;} | |
| button:hover{box-shadow:0 8px 25px #00aaffdd;} | |
| input,textarea{background:#002244 !important;border:1px solid #004080 !important;color:#00f0ff !important;border-radius:8px;} | |
| .gr-html{background:#0a1421;padding:1.5rem;border-radius:8px;border:1px solid #00408088;} | |
| footer{display:none;} | |
| """ | |
| with gr.Blocks(title="Strategische Analyse")as d: | |
| with gr.Tabs(): | |
| with gr.Tab("Document Analyse"): | |
| with gr.Row(): | |
| with gr.Column(scale=1,min_width=320): | |
| f=gr.File(file_count="multiple",file_types=[".pdf",".docx",".pptx",".txt",".md"],label="Bestanden") | |
| l=gr.Dropdown(["Nederlands","English","Español","Deutsch"],value="Nederlands",label="Taal") | |
| gr.Markdown("**Model suggesties** \n• Diep/vergelijken → gpt-oss-120b \n• Snel samenvatten → gpt-oss-20b \n• Strategisch → llama-3.3-70b-versatile \n• Testen → llama-3.1-8b-instant") | |
| m=gr.Dropdown(MODELS,value=MODELS[0],label="Kies een model...",interactive=True) | |
| q=gr.Textbox(label="Vraag",lines=3,placeholder="Wat wil je weten?") | |
| with gr.Row():r=gr.Button("Analyseren",variant="primary");c=gr.Button("Reset") | |
| dl=gr.DownloadButton("Opslaan als PDF",visible=False) | |
| with gr.Column(scale=2): | |
| o=gr.HTML("<div style='padding:1rem;color:#00f0ff;text-shadow:0 0 10px #00f0ff;'>Klaar…</div>") | |
| def rst():return None,"Nederlands",MODELS[0],"","<div style='padding:1rem;color:#00f0ff;text-shadow:0 0 10px #00f0ff;'>Klaar…</div>",gr.update(visible=False) | |
| c.click(rst,outputs=[f,l,m,q,o,dl]) | |
| r.click(analyse,inputs=[q,f,l,m],outputs=[o,dl]) | |
| with gr.Tab("Dashboard (ECB 2026)"): | |
| gr.Markdown("### Actuele ECB Verwachtingen – Februari 2026") | |
| gr.Markdown(ECB_RENTE) | |
| gr.Markdown(ECB_INFLATIE) | |
| gr.Markdown("**Bron:** ECB projections, SPF Q1 2026, Reuters-poll. Data-dependent beleid.") | |
| d.launch(css=css) |