From 7a54693edb4d5b840cba7d2df579ea75b8048314 Mon Sep 17 00:00:00 2001 From: udaya reddy Date: Sat, 12 Sep 2026 14:09:48 +0530 Subject: [PATCH 1/2] Added Slab project --- .gitignore | Bin 829 -> 919 bytes .../.env.example | 3 + .../Dockerfile | 23 + .../README.md | 57 ++ .../__pycache__/main.cpython-313.pyc | Bin 0 -> 1549 bytes .../app/__init__.py | 1 + .../app/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 182 bytes .../app/__pycache__/config.cpython-313.pyc | Bin 0 -> 1108 bytes .../app/__pycache__/models.cpython-313.pyc | Bin 0 -> 2520 bytes .../app/config.py | 17 + .../app/models.py | 40 ++ .../app/routers/__init__.py | 1 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 190 bytes .../__pycache__/research.cpython-313.pyc | Bin 0 -> 1519 bytes .../app/routers/research.py | 27 + .../app/services/__init__.py | 1 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 191 bytes .../__pycache__/agent.cpython-313.pyc | Bin 0 -> 3391 bytes .../__pycache__/gemini.cpython-313.pyc | Bin 0 -> 8200 bytes .../webcmd_service.cpython-313.pyc | Bin 0 -> 10069 bytes .../app/services/agent.py | 69 +++ .../app/services/gemini.py | 181 +++++++ .../app/services/webcmd_service.py | 211 ++++++++ .../list_models.py | 12 + .../Deadline_Detective_Backend_Webcmd/main.py | 33 ++ .../requirements.txt | 7 + SLAB/frontend/index.html | 172 ++++++ SLAB/frontend/script.js | 251 +++++++++ SLAB/frontend/style.css | 497 ++++++++++++++++++ 29 files changed, 1603 insertions(+) create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/.env.example create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/Dockerfile create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/README.md create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/__pycache__/main.cpython-313.pyc create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/__init__.py create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/__pycache__/__init__.cpython-313.pyc create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/__pycache__/config.cpython-313.pyc create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/__pycache__/models.cpython-313.pyc create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/config.py create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/models.py create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/routers/__init__.py create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/routers/__pycache__/__init__.cpython-313.pyc create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/routers/__pycache__/research.cpython-313.pyc create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/routers/research.py create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/services/__init__.py create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/services/__pycache__/__init__.cpython-313.pyc create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/services/__pycache__/agent.cpython-313.pyc create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/services/__pycache__/gemini.cpython-313.pyc create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/services/__pycache__/webcmd_service.cpython-313.pyc create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/services/agent.py create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/services/gemini.py create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/app/services/webcmd_service.py create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/list_models.py create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/main.py create mode 100644 SLAB/Deadline_Detective_Backend_Webcmd/requirements.txt create mode 100644 SLAB/frontend/index.html create mode 100644 SLAB/frontend/script.js create mode 100644 SLAB/frontend/style.css diff --git a/.gitignore b/.gitignore index 8a6192a264dce403a4c06a96601d2c1d133928cc..697533397b19f8c80bdcdd3b9174041be3f06c23 100644 GIT binary patch delta 98 zcmdnXHl2NgEpt>bgAaovgA;>3g9}3{Ln1>8Lk>eGLmrSF4;Co_vXU7}fMR7}F(;s! hWQJ@YI}fNfo*^8_N&<@H0(trjdSLM~23`g(1_0uT5`+K% delta 7 OcmbQvzL#x-Ei(WLr~*;| diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/.env.example b/SLAB/Deadline_Detective_Backend_Webcmd/.env.example new file mode 100644 index 000000000..2c4f49e40 --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/.env.example @@ -0,0 +1,3 @@ +GEMINI_API_KEY=your_gemini_api_key_here +WEBCMD_PROFILE=default +MAX_PAGES=5 diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/Dockerfile b/SLAB/Deadline_Detective_Backend_Webcmd/Dockerfile new file mode 100644 index 000000000..2e3f142f5 --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Install Node.js (required for Webcmd) +RUN apt-get update && apt-get install -y curl && \ + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ + apt-get install -y nodejs && \ + rm -rf /var/lib/apt/lists/* + +# Install Webcmd globally +RUN npm install -g @agentrhq/webcmd + +# Install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY . . + +EXPOSE 8000 + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/README.md b/SLAB/Deadline_Detective_Backend_Webcmd/README.md new file mode 100644 index 000000000..040fbc28e --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/README.md @@ -0,0 +1,57 @@ +# Deadline Detective – Backend (Webcmd Version) + +AI Browser Agent that finds real college opportunities using **Webcmd** for browser automation. + +## What changed +- Replaced raw Playwright with **Webcmd** +- Still uses FastAPI + Gemini for planning and analysis +- Browser control now goes through Webcmd sessions + +## Tech Stack +- FastAPI +- Google Gemini +- **Webcmd** (self-learning browser infrastructure) +- Docker ready + +## Prerequisites + +1. Node.js 20+ +2. Webcmd installed: + ```bash + npm install -g @agentrhq/webcmd + webcmd doctor + ``` + +## Local Setup + +```bash +python -m venv venv +source venv/bin/activate # Windows: venv\Scripts\activate +pip install -r requirements.txt + +# Create .env +cp .env.example .env +# Edit .env and add your GEMINI_API_KEY + +uvicorn main:app --reload --host 0.0.0.0 --port 8000 +``` + +## API + +- `POST /research` → main agent endpoint +- `GET /health` → health check +- Docs: http://localhost:8000/docs + +## How it works + +1. Gemini creates a research plan +2. Webcmd opens a browser session +3. Webcmd visits real websites and extracts content +4. Gemini analyzes eligibility + deadlines +5. Returns prioritized action plan + +## Deploy on Render + +- Use the included Dockerfile +- Add environment variable `GEMINI_API_KEY` +- Make sure the instance has enough memory (Webcmd + browser needs it) diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/__pycache__/main.cpython-313.pyc b/SLAB/Deadline_Detective_Backend_Webcmd/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1aa20de5a9dff4912ddfef13a0fecf69c8e2133 GIT binary patch literal 1549 zcmZux%}*Og6rc6lUVmW&4ww>}u0m+#e3-NqN*vLq5ElhIfykSx2Uc1w-Z9>?-d)WM zP*7Bn=8#CekVDil!4lu1fYRnG?@Po*BwlGBow>aGF}6v02( zzI$pDAU#>V5a3fe1Gg3fPDvM$J{a)oWKVx~o#~V6rL)=|ew$89UrQoHvT&dL|4(Qc zU4e)rFtb5aZLlb5L)vf=G6tuvz2(%Y?YVIj+=%4XM)IR?gn3@#;RhybfK(Pt%H|#{ z0!-gou4?yf%X09RNib-V8Z(()Td*BWfu;ngV(NP?727d_DK<&1u`^u4rsdc!R!f-S z8nZX?4xL+6=ZUvPF;VB%u*+1|FqvAnU5ly&n~qxZ90#vq)$@IiunpH{Hl|nAO-$@M zAQe0In!Rc}HrrO;SFL^surOUqt$_;$MkYk_T-A3>mlk#+9~Q0`uIFVQVK#FxPg{>g@H0>q|auT;}0txWQHjkbt!>-xHT$-+(tyL7r6PU-J!7>TW^#ZN*8x5fq8I;|z z5)P0iiB<{PAdr{P4Y@AmLrXge$SnT4&|pm`FB1ihahWm#WvebPJ^XyBWRw=GuhFsa z$CvKh(!YSxP~CLbZKf}~xJ00AdI{5Y=J~pIZ*Cq)udK#NS&ezKwhnJLzQn7wrlmJc z+b#IpJWM=~kqL199!xq8%Nw+d{!Zpz$}jJ|nr%(o*dM#OpDg|oF8)i!o4%d6AT8y` zxZ*=6I`%3NPv%M;`qA`lB20L$&kD_xwk*^rq&6CBqw{U_ zQ5#)pqswii{uv!OOkC)M(9lBYFqJ)0!b)gY?xc|vK0w1QG<+ByKFp5pO&yMo_oN`5 zB$Bd6C@O_IgC~}IH^0CA{PwSC@+gW1&+Xm)?wfJ{F`UA;N!8@U-}<2JB$qg literal 0 HcmV?d00001 diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/__init__.py b/SLAB/Deadline_Detective_Backend_Webcmd/app/__init__.py new file mode 100644 index 000000000..b9ca8ad19 --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/app/__init__.py @@ -0,0 +1 @@ +# Deadline Detective Backend diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/__pycache__/__init__.cpython-313.pyc b/SLAB/Deadline_Detective_Backend_Webcmd/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..565caa69dc42c6a01c7233b0a329b82dfca7b402 GIT binary patch literal 182 zcmey&%f$c$&Ci!)$pY!eAPx*LLK&Y~fQ+dO=?t2Tek&P@n1H;`AgNnn&Q>v@#i>Qb zF^PHEnI$p)d8sZ%nPsUlE~&-YCHVz0!9I>oKu%&xPG(+eyi00HYH|rsKHe!YIXg8k zB|bbgDLFSKCb6I(CO$qhFS8^*Uaz3?7Kcr4eoARhs$CH)&>E04ib0Hz%#4hTMa)1J E0ELb*T>t<8 literal 0 HcmV?d00001 diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/__pycache__/config.cpython-313.pyc b/SLAB/Deadline_Detective_Backend_Webcmd/app/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f0dbcd8182fccd2571f7b5370f86c944241aa5d GIT binary patch literal 1108 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 CU{UM< literal 0 HcmV?d00001 diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/config.py b/SLAB/Deadline_Detective_Backend_Webcmd/app/config.py new file mode 100644 index 000000000..ed088e40c --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/app/config.py @@ -0,0 +1,17 @@ +from pydantic_settings import BaseSettings +from functools import lru_cache + + +class Settings(BaseSettings): + gemini_api_key: str + webcmd_profile: str = "default" + max_pages: int = 5 + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + + +@lru_cache() +def get_settings() -> Settings: + return Settings() diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/models.py b/SLAB/Deadline_Detective_Backend_Webcmd/app/models.py new file mode 100644 index 000000000..f7dfa77c6 --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/app/models.py @@ -0,0 +1,40 @@ +from pydantic import BaseModel, Field, HttpUrl +from typing import List, Optional +from enum import Enum + + +class StudentProfile(BaseModel): + year: Optional[str] = Field(None, example="1st Year") + branch: Optional[str] = Field(None, example="CSE / AI") + interests: Optional[str] = Field(None, example="hackathons, AI, web development") + location: Optional[str] = Field(None, example="India") + + +class ResearchRequest(BaseModel): + task: str = Field( + ..., + min_length=10, + example="Find currently open opportunities for a first-year CSE/AI student. Check official pages, verify eligibility and deadline, and create a priority list of what I should apply for this week." + ) + profile: Optional[StudentProfile] = None + + +class Opportunity(BaseModel): + title: str + eligible: bool + deadline: Optional[str] = None + what_to_do: str + source: str + reason: Optional[str] = None + + +class ResearchResponse(BaseModel): + opportunities: List[Opportunity] + next_3_actions: List[str] + summary: str + sources_checked: List[str] = [] + + +class HealthResponse(BaseModel): + status: str + message: str diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/routers/__init__.py b/SLAB/Deadline_Detective_Backend_Webcmd/app/routers/__init__.py new file mode 100644 index 000000000..873f7bbbe --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/app/routers/__init__.py @@ -0,0 +1 @@ +# Routers package diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/routers/__pycache__/__init__.cpython-313.pyc b/SLAB/Deadline_Detective_Backend_Webcmd/app/routers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14431124488332a5f8dd51cbcb32b670d79818ca GIT binary patch literal 190 zcmey&%f$c$&Ci!)2?6QHAPx*LLK&Y~fQ+dO=?t2Tek&P@n1H;`AgNn%&Q>v@#i>Qb zF^PHEnI$p)d8sZ%nPsUlE~&-YCHVz0!9I>oKu%&xPG(+eyi00HYH|rsKHe!YIXg8k zB|bbgDLFSKCb6I(rYOI(1Y}Ztd}dx|NqoFsLFFwDo80`A(wtPgB37VvAm%Y+dz)=p3R-me)63WeyPrdPG?R5}hq@8*1?VEXTzVF-Z zr4tCo!@qyMb6rH}FYZJ`9vHo6U_3zvGVnSoVv36b6*TS(>ta!&Qcq-jt>R_>wSdEz+8X) ze{LyaGD?Fr!gX;_-M~wu2w9+pax6+S1Vc2Wyg+bSjbf!_WSy_uC^}^g8FCG}*~d@B z4LOBeyp5-jiZ+z1;%%%e2TO??mg8!~-EDe~>n~}gmaF+SqD7d3F#uKDq266asAh3= zXA`Gfsm}M%?}5H$vAVX`V!pORG|Sf-!~%Q%Bh9W^)UttVrZqgbIt%Qg;T`pf!H8!ATysjO)5cwKn}|VB5k?4KvO)V0CTN|&jfHZE%@rQC371Z zW2WWS9pC&Clt4iR<_ckT-)ox27lmu!^hHt`UZ`@-vg>g3%1yFk@9mmavuV;$n9PiH zGTZz%NI~gJ6rdNOD8D(|gy|HuQ8$57V~>{~FaLby@a`}7e!BN!a{g#?{&@1rac-E^hAM-!t4{siiO}|J_x38Zh-)i4D6H#X74Fk^6gF>z_ zgP&i*g(>OzL=5bM*jz1Cy$!F2W2nsu2=%pD9%|RXTrGKF3WZEd8-d`}52hm&{!MEi z1jw{so5 t)D=`b`gEj+VCjiS_#_N;$9ZD*e)iGYQ@5S%;KegR!U|9LIJCls{sBp?bGiTk literal 0 HcmV?d00001 diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/routers/research.py b/SLAB/Deadline_Detective_Backend_Webcmd/app/routers/research.py new file mode 100644 index 000000000..0f336d643 --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/app/routers/research.py @@ -0,0 +1,27 @@ +from fastapi import APIRouter, HTTPException +from app.models import ResearchRequest, ResearchResponse, HealthResponse +from app.services.agent import DeadlineDetectiveAgent + +router = APIRouter(tags=["Research"]) + +agent = DeadlineDetectiveAgent() + + +@router.post("/research", response_model=ResearchResponse) +async def run_research(request: ResearchRequest): + """ + Main endpoint: Run the Deadline Detective browser agent. + """ + if not request.task or len(request.task.strip()) < 10: + raise HTTPException(status_code=400, detail="Task must be at least 10 characters long.") + + result = await agent.run(request) + return result + + +@router.get("/health", response_model=HealthResponse) +async def health_check(): + return HealthResponse( + status="ok", + message="Deadline Detective backend is running" + ) diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/services/__init__.py b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/__init__.py new file mode 100644 index 000000000..453a80dff --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/__init__.py @@ -0,0 +1 @@ +# Services package - Webcmd version diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/services/__pycache__/__init__.cpython-313.pyc b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad91bb3b878d785ac0b2a069f4c4fcff4c17550a GIT binary patch literal 191 zcmey&%f$c$&Ci!)sQ~H6APx*LLK&Y~fQ+dO=?t2Tek&P@n1H;`AgNpN&Q>v@#i>Qb zF^PHEnI$p)d8sZ%nPsUlE~&-YCHVz0!9I>oKu%&xPG(+eyi00HYH|rsKHe!YIXg8k zB|bbgDLFSKCb6I(253xKW^!tAOniK1US>&ryk0@&Ee@O9{FKt1RJ$TppnV|s6oVKa MnHd=wi7vse zy?3;Jl82%xS_No<7^&gvNj6ZxPM_ST_|ZmF+kKPEq9U#`TA)P>ztL9Pv}hkXd*o5J zVZrDE+?}1-o1OV)cV@ZMP=}y>^tYeCe-EfX(}~x(DzWozAg&+@Nz5dgU?AkA6Mn)^{1XBZTpl+Wmu|{k}%+_XeRY@d@}HuPLhn% zFpd{BLz6JMq^X!PzJ+Ji#jM9*oyKWwVTGvk)A(WmTecI~WmtLBu&`7AUOsOUyI^Sc z3aH@DYL-2vR<^73HLs4s{0hn-!bphtB!;k(uLBWIVl%wNW&Ce*65oN|=ZGNrNnlP$ z2AuX$tYme~z@ym4s;ynZLvz@$9|u5{$~?0Rb&!SM&ex#3f@W&60hrU4MtY4m!&i6+ zJtL)GyKN4znM!hmG93S$ixHttIQ}IUAL^!s^_)c&icV%^Fq5`0u`-IWpxK%Cz*+*9 zW=65KV4L}jbarSMCKX5InkVwGqAp+~D_2m@DEWK_)X-RfD(Yq`zv2XB8DeD1vJ>}I z)HJwQq`U<|u*PbI?t5svz2{SNjr(zZmH0{h4)Bp9kZK(L1lt8-sMT{pg{T33AHi?u z4tU;!M1ZjiV4J407us|UtX)Bxnf4;R%S6ESKQpb85_7_5_&_&};|p!N{sjh1)4qXf z@V*W=VIFqB#9U#g_z*JKG<&4_K^8oip zGM8S8K(qI4E#`27&vn2F5u|2%tH0qpm#FA80*0`A8q#}3r)4)fd&I9*YO8q{sGOw$ z8fT`yYWYO4*AQ|jfo5V}JOrc+LX3qOu&UCg_j}at*j-CK;&W#fPHX!N_+M#h!SfgL zf0RRE)DIX@8!TTOZh|2U3BA=Z$K=qvY#im7V{DE|#-@@!hqD!H!4dMr%xOAaot`p9 zvzD$kY>7FuU}RI`d9orZ;-X2gXyvi0ZSiHeaG zmrSyt8FS*13zd8;W=+*L$%SMp$vF+rIpBm1*h}u0VK>ljIf7MKTvW)4(`cCm0-02H z&Bxg!OX!s$G)_*qY9P+aS;bb!KFZ{6&DOCaq>Tl`Tr!*h*0nipR>!BEK(?B_ow}uY z#g=VT&YG*y5f>R705Ed^nWpGs9-wgimAxIP7z8VpX(aumi*7E`4$dbc-L&hW-9hLa zwm@`34-DyqZZ+kVS-^BLuS4z20+tk$DrU39T!LCr0Y0_*`j8=FLLgYCsul===;MlT?^E5a!(ZS@s%hr}2TtA|Egl%!iVm%v`D;t(cJy$mySLQ4yxFr-_Mrol zzEba@CqX{eTt<9Tb2)-y`>%d*`Gc)!|HJ4Ddz9jb%L0nQHe4*ccBULgZ@#s5t`rkD zLhGSx@~zq5$4-30l_CeWBE1hIz1ywv64)`nK7VcLR&rI-x4Mh*f!eGX|H_jF zp{@O&Y_O^M35)g(K8c~%-YT{a{~~s6==1b1)3*mc&wRT4$;F?w{dD=W*xLAJ`|w{{ zdP-fdgZ&#Hu79|B^qt$Y#rTkC_;)tnyiklOCGjxe&>99D8p2OU7!(_3{{8e6ijFY< z`g=2q?At*?FuWaV+X}^h8;bufl-QKOY%eUSK7?!oy7>K*;gcQ<3_OJcXH z?jKa$f66?3`(I0kkba*T?_z#16gnM6cO%T{M(%D?l+s5i|88=m875zx+;_T}`*kcx z`@|c-xfiD5dyNdGn|azt>&83Tdx^gBcJ_XR0s4Lu4{PqX^OWvnflm6|i{xIEsbr8o z*9>J5!*i`pmKV)zL8o*`mM<0*y((#x<(x(=o4#ueQH~V1$c&a>lvHfyY9DF@{nx^wTGl= z=bCtmQZ=)kz&jOOPLUB1!0kg8O^IceVVFngjYnwUk7(>68heDIe?XBh{qHj2rwBS% G#s2`50UBfg literal 0 HcmV?d00001 diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/services/__pycache__/gemini.cpython-313.pyc b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/__pycache__/gemini.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37be005e53d42882f7da1cbf9f2dd4d03c5c06ca GIT binary patch literal 8200 zcmdT}ZA={3dOow?J1i`)*nGpuV#lm)*4Ka&dmV!?ki^)*3FA$iLb4fl2N*NVZq5w0 z!OfL_Rb1Ux&Bu*w-~I?!YE?(7TKAv(FKHYpmHGqXsBGtwd!w{cm4BP-v^R1pwa=N^ zSuo|MS8b)XBRF$r&O7Jheb4(m?|Jt@wMcM6_t01L?q!X z855X5dEIl?Gv;MpyY4;f8}l>2UH6?0j0IV6EW|?gv;S;(Ol0C%6{{MHu!#LGaJHIN zqmRJZ8dhW9qb%A);zSCz5Gi!bmsMUdmkNr3N@P|sGlpWC zsy=HZJXVclF6I>7e4gcJR86s}&KC-KW-jWgxs>o)zH_Q!TD}oAYg*nReQ7-F4C9nl z<%b7@}aiZDSU%p(cRD|uQ7^GV(*zvP<=^!p@#3%TTDL8)E};9YPkB!#BJQW$sM zE=nSAza%hGszQ5Ys!FQv2@-PQfs&S+;<*%$j)_Lz=$mbLdmi8HnHe-cc+Z%l9lv=M0r>4?|!i*_d zpI6PP^SUy^)XU1$h+@o}`NEWRZs;@~kaK~?e_7F zL#NmAw|98O_xn}FJqK2Ne;_`9B(Z#^a@Di~OfeUko~;-GOYw4b}r@P#P+(}5Ixxpija%Lsh^+* zwf*O_z8FjUe8la|@N|;jYWA=hC7tBFZ-$)mc9I$Beb1mb{DLyYDCBiRq1|(`p3@YAF34;?m%pOZEL@mQYx(TF zkw`9gzV_N{-Gix1MqW=0P8kQgC#Q@l>6L?n2|S?fE583M;hH79@zuZKvV=)XcqI|G z{D#TYg5_a~6)+T;W#>Q;EWcQ=e7rNuujS>OVMY1WMif5iC}VkM`DRo-XZabwCH$-b z-Ln=xhJQgec_)S`Gw=h@z~I0{`Nj5fiRug-m9IBrkI=dU1GYEvU1_vo?$^l|(byj@ z|7iKgzq{i5$J+WE;kUzUEl=Ie-LD;9@&BT#wis)=aq_K`H(!5eU?X;5J$B%JEU_|D z^2EaRo3X|lskc)%bN6FiYmu&RH>0tQXxDnQ>j9|?*MHH_bmPohXKrTS8DAMK*6+P> z@U4S4rFV|34z1VkUl}e&8*X}jeC?+3N7qUoZ(Z|dYv+5ZpQcuGcVesg`>juW+VI4; zn~kj-jfd774?Q44UGv{JG?l!#KP&~vp637AzKab_#YkPr&%6Hul`bscp^=7`K8)@e z+D|^*Kja(rd;j`i3kszS{k*{I6)&EhGhu2#KUT^I~{a&kBJ|3D^S2@ESQ$KAYaXxd0@L?Rhh~{L18jrRTwn9~y3r8*+ zZ@;whRuXjo7Ez{sjt{yX86(fiz?MJ!G30Gs1I-7`8ouvYeI~!9`s#W@WyZZ3mz2 zwwZ?Q*;Tk2cJrSSTz1c4Z{kfz5q9>P5SK@XWleKI`-*DL0l7`KM78{^nx!*&Mge%b z!Gkh7BdZ!*$DlxT2k{FsGZ1JJx|vKSm-kn`d|K6I4Sj3cG=e=UYev2@QX&dQko8%G@l9g|>@d@?5UlDP zFqBMr;`TBak9Dm*&y?GSoi*Wx1+gP&v-yM_;I#x74Ub;UDg{&JtW;o_7(zZw7gVDu zN+A(;Na!e+@8t#8BJ1a0iinWSjA9d4PC1qB~MLte-SKl^47_X)}Hm&o{iRl z_11x6OUHW$ZyhYQcWt)P4G>G~6UBW;iU<3@_Ium*mV%^hutb7w^>2nswWO(SqcO4G zn7G~j7d`Ly+;2SoY3=c^_mHN8rFeBXz8R@ot3UTyy2w~U2|kN8dK|ysp8(2 z8<*a?RBZ0Vv^E-hKW*$S(nH0^e}G|{>M=~yNet6ev+^v4X=vJr?O%`WU!D8Yg`X|l zkM*xb`oI0=G0@sZL-%?^_n#%!V*L+@4-0DVcvIZ0-Luw|xP9Qx(EEpOx8G4e5bhdl zdxt-(9Vy0I?#K3Rf@;U!9=mC*&fXDzHoxB4yV1~hzoGA6yrg0Occmcu`R;LG)lOqHEdjo#dKMshaN$+Zw0|@{BS#-h!z^{=Sl zs4gI)+=}mi2Z(}rf5MgCcHfB;k{4*_gX}JhwyUfkvfLqdT)WB!AiK*E@;}H1fwdvs zD}~a0HsQ2@Qtb01jwcg*r8_P#bCnd4s!w?Lkwne-!}jq!H3~{rodJfG)y40Z)tr(` zJ`B8!a4|(6R*&aRS5C=242!1$0oggUM8q?|DY%(wr08;$crKrnx%;N5$17Hn=(kbzpNNL=eYUcGtg-2WQ6rGIo`qI((Vd~U+E49A( zD~I_v2*B;cj%t>_m^00Skveh&{Vxx0)3Cx%K14`!^7f;c+t#+E%sr#K_vk%s+V_15$zo+*o z&B;p+FV3jUFng9@AbNyOE3>Mz6R>z3gSc(BnI$g10hKP|bd(Z%unUwK*gijlU8{nr z3i523Yp*Dp)-$j3!xOfa$@L49ra^Q0!eKh1T%O5mYW^_xm19_0ledg#a4jCDLuZCB zj2@%g6O&1*UC(v%>a<z2|JIQZ1ndf-PKZ!6t!ugcAO!`C+sXQ&hg?oUW}voic{UwNGb+^qd+<n8S zt{@v%%25{BD24OXqEdP9E}HYq(PLD?DS`I>p!XQX!AoD%w5=YttroHqAQr>1pRMyp zNCU((1X^1H{eUWzie|SuGq4k~d@^yDS!Xa<&mz+tYIUcLjfzwnmy_o3I|?+(pY6D2 zyZLk25eD=hx4(M>rNiT#A2WR!$2c}8+X8WHzimIvP+N0fUjIpDqV(cLcom*GwWvmU z-f`?b&iIb<;@Cj^sWO2SOI(>l#8}81hB}St3SFF5C}KrWGTc3HuS?Z=5Lhvh!FT8*u8{=gQ$l7P%KxiD z_5yGdKko2D2a|YlffxK#Z25DG3kAdB_@Hz8t#CYhnTx&1#ll>-+o+y%n(cLNLbg^0 zHZCm#sqxbdf{A&nX8g0Z&@q1L^ByXJmtTA>G@2CHR4;#3vgyU*J$T1EH9!l%Rhtc zxoI1Saeij})Wu%n1 z6u(D)Pkif7o^u}7UP!OK`10E1t7|X5y8c4u8(X4uP8fvX7d6O0^9od7 zPgnP?*F9be+Rq`<+D>nV$DkS&AyenA9eh?iobFe$JYg= z2WIp6Sxs?%yNoywNze``j>lq_2&e#}+KC~aXLepV$t6dxd`nva} literal 0 HcmV?d00001 diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/services/__pycache__/webcmd_service.cpython-313.pyc b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/__pycache__/webcmd_service.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12e1fbb39f4dbdd4d9349af61dfe21bad28e527d GIT binary patch literal 10069 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 JBb-a){6Bz&qj>-T literal 0 HcmV?d00001 diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/services/agent.py b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/agent.py new file mode 100644 index 000000000..6d3281038 --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/agent.py @@ -0,0 +1,69 @@ +from app.services.gemini import GeminiService +from app.services.webcmd_service import WebcmdService +from app.models import ResearchRequest, ResearchResponse, Opportunity +from typing import List +import traceback + + +class DeadlineDetectiveAgent: + def __init__(self): + self.gemini = GeminiService() + self.webcmd = WebcmdService() + + async def run(self, request: ResearchRequest) -> ResearchResponse: + try: + # Step 1: Gemini creates the research plan + plan = self.gemini.create_research_plan( + task=request.task, + profile=request.profile + ) + + # Step 2: Webcmd collects evidence from real websites + evidence = await self.webcmd.search_and_collect(plan) + + if not evidence: + return ResearchResponse( + opportunities=[], + next_3_actions=["No opportunities found. Try a more specific query or check if Webcmd is installed."], + summary="The agent could not extract useful information. Make sure Webcmd is installed and working (`webcmd doctor`).", + sources_checked=[] + ) + + # Step 3: Gemini analyzes eligibility and ranks + analysis = self.gemini.analyze_and_rank( + task=request.task, + profile=request.profile, + extracted_data=evidence + ) + + # Step 4: Build final structured response + opportunities: List[Opportunity] = [] + for item in analysis.get("opportunities", []): + opportunities.append( + Opportunity( + title=item.get("title", "Unknown"), + eligible=bool(item.get("eligible", False)), + deadline=item.get("deadline"), + what_to_do=item.get("what_to_do", "Check the official page"), + source=item.get("source", ""), + reason=item.get("reason") + ) + ) + + sources = [e["url"] for e in evidence if e.get("url")] + + return ResearchResponse( + opportunities=opportunities, + next_3_actions=analysis.get("next_3_actions", []), + summary=analysis.get("summary", "Research completed using Webcmd browser agent."), + sources_checked=sources + ) + + except Exception as e: + traceback.print_exc() + return ResearchResponse( + opportunities=[], + next_3_actions=[], + summary=f"An error occurred while running the agent: {str(e)[:300]}", + sources_checked=[] + ) diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/services/gemini.py b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/gemini.py new file mode 100644 index 000000000..125e94d90 --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/gemini.py @@ -0,0 +1,181 @@ +from google import genai +from google.genai import types +from app.config import get_settings +from app.models import StudentProfile, Opportunity +from typing import List, Dict, Any +import json +import re +import time + + +class GeminiService: + def __init__(self): + settings = get_settings() + self.client = genai.Client(api_key=settings.gemini_api_key) + self.model = "gemini-2.5-flash" + + def _extract_json(self, text: str) -> Any: + """Extract JSON from Gemini response (handles markdown code blocks).""" + text = text.strip() + # Try to find JSON inside ```json ... ``` + match = re.search(r"```(?:json)?\s*([\s\S]*?)\s*```", text) + if match: + text = match.group(1).strip() + try: + return json.loads(text) + except json.JSONDecodeError: + # Try to find first { ... } or [ ... ] + start = text.find("{") + end = text.rfind("}") + 1 + if start != -1 and end > start: + try: + return json.loads(text[start:end]) + except json.JSONDecodeError: + pass + # Try array + start = text.find("[") + end = text.rfind("]") + 1 + if start != -1 and end > start: + try: + return json.loads(text[start:end]) + except json.JSONDecodeError: + pass + raise + + def _generate_with_retry(self, prompt: str, max_tokens: int, temperature: float = 0.2, retries: int = 2) -> Any: + """Call Gemini with retry logic for transient failures and JSON parsing.""" + last_error = None + for attempt in range(retries + 1): + try: + response = self.client.models.generate_content( + model=self.model, + contents=prompt, + config=types.GenerateContentConfig( + temperature=temperature, + max_output_tokens=max_tokens, + ) + ) + return self._extract_json(response.text) + except (json.JSONDecodeError, Exception) as e: + last_error = e + print(f"[Gemini] Attempt {attempt + 1} failed: {e}") + if attempt < retries: + time.sleep(1.5 * (attempt + 1)) + # If all retries failed, try once more without response_mime_type + try: + print("[Gemini] Trying without response_mime_type constraint...") + response = self.client.models.generate_content( + model=self.model, + contents=prompt, + config=types.GenerateContentConfig( + temperature=temperature, + max_output_tokens=max_tokens, + ) + ) + return self._extract_json(response.text) + except Exception as e: + print(f"[Gemini] Final fallback also failed: {e}") + raise last_error or e + + def create_research_plan(self, task: str, profile: StudentProfile | None) -> Dict[str, Any]: + profile_text = "No specific profile provided." + if profile: + profile_text = ( + f"Year: {profile.year or 'Not specified'}\n" + f"Branch: {profile.branch or 'Not specified'}\n" + f"Interests: {profile.interests or 'Not specified'}\n" + f"Location: {profile.location or 'India'}" + ) + + prompt = f""" +You are an expert research planner for college students in India. + +Student Profile: +{profile_text} + +User Task: +{task} + +Create a focused research plan to find currently open opportunities (hackathons, internships, scholarships, competitions, college events). + +Return ONLY valid JSON in this exact format: +{{ + "search_queries": ["query1", "query2", "query3"], + "target_sites": ["https://example.com", "..."], + "extraction_goals": ["title", "eligibility", "deadline", "application link"], + "priority": "focus on deadlines within next 14-21 days and first-year / beginner friendly opportunities" +}} + +Rules: +- Prefer official pages and well-known platforms (Unstop, Devfolio, Internshala, college sites, AICTE, etc.) +- Maximum 5 search queries +- Maximum 6 target sites +- Make queries specific to the student profile +""" + + return self._generate_with_retry(prompt, max_tokens=4096, temperature=0.2) + + def analyze_and_rank( + self, + task: str, + profile: StudentProfile | None, + extracted_data: List[Dict[str, Any]] + ) -> Dict[str, Any]: + profile_text = "No specific profile provided." + if profile: + profile_text = ( + f"Year: {profile.year or 'Not specified'}\n" + f"Branch: {profile.branch or 'Not specified'}\n" + f"Interests: {profile.interests or 'Not specified'}" + ) + + data_text = json.dumps(extracted_data, indent=2, ensure_ascii=False) + + prompt = f""" +You are Deadline Detective – an expert AI that helps college students find real opportunities. + +Student Profile: +{profile_text} + +Original Task: +{task} + +Here is the raw data extracted from live websites: +{data_text} + +Your job: +1. Filter only relevant and currently open opportunities. +2. Decide eligibility based on the student profile (be strict but fair). +3. Extract clean deadline (prefer exact date). +4. Create a clear "what_to_do" action. +5. Rank by urgency + relevance. + +Return ONLY valid JSON in this exact format: +{{ + "opportunities": [ + {{ + "title": "Name of opportunity", + "eligible": true, + "deadline": "15 Sept 2025 or null", + "what_to_do": "Register on the official page / Apply before deadline", + "source": "https://full-url.com", + "reason": "Short reason for eligibility decision" + }} + ], + "next_3_actions": [ + "1. Opportunity Name — deadline XX — do this", + "2. ...", + "3. ..." + ], + "summary": "2-3 sentence overall summary for the student" +}} + +Rules: +- Only include real opportunities that appear in the data. +- If not eligible, still include it with eligible=false and clear reason. +- Prefer opportunities with clear deadlines in the near future. +- next_3_actions should only contain eligible items when possible. +- Be honest. Do not invent deadlines or opportunities. +""" + + return self._generate_with_retry(prompt, max_tokens=8192, temperature=0.1) diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/services/webcmd_service.py b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/webcmd_service.py new file mode 100644 index 000000000..dd67a997b --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/webcmd_service.py @@ -0,0 +1,211 @@ +import asyncio +import json +import subprocess +import shlex +from typing import List, Dict, Any, Optional +from app.config import get_settings + + +class WebcmdService: + """ + Service that controls the browser through Webcmd CLI. + Replaces the old Playwright service. + """ + + def __init__(self): + self.settings = get_settings() + self.profile = self.settings.webcmd_profile + self.session_id: Optional[str] = None + + def _run_cmd(self, args: List[str], input_text: str = None, timeout: int = 60) -> Dict[str, Any]: + """Run a webcmd command and return parsed JSON when possible.""" + webcmd_path = __import__('shutil').which('webcmd') or 'webcmd' + cmd = [webcmd_path, "--profile", self.profile] + args + + try: + result = subprocess.run( + cmd, + input=input_text, + capture_output=True, + text=True, + timeout=timeout, + ) + + stdout = result.stdout.strip() + stderr = result.stderr.strip() + + if result.returncode != 0: + return { + "success": False, + "error": stderr or stdout or "Webcmd command failed", + "raw": stdout + } + + # Try to parse JSON + try: + data = json.loads(stdout) + return {"success": True, "data": data, "raw": stdout} + except json.JSONDecodeError: + return {"success": True, "data": stdout, "raw": stdout} + + except subprocess.TimeoutExpired: + return {"success": False, "error": "Webcmd command timed out"} + except FileNotFoundError: + return { + "success": False, + "error": "webcmd command not found. Please install it: npm install -g @agentrhq/webcmd" + } + except Exception as e: + return {"success": False, "error": str(e)} + + async def start_session(self, name: str = "DeadlineDetective") -> bool: + """Create a new Webcmd browser session.""" + result = self._run_cmd(["session", "create", name, "-f", "json"]) + + if not result["success"]: + print(f"[Webcmd] Failed to create session: {result.get('error')}") + return False + + data = result.get("data") + if isinstance(data, dict): + self.session_id = data.get("id") or data.get("session_id") or data.get("session") + elif isinstance(data, str): + # Sometimes the ID is printed as plain text + self.session_id = data.strip() + + if not self.session_id: + # Fallback: try to extract from raw output + raw = result.get("raw", "") + for line in raw.splitlines(): + if "id:" in line.lower(): + self.session_id = line.split(":")[-1].strip() + break + + print(f"[Webcmd] Session started: {self.session_id}") + return bool(self.session_id) + + async def close_session(self): + """Close the current Webcmd session.""" + if not self.session_id: + return + + self._run_cmd(["session", "close", self.session_id]) + print(f"[Webcmd] Session closed: {self.session_id}") + self.session_id = None + + async def run_browser_js(self, js_code: str, timeout: int = 45) -> Dict[str, Any]: + """ + Execute a Playwright-style JavaScript program inside the Webcmd session. + """ + if not self.session_id: + return {"success": False, "error": "No active Webcmd session"} + + args = [ + "--session", self.session_id, + "browser", "run", + "--stdin", + "--no-snapshot-diff", + "--timeout", str(timeout), + "-f", "json" + ] + + result = self._run_cmd(args, input_text=js_code, timeout=timeout + 10) + return result + + async def visit_and_extract(self, url: str) -> Dict[str, Any]: + """Visit a URL and extract title + readable text.""" + js_code = f""" +await page.goto('{url}', {{ waitUntil: 'domcontentloaded', timeout: 30000 }}); +await page.waitForTimeout(1500); + +const title = await page.title(); +const text = await page.innerText('body'); + +return {{ + url: page.url(), + title: title, + text: text.slice(0, 10000) +}}; +""" + result = await self.run_browser_js(js_code) + + if not result["success"]: + return { + "url": url, + "title": "", + "text": "", + "success": False, + "error": result.get("error", "Failed to extract") + } + + data = result.get("data") + if isinstance(data, dict): + # Webcmd sometimes wraps the return value + content = data.get("result") or data.get("data") or data + if isinstance(content, dict): + return { + "url": content.get("url", url), + "title": content.get("title", ""), + "text": content.get("text", ""), + "success": True, + "error": None + } + + return { + "url": url, + "title": "", + "text": str(data)[:5000] if data else "", + "success": True, + "error": None + } + + async def search_and_collect(self, plan: Dict[str, Any]) -> List[Dict[str, Any]]: + """ + Execute the research plan using Webcmd. + Visits target sites + does simple searches. + """ + if not await self.start_session("DeadlineDetective"): + return [] + + all_urls = set() + + # Add target sites from plan + for site in plan.get("target_sites", [])[:4]: + if isinstance(site, str) and site.startswith("http"): + all_urls.add(site) + + # Simple search via DuckDuckGo using browser + for query in plan.get("search_queries", [])[:2]: + try: + search_js = f""" +await page.goto('https://duckduckgo.com/?q={query.replace(" ", "+")}', {{ waitUntil: 'domcontentloaded' }}); +await page.waitForTimeout(2000); + +const links = await page.$$eval('a[data-testid="result-title-a"], a.result__a', els => + els.slice(0, 3).map(a => a.href).filter(h => h && h.startsWith('http')) +); +return {{ links }}; +""" + search_result = await self.run_browser_js(search_js) + if search_result["success"]: + data = search_result.get("data") + if isinstance(data, dict): + content = data.get("result") or data.get("data") or data + links = content.get("links", []) if isinstance(content, dict) else [] + for link in links: + all_urls.add(link) + except Exception as e: + print(f"[Webcmd] Search failed for '{query}': {e}") + + urls_to_visit = list(all_urls)[: self.settings.max_pages] + results = [] + + for url in urls_to_visit: + print(f"[Webcmd] Visiting: {url}") + page_data = await self.visit_and_extract(url) + if page_data.get("success") and len(page_data.get("text", "")) > 80: + results.append(page_data) + await asyncio.sleep(1) + + await self.close_session() + return results diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/list_models.py b/SLAB/Deadline_Detective_Backend_Webcmd/list_models.py new file mode 100644 index 000000000..ea6f9ecfa --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/list_models.py @@ -0,0 +1,12 @@ +import os +from google import genai +from dotenv import load_dotenv + +load_dotenv() +api_key = os.environ.get("GEMINI_API_KEY") +client = genai.Client(api_key=api_key) + +print("Available models:") +for m in client.models.list(): + if "gemini" in m.name: + print(m.name) diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/main.py b/SLAB/Deadline_Detective_Backend_Webcmd/main.py new file mode 100644 index 000000000..54136e820 --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/main.py @@ -0,0 +1,33 @@ +from pathlib import Path +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse +from app.routers import research + +app = FastAPI( + title="Deadline Detective", + description="AI Browser Agent that finds real college opportunities, verifies eligibility & deadlines, and creates action plans.", + version="1.0.0" +) + +# Allow frontend (Vercel / local / Render static) to call the API +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # For hackathon – tighten later + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(research.router) + +# Serve frontend static files +FRONTEND_DIR = Path(__file__).resolve().parent.parent / "frontend" + +app.mount("/static", StaticFiles(directory=str(FRONTEND_DIR)), name="static") + + +@app.get("/") +async def root(): + return FileResponse(str(FRONTEND_DIR / "index.html")) diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/requirements.txt b/SLAB/Deadline_Detective_Backend_Webcmd/requirements.txt new file mode 100644 index 000000000..63be73c8a --- /dev/null +++ b/SLAB/Deadline_Detective_Backend_Webcmd/requirements.txt @@ -0,0 +1,7 @@ +fastapi==0.115.0 +uvicorn[standard]==0.30.6 +google-genai==0.3.0 +pydantic==2.9.2 +pydantic-settings==2.5.2 +python-dotenv==1.0.1 +httpx==0.27.2 diff --git a/SLAB/frontend/index.html b/SLAB/frontend/index.html new file mode 100644 index 000000000..f667dd5e5 --- /dev/null +++ b/SLAB/frontend/index.html @@ -0,0 +1,172 @@ + + + + + +Deadline Detective — AI Browser Agent for College Opportunities + + + + + + + +
+ + + +
+ + +
+
+ File No. 001 +

Open a new case

+

Tell the detective what you're hunting for — scholarships, hackathons, internships, research programs — and it'll comb the web for leads.

+
+ +
+
+ + +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+
+ + + + + + + + + + +
+ +
+

Powered by Webcmd + Gemini

+
+ + + + diff --git a/SLAB/frontend/script.js b/SLAB/frontend/script.js new file mode 100644 index 000000000..7db7e8fea --- /dev/null +++ b/SLAB/frontend/script.js @@ -0,0 +1,251 @@ +(() => { + const form = document.getElementById('research-form'); + const submitBtn = document.getElementById('submit-btn'); + const btnLabel = submitBtn.querySelector('.btn-label'); + + const intakePanel = document.getElementById('intake-panel'); + const loadingPanel = document.getElementById('loading-panel'); + const errorPanel = document.getElementById('error-panel'); + const errorMessage = document.getElementById('error-message'); + const resultsSection = document.getElementById('results-section'); + const retryBtn = document.getElementById('retry-btn'); + + const progressItems = Array.from(document.querySelectorAll('#progress-list li')); + + const summaryText = document.getElementById('summary-text'); + const actionsList = document.getElementById('actions-list'); + const resultsTbody = document.getElementById('results-tbody'); + const sourcesList = document.getElementById('sources-list'); + + const API_ENDPOINT = '/research'; + + let progressTimer = null; + + function showPanel(el) { el.hidden = false; } + function hidePanel(el) { el.hidden = true; } + + function resetProgressUI() { + progressItems.forEach(li => li.classList.remove('active', 'done')); + } + + // Step through the progress list while the request is in flight. + // If the response comes back before the animation finishes, we + // immediately mark everything done in advance() on completion. + function startProgressAnimation() { + resetProgressUI(); + let index = 0; + const advance = () => { + progressItems.forEach((li, i) => { + li.classList.toggle('active', i === index); + li.classList.toggle('done', i < index); + }); + if (index < progressItems.length - 1) { + index += 1; + progressTimer = setTimeout(advance, 1600); + } + }; + advance(); + } + + function stopProgressAnimation() { + if (progressTimer) { + clearTimeout(progressTimer); + progressTimer = null; + } + progressItems.forEach(li => { + li.classList.add('done'); + li.classList.remove('active'); + }); + } + + function setSubmitting(isSubmitting) { + submitBtn.disabled = isSubmitting; + btnLabel.textContent = isSubmitting ? 'Investigating…' : 'Start Research'; + } + + function escapeHtml(str) { + return String(str ?? '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + } + + function isLikelyUrl(str) { + if (!str) return false; + try { + const u = new URL(str); + return u.protocol === 'http:' || u.protocol === 'https:'; + } catch { + return false; + } + } + + function renderEligibilityPill(value) { + const v = String(value ?? '').toLowerCase(); + if (v === 'true' || v === 'yes' || v === 'eligible') { + return 'Eligible'; + } + if (v === 'false' || v === 'no' || v === 'not eligible' || v === 'ineligible') { + return 'Not eligible'; + } + return `${escapeHtml(value || 'Unclear')}`; + } + + function isEligibleValue(value) { + const v = String(value ?? '').toLowerCase(); + return v === 'true' || v === 'yes' || v === 'eligible'; + } + + function renderResults(data) { + // Summary + summaryText.textContent = data.summary || 'The investigation is complete. See the findings below.'; + + // Next actions + const actions = data.next_3_actions || data.next_actions || data.nextActions || data.actions || []; + actionsList.innerHTML = ''; + if (actions.length === 0) { + const li = document.createElement('li'); + li.innerHTML = 'No specific actions were identified. Review the opportunities table below.'; + actionsList.appendChild(li); + } else { + actions.slice(0, 3).forEach(action => { + const li = document.createElement('li'); + li.innerHTML = `${escapeHtml(action)}`; + actionsList.appendChild(li); + }); + } + + // Opportunities table + const opportunities = data.opportunities || data.results || []; + resultsTbody.innerHTML = ''; + if (opportunities.length === 0) { + const tr = document.createElement('tr'); + tr.innerHTML = 'No opportunities were found for this task. Try broadening the search.'; + resultsTbody.appendChild(tr); + } else { + opportunities.forEach(op => { + const name = op.name || op.opportunity || op.title || 'Untitled opportunity'; + const eligible = op.eligible ?? op.eligibility; + const deadline = op.deadline || 'Not specified'; + const action = op.action || op.what_to_do || op.next_step || '—'; + const source = op.source || op.source_url || op.url || ''; + + const tr = document.createElement('tr'); + if (isEligibleValue(eligible)) tr.classList.add('eligible-row'); + + const sourceCell = isLikelyUrl(source) + ? `${escapeHtml(source)}` + : escapeHtml(source || 'Not provided'); + + tr.innerHTML = ` + ${escapeHtml(name)} + ${renderEligibilityPill(eligible)} + ${escapeHtml(deadline)} + ${escapeHtml(action)} + ${sourceCell} + `; + resultsTbody.appendChild(tr); + }); + } + + // Sources checked + const sources = data.sources || data.sources_checked || data.sourcesChecked || []; + sourcesList.innerHTML = ''; + if (sources.length === 0) { + const li = document.createElement('li'); + li.textContent = 'No source list was returned.'; + sourcesList.appendChild(li); + } else { + sources.forEach(src => { + const li = document.createElement('li'); + if (isLikelyUrl(src)) { + li.innerHTML = `${escapeHtml(src)}`; + } else { + li.textContent = src; + } + sourcesList.appendChild(li); + }); + } + } + + function showError(message) { + errorMessage.textContent = message || 'Something interrupted the investigation. Please try again.'; + hidePanel(loadingPanel); + hidePanel(resultsSection); + showPanel(errorPanel); + showPanel(intakePanel); + } + + async function runResearch(payload) { + stopProgressAnimation(); // clear any stale timers first + resetProgressUI(); + hidePanel(errorPanel); + hidePanel(resultsSection); + hidePanel(intakePanel); + showPanel(loadingPanel); + setSubmitting(true); + startProgressAnimation(); + + try { + const response = await fetch(API_ENDPOINT, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + let detail = ''; + try { + const errJson = await response.json(); + detail = errJson.detail || errJson.message || ''; + } catch { + /* ignore parse errors on error body */ + } + throw new Error(detail || `The research service returned an error (${response.status}).`); + } + + const data = await response.json(); + stopProgressAnimation(); + + // brief pause so the final "done" state is visible before switching views + await new Promise(r => setTimeout(r, 350)); + + renderResults(data); + hidePanel(loadingPanel); + showPanel(resultsSection); + } catch (err) { + stopProgressAnimation(); + showError( + err && err.message + ? err.message + : 'Could not reach the research service. Check your connection and try again.' + ); + } finally { + setSubmitting(false); + } + } + + form.addEventListener('submit', (e) => { + e.preventDefault(); + + const task = document.getElementById('task').value.trim(); + if (!task) return; + + const payload = { + task, + profile: { + year: document.getElementById('year').value || null, + branch: document.getElementById('branch').value.trim() || null, + interests: document.getElementById('interests').value.trim() || null, + }, + }; + + runResearch(payload); + }); + + retryBtn.addEventListener('click', () => { + hidePanel(errorPanel); + showPanel(intakePanel); + }); +})(); diff --git a/SLAB/frontend/style.css b/SLAB/frontend/style.css new file mode 100644 index 000000000..7c8d4a024 --- /dev/null +++ b/SLAB/frontend/style.css @@ -0,0 +1,497 @@ +/* ------------------------------------------------------- + Deadline Detective — Design tokens + Color: + --ink #12172A (base background, deep ink navy) + --ink-soft #1B2140 (raised panels on dark) + --paper #F6F1E4 (case-file paper cream) + --paper-line #E4DBC4 (paper rules / borders) + --brass #C69A3C (accent — desk-lamp brass) + --brass-dark #9C7626 (accent, pressed/hover) + --ok #3F7D58 (eligible green, muted) + --no #A24B3F (not eligible, muted rust) + --ink-text #21241C (body text on paper) + Type: + Display / headings: 'Fraunces' (warm slab-serif, case-file stamp feel) + Body / UI: 'Inter' + Layout: single column, centered, max-width 880px, generous vertical rhythm +------------------------------------------------------- */ + +:root { + --ink: #12172A; + --ink-soft: #1B2140; + --ink-line: #2C335A; + --paper: #F6F1E4; + --paper-raised: #FBF8EF; + --paper-line: #E4DBC4; + --brass: #C69A3C; + --brass-dark: #9C7626; + --ok: #3F7D58; + --ok-bg: #E7F0E7; + --no: #A24B3F; + --no-bg: #F5E7E3; + --ink-text: #21241C; + --muted-text: #6B6455; + --radius-panel: 6px; + --shadow-panel: 0 18px 40px -20px rgba(0,0,0,0.55); + --font-display: 'Fraunces', Georgia, serif; + --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; +} + +* { box-sizing: border-box; } + +html { scroll-behavior: smooth; } + +body { + margin: 0; + background: var(--ink); + color: var(--paper); + font-family: var(--font-body); + line-height: 1.55; + -webkit-font-smoothing: antialiased; +} + +.case-backdrop { + position: fixed; + inset: 0; + z-index: -1; + background: + radial-gradient(ellipse 900px 500px at 15% -10%, rgba(198,154,60,0.10), transparent 60%), + radial-gradient(ellipse 700px 500px at 100% 10%, rgba(198,154,60,0.06), transparent 55%), + var(--ink); +} + +a { color: var(--brass); } + +:focus-visible { + outline: 2px solid var(--brass); + outline-offset: 3px; +} + +/* ---------- Header ---------- */ + +.site-header { + padding: 2.4rem 1.5rem 1.6rem; + border-bottom: 1px solid var(--ink-line); +} + +.header-inner { + max-width: 880px; + margin: 0 auto; + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 1.5rem; + flex-wrap: wrap; +} + +.brand { + display: flex; + align-items: center; + gap: 0.85rem; +} + +.brand-mark { + color: var(--brass); + flex-shrink: 0; +} + +.brand-text h1 { + font-family: var(--font-display); + font-weight: 600; + font-size: clamp(1.7rem, 3.4vw, 2.3rem); + margin: 0 0 0.15rem; + letter-spacing: -0.01em; + color: var(--paper); +} + +.tagline { + margin: 0; + color: #B9BEDB; + font-size: 0.95rem; +} + +.case-stamp { + font-family: var(--font-body); + font-size: 0.78rem; + color: var(--brass); + border: 1px solid var(--ink-line); + padding: 0.4rem 0.75rem; + border-radius: 999px; + white-space: nowrap; +} + +/* ---------- Layout ---------- */ + +main { + max-width: 880px; + margin: 0 auto; + padding: 2.5rem 1.5rem 4rem; + display: flex; + flex-direction: column; + gap: 2rem; +} + +.panel { + background: var(--paper); + color: var(--ink-text); + border-radius: var(--radius-panel); + box-shadow: var(--shadow-panel); + padding: 2rem; + border: 1px solid var(--paper-line); +} + +.panel-header { margin-bottom: 1.4rem; } + +.panel-index { + display: inline-block; + font-size: 0.72rem; + letter-spacing: 0.02em; + color: var(--brass-dark); + font-weight: 600; + margin-bottom: 0.5rem; +} + +.panel-header h2 { + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 600; + margin: 0 0 0.4rem; + color: var(--ink-text); +} + +.panel-sub { + margin: 0; + color: var(--muted-text); + max-width: 60ch; +} + +/* ---------- Intake form ---------- */ + +.field { margin-bottom: 1.3rem; } + +.field label { + display: block; + font-size: 0.85rem; + font-weight: 600; + margin-bottom: 0.45rem; + color: var(--ink-text); +} + +textarea, input[type="text"], select { + width: 100%; + font-family: var(--font-body); + font-size: 0.96rem; + color: var(--ink-text); + background: var(--paper-raised); + border: 1px solid var(--paper-line); + border-radius: 4px; + padding: 0.75rem 0.9rem; + resize: vertical; + transition: border-color 0.15s ease; +} + +textarea:focus, input[type="text"]:focus, select:focus { + border-color: var(--brass); +} + +.field-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; + margin-bottom: 0.5rem; +} + +.btn-primary { + margin-top: 0.6rem; + display: inline-flex; + align-items: center; + gap: 0.6rem; + background: var(--ink); + color: var(--paper); + border: none; + font-family: var(--font-body); + font-weight: 600; + font-size: 1rem; + padding: 0.85rem 1.6rem; + border-radius: 4px; + cursor: pointer; + transition: background 0.15s ease, transform 0.1s ease; +} + +.btn-primary:hover:not(:disabled) { background: #23294A; } +.btn-primary:active:not(:disabled) { transform: translateY(1px); } + +.btn-primary:disabled { + opacity: 0.55; + cursor: not-allowed; +} + +.btn-icon { flex-shrink: 0; } + +.btn-secondary { + background: transparent; + color: var(--ink-text); + border: 1px solid var(--paper-line); + font-family: var(--font-body); + font-weight: 600; + font-size: 0.95rem; + padding: 0.7rem 1.4rem; + border-radius: 4px; + cursor: pointer; +} + +.btn-secondary:hover { border-color: var(--brass); color: var(--brass-dark); } + +/* ---------- Loading ---------- */ + +.loading-panel { text-align: center; } + +.loading-inner { + display: flex; + flex-direction: column; + align-items: center; + padding: 1rem 0; +} + +.magnifier-spinner { + color: var(--brass); + margin-bottom: 1.2rem; + animation: spin-sweep 1.8s ease-in-out infinite; +} + +@keyframes spin-sweep { + 0% { transform: rotate(0deg); } + 50% { transform: rotate(25deg); } + 100% { transform: rotate(0deg); } +} + +.loading-panel h2 { + font-family: var(--font-display); + font-size: 1.4rem; + margin: 0 0 1.4rem; +} + +.progress-list { + list-style: none; + margin: 0; + padding: 0; + text-align: left; + display: inline-flex; + flex-direction: column; + gap: 0.75rem; + min-width: min(420px, 100%); +} + +.progress-list li { + display: flex; + align-items: center; + gap: 0.7rem; + color: var(--muted-text); + font-size: 0.95rem; + transition: color 0.2s ease; +} + +.step-marker { + width: 9px; + height: 9px; + border-radius: 50%; + border: 1.5px solid var(--paper-line); + flex-shrink: 0; + transition: background 0.2s ease, border-color 0.2s ease; +} + +.progress-list li.active { + color: var(--ink-text); + font-weight: 600; +} + +.progress-list li.active .step-marker { + background: var(--brass); + border-color: var(--brass); + box-shadow: 0 0 0 4px rgba(198,154,60,0.18); +} + +.progress-list li.done .step-marker { + background: var(--ok); + border-color: var(--ok); +} + +.progress-list li.done { color: var(--muted-text); } + +/* ---------- Error ---------- */ + +.error-panel { + text-align: left; + border-left: 4px solid var(--no); +} + +.error-panel h2 { + font-family: var(--font-display); + font-size: 1.4rem; + margin: 0 0 0.5rem; +} + +.error-panel p { color: var(--muted-text); margin: 0 0 1.2rem; } + +/* ---------- Results ---------- */ + +.results-wrap { + display: flex; + flex-direction: column; + gap: 1.6rem; +} + +.summary-text { + margin: 0; + font-size: 1.02rem; + color: var(--ink-text); + max-width: 68ch; +} + +/* Next 3 actions — most prominent card */ +.actions-panel { + background: var(--ink-soft); + color: var(--paper); + border: 1px solid var(--ink-line); + box-shadow: 0 22px 48px -18px rgba(0,0,0,0.65); +} + +.actions-panel .panel-index { color: var(--brass); } +.actions-panel .panel-header h2 { color: var(--paper); } + +.actions-list { + list-style: none; + counter-reset: action-counter; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.9rem; +} + +.actions-list li { + counter-increment: action-counter; + display: flex; + gap: 1rem; + align-items: flex-start; + background: rgba(246,241,228,0.05); + border: 1px solid var(--ink-line); + border-radius: 5px; + padding: 0.95rem 1.1rem; +} + +.actions-list li::before { + content: counter(action-counter); + font-family: var(--font-display); + font-weight: 600; + font-size: 1.1rem; + color: var(--ink); + background: var(--brass); + width: 1.9rem; + height: 1.9rem; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.actions-list .action-text { padding-top: 0.15rem; font-size: 0.98rem; } + +/* Table */ + +.table-scroll { overflow-x: auto; } + +table { + width: 100%; + border-collapse: collapse; + font-size: 0.92rem; + min-width: 640px; +} + +thead th { + text-align: left; + font-size: 0.75rem; + letter-spacing: 0.02em; + color: var(--muted-text); + font-weight: 600; + padding: 0.6rem 0.8rem; + border-bottom: 2px solid var(--paper-line); +} + +tbody td { + padding: 0.85rem 0.8rem; + border-bottom: 1px solid var(--paper-line); + vertical-align: top; +} + +tbody tr:last-child td { border-bottom: none; } + +tbody tr.eligible-row { + background: var(--ok-bg); +} + +.eligible-pill, .not-eligible-pill, .unclear-pill { + display: inline-block; + font-size: 0.78rem; + font-weight: 600; + padding: 0.25rem 0.6rem; + border-radius: 999px; +} + +.eligible-pill { background: var(--ok); color: #fff; } +.not-eligible-pill { background: var(--no); color: #fff; } +.unclear-pill { background: #D8CBA0; color: #5C4A1E; } + +td.opportunity-cell { font-weight: 600; } + +td a.source-link { + color: var(--brass-dark); + text-decoration: underline; + text-underline-offset: 2px; + word-break: break-word; +} + +/* Sources */ + +.sources-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.55rem; +} + +.sources-list li { + font-size: 0.9rem; + padding-bottom: 0.55rem; + border-bottom: 1px dashed var(--paper-line); +} + +.sources-list li:last-child { border-bottom: none; padding-bottom: 0; } + +.sources-list a { word-break: break-word; } + +/* ---------- Footer ---------- */ + +.site-footer { + text-align: center; + padding: 2rem 1.5rem 3rem; + color: #8890B8; + font-size: 0.85rem; +} + +/* ---------- Responsive ---------- */ + +@media (max-width: 640px) { + .field-grid { grid-template-columns: 1fr; } + .panel { padding: 1.4rem; } + .header-inner { align-items: flex-start; } + .case-stamp { order: 3; } +} + +@media (prefers-reduced-motion: reduce) { + .magnifier-spinner { animation: none; } + html { scroll-behavior: auto; } +} From 6a1a7362f12c27c6ad96e87fb074347c4852f24b Mon Sep 17 00:00:00 2001 From: udaya reddy Date: Sat, 12 Sep 2026 14:57:50 +0530 Subject: [PATCH 2/2] Update Gemini model to 3.6-flash --- SLAB/Deadline_Detective_Backend_Webcmd/app/services/gemini.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SLAB/Deadline_Detective_Backend_Webcmd/app/services/gemini.py b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/gemini.py index 125e94d90..927acf03d 100644 --- a/SLAB/Deadline_Detective_Backend_Webcmd/app/services/gemini.py +++ b/SLAB/Deadline_Detective_Backend_Webcmd/app/services/gemini.py @@ -12,7 +12,7 @@ class GeminiService: def __init__(self): settings = get_settings() self.client = genai.Client(api_key=settings.gemini_api_key) - self.model = "gemini-2.5-flash" + self.model = "gemini-3.6-flash" def _extract_json(self, text: str) -> Any: """Extract JSON from Gemini response (handles markdown code blocks)."""