diff --git a/code-reference-finder/.env.example b/code-reference-finder/.env.example index 4c41230ff..f3665b3c8 100644 --- a/code-reference-finder/.env.example +++ b/code-reference-finder/.env.example @@ -1,4 +1,7 @@ -OPENROUTER_API_KEY=your_openrouter_api_key -TINYFISH_API_KEY=your_tinyfish_api_key -GITHUB_TOKEN=your_github_personal_access_token -STACKEXCHANGE_KEY=your_stackexchange_api_key +# TinyFish Web Agent API key (server-side only) +# Get yours at: https://agent.tinyfish.ai/api-keys +TINYFISH_API_KEY= + +# Google Gemini API key — used for code analysis and search query generation +# Get yours at: https://aistudio.google.com/apikey +GEMINI_API_KEY= diff --git a/code-reference-finder/.gitignore b/code-reference-finder/.gitignore index 5ef6a5207..3fdd765d0 100644 --- a/code-reference-finder/.gitignore +++ b/code-reference-finder/.gitignore @@ -1,41 +1,22 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# Dependencies +node_modules/ -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions +# Next.js +.next/ +out/ -# testing -/coverage +# Environment files +.env +.env.local +.env.*.local -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* +# Build outputs +*.tsbuildinfo -# vercel +# Vercel .vercel -# typescript -*.tsbuildinfo +# OS +.DS_Store +Thumbs.db next-env.d.ts diff --git a/code-reference-finder/README.md b/code-reference-finder/README.md index 33a654c39..0922c0902 100644 --- a/code-reference-finder/README.md +++ b/code-reference-finder/README.md @@ -1,151 +1,182 @@ # Code Reference Finder +**Live Demo:** _add URL after deploy_ -**Live:** [https://code-reference-finder.vercel.app](https://code-reference-finder.vercel.app) +**Find real-world usage examples for unfamiliar code — Gemini analyzes your code, TinyFish Search finds relevant GitHub repos and Stack Overflow questions, then parallel browser agents extract code snippets and relevance scores in real time.** -Code Reference Finder helps you understand unfamiliar code by finding real-world usage examples from GitHub repositories and Stack Overflow. Paste a code snippet (or right-click selected code on GitHub), and it uses AI to analyze the libraries and APIs used, then dispatches web agents to search GitHub and Stack Overflow, extract relevant examples, and display them side-by-side with relevance scores. +Paste any code snippet. The app identifies the libraries, APIs, and patterns used, generates targeted search queries, finds the most relevant GitHub repos and Stack Overflow threads via the TinyFish Search API, then fires one browser agent per result in parallel to extract actual code examples and explain how each relates to your snippet. -## Demo +## Architecture -https://github.com/user-attachments/assets/73feb7c2-60dd-492b-b440-165d0170a4aa - - -## TinyFish API Usage - -The app dispatches 10 parallel TinyFish web agents — 5 for GitHub repos and 5 for Stack Overflow posts. Each agent receives a goal prompt tailored to its platform. - -**GitHub agents** navigate the repository and read the README to extract relevant code examples: - -```typescript -const response = await fetch("https://agent.tinyfish.ai/v1/automation/run-sse", { - method: "POST", - headers: { - "X-API-Key": process.env.TINYFISH_API_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - url: "https://github.com/owner/repo", - goal: `You are analyzing a GitHub repository to determine how it relates to specific libraries and APIs. - - TARGET LIBRARIES: @tanstack/react-query, axios - TARGET APIs/SYMBOLS: useQuery, axios.get - - INSTRUCTIONS: - 1. Go to the repository page and read ONLY the README. - 2. Extract: what the project does, any code examples shown, and how it relates to the target libraries/APIs. - 3. Score relevance 0-100. - - Return a JSON object with: title, sourceUrl, platform, relevanceScore, alignmentExplanation, codeSnippets...`, - }), -}); +``` +┌─────────────────────────────────────────────────────────────┐ +│ Browser (Client) │ +│ │ +│ CodeInput → PipelineProgress → AgentCard grid │ +│ ReferenceGrid → ReferenceCard (results stream in) │ +└──────────────────────────┬──────────────────────────────────┘ + │ POST /api/analyze { code } + │ (SSE — events stream as pipeline runs) +┌──────────────────────────▼──────────────────────────────────┐ +│ /api/analyze/route.ts │ +│ │ +│ Stage 1 — Gemini analyzes code │ +│ analyzeCode() → language, libraries, APIs, patterns │ +│ generateSearchQueries() → 5 GitHub + 5 SO queries │ +│ → analysis_complete SSE event │ +│ │ +│ Stage 2 — TinyFish Search API │ +│ client.search.query({ query: "... site:github.com" }) │ +│ client.search.query({ query: "... site:stackoverflow.com"}) +│ 5+5 parallel searches → deduplicated results │ +│ → search_complete SSE event │ +│ │ +│ Stage 3 — TinyFish Agents (Promise.allSettled, parallel) │ +│ client.agent.stream({ url, goal }) │ +│ GitHub agent → reads README, extracts code snippets │ +│ SO agent → reasons over question data, scores relevance│ +│ │ +│ EventType.STREAMING_URL → live iframe per agent │ +│ EventType.PROGRESS → step updates │ +│ EventType.COMPLETE + RunStatus.COMPLETED │ +│ // COMPLETED only means the browser ran without crashing│ +│ // — always validate result content, not just the status│ +│ → ReferenceData → agent_complete SSE event │ +└─────────────────────────────────────────────────────────────┘ + +No database. No cache. No GitHub API token. No StackExchange key. +Pure in-memory — results fetched live every search. ``` -**Stack Overflow agents** reason over the post metadata (title, tags, score, excerpt) without navigating: - -```typescript -const response = await fetch("https://agent.tinyfish.ai/v1/automation/run-sse", { - method: "POST", - headers: { - "X-API-Key": process.env.TINYFISH_API_KEY, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - url: "https://example.com", - goal: `You are a reasoning agent analyzing a Stack Overflow post. - - STACK OVERFLOW POST DATA: - - Title: How to pass parameters to useQuery with Axios - - Score: 38 | Answered: true | Tags: reactjs, axios, react-query +### Gemini usage - TARGET LIBRARIES: @tanstack/react-query, axios +``` +gemini-client.ts: + analyzeCode() → identify language, libraries, APIs, patterns + generateSearchQueries() → generate 5 GitHub + 5 SO search queries +``` - Score relevance 0-100 based on: Do the tags match? Does the title discuss the target APIs? - Would this post help someone understand how to use these libraries? +### TinyFish Search usage - Return a JSON object with: title, sourceUrl, platform, relevanceScore, alignmentExplanation, questionTitle, votes, tags...`, - }), -}); +``` +// Two parallel batches — one per platform +const [ghSearches, soSearches] = await Promise.all([ + Promise.all(ghQueries.map(q => client.search.query({ + query: `${q.query} site:github.com` + }))), + Promise.all(soQueries.map(q => client.search.query({ + query: `${q.query} site:stackoverflow.com` + }))), +]); +// Results deduplicated and capped at 5 per platform ``` -Both agent types stream SSE events including a `STREAMING_URL` (live view of the agent working) and a final `COMPLETE` event with the extracted reference data JSON. +## Pipeline Stages -## How to Run +1. **Code Analysis** — Gemini identifies language, external libraries, APIs/hooks, and usage patterns +2. **Query Generation** — Gemini generates 5 GitHub + 5 Stack Overflow search queries tailored to the code +3. **Search** — TinyFish Search finds real GitHub repos and SO questions for each query (replaces GitHub API + StackExchange API) +4. **Agent Extraction** — one TinyFish browser agent per result fires in parallel: + - **GitHub agents** — navigate to repo, read README, extract code snippets and relevance score + - **Stack Overflow agents** — reason over question data, score relevance, extract any code in the excerpt +5. Results stream back to the UI as each agent finishes -### Prerequisites +## Setup -- Node.js 18+ -- API keys for: [OpenRouter](https://openrouter.ai/keys), [TinyFish](https://mino.ai/api-keys), [GitHub](https://github.com/settings/tokens), and [Stack Exchange](https://stackapps.com/apps/oauth/register) +### Prerequisites -### Setup +- Node.js 22.x +- TinyFish API key +- Gemini API key -1. Install dependencies: +### Environment Variables ```bash -npm install +cp .env.example .env.local ``` -2. Create a `.env.local` file with your API keys (see `.env.example`): +Then fill in: -``` -OPENROUTER_API_KEY=your_openrouter_api_key -TINYFISH_API_KEY=your_tinyfish_api_key -GITHUB_TOKEN=your_github_personal_access_token -STACKEXCHANGE_KEY=your_stackexchange_api_key +```env +# TinyFish Web Agent API key (server-side only) +# Get yours at: https://agent.tinyfish.ai/api-keys +TINYFISH_API_KEY= + +# Gemini API key — used for code analysis and search query generation +# Get yours at: https://aistudio.google.com/apikey +GEMINI_API_KEY= ``` -3. Start the dev server: +### Install & Run ```bash +npm install npm run dev ``` -4. Open [http://localhost:3000](http://localhost:3000) - -### Chrome Extension (optional) +Open http://localhost:3000 -To use the side panel and right-click context menu on GitHub: - -1. Go to `chrome://extensions` and enable Developer mode -2. Click "Load unpacked" and select the `extension/` folder -3. Copy any unknown code and paste it in the input area to start using it. - -## Architecture Diagram +## Project Structure ``` -┌──────────────────────────────────────────────────────────────────┐ -│ User (Browser) │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ Next.js Frontend (React + Tailwind + Framer Motion) │ │ -│ │ │ │ -│ │ 1. Paste code snippet or right-click on GitHub │ │ -│ │ 2. View analysis (language, libraries, APIs, patterns) │ │ -│ │ 3. Watch agents search & extract in real-time │ │ -│ │ 4. Browse results sorted by relevance score │ │ -│ └───────────────────────┬────────────────────────────────────┘ │ -└──────────────────────────┼───────────────────────────────────────┘ - │ POST /api/analyze (SSE stream) - ▼ -┌──────────────────────────────────────────────────────────────────┐ -│ Next.js API Route (SSE) │ -│ │ -│ Stage 1 — Code Analysis (OpenRouter / Gemini Flash) │ -│ • Identifies language, libraries, APIs, patterns │ -│ • Generates 10 search queries (5 GitHub + 5 Stack Overflow) │ -│ │ -│ Stage 2 — Search Execution │ -│ • GitHub Search API (5 queries, rate-limited) │ -│ • Stack Exchange API (5 queries, parallel) │ -│ • Deduplicates and picks top 5 from each platform │ -│ │ -│ Stage 3 — Agent Extraction (10 parallel TinyFish agents) │ -│ • GitHub agents: navigate repo, read README, extract examples │ -│ • SO agents: reason over post metadata, score relevance │ -│ │ -│ Stage 4 — Pipeline Complete │ -└────────┬──────────────┬──────────────┬──────────────┬────────────┘ - │ │ │ │ - ▼ ▼ ▼ ▼ - ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐ - │ OpenRouter│ │ GitHub │ │ Stack │ │ TinyFish │ - │ (LLM) │ │ API │ │ Exchange │ │ (Agents) │ - └──────────┘ └──────────┘ └───────────┘ └───────────┘ +code-reference-finder/ +├── src/ +│ ├── app/ +│ │ ├── layout.tsx +│ │ ├── page.tsx # Main UI +│ │ ├── globals.css +│ │ └── api/ +│ │ └── analyze/route.ts # POST — SSE pipeline endpoint +│ ├── components/ +│ │ ├── CodeInput.tsx # Code paste area +│ │ ├── Dashboard.tsx # App shell +│ │ ├── Header.tsx +│ │ ├── PipelineProgress.tsx # Stage progress indicator +│ │ ├── AgentCard.tsx # Per-agent status + live iframe +│ │ ├── LiveBrowserPreview.tsx # Expanded agent browser view +│ │ ├── ReferenceGrid.tsx # Results grid +│ │ ├── ReferenceCard.tsx # Individual reference result +│ │ └── AnalysisSummary.tsx # Code analysis summary +│ ├── context/ +│ │ └── AppContext.tsx # React context + reducer +│ ├── hooks/ +│ │ └── useCodeAnalysis.ts # SSE client + dispatch +│ └── lib/ +│ ├── gemini-client.ts # Gemini — code analysis + query gen +│ ├── search.ts # TinyFish Search — GitHub + SO +│ ├── orchestrator.ts # Pipeline coordinator +│ ├── goal-builder.ts # Agent goal prompts +│ ├── constants.ts +│ └── types.ts +├── extension/ # Chrome extension (side panel) +│ ├── manifest.json +│ ├── background.js +│ ├── sidepanel.html +│ └── sidepanel.js +├── .env.example +├── .gitignore +└── package.json ``` + +## Constraint Checklist + +| Constraint | Status | +|---|---| +| External database used? | NO (pure in-memory) | +| Raw SSE fetch? | NO (TinyFish SDK throughout) | +| GitHub API token needed? | NO (TinyFish Search replaces GitHub API) | +| StackExchange API key needed? | NO (TinyFish Search replaces SO API) | +| OpenRouter? | NO (Gemini SDK directly) | +| Search parallel? | YES (all queries run concurrently via Promise.all) | +| Agent execution parallel? | YES (Promise.allSettled across all results) | +| Live browser preview? | YES (EventType.STREAMING_URL → iframe per agent) | +| Result validation? | YES (COMPLETED ≠ goal achieved — content validated) | + +## Tech Stack + +- **Framework:** Next.js 16 (App Router), TypeScript, Tailwind CSS 3 +- **Animations:** Framer Motion +- **Browser Agents:** TinyFish SDK (`client.agent.stream`) +- **Search:** TinyFish Search API (`client.search.query`) +- **LLM:** Gemini (`gemini-2.0-flash`) +- **Icons:** Lucide React +- **Deployment:** Vercel diff --git a/code-reference-finder/package-lock.json b/code-reference-finder/package-lock.json deleted file mode 100644 index 250d74a35..000000000 --- a/code-reference-finder/package-lock.json +++ /dev/null @@ -1,1717 +0,0 @@ -{ - "name": "code-ref-temp", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "code-ref-temp", - "version": "0.1.0", - "dependencies": { - "framer-motion": "^12.34.0", - "lucide-react": "^0.563.0", - "next": "16.1.6", - "react": "19.2.3", - "react-dom": "19.2.3" - }, - "devDependencies": { - "@tailwindcss/postcss": "^4", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "tailwindcss": "^4", - "typescript": "^5" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@img/colour": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", - "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", - "cpu": [ - "ppc64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", - "cpu": [ - "riscv64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", - "cpu": [ - "ppc64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", - "cpu": [ - "riscv64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@next/env": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", - "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", - "license": "MIT" - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", - "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", - "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", - "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", - "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", - "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", - "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", - "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", - "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", - "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.1", - "lightningcss": "1.30.2", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.18" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", - "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-x64": "4.1.18", - "@tailwindcss/oxide-freebsd-x64": "4.1.18", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-x64-musl": "4.1.18", - "@tailwindcss/oxide-wasm32-wasi": "4.1.18", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", - "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", - "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", - "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", - "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", - "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", - "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", - "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", - "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", - "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", - "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.0", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", - "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", - "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/postcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", - "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.18", - "@tailwindcss/oxide": "4.1.18", - "postcss": "^8.4.41", - "tailwindcss": "4.1.18" - } - }, - "node_modules/@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", - "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/framer-motion": { - "version": "12.34.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.0.tgz", - "integrity": "sha512-+/H49owhzkzQyxtn7nZeF4kdH++I2FWrESQ184Zbcw5cEqNHYkE5yxWxcTLSj5lNx3NWdbIRy5FHqUvetD8FWg==", - "license": "MIT", - "dependencies": { - "motion-dom": "^12.34.0", - "motion-utils": "^12.29.2", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lucide-react": { - "version": "0.563.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz", - "integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/motion-dom": { - "version": "12.34.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.0.tgz", - "integrity": "sha512-Lql3NuEcScRDxTAO6GgUsRHBZOWI/3fnMlkMcH5NftzcN37zJta+bpbMAV9px4Nj057TuvRooMK7QrzMCgtz6Q==", - "license": "MIT", - "dependencies": { - "motion-utils": "^12.29.2" - } - }, - "node_modules/motion-utils": { - "version": "12.29.2", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", - "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/next": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", - "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", - "license": "MIT", - "dependencies": { - "@next/env": "16.1.6", - "@swc/helpers": "0.5.15", - "baseline-browser-mapping": "^2.8.3", - "caniuse-lite": "^1.0.30001579", - "postcss": "8.4.31", - "styled-jsx": "5.1.6" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=20.9.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "16.1.6", - "@next/swc-darwin-x64": "16.1.6", - "@next/swc-linux-arm64-gnu": "16.1.6", - "@next/swc-linux-arm64-musl": "16.1.6", - "@next/swc-linux-x64-gnu": "16.1.6", - "@next/swc-linux-x64-musl": "16.1.6", - "@next/swc-win32-arm64-msvc": "16.1.6", - "@next/swc-win32-x64-msvc": "16.1.6", - "sharp": "^0.34.4" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.51.1", - "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/next/node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", - "license": "MIT", - "peer": true, - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.3" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@img/colour": "^1.0.0", - "detect-libc": "^2.1.2", - "semver": "^7.7.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/styled-jsx": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", - "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", - "license": "MIT", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/tailwindcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", - "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/code-reference-finder/package.json b/code-reference-finder/package.json index ae3c38a1c..2e6ce16f2 100644 --- a/code-reference-finder/package.json +++ b/code-reference-finder/package.json @@ -2,24 +2,31 @@ "name": "code-reference-finder", "version": "0.1.0", "private": true, + "engines": { + "node": "22.x" + }, "scripts": { "dev": "next dev", "build": "next build", - "start": "next start" + "start": "next start", + "lint": "next lint" }, "dependencies": { + "@tiny-fish/sdk": "latest", "framer-motion": "^12.34.0", "lucide-react": "^0.563.0", "next": "16.1.6", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "@google/generative-ai": "latest" }, "devDependencies": { - "@tailwindcss/postcss": "^4", - "@types/node": "^20", + "@types/node": "^22", "@types/react": "^19", "@types/react-dom": "^19", - "tailwindcss": "^4", + "autoprefixer": "^10.4.21", + "postcss": "^8.5", + "tailwindcss": "^3.4.17", "typescript": "^5" } -} +} \ No newline at end of file diff --git a/code-reference-finder/postcss.config.mjs b/code-reference-finder/postcss.config.mjs index 61e36849c..a982c6414 100644 --- a/code-reference-finder/postcss.config.mjs +++ b/code-reference-finder/postcss.config.mjs @@ -1,6 +1,7 @@ const config = { plugins: { - "@tailwindcss/postcss": {}, + tailwindcss: {}, + autoprefixer: {}, }, }; diff --git a/code-reference-finder/src/app/api/analyze/route.ts b/code-reference-finder/src/app/api/analyze/route.ts index be8dcee18..739bd5742 100644 --- a/code-reference-finder/src/app/api/analyze/route.ts +++ b/code-reference-finder/src/app/api/analyze/route.ts @@ -1,47 +1,43 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { runPipeline } from '@/lib/orchestrator'; +import { NextRequest, NextResponse } from "next/server"; +import { runPipeline } from "@/lib/orchestrator"; -export async function POST(request: NextRequest) { - try { - const body = await request.json(); - const code = body?.code; - - if (!code || typeof code !== 'string' || code.trim().length === 0) { - return NextResponse.json( - { error: 'Missing or empty "code" field in request body' }, - { status: 400 } - ); - } +export const runtime = "nodejs"; +export const maxDuration = 300; - // Create a TransformStream for SSE - const { readable, writable } = new TransformStream(); - const writer = writable.getWriter(); - - // Run the pipeline in the background — don't await - runPipeline(code.trim(), writer) - .catch((err) => { - const encoder = new TextEncoder(); - const errorEvent = `data: ${JSON.stringify({ - type: 'pipeline_error', - data: { error: (err as Error).message }, - })}\n\n`; - writer.write(encoder.encode(errorEvent)).catch(() => {}); - }) - .finally(() => { - writer.close().catch(() => {}); - }); +export async function POST(request: NextRequest) { + const body = await request.json().catch(() => null); + const code = body?.code; - return new Response(readable, { - headers: { - 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', - Connection: 'keep-alive', - }, - }); - } catch (error) { + if (!code || typeof code !== "string" || code.trim().length === 0) { return NextResponse.json( - { error: (error as Error).message }, - { status: 500 } + { error: 'Missing or empty "code" field in request body' }, + { status: 400 } ); } + + const stream = new ReadableStream({ + async start(controller) { + try { + await runPipeline(code.trim(), controller); + } catch (err) { + const encoder = new TextEncoder(); + controller.enqueue( + encoder.encode( + `data: ${JSON.stringify({ type: "pipeline_error", data: { error: (err as Error).message } })}\n\n` + ) + ); + } finally { + controller.close(); + } + }, + }); + + return new Response(stream, { + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache, no-transform", + Connection: "keep-alive", + "X-Accel-Buffering": "no", + }, + }); } diff --git a/code-reference-finder/src/app/globals.css b/code-reference-finder/src/app/globals.css index b2ef4016b..6cfdb8a97 100644 --- a/code-reference-finder/src/app/globals.css +++ b/code-reference-finder/src/app/globals.css @@ -1,21 +1,16 @@ -@import "tailwindcss"; +@tailwind base; +@tailwind components; +@tailwind utilities; :root { --background: #09090b; --foreground: #fafafa; } -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - body { background: var(--background); color: var(--foreground); - font-family: var(--font-sans), system-ui, -apple-system, sans-serif; + font-family: system-ui, -apple-system, sans-serif; } /* Scrollbar styling */ diff --git a/code-reference-finder/src/lib/constants.ts b/code-reference-finder/src/lib/constants.ts index 6b728b95c..ba9c129de 100644 --- a/code-reference-finder/src/lib/constants.ts +++ b/code-reference-finder/src/lib/constants.ts @@ -1,15 +1,5 @@ -export const MINO_API_URL = 'https://agent.tinyfish.ai/v1/automation/run-sse'; -export const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions'; -export const GITHUB_API_URL = 'https://api.github.com'; -export const STACKEXCHANGE_API_URL = 'https://api.stackexchange.com/2.3'; - -export const OPENROUTER_MODEL = 'google/gemini-3.1-flash-lite-preview'; -export const OPENROUTER_TEMPERATURE = 0.2; - export const MAX_AGENTS = 10; export const AGENT_TIMEOUT_MS = 360_000; // 6 minutes -export const GITHUB_RESULTS_PER_QUERY = 8; -export const STACKOVERFLOW_RESULTS_PER_QUERY = 10; -export const APP_NAME = 'Code Reference Finder'; -export const APP_DESCRIPTION = 'Find real-world usage examples for unfamiliar code'; +export const APP_NAME = "Code Reference Finder"; +export const APP_DESCRIPTION = "Find real-world usage examples for unfamiliar code"; diff --git a/code-reference-finder/src/lib/gemini-client.ts b/code-reference-finder/src/lib/gemini-client.ts new file mode 100644 index 000000000..fda18edbd --- /dev/null +++ b/code-reference-finder/src/lib/gemini-client.ts @@ -0,0 +1,105 @@ +import { GoogleGenerativeAI } from "@google/generative-ai"; +import type { CodeAnalysis, SearchQuery } from "./types"; + +function getGemini() { + const apiKey = process.env.GEMINI_API_KEY; + if (!apiKey) throw new Error("GEMINI_API_KEY is not configured"); + const genAI = new GoogleGenerativeAI(apiKey); + return genAI.getGenerativeModel({ model: "gemini-2.0-flash" }); +} + +function extractJSON(text: string): unknown { + try { + return JSON.parse(text); + } catch { + const match = text.match(/\{[\s\S]*\}/); + if (match) return JSON.parse(match[0]); + throw new Error("Could not parse JSON from Gemini response"); + } +} + +async function callGemini(systemPrompt: string, userPrompt: string): Promise { + const model = getGemini(); + const result = await model.generateContent(`${systemPrompt}\n\n${userPrompt}`); + return result.response.text() ?? ""; +} + +export async function analyzeCode(code: string): Promise { + const content = await callGemini( + `You are a code analysis assistant. +Analyze the following code snippet and return a structured analysis. + +Identify: +1. The programming language +2. All external libraries, packages, and frameworks imported or used +3. All APIs, hooks, classes, and notable symbols invoked +4. Real-world usage patterns present (e.g. data fetching, state management, authentication, middleware chaining) + +Do NOT return any URLs. Only analyze the code provided. + +Return ONLY a JSON object (no markdown, no explanation): +{ + "language": "...", + "libraries": ["library1", "library2"], + "apis": ["api1", "api2"], + "patterns": ["pattern1", "pattern2"] +}`, + code + ); + + const parsed = extractJSON(content) as CodeAnalysis; + return { + language: parsed.language || "unknown", + libraries: Array.isArray(parsed.libraries) ? parsed.libraries : [], + apis: Array.isArray(parsed.apis) ? parsed.apis : [], + patterns: Array.isArray(parsed.patterns) ? parsed.patterns : [], + }; +} + +export async function generateSearchQueries(analysis: CodeAnalysis): Promise { + const content = await callGemini( + `You are a search query strategist for developer tools. +Given a code analysis, generate search queries that surface high-quality real-world usage examples from GitHub and Stack Overflow. + +Requirements: +- Generate exactly 10 queries: 5 with target "github" and 5 with target "stackoverflow" +- Keep queries SHORT (2-4 words max). Shorter = more results. +- GitHub: library/framework names only, e.g. "tanstack react-query", "express middleware typescript" +- Stack Overflow: concise problem keywords, e.g. "useQuery refetch interval", "prisma findMany include" +- Do NOT write full sentences or long phrases +- For each query, indicate target: "github" or "stackoverflow" +- Provide ranking heuristics: what signals indicate a high-quality result + +Do NOT return any URLs. Only return queries and heuristics. + +Return ONLY a JSON object (no markdown, no explanation): +{ + "queries": [ + { + "query": "search terms here", + "target": "github", + "heuristic": "what makes a good result for this query" + } + ] +}`, + `Language: ${analysis.language} +Libraries: ${analysis.libraries.join(", ")} +APIs: ${analysis.apis.join(", ")} +Patterns: ${analysis.patterns.join(", ")}` + ); + + const parsed = extractJSON(content) as { queries: SearchQuery[] }; + if (!Array.isArray(parsed.queries)) throw new Error("Gemini did not return a queries array"); + + const all = parsed.queries + .filter((q) => q.query && q.target && q.heuristic) + .map((q) => { + const t = q.target.toLowerCase().replace(/[\s_-]/g, ""); + const target = t.includes("stack") ? "stackoverflow" : "github"; + return { ...q, target } as SearchQuery; + }); + + const ghQueries = all.filter((q) => q.target === "github").slice(0, 5); + const soQueries = all.filter((q) => q.target === "stackoverflow").slice(0, 5); + return [...ghQueries, ...soQueries]; +} diff --git a/code-reference-finder/src/lib/goal-builder.ts b/code-reference-finder/src/lib/goal-builder.ts index 15f2bf05e..932b95bbf 100644 --- a/code-reference-finder/src/lib/goal-builder.ts +++ b/code-reference-finder/src/lib/goal-builder.ts @@ -50,7 +50,6 @@ export function buildSOReasoningGoal( ): { url: string; goal: string } { const libs = analysis.libraries.join(', '); const apis = analysis.apis.join(', '); - const apiData = searchResult.apiData; const goal = `You are a reasoning agent analyzing a Stack Overflow post to determine its relevance to specific libraries and APIs. @@ -63,7 +62,7 @@ STACK OVERFLOW POST DATA: - Answer count: ${searchResult.answerCount ?? 'unknown'} - Answered: ${searchResult.isAnswered ?? 'unknown'} - Tags: ${searchResult.tags?.join(', ') ?? 'none'} -- Excerpt: ${searchResult.snippet || apiData?.body_excerpt || 'No excerpt available'} +- Excerpt: ${searchResult.snippet || 'No excerpt available'} TARGET LIBRARIES: ${libs} TARGET APIs/SYMBOLS: ${apis} diff --git a/code-reference-finder/src/lib/mino-client.ts b/code-reference-finder/src/lib/mino-client.ts deleted file mode 100644 index 2f2b8b13e..000000000 --- a/code-reference-finder/src/lib/mino-client.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { MINO_API_URL } from './constants'; -import type { MinoRequestConfig, MinoCallbacks, MinoSSEEvent } from './types'; - -function parseSSELine(line: string): MinoSSEEvent | null { - if (!line.startsWith('data: ')) return null; - try { - return JSON.parse(line.slice(6)) as MinoSSEEvent; - } catch { - return null; - } -} - -/** - * Start a Mino agent and handle SSE stream (server-side). - * Returns an AbortController for cancellation. - */ -export function startMinoAgent( - config: MinoRequestConfig, - callbacks: MinoCallbacks -): AbortController { - const controller = new AbortController(); - const apiKey = process.env.TINYFISH_API_KEY; - - if (!apiKey || apiKey.includes('placeholder')) { - callbacks.onError('TINYFISH_API_KEY is not configured'); - return controller; - } - - fetch(MINO_API_URL, { - method: 'POST', - headers: { - 'X-API-Key': apiKey, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - url: config.url, - goal: config.goal, - }), - signal: controller.signal, - }) - .then(async (response) => { - if (!response.ok) { - throw new Error(`Mino HTTP error: ${response.status}`); - } - if (!response.body) { - throw new Error('Mino response body is null'); - } - - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - let buffer = ''; - let streamingUrlCaptured = false; - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - - buffer += decoder.decode(value, { stream: true }); - const lines = buffer.split('\n'); - buffer = lines.pop() ?? ''; - - for (const line of lines) { - const event = parseSSELine(line); - if (!event) continue; - - // Capture streaming URL (comes early, only once) - if (event.streamingUrl && !streamingUrlCaptured) { - streamingUrlCaptured = true; - callbacks.onStreamingUrl(event.streamingUrl); - } - - // Progress steps - if (event.type === 'STEP' || event.purpose || event.action) { - callbacks.onStep(event); - } - - // Final result - if (event.type === 'COMPLETE' || event.status === 'COMPLETED') { - if (event.resultJson) { - callbacks.onComplete(event.resultJson); - } - return; - } - - // Error - if (event.type === 'ERROR' || event.status === 'FAILED') { - callbacks.onError(event.message || 'Agent automation failed'); - return; - } - } - } - }) - .catch((error) => { - if ((error as Error).name !== 'AbortError') { - callbacks.onError((error as Error).message); - } - }); - - return controller; -} diff --git a/code-reference-finder/src/lib/openrouter.ts b/code-reference-finder/src/lib/openrouter.ts deleted file mode 100644 index 90b2bc95c..000000000 --- a/code-reference-finder/src/lib/openrouter.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { OPENROUTER_API_URL, OPENROUTER_MODEL, OPENROUTER_TEMPERATURE } from './constants'; -import type { CodeAnalysis, SearchQuery } from './types'; - -function extractJSON(text: string): unknown { - try { - return JSON.parse(text); - } catch { - // Fallback: extract JSON object from markdown code blocks or surrounding text - const match = text.match(/\{[\s\S]*\}/); - if (match) { - return JSON.parse(match[0]); - } - throw new Error('Could not parse JSON from OpenRouter response'); - } -} - -async function callOpenRouter(systemPrompt: string, userPrompt: string): Promise { - const apiKey = process.env.OPENROUTER_API_KEY; - if (!apiKey || apiKey.includes('placeholder')) { - throw new Error('OPENROUTER_API_KEY is not configured'); - } - - const response = await fetch(OPENROUTER_API_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}`, - }, - body: JSON.stringify({ - model: OPENROUTER_MODEL, - temperature: OPENROUTER_TEMPERATURE, - messages: [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: userPrompt }, - ], - }), - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`OpenRouter API error ${response.status}: ${errorText}`); - } - - const data = await response.json(); - return data.choices?.[0]?.message?.content ?? ''; -} - -export async function analyzeCode(code: string): Promise { - const systemPrompt = `You are a code analysis assistant. -Analyze the following code snippet and return a structured analysis. - -Identify: -1. The programming language -2. All external libraries, packages, and frameworks imported or used -3. All APIs, hooks, classes, and notable symbols invoked -4. Real-world usage patterns present (e.g. data fetching, state management, authentication, middleware chaining) - -Do NOT return any URLs. Do NOT search the web. Only analyze the code provided. - -Return ONLY a JSON object with this exact shape (no markdown, no explanation): -{ - "language": "...", - "libraries": ["library1", "library2"], - "apis": ["api1", "api2"], - "patterns": ["pattern1", "pattern2"] -}`; - - const content = await callOpenRouter(systemPrompt, code); - const parsed = extractJSON(content) as CodeAnalysis; - - return { - language: parsed.language || 'unknown', - libraries: Array.isArray(parsed.libraries) ? parsed.libraries : [], - apis: Array.isArray(parsed.apis) ? parsed.apis : [], - patterns: Array.isArray(parsed.patterns) ? parsed.patterns : [], - }; -} - -export async function generateSearchQueries(analysis: CodeAnalysis): Promise { - const systemPrompt = `You are a search query strategist for developer tools. -Given the structured analysis of a code snippet, generate search queries that will surface high-quality real-world usage examples from GitHub and Stack Overflow. - -Requirements: -- Generate exactly 10 search queries: 5 with target "github" and 5 with target "stackoverflow" -- Keep queries SHORT (2-4 words max). Shorter queries return more results. -- For GitHub: use library/framework names only, e.g. "tanstack react-query", "express middleware typescript" -- For Stack Overflow: use concise problem keywords, e.g. "useQuery refetch interval", "prisma findMany include" -- Do NOT write full sentences or long phrases as queries -- For each query, indicate the intended target: "github" or "stackoverflow" -- Provide ranking heuristics: what signals indicate a high-quality result - -Do NOT return any URLs. Do NOT invent links. Only return queries and heuristics. - -Return ONLY a JSON object with this exact shape (no markdown, no explanation): -{ - "queries": [ - { - "query": "search terms here", - "target": "github", - "heuristic": "what makes a good result for this query" - } - ] -}`; - - const userPrompt = `Language: ${analysis.language} -Libraries: ${analysis.libraries.join(', ')} -APIs: ${analysis.apis.join(', ')} -Patterns: ${analysis.patterns.join(', ')}`; - - const content = await callOpenRouter(systemPrompt, userPrompt); - const parsed = extractJSON(content) as { queries: SearchQuery[] }; - - if (!Array.isArray(parsed.queries)) { - throw new Error('OpenRouter did not return a queries array'); - } - - const all = parsed.queries - .filter((q) => q.query && q.target && q.heuristic) - .map((q) => { - // Normalize target: LLMs may return "GitHub", "StackOverflow", "stack_overflow", etc. - const t = q.target.toLowerCase().replace(/[\s_-]/g, ''); - const target = t.includes('stack') ? 'stackoverflow' : 'github'; - return { ...q, target } as SearchQuery; - }); - - // Guarantee exactly 5 of each platform - const ghQueries = all.filter((q) => q.target === 'github').slice(0, 5); - const soQueries = all.filter((q) => q.target === 'stackoverflow').slice(0, 5); - - return [...ghQueries, ...soQueries]; -} diff --git a/code-reference-finder/src/lib/orchestrator.ts b/code-reference-finder/src/lib/orchestrator.ts index 30cc2be28..3b369df1f 100644 --- a/code-reference-finder/src/lib/orchestrator.ts +++ b/code-reference-finder/src/lib/orchestrator.ts @@ -1,170 +1,179 @@ -import { analyzeCode, generateSearchQueries } from './openrouter'; -import { executeSearches } from './search'; -import { startMinoAgent } from './mino-client'; -import { buildGitHubGoal, buildSOReasoningGoal } from './goal-builder'; -import { AGENT_TIMEOUT_MS } from './constants'; -import type { CodeAnalysis, OrchestratorEvent, SearchResult, ReferenceData } from './types'; +import { TinyFish, EventType, RunStatus } from "@tiny-fish/sdk"; +import { analyzeCode, generateSearchQueries } from "./gemini-client"; +import { executeSearches } from "./search"; +import { buildGitHubGoal, buildSOReasoningGoal } from "./goal-builder"; +import { AGENT_TIMEOUT_MS } from "./constants"; +import type { CodeAnalysis, OrchestratorEvent, SearchResult, ReferenceData } from "./types"; const encoder = new TextEncoder(); function emitEvent( - writer: WritableStreamDefaultWriter, + controller: ReadableStreamDefaultController, event: OrchestratorEvent ) { - const payload = `data: ${JSON.stringify(event)}\n\n`; - writer.write(encoder.encode(payload)).catch(() => { + try { + controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`)); + } catch { // Stream may be closed by client — ignore - }); + } } function makeAgentId(index: number, platform: string): string { return `agent-${platform}-${index}-${Date.now()}`; } -function launchAgent( +async function launchAgent( agentId: string, searchResult: SearchResult, analysis: CodeAnalysis, - writer: WritableStreamDefaultWriter + apiKey: string, + controller: ReadableStreamDefaultController ): Promise { - return new Promise((resolve) => { - // Build the appropriate goal - const config = - searchResult.platform === 'github' - ? buildGitHubGoal(searchResult.url, analysis) - : buildSOReasoningGoal(searchResult, analysis); - - emitEvent(writer, { - type: 'agent_connecting', - data: { - id: agentId, - url: searchResult.url, - platform: searchResult.platform, - title: searchResult.title, - }, - }); - - let controller: AbortController; + const config = + searchResult.platform === "github" + ? buildGitHubGoal(searchResult.url, analysis) + : buildSOReasoningGoal(searchResult, analysis); + + emitEvent(controller, { + type: "agent_connecting", + data: { + id: agentId, + url: searchResult.url, + platform: searchResult.platform, + title: searchResult.title, + }, + }); - // Timeout - const timeout = setTimeout(() => { - controller?.abort(); - emitEvent(writer, { - type: 'agent_error', - data: { id: agentId, error: 'Agent timed out after 6 minutes' }, + const timeoutPromise = new Promise((resolve) => { + setTimeout(() => { + emitEvent(controller, { + type: "agent_error", + data: { id: agentId, error: "Agent timed out after 6 minutes" }, }); resolve(); }, AGENT_TIMEOUT_MS); - - controller = startMinoAgent(config, { - onStep(event) { - const message = - event.message || event.purpose || event.action || 'Working...'; - emitEvent(writer, { - type: 'agent_step', - data: { id: agentId, step: message }, - }); - }, - - onStreamingUrl(url) { - emitEvent(writer, { - type: 'agent_streaming_url', - data: { id: agentId, streamingUrl: url }, - }); - }, - - onComplete(resultJson) { - clearTimeout(timeout); - const result = resultJson as ReferenceData; - // Ensure required fields have defaults - const normalized: ReferenceData = { - sourceUrl: result.sourceUrl || searchResult.url, - platform: searchResult.platform, - title: result.title || searchResult.title, - relevanceScore: result.relevanceScore ?? 50, - alignmentExplanation: result.alignmentExplanation || '', - codeSnippets: Array.isArray(result.codeSnippets) ? result.codeSnippets : [], - repoName: result.repoName, - repoDescription: result.repoDescription, - stars: result.stars, - repoLanguage: result.repoLanguage, - readmeExcerpt: result.readmeExcerpt, - questionTitle: result.questionTitle, - votes: result.votes, - answerSnippets: result.answerSnippets, - tags: result.tags, - isAccepted: result.isAccepted, - }; - emitEvent(writer, { - type: 'agent_complete', - data: { id: agentId, result: normalized }, - }); - resolve(); - }, - - onError(error) { - clearTimeout(timeout); - emitEvent(writer, { - type: 'agent_error', - data: { id: agentId, error }, - }); - resolve(); - }, - }); }); + + const agentPromise = (async () => { + try { + const client = new TinyFish({ apiKey }); + const tfStream = await client.agent.stream({ url: config.url, goal: config.goal }); + + for await (const event of tfStream) { + if (event.type === EventType.STREAMING_URL) { + emitEvent(controller, { + type: "agent_streaming_url", + data: { id: agentId, streamingUrl: event.streaming_url }, + }); + } else if (event.type === EventType.PROGRESS) { + emitEvent(controller, { + type: "agent_step", + data: { id: agentId, step: event.purpose }, + }); + } else if (event.type === EventType.COMPLETE) { + if (event.status === RunStatus.COMPLETED) { + // COMPLETED only means the browser ran without crashing + // — always validate result content, not just the status + const result = (event.result ?? {}) as unknown as ReferenceData; + + const normalized: ReferenceData = { + sourceUrl: result.sourceUrl || searchResult.url, + platform: searchResult.platform, + title: result.title || searchResult.title, + relevanceScore: result.relevanceScore ?? 50, + alignmentExplanation: result.alignmentExplanation || "", + codeSnippets: Array.isArray(result.codeSnippets) ? result.codeSnippets : [], + repoName: result.repoName, + repoDescription: result.repoDescription, + stars: result.stars, + repoLanguage: result.repoLanguage, + readmeExcerpt: result.readmeExcerpt, + questionTitle: result.questionTitle, + votes: result.votes, + answerSnippets: result.answerSnippets, + tags: result.tags, + isAccepted: result.isAccepted, + }; + + emitEvent(controller, { + type: "agent_complete", + data: { id: agentId, result: normalized }, + }); + } else { + emitEvent(controller, { + type: "agent_error", + data: { id: agentId, error: event.error?.message || "Agent run failed" }, + }); + } + break; + } + } + } catch (error) { + emitEvent(controller, { + type: "agent_error", + data: { id: agentId, error: (error as Error).message }, + }); + } + })(); + + await Promise.race([agentPromise, timeoutPromise]); } export async function runPipeline( code: string, - writer: WritableStreamDefaultWriter + controller: ReadableStreamDefaultController ): Promise { + const apiKey = process.env.TINYFISH_API_KEY; + if (!apiKey) { + emitEvent(controller, { + type: "pipeline_error", + data: { error: "Missing TINYFISH_API_KEY" }, + }); + return; + } + try { - // Stage 1: Analyze code + generate queries + // Stage 1: Analyze code + generate queries via Gemini const analysis = await analyzeCode(code); const queries = await generateSearchQueries(analysis); - emitEvent(writer, { - type: 'analysis_complete', - data: { - analysis, - queries, - }, + emitEvent(controller, { + type: "analysis_complete", + data: { analysis, queries }, }); - // Stage 2: Execute indexed searches + // Stage 2: Search via TinyFish Search API const searchResults = await executeSearches(queries); - emitEvent(writer, { - type: 'search_complete', - data: { - results: searchResults, - }, + emitEvent(controller, { + type: "search_complete", + data: { results: searchResults }, }); if (searchResults.length === 0) { - emitEvent(writer, { - type: 'pipeline_complete', - data: { message: 'No search results found' }, + emitEvent(controller, { + type: "pipeline_complete", + data: { message: "No search results found" }, }); return; } - // Stage 3: Launch parallel Mino agents - const agentPromises = searchResults.map((result, index) => { - const agentId = makeAgentId(index, result.platform); - return launchAgent(agentId, result, analysis, writer); - }); - - await Promise.allSettled(agentPromises); - - // Stage 4: Pipeline complete - emitEvent(writer, { - type: 'pipeline_complete', - data: { message: 'All agents finished' }, + // Stage 3: Launch parallel TinyFish agents — one per search result + await Promise.allSettled( + searchResults.map((result, index) => { + const agentId = makeAgentId(index, result.platform); + return launchAgent(agentId, result, analysis, apiKey, controller); + }) + ); + + // Stage 4: Done + emitEvent(controller, { + type: "pipeline_complete", + data: { message: "All agents finished" }, }); } catch (error) { - emitEvent(writer, { - type: 'pipeline_error', + emitEvent(controller, { + type: "pipeline_error", data: { error: (error as Error).message }, }); } diff --git a/code-reference-finder/src/lib/search.ts b/code-reference-finder/src/lib/search.ts index 999b86086..ead5d8d20 100644 --- a/code-reference-finder/src/lib/search.ts +++ b/code-reference-finder/src/lib/search.ts @@ -1,175 +1,76 @@ -import { - GITHUB_API_URL, - STACKEXCHANGE_API_URL, - GITHUB_RESULTS_PER_QUERY, - STACKOVERFLOW_RESULTS_PER_QUERY, - MAX_AGENTS, -} from './constants'; -import type { SearchQuery, SearchResult, StackExchangeItem } from './types'; - -interface GitHubRepoItem { - html_url: string; - full_name: string; - description: string | null; - stargazers_count: number; - language: string | null; -} - -function delay(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -async function fetchWithRetry( - url: string, - options: RequestInit, - retries = 3 -): Promise { - for (let attempt = 0; attempt < retries; attempt++) { - const response = await fetch(url, options); - - if (response.status === 429) { - const retryAfter = response.headers.get('Retry-After'); - const waitMs = retryAfter - ? parseInt(retryAfter, 10) * 1000 - : (attempt + 1) * 5000; // 5s, 10s, 15s - console.warn(`GitHub 429 rate-limited. Retrying in ${waitMs}ms (attempt ${attempt + 1}/${retries})`); - await delay(waitMs); - continue; - } - - return response; - } - - return fetch(url, options); -} - -async function searchGitHub(query: string): Promise { - const params = new URLSearchParams({ - q: query, - sort: 'stars', - per_page: String(GITHUB_RESULTS_PER_QUERY), - }); - - const headers: Record = { - Accept: 'application/vnd.github.v3+json', - }; - - const token = process.env.GITHUB_TOKEN; - if (token && !token.includes('placeholder')) { - // Use Bearer prefix — works for both classic (ghp_) and fine-grained (github_pat_) tokens - headers['Authorization'] = `Bearer ${token}`; - } - - const response = await fetchWithRetry( - `${GITHUB_API_URL}/search/repositories?${params}`, - { headers } - ); - - if (!response.ok) { - console.error(`GitHub search failed: ${response.status}`); - return []; - } - - const data = await response.json(); - const items: GitHubRepoItem[] = data.items ?? []; - - return items.map((item) => ({ - platform: 'github' as const, - url: item.html_url, - title: item.full_name, - snippet: item.description ?? '', - stars: item.stargazers_count, - language: item.language ?? undefined, - })); -} - -async function searchStackOverflow(query: string): Promise { - const params = new URLSearchParams({ - order: 'desc', - sort: 'votes', - q: query, - site: 'stackoverflow', - pagesize: String(STACKOVERFLOW_RESULTS_PER_QUERY), - filter: '!nNPvSNdWme', - }); - - const key = process.env.STACKEXCHANGE_KEY; - if (key && !key.includes('placeholder')) { - params.set('key', key); - } - - try { - const response = await fetch(`${STACKEXCHANGE_API_URL}/search/advanced?${params}`, { - headers: { 'User-Agent': 'CodeReferenceFinder/1.0' }, - }); - - if (!response.ok) { - console.error(`SO search failed: ${response.status} for "${query}"`); - return []; - } - - // Node.js fetch decompresses gzip automatically — just use .json() - const data = await response.json(); - - if (data.error_id) { - console.error(`SO API error: ${data.error_name} — ${data.error_message}`); - return []; - } - - const items: StackExchangeItem[] = data.items ?? []; - - return items.map((item) => ({ - platform: 'stackoverflow' as const, - url: item.link, - title: item.title, - snippet: item.body_excerpt ?? '', - score: item.score, - answerCount: item.answer_count, - tags: item.tags, - isAnswered: item.is_answered, - apiData: item, - })); - } catch (err) { - console.error(`SO search error for "${query}":`, err); - return []; - } -} - -function deduplicate(results: SearchResult[]): SearchResult[] { - const seen = new Set(); - return results.filter((r) => { - if (seen.has(r.url)) return false; - seen.add(r.url); - return true; - }); -} - -export async function executeSearches(queries: SearchQuery[]): Promise { - const githubQueries = queries.filter((q) => q.target === 'github'); - const soQueries = queries.filter((q) => q.target === 'stackoverflow'); - const halfTarget = Math.ceil(MAX_AGENTS / 2); // 5 - - // Run SO queries in parallel (no strict rate limits) - const soResults = (await Promise.all( - soQueries.map((q) => searchStackOverflow(q.query)) - )).flat(); - - // Run GitHub queries sequentially with a 2s gap to avoid 429 rate limiting - const githubResults: SearchResult[] = []; - for (let i = 0; i < githubQueries.length; i++) { - const results = await searchGitHub(githubQueries[i].query); - githubResults.push(...results); - if (i < githubQueries.length - 1) { - await delay(2000); - } - } - - const dedupedGH = deduplicate(githubResults); - const dedupedSO = deduplicate(soResults); - - // Strict 5+5 split: take up to 5 from each platform - const pickedGH = dedupedGH.slice(0, halfTarget); - const pickedSO = dedupedSO.slice(0, halfTarget); - - return [...pickedGH, ...pickedSO]; +import { TinyFish } from "@tiny-fish/sdk"; +import type { CodeAnalysis, SearchQuery, SearchResult } from "./types"; +import { MAX_AGENTS } from "./constants"; + +const halfTarget = Math.ceil(MAX_AGENTS / 2); // 5 from each platform + +/** + * Use TinyFish Search API to find real GitHub and Stack Overflow results + * for each generated query. + */ +export async function executeSearches( + queries: SearchQuery[], + _analysis?: CodeAnalysis +): Promise { + const apiKey = process.env.TINYFISH_API_KEY; + if (!apiKey) throw new Error("Missing TINYFISH_API_KEY"); + + const client = new TinyFish({ apiKey }); + + const ghQueries = queries.filter((q) => q.target === "github").slice(0, halfTarget); + const soQueries = queries.filter((q) => q.target === "stackoverflow").slice(0, halfTarget); + + const results: SearchResult[] = []; + + // Run all searches in parallel + await Promise.allSettled([ + ...ghQueries.map(async (q) => { + try { + const res = await client.search.query({ + query: `site:github.com ${q.query}`, + }); + for (const r of res.results || []) { + results.push({ + platform: "github", + url: r.url, + title: r.title || `GitHub: ${q.query}`, + snippet: r.snippet || q.heuristic, + }); + } + } catch { + // Fallback to constructed URL if Search API fails + results.push({ + platform: "github", + url: `https://github.com/search?q=${encodeURIComponent(q.query)}&type=repositories&s=stars&o=desc`, + title: `GitHub: ${q.query}`, + snippet: q.heuristic, + }); + } + }), + ...soQueries.map(async (q) => { + try { + const res = await client.search.query({ + query: `site:stackoverflow.com ${q.query}`, + }); + for (const r of res.results || []) { + results.push({ + platform: "stackoverflow", + url: r.url, + title: r.title || `Stack Overflow: ${q.query}`, + snippet: r.snippet || q.heuristic, + }); + } + } catch { + // Fallback to constructed URL if Search API fails + results.push({ + platform: "stackoverflow", + url: `https://stackoverflow.com/search?q=${encodeURIComponent(q.query)}&tab=votes`, + title: `Stack Overflow: ${q.query}`, + snippet: q.heuristic, + }); + } + }), + ]); + + return results; } diff --git a/code-reference-finder/src/lib/types.ts b/code-reference-finder/src/lib/types.ts index 9ae780972..29d24d2d8 100644 --- a/code-reference-finder/src/lib/types.ts +++ b/code-reference-finder/src/lib/types.ts @@ -1,7 +1,7 @@ // --- Source Platform --- -export type SourcePlatform = 'github' | 'stackoverflow'; +export type SourcePlatform = "github" | "stackoverflow"; -// --- OpenRouter Analysis Output --- +// --- Code Analysis Output --- export interface CodeAnalysis { language: string; libraries: string[]; @@ -9,26 +9,14 @@ export interface CodeAnalysis { patterns: string[]; } -// --- Search Query (generated by OpenRouter) --- +// --- Search Query (generated by Gemini) --- export interface SearchQuery { query: string; - target: 'github' | 'stackoverflow'; + target: "github" | "stackoverflow"; heuristic: string; } -// --- Stack Exchange API item shape --- -export interface StackExchangeItem { - question_id: number; - title: string; - tags: string[]; - score: number; - answer_count: number; - is_answered: boolean; - link: string; - body_excerpt?: string; -} - -// --- Search Result (from indexed APIs) --- +// --- Search Result (from TinyFish Search API) --- export interface SearchResult { platform: SourcePlatform; url: string; @@ -40,7 +28,6 @@ export interface SearchResult { answerCount?: number; tags?: string[]; isAnswered?: boolean; - apiData?: StackExchangeItem; } // --- Code Snippet extracted by agents --- @@ -50,7 +37,7 @@ export interface CodeSnippet { context: string; } -// --- Extracted Reference Data (from Mino agents) --- +// --- Extracted Reference Data (from TinyFish agents) --- export interface ReferenceData { sourceUrl: string; platform: SourcePlatform; @@ -72,12 +59,12 @@ export interface ReferenceData { // --- Agent State --- export type AgentStatus = - | 'connecting' - | 'navigating' - | 'extracting' - | 'reasoning' - | 'complete' - | 'error'; + | "connecting" + | "navigating" + | "extracting" + | "reasoning" + | "complete" + | "error"; export interface AgentStep { message: string; @@ -100,11 +87,11 @@ export interface ReferenceAgentState { // --- App State --- export type AppPhase = - | 'input' - | 'analyzing' - | 'searching' - | 'extracting' - | 'complete'; + | "input" + | "analyzing" + | "searching" + | "extracting" + | "complete"; export interface AppState { phase: AppPhase; @@ -119,52 +106,27 @@ export interface AppState { // --- Reducer Actions --- export type AppAction = - | { type: 'START_ANALYSIS'; payload: { code: string } } - | { type: 'ANALYSIS_COMPLETE'; payload: { analysis: CodeAnalysis; queries: SearchQuery[] } } - | { type: 'SEARCH_COMPLETE'; payload: { results: SearchResult[] } } - | { type: 'AGENT_CONNECTING'; payload: { id: string; url: string; platform: SourcePlatform } } - | { type: 'AGENT_STEP'; payload: { id: string; step: string } } - | { type: 'AGENT_STREAMING_URL'; payload: { id: string; streamingUrl: string } } - | { type: 'AGENT_COMPLETE'; payload: { id: string; result: ReferenceData } } - | { type: 'AGENT_ERROR'; payload: { id: string; error: string } } - | { type: 'RESET' }; - -// --- Mino SSE types --- -export interface MinoSSEEvent { - type?: string; - status?: string; - message?: string; - purpose?: string; - action?: string; - resultJson?: unknown; - streamingUrl?: string; - step?: number; - totalSteps?: number; -} - -export interface MinoCallbacks { - onStep: (event: MinoSSEEvent) => void; - onStreamingUrl: (url: string) => void; - onComplete: (result: unknown) => void; - onError: (error: string) => void; -} - -export interface MinoRequestConfig { - url: string; - goal: string; -} + | { type: "START_ANALYSIS"; payload: { code: string } } + | { type: "ANALYSIS_COMPLETE"; payload: { analysis: CodeAnalysis; queries: SearchQuery[] } } + | { type: "SEARCH_COMPLETE"; payload: { results: SearchResult[] } } + | { type: "AGENT_CONNECTING"; payload: { id: string; url: string; platform: SourcePlatform } } + | { type: "AGENT_STEP"; payload: { id: string; step: string } } + | { type: "AGENT_STREAMING_URL"; payload: { id: string; streamingUrl: string } } + | { type: "AGENT_COMPLETE"; payload: { id: string; result: ReferenceData } } + | { type: "AGENT_ERROR"; payload: { id: string; error: string } } + | { type: "RESET" }; // --- SSE Orchestration Events (API route -> client) --- export type OrchestratorEventType = - | 'analysis_complete' - | 'search_complete' - | 'agent_connecting' - | 'agent_step' - | 'agent_streaming_url' - | 'agent_complete' - | 'agent_error' - | 'pipeline_complete' - | 'pipeline_error'; + | "analysis_complete" + | "search_complete" + | "agent_connecting" + | "agent_step" + | "agent_streaming_url" + | "agent_complete" + | "agent_error" + | "pipeline_complete" + | "pipeline_error"; export interface OrchestratorEvent { type: OrchestratorEventType; diff --git a/code-reference-finder/tailwind.config.ts b/code-reference-finder/tailwind.config.ts new file mode 100644 index 000000000..76f9ee392 --- /dev/null +++ b/code-reference-finder/tailwind.config.ts @@ -0,0 +1,11 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: ["./src/**/*.{ts,tsx}"], + theme: { + extend: {}, + }, + plugins: [], +}; + +export default config;