vibefast-cli 1.1.5 → 1.3.0

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.
Files changed (301) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +63 -169
  3. package/dist/__tests__/recipes.test.js +89 -85
  4. package/dist/__tests__/recipes.test.js.map +1 -1
  5. package/dist/commands/add.d.ts +1 -1
  6. package/dist/commands/add.d.ts.map +1 -1
  7. package/dist/commands/add.js +576 -588
  8. package/dist/commands/add.js.map +1 -1
  9. package/dist/commands/checklist.d.ts +1 -1
  10. package/dist/commands/checklist.d.ts.map +1 -1
  11. package/dist/commands/checklist.js +40 -39
  12. package/dist/commands/checklist.js.map +1 -1
  13. package/dist/commands/doctor.d.ts +1 -1
  14. package/dist/commands/doctor.js +22 -22
  15. package/dist/commands/doctor.js.map +1 -1
  16. package/dist/commands/env.d.ts +1 -1
  17. package/dist/commands/env.d.ts.map +1 -1
  18. package/dist/commands/env.js +58 -53
  19. package/dist/commands/env.js.map +1 -1
  20. package/dist/commands/health.d.ts +1 -1
  21. package/dist/commands/health.d.ts.map +1 -1
  22. package/dist/commands/health.js +101 -93
  23. package/dist/commands/health.js.map +1 -1
  24. package/dist/commands/init.d.ts +1 -1
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +416 -296
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/remove.d.ts +1 -1
  29. package/dist/commands/remove.d.ts.map +1 -1
  30. package/dist/commands/remove.js +77 -64
  31. package/dist/commands/remove.js.map +1 -1
  32. package/dist/commands/status.d.ts +1 -1
  33. package/dist/commands/status.d.ts.map +1 -1
  34. package/dist/commands/status.js +15 -14
  35. package/dist/commands/status.js.map +1 -1
  36. package/dist/core/__tests__/detect.test.js +68 -34
  37. package/dist/core/__tests__/detect.test.js.map +1 -1
  38. package/dist/core/ast.d.ts +14 -0
  39. package/dist/core/ast.d.ts.map +1 -0
  40. package/dist/core/ast.js +239 -0
  41. package/dist/core/ast.js.map +1 -0
  42. package/dist/core/codemod.d.ts.map +1 -1
  43. package/dist/core/codemod.js +62 -44
  44. package/dist/core/codemod.js.map +1 -1
  45. package/dist/core/config.d.ts +10 -0
  46. package/dist/core/config.d.ts.map +1 -0
  47. package/dist/core/config.js +51 -0
  48. package/dist/core/config.js.map +1 -0
  49. package/dist/core/detect.d.ts +8 -2
  50. package/dist/core/detect.d.ts.map +1 -1
  51. package/dist/core/detect.js +52 -21
  52. package/dist/core/detect.js.map +1 -1
  53. package/dist/core/errors.d.ts.map +1 -1
  54. package/dist/core/errors.js +9 -8
  55. package/dist/core/errors.js.map +1 -1
  56. package/dist/core/exec.d.ts +16 -0
  57. package/dist/core/exec.d.ts.map +1 -0
  58. package/dist/core/exec.js +48 -0
  59. package/dist/core/exec.js.map +1 -0
  60. package/dist/core/manualSteps.d.ts +7 -0
  61. package/dist/core/manualSteps.d.ts.map +1 -0
  62. package/dist/core/manualSteps.js +59 -0
  63. package/dist/core/manualSteps.js.map +1 -0
  64. package/dist/core/paths.d.ts +3 -1
  65. package/dist/core/paths.d.ts.map +1 -1
  66. package/dist/core/paths.js +14 -10
  67. package/dist/core/paths.js.map +1 -1
  68. package/dist/core/spinner.d.ts +1 -1
  69. package/dist/core/spinner.d.ts.map +1 -1
  70. package/dist/core/spinner.js +38 -8
  71. package/dist/core/spinner.js.map +1 -1
  72. package/dist/core/vosk.d.ts.map +1 -1
  73. package/dist/core/vosk.js +50 -39
  74. package/dist/core/vosk.js.map +1 -1
  75. package/docs/manual-testing.md +91 -0
  76. package/package.json +6 -3
  77. package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
  78. package/recipes/audio-recorder/recipe.json +3 -3
  79. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
  80. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
  81. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
  82. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/index.ts +4 -0
  83. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
  84. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
  85. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
  86. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
  87. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
  88. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
  89. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
  90. package/recipes/audio-recorder-supabase/packages/backend/src/services/recordings.ts +369 -0
  91. package/recipes/audio-recorder-supabase/packages/backend/supabase/migrations/recordings.sql +70 -0
  92. package/recipes/audio-recorder-supabase/recipe.json +52 -0
  93. package/recipes/audio-recorder-supabase@latest.zip +0 -0
  94. package/recipes/audio-recorder@latest.zip +0 -0
  95. package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +3 -3
  96. package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +2 -2
  97. package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +5 -5
  98. package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +3 -3
  99. package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +20 -4
  100. package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +7 -6
  101. package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +6 -4
  102. package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +1 -1
  103. package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +5 -4
  104. package/recipes/charts/recipe.json +4 -13
  105. package/recipes/charts@latest.zip +0 -0
  106. package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
  107. package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +86 -86
  108. package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +86 -53
  109. package/recipes/chatbot/recipe.json +26 -92
  110. package/recipes/chatbot-supabase/apps/native/src/api-client/supabase/chatbot.ts +515 -0
  111. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/app/index.tsx +257 -0
  112. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
  113. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-input-bar.tsx +485 -0
  114. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
  115. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +223 -0
  116. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
  117. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/image-preview-list.tsx +116 -0
  118. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
  119. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
  120. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
  121. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
  122. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-list.tsx +170 -0
  123. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
  124. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
  125. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
  126. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/models.ts +20 -0
  127. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
  128. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +142 -0
  129. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-config.ts +458 -0
  130. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +429 -0
  131. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
  132. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-conversation.ts +90 -0
  133. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
  134. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
  135. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +213 -0
  136. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/index.ts +86 -0
  137. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/models.ts +162 -0
  138. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/providers.ts +62 -0
  139. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/types.ts +40 -0
  140. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/file-uploader.ts +287 -0
  141. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/message-handler-service.ts +189 -0
  142. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/types/index.ts +70 -0
  143. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
  144. package/recipes/chatbot-supabase/packages/backend/src/services/conversations.ts +243 -0
  145. package/recipes/chatbot-supabase/packages/backend/src/services/messages.ts +327 -0
  146. package/recipes/chatbot-supabase/packages/backend/supabase/functions/chat-stream/index.ts +347 -0
  147. package/recipes/chatbot-supabase/packages/backend/supabase/migrations/chatbot.sql +104 -0
  148. package/recipes/chatbot-supabase/recipe.json +106 -0
  149. package/recipes/chatbot-supabase@latest.zip +0 -0
  150. package/recipes/chatbot.zip +0 -0
  151. package/recipes/chatbot@latest.zip +0 -0
  152. package/recipes/image-analysis/packages/backend/convex/imageAnalysis/index.ts +2 -2
  153. package/recipes/image-analysis/packages/backend/convex/imageAnalysis.ts +0 -1
  154. package/recipes/image-analysis/recipe.json +15 -55
  155. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/analysis-options-screen.tsx +304 -0
  156. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/camera.tsx +221 -0
  157. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx +333 -0
  158. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading-screen.tsx +214 -0
  159. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading.tsx +191 -0
  160. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/results.tsx +137 -0
  161. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/trait-details.tsx +172 -0
  162. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-analysis-data.ts +160 -0
  163. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-results-screen.ts +151 -0
  164. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-badge.tsx +77 -0
  165. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-card.tsx +75 -0
  166. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-unlocked-modal.tsx +162 -0
  167. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievements-section.tsx +44 -0
  168. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/advice-list.tsx +42 -0
  169. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/circular-progress.tsx +233 -0
  170. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/content-card.tsx +38 -0
  171. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/error-state.tsx +42 -0
  172. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/index.ts +9 -0
  173. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/loading-state.tsx +26 -0
  174. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/profile-image.tsx +60 -0
  175. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/results-header.tsx +62 -0
  176. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/score-display.tsx +54 -0
  177. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/share-options-modal.tsx +110 -0
  178. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/traits-grid.tsx +74 -0
  179. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/analysis-config.ts +80 -0
  180. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/master-analysis-config.ts +157 -0
  181. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/index.ts +1 -0
  182. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-analysis.ts +38 -0
  183. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts +208 -0
  184. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/analysis-service.ts +262 -0
  185. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/share-service.ts +176 -0
  186. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/trait-details-service.ts +289 -0
  187. package/recipes/image-analysis-supabase/packages/backend/src/services/image-analyses.ts +132 -0
  188. package/recipes/image-analysis-supabase/packages/backend/supabase/functions/analyze-image/index.ts +312 -0
  189. package/recipes/image-analysis-supabase/packages/backend/supabase/migrations/image_analysis.sql +42 -0
  190. package/recipes/image-analysis-supabase/recipe.json +90 -0
  191. package/recipes/image-analysis-supabase@latest.zip +0 -0
  192. package/recipes/image-analysis@latest.zip +0 -0
  193. package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +16 -2
  194. package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +11 -5
  195. package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +11 -5
  196. package/recipes/image-generator/packages/backend/convex/imageGeneration/index.ts +2 -2
  197. package/recipes/image-generator/recipe.json +16 -39
  198. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/_layout.tsx +26 -0
  199. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
  200. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/index.tsx +251 -0
  201. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/gallery-image.tsx +25 -0
  202. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
  203. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-model-selector.tsx +216 -0
  204. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
  205. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
  206. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
  207. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator.ts +103 -0
  208. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/models/models.ts +66 -0
  209. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-gallery-service.ts +96 -0
  210. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-save-service.ts +120 -0
  211. package/recipes/image-generator-supabase/packages/backend/supabase/functions/generate-image/index.ts +291 -0
  212. package/recipes/image-generator-supabase/packages/backend/supabase/migrations/image_generator.sql +71 -0
  213. package/recipes/image-generator-supabase/recipe.json +86 -0
  214. package/recipes/image-generator-supabase@latest.zip +0 -0
  215. package/recipes/image-generator@latest.zip +0 -0
  216. package/recipes/ios-widget/recipe.json +15 -24
  217. package/recipes/ios-widget@latest.zip +0 -0
  218. package/recipes/onboarding/apps/native/src/features/onboarding/analytics/index.ts +9 -0
  219. package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding-with-analytics.tsx +141 -0
  220. package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding.tsx +173 -0
  221. package/recipes/onboarding/apps/native/src/features/onboarding/config/onboarding-flow-config.ts +189 -0
  222. package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/app/index.tsx +42 -0
  223. package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/data.ts +32 -0
  224. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/app/index.tsx +43 -0
  225. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/interactive-onboarding.tsx +222 -0
  226. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/ai-tone-step.tsx +133 -0
  227. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/currency-step.tsx +165 -0
  228. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-ai-step.tsx +199 -0
  229. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-chatbot-step.tsx +154 -0
  230. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-manual-step.tsx +156 -0
  231. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-scan-step.tsx +158 -0
  232. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/main-reason-step.tsx +139 -0
  233. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/notification-step.tsx +129 -0
  234. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/overspend-step.tsx +138 -0
  235. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/personalizing-step.tsx +190 -0
  236. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/rating-step.tsx +98 -0
  237. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/reminder-step.tsx +181 -0
  238. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/safety-step.tsx +110 -0
  239. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/struggle-step.tsx +139 -0
  240. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/welcome-step.tsx +217 -0
  241. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/ui/onboarding-header.tsx +58 -0
  242. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/constants.ts +179 -0
  243. package/recipes/onboarding/apps/native/src/features/onboarding/hooks/use-onboarding-analytics.ts +323 -0
  244. package/recipes/onboarding/apps/native/src/features/onboarding/services/onboarding-analytics.ts +432 -0
  245. package/recipes/onboarding/recipe.json +15 -0
  246. package/recipes/onboarding@latest.zip +0 -0
  247. package/recipes/payments/recipe.json +28 -61
  248. package/recipes/payments-supabase/apps/native/src/features/payments/README.md +200 -0
  249. package/recipes/payments-supabase/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
  250. package/recipes/payments-supabase/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
  251. package/recipes/payments-supabase/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
  252. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
  253. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
  254. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
  255. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
  256. package/recipes/payments-supabase/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
  257. package/recipes/payments-supabase/apps/native/src/features/payments/index.ts +8 -0
  258. package/recipes/payments-supabase/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
  259. package/recipes/payments-supabase/packages/backend/src/services/payments.ts +201 -0
  260. package/recipes/payments-supabase/packages/backend/supabase/migrations/payments.sql +35 -0
  261. package/recipes/payments-supabase/recipe.json +72 -0
  262. package/recipes/payments-supabase@latest.zip +0 -0
  263. package/recipes/payments@latest.zip +0 -0
  264. package/recipes/quiz/apps/native/src/features/quiz/index.tsx +1 -2
  265. package/recipes/quiz/recipe.json +6 -9
  266. package/recipes/quiz@latest.zip +0 -0
  267. package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +1 -2
  268. package/recipes/tracker-app/recipe.json +7 -10
  269. package/recipes/tracker-app@latest.zip +0 -0
  270. package/recipes/voice-bot/recipe.json +8 -68
  271. package/recipes/voice-bot.zip +0 -0
  272. package/recipes/voice-bot@latest.zip +0 -0
  273. package/recipes/wake-word/recipe.json +10 -9
  274. package/recipes/wake-word.zip +0 -0
  275. package/recipes/wake-word@latest.zip +0 -0
  276. package/recipes/charts/apps/native/src/app/(root)/(protected)/charts/index.tsx +0 -3
  277. package/recipes/chatbot/packages/backend/convex/lib/rateLimit.ts +0 -100
  278. package/recipes/chatbot/packages/backend/convex/lib/telemetry.ts +0 -29
  279. package/recipes/chatbot/packages/backend/convex/ragKnowledge.ts +0 -0
  280. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/front.jpg +0 -0
  281. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/side.jpg +0 -0
  282. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/threeQuarter.jpg +0 -0
  283. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/_layout.tsx +0 -5
  284. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/analysis-options.tsx +0 -50
  285. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/camera.tsx +0 -2
  286. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/index.tsx +0 -50
  287. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/loading.tsx +0 -50
  288. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/results.tsx +0 -2
  289. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/trait-details.tsx +0 -3
  290. package/recipes/image-analysis/packages/backend/convex/imageAnalysisFunctions.ts +0 -325
  291. package/recipes/image-analysis/packages/backend/convex/lib/ai/imageAnalysisAdapter.ts +0 -200
  292. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +0 -74
  293. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +0 -25
  294. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +0 -23
  295. package/recipes/quiz/apps/native/src/app/(root)/(protected)/quiz/index.tsx +0 -47
  296. package/recipes/tracker-app/apps/native/src/app/(root)/(protected)/tracker-app/index.tsx +0 -1
  297. package/recipes/voice-bot/apps/native/src/app/(root)/(protected)/voice-bot/index.tsx +0 -27
  298. package/recipes/voice-bot/packages/backend/convex/router.ts +0 -81
  299. /package/recipes/{chatbot/apps/native/src/app/(root)/(protected) → chatbot-supabase/apps/native/src/app}/chatbot/index.tsx +0 -0
  300. /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/gallery.tsx +0 -0
  301. /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,103 @@
1
+ import { useState } from 'react';
2
+ import { Alert } from 'react-native';
3
+
4
+ import { useGenerateImage } from '@/api-client/supabase/image-generator';
5
+
6
+ import { getImageModelById } from '../models/models';
7
+ import type { ImageGeneratorSettings } from './use-image-generator-settings';
8
+
9
+ type GeneratedImageData = {
10
+ imageUri: string;
11
+ prompt: string;
12
+ provider: string;
13
+ model: string;
14
+ mimeType: string;
15
+ imageId?: string;
16
+ storageId?: string;
17
+ };
18
+
19
+ /**
20
+ * Custom hook for managing image generation state and operations
21
+ */
22
+ type UseImageGeneratorOptions = {
23
+ settings: ImageGeneratorSettings;
24
+ isSettingsLoaded: boolean;
25
+ };
26
+
27
+ export const useImageGenerator = ({
28
+ settings,
29
+ isSettingsLoaded,
30
+ }: UseImageGeneratorOptions) => {
31
+ const [prompt, setPrompt] = useState('');
32
+ const [isLoading, setIsLoading] = useState(false);
33
+ const [generatedImageData, setGeneratedImageData] =
34
+ useState<GeneratedImageData | null>(null);
35
+
36
+ const generateImageMutation = useGenerateImage();
37
+
38
+ /**
39
+ * Generate an image based on the current prompt
40
+ */
41
+ const handleGenerateImage = async () => {
42
+ if (!prompt.trim()) {
43
+ Alert.alert('Warning', 'Please enter a prompt to generate an image');
44
+ return;
45
+ }
46
+
47
+ if (!isSettingsLoaded) {
48
+ Alert.alert('Please wait', 'Settings are still loading...');
49
+ return;
50
+ }
51
+
52
+ setIsLoading(true);
53
+ try {
54
+ const selectedModel = getImageModelById(settings.modelId);
55
+ const provider = selectedModel?.provider ?? settings.provider;
56
+ const result = await generateImageMutation.mutateAsync({
57
+ prompt: prompt.trim(),
58
+ provider,
59
+ model: selectedModel?.id ?? settings.modelId,
60
+ });
61
+
62
+ const imageUri = result.imageDataUri || result.imageUrl || '';
63
+ const mimeType = result.mimeType || (result.imageUrl ? 'image/png' : '');
64
+
65
+ console.log('Frontend received result:', {
66
+ provider: result.provider,
67
+ model: result.model,
68
+ mimeType,
69
+ dataUriLength: imageUri.length,
70
+ dataUriPreview: imageUri.substring(0, 100),
71
+ });
72
+
73
+ setGeneratedImageData({
74
+ imageUri,
75
+ prompt: result.prompt ?? prompt.trim(),
76
+ provider: result.provider ?? provider,
77
+ model: result.model ?? settings.modelId,
78
+ mimeType: mimeType || 'image/png',
79
+ imageId: result.imageId,
80
+ storageId: result.storageId,
81
+ });
82
+ // Alert.alert('Success', 'Image generated successfully!');
83
+ } catch (error) {
84
+ console.error('Image generation error:', error);
85
+ Alert.alert(
86
+ 'Error',
87
+ error instanceof Error ? error.message : 'Failed to generate image',
88
+ );
89
+ } finally {
90
+ setIsLoading(false);
91
+ }
92
+ };
93
+
94
+ return {
95
+ prompt,
96
+ setPrompt,
97
+ isLoading,
98
+ generatedImageData,
99
+ generatedImageUri: generatedImageData?.imageUri || null, // backward compatibility
100
+ handleGenerateImage,
101
+ settings,
102
+ };
103
+ };
@@ -0,0 +1,66 @@
1
+ export type ImageProvider = 'openai' | 'gemini';
2
+
3
+ export type ImageModelId =
4
+ | 'dall-e-3'
5
+ | 'gpt-image-1'
6
+ | 'gemini-2.5-flash-image';
7
+
8
+ export type ImageModel = {
9
+ id: ImageModelId;
10
+ name: string;
11
+ description: string;
12
+ provider: ImageProvider;
13
+ defaultSize?: string;
14
+ defaultAspectRatio?: string;
15
+ isPremium?: boolean;
16
+ };
17
+
18
+ export const IMAGE_MODELS: ImageModel[] = [
19
+ {
20
+ id: 'dall-e-3',
21
+ name: 'DALL·E 3',
22
+ description:
23
+ 'OpenAI’s flagship model for creative storytelling and illustration.',
24
+ provider: 'openai',
25
+ defaultSize: '1024x1024',
26
+ },
27
+ {
28
+ id: 'gpt-image-1',
29
+ name: 'GPT Image 1',
30
+ description:
31
+ 'GPT-4o based image model with strong photorealism and typography.',
32
+ provider: 'openai',
33
+ defaultSize: '1024x1024',
34
+ },
35
+ {
36
+ id: 'gemini-2.5-flash-image',
37
+ name: 'Gemini 2.5 Flash Image',
38
+ description:
39
+ 'Fast and cost-effective image generation ideal for rapid iteration.',
40
+ provider: 'gemini',
41
+ defaultSize: '1024x1024',
42
+ },
43
+ ];
44
+
45
+ export const DEFAULT_IMAGE_MODEL_ID: ImageModelId = 'gemini-2.5-flash-image';
46
+
47
+ export const getImageModelsByProvider = (provider: ImageProvider) =>
48
+ IMAGE_MODELS.filter((model) => model.provider === provider);
49
+
50
+ export const getImageModelById = (id: ImageModelId) =>
51
+ IMAGE_MODELS.find((model) => model.id === id) ?? null;
52
+
53
+ export const getProviderDisplayName = (provider: ImageProvider) => {
54
+ switch (provider) {
55
+ case 'openai':
56
+ return 'OpenAI';
57
+ case 'gemini':
58
+ return 'Google Gemini';
59
+ default:
60
+ return provider;
61
+ }
62
+ };
63
+
64
+ export const IMAGE_MODEL_PROVIDERS: ImageProvider[] = Array.from(
65
+ new Set(IMAGE_MODELS.map((model) => model.provider)),
66
+ ) as ImageProvider[];
@@ -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
+ }
@@ -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
+ });