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.
Files changed (300) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +63 -169
  3. package/dist/__tests__/recipes.test.js +25 -3
  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 +547 -543
  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 +35 -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 +79 -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/{imageAnalysisFunctions.ts → imageAnalysis.ts} +5 -5
  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 +57 -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 +59 -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 +51 -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/lib/ai/imageAnalysisAdapter.ts +0 -200
  291. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +0 -74
  292. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +0 -25
  293. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +0 -23
  294. package/recipes/quiz/apps/native/src/app/(root)/(protected)/quiz/index.tsx +0 -47
  295. package/recipes/tracker-app/apps/native/src/app/(root)/(protected)/tracker-app/index.tsx +0 -1
  296. package/recipes/voice-bot/apps/native/src/app/(root)/(protected)/voice-bot/index.tsx +0 -27
  297. package/recipes/voice-bot/packages/backend/convex/router.ts +0 -81
  298. /package/recipes/{chatbot/apps/native/src/app/(root)/(protected) → chatbot-supabase/apps/native/src/app}/chatbot/index.tsx +0 -0
  299. /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/gallery.tsx +0 -0
  300. /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,327 @@
1
+ /**
2
+ * Messages Service
3
+ *
4
+ * Handles message operations including storeMessage, listMessages,
5
+ * deleteUserMessage, and clearConversationMessages.
6
+ *
7
+ * Requirements: 4.3, 4.4, 4.5, 4.6, 4.7
8
+ */
9
+
10
+ import type { SupabaseClientType } from '../lib/supabase';
11
+ import type { Message } from '../types';
12
+ import type { Json } from '../types/database';
13
+
14
+ export interface StoreMessageInput {
15
+ conversationId: string;
16
+ authorType: 'user' | 'bot';
17
+ text?: string | null;
18
+ attachments?: Json | null;
19
+ aiProvider?: string | null;
20
+ aiModel?: string | null;
21
+ toolCalls?: Json | null;
22
+ metadata?: Json | null;
23
+ }
24
+
25
+ export interface StoreMessageResult {
26
+ success: boolean;
27
+ message?: Message;
28
+ error?: string;
29
+ }
30
+
31
+ export interface ListMessagesResult {
32
+ success: boolean;
33
+ messages?: Message[];
34
+ hasMore?: boolean;
35
+ error?: string;
36
+ }
37
+
38
+ export interface DeleteMessageResult {
39
+ success: boolean;
40
+ error?: string;
41
+ }
42
+
43
+ export interface ClearMessagesResult {
44
+ success: boolean;
45
+ deletedCount?: number;
46
+ error?: string;
47
+ }
48
+
49
+ const DEFAULT_PAGE_SIZE = 50;
50
+ const BATCH_DELETE_SIZE = 100;
51
+
52
+ /**
53
+ * Store a new message in a conversation
54
+ *
55
+ * Validates that the message has either text or attachments (not both empty).
56
+ *
57
+ * @param supabase - Supabase client (authenticated)
58
+ * @param input - Message data to store
59
+ * @returns The created message
60
+ *
61
+ * Requirements: 4.3, 4.4
62
+ */
63
+ export async function storeMessage(
64
+ supabase: SupabaseClientType,
65
+ input: StoreMessageInput
66
+ ): Promise<StoreMessageResult> {
67
+ // Get the current auth user
68
+ const { data: authData, error: authError } = await supabase.auth.getUser();
69
+
70
+ if (authError || !authData.user) {
71
+ return { success: false, error: 'Not authenticated' };
72
+ }
73
+
74
+ const userId = authData.user.id;
75
+
76
+ // Validate input
77
+ if (!input.conversationId) {
78
+ return { success: false, error: 'Conversation ID is required' };
79
+ }
80
+
81
+ // Check if text is empty/whitespace and no attachments
82
+ const hasText = input.text && input.text.trim().length > 0;
83
+ const hasAttachments = input.attachments &&
84
+ (Array.isArray(input.attachments) ? input.attachments.length > 0 : true);
85
+
86
+ if (!hasText && !hasAttachments) {
87
+ return { success: false, error: 'Message must have text or attachments' };
88
+ }
89
+
90
+ // Verify user owns the conversation
91
+ const { data: conversation, error: convError } = await supabase
92
+ .from('conversations')
93
+ .select('id')
94
+ .eq('id', input.conversationId)
95
+ .eq('user_id', userId)
96
+ .single();
97
+
98
+ if (convError || !conversation) {
99
+ return { success: false, error: 'Conversation not found or access denied' };
100
+ }
101
+
102
+ // Create the message
103
+ const { data: message, error: createError } = await supabase
104
+ .from('messages')
105
+ .insert({
106
+ conversation_id: input.conversationId,
107
+ user_id: userId,
108
+ author_type: input.authorType,
109
+ text: input.text || null,
110
+ attachments: input.attachments || null,
111
+ ai_provider: input.aiProvider || null,
112
+ ai_model: input.aiModel || null,
113
+ tool_calls: input.toolCalls || null,
114
+ metadata: input.metadata || null,
115
+ })
116
+ .select()
117
+ .single();
118
+
119
+ if (createError) {
120
+ return { success: false, error: `Failed to store message: ${createError.message}` };
121
+ }
122
+
123
+ return { success: true, message };
124
+ }
125
+
126
+
127
+ /**
128
+ * List messages for a conversation with pagination
129
+ *
130
+ * Returns messages ordered by creation time (oldest first for chat display).
131
+ *
132
+ * @param supabase - Supabase client (authenticated)
133
+ * @param conversationId - The conversation to list messages from
134
+ * @param options - Pagination options
135
+ * @returns Paginated list of messages
136
+ *
137
+ * Requirements: 4.5
138
+ */
139
+ export async function listMessages(
140
+ supabase: SupabaseClientType,
141
+ conversationId: string,
142
+ options?: {
143
+ limit?: number;
144
+ cursor?: string; // created_at timestamp for cursor-based pagination
145
+ }
146
+ ): Promise<ListMessagesResult> {
147
+ // Get the current auth user
148
+ const { data: authData, error: authError } = await supabase.auth.getUser();
149
+
150
+ if (authError || !authData.user) {
151
+ return { success: false, error: 'Not authenticated' };
152
+ }
153
+
154
+ if (!conversationId) {
155
+ return { success: false, error: 'Conversation ID is required' };
156
+ }
157
+
158
+ const limit = options?.limit || DEFAULT_PAGE_SIZE;
159
+
160
+ // Build query
161
+ let query = supabase
162
+ .from('messages')
163
+ .select('*')
164
+ .eq('conversation_id', conversationId)
165
+ .order('created_at', { ascending: true })
166
+ .limit(limit + 1); // Fetch one extra to check if there are more
167
+
168
+ // Apply cursor if provided
169
+ if (options?.cursor) {
170
+ query = query.gt('created_at', options.cursor);
171
+ }
172
+
173
+ const { data: messages, error: listError } = await query;
174
+
175
+ if (listError) {
176
+ return { success: false, error: `Failed to list messages: ${listError.message}` };
177
+ }
178
+
179
+ // Check if there are more results
180
+ const hasMore = messages && messages.length > limit;
181
+ const resultMessages = hasMore ? messages.slice(0, limit) : (messages || []);
182
+
183
+ return {
184
+ success: true,
185
+ messages: resultMessages,
186
+ hasMore,
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Delete a single user message
192
+ *
193
+ * Only allows deletion if the user owns the message.
194
+ *
195
+ * @param supabase - Supabase client (authenticated)
196
+ * @param messageId - The ID of the message to delete
197
+ * @returns Success or error
198
+ *
199
+ * Requirements: 4.7
200
+ */
201
+ export async function deleteUserMessage(
202
+ supabase: SupabaseClientType,
203
+ messageId: string
204
+ ): Promise<DeleteMessageResult> {
205
+ // Get the current auth user
206
+ const { data: authData, error: authError } = await supabase.auth.getUser();
207
+
208
+ if (authError || !authData.user) {
209
+ return { success: false, error: 'Not authenticated' };
210
+ }
211
+
212
+ if (!messageId) {
213
+ return { success: false, error: 'Message ID is required' };
214
+ }
215
+
216
+ // Delete the message (RLS + user_id check ensures ownership)
217
+ const { data, error: deleteError } = await supabase
218
+ .from('messages')
219
+ .delete()
220
+ .eq('id', messageId)
221
+ .eq('user_id', authData.user.id)
222
+ .select('id')
223
+ .single();
224
+
225
+ if (deleteError) {
226
+ if (deleteError.code === 'PGRST116') {
227
+ return { success: false, error: 'Message not found or access denied' };
228
+ }
229
+ return { success: false, error: `Failed to delete message: ${deleteError.message}` };
230
+ }
231
+
232
+ if (!data) {
233
+ return { success: false, error: 'Message not found or access denied' };
234
+ }
235
+
236
+ return { success: true };
237
+ }
238
+
239
+ /**
240
+ * Clear all messages in a conversation
241
+ *
242
+ * Deletes messages in batches to handle large conversations.
243
+ * The conversation itself is preserved.
244
+ *
245
+ * @param supabase - Supabase client (authenticated)
246
+ * @param conversationId - The conversation to clear
247
+ * @returns Success with count of deleted messages
248
+ *
249
+ * Requirements: 4.6
250
+ */
251
+ export async function clearConversationMessages(
252
+ supabase: SupabaseClientType,
253
+ conversationId: string
254
+ ): Promise<ClearMessagesResult> {
255
+ // Get the current auth user
256
+ const { data: authData, error: authError } = await supabase.auth.getUser();
257
+
258
+ if (authError || !authData.user) {
259
+ return { success: false, error: 'Not authenticated' };
260
+ }
261
+
262
+ if (!conversationId) {
263
+ return { success: false, error: 'Conversation ID is required' };
264
+ }
265
+
266
+ const userId = authData.user.id;
267
+
268
+ // Verify user owns the conversation
269
+ const { data: conversation, error: convError } = await supabase
270
+ .from('conversations')
271
+ .select('id')
272
+ .eq('id', conversationId)
273
+ .eq('user_id', userId)
274
+ .single();
275
+
276
+ if (convError || !conversation) {
277
+ return { success: false, error: 'Conversation not found or access denied' };
278
+ }
279
+
280
+ let totalDeleted = 0;
281
+ let hasMore = true;
282
+
283
+ // Delete in batches
284
+ while (hasMore) {
285
+ // Get batch of message IDs
286
+ const { data: messagesToDelete, error: fetchError } = await supabase
287
+ .from('messages')
288
+ .select('id')
289
+ .eq('conversation_id', conversationId)
290
+ .limit(BATCH_DELETE_SIZE);
291
+
292
+ if (fetchError) {
293
+ return { success: false, error: `Failed to fetch messages: ${fetchError.message}` };
294
+ }
295
+
296
+ if (!messagesToDelete || messagesToDelete.length === 0) {
297
+ hasMore = false;
298
+ break;
299
+ }
300
+
301
+ const idsToDelete = messagesToDelete.map((m) => m.id);
302
+
303
+ // Delete the batch
304
+ const { error: deleteError } = await supabase
305
+ .from('messages')
306
+ .delete()
307
+ .in('id', idsToDelete);
308
+
309
+ if (deleteError) {
310
+ return { success: false, error: `Failed to delete messages: ${deleteError.message}` };
311
+ }
312
+
313
+ totalDeleted += idsToDelete.length;
314
+
315
+ // Check if we got a full batch (might be more)
316
+ hasMore = messagesToDelete.length === BATCH_DELETE_SIZE;
317
+ }
318
+
319
+ return { success: true, deletedCount: totalDeleted };
320
+ }
321
+
322
+ export const messagesService = {
323
+ storeMessage,
324
+ listMessages,
325
+ deleteUserMessage,
326
+ clearConversationMessages,
327
+ };
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Chat Stream Edge Function
3
+ *
4
+ * Handles AI chat with real-time streaming via Supabase Realtime Broadcast.
5
+ * Broadcasts chunks as they arrive, client subscribes to channel.
6
+ */
7
+
8
+ import 'jsr:@supabase/functions-js/edge-runtime.d.ts';
9
+ import { createClient } from 'npm:@supabase/supabase-js@2';
10
+ import { streamText } from 'npm:ai@5.0.98';
11
+ import { createOpenAI } from 'npm:@ai-sdk/openai@2.0.71';
12
+ import { createGoogleGenerativeAI } from 'npm:@ai-sdk/google@2.0.40';
13
+
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 ChatStreamRequest {
22
+ conversationId: string;
23
+ message: string;
24
+ attachments?: Array<{
25
+ type: string;
26
+ url?: string;
27
+ data?: string;
28
+ mimeType?: string;
29
+ }>;
30
+ preferredModel?: string;
31
+ preferredProvider?: 'openai' | 'google';
32
+ streamId?: string; // Client-provided channel ID for broadcast
33
+ }
34
+
35
+ interface Message {
36
+ id: string;
37
+ author_type: 'user' | 'bot';
38
+ text: string | null;
39
+ created_at: string;
40
+ }
41
+
42
+ Deno.serve(async (req: Request) => {
43
+ if (req.method === 'OPTIONS') {
44
+ return new Response('ok', { headers: corsHeaders });
45
+ }
46
+
47
+ if (req.method !== 'POST') {
48
+ return new Response(JSON.stringify({ error: 'Method not allowed' }), {
49
+ status: 405,
50
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
51
+ });
52
+ }
53
+
54
+ try {
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
+ const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
65
+ const supabaseAnonKey = Deno.env.get('SUPABASE_ANON_KEY')!;
66
+ const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
67
+
68
+ // User client for auth verification
69
+ const supabaseUser = createClient(supabaseUrl, supabaseAnonKey, {
70
+ global: { headers: { Authorization: `Bearer ${token}` } },
71
+ });
72
+
73
+ // Service client for DB operations (bypasses RLS)
74
+ const supabase = createClient(supabaseUrl, supabaseServiceKey);
75
+
76
+ const { data: { user }, error: authError } = await supabaseUser.auth.getUser();
77
+ if (authError || !user) {
78
+ return new Response(JSON.stringify({ error: 'Invalid or expired token' }), {
79
+ status: 401,
80
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
81
+ });
82
+ }
83
+
84
+ const body: ChatStreamRequest = await req.json();
85
+ const { conversationId, message, attachments, preferredModel, preferredProvider, streamId } = body;
86
+
87
+ if (!conversationId || !message) {
88
+ return new Response(
89
+ JSON.stringify({ error: 'conversationId and message are required' }),
90
+ {
91
+ status: 400,
92
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
93
+ }
94
+ );
95
+ }
96
+
97
+ // Verify user owns the conversation
98
+ const { data: conversation, error: convError } = await supabase
99
+ .from('conversations')
100
+ .select('id, user_id')
101
+ .eq('id', conversationId)
102
+ .single();
103
+
104
+ if (convError || !conversation) {
105
+ return new Response(JSON.stringify({ error: 'Conversation not found' }), {
106
+ status: 404,
107
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
108
+ });
109
+ }
110
+
111
+ if (conversation.user_id !== user.id) {
112
+ return new Response(JSON.stringify({ error: 'Access denied to conversation' }), {
113
+ status: 403,
114
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
115
+ });
116
+ }
117
+
118
+ // Retrieve conversation history
119
+ const { data: messages, error: msgError } = await supabase
120
+ .from('messages')
121
+ .select('id, author_type, text, created_at')
122
+ .eq('conversation_id', conversationId)
123
+ .order('created_at', { ascending: true })
124
+ .limit(50);
125
+
126
+ if (msgError) {
127
+ console.error('Error fetching messages:', msgError);
128
+ }
129
+
130
+ // Build conversation history for AI - filter out empty messages
131
+ const conversationHistory = (messages || [])
132
+ .filter((msg: Message) => msg.text && msg.text.trim().length > 0)
133
+ .map((msg: Message) => ({
134
+ role: msg.author_type === 'user' ? 'user' : 'assistant',
135
+ content: msg.text!,
136
+ }));
137
+
138
+ // Add the current user message (include attachments as multimodal parts)
139
+ const userParts: Array<
140
+ | { type: 'text'; text: string }
141
+ | { type: 'image'; image: string; mimeType?: string }
142
+ > = [{ type: 'text', text: message }];
143
+
144
+ (attachments || [])
145
+ .filter((att) => att?.url)
146
+ .forEach((att) => {
147
+ userParts.push({
148
+ type: 'image',
149
+ image: att.url!,
150
+ mimeType: att.mimeType || 'image/jpeg',
151
+ });
152
+ });
153
+
154
+ conversationHistory.push({
155
+ role: 'user',
156
+ content: userParts,
157
+ });
158
+
159
+ console.log('Filtered conversation history:', conversationHistory.length, 'messages');
160
+
161
+ // Model mapping
162
+ const modelMapping: Record<string, { provider: 'openai' | 'google'; model: string }> = {
163
+ 'gpt-5': { provider: 'openai', model: 'gpt-4o' },
164
+ 'gpt-5-mini': { provider: 'openai', model: 'gpt-4o-mini' },
165
+ 'gpt-5-nano': { provider: 'openai', model: 'gpt-4o-mini' },
166
+ 'gpt-4o': { provider: 'openai', model: 'gpt-4o' },
167
+ 'gpt-4o-mini': { provider: 'openai', model: 'gpt-4o-mini' },
168
+ 'gemini-2.0-flash': { provider: 'google', model: 'gemini-2.0-flash' },
169
+ 'gemini-2.5-flash': { provider: 'google', model: 'gemini-2.0-flash' },
170
+ 'gemini-2.5-pro': { provider: 'google', model: 'gemini-1.5-pro' },
171
+ };
172
+
173
+ const mappedModel = preferredModel ? modelMapping[preferredModel] : null;
174
+ const provider = mappedModel?.provider || preferredProvider || 'openai';
175
+ let model;
176
+ let aiProvider: string;
177
+ let aiModel: string;
178
+
179
+ if (provider === 'google') {
180
+ const googleApiKey = Deno.env.get('GEMINI_API_KEY');
181
+ if (!googleApiKey) {
182
+ return new Response(JSON.stringify({ error: 'Google AI not configured' }), {
183
+ status: 500,
184
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
185
+ });
186
+ }
187
+ const google = createGoogleGenerativeAI({ apiKey: googleApiKey });
188
+ aiModel = mappedModel?.model || preferredModel || 'gemini-2.0-flash';
189
+ model = google(aiModel);
190
+ aiProvider = 'google';
191
+ } else {
192
+ const openaiApiKey = Deno.env.get('OPENAI_API_KEY');
193
+ if (!openaiApiKey) {
194
+ return new Response(JSON.stringify({ error: 'OpenAI not configured' }), {
195
+ status: 500,
196
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
197
+ });
198
+ }
199
+ const openai = createOpenAI({ apiKey: openaiApiKey });
200
+ aiModel = mappedModel?.model || preferredModel || 'gpt-4o-mini';
201
+ model = openai(aiModel);
202
+ aiProvider = 'openai';
203
+ }
204
+
205
+ // CREATE BOT MESSAGE IMMEDIATELY with is_streaming = true
206
+ const { data: botMessage, error: createError } = await supabase
207
+ .from('messages')
208
+ .insert({
209
+ conversation_id: conversationId,
210
+ user_id: user.id,
211
+ author_type: 'bot',
212
+ text: null,
213
+ streaming_content: '',
214
+ is_streaming: true,
215
+ ai_provider: aiProvider,
216
+ ai_model: aiModel,
217
+ })
218
+ .select('id')
219
+ .single();
220
+
221
+ if (createError || !botMessage) {
222
+ console.error('Error creating bot message:', createError);
223
+ return new Response(JSON.stringify({ error: 'Failed to create message' }), {
224
+ status: 500,
225
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
226
+ });
227
+ }
228
+
229
+ const messageId = botMessage.id;
230
+ console.log('Created streaming message:', messageId);
231
+
232
+ // Create broadcast channel using client-provided streamId
233
+ const channelName = streamId ? `stream:${streamId}` : `chat:${messageId}`;
234
+ console.log('Using broadcast channel:', channelName);
235
+ const channel = supabase.channel(channelName);
236
+
237
+ // Subscribe to channel (required before sending)
238
+ await new Promise<void>((resolve) => {
239
+ channel.subscribe((status) => {
240
+ if (status === 'SUBSCRIBED') {
241
+ console.log('Broadcast channel ready:', channelName);
242
+ resolve();
243
+ }
244
+ });
245
+ });
246
+
247
+ let fullResponse = '';
248
+
249
+ try {
250
+ console.log('Starting AI stream with model:', aiModel, 'provider:', aiProvider);
251
+ console.log('Conversation history:', JSON.stringify(conversationHistory));
252
+
253
+ const result = streamText({
254
+ model,
255
+ messages: conversationHistory as any,
256
+ system: 'You are a helpful AI assistant. Be concise and helpful.',
257
+ });
258
+
259
+ console.log('streamText called, iterating textStream...');
260
+
261
+ // Stream chunks and broadcast each one
262
+ let chunkCount = 0;
263
+ for await (const chunk of result.textStream) {
264
+ chunkCount++;
265
+ fullResponse += chunk;
266
+ console.log(`Chunk ${chunkCount}: "${chunk.substring(0, 20)}..." (total: ${fullResponse.length})`);
267
+
268
+ // Broadcast chunk to client
269
+ await channel.send({
270
+ type: 'broadcast',
271
+ event: 'chunk',
272
+ payload: { chunk, accumulated: fullResponse },
273
+ });
274
+ }
275
+
276
+ console.log('Stream completed, chunks:', chunkCount, 'total length:', fullResponse.length);
277
+
278
+ if (fullResponse.length === 0) {
279
+ fullResponse = 'I apologize, but I was unable to generate a response. Please try again.';
280
+ }
281
+
282
+ // Broadcast completion
283
+ await channel.send({
284
+ type: 'broadcast',
285
+ event: 'done',
286
+ payload: { content: fullResponse, msgId: messageId },
287
+ });
288
+
289
+ // Final DB update
290
+ await supabase
291
+ .from('messages')
292
+ .update({
293
+ text: fullResponse,
294
+ streaming_content: fullResponse,
295
+ is_streaming: false,
296
+ })
297
+ .eq('id', messageId);
298
+
299
+ // Cleanup channel
300
+ await supabase.removeChannel(channel);
301
+
302
+ return new Response(
303
+ JSON.stringify({ success: true, messageId, content: fullResponse }),
304
+ { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
305
+ );
306
+ } catch (error) {
307
+ console.error('AI error:', error);
308
+ console.error('Error name:', (error as Error)?.name);
309
+ console.error('Error message:', (error as Error)?.message);
310
+ console.error('Error stack:', (error as Error)?.stack);
311
+
312
+ // Broadcast error
313
+ await channel.send({
314
+ type: 'broadcast',
315
+ event: 'error',
316
+ payload: { error: String(error) },
317
+ });
318
+
319
+ await supabase.removeChannel(channel);
320
+
321
+ await supabase
322
+ .from('messages')
323
+ .update({
324
+ text: 'Sorry, an error occurred.',
325
+ streaming_content: null,
326
+ is_streaming: false,
327
+ })
328
+ .eq('id', messageId);
329
+
330
+ return new Response(
331
+ JSON.stringify({ success: false, messageId, error: String(error) }),
332
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
333
+ );
334
+ }
335
+ } catch (error) {
336
+ console.error('Chat stream error:', error);
337
+ return new Response(
338
+ JSON.stringify({
339
+ error: error instanceof Error ? error.message : 'Internal server error',
340
+ }),
341
+ {
342
+ status: 500,
343
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
344
+ }
345
+ );
346
+ }
347
+ });