fastapi-fullstack 0.1.2__py3-none-any.whl

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 (221) hide show
  1. fastapi_fullstack-0.1.2.dist-info/METADATA +545 -0
  2. fastapi_fullstack-0.1.2.dist-info/RECORD +221 -0
  3. fastapi_fullstack-0.1.2.dist-info/WHEEL +4 -0
  4. fastapi_fullstack-0.1.2.dist-info/entry_points.txt +2 -0
  5. fastapi_fullstack-0.1.2.dist-info/licenses/LICENSE +21 -0
  6. fastapi_gen/__init__.py +3 -0
  7. fastapi_gen/cli.py +256 -0
  8. fastapi_gen/config.py +255 -0
  9. fastapi_gen/generator.py +181 -0
  10. fastapi_gen/prompts.py +648 -0
  11. fastapi_gen/template/cookiecutter.json +76 -0
  12. fastapi_gen/template/hooks/post_gen_project.py +111 -0
  13. fastapi_gen/template/{{cookiecutter.project_slug}}/.env.example +136 -0
  14. fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
  15. fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +108 -0
  16. fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +357 -0
  17. fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +298 -0
  18. fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +723 -0
  19. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
  20. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
  21. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
  22. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
  23. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
  24. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
  25. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
  26. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
  27. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +115 -0
  28. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +13 -0
  29. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +202 -0
  30. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
  31. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
  32. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
  33. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +528 -0
  34. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +85 -0
  35. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
  36. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
  37. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
  38. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +448 -0
  39. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
  40. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +490 -0
  41. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
  42. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
  43. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
  44. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
  45. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
  46. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
  47. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
  48. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
  49. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
  50. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
  51. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
  52. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
  53. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
  54. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
  55. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
  56. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
  57. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +247 -0
  58. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
  59. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
  60. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
  61. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
  62. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
  63. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
  64. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
  65. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
  66. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
  67. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
  68. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
  69. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
  70. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
  71. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
  72. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
  73. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
  74. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +113 -0
  75. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +326 -0
  76. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
  77. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
  78. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
  79. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
  80. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +760 -0
  81. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
  82. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
  83. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
  84. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
  85. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
  86. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
  87. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +195 -0
  88. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
  89. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
  90. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
  91. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
  92. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
  93. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
  94. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +797 -0
  95. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
  96. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
  97. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
  98. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
  99. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
  100. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
  101. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
  102. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
  103. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
  104. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
  105. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
  106. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
  107. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
  108. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +158 -0
  109. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
  110. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
  111. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
  112. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
  113. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
  114. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
  115. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
  116. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
  117. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
  118. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +121 -0
  119. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
  120. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
  121. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
  122. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
  123. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
  124. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
  125. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
  126. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
  127. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
  128. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
  129. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +382 -0
  130. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
  131. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
  132. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
  133. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
  134. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
  135. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
  136. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +693 -0
  137. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
  138. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
  139. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
  140. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
  141. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
  142. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
  143. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
  144. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
  145. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +66 -0
  146. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
  147. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
  148. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/layout.tsx +11 -0
  149. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/login/page.tsx +5 -0
  150. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/register/page.tsx +5 -0
  151. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/chat/page.tsx +20 -0
  152. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/dashboard/page.tsx +99 -0
  153. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/layout.tsx +17 -0
  154. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/profile/page.tsx +156 -0
  155. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
  156. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
  157. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
  158. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
  159. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
  160. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
  161. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
  162. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
  163. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
  164. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
  165. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/auth/callback/page.tsx +96 -0
  166. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +108 -0
  167. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +25 -0
  168. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/page.tsx +73 -0
  169. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
  170. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
  171. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
  172. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
  173. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +135 -0
  174. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +73 -0
  175. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +261 -0
  176. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +8 -0
  177. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +63 -0
  178. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
  179. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +60 -0
  180. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
  181. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
  182. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
  183. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +45 -0
  184. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
  185. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +48 -0
  186. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
  187. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
  188. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +83 -0
  189. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
  190. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
  191. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +54 -0
  192. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
  193. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +12 -0
  194. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
  195. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
  196. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +6 -0
  197. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
  198. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
  199. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +175 -0
  200. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
  201. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +32 -0
  202. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
  203. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
  204. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
  205. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
  206. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
  207. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +33 -0
  208. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
  209. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +48 -0
  210. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
  211. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
  212. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +6 -0
  213. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
  214. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
  215. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
  216. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +81 -0
  217. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
  218. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
  219. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
  220. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
  221. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.setup.ts +56 -0
@@ -0,0 +1,87 @@
1
+ """API v1 router aggregation."""
2
+ {%- if cookiecutter.use_jwt or cookiecutter.enable_oauth or cookiecutter.include_example_crud or cookiecutter.enable_conversation_persistence or cookiecutter.enable_webhooks or cookiecutter.enable_websockets or cookiecutter.enable_ai_agent %}
3
+ # ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
4
+ {%- endif %}
5
+
6
+ from fastapi import APIRouter
7
+
8
+ from app.api.routes.v1 import health
9
+ {%- if cookiecutter.use_jwt %}
10
+ from app.api.routes.v1 import auth, users
11
+ {%- endif %}
12
+ {%- if cookiecutter.enable_oauth %}
13
+ from app.api.routes.v1 import oauth
14
+ {%- endif %}
15
+ {%- if cookiecutter.enable_session_management and cookiecutter.use_jwt %}
16
+ from app.api.routes.v1 import sessions
17
+ {%- endif %}
18
+ {%- if cookiecutter.include_example_crud and cookiecutter.use_database %}
19
+ from app.api.routes.v1 import items
20
+ {%- endif %}
21
+ {%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
22
+ from app.api.routes.v1 import conversations
23
+ {%- endif %}
24
+ {%- if cookiecutter.enable_webhooks and cookiecutter.use_database %}
25
+ from app.api.routes.v1 import webhooks
26
+ {%- endif %}
27
+ {%- if cookiecutter.enable_websockets %}
28
+ from app.api.routes.v1 import ws
29
+ {%- endif %}
30
+ {%- if cookiecutter.enable_ai_agent %}
31
+ from app.api.routes.v1 import agent
32
+ {%- endif %}
33
+
34
+ v1_router = APIRouter()
35
+
36
+ # Health check routes (no auth required)
37
+ v1_router.include_router(health.router, tags=["health"])
38
+
39
+ {%- if cookiecutter.use_jwt %}
40
+
41
+ # Authentication routes
42
+ v1_router.include_router(auth.router, prefix="/auth", tags=["auth"])
43
+
44
+ # User routes
45
+ v1_router.include_router(users.router, prefix="/users", tags=["users"])
46
+ {%- endif %}
47
+
48
+ {%- if cookiecutter.enable_oauth %}
49
+
50
+ # OAuth2 routes
51
+ v1_router.include_router(oauth.router, prefix="/oauth", tags=["oauth"])
52
+ {%- endif %}
53
+
54
+ {%- if cookiecutter.enable_session_management and cookiecutter.use_jwt %}
55
+
56
+ # Session management routes
57
+ v1_router.include_router(sessions.router, prefix="/sessions", tags=["sessions"])
58
+ {%- endif %}
59
+
60
+ {%- if cookiecutter.include_example_crud and cookiecutter.use_database %}
61
+
62
+ # Example CRUD routes (items)
63
+ v1_router.include_router(items.router, prefix="/items", tags=["items"])
64
+ {%- endif %}
65
+
66
+ {%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
67
+
68
+ # Conversation routes (AI chat persistence)
69
+ v1_router.include_router(conversations.router, prefix="/conversations", tags=["conversations"])
70
+ {%- endif %}
71
+
72
+ {%- if cookiecutter.enable_webhooks and cookiecutter.use_database %}
73
+
74
+ # Webhook routes
75
+ v1_router.include_router(webhooks.router, prefix="/webhooks", tags=["webhooks"])
76
+ {%- endif %}
77
+
78
+ {%- if cookiecutter.enable_websockets %}
79
+
80
+ # WebSocket routes
81
+ v1_router.include_router(ws.router, tags=["websocket"])
82
+ {%- endif %}
83
+ {%- if cookiecutter.enable_ai_agent %}
84
+
85
+ # AI Agent routes
86
+ v1_router.include_router(agent.router, tags=["agent"])
87
+ {%- endif %}
@@ -0,0 +1,448 @@
1
+ {%- if cookiecutter.enable_ai_agent %}
2
+ """AI Agent WebSocket routes with streaming support."""
3
+
4
+ import logging
5
+ from typing import Any
6
+ {%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
7
+ from datetime import datetime, UTC
8
+ {%- if cookiecutter.use_postgresql %}
9
+ from uuid import UUID
10
+ {%- endif %}
11
+ {%- endif %}
12
+
13
+ from fastapi import APIRouter, WebSocket, WebSocketDisconnect{%- if cookiecutter.websocket_auth_jwt %}, Depends{%- endif %}{%- if cookiecutter.websocket_auth_api_key %}, Query{%- endif %}
14
+
15
+ from pydantic_ai import (
16
+ Agent,
17
+ FinalResultEvent,
18
+ FunctionToolCallEvent,
19
+ FunctionToolResultEvent,
20
+ PartDeltaEvent,
21
+ PartStartEvent,
22
+ TextPartDelta,
23
+ ToolCallPartDelta,
24
+ )
25
+ from pydantic_ai.messages import (
26
+ ModelRequest,
27
+ ModelResponse,
28
+ SystemPromptPart,
29
+ TextPart,
30
+ UserPromptPart,
31
+ )
32
+
33
+ from app.agents.assistant import Deps, get_agent
34
+ {%- if cookiecutter.websocket_auth_jwt %}
35
+ from app.api.deps import get_current_user_ws
36
+ from app.db.models.user import User
37
+ {%- endif %}
38
+ {%- if cookiecutter.websocket_auth_api_key %}
39
+ from app.core.config import settings
40
+ {%- endif %}
41
+ {%- if cookiecutter.enable_conversation_persistence and (cookiecutter.use_postgresql or cookiecutter.use_sqlite) %}
42
+ from app.db.session import get_db_session
43
+ from app.api.deps import ConversationSvc, get_conversation_service
44
+ from app.schemas.conversation import ConversationCreate, MessageCreate, ToolCallCreate, ToolCallComplete
45
+ {%- elif cookiecutter.enable_conversation_persistence and cookiecutter.use_mongodb %}
46
+ from app.api.deps import ConversationSvc, get_conversation_service
47
+ from app.schemas.conversation import ConversationCreate, MessageCreate, ToolCallCreate, ToolCallComplete
48
+ {%- endif %}
49
+
50
+ logger = logging.getLogger(__name__)
51
+
52
+ router = APIRouter()
53
+
54
+
55
+ class AgentConnectionManager:
56
+ """WebSocket connection manager for AI agent."""
57
+
58
+ def __init__(self) -> None:
59
+ self.active_connections: list[WebSocket] = []
60
+
61
+ async def connect(self, websocket: WebSocket) -> None:
62
+ """Accept and store a new WebSocket connection."""
63
+ await websocket.accept()
64
+ self.active_connections.append(websocket)
65
+ logger.info(f"Agent WebSocket connected. Total connections: {len(self.active_connections)}")
66
+
67
+ def disconnect(self, websocket: WebSocket) -> None:
68
+ """Remove a WebSocket connection."""
69
+ if websocket in self.active_connections:
70
+ self.active_connections.remove(websocket)
71
+ logger.info(f"Agent WebSocket disconnected. Total connections: {len(self.active_connections)}")
72
+
73
+ async def send_event(self, websocket: WebSocket, event_type: str, data: Any) -> bool:
74
+ """Send a JSON event to a specific WebSocket client.
75
+
76
+ Returns True if sent successfully, False if connection is closed.
77
+ """
78
+ try:
79
+ await websocket.send_json({"type": event_type, "data": data})
80
+ return True
81
+ except (WebSocketDisconnect, RuntimeError):
82
+ # Connection already closed
83
+ return False
84
+
85
+
86
+ manager = AgentConnectionManager()
87
+
88
+
89
+ def build_message_history(history: list[dict[str, str]]) -> list[ModelRequest | ModelResponse]:
90
+ """Convert conversation history to PydanticAI message format."""
91
+ model_history: list[ModelRequest | ModelResponse] = []
92
+
93
+ for msg in history:
94
+ if msg["role"] == "user":
95
+ model_history.append(ModelRequest(parts=[UserPromptPart(content=msg["content"])]))
96
+ elif msg["role"] == "assistant":
97
+ model_history.append(ModelResponse(parts=[TextPart(content=msg["content"])]))
98
+ elif msg["role"] == "system":
99
+ model_history.append(ModelRequest(parts=[SystemPromptPart(content=msg["content"])]))
100
+
101
+ return model_history
102
+
103
+ {%- if cookiecutter.websocket_auth_api_key %}
104
+
105
+
106
+ async def verify_api_key(api_key: str) -> bool:
107
+ """Verify the API key for WebSocket authentication."""
108
+ return api_key == settings.API_KEY
109
+ {%- endif %}
110
+
111
+
112
+ @router.websocket("/ws/agent")
113
+ async def agent_websocket(
114
+ websocket: WebSocket,
115
+ {%- if cookiecutter.websocket_auth_jwt %}
116
+ user: User = Depends(get_current_user_ws),
117
+ {%- elif cookiecutter.websocket_auth_api_key %}
118
+ api_key: str = Query(..., alias="api_key"),
119
+ {%- endif %}
120
+ ) -> None:
121
+ """WebSocket endpoint for AI agent with full event streaming.
122
+
123
+ Uses PydanticAI iter() to stream all agent events including:
124
+ - user_prompt: When user input is received
125
+ - model_request_start: When model request begins
126
+ - text_delta: Streaming text from the model
127
+ - tool_call_delta: Streaming tool call arguments
128
+ - tool_call: When a tool is called (with full args)
129
+ - tool_result: When a tool returns a result
130
+ - final_result: When the final result is ready
131
+ - complete: When processing is complete
132
+ - error: When an error occurs
133
+
134
+ Expected input message format:
135
+ {
136
+ "message": "user message here",
137
+ "history": [{"role": "user|assistant|system", "content": "..."}]{% if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %},
138
+ "conversation_id": "optional-uuid-to-continue-existing-conversation"{% endif %}
139
+ }
140
+ {%- if cookiecutter.websocket_auth_jwt %}
141
+
142
+ Authentication: Requires a valid JWT token passed as a query parameter or header.
143
+ {%- elif cookiecutter.websocket_auth_api_key %}
144
+
145
+ Authentication: Requires a valid API key passed as 'api_key' query parameter.
146
+ Example: ws://localhost:{{ cookiecutter.backend_port }}/api/v1/ws/agent?api_key=your-api-key
147
+ {%- endif %}
148
+ {%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
149
+
150
+ Persistence: Set 'conversation_id' to continue an existing conversation.
151
+ If not provided, a new conversation is created. The conversation_id is
152
+ returned in the 'conversation_started' event.
153
+ {%- endif %}
154
+ """
155
+ {%- if cookiecutter.websocket_auth_api_key %}
156
+ # Verify API key before accepting connection
157
+ if not await verify_api_key(api_key):
158
+ await websocket.close(code=4001, reason="Invalid API key")
159
+ return
160
+ {%- endif %}
161
+
162
+ await manager.connect(websocket)
163
+
164
+ # Conversation state per connection
165
+ conversation_history: list[dict[str, str]] = []
166
+ deps = Deps()
167
+ {%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
168
+ current_conversation_id: str | None = None
169
+ {%- endif %}
170
+
171
+ try:
172
+ while True:
173
+ # Receive user message
174
+ data = await websocket.receive_json()
175
+ user_message = data.get("message", "")
176
+ # Optionally accept history from client (or use server-side tracking)
177
+ if "history" in data:
178
+ conversation_history = data["history"]
179
+
180
+ if not user_message:
181
+ await manager.send_event(websocket, "error", {"message": "Empty message"})
182
+ continue
183
+
184
+ {%- if cookiecutter.enable_conversation_persistence and (cookiecutter.use_postgresql or cookiecutter.use_sqlite) %}
185
+
186
+ # Handle conversation persistence
187
+ db_gen = get_db_session()
188
+ db = await anext(db_gen) if hasattr(db_gen, "__anext__") else next(db_gen)
189
+ try:
190
+ conv_service = get_conversation_service(db)
191
+
192
+ # Get or create conversation
193
+ requested_conv_id = data.get("conversation_id")
194
+ if requested_conv_id:
195
+ {%- if cookiecutter.use_postgresql %}
196
+ current_conversation_id = requested_conv_id
197
+ # Verify conversation exists
198
+ await conv_service.get_conversation(UUID(requested_conv_id))
199
+ {%- else %}
200
+ current_conversation_id = requested_conv_id
201
+ conv_service.get_conversation(requested_conv_id)
202
+ {%- endif %}
203
+ elif not current_conversation_id:
204
+ # Create new conversation
205
+ conv_data = ConversationCreate(
206
+ {%- if cookiecutter.use_jwt %}
207
+ user_id={% if cookiecutter.use_postgresql %}user.id{% else %}str(user.id){% endif %},
208
+ {%- endif %}
209
+ title=user_message[:50] if len(user_message) > 50 else user_message,
210
+ )
211
+ {%- if cookiecutter.use_postgresql %}
212
+ conversation = await conv_service.create_conversation(conv_data)
213
+ {%- else %}
214
+ conversation = conv_service.create_conversation(conv_data)
215
+ {%- endif %}
216
+ current_conversation_id = str(conversation.id)
217
+ await manager.send_event(
218
+ websocket,
219
+ "conversation_started",
220
+ {"conversation_id": current_conversation_id},
221
+ )
222
+
223
+ # Save user message
224
+ {%- if cookiecutter.use_postgresql %}
225
+ user_msg = await conv_service.add_message(
226
+ UUID(current_conversation_id),
227
+ MessageCreate(role="user", content=user_message),
228
+ )
229
+ {%- else %}
230
+ user_msg = conv_service.add_message(
231
+ current_conversation_id,
232
+ MessageCreate(role="user", content=user_message),
233
+ )
234
+ {%- endif %}
235
+ await db.commit()
236
+ except Exception as e:
237
+ logger.warning(f"Failed to persist conversation: {e}")
238
+ # Continue without persistence
239
+ {%- elif cookiecutter.enable_conversation_persistence and cookiecutter.use_mongodb %}
240
+
241
+ # Handle conversation persistence (MongoDB)
242
+ conv_service = get_conversation_service()
243
+
244
+ requested_conv_id = data.get("conversation_id")
245
+ if requested_conv_id:
246
+ current_conversation_id = requested_conv_id
247
+ await conv_service.get_conversation(requested_conv_id)
248
+ elif not current_conversation_id:
249
+ conv_data = ConversationCreate(
250
+ {%- if cookiecutter.use_jwt %}
251
+ user_id=str(user.id),
252
+ {%- endif %}
253
+ title=user_message[:50] if len(user_message) > 50 else user_message,
254
+ )
255
+ conversation = await conv_service.create_conversation(conv_data)
256
+ current_conversation_id = str(conversation.id)
257
+ await manager.send_event(
258
+ websocket,
259
+ "conversation_started",
260
+ {"conversation_id": current_conversation_id},
261
+ )
262
+
263
+ # Save user message
264
+ user_msg = await conv_service.add_message(
265
+ current_conversation_id,
266
+ MessageCreate(role="user", content=user_message),
267
+ )
268
+ {%- endif %}
269
+
270
+ await manager.send_event(websocket, "user_prompt", {"content": user_message})
271
+
272
+ try:
273
+ assistant = get_agent()
274
+ model_history = build_message_history(conversation_history)
275
+
276
+ # Use iter() on the underlying PydanticAI agent to stream all events
277
+ async with assistant.agent.iter(
278
+ user_message,
279
+ deps=deps,
280
+ message_history=model_history,
281
+ ) as agent_run:
282
+ async for node in agent_run:
283
+ if Agent.is_user_prompt_node(node):
284
+ await manager.send_event(
285
+ websocket,
286
+ "user_prompt_processed",
287
+ {"prompt": node.user_prompt},
288
+ )
289
+
290
+ elif Agent.is_model_request_node(node):
291
+ await manager.send_event(websocket, "model_request_start", {})
292
+
293
+ async with node.stream(agent_run.ctx) as request_stream:
294
+ async for event in request_stream:
295
+ if isinstance(event, PartStartEvent):
296
+ await manager.send_event(
297
+ websocket,
298
+ "part_start",
299
+ {
300
+ "index": event.index,
301
+ "part_type": type(event.part).__name__,
302
+ },
303
+ )
304
+ # Send initial content from TextPart if present
305
+ if isinstance(event.part, TextPart) and event.part.content:
306
+ await manager.send_event(
307
+ websocket,
308
+ "text_delta",
309
+ {
310
+ "index": event.index,
311
+ "content": event.part.content,
312
+ },
313
+ )
314
+
315
+ elif isinstance(event, PartDeltaEvent):
316
+ if isinstance(event.delta, TextPartDelta):
317
+ await manager.send_event(
318
+ websocket,
319
+ "text_delta",
320
+ {
321
+ "index": event.index,
322
+ "content": event.delta.content_delta,
323
+ },
324
+ )
325
+ elif isinstance(event.delta, ToolCallPartDelta):
326
+ await manager.send_event(
327
+ websocket,
328
+ "tool_call_delta",
329
+ {
330
+ "index": event.index,
331
+ "args_delta": event.delta.args_delta,
332
+ },
333
+ )
334
+
335
+ elif isinstance(event, FinalResultEvent):
336
+ await manager.send_event(
337
+ websocket,
338
+ "final_result_start",
339
+ {"tool_name": event.tool_name},
340
+ )
341
+
342
+ elif Agent.is_call_tools_node(node):
343
+ await manager.send_event(websocket, "call_tools_start", {})
344
+
345
+ async with node.stream(agent_run.ctx) as handle_stream:
346
+ async for event in handle_stream:
347
+ if isinstance(event, FunctionToolCallEvent):
348
+ await manager.send_event(
349
+ websocket,
350
+ "tool_call",
351
+ {
352
+ "tool_name": event.part.tool_name,
353
+ "args": event.part.args,
354
+ "tool_call_id": event.part.tool_call_id,
355
+ },
356
+ )
357
+
358
+ elif isinstance(event, FunctionToolResultEvent):
359
+ await manager.send_event(
360
+ websocket,
361
+ "tool_result",
362
+ {
363
+ "tool_call_id": event.tool_call_id,
364
+ "content": str(event.result.content),
365
+ },
366
+ )
367
+
368
+ elif Agent.is_end_node(node) and agent_run.result is not None:
369
+ await manager.send_event(
370
+ websocket,
371
+ "final_result",
372
+ {"output": agent_run.result.output},
373
+ )
374
+
375
+ # Update conversation history
376
+ conversation_history.append({"role": "user", "content": user_message})
377
+ if agent_run.result:
378
+ conversation_history.append(
379
+ {"role": "assistant", "content": agent_run.result.output}
380
+ )
381
+
382
+ {%- if cookiecutter.enable_conversation_persistence and (cookiecutter.use_postgresql or cookiecutter.use_sqlite) %}
383
+
384
+ # Save assistant response to database
385
+ if current_conversation_id and agent_run.result:
386
+ try:
387
+ {%- if cookiecutter.use_postgresql %}
388
+ await conv_service.add_message(
389
+ UUID(current_conversation_id),
390
+ MessageCreate(
391
+ role="assistant",
392
+ content=agent_run.result.output,
393
+ model_name=assistant.model_name if hasattr(assistant, "model_name") else None,
394
+ ),
395
+ )
396
+ await db.commit()
397
+ {%- else %}
398
+ conv_service.add_message(
399
+ current_conversation_id,
400
+ MessageCreate(
401
+ role="assistant",
402
+ content=agent_run.result.output,
403
+ model_name=assistant.model_name if hasattr(assistant, "model_name") else None,
404
+ ),
405
+ )
406
+ db.commit()
407
+ {%- endif %}
408
+ except Exception as e:
409
+ logger.warning(f"Failed to persist assistant response: {e}")
410
+ {%- elif cookiecutter.enable_conversation_persistence and cookiecutter.use_mongodb %}
411
+
412
+ # Save assistant response to database
413
+ if current_conversation_id and agent_run.result:
414
+ try:
415
+ await conv_service.add_message(
416
+ current_conversation_id,
417
+ MessageCreate(
418
+ role="assistant",
419
+ content=agent_run.result.output,
420
+ model_name=assistant.model_name if hasattr(assistant, "model_name") else None,
421
+ ),
422
+ )
423
+ except Exception as e:
424
+ logger.warning(f"Failed to persist assistant response: {e}")
425
+ {%- endif %}
426
+
427
+ await manager.send_event(websocket, "complete", {
428
+ {%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
429
+ "conversation_id": current_conversation_id,
430
+ {%- endif %}
431
+ })
432
+
433
+ except WebSocketDisconnect:
434
+ # Client disconnected during processing - this is normal
435
+ logger.info("Client disconnected during agent processing")
436
+ break
437
+ except Exception as e:
438
+ logger.exception(f"Error processing agent request: {e}")
439
+ # Try to send error, but don't fail if connection is closed
440
+ await manager.send_event(websocket, "error", {"message": str(e)})
441
+
442
+ except WebSocketDisconnect:
443
+ pass # Normal disconnect
444
+ finally:
445
+ manager.disconnect(websocket)
446
+ {%- else %}
447
+ """AI Agent routes - not configured."""
448
+ {%- endif %}