{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "29b1d5b9",
   "metadata": {},
   "source": [
    "# Cum am construit `app/app.py` — tutorial Gradio (pas cu pas)\n",
    "\n",
    "Ideea Gradio, într-o propoziție: **o funcție Python devine o interfață web.**\n",
    "Tu scrii funcția, Gradio face caseta, butonul și layout-ul.\n",
    "\n",
    "Construim app-ul incremental, exact în ordinea în care e scris `app.py`:\n",
    "funcția simplă -> mai multe input-uri -> tab Chat -> starea partajată ->\n",
    "regula subiect/știre -> tab Agent -> punem tab-urile împreună -> recapitulare.\n",
    "\n",
    "Inspirat din [Gradio Quickstart](https://www.gradio.app/guides/quickstart).\n",
    "Regula tutorialului: **cât mai simplu, doar esențialul.**\n",
    "\n",
    "> `app/app.py` este doar un strat subțire de Gradio peste `core/` (agent, graph),\n",
    "> construit în cursurile C2–C7. Aici nu rescriem `core/` — îl chemăm.\n",
    "> Ca să ruleze fără chei API, folosim un backend fals."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fd15fafe",
   "metadata": {},
   "outputs": [],
   "source": [
    "# o singură dată:  %pip install -q gradio\n",
    "import gradio as gr\n",
    "print(\"Gradio\", gr.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b817ed15",
   "metadata": {},
   "source": [
    "## 1. Cel mai simplu Gradio\n",
    "\n",
    "`gr.Interface` are nevoie de 3 lucruri: `fn` (funcția), `inputs`, `outputs`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "987a917d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def saluta(nume):\n",
    "    return \"Salut, \" + nume\n",
    "\n",
    "gr.Interface(fn=saluta, inputs=\"text\", outputs=\"text\").launch()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5de2ce84",
   "metadata": {},
   "source": [
    "Atât. Caseta, butonul *Submit*, totul l-a făcut Gradio. Pe asta se construiește\n",
    "toată aplicația."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e4b3fbe",
   "metadata": {},
   "source": [
    "## 2. Mai multe input-uri = o listă\n",
    "\n",
    "Tab-urile noastre au mai multe câmpuri. Dacă funcția are mai multe argumente,\n",
    "dai o **listă** la `inputs` (ordinea = ordinea argumentelor)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "49ac7880",
   "metadata": {},
   "outputs": [],
   "source": [
    "def combina(text, optiune, numar):\n",
    "    return f\"[{optiune} @ {numar}] {text}\"\n",
    "\n",
    "gr.Interface(\n",
    "    fn=combina,\n",
    "    inputs=[gr.Textbox(label=\"Text\"),\n",
    "            gr.Dropdown([\"a\", \"b\"], value=\"a\", label=\"Opțiune\"),\n",
    "            gr.Slider(0, 1, value=0.3, step=0.1, label=\"Număr\")],\n",
    "    outputs=gr.Textbox(label=\"Rezultat\"),\n",
    ").launch()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ca10ec6",
   "metadata": {},
   "source": [
    "Reține tiparul `[text, dropdown, slider] -> funcție -> text`. **Asta e un tab.**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3100733c",
   "metadata": {},
   "source": [
    "## 3. Backend fals (ca să rulăm fără chei API)\n",
    "\n",
    "În `app.py` real, sus, sunt 2 importuri din `core/` (construite în C6–C7):\n",
    "\n",
    "```python\n",
    "from core.agent import generate_agent_response   # un agent RAG\n",
    "from core.graph import run_thread                 # dezbatere multi-agent\n",
    "```\n",
    "\n",
    "Aici le înlocuim cu funcții-jucărie. Restul codului rămâne identic ca structură."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c73aeb92",
   "metadata": {},
   "outputs": [],
   "source": [
    "def fake_llm(prompt):\n",
    "    return \"(răspuns simulat) despre: \" + prompt[:70]\n",
    "\n",
    "def fake_agent(slug, stimulus):\n",
    "    voci = {\"anti_sistem\": \"Instituțiile par din nou rupte de oameni.\",\n",
    "            \"pro_european\": \"Să discutăm pe baza procedurilor.\"}\n",
    "    return voci.get(slug, f\"[{slug}] {stimulus[:50]}\")\n",
    "\n",
    "AGENTS = [(\"Anti-sistem\", \"anti_sistem\"), (\"Pro-european\", \"pro_european\")]\n",
    "print(\"backend fals pregătit\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c90c9d3b",
   "metadata": {},
   "source": [
    "## 4. Primul tab real: Chat\n",
    "\n",
    "În `app.py`, tab-ul Chat e funcția `chat()` + un `gr.Interface`. O reproducem\n",
    "cu `fake_llm`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "adcbd188",
   "metadata": {},
   "outputs": [],
   "source": [
    "def chat(prompt):\n",
    "    return fake_llm(prompt) if prompt.strip() else \"Scrie un prompt.\"\n",
    "\n",
    "gr.Interface(\n",
    "    fn=chat,\n",
    "    inputs=gr.Textbox(label=\"Întrebare / prompt\", lines=4),\n",
    "    outputs=gr.Textbox(label=\"Răspuns\", lines=10),\n",
    "    title=\"Chat\",\n",
    ").launch()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1fd5730",
   "metadata": {},
   "source": [
    "Tab-ul Chat complet, fără să scriem vreun buton. Gradio l-a făcut."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "612407c3",
   "metadata": {},
   "source": [
    "## 5. Starea partajată: `CFG` și `ART`\n",
    "\n",
    "Tab-ul **Setări** alege modelul și (opțional) încarcă o știre. Celelalte tab-uri\n",
    "trebuie să **vadă** acea știre. Soluția minimă: două dicționare la nivel de modul.\n",
    "\n",
    "- `CFG` = provider / model / temperatură\n",
    "- `ART` = textul + titlul știrii încărcate\n",
    "\n",
    "Setări **scrie** în ele, restul tab-urilor **citesc**. (Alternativa „canonică\"\n",
    "ar fi `gr.State`; varianta minimă alege simplitatea.)\n",
    "\n",
    "Truc din `app.py`: provider + model sunt **un singur dropdown**\n",
    "(`\"provider|model\"`) — imposibil să fie nepotrivite."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "00c8112e",
   "metadata": {},
   "outputs": [],
   "source": [
    "CFG = {\"provider\": \"gemini\", \"model\": \"gemini-2.5-flash-lite\", \"temp\": 0.3}\n",
    "ART = {\"text\": \"\", \"title\": \"\"}\n",
    "\n",
    "MODEL_CHOICES = [(\"gemini · gemini-2.5-flash-lite\", \"gemini|gemini-2.5-flash-lite\"),\n",
    "                 (\"deepseek · deepseek-chat\",       \"deepseek|deepseek-chat\")]\n",
    "\n",
    "def setup(model_choice, temperature, fake_url):\n",
    "    provider, model = model_choice.split(\"|\", 1)     # despărțim \"provider|model\"\n",
    "    CFG.update(provider=provider, model=model, temp=temperature)\n",
    "    if fake_url.strip():\n",
    "        ART.update(text=f\"Text fals al știrii de la {fake_url}\", title=fake_url)\n",
    "        return f\"Setări salvate. Știre ACTIVĂ: {fake_url}\"\n",
    "    ART.update(text=\"\", title=\"\")\n",
    "    return f\"Setări salvate ({provider} · {model}). Fără știre.\"\n",
    "\n",
    "gr.Interface(\n",
    "    fn=setup,\n",
    "    inputs=[gr.Dropdown(MODEL_CHOICES, value=MODEL_CHOICES[0][1],\n",
    "                        label=\"Provider · Model\"),\n",
    "            gr.Slider(0, 1, value=0.3, step=0.1, label=\"Temperatură\"),\n",
    "            gr.Textbox(label=\"URL știre (gol = fără știre)\")],\n",
    "    outputs=gr.Textbox(label=\"Stare\"),\n",
    "    title=\"Setări\",\n",
    ").launch()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f02655d",
   "metadata": {},
   "source": [
    "## 6. Regula cheie: subiectul intră *peste* știre\n",
    "\n",
    "`_subject()` decide ce primește un agent:\n",
    "\n",
    "- **știre + subiect** -> vorbim despre subiect, dar **în contextul știrii**\n",
    "- **doar știre** -> vorbim despre știre\n",
    "- **doar subiect** -> vorbim doar despre subiect"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af396016",
   "metadata": {},
   "outputs": [],
   "source": [
    "def _subject(typed):\n",
    "    typed = (typed or \"\").strip()\n",
    "    news = ART[\"text\"].strip()\n",
    "    if news and typed:\n",
    "        return f\"{typed}\\n\\n[În contextul acestei știri:]\\n{news[:600]}\"\n",
    "    if news:\n",
    "        return news[:700]\n",
    "    return typed\n",
    "\n",
    "ART.update(text=\"Știre despre UE și energie.\")\n",
    "print(_subject(\"Bolojan\"))     # subiect peste știre\n",
    "ART.update(text=\"\")\n",
    "print(_subject(\"Bolojan\"))     # doar subiect"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6dd4d441",
   "metadata": {},
   "source": [
    "## 7. Tab-ul Agent\n",
    "\n",
    "Tab-ul Agent = `_subject()` + chemarea backend-ului. În `app.py` real,\n",
    "`fake_agent` e `generate_agent_response` din `core.agent` (C6)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0bbe2345",
   "metadata": {},
   "outputs": [],
   "source": [
    "def agent(text, slug):\n",
    "    s = _subject(text)\n",
    "    if not s.strip():\n",
    "        return \"Încarcă o știre sau scrie un subiect.\"\n",
    "    return fake_agent(slug, s)               # în app: generate_agent_response(...)\n",
    "\n",
    "gr.Interface(\n",
    "    fn=agent,\n",
    "    inputs=[gr.Textbox(label=\"Subiect (intră peste știre, dacă e încărcată)\",\n",
    "                       lines=3),\n",
    "            gr.Dropdown(AGENTS, value=\"anti_sistem\", label=\"Agent\")],\n",
    "    outputs=gr.Textbox(label=\"Comentariu\", lines=10),\n",
    "    title=\"Agent\",\n",
    ").launch()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69b5e182",
   "metadata": {},
   "source": [
    "Tab-urile **Rezumat**, **Toți agenții** și **Dezbatere** au exact același tipar:\n",
    "o funcție + un `gr.Interface`. Doar funcția diferă (rezumat / loop pe roluri /\n",
    "`core.graph.run_thread`)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f792b36",
   "metadata": {},
   "source": [
    "## 8. Punem tab-urile împreună\n",
    "\n",
    "`app.py` are 6 tab-uri cu o **temă comună**. `gr.TabbedInterface` nu acceptă\n",
    "`theme=` pe toate versiunile, așa că facem ce face el intern: un `gr.Blocks`\n",
    "cu temă, `gr.Tabs`, și randăm fiecare `Interface` cu `.render()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "04bf3921",
   "metadata": {},
   "outputs": [],
   "source": [
    "tab_setup = gr.Interface(setup,\n",
    "    [gr.Dropdown(MODEL_CHOICES, value=MODEL_CHOICES[0][1], label=\"Provider · Model\"),\n",
    "     gr.Slider(0, 1, value=0.3, step=0.1, label=\"Temperatură\"),\n",
    "     gr.Textbox(label=\"URL știre\")],\n",
    "    gr.Textbox(label=\"Stare\"), title=\"Setări\")\n",
    "\n",
    "tab_chat = gr.Interface(chat, gr.Textbox(label=\"Prompt\", lines=3),\n",
    "    gr.Textbox(label=\"Răspuns\", lines=8), title=\"Chat\")\n",
    "\n",
    "tab_agent = gr.Interface(agent,\n",
    "    [gr.Textbox(label=\"Subiect\", lines=3),\n",
    "     gr.Dropdown(AGENTS, value=\"anti_sistem\", label=\"Agent\")],\n",
    "    gr.Textbox(label=\"Comentariu\", lines=8), title=\"Agent\")\n",
    "\n",
    "TABS = [(\"Setări\", tab_setup), (\"Chat\", tab_chat), (\"Agent\", tab_agent)]\n",
    "\n",
    "with gr.Blocks(theme=gr.themes.Soft(primary_hue=\"orange\")) as demo:\n",
    "    gr.Markdown(\"# EchoChamber Studio\")\n",
    "    with gr.Tabs():\n",
    "        for nume, iface in TABS:\n",
    "            with gr.Tab(nume):\n",
    "                iface.render()\n",
    "\n",
    "demo.launch()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5db5224e",
   "metadata": {},
   "source": [
    "Acesta e scheletul exact din `app.py`. În aplicația reală sunt 6 tab-uri în\n",
    "loc de 3, iar funcțiile cheamă `core/` în loc de `fake_*`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98389176",
   "metadata": {},
   "source": [
    "## 9. Recapitulare\n",
    "\n",
    "**Ce face Gradio (tot tutorialul, pe scurt):**\n",
    "\n",
    "1. `gr.Interface(fn, inputs, outputs)` — o funcție devine interfață.\n",
    "2. Mai multe input-uri = o listă. Un astfel de bloc = un tab.\n",
    "3. `CFG` / `ART` (dict-uri de modul) = starea partajată: Setări scrie, restul citesc.\n",
    "4. `_subject()` = regula subiect-peste-știre.\n",
    "5. `gr.Blocks` + `gr.Tabs` + `.render()` = cele 6 tab-uri cu temă comună.\n",
    "\n",
    "**Dependențe (din structura repo):** `app/app.py` cheamă doar `core/` —\n",
    "nu rescrie nimic.\n",
    "\n",
    "| Tab(uri) | Funcție în app.py | Backend | Curs |\n",
    "|---|---|---|---|\n",
    "| Setări · Chat · Rezumat | `setup` · `chat` · `summary` | apel LLM direct | C2 |\n",
    "| Agent | `agent` -> `_agent` | `core.agent` (FAISS + rol) | C5 + C6 |\n",
    "| Toți agenții | `all_agents` | loop pe `roles.yaml` -> `core.agent` | C6 |\n",
    "| Dezbatere | `debate` | `core.graph.run_thread` (LangGraph) | C7 |\n",
    "\n",
    "`core.agent` -> `core.retriever` (FAISS) + `roles.yaml` + LLM.\n",
    "`core.graph` orchestrează `core.agent` (round-robin). Singura punte offline->runtime:\n",
    "vectorstore-urile construite offline, citite de retriever la fiecare cerere.\n",
    "\n",
    "**Mesajul cheie:** aplicația nu e un proiect nou. E un strat subțire Gradio\n",
    "peste funcțiile din C2–C7. Fiecare tab = un buton peste o funcție de curs."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
