vibefast-cli 1.1.3 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +63 -169
- package/dist/__tests__/recipes.test.js +25 -3
- package/dist/__tests__/recipes.test.js.map +1 -1
- package/dist/commands/add.d.ts +1 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +547 -543
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/checklist.d.ts +1 -1
- package/dist/commands/checklist.d.ts.map +1 -1
- package/dist/commands/checklist.js +40 -39
- package/dist/commands/checklist.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +22 -22
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/env.d.ts +1 -1
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +58 -53
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/health.d.ts +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +101 -93
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +416 -296
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/remove.d.ts +1 -1
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +77 -64
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +15 -14
- package/dist/commands/status.js.map +1 -1
- package/dist/core/__tests__/detect.test.js +68 -34
- package/dist/core/__tests__/detect.test.js.map +1 -1
- package/dist/core/ast.d.ts +14 -0
- package/dist/core/ast.d.ts.map +1 -0
- package/dist/core/ast.js +239 -0
- package/dist/core/ast.js.map +1 -0
- package/dist/core/codemod.d.ts.map +1 -1
- package/dist/core/codemod.js +62 -44
- package/dist/core/codemod.js.map +1 -1
- package/dist/core/config.d.ts +10 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +51 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/detect.d.ts +8 -2
- package/dist/core/detect.d.ts.map +1 -1
- package/dist/core/detect.js +52 -21
- package/dist/core/detect.js.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -8
- package/dist/core/errors.js.map +1 -1
- package/dist/core/exec.d.ts +16 -0
- package/dist/core/exec.d.ts.map +1 -0
- package/dist/core/exec.js +48 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/manualSteps.d.ts +7 -0
- package/dist/core/manualSteps.d.ts.map +1 -0
- package/dist/core/manualSteps.js +59 -0
- package/dist/core/manualSteps.js.map +1 -0
- package/dist/core/paths.d.ts +3 -1
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +14 -10
- package/dist/core/paths.js.map +1 -1
- package/dist/core/spinner.d.ts +1 -1
- package/dist/core/spinner.d.ts.map +1 -1
- package/dist/core/spinner.js +38 -8
- package/dist/core/spinner.js.map +1 -1
- package/dist/core/vosk.d.ts.map +1 -1
- package/dist/core/vosk.js +50 -39
- package/dist/core/vosk.js.map +1 -1
- package/docs/manual-testing.md +91 -0
- package/package.json +6 -3
- package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
- package/recipes/audio-recorder/recipe.json +3 -3
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/index.ts +4 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
- package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
- package/recipes/audio-recorder-supabase/packages/backend/src/services/recordings.ts +369 -0
- package/recipes/audio-recorder-supabase/packages/backend/supabase/migrations/recordings.sql +70 -0
- package/recipes/audio-recorder-supabase/recipe.json +35 -0
- package/recipes/audio-recorder-supabase@latest.zip +0 -0
- package/recipes/audio-recorder@latest.zip +0 -0
- package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +3 -3
- package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +2 -2
- package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +5 -5
- package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +3 -3
- package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +20 -4
- package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +7 -6
- package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +6 -4
- package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +1 -1
- package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +5 -4
- package/recipes/charts/recipe.json +4 -13
- package/recipes/charts@latest.zip +0 -0
- package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +86 -86
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +86 -53
- package/recipes/chatbot/recipe.json +26 -92
- package/recipes/chatbot-supabase/apps/native/src/api-client/supabase/chatbot.ts +515 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/app/index.tsx +257 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-input-bar.tsx +485 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +223 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/image-preview-list.tsx +116 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-list.tsx +170 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/models.ts +20 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +142 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-config.ts +458 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +429 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-conversation.ts +90 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +213 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/index.ts +86 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/models.ts +162 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/providers.ts +62 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/types.ts +40 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/file-uploader.ts +287 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/message-handler-service.ts +189 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/types/index.ts +70 -0
- package/recipes/chatbot-supabase/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
- package/recipes/chatbot-supabase/packages/backend/src/services/conversations.ts +243 -0
- package/recipes/chatbot-supabase/packages/backend/src/services/messages.ts +327 -0
- package/recipes/chatbot-supabase/packages/backend/supabase/functions/chat-stream/index.ts +347 -0
- package/recipes/chatbot-supabase/packages/backend/supabase/migrations/chatbot.sql +104 -0
- package/recipes/chatbot-supabase/recipe.json +79 -0
- package/recipes/chatbot-supabase@latest.zip +0 -0
- package/recipes/chatbot.zip +0 -0
- package/recipes/chatbot@latest.zip +0 -0
- package/recipes/image-analysis/packages/backend/convex/imageAnalysis/index.ts +2 -2
- package/recipes/image-analysis/packages/backend/convex/{imageAnalysisFunctions.ts → imageAnalysis.ts} +5 -5
- package/recipes/image-analysis/recipe.json +15 -55
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/analysis-options-screen.tsx +304 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/camera.tsx +221 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx +333 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading-screen.tsx +214 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading.tsx +191 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/results.tsx +137 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/trait-details.tsx +172 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-analysis-data.ts +160 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-results-screen.ts +151 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-badge.tsx +77 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-card.tsx +75 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-unlocked-modal.tsx +162 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievements-section.tsx +44 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/advice-list.tsx +42 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/circular-progress.tsx +233 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/content-card.tsx +38 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/error-state.tsx +42 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/index.ts +9 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/loading-state.tsx +26 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/profile-image.tsx +60 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/results-header.tsx +62 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/score-display.tsx +54 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/share-options-modal.tsx +110 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/traits-grid.tsx +74 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/analysis-config.ts +80 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/master-analysis-config.ts +157 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/index.ts +1 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-analysis.ts +38 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts +208 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/analysis-service.ts +262 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/share-service.ts +176 -0
- package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/trait-details-service.ts +289 -0
- package/recipes/image-analysis-supabase/packages/backend/src/services/image-analyses.ts +132 -0
- package/recipes/image-analysis-supabase/packages/backend/supabase/functions/analyze-image/index.ts +312 -0
- package/recipes/image-analysis-supabase/packages/backend/supabase/migrations/image_analysis.sql +42 -0
- package/recipes/image-analysis-supabase/recipe.json +57 -0
- package/recipes/image-analysis-supabase@latest.zip +0 -0
- package/recipes/image-analysis@latest.zip +0 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +16 -2
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +11 -5
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +11 -5
- package/recipes/image-generator/packages/backend/convex/imageGeneration/index.ts +2 -2
- package/recipes/image-generator/recipe.json +16 -39
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/_layout.tsx +26 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/index.tsx +251 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/gallery-image.tsx +25 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-model-selector.tsx +216 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator.ts +103 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/models/models.ts +66 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-gallery-service.ts +96 -0
- package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-save-service.ts +120 -0
- package/recipes/image-generator-supabase/packages/backend/supabase/functions/generate-image/index.ts +291 -0
- package/recipes/image-generator-supabase/packages/backend/supabase/migrations/image_generator.sql +71 -0
- package/recipes/image-generator-supabase/recipe.json +59 -0
- package/recipes/image-generator-supabase@latest.zip +0 -0
- package/recipes/image-generator@latest.zip +0 -0
- package/recipes/ios-widget/recipe.json +15 -24
- package/recipes/ios-widget@latest.zip +0 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/analytics/index.ts +9 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding-with-analytics.tsx +141 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding.tsx +173 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/config/onboarding-flow-config.ts +189 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/app/index.tsx +42 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/data.ts +32 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/app/index.tsx +43 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/interactive-onboarding.tsx +222 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/ai-tone-step.tsx +133 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/currency-step.tsx +165 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-ai-step.tsx +199 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-chatbot-step.tsx +154 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-manual-step.tsx +156 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-scan-step.tsx +158 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/main-reason-step.tsx +139 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/notification-step.tsx +129 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/overspend-step.tsx +138 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/personalizing-step.tsx +190 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/rating-step.tsx +98 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/reminder-step.tsx +181 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/safety-step.tsx +110 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/struggle-step.tsx +139 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/welcome-step.tsx +217 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/ui/onboarding-header.tsx +58 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/constants.ts +179 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/hooks/use-onboarding-analytics.ts +323 -0
- package/recipes/onboarding/apps/native/src/features/onboarding/services/onboarding-analytics.ts +432 -0
- package/recipes/onboarding/recipe.json +15 -0
- package/recipes/onboarding@latest.zip +0 -0
- package/recipes/payments/recipe.json +28 -61
- package/recipes/payments-supabase/apps/native/src/features/payments/README.md +200 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/index.ts +8 -0
- package/recipes/payments-supabase/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
- package/recipes/payments-supabase/packages/backend/src/services/payments.ts +201 -0
- package/recipes/payments-supabase/packages/backend/supabase/migrations/payments.sql +35 -0
- package/recipes/payments-supabase/recipe.json +51 -0
- package/recipes/payments-supabase@latest.zip +0 -0
- package/recipes/payments@latest.zip +0 -0
- package/recipes/quiz/apps/native/src/features/quiz/index.tsx +1 -2
- package/recipes/quiz/recipe.json +6 -9
- package/recipes/quiz@latest.zip +0 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +1 -2
- package/recipes/tracker-app/recipe.json +7 -10
- package/recipes/tracker-app@latest.zip +0 -0
- package/recipes/voice-bot/recipe.json +8 -68
- package/recipes/voice-bot.zip +0 -0
- package/recipes/voice-bot@latest.zip +0 -0
- package/recipes/wake-word/recipe.json +10 -9
- package/recipes/wake-word.zip +0 -0
- package/recipes/wake-word@latest.zip +0 -0
- package/recipes/charts/apps/native/src/app/(root)/(protected)/charts/index.tsx +0 -3
- package/recipes/chatbot/packages/backend/convex/lib/rateLimit.ts +0 -100
- package/recipes/chatbot/packages/backend/convex/lib/telemetry.ts +0 -29
- package/recipes/chatbot/packages/backend/convex/ragKnowledge.ts +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/front.jpg +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/side.jpg +0 -0
- package/recipes/image-analysis/apps/native/assets/features/image-analyzer/threeQuarter.jpg +0 -0
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/_layout.tsx +0 -5
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/analysis-options.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/camera.tsx +0 -2
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/index.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/loading.tsx +0 -50
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/results.tsx +0 -2
- package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/trait-details.tsx +0 -3
- package/recipes/image-analysis/packages/backend/convex/lib/ai/imageAnalysisAdapter.ts +0 -200
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +0 -74
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +0 -25
- package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +0 -23
- package/recipes/quiz/apps/native/src/app/(root)/(protected)/quiz/index.tsx +0 -47
- package/recipes/tracker-app/apps/native/src/app/(root)/(protected)/tracker-app/index.tsx +0 -1
- package/recipes/voice-bot/apps/native/src/app/(root)/(protected)/voice-bot/index.tsx +0 -27
- package/recipes/voice-bot/packages/backend/convex/router.ts +0 -81
- /package/recipes/{chatbot/apps/native/src/app/(root)/(protected) → chatbot-supabase/apps/native/src/app}/chatbot/index.tsx +0 -0
- /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/gallery.tsx +0 -0
- /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/index.tsx +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { defaultCacheService } from '@/core/cache';
|
|
2
|
+
|
|
3
|
+
export type SavedImageMetadata = {
|
|
4
|
+
id: string;
|
|
5
|
+
prompt: string;
|
|
6
|
+
provider: string;
|
|
7
|
+
model: string;
|
|
8
|
+
savedAt: number;
|
|
9
|
+
localUri: string;
|
|
10
|
+
remoteImageId?: string;
|
|
11
|
+
storageId?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const GALLERY_CACHE_KEY = 'image_gallery_v1';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Service for managing the local image gallery with MMKV persistence
|
|
18
|
+
*/
|
|
19
|
+
export class ImageGalleryService {
|
|
20
|
+
/**
|
|
21
|
+
* Get all saved images from the gallery
|
|
22
|
+
*/
|
|
23
|
+
static async getAllImages(): Promise<SavedImageMetadata[]> {
|
|
24
|
+
try {
|
|
25
|
+
const images =
|
|
26
|
+
await defaultCacheService.getItem<SavedImageMetadata[]>(
|
|
27
|
+
GALLERY_CACHE_KEY,
|
|
28
|
+
);
|
|
29
|
+
return images || [];
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Failed to load gallery images:', error);
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Add a new image to the gallery
|
|
38
|
+
*/
|
|
39
|
+
static async addImage(
|
|
40
|
+
metadata: Omit<SavedImageMetadata, 'id' | 'savedAt'>,
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
try {
|
|
43
|
+
const existingImages = await ImageGalleryService.getAllImages();
|
|
44
|
+
const newImage: SavedImageMetadata = {
|
|
45
|
+
...metadata,
|
|
46
|
+
id: `img_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
47
|
+
savedAt: Date.now(),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const updatedImages = [newImage, ...existingImages];
|
|
51
|
+
await defaultCacheService.setItem(GALLERY_CACHE_KEY, updatedImages);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Failed to add image to gallery:', error);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Remove an image from the gallery
|
|
60
|
+
*/
|
|
61
|
+
static async removeImage(imageId: string): Promise<void> {
|
|
62
|
+
try {
|
|
63
|
+
const existingImages = await ImageGalleryService.getAllImages();
|
|
64
|
+
const updatedImages = existingImages.filter((img) => img.id !== imageId);
|
|
65
|
+
await defaultCacheService.setItem(GALLERY_CACHE_KEY, updatedImages);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('Failed to remove image from gallery:', error);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Clear all images from the gallery
|
|
74
|
+
*/
|
|
75
|
+
static async clearGallery(): Promise<void> {
|
|
76
|
+
try {
|
|
77
|
+
await defaultCacheService.setItem(GALLERY_CACHE_KEY, []);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Failed to clear gallery:', error);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get the total number of saved images
|
|
86
|
+
*/
|
|
87
|
+
static async getImageCount(): Promise<number> {
|
|
88
|
+
try {
|
|
89
|
+
const images = await ImageGalleryService.getAllImages();
|
|
90
|
+
return images.length;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error('Failed to get image count:', error);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { File, Paths } from 'expo-file-system';
|
|
2
|
+
import * as MediaLibrary from 'expo-media-library';
|
|
3
|
+
import { Alert, Platform } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { ImageGalleryService } from './image-gallery-service';
|
|
6
|
+
|
|
7
|
+
export type ImageSaveMetadata = {
|
|
8
|
+
prompt: string;
|
|
9
|
+
provider: string;
|
|
10
|
+
model: string;
|
|
11
|
+
remoteImageId?: string;
|
|
12
|
+
storageId?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Service for handling image saving operations to the device gallery and local storage
|
|
17
|
+
*/
|
|
18
|
+
export class ImageSaveService {
|
|
19
|
+
/**
|
|
20
|
+
* Save a base64 image data URI to the device gallery and local gallery
|
|
21
|
+
* @param imageDataUri - The data URI containing the image (e.g., 'data:image/png;base64,iVBORw0KGgo...')
|
|
22
|
+
* @param metadata - Optional metadata about the image generation
|
|
23
|
+
* @returns Promise that resolves when save is complete
|
|
24
|
+
*/
|
|
25
|
+
static async saveImageToGallery(
|
|
26
|
+
imageDataUri: string,
|
|
27
|
+
metadata?: ImageSaveMetadata,
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
try {
|
|
30
|
+
// Check if running on web - FileSystem is not available
|
|
31
|
+
if (Platform.OS === 'web') {
|
|
32
|
+
// On web, just save to local gallery
|
|
33
|
+
if (metadata) {
|
|
34
|
+
await ImageGalleryService.addImage({
|
|
35
|
+
prompt: metadata.prompt,
|
|
36
|
+
provider: metadata.provider,
|
|
37
|
+
model: metadata.model,
|
|
38
|
+
localUri: imageDataUri,
|
|
39
|
+
remoteImageId: metadata.remoteImageId,
|
|
40
|
+
storageId: metadata.storageId,
|
|
41
|
+
});
|
|
42
|
+
Alert.alert('Success!', 'Image saved to your gallery.');
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Request permissions (native only)
|
|
48
|
+
const { status } = await MediaLibrary.requestPermissionsAsync();
|
|
49
|
+
if (status !== 'granted') {
|
|
50
|
+
Alert.alert(
|
|
51
|
+
'Permission Required',
|
|
52
|
+
'We need permission to save photos to your gallery.',
|
|
53
|
+
);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Extract base64 data from data URI (supports multiple formats)
|
|
58
|
+
const match = imageDataUri.match(/^data:image\/(\w+);base64,(.+)$/);
|
|
59
|
+
if (!match) {
|
|
60
|
+
throw new Error('Invalid image data format');
|
|
61
|
+
}
|
|
62
|
+
const [, imageFormat, base64Code] = match;
|
|
63
|
+
const fileExtension = imageFormat === 'jpeg' ? 'jpg' : imageFormat;
|
|
64
|
+
|
|
65
|
+
// Create temporary file using new File API
|
|
66
|
+
const filename = `generated-image-${Date.now()}.${fileExtension}`;
|
|
67
|
+
const file = new File(Paths.cache, filename);
|
|
68
|
+
|
|
69
|
+
// Create and write the file
|
|
70
|
+
file.create();
|
|
71
|
+
await file.write(base64Code, { encoding: 'base64' });
|
|
72
|
+
|
|
73
|
+
// Save to device gallery
|
|
74
|
+
const asset = await MediaLibrary.createAssetAsync(file.uri);
|
|
75
|
+
await MediaLibrary.createAlbumAsync('MyAppImages', asset, false);
|
|
76
|
+
|
|
77
|
+
// Save to local gallery if metadata is provided
|
|
78
|
+
if (metadata) {
|
|
79
|
+
await ImageGalleryService.addImage({
|
|
80
|
+
prompt: metadata.prompt,
|
|
81
|
+
provider: metadata.provider,
|
|
82
|
+
model: metadata.model,
|
|
83
|
+
localUri: imageDataUri,
|
|
84
|
+
remoteImageId: metadata.remoteImageId,
|
|
85
|
+
storageId: metadata.storageId,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
Alert.alert('Success!', 'Image saved to your gallery.');
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('Failed to save image:', error);
|
|
92
|
+
Alert.alert('Error', 'Could not save the image.');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Save image only to local gallery (without device gallery)
|
|
98
|
+
* @param imageDataUri - The data URI containing the image
|
|
99
|
+
* @param metadata - Metadata about the image generation
|
|
100
|
+
* @returns Promise that resolves when save is complete
|
|
101
|
+
*/
|
|
102
|
+
static async saveToLocalGallery(
|
|
103
|
+
imageDataUri: string,
|
|
104
|
+
metadata: ImageSaveMetadata,
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
try {
|
|
107
|
+
await ImageGalleryService.addImage({
|
|
108
|
+
prompt: metadata.prompt,
|
|
109
|
+
provider: metadata.provider,
|
|
110
|
+
model: metadata.model,
|
|
111
|
+
localUri: imageDataUri,
|
|
112
|
+
remoteImageId: metadata.remoteImageId,
|
|
113
|
+
storageId: metadata.storageId,
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('Failed to save to local gallery:', error);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
package/recipes/image-generator-supabase/packages/backend/supabase/functions/generate-image/index.ts
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate Image Edge Function
|
|
3
|
+
*
|
|
4
|
+
* Handles AI image generation with JWT authentication.
|
|
5
|
+
* Supports OpenAI (DALL-E 3) and Google Gemini providers.
|
|
6
|
+
*
|
|
7
|
+
* Requirements: 6.1, 6.2, 6.3, 6.4, 6.5
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import 'jsr:@supabase/functions-js/edge-runtime.d.ts';
|
|
11
|
+
import { createClient } from 'npm:@supabase/supabase-js@2';
|
|
12
|
+
|
|
13
|
+
// CORS headers for browser requests
|
|
14
|
+
const corsHeaders = {
|
|
15
|
+
'Access-Control-Allow-Origin': '*',
|
|
16
|
+
'Access-Control-Allow-Headers':
|
|
17
|
+
'authorization, x-client-info, apikey, content-type',
|
|
18
|
+
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
interface GenerateImageRequest {
|
|
22
|
+
prompt: string;
|
|
23
|
+
provider?: 'openai' | 'gemini';
|
|
24
|
+
model?: string;
|
|
25
|
+
size?: string;
|
|
26
|
+
quality?: 'standard' | 'hd';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface GenerateImageResponse {
|
|
30
|
+
success: boolean;
|
|
31
|
+
imageUrl?: string;
|
|
32
|
+
imageDataUri?: string;
|
|
33
|
+
storageId?: string;
|
|
34
|
+
recordId?: string;
|
|
35
|
+
provider: string;
|
|
36
|
+
model: string;
|
|
37
|
+
error?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Deno.serve(async (req: Request) => {
|
|
41
|
+
// Handle CORS preflight
|
|
42
|
+
if (req.method === 'OPTIONS') {
|
|
43
|
+
return new Response('ok', { headers: corsHeaders });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (req.method !== 'POST') {
|
|
47
|
+
return new Response(JSON.stringify({ error: 'Method not allowed' }), {
|
|
48
|
+
status: 405,
|
|
49
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// 6.1: Authenticate the request
|
|
55
|
+
const authHeader = req.headers.get('Authorization');
|
|
56
|
+
if (!authHeader) {
|
|
57
|
+
return new Response(JSON.stringify({ error: 'Missing authorization header' }), {
|
|
58
|
+
status: 401,
|
|
59
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const token = authHeader.replace('Bearer ', '');
|
|
64
|
+
|
|
65
|
+
// Create Supabase client with user's JWT
|
|
66
|
+
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
|
67
|
+
const supabasePublishableKey = Deno.env.get('SUPABASE_ANON_KEY')!;
|
|
68
|
+
const supabaseSecretKey = Deno.env.get('SUPABASE_SECRET_KEY')!;
|
|
69
|
+
|
|
70
|
+
const supabase = createClient(supabaseUrl, supabasePublishableKey, {
|
|
71
|
+
global: { headers: { Authorization: `Bearer ${token}` } },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Admin client for storage operations
|
|
75
|
+
const supabaseAdmin = createClient(supabaseUrl, supabaseSecretKey);
|
|
76
|
+
|
|
77
|
+
// Verify the user
|
|
78
|
+
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
79
|
+
if (authError || !user) {
|
|
80
|
+
return new Response(JSON.stringify({ error: 'Invalid or expired token' }), {
|
|
81
|
+
status: 401,
|
|
82
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Parse request body
|
|
87
|
+
const body: GenerateImageRequest = await req.json();
|
|
88
|
+
const { prompt, provider = 'openai', model, size = '1024x1024', quality = 'standard' } = body;
|
|
89
|
+
|
|
90
|
+
if (!prompt || prompt.trim().length === 0) {
|
|
91
|
+
return new Response(JSON.stringify({ error: 'Prompt is required' }), {
|
|
92
|
+
status: 400,
|
|
93
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let imageData: { url?: string; b64_json?: string } | null = null;
|
|
98
|
+
let usedProvider: string;
|
|
99
|
+
let usedModel: string;
|
|
100
|
+
|
|
101
|
+
if (provider === 'gemini') {
|
|
102
|
+
// 6.3: Generate with Gemini provider
|
|
103
|
+
const geminiApiKey = Deno.env.get('GEMINI_API_KEY');
|
|
104
|
+
if (!geminiApiKey) {
|
|
105
|
+
return new Response(JSON.stringify({ error: 'Gemini API not configured' }), {
|
|
106
|
+
status: 500,
|
|
107
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
usedModel = model || 'gemini-2.0-flash-preview-image-generation';
|
|
112
|
+
usedProvider = 'gemini';
|
|
113
|
+
|
|
114
|
+
// Use Gemini's image generation API
|
|
115
|
+
const geminiResponse = await fetch(
|
|
116
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${usedModel}:generateContent?key=${geminiApiKey}`,
|
|
117
|
+
{
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: { 'Content-Type': 'application/json' },
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
contents: [
|
|
122
|
+
{
|
|
123
|
+
parts: [{ text: `Generate an image: ${prompt}` }],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
generationConfig: {
|
|
127
|
+
responseModalities: ['TEXT', 'IMAGE'],
|
|
128
|
+
},
|
|
129
|
+
}),
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (!geminiResponse.ok) {
|
|
134
|
+
const errorText = await geminiResponse.text();
|
|
135
|
+
console.error('Gemini API error:', errorText);
|
|
136
|
+
return new Response(
|
|
137
|
+
JSON.stringify({ error: 'Failed to generate image with Gemini' }),
|
|
138
|
+
{
|
|
139
|
+
status: 500,
|
|
140
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const geminiData = await geminiResponse.json();
|
|
146
|
+
|
|
147
|
+
// Extract image from Gemini response
|
|
148
|
+
const parts = geminiData.candidates?.[0]?.content?.parts || [];
|
|
149
|
+
const imagePart = parts.find((p: any) => p.inlineData?.mimeType?.startsWith('image/'));
|
|
150
|
+
|
|
151
|
+
if (imagePart?.inlineData) {
|
|
152
|
+
imageData = { b64_json: imagePart.inlineData.data };
|
|
153
|
+
} else {
|
|
154
|
+
return new Response(
|
|
155
|
+
JSON.stringify({ error: 'No image generated by Gemini' }),
|
|
156
|
+
{
|
|
157
|
+
status: 500,
|
|
158
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
// 6.2: Generate with OpenAI provider (DALL-E 3)
|
|
164
|
+
const openaiApiKey = Deno.env.get('OPENAI_API_KEY');
|
|
165
|
+
if (!openaiApiKey) {
|
|
166
|
+
return new Response(JSON.stringify({ error: 'OpenAI API not configured' }), {
|
|
167
|
+
status: 500,
|
|
168
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
usedModel = model || 'dall-e-3';
|
|
173
|
+
usedProvider = 'openai';
|
|
174
|
+
|
|
175
|
+
const openaiResponse = await fetch('https://api.openai.com/v1/images/generations', {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: {
|
|
178
|
+
'Content-Type': 'application/json',
|
|
179
|
+
Authorization: `Bearer ${openaiApiKey}`,
|
|
180
|
+
},
|
|
181
|
+
body: JSON.stringify({
|
|
182
|
+
model: usedModel,
|
|
183
|
+
prompt,
|
|
184
|
+
n: 1,
|
|
185
|
+
size,
|
|
186
|
+
quality,
|
|
187
|
+
response_format: 'b64_json',
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (!openaiResponse.ok) {
|
|
192
|
+
const errorData = await openaiResponse.json();
|
|
193
|
+
console.error('OpenAI API error:', errorData);
|
|
194
|
+
return new Response(
|
|
195
|
+
JSON.stringify({ error: errorData.error?.message || 'Failed to generate image' }),
|
|
196
|
+
{
|
|
197
|
+
status: 500,
|
|
198
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const openaiData = await openaiResponse.json();
|
|
204
|
+
imageData = openaiData.data?.[0];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!imageData) {
|
|
208
|
+
return new Response(JSON.stringify({ error: 'No image data received' }), {
|
|
209
|
+
status: 500,
|
|
210
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 6.4: Store image in Supabase Storage and create generated_images record
|
|
215
|
+
let storageId: string | null = null;
|
|
216
|
+
let imageUrl: string | null = null;
|
|
217
|
+
|
|
218
|
+
if (imageData.b64_json) {
|
|
219
|
+
// Decode base64 and upload to storage
|
|
220
|
+
const imageBuffer = Uint8Array.from(atob(imageData.b64_json), (c) => c.charCodeAt(0));
|
|
221
|
+
const filename = `${user.id}/${crypto.randomUUID()}.png`;
|
|
222
|
+
|
|
223
|
+
const { data: uploadData, error: uploadError } = await supabaseAdmin.storage
|
|
224
|
+
.from('generated-images')
|
|
225
|
+
.upload(filename, imageBuffer, {
|
|
226
|
+
contentType: 'image/png',
|
|
227
|
+
upsert: false,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (uploadError) {
|
|
231
|
+
console.error('Storage upload error:', uploadError);
|
|
232
|
+
} else {
|
|
233
|
+
storageId = uploadData.path;
|
|
234
|
+
|
|
235
|
+
// Get signed URL for the image
|
|
236
|
+
const { data: signedUrlData } = await supabaseAdmin.storage
|
|
237
|
+
.from('generated-images')
|
|
238
|
+
.createSignedUrl(filename, 3600); // 1 hour expiry
|
|
239
|
+
|
|
240
|
+
imageUrl = signedUrlData?.signedUrl || null;
|
|
241
|
+
}
|
|
242
|
+
} else if (imageData.url) {
|
|
243
|
+
imageUrl = imageData.url;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Create generated_images record
|
|
247
|
+
const { data: record, error: recordError } = await supabase
|
|
248
|
+
.from('generated_images')
|
|
249
|
+
.insert({
|
|
250
|
+
user_id: user.id,
|
|
251
|
+
prompt,
|
|
252
|
+
image_url: imageUrl,
|
|
253
|
+
storage_id: storageId,
|
|
254
|
+
mime_type: 'image/png',
|
|
255
|
+
provider: usedProvider,
|
|
256
|
+
model: usedModel,
|
|
257
|
+
})
|
|
258
|
+
.select('id')
|
|
259
|
+
.single();
|
|
260
|
+
|
|
261
|
+
if (recordError) {
|
|
262
|
+
console.error('Error creating generated_images record:', recordError);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 6.5: Return the image data URI and metadata
|
|
266
|
+
const response: GenerateImageResponse = {
|
|
267
|
+
success: true,
|
|
268
|
+
imageUrl: imageUrl || undefined,
|
|
269
|
+
imageDataUri: imageData.b64_json ? `data:image/png;base64,${imageData.b64_json}` : undefined,
|
|
270
|
+
storageId: storageId || undefined,
|
|
271
|
+
recordId: record?.id,
|
|
272
|
+
provider: usedProvider,
|
|
273
|
+
model: usedModel,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
return new Response(JSON.stringify(response), {
|
|
277
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
278
|
+
});
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error('Generate image error:', error);
|
|
281
|
+
return new Response(
|
|
282
|
+
JSON.stringify({
|
|
283
|
+
error: error instanceof Error ? error.message : 'Internal server error',
|
|
284
|
+
}),
|
|
285
|
+
{
|
|
286
|
+
status: 500,
|
|
287
|
+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
});
|
package/recipes/image-generator-supabase/packages/backend/supabase/migrations/image_generator.sql
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
-- Image Generator Feature Migration
|
|
2
|
+
-- Creates tables for AI-generated images
|
|
3
|
+
-- Run this migration when adding the image-generator feature via CLI
|
|
4
|
+
|
|
5
|
+
-- ============================================================================
|
|
6
|
+
-- GENERATED_IMAGES TABLE
|
|
7
|
+
-- Stores AI-generated images metadata
|
|
8
|
+
-- ============================================================================
|
|
9
|
+
CREATE TABLE IF NOT EXISTS generated_images (
|
|
10
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
11
|
+
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
12
|
+
prompt TEXT NOT NULL,
|
|
13
|
+
image_url TEXT,
|
|
14
|
+
storage_id TEXT,
|
|
15
|
+
mime_type TEXT,
|
|
16
|
+
provider TEXT NOT NULL,
|
|
17
|
+
model TEXT NOT NULL,
|
|
18
|
+
created_at TIMESTAMPTZ DEFAULT now()
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
-- ============================================================================
|
|
22
|
+
-- INDEXES
|
|
23
|
+
-- ============================================================================
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_generated_images_user_id ON generated_images(user_id);
|
|
25
|
+
|
|
26
|
+
-- ============================================================================
|
|
27
|
+
-- RLS POLICIES
|
|
28
|
+
-- ============================================================================
|
|
29
|
+
ALTER TABLE generated_images ENABLE ROW LEVEL SECURITY;
|
|
30
|
+
|
|
31
|
+
CREATE POLICY "generated_images_select_own" ON generated_images
|
|
32
|
+
FOR SELECT USING (auth.uid() = user_id);
|
|
33
|
+
|
|
34
|
+
CREATE POLICY "generated_images_insert_own" ON generated_images
|
|
35
|
+
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
36
|
+
|
|
37
|
+
CREATE POLICY "generated_images_update_own" ON generated_images
|
|
38
|
+
FOR UPDATE USING (auth.uid() = user_id);
|
|
39
|
+
|
|
40
|
+
CREATE POLICY "generated_images_delete_own" ON generated_images
|
|
41
|
+
FOR DELETE USING (auth.uid() = user_id);
|
|
42
|
+
|
|
43
|
+
-- ============================================================================
|
|
44
|
+
-- STORAGE BUCKET - generated-images
|
|
45
|
+
-- ============================================================================
|
|
46
|
+
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
|
|
47
|
+
VALUES (
|
|
48
|
+
'generated-images',
|
|
49
|
+
'generated-images',
|
|
50
|
+
false,
|
|
51
|
+
10485760, -- 10MB limit
|
|
52
|
+
ARRAY['image/jpeg', 'image/png', 'image/webp']
|
|
53
|
+
) ON CONFLICT (id) DO NOTHING;
|
|
54
|
+
|
|
55
|
+
-- Storage RLS for generated-images
|
|
56
|
+
CREATE POLICY "Users can view their own generated images"
|
|
57
|
+
ON storage.objects FOR SELECT TO authenticated
|
|
58
|
+
USING (bucket_id = 'generated-images' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
59
|
+
|
|
60
|
+
CREATE POLICY "Users can upload their own generated images"
|
|
61
|
+
ON storage.objects FOR INSERT TO authenticated
|
|
62
|
+
WITH CHECK (bucket_id = 'generated-images' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
63
|
+
|
|
64
|
+
CREATE POLICY "Users can update their own generated images"
|
|
65
|
+
ON storage.objects FOR UPDATE TO authenticated
|
|
66
|
+
USING (bucket_id = 'generated-images' AND (storage.foldername(name))[1] = auth.uid()::text)
|
|
67
|
+
WITH CHECK (bucket_id = 'generated-images' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
68
|
+
|
|
69
|
+
CREATE POLICY "Users can delete their own generated images"
|
|
70
|
+
ON storage.objects FOR DELETE TO authenticated
|
|
71
|
+
USING (bucket_id = 'generated-images' AND (storage.foldername(name))[1] = auth.uid()::text);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "image-generator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered image generation with Supabase backend",
|
|
5
|
+
"copy": [
|
|
6
|
+
{
|
|
7
|
+
"from": "apps/native/src/app/image-generator",
|
|
8
|
+
"to": "apps/native/src/app/(root)/(protected)/image-generator"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"from": "apps/native/src/features/image-generator",
|
|
12
|
+
"to": "apps/native/src/features/image-generator"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"from": "packages/backend/supabase/functions/generate-image",
|
|
16
|
+
"to": "packages/backend/supabase/functions/generate-image"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"from": "packages/backend/supabase/migrations/image_generator.sql",
|
|
20
|
+
"to": "packages/backend/supabase/migrations/image_generator.sql"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"nav": {
|
|
24
|
+
"href": "/(root)/(protected)/image-generator",
|
|
25
|
+
"label": "Image Generator",
|
|
26
|
+
"icon": "🎨",
|
|
27
|
+
"color": "#8B5CF6"
|
|
28
|
+
},
|
|
29
|
+
"target": "native",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"expo": [
|
|
32
|
+
"@supabase/supabase-js",
|
|
33
|
+
"@tanstack/react-query",
|
|
34
|
+
"expo-file-system",
|
|
35
|
+
"expo-media-library",
|
|
36
|
+
"expo-sharing"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"env": [
|
|
40
|
+
{
|
|
41
|
+
"key": "OPENAI_API_KEY",
|
|
42
|
+
"description": "OpenAI API key for DALL-E image generation",
|
|
43
|
+
"example": "sk-...",
|
|
44
|
+
"link": "https://platform.openai.com/api-keys",
|
|
45
|
+
"file": "packages/backend/supabase/.env",
|
|
46
|
+
"required": true
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"manualSteps": [
|
|
50
|
+
{
|
|
51
|
+
"title": "Apply Supabase migration",
|
|
52
|
+
"description": "Run: cd packages/backend && supabase db push"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"title": "Deploy Edge Function",
|
|
56
|
+
"description": "Run: cd packages/backend && supabase functions deploy generate-image"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
Binary file
|
|
Binary file
|