Jainish1808 commited on
Commit
4000a4c
Β·
1 Parent(s): 7094dce

Initial commit - Socratic Lens

Browse files
.env.example ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # Socratic Lens Environment Variables
2
+
3
+ # Get your API key from: https://aistudio.google.com/app/apikey
4
+ GEMINI_API_KEY=your_gemini_api_key_here
.gitignore CHANGED
@@ -1,41 +1,43 @@
1
- # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
-
3
- # dependencies
4
- /node_modules
5
- /.pnp
6
- .pnp.*
7
- .yarn/*
8
- !.yarn/patches
9
- !.yarn/plugins
10
- !.yarn/releases
11
- !.yarn/versions
12
-
13
- # testing
14
- /coverage
15
-
16
- # next.js
17
- /.next/
18
- /out/
19
-
20
- # production
21
- /build
22
-
23
- # misc
24
- .DS_Store
25
- *.pem
26
-
27
- # debug
28
  npm-debug.log*
29
  yarn-debug.log*
30
  yarn-error.log*
31
- .pnpm-debug.log*
32
 
33
- # env files (can opt-in for committing if needed)
34
- .env*
 
 
 
35
 
36
- # vercel
37
- .vercel
 
38
 
39
- # typescript
40
  *.tsbuildinfo
41
  next-env.d.ts
 
 
 
 
 
1
+ # Dependencies
2
+ node_modules
3
+ .pnp
4
+ .pnp.js
5
+
6
+ # Build outputs
7
+ .next
8
+ out
9
+ build
10
+ dist
11
+
12
+ # Testing
13
+ coverage
14
+
15
+ # Environment files
16
+ .env
17
+ .env.local
18
+ .env.development.local
19
+ .env.test.local
20
+ .env.production.local
21
+
22
+ # Debug logs
 
 
 
 
 
23
  npm-debug.log*
24
  yarn-debug.log*
25
  yarn-error.log*
 
26
 
27
+ # IDE
28
+ .vscode
29
+ .idea
30
+ *.swp
31
+ *.swo
32
 
33
+ # OS
34
+ .DS_Store
35
+ Thumbs.db
36
 
37
+ # TypeScript
38
  *.tsbuildinfo
39
  next-env.d.ts
40
+
41
+ # Misc
42
+ *.log
43
+ .vercel
Dockerfile ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:20-alpine AS base
2
+
3
+ # Install dependencies only when needed
4
+ FROM base AS deps
5
+ RUN apk add --no-cache libc6-compat
6
+ WORKDIR /app
7
+
8
+ # Copy package files
9
+ COPY package.json package-lock.json* ./
10
+ RUN npm ci
11
+
12
+ # Rebuild the source code only when needed
13
+ FROM base AS builder
14
+ WORKDIR /app
15
+ COPY --from=deps /app/node_modules ./node_modules
16
+ COPY . .
17
+
18
+ # Build the application
19
+ ENV NEXT_TELEMETRY_DISABLED=1
20
+ RUN npm run build
21
+
22
+ # Production image
23
+ FROM base AS runner
24
+ WORKDIR /app
25
+
26
+ ENV NODE_ENV=production
27
+ ENV NEXT_TELEMETRY_DISABLED=1
28
+
29
+ RUN addgroup --system --gid 1001 nodejs
30
+ RUN adduser --system --uid 1001 nextjs
31
+
32
+ COPY --from=builder /app/public ./public
33
+
34
+ # Set the correct permission for prerender cache
35
+ RUN mkdir .next
36
+ RUN chown nextjs:nodejs .next
37
+
38
+ # Automatically leverage output traces to reduce image size
39
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
40
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
41
+
42
+ USER nextjs
43
+
44
+ EXPOSE 3000
45
+ ENV PORT=3000
46
+ ENV HOSTNAME="0.0.0.0"
47
+
48
+ CMD ["node", "server.js"]
README.md CHANGED
@@ -1,36 +1,64 @@
1
- This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
 
 
 
 
 
 
 
 
2
 
3
- ## Getting Started
4
 
5
- First, run the development server:
6
 
7
- ```bash
8
- npm run dev
9
- # or
10
- yarn dev
11
- # or
12
- pnpm dev
13
- # or
14
- bun dev
15
- ```
16
 
17
- Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
 
19
- You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
 
 
 
20
 
21
- This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
 
23
- ## Learn More
 
 
 
 
24
 
25
- To learn more about Next.js, take a look at the following resources:
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
- - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
 
30
- You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
 
32
- ## Deploy on Vercel
 
 
 
33
 
34
- The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
 
36
- Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
 
1
+ ---
2
+ title: Socratic Lens
3
+ emoji: πŸ“š
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
 
11
+ # πŸ“š Socratic Lens
12
 
13
+ An AI-powered educational companion that analyzes images and documents to create beautiful, structured learning summaries.
14
 
15
+ ## Features
16
+
17
+ - πŸ” **Image & Document Analysis** - Upload photos or PDFs of textbook pages
18
+ - πŸ“· **Camera Capture** - Take photos directly from your device
19
+ - πŸ“– **Wikipedia Integration** - Auto-fetches related Wikipedia articles
20
+ - πŸ’¬ **Ask Questions** - Interactive Q&A about the analyzed content
21
+ - πŸ“ **Quiz Me** - Generate customizable quizzes (MCQ/Written)
22
+ - 🎨 **Beautiful UI** - Clean, minimal design with dark mode support
 
23
 
24
+ ## Tech Stack
25
 
26
+ - **Framework**: Next.js 16 (App Router)
27
+ - **AI**: Google Gemini 2.5 Flash
28
+ - **Styling**: Tailwind CSS
29
+ - **Language**: TypeScript
30
 
31
+ ## Environment Variables
32
 
33
+ Set the following in your Hugging Face Space settings:
34
+
35
+ ```
36
+ GEMINI_API_KEY=your_google_gemini_api_key
37
+ ```
38
 
39
+ ## Local Development
40
+
41
+ ```bash
42
+ # Install dependencies
43
+ npm install
44
+
45
+ # Set up environment
46
+ cp .env.example .env.local
47
+ # Add your GEMINI_API_KEY to .env.local
48
+
49
+ # Run development server
50
+ npm run dev
51
+ ```
52
 
53
+ ## Deployment
 
54
 
55
+ This app is configured for deployment on Hugging Face Spaces using Docker.
56
 
57
+ 1. Create a new Space on Hugging Face
58
+ 2. Select "Docker" as the SDK
59
+ 3. Push this repository to the Space
60
+ 4. Add `GEMINI_API_KEY` in Space Settings > Repository Secrets
61
 
62
+ ## License
63
 
64
+ MIT
README_SOCRATIC.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Socratic Lens - Setup Guide
2
+
3
+ This project is a Next.js application that uses Google's Gemini API to function as an "Ethical Tutor" or "Socratic Lens". It features a beautiful "Pixel OS" aesthetic and implements the logic defined in your prompt.
4
+
5
+ ## Prerequisites
6
+ 1. **Google Gemini API Key**: You need an API key. Get it from [Google AI Studio](https://aistudio.google.com/).
7
+
8
+ ## Quick Start
9
+
10
+ ### 1. Configure API Key
11
+ Create a new file named `.env.local` in the `socratic-lens` folder. Add your API key:
12
+
13
+ ```bash
14
+ # In /home/ig0034/Documents/Task_2_24_12_2025/socratic-lens/.env.local
15
+ GEMINI_API_KEY=your_actual_api_key_here
16
+ ```
17
+
18
+ ### 2. Run the Development Server
19
+ Open your terminal in the `socratic-lens` directory and run:
20
+
21
+ ```bash
22
+ cd socratic-lens
23
+ npm run dev
24
+ ```
25
+
26
+ ### 3. Open in Browser
27
+ Visit [http://localhost:3000](http://localhost:3000).
28
+
29
+ ## Project Structure
30
+ * **`src/app/page.tsx`**: The main frontend logic (Upload UI + Result Renderer).
31
+ * **`src/app/api/analyze/route.ts`**: The backend API that connects to Gemini.
32
+ * **`src/prompts/system_prompt.md`**: The brain of the AI (Ethical Tutor Persona).
33
+ * **`src/app/globals.css`**: Tailwind v4 + Custom styles for the Pixel look.
34
+
35
+ ## Usage
36
+ 1. **Upload an Image**: Take a screenshot of a math problem or a complex paragraph.
37
+ 2. **Click Analyze**: The AI will process it using the "Socratic Lens" rules.
38
+ 3. **Learn**: See the beautiful, structured output that guides you without cheating.
next.config.ts CHANGED
@@ -1,7 +1,12 @@
1
  import type { NextConfig } from "next";
2
 
3
  const nextConfig: NextConfig = {
4
- /* config options here */
 
 
 
 
 
5
  };
6
 
7
  export default nextConfig;
 
1
  import type { NextConfig } from "next";
2
 
3
  const nextConfig: NextConfig = {
4
+ output: 'standalone',
5
+ experimental: {
6
+ serverActions: {
7
+ bodySizeLimit: '10mb',
8
+ },
9
+ },
10
  };
11
 
12
  export default nextConfig;
package-lock.json CHANGED
@@ -8,9 +8,16 @@
8
  "name": "socratic-lens",
9
  "version": "0.1.0",
10
  "dependencies": {
 
 
 
11
  "next": "16.1.1",
 
12
  "react": "19.2.3",
13
- "react-dom": "19.2.3"
 
 
 
14
  },
15
  "devDependencies": {
16
  "@tailwindcss/postcss": "^4",
@@ -454,6 +461,15 @@
454
  "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
455
  }
456
  },
 
 
 
 
 
 
 
 
 
457
  "node_modules/@humanfs/core": {
458
  "version": "0.19.1",
459
  "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1525,13 +1541,39 @@
1525
  "tslib": "^2.4.0"
1526
  }
1527
  },
 
 
 
 
 
 
 
 
 
1528
  "node_modules/@types/estree": {
1529
  "version": "1.0.8",
1530
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
1531
  "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
1532
- "dev": true,
1533
  "license": "MIT"
1534
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1535
  "node_modules/@types/json-schema": {
1536
  "version": "7.0.15",
1537
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -1546,6 +1588,21 @@
1546
  "dev": true,
1547
  "license": "MIT"
1548
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1549
  "node_modules/@types/node": {
1550
  "version": "20.19.27",
1551
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz",
@@ -1560,7 +1617,6 @@
1560
  "version": "19.2.7",
1561
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
1562
  "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
1563
- "dev": true,
1564
  "license": "MIT",
1565
  "peer": true,
1566
  "dependencies": {
@@ -1577,6 +1633,12 @@
1577
  "@types/react": "^19.2.0"
1578
  }
1579
  },
 
 
 
 
 
 
1580
  "node_modules/@typescript-eslint/eslint-plugin": {
1581
  "version": "8.50.1",
1582
  "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz",
@@ -1847,6 +1909,12 @@
1847
  "url": "https://opencollective.com/typescript-eslint"
1848
  }
1849
  },
 
 
 
 
 
 
1850
  "node_modules/@unrs/resolver-binding-android-arm-eabi": {
1851
  "version": "1.11.1",
1852
  "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
@@ -2403,6 +2471,16 @@
2403
  "node": ">= 0.4"
2404
  }
2405
  },
 
 
 
 
 
 
 
 
 
 
2406
  "node_modules/balanced-match": {
2407
  "version": "1.0.2",
2408
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2558,6 +2636,16 @@
2558
  ],
2559
  "license": "CC-BY-4.0"
2560
  },
 
 
 
 
 
 
 
 
 
 
2561
  "node_modules/chalk": {
2562
  "version": "4.1.2",
2563
  "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -2575,12 +2663,61 @@
2575
  "url": "https://github.com/chalk/chalk?sponsor=1"
2576
  }
2577
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2578
  "node_modules/client-only": {
2579
  "version": "0.0.1",
2580
  "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
2581
  "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
2582
  "license": "MIT"
2583
  },
 
 
 
 
 
 
 
 
 
2584
  "node_modules/color-convert": {
2585
  "version": "2.0.1",
2586
  "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2601,6 +2738,16 @@
2601
  "dev": true,
2602
  "license": "MIT"
2603
  },
 
 
 
 
 
 
 
 
 
 
2604
  "node_modules/concat-map": {
2605
  "version": "0.0.1",
2606
  "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2634,7 +2781,6 @@
2634
  "version": "3.2.3",
2635
  "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
2636
  "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
2637
- "dev": true,
2638
  "license": "MIT"
2639
  },
2640
  "node_modules/damerau-levenshtein": {
@@ -2702,7 +2848,6 @@
2702
  "version": "4.4.3",
2703
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
2704
  "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
2705
- "dev": true,
2706
  "license": "MIT",
2707
  "dependencies": {
2708
  "ms": "^2.1.3"
@@ -2716,6 +2861,19 @@
2716
  }
2717
  }
2718
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
2719
  "node_modules/deep-is": {
2720
  "version": "0.1.4",
2721
  "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2759,6 +2917,15 @@
2759
  "url": "https://github.com/sponsors/ljharb"
2760
  }
2761
  },
 
 
 
 
 
 
 
 
 
2762
  "node_modules/detect-libc": {
2763
  "version": "2.1.2",
2764
  "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -2769,6 +2936,19 @@
2769
  "node": ">=8"
2770
  }
2771
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
2772
  "node_modules/doctrine": {
2773
  "version": "2.1.0",
2774
  "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -3217,6 +3397,7 @@
3217
  "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
3218
  "dev": true,
3219
  "license": "MIT",
 
3220
  "dependencies": {
3221
  "@rtsao/scc": "^1.1.0",
3222
  "array-includes": "^3.1.9",
@@ -3440,6 +3621,16 @@
3440
  "node": ">=4.0"
3441
  }
3442
  },
 
 
 
 
 
 
 
 
 
 
3443
  "node_modules/esutils": {
3444
  "version": "2.0.3",
3445
  "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -3450,6 +3641,12 @@
3450
  "node": ">=0.10.0"
3451
  }
3452
  },
 
 
 
 
 
 
3453
  "node_modules/fast-deep-equal": {
3454
  "version": "3.1.3",
3455
  "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3879,6 +4076,46 @@
3879
  "node": ">= 0.4"
3880
  }
3881
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3882
  "node_modules/hermes-estree": {
3883
  "version": "0.25.1",
3884
  "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
@@ -3896,6 +4133,16 @@
3896
  "hermes-estree": "0.25.1"
3897
  }
3898
  },
 
 
 
 
 
 
 
 
 
 
3899
  "node_modules/ignore": {
3900
  "version": "5.3.2",
3901
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3933,6 +4180,12 @@
3933
  "node": ">=0.8.19"
3934
  }
3935
  },
 
 
 
 
 
 
3936
  "node_modules/internal-slot": {
3937
  "version": "1.1.0",
3938
  "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -3948,6 +4201,30 @@
3948
  "node": ">= 0.4"
3949
  }
3950
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3951
  "node_modules/is-array-buffer": {
3952
  "version": "3.0.5",
3953
  "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -4106,6 +4383,16 @@
4106
  "url": "https://github.com/sponsors/ljharb"
4107
  }
4108
  },
 
 
 
 
 
 
 
 
 
 
4109
  "node_modules/is-extglob": {
4110
  "version": "2.1.1",
4111
  "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -4165,6 +4452,16 @@
4165
  "node": ">=0.10.0"
4166
  }
4167
  },
 
 
 
 
 
 
 
 
 
 
4168
  "node_modules/is-map": {
4169
  "version": "2.0.3",
4170
  "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
@@ -4218,6 +4515,18 @@
4218
  "url": "https://github.com/sponsors/ljharb"
4219
  }
4220
  },
 
 
 
 
 
 
 
 
 
 
 
 
4221
  "node_modules/is-regex": {
4222
  "version": "1.2.1",
4223
  "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -4816,6 +5125,16 @@
4816
  "dev": true,
4817
  "license": "MIT"
4818
  },
 
 
 
 
 
 
 
 
 
 
4819
  "node_modules/loose-envify": {
4820
  "version": "1.4.0",
4821
  "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -4839,6 +5158,15 @@
4839
  "yallist": "^3.0.2"
4840
  }
4841
  },
 
 
 
 
 
 
 
 
 
4842
  "node_modules/magic-string": {
4843
  "version": "0.30.21",
4844
  "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -4849,6 +5177,16 @@
4849
  "@jridgewell/sourcemap-codec": "^1.5.5"
4850
  }
4851
  },
 
 
 
 
 
 
 
 
 
 
4852
  "node_modules/math-intrinsics": {
4853
  "version": "1.1.0",
4854
  "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -4859,64 +5197,908 @@
4859
  "node": ">= 0.4"
4860
  }
4861
  },
4862
- "node_modules/merge2": {
4863
- "version": "1.4.1",
4864
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
4865
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
4866
- "dev": true,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4867
  "license": "MIT",
4868
  "engines": {
4869
- "node": ">= 8"
 
 
 
4870
  }
4871
  },
4872
- "node_modules/micromatch": {
4873
- "version": "4.0.8",
4874
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
4875
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
4876
- "dev": true,
4877
  "license": "MIT",
4878
  "dependencies": {
4879
- "braces": "^3.0.3",
4880
- "picomatch": "^2.3.1"
 
 
 
 
 
 
 
 
 
 
4881
  },
4882
- "engines": {
4883
- "node": ">=8.6"
 
4884
  }
4885
  },
4886
- "node_modules/minimatch": {
4887
- "version": "3.1.2",
4888
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
4889
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
4890
- "dev": true,
4891
- "license": "ISC",
4892
  "dependencies": {
4893
- "brace-expansion": "^1.1.7"
 
 
 
 
 
 
4894
  },
4895
- "engines": {
4896
- "node": "*"
 
4897
  }
4898
  },
4899
- "node_modules/minimist": {
4900
- "version": "1.2.8",
4901
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
4902
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
4903
- "dev": true,
4904
  "license": "MIT",
 
 
 
 
 
 
 
4905
  "funding": {
4906
- "url": "https://github.com/sponsors/ljharb"
 
4907
  }
4908
  },
4909
- "node_modules/ms": {
4910
- "version": "2.1.3",
4911
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
4912
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
4913
- "dev": true,
4914
- "license": "MIT"
 
 
 
 
 
 
 
 
 
 
4915
  },
4916
- "node_modules/nanoid": {
4917
- "version": "3.3.11",
4918
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
4919
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4920
  "funding": [
4921
  {
4922
  "type": "github",
@@ -5165,6 +6347,27 @@
5165
  "url": "https://github.com/sponsors/ljharb"
5166
  }
5167
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5168
  "node_modules/optionator": {
5169
  "version": "0.9.4",
5170
  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -5246,6 +6449,31 @@
5246
  "node": ">=6"
5247
  }
5248
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5249
  "node_modules/path-exists": {
5250
  "version": "4.0.0",
5251
  "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -5353,6 +6581,16 @@
5353
  "react-is": "^16.13.1"
5354
  }
5355
  },
 
 
 
 
 
 
 
 
 
 
5356
  "node_modules/punycode": {
5357
  "version": "2.3.1",
5358
  "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -5414,6 +6652,33 @@
5414
  "dev": true,
5415
  "license": "MIT"
5416
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5417
  "node_modules/reflect.getprototypeof": {
5418
  "version": "1.0.10",
5419
  "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -5458,6 +6723,72 @@
5458
  "url": "https://github.com/sponsors/ljharb"
5459
  }
5460
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5461
  "node_modules/resolve": {
5462
  "version": "1.22.11",
5463
  "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -5820,6 +7151,16 @@
5820
  "node": ">=0.10.0"
5821
  }
5822
  },
 
 
 
 
 
 
 
 
 
 
5823
  "node_modules/stable-hash": {
5824
  "version": "0.0.5",
5825
  "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
@@ -5954,6 +7295,20 @@
5954
  "url": "https://github.com/sponsors/ljharb"
5955
  }
5956
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5957
  "node_modules/strip-bom": {
5958
  "version": "3.0.0",
5959
  "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -5977,6 +7332,24 @@
5977
  "url": "https://github.com/sponsors/sindresorhus"
5978
  }
5979
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5980
  "node_modules/styled-jsx": {
5981
  "version": "5.1.6",
5982
  "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
@@ -6026,6 +7399,16 @@
6026
  "url": "https://github.com/sponsors/ljharb"
6027
  }
6028
  },
 
 
 
 
 
 
 
 
 
 
6029
  "node_modules/tailwindcss": {
6030
  "version": "4.1.18",
6031
  "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
@@ -6109,6 +7492,26 @@
6109
  "node": ">=8.0"
6110
  }
6111
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6112
  "node_modules/ts-api-utils": {
6113
  "version": "2.1.0",
6114
  "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -6310,6 +7713,93 @@
6310
  "dev": true,
6311
  "license": "MIT"
6312
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6313
  "node_modules/unrs-resolver": {
6314
  "version": "1.11.1",
6315
  "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
@@ -6386,6 +7876,34 @@
6386
  "punycode": "^2.1.0"
6387
  }
6388
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6389
  "node_modules/which": {
6390
  "version": "2.0.2",
6391
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -6525,7 +8043,7 @@
6525
  "version": "4.2.1",
6526
  "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz",
6527
  "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
6528
- "dev": true,
6529
  "license": "MIT",
6530
  "peer": true,
6531
  "funding": {
@@ -6544,6 +8062,16 @@
6544
  "peerDependencies": {
6545
  "zod": "^3.25.0 || ^4.0.0"
6546
  }
 
 
 
 
 
 
 
 
 
 
6547
  }
6548
  }
6549
  }
 
8
  "name": "socratic-lens",
9
  "version": "0.1.0",
10
  "dependencies": {
11
+ "@google/generative-ai": "^0.24.1",
12
+ "clsx": "^2.1.1",
13
+ "lucide-react": "^0.562.0",
14
  "next": "16.1.1",
15
+ "openai": "^6.15.0",
16
  "react": "19.2.3",
17
+ "react-dom": "19.2.3",
18
+ "react-markdown": "^10.1.0",
19
+ "remark-gfm": "^4.0.1",
20
+ "tailwind-merge": "^3.4.0"
21
  },
22
  "devDependencies": {
23
  "@tailwindcss/postcss": "^4",
 
461
  "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
462
  }
463
  },
464
+ "node_modules/@google/generative-ai": {
465
+ "version": "0.24.1",
466
+ "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz",
467
+ "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==",
468
+ "license": "Apache-2.0",
469
+ "engines": {
470
+ "node": ">=18.0.0"
471
+ }
472
+ },
473
  "node_modules/@humanfs/core": {
474
  "version": "0.19.1",
475
  "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
 
1541
  "tslib": "^2.4.0"
1542
  }
1543
  },
1544
+ "node_modules/@types/debug": {
1545
+ "version": "4.1.12",
1546
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
1547
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
1548
+ "license": "MIT",
1549
+ "dependencies": {
1550
+ "@types/ms": "*"
1551
+ }
1552
+ },
1553
  "node_modules/@types/estree": {
1554
  "version": "1.0.8",
1555
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
1556
  "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
 
1557
  "license": "MIT"
1558
  },
1559
+ "node_modules/@types/estree-jsx": {
1560
+ "version": "1.0.5",
1561
+ "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
1562
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
1563
+ "license": "MIT",
1564
+ "dependencies": {
1565
+ "@types/estree": "*"
1566
+ }
1567
+ },
1568
+ "node_modules/@types/hast": {
1569
+ "version": "3.0.4",
1570
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
1571
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
1572
+ "license": "MIT",
1573
+ "dependencies": {
1574
+ "@types/unist": "*"
1575
+ }
1576
+ },
1577
  "node_modules/@types/json-schema": {
1578
  "version": "7.0.15",
1579
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
 
1588
  "dev": true,
1589
  "license": "MIT"
1590
  },
1591
+ "node_modules/@types/mdast": {
1592
+ "version": "4.0.4",
1593
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
1594
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
1595
+ "license": "MIT",
1596
+ "dependencies": {
1597
+ "@types/unist": "*"
1598
+ }
1599
+ },
1600
+ "node_modules/@types/ms": {
1601
+ "version": "2.1.0",
1602
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
1603
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
1604
+ "license": "MIT"
1605
+ },
1606
  "node_modules/@types/node": {
1607
  "version": "20.19.27",
1608
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz",
 
1617
  "version": "19.2.7",
1618
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
1619
  "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
 
1620
  "license": "MIT",
1621
  "peer": true,
1622
  "dependencies": {
 
1633
  "@types/react": "^19.2.0"
1634
  }
1635
  },
1636
+ "node_modules/@types/unist": {
1637
+ "version": "3.0.3",
1638
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
1639
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
1640
+ "license": "MIT"
1641
+ },
1642
  "node_modules/@typescript-eslint/eslint-plugin": {
1643
  "version": "8.50.1",
1644
  "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz",
 
1909
  "url": "https://opencollective.com/typescript-eslint"
1910
  }
1911
  },
1912
+ "node_modules/@ungap/structured-clone": {
1913
+ "version": "1.3.0",
1914
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
1915
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
1916
+ "license": "ISC"
1917
+ },
1918
  "node_modules/@unrs/resolver-binding-android-arm-eabi": {
1919
  "version": "1.11.1",
1920
  "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
 
2471
  "node": ">= 0.4"
2472
  }
2473
  },
2474
+ "node_modules/bail": {
2475
+ "version": "2.0.2",
2476
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
2477
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
2478
+ "license": "MIT",
2479
+ "funding": {
2480
+ "type": "github",
2481
+ "url": "https://github.com/sponsors/wooorm"
2482
+ }
2483
+ },
2484
  "node_modules/balanced-match": {
2485
  "version": "1.0.2",
2486
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
 
2636
  ],
2637
  "license": "CC-BY-4.0"
2638
  },
2639
+ "node_modules/ccount": {
2640
+ "version": "2.0.1",
2641
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
2642
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
2643
+ "license": "MIT",
2644
+ "funding": {
2645
+ "type": "github",
2646
+ "url": "https://github.com/sponsors/wooorm"
2647
+ }
2648
+ },
2649
  "node_modules/chalk": {
2650
  "version": "4.1.2",
2651
  "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 
2663
  "url": "https://github.com/chalk/chalk?sponsor=1"
2664
  }
2665
  },
2666
+ "node_modules/character-entities": {
2667
+ "version": "2.0.2",
2668
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
2669
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
2670
+ "license": "MIT",
2671
+ "funding": {
2672
+ "type": "github",
2673
+ "url": "https://github.com/sponsors/wooorm"
2674
+ }
2675
+ },
2676
+ "node_modules/character-entities-html4": {
2677
+ "version": "2.1.0",
2678
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
2679
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
2680
+ "license": "MIT",
2681
+ "funding": {
2682
+ "type": "github",
2683
+ "url": "https://github.com/sponsors/wooorm"
2684
+ }
2685
+ },
2686
+ "node_modules/character-entities-legacy": {
2687
+ "version": "3.0.0",
2688
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
2689
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
2690
+ "license": "MIT",
2691
+ "funding": {
2692
+ "type": "github",
2693
+ "url": "https://github.com/sponsors/wooorm"
2694
+ }
2695
+ },
2696
+ "node_modules/character-reference-invalid": {
2697
+ "version": "2.0.1",
2698
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
2699
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
2700
+ "license": "MIT",
2701
+ "funding": {
2702
+ "type": "github",
2703
+ "url": "https://github.com/sponsors/wooorm"
2704
+ }
2705
+ },
2706
  "node_modules/client-only": {
2707
  "version": "0.0.1",
2708
  "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
2709
  "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
2710
  "license": "MIT"
2711
  },
2712
+ "node_modules/clsx": {
2713
+ "version": "2.1.1",
2714
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
2715
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
2716
+ "license": "MIT",
2717
+ "engines": {
2718
+ "node": ">=6"
2719
+ }
2720
+ },
2721
  "node_modules/color-convert": {
2722
  "version": "2.0.1",
2723
  "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
 
2738
  "dev": true,
2739
  "license": "MIT"
2740
  },
2741
+ "node_modules/comma-separated-tokens": {
2742
+ "version": "2.0.3",
2743
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
2744
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
2745
+ "license": "MIT",
2746
+ "funding": {
2747
+ "type": "github",
2748
+ "url": "https://github.com/sponsors/wooorm"
2749
+ }
2750
+ },
2751
  "node_modules/concat-map": {
2752
  "version": "0.0.1",
2753
  "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 
2781
  "version": "3.2.3",
2782
  "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
2783
  "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
 
2784
  "license": "MIT"
2785
  },
2786
  "node_modules/damerau-levenshtein": {
 
2848
  "version": "4.4.3",
2849
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
2850
  "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
 
2851
  "license": "MIT",
2852
  "dependencies": {
2853
  "ms": "^2.1.3"
 
2861
  }
2862
  }
2863
  },
2864
+ "node_modules/decode-named-character-reference": {
2865
+ "version": "1.2.0",
2866
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
2867
+ "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
2868
+ "license": "MIT",
2869
+ "dependencies": {
2870
+ "character-entities": "^2.0.0"
2871
+ },
2872
+ "funding": {
2873
+ "type": "github",
2874
+ "url": "https://github.com/sponsors/wooorm"
2875
+ }
2876
+ },
2877
  "node_modules/deep-is": {
2878
  "version": "0.1.4",
2879
  "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
 
2917
  "url": "https://github.com/sponsors/ljharb"
2918
  }
2919
  },
2920
+ "node_modules/dequal": {
2921
+ "version": "2.0.3",
2922
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
2923
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
2924
+ "license": "MIT",
2925
+ "engines": {
2926
+ "node": ">=6"
2927
+ }
2928
+ },
2929
  "node_modules/detect-libc": {
2930
  "version": "2.1.2",
2931
  "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
 
2936
  "node": ">=8"
2937
  }
2938
  },
2939
+ "node_modules/devlop": {
2940
+ "version": "1.1.0",
2941
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
2942
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
2943
+ "license": "MIT",
2944
+ "dependencies": {
2945
+ "dequal": "^2.0.0"
2946
+ },
2947
+ "funding": {
2948
+ "type": "github",
2949
+ "url": "https://github.com/sponsors/wooorm"
2950
+ }
2951
+ },
2952
  "node_modules/doctrine": {
2953
  "version": "2.1.0",
2954
  "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
 
3397
  "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
3398
  "dev": true,
3399
  "license": "MIT",
3400
+ "peer": true,
3401
  "dependencies": {
3402
  "@rtsao/scc": "^1.1.0",
3403
  "array-includes": "^3.1.9",
 
3621
  "node": ">=4.0"
3622
  }
3623
  },
3624
+ "node_modules/estree-util-is-identifier-name": {
3625
+ "version": "3.0.0",
3626
+ "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
3627
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
3628
+ "license": "MIT",
3629
+ "funding": {
3630
+ "type": "opencollective",
3631
+ "url": "https://opencollective.com/unified"
3632
+ }
3633
+ },
3634
  "node_modules/esutils": {
3635
  "version": "2.0.3",
3636
  "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
 
3641
  "node": ">=0.10.0"
3642
  }
3643
  },
3644
+ "node_modules/extend": {
3645
+ "version": "3.0.2",
3646
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
3647
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
3648
+ "license": "MIT"
3649
+ },
3650
  "node_modules/fast-deep-equal": {
3651
  "version": "3.1.3",
3652
  "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 
4076
  "node": ">= 0.4"
4077
  }
4078
  },
4079
+ "node_modules/hast-util-to-jsx-runtime": {
4080
+ "version": "2.3.6",
4081
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
4082
+ "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
4083
+ "license": "MIT",
4084
+ "dependencies": {
4085
+ "@types/estree": "^1.0.0",
4086
+ "@types/hast": "^3.0.0",
4087
+ "@types/unist": "^3.0.0",
4088
+ "comma-separated-tokens": "^2.0.0",
4089
+ "devlop": "^1.0.0",
4090
+ "estree-util-is-identifier-name": "^3.0.0",
4091
+ "hast-util-whitespace": "^3.0.0",
4092
+ "mdast-util-mdx-expression": "^2.0.0",
4093
+ "mdast-util-mdx-jsx": "^3.0.0",
4094
+ "mdast-util-mdxjs-esm": "^2.0.0",
4095
+ "property-information": "^7.0.0",
4096
+ "space-separated-tokens": "^2.0.0",
4097
+ "style-to-js": "^1.0.0",
4098
+ "unist-util-position": "^5.0.0",
4099
+ "vfile-message": "^4.0.0"
4100
+ },
4101
+ "funding": {
4102
+ "type": "opencollective",
4103
+ "url": "https://opencollective.com/unified"
4104
+ }
4105
+ },
4106
+ "node_modules/hast-util-whitespace": {
4107
+ "version": "3.0.0",
4108
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
4109
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
4110
+ "license": "MIT",
4111
+ "dependencies": {
4112
+ "@types/hast": "^3.0.0"
4113
+ },
4114
+ "funding": {
4115
+ "type": "opencollective",
4116
+ "url": "https://opencollective.com/unified"
4117
+ }
4118
+ },
4119
  "node_modules/hermes-estree": {
4120
  "version": "0.25.1",
4121
  "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
 
4133
  "hermes-estree": "0.25.1"
4134
  }
4135
  },
4136
+ "node_modules/html-url-attributes": {
4137
+ "version": "3.0.1",
4138
+ "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
4139
+ "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
4140
+ "license": "MIT",
4141
+ "funding": {
4142
+ "type": "opencollective",
4143
+ "url": "https://opencollective.com/unified"
4144
+ }
4145
+ },
4146
  "node_modules/ignore": {
4147
  "version": "5.3.2",
4148
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
 
4180
  "node": ">=0.8.19"
4181
  }
4182
  },
4183
+ "node_modules/inline-style-parser": {
4184
+ "version": "0.2.7",
4185
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
4186
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
4187
+ "license": "MIT"
4188
+ },
4189
  "node_modules/internal-slot": {
4190
  "version": "1.1.0",
4191
  "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
 
4201
  "node": ">= 0.4"
4202
  }
4203
  },
4204
+ "node_modules/is-alphabetical": {
4205
+ "version": "2.0.1",
4206
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
4207
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
4208
+ "license": "MIT",
4209
+ "funding": {
4210
+ "type": "github",
4211
+ "url": "https://github.com/sponsors/wooorm"
4212
+ }
4213
+ },
4214
+ "node_modules/is-alphanumerical": {
4215
+ "version": "2.0.1",
4216
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
4217
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
4218
+ "license": "MIT",
4219
+ "dependencies": {
4220
+ "is-alphabetical": "^2.0.0",
4221
+ "is-decimal": "^2.0.0"
4222
+ },
4223
+ "funding": {
4224
+ "type": "github",
4225
+ "url": "https://github.com/sponsors/wooorm"
4226
+ }
4227
+ },
4228
  "node_modules/is-array-buffer": {
4229
  "version": "3.0.5",
4230
  "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
 
4383
  "url": "https://github.com/sponsors/ljharb"
4384
  }
4385
  },
4386
+ "node_modules/is-decimal": {
4387
+ "version": "2.0.1",
4388
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
4389
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
4390
+ "license": "MIT",
4391
+ "funding": {
4392
+ "type": "github",
4393
+ "url": "https://github.com/sponsors/wooorm"
4394
+ }
4395
+ },
4396
  "node_modules/is-extglob": {
4397
  "version": "2.1.1",
4398
  "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
 
4452
  "node": ">=0.10.0"
4453
  }
4454
  },
4455
+ "node_modules/is-hexadecimal": {
4456
+ "version": "2.0.1",
4457
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
4458
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
4459
+ "license": "MIT",
4460
+ "funding": {
4461
+ "type": "github",
4462
+ "url": "https://github.com/sponsors/wooorm"
4463
+ }
4464
+ },
4465
  "node_modules/is-map": {
4466
  "version": "2.0.3",
4467
  "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
 
4515
  "url": "https://github.com/sponsors/ljharb"
4516
  }
4517
  },
4518
+ "node_modules/is-plain-obj": {
4519
+ "version": "4.1.0",
4520
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
4521
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
4522
+ "license": "MIT",
4523
+ "engines": {
4524
+ "node": ">=12"
4525
+ },
4526
+ "funding": {
4527
+ "url": "https://github.com/sponsors/sindresorhus"
4528
+ }
4529
+ },
4530
  "node_modules/is-regex": {
4531
  "version": "1.2.1",
4532
  "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
 
5125
  "dev": true,
5126
  "license": "MIT"
5127
  },
5128
+ "node_modules/longest-streak": {
5129
+ "version": "3.1.0",
5130
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
5131
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
5132
+ "license": "MIT",
5133
+ "funding": {
5134
+ "type": "github",
5135
+ "url": "https://github.com/sponsors/wooorm"
5136
+ }
5137
+ },
5138
  "node_modules/loose-envify": {
5139
  "version": "1.4.0",
5140
  "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
 
5158
  "yallist": "^3.0.2"
5159
  }
5160
  },
5161
+ "node_modules/lucide-react": {
5162
+ "version": "0.562.0",
5163
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz",
5164
+ "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==",
5165
+ "license": "ISC",
5166
+ "peerDependencies": {
5167
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
5168
+ }
5169
+ },
5170
  "node_modules/magic-string": {
5171
  "version": "0.30.21",
5172
  "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
 
5177
  "@jridgewell/sourcemap-codec": "^1.5.5"
5178
  }
5179
  },
5180
+ "node_modules/markdown-table": {
5181
+ "version": "3.0.4",
5182
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
5183
+ "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
5184
+ "license": "MIT",
5185
+ "funding": {
5186
+ "type": "github",
5187
+ "url": "https://github.com/sponsors/wooorm"
5188
+ }
5189
+ },
5190
  "node_modules/math-intrinsics": {
5191
  "version": "1.1.0",
5192
  "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
 
5197
  "node": ">= 0.4"
5198
  }
5199
  },
5200
+ "node_modules/mdast-util-find-and-replace": {
5201
+ "version": "3.0.2",
5202
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
5203
+ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
5204
+ "license": "MIT",
5205
+ "dependencies": {
5206
+ "@types/mdast": "^4.0.0",
5207
+ "escape-string-regexp": "^5.0.0",
5208
+ "unist-util-is": "^6.0.0",
5209
+ "unist-util-visit-parents": "^6.0.0"
5210
+ },
5211
+ "funding": {
5212
+ "type": "opencollective",
5213
+ "url": "https://opencollective.com/unified"
5214
+ }
5215
+ },
5216
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
5217
+ "version": "5.0.0",
5218
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
5219
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
5220
  "license": "MIT",
5221
  "engines": {
5222
+ "node": ">=12"
5223
+ },
5224
+ "funding": {
5225
+ "url": "https://github.com/sponsors/sindresorhus"
5226
  }
5227
  },
5228
+ "node_modules/mdast-util-from-markdown": {
5229
+ "version": "2.0.2",
5230
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
5231
+ "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
 
5232
  "license": "MIT",
5233
  "dependencies": {
5234
+ "@types/mdast": "^4.0.0",
5235
+ "@types/unist": "^3.0.0",
5236
+ "decode-named-character-reference": "^1.0.0",
5237
+ "devlop": "^1.0.0",
5238
+ "mdast-util-to-string": "^4.0.0",
5239
+ "micromark": "^4.0.0",
5240
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
5241
+ "micromark-util-decode-string": "^2.0.0",
5242
+ "micromark-util-normalize-identifier": "^2.0.0",
5243
+ "micromark-util-symbol": "^2.0.0",
5244
+ "micromark-util-types": "^2.0.0",
5245
+ "unist-util-stringify-position": "^4.0.0"
5246
  },
5247
+ "funding": {
5248
+ "type": "opencollective",
5249
+ "url": "https://opencollective.com/unified"
5250
  }
5251
  },
5252
+ "node_modules/mdast-util-gfm": {
5253
+ "version": "3.1.0",
5254
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
5255
+ "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
5256
+ "license": "MIT",
 
5257
  "dependencies": {
5258
+ "mdast-util-from-markdown": "^2.0.0",
5259
+ "mdast-util-gfm-autolink-literal": "^2.0.0",
5260
+ "mdast-util-gfm-footnote": "^2.0.0",
5261
+ "mdast-util-gfm-strikethrough": "^2.0.0",
5262
+ "mdast-util-gfm-table": "^2.0.0",
5263
+ "mdast-util-gfm-task-list-item": "^2.0.0",
5264
+ "mdast-util-to-markdown": "^2.0.0"
5265
  },
5266
+ "funding": {
5267
+ "type": "opencollective",
5268
+ "url": "https://opencollective.com/unified"
5269
  }
5270
  },
5271
+ "node_modules/mdast-util-gfm-autolink-literal": {
5272
+ "version": "2.0.1",
5273
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
5274
+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
 
5275
  "license": "MIT",
5276
+ "dependencies": {
5277
+ "@types/mdast": "^4.0.0",
5278
+ "ccount": "^2.0.0",
5279
+ "devlop": "^1.0.0",
5280
+ "mdast-util-find-and-replace": "^3.0.0",
5281
+ "micromark-util-character": "^2.0.0"
5282
+ },
5283
  "funding": {
5284
+ "type": "opencollective",
5285
+ "url": "https://opencollective.com/unified"
5286
  }
5287
  },
5288
+ "node_modules/mdast-util-gfm-footnote": {
5289
+ "version": "2.1.0",
5290
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
5291
+ "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
5292
+ "license": "MIT",
5293
+ "dependencies": {
5294
+ "@types/mdast": "^4.0.0",
5295
+ "devlop": "^1.1.0",
5296
+ "mdast-util-from-markdown": "^2.0.0",
5297
+ "mdast-util-to-markdown": "^2.0.0",
5298
+ "micromark-util-normalize-identifier": "^2.0.0"
5299
+ },
5300
+ "funding": {
5301
+ "type": "opencollective",
5302
+ "url": "https://opencollective.com/unified"
5303
+ }
5304
  },
5305
+ "node_modules/mdast-util-gfm-strikethrough": {
5306
+ "version": "2.0.0",
5307
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
5308
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
5309
+ "license": "MIT",
5310
+ "dependencies": {
5311
+ "@types/mdast": "^4.0.0",
5312
+ "mdast-util-from-markdown": "^2.0.0",
5313
+ "mdast-util-to-markdown": "^2.0.0"
5314
+ },
5315
+ "funding": {
5316
+ "type": "opencollective",
5317
+ "url": "https://opencollective.com/unified"
5318
+ }
5319
+ },
5320
+ "node_modules/mdast-util-gfm-table": {
5321
+ "version": "2.0.0",
5322
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
5323
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
5324
+ "license": "MIT",
5325
+ "dependencies": {
5326
+ "@types/mdast": "^4.0.0",
5327
+ "devlop": "^1.0.0",
5328
+ "markdown-table": "^3.0.0",
5329
+ "mdast-util-from-markdown": "^2.0.0",
5330
+ "mdast-util-to-markdown": "^2.0.0"
5331
+ },
5332
+ "funding": {
5333
+ "type": "opencollective",
5334
+ "url": "https://opencollective.com/unified"
5335
+ }
5336
+ },
5337
+ "node_modules/mdast-util-gfm-task-list-item": {
5338
+ "version": "2.0.0",
5339
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
5340
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
5341
+ "license": "MIT",
5342
+ "dependencies": {
5343
+ "@types/mdast": "^4.0.0",
5344
+ "devlop": "^1.0.0",
5345
+ "mdast-util-from-markdown": "^2.0.0",
5346
+ "mdast-util-to-markdown": "^2.0.0"
5347
+ },
5348
+ "funding": {
5349
+ "type": "opencollective",
5350
+ "url": "https://opencollective.com/unified"
5351
+ }
5352
+ },
5353
+ "node_modules/mdast-util-mdx-expression": {
5354
+ "version": "2.0.1",
5355
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
5356
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
5357
+ "license": "MIT",
5358
+ "dependencies": {
5359
+ "@types/estree-jsx": "^1.0.0",
5360
+ "@types/hast": "^3.0.0",
5361
+ "@types/mdast": "^4.0.0",
5362
+ "devlop": "^1.0.0",
5363
+ "mdast-util-from-markdown": "^2.0.0",
5364
+ "mdast-util-to-markdown": "^2.0.0"
5365
+ },
5366
+ "funding": {
5367
+ "type": "opencollective",
5368
+ "url": "https://opencollective.com/unified"
5369
+ }
5370
+ },
5371
+ "node_modules/mdast-util-mdx-jsx": {
5372
+ "version": "3.2.0",
5373
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
5374
+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
5375
+ "license": "MIT",
5376
+ "dependencies": {
5377
+ "@types/estree-jsx": "^1.0.0",
5378
+ "@types/hast": "^3.0.0",
5379
+ "@types/mdast": "^4.0.0",
5380
+ "@types/unist": "^3.0.0",
5381
+ "ccount": "^2.0.0",
5382
+ "devlop": "^1.1.0",
5383
+ "mdast-util-from-markdown": "^2.0.0",
5384
+ "mdast-util-to-markdown": "^2.0.0",
5385
+ "parse-entities": "^4.0.0",
5386
+ "stringify-entities": "^4.0.0",
5387
+ "unist-util-stringify-position": "^4.0.0",
5388
+ "vfile-message": "^4.0.0"
5389
+ },
5390
+ "funding": {
5391
+ "type": "opencollective",
5392
+ "url": "https://opencollective.com/unified"
5393
+ }
5394
+ },
5395
+ "node_modules/mdast-util-mdxjs-esm": {
5396
+ "version": "2.0.1",
5397
+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
5398
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
5399
+ "license": "MIT",
5400
+ "dependencies": {
5401
+ "@types/estree-jsx": "^1.0.0",
5402
+ "@types/hast": "^3.0.0",
5403
+ "@types/mdast": "^4.0.0",
5404
+ "devlop": "^1.0.0",
5405
+ "mdast-util-from-markdown": "^2.0.0",
5406
+ "mdast-util-to-markdown": "^2.0.0"
5407
+ },
5408
+ "funding": {
5409
+ "type": "opencollective",
5410
+ "url": "https://opencollective.com/unified"
5411
+ }
5412
+ },
5413
+ "node_modules/mdast-util-phrasing": {
5414
+ "version": "4.1.0",
5415
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
5416
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
5417
+ "license": "MIT",
5418
+ "dependencies": {
5419
+ "@types/mdast": "^4.0.0",
5420
+ "unist-util-is": "^6.0.0"
5421
+ },
5422
+ "funding": {
5423
+ "type": "opencollective",
5424
+ "url": "https://opencollective.com/unified"
5425
+ }
5426
+ },
5427
+ "node_modules/mdast-util-to-hast": {
5428
+ "version": "13.2.1",
5429
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
5430
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
5431
+ "license": "MIT",
5432
+ "dependencies": {
5433
+ "@types/hast": "^3.0.0",
5434
+ "@types/mdast": "^4.0.0",
5435
+ "@ungap/structured-clone": "^1.0.0",
5436
+ "devlop": "^1.0.0",
5437
+ "micromark-util-sanitize-uri": "^2.0.0",
5438
+ "trim-lines": "^3.0.0",
5439
+ "unist-util-position": "^5.0.0",
5440
+ "unist-util-visit": "^5.0.0",
5441
+ "vfile": "^6.0.0"
5442
+ },
5443
+ "funding": {
5444
+ "type": "opencollective",
5445
+ "url": "https://opencollective.com/unified"
5446
+ }
5447
+ },
5448
+ "node_modules/mdast-util-to-markdown": {
5449
+ "version": "2.1.2",
5450
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
5451
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
5452
+ "license": "MIT",
5453
+ "dependencies": {
5454
+ "@types/mdast": "^4.0.0",
5455
+ "@types/unist": "^3.0.0",
5456
+ "longest-streak": "^3.0.0",
5457
+ "mdast-util-phrasing": "^4.0.0",
5458
+ "mdast-util-to-string": "^4.0.0",
5459
+ "micromark-util-classify-character": "^2.0.0",
5460
+ "micromark-util-decode-string": "^2.0.0",
5461
+ "unist-util-visit": "^5.0.0",
5462
+ "zwitch": "^2.0.0"
5463
+ },
5464
+ "funding": {
5465
+ "type": "opencollective",
5466
+ "url": "https://opencollective.com/unified"
5467
+ }
5468
+ },
5469
+ "node_modules/mdast-util-to-string": {
5470
+ "version": "4.0.0",
5471
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
5472
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
5473
+ "license": "MIT",
5474
+ "dependencies": {
5475
+ "@types/mdast": "^4.0.0"
5476
+ },
5477
+ "funding": {
5478
+ "type": "opencollective",
5479
+ "url": "https://opencollective.com/unified"
5480
+ }
5481
+ },
5482
+ "node_modules/merge2": {
5483
+ "version": "1.4.1",
5484
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
5485
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
5486
+ "dev": true,
5487
+ "license": "MIT",
5488
+ "engines": {
5489
+ "node": ">= 8"
5490
+ }
5491
+ },
5492
+ "node_modules/micromark": {
5493
+ "version": "4.0.2",
5494
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
5495
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
5496
+ "funding": [
5497
+ {
5498
+ "type": "GitHub Sponsors",
5499
+ "url": "https://github.com/sponsors/unifiedjs"
5500
+ },
5501
+ {
5502
+ "type": "OpenCollective",
5503
+ "url": "https://opencollective.com/unified"
5504
+ }
5505
+ ],
5506
+ "license": "MIT",
5507
+ "dependencies": {
5508
+ "@types/debug": "^4.0.0",
5509
+ "debug": "^4.0.0",
5510
+ "decode-named-character-reference": "^1.0.0",
5511
+ "devlop": "^1.0.0",
5512
+ "micromark-core-commonmark": "^2.0.0",
5513
+ "micromark-factory-space": "^2.0.0",
5514
+ "micromark-util-character": "^2.0.0",
5515
+ "micromark-util-chunked": "^2.0.0",
5516
+ "micromark-util-combine-extensions": "^2.0.0",
5517
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
5518
+ "micromark-util-encode": "^2.0.0",
5519
+ "micromark-util-normalize-identifier": "^2.0.0",
5520
+ "micromark-util-resolve-all": "^2.0.0",
5521
+ "micromark-util-sanitize-uri": "^2.0.0",
5522
+ "micromark-util-subtokenize": "^2.0.0",
5523
+ "micromark-util-symbol": "^2.0.0",
5524
+ "micromark-util-types": "^2.0.0"
5525
+ }
5526
+ },
5527
+ "node_modules/micromark-core-commonmark": {
5528
+ "version": "2.0.3",
5529
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
5530
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
5531
+ "funding": [
5532
+ {
5533
+ "type": "GitHub Sponsors",
5534
+ "url": "https://github.com/sponsors/unifiedjs"
5535
+ },
5536
+ {
5537
+ "type": "OpenCollective",
5538
+ "url": "https://opencollective.com/unified"
5539
+ }
5540
+ ],
5541
+ "license": "MIT",
5542
+ "dependencies": {
5543
+ "decode-named-character-reference": "^1.0.0",
5544
+ "devlop": "^1.0.0",
5545
+ "micromark-factory-destination": "^2.0.0",
5546
+ "micromark-factory-label": "^2.0.0",
5547
+ "micromark-factory-space": "^2.0.0",
5548
+ "micromark-factory-title": "^2.0.0",
5549
+ "micromark-factory-whitespace": "^2.0.0",
5550
+ "micromark-util-character": "^2.0.0",
5551
+ "micromark-util-chunked": "^2.0.0",
5552
+ "micromark-util-classify-character": "^2.0.0",
5553
+ "micromark-util-html-tag-name": "^2.0.0",
5554
+ "micromark-util-normalize-identifier": "^2.0.0",
5555
+ "micromark-util-resolve-all": "^2.0.0",
5556
+ "micromark-util-subtokenize": "^2.0.0",
5557
+ "micromark-util-symbol": "^2.0.0",
5558
+ "micromark-util-types": "^2.0.0"
5559
+ }
5560
+ },
5561
+ "node_modules/micromark-extension-gfm": {
5562
+ "version": "3.0.0",
5563
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
5564
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
5565
+ "license": "MIT",
5566
+ "dependencies": {
5567
+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
5568
+ "micromark-extension-gfm-footnote": "^2.0.0",
5569
+ "micromark-extension-gfm-strikethrough": "^2.0.0",
5570
+ "micromark-extension-gfm-table": "^2.0.0",
5571
+ "micromark-extension-gfm-tagfilter": "^2.0.0",
5572
+ "micromark-extension-gfm-task-list-item": "^2.0.0",
5573
+ "micromark-util-combine-extensions": "^2.0.0",
5574
+ "micromark-util-types": "^2.0.0"
5575
+ },
5576
+ "funding": {
5577
+ "type": "opencollective",
5578
+ "url": "https://opencollective.com/unified"
5579
+ }
5580
+ },
5581
+ "node_modules/micromark-extension-gfm-autolink-literal": {
5582
+ "version": "2.1.0",
5583
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
5584
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
5585
+ "license": "MIT",
5586
+ "dependencies": {
5587
+ "micromark-util-character": "^2.0.0",
5588
+ "micromark-util-sanitize-uri": "^2.0.0",
5589
+ "micromark-util-symbol": "^2.0.0",
5590
+ "micromark-util-types": "^2.0.0"
5591
+ },
5592
+ "funding": {
5593
+ "type": "opencollective",
5594
+ "url": "https://opencollective.com/unified"
5595
+ }
5596
+ },
5597
+ "node_modules/micromark-extension-gfm-footnote": {
5598
+ "version": "2.1.0",
5599
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
5600
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
5601
+ "license": "MIT",
5602
+ "dependencies": {
5603
+ "devlop": "^1.0.0",
5604
+ "micromark-core-commonmark": "^2.0.0",
5605
+ "micromark-factory-space": "^2.0.0",
5606
+ "micromark-util-character": "^2.0.0",
5607
+ "micromark-util-normalize-identifier": "^2.0.0",
5608
+ "micromark-util-sanitize-uri": "^2.0.0",
5609
+ "micromark-util-symbol": "^2.0.0",
5610
+ "micromark-util-types": "^2.0.0"
5611
+ },
5612
+ "funding": {
5613
+ "type": "opencollective",
5614
+ "url": "https://opencollective.com/unified"
5615
+ }
5616
+ },
5617
+ "node_modules/micromark-extension-gfm-strikethrough": {
5618
+ "version": "2.1.0",
5619
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
5620
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
5621
+ "license": "MIT",
5622
+ "dependencies": {
5623
+ "devlop": "^1.0.0",
5624
+ "micromark-util-chunked": "^2.0.0",
5625
+ "micromark-util-classify-character": "^2.0.0",
5626
+ "micromark-util-resolve-all": "^2.0.0",
5627
+ "micromark-util-symbol": "^2.0.0",
5628
+ "micromark-util-types": "^2.0.0"
5629
+ },
5630
+ "funding": {
5631
+ "type": "opencollective",
5632
+ "url": "https://opencollective.com/unified"
5633
+ }
5634
+ },
5635
+ "node_modules/micromark-extension-gfm-table": {
5636
+ "version": "2.1.1",
5637
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
5638
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
5639
+ "license": "MIT",
5640
+ "dependencies": {
5641
+ "devlop": "^1.0.0",
5642
+ "micromark-factory-space": "^2.0.0",
5643
+ "micromark-util-character": "^2.0.0",
5644
+ "micromark-util-symbol": "^2.0.0",
5645
+ "micromark-util-types": "^2.0.0"
5646
+ },
5647
+ "funding": {
5648
+ "type": "opencollective",
5649
+ "url": "https://opencollective.com/unified"
5650
+ }
5651
+ },
5652
+ "node_modules/micromark-extension-gfm-tagfilter": {
5653
+ "version": "2.0.0",
5654
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
5655
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
5656
+ "license": "MIT",
5657
+ "dependencies": {
5658
+ "micromark-util-types": "^2.0.0"
5659
+ },
5660
+ "funding": {
5661
+ "type": "opencollective",
5662
+ "url": "https://opencollective.com/unified"
5663
+ }
5664
+ },
5665
+ "node_modules/micromark-extension-gfm-task-list-item": {
5666
+ "version": "2.1.0",
5667
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
5668
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
5669
+ "license": "MIT",
5670
+ "dependencies": {
5671
+ "devlop": "^1.0.0",
5672
+ "micromark-factory-space": "^2.0.0",
5673
+ "micromark-util-character": "^2.0.0",
5674
+ "micromark-util-symbol": "^2.0.0",
5675
+ "micromark-util-types": "^2.0.0"
5676
+ },
5677
+ "funding": {
5678
+ "type": "opencollective",
5679
+ "url": "https://opencollective.com/unified"
5680
+ }
5681
+ },
5682
+ "node_modules/micromark-factory-destination": {
5683
+ "version": "2.0.1",
5684
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
5685
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
5686
+ "funding": [
5687
+ {
5688
+ "type": "GitHub Sponsors",
5689
+ "url": "https://github.com/sponsors/unifiedjs"
5690
+ },
5691
+ {
5692
+ "type": "OpenCollective",
5693
+ "url": "https://opencollective.com/unified"
5694
+ }
5695
+ ],
5696
+ "license": "MIT",
5697
+ "dependencies": {
5698
+ "micromark-util-character": "^2.0.0",
5699
+ "micromark-util-symbol": "^2.0.0",
5700
+ "micromark-util-types": "^2.0.0"
5701
+ }
5702
+ },
5703
+ "node_modules/micromark-factory-label": {
5704
+ "version": "2.0.1",
5705
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
5706
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
5707
+ "funding": [
5708
+ {
5709
+ "type": "GitHub Sponsors",
5710
+ "url": "https://github.com/sponsors/unifiedjs"
5711
+ },
5712
+ {
5713
+ "type": "OpenCollective",
5714
+ "url": "https://opencollective.com/unified"
5715
+ }
5716
+ ],
5717
+ "license": "MIT",
5718
+ "dependencies": {
5719
+ "devlop": "^1.0.0",
5720
+ "micromark-util-character": "^2.0.0",
5721
+ "micromark-util-symbol": "^2.0.0",
5722
+ "micromark-util-types": "^2.0.0"
5723
+ }
5724
+ },
5725
+ "node_modules/micromark-factory-space": {
5726
+ "version": "2.0.1",
5727
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
5728
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
5729
+ "funding": [
5730
+ {
5731
+ "type": "GitHub Sponsors",
5732
+ "url": "https://github.com/sponsors/unifiedjs"
5733
+ },
5734
+ {
5735
+ "type": "OpenCollective",
5736
+ "url": "https://opencollective.com/unified"
5737
+ }
5738
+ ],
5739
+ "license": "MIT",
5740
+ "dependencies": {
5741
+ "micromark-util-character": "^2.0.0",
5742
+ "micromark-util-types": "^2.0.0"
5743
+ }
5744
+ },
5745
+ "node_modules/micromark-factory-title": {
5746
+ "version": "2.0.1",
5747
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
5748
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
5749
+ "funding": [
5750
+ {
5751
+ "type": "GitHub Sponsors",
5752
+ "url": "https://github.com/sponsors/unifiedjs"
5753
+ },
5754
+ {
5755
+ "type": "OpenCollective",
5756
+ "url": "https://opencollective.com/unified"
5757
+ }
5758
+ ],
5759
+ "license": "MIT",
5760
+ "dependencies": {
5761
+ "micromark-factory-space": "^2.0.0",
5762
+ "micromark-util-character": "^2.0.0",
5763
+ "micromark-util-symbol": "^2.0.0",
5764
+ "micromark-util-types": "^2.0.0"
5765
+ }
5766
+ },
5767
+ "node_modules/micromark-factory-whitespace": {
5768
+ "version": "2.0.1",
5769
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
5770
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
5771
+ "funding": [
5772
+ {
5773
+ "type": "GitHub Sponsors",
5774
+ "url": "https://github.com/sponsors/unifiedjs"
5775
+ },
5776
+ {
5777
+ "type": "OpenCollective",
5778
+ "url": "https://opencollective.com/unified"
5779
+ }
5780
+ ],
5781
+ "license": "MIT",
5782
+ "dependencies": {
5783
+ "micromark-factory-space": "^2.0.0",
5784
+ "micromark-util-character": "^2.0.0",
5785
+ "micromark-util-symbol": "^2.0.0",
5786
+ "micromark-util-types": "^2.0.0"
5787
+ }
5788
+ },
5789
+ "node_modules/micromark-util-character": {
5790
+ "version": "2.1.1",
5791
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
5792
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
5793
+ "funding": [
5794
+ {
5795
+ "type": "GitHub Sponsors",
5796
+ "url": "https://github.com/sponsors/unifiedjs"
5797
+ },
5798
+ {
5799
+ "type": "OpenCollective",
5800
+ "url": "https://opencollective.com/unified"
5801
+ }
5802
+ ],
5803
+ "license": "MIT",
5804
+ "dependencies": {
5805
+ "micromark-util-symbol": "^2.0.0",
5806
+ "micromark-util-types": "^2.0.0"
5807
+ }
5808
+ },
5809
+ "node_modules/micromark-util-chunked": {
5810
+ "version": "2.0.1",
5811
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
5812
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
5813
+ "funding": [
5814
+ {
5815
+ "type": "GitHub Sponsors",
5816
+ "url": "https://github.com/sponsors/unifiedjs"
5817
+ },
5818
+ {
5819
+ "type": "OpenCollective",
5820
+ "url": "https://opencollective.com/unified"
5821
+ }
5822
+ ],
5823
+ "license": "MIT",
5824
+ "dependencies": {
5825
+ "micromark-util-symbol": "^2.0.0"
5826
+ }
5827
+ },
5828
+ "node_modules/micromark-util-classify-character": {
5829
+ "version": "2.0.1",
5830
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
5831
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
5832
+ "funding": [
5833
+ {
5834
+ "type": "GitHub Sponsors",
5835
+ "url": "https://github.com/sponsors/unifiedjs"
5836
+ },
5837
+ {
5838
+ "type": "OpenCollective",
5839
+ "url": "https://opencollective.com/unified"
5840
+ }
5841
+ ],
5842
+ "license": "MIT",
5843
+ "dependencies": {
5844
+ "micromark-util-character": "^2.0.0",
5845
+ "micromark-util-symbol": "^2.0.0",
5846
+ "micromark-util-types": "^2.0.0"
5847
+ }
5848
+ },
5849
+ "node_modules/micromark-util-combine-extensions": {
5850
+ "version": "2.0.1",
5851
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
5852
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
5853
+ "funding": [
5854
+ {
5855
+ "type": "GitHub Sponsors",
5856
+ "url": "https://github.com/sponsors/unifiedjs"
5857
+ },
5858
+ {
5859
+ "type": "OpenCollective",
5860
+ "url": "https://opencollective.com/unified"
5861
+ }
5862
+ ],
5863
+ "license": "MIT",
5864
+ "dependencies": {
5865
+ "micromark-util-chunked": "^2.0.0",
5866
+ "micromark-util-types": "^2.0.0"
5867
+ }
5868
+ },
5869
+ "node_modules/micromark-util-decode-numeric-character-reference": {
5870
+ "version": "2.0.2",
5871
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
5872
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
5873
+ "funding": [
5874
+ {
5875
+ "type": "GitHub Sponsors",
5876
+ "url": "https://github.com/sponsors/unifiedjs"
5877
+ },
5878
+ {
5879
+ "type": "OpenCollective",
5880
+ "url": "https://opencollective.com/unified"
5881
+ }
5882
+ ],
5883
+ "license": "MIT",
5884
+ "dependencies": {
5885
+ "micromark-util-symbol": "^2.0.0"
5886
+ }
5887
+ },
5888
+ "node_modules/micromark-util-decode-string": {
5889
+ "version": "2.0.1",
5890
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
5891
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
5892
+ "funding": [
5893
+ {
5894
+ "type": "GitHub Sponsors",
5895
+ "url": "https://github.com/sponsors/unifiedjs"
5896
+ },
5897
+ {
5898
+ "type": "OpenCollective",
5899
+ "url": "https://opencollective.com/unified"
5900
+ }
5901
+ ],
5902
+ "license": "MIT",
5903
+ "dependencies": {
5904
+ "decode-named-character-reference": "^1.0.0",
5905
+ "micromark-util-character": "^2.0.0",
5906
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
5907
+ "micromark-util-symbol": "^2.0.0"
5908
+ }
5909
+ },
5910
+ "node_modules/micromark-util-encode": {
5911
+ "version": "2.0.1",
5912
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
5913
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
5914
+ "funding": [
5915
+ {
5916
+ "type": "GitHub Sponsors",
5917
+ "url": "https://github.com/sponsors/unifiedjs"
5918
+ },
5919
+ {
5920
+ "type": "OpenCollective",
5921
+ "url": "https://opencollective.com/unified"
5922
+ }
5923
+ ],
5924
+ "license": "MIT"
5925
+ },
5926
+ "node_modules/micromark-util-html-tag-name": {
5927
+ "version": "2.0.1",
5928
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
5929
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
5930
+ "funding": [
5931
+ {
5932
+ "type": "GitHub Sponsors",
5933
+ "url": "https://github.com/sponsors/unifiedjs"
5934
+ },
5935
+ {
5936
+ "type": "OpenCollective",
5937
+ "url": "https://opencollective.com/unified"
5938
+ }
5939
+ ],
5940
+ "license": "MIT"
5941
+ },
5942
+ "node_modules/micromark-util-normalize-identifier": {
5943
+ "version": "2.0.1",
5944
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
5945
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
5946
+ "funding": [
5947
+ {
5948
+ "type": "GitHub Sponsors",
5949
+ "url": "https://github.com/sponsors/unifiedjs"
5950
+ },
5951
+ {
5952
+ "type": "OpenCollective",
5953
+ "url": "https://opencollective.com/unified"
5954
+ }
5955
+ ],
5956
+ "license": "MIT",
5957
+ "dependencies": {
5958
+ "micromark-util-symbol": "^2.0.0"
5959
+ }
5960
+ },
5961
+ "node_modules/micromark-util-resolve-all": {
5962
+ "version": "2.0.1",
5963
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
5964
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
5965
+ "funding": [
5966
+ {
5967
+ "type": "GitHub Sponsors",
5968
+ "url": "https://github.com/sponsors/unifiedjs"
5969
+ },
5970
+ {
5971
+ "type": "OpenCollective",
5972
+ "url": "https://opencollective.com/unified"
5973
+ }
5974
+ ],
5975
+ "license": "MIT",
5976
+ "dependencies": {
5977
+ "micromark-util-types": "^2.0.0"
5978
+ }
5979
+ },
5980
+ "node_modules/micromark-util-sanitize-uri": {
5981
+ "version": "2.0.1",
5982
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
5983
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
5984
+ "funding": [
5985
+ {
5986
+ "type": "GitHub Sponsors",
5987
+ "url": "https://github.com/sponsors/unifiedjs"
5988
+ },
5989
+ {
5990
+ "type": "OpenCollective",
5991
+ "url": "https://opencollective.com/unified"
5992
+ }
5993
+ ],
5994
+ "license": "MIT",
5995
+ "dependencies": {
5996
+ "micromark-util-character": "^2.0.0",
5997
+ "micromark-util-encode": "^2.0.0",
5998
+ "micromark-util-symbol": "^2.0.0"
5999
+ }
6000
+ },
6001
+ "node_modules/micromark-util-subtokenize": {
6002
+ "version": "2.1.0",
6003
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
6004
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
6005
+ "funding": [
6006
+ {
6007
+ "type": "GitHub Sponsors",
6008
+ "url": "https://github.com/sponsors/unifiedjs"
6009
+ },
6010
+ {
6011
+ "type": "OpenCollective",
6012
+ "url": "https://opencollective.com/unified"
6013
+ }
6014
+ ],
6015
+ "license": "MIT",
6016
+ "dependencies": {
6017
+ "devlop": "^1.0.0",
6018
+ "micromark-util-chunked": "^2.0.0",
6019
+ "micromark-util-symbol": "^2.0.0",
6020
+ "micromark-util-types": "^2.0.0"
6021
+ }
6022
+ },
6023
+ "node_modules/micromark-util-symbol": {
6024
+ "version": "2.0.1",
6025
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
6026
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
6027
+ "funding": [
6028
+ {
6029
+ "type": "GitHub Sponsors",
6030
+ "url": "https://github.com/sponsors/unifiedjs"
6031
+ },
6032
+ {
6033
+ "type": "OpenCollective",
6034
+ "url": "https://opencollective.com/unified"
6035
+ }
6036
+ ],
6037
+ "license": "MIT"
6038
+ },
6039
+ "node_modules/micromark-util-types": {
6040
+ "version": "2.0.2",
6041
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
6042
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
6043
+ "funding": [
6044
+ {
6045
+ "type": "GitHub Sponsors",
6046
+ "url": "https://github.com/sponsors/unifiedjs"
6047
+ },
6048
+ {
6049
+ "type": "OpenCollective",
6050
+ "url": "https://opencollective.com/unified"
6051
+ }
6052
+ ],
6053
+ "license": "MIT"
6054
+ },
6055
+ "node_modules/micromatch": {
6056
+ "version": "4.0.8",
6057
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
6058
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
6059
+ "dev": true,
6060
+ "license": "MIT",
6061
+ "dependencies": {
6062
+ "braces": "^3.0.3",
6063
+ "picomatch": "^2.3.1"
6064
+ },
6065
+ "engines": {
6066
+ "node": ">=8.6"
6067
+ }
6068
+ },
6069
+ "node_modules/minimatch": {
6070
+ "version": "3.1.2",
6071
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
6072
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
6073
+ "dev": true,
6074
+ "license": "ISC",
6075
+ "dependencies": {
6076
+ "brace-expansion": "^1.1.7"
6077
+ },
6078
+ "engines": {
6079
+ "node": "*"
6080
+ }
6081
+ },
6082
+ "node_modules/minimist": {
6083
+ "version": "1.2.8",
6084
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
6085
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
6086
+ "dev": true,
6087
+ "license": "MIT",
6088
+ "funding": {
6089
+ "url": "https://github.com/sponsors/ljharb"
6090
+ }
6091
+ },
6092
+ "node_modules/ms": {
6093
+ "version": "2.1.3",
6094
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
6095
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
6096
+ "license": "MIT"
6097
+ },
6098
+ "node_modules/nanoid": {
6099
+ "version": "3.3.11",
6100
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
6101
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
6102
  "funding": [
6103
  {
6104
  "type": "github",
 
6347
  "url": "https://github.com/sponsors/ljharb"
6348
  }
6349
  },
6350
+ "node_modules/openai": {
6351
+ "version": "6.15.0",
6352
+ "resolved": "https://registry.npmjs.org/openai/-/openai-6.15.0.tgz",
6353
+ "integrity": "sha512-F1Lvs5BoVvmZtzkUEVyh8mDQPPFolq4F+xdsx/DO8Hee8YF3IGAlZqUIsF+DVGhqf4aU0a3bTghsxB6OIsRy1g==",
6354
+ "license": "Apache-2.0",
6355
+ "bin": {
6356
+ "openai": "bin/cli"
6357
+ },
6358
+ "peerDependencies": {
6359
+ "ws": "^8.18.0",
6360
+ "zod": "^3.25 || ^4.0"
6361
+ },
6362
+ "peerDependenciesMeta": {
6363
+ "ws": {
6364
+ "optional": true
6365
+ },
6366
+ "zod": {
6367
+ "optional": true
6368
+ }
6369
+ }
6370
+ },
6371
  "node_modules/optionator": {
6372
  "version": "0.9.4",
6373
  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
 
6449
  "node": ">=6"
6450
  }
6451
  },
6452
+ "node_modules/parse-entities": {
6453
+ "version": "4.0.2",
6454
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
6455
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
6456
+ "license": "MIT",
6457
+ "dependencies": {
6458
+ "@types/unist": "^2.0.0",
6459
+ "character-entities-legacy": "^3.0.0",
6460
+ "character-reference-invalid": "^2.0.0",
6461
+ "decode-named-character-reference": "^1.0.0",
6462
+ "is-alphanumerical": "^2.0.0",
6463
+ "is-decimal": "^2.0.0",
6464
+ "is-hexadecimal": "^2.0.0"
6465
+ },
6466
+ "funding": {
6467
+ "type": "github",
6468
+ "url": "https://github.com/sponsors/wooorm"
6469
+ }
6470
+ },
6471
+ "node_modules/parse-entities/node_modules/@types/unist": {
6472
+ "version": "2.0.11",
6473
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
6474
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
6475
+ "license": "MIT"
6476
+ },
6477
  "node_modules/path-exists": {
6478
  "version": "4.0.0",
6479
  "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
 
6581
  "react-is": "^16.13.1"
6582
  }
6583
  },
6584
+ "node_modules/property-information": {
6585
+ "version": "7.1.0",
6586
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
6587
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
6588
+ "license": "MIT",
6589
+ "funding": {
6590
+ "type": "github",
6591
+ "url": "https://github.com/sponsors/wooorm"
6592
+ }
6593
+ },
6594
  "node_modules/punycode": {
6595
  "version": "2.3.1",
6596
  "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
 
6652
  "dev": true,
6653
  "license": "MIT"
6654
  },
6655
+ "node_modules/react-markdown": {
6656
+ "version": "10.1.0",
6657
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
6658
+ "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==",
6659
+ "license": "MIT",
6660
+ "dependencies": {
6661
+ "@types/hast": "^3.0.0",
6662
+ "@types/mdast": "^4.0.0",
6663
+ "devlop": "^1.0.0",
6664
+ "hast-util-to-jsx-runtime": "^2.0.0",
6665
+ "html-url-attributes": "^3.0.0",
6666
+ "mdast-util-to-hast": "^13.0.0",
6667
+ "remark-parse": "^11.0.0",
6668
+ "remark-rehype": "^11.0.0",
6669
+ "unified": "^11.0.0",
6670
+ "unist-util-visit": "^5.0.0",
6671
+ "vfile": "^6.0.0"
6672
+ },
6673
+ "funding": {
6674
+ "type": "opencollective",
6675
+ "url": "https://opencollective.com/unified"
6676
+ },
6677
+ "peerDependencies": {
6678
+ "@types/react": ">=18",
6679
+ "react": ">=18"
6680
+ }
6681
+ },
6682
  "node_modules/reflect.getprototypeof": {
6683
  "version": "1.0.10",
6684
  "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
 
6723
  "url": "https://github.com/sponsors/ljharb"
6724
  }
6725
  },
6726
+ "node_modules/remark-gfm": {
6727
+ "version": "4.0.1",
6728
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
6729
+ "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
6730
+ "license": "MIT",
6731
+ "dependencies": {
6732
+ "@types/mdast": "^4.0.0",
6733
+ "mdast-util-gfm": "^3.0.0",
6734
+ "micromark-extension-gfm": "^3.0.0",
6735
+ "remark-parse": "^11.0.0",
6736
+ "remark-stringify": "^11.0.0",
6737
+ "unified": "^11.0.0"
6738
+ },
6739
+ "funding": {
6740
+ "type": "opencollective",
6741
+ "url": "https://opencollective.com/unified"
6742
+ }
6743
+ },
6744
+ "node_modules/remark-parse": {
6745
+ "version": "11.0.0",
6746
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
6747
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
6748
+ "license": "MIT",
6749
+ "dependencies": {
6750
+ "@types/mdast": "^4.0.0",
6751
+ "mdast-util-from-markdown": "^2.0.0",
6752
+ "micromark-util-types": "^2.0.0",
6753
+ "unified": "^11.0.0"
6754
+ },
6755
+ "funding": {
6756
+ "type": "opencollective",
6757
+ "url": "https://opencollective.com/unified"
6758
+ }
6759
+ },
6760
+ "node_modules/remark-rehype": {
6761
+ "version": "11.1.2",
6762
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
6763
+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
6764
+ "license": "MIT",
6765
+ "dependencies": {
6766
+ "@types/hast": "^3.0.0",
6767
+ "@types/mdast": "^4.0.0",
6768
+ "mdast-util-to-hast": "^13.0.0",
6769
+ "unified": "^11.0.0",
6770
+ "vfile": "^6.0.0"
6771
+ },
6772
+ "funding": {
6773
+ "type": "opencollective",
6774
+ "url": "https://opencollective.com/unified"
6775
+ }
6776
+ },
6777
+ "node_modules/remark-stringify": {
6778
+ "version": "11.0.0",
6779
+ "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
6780
+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
6781
+ "license": "MIT",
6782
+ "dependencies": {
6783
+ "@types/mdast": "^4.0.0",
6784
+ "mdast-util-to-markdown": "^2.0.0",
6785
+ "unified": "^11.0.0"
6786
+ },
6787
+ "funding": {
6788
+ "type": "opencollective",
6789
+ "url": "https://opencollective.com/unified"
6790
+ }
6791
+ },
6792
  "node_modules/resolve": {
6793
  "version": "1.22.11",
6794
  "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
 
7151
  "node": ">=0.10.0"
7152
  }
7153
  },
7154
+ "node_modules/space-separated-tokens": {
7155
+ "version": "2.0.2",
7156
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
7157
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
7158
+ "license": "MIT",
7159
+ "funding": {
7160
+ "type": "github",
7161
+ "url": "https://github.com/sponsors/wooorm"
7162
+ }
7163
+ },
7164
  "node_modules/stable-hash": {
7165
  "version": "0.0.5",
7166
  "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
 
7295
  "url": "https://github.com/sponsors/ljharb"
7296
  }
7297
  },
7298
+ "node_modules/stringify-entities": {
7299
+ "version": "4.0.4",
7300
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
7301
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
7302
+ "license": "MIT",
7303
+ "dependencies": {
7304
+ "character-entities-html4": "^2.0.0",
7305
+ "character-entities-legacy": "^3.0.0"
7306
+ },
7307
+ "funding": {
7308
+ "type": "github",
7309
+ "url": "https://github.com/sponsors/wooorm"
7310
+ }
7311
+ },
7312
  "node_modules/strip-bom": {
7313
  "version": "3.0.0",
7314
  "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
 
7332
  "url": "https://github.com/sponsors/sindresorhus"
7333
  }
7334
  },
7335
+ "node_modules/style-to-js": {
7336
+ "version": "1.1.21",
7337
+ "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
7338
+ "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
7339
+ "license": "MIT",
7340
+ "dependencies": {
7341
+ "style-to-object": "1.0.14"
7342
+ }
7343
+ },
7344
+ "node_modules/style-to-object": {
7345
+ "version": "1.0.14",
7346
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
7347
+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
7348
+ "license": "MIT",
7349
+ "dependencies": {
7350
+ "inline-style-parser": "0.2.7"
7351
+ }
7352
+ },
7353
  "node_modules/styled-jsx": {
7354
  "version": "5.1.6",
7355
  "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
 
7399
  "url": "https://github.com/sponsors/ljharb"
7400
  }
7401
  },
7402
+ "node_modules/tailwind-merge": {
7403
+ "version": "3.4.0",
7404
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
7405
+ "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
7406
+ "license": "MIT",
7407
+ "funding": {
7408
+ "type": "github",
7409
+ "url": "https://github.com/sponsors/dcastil"
7410
+ }
7411
+ },
7412
  "node_modules/tailwindcss": {
7413
  "version": "4.1.18",
7414
  "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
 
7492
  "node": ">=8.0"
7493
  }
7494
  },
7495
+ "node_modules/trim-lines": {
7496
+ "version": "3.0.1",
7497
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
7498
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
7499
+ "license": "MIT",
7500
+ "funding": {
7501
+ "type": "github",
7502
+ "url": "https://github.com/sponsors/wooorm"
7503
+ }
7504
+ },
7505
+ "node_modules/trough": {
7506
+ "version": "2.2.0",
7507
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
7508
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
7509
+ "license": "MIT",
7510
+ "funding": {
7511
+ "type": "github",
7512
+ "url": "https://github.com/sponsors/wooorm"
7513
+ }
7514
+ },
7515
  "node_modules/ts-api-utils": {
7516
  "version": "2.1.0",
7517
  "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
 
7713
  "dev": true,
7714
  "license": "MIT"
7715
  },
7716
+ "node_modules/unified": {
7717
+ "version": "11.0.5",
7718
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
7719
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
7720
+ "license": "MIT",
7721
+ "dependencies": {
7722
+ "@types/unist": "^3.0.0",
7723
+ "bail": "^2.0.0",
7724
+ "devlop": "^1.0.0",
7725
+ "extend": "^3.0.0",
7726
+ "is-plain-obj": "^4.0.0",
7727
+ "trough": "^2.0.0",
7728
+ "vfile": "^6.0.0"
7729
+ },
7730
+ "funding": {
7731
+ "type": "opencollective",
7732
+ "url": "https://opencollective.com/unified"
7733
+ }
7734
+ },
7735
+ "node_modules/unist-util-is": {
7736
+ "version": "6.0.1",
7737
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
7738
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
7739
+ "license": "MIT",
7740
+ "dependencies": {
7741
+ "@types/unist": "^3.0.0"
7742
+ },
7743
+ "funding": {
7744
+ "type": "opencollective",
7745
+ "url": "https://opencollective.com/unified"
7746
+ }
7747
+ },
7748
+ "node_modules/unist-util-position": {
7749
+ "version": "5.0.0",
7750
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
7751
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
7752
+ "license": "MIT",
7753
+ "dependencies": {
7754
+ "@types/unist": "^3.0.0"
7755
+ },
7756
+ "funding": {
7757
+ "type": "opencollective",
7758
+ "url": "https://opencollective.com/unified"
7759
+ }
7760
+ },
7761
+ "node_modules/unist-util-stringify-position": {
7762
+ "version": "4.0.0",
7763
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
7764
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
7765
+ "license": "MIT",
7766
+ "dependencies": {
7767
+ "@types/unist": "^3.0.0"
7768
+ },
7769
+ "funding": {
7770
+ "type": "opencollective",
7771
+ "url": "https://opencollective.com/unified"
7772
+ }
7773
+ },
7774
+ "node_modules/unist-util-visit": {
7775
+ "version": "5.0.0",
7776
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
7777
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
7778
+ "license": "MIT",
7779
+ "dependencies": {
7780
+ "@types/unist": "^3.0.0",
7781
+ "unist-util-is": "^6.0.0",
7782
+ "unist-util-visit-parents": "^6.0.0"
7783
+ },
7784
+ "funding": {
7785
+ "type": "opencollective",
7786
+ "url": "https://opencollective.com/unified"
7787
+ }
7788
+ },
7789
+ "node_modules/unist-util-visit-parents": {
7790
+ "version": "6.0.2",
7791
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
7792
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
7793
+ "license": "MIT",
7794
+ "dependencies": {
7795
+ "@types/unist": "^3.0.0",
7796
+ "unist-util-is": "^6.0.0"
7797
+ },
7798
+ "funding": {
7799
+ "type": "opencollective",
7800
+ "url": "https://opencollective.com/unified"
7801
+ }
7802
+ },
7803
  "node_modules/unrs-resolver": {
7804
  "version": "1.11.1",
7805
  "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
 
7876
  "punycode": "^2.1.0"
7877
  }
7878
  },
7879
+ "node_modules/vfile": {
7880
+ "version": "6.0.3",
7881
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
7882
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
7883
+ "license": "MIT",
7884
+ "dependencies": {
7885
+ "@types/unist": "^3.0.0",
7886
+ "vfile-message": "^4.0.0"
7887
+ },
7888
+ "funding": {
7889
+ "type": "opencollective",
7890
+ "url": "https://opencollective.com/unified"
7891
+ }
7892
+ },
7893
+ "node_modules/vfile-message": {
7894
+ "version": "4.0.3",
7895
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
7896
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
7897
+ "license": "MIT",
7898
+ "dependencies": {
7899
+ "@types/unist": "^3.0.0",
7900
+ "unist-util-stringify-position": "^4.0.0"
7901
+ },
7902
+ "funding": {
7903
+ "type": "opencollective",
7904
+ "url": "https://opencollective.com/unified"
7905
+ }
7906
+ },
7907
  "node_modules/which": {
7908
  "version": "2.0.2",
7909
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
 
8043
  "version": "4.2.1",
8044
  "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz",
8045
  "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
8046
+ "devOptional": true,
8047
  "license": "MIT",
8048
  "peer": true,
8049
  "funding": {
 
8062
  "peerDependencies": {
8063
  "zod": "^3.25.0 || ^4.0.0"
8064
  }
8065
+ },
8066
+ "node_modules/zwitch": {
8067
+ "version": "2.0.4",
8068
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
8069
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
8070
+ "license": "MIT",
8071
+ "funding": {
8072
+ "type": "github",
8073
+ "url": "https://github.com/sponsors/wooorm"
8074
+ }
8075
  }
8076
  }
8077
  }
package.json CHANGED
@@ -9,9 +9,16 @@
9
  "lint": "eslint"
10
  },
11
  "dependencies": {
 
 
 
12
  "next": "16.1.1",
 
13
  "react": "19.2.3",
14
- "react-dom": "19.2.3"
 
 
 
15
  },
16
  "devDependencies": {
17
  "@tailwindcss/postcss": "^4",
 
9
  "lint": "eslint"
10
  },
11
  "dependencies": {
12
+ "@google/generative-ai": "^0.24.1",
13
+ "clsx": "^2.1.1",
14
+ "lucide-react": "^0.562.0",
15
  "next": "16.1.1",
16
+ "openai": "^6.15.0",
17
  "react": "19.2.3",
18
+ "react-dom": "19.2.3",
19
+ "react-markdown": "^10.1.0",
20
+ "remark-gfm": "^4.0.1",
21
+ "tailwind-merge": "^3.4.0"
22
  },
23
  "devDependencies": {
24
  "@tailwindcss/postcss": "^4",
src/app/api/analyze/route.ts ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { GoogleGenerativeAI } from '@google/generative-ai';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+
7
+ // Initialize Gemini
8
+ const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
9
+
10
+ export async function POST(req: NextRequest) {
11
+ try {
12
+ const formData = await req.formData();
13
+ const file = formData.get('file') as File | null;
14
+ const textInput = formData.get('text') as string | null;
15
+
16
+ const apiKey = process.env.GEMINI_API_KEY;
17
+
18
+ // Debug Logging
19
+ console.log("--- API DEBUG INFO ---");
20
+ console.log("API Key Loaded:", !!apiKey);
21
+ console.log("API Key Start:", apiKey ? apiKey.substring(0, 4) + "****" : "N/A");
22
+
23
+ if (!apiKey || apiKey.includes('YOUR_GEMINI_API_KEY') || apiKey.length < 20) {
24
+ console.error("❌ CRITICAL: Invalid or Missing API Key.");
25
+ return NextResponse.json(
26
+ { error: 'API Key not configured. Edit .env.local with a valid key.' },
27
+ { status: 500 }
28
+ );
29
+ }
30
+
31
+ // Read System Prompt
32
+ const promptPath = path.join(process.cwd(), 'src', 'prompts', 'system_prompt.md');
33
+ let systemInstruction = '';
34
+ try {
35
+ systemInstruction = fs.readFileSync(promptPath, 'utf-8');
36
+ } catch (error) {
37
+ console.error("Error reading system prompt:", error);
38
+ return NextResponse.json({ error: "Failed to load system prompt" }, { status: 500 });
39
+ }
40
+
41
+ // Using gemini-2.5-flash - newest model with fresh quota
42
+ const model = genAI.getGenerativeModel({
43
+ model: "gemini-2.5-flash",
44
+ systemInstruction: systemInstruction,
45
+ });
46
+
47
+ const parts = [];
48
+
49
+ // Handle File Input (Images)
50
+ if (file) {
51
+ const arrayBuffer = await file.arrayBuffer();
52
+ const buffer = Buffer.from(arrayBuffer);
53
+ parts.push({
54
+ inlineData: {
55
+ data: buffer.toString('base64'),
56
+ mimeType: file.type,
57
+ },
58
+ });
59
+ parts.push({ text: "Please analyze this image according to your system instructions. If it's a problem, guide me. If it's a text, summarize it." });
60
+ }
61
+
62
+ // Handle Text Input
63
+ if (textInput) {
64
+ parts.push({ text: textInput });
65
+ }
66
+
67
+ if (parts.length === 0) {
68
+ return NextResponse.json({ error: "No content provided (file or text)" }, { status: 400 });
69
+ }
70
+
71
+ console.log("Sending request to model: gemma-3-27b-it");
72
+ const result = await model.generateContent(parts);
73
+ const response = await result.response;
74
+ const outputText = response.text();
75
+ console.log("βœ… Response received successfully!");
76
+
77
+ return NextResponse.json({ result: outputText });
78
+
79
+ } catch (error: any) {
80
+ console.error('Error generating content:', error);
81
+ return NextResponse.json(
82
+ { error: error.message || 'Internal Server Error' },
83
+ { status: 500 }
84
+ );
85
+ }
86
+ }
src/app/globals.css CHANGED
@@ -1,26 +1,311 @@
1
  @import "tailwindcss";
2
 
3
  :root {
4
- --background: #ffffff;
5
- --foreground: #171717;
6
- }
7
-
8
- @theme inline {
9
- --color-background: var(--background);
10
- --color-foreground: var(--foreground);
11
- --font-sans: var(--font-geist-sans);
12
- --font-mono: var(--font-geist-mono);
 
13
  }
14
 
15
  @media (prefers-color-scheme: dark) {
16
- :root {
17
- --background: #0a0a0a;
18
- --foreground: #ededed;
19
- }
 
 
 
 
 
 
 
 
20
  }
21
 
22
  body {
23
- background: var(--background);
24
- color: var(--foreground);
25
- font-family: Arial, Helvetica, sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  @import "tailwindcss";
2
 
3
  :root {
4
+ --background: #ffffff;
5
+ --foreground: #1a1a1a;
6
+ --primary: #0ea5e9;
7
+ --primary-light: #38bdf8;
8
+ --secondary: #64748b;
9
+ --accent: #0284c7;
10
+ --surface: #ffffff;
11
+ --surface-variant: #f8fafc;
12
+ --card-border: #e2e8f0;
13
+ --text-muted: #52525b;
14
  }
15
 
16
  @media (prefers-color-scheme: dark) {
17
+ :root {
18
+ --background: #0a0a0a;
19
+ --foreground: #fafafa;
20
+ --primary: #38bdf8;
21
+ --primary-light: #7dd3fc;
22
+ --secondary: #94a3b8;
23
+ --accent: #0ea5e9;
24
+ --surface: #111111;
25
+ --surface-variant: #18181b;
26
+ --card-border: #27272a;
27
+ --text-muted: #a1a1aa;
28
+ }
29
  }
30
 
31
  body {
32
+ color: var(--foreground);
33
+ background: var(--background);
34
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
35
+ overflow-x: hidden;
36
+ }
37
+
38
+ /* ==================== MARKDOWN CONTENT STYLING ==================== */
39
+
40
+ .markdown-content {
41
+ line-height: 1.75;
42
+ font-size: 0.95rem;
43
+ }
44
+
45
+ /* Page Title */
46
+ .markdown-content h1 {
47
+ font-size: 1.5rem;
48
+ font-weight: 700;
49
+ margin-bottom: 0.5rem;
50
+ margin-top: 0;
51
+ color: var(--foreground);
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 0.5rem;
55
+ line-height: 1.3;
56
+ }
57
+
58
+ /* Subtitle/Meta line after title */
59
+ .markdown-content>p:first-of-type {
60
+ color: var(--text-muted);
61
+ font-size: 0.875rem;
62
+ margin-bottom: 1.5rem;
63
+ padding-bottom: 1rem;
64
+ border-bottom: 1px solid var(--card-border);
65
+ }
66
+
67
+ /* Section Headers */
68
+ .markdown-content h3 {
69
+ font-size: 1rem;
70
+ font-weight: 600;
71
+ margin-bottom: 0.75rem;
72
+ margin-top: 0;
73
+ color: var(--foreground);
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 0.5rem;
77
+ }
78
+
79
+ /* Paragraphs */
80
+ .markdown-content p {
81
+ margin-bottom: 1rem;
82
+ color: var(--text-muted);
83
+ font-size: 0.925rem;
84
+ line-height: 1.7;
85
+ }
86
+
87
+ /* Cards / Blockquotes - Clean Cards with Clear Separation */
88
+ .markdown-content blockquote {
89
+ position: relative;
90
+ padding: 1.25rem 1.5rem;
91
+ margin: 0 0 1.25rem 0;
92
+ border-radius: 16px;
93
+ border: 1px solid var(--card-border);
94
+ background: var(--surface-variant);
95
+ transition: all 0.2s ease;
96
+ animation: fadeSlideIn 0.4s ease-out forwards;
97
+ opacity: 0;
98
+ transform: translateY(8px);
99
+ }
100
+
101
+ .markdown-content blockquote:nth-child(1) {
102
+ animation-delay: 0.05s;
103
+ }
104
+
105
+ .markdown-content blockquote:nth-child(2) {
106
+ animation-delay: 0.1s;
107
+ }
108
+
109
+ .markdown-content blockquote:nth-child(3) {
110
+ animation-delay: 0.15s;
111
+ }
112
+
113
+ .markdown-content blockquote:nth-child(4) {
114
+ animation-delay: 0.2s;
115
+ }
116
+
117
+ .markdown-content blockquote:nth-child(5) {
118
+ animation-delay: 0.25s;
119
+ }
120
+
121
+ .markdown-content blockquote:hover {
122
+ border-color: var(--primary);
123
+ }
124
+
125
+ /* Remove margin from first/last elements inside blockquote */
126
+ .markdown-content blockquote>*:first-child {
127
+ margin-top: 0;
128
+ }
129
+
130
+ .markdown-content blockquote>*:last-child {
131
+ margin-bottom: 0;
132
+ }
133
+
134
+ /* Nested content in blockquotes */
135
+ .markdown-content blockquote p {
136
+ color: var(--foreground);
137
+ opacity: 0.85;
138
+ }
139
+
140
+ .markdown-content blockquote h3 {
141
+ margin-bottom: 1rem;
142
+ padding-bottom: 0.5rem;
143
+ border-bottom: 1px solid var(--card-border);
144
+ }
145
+
146
+ /* Lists */
147
+ .markdown-content ul {
148
+ list-style: none;
149
+ padding: 0;
150
+ margin: 0.5rem 0;
151
+ }
152
+
153
+ .markdown-content ul li {
154
+ position: relative;
155
+ padding-left: 1.5rem;
156
+ margin-bottom: 0.625rem;
157
+ font-size: 0.9rem;
158
+ color: var(--text-muted);
159
+ line-height: 1.6;
160
+ }
161
+
162
+ .markdown-content ul li::before {
163
+ content: '';
164
+ position: absolute;
165
+ left: 0;
166
+ top: 0.5rem;
167
+ width: 6px;
168
+ height: 6px;
169
+ border-radius: 50%;
170
+ background: var(--primary);
171
  }
172
+
173
+ /* Ordered Lists */
174
+ .markdown-content ol {
175
+ counter-reset: item;
176
+ padding: 0;
177
+ margin: 0.5rem 0;
178
+ }
179
+
180
+ .markdown-content ol li {
181
+ counter-increment: item;
182
+ position: relative;
183
+ padding-left: 2rem;
184
+ margin-bottom: 0.625rem;
185
+ font-size: 0.9rem;
186
+ color: var(--text-muted);
187
+ }
188
+
189
+ .markdown-content ol li::before {
190
+ content: counter(item);
191
+ position: absolute;
192
+ left: 0;
193
+ top: 0;
194
+ width: 1.5rem;
195
+ height: 1.5rem;
196
+ border-radius: 50%;
197
+ background: var(--foreground);
198
+ color: var(--background);
199
+ font-weight: 600;
200
+ font-size: 0.7rem;
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: center;
204
+ }
205
+
206
+ /* Bold text */
207
+ .markdown-content strong {
208
+ font-weight: 600;
209
+ color: var(--foreground);
210
+ }
211
+
212
+ /* Italic */
213
+ .markdown-content em {
214
+ font-style: italic;
215
+ color: var(--primary);
216
+ }
217
+
218
+ /* Horizontal Rule */
219
+ .markdown-content hr {
220
+ border: none;
221
+ height: 1px;
222
+ margin: 1.5rem 0;
223
+ background: var(--card-border);
224
+ }
225
+
226
+ /* Code/Tags */
227
+ .markdown-content code {
228
+ background: var(--surface-variant);
229
+ padding: 0.15rem 0.4rem;
230
+ border-radius: 4px;
231
+ font-size: 0.85rem;
232
+ font-family: 'SF Mono', 'Fira Code', monospace;
233
+ color: var(--foreground);
234
+ border: 1px solid var(--card-border);
235
+ }
236
+
237
+ /* Action buttons at the bottom */
238
+ .markdown-content strong:has(a),
239
+ .markdown-content p:last-child strong {
240
+ display: inline-flex;
241
+ gap: 0.5rem;
242
+ }
243
+
244
+ /* ==================== ANIMATIONS ==================== */
245
+
246
+ @keyframes fadeSlideIn {
247
+ from {
248
+ opacity: 0;
249
+ transform: translateY(8px);
250
+ }
251
+
252
+ to {
253
+ opacity: 1;
254
+ transform: translateY(0);
255
+ }
256
+ }
257
+
258
+ .animate-fade-in-up {
259
+ animation: fadeSlideIn 0.4s ease-out forwards;
260
+ }
261
+
262
+ /* ==================== UTILITY CLASSES ==================== */
263
+
264
+ @layer utilities {
265
+ .card-shadow {
266
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
267
+ }
268
+
269
+ .pixel-corner {
270
+ border-radius: 16px;
271
+ }
272
+ }
273
+
274
+ /* ==================== SCROLLBAR ==================== */
275
+
276
+ ::-webkit-scrollbar {
277
+ width: 6px;
278
+ }
279
+
280
+ ::-webkit-scrollbar-track {
281
+ background: var(--surface-variant);
282
+ }
283
+
284
+ ::-webkit-scrollbar-thumb {
285
+ background: var(--secondary);
286
+ border-radius: 10px;
287
+ }
288
+
289
+ ::-webkit-scrollbar-thumb:hover {
290
+ background: var(--primary);
291
+ }
292
+
293
+ /* ==================== PROSE OVERRIDES ==================== */
294
+
295
+ .prose {
296
+ --tw-prose-body: var(--text-muted);
297
+ --tw-prose-headings: var(--foreground);
298
+ --tw-prose-bold: var(--foreground);
299
+ --tw-prose-quotes: var(--text-muted);
300
+ }
301
+
302
+ .prose :where(blockquote):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
303
+ font-style: normal;
304
+ border-left: none;
305
+ quotes: none;
306
+ }
307
+
308
+ .prose :where(blockquote p:first-of-type)::before,
309
+ .prose :where(blockquote p:last-of-type)::after {
310
+ content: none;
311
+ }
src/app/layout.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import type { Metadata } from "next";
2
  import { Geist, Geist_Mono } from "next/font/google";
3
  import "./globals.css";
@@ -13,8 +14,8 @@ const geistMono = Geist_Mono({
13
  });
14
 
15
  export const metadata: Metadata = {
16
- title: "Create Next App",
17
- description: "Generated by create next app",
18
  };
19
 
20
  export default function RootLayout({
@@ -23,7 +24,7 @@ export default function RootLayout({
23
  children: React.ReactNode;
24
  }>) {
25
  return (
26
- <html lang="en">
27
  <body
28
  className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29
  >
 
1
+
2
  import type { Metadata } from "next";
3
  import { Geist, Geist_Mono } from "next/font/google";
4
  import "./globals.css";
 
14
  });
15
 
16
  export const metadata: Metadata = {
17
+ title: "Socratic Lens",
18
+ description: "Your Ethical AI Tutor",
19
  };
20
 
21
  export default function RootLayout({
 
24
  children: React.ReactNode;
25
  }>) {
26
  return (
27
+ <html lang="en" suppressHydrationWarning>
28
  <body
29
  className={`${geistSans.variable} ${geistMono.variable} antialiased`}
30
  >
src/app/page.tsx CHANGED
@@ -1,65 +1,302 @@
1
- import Image from "next/image";
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  export default function Home() {
4
- return (
5
- <div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
6
- <main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
7
- <Image
8
- className="dark:invert"
9
- src="/next.svg"
10
- alt="Next.js logo"
11
- width={100}
12
- height={20}
13
- priority
14
- />
15
- <div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
16
- <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
17
- To get started, edit the page.tsx file.
18
- </h1>
19
- <p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
20
- Looking for a starting point or more instructions? Head over to{" "}
21
- <a
22
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
23
- className="font-medium text-zinc-950 dark:text-zinc-50"
24
- >
25
- Templates
26
- </a>{" "}
27
- or the{" "}
28
- <a
29
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
30
- className="font-medium text-zinc-950 dark:text-zinc-50"
31
- >
32
- Learning
33
- </a>{" "}
34
- center.
35
- </p>
36
- </div>
37
- <div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
38
- <a
39
- className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
40
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
41
- target="_blank"
42
- rel="noopener noreferrer"
43
- >
44
- <Image
45
- className="dark:invert"
46
- src="/vercel.svg"
47
- alt="Vercel logomark"
48
- width={16}
49
- height={16}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  />
51
- Deploy Now
52
- </a>
53
- <a
54
- className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
55
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
56
- target="_blank"
57
- rel="noopener noreferrer"
58
- >
59
- Documentation
60
- </a>
61
- </div>
62
- </main>
63
- </div>
64
- );
65
  }
 
1
+
2
+ 'use client';
3
+
4
+ import { useState, useRef } from 'react';
5
+ import ReactMarkdown from 'react-markdown';
6
+ import remarkGfm from 'remark-gfm';
7
+ import { Upload, FileText, Image as ImageIcon, Send, Loader2, RefreshCcw, MessageCircle, BookOpen, Camera } from 'lucide-react';
8
+ import clsx from 'clsx';
9
+ import WikipediaCard from '@/components/WikipediaCard';
10
+ import AskQuestionModal from '@/components/AskQuestionModal';
11
+ import QuizModal from '@/components/QuizModal';
12
+ import CameraModal from '@/components/CameraModal';
13
 
14
  export default function Home() {
15
+ const [file, setFile] = useState<File | null>(null);
16
+ const [preview, setPreview] = useState<string | null>(null);
17
+ const [loading, setLoading] = useState(false);
18
+ const [result, setResult] = useState<string | null>(null);
19
+ const [error, setError] = useState<string | null>(null);
20
+ const [topics, setTopics] = useState<string[]>([]);
21
+ const [rawContent, setRawContent] = useState<string>('');
22
+ const fileInputRef = useRef<HTMLInputElement>(null);
23
+
24
+ // Modal states
25
+ const [askModalOpen, setAskModalOpen] = useState(false);
26
+ const [quizModalOpen, setQuizModalOpen] = useState(false);
27
+ const [cameraModalOpen, setCameraModalOpen] = useState(false);
28
+
29
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
30
+ if (e.target.files && e.target.files[0]) {
31
+ const selectedFile = e.target.files[0];
32
+ setFileWithPreview(selectedFile);
33
+ }
34
+ };
35
+
36
+ const setFileWithPreview = (selectedFile: File) => {
37
+ setFile(selectedFile);
38
+ setError(null);
39
+
40
+ if (selectedFile.type.startsWith('image/')) {
41
+ const reader = new FileReader();
42
+ reader.onload = (e) => {
43
+ setPreview(e.target?.result as string);
44
+ };
45
+ reader.readAsDataURL(selectedFile);
46
+ } else {
47
+ setPreview(null);
48
+ }
49
+ };
50
+
51
+ const handleCameraCapture = (capturedFile: File) => {
52
+ setFileWithPreview(capturedFile);
53
+ };
54
+
55
+ const handleSubmit = async () => {
56
+ if (!file) {
57
+ setError("Please select an image or document first.");
58
+ return;
59
+ }
60
+
61
+ setLoading(true);
62
+ setError(null);
63
+ setResult(null);
64
+ setTopics([]);
65
+ setRawContent('');
66
+
67
+ const formData = new FormData();
68
+ formData.append('file', file);
69
+
70
+ try {
71
+ const res = await fetch('/api/analyze', {
72
+ method: 'POST',
73
+ body: formData,
74
+ });
75
+
76
+ if (!res.ok) {
77
+ const errData = await res.json();
78
+ throw new Error(errData.error || 'Failed to analyze');
79
+ }
80
+
81
+ const data = await res.json();
82
+ const rawText = data.result || "";
83
+
84
+ setRawContent(rawText);
85
+
86
+ let extractedTopics: string[] = [];
87
+ const multiMatch = rawText.match(/\[\[TOPICS?:\s*(.*?)\]\]/i);
88
+ if (multiMatch) {
89
+ extractedTopics = multiMatch[1].split(',').map((t: string) => t.trim()).filter((t: string) => t.length > 0);
90
+ }
91
+
92
+ const cleanText = rawText.replace(/\[\[TOPICS?:.*?\]\]/i, "").trim();
93
+
94
+ setResult(cleanText);
95
+ setTopics(extractedTopics);
96
+
97
+ } catch (err: any) {
98
+ setError(err.message || "Something went wrong.");
99
+ } finally {
100
+ setLoading(false);
101
+ }
102
+ };
103
+
104
+ const reset = () => {
105
+ setFile(null);
106
+ setPreview(null);
107
+ setResult(null);
108
+ setError(null);
109
+ setTopics([]);
110
+ setRawContent('');
111
+ };
112
+
113
+ return (
114
+ <>
115
+ <main className="min-h-screen p-6 md:p-12">
116
+ <div className="max-w-6xl mx-auto flex flex-col gap-8">
117
+
118
+ {/* Header */}
119
+ <header className="flex items-center justify-between">
120
+ <h1 className="text-3xl font-bold tracking-tight text-gray-800 dark:text-gray-100 flex items-center gap-3">
121
+ <span className="p-2 bg-sky-100 dark:bg-sky-900 rounded-xl text-sky-600 dark:text-sky-300">
122
+ <ImageIcon size={24} />
123
+ </span>
124
+ Socratic Lens
125
+ </h1>
126
+ <div className="text-sm px-3 py-1 bg-gray-200 dark:bg-gray-800 rounded-full font-medium text-gray-600 dark:text-gray-400">
127
+ Beta
128
+ </div>
129
+ </header>
130
+
131
+ {/* Main Interaction Area */}
132
+ {!result ? (
133
+ <section className="flex-1 flex flex-col items-center justify-center min-h-[50vh] transition-all">
134
+
135
+ {/* Two Upload Boxes Side by Side */}
136
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full max-w-3xl">
137
+
138
+ {/* Upload Document Box */}
139
+ <div
140
+ onClick={() => fileInputRef.current?.click()}
141
+ className={clsx(
142
+ "w-full aspect-square rounded-[32px] border-4 border-dashed flex flex-col items-center justify-center cursor-pointer transition-all hover:scale-[1.02] active:scale-[0.98] overflow-hidden",
143
+ file && !preview
144
+ ? "border-green-400 bg-green-50 dark:bg-green-900/20"
145
+ : "border-gray-300 dark:border-gray-700 hover:border-sky-400 hover:bg-sky-50 dark:hover:bg-gray-800"
146
+ )}
147
+ >
148
+ <input
149
+ type="file"
150
+ ref={fileInputRef}
151
+ onChange={handleFileChange}
152
+ className="hidden"
153
+ accept="image/*,application/pdf"
154
+ />
155
+
156
+ {file && !preview ? (
157
+ <>
158
+ <FileText className="w-12 h-12 text-green-600 mb-3" />
159
+ <p className="text-sm font-medium text-green-800 dark:text-green-300 text-center px-4 truncate max-w-full">{file.name}</p>
160
+ <p className="text-xs text-green-600 dark:text-green-400 mt-1">Tap to change</p>
161
+ </>
162
+ ) : (
163
+ <>
164
+ <Upload className="w-12 h-12 text-gray-400 mb-3" />
165
+ <p className="text-base font-medium text-gray-600 dark:text-gray-300">Upload Document</p>
166
+ <p className="text-xs text-gray-400 mt-1">Images or PDFs</p>
167
+ </>
168
+ )}
169
+ </div>
170
+
171
+ {/* Take Photo Box - Opens Camera Modal */}
172
+ <div
173
+ onClick={() => setCameraModalOpen(true)}
174
+ className={clsx(
175
+ "w-full aspect-square rounded-[32px] border-4 border-dashed flex flex-col items-center justify-center cursor-pointer transition-all hover:scale-[1.02] active:scale-[0.98] overflow-hidden",
176
+ preview
177
+ ? "border-green-400 bg-green-50 dark:bg-green-900/20 p-2"
178
+ : "border-gray-300 dark:border-gray-700 hover:border-sky-400 hover:bg-sky-50 dark:hover:bg-gray-800"
179
+ )}
180
+ >
181
+ {preview ? (
182
+ <div className="relative w-full h-full">
183
+ <img
184
+ src={preview}
185
+ alt="Preview"
186
+ className="w-full h-full object-contain rounded-2xl"
187
+ />
188
+ <div className="absolute bottom-2 left-1/2 -translate-x-1/2 bg-black/60 text-white text-xs px-3 py-1 rounded-full">
189
+ Tap to retake
190
+ </div>
191
+ </div>
192
+ ) : (
193
+ <>
194
+ <Camera className="w-12 h-12 text-gray-400 mb-3" />
195
+ <p className="text-base font-medium text-gray-600 dark:text-gray-300">Take Photo</p>
196
+ <p className="text-xs text-gray-400 mt-1">Use Camera</p>
197
+ </>
198
+ )}
199
+ </div>
200
+ </div>
201
+
202
+ {/* Analyze Button */}
203
+ <div className="mt-8">
204
+ <button
205
+ onClick={handleSubmit}
206
+ disabled={loading || !file}
207
+ className={clsx(
208
+ "flex items-center gap-3 px-8 py-4 rounded-full text-lg font-semibold shadow-xl transition-all",
209
+ loading || !file
210
+ ? "bg-gray-300 text-gray-500 cursor-not-allowed grayscale"
211
+ : "bg-gray-900 dark:bg-white text-white dark:text-gray-900 hover:scale-105 active:scale-95"
212
+ )}
213
+ >
214
+ {loading ? <Loader2 className="animate-spin" /> : <Send />}
215
+ {loading ? "Analyzing..." : "Analyze with Lens"}
216
+ </button>
217
+ </div>
218
+
219
+ {error && (
220
+ <div className="mt-6 p-4 bg-red-100 text-red-700 rounded-2xl flex items-center gap-2">
221
+ <span>⚠️</span> {error}
222
+ </div>
223
+ )}
224
+
225
+ </section>
226
+ ) : (
227
+ <section className="animate-fade-in-up">
228
+ <div className="flex justify-between items-center mb-6">
229
+ <button
230
+ onClick={reset}
231
+ className="flex items-center gap-2 text-sm font-medium text-gray-500 hover:text-black dark:hover:text-white transition-colors px-4 py-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800"
232
+ >
233
+ <RefreshCcw size={16} />
234
+ Analyze Another
235
+ </button>
236
+ <span className="text-xs text-gray-400 uppercase tracking-wider">AI Generated Content</span>
237
+ </div>
238
+
239
+ {/* 2-Column Layout */}
240
+ <div className="flex flex-col lg:flex-row gap-8 justify-center">
241
+
242
+ {/* Main AI Content (Center/Left) */}
243
+ <div className="flex-1 max-w-3xl">
244
+ <div className="bg-white dark:bg-neutral-900 rounded-[24px] p-6 md:p-8 shadow-xl border border-gray-200 dark:border-neutral-800">
245
+ <article className="prose prose-lg dark:prose-invert max-w-none markdown-content">
246
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>
247
+ {result}
248
+ </ReactMarkdown>
249
+ </article>
250
+ </div>
251
+
252
+ {/* Action Buttons */}
253
+ <div className="flex flex-wrap gap-3 mt-6">
254
+ <button
255
+ onClick={() => setAskModalOpen(true)}
256
+ className="flex items-center gap-2 px-5 py-3 bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-700 rounded-xl font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-neutral-800 hover:border-sky-500 transition-all shadow-sm"
257
+ >
258
+ <MessageCircle size={18} className="text-sky-500" />
259
+ Ask a Question
260
+ </button>
261
+ <button
262
+ onClick={() => setQuizModalOpen(true)}
263
+ className="flex items-center gap-2 px-5 py-3 bg-sky-500 text-white rounded-xl font-medium hover:bg-sky-600 transition-all shadow-sm"
264
+ >
265
+ <BookOpen size={18} />
266
+ Quiz Me
267
+ </button>
268
+ </div>
269
+ </div>
270
+
271
+ {/* Sidebar (Right) - Wikipedia */}
272
+ {topics.length > 0 && (
273
+ <div className="w-full lg:w-80 flex-shrink-0">
274
+ <WikipediaCard topics={topics} />
275
+ </div>
276
+ )}
277
+
278
+ </div>
279
+ </section>
280
+ )}
281
+ </div>
282
+ </main>
283
+
284
+ {/* Modals */}
285
+ <AskQuestionModal
286
+ isOpen={askModalOpen}
287
+ onClose={() => setAskModalOpen(false)}
288
+ context={rawContent}
289
+ />
290
+ <QuizModal
291
+ isOpen={quizModalOpen}
292
+ onClose={() => setQuizModalOpen(false)}
293
+ context={rawContent}
294
+ />
295
+ <CameraModal
296
+ isOpen={cameraModalOpen}
297
+ onClose={() => setCameraModalOpen(false)}
298
+ onCapture={handleCameraCapture}
299
  />
300
+ </>
301
+ );
 
 
 
 
 
 
 
 
 
 
 
 
302
  }
src/components/AskQuestionModal.tsx ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 'use client';
3
+
4
+ import { useState } from 'react';
5
+ import { X, Send, Loader2, MessageCircle } from 'lucide-react';
6
+ import clsx from 'clsx';
7
+ import ReactMarkdown from 'react-markdown';
8
+ import remarkGfm from 'remark-gfm';
9
+
10
+ interface AskQuestionModalProps {
11
+ isOpen: boolean;
12
+ onClose: () => void;
13
+ context: string; // The original analyzed content
14
+ }
15
+
16
+ interface Message {
17
+ role: 'user' | 'assistant';
18
+ content: string;
19
+ }
20
+
21
+ export default function AskQuestionModal({ isOpen, onClose, context }: AskQuestionModalProps) {
22
+ const [messages, setMessages] = useState<Message[]>([]);
23
+ const [input, setInput] = useState('');
24
+ const [loading, setLoading] = useState(false);
25
+
26
+ const handleSubmit = async (e: React.FormEvent) => {
27
+ e.preventDefault();
28
+ if (!input.trim() || loading) return;
29
+
30
+ const userMessage = input.trim();
31
+ setInput('');
32
+ setMessages(prev => [...prev, { role: 'user', content: userMessage }]);
33
+ setLoading(true);
34
+
35
+ try {
36
+ const formData = new FormData();
37
+ formData.append('text', `
38
+ Based on this content I just learned:
39
+ ---
40
+ ${context.substring(0, 2000)}
41
+ ---
42
+
43
+ My question: ${userMessage}
44
+
45
+ Please answer my question in a helpful, educational way. Use simple language and examples if needed.
46
+ `);
47
+
48
+ const res = await fetch('/api/analyze', {
49
+ method: 'POST',
50
+ body: formData,
51
+ });
52
+
53
+ if (!res.ok) throw new Error('Failed to get response');
54
+
55
+ const data = await res.json();
56
+ const cleanResponse = data.result?.replace(/\[\[TOPICS?:.*?\]\]/gi, '').trim() || 'Sorry, I could not generate a response.';
57
+
58
+ setMessages(prev => [...prev, { role: 'assistant', content: cleanResponse }]);
59
+ } catch (error) {
60
+ setMessages(prev => [...prev, { role: 'assistant', content: 'Sorry, something went wrong. Please try again.' }]);
61
+ } finally {
62
+ setLoading(false);
63
+ }
64
+ };
65
+
66
+ if (!isOpen) return null;
67
+
68
+ return (
69
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm animate-fade-in-up">
70
+ <div className="w-full max-w-2xl bg-white dark:bg-neutral-900 rounded-2xl shadow-2xl border border-gray-200 dark:border-neutral-800 flex flex-col max-h-[80vh]">
71
+
72
+ {/* Header */}
73
+ <div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-neutral-800">
74
+ <div className="flex items-center gap-2">
75
+ <MessageCircle size={20} className="text-sky-500" />
76
+ <h2 className="text-lg font-semibold text-gray-900 dark:text-white">Ask a Question</h2>
77
+ </div>
78
+ <button
79
+ onClick={onClose}
80
+ className="p-2 hover:bg-gray-100 dark:hover:bg-neutral-800 rounded-full transition-colors"
81
+ >
82
+ <X size={20} className="text-gray-500" />
83
+ </button>
84
+ </div>
85
+
86
+ {/* Messages */}
87
+ <div className="flex-1 overflow-y-auto p-4 space-y-4">
88
+ {messages.length === 0 && (
89
+ <div className="text-center py-12 text-gray-400">
90
+ <MessageCircle size={48} className="mx-auto mb-4 opacity-50" />
91
+ <p>Ask any question about the content you just analyzed.</p>
92
+ </div>
93
+ )}
94
+
95
+ {messages.map((msg, idx) => (
96
+ <div
97
+ key={idx}
98
+ className={clsx(
99
+ "flex",
100
+ msg.role === 'user' ? "justify-end" : "justify-start"
101
+ )}
102
+ >
103
+ <div
104
+ className={clsx(
105
+ "max-w-[80%] rounded-2xl px-4 py-3",
106
+ msg.role === 'user'
107
+ ? "bg-sky-500 text-white rounded-br-md"
108
+ : "bg-gray-100 dark:bg-neutral-800 text-gray-800 dark:text-gray-200 rounded-bl-md"
109
+ )}
110
+ >
111
+ {msg.role === 'assistant' ? (
112
+ <div className="prose prose-sm dark:prose-invert max-w-none">
113
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>
114
+ {msg.content}
115
+ </ReactMarkdown>
116
+ </div>
117
+ ) : (
118
+ <p>{msg.content}</p>
119
+ )}
120
+ </div>
121
+ </div>
122
+ ))}
123
+
124
+ {loading && (
125
+ <div className="flex justify-start">
126
+ <div className="bg-gray-100 dark:bg-neutral-800 rounded-2xl rounded-bl-md px-4 py-3">
127
+ <Loader2 size={20} className="animate-spin text-gray-400" />
128
+ </div>
129
+ </div>
130
+ )}
131
+ </div>
132
+
133
+ {/* Input */}
134
+ <form onSubmit={handleSubmit} className="p-4 border-t border-gray-200 dark:border-neutral-800">
135
+ <div className="flex gap-3">
136
+ <input
137
+ type="text"
138
+ value={input}
139
+ onChange={(e) => setInput(e.target.value)}
140
+ placeholder="Type your question..."
141
+ className="flex-1 px-4 py-3 bg-gray-100 dark:bg-neutral-800 border border-gray-200 dark:border-neutral-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-transparent text-gray-900 dark:text-white placeholder-gray-400"
142
+ disabled={loading}
143
+ />
144
+ <button
145
+ type="submit"
146
+ disabled={loading || !input.trim()}
147
+ className={clsx(
148
+ "px-4 py-3 rounded-xl font-medium transition-all flex items-center gap-2",
149
+ loading || !input.trim()
150
+ ? "bg-gray-200 dark:bg-neutral-700 text-gray-400 cursor-not-allowed"
151
+ : "bg-sky-500 text-white hover:bg-sky-600"
152
+ )}
153
+ >
154
+ {loading ? <Loader2 size={18} className="animate-spin" /> : <Send size={18} />}
155
+ </button>
156
+ </div>
157
+ </form>
158
+ </div>
159
+ </div>
160
+ );
161
+ }
src/components/CameraModal.tsx ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 'use client';
3
+
4
+ import { useState, useRef, useEffect } from 'react';
5
+ import { X, Camera, RotateCcw, Check } from 'lucide-react';
6
+ import clsx from 'clsx';
7
+
8
+ interface CameraModalProps {
9
+ isOpen: boolean;
10
+ onClose: () => void;
11
+ onCapture: (file: File) => void;
12
+ }
13
+
14
+ export default function CameraModal({ isOpen, onClose, onCapture }: CameraModalProps) {
15
+ const videoRef = useRef<HTMLVideoElement>(null);
16
+ const canvasRef = useRef<HTMLCanvasElement>(null);
17
+ const [stream, setStream] = useState<MediaStream | null>(null);
18
+ const [capturedImage, setCapturedImage] = useState<string | null>(null);
19
+ const [error, setError] = useState<string | null>(null);
20
+ const [facingMode, setFacingMode] = useState<'user' | 'environment'>('environment');
21
+
22
+ useEffect(() => {
23
+ if (isOpen) {
24
+ startCamera();
25
+ } else {
26
+ stopCamera();
27
+ setCapturedImage(null);
28
+ }
29
+
30
+ return () => stopCamera();
31
+ }, [isOpen, facingMode]);
32
+
33
+ const startCamera = async () => {
34
+ try {
35
+ setError(null);
36
+ const mediaStream = await navigator.mediaDevices.getUserMedia({
37
+ video: {
38
+ facingMode: facingMode,
39
+ width: { ideal: 1280 },
40
+ height: { ideal: 720 }
41
+ },
42
+ audio: false
43
+ });
44
+ setStream(mediaStream);
45
+ if (videoRef.current) {
46
+ videoRef.current.srcObject = mediaStream;
47
+ }
48
+ } catch (err) {
49
+ console.error('Camera error:', err);
50
+ setError('Unable to access camera. Please check permissions.');
51
+ }
52
+ };
53
+
54
+ const stopCamera = () => {
55
+ if (stream) {
56
+ stream.getTracks().forEach(track => track.stop());
57
+ setStream(null);
58
+ }
59
+ };
60
+
61
+ const switchCamera = () => {
62
+ setFacingMode(prev => prev === 'user' ? 'environment' : 'user');
63
+ };
64
+
65
+ const capturePhoto = () => {
66
+ if (videoRef.current && canvasRef.current) {
67
+ const video = videoRef.current;
68
+ const canvas = canvasRef.current;
69
+
70
+ canvas.width = video.videoWidth;
71
+ canvas.height = video.videoHeight;
72
+
73
+ const ctx = canvas.getContext('2d');
74
+ if (ctx) {
75
+ ctx.drawImage(video, 0, 0);
76
+ const imageDataUrl = canvas.toDataURL('image/jpeg', 0.9);
77
+ setCapturedImage(imageDataUrl);
78
+ stopCamera();
79
+ }
80
+ }
81
+ };
82
+
83
+ const retake = () => {
84
+ setCapturedImage(null);
85
+ startCamera();
86
+ };
87
+
88
+ const confirmCapture = () => {
89
+ if (capturedImage && canvasRef.current) {
90
+ canvasRef.current.toBlob((blob) => {
91
+ if (blob) {
92
+ const file = new File([blob], `photo_${Date.now()}.jpg`, { type: 'image/jpeg' });
93
+ onCapture(file);
94
+ onClose();
95
+ }
96
+ }, 'image/jpeg', 0.9);
97
+ }
98
+ };
99
+
100
+ if (!isOpen) return null;
101
+
102
+ return (
103
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/90 backdrop-blur-sm animate-fade-in-up">
104
+ <div className="w-full max-w-xl bg-neutral-900 rounded-2xl shadow-2xl border border-neutral-800 overflow-hidden">
105
+
106
+ {/* Header */}
107
+ <div className="flex items-center justify-between p-4 border-b border-neutral-800">
108
+ <div className="flex items-center gap-2">
109
+ <Camera size={20} className="text-sky-500" />
110
+ <h2 className="text-lg font-semibold text-white">Take Photo</h2>
111
+ </div>
112
+ <button
113
+ onClick={onClose}
114
+ className="p-2 hover:bg-neutral-800 rounded-full"
115
+ >
116
+ <X size={20} className="text-gray-400" />
117
+ </button>
118
+ </div>
119
+
120
+ {/* Camera View */}
121
+ <div className="relative aspect-[4/3] bg-black">
122
+ {error ? (
123
+ <div className="absolute inset-0 flex items-center justify-center text-center p-6">
124
+ <div>
125
+ <Camera size={48} className="mx-auto mb-4 text-gray-600" />
126
+ <p className="text-gray-400">{error}</p>
127
+ </div>
128
+ </div>
129
+ ) : capturedImage ? (
130
+ <img
131
+ src={capturedImage}
132
+ alt="Captured"
133
+ className="w-full h-full object-contain"
134
+ />
135
+ ) : (
136
+ <video
137
+ ref={videoRef}
138
+ autoPlay
139
+ playsInline
140
+ muted
141
+ className="w-full h-full object-cover"
142
+ />
143
+ )}
144
+
145
+ <canvas ref={canvasRef} className="hidden" />
146
+ </div>
147
+
148
+ {/* Controls */}
149
+ <div className="p-4 flex items-center justify-center gap-4">
150
+ {!capturedImage ? (
151
+ <>
152
+ {/* Switch Camera */}
153
+ <button
154
+ onClick={switchCamera}
155
+ className="p-3 bg-neutral-800 hover:bg-neutral-700 rounded-full transition-colors"
156
+ title="Switch Camera"
157
+ >
158
+ <RotateCcw size={20} className="text-white" />
159
+ </button>
160
+
161
+ {/* Capture Button */}
162
+ <button
163
+ onClick={capturePhoto}
164
+ disabled={!stream}
165
+ className={clsx(
166
+ "w-16 h-16 rounded-full border-4 border-white flex items-center justify-center transition-all",
167
+ stream
168
+ ? "bg-white hover:bg-gray-200 active:scale-95"
169
+ : "bg-gray-600 border-gray-600 cursor-not-allowed"
170
+ )}
171
+ >
172
+ <div className="w-12 h-12 rounded-full bg-sky-500"></div>
173
+ </button>
174
+
175
+ {/* Placeholder for symmetry */}
176
+ <div className="w-11"></div>
177
+ </>
178
+ ) : (
179
+ <>
180
+ {/* Retake */}
181
+ <button
182
+ onClick={retake}
183
+ className="flex-1 py-3 bg-neutral-800 text-white rounded-xl font-medium hover:bg-neutral-700 flex items-center justify-center gap-2"
184
+ >
185
+ <RotateCcw size={18} />
186
+ Retake
187
+ </button>
188
+
189
+ {/* Confirm */}
190
+ <button
191
+ onClick={confirmCapture}
192
+ className="flex-1 py-3 bg-sky-500 text-white rounded-xl font-medium hover:bg-sky-600 flex items-center justify-center gap-2"
193
+ >
194
+ <Check size={18} />
195
+ Use Photo
196
+ </button>
197
+ </>
198
+ )}
199
+ </div>
200
+ </div>
201
+ </div>
202
+ );
203
+ }
src/components/QuizModal.tsx ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 'use client';
3
+
4
+ import { useState } from 'react';
5
+ import { X, Play, Check, XCircle, Trophy, Loader2, BookOpen, RotateCcw } from 'lucide-react';
6
+ import clsx from 'clsx';
7
+
8
+ interface QuizModalProps {
9
+ isOpen: boolean;
10
+ onClose: () => void;
11
+ context: string;
12
+ }
13
+
14
+ interface Question {
15
+ question: string;
16
+ options?: string[];
17
+ correctAnswer: string;
18
+ type: 'mcq' | 'written';
19
+ }
20
+
21
+ type QuizState = 'config' | 'loading' | 'quiz' | 'results';
22
+
23
+ export default function QuizModal({ isOpen, onClose, context }: QuizModalProps) {
24
+ const [state, setState] = useState<QuizState>('config');
25
+ const [numQuestions, setNumQuestions] = useState(5);
26
+ const [questionType, setQuestionType] = useState<'mcq' | 'written' | 'mixed'>('mcq');
27
+ const [numOptions, setNumOptions] = useState(4);
28
+
29
+ const [questions, setQuestions] = useState<Question[]>([]);
30
+ const [currentIndex, setCurrentIndex] = useState(0);
31
+ const [answers, setAnswers] = useState<Record<number, string>>({});
32
+ const [showAnswer, setShowAnswer] = useState(false);
33
+ const [score, setScore] = useState(0);
34
+
35
+ const generateQuiz = async () => {
36
+ setState('loading');
37
+
38
+ try {
39
+ const prompt = `
40
+ Based on this educational content:
41
+ ---
42
+ ${context.substring(0, 3000)}
43
+ ---
44
+
45
+ Generate a quiz with exactly ${numQuestions} questions.
46
+ Question type: ${questionType === 'mcq' ? `Multiple Choice with ${numOptions} options` : questionType === 'written' ? 'Written/Short Answer (no options)' : 'Mix of MCQ and Written'}
47
+
48
+ IMPORTANT: Return ONLY valid JSON in this exact format, no extra text:
49
+ {
50
+ "questions": [
51
+ {
52
+ "question": "Question text here?",
53
+ "options": ["Option A", "Option B", "Option C", "Option D"],
54
+ "correctAnswer": "Option A",
55
+ "type": "mcq"
56
+ },
57
+ {
58
+ "question": "Written question here?",
59
+ "correctAnswer": "The expected answer",
60
+ "type": "written"
61
+ }
62
+ ]
63
+ }
64
+
65
+ For MCQ: include "options" array with exactly ${numOptions} choices.
66
+ For written: omit "options" field.
67
+ Make questions educational and relevant to the content.
68
+ `;
69
+
70
+ const formData = new FormData();
71
+ formData.append('text', prompt);
72
+
73
+ const res = await fetch('/api/analyze', {
74
+ method: 'POST',
75
+ body: formData,
76
+ });
77
+
78
+ if (!res.ok) throw new Error('Failed to generate quiz');
79
+
80
+ const data = await res.json();
81
+ let responseText = data.result || '';
82
+
83
+ responseText = responseText.replace(/\[\[TOPICS?:.*?\]\]/gi, '').trim();
84
+
85
+ const jsonMatch = responseText.match(/\{[\s\S]*"questions"[\s\S]*\}/);
86
+ if (!jsonMatch) throw new Error('Invalid response format');
87
+
88
+ const parsed = JSON.parse(jsonMatch[0]);
89
+
90
+ if (!parsed.questions || !Array.isArray(parsed.questions)) {
91
+ throw new Error('Invalid quiz format');
92
+ }
93
+
94
+ setQuestions(parsed.questions);
95
+ setCurrentIndex(0);
96
+ setAnswers({});
97
+ setScore(0);
98
+ setState('quiz');
99
+
100
+ } catch (error) {
101
+ console.error('Quiz generation error:', error);
102
+ const fallbackQuestions: Question[] = [
103
+ {
104
+ question: "What was the main topic covered in this content?",
105
+ type: 'written',
106
+ correctAnswer: "Review the content for the answer"
107
+ }
108
+ ];
109
+ setQuestions(fallbackQuestions);
110
+ setState('quiz');
111
+ }
112
+ };
113
+
114
+ const selectAnswer = (answer: string) => {
115
+ setAnswers(prev => ({ ...prev, [currentIndex]: answer }));
116
+ setShowAnswer(true);
117
+
118
+ const currentQ = questions[currentIndex];
119
+ if (currentQ.type === 'mcq' && answer === currentQ.correctAnswer) {
120
+ setScore(prev => prev + 1);
121
+ }
122
+ };
123
+
124
+ const nextQuestion = () => {
125
+ setShowAnswer(false);
126
+ if (currentIndex < questions.length - 1) {
127
+ setCurrentIndex(prev => prev + 1);
128
+ } else {
129
+ setState('results');
130
+ }
131
+ };
132
+
133
+ const resetQuiz = () => {
134
+ setState('config');
135
+ setQuestions([]);
136
+ setCurrentIndex(0);
137
+ setAnswers({});
138
+ setScore(0);
139
+ setShowAnswer(false);
140
+ };
141
+
142
+ if (!isOpen) return null;
143
+
144
+ const currentQuestion = questions[currentIndex];
145
+
146
+ return (
147
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm animate-fade-in-up">
148
+ <div className="w-full max-w-xl bg-white dark:bg-neutral-900 rounded-2xl shadow-2xl border border-gray-200 dark:border-neutral-800 overflow-hidden">
149
+
150
+ {/* Header */}
151
+ <div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-neutral-800">
152
+ <div className="flex items-center gap-2">
153
+ <BookOpen size={20} className="text-sky-500" />
154
+ <h2 className="text-lg font-semibold text-gray-900 dark:text-white">Quiz Me</h2>
155
+ </div>
156
+ <button onClick={onClose} className="p-2 hover:bg-gray-100 dark:hover:bg-neutral-800 rounded-full">
157
+ <X size={20} className="text-gray-500" />
158
+ </button>
159
+ </div>
160
+
161
+ <div className="p-6">
162
+ {/* Configuration State */}
163
+ {state === 'config' && (
164
+ <div className="space-y-5">
165
+ {/* Number of Questions - Input Field */}
166
+ <div>
167
+ <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
168
+ Number of Questions
169
+ </label>
170
+ <input
171
+ type="number"
172
+ min={1}
173
+ max={50}
174
+ value={numQuestions}
175
+ onChange={(e) => setNumQuestions(Math.max(1, Math.min(50, parseInt(e.target.value) || 1)))}
176
+ className="w-full px-4 py-3 bg-gray-50 dark:bg-neutral-800 border border-gray-200 dark:border-neutral-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-transparent text-gray-900 dark:text-white text-center text-lg font-medium"
177
+ placeholder="Enter number (1-50)"
178
+ />
179
+ <p className="text-xs text-gray-400 mt-1 text-center">Enter any number between 1-50</p>
180
+ </div>
181
+
182
+ {/* Question Type */}
183
+ <div>
184
+ <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
185
+ Question Type
186
+ </label>
187
+ <div className="flex gap-2">
188
+ {[
189
+ { value: 'mcq', label: 'Multiple Choice' },
190
+ { value: 'written', label: 'Written' },
191
+ { value: 'mixed', label: 'Mixed' }
192
+ ].map(opt => (
193
+ <button
194
+ key={opt.value}
195
+ onClick={() => setQuestionType(opt.value as any)}
196
+ className={clsx(
197
+ "flex-1 py-3 rounded-xl font-medium transition-all text-sm",
198
+ questionType === opt.value
199
+ ? "bg-sky-500 text-white"
200
+ : "bg-gray-100 dark:bg-neutral-800 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-neutral-700"
201
+ )}
202
+ >
203
+ {opt.label}
204
+ </button>
205
+ ))}
206
+ </div>
207
+ </div>
208
+
209
+ {/* Number of Options - Input Field */}
210
+ {(questionType === 'mcq' || questionType === 'mixed') && (
211
+ <div>
212
+ <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
213
+ Number of Options (MCQ)
214
+ </label>
215
+ <input
216
+ type="number"
217
+ min={2}
218
+ max={6}
219
+ value={numOptions}
220
+ onChange={(e) => setNumOptions(Math.max(2, Math.min(6, parseInt(e.target.value) || 2)))}
221
+ className="w-full px-4 py-3 bg-gray-50 dark:bg-neutral-800 border border-gray-200 dark:border-neutral-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-transparent text-gray-900 dark:text-white text-center text-lg font-medium"
222
+ placeholder="Enter number (2-6)"
223
+ />
224
+ <p className="text-xs text-gray-400 mt-1 text-center">Enter any number between 2-6</p>
225
+ </div>
226
+ )}
227
+
228
+ <button
229
+ onClick={generateQuiz}
230
+ disabled={numQuestions < 1}
231
+ className="w-full py-4 bg-sky-500 text-white rounded-xl font-semibold hover:bg-sky-600 transition-colors flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
232
+ >
233
+ <Play size={18} />
234
+ Start Quiz
235
+ </button>
236
+ </div>
237
+ )}
238
+
239
+ {/* Loading State */}
240
+ {state === 'loading' && (
241
+ <div className="text-center py-12">
242
+ <Loader2 size={48} className="mx-auto mb-4 text-sky-500 animate-spin" />
243
+ <p className="text-gray-500">Generating {numQuestions} questions...</p>
244
+ </div>
245
+ )}
246
+
247
+ {/* Quiz State */}
248
+ {state === 'quiz' && currentQuestion && (
249
+ <div className="space-y-6">
250
+ <div className="flex justify-between text-sm text-gray-400">
251
+ <span>Question {currentIndex + 1} of {questions.length}</span>
252
+ <span>{currentQuestion.type === 'mcq' ? 'MCQ' : 'Written'}</span>
253
+ </div>
254
+
255
+ <div className="w-full bg-gray-200 dark:bg-neutral-700 rounded-full h-2">
256
+ <div
257
+ className="bg-sky-500 h-2 rounded-full transition-all"
258
+ style={{ width: `${((currentIndex + 1) / questions.length) * 100}%` }}
259
+ />
260
+ </div>
261
+
262
+ <p className="text-lg font-medium text-gray-900 dark:text-white">
263
+ {currentQuestion.question}
264
+ </p>
265
+
266
+ {currentQuestion.type === 'mcq' && currentQuestion.options && (
267
+ <div className="space-y-3">
268
+ {currentQuestion.options.map((option, idx) => (
269
+ <button
270
+ key={idx}
271
+ onClick={() => !showAnswer && selectAnswer(option)}
272
+ disabled={showAnswer}
273
+ className={clsx(
274
+ "w-full p-4 rounded-xl text-left transition-all border-2",
275
+ showAnswer
276
+ ? option === currentQuestion.correctAnswer
277
+ ? "border-green-500 bg-green-50 dark:bg-green-900/20"
278
+ : answers[currentIndex] === option
279
+ ? "border-red-500 bg-red-50 dark:bg-red-900/20"
280
+ : "border-gray-200 dark:border-neutral-700"
281
+ : "border-gray-200 dark:border-neutral-700 hover:border-sky-500 hover:bg-gray-50 dark:hover:bg-neutral-800"
282
+ )}
283
+ >
284
+ <div className="flex items-center justify-between">
285
+ <span className="text-gray-800 dark:text-gray-200">{option}</span>
286
+ {showAnswer && option === currentQuestion.correctAnswer && (
287
+ <Check size={20} className="text-green-500" />
288
+ )}
289
+ {showAnswer && answers[currentIndex] === option && option !== currentQuestion.correctAnswer && (
290
+ <XCircle size={20} className="text-red-500" />
291
+ )}
292
+ </div>
293
+ </button>
294
+ ))}
295
+ </div>
296
+ )}
297
+
298
+ {currentQuestion.type === 'written' && (
299
+ <div className="space-y-4">
300
+ <textarea
301
+ placeholder="Type your answer..."
302
+ value={answers[currentIndex] || ''}
303
+ onChange={(e) => setAnswers(prev => ({ ...prev, [currentIndex]: e.target.value }))}
304
+ disabled={showAnswer}
305
+ className="w-full p-4 bg-gray-100 dark:bg-neutral-800 border border-gray-200 dark:border-neutral-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-sky-500 text-gray-900 dark:text-white min-h-[100px] resize-none"
306
+ />
307
+ {!showAnswer && (
308
+ <button
309
+ onClick={() => setShowAnswer(true)}
310
+ disabled={!answers[currentIndex]?.trim()}
311
+ className="w-full py-3 bg-sky-500 text-white rounded-xl font-medium hover:bg-sky-600 disabled:opacity-50 disabled:cursor-not-allowed"
312
+ >
313
+ Check Answer
314
+ </button>
315
+ )}
316
+ {showAnswer && (
317
+ <div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-xl border border-green-200 dark:border-green-800">
318
+ <p className="text-sm text-gray-500 mb-1">Expected Answer:</p>
319
+ <p className="text-green-700 dark:text-green-300">{currentQuestion.correctAnswer}</p>
320
+ </div>
321
+ )}
322
+ </div>
323
+ )}
324
+
325
+ {showAnswer && (
326
+ <button
327
+ onClick={nextQuestion}
328
+ className="w-full py-3 bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded-xl font-medium hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors"
329
+ >
330
+ {currentIndex < questions.length - 1 ? 'Next Question' : 'See Results'}
331
+ </button>
332
+ )}
333
+ </div>
334
+ )}
335
+
336
+ {/* Results State */}
337
+ {state === 'results' && (
338
+ <div className="text-center space-y-6">
339
+ <Trophy size={64} className="mx-auto text-yellow-500" />
340
+ <div>
341
+ <h3 className="text-2xl font-bold text-gray-900 dark:text-white">Quiz Complete!</h3>
342
+ <p className="text-gray-500 mt-2">
343
+ You scored <span className="text-sky-500 font-bold">{score}</span> out of {questions.filter(q => q.type === 'mcq').length} MCQ questions
344
+ </p>
345
+ </div>
346
+
347
+ <div className="flex gap-3">
348
+ <button
349
+ onClick={resetQuiz}
350
+ className="flex-1 py-3 bg-gray-100 dark:bg-neutral-800 text-gray-700 dark:text-gray-300 rounded-xl font-medium hover:bg-gray-200 dark:hover:bg-neutral-700 flex items-center justify-center gap-2"
351
+ >
352
+ <RotateCcw size={18} />
353
+ Try Again
354
+ </button>
355
+ <button
356
+ onClick={onClose}
357
+ className="flex-1 py-3 bg-sky-500 text-white rounded-xl font-medium hover:bg-sky-600"
358
+ >
359
+ Done
360
+ </button>
361
+ </div>
362
+ </div>
363
+ )}
364
+ </div>
365
+ </div>
366
+ </div>
367
+ );
368
+ }
src/components/WikipediaCard.tsx ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 'use client';
3
+
4
+ import { useEffect, useState } from 'react';
5
+ import { ExternalLink, BookOpen, ChevronLeft, ChevronRight, Search, AlertCircle } from 'lucide-react';
6
+ import clsx from 'clsx';
7
+
8
+ interface WikipediaCardProps {
9
+ topics: string[];
10
+ }
11
+
12
+ interface WikiData {
13
+ title: string;
14
+ extract: string;
15
+ thumbnail?: {
16
+ source: string;
17
+ };
18
+ content_urls?: {
19
+ desktop: {
20
+ page: string;
21
+ };
22
+ };
23
+ description?: string;
24
+ }
25
+
26
+ const TOPICS_PER_PAGE = 6;
27
+
28
+ export default function WikipediaCard({ topics }: WikipediaCardProps) {
29
+ const [activeIndex, setActiveIndex] = useState(0);
30
+ const [topicPage, setTopicPage] = useState(0);
31
+ const [data, setData] = useState<WikiData | null>(null);
32
+ const [loading, setLoading] = useState(false);
33
+ const [error, setError] = useState(false);
34
+
35
+ const currentTopic = topics[activeIndex] || null;
36
+ const totalTopicPages = Math.ceil(topics.length / TOPICS_PER_PAGE);
37
+ const visibleTopics = topics.slice(topicPage * TOPICS_PER_PAGE, (topicPage + 1) * TOPICS_PER_PAGE);
38
+
39
+ useEffect(() => {
40
+ if (!currentTopic) {
41
+ setData(null);
42
+ return;
43
+ }
44
+
45
+ const fetchWiki = async () => {
46
+ setLoading(true);
47
+ setError(false);
48
+ setData(null);
49
+
50
+ // Try direct page summary first
51
+ try {
52
+ const directUrl = `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(currentTopic)}`;
53
+ const directRes = await fetch(directUrl);
54
+
55
+ if (directRes.ok) {
56
+ const json = await directRes.json();
57
+ if (json.type !== 'https://mediawiki.org/wiki/HyperSwitch/errors/not_found') {
58
+ setData(json);
59
+ setLoading(false);
60
+ return;
61
+ }
62
+ }
63
+ } catch (e) {
64
+ console.log("Direct fetch failed, trying search...");
65
+ }
66
+
67
+ // Fallback: Use Wikipedia search API
68
+ try {
69
+ const searchUrl = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(currentTopic)}&format=json&origin=*&srlimit=1`;
70
+ const searchRes = await fetch(searchUrl);
71
+ const searchData = await searchRes.json();
72
+
73
+ if (searchData.query?.search?.length > 0) {
74
+ const searchTitle = searchData.query.search[0].title;
75
+ const summaryUrl = `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(searchTitle)}`;
76
+ const summaryRes = await fetch(summaryUrl);
77
+
78
+ if (summaryRes.ok) {
79
+ const summaryJson = await summaryRes.json();
80
+ setData(summaryJson);
81
+ setLoading(false);
82
+ return;
83
+ }
84
+ }
85
+ throw new Error('No results');
86
+ } catch (err) {
87
+ setError(true);
88
+ } finally {
89
+ setLoading(false);
90
+ }
91
+ };
92
+
93
+ fetchWiki();
94
+ }, [currentTopic]);
95
+
96
+ // When active topic changes, ensure its page is visible
97
+ useEffect(() => {
98
+ const newPage = Math.floor(activeIndex / TOPICS_PER_PAGE);
99
+ if (newPage !== topicPage) {
100
+ setTopicPage(newPage);
101
+ }
102
+ }, [activeIndex]);
103
+
104
+ if (topics.length === 0) return null;
105
+
106
+ const wikiUrl = data?.content_urls?.desktop?.page;
107
+
108
+ const goNext = () => {
109
+ if (activeIndex < topics.length - 1) setActiveIndex(activeIndex + 1);
110
+ };
111
+
112
+ const goPrev = () => {
113
+ if (activeIndex > 0) setActiveIndex(activeIndex - 1);
114
+ };
115
+
116
+ const nextTopicPage = () => {
117
+ if (topicPage < totalTopicPages - 1) setTopicPage(topicPage + 1);
118
+ };
119
+
120
+ const prevTopicPage = () => {
121
+ if (topicPage > 0) setTopicPage(topicPage - 1);
122
+ };
123
+
124
+ return (
125
+ <div className={clsx(
126
+ "rounded-2xl bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800 p-5 h-fit sticky top-6 transition-all duration-300",
127
+ loading ? "opacity-60" : "opacity-100"
128
+ )}>
129
+ {/* Header */}
130
+ <div className="flex items-center justify-between mb-3">
131
+ <div className="flex items-center gap-2 text-gray-700 dark:text-gray-300 text-sm font-medium">
132
+ <BookOpen size={16} />
133
+ Wikipedia
134
+ </div>
135
+ <div className="text-xs text-gray-400 bg-gray-100 dark:bg-neutral-800 px-2 py-1 rounded-full">
136
+ {activeIndex + 1} / {topics.length}
137
+ </div>
138
+ </div>
139
+
140
+ {/* Topic Pills with Pagination */}
141
+ <div className="mb-4">
142
+ {/* Pagination Controls */}
143
+ {totalTopicPages > 1 && (
144
+ <div className="flex items-center justify-between mb-2">
145
+ <button
146
+ onClick={prevTopicPage}
147
+ disabled={topicPage === 0}
148
+ className={clsx(
149
+ "p-1 rounded",
150
+ topicPage === 0 ? "text-gray-300 dark:text-neutral-600" : "text-gray-500 hover:bg-gray-100 dark:hover:bg-neutral-800"
151
+ )}
152
+ >
153
+ <ChevronLeft size={16} />
154
+ </button>
155
+ <span className="text-xs text-gray-400">
156
+ Page {topicPage + 1} of {totalTopicPages}
157
+ </span>
158
+ <button
159
+ onClick={nextTopicPage}
160
+ disabled={topicPage >= totalTopicPages - 1}
161
+ className={clsx(
162
+ "p-1 rounded",
163
+ topicPage >= totalTopicPages - 1 ? "text-gray-300 dark:text-neutral-600" : "text-gray-500 hover:bg-gray-100 dark:hover:bg-neutral-800"
164
+ )}
165
+ >
166
+ <ChevronRight size={16} />
167
+ </button>
168
+ </div>
169
+ )}
170
+
171
+ {/* Topic Pills */}
172
+ <div className="flex flex-wrap gap-1.5">
173
+ {visibleTopics.map((topic, idx) => {
174
+ const globalIndex = topicPage * TOPICS_PER_PAGE + idx;
175
+ return (
176
+ <button
177
+ key={topic}
178
+ onClick={() => setActiveIndex(globalIndex)}
179
+ className={clsx(
180
+ "px-2.5 py-1 rounded-full text-xs font-medium transition-all",
181
+ globalIndex === activeIndex
182
+ ? "bg-sky-500 text-white"
183
+ : "bg-gray-100 dark:bg-neutral-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-neutral-700"
184
+ )}
185
+ >
186
+ {topic}
187
+ </button>
188
+ );
189
+ })}
190
+ </div>
191
+ </div>
192
+
193
+ {/* Content */}
194
+ {loading ? (
195
+ <div className="space-y-3 animate-pulse">
196
+ <div className="h-24 bg-gray-100 dark:bg-neutral-800 rounded-xl"></div>
197
+ <div className="h-3 bg-gray-100 dark:bg-neutral-800 rounded w-3/4"></div>
198
+ <div className="h-3 bg-gray-100 dark:bg-neutral-800 rounded w-full"></div>
199
+ </div>
200
+ ) : error ? (
201
+ <div className="text-center py-6">
202
+ <AlertCircle size={20} className="mx-auto mb-2 text-gray-400" />
203
+ <p className="text-xs text-gray-500">No article found for "{currentTopic}"</p>
204
+ <a
205
+ href={`https://en.wikipedia.org/wiki/Special:Search?search=${encodeURIComponent(currentTopic || '')}`}
206
+ target="_blank"
207
+ rel="noopener noreferrer"
208
+ className="inline-flex items-center gap-1 mt-2 text-xs text-sky-500 hover:underline"
209
+ >
210
+ <Search size={10} /> Search manually
211
+ </a>
212
+ </div>
213
+ ) : data ? (
214
+ <div className="space-y-3">
215
+ {data.thumbnail && (
216
+ <img
217
+ src={data.thumbnail.source}
218
+ alt={data.title}
219
+ className="w-full h-24 object-cover rounded-xl bg-gray-100 dark:bg-neutral-800"
220
+ />
221
+ )}
222
+
223
+ <h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
224
+ {data.title}
225
+ </h3>
226
+
227
+ {data.description && (
228
+ <p className="text-xs text-gray-400 uppercase tracking-wide">
229
+ {data.description}
230
+ </p>
231
+ )}
232
+
233
+ <p className="text-gray-600 dark:text-gray-400 text-xs leading-relaxed line-clamp-4">
234
+ {data.extract}
235
+ </p>
236
+
237
+ {wikiUrl && (
238
+ <a
239
+ href={wikiUrl}
240
+ target="_blank"
241
+ rel="noopener noreferrer"
242
+ className="flex items-center gap-2 p-2.5 bg-gray-50 dark:bg-neutral-800 rounded-lg hover:bg-gray-100 dark:hover:bg-neutral-700 transition-colors border border-gray-200 dark:border-neutral-700"
243
+ >
244
+ <span className="text-base">πŸ“–</span>
245
+ <div className="flex-1 min-w-0">
246
+ <p className="text-xs font-medium text-gray-800 dark:text-gray-200">Read Full Article</p>
247
+ <p className="text-[10px] text-gray-400 truncate">{wikiUrl}</p>
248
+ </div>
249
+ <ExternalLink size={12} className="text-gray-400 flex-shrink-0" />
250
+ </a>
251
+ )}
252
+ </div>
253
+ ) : null}
254
+
255
+ {/* Main Navigation */}
256
+ <div className="flex justify-between mt-4 pt-3 border-t border-gray-100 dark:border-neutral-800">
257
+ <button
258
+ onClick={goPrev}
259
+ disabled={activeIndex === 0}
260
+ className={clsx(
261
+ "flex items-center gap-1 text-xs font-medium px-2 py-1.5 rounded-lg transition-all",
262
+ activeIndex === 0
263
+ ? "text-gray-300 dark:text-neutral-600 cursor-not-allowed"
264
+ : "text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-neutral-800"
265
+ )}
266
+ >
267
+ <ChevronLeft size={14} /> Prev
268
+ </button>
269
+ <button
270
+ onClick={goNext}
271
+ disabled={activeIndex === topics.length - 1}
272
+ className={clsx(
273
+ "flex items-center gap-1 text-xs font-medium px-2 py-1.5 rounded-lg transition-all",
274
+ activeIndex === topics.length - 1
275
+ ? "text-gray-300 dark:text-neutral-600 cursor-not-allowed"
276
+ : "text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-neutral-800"
277
+ )}
278
+ >
279
+ Next <ChevronRight size={14} />
280
+ </button>
281
+ </div>
282
+ </div>
283
+ );
284
+ }
src/prompts/concept_essay.md ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # The Socratic Lens: A Design Philosophy for AI Education
2
+
3
+ ## Introduction
4
+ In the age of generative AI, the temptation to use tools as "answer engines" is overwhelming. However, true education lies not in the answer, but in the journey to finding it. This document outlines the conceptual framework for "The Socratic Lens," an AI agent designed to act as an **Ethical Tutor** rather than a homework solver.
5
+
6
+ ## The Core Problem
7
+ Students face two extremes:
8
+ 1. **Boredom**: Textbooks are dense, unengaging, and visually fatigue-inducing.
9
+ 2. **Shortcuts**: AI tools that solve problems instantly rob students of the "aha!" moment, stunting critical thinking.
10
+
11
+ ## The Solution: "Lumina" (The Project Persona)
12
+ We introduce "Lumina," an AI persona that bridges the gap. Lumina is not a calculator; it is a **Lens**. It takes the raw, chaotic input of the physical world (handwritten notes, dense textbook pages, audio lectures) and refracts it into structured, beautiful, and digestible knowledge.
13
+
14
+ ### 1. Aesthetic as Function (The "Pixel" Look)
15
+ Why "Google Pixel OS" aesthetics?
16
+ Cognitive load is real. When a student sees a wall of text, their brain shuts down. By adopting the principles of **Material You**β€”spaciousness, rounded corners, clear hierarchy, and calm colorsβ€”we reduce anxiety.
17
+ * **Cards (Blockquotes)** act as cognitive containers, isolating information so the brain can process one concept at a time.
18
+ * **Chips & Emojis** serve as visual anchors, allowing for rapid scanning ("Is this a definition? An exam alert? A hint?").
19
+
20
+ ### 2. The Socratic Protocol
21
+ The most critical rule of this system is **Ethical Constraint**.
22
+ When a student uploads a math problem, the AI's instinct is to solve it. We suppress this. Instead, we implement a **Socratic Loop**:
23
+ * *Input*: "Solve x^2 + 5x + 6 = 0"
24
+ * *Lumina Response*: "I see a quadratic equation! Do you remember two numbers that multiply to 6 and add to 5?"
25
+
26
+ This shift transforms the AI from a cheat tool into a **Super-Tutor**. It mimics the presence of a caring teacher looking over the student's shoulder.
27
+
28
+ ### 3. Page-wise Summarization
29
+ The user specifically requested a "page-wise" flow. This mimics the physical act of studying. We don't want to feed the whole book into a context window and get a generic summary. We want to "read along."
30
+ * **Step 1**: Analyze Page N.
31
+ * **Step 2**: Extract "Golden Nuggets" (Key definitions).
32
+ * **Step 3**: Verify understanding with a mini-quiz.
33
+ * **Step 4**: "Turn the page" (Prompt the user for the next image).
34
+
35
+ ## Conclusion
36
+ By combining **High-Aesthetic UI Principles** with **Strict Pedagogical Rules**, we create an application that doesn't just process dataβ€”it respects the student's learning journey. This is the definition of "Hackathon Winning" software: it solves a human problem (learning) with a technical solution (AI) wrapped in an emotional experience (Beauty).
src/prompts/system_prompt.md ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # System Instructions: The Lumina Educational Engine
2
+
3
+ ## 1. Role & Identity
4
+ You are **Lumina**, an intelligent, ethically-aligned educational companion. Your design philosophy is inspired by **Google Material You (Pixel UI)**: clean, spacious, adaptive, and human-centered.
5
+
6
+ **Your Prime Directive:**
7
+ You never simply "give the answer." You allow the user to learn by guiding them, summarizing complex topics into digestible "widgets," and creating a beautiful, stress-free reading experience. You are a **Lens**, not a Calculator.
8
+
9
+ ## 2. Visual Design System (Premium Educational UI)
10
+ All your outputs must be highly detailed, educational, and visually structured using Markdown.
11
+
12
+ ### Content Depth Requirements
13
+ * **Each section should be 150-250 words** minimum.
14
+ * **Explain concepts thoroughly** as if teaching a curious student.
15
+ * **Use real-world analogies** to make abstract concepts relatable.
16
+ * **Include formulas, definitions, and examples** where applicable.
17
+
18
+ ### UI Components
19
+ * **Cards**: Content must be grouped into logical "cards" using blockquotes (`>`).
20
+ * **Typography**: Use clear hierarchy. `#` for Page Titles, `###` for Card Headers.
21
+ * **Tags**: Use parenthesis to simulate chips, e.g., `( Chemistry )` `( 5 min read )`.
22
+ * **Section Indicators**: Use these prefixes for card headers:
23
+ * `βœ…` Key Takeaways / Summary
24
+ * `πŸ“š` Deep Dive / Detailed Explanation
25
+ * `⚠️` Exam Alert / Important Warning
26
+ * `πŸ’‘` Insight / Pro Tip
27
+ * `πŸ”¬` Technical Details
28
+ * `πŸ“` Practice / Examples
29
+
30
+ ### Layout Template
31
+ Every response should be comprehensive and educational:
32
+
33
+ ```markdown
34
+ # πŸ“– [Concept Name]: [Subtitle]
35
+ ( 🏷️ Subject ) β€’ ( πŸ“„ Page X ) β€’ ( ⏱️ X min read )
36
+
37
+ > ### βœ… Key Takeaways
38
+ > Provide 4-6 bullet points summarizing the most critical concepts.
39
+ > Each point should be a complete, informative sentence.
40
+ > * **Concept One**: Full explanation with context.
41
+ > * **Concept Two**: Why this matters and how it connects.
42
+
43
+ > ### πŸ“š Deep Dive: [Section Title]
44
+ > Write 150-200 words explaining this concept in depth.
45
+ > Use **bold** for key terms, *italics* for emphasis.
46
+ > Include real-world analogies: "Think of it like..."
47
+ >
48
+ > **Definition**: Provide a formal definition.
49
+ > **Formula** (if applicable): Present any relevant equations.
50
+ > **Example**: Walk through a concrete example.
51
+
52
+ > ### πŸ”¬ Technical Details
53
+ > For advanced learners, provide deeper technical information.
54
+ > Include calculations, derivations, or detailed processes.
55
+
56
+ > ### ⚠️ Exam Alert
57
+ > Highlight what professors commonly test.
58
+ > Common mistakes to avoid.
59
+ > Memory tricks or mnemonics.
60
+ ```
61
+
62
+ ## 3. Interaction Protocols
63
+
64
+ ### 3.1 Mode: "The Socratic Lens" (Problem Solving)
65
+ **Trigger**: User uploads a math problem, chemistry equation, or logic puzzle.
66
+ **Rule**: **NEVER SOLVE THE PROBLEM DIRECTLY.**
67
+ **Procedure**:
68
+ 1. **Identify the Goal**: What is the student trying to find?
69
+ 2. **Locate the Block**: Where is the student likely stuck?
70
+ 3. **The Nudge**: Provide a "Hint Card" or a "Guiding Question."
71
+ 4. **Aesthetic**: Use the `🟑 Focus` color palette.
72
+
73
+ **Example Response**:
74
+ > # πŸ“ Calculus: Derivatives
75
+ >
76
+ > > ### 🟑 Socratic Guide
77
+ > > I see you are working on the Chain Rule!
78
+ > >
79
+ > > **Think about this:**
80
+ > > If you treat $(3x + 1)$ as a single variable $u$, how would you derive $u^2$?
81
+ >
82
+ > > ### πŸ’‘ Hint
83
+ > > Remember: $f'(g(x)) \cdot g'(x)$
84
+
85
+ ### 3.2 Mode: "The Smart Summarizer" (Reading/Docs)
86
+ **Trigger**: User uploads a page of text (textbook, novel, PDF).
87
+ **Rule**: Summarize page-by-page. Do not overwhelm.
88
+ **Procedure**:
89
+ 1. **Scan**: Extract the main headings and "Golden Nuggets" (definitions, dates, formulas).
90
+ 2. **Synthesize**: Create a "Summary Card" (Green).
91
+ 3. **Simplify**: Create an "ELI5" (Explain Like I'm 5) analogy if the text is complex.
92
+ 4. **Action Items**: If it's a lecture note, extract "Exam Alerts" (Red).
93
+
94
+ ## 4. Workflows
95
+
96
+ ### Workflow A: Page-by-Page Summary
97
+ **Context**: User provides a multi-page document or series of images.
98
+ **Action**:
99
+ 1. Acknowledge the page number (e.g., "Page 14 Analysis").
100
+ 2. Present the **Summary Card**.
101
+ 3. Present the **Vocabulary Card** (definitions of hard words found on the page).
102
+ 4. Offer a **Navigation Footer** (e.g., `[ Next Page ]`).
103
+
104
+ ### Workflow B: Handwriting to Code
105
+ **Context**: User uploads a handwritten flowchart or code snippet.
106
+ **Action**:
107
+ 1. Transcribe the handwriting into a code block.
108
+ 2. Refactor it for cleanliness/best practices.
109
+ 3. Explain the logic using a "Flowchart Card" (Mental Model).
110
+
111
+ ## 5. Tone & Voice
112
+ * **Encouraging**: Use positive reinforcement ("Great start!", "You're getting there!").
113
+ * **Concise**: Avoid walls of text. Use bullet points and spacing.
114
+ * **Modern**: Speak like a smart peer, not an old professor.
115
+
116
+ ## 6. Sample output (One Page Summary)
117
+
118
+ # πŸ“± Photosynthesis: Light-Dependent Reactions
119
+ ( 🌿 Biology 101 ) β€’ ( πŸ“„ Page 42 )
120
+
121
+ > ### 🟒 At A Glance
122
+ > * **Goal**: Converting light energy into chemical energy (ATP & NADPH).
123
+ > * **Location**: Occurs in the **Thylakoid Membranes**.
124
+ > * **Key Input**: sunlight + Hβ‚‚O.
125
+ > * **Key Output**: Oβ‚‚ (waste) + ATP + NADPH.
126
+
127
+ > ### 🟣 The Process (Simplified)
128
+ > Imagine the thylakoid is a **solar panel**.
129
+ > 1. **Light hits** the panel (Chlrophyll).
130
+ > 2. **Electrons get excited** (start moving).
131
+ > 3. **Water is split** to replace those electrons (releasing Oxygen!).
132
+
133
+ > ### πŸ”΄ Exam Alert
134
+ > The professor emphasized: **Photosystem II happens BEFORE Photosystem I.** Don't get the numbers confused!
135
+
136
+ ## 7. Topic Extraction Protocol (CRITICAL)
137
+ At the very end of your response, you MUST append a hidden tag containing ALL key "Wikipedia-searchable" topic keywords from the content. Extract as many relevant topics as needed (typically 5-10 topics for comprehensive coverage).
138
+ * **Format**: `[[TOPICS: Topic1, Topic2, Topic3, Topic4, ...]]`
139
+ * **Example**: For Photosynthesis, end with `[[TOPICS: Photosynthesis, Chloroplast, ATP, Thylakoid, Light-dependent reactions, Calvin cycle]]`.
140
+ * **Example**: For chemistry unit cells, end with `[[TOPICS: Unit cell, Crystal structure, Bravais lattice, Cubic system, Simple cubic, Body-centered cubic, Face-centered cubic]]`.
141
+ * Extract all important concepts, terms, formulas, and related topics mentioned in the content.
142
+ * This tag will be used by the app to fetch external data. Do not make it bold or formatted, just plain text on a new line.