GitHub Action commited on
Commit
bcce530
·
0 Parent(s):

Automated sync to Hugging Face

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +3 -0
  2. .github/workflows/sync_to_hf.yml +33 -0
  3. .gitignore +49 -0
  4. .nvmrc +1 -0
  5. DEPLOYMENT.md +414 -0
  6. README.md +414 -0
  7. components.json +22 -0
  8. eslint.config.mjs +18 -0
  9. next.config.ts +95 -0
  10. openprompt-extension/README.md +137 -0
  11. openprompt-extension/package-lock.json +0 -0
  12. openprompt-extension/package.json +28 -0
  13. openprompt-extension/popup.html +12 -0
  14. openprompt-extension/postcss.config.js +6 -0
  15. openprompt-extension/public/icon.svg +17 -0
  16. openprompt-extension/public/icons/icon128.png +3 -0
  17. openprompt-extension/public/icons/icon16.png +3 -0
  18. openprompt-extension/public/icons/icon48.png +3 -0
  19. openprompt-extension/public/icons/logo.svg +53 -0
  20. openprompt-extension/public/manifest.json +150 -0
  21. openprompt-extension/public/small-promo-tile.png +3 -0
  22. openprompt-extension/scripts/create-icons.js +27 -0
  23. openprompt-extension/scripts/generate-icons.js +36 -0
  24. openprompt-extension/src/background/index.ts +450 -0
  25. openprompt-extension/src/content-scripts/chatgpt.ts +87 -0
  26. openprompt-extension/src/content-scripts/claude.ts +83 -0
  27. openprompt-extension/src/content-scripts/copilot.ts +72 -0
  28. openprompt-extension/src/content-scripts/gemini.ts +87 -0
  29. openprompt-extension/src/content-scripts/mistral.ts +83 -0
  30. openprompt-extension/src/content-scripts/openprompt-site.ts +120 -0
  31. openprompt-extension/src/content-scripts/perplexity.ts +83 -0
  32. openprompt-extension/src/content-scripts/utils.ts +258 -0
  33. openprompt-extension/src/popup/App.tsx +743 -0
  34. openprompt-extension/src/popup/index.css +34 -0
  35. openprompt-extension/src/popup/main.tsx +10 -0
  36. openprompt-extension/tailwind.config.js +26 -0
  37. openprompt-extension/tsconfig.json +21 -0
  38. openprompt-extension/vite.config.ts +63 -0
  39. package-lock.json +0 -0
  40. package.json +81 -0
  41. postcss.config.mjs +7 -0
  42. prisma.config.ts +14 -0
  43. prisma/schema.prisma +760 -0
  44. prisma/seed-more.ts +128 -0
  45. prisma/seed.ts +644 -0
  46. prisma/tool-models.txt +43 -0
  47. prisma/update-models.ts +34 -0
  48. public/favicon.ico +3 -0
  49. public/file.svg +1 -0
  50. public/globe.svg +1 -0
.gitattributes ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ *.png filter=lfs diff=lfs merge=lfs -text
2
+ *.jpg filter=lfs diff=lfs merge=lfs -text
3
+ *.ico filter=lfs diff=lfs merge=lfs -text
.github/workflows/sync_to_hf.yml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face
2
+ on:
3
+ push:
4
+ branches: [master]
5
+
6
+ jobs:
7
+ sync-to-hub:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v3
11
+ - name: Push to hub
12
+ env:
13
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
14
+ run: |
15
+ # 1. Turn on Large File Storage (LFS)
16
+ git lfs install
17
+
18
+ # 2. Tell Hugging Face to properly handle images
19
+ echo "*.png filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
20
+ echo "*.jpg filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
21
+ echo "*.ico filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
22
+
23
+ # 3. Wipe the hidden git history in the runner to clear past image errors
24
+ rm -rf .git
25
+ git config --global user.email "action@github.com"
26
+ git config --global user.name "GitHub Action"
27
+
28
+ # 4. Create a fresh, clean package and force push it
29
+ git init
30
+ git checkout -b main
31
+ git add .
32
+ git commit -m "Automated sync to Hugging Face"
33
+ git push --force https://anky2002:$HF_TOKEN@huggingface.co/spaces/anky2002/open-prompt main
.gitignore ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
42
+
43
+ /src/generated/prisma
44
+ # openprompt extension build output
45
+ /openprompt-extension/dist
46
+ /openprompt-extension/build
47
+ /openprompt-extension/node_modules
48
+ goal.md
49
+ features.md
.nvmrc ADDED
@@ -0,0 +1 @@
 
 
1
+ 20
DEPLOYMENT.md ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 OpenPrompt - Quick Deployment Guide
2
+
3
+ **From Zero to Live in 10 Minutes**
4
+
5
+ ---
6
+
7
+ ## Prerequisites
8
+
9
+ - ✅ Node.js 18+ installed
10
+ - ✅ Git installed
11
+ - ✅ Vercel account (free)
12
+ - ✅ OpenAI API key
13
+
14
+ ---
15
+
16
+ ## Step 1: Environment Variables (2 minutes)
17
+
18
+ Create `.env.local` file in the root directory:
19
+
20
+ ```env
21
+ # Database (already configured)
22
+ DATABASE_URL="your-neon-postgres-url"
23
+
24
+ # Authentication (Stack Auth - already configured)
25
+ NEXT_PUBLIC_STACK_PROJECT_ID="your-stack-project-id"
26
+ NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY="your-stack-key"
27
+ STACK_SECRET_SERVER_KEY="your-stack-secret"
28
+
29
+ # AI Models (REQUIRED for tools to work)
30
+ OPENAI_API_KEY="sk-proj-..."
31
+
32
+ # Optional but recommended
33
+ ANTHROPIC_API_KEY="sk-ant-..."
34
+ GOOGLE_AI_API_KEY="..."
35
+
36
+ # Redis Cache (Optional - improves performance)
37
+ UPSTASH_REDIS_REST_URL="https://..."
38
+ UPSTASH_REDIS_REST_TOKEN="..."
39
+
40
+ # Bot Protection (Optional)
41
+ NEXT_PUBLIC_TURNSTILE_SITE_KEY="..."
42
+ TURNSTILE_SECRET_KEY="..."
43
+ ```
44
+
45
+ ### Get API Keys:
46
+
47
+ **OpenAI (Required):**
48
+ 1. Go to https://platform.openai.com/api-keys
49
+ 2. Create new secret key
50
+ 3. Copy to `OPENAI_API_KEY`
51
+
52
+ **Upstash Redis (Recommended):**
53
+ 1. Go to https://upstash.com
54
+ 2. Create new database
55
+ 3. Copy REST URL and Token
56
+
57
+ ---
58
+
59
+ ## Step 2: Install & Build (3 minutes)
60
+
61
+ ```bash
62
+ # Install dependencies
63
+ npm install
64
+
65
+ # Generate Prisma client
66
+ npx prisma generate
67
+
68
+ # Push database schema
69
+ npx prisma db push
70
+
71
+ # Optional: Seed with sample data
72
+ npx prisma db seed
73
+
74
+ # Build for production
75
+ npm run build
76
+
77
+ # Test locally
78
+ npm start
79
+ ```
80
+
81
+ Visit http://localhost:3000 to verify everything works.
82
+
83
+ ---
84
+
85
+ ## Step 3: Deploy to Vercel (5 minutes)
86
+
87
+ ### Option A: Vercel CLI (Fastest)
88
+
89
+ ```bash
90
+ # Install Vercel CLI
91
+ npm i -g vercel
92
+
93
+ # Login
94
+ vercel login
95
+
96
+ # Deploy
97
+ vercel --prod
98
+ ```
99
+
100
+ ### Option B: Vercel Dashboard
101
+
102
+ 1. Go to https://vercel.com/new
103
+ 2. Import your Git repository
104
+ 3. Add environment variables in dashboard
105
+ 4. Click "Deploy"
106
+
107
+ **Environment Variables to Add in Vercel:**
108
+ - `DATABASE_URL`
109
+ - `NEXT_PUBLIC_STACK_PROJECT_ID`
110
+ - `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY`
111
+ - `STACK_SECRET_SERVER_KEY`
112
+ - `OPENAI_API_KEY`
113
+ - `UPSTASH_REDIS_REST_URL` (if using)
114
+ - `UPSTASH_REDIS_REST_TOKEN` (if using)
115
+
116
+ ---
117
+
118
+ ## Step 4: Post-Deployment Checks (2 minutes)
119
+
120
+ Visit your live site and verify:
121
+
122
+ **Basic Functionality:**
123
+ - [ ] Homepage loads
124
+ - [ ] Can browse `/explore`
125
+ - [ ] Can view `/tools`
126
+ - [ ] Can sign in/sign up
127
+ - [ ] Dark mode toggle works
128
+
129
+ **Critical Features:**
130
+ - [ ] Can run a prompt at `/p/[slug]`
131
+ - [ ] AI response streams correctly
132
+ - [ ] Can execute a tool at `/tools/[slug]`
133
+ - [ ] Star/unstar works
134
+ - [ ] Search works
135
+
136
+ **Performance:**
137
+ - [ ] Page load < 3 seconds
138
+ - [ ] No console errors
139
+ - [ ] Images load properly
140
+ - [ ] Mobile responsive
141
+
142
+ ---
143
+
144
+ ## Step 5: Domain Setup (Optional)
145
+
146
+ **Add Custom Domain in Vercel:**
147
+
148
+ 1. Go to your project settings
149
+ 2. Click "Domains"
150
+ 3. Add your domain (e.g., https://open-prompt.netlify.app)
151
+ 4. Update DNS records as instructed
152
+ 5. Wait for SSL certificate (automatic)
153
+
154
+ ---
155
+
156
+ ## Troubleshooting
157
+
158
+ ### Build Errors
159
+
160
+ **Error:** "Module not found"
161
+ ```bash
162
+ # Clear cache and reinstall
163
+ rm -rf node_modules .next
164
+ npm install
165
+ npm run build
166
+ ```
167
+
168
+ **Error:** "Prisma Client not generated"
169
+ ```bash
170
+ npx prisma generate
171
+ npm run build
172
+ ```
173
+
174
+ ### Runtime Errors
175
+
176
+ **Error:** "OpenAI API key not found"
177
+ - Check `OPENAI_API_KEY` is set in Vercel environment variables
178
+ - Redeploy after adding env vars
179
+
180
+ **Error:** "Database connection failed"
181
+ - Verify `DATABASE_URL` is correct
182
+ - Check if Neon database is active
183
+ - Ensure IP allowlist includes Vercel IPs
184
+
185
+ **Error:** "Rate limit exceeded"
186
+ - Redis not configured (optional but recommended)
187
+ - Add Upstash Redis credentials
188
+ - Or increase rate limits in `lib/rate-limit.ts`
189
+
190
+ ### Performance Issues
191
+
192
+ **Slow page loads:**
193
+ - Enable Redis caching
194
+ - Optimize images (use Next.js Image component)
195
+ - Check database query performance
196
+
197
+ **AI tools timeout:**
198
+ - Increase timeout in tool execution API
199
+ - Use streaming for long responses
200
+ - Consider using GPT-4o-mini for faster responses
201
+
202
+ ---
203
+
204
+ ## Monitoring & Analytics
205
+
206
+ ### Add Vercel Analytics (Free)
207
+
208
+ 1. Go to Vercel project settings
209
+ 2. Enable "Analytics" tab
210
+ 3. Automatically tracks:
211
+ - Page views
212
+ - Performance metrics
213
+ - User engagement
214
+
215
+ ### Add Vercel Speed Insights
216
+
217
+ ```bash
218
+ npm install @vercel/speed-insights
219
+ ```
220
+
221
+ In `app/layout.tsx`:
222
+ ```typescript
223
+ import { SpeedInsights } from '@vercel/speed-insights/next'
224
+
225
+ export default function RootLayout({ children }) {
226
+ return (
227
+ <html>
228
+ <body>
229
+ {children}
230
+ <SpeedInsights />
231
+ </body>
232
+ </html>
233
+ )
234
+ }
235
+ ```
236
+
237
+ ### Optional: Add PostHog (Product Analytics)
238
+
239
+ 1. Sign up at https://posthog.com
240
+ 2. Add tracking code to layout
241
+ 3. Track:
242
+ - Tool usage
243
+ - Prompt runs
244
+ - User journeys
245
+ - Conversions
246
+
247
+ ---
248
+
249
+ ## Scaling Considerations
250
+
251
+ ### When to Upgrade
252
+
253
+ **Database:**
254
+ - Free tier: ~10GB storage, 1GB bandwidth
255
+ - Upgrade when: >5,000 users or >100,000 prompts
256
+
257
+ **Redis:**
258
+ - Free tier: 10,000 commands/day
259
+ - Upgrade when: >1,000 daily active users
260
+
261
+ **AI APIs:**
262
+ - Monitor usage in OpenAI dashboard
263
+ - Set usage limits to prevent surprise bills
264
+ - Consider caching common responses
265
+
266
+ ### Cost Estimates
267
+
268
+ **MVP (0-1,000 users):**
269
+ - Vercel: Free
270
+ - Neon DB: Free
271
+ - Redis: Free
272
+ - OpenAI: ~$50-100/month
273
+ - **Total: ~$50-100/month**
274
+
275
+ **Growth (1,000-10,000 users):**
276
+ - Vercel: $20/month
277
+ - Neon DB: $19/month
278
+ - Redis: $10/month
279
+ - OpenAI: ~$300-500/month
280
+ - **Total: ~$350-550/month**
281
+
282
+ **Scale (10,000+ users):**
283
+ - Vercel: $20-50/month
284
+ - Neon DB: $50+/month
285
+ - Redis: $50+/month
286
+ - OpenAI: ~$1,000+/month
287
+ - **Total: ~$1,120+/month**
288
+
289
+ ---
290
+
291
+ ## Production Checklist
292
+
293
+ ### Security
294
+ - [ ] Environment variables secured
295
+ - [ ] API keys rotated regularly
296
+ - [ ] Rate limiting enabled
297
+ - [ ] CORS configured
298
+ - [ ] Input validation on all forms
299
+ - [ ] XSS protection enabled
300
+
301
+ ### Performance
302
+ - [ ] Redis caching enabled
303
+ - [ ] Images optimized
304
+ - [ ] Code split by route
305
+ - [ ] Lazy loading implemented
306
+ - [ ] CDN configured (automatic with Vercel)
307
+
308
+ ### Monitoring
309
+ - [ ] Error tracking (Sentry recommended)
310
+ - [ ] Analytics enabled
311
+ - [ ] Uptime monitoring
312
+ - [ ] Database backups configured
313
+ - [ ] Logs accessible
314
+
315
+ ### SEO
316
+ - [ ] Meta tags complete
317
+ - [ ] Sitemap generated
318
+ - [ ] robots.txt configured
319
+ - [ ] Open Graph images
320
+ - [ ] Schema markup added
321
+
322
+ ### Legal
323
+ - [ ] Privacy policy published
324
+ - [ ] Terms of service published
325
+ - [ ] Cookie consent (if EU users)
326
+ - [ ] GDPR compliance (if EU users)
327
+ - [ ] CCPA compliance (if CA users)
328
+
329
+ ---
330
+
331
+ ## Launch Day Actions
332
+
333
+ **Hour 1: Deploy**
334
+ - [ ] Final build successful
335
+ - [ ] All environment variables set
336
+ - [ ] Domain connected
337
+ - [ ] SSL active
338
+
339
+ **Hour 2: Verify**
340
+ - [ ] Test all critical paths
341
+ - [ ] Check mobile experience
342
+ - [ ] Verify AI tools work
343
+ - [ ] Test authentication flow
344
+
345
+ **Hour 3: Announce**
346
+ - [ ] Post on social media
347
+ - [ ] Email early users
348
+ - [ ] Submit to Product Hunt
349
+ - [ ] Share in communities
350
+
351
+ ---
352
+
353
+ ## Support Resources
354
+
355
+ **Documentation:**
356
+ - Next.js: https://nextjs.org/docs
357
+ - Prisma: https://www.prisma.io/docs
358
+ - Vercel: https://vercel.com/docs
359
+ - Stack Auth: https://docs.stack-auth.com
360
+
361
+ **Community:**
362
+ - Discord servers for each stack
363
+ - GitHub discussions
364
+ - Stack Overflow
365
+
366
+ **Paid Support:**
367
+ - Vercel Enterprise support
368
+ - Database managed services
369
+ - Development agencies
370
+
371
+ ---
372
+
373
+ ## Quick Commands Reference
374
+
375
+ ```bash
376
+ # Development
377
+ npm run dev # Start dev server
378
+ npm run build # Build for production
379
+ npm start # Run production build
380
+ npm run lint # Run ESLint
381
+
382
+ # Database
383
+ npx prisma studio # Open database GUI
384
+ npx prisma db push # Push schema to database
385
+ npx prisma db seed # Seed database
386
+ npx prisma generate # Generate Prisma client
387
+ npx prisma migrate dev # Create migration
388
+
389
+ # Deployment
390
+ vercel # Deploy to preview
391
+ vercel --prod # Deploy to production
392
+ vercel env pull # Pull env variables locally
393
+ vercel logs # View deployment logs
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Success! 🎉
399
+
400
+ Your OpenPrompt platform is now LIVE and ready to:
401
+ - Serve users
402
+ - Generate revenue
403
+ - Scale infinitely
404
+ - Change the world
405
+
406
+ **Next:** Monitor usage, gather feedback, iterate!
407
+
408
+ ---
409
+
410
+ **Need Help?** Check the documentation or open an issue on GitHub.
411
+
412
+ **Ready to Scale?** Consider the growth roadmap in `final_summary.md`.
413
+
414
+ **Want More Features?** See `implementation_plan.md` for adding more tools.
README.md ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 OpenPrompt - AI Prompts Marketplace + Tools Suite
2
+
3
+ > **Production-ready platform combining a prompts marketplace with 177+ AI-powered tools, browser extension, and multi-model support**
4
+
5
+ [![Next.js](https://img.shields.io/badge/Next.js-16.0.8-black)](https://nextjs.org/)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.8-blue)](https://www.typescriptlang.org/)
7
+ [![Prisma](https://img.shields.io/badge/Prisma-7.1-2D3748)](https://www.prisma.io/)
8
+ [![React](https://img.shields.io/badge/React-19.2-61DAFB)](https://react.dev/)
9
+
10
+ **Live Demo:** [https://open-prompt.netlify.app](https://open-prompt.netlify.app)
11
+
12
+ ---
13
+
14
+ ## 📖 Table of Contents
15
+
16
+ - [Features](#-features)
17
+ - [Tech Stack](#-tech-stack)
18
+ - [Getting Started](#-getting-started)
19
+ - [Project Structure](#-project-structure)
20
+ - [Browser Extension](#-browser-extension)
21
+ - [Available Tools](#-available-tools)
22
+ - [Deployment](#-deployment)
23
+ - [Documentation](#-documentation)
24
+ - [Support](#-support)
25
+
26
+ ---
27
+
28
+ ## ✨ Features
29
+
30
+ ### Prompts Marketplace
31
+
32
+ - 🔍 **Advanced Search** - Full-text search with real-time filtering
33
+ - 📊 **Trending Algorithm** - Hot score calculation for viral prompts
34
+ - 🏷️ **7 Categories** - Content, Development, Marketing, Business, Education, Creative, Research
35
+ - ⭐ **User Engagement** - Star, share, remix, and collect prompts
36
+ - 👥 **Creator Economy** - Profile pages, rankings (Bronze/Silver/Gold/Verified), stats
37
+ - 🎨 **10 Frameworks** - RACE, CARE, APE, CREATE, RISEN, RTF, TAG, BAB, STAR, PREP
38
+ - 🏅 **Quality Badges** - Auto-calculated (Hot, Viral, Featured, Top Rated, etc.)
39
+ - 📦 **Collections** - Organize and share prompt collections
40
+ - 🔗 **Embeds** - Share prompts with 3 theme options
41
+ - 💬 **Comments** - Threaded discussions with likes and replies
42
+ - 🌓 **Dark Mode** - Beautiful light and dark themes
43
+
44
+ ### AI Tools Suite (177 Tools Across 15 Categories)
45
+
46
+ - 🎯 **Prompting** (9) - Optimizer, Checker, Chain-of-Thought, Few-Shot, Meta-Prompt, etc.
47
+ - 📢 **Marketing** (14) - Strategy, Sales Copy, Hooks, Ads, Campaigns
48
+ - 🏢 **Branding** (11) - Business Names, Slogans, USP, Brand Voice
49
+ - ✍️ **Copywriting** (12) - Titles, Headlines, Meta, Landing Pages
50
+ - 💼 **Business** (15) - Plans, Mission, SWOT, Pitches, Financial Models
51
+ - 📧 **Email** (10) - Subject Lines, Cold Email, Sequences, Templates
52
+ - 📦 **Product** (12) - Descriptions, PRDs, User Stories, Roadmaps
53
+ - 💼 **HR** (11) - Job Descriptions, Interviews, Reviews, Onboarding
54
+ - ⭐ **Personal Brand** (8) - LinkedIn, Twitter, Instagram, Bio
55
+ - 📋 **Operations** (9) - SOPs, KPIs, Process Improvement
56
+ - 📱 **Social Media** (12) - Posts, Captions, Hashtags, Calendars
57
+ - 🎓 **Education** (10) - Lesson Plans, Quizzes, Curriculum
58
+ - 💻 **Development** (15) - Code Review, Documentation, APIs
59
+ - 🎨 **Creative** (14) - Stories, Scripts, Poetry, Art Prompts
60
+ - 🔬 **Research** (15) - Analysis, Summaries, Literature Review
61
+
62
+ ### Image Prompts Gallery
63
+ - 🎨 **Midjourney** - v6.1, v6, v5.2, niji 6
64
+ - 🖼️ **DALL-E** - DALL-E 3, DALL-E 2
65
+ - 🎯 **Stable Diffusion** - SDXL, SD 3.5, SD 1.5
66
+ - ⚡ **FLUX** - FLUX.1-dev, schnell, pro
67
+ - 🦁 **Leonardo AI** - Phoenix, Kino XL, Vision XL
68
+ - 🔥 **Adobe Firefly** - Firefly 3, Firefly 2
69
+
70
+ ### AI Characters
71
+ - 🤖 **Experts** - Coding, Writing, Business mentors
72
+ - 🎭 **Roleplay** - Storytelling, Adventure companions
73
+ - 🎓 **Education** - Tutors, Study buddies
74
+ - 💼 **Productivity** - Task assistants, Planners
75
+ - 🎨 **Creative** - Art directors, Music composers
76
+
77
+ ### Thunderdome ⚔️
78
+ - **Model vs Model** - Compare AI responses head-to-head
79
+ - **Community Voting** - Real-time leaderboard
80
+ - **20+ Models** - GPT-4o, Claude 3.5, Gemini 2.5, Ollama models
81
+
82
+ ### Workflow Chains 🔗
83
+ - **Multi-step Prompts** - Chain prompts together
84
+ - **Variable Passing** - Output → Input automation
85
+ - **Templates** - Pre-built workflow patterns
86
+
87
+ ### Infrastructure
88
+
89
+ - ⚡ **Multi-Model AI** - OpenAI, Anthropic, Google AI, Ollama (20+ local models)
90
+ - 🔄 **Response Caching** - Redis with 7-day TTL
91
+ - 🛡️ **Rate Limiting** - 10/hr guests, 50/hr users
92
+ - 🤖 **Bot Protection** - Cloudflare Turnstile
93
+ - 📊 **Analytics** - Usage tracking, engagement metrics
94
+ - 🔐 **Authentication** - Stack Auth integration
95
+ - 📱 **Mobile Responsive** - Works on all devices
96
+ - 🧩 **Browser Extension** - Open prompts in any AI chat
97
+
98
+ ---
99
+
100
+ ## 🛠️ Tech Stack
101
+
102
+ **Frontend:**
103
+ - [Next.js 16.0.8](https://nextjs.org/) - React framework with Turbopack
104
+ - [React 19.2.1](https://react.dev/) - UI library
105
+ - [TypeScript 5.8](https://www.typescriptlang.org/) - Type safety
106
+ - [Tailwind CSS 4.x](https://tailwindcss.com/) - Styling
107
+ - [Shadcn/ui](https://ui.shadcn.com/) - UI components
108
+ - [Framer Motion](https://www.framer.com/motion/) - Animations
109
+
110
+ **Backend:**
111
+ - [Next.js API Routes](https://nextjs.org/docs/api-routes/introduction) - Serverless functions
112
+ - [Prisma 7.1](https://www.prisma.io/) - Database ORM
113
+ - [PostgreSQL](https://www.postgresql.org/) - Database (via Neon)
114
+ - [Redis](https://redis.io/) - Caching (via Upstash)
115
+
116
+ **AI & Services:**
117
+ - [OpenAI API](https://platform.openai.com/) - GPT-4o, GPT-4o-mini
118
+ - [Anthropic API](https://www.anthropic.com/) - Claude 3.5 Sonnet, Claude 3 Haiku
119
+ - [Google AI](https://ai.google.dev/) - Gemini 2.5 Flash, Gemini 2.0 Flash
120
+ - [Ollama](https://ollama.ai/) - 20+ local models (Llama, Mistral, Phi, etc.)
121
+ - [Stack Auth](https://stack-auth.com/) - Authentication
122
+ - [Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/) - Bot protection
123
+
124
+ **Deployment:**
125
+ - [Vercel](https://vercel.com/) - Hosting & deployments
126
+ - [GitHub](https://github.com/) - Version control
127
+
128
+ ---
129
+
130
+ ## 🚀 Getting Started
131
+
132
+ ### Prerequisites
133
+
134
+ - Node.js 18+
135
+ - npm or yarn
136
+ - PostgreSQL database (or Neon account)
137
+ - OpenAI API key
138
+
139
+ ### Installation
140
+
141
+ 1. **Clone the repository**
142
+
143
+ ```bash
144
+ git clone https://github.com/Anky9972/open-prompt.git
145
+ cd open-prompt
146
+ ```
147
+
148
+ 2. **Install dependencies**
149
+
150
+ ```bash
151
+ npm install
152
+ ```
153
+
154
+ 3. **Set up environment variables**
155
+
156
+ Create `.env.local`:
157
+
158
+ ```env
159
+ # Database
160
+ DATABASE_URL="postgresql://..."
161
+
162
+ # Authentication (Stack Auth)
163
+ NEXT_PUBLIC_STACK_PROJECT_ID="..."
164
+ NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY="..."
165
+ STACK_SECRET_SERVER_KEY="..."
166
+
167
+ # AI Models (Required)
168
+ OPENAI_API_KEY="sk-..."
169
+
170
+ # Optional
171
+ ANTHROPIC_API_KEY="sk-ant-..."
172
+ GOOGLE_AI_API_KEY="..."
173
+ UPSTASH_REDIS_REST_URL="..."
174
+ UPSTASH_REDIS_REST_TOKEN="..."
175
+ NEXT_PUBLIC_TURNSTILE_SITE_KEY="..."
176
+ TURNSTILE_SECRET_KEY="..."
177
+ ```
178
+
179
+ 4. **Set up database**
180
+
181
+ ```bash
182
+ npx prisma generate
183
+ npx prisma db push
184
+ npx prisma db seed # Optional: adds sample data
185
+ ```
186
+
187
+ 5. **Run development server**
188
+
189
+ ```bash
190
+ npm run dev
191
+ ```
192
+
193
+ Visit [http://localhost:3000](http://localhost:3000)
194
+
195
+ ---
196
+
197
+ ## 📁 Project Structure
198
+
199
+ ```
200
+ openprompt/
201
+ ├── src/
202
+ │ ├── app/ # Next.js app directory
203
+ │ │ ├── api/ # API routes
204
+ │ │ ├── categories/ # Category pages
205
+ │ │ ├── category/ # Dynamic category
206
+ │ │ ├── collections/ # Collections
207
+ │ │ ├── create/ # Create prompt
208
+ │ │ ├── creators/ # Creators index
209
+ │ │ ├── creator/ # Creator profile
210
+ │ │ ├── embed/ # Embed pages
211
+ │ │ ├── explore/ # Explore page
212
+ │ │ ├── image-prompts/ # Image generation prompts
213
+ │ │ ├── characters/ # AI characters
214
+ │ │ ├── leaderboard/ # Creator rankings
215
+ │ │ ├── p/ # Prompt runner
216
+ │ │ ├── thunderdome/ # Model comparison
217
+ │ │ ├── workflows/ # Workflow chains
218
+ │ │ ├── tools/ # AI tools suite
219
+ │ │ ├── about/ # About page
220
+ │ │ ├── docs/ # Documentation
221
+ │ │ ├── guides/ # User guides
222
+ │ │ ├── blog/ # Blog
223
+ │ │ ├── layout.tsx # Root layout
224
+ │ │ └── page.tsx # Landing page
225
+ │ ├── components/ # React components
226
+ │ │ ├── auth/ # Auth components
227
+ │ │ ├── comments/ # Comments system
228
+ │ │ ├── create/ # Creation components
229
+ │ │ ├── engagement/ # Engagement metrics
230
+ │ │ ├── explore/ # Discovery components
231
+ │ │ ├── layout/ # Layout (header, footer)
232
+ │ │ ├── prompt-runner/ # Prompt execution
233
+ │ │ ├── prompts/ # Prompt components
234
+ │ │ ├── thunderdome/ # Model comparison UI
235
+ │ │ ├── tools/ # Tool components
236
+ │ │ ├── workflow/ # Workflow builder
237
+ │ │ └── ui/ # Shadcn UI components
238
+ │ ├── lib/ # Utilities
239
+ │ │ ├── prisma.ts # Prisma client
240
+ │ │ ├── tools.ts # 177 tool definitions
241
+ │ │ ├── frameworks.ts # Prompt frameworks
242
+ │ │ └── utils.ts # Helper functions
243
+ │ └── types/ # TypeScript types
244
+ ├── prisma/
245
+ │ ├── schema.prisma # Database schema
246
+ │ └── seed.ts # Seed data
247
+ ├── openprompt-extension/ # Browser extension
248
+ │ ├── src/ # Extension source
249
+ │ ├── dist/ # Built extension
250
+ │ └── README.md # Extension docs
251
+ ├── public/ # Static assets
252
+ ├── DEPLOYMENT.md # Deployment guide
253
+ ├── FEATURE-GAP-ANALYSIS.md # Feature roadmap
254
+ └── README.md # This file
255
+ ```
256
+
257
+ ---
258
+
259
+ ## 🧩 Browser Extension
260
+
261
+ Open prompts directly in ChatGPT, Claude, Gemini, and other AI interfaces with one click!
262
+
263
+ ### Supported Platforms
264
+ - ✅ ChatGPT (chatgpt.com)
265
+ - ✅ Claude (claude.ai)
266
+ - ✅ Gemini (gemini.google.com)
267
+ - ✅ Perplexity (perplexity.ai)
268
+ - ✅ Mistral (chat.mistral.ai)
269
+ - ✅ Microsoft Copilot (copilot.microsoft.com)
270
+
271
+ ### Installation
272
+
273
+ ```bash
274
+ cd openprompt-extension
275
+ npm install
276
+ npm run build
277
+ ```
278
+
279
+ Then load the `dist` folder as an unpacked extension in Chrome.
280
+
281
+ See [openprompt-extension/README.md](openprompt-extension/README.md) for detailed instructions.
282
+
283
+ ---
284
+
285
+ ## 🔧 Available Tools
286
+
287
+ ### 177 Tools Across 15 Categories
288
+
289
+ | Category | Count | Examples |
290
+ |----------|-------|----------|
291
+ | **Prompting** | 9 | Optimizer, Chain-of-Thought, Meta-Prompt |
292
+ | **Marketing** | 14 | Strategy, Ads, Campaigns, Funnels |
293
+ | **Branding** | 11 | Names, Slogans, Voice, Guidelines |
294
+ | **Copywriting** | 12 | Headlines, Landing Pages, CTAs |
295
+ | **Business** | 15 | Plans, SWOT, Pitches, Financials |
296
+ | **Email** | 10 | Sequences, Templates, Subject Lines |
297
+ | **Product** | 12 | PRDs, Roadmaps, User Stories |
298
+ | **HR** | 11 | Job Posts, Interviews, Onboarding |
299
+ | **Personal Brand** | 8 | LinkedIn, Twitter, Bio Generators |
300
+ | **Operations** | 9 | SOPs, KPIs, Process Improvement |
301
+ | **Social Media** | 12 | Posts, Reels, Calendars, Hashtags |
302
+ | **Education** | 10 | Lesson Plans, Quizzes, Curriculum |
303
+ | **Development** | 15 | Code Review, Docs, API Design |
304
+ | **Creative** | 14 | Stories, Scripts, Poetry, Art |
305
+ | **Research** | 15 | Analysis, Summaries, Literature |
306
+
307
+ See [src/lib/tools.ts](src/lib/tools.ts) for complete definitions.
308
+
309
+ ---
310
+
311
+ ## 🌐 Deployment
312
+
313
+ ### Deploy to Vercel (Recommended)
314
+
315
+ 1. **Push to GitHub**
316
+
317
+ ```bash
318
+ git add .
319
+ git commit -m "Initial commit"
320
+ git push origin main
321
+ ```
322
+
323
+ 2. **Import to Vercel**
324
+
325
+ - Go to [vercel.com/new](https://vercel.com/new)
326
+ - Import your repository
327
+ - Add environment variables
328
+ - Deploy!
329
+
330
+ 3. **Set up domain** (optional)
331
+
332
+ - Add custom domain in Vercel dashboard
333
+ - Configure DNS settings
334
+ - SSL automatically provisioned
335
+
336
+ For detailed instructions, see [DEPLOYMENT.md](DEPLOYMENT.md)
337
+
338
+ ---
339
+
340
+ ## 📚 Documentation
341
+
342
+ - **[DEPLOYMENT.md](DEPLOYMENT.md)** - Complete deployment guide
343
+ - **[FEATURE-GAP-ANALYSIS.md](FEATURE-GAP-ANALYSIS.md)** - Competitor analysis & roadmap
344
+ - **[openprompt-extension/README.md](openprompt-extension/README.md)** - Browser extension docs
345
+
346
+ ---
347
+
348
+ ## 🎯 Roadmap
349
+
350
+ ### ✅ Current: v1.0 (Complete)
351
+ - ✅ Full prompts marketplace with 7 categories
352
+ - ✅ 177 AI tools across 15 categories
353
+ - ✅ Multi-model support (OpenAI, Anthropic, Google, Ollama)
354
+ - ✅ Thunderdome model comparison
355
+ - ✅ Workflow chains
356
+ - ✅ Image prompts gallery
357
+ - ✅ AI characters
358
+ - ✅ Comments & engagement
359
+ - ✅ Creator leaderboard
360
+ - ✅ Browser extension
361
+
362
+ ### 🚧 Next: v1.5
363
+ - [ ] Prompt marketplace (buy/sell)
364
+ - [ ] Team workspaces
365
+ - [ ] API access
366
+ - [ ] Custom fine-tuning
367
+
368
+ ### 🔮 Future: v2.0
369
+ - [ ] Mobile app
370
+ - [ ] Enterprise features
371
+ - [ ] White-label option
372
+ - [ ] Plugin ecosystem
373
+
374
+ ---
375
+
376
+ ## 💰 Monetization
377
+
378
+ ### Free Tier
379
+ - All basic features
380
+ - 20 tool executions/day
381
+ - Community prompts
382
+
383
+ ### Pro ($9/month)
384
+ - Unlimited tool executions
385
+ - Access to PRO tools
386
+ - Execution history
387
+ - Priority support
388
+
389
+ ### Enterprise (Custom)
390
+ - API access
391
+ - Custom tools
392
+ - White-label
393
+ - SLA guarantee
394
+
395
+ ---
396
+
397
+ ## 📞 Support
398
+
399
+ - **Issues:** [GitHub Issues](https://github.com/Anky9972/open-prompt/issues)
400
+ - **Discussions:** [GitHub Discussions](https://github.com/Anky9972/open-prompt/discussions)
401
+ - **Email:** ankygaur9972@gmail.com
402
+ - **Twitter:** [@anky_vivek](https://x.com/anky_vivek)
403
+
404
+ ---
405
+
406
+ ## 🌟 Star History
407
+
408
+ If you find this project useful, please consider giving it a star ⭐
409
+
410
+ ---
411
+
412
+ **Built with ❤️ by [Anky9972](https://github.com/Anky9972)**
413
+
414
+ [Website](https://open-prompt.netlify.app) · [Twitter](https://x.com/anky_vivek) · [GitHub](https://github.com/Anky9972/open-prompt)
components.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "aliases": {
15
+ "components": "@/components",
16
+ "utils": "@/lib/utils",
17
+ "ui": "@/components/ui",
18
+ "lib": "@/lib",
19
+ "hooks": "@/hooks"
20
+ },
21
+ "registries": {}
22
+ }
eslint.config.mjs ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
next.config.ts ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ // Image optimization – whitelist only known-safe domains (no wildcard **)
5
+ images: {
6
+ remotePatterns: [
7
+ // GitHub avatars (OAuth)
8
+ { protocol: "https", hostname: "avatars.githubusercontent.com" },
9
+ // Google avatars (OAuth)
10
+ { protocol: "https", hostname: "lh3.googleusercontent.com" },
11
+ { protocol: "https", hostname: "lh4.googleusercontent.com" },
12
+ { protocol: "https", hostname: "lh5.googleusercontent.com" },
13
+ // Stack Auth CDN
14
+ { protocol: "https", hostname: "*.stackauth.com" },
15
+ // Prompt preview / gallery images (Cloudflare R2 / storage)
16
+ { protocol: "https", hostname: "imagedelivery.net" },
17
+ { protocol: "https", hostname: "images.unsplash.com" },
18
+ // AI image CDNs
19
+ { protocol: "https", hostname: "oaidalleapiprodscus.blob.core.windows.net" },
20
+ { protocol: "https", hostname: "cdn.midjourney.com" },
21
+ { protocol: "https", hostname: "image.civitai.com" },
22
+ ],
23
+ formats: ["image/avif", "image/webp"],
24
+ },
25
+
26
+ // Security headers
27
+ async headers() {
28
+ return [
29
+ {
30
+ source: "/(.*)",
31
+ headers: [
32
+ // ✅ SAMEORIGIN (not DENY) so /embed/ iframes work
33
+ { key: "X-Frame-Options", value: "SAMEORIGIN" },
34
+ { key: "X-Content-Type-Options", value: "nosniff" },
35
+ { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
36
+ { key: "X-DNS-Prefetch-Control", value: "on" },
37
+ { key: "X-Permitted-Cross-Domain-Policies", value: "none" },
38
+ { key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains; preload" },
39
+ { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
40
+ {
41
+ key: "Content-Security-Policy",
42
+ value: [
43
+ "default-src 'self'",
44
+ // Scripts: Next.js needs unsafe-inline for inline event handlers; Turnstile needs its domain
45
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://www.googletagmanager.com",
46
+ // Styles: Google Fonts + Tailwind inline
47
+ "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
48
+ // Fonts: Google Fonts CDN
49
+ "font-src 'self' https://fonts.gstatic.com",
50
+ // Images: broad HTTPS allowed (user avatar, AI-generated images)
51
+ "img-src 'self' data: blob: https:",
52
+ // Frames: Cloudflare Turnstile widget + same-origin embeds
53
+ "frame-src 'self' https://challenges.cloudflare.com",
54
+ // Connect: AI APIs + Cloudflare + analytics
55
+ "connect-src 'self' https://api.openai.com https://api.anthropic.com https://generativelanguage.googleapis.com https://challenges.cloudflare.com wss: ws:",
56
+ // Media / workers (Next.js internals)
57
+ "media-src 'self' blob:",
58
+ "worker-src 'self' blob:",
59
+ ].join("; "),
60
+ },
61
+ ],
62
+ },
63
+ {
64
+ // Cache logos & brand assets for 1 year
65
+ source: "/logos/(.*)",
66
+ headers: [
67
+ { key: "Cache-Control", value: "public, max-age=31536000, immutable" },
68
+ ],
69
+ },
70
+ {
71
+ // Cache other static public assets for 1 day
72
+ source: "/(.*\\.(?:svg|png|jpg|jpeg|gif|ico|woff2|woff|ttf))",
73
+ headers: [
74
+ { key: "Cache-Control", value: "public, max-age=86400, stale-while-revalidate=3600" },
75
+ ],
76
+ },
77
+ ];
78
+ },
79
+
80
+ // Performance optimizations – tree-shake large packages
81
+ experimental: {
82
+ optimizePackageImports: [
83
+ "lucide-react",
84
+ "react-icons",
85
+ "framer-motion",
86
+ "recharts",
87
+ "@radix-ui/react-dropdown-menu",
88
+ "@radix-ui/react-select",
89
+ "@radix-ui/react-tabs",
90
+ "@radix-ui/react-tooltip",
91
+ ],
92
+ },
93
+ };
94
+
95
+ export default nextConfig;
openprompt-extension/README.md ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OpenPrompt Browser Extension
2
+
3
+ A browser extension that allows you to open prompts directly in ChatGPT, Claude, Gemini, Perplexity, Mistral, and Microsoft Copilot with one click.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **One-Click Injection** - Send prompts directly to your favorite AI chat interface
8
+ - 🤖 **Multiple Platforms** - Supports ChatGPT, Claude, Gemini, Perplexity, Mistral, Copilot
9
+ - 📝 **Recent History** - Access your recently used prompts
10
+ - ⚡ **Default Platform** - Set your preferred AI platform for quick access
11
+ - 🔒 **Privacy First** - All data stays in your browser
12
+
13
+ ## Supported Platforms
14
+
15
+ | Platform | URL | Status |
16
+ |----------|-----|--------|
17
+ | ChatGPT | chatgpt.com | ✅ Supported |
18
+ | Claude | claude.ai | ✅ Supported |
19
+ | Gemini | gemini.google.com | ✅ Supported |
20
+ | Perplexity | perplexity.ai | ✅ Supported |
21
+ | Mistral | chat.mistral.ai | ✅ Supported |
22
+ | Copilot | copilot.microsoft.com | ✅ Supported |
23
+
24
+ ## Installation
25
+
26
+ ### Development Build
27
+
28
+ 1. Install dependencies:
29
+ ```bash
30
+ cd openprompt-extension
31
+ npm install
32
+ ```
33
+
34
+ 2. Build the extension:
35
+ ```bash
36
+ npm run build
37
+ ```
38
+
39
+ 3. Load in Chrome:
40
+ - Open `chrome://extensions/`
41
+ - Enable "Developer mode"
42
+ - Click "Load unpacked"
43
+ - Select the `dist` folder
44
+
45
+ ### Development Mode
46
+
47
+ For live reloading during development:
48
+
49
+ ```bash
50
+ npm run dev
51
+ ```
52
+
53
+ This will watch for changes and rebuild automatically.
54
+
55
+ ## Project Structure
56
+
57
+ ```
58
+ openprompt-extension/
59
+ ├── public/
60
+ │ ├── manifest.json # Chrome extension manifest
61
+ │ └── icons/ # Extension icons
62
+ ├── src/
63
+ │ ├── popup/ # Extension popup UI (React)
64
+ │ │ ├── App.tsx
65
+ │ │ ├── main.tsx
66
+ │ │ └── index.css
67
+ │ ├── background/ # Service worker
68
+ │ │ └── index.ts
69
+ │ └── content-scripts/ # Platform-specific injection scripts
70
+ │ ├── chatgpt.ts
71
+ │ ├── claude.ts
72
+ │ ├── gemini.ts
73
+ │ ├── perplexity.ts
74
+ │ ├── mistral.ts
75
+ │ ├── copilot.ts
76
+ │ ├── openprompt-site.ts
77
+ │ └── utils.ts
78
+ ├── popup.html
79
+ ├── package.json
80
+ ├── vite.config.ts
81
+ ├── tailwind.config.js
82
+ └── tsconfig.json
83
+ ```
84
+
85
+ ## How It Works
86
+
87
+ 1. **From OpenPrompt Website:**
88
+ - Click "Open In..." on any prompt
89
+ - Select your preferred AI platform
90
+ - The prompt is automatically filled in the chat input
91
+
92
+ 2. **From Extension Popup:**
93
+ - Click the extension icon
94
+ - View pending prompts
95
+ - Choose where to open them
96
+ - Access recent prompt history
97
+
98
+ ## API Communication
99
+
100
+ The extension communicates with the OpenPrompt website via:
101
+ - `window.postMessage` for content script communication
102
+ - `chrome.runtime.sendMessage` for external messaging
103
+ - Chrome storage API for state persistence
104
+
105
+ ## Building for Production
106
+
107
+ ```bash
108
+ npm run build
109
+ ```
110
+
111
+ The built extension will be in the `dist` folder, ready for Chrome Web Store submission.
112
+
113
+ ## Publishing
114
+
115
+ 1. Create a developer account on Chrome Web Store
116
+ 2. Package the `dist` folder as a ZIP
117
+ 3. Upload to Chrome Web Store Developer Dashboard
118
+ 4. Submit for review
119
+
120
+ ## Icons
121
+
122
+ For production, you'll need to convert the SVG icon to PNG at these sizes:
123
+ - icon16.png (16x16)
124
+ - icon48.png (48x48)
125
+ - icon128.png (128x128)
126
+
127
+ Use a tool like [CloudConvert](https://cloudconvert.com/svg-to-png) or run:
128
+ ```bash
129
+ # With ImageMagick
130
+ convert -background none -resize 16x16 public/icons/icon.svg public/icons/icon16.png
131
+ convert -background none -resize 48x48 public/icons/icon.svg public/icons/icon48.png
132
+ convert -background none -resize 128x128 public/icons/icon.svg public/icons/icon128.png
133
+ ```
134
+
135
+ ## License
136
+
137
+ MIT - Part of the OpenPrompt project
openprompt-extension/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
openprompt-extension/package.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "openprompt-extension",
3
+ "version": "1.0.0",
4
+ "description": "Open prompts directly in ChatGPT, Claude, Gemini, and other AI chat interfaces",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite build --watch",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "lucide-react": "^0.469.0",
13
+ "react": "^18.3.1",
14
+ "react-dom": "^18.3.1"
15
+ },
16
+ "devDependencies": {
17
+ "@types/chrome": "^0.0.283",
18
+ "@types/react": "^18.3.17",
19
+ "@types/react-dom": "^18.3.5",
20
+ "@vitejs/plugin-react": "^4.3.4",
21
+ "autoprefixer": "^10.4.20",
22
+ "postcss": "^8.4.49",
23
+ "sharp": "^0.34.5",
24
+ "tailwindcss": "^3.4.17",
25
+ "typescript": "^5.7.2",
26
+ "vite": "^6.0.5"
27
+ }
28
+ }
openprompt-extension/popup.html ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>OpenPrompt</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/popup/main.tsx"></script>
11
+ </body>
12
+ </html>
openprompt-extension/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
openprompt-extension/public/icon.svg ADDED
openprompt-extension/public/icons/icon128.png ADDED

Git LFS Details

  • SHA256: 35287b08bfd69409b3d6a53f3df5f7c9d8f9b54831cba0466c92fe52585e3046
  • Pointer size: 129 Bytes
  • Size of remote file: 7.71 kB
openprompt-extension/public/icons/icon16.png ADDED

Git LFS Details

  • SHA256: 46be2ab7741008406fc7920ab6f98c8be47bbd0f0664abf3a06d75849f26ca60
  • Pointer size: 128 Bytes
  • Size of remote file: 589 Bytes
openprompt-extension/public/icons/icon48.png ADDED

Git LFS Details

  • SHA256: 55ee3d3b0d30f8790b1e39311a1b2b6362d8ec0e9f18337a01c77f3920e2521c
  • Pointer size: 129 Bytes
  • Size of remote file: 2.33 kB
openprompt-extension/public/icons/logo.svg ADDED
openprompt-extension/public/manifest.json ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "OpenPrompt - Open in AI",
4
+ "version": "1.1.0",
5
+ "description": "Open prompts directly in ChatGPT, Claude, Gemini, Perplexity, and other AI chat interfaces with one click.",
6
+ "permissions": [
7
+ "activeTab",
8
+ "storage",
9
+ "tabs",
10
+ "contextMenus",
11
+ "clipboardWrite",
12
+ "clipboardRead"
13
+ ],
14
+ "commands": {
15
+ "_execute_action": {
16
+ "suggested_key": {
17
+ "default": "Ctrl+Shift+O",
18
+ "mac": "Command+Shift+O"
19
+ },
20
+ "description": "Open OpenPrompt popup"
21
+ },
22
+ "quick-search": {
23
+ "suggested_key": {
24
+ "default": "Ctrl+Shift+P",
25
+ "mac": "Command+Shift+P"
26
+ },
27
+ "description": "Quick search prompts"
28
+ },
29
+ "capture-selection": {
30
+ "suggested_key": {
31
+ "default": "Ctrl+Shift+S",
32
+ "mac": "Command+Shift+S"
33
+ },
34
+ "description": "Capture selected text as prompt"
35
+ }
36
+ },
37
+ "host_permissions": [
38
+ "https://chat.openai.com/*",
39
+ "https://chatgpt.com/*",
40
+ "https://claude.ai/*",
41
+ "https://gemini.google.com/*",
42
+ "https://www.perplexity.ai/*",
43
+ "https://chat.mistral.ai/*",
44
+ "https://poe.com/*",
45
+ "https://you.com/*",
46
+ "https://copilot.microsoft.com/*",
47
+ "https://open-prompt.netlify.app/*",
48
+ "http://localhost:3000/*"
49
+ ],
50
+ "action": {
51
+ "default_popup": "popup.html",
52
+ "default_icon": {
53
+ "16": "icons/icon16.png",
54
+ "48": "icons/icon48.png",
55
+ "128": "icons/icon128.png"
56
+ }
57
+ },
58
+ "icons": {
59
+ "16": "icons/icon16.png",
60
+ "48": "icons/icon48.png",
61
+ "128": "icons/icon128.png"
62
+ },
63
+ "background": {
64
+ "service_worker": "background.js",
65
+ "type": "module"
66
+ },
67
+ "content_scripts": [
68
+ {
69
+ "matches": [
70
+ "https://chat.openai.com/*",
71
+ "https://chatgpt.com/*"
72
+ ],
73
+ "js": [
74
+ "content-scripts/chatgpt.js"
75
+ ],
76
+ "run_at": "document_idle"
77
+ },
78
+ {
79
+ "matches": [
80
+ "https://claude.ai/*"
81
+ ],
82
+ "js": [
83
+ "content-scripts/claude.js"
84
+ ],
85
+ "run_at": "document_idle"
86
+ },
87
+ {
88
+ "matches": [
89
+ "https://gemini.google.com/*"
90
+ ],
91
+ "js": [
92
+ "content-scripts/gemini.js"
93
+ ],
94
+ "run_at": "document_idle"
95
+ },
96
+ {
97
+ "matches": [
98
+ "https://www.perplexity.ai/*"
99
+ ],
100
+ "js": [
101
+ "content-scripts/perplexity.js"
102
+ ],
103
+ "run_at": "document_idle"
104
+ },
105
+ {
106
+ "matches": [
107
+ "https://chat.mistral.ai/*"
108
+ ],
109
+ "js": [
110
+ "content-scripts/mistral.js"
111
+ ],
112
+ "run_at": "document_idle"
113
+ },
114
+ {
115
+ "matches": [
116
+ "https://copilot.microsoft.com/*"
117
+ ],
118
+ "js": [
119
+ "content-scripts/copilot.js"
120
+ ],
121
+ "run_at": "document_idle"
122
+ },
123
+ {
124
+ "matches": [
125
+ "https://open-prompt.netlify.app/*",
126
+ "http://localhost:3000/*"
127
+ ],
128
+ "js": [
129
+ "content-scripts/openprompt-site.js"
130
+ ],
131
+ "run_at": "document_idle"
132
+ }
133
+ ],
134
+ "externally_connectable": {
135
+ "matches": [
136
+ "https://open-prompt.netlify.app/*",
137
+ "http://localhost:3000/*"
138
+ ]
139
+ },
140
+ "web_accessible_resources": [
141
+ {
142
+ "resources": [
143
+ "icons/*"
144
+ ],
145
+ "matches": [
146
+ "<all_urls>"
147
+ ]
148
+ }
149
+ ]
150
+ }
openprompt-extension/public/small-promo-tile.png ADDED

Git LFS Details

  • SHA256: f71a2c2e29c482527b1dd7306736923ba4cebc80c95d924e79b33a62dd7741ef
  • Pointer size: 130 Bytes
  • Size of remote file: 20.9 kB
openprompt-extension/scripts/create-icons.js ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Simple script to create placeholder icon PNGs
2
+ // Run with: node scripts/create-icons.js
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ // Simple 1x1 purple pixel PNG base64 - we'll use SVG icons for now
8
+ // For production, use a proper image conversion tool
9
+
10
+ const iconSizes = [16, 48, 128];
11
+ const iconsDir = path.join(__dirname, '../public/icons');
12
+
13
+ // Create a simple colored square PNG (this is a minimal valid PNG)
14
+ function createMinimalPng(size) {
15
+ // For now, just create a note that real icons are needed
16
+ console.log(`Note: Create ${size}x${size} PNG icon at public/icons/icon${size}.png`);
17
+ console.log(`You can convert the SVG using: https://cloudconvert.com/svg-to-png`);
18
+ }
19
+
20
+ iconSizes.forEach(size => {
21
+ createMinimalPng(size);
22
+ });
23
+
24
+ console.log('\nTo create proper icons, run:');
25
+ console.log('npx sharp-cli --input public/icons/icon.svg --output public/icons/icon16.png --resize 16');
26
+ console.log('npx sharp-cli --input public/icons/icon.svg --output public/icons/icon48.png --resize 48');
27
+ console.log('npx sharp-cli --input public/icons/icon.svg --output public/icons/icon128.png --resize 128');
openprompt-extension/scripts/generate-icons.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Create extension icons from SVG
2
+ import sharp from 'sharp';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const sizes = [16, 48, 128];
11
+ const svgPath = path.join(__dirname, '../public/icons/icon.svg');
12
+ const outputDir = path.join(__dirname, '../public/icons');
13
+
14
+ // Ensure output directory exists
15
+ if (!fs.existsSync(outputDir)) {
16
+ fs.mkdirSync(outputDir, { recursive: true });
17
+ }
18
+
19
+ async function createIcons() {
20
+ const svgBuffer = fs.readFileSync(svgPath);
21
+
22
+ for (const size of sizes) {
23
+ const outputPath = path.join(outputDir, `icon${size}.png`);
24
+
25
+ await sharp(svgBuffer)
26
+ .resize(size, size)
27
+ .png()
28
+ .toFile(outputPath);
29
+
30
+ console.log(`Created: icon${size}.png`);
31
+ }
32
+
33
+ console.log('\nAll icons created successfully!');
34
+ }
35
+
36
+ createIcons().catch(console.error);
openprompt-extension/src/background/index.ts ADDED
@@ -0,0 +1,450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Background Service Worker for OpenPrompt Extension
2
+
3
+ // Configurable API base URL — defaults to production, overridable via extension storage
4
+ let API_BASE = 'https://open-prompt.netlify.app';
5
+
6
+ // Load custom API URL from extension settings (set in options page)
7
+ (async () => {
8
+ try {
9
+ const result = await chrome.storage.sync.get(['apiBaseUrl']);
10
+ if (result.apiBaseUrl) API_BASE = result.apiBaseUrl;
11
+ } catch { /* use default */ }
12
+ })();
13
+
14
+ const PLATFORMS: Record<string, string> = {
15
+ chatgpt: 'https://chatgpt.com',
16
+ claude: 'https://claude.ai',
17
+ gemini: 'https://gemini.google.com',
18
+ perplexity: 'https://www.perplexity.ai',
19
+ mistral: 'https://chat.mistral.ai',
20
+ copilot: 'https://copilot.microsoft.com',
21
+ };
22
+
23
+ const PLATFORM_URLS: Record<string, string[]> = {
24
+ chatgpt: ['chat.openai.com', 'chatgpt.com'],
25
+ claude: ['claude.ai'],
26
+ gemini: ['gemini.google.com'],
27
+ perplexity: ['perplexity.ai'],
28
+ mistral: ['chat.mistral.ai'],
29
+ copilot: ['copilot.microsoft.com'],
30
+ };
31
+
32
+ // Get or create device ID for analytics
33
+ async function getDeviceId(): Promise<string> {
34
+ const result = await chrome.storage.local.get(['deviceId']);
35
+ if (result.deviceId) return result.deviceId;
36
+
37
+ const deviceId = 'ext_' + crypto.randomUUID();
38
+ await chrome.storage.local.set({ deviceId });
39
+ return deviceId;
40
+ }
41
+
42
+ // Get user ID if logged in
43
+ async function getUserId(): Promise<string | null> {
44
+ const result = await chrome.storage.local.get(['userId']);
45
+ return result.userId || null;
46
+ }
47
+
48
+ // Track analytics
49
+ async function trackAnalytics(data: {
50
+ action: string;
51
+ platform?: string;
52
+ platforms?: string[];
53
+ promptId?: string;
54
+ promptTitle?: string;
55
+ promptText?: string;
56
+ metadata?: Record<string, unknown>;
57
+ }): Promise<void> {
58
+ try {
59
+ const deviceId = await getDeviceId();
60
+ const userId = await getUserId();
61
+
62
+ await fetch(`${API_BASE}/api/extension/analytics`, {
63
+ method: 'POST',
64
+ headers: { 'Content-Type': 'application/json' },
65
+ body: JSON.stringify({
66
+ deviceId,
67
+ userId,
68
+ ...data,
69
+ }),
70
+ });
71
+ } catch (error) {
72
+ console.error('[OpenPrompt] Analytics error:', error);
73
+ }
74
+ }
75
+
76
+ // Create context menus on install
77
+ function createContextMenus(): void {
78
+ chrome.contextMenus.removeAll(() => {
79
+ // Send selected text to AI
80
+ chrome.contextMenus.create({
81
+ id: 'send-to-ai',
82
+ title: 'Send to AI',
83
+ contexts: ['selection'],
84
+ });
85
+
86
+ // Sub-menus for each platform
87
+ Object.entries(PLATFORMS).forEach(([id, _url]) => {
88
+ chrome.contextMenus.create({
89
+ id: `send-to-${id}`,
90
+ parentId: 'send-to-ai',
91
+ title: id.charAt(0).toUpperCase() + id.slice(1),
92
+ contexts: ['selection'],
93
+ });
94
+ });
95
+
96
+ // Capture prompt
97
+ chrome.contextMenus.create({
98
+ id: 'capture-prompt',
99
+ title: 'Save to OpenPrompt',
100
+ contexts: ['selection'],
101
+ });
102
+
103
+ // Open OpenPrompt
104
+ chrome.contextMenus.create({
105
+ id: 'open-openprompt',
106
+ title: 'Browse OpenPrompt',
107
+ contexts: ['page'],
108
+ });
109
+ });
110
+ }
111
+
112
+ // Handle context menu clicks
113
+ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
114
+ const selectedText = info.selectionText;
115
+
116
+ if (info.menuItemId === 'open-openprompt') {
117
+ chrome.tabs.create({ url: API_BASE });
118
+ return;
119
+ }
120
+
121
+ if (info.menuItemId === 'capture-prompt' && selectedText) {
122
+ // Save to captured prompts
123
+ const deviceId = await getDeviceId();
124
+ const userId = await getUserId();
125
+ const platform = detectPlatformFromUrl(tab?.url || '');
126
+
127
+ try {
128
+ await fetch(`${API_BASE}/api/extension/capture`, {
129
+ method: 'POST',
130
+ headers: { 'Content-Type': 'application/json' },
131
+ body: JSON.stringify({
132
+ deviceId,
133
+ userId,
134
+ text: selectedText,
135
+ platform: platform || 'unknown',
136
+ url: tab?.url,
137
+ }),
138
+ });
139
+
140
+ // Show notification
141
+ if (tab?.id) {
142
+ chrome.tabs.sendMessage(tab.id, {
143
+ type: 'SHOW_NOTIFICATION',
144
+ message: '✓ Prompt saved to OpenPrompt!',
145
+ }).catch(() => {});
146
+ }
147
+
148
+ trackAnalytics({
149
+ action: 'capture',
150
+ platform: platform || 'unknown',
151
+ promptText: selectedText.slice(0, 500),
152
+ metadata: { source: 'context_menu' },
153
+ });
154
+ } catch (error) {
155
+ console.error('[OpenPrompt] Capture error:', error);
156
+ }
157
+ return;
158
+ }
159
+
160
+ // Handle send-to-* menus
161
+ if (info.menuItemId?.toString().startsWith('send-to-') && selectedText) {
162
+ const platformId = info.menuItemId.toString().replace('send-to-', '');
163
+ const url = PLATFORMS[platformId];
164
+
165
+ if (url) {
166
+ await chrome.storage.local.set({
167
+ promptToInject: {
168
+ prompt: selectedText,
169
+ platform: platformId,
170
+ timestamp: Date.now(),
171
+ },
172
+ });
173
+
174
+ chrome.tabs.create({ url });
175
+
176
+ trackAnalytics({
177
+ action: 'context_menu',
178
+ platform: platformId,
179
+ promptText: selectedText.slice(0, 500),
180
+ });
181
+ }
182
+ }
183
+ });
184
+
185
+ // Detect which AI platform from URL
186
+ function detectPlatformFromUrl(url: string): string | null {
187
+ for (const [platform, urls] of Object.entries(PLATFORM_URLS)) {
188
+ if (urls.some(u => url.includes(u))) {
189
+ return platform;
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+
195
+ // Handle keyboard shortcuts
196
+ chrome.commands.onCommand.addListener(async (command) => {
197
+ console.log('[OpenPrompt] Command:', command);
198
+
199
+ if (command === 'quick-search') {
200
+ // Open popup with search focused
201
+ chrome.action.openPopup().catch(() => {
202
+ // Fallback: set badge to indicate action
203
+ chrome.action.setBadgeText({ text: 'S' });
204
+ chrome.action.setBadgeBackgroundColor({ color: '#9333ea' });
205
+ setTimeout(() => chrome.action.setBadgeText({ text: '' }), 3000);
206
+ });
207
+
208
+ trackAnalytics({
209
+ action: 'shortcut_use',
210
+ metadata: { shortcut: 'quick-search' },
211
+ });
212
+ }
213
+
214
+ if (command === 'capture-selection') {
215
+ // Get current tab and capture selected text
216
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
217
+ if (tab?.id) {
218
+ chrome.tabs.sendMessage(tab.id, { type: 'CAPTURE_SELECTION' }).catch(() => {});
219
+ }
220
+
221
+ trackAnalytics({
222
+ action: 'shortcut_use',
223
+ metadata: { shortcut: 'capture-selection' },
224
+ });
225
+ }
226
+ });
227
+
228
+ // Handle messages from content scripts
229
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
230
+ if (message.type === 'OPEN_TAB') {
231
+ chrome.tabs.create({ url: message.url }, (tab) => {
232
+ sendResponse({ success: true, tabId: tab?.id });
233
+ });
234
+ return true;
235
+ }
236
+
237
+ if (message.type === 'OPEN_POPUP') {
238
+ chrome.action.setBadgeText({ text: '1' });
239
+ chrome.action.setBadgeBackgroundColor({ color: '#9333ea' });
240
+ setTimeout(() => chrome.action.setBadgeText({ text: '' }), 5000);
241
+ sendResponse({ success: true });
242
+ return true;
243
+ }
244
+
245
+ if (message.type === 'CAPTURE_PROMPT') {
246
+ // Capture from content script
247
+ (async () => {
248
+ const deviceId = await getDeviceId();
249
+ const userId = await getUserId();
250
+ const platform = detectPlatformFromUrl(sender.tab?.url || '');
251
+
252
+ try {
253
+ const response = await fetch(`${API_BASE}/api/extension/capture`, {
254
+ method: 'POST',
255
+ headers: { 'Content-Type': 'application/json' },
256
+ body: JSON.stringify({
257
+ deviceId,
258
+ userId,
259
+ text: message.text,
260
+ title: message.title,
261
+ platform: platform || 'unknown',
262
+ url: sender.tab?.url,
263
+ }),
264
+ });
265
+
266
+ const data = await response.json();
267
+ sendResponse({ success: true, id: data.id });
268
+
269
+ trackAnalytics({
270
+ action: 'capture',
271
+ platform: platform || 'unknown',
272
+ promptText: message.text?.slice(0, 500),
273
+ });
274
+ } catch (error) {
275
+ sendResponse({ success: false, error: String(error) });
276
+ }
277
+ })();
278
+ return true;
279
+ }
280
+
281
+ if (message.type === 'BROADCAST_PROMPT') {
282
+ // Send to multiple platforms
283
+ const { prompt, platforms: targetPlatforms } = message;
284
+
285
+ (async () => {
286
+ for (const platformId of targetPlatforms) {
287
+ const url = PLATFORMS[platformId];
288
+ if (url) {
289
+ await chrome.storage.local.set({
290
+ [`promptToInject_${platformId}`]: {
291
+ prompt,
292
+ platform: platformId,
293
+ timestamp: Date.now(),
294
+ },
295
+ });
296
+ chrome.tabs.create({ url });
297
+ }
298
+ }
299
+
300
+ trackAnalytics({
301
+ action: 'broadcast',
302
+ platforms: targetPlatforms,
303
+ promptText: prompt?.slice(0, 500),
304
+ });
305
+
306
+ sendResponse({ success: true });
307
+ })();
308
+ return true;
309
+ }
310
+
311
+ if (message.type === 'TRACK_ANALYTICS') {
312
+ trackAnalytics(message.data).then(() => sendResponse({ success: true }));
313
+ return true;
314
+ }
315
+
316
+ if (message.type === 'SET_USER_ID') {
317
+ chrome.storage.local.set({ userId: message.userId }).then(() => {
318
+ sendResponse({ success: true });
319
+ });
320
+ return true;
321
+ }
322
+
323
+ if (message.type === 'GET_DEVICE_ID') {
324
+ getDeviceId().then(deviceId => sendResponse({ deviceId }));
325
+ return true;
326
+ }
327
+ });
328
+
329
+ // Handle external messages from the website
330
+ chrome.runtime.onMessageExternal.addListener(
331
+ (message, sender, sendResponse) => {
332
+ console.log('[OpenPrompt] External message received:', message, 'from:', sender);
333
+
334
+ if (message.type === 'CHECK_EXTENSION') {
335
+ sendResponse({ installed: true, version: chrome.runtime.getManifest().version });
336
+ return true;
337
+ }
338
+
339
+ if (message.type === 'SYNC_USER') {
340
+ chrome.storage.local.set({ userId: message.userId }).then(() => {
341
+ sendResponse({ success: true });
342
+ });
343
+ return true;
344
+ }
345
+
346
+ if (message.type === 'OPEN_IN_AI') {
347
+ chrome.storage.local.set({
348
+ pendingPrompt: {
349
+ prompt: message.prompt,
350
+ title: message.title,
351
+ promptId: message.promptId,
352
+ timestamp: Date.now(),
353
+ }
354
+ }).then(async () => {
355
+ if (message.platform) {
356
+ const url = PLATFORMS[message.platform];
357
+ if (url) {
358
+ await chrome.storage.local.set({
359
+ promptToInject: {
360
+ prompt: message.prompt,
361
+ platform: message.platform,
362
+ timestamp: Date.now(),
363
+ }
364
+ });
365
+ chrome.tabs.create({ url });
366
+
367
+ trackAnalytics({
368
+ action: 'inject',
369
+ platform: message.platform,
370
+ promptId: message.promptId,
371
+ promptTitle: message.title,
372
+ });
373
+ }
374
+ }
375
+ sendResponse({ success: true });
376
+ });
377
+ return true;
378
+ }
379
+ }
380
+ );
381
+
382
+ // Handle installation
383
+ chrome.runtime.onInstalled.addListener((details) => {
384
+ if (details.reason === 'install') {
385
+ console.log('[OpenPrompt] Extension installed');
386
+ createContextMenus();
387
+ chrome.tabs.create({
388
+ url: `${API_BASE}/extension?installed=true`
389
+ });
390
+
391
+ trackAnalytics({
392
+ action: 'install',
393
+ metadata: { version: chrome.runtime.getManifest().version },
394
+ });
395
+ } else if (details.reason === 'update') {
396
+ console.log('[OpenPrompt] Extension updated to version', chrome.runtime.getManifest().version);
397
+ createContextMenus();
398
+
399
+ trackAnalytics({
400
+ action: 'update',
401
+ metadata: { version: chrome.runtime.getManifest().version },
402
+ });
403
+ }
404
+ });
405
+
406
+ // Handle tab updates to inject prompts
407
+ chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
408
+ if (changeInfo.status === 'complete' && tab.url) {
409
+ // Check for regular prompt injection
410
+ const result = await chrome.storage.local.get(['promptToInject']);
411
+
412
+ if (result.promptToInject) {
413
+ const { platform } = result.promptToInject;
414
+ const urls = PLATFORM_URLS[platform] || [];
415
+ const isMatchingUrl = urls.some(url => tab.url?.includes(url));
416
+
417
+ if (isMatchingUrl) {
418
+ setTimeout(() => {
419
+ chrome.tabs.sendMessage(tabId, { type: 'INJECT_PROMPT' }).catch(() => {});
420
+ }, 1000);
421
+ }
422
+ }
423
+
424
+ // Check for broadcast prompts
425
+ for (const platformId of Object.keys(PLATFORMS)) {
426
+ const broadcastResult = await chrome.storage.local.get([`promptToInject_${platformId}`]);
427
+ const promptData = broadcastResult[`promptToInject_${platformId}`];
428
+
429
+ if (promptData) {
430
+ const urls = PLATFORM_URLS[platformId] || [];
431
+ const isMatchingUrl = urls.some(url => tab.url?.includes(url));
432
+
433
+ if (isMatchingUrl) {
434
+ // Move broadcast prompt to regular prompt slot for injection
435
+ await chrome.storage.local.set({ promptToInject: promptData });
436
+ await chrome.storage.local.remove(`promptToInject_${platformId}`);
437
+
438
+ setTimeout(() => {
439
+ chrome.tabs.sendMessage(tabId, { type: 'INJECT_PROMPT' }).catch(() => {});
440
+ }, 1000);
441
+ }
442
+ }
443
+ }
444
+ }
445
+ });
446
+
447
+ // Initialize context menus on startup
448
+ createContextMenus();
449
+
450
+ console.log('[OpenPrompt] Background service worker started');
openprompt-extension/src/content-scripts/chatgpt.ts ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ChatGPT Content Script
2
+ import {
3
+ getPromptToInject,
4
+ clearPromptToInject,
5
+ waitForElement,
6
+ showNotification,
7
+ setupCaptureListener,
8
+ createCaptureButton
9
+ } from './utils';
10
+
11
+ async function injectPrompt(): Promise<void> {
12
+ const promptData = await getPromptToInject();
13
+
14
+ if (!promptData || promptData.platform !== 'chatgpt') {
15
+ return;
16
+ }
17
+
18
+ console.log('[OpenPrompt] Injecting prompt into ChatGPT...');
19
+
20
+ // Wait for the textarea to be available
21
+ // ChatGPT uses a contenteditable div with id="prompt-textarea"
22
+ const textareaSelector = '#prompt-textarea';
23
+ const textarea = await waitForElement(textareaSelector, 15000);
24
+
25
+ if (!textarea) {
26
+ console.error('[OpenPrompt] Could not find ChatGPT textarea');
27
+ showNotification('Could not find input field. Please try again.', 'error');
28
+ return;
29
+ }
30
+
31
+ try {
32
+ // Focus the textarea
33
+ (textarea as HTMLElement).focus();
34
+
35
+ // Clear existing content
36
+ textarea.innerHTML = '';
37
+
38
+ // Create a paragraph element with the prompt text
39
+ const p = document.createElement('p');
40
+ p.textContent = promptData.prompt;
41
+ textarea.appendChild(p);
42
+
43
+ // Dispatch input event to trigger React state update
44
+ textarea.dispatchEvent(new InputEvent('input', {
45
+ bubbles: true,
46
+ cancelable: true,
47
+ inputType: 'insertText',
48
+ data: promptData.prompt,
49
+ }));
50
+
51
+ // Clear the pending prompt
52
+ await clearPromptToInject();
53
+
54
+ showNotification('✓ Prompt loaded! Press Enter to send.');
55
+ console.log('[OpenPrompt] Prompt injected successfully');
56
+ } catch (error) {
57
+ console.error('[OpenPrompt] Error injecting prompt:', error);
58
+ showNotification('Failed to inject prompt. Please try again.', 'error');
59
+ }
60
+ }
61
+
62
+ // Initialize
63
+ function init(): void {
64
+ // Setup capture listener for keyboard shortcut
65
+ setupCaptureListener();
66
+
67
+ // Add floating capture button
68
+ createCaptureButton('ChatGPT');
69
+
70
+ // Try to inject prompt
71
+ setTimeout(injectPrompt, 1500);
72
+ }
73
+
74
+ // Run on page load
75
+ if (document.readyState === 'loading') {
76
+ document.addEventListener('DOMContentLoaded', init);
77
+ } else {
78
+ init();
79
+ }
80
+
81
+ // Listen for messages from the extension
82
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
83
+ if (message.type === 'INJECT_PROMPT') {
84
+ injectPrompt().then(() => sendResponse({ success: true }));
85
+ return true;
86
+ }
87
+ });
openprompt-extension/src/content-scripts/claude.ts ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Claude Content Script
2
+ import {
3
+ getPromptToInject,
4
+ clearPromptToInject,
5
+ waitForElement,
6
+ showNotification,
7
+ setupCaptureListener,
8
+ createCaptureButton
9
+ } from './utils';
10
+
11
+ async function injectPrompt(): Promise<void> {
12
+ const promptData = await getPromptToInject();
13
+
14
+ if (!promptData || promptData.platform !== 'claude') {
15
+ return;
16
+ }
17
+
18
+ console.log('[OpenPrompt] Injecting prompt into Claude...');
19
+
20
+ // Claude uses a contenteditable div with class containing "ProseMirror"
21
+ const textareaSelector = '[contenteditable="true"].ProseMirror, div[contenteditable="true"]';
22
+ const textarea = await waitForElement(textareaSelector, 15000);
23
+
24
+ if (!textarea) {
25
+ console.error('[OpenPrompt] Could not find Claude textarea');
26
+ showNotification('Could not find input field. Please try again.', 'error');
27
+ return;
28
+ }
29
+
30
+ try {
31
+ const element = textarea as HTMLElement;
32
+
33
+ // Focus the element
34
+ element.focus();
35
+
36
+ // Clear existing content
37
+ element.innerHTML = '';
38
+
39
+ // For ProseMirror editors, we need to create a paragraph
40
+ const p = document.createElement('p');
41
+ p.textContent = promptData.prompt;
42
+ element.appendChild(p);
43
+
44
+ // Dispatch input event
45
+ element.dispatchEvent(new InputEvent('input', {
46
+ bubbles: true,
47
+ cancelable: true,
48
+ inputType: 'insertText',
49
+ data: promptData.prompt,
50
+ }));
51
+
52
+ // Clear the pending prompt
53
+ await clearPromptToInject();
54
+
55
+ showNotification('✓ Prompt loaded! Press Enter to send.');
56
+ console.log('[OpenPrompt] Prompt injected successfully');
57
+ } catch (error) {
58
+ console.error('[OpenPrompt] Error injecting prompt:', error);
59
+ showNotification('Failed to inject prompt. Please try again.', 'error');
60
+ }
61
+ }
62
+
63
+ // Initialize
64
+ function init(): void {
65
+ setupCaptureListener();
66
+ createCaptureButton('Claude');
67
+ setTimeout(injectPrompt, 2000);
68
+ }
69
+
70
+ // Run on page load
71
+ if (document.readyState === 'loading') {
72
+ document.addEventListener('DOMContentLoaded', init);
73
+ } else {
74
+ init();
75
+ }
76
+
77
+ // Listen for messages from the extension
78
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
79
+ if (message.type === 'INJECT_PROMPT') {
80
+ injectPrompt().then(() => sendResponse({ success: true }));
81
+ return true;
82
+ }
83
+ });
openprompt-extension/src/content-scripts/copilot.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Microsoft Copilot Content Script
2
+ import {
3
+ getPromptToInject,
4
+ clearPromptToInject,
5
+ waitForElement,
6
+ showNotification,
7
+ setupCaptureListener,
8
+ createCaptureButton
9
+ } from './utils';
10
+
11
+ async function injectPrompt(): Promise<void> {
12
+ const promptData = await getPromptToInject();
13
+
14
+ if (!promptData || promptData.platform !== 'copilot') {
15
+ return;
16
+ }
17
+
18
+ console.log('[OpenPrompt] Injecting prompt into Copilot...');
19
+
20
+ // Copilot uses a textarea
21
+ const textareaSelector = 'textarea#userInput, textarea[name="searchbox"], textarea';
22
+ const textarea = await waitForElement(textareaSelector, 15000);
23
+
24
+ if (!textarea) {
25
+ console.error('[OpenPrompt] Could not find Copilot textarea');
26
+ showNotification('Could not find input field. Please try again.', 'error');
27
+ return;
28
+ }
29
+
30
+ try {
31
+ const element = textarea as HTMLTextAreaElement;
32
+
33
+ // Focus and set value
34
+ element.focus();
35
+ element.value = promptData.prompt;
36
+
37
+ // Dispatch events
38
+ element.dispatchEvent(new Event('input', { bubbles: true }));
39
+ element.dispatchEvent(new Event('change', { bubbles: true }));
40
+
41
+ // Clear the pending prompt
42
+ await clearPromptToInject();
43
+
44
+ showNotification('✓ Prompt loaded! Press Enter to send.');
45
+ console.log('[OpenPrompt] Prompt injected successfully');
46
+ } catch (error) {
47
+ console.error('[OpenPrompt] Error injecting prompt:', error);
48
+ showNotification('Failed to inject prompt. Please try again.', 'error');
49
+ }
50
+ }
51
+
52
+ // Initialize
53
+ function init(): void {
54
+ setupCaptureListener();
55
+ createCaptureButton('Copilot');
56
+ setTimeout(injectPrompt, 2000);
57
+ }
58
+
59
+ // Run on page load
60
+ if (document.readyState === 'loading') {
61
+ document.addEventListener('DOMContentLoaded', init);
62
+ } else {
63
+ init();
64
+ }
65
+
66
+ // Listen for messages from the extension
67
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
68
+ if (message.type === 'INJECT_PROMPT') {
69
+ injectPrompt().then(() => sendResponse({ success: true }));
70
+ return true;
71
+ }
72
+ });
openprompt-extension/src/content-scripts/gemini.ts ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Gemini Content Script
2
+ import {
3
+ getPromptToInject,
4
+ clearPromptToInject,
5
+ waitForElement,
6
+ showNotification,
7
+ setupCaptureListener,
8
+ createCaptureButton
9
+ } from './utils';
10
+
11
+ async function injectPrompt(): Promise<void> {
12
+ const promptData = await getPromptToInject();
13
+
14
+ if (!promptData || promptData.platform !== 'gemini') {
15
+ return;
16
+ }
17
+
18
+ console.log('[OpenPrompt] Injecting prompt into Gemini...');
19
+
20
+ // Gemini uses a rich text input with contenteditable
21
+ const textareaSelector = '.ql-editor, [contenteditable="true"], textarea';
22
+ const textarea = await waitForElement(textareaSelector, 15000);
23
+
24
+ if (!textarea) {
25
+ console.error('[OpenPrompt] Could not find Gemini textarea');
26
+ showNotification('Could not find input field. Please try again.', 'error');
27
+ return;
28
+ }
29
+
30
+ try {
31
+ const element = textarea as HTMLElement;
32
+
33
+ // Focus the element
34
+ element.focus();
35
+
36
+ if (element.getAttribute('contenteditable') === 'true') {
37
+ // For contenteditable elements
38
+ element.innerHTML = '';
39
+
40
+ const p = document.createElement('p');
41
+ p.textContent = promptData.prompt;
42
+ element.appendChild(p);
43
+
44
+ element.dispatchEvent(new InputEvent('input', {
45
+ bubbles: true,
46
+ cancelable: true,
47
+ inputType: 'insertText',
48
+ data: promptData.prompt,
49
+ }));
50
+ } else if (element instanceof HTMLTextAreaElement) {
51
+ // For textarea elements
52
+ element.value = promptData.prompt;
53
+ element.dispatchEvent(new Event('input', { bubbles: true }));
54
+ }
55
+
56
+ // Clear the pending prompt
57
+ await clearPromptToInject();
58
+
59
+ showNotification('✓ Prompt loaded! Press Enter to send.');
60
+ console.log('[OpenPrompt] Prompt injected successfully');
61
+ } catch (error) {
62
+ console.error('[OpenPrompt] Error injecting prompt:', error);
63
+ showNotification('Failed to inject prompt. Please try again.', 'error');
64
+ }
65
+ }
66
+
67
+ // Initialize
68
+ function init(): void {
69
+ setupCaptureListener();
70
+ createCaptureButton('Gemini');
71
+ setTimeout(injectPrompt, 2000);
72
+ }
73
+
74
+ // Run on page load
75
+ if (document.readyState === 'loading') {
76
+ document.addEventListener('DOMContentLoaded', init);
77
+ } else {
78
+ init();
79
+ }
80
+
81
+ // Listen for messages from the extension
82
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
83
+ if (message.type === 'INJECT_PROMPT') {
84
+ injectPrompt().then(() => sendResponse({ success: true }));
85
+ return true;
86
+ }
87
+ });
openprompt-extension/src/content-scripts/mistral.ts ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Mistral Content Script
2
+ import {
3
+ getPromptToInject,
4
+ clearPromptToInject,
5
+ waitForElement,
6
+ showNotification,
7
+ setupCaptureListener,
8
+ createCaptureButton
9
+ } from './utils';
10
+
11
+ async function injectPrompt(): Promise<void> {
12
+ const promptData = await getPromptToInject();
13
+
14
+ if (!promptData || promptData.platform !== 'mistral') {
15
+ return;
16
+ }
17
+
18
+ console.log('[OpenPrompt] Injecting prompt into Mistral...');
19
+
20
+ // Mistral uses a textarea or contenteditable
21
+ const textareaSelector = 'textarea, [contenteditable="true"]';
22
+ const textarea = await waitForElement(textareaSelector, 15000);
23
+
24
+ if (!textarea) {
25
+ console.error('[OpenPrompt] Could not find Mistral textarea');
26
+ showNotification('Could not find input field. Please try again.', 'error');
27
+ return;
28
+ }
29
+
30
+ try {
31
+ const element = textarea as HTMLElement;
32
+ element.focus();
33
+
34
+ if (element instanceof HTMLTextAreaElement) {
35
+ element.value = promptData.prompt;
36
+ element.dispatchEvent(new Event('input', { bubbles: true }));
37
+ element.dispatchEvent(new Event('change', { bubbles: true }));
38
+ } else if (element.getAttribute('contenteditable') === 'true') {
39
+ element.innerHTML = '';
40
+ const p = document.createElement('p');
41
+ p.textContent = promptData.prompt;
42
+ element.appendChild(p);
43
+
44
+ element.dispatchEvent(new InputEvent('input', {
45
+ bubbles: true,
46
+ cancelable: true,
47
+ inputType: 'insertText',
48
+ data: promptData.prompt,
49
+ }));
50
+ }
51
+
52
+ // Clear the pending prompt
53
+ await clearPromptToInject();
54
+
55
+ showNotification('✓ Prompt loaded! Press Enter to send.');
56
+ console.log('[OpenPrompt] Prompt injected successfully');
57
+ } catch (error) {
58
+ console.error('[OpenPrompt] Error injecting prompt:', error);
59
+ showNotification('Failed to inject prompt. Please try again.', 'error');
60
+ }
61
+ }
62
+
63
+ // Initialize
64
+ function init(): void {
65
+ setupCaptureListener();
66
+ createCaptureButton('Mistral');
67
+ setTimeout(injectPrompt, 1500);
68
+ }
69
+
70
+ // Run on page load
71
+ if (document.readyState === 'loading') {
72
+ document.addEventListener('DOMContentLoaded', init);
73
+ } else {
74
+ init();
75
+ }
76
+
77
+ // Listen for messages from the extension
78
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
79
+ if (message.type === 'INJECT_PROMPT') {
80
+ injectPrompt().then(() => sendResponse({ success: true }));
81
+ return true;
82
+ }
83
+ });
openprompt-extension/src/content-scripts/openprompt-site.ts ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // OpenPrompt Website Content Script
2
+ // This script runs on the OpenPrompt website to enable communication with the extension
3
+
4
+ interface OpenInMessage {
5
+ type: 'OPEN_IN_AI';
6
+ prompt: string;
7
+ title?: string;
8
+ platform?: string;
9
+ }
10
+
11
+ // Helper to check if extension context is still valid
12
+ function isExtensionContextValid(): boolean {
13
+ try {
14
+ // This will throw if context is invalidated
15
+ return !!chrome.runtime?.id;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ // Safely call chrome APIs with error handling
22
+ async function safeStorageSet(data: Record<string, unknown>): Promise<boolean> {
23
+ try {
24
+ if (!isExtensionContextValid()) return false;
25
+ await chrome.storage.local.set(data);
26
+ return true;
27
+ } catch (error) {
28
+ console.warn('[OpenPrompt Extension] Storage error (extension may need reload):', error);
29
+ return false;
30
+ }
31
+ }
32
+
33
+ function safeSendMessage(message: unknown): void {
34
+ try {
35
+ if (!isExtensionContextValid()) return;
36
+ chrome.runtime.sendMessage(message);
37
+ } catch (error) {
38
+ console.warn('[OpenPrompt Extension] Message error (extension may need reload):', error);
39
+ }
40
+ }
41
+
42
+ // Listen for messages from the website
43
+ window.addEventListener('message', async (event) => {
44
+ // Only accept messages from the same origin
45
+ if (event.origin !== window.location.origin) {
46
+ return;
47
+ }
48
+
49
+ const message = event.data as OpenInMessage;
50
+
51
+ if (message.type === 'OPEN_IN_AI') {
52
+ console.log('[OpenPrompt Extension] Received prompt from website:', message);
53
+
54
+ // Check if extension context is still valid
55
+ if (!isExtensionContextValid()) {
56
+ console.warn('[OpenPrompt Extension] Extension context invalidated. Please refresh the page.');
57
+ window.postMessage({
58
+ type: 'OPEN_IN_AI_RECEIVED',
59
+ success: false,
60
+ error: 'Extension needs to be reloaded. Please refresh the page.',
61
+ }, '*');
62
+ return;
63
+ }
64
+
65
+ // Store the prompt for the popup or direct injection
66
+ await safeStorageSet({
67
+ pendingPrompt: {
68
+ prompt: message.prompt,
69
+ title: message.title,
70
+ timestamp: Date.now(),
71
+ }
72
+ });
73
+
74
+ // If a specific platform is requested, open it directly
75
+ if (message.platform) {
76
+ const platforms: Record<string, string> = {
77
+ chatgpt: 'https://chatgpt.com',
78
+ claude: 'https://claude.ai',
79
+ gemini: 'https://gemini.google.com',
80
+ perplexity: 'https://www.perplexity.ai',
81
+ mistral: 'https://chat.mistral.ai',
82
+ copilot: 'https://copilot.microsoft.com',
83
+ };
84
+
85
+ const url = platforms[message.platform];
86
+ if (url) {
87
+ await safeStorageSet({
88
+ promptToInject: {
89
+ prompt: message.prompt,
90
+ platform: message.platform,
91
+ timestamp: Date.now(),
92
+ }
93
+ });
94
+
95
+ safeSendMessage({
96
+ type: 'OPEN_TAB',
97
+ url: url,
98
+ });
99
+ }
100
+ } else {
101
+ // Open the extension popup to let user choose
102
+ safeSendMessage({ type: 'OPEN_POPUP' });
103
+ }
104
+
105
+ // Send confirmation back to the website
106
+ window.postMessage({
107
+ type: 'OPEN_IN_AI_RECEIVED',
108
+ success: true,
109
+ }, '*');
110
+ }
111
+ });
112
+
113
+ // Inject a script to add the extension detection
114
+ // We use postMessage instead of inline script to avoid CSP issues
115
+ window.postMessage({ type: 'OPENPROMPT_EXTENSION_READY' }, '*');
116
+
117
+ // Also set a data attribute on the body
118
+ document.body.setAttribute('data-openprompt-extension', 'true');
119
+
120
+ console.log('[OpenPrompt Extension] Content script loaded on OpenPrompt website');
openprompt-extension/src/content-scripts/perplexity.ts ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Perplexity Content Script
2
+ import {
3
+ getPromptToInject,
4
+ clearPromptToInject,
5
+ waitForElement,
6
+ showNotification,
7
+ setupCaptureListener,
8
+ createCaptureButton
9
+ } from './utils';
10
+
11
+ async function injectPrompt(): Promise<void> {
12
+ const promptData = await getPromptToInject();
13
+
14
+ if (!promptData || promptData.platform !== 'perplexity') {
15
+ return;
16
+ }
17
+
18
+ console.log('[OpenPrompt] Injecting prompt into Perplexity...');
19
+
20
+ // Perplexity uses a textarea
21
+ const textareaSelector = 'textarea[placeholder*="Ask"], textarea';
22
+ const textarea = await waitForElement(textareaSelector, 15000);
23
+
24
+ if (!textarea) {
25
+ console.error('[OpenPrompt] Could not find Perplexity textarea');
26
+ showNotification('Could not find input field. Please try again.', 'error');
27
+ return;
28
+ }
29
+
30
+ try {
31
+ const element = textarea as HTMLTextAreaElement;
32
+
33
+ // Focus and set value
34
+ element.focus();
35
+ element.value = promptData.prompt;
36
+
37
+ // Dispatch events for React
38
+ element.dispatchEvent(new Event('input', { bubbles: true }));
39
+ element.dispatchEvent(new Event('change', { bubbles: true }));
40
+
41
+ // For React synthetic events
42
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
43
+ HTMLTextAreaElement.prototype,
44
+ 'value'
45
+ )?.set;
46
+
47
+ if (nativeInputValueSetter) {
48
+ nativeInputValueSetter.call(element, promptData.prompt);
49
+ element.dispatchEvent(new Event('input', { bubbles: true }));
50
+ }
51
+
52
+ // Clear the pending prompt
53
+ await clearPromptToInject();
54
+
55
+ showNotification('✓ Prompt loaded! Press Enter to send.');
56
+ console.log('[OpenPrompt] Prompt injected successfully');
57
+ } catch (error) {
58
+ console.error('[OpenPrompt] Error injecting prompt:', error);
59
+ showNotification('Failed to inject prompt. Please try again.', 'error');
60
+ }
61
+ }
62
+
63
+ // Initialize
64
+ function init(): void {
65
+ setupCaptureListener();
66
+ createCaptureButton('Perplexity');
67
+ setTimeout(injectPrompt, 1500);
68
+ }
69
+
70
+ // Run on page load
71
+ if (document.readyState === 'loading') {
72
+ document.addEventListener('DOMContentLoaded', init);
73
+ } else {
74
+ init();
75
+ }
76
+
77
+ // Listen for messages from the extension
78
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
79
+ if (message.type === 'INJECT_PROMPT') {
80
+ injectPrompt().then(() => sendResponse({ success: true }));
81
+ return true;
82
+ }
83
+ });
openprompt-extension/src/content-scripts/utils.ts ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Shared utilities for content scripts
2
+
3
+ export interface PromptToInject {
4
+ prompt: string;
5
+ platform: string;
6
+ timestamp: number;
7
+ }
8
+
9
+ export async function getPromptToInject(): Promise<PromptToInject | null> {
10
+ return new Promise((resolve) => {
11
+ chrome.storage.local.get(['promptToInject'], (result) => {
12
+ if (result.promptToInject) {
13
+ // Check if prompt is recent (within last 30 seconds)
14
+ const isRecent = Date.now() - result.promptToInject.timestamp < 30000;
15
+ if (isRecent) {
16
+ resolve(result.promptToInject);
17
+ } else {
18
+ // Clear stale prompt
19
+ chrome.storage.local.remove('promptToInject');
20
+ resolve(null);
21
+ }
22
+ } else {
23
+ resolve(null);
24
+ }
25
+ });
26
+ });
27
+ }
28
+
29
+ export async function clearPromptToInject(): Promise<void> {
30
+ return new Promise((resolve) => {
31
+ chrome.storage.local.remove('promptToInject', () => resolve());
32
+ });
33
+ }
34
+
35
+ export function waitForElement(
36
+ selector: string,
37
+ timeout = 10000
38
+ ): Promise<Element | null> {
39
+ return new Promise((resolve) => {
40
+ const element = document.querySelector(selector);
41
+ if (element) {
42
+ resolve(element);
43
+ return;
44
+ }
45
+
46
+ const observer = new MutationObserver(() => {
47
+ const element = document.querySelector(selector);
48
+ if (element) {
49
+ observer.disconnect();
50
+ resolve(element);
51
+ }
52
+ });
53
+
54
+ observer.observe(document.body, {
55
+ childList: true,
56
+ subtree: true,
57
+ });
58
+
59
+ setTimeout(() => {
60
+ observer.disconnect();
61
+ resolve(null);
62
+ }, timeout);
63
+ });
64
+ }
65
+
66
+ export function simulateInput(element: HTMLElement, text: string): void {
67
+ // For contenteditable elements
68
+ if (element.getAttribute('contenteditable') === 'true') {
69
+ element.focus();
70
+ element.innerHTML = '';
71
+
72
+ // Use execCommand for better compatibility
73
+ document.execCommand('insertText', false, text);
74
+
75
+ // Dispatch input event
76
+ element.dispatchEvent(new InputEvent('input', {
77
+ bubbles: true,
78
+ cancelable: true,
79
+ inputType: 'insertText',
80
+ data: text,
81
+ }));
82
+ }
83
+ // For textarea/input elements
84
+ else if (element instanceof HTMLTextAreaElement || element instanceof HTMLInputElement) {
85
+ element.focus();
86
+ element.value = text;
87
+
88
+ // Dispatch events to trigger React state updates
89
+ element.dispatchEvent(new Event('input', { bubbles: true }));
90
+ element.dispatchEvent(new Event('change', { bubbles: true }));
91
+
92
+ // For React 17+ synthetic events
93
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
94
+ element instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,
95
+ 'value'
96
+ )?.set;
97
+
98
+ if (nativeInputValueSetter) {
99
+ nativeInputValueSetter.call(element, text);
100
+ element.dispatchEvent(new Event('input', { bubbles: true }));
101
+ }
102
+ }
103
+ }
104
+
105
+ export function showNotification(message: string, type: 'success' | 'error' = 'success'): void {
106
+ const notification = document.createElement('div');
107
+ notification.style.cssText = `
108
+ position: fixed;
109
+ top: 20px;
110
+ right: 20px;
111
+ padding: 12px 20px;
112
+ border-radius: 8px;
113
+ background: ${type === 'success' ? 'linear-gradient(135deg, #10b981, #059669)' : 'linear-gradient(135deg, #ef4444, #dc2626)'};
114
+ color: white;
115
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
116
+ font-size: 14px;
117
+ font-weight: 500;
118
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
119
+ z-index: 999999;
120
+ animation: slideIn 0.3s ease;
121
+ `;
122
+
123
+ notification.textContent = message;
124
+
125
+ // Add animation keyframes
126
+ const style = document.createElement('style');
127
+ style.textContent = `
128
+ @keyframes slideIn {
129
+ from {
130
+ transform: translateX(100%);
131
+ opacity: 0;
132
+ }
133
+ to {
134
+ transform: translateX(0);
135
+ opacity: 1;
136
+ }
137
+ }
138
+ `;
139
+ document.head.appendChild(style);
140
+ document.body.appendChild(notification);
141
+
142
+ setTimeout(() => {
143
+ notification.style.animation = 'slideIn 0.3s ease reverse';
144
+ setTimeout(() => {
145
+ notification.remove();
146
+ style.remove();
147
+ }, 300);
148
+ }, 3000);
149
+ }
150
+
151
+ // Capture selected text
152
+ export function getSelectedText(): string {
153
+ const selection = window.getSelection();
154
+ return selection ? selection.toString().trim() : '';
155
+ }
156
+
157
+ // Handle capture selection command from background
158
+ export function setupCaptureListener(): void {
159
+ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
160
+ if (message.type === 'CAPTURE_SELECTION') {
161
+ const text = getSelectedText();
162
+ if (text) {
163
+ chrome.runtime.sendMessage({
164
+ type: 'CAPTURE_PROMPT',
165
+ text,
166
+ title: document.title,
167
+ }).then(() => {
168
+ showNotification('✓ Prompt captured!');
169
+ }).catch(() => {
170
+ showNotification('Failed to capture prompt', 'error');
171
+ });
172
+ } else {
173
+ showNotification('No text selected', 'error');
174
+ }
175
+ sendResponse({ success: true });
176
+ return true;
177
+ }
178
+
179
+ if (message.type === 'SHOW_NOTIFICATION') {
180
+ showNotification(message.message, message.type || 'success');
181
+ sendResponse({ success: true });
182
+ return true;
183
+ }
184
+ });
185
+ }
186
+
187
+ // Create floating capture button for AI chat pages
188
+ export function createCaptureButton(platform: string): void {
189
+ // Remove existing button
190
+ const existing = document.getElementById('openprompt-capture-btn');
191
+ if (existing) existing.remove();
192
+
193
+ const button = document.createElement('button');
194
+ button.id = 'openprompt-capture-btn';
195
+ button.innerHTML = `
196
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
197
+ <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
198
+ <polyline points="17 21 17 13 7 13 7 21"/>
199
+ <polyline points="7 3 7 8 15 8"/>
200
+ </svg>
201
+ <span>Save to OpenPrompt</span>
202
+ `;
203
+ button.style.cssText = `
204
+ position: fixed;
205
+ bottom: 20px;
206
+ right: 20px;
207
+ display: none;
208
+ align-items: center;
209
+ gap: 8px;
210
+ padding: 10px 16px;
211
+ border: none;
212
+ border-radius: 8px;
213
+ background: linear-gradient(135deg, #9333ea, #db2777);
214
+ color: white;
215
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
216
+ font-size: 14px;
217
+ font-weight: 500;
218
+ cursor: pointer;
219
+ box-shadow: 0 4px 12px rgba(147, 51, 234, 0.4);
220
+ z-index: 999998;
221
+ transition: transform 0.2s, box-shadow 0.2s;
222
+ `;
223
+
224
+ button.addEventListener('mouseenter', () => {
225
+ button.style.transform = 'scale(1.05)';
226
+ button.style.boxShadow = '0 6px 16px rgba(147, 51, 234, 0.5)';
227
+ });
228
+
229
+ button.addEventListener('mouseleave', () => {
230
+ button.style.transform = 'scale(1)';
231
+ button.style.boxShadow = '0 4px 12px rgba(147, 51, 234, 0.4)';
232
+ });
233
+
234
+ button.addEventListener('click', () => {
235
+ const text = getSelectedText();
236
+ if (text) {
237
+ chrome.runtime.sendMessage({
238
+ type: 'CAPTURE_PROMPT',
239
+ text,
240
+ title: `Captured from ${platform}`,
241
+ }).then(() => {
242
+ showNotification('✓ Prompt saved to OpenPrompt!');
243
+ }).catch(() => {
244
+ showNotification('Failed to save prompt', 'error');
245
+ });
246
+ } else {
247
+ showNotification('Select text to capture', 'error');
248
+ }
249
+ });
250
+
251
+ document.body.appendChild(button);
252
+
253
+ // Show/hide based on selection
254
+ document.addEventListener('selectionchange', () => {
255
+ const text = getSelectedText();
256
+ button.style.display = text.length > 10 ? 'flex' : 'none';
257
+ });
258
+ }
openprompt-extension/src/popup/App.tsx ADDED
@@ -0,0 +1,743 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useMemo } from 'react';
2
+ import {
3
+ Sparkles,
4
+ ExternalLink,
5
+ Settings,
6
+ History,
7
+ Copy,
8
+ Check,
9
+ ChevronRight,
10
+ Zap,
11
+ Search,
12
+ BarChart3,
13
+ Send,
14
+ Layers,
15
+ X,
16
+ Keyboard,
17
+ Save,
18
+ Bot,
19
+ Brain,
20
+ Wind,
21
+ Rocket,
22
+ FileText
23
+ } from 'lucide-react';
24
+
25
+ const API_BASE = 'https://open-prompt.netlify.app';
26
+ // const API_BASE = 'http://localhost:3000'; // For development
27
+
28
+ // Icon components for platforms
29
+ const PlatformIcons: Record<string, typeof Bot> = {
30
+ chatgpt: Bot,
31
+ claude: Brain,
32
+ gemini: Sparkles,
33
+ perplexity: Search,
34
+ mistral: Wind,
35
+ copilot: Rocket,
36
+ };
37
+
38
+ // Supported AI platforms
39
+ const AI_PLATFORMS = [
40
+ {
41
+ id: 'chatgpt',
42
+ name: 'ChatGPT',
43
+ url: 'https://chatgpt.com',
44
+ color: 'from-green-500 to-emerald-600',
45
+ bgColor: 'bg-green-500/10',
46
+ borderColor: 'border-green-500/30',
47
+ },
48
+ {
49
+ id: 'claude',
50
+ name: 'Claude',
51
+ url: 'https://claude.ai',
52
+ color: 'from-orange-500 to-amber-600',
53
+ bgColor: 'bg-orange-500/10',
54
+ borderColor: 'border-orange-500/30',
55
+ },
56
+ {
57
+ id: 'gemini',
58
+ name: 'Gemini',
59
+ url: 'https://gemini.google.com',
60
+ color: 'from-blue-500 to-indigo-600',
61
+ bgColor: 'bg-blue-500/10',
62
+ borderColor: 'border-blue-500/30',
63
+ },
64
+ {
65
+ id: 'perplexity',
66
+ name: 'Perplexity',
67
+ url: 'https://www.perplexity.ai',
68
+ color: 'from-cyan-500 to-teal-600',
69
+ bgColor: 'bg-cyan-500/10',
70
+ borderColor: 'border-cyan-500/30',
71
+ },
72
+ {
73
+ id: 'mistral',
74
+ name: 'Mistral',
75
+ url: 'https://chat.mistral.ai',
76
+ color: 'from-purple-500 to-violet-600',
77
+ bgColor: 'bg-purple-500/10',
78
+ borderColor: 'border-purple-500/30',
79
+ },
80
+ {
81
+ id: 'copilot',
82
+ name: 'Copilot',
83
+ url: 'https://copilot.microsoft.com',
84
+ color: 'from-sky-500 to-blue-600',
85
+ bgColor: 'bg-sky-500/10',
86
+ borderColor: 'border-sky-500/30',
87
+ },
88
+ ];
89
+
90
+ interface PendingPrompt {
91
+ prompt: string;
92
+ title?: string;
93
+ promptId?: string;
94
+ timestamp: number;
95
+ }
96
+
97
+ interface RecentPrompt {
98
+ prompt: string;
99
+ title?: string;
100
+ platform: string;
101
+ timestamp: number;
102
+ }
103
+
104
+ interface CapturedPrompt {
105
+ id: string;
106
+ text: string;
107
+ title?: string;
108
+ platform: string;
109
+ createdAt: string;
110
+ }
111
+
112
+ interface Analytics {
113
+ stats: { action: string; count: number }[];
114
+ platformStats: { platform: string; count: number }[];
115
+ }
116
+
117
+ type TabType = 'main' | 'settings' | 'analytics' | 'search' | 'captured';
118
+
119
+ // Helper to get platform icon component
120
+ const getPlatformIcon = (platformId: string, className: string = "h-5 w-5") => {
121
+ const IconComponent = PlatformIcons[platformId] || FileText;
122
+ return <IconComponent className={className} />;
123
+ };
124
+
125
+ function App() {
126
+ const [pendingPrompt, setPendingPrompt] = useState<PendingPrompt | null>(null);
127
+ const [recentPrompts, setRecentPrompts] = useState<RecentPrompt[]>([]);
128
+ const [capturedPrompts, setCapturedPrompts] = useState<CapturedPrompt[]>([]);
129
+ const [defaultPlatform, setDefaultPlatform] = useState('chatgpt');
130
+ const [activeTab, setActiveTab] = useState<TabType>('main');
131
+ const [copied, setCopied] = useState(false);
132
+ const [sending, setSending] = useState<string | null>(null);
133
+ const [searchQuery, setSearchQuery] = useState('');
134
+ const [analytics, setAnalytics] = useState<Analytics | null>(null);
135
+ const [broadcastMode, setBroadcastMode] = useState(false);
136
+ const [selectedPlatforms, setSelectedPlatforms] = useState<string[]>([]);
137
+
138
+ useEffect(() => {
139
+ // Load data from storage
140
+ chrome.storage.local.get(['pendingPrompt', 'recentPrompts', 'defaultPlatform', 'deviceId'], (result) => {
141
+ if (result.pendingPrompt) {
142
+ setPendingPrompt(result.pendingPrompt);
143
+ }
144
+ if (result.recentPrompts) {
145
+ setRecentPrompts(result.recentPrompts.slice(0, 10));
146
+ }
147
+ if (result.defaultPlatform) {
148
+ setDefaultPlatform(result.defaultPlatform);
149
+ }
150
+
151
+ // Fetch analytics and captured prompts
152
+ if (result.deviceId) {
153
+ fetchAnalytics(result.deviceId);
154
+ fetchCapturedPrompts(result.deviceId);
155
+ }
156
+ });
157
+ }, []);
158
+
159
+ const fetchAnalytics = async (deviceId: string) => {
160
+ try {
161
+ const response = await fetch(`${API_BASE}/api/extension/analytics?deviceId=${deviceId}`);
162
+ if (response.ok) {
163
+ const data = await response.json();
164
+ setAnalytics(data);
165
+ }
166
+ } catch (error) {
167
+ console.error('Failed to fetch analytics:', error);
168
+ }
169
+ };
170
+
171
+ const fetchCapturedPrompts = async (deviceId: string) => {
172
+ try {
173
+ const response = await fetch(`${API_BASE}/api/extension/capture?deviceId=${deviceId}&limit=10`);
174
+ if (response.ok) {
175
+ const data = await response.json();
176
+ setCapturedPrompts(data.captured || []);
177
+ }
178
+ } catch (error) {
179
+ console.error('Failed to fetch captured prompts:', error);
180
+ }
181
+ };
182
+
183
+ // Fuzzy search through recent prompts
184
+ const filteredPrompts = useMemo(() => {
185
+ if (!searchQuery.trim()) return recentPrompts;
186
+
187
+ const query = searchQuery.toLowerCase();
188
+ return recentPrompts.filter(p =>
189
+ p.title?.toLowerCase().includes(query) ||
190
+ p.prompt.toLowerCase().includes(query)
191
+ );
192
+ }, [searchQuery, recentPrompts]);
193
+
194
+ const handleOpenIn = async (platformId: string) => {
195
+ if (!pendingPrompt) return;
196
+
197
+ setSending(platformId);
198
+ const platform = AI_PLATFORMS.find(p => p.id === platformId);
199
+ if (!platform) return;
200
+
201
+ // Save to recent prompts
202
+ const newRecent: RecentPrompt = {
203
+ prompt: pendingPrompt.prompt,
204
+ title: pendingPrompt.title,
205
+ platform: platformId,
206
+ timestamp: Date.now(),
207
+ };
208
+
209
+ const updatedRecents = [newRecent, ...recentPrompts.filter(r =>
210
+ r.prompt !== pendingPrompt.prompt || r.platform !== platformId
211
+ )].slice(0, 10);
212
+
213
+ await chrome.storage.local.set({ recentPrompts: updatedRecents });
214
+ setRecentPrompts(updatedRecents);
215
+
216
+ // Store prompt for the content script to pick up
217
+ await chrome.storage.local.set({
218
+ promptToInject: {
219
+ prompt: pendingPrompt.prompt,
220
+ platform: platformId,
221
+ timestamp: Date.now(),
222
+ }
223
+ });
224
+
225
+ // Track analytics
226
+ chrome.runtime.sendMessage({
227
+ type: 'TRACK_ANALYTICS',
228
+ data: {
229
+ action: 'inject',
230
+ platform: platformId,
231
+ promptId: pendingPrompt.promptId,
232
+ promptTitle: pendingPrompt.title,
233
+ }
234
+ });
235
+
236
+ // Open the platform in a new tab
237
+ chrome.tabs.create({ url: platform.url }, () => {
238
+ setSending(null);
239
+ chrome.storage.local.remove('pendingPrompt');
240
+ setPendingPrompt(null);
241
+ });
242
+ };
243
+
244
+ const handleBroadcast = async () => {
245
+ if (!pendingPrompt || selectedPlatforms.length === 0) return;
246
+
247
+ setSending('broadcast');
248
+
249
+ // Track analytics
250
+ chrome.runtime.sendMessage({
251
+ type: 'TRACK_ANALYTICS',
252
+ data: {
253
+ action: 'broadcast',
254
+ platforms: selectedPlatforms,
255
+ promptTitle: pendingPrompt.title,
256
+ }
257
+ });
258
+
259
+ // Send broadcast message
260
+ chrome.runtime.sendMessage({
261
+ type: 'BROADCAST_PROMPT',
262
+ prompt: pendingPrompt.prompt,
263
+ platforms: selectedPlatforms,
264
+ }, () => {
265
+ setSending(null);
266
+ setBroadcastMode(false);
267
+ setSelectedPlatforms([]);
268
+ chrome.storage.local.remove('pendingPrompt');
269
+ setPendingPrompt(null);
270
+ });
271
+ };
272
+
273
+ const togglePlatformSelection = (platformId: string) => {
274
+ setSelectedPlatforms(prev =>
275
+ prev.includes(platformId)
276
+ ? prev.filter(p => p !== platformId)
277
+ : [...prev, platformId]
278
+ );
279
+ };
280
+
281
+ const handleCopyPrompt = () => {
282
+ if (!pendingPrompt) return;
283
+ navigator.clipboard.writeText(pendingPrompt.prompt);
284
+ setCopied(true);
285
+ setTimeout(() => setCopied(false), 2000);
286
+ };
287
+
288
+ const handleSetDefault = (platformId: string) => {
289
+ setDefaultPlatform(platformId);
290
+ chrome.storage.local.set({ defaultPlatform: platformId });
291
+ };
292
+
293
+ const handleReopenPrompt = async (recent: RecentPrompt) => {
294
+ const platform = AI_PLATFORMS.find(p => p.id === recent.platform);
295
+ if (!platform) return;
296
+
297
+ await chrome.storage.local.set({
298
+ promptToInject: {
299
+ prompt: recent.prompt,
300
+ platform: recent.platform,
301
+ timestamp: Date.now(),
302
+ }
303
+ });
304
+
305
+ chrome.tabs.create({ url: platform.url });
306
+ };
307
+
308
+ const handleUseCaptured = async (captured: CapturedPrompt) => {
309
+ setPendingPrompt({
310
+ prompt: captured.text,
311
+ title: captured.title || `From ${captured.platform}`,
312
+ timestamp: Date.now(),
313
+ });
314
+ setActiveTab('main');
315
+ };
316
+
317
+ const renderTabContent = () => {
318
+ switch (activeTab) {
319
+ case 'settings':
320
+ return (
321
+ <div className="space-y-4">
322
+ <h2 className="text-sm font-medium text-gray-300">Default Platform</h2>
323
+ <div className="space-y-2">
324
+ {AI_PLATFORMS.map((platform) => (
325
+ <button
326
+ key={platform.id}
327
+ onClick={() => handleSetDefault(platform.id)}
328
+ className={`w-full flex items-center gap-3 p-3 rounded-lg border transition-all ${
329
+ defaultPlatform === platform.id
330
+ ? `${platform.bgColor} ${platform.borderColor}`
331
+ : 'border-white/10 hover:border-white/20 hover:bg-white/5'
332
+ }`}
333
+ >
334
+ {getPlatformIcon(platform.id, "h-5 w-5")}
335
+ <span className="flex-1 text-left text-sm font-medium">{platform.name}</span>
336
+ {defaultPlatform === platform.id && (
337
+ <Check className="h-4 w-4 text-green-400" />
338
+ )}
339
+ </button>
340
+ ))}
341
+ </div>
342
+
343
+ <div className="pt-4 border-t border-white/10">
344
+ <h3 className="text-sm font-medium text-gray-300 mb-3 flex items-center gap-2">
345
+ <Keyboard className="h-4 w-4" />
346
+ Keyboard Shortcuts
347
+ </h3>
348
+ <div className="space-y-2 text-xs text-gray-400">
349
+ <div className="flex justify-between">
350
+ <span>Open Popup</span>
351
+ <kbd className="px-2 py-1 bg-white/10 rounded">Ctrl+Shift+O</kbd>
352
+ </div>
353
+ <div className="flex justify-between">
354
+ <span>Quick Search</span>
355
+ <kbd className="px-2 py-1 bg-white/10 rounded">Ctrl+Shift+P</kbd>
356
+ </div>
357
+ <div className="flex justify-between">
358
+ <span>Capture Selection</span>
359
+ <kbd className="px-2 py-1 bg-white/10 rounded">Ctrl+Shift+S</kbd>
360
+ </div>
361
+ </div>
362
+ </div>
363
+ </div>
364
+ );
365
+
366
+ case 'analytics':
367
+ return (
368
+ <div className="space-y-4">
369
+ <h2 className="text-sm font-medium text-gray-300 flex items-center gap-2">
370
+ <BarChart3 className="h-4 w-4" />
371
+ Your Usage Stats
372
+ </h2>
373
+
374
+ {analytics ? (
375
+ <>
376
+ <div className="grid grid-cols-2 gap-3">
377
+ {analytics.stats.map(stat => (
378
+ <div key={stat.action} className="p-3 rounded-lg bg-white/5 border border-white/10">
379
+ <div className="text-2xl font-bold text-purple-400">{stat.count}</div>
380
+ <div className="text-xs text-gray-400 capitalize">{stat.action}s</div>
381
+ </div>
382
+ ))}
383
+ </div>
384
+
385
+ <h3 className="text-sm font-medium text-gray-300 mt-4">By Platform</h3>
386
+ <div className="space-y-2">
387
+ {analytics.platformStats.map(stat => {
388
+ const platform = AI_PLATFORMS.find(p => p.id === stat.platform);
389
+ return (
390
+ <div key={stat.platform} className="flex items-center gap-3 p-2 rounded-lg bg-white/5">
391
+ {getPlatformIcon(stat.platform)}
392
+ <span className="flex-1 text-sm">{platform?.name || stat.platform}</span>
393
+ <span className="text-sm font-semibold text-purple-400">{stat.count}</span>
394
+ </div>
395
+ );
396
+ })}
397
+ </div>
398
+ </>
399
+ ) : (
400
+ <div className="text-center py-8 text-gray-500">
401
+ <BarChart3 className="h-12 w-12 mx-auto mb-3 opacity-50" />
402
+ <p>No usage data yet</p>
403
+ <p className="text-xs mt-1">Start using prompts to see stats</p>
404
+ </div>
405
+ )}
406
+ </div>
407
+ );
408
+
409
+ case 'search':
410
+ return (
411
+ <div className="space-y-4">
412
+ <div className="relative">
413
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-500" />
414
+ <input
415
+ type="text"
416
+ value={searchQuery}
417
+ onChange={(e) => setSearchQuery(e.target.value)}
418
+ placeholder="Search your prompts..."
419
+ className="w-full pl-10 pr-4 py-2 rounded-lg bg-white/10 border border-white/20 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
420
+ autoFocus
421
+ />
422
+ </div>
423
+
424
+ <div className="space-y-2">
425
+ {filteredPrompts.length > 0 ? (
426
+ filteredPrompts.map((prompt, index) => (
427
+ <button
428
+ key={index}
429
+ onClick={() => handleReopenPrompt(prompt)}
430
+ className="w-full flex items-center gap-3 p-3 rounded-lg border border-white/10 hover:border-white/20 hover:bg-white/5 transition-all text-left"
431
+ >
432
+ {getPlatformIcon(prompt.platform)}
433
+ <div className="flex-1 min-w-0">
434
+ <p className="text-sm font-medium truncate">
435
+ {prompt.title || 'Untitled Prompt'}
436
+ </p>
437
+ <p className="text-xs text-gray-500 truncate">
438
+ {prompt.prompt.slice(0, 50)}...
439
+ </p>
440
+ </div>
441
+ <ChevronRight className="h-4 w-4 text-gray-500" />
442
+ </button>
443
+ ))
444
+ ) : (
445
+ <div className="text-center py-8 text-gray-500">
446
+ <Search className="h-12 w-12 mx-auto mb-3 opacity-50" />
447
+ <p>No matching prompts</p>
448
+ </div>
449
+ )}
450
+ </div>
451
+ </div>
452
+ );
453
+
454
+ case 'captured':
455
+ return (
456
+ <div className="space-y-4">
457
+ <h2 className="text-sm font-medium text-gray-300 flex items-center gap-2">
458
+ <Save className="h-4 w-4" />
459
+ Captured Prompts
460
+ </h2>
461
+
462
+ {capturedPrompts.length > 0 ? (
463
+ <div className="space-y-2">
464
+ {capturedPrompts.map((captured) => (
465
+ <button
466
+ key={captured.id}
467
+ onClick={() => handleUseCaptured(captured)}
468
+ className="w-full flex items-center gap-3 p-3 rounded-lg border border-white/10 hover:border-white/20 hover:bg-white/5 transition-all text-left"
469
+ >
470
+ {getPlatformIcon(captured.platform)}
471
+ <div className="flex-1 min-w-0">
472
+ <p className="text-sm font-medium truncate">
473
+ {captured.title || `From ${captured.platform}`}
474
+ </p>
475
+ <p className="text-xs text-gray-500 truncate">
476
+ {captured.text.slice(0, 50)}...
477
+ </p>
478
+ </div>
479
+ <ChevronRight className="h-4 w-4 text-gray-500" />
480
+ </button>
481
+ ))}
482
+ </div>
483
+ ) : (
484
+ <div className="text-center py-8 text-gray-500">
485
+ <Save className="h-12 w-12 mx-auto mb-3 opacity-50" />
486
+ <p>No captured prompts yet</p>
487
+ <p className="text-xs mt-1">Select text in any AI chat and use Ctrl+Shift+S</p>
488
+ </div>
489
+ )}
490
+ </div>
491
+ );
492
+
493
+ default:
494
+ // Main view
495
+ if (pendingPrompt) {
496
+ return (
497
+ <div className="space-y-4">
498
+ <div className="p-3 rounded-lg bg-white/5 border border-white/10">
499
+ <div className="flex items-start justify-between gap-2 mb-2">
500
+ <h3 className="text-sm font-medium text-purple-300">
501
+ {pendingPrompt.title || 'Prompt Ready'}
502
+ </h3>
503
+ <button
504
+ onClick={handleCopyPrompt}
505
+ className="p-1.5 rounded hover:bg-white/10 transition-colors"
506
+ title="Copy prompt"
507
+ >
508
+ {copied ? (
509
+ <Check className="h-3.5 w-3.5 text-green-400" />
510
+ ) : (
511
+ <Copy className="h-3.5 w-3.5 text-gray-400" />
512
+ )}
513
+ </button>
514
+ </div>
515
+ <p className="text-xs text-gray-400 line-clamp-3">
516
+ {pendingPrompt.prompt}
517
+ </p>
518
+ </div>
519
+
520
+ {broadcastMode ? (
521
+ <>
522
+ <div className="flex items-center justify-between">
523
+ <h2 className="text-sm font-medium text-gray-300 flex items-center gap-2">
524
+ <Layers className="h-4 w-4 text-yellow-400" />
525
+ Broadcast to Multiple
526
+ </h2>
527
+ <button
528
+ onClick={() => {
529
+ setBroadcastMode(false);
530
+ setSelectedPlatforms([]);
531
+ }}
532
+ className="p-1 rounded hover:bg-white/10"
533
+ >
534
+ <X className="h-4 w-4 text-gray-400" />
535
+ </button>
536
+ </div>
537
+
538
+ <div className="grid grid-cols-2 gap-2">
539
+ {AI_PLATFORMS.map((platform) => (
540
+ <button
541
+ key={platform.id}
542
+ onClick={() => togglePlatformSelection(platform.id)}
543
+ className={`flex items-center gap-2 p-3 rounded-lg border transition-all ${
544
+ selectedPlatforms.includes(platform.id)
545
+ ? `${platform.bgColor} ${platform.borderColor}`
546
+ : 'border-white/10 hover:border-white/20'
547
+ }`}
548
+ >
549
+ {getPlatformIcon(platform.id)}
550
+ <span className="text-sm font-medium">{platform.name}</span>
551
+ {selectedPlatforms.includes(platform.id) && (
552
+ <Check className="ml-auto h-4 w-4 text-green-400" />
553
+ )}
554
+ </button>
555
+ ))}
556
+ </div>
557
+
558
+ <button
559
+ onClick={handleBroadcast}
560
+ disabled={selectedPlatforms.length === 0 || sending === 'broadcast'}
561
+ className="w-full py-3 px-4 rounded-lg bg-gradient-to-r from-yellow-600 to-orange-600 hover:from-yellow-700 hover:to-orange-700 disabled:opacity-50 transition-colors text-sm font-medium flex items-center justify-center gap-2"
562
+ >
563
+ {sending === 'broadcast' ? (
564
+ <div className="h-4 w-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
565
+ ) : (
566
+ <>
567
+ <Send className="h-4 w-4" />
568
+ Send to {selectedPlatforms.length} Platform{selectedPlatforms.length !== 1 ? 's' : ''}
569
+ </>
570
+ )}
571
+ </button>
572
+ </>
573
+ ) : (
574
+ <>
575
+ <div className="flex items-center justify-between">
576
+ <h2 className="text-sm font-medium text-gray-300 flex items-center gap-2">
577
+ <Zap className="h-4 w-4 text-yellow-400" />
578
+ Open in...
579
+ </h2>
580
+ <button
581
+ onClick={() => setBroadcastMode(true)}
582
+ className="text-xs text-purple-400 hover:text-purple-300 flex items-center gap-1"
583
+ >
584
+ <Layers className="h-3 w-3" />
585
+ Broadcast
586
+ </button>
587
+ </div>
588
+
589
+ <div className="grid grid-cols-2 gap-2">
590
+ {AI_PLATFORMS.map((platform) => (
591
+ <button
592
+ key={platform.id}
593
+ onClick={() => handleOpenIn(platform.id)}
594
+ disabled={sending !== null}
595
+ className={`flex items-center gap-2 p-3 rounded-lg border transition-all ${
596
+ platform.bgColor
597
+ } ${platform.borderColor} hover:scale-[1.02] active:scale-[0.98] disabled:opacity-50`}
598
+ >
599
+ {getPlatformIcon(platform.id)}
600
+ <span className="text-sm font-medium">{platform.name}</span>
601
+ {sending === platform.id ? (
602
+ <div className="ml-auto h-4 w-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
603
+ ) : (
604
+ <ExternalLink className="ml-auto h-3.5 w-3.5 opacity-50" />
605
+ )}
606
+ </button>
607
+ ))}
608
+ </div>
609
+ </>
610
+ )}
611
+ </div>
612
+ );
613
+ }
614
+
615
+ // No pending prompt
616
+ return (
617
+ <div className="space-y-4">
618
+ <div className="text-center py-6">
619
+ <div className="inline-flex items-center justify-center h-14 w-14 rounded-2xl bg-gradient-to-br from-purple-500/20 to-pink-500/20 border border-purple-500/30 mb-3">
620
+ <Sparkles className="h-7 w-7 text-purple-400" />
621
+ </div>
622
+ <h2 className="text-base font-medium mb-1">No Prompt Selected</h2>
623
+ <p className="text-xs text-gray-400 max-w-[280px] mx-auto">
624
+ Visit OpenPrompt and click "Open In..." on any prompt to send it to your favorite AI chat.
625
+ </p>
626
+ </div>
627
+
628
+ {recentPrompts.length > 0 && (
629
+ <div>
630
+ <h3 className="text-sm font-medium text-gray-300 flex items-center gap-2 mb-3">
631
+ <History className="h-4 w-4" />
632
+ Recent Prompts
633
+ </h3>
634
+ <div className="space-y-2">
635
+ {recentPrompts.slice(0, 3).map((recent, index) => (
636
+ <button
637
+ key={index}
638
+ onClick={() => handleReopenPrompt(recent)}
639
+ className="w-full flex items-center gap-3 p-3 rounded-lg border border-white/10 hover:border-white/20 hover:bg-white/5 transition-all text-left"
640
+ >
641
+ {getPlatformIcon(recent.platform)}
642
+ <div className="flex-1 min-w-0">
643
+ <p className="text-sm font-medium truncate">
644
+ {recent.title || 'Untitled Prompt'}
645
+ </p>
646
+ <p className="text-xs text-gray-500 truncate">
647
+ {recent.prompt.slice(0, 50)}...
648
+ </p>
649
+ </div>
650
+ <ChevronRight className="h-4 w-4 text-gray-500" />
651
+ </button>
652
+ ))}
653
+ </div>
654
+ </div>
655
+ )}
656
+
657
+ <a
658
+ href={API_BASE}
659
+ target="_blank"
660
+ rel="noopener noreferrer"
661
+ className="block w-full py-3 px-4 rounded-lg bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 transition-colors text-center text-sm font-medium"
662
+ >
663
+ Browse Prompts on OpenPrompt
664
+ <ExternalLink className="inline-block ml-2 h-3.5 w-3.5" />
665
+ </a>
666
+ </div>
667
+ );
668
+ }
669
+ };
670
+
671
+ return (
672
+ <div className="w-[360px] min-h-[480px] flex flex-col bg-[#0f0f14]">
673
+ {/* Header */}
674
+ <header className="p-4 border-b border-white/10">
675
+ <div className="flex items-center justify-between">
676
+ <div className="flex items-center gap-2">
677
+ <div className="h-8 w-8 rounded-lg bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center">
678
+ <Sparkles className="h-4 w-4 text-white" />
679
+ </div>
680
+ <div>
681
+ <h1 className="font-semibold text-white">OpenPrompt</h1>
682
+ <p className="text-xs text-gray-400">Open in AI</p>
683
+ </div>
684
+ </div>
685
+
686
+ {/* Tab buttons */}
687
+ <div className="flex items-center gap-1">
688
+ <button
689
+ onClick={() => setActiveTab('search')}
690
+ className={`p-2 rounded-lg transition-colors ${activeTab === 'search' ? 'bg-white/20' : 'hover:bg-white/10'}`}
691
+ title="Search"
692
+ >
693
+ <Search className="h-4 w-4 text-gray-400" />
694
+ </button>
695
+ <button
696
+ onClick={() => setActiveTab('captured')}
697
+ className={`p-2 rounded-lg transition-colors ${activeTab === 'captured' ? 'bg-white/20' : 'hover:bg-white/10'}`}
698
+ title="Captured Prompts"
699
+ >
700
+ <Save className="h-4 w-4 text-gray-400" />
701
+ </button>
702
+ <button
703
+ onClick={() => setActiveTab('analytics')}
704
+ className={`p-2 rounded-lg transition-colors ${activeTab === 'analytics' ? 'bg-white/20' : 'hover:bg-white/10'}`}
705
+ title="Analytics"
706
+ >
707
+ <BarChart3 className="h-4 w-4 text-gray-400" />
708
+ </button>
709
+ <button
710
+ onClick={() => setActiveTab('settings')}
711
+ className={`p-2 rounded-lg transition-colors ${activeTab === 'settings' ? 'bg-white/20' : 'hover:bg-white/10'}`}
712
+ title="Settings"
713
+ >
714
+ <Settings className="h-4 w-4 text-gray-400" />
715
+ </button>
716
+ </div>
717
+ </div>
718
+ </header>
719
+
720
+ {/* Main Content */}
721
+ <main className="flex-1 p-4 overflow-y-auto">
722
+ {activeTab !== 'main' && (
723
+ <button
724
+ onClick={() => setActiveTab('main')}
725
+ className="mb-4 text-sm text-purple-400 hover:text-purple-300 flex items-center gap-1"
726
+ >
727
+ ← Back
728
+ </button>
729
+ )}
730
+ {renderTabContent()}
731
+ </main>
732
+
733
+ {/* Footer */}
734
+ <footer className="p-3 border-t border-white/10 text-center">
735
+ <p className="text-xs text-gray-500">
736
+ OpenPrompt Extension v1.1.0
737
+ </p>
738
+ </footer>
739
+ </div>
740
+ );
741
+ }
742
+
743
+ export default App;
openprompt-extension/src/popup/index.css ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ * {
6
+ margin: 0;
7
+ padding: 0;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ body {
12
+ width: 360px;
13
+ min-height: 480px;
14
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
15
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0f23 100%);
16
+ color: #fff;
17
+ }
18
+
19
+ ::-webkit-scrollbar {
20
+ width: 6px;
21
+ }
22
+
23
+ ::-webkit-scrollbar-track {
24
+ background: rgba(255, 255, 255, 0.05);
25
+ }
26
+
27
+ ::-webkit-scrollbar-thumb {
28
+ background: rgba(168, 85, 247, 0.4);
29
+ border-radius: 3px;
30
+ }
31
+
32
+ ::-webkit-scrollbar-thumb:hover {
33
+ background: rgba(168, 85, 247, 0.6);
34
+ }
openprompt-extension/src/popup/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+ import './index.css';
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );
openprompt-extension/tailwind.config.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./popup.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ primary: {
11
+ 50: '#faf5ff',
12
+ 100: '#f3e8ff',
13
+ 200: '#e9d5ff',
14
+ 300: '#d8b4fe',
15
+ 400: '#c084fc',
16
+ 500: '#a855f7',
17
+ 600: '#9333ea',
18
+ 700: '#7c3aed',
19
+ 800: '#6b21a8',
20
+ 900: '#581c87',
21
+ },
22
+ },
23
+ },
24
+ },
25
+ plugins: [],
26
+ }
openprompt-extension/tsconfig.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "types": ["chrome"],
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "noEmit": true,
14
+ "jsx": "react-jsx",
15
+ "strict": true,
16
+ "noUnusedLocals": true,
17
+ "noUnusedParameters": true,
18
+ "noFallthroughCasesInSwitch": true
19
+ },
20
+ "include": ["src"]
21
+ }
openprompt-extension/vite.config.ts ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig, build } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import { resolve } from 'path';
4
+
5
+ // Custom plugin to build content scripts as IIFE after main build
6
+ function buildContentScriptsPlugin() {
7
+ return {
8
+ name: 'build-content-scripts',
9
+ async closeBundle() {
10
+ const contentScripts = [
11
+ 'chatgpt',
12
+ 'claude',
13
+ 'gemini',
14
+ 'perplexity',
15
+ 'mistral',
16
+ 'copilot',
17
+ 'openprompt-site',
18
+ ];
19
+
20
+ for (const script of contentScripts) {
21
+ await build({
22
+ configFile: false,
23
+ build: {
24
+ outDir: 'dist/content-scripts',
25
+ emptyOutDir: false,
26
+ lib: {
27
+ entry: resolve(__dirname, `src/content-scripts/${script}.ts`),
28
+ name: script,
29
+ fileName: () => `${script}.js`,
30
+ formats: ['iife'],
31
+ },
32
+ rollupOptions: {
33
+ output: {
34
+ extend: true,
35
+ },
36
+ },
37
+ },
38
+ });
39
+ }
40
+ },
41
+ };
42
+ }
43
+
44
+ export default defineConfig({
45
+ plugins: [react(), buildContentScriptsPlugin()],
46
+ build: {
47
+ outDir: 'dist',
48
+ rollupOptions: {
49
+ input: {
50
+ popup: resolve(__dirname, 'popup.html'),
51
+ background: resolve(__dirname, 'src/background/index.ts'),
52
+ },
53
+ output: {
54
+ entryFileNames: '[name].js',
55
+ assetFileNames: 'assets/[name].[ext]',
56
+ format: 'es',
57
+ },
58
+ },
59
+ emptyOutDir: true,
60
+ sourcemap: process.env.NODE_ENV === 'development',
61
+ },
62
+ publicDir: 'public',
63
+ });
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "openprompt",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "author": {
6
+ "name": "Anky9972",
7
+ "url": "https://github.com/Anky9972"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/Anky9972/open-prompt.git"
12
+ },
13
+ "scripts": {
14
+ "dev": "next dev",
15
+ "build": "prisma generate && next build",
16
+ "postinstall": "prisma generate",
17
+ "start": "next start",
18
+ "lint": "eslint"
19
+ },
20
+ "dependencies": {
21
+ "@ai-sdk/anthropic": "^2.0.54",
22
+ "@ai-sdk/google": "^2.0.45",
23
+ "@ai-sdk/openai": "^2.0.80",
24
+ "@ai-sdk/react": "^2.0.109",
25
+ "@hookform/resolvers": "^5.2.2",
26
+ "@neondatabase/serverless": "^1.0.2",
27
+ "@prisma/adapter-neon": "^7.1.0",
28
+ "@prisma/adapter-pg": "^7.1.0",
29
+ "@prisma/client": "^7.1.0",
30
+ "@radix-ui/react-checkbox": "^1.3.3",
31
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
32
+ "@radix-ui/react-label": "^2.1.8",
33
+ "@radix-ui/react-select": "^2.2.6",
34
+ "@radix-ui/react-slot": "^1.2.4",
35
+ "@radix-ui/react-tabs": "^1.1.13",
36
+ "@radix-ui/react-tooltip": "^1.2.8",
37
+ "@react-three/drei": "^10.7.7",
38
+ "@react-three/fiber": "^9.4.2",
39
+ "@stackframe/stack": "^2.8.56",
40
+ "@stackframe/stack-shared": "^2.8.56",
41
+ "@stripe/stripe-js": "^9.4.0",
42
+ "@studio-freight/lenis": "^1.0.42",
43
+ "@types/three": "^0.182.0",
44
+ "@upstash/redis": "^1.35.8",
45
+ "ai": "^5.0.108",
46
+ "class-variance-authority": "^0.7.1",
47
+ "clsx": "^2.1.1",
48
+ "framer-motion": "^12.23.26",
49
+ "lucide-react": "^0.559.0",
50
+ "nanoid": "^5.1.6",
51
+ "next": "16.0.8",
52
+ "next-themes": "^0.4.6",
53
+ "pg": "^8.16.3",
54
+ "prisma": "^7.1.0",
55
+ "qrcode.react": "^4.2.0",
56
+ "radix-ui": "^1.4.3",
57
+ "react": "19.2.1",
58
+ "react-dom": "19.2.1",
59
+ "react-hook-form": "^7.68.0",
60
+ "react-icons": "^5.5.0",
61
+ "react-markdown": "^10.1.0",
62
+ "recharts": "^3.5.1",
63
+ "stripe": "^22.1.0",
64
+ "tailwind-merge": "^3.4.0",
65
+ "three": "^0.182.0",
66
+ "zod": "^4.1.13",
67
+ "zustand": "^5.0.9"
68
+ },
69
+ "devDependencies": {
70
+ "@tailwindcss/postcss": "^4",
71
+ "@types/node": "^20",
72
+ "@types/react": "^19",
73
+ "@types/react-dom": "^19",
74
+ "eslint": "^9",
75
+ "eslint-config-next": "16.0.8",
76
+ "sharp": "^0.34.5",
77
+ "tailwindcss": "^4",
78
+ "tw-animate-css": "^1.4.0",
79
+ "typescript": "^5"
80
+ }
81
+ }
postcss.config.mjs ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
prisma.config.ts ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // This file was generated by Prisma and assumes you have installed the following:
2
+ // npm install --save-dev prisma dotenv
3
+ import "dotenv/config";
4
+ import { defineConfig, env } from "prisma/config";
5
+
6
+ export default defineConfig({
7
+ schema: "prisma/schema.prisma",
8
+ migrations: {
9
+ path: "prisma/migrations",
10
+ },
11
+ datasource: {
12
+ url: env("DATABASE_URL"),
13
+ },
14
+ });
prisma/schema.prisma ADDED
@@ -0,0 +1,760 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ }
8
+
9
+ model User {
10
+ id String @id @default(cuid())
11
+ email String @unique
12
+ name String?
13
+ username String? @unique
14
+ image String?
15
+ bio String?
16
+ rank String? @default("bronze") // bronze, silver, gold, verified
17
+ totalRuns Int @default(0)
18
+ totalStars Int @default(0)
19
+ totalRemixes Int @default(0)
20
+ socialLinks Json?
21
+
22
+ // Notification preferences
23
+ notifyEmail Boolean @default(true)
24
+ notifyStars Boolean @default(true)
25
+ notifyRemixes Boolean @default(true)
26
+
27
+ // Stripe / Billing
28
+ stripeCustomerId String? @unique
29
+ stripePlan String @default("free") // free, pro
30
+ stripeSubscriptionId String? @unique
31
+ stripeSubscriptionStatus String? // active, canceled, past_due, etc.
32
+
33
+ createdAt DateTime @default(now())
34
+ updatedAt DateTime @updatedAt
35
+
36
+ prompts Prompt[]
37
+ stars Star[]
38
+ runs Run[]
39
+ collections Collection[]
40
+ comments Comment[]
41
+ commentLikes CommentLike[]
42
+ imagePrompts ImagePrompt[]
43
+ imageVotes ImagePromptVote[]
44
+ characters Character[]
45
+ characterVotes CharacterVote[]
46
+ workflows Workflow[] @relation("WorkflowCreator")
47
+ forumPosts ForumPost[]
48
+ forumReplies ForumReply[]
49
+ notifications Notification[] @relation("NotificationReceiver")
50
+ followers Follow[] @relation("Following")
51
+ following Follow[] @relation("Follower")
52
+ subscription Subscription?
53
+ purchases PromptPurchase[]
54
+ }
55
+
56
+ model Prompt {
57
+ id String @id @default(cuid())
58
+ slug String @unique
59
+ title String
60
+ description String?
61
+ template String @db.Text
62
+ schema Json @default("{\"variables\":[]}")
63
+
64
+ category String?
65
+ tags String[]
66
+ visibility String @default("public") // public, unlisted, private
67
+
68
+ modelDefault String @default("gpt-4o-mini")
69
+ modelAllowed String[] @default(["gpt-4o-mini", "gpt-4o", "claude-3-5-sonnet"])
70
+ maxTokens Int @default(2048)
71
+
72
+ totalRuns Int @default(0)
73
+ starsCount Int @default(0)
74
+ remixesCount Int @default(0)
75
+
76
+ // Tier 3 fields
77
+ framework String? // RACE, CARE, APE, etc.
78
+ badges String[] @default([]) // Auto-calculated quality badges
79
+
80
+ // Version tracking
81
+ currentVersion Int @default(1)
82
+
83
+ // Premium marketplace
84
+ isPremium Boolean @default(false) // Is this a paid prompt?
85
+ price Int? // Price in cents (e.g., 299 = $2.99)
86
+
87
+ creatorId String?
88
+ creator User? @relation(fields: [creatorId], references: [id])
89
+
90
+ parentId String?
91
+ parent Prompt? @relation("Remixes", fields: [parentId], references: [id])
92
+ remixes Prompt[] @relation("Remixes")
93
+
94
+ stars Star[]
95
+ runs Run[]
96
+ savedRuns SavedPromptRun[]
97
+ collections CollectionPrompt[]
98
+ comments Comment[]
99
+ versions PromptVersion[]
100
+ purchases PromptPurchase[] @relation("PremiumPurchases")
101
+
102
+ createdAt DateTime @default(now())
103
+ updatedAt DateTime @updatedAt
104
+
105
+ @@index([slug])
106
+ @@index([creatorId])
107
+ @@index([category])
108
+ @@index([visibility])
109
+ }
110
+
111
+ model Star {
112
+ userId String
113
+ promptId String
114
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
115
+ prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade)
116
+ createdAt DateTime @default(now())
117
+
118
+ @@id([userId, promptId])
119
+ }
120
+
121
+ model Run {
122
+ id String @id @default(cuid())
123
+ promptId String
124
+ userId String?
125
+ model String
126
+ tokens Int?
127
+ cached Boolean @default(false)
128
+ ipHash String?
129
+ prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade)
130
+ user User? @relation(fields: [userId], references: [id])
131
+ createdAt DateTime @default(now())
132
+
133
+ @@index([promptId])
134
+ @@index([userId])
135
+ }
136
+
137
+ model Collection {
138
+ id String @id @default(cuid())
139
+ name String
140
+ description String?
141
+ userId String
142
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
143
+ prompts CollectionPrompt[]
144
+ isPublic Boolean @default(false)
145
+ viewCount Int @default(0)
146
+ createdAt DateTime @default(now())
147
+ updatedAt DateTime @updatedAt
148
+
149
+ @@index([userId])
150
+ @@index([isPublic])
151
+ }
152
+
153
+ model CollectionPrompt {
154
+ collectionId String
155
+ promptId String
156
+ collection Collection @relation(fields: [collectionId], references: [id], onDelete: Cascade)
157
+ prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade)
158
+ addedAt DateTime @default(now())
159
+
160
+ @@id([collectionId, promptId])
161
+ @@index([collectionId])
162
+ @@index([promptId])
163
+ }
164
+
165
+ model Workflow {
166
+ id String @id @default(cuid())
167
+ slug String @unique
168
+ name String
169
+ description String?
170
+ steps Json // Array of workflow steps
171
+ variables Json? // Input variables for the workflow
172
+
173
+ visibility String @default("private") // public, unlisted, private
174
+
175
+ totalRuns Int @default(0)
176
+ starsCount Int @default(0)
177
+
178
+ creatorId String?
179
+ creator User? @relation("WorkflowCreator", fields: [creatorId], references: [id])
180
+
181
+ runs WorkflowRun[]
182
+
183
+ createdAt DateTime @default(now())
184
+ updatedAt DateTime @updatedAt
185
+
186
+ @@index([slug])
187
+ @@index([creatorId])
188
+ @@index([visibility])
189
+ }
190
+
191
+ model WorkflowRun {
192
+ id String @id @default(cuid())
193
+
194
+ workflowId String
195
+ workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
196
+
197
+ userId String? // Who ran it
198
+
199
+ name String? // Optional name for the run (user can name it)
200
+
201
+ variables Json? // The variable values used for this run
202
+ outputs Json // Array of step outputs
203
+ model String // Model used for the run
204
+
205
+ createdAt DateTime @default(now())
206
+
207
+ @@index([workflowId])
208
+ @@index([userId])
209
+ }
210
+
211
+ // Saved prompt execution results
212
+ model SavedPromptRun {
213
+ id String @id @default(cuid())
214
+
215
+ promptId String
216
+ prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade)
217
+
218
+ userId String?
219
+
220
+ name String?
221
+ variables Json? // Variable values used
222
+ output String @db.Text // The AI output
223
+ model String
224
+
225
+ createdAt DateTime @default(now())
226
+
227
+ @@index([promptId])
228
+ @@index([userId])
229
+ }
230
+
231
+ // Saved AI tool execution results
232
+ model SavedToolRun {
233
+ id String @id @default(cuid())
234
+
235
+ toolSlug String // Tool identifier (no relation, tools are static)
236
+
237
+ userId String?
238
+
239
+ name String?
240
+ inputs Json? // Input values used
241
+ output String @db.Text // The AI output
242
+ model String
243
+
244
+ createdAt DateTime @default(now())
245
+
246
+ @@index([toolSlug])
247
+ @@index([userId])
248
+ }
249
+
250
+ // Saved model comparison results (Thunderdome / Performance)
251
+ model SavedComparison {
252
+ id String @id @default(cuid())
253
+
254
+ type String // "thunderdome" or "performance"
255
+
256
+ userId String?
257
+
258
+ name String?
259
+ prompt String @db.Text // The prompt used
260
+ results Json // Array of model results with outputs
261
+ winner String? // Winning model (for thunderdome)
262
+
263
+ createdAt DateTime @default(now())
264
+
265
+ @@index([userId])
266
+ @@index([type])
267
+ }
268
+
269
+ // ============================================
270
+ // NEW FEATURES - December 2025
271
+ // ============================================
272
+
273
+ // Comments on prompts (threaded discussions)
274
+ model Comment {
275
+ id String @id @default(cuid())
276
+ content String @db.Text
277
+
278
+ promptId String
279
+ prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade)
280
+
281
+ userId String
282
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
283
+
284
+ parentId String? // For threaded replies
285
+ parent Comment? @relation("CommentReplies", fields: [parentId], references: [id], onDelete: Cascade)
286
+ replies Comment[] @relation("CommentReplies")
287
+
288
+ // Per-user like tracking (replaces plain Int to prevent spam)
289
+ commentLikes CommentLike[]
290
+ likesCount Int @default(0) // Denormalized count for quick display
291
+
292
+ createdAt DateTime @default(now())
293
+ updatedAt DateTime @updatedAt
294
+
295
+ @@index([promptId])
296
+ @@index([userId])
297
+ @@index([parentId])
298
+ }
299
+
300
+ // Per-user comment likes — prevents duplicate/spam likes
301
+ model CommentLike {
302
+ commentId String
303
+ comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade)
304
+
305
+ userId String
306
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
307
+
308
+ createdAt DateTime @default(now())
309
+
310
+ @@id([commentId, userId])
311
+ @@index([commentId])
312
+ @@index([userId])
313
+ }
314
+
315
+ // Image Generation Prompts (Midjourney, DALL-E, Stable Diffusion, FLUX)
316
+ model ImagePrompt {
317
+ id String @id @default(cuid())
318
+ slug String @unique
319
+ title String
320
+ description String?
321
+
322
+ // The actual prompt text
323
+ prompt String @db.Text
324
+ negativePrompt String? @db.Text // For SD/FLUX
325
+
326
+ // Target model
327
+ model String // midjourney, dalle, stable-diffusion, flux, leonardo
328
+ modelVersion String? // v6.1, SDXL, FLUX.1-dev, etc.
329
+
330
+ // Image settings
331
+ aspectRatio String? // 16:9, 1:1, 9:16, etc.
332
+ style String? // photorealistic, anime, cartoon, etc.
333
+
334
+ // Generated image preview (URL)
335
+ previewImage String?
336
+ gallery String[] @default([]) // Multiple generated images
337
+
338
+ // Metadata
339
+ category String? // portrait, landscape, abstract, etc.
340
+ tags String[]
341
+ visibility String @default("public")
342
+
343
+ // Stats
344
+ totalUses Int @default(0)
345
+ votesCount Int @default(0)
346
+ savesCount Int @default(0)
347
+
348
+ creatorId String?
349
+ creator User? @relation(fields: [creatorId], references: [id])
350
+
351
+ votes ImagePromptVote[]
352
+
353
+ createdAt DateTime @default(now())
354
+ updatedAt DateTime @updatedAt
355
+
356
+ @@index([slug])
357
+ @@index([model])
358
+ @@index([creatorId])
359
+ @@index([category])
360
+ }
361
+
362
+ // Votes for image prompts
363
+ model ImagePromptVote {
364
+ id String @id @default(cuid())
365
+
366
+ imagePromptId String
367
+ imagePrompt ImagePrompt @relation(fields: [imagePromptId], references: [id], onDelete: Cascade)
368
+
369
+ userId String
370
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
371
+
372
+ value Int @default(1) // 1 for upvote, -1 for downvote
373
+
374
+ createdAt DateTime @default(now())
375
+
376
+ @@unique([imagePromptId, userId])
377
+ @@index([imagePromptId])
378
+ @@index([userId])
379
+ }
380
+
381
+ // Character/Persona System
382
+ model Character {
383
+ id String @id @default(cuid())
384
+ slug String @unique
385
+ name String
386
+ description String? @db.Text
387
+
388
+ // Persona definition
389
+ personality String @db.Text // Character's personality traits
390
+ background String? @db.Text // Character's backstory
391
+ systemPrompt String @db.Text // The actual system prompt
392
+
393
+ // Visual representation
394
+ avatar String? // URL to avatar image
395
+ style String? // anime, realistic, cartoon, etc.
396
+
397
+ // Metadata
398
+ category String? // assistant, roleplay, educational, etc.
399
+ tags String[]
400
+ visibility String @default("public")
401
+
402
+ // Conversation settings
403
+ responseStyle String? // formal, casual, playful, etc.
404
+ temperature Float @default(0.7)
405
+
406
+ // Stats
407
+ totalChats Int @default(0)
408
+ votesCount Int @default(0)
409
+ savesCount Int @default(0)
410
+
411
+ creatorId String?
412
+ creator User? @relation(fields: [creatorId], references: [id])
413
+
414
+ votes CharacterVote[]
415
+
416
+ createdAt DateTime @default(now())
417
+ updatedAt DateTime @updatedAt
418
+
419
+ @@index([slug])
420
+ @@index([creatorId])
421
+ @@index([category])
422
+ }
423
+
424
+ // Votes for characters
425
+ model CharacterVote {
426
+ id String @id @default(cuid())
427
+
428
+ characterId String
429
+ character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
430
+
431
+ userId String
432
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
433
+
434
+ value Int @default(1)
435
+
436
+ createdAt DateTime @default(now())
437
+
438
+ @@unique([characterId, userId])
439
+ @@index([characterId])
440
+ @@index([userId])
441
+ }
442
+
443
+ // Engagement metrics tracking
444
+ model EngagementMetric {
445
+ id String @id @default(cuid())
446
+
447
+ // What was engaged with
448
+ targetType String // prompt, imagePrompt, character, workflow
449
+ targetId String
450
+
451
+ // Type of engagement
452
+ action String // view, use, save, share, copy
453
+
454
+ userId String? // Null for anonymous
455
+ ipHash String? // For anonymous tracking
456
+
457
+ createdAt DateTime @default(now())
458
+
459
+ @@index([targetType, targetId])
460
+ @@index([action])
461
+ @@index([createdAt])
462
+ }
463
+
464
+ // Extension Analytics - tracks prompt usage from extension
465
+ model ExtensionAnalytics {
466
+ id String @id @default(cuid())
467
+
468
+ // User tracking
469
+ userId String? // Null for anonymous users
470
+ deviceId String // Unique device identifier from extension
471
+
472
+ // What action was taken
473
+ action String // inject, capture, broadcast, shortcut_use, context_menu
474
+
475
+ // Platform info
476
+ platform String // chatgpt, claude, gemini, etc.
477
+ platforms String[] @default([]) // For broadcast (multiple platforms)
478
+
479
+ // Prompt info (optional - for inject/capture)
480
+ promptId String? // If from OpenPrompt library
481
+ promptTitle String?
482
+ promptText String? @db.Text // For captured prompts
483
+
484
+ // Source info
485
+ source String @default("extension") // extension, website
486
+
487
+ // Metadata
488
+ metadata Json? // Additional data like shortcut used, etc.
489
+
490
+ createdAt DateTime @default(now())
491
+
492
+ @@index([userId])
493
+ @@index([deviceId])
494
+ @@index([action])
495
+ @@index([platform])
496
+ @@index([createdAt])
497
+ }
498
+
499
+ // Captured prompts from AI chats (saved by users via extension)
500
+ model CapturedPrompt {
501
+ id String @id @default(cuid())
502
+
503
+ userId String?
504
+ deviceId String
505
+
506
+ title String?
507
+ text String @db.Text
508
+
509
+ platform String // Where it was captured from
510
+ url String? // Original URL
511
+
512
+ // If saved to library
513
+ savedToLibrary Boolean @default(false)
514
+ promptId String? // If converted to a Prompt
515
+
516
+ tags String[] @default([])
517
+
518
+ createdAt DateTime @default(now())
519
+
520
+ @@index([userId])
521
+ @@index([deviceId])
522
+ @@index([platform])
523
+ @@index([createdAt])
524
+ }
525
+
526
+ // ============================================
527
+ // COMMUNITY FEATURES - December 2025
528
+ // ============================================
529
+
530
+ // Forum Posts (Community Q&A)
531
+ model ForumPost {
532
+ id String @id @default(cuid())
533
+ title String
534
+ content String @db.Text
535
+ category String // general, help, showcase, feature-request, tips
536
+
537
+ userId String
538
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
539
+
540
+ replies ForumReply[]
541
+
542
+ views Int @default(0)
543
+ likes Int @default(0)
544
+ isPinned Boolean @default(false)
545
+ isClosed Boolean @default(false)
546
+
547
+ createdAt DateTime @default(now())
548
+ updatedAt DateTime @updatedAt
549
+
550
+ @@index([userId])
551
+ @@index([category])
552
+ @@index([createdAt])
553
+ }
554
+
555
+ // Forum Replies
556
+ model ForumReply {
557
+ id String @id @default(cuid())
558
+ content String @db.Text
559
+
560
+ postId String
561
+ post ForumPost @relation(fields: [postId], references: [id], onDelete: Cascade)
562
+
563
+ userId String
564
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
565
+
566
+ parentId String? // For nested replies
567
+
568
+ likes Int @default(0)
569
+ isAccepted Boolean @default(false)
570
+
571
+ createdAt DateTime @default(now())
572
+ updatedAt DateTime @updatedAt
573
+
574
+ @@index([postId])
575
+ @@index([userId])
576
+ @@index([parentId])
577
+ }
578
+
579
+ // User Conversations (Messaging)
580
+ model Conversation {
581
+ id String @id @default(cuid())
582
+
583
+ participant1 String
584
+ participant2 String
585
+
586
+ messages Message[]
587
+
588
+ lastMessage String? @db.Text
589
+ lastActivity DateTime @default(now())
590
+
591
+ createdAt DateTime @default(now())
592
+
593
+ @@unique([participant1, participant2])
594
+ @@index([participant1])
595
+ @@index([participant2])
596
+ @@index([lastActivity])
597
+ }
598
+
599
+ // Direct Messages
600
+ model Message {
601
+ id String @id @default(cuid())
602
+ content String @db.Text
603
+
604
+ senderId String
605
+
606
+ conversationId String
607
+ conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
608
+
609
+ isRead Boolean @default(false)
610
+
611
+ createdAt DateTime @default(now())
612
+
613
+ @@index([conversationId])
614
+ @@index([senderId])
615
+ @@index([createdAt])
616
+ }
617
+
618
+ // Character Memory (Persistent context)
619
+ model CharacterMemory {
620
+ id String @id @default(cuid())
621
+
622
+ characterId String
623
+ userId String
624
+
625
+ contextSummary String? @db.Text
626
+ recentMessages Json @default("[]")
627
+ userFacts Json @default("[]")
628
+ messageCount Int @default(0)
629
+
630
+ createdAt DateTime @default(now())
631
+ updatedAt DateTime @updatedAt
632
+
633
+ @@unique([characterId, userId])
634
+ @@index([characterId])
635
+ @@index([userId])
636
+ }
637
+
638
+ // ============================================
639
+ // NEW FEATURES - Follow & Notifications
640
+ // ============================================
641
+
642
+ // Follow system for creators
643
+ model Follow {
644
+ id String @id @default(cuid())
645
+
646
+ followerId String
647
+ follower User @relation("Follower", fields: [followerId], references: [id], onDelete: Cascade)
648
+
649
+ followingId String
650
+ following User @relation("Following", fields: [followingId], references: [id], onDelete: Cascade)
651
+
652
+ createdAt DateTime @default(now())
653
+
654
+ @@unique([followerId, followingId])
655
+ @@index([followerId])
656
+ @@index([followingId])
657
+ }
658
+
659
+ // Notification system
660
+ model Notification {
661
+ id String @id @default(cuid())
662
+
663
+ userId String // Who receives the notification
664
+ user User @relation("NotificationReceiver", fields: [userId], references: [id], onDelete: Cascade)
665
+
666
+ type String // star, remix, comment, follow, mention
667
+ title String
668
+ message String?
669
+
670
+ // What triggered the notification
671
+ actorId String? // Who performed the action
672
+ targetType String? // prompt, workflow, comment, etc.
673
+ targetId String?
674
+ targetSlug String? // For linking
675
+
676
+ isRead Boolean @default(false)
677
+
678
+ createdAt DateTime @default(now())
679
+
680
+ @@index([userId, isRead])
681
+ @@index([userId, createdAt])
682
+ }
683
+
684
+ // Prompt version history
685
+ model PromptVersion {
686
+ id String @id @default(cuid())
687
+
688
+ promptId String
689
+ prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade)
690
+
691
+ version Int @default(1)
692
+ title String
693
+ template String @db.Text
694
+ schema Json @default("{\"variables\":[]}")
695
+ changeNote String? // What changed
696
+
697
+ createdAt DateTime @default(now())
698
+
699
+ @@index([promptId])
700
+ @@index([promptId, version])
701
+ }
702
+
703
+ // ============================================
704
+ // BILLING — Stripe Integration
705
+ // ============================================
706
+
707
+ // Active subscription record (one per user)
708
+ model Subscription {
709
+ id String @id @default(cuid())
710
+
711
+ userId String @unique
712
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
713
+
714
+ // Stripe identifiers
715
+ stripeCustomerId String @unique
716
+ stripeSubscriptionId String @unique
717
+ stripePriceId String
718
+
719
+ // Plan info
720
+ plan String @default("pro") // pro, enterprise
721
+ status String // active, canceled, past_due, trialing, unpaid
722
+
723
+ // Billing cycle
724
+ currentPeriodStart DateTime
725
+ currentPeriodEnd DateTime
726
+ cancelAtPeriodEnd Boolean @default(false)
727
+ canceledAt DateTime?
728
+
729
+ createdAt DateTime @default(now())
730
+ updatedAt DateTime @updatedAt
731
+
732
+ @@index([userId])
733
+ @@index([stripeSubscriptionId])
734
+ }
735
+
736
+ // Records when a user purchases a premium prompt
737
+ model PromptPurchase {
738
+ id String @id @default(cuid())
739
+
740
+ userId String
741
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
742
+
743
+ promptId String
744
+ prompt Prompt @relation("PremiumPurchases", fields: [promptId], references: [id], onDelete: Cascade)
745
+
746
+ // Payment info
747
+ amount Int // in cents
748
+ currency String @default("usd")
749
+ stripePaymentIntentId String @unique
750
+
751
+ // Creator payout (80% to creator)
752
+ creatorPayout Int // 80% of amount in cents
753
+ creatorPaid Boolean @default(false)
754
+
755
+ createdAt DateTime @default(now())
756
+
757
+ @@unique([userId, promptId]) // One purchase per user per prompt
758
+ @@index([userId])
759
+ @@index([promptId])
760
+ }
prisma/seed-more.ts ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Pool } from 'pg'
2
+ import { PrismaPg } from '@prisma/adapter-pg'
3
+ import { PrismaClient } from '@prisma/client'
4
+ import 'dotenv/config'
5
+
6
+ const connectionString = process.env.DATABASE_URL
7
+ if (!connectionString) throw new Error('DATABASE_URL not set')
8
+
9
+ const pool = new Pool({ connectionString })
10
+ const adapter = new PrismaPg(pool)
11
+ const prisma = new PrismaClient({ adapter })
12
+
13
+ const morePrompts = [
14
+ // ============ CONTENT (10 more) ============
15
+ { slug: 'newsletter-writer', title: 'Newsletter Writer Pro', description: 'Create engaging email newsletters that get opened and read.', category: 'Content', template: 'Write a {{frequency}} newsletter about {{topic}} for {{audience}}.\n\nInclude:\n- Catchy subject line\n- Personal intro\n- 3 main sections\n- CTA\n- P.S. line', schema: { variables: [{ name: 'topic', type: 'text', label: 'Newsletter Topic', required: true }, { name: 'frequency', type: 'dropdown', label: 'Frequency', options: ['Weekly', 'Monthly', 'Daily'] }, { name: 'audience', type: 'text', label: 'Target Audience' }] }, tags: ['newsletter', 'email'], totalRuns: 4532, starsCount: 312 },
16
+ { slug: 'linkedin-post', title: 'LinkedIn Post Generator', description: 'Create viral LinkedIn posts that build your personal brand.', category: 'Content', template: 'Write a LinkedIn post about {{topic}}.\n\nStyle: {{style}}\nInclude hook, story, lesson, CTA.\nUse line breaks for readability.\nAdd relevant emojis.', schema: { variables: [{ name: 'topic', type: 'text', label: 'Post Topic', required: true }, { name: 'style', type: 'dropdown', label: 'Style', options: ['Story', 'Tips', 'Hot Take', 'Celebration', 'Question'] }] }, tags: ['linkedin', 'social-media'], totalRuns: 7654, starsCount: 543 },
17
+ { slug: 'product-description', title: 'E-Commerce Product Description', description: 'Write product descriptions that convert browsers to buyers.', category: 'Content', template: 'Write a product description for:\n\nProduct: {{product}}\nPrice: {{price}}\nTarget Customer: {{customer}}\n\nInclude features, benefits, social proof elements, and urgency.', schema: { variables: [{ name: 'product', type: 'text', label: 'Product Name', required: true }, { name: 'price', type: 'text', label: 'Price' }, { name: 'customer', type: 'text', label: 'Target Customer' }] }, tags: ['ecommerce', 'product'], totalRuns: 8765, starsCount: 654 },
18
+ { slug: 'podcast-outline', title: 'Podcast Episode Outline', description: 'Plan engaging podcast episodes with structured outlines.', category: 'Content', template: 'Create a podcast outline for:\n\nTopic: {{topic}}\nDuration: {{duration}} minutes\nFormat: {{format}}\n\nInclude intro hook, main segments, key talking points, and outro.', schema: { variables: [{ name: 'topic', type: 'text', label: 'Episode Topic', required: true }, { name: 'duration', type: 'dropdown', label: 'Duration', options: ['15', '30', '45', '60'] }, { name: 'format', type: 'dropdown', label: 'Format', options: ['Solo', 'Interview', 'Co-hosted', 'Panel'] }] }, tags: ['podcast', 'audio'], totalRuns: 3456, starsCount: 234 },
19
+ { slug: 'press-release', title: 'Press Release Generator', description: 'Write professional press releases for announcements.', category: 'Content', template: 'Write a press release for:\n\nCompany: {{company}}\nAnnouncement: {{announcement}}\nQuote from: {{spokesperson}}\n\nFollow AP style, include headline, dateline, boilerplate.', schema: { variables: [{ name: 'company', type: 'text', label: 'Company Name', required: true }, { name: 'announcement', type: 'textarea', label: 'Announcement', required: true }, { name: 'spokesperson', type: 'text', label: 'Spokesperson Name' }] }, tags: ['pr', 'press'], totalRuns: 2345, starsCount: 187 },
20
+ { slug: 'case-study', title: 'Case Study Writer', description: 'Create compelling case studies that showcase results.', category: 'Content', template: 'Write a case study about:\n\nClient: {{client}}\nChallenge: {{challenge}}\nSolution: {{solution}}\nResults: {{results}}\n\nFormat with Problem, Solution, Results sections.', schema: { variables: [{ name: 'client', type: 'text', label: 'Client/Company', required: true }, { name: 'challenge', type: 'textarea', label: 'Challenge', required: true }, { name: 'solution', type: 'textarea', label: 'Solution' }, { name: 'results', type: 'textarea', label: 'Results' }] }, tags: ['case-study', 'marketing'], totalRuns: 4567, starsCount: 345 },
21
+ { slug: 'faq-generator', title: 'FAQ Generator', description: 'Generate comprehensive FAQs for products or services.', category: 'Content', template: 'Generate {{count}} FAQs for:\n\nProduct/Service: {{product}}\nTarget Audience: {{audience}}\n\nInclude common questions and detailed, helpful answers.', schema: { variables: [{ name: 'product', type: 'text', label: 'Product/Service', required: true }, { name: 'count', type: 'dropdown', label: 'Number of FAQs', options: ['5', '10', '15', '20'] }, { name: 'audience', type: 'text', label: 'Target Audience' }] }, tags: ['faq', 'support'], totalRuns: 5678, starsCount: 432 },
22
+ { slug: 'meta-description', title: 'SEO Meta Description Writer', description: 'Write click-worthy meta descriptions for better CTR.', category: 'Content', template: 'Write 3 meta description variations for:\n\nPage: {{page}}\nKeyword: {{keyword}}\nTarget Length: 155 characters\n\nMake them compelling with CTAs.', schema: { variables: [{ name: 'page', type: 'text', label: 'Page Description', required: true }, { name: 'keyword', type: 'text', label: 'Target Keyword', required: true }] }, tags: ['seo', 'meta'], totalRuns: 8765, starsCount: 567 },
23
+ { slug: 'book-summary', title: 'Book Summary Generator', description: 'Create comprehensive book summaries with key takeaways.', category: 'Content', template: 'Summarize the book "{{book}}" by {{author}}.\n\nInclude:\n- Overview\n- Key concepts\n- Main takeaways\n- Who should read it\n- Best quotes', schema: { variables: [{ name: 'book', type: 'text', label: 'Book Title', required: true }, { name: 'author', type: 'text', label: 'Author' }] }, tags: ['books', 'summary'], totalRuns: 6543, starsCount: 456 },
24
+ { slug: 'caption-writer', title: 'Instagram Caption Writer', description: 'Write engaging Instagram captions with hashtags.', category: 'Content', template: 'Write an Instagram caption for:\n\nPhoto/Video: {{content}}\nBrand Voice: {{voice}}\nGoal: {{goal}}\n\nInclude emojis, call-to-action, and 10-15 relevant hashtags.', schema: { variables: [{ name: 'content', type: 'text', label: 'Content Description', required: true }, { name: 'voice', type: 'dropdown', label: 'Brand Voice', options: ['Professional', 'Fun', 'Inspirational', 'Educational'] }, { name: 'goal', type: 'dropdown', label: 'Goal', options: ['Engagement', 'Sales', 'Brand Awareness', 'Traffic'] }] }, tags: ['instagram', 'social-media', 'captions'], totalRuns: 9876, starsCount: 765 },
25
+
26
+ // ============ DEVELOPMENT (10 more) ============
27
+ { slug: 'unit-test-writer', title: 'Unit Test Generator', description: 'Generate comprehensive unit tests for your code.', category: 'Development', template: 'Write unit tests for this {{language}} code:\n\n```{{language}}\n{{code}}\n```\n\nFramework: {{framework}}\n\nInclude edge cases, error handling, and mocks.', schema: { variables: [{ name: 'language', type: 'dropdown', label: 'Language', options: ['JavaScript', 'TypeScript', 'Python', 'Java', 'Go'], required: true }, { name: 'code', type: 'textarea', label: 'Code to Test', required: true }, { name: 'framework', type: 'text', label: 'Test Framework', placeholder: 'e.g., Jest, pytest' }] }, tags: ['testing', 'unit-tests'], totalRuns: 7654, starsCount: 543 },
28
+ { slug: 'sql-query-builder', title: 'SQL Query Builder', description: 'Generate optimized SQL queries from natural language.', category: 'Development', template: 'Generate a SQL query for:\n\n{{request}}\n\nDatabase: {{database}}\nTables: {{tables}}\n\nOptimize for performance and include comments.', schema: { variables: [{ name: 'request', type: 'textarea', label: 'What do you need?', required: true }, { name: 'database', type: 'dropdown', label: 'Database', options: ['PostgreSQL', 'MySQL', 'SQLite', 'SQL Server'] }, { name: 'tables', type: 'textarea', label: 'Table Schemas' }] }, tags: ['sql', 'database'], totalRuns: 8765, starsCount: 654 },
29
+ { slug: 'git-commit-message', title: 'Git Commit Message Generator', description: 'Write conventional, descriptive commit messages.', category: 'Development', template: 'Write a git commit message for these changes:\n\n{{changes}}\n\nStyle: {{style}}\n\nFollow conventional commits format.', schema: { variables: [{ name: 'changes', type: 'textarea', label: 'Describe your changes', required: true }, { name: 'style', type: 'dropdown', label: 'Style', options: ['Conventional Commits', 'Detailed', 'Brief'] }] }, tags: ['git', 'commits'], totalRuns: 12345, starsCount: 876 },
30
+ { slug: 'dockerfile-generator', title: 'Dockerfile Generator', description: 'Create optimized Dockerfiles for your applications.', category: 'Development', template: 'Create a Dockerfile for:\n\nApp Type: {{appType}}\nBase Image: {{base}}\nRequirements: {{requirements}}\n\nOptimize for size and security.', schema: { variables: [{ name: 'appType', type: 'dropdown', label: 'Application Type', options: ['Node.js', 'Python', 'Go', 'Java', 'Rust', 'Multi-stage'], required: true }, { name: 'base', type: 'text', label: 'Base Image', placeholder: 'e.g., node:20-alpine' }, { name: 'requirements', type: 'textarea', label: 'Additional Requirements' }] }, tags: ['docker', 'devops'], totalRuns: 5678, starsCount: 432 },
31
+ { slug: 'error-debugger', title: 'Error Message Debugger', description: 'Debug error messages and get solutions.', category: 'Development', template: 'Help me debug this error:\n\n```\n{{error}}\n```\n\nContext: {{context}}\nLanguage/Framework: {{language}}\n\nExplain the cause and provide solutions.', schema: { variables: [{ name: 'error', type: 'textarea', label: 'Error Message', required: true }, { name: 'context', type: 'textarea', label: 'What were you doing?' }, { name: 'language', type: 'text', label: 'Language/Framework' }] }, tags: ['debugging', 'errors'], totalRuns: 15678, starsCount: 1234 },
32
+ { slug: 'api-request-builder', title: 'API Request Builder', description: 'Generate API requests in multiple languages.', category: 'Development', template: 'Generate API request code for:\n\nEndpoint: {{endpoint}}\nMethod: {{method}}\nHeaders: {{headers}}\nBody: {{body}}\n\nLanguages: {{languages}}', schema: { variables: [{ name: 'endpoint', type: 'text', label: 'API Endpoint', required: true }, { name: 'method', type: 'dropdown', label: 'Method', options: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] }, { name: 'headers', type: 'textarea', label: 'Headers (JSON)' }, { name: 'body', type: 'textarea', label: 'Request Body' }, { name: 'languages', type: 'text', label: 'Languages', default: 'JavaScript, Python, cURL' }] }, tags: ['api', 'http'], totalRuns: 6789, starsCount: 543 },
33
+ { slug: 'readme-generator', title: 'README.md Generator', description: 'Create professional README files for your projects.', category: 'Development', template: 'Generate a README.md for:\n\nProject: {{project}}\nDescription: {{description}}\nTech Stack: {{tech}}\nFeatures: {{features}}\n\nInclude badges, installation, usage, contributing.', schema: { variables: [{ name: 'project', type: 'text', label: 'Project Name', required: true }, { name: 'description', type: 'textarea', label: 'Description', required: true }, { name: 'tech', type: 'text', label: 'Tech Stack' }, { name: 'features', type: 'textarea', label: 'Key Features' }] }, tags: ['readme', 'documentation'], totalRuns: 9876, starsCount: 765 },
34
+ { slug: 'cron-expression', title: 'Cron Expression Generator', description: 'Generate cron expressions from natural language.', category: 'Development', template: 'Generate a cron expression for:\n\n{{schedule}}\n\nProvide:\n- Cron expression\n- Explanation of each field\n- Example run times', schema: { variables: [{ name: 'schedule', type: 'text', label: 'Schedule Description', placeholder: 'e.g., Every Monday at 9 AM', required: true }] }, tags: ['cron', 'scheduling'], totalRuns: 4567, starsCount: 345 },
35
+ { slug: 'shell-script', title: 'Shell Script Generator', description: 'Generate bash/shell scripts for automation.', category: 'Development', template: 'Write a shell script to:\n\n{{task}}\n\nOS: {{os}}\nShell: {{shell}}\n\nInclude error handling and comments.', schema: { variables: [{ name: 'task', type: 'textarea', label: 'What should the script do?', required: true }, { name: 'os', type: 'dropdown', label: 'Operating System', options: ['Linux', 'macOS', 'Windows (PowerShell)'] }, { name: 'shell', type: 'dropdown', label: 'Shell', options: ['bash', 'zsh', 'sh', 'PowerShell'] }] }, tags: ['bash', 'automation', 'scripts'], totalRuns: 7890, starsCount: 567 },
36
+ { slug: 'prisma-schema', title: 'Prisma Schema Generator', description: 'Generate Prisma schemas from requirements.', category: 'Development', template: 'Generate a Prisma schema for:\n\n{{requirements}}\n\nDatabase: {{database}}\n\nInclude relations, indexes, and enums as needed.', schema: { variables: [{ name: 'requirements', type: 'textarea', label: 'Describe your data model', required: true }, { name: 'database', type: 'dropdown', label: 'Database', options: ['PostgreSQL', 'MySQL', 'SQLite', 'MongoDB'] }] }, tags: ['prisma', 'database', 'orm'], totalRuns: 3456, starsCount: 234 },
37
+
38
+ // ============ MARKETING (8 more) ============
39
+ { slug: 'email-sequence', title: 'Email Sequence Creator', description: 'Create automated email sequences that convert.', category: 'Marketing', template: 'Create a {{emails}}-email sequence for:\n\nGoal: {{goal}}\nProduct: {{product}}\nAudience: {{audience}}\n\nInclude subject lines, preview text, and body for each.', schema: { variables: [{ name: 'emails', type: 'dropdown', label: 'Number of Emails', options: ['3', '5', '7', '10'], required: true }, { name: 'goal', type: 'dropdown', label: 'Sequence Goal', options: ['Welcome', 'Sales', 'Onboarding', 'Re-engagement'] }, { name: 'product', type: 'text', label: 'Product/Service' }, { name: 'audience', type: 'text', label: 'Target Audience' }] }, tags: ['email-marketing', 'automation'], totalRuns: 5678, starsCount: 432 },
40
+ { slug: 'seo-keywords', title: 'SEO Keyword Research', description: 'Generate keyword ideas with search intent analysis.', category: 'Marketing', template: 'Generate keyword ideas for:\n\nTopic: {{topic}}\nNiche: {{niche}}\n\nFor each keyword, include:\n- Search intent\n- Difficulty estimate\n- Content type recommendation', schema: { variables: [{ name: 'topic', type: 'text', label: 'Main Topic', required: true }, { name: 'niche', type: 'text', label: 'Industry/Niche' }] }, tags: ['seo', 'keywords'], totalRuns: 7654, starsCount: 543 },
41
+ { slug: 'ab-test-ideas', title: 'A/B Test Ideas Generator', description: 'Generate A/B test hypotheses to improve conversion.', category: 'Marketing', template: 'Generate A/B test ideas for:\n\nPage Type: {{page}}\nCurrent Conversion Rate: {{rate}}\nGoal: {{goal}}\n\nFor each test, include hypothesis, expected impact, and how to measure.', schema: { variables: [{ name: 'page', type: 'dropdown', label: 'Page Type', options: ['Landing Page', 'Checkout', 'Pricing', 'Homepage', 'Product Page'], required: true }, { name: 'rate', type: 'text', label: 'Current Conversion Rate' }, { name: 'goal', type: 'text', label: 'Improvement Goal' }] }, tags: ['ab-testing', 'cro'], totalRuns: 3456, starsCount: 234 },
42
+ { slug: 'brand-voice', title: 'Brand Voice Guidelines', description: 'Define your brand voice and communication style.', category: 'Marketing', template: 'Create brand voice guidelines for:\n\nBrand: {{brand}}\nIndustry: {{industry}}\nTarget Audience: {{audience}}\nPersonality: {{personality}}\n\nInclude dos, don\'ts, and examples.', schema: { variables: [{ name: 'brand', type: 'text', label: 'Brand Name', required: true }, { name: 'industry', type: 'text', label: 'Industry' }, { name: 'audience', type: 'text', label: 'Target Audience' }, { name: 'personality', type: 'text', label: 'Brand Personality Traits' }] }, tags: ['branding', 'voice'], totalRuns: 4567, starsCount: 345 },
43
+ { slug: 'competitor-analysis', title: 'Competitor Analysis', description: 'Analyze competitors and find opportunities.', category: 'Marketing', template: 'Analyze these competitors for {{company}}:\n\nCompetitors: {{competitors}}\n\nAnalyze:\n- Strengths & weaknesses\n- Pricing strategies\n- Marketing channels\n- Opportunity gaps', schema: { variables: [{ name: 'company', type: 'text', label: 'Your Company', required: true }, { name: 'competitors', type: 'textarea', label: 'Competitor Names', required: true }] }, tags: ['competitive-analysis', 'strategy'], totalRuns: 2345, starsCount: 187 },
44
+ { slug: 'utm-generator', title: 'UTM Parameter Generator', description: 'Generate UTM parameters for campaign tracking.', category: 'Marketing', template: 'Generate UTM parameters for:\n\nCampaign: {{campaign}}\nChannels: {{channels}}\nBase URL: {{url}}\n\nProvide full URLs with proper naming conventions.', schema: { variables: [{ name: 'campaign', type: 'text', label: 'Campaign Name', required: true }, { name: 'channels', type: 'text', label: 'Marketing Channels', placeholder: 'e.g., email, twitter, facebook' }, { name: 'url', type: 'text', label: 'Base URL', required: true }] }, tags: ['utm', 'analytics'], totalRuns: 5678, starsCount: 432 },
45
+ { slug: 'value-proposition', title: 'Value Proposition Canvas', description: 'Create a compelling value proposition.', category: 'Marketing', template: 'Create a value proposition for:\n\nProduct: {{product}}\nTarget Customer: {{customer}}\nMain Problem: {{problem}}\n\nUse the Value Proposition Canvas framework.', schema: { variables: [{ name: 'product', type: 'text', label: 'Product/Service', required: true }, { name: 'customer', type: 'text', label: 'Target Customer', required: true }, { name: 'problem', type: 'textarea', label: 'Main Problem You Solve' }] }, tags: ['value-proposition', 'positioning'], totalRuns: 4567, starsCount: 345 },
46
+ { slug: 'influencer-brief', title: 'Influencer Brief Creator', description: 'Create briefs for influencer collaborations.', category: 'Marketing', template: 'Create an influencer brief for:\n\nBrand: {{brand}}\nCampaign: {{campaign}}\nPlatform: {{platform}}\nDeliverables: {{deliverables}}\n\nInclude brand guidelines, key messages, dos and don\'ts.', schema: { variables: [{ name: 'brand', type: 'text', label: 'Brand Name', required: true }, { name: 'campaign', type: 'text', label: 'Campaign Name' }, { name: 'platform', type: 'dropdown', label: 'Platform', options: ['Instagram', 'TikTok', 'YouTube', 'Twitter'] }, { name: 'deliverables', type: 'textarea', label: 'Deliverables' }] }, tags: ['influencer', 'social-media'], totalRuns: 3456, starsCount: 234 },
47
+
48
+ // ============ BUSINESS (8 more) ============
49
+ { slug: 'meeting-agenda', title: 'Meeting Agenda Generator', description: 'Create structured meeting agendas.', category: 'Business', template: 'Create a meeting agenda for:\n\nMeeting Type: {{type}}\nDuration: {{duration}} minutes\nTopics: {{topics}}\nAttendees: {{attendees}}\n\nInclude time allocations and action items section.', schema: { variables: [{ name: 'type', type: 'dropdown', label: 'Meeting Type', options: ['Team Standup', 'Project Review', 'Brainstorm', 'Client Call', 'All Hands'], required: true }, { name: 'duration', type: 'dropdown', label: 'Duration (min)', options: ['15', '30', '45', '60'] }, { name: 'topics', type: 'textarea', label: 'Topics to Discuss' }, { name: 'attendees', type: 'text', label: 'Key Attendees' }] }, tags: ['meetings', 'productivity'], totalRuns: 8765, starsCount: 654 },
50
+ { slug: 'okr-generator', title: 'OKR Generator', description: 'Create objectives and key results for teams.', category: 'Business', template: 'Generate OKRs for:\n\nTeam: {{team}}\nQuarter: {{quarter}}\nFocus Area: {{focus}}\nCurrent Challenges: {{challenges}}\n\nCreate 3-5 objectives with 3-4 key results each.', schema: { variables: [{ name: 'team', type: 'text', label: 'Team/Department', required: true }, { name: 'quarter', type: 'dropdown', label: 'Quarter', options: ['Q1', 'Q2', 'Q3', 'Q4'] }, { name: 'focus', type: 'text', label: 'Focus Area' }, { name: 'challenges', type: 'textarea', label: 'Current Challenges' }] }, tags: ['okrs', 'goals', 'planning'], totalRuns: 4567, starsCount: 345 },
51
+ { slug: 'job-description', title: 'Job Description Writer', description: 'Write compelling job descriptions that attract talent.', category: 'Business', template: 'Write a job description for:\n\nPosition: {{position}}\nCompany: {{company}}\nLevel: {{level}}\nLocation: {{location}}\nKey Skills: {{skills}}\n\nMake it engaging and inclusive.', schema: { variables: [{ name: 'position', type: 'text', label: 'Job Title', required: true }, { name: 'company', type: 'text', label: 'Company Name' }, { name: 'level', type: 'dropdown', label: 'Level', options: ['Entry', 'Mid', 'Senior', 'Lead', 'Manager', 'Director'] }, { name: 'location', type: 'text', label: 'Location' }, { name: 'skills', type: 'textarea', label: 'Required Skills' }] }, tags: ['hiring', 'hr', 'jobs'], totalRuns: 6543, starsCount: 456 },
52
+ { slug: 'swot-analysis', title: 'SWOT Analysis Generator', description: 'Create comprehensive SWOT analyses.', category: 'Business', template: 'Create a SWOT analysis for:\n\nCompany/Project: {{subject}}\nIndustry: {{industry}}\nContext: {{context}}\n\nProvide detailed Strengths, Weaknesses, Opportunities, and Threats.', schema: { variables: [{ name: 'subject', type: 'text', label: 'Company/Project', required: true }, { name: 'industry', type: 'text', label: 'Industry' }, { name: 'context', type: 'textarea', label: 'Context/Background' }] }, tags: ['swot', 'strategy', 'analysis'], totalRuns: 5678, starsCount: 432 },
53
+ { slug: 'performance-review', title: 'Performance Review Helper', description: 'Write constructive performance reviews.', category: 'Business', template: 'Help write a performance review for:\n\nRole: {{role}}\nAchievements: {{achievements}}\nAreas to Improve: {{improvements}}\nTone: {{tone}}\n\nBe constructive and specific.', schema: { variables: [{ name: 'role', type: 'text', label: 'Employee Role', required: true }, { name: 'achievements', type: 'textarea', label: 'Key Achievements' }, { name: 'improvements', type: 'textarea', label: 'Areas for Improvement' }, { name: 'tone', type: 'dropdown', label: 'Tone', options: ['Highly Positive', 'Positive', 'Constructive', 'Needs Improvement'] }] }, tags: ['hr', 'reviews', 'management'], totalRuns: 7654, starsCount: 543 },
54
+ { slug: 'invoice-email', title: 'Invoice & Payment Email', description: 'Write professional invoice and payment emails.', category: 'Business', template: 'Write {{type}} email for:\n\nClient: {{client}}\nAmount: {{amount}}\nProject: {{project}}\nDue Date: {{due}}\n\nBe professional but friendly.', schema: { variables: [{ name: 'type', type: 'dropdown', label: 'Email Type', options: ['Invoice', 'Payment Reminder', 'Overdue Notice', 'Payment Received'], required: true }, { name: 'client', type: 'text', label: 'Client Name' }, { name: 'amount', type: 'text', label: 'Amount' }, { name: 'project', type: 'text', label: 'Project Name' }, { name: 'due', type: 'text', label: 'Due Date' }] }, tags: ['invoice', 'payment', 'finance'], totalRuns: 4567, starsCount: 345 },
55
+ { slug: 'sop-writer', title: 'SOP Document Writer', description: 'Create Standard Operating Procedures.', category: 'Business', template: 'Create an SOP for:\n\nProcess: {{process}}\nDepartment: {{department}}\nComplexity: {{complexity}}\n\nInclude purpose, scope, procedure steps, and exceptions.', schema: { variables: [{ name: 'process', type: 'text', label: 'Process Name', required: true }, { name: 'department', type: 'text', label: 'Department' }, { name: 'complexity', type: 'dropdown', label: 'Complexity', options: ['Simple', 'Moderate', 'Complex'] }] }, tags: ['sop', 'documentation', 'processes'], totalRuns: 3456, starsCount: 234 },
56
+ { slug: 'pitch-deck', title: 'Pitch Deck Outline', description: 'Create investor pitch deck structure.', category: 'Business', template: 'Create a pitch deck outline for:\n\nStartup: {{startup}}\nStage: {{stage}}\nAsking: {{ask}}\nProblem: {{problem}}\n\nInclude all essential slides with talking points.', schema: { variables: [{ name: 'startup', type: 'text', label: 'Startup Name', required: true }, { name: 'stage', type: 'dropdown', label: 'Stage', options: ['Pre-seed', 'Seed', 'Series A', 'Series B+'] }, { name: 'ask', type: 'text', label: 'Funding Ask' }, { name: 'problem', type: 'textarea', label: 'Problem You Solve' }] }, tags: ['pitch', 'investors', 'startup'], totalRuns: 5678, starsCount: 432 },
57
+
58
+ // ============ EDUCATION (6 more) ============
59
+ { slug: 'quiz-generator', title: 'Quiz Generator', description: 'Create quizzes and tests on any topic.', category: 'Education', template: 'Create a quiz on:\n\nTopic: {{topic}}\nDifficulty: {{difficulty}}\nQuestions: {{count}}\nType: {{type}}\n\nInclude answers and explanations.', schema: { variables: [{ name: 'topic', type: 'text', label: 'Topic', required: true }, { name: 'difficulty', type: 'dropdown', label: 'Difficulty', options: ['Easy', 'Medium', 'Hard'] }, { name: 'count', type: 'dropdown', label: 'Questions', options: ['5', '10', '15', '20'] }, { name: 'type', type: 'dropdown', label: 'Question Type', options: ['Multiple Choice', 'True/False', 'Short Answer', 'Mixed'] }] }, tags: ['quiz', 'testing', 'education'], totalRuns: 8765, starsCount: 654 },
60
+ { slug: 'study-guide', title: 'Study Guide Creator', description: 'Create comprehensive study guides.', category: 'Education', template: 'Create a study guide for:\n\nSubject: {{subject}}\nTopics: {{topics}}\nExam Type: {{exam}}\n\nInclude key concepts, practice questions, and memory tips.', schema: { variables: [{ name: 'subject', type: 'text', label: 'Subject', required: true }, { name: 'topics', type: 'textarea', label: 'Topics to Cover' }, { name: 'exam', type: 'dropdown', label: 'Exam Type', options: ['Multiple Choice', 'Essay', 'Practical', 'Mixed'] }] }, tags: ['study', 'exam', 'learning'], totalRuns: 6543, starsCount: 456 },
61
+ { slug: 'flashcard-maker', title: 'Flashcard Generator', description: 'Create flashcards for spaced repetition learning.', category: 'Education', template: 'Create {{count}} flashcards for:\n\nTopic: {{topic}}\nLevel: {{level}}\n\nFormat: Front | Back\nInclude hints where helpful.', schema: { variables: [{ name: 'topic', type: 'text', label: 'Topic', required: true }, { name: 'count', type: 'dropdown', label: 'Number', options: ['10', '20', '30', '50'] }, { name: 'level', type: 'dropdown', label: 'Level', options: ['Beginner', 'Intermediate', 'Advanced'] }] }, tags: ['flashcards', 'memorization'], totalRuns: 7654, starsCount: 543 },
62
+ { slug: 'essay-outline', title: 'Essay Outline Generator', description: 'Structure essays with compelling arguments.', category: 'Education', template: 'Create an essay outline for:\n\nTopic: {{topic}}\nThesis: {{thesis}}\nLength: {{length}} words\nStyle: {{style}}\n\nInclude intro, body paragraphs, and conclusion structure.', schema: { variables: [{ name: 'topic', type: 'text', label: 'Essay Topic', required: true }, { name: 'thesis', type: 'text', label: 'Thesis Statement' }, { name: 'length', type: 'dropdown', label: 'Length (words)', options: ['500', '1000', '1500', '2000', '3000'] }, { name: 'style', type: 'dropdown', label: 'Style', options: ['Argumentative', 'Expository', 'Narrative', 'Persuasive'] }] }, tags: ['essay', 'writing', 'academic'], totalRuns: 9876, starsCount: 765 },
63
+ { slug: 'presentation-outline', title: 'Presentation Outline', description: 'Create structured presentation outlines.', category: 'Education', template: 'Create a presentation outline for:\n\nTopic: {{topic}}\nSlides: {{slides}}\nAudience: {{audience}}\nDuration: {{duration}} minutes\n\nInclude slide titles and key points.', schema: { variables: [{ name: 'topic', type: 'text', label: 'Presentation Topic', required: true }, { name: 'slides', type: 'dropdown', label: 'Number of Slides', options: ['5', '10', '15', '20'] }, { name: 'audience', type: 'text', label: 'Audience' }, { name: 'duration', type: 'dropdown', label: 'Duration (min)', options: ['5', '10', '15', '30'] }] }, tags: ['presentation', 'slides', 'speaking'], totalRuns: 5678, starsCount: 432 },
64
+ { slug: 'language-tutor', title: 'Language Learning Tutor', description: 'Practice conversations in any language.', category: 'Education', template: 'Help me practice {{language}}.\n\nLevel: {{level}}\nScenario: {{scenario}}\nFocus: {{focus}}\n\nCorrect my mistakes and explain grammar.', schema: { variables: [{ name: 'language', type: 'dropdown', label: 'Language', options: ['Spanish', 'French', 'German', 'Japanese', 'Chinese', 'Italian', 'Portuguese', 'Korean'], required: true }, { name: 'level', type: 'dropdown', label: 'Level', options: ['Beginner', 'Intermediate', 'Advanced'] }, { name: 'scenario', type: 'text', label: 'Scenario', placeholder: 'e.g., ordering at a restaurant' }, { name: 'focus', type: 'dropdown', label: 'Focus Area', options: ['Conversation', 'Grammar', 'Vocabulary', 'Pronunciation'] }] }, tags: ['language', 'learning', 'tutor'], totalRuns: 12345, starsCount: 876 },
65
+
66
+ // ============ CREATIVE (6 more) ============
67
+ { slug: 'character-creator', title: 'Character Creator', description: 'Create detailed fictional characters.', category: 'Creative', template: 'Create a character for {{genre}} fiction:\n\nRole: {{role}}\nPersonality: {{personality}}\nBackground: {{background}}\n\nInclude appearance, motivation, flaws, and quirks.', schema: { variables: [{ name: 'genre', type: 'dropdown', label: 'Genre', options: ['Fantasy', 'Sci-Fi', 'Mystery', 'Romance', 'Horror', 'Literary'], required: true }, { name: 'role', type: 'text', label: 'Character Role', placeholder: 'e.g., protagonist, villain' }, { name: 'personality', type: 'text', label: 'Personality Traits' }, { name: 'background', type: 'text', label: 'Background' }] }, tags: ['character', 'fiction', 'writing'], totalRuns: 6543, starsCount: 456 },
68
+ { slug: 'world-builder', title: 'World Builder', description: 'Create detailed fictional worlds.', category: 'Creative', template: 'Build a world for {{genre}} fiction:\n\nSetting: {{setting}}\nMagic/Tech Level: {{tech}}\nKey Features: {{features}}\n\nInclude geography, culture, history, and unique elements.', schema: { variables: [{ name: 'genre', type: 'dropdown', label: 'Genre', options: ['Fantasy', 'Sci-Fi', 'Post-Apocalyptic', 'Steampunk', 'Urban Fantasy'], required: true }, { name: 'setting', type: 'text', label: 'Basic Setting', placeholder: 'e.g., floating cities' }, { name: 'tech', type: 'dropdown', label: 'Tech/Magic Level', options: ['Low', 'Medium', 'High', 'Mixed'] }, { name: 'features', type: 'textarea', label: 'Key Features' }] }, tags: ['worldbuilding', 'fiction'], totalRuns: 4567, starsCount: 345 },
69
+ { slug: 'poetry-generator', title: 'Poetry Generator', description: 'Write poems in various styles.', category: 'Creative', template: 'Write a {{style}} poem about:\n\n{{topic}}\n\nMood: {{mood}}\nLength: {{length}}\n\nInclude vivid imagery and emotion.', schema: { variables: [{ name: 'style', type: 'dropdown', label: 'Style', options: ['Free Verse', 'Sonnet', 'Haiku', 'Limerick', 'Ballad', 'Ode'], required: true }, { name: 'topic', type: 'text', label: 'Topic/Theme', required: true }, { name: 'mood', type: 'dropdown', label: 'Mood', options: ['Happy', 'Sad', 'Romantic', 'Angry', 'Peaceful', 'Dark'] }, { name: 'length', type: 'dropdown', label: 'Length', options: ['Short', 'Medium', 'Long'] }] }, tags: ['poetry', 'creative-writing'], totalRuns: 5678, starsCount: 432 },
70
+ { slug: 'dialogue-writer', title: 'Dialogue Writer', description: 'Write realistic dialogue between characters.', category: 'Creative', template: 'Write dialogue between:\n\nCharacter 1: {{char1}}\nCharacter 2: {{char2}}\nSituation: {{situation}}\nTone: {{tone}}\n\nMake it natural and reveal character.', schema: { variables: [{ name: 'char1', type: 'text', label: 'Character 1', required: true }, { name: 'char2', type: 'text', label: 'Character 2', required: true }, { name: 'situation', type: 'textarea', label: 'Situation/Context' }, { name: 'tone', type: 'dropdown', label: 'Tone', options: ['Serious', 'Humorous', 'Tense', 'Romantic', 'Casual'] }] }, tags: ['dialogue', 'screenplay'], totalRuns: 4567, starsCount: 345 },
71
+ { slug: 'plot-twist', title: 'Plot Twist Generator', description: 'Generate surprising plot twists.', category: 'Creative', template: 'Generate plot twists for:\n\nGenre: {{genre}}\nCurrent Setup: {{setup}}\nCharacters: {{characters}}\n\nProvide 5 plot twist ideas from subtle to shocking.', schema: { variables: [{ name: 'genre', type: 'dropdown', label: 'Genre', options: ['Mystery', 'Thriller', 'Fantasy', 'Romance', 'Horror', 'Drama'], required: true }, { name: 'setup', type: 'textarea', label: 'Current Plot Setup', required: true }, { name: 'characters', type: 'textarea', label: 'Main Characters' }] }, tags: ['plot', 'storytelling'], totalRuns: 3456, starsCount: 234 },
72
+ { slug: 'name-generator', title: 'Character Name Generator', description: 'Generate character names for any setting.', category: 'Creative', template: 'Generate {{count}} names for:\n\nSetting: {{setting}}\nGender: {{gender}}\nVibe: {{vibe}}\n\nInclude meaning or origin if relevant.', schema: { variables: [{ name: 'count', type: 'dropdown', label: 'Number of Names', options: ['5', '10', '20'], required: true }, { name: 'setting', type: 'dropdown', label: 'Setting', options: ['Fantasy', 'Sci-Fi', 'Historical', 'Modern', 'Mythological'] }, { name: 'gender', type: 'dropdown', label: 'Gender', options: ['Male', 'Female', 'Neutral', 'Mixed'] }, { name: 'vibe', type: 'text', label: 'Vibe/Feeling', placeholder: 'e.g., elegant, fierce, mysterious' }] }, tags: ['names', 'fantasy', 'characters'], totalRuns: 8765, starsCount: 654 },
73
+
74
+ // ============ RESEARCH (2 more) ============
75
+ { slug: 'literature-review', title: 'Literature Review Helper', description: 'Structure and write literature reviews.', category: 'Research', template: 'Help write a literature review on:\n\nTopic: {{topic}}\nField: {{field}}\nSources Summary: {{sources}}\n\nOrganize thematically with synthesis and gaps.', schema: { variables: [{ name: 'topic', type: 'text', label: 'Research Topic', required: true }, { name: 'field', type: 'text', label: 'Academic Field' }, { name: 'sources', type: 'textarea', label: 'Brief Summary of Sources' }] }, tags: ['literature-review', 'academic'], totalRuns: 3456, starsCount: 234 },
76
+ { slug: 'hypothesis-generator', title: 'Research Hypothesis Generator', description: 'Generate testable research hypotheses.', category: 'Research', template: 'Generate research hypotheses for:\n\nResearch Question: {{question}}\nField: {{field}}\nVariables: {{variables}}\n\nInclude null and alternative hypotheses with rationale.', schema: { variables: [{ name: 'question', type: 'textarea', label: 'Research Question', required: true }, { name: 'field', type: 'text', label: 'Research Field' }, { name: 'variables', type: 'textarea', label: 'Key Variables' }] }, tags: ['hypothesis', 'research-methods'], totalRuns: 2345, starsCount: 187 },
77
+ ]
78
+
79
+ async function seedMore() {
80
+ console.log('🌱 Adding 50 more prompts...\n')
81
+
82
+ let created = 0
83
+ let skipped = 0
84
+
85
+ for (const prompt of morePrompts) {
86
+ const existing = await prisma.prompt.findUnique({
87
+ where: { slug: prompt.slug },
88
+ })
89
+
90
+ if (existing) {
91
+ console.log(` ⏭️ Skipping "${prompt.title}"`)
92
+ skipped++
93
+ continue
94
+ }
95
+
96
+ await prisma.prompt.create({
97
+ data: {
98
+ slug: prompt.slug,
99
+ title: prompt.title,
100
+ description: prompt.description,
101
+ template: prompt.template,
102
+ schema: prompt.schema,
103
+ category: prompt.category,
104
+ tags: prompt.tags,
105
+ totalRuns: prompt.totalRuns,
106
+ starsCount: prompt.starsCount,
107
+ remixesCount: 0,
108
+ modelDefault: 'gpt-4o-mini',
109
+ modelAllowed: ['gpt-4o-mini', 'gpt-4o', 'claude-3-5-sonnet'],
110
+ visibility: 'public',
111
+ },
112
+ })
113
+ console.log(` ✅ Created "${prompt.title}" [${prompt.category}]`)
114
+ created++
115
+ }
116
+
117
+ console.log(`\n✨ Done! Created ${created} prompts, skipped ${skipped}`)
118
+ }
119
+
120
+ seedMore()
121
+ .catch((e) => {
122
+ console.error('❌ Seeding failed:', e)
123
+ process.exit(1)
124
+ })
125
+ .finally(async () => {
126
+ await prisma.$disconnect()
127
+ await pool.end()
128
+ })
prisma/seed.ts ADDED
@@ -0,0 +1,644 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Pool } from 'pg'
2
+ import { PrismaPg } from '@prisma/adapter-pg'
3
+ import { PrismaClient } from '@prisma/client'
4
+ import 'dotenv/config'
5
+
6
+ const connectionString = process.env.DATABASE_URL
7
+ if (!connectionString) {
8
+ throw new Error('DATABASE_URL not set')
9
+ }
10
+
11
+ const pool = new Pool({ connectionString })
12
+ const adapter = new PrismaPg(pool)
13
+ const prisma = new PrismaClient({ adapter })
14
+
15
+ const seedPrompts = [
16
+ // ============ CONTENT ============
17
+ {
18
+ slug: 'blog-writer-pro',
19
+ title: 'AI Blog Writer Pro',
20
+ description: 'Generate SEO-optimized, engaging blog posts on any topic with customizable tone and length.',
21
+ template: `Write a comprehensive blog post about {{topic}}.
22
+
23
+ Tone: {{tone}}
24
+ Target Length: {{length}} words
25
+ Target Audience: {{audience}}
26
+
27
+ Requirements:
28
+ - Write an attention-grabbing headline
29
+ - Include an engaging introduction that hooks the reader
30
+ - Cover 3-5 main points with detailed explanations
31
+ - Use subheadings for better readability
32
+ - Include relevant examples or case studies
33
+ - End with a strong conclusion and call-to-action
34
+ - Optimize for SEO with natural keyword usage`,
35
+ schema: {
36
+ variables: [
37
+ { name: 'topic', type: 'text', label: 'Blog Topic', placeholder: 'e.g., The Future of AI in Healthcare', required: true },
38
+ { name: 'tone', type: 'dropdown', label: 'Writing Tone', options: ['Professional', 'Casual', 'Humorous', 'Academic', 'Inspirational'], default: 'Professional' },
39
+ { name: 'length', type: 'dropdown', label: 'Target Length (words)', options: ['500', '1000', '1500', '2000'], default: '1000' },
40
+ { name: 'audience', type: 'text', label: 'Target Audience', placeholder: 'e.g., Tech professionals', default: 'General audience' },
41
+ ],
42
+ },
43
+ category: 'Content',
44
+ tags: ['blog', 'writing', 'seo', 'content-marketing'],
45
+ totalRuns: 12453,
46
+ starsCount: 892,
47
+ },
48
+ {
49
+ slug: 'twitter-thread-generator',
50
+ title: 'Viral Twitter Thread Generator',
51
+ description: 'Create engaging Twitter/X threads that get likes and retweets.',
52
+ template: `Create a viral Twitter thread about {{topic}}.
53
+
54
+ Style: {{style}}
55
+ Number of tweets: {{count}}
56
+ Call-to-action: {{cta}}
57
+
58
+ Each tweet should:
59
+ - Start with a hook
60
+ - Be under 280 characters
61
+ - Use emojis strategically
62
+ - End with engagement prompts
63
+
64
+ Format as:
65
+ 🧵 1/{{count}}
66
+ [Tweet content]
67
+
68
+ Continue for all tweets.`,
69
+ schema: {
70
+ variables: [
71
+ { name: 'topic', type: 'text', label: 'Thread Topic', placeholder: 'e.g., 10 AI tools that will 10x your productivity', required: true },
72
+ { name: 'style', type: 'dropdown', label: 'Style', options: ['Educational', 'Storytelling', 'Tips & Tricks', 'Hot Take', 'Thread of Resources'] },
73
+ { name: 'count', type: 'dropdown', label: 'Number of Tweets', options: ['5', '7', '10', '15'], default: '10' },
74
+ { name: 'cta', type: 'text', label: 'Call-to-Action', placeholder: 'e.g., Follow for more AI tips', default: 'Like & Retweet if helpful!' },
75
+ ],
76
+ },
77
+ category: 'Content',
78
+ tags: ['twitter', 'social-media', 'viral', 'threads'],
79
+ totalRuns: 8234,
80
+ starsCount: 567,
81
+ },
82
+ {
83
+ slug: 'youtube-script-writer',
84
+ title: 'YouTube Video Script Writer',
85
+ description: 'Create engaging YouTube scripts with hooks, chapters, and CTAs.',
86
+ template: `Write a YouTube video script about {{topic}}.
87
+
88
+ Video Length: {{length}} minutes
89
+ Style: {{style}}
90
+ Target Audience: {{audience}}
91
+
92
+ Include:
93
+ 1. **HOOK** (First 30 seconds) - Grab attention immediately
94
+ 2. **INTRO** - Brief channel intro and video overview
95
+ 3. **MAIN CONTENT** - Divided into timestamped chapters
96
+ 4. **EXAMPLES** - Real-world examples or demonstrations
97
+ 5. **OUTRO** - Summary + CTA (like, subscribe, comment)
98
+
99
+ Format with timestamps like:
100
+ [0:00] Hook
101
+ [0:30] Intro
102
+ ...`,
103
+ schema: {
104
+ variables: [
105
+ { name: 'topic', type: 'text', label: 'Video Topic', placeholder: 'e.g., How to build a SaaS in 30 days', required: true },
106
+ { name: 'length', type: 'dropdown', label: 'Video Length', options: ['5', '10', '15', '20'], default: '10' },
107
+ { name: 'style', type: 'dropdown', label: 'Content Style', options: ['Tutorial', 'Review', 'Commentary', 'Story', 'Listicle'] },
108
+ { name: 'audience', type: 'text', label: 'Target Audience', placeholder: 'e.g., Beginner developers' },
109
+ ],
110
+ },
111
+ category: 'Content',
112
+ tags: ['youtube', 'video', 'script', 'content-creation'],
113
+ totalRuns: 6789,
114
+ starsCount: 445,
115
+ },
116
+
117
+ // ============ DEVELOPMENT ============
118
+ {
119
+ slug: 'code-review-assistant',
120
+ title: 'Senior Dev Code Review',
121
+ description: 'Get detailed, constructive code reviews like from a senior developer.',
122
+ template: `Act as a senior software engineer doing a thorough code review.
123
+
124
+ Language: {{language}}
125
+
126
+ \`\`\`{{language}}
127
+ {{code}}
128
+ \`\`\`
129
+
130
+ Provide your review with:
131
+
132
+ ## 📊 Overall Assessment
133
+ Brief quality rating and summary
134
+
135
+ ## ✅ What's Good
136
+ Highlight good practices
137
+
138
+ ## 🚨 Critical Issues
139
+ Security vulnerabilities, bugs, performance issues
140
+
141
+ ## ⚠️ Suggestions
142
+ Code improvements and best practices
143
+
144
+ ## 🔧 Refactored Code
145
+ Show the improved version with comments`,
146
+ schema: {
147
+ variables: [
148
+ { name: 'language', type: 'dropdown', label: 'Language', options: ['JavaScript', 'TypeScript', 'Python', 'Java', 'Go', 'Rust', 'C++', 'C#'], default: 'TypeScript', required: true },
149
+ { name: 'code', type: 'textarea', label: 'Code to Review', placeholder: 'Paste your code here...', required: true },
150
+ ],
151
+ },
152
+ category: 'Development',
153
+ tags: ['code-review', 'programming', 'best-practices'],
154
+ totalRuns: 15672,
155
+ starsCount: 1024,
156
+ },
157
+ {
158
+ slug: 'regex-generator',
159
+ title: 'Regex Pattern Generator',
160
+ description: 'Generate and explain regex patterns for any use case.',
161
+ template: `Create a regex pattern for: {{description}}
162
+
163
+ Language/Tool: {{language}}
164
+
165
+ Please provide:
166
+
167
+ 1. **The Regex Pattern**
168
+ \`\`\`
169
+ [pattern here]
170
+ \`\`\`
171
+
172
+ 2. **Explanation**
173
+ Break down each part of the regex
174
+
175
+ 3. **Test Cases**
176
+ - ✅ Should match: [examples]
177
+ - ❌ Should NOT match: [examples]
178
+
179
+ 4. **Code Example**
180
+ Show how to use it in {{language}}
181
+
182
+ 5. **Common Variations**
183
+ Any alternative patterns for edge cases`,
184
+ schema: {
185
+ variables: [
186
+ { name: 'description', type: 'textarea', label: 'What should the regex match?', placeholder: 'e.g., Email addresses with common domains only', required: true },
187
+ { name: 'language', type: 'dropdown', label: 'Language/Tool', options: ['JavaScript', 'Python', 'Java', 'PHP', 'Go', 'grep', 'sed'], default: 'JavaScript' },
188
+ ],
189
+ },
190
+ category: 'Development',
191
+ tags: ['regex', 'programming', 'patterns'],
192
+ totalRuns: 9876,
193
+ starsCount: 678,
194
+ },
195
+ {
196
+ slug: 'api-documentation',
197
+ title: 'API Documentation Generator',
198
+ description: 'Generate comprehensive API documentation from code or descriptions.',
199
+ template: `Generate professional API documentation for:
200
+
201
+ {{api_description}}
202
+
203
+ Format: {{format}}
204
+
205
+ Include:
206
+ - **Endpoint Overview**
207
+ - **Authentication** requirements
208
+ - **Request** (method, URL, headers, body with types)
209
+ - **Response** (success and error responses with examples)
210
+ - **Code Examples** in {{languages}}
211
+ - **Rate Limits** and error handling
212
+ - **Changelog** section`,
213
+ schema: {
214
+ variables: [
215
+ { name: 'api_description', type: 'textarea', label: 'API Description or Code', placeholder: 'Describe your API endpoints or paste code...', required: true },
216
+ { name: 'format', type: 'dropdown', label: 'Documentation Format', options: ['OpenAPI/Swagger', 'Markdown', 'REST API Blueprint', 'Postman Collection'], default: 'Markdown' },
217
+ { name: 'languages', type: 'text', label: 'Code Example Languages', placeholder: 'e.g., JavaScript, Python, cURL', default: 'JavaScript, Python, cURL' },
218
+ ],
219
+ },
220
+ category: 'Development',
221
+ tags: ['api', 'documentation', 'swagger', 'developer-tools'],
222
+ totalRuns: 5432,
223
+ starsCount: 389,
224
+ },
225
+
226
+ // ============ MARKETING ============
227
+ {
228
+ slug: 'landing-page-copy',
229
+ title: 'High-Converting Landing Page Copy',
230
+ description: 'Generate landing page copy that converts visitors into customers.',
231
+ template: `Create high-converting landing page copy for:
232
+
233
+ Product/Service: {{product}}
234
+ Target Audience: {{audience}}
235
+ Unique Value: {{uvp}}
236
+ Key Benefits: {{benefits}}
237
+
238
+ Generate:
239
+
240
+ # 🎯 HERO SECTION
241
+ - Headline (max 10 words, powerful)
242
+ - Subheadline (expand the promise)
243
+ - CTA button text
244
+
245
+ # 😰 PROBLEM SECTION
246
+ Pain points your audience faces
247
+
248
+ # ✨ SOLUTION SECTION
249
+ How you solve it
250
+
251
+ # 💎 BENEFITS (not features)
252
+ 3-4 benefit blocks with icons
253
+
254
+ # 🏆 SOCIAL PROOF
255
+ Testimonial templates and stats placement
256
+
257
+ # 🚀 FINAL CTA
258
+ Urgency-driven closing`,
259
+ schema: {
260
+ variables: [
261
+ { name: 'product', type: 'text', label: 'Product/Service', placeholder: 'e.g., CloudSync - AI-Powered Note Taking', required: true },
262
+ { name: 'audience', type: 'text', label: 'Target Audience', placeholder: 'e.g., Busy professionals who take lots of notes', required: true },
263
+ { name: 'uvp', type: 'textarea', label: 'Unique Value Proposition', placeholder: 'What makes you different?', required: true },
264
+ { name: 'benefits', type: 'textarea', label: 'Key Benefits', placeholder: 'List 3-5 main benefits' },
265
+ ],
266
+ },
267
+ category: 'Marketing',
268
+ tags: ['copywriting', 'landing-page', 'conversion'],
269
+ totalRuns: 11234,
270
+ starsCount: 876,
271
+ },
272
+ {
273
+ slug: 'ad-copy-generator',
274
+ title: 'Facebook/Google Ad Copy',
275
+ description: 'Create high-CTR ad copy for paid campaigns.',
276
+ template: `Create {{platform}} ad copy for:
277
+
278
+ Product: {{product}}
279
+ Target Audience: {{audience}}
280
+ Offer: {{offer}}
281
+ Tone: {{tone}}
282
+
283
+ Generate 5 variations of:
284
+ - **Headline** ({{platform}} character limit)
285
+ - **Description**
286
+ - **Call-to-Action**
287
+
288
+ For each variation, include:
289
+ - 📊 Estimated CTR potential
290
+ - 🎯 Best audience type
291
+ - 💡 A/B testing suggestion`,
292
+ schema: {
293
+ variables: [
294
+ { name: 'platform', type: 'dropdown', label: 'Platform', options: ['Facebook/Instagram', 'Google Ads', 'LinkedIn', 'TikTok'], default: 'Facebook/Instagram', required: true },
295
+ { name: 'product', type: 'text', label: 'Product/Service', placeholder: 'e.g., Online Fitness Coaching', required: true },
296
+ { name: 'audience', type: 'text', label: 'Target Audience', placeholder: 'e.g., Women 25-45 interested in weight loss' },
297
+ { name: 'offer', type: 'text', label: 'Offer/Discount', placeholder: 'e.g., 50% off first month' },
298
+ { name: 'tone', type: 'dropdown', label: 'Tone', options: ['Professional', 'Friendly', 'Urgent', 'Playful', 'Luxury'], default: 'Friendly' },
299
+ ],
300
+ },
301
+ category: 'Marketing',
302
+ tags: ['ads', 'facebook', 'google-ads', 'copywriting'],
303
+ totalRuns: 8765,
304
+ starsCount: 654,
305
+ },
306
+
307
+ // ============ BUSINESS ============
308
+ {
309
+ slug: 'professional-email',
310
+ title: 'Professional Email Composer',
311
+ description: 'Write perfect professional emails for any situation.',
312
+ template: `Write a professional email for:
313
+
314
+ Purpose: {{purpose}}
315
+ Recipient: {{recipient}}
316
+ Key Points: {{key_points}}
317
+ Tone: {{tone}}
318
+
319
+ Include:
320
+ - Appropriate subject line
321
+ - Professional greeting
322
+ - Clear, concise body
323
+ - Specific call-to-action
324
+ - Professional sign-off
325
+
326
+ Context: {{context}}`,
327
+ schema: {
328
+ variables: [
329
+ { name: 'purpose', type: 'dropdown', label: 'Email Purpose', options: ['Follow-up', 'Cold Outreach', 'Thank You', 'Request', 'Apology', 'Announcement', 'Negotiation', 'Introduction'], required: true },
330
+ { name: 'recipient', type: 'text', label: 'Recipient', placeholder: 'e.g., Hiring Manager at Google', required: true },
331
+ { name: 'key_points', type: 'textarea', label: 'Key Points', placeholder: 'What do you need to communicate?', required: true },
332
+ { name: 'tone', type: 'dropdown', label: 'Tone', options: ['Formal', 'Friendly Professional', 'Casual', 'Urgent'], default: 'Friendly Professional' },
333
+ { name: 'context', type: 'textarea', label: 'Additional Context', placeholder: 'Any relevant background...' },
334
+ ],
335
+ },
336
+ category: 'Business',
337
+ tags: ['email', 'professional', 'communication'],
338
+ totalRuns: 18234,
339
+ starsCount: 1234,
340
+ },
341
+ {
342
+ slug: 'business-proposal',
343
+ title: 'Business Proposal Generator',
344
+ description: 'Create compelling business proposals that win clients.',
345
+ template: `Create a professional business proposal for:
346
+
347
+ Client: {{client}}
348
+ Project: {{project}}
349
+ Budget Range: {{budget}}
350
+ Timeline: {{timeline}}
351
+
352
+ Include these sections:
353
+
354
+ 1. **Executive Summary**
355
+ 2. **Understanding of Needs**
356
+ 3. **Proposed Solution**
357
+ 4. **Scope of Work** (detailed deliverables)
358
+ 5. **Timeline & Milestones**
359
+ 6. **Investment** (pricing breakdown)
360
+ 7. **About Us**
361
+ 8. **Next Steps**`,
362
+ schema: {
363
+ variables: [
364
+ { name: 'client', type: 'text', label: 'Client Name/Company', placeholder: 'e.g., Acme Corporation', required: true },
365
+ { name: 'project', type: 'textarea', label: 'Project Description', placeholder: 'Describe the project...', required: true },
366
+ { name: 'budget', type: 'text', label: 'Budget Range', placeholder: 'e.g., $5,000 - $10,000' },
367
+ { name: 'timeline', type: 'text', label: 'Timeline', placeholder: 'e.g., 4-6 weeks' },
368
+ ],
369
+ },
370
+ category: 'Business',
371
+ tags: ['proposal', 'business', 'sales', 'client'],
372
+ totalRuns: 6543,
373
+ starsCount: 456,
374
+ },
375
+
376
+ // ============ EDUCATION ============
377
+ {
378
+ slug: 'lesson-plan-creator',
379
+ title: 'AI Lesson Plan Creator',
380
+ description: 'Create comprehensive lesson plans for any subject and grade level.',
381
+ template: `Create a detailed lesson plan for:
382
+
383
+ Subject: {{subject}}
384
+ Topic: {{topic}}
385
+ Grade Level: {{grade}}
386
+ Duration: {{duration}} minutes
387
+
388
+ Include:
389
+ 1. **Learning Objectives** (3-5 measurable goals)
390
+ 2. **Materials Needed**
391
+ 3. **Warm-Up Activity** (5-10 min)
392
+ 4. **Main Instruction** (step-by-step)
393
+ 5. **Guided Practice**
394
+ 6. **Independent Practice**
395
+ 7. **Assessment** (formative/summative)
396
+ 8. **Differentiation** (for different learners)
397
+ 9. **Homework/Extension**`,
398
+ schema: {
399
+ variables: [
400
+ { name: 'subject', type: 'dropdown', label: 'Subject', options: ['Math', 'Science', 'English', 'History', 'Art', 'Music', 'Computer Science', 'Physical Education'], required: true },
401
+ { name: 'topic', type: 'text', label: 'Topic', placeholder: 'e.g., Introduction to Fractions', required: true },
402
+ { name: 'grade', type: 'dropdown', label: 'Grade Level', options: ['K-2', '3-5', '6-8', '9-12', 'College', 'Adult'], default: '6-8' },
403
+ { name: 'duration', type: 'dropdown', label: 'Duration (minutes)', options: ['30', '45', '60', '90'], default: '45' },
404
+ ],
405
+ },
406
+ category: 'Education',
407
+ tags: ['lesson-plan', 'teaching', 'education'],
408
+ totalRuns: 7654,
409
+ starsCount: 543,
410
+ },
411
+ {
412
+ slug: 'explain-like-im-5',
413
+ title: 'Explain Like I\'m 5',
414
+ description: 'Break down complex topics into simple, fun explanations.',
415
+ template: `Explain {{topic}} like I'm {{age}} years old.
416
+
417
+ Use:
418
+ - Simple words
419
+ - Fun analogies (like games, toys, food)
420
+ - Short sentences
421
+ - Emojis where appropriate
422
+ - A fun example at the end
423
+
424
+ Topic complexity: {{complexity}}
425
+
426
+ Make it engaging and memorable!`,
427
+ schema: {
428
+ variables: [
429
+ { name: 'topic', type: 'text', label: 'Topic to Explain', placeholder: 'e.g., How does WiFi work?', required: true },
430
+ { name: 'age', type: 'dropdown', label: 'Target Age', options: ['5', '8', '10', '12', '15'], default: '5' },
431
+ { name: 'complexity', type: 'dropdown', label: 'Original Complexity', options: ['Simple', 'Moderate', 'Complex', 'Expert-level'], default: 'Complex' },
432
+ ],
433
+ },
434
+ category: 'Education',
435
+ tags: ['learning', 'simple', 'explanation', 'eli5'],
436
+ totalRuns: 12345,
437
+ starsCount: 876,
438
+ },
439
+
440
+ // ============ CREATIVE ============
441
+ {
442
+ slug: 'story-generator',
443
+ title: 'Creative Story Generator',
444
+ description: 'Generate creative stories in any genre with compelling characters.',
445
+ template: `Write a {{genre}} story with:
446
+
447
+ Main Character: {{character}}
448
+ Setting: {{setting}}
449
+ Conflict: {{conflict}}
450
+ Length: {{length}}
451
+ Tone: {{tone}}
452
+
453
+ Include:
454
+ - Vivid descriptions
455
+ - Dialogue
456
+ - Character development
457
+ - Satisfying resolution`,
458
+ schema: {
459
+ variables: [
460
+ { name: 'genre', type: 'dropdown', label: 'Genre', options: ['Fantasy', 'Sci-Fi', 'Romance', 'Mystery', 'Horror', 'Adventure', 'Comedy', 'Drama'], required: true },
461
+ { name: 'character', type: 'text', label: 'Main Character', placeholder: 'e.g., A time-traveling librarian', required: true },
462
+ { name: 'setting', type: 'text', label: 'Setting', placeholder: 'e.g., Victorian London' },
463
+ { name: 'conflict', type: 'text', label: 'Central Conflict', placeholder: 'e.g., Must prevent a paradox' },
464
+ { name: 'length', type: 'dropdown', label: 'Length', options: ['Flash Fiction (500 words)', 'Short Story (1500 words)', 'Long Story (3000 words)'], default: 'Short Story (1500 words)' },
465
+ { name: 'tone', type: 'dropdown', label: 'Tone', options: ['Light & Fun', 'Dark & Serious', 'Suspenseful', 'Heartwarming'], default: 'Light & Fun' },
466
+ ],
467
+ },
468
+ category: 'Creative',
469
+ tags: ['story', 'fiction', 'creative-writing'],
470
+ totalRuns: 9876,
471
+ starsCount: 765,
472
+ },
473
+ {
474
+ slug: 'song-lyrics-writer',
475
+ title: 'Song Lyrics Writer',
476
+ description: 'Create original song lyrics in any genre with custom themes.',
477
+ template: `Write song lyrics about {{theme}}.
478
+
479
+ Genre: {{genre}}
480
+ Mood: {{mood}}
481
+ Structure: {{structure}}
482
+
483
+ Include:
484
+ - Verse 1
485
+ - Chorus
486
+ - Verse 2
487
+ - Chorus
488
+ - Bridge
489
+ - Final Chorus
490
+
491
+ Make it catchy, emotional, and singable!`,
492
+ schema: {
493
+ variables: [
494
+ { name: 'theme', type: 'text', label: 'Song Theme', placeholder: 'e.g., Overcoming heartbreak', required: true },
495
+ { name: 'genre', type: 'dropdown', label: 'Genre', options: ['Pop', 'Rock', 'Hip-Hop', 'Country', 'R&B', 'Indie', 'EDM'], default: 'Pop' },
496
+ { name: 'mood', type: 'dropdown', label: 'Mood', options: ['Happy', 'Sad', 'Empowering', 'Romantic', 'Angry', 'Nostalgic'], default: 'Empowering' },
497
+ { name: 'structure', type: 'dropdown', label: 'Structure', options: ['Standard', 'Extended', 'Minimal'], default: 'Standard' },
498
+ ],
499
+ },
500
+ category: 'Creative',
501
+ tags: ['music', 'lyrics', 'songwriting'],
502
+ totalRuns: 5678,
503
+ starsCount: 432,
504
+ },
505
+
506
+ // ============ RESEARCH ============
507
+ {
508
+ slug: 'research-summarizer',
509
+ title: 'Research Paper Summarizer',
510
+ description: 'Summarize academic papers and research into digestible insights.',
511
+ template: `Summarize this research:
512
+
513
+ {{content}}
514
+
515
+ Provide:
516
+
517
+ ## 📋 TL;DR
518
+ One paragraph summary
519
+
520
+ ## 🎯 Key Findings
521
+ Bullet points of main discoveries
522
+
523
+ ## 🔬 Methodology
524
+ How the research was conducted
525
+
526
+ ## 📊 Data & Results
527
+ Key statistics and outcomes
528
+
529
+ ## 💡 Implications
530
+ What this means for {{field}}
531
+
532
+ ## ❓ Limitations
533
+ Study limitations and gaps
534
+
535
+ ## 📚 Citation
536
+ Proper academic citation format`,
537
+ schema: {
538
+ variables: [
539
+ { name: 'content', type: 'textarea', label: 'Paste Research Content', placeholder: 'Paste the abstract, paper, or research text...', required: true },
540
+ { name: 'field', type: 'text', label: 'Research Field', placeholder: 'e.g., Machine Learning, Medicine', default: 'the field' },
541
+ ],
542
+ },
543
+ category: 'Research',
544
+ tags: ['research', 'academic', 'summary', 'papers'],
545
+ totalRuns: 4567,
546
+ starsCount: 345,
547
+ },
548
+ {
549
+ slug: 'fact-checker',
550
+ title: 'AI Fact Checker',
551
+ description: 'Verify claims and get evidence-based analysis.',
552
+ template: `Analyze this claim for accuracy:
553
+
554
+ Claim: "{{claim}}"
555
+
556
+ Provide:
557
+
558
+ ## 📊 Verdict
559
+ [TRUE / MOSTLY TRUE / MIXED / MOSTLY FALSE / FALSE]
560
+
561
+ ## 🔍 Analysis
562
+ Detailed breakdown of the claim
563
+
564
+ ## 📚 Evidence
565
+ Supporting or refuting evidence
566
+
567
+ ## 🌐 Sources
568
+ Where to verify (types of sources to check)
569
+
570
+ ## ⚠️ Context
571
+ Important context that affects accuracy
572
+
573
+ ## 🎯 Bottom Line
574
+ One sentence summary`,
575
+ schema: {
576
+ variables: [
577
+ { name: 'claim', type: 'textarea', label: 'Claim to Check', placeholder: 'e.g., Drinking 8 glasses of water a day is necessary for health', required: true },
578
+ ],
579
+ },
580
+ category: 'Research',
581
+ tags: ['fact-check', 'verification', 'research'],
582
+ totalRuns: 3456,
583
+ starsCount: 234,
584
+ },
585
+ ]
586
+
587
+ async function seed() {
588
+ console.log('🌱 Seeding database with awesome prompts...\n')
589
+
590
+ let created = 0
591
+ let skipped = 0
592
+
593
+ for (const prompt of seedPrompts) {
594
+ const existing = await prisma.prompt.findUnique({
595
+ where: { slug: prompt.slug },
596
+ })
597
+
598
+ if (existing) {
599
+ console.log(` ⏭️ Skipping "${prompt.title}" (already exists)`)
600
+ skipped++
601
+ continue
602
+ }
603
+
604
+ await prisma.prompt.create({
605
+ data: {
606
+ slug: prompt.slug,
607
+ title: prompt.title,
608
+ description: prompt.description,
609
+ template: prompt.template,
610
+ schema: prompt.schema,
611
+ category: prompt.category,
612
+ tags: prompt.tags,
613
+ totalRuns: prompt.totalRuns,
614
+ starsCount: prompt.starsCount,
615
+ remixesCount: 0,
616
+ modelDefault: 'gpt-4o-mini',
617
+ modelAllowed: ['gpt-4o-mini', 'gpt-4o', 'claude-3-5-sonnet'],
618
+ visibility: 'public',
619
+ },
620
+ })
621
+ console.log(` ✅ Created "${prompt.title}" [${prompt.category}]`)
622
+ created++
623
+ }
624
+
625
+ console.log(`\n✨ Done! Created ${created} prompts, skipped ${skipped}`)
626
+ console.log('\n📊 Prompts by category:')
627
+ console.log(' • Content: 3')
628
+ console.log(' • Development: 3')
629
+ console.log(' • Marketing: 2')
630
+ console.log(' • Business: 2')
631
+ console.log(' • Education: 2')
632
+ console.log(' • Creative: 2')
633
+ console.log(' • Research: 2')
634
+ }
635
+
636
+ seed()
637
+ .catch((e) => {
638
+ console.error('❌ Seeding failed:', e)
639
+ process.exit(1)
640
+ })
641
+ .finally(async () => {
642
+ await prisma.$disconnect()
643
+ await pool.end()
644
+ })
prisma/tool-models.txt ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // AI Tools System
3
+ model Tool {
4
+ id String @id @default(cuid())
5
+ slug String @unique
6
+ name String
7
+ description String
8
+ category String // "prompting", "marketing", "branding", etc.
9
+ icon String? // Emoji or icon name
10
+ isActive Boolean @default(true)
11
+ isPremium Boolean @default(false)
12
+
13
+ // Tool configuration
14
+ inputSchema Json // { fields: [{name, type, label, placeholder}] }
15
+ systemPrompt String @db.Text // The AI prompt template
16
+ model String @default("gpt-4o-mini") // AI model to use
17
+
18
+ // Stats
19
+ usageCount Int @default(0)
20
+
21
+ executions ToolExecution[]
22
+
23
+ createdAt DateTime @default(now())
24
+ updatedAt DateTime @updatedAt
25
+
26
+ @@index([category])
27
+ @@index([slug])
28
+ }
29
+
30
+ model ToolExecution {
31
+ id String @id @default(cuid())
32
+ toolId String
33
+ tool Tool @relation(fields: [toolId], references: [id], onDelete: Cascade)
34
+ userId String?
35
+
36
+ inputs Json // User's inputs
37
+ output String @db.Text // AI-generated output
38
+
39
+ createdAt DateTime @default(now())
40
+
41
+ @@index([toolId])
42
+ @@index([userId])
43
+ }
prisma/update-models.ts ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Pool } from 'pg'
2
+ import { PrismaPg } from '@prisma/adapter-pg'
3
+ import { PrismaClient } from '@prisma/client'
4
+ import 'dotenv/config'
5
+
6
+ const connectionString = process.env.DATABASE_URL
7
+ if (!connectionString) throw new Error('DATABASE_URL not set')
8
+
9
+ const pool = new Pool({ connectionString })
10
+ const adapter = new PrismaPg(pool)
11
+ const prisma = new PrismaClient({ adapter })
12
+
13
+ async function updateToGemini() {
14
+ console.log('🔄 Updating all prompts to use Gemini 2.5 Flash as default...\n')
15
+
16
+ const result = await prisma.prompt.updateMany({
17
+ data: {
18
+ modelDefault: 'gemini-2.5-flash',
19
+ modelAllowed: ['gemini-2.5-flash', 'gemini-2.0-flash', 'gpt-4o-mini', 'gpt-4o', 'claude-3-5-sonnet'],
20
+ },
21
+ })
22
+
23
+ console.log(`✅ Updated ${result.count} prompts to use Gemini 2.5 Flash!`)
24
+ }
25
+
26
+ updateToGemini()
27
+ .catch((e) => {
28
+ console.error('❌ Update failed:', e)
29
+ process.exit(1)
30
+ })
31
+ .finally(async () => {
32
+ await prisma.$disconnect()
33
+ await pool.end()
34
+ })
public/favicon.ico ADDED

Git LFS Details

  • SHA256: e64d99b64c8f1d377f25e88d46da1c3ec52df30404aab339e92659b3f6cb7846
  • Pointer size: 129 Bytes
  • Size of remote file: 1.41 kB
public/file.svg ADDED
public/globe.svg ADDED