fastapi-fullstack 0.1.7__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 (241) hide show
  1. fastapi_fullstack-0.1.7.dist-info/METADATA +739 -0
  2. fastapi_fullstack-0.1.7.dist-info/RECORD +241 -0
  3. fastapi_fullstack-0.1.7.dist-info/WHEEL +4 -0
  4. fastapi_fullstack-0.1.7.dist-info/entry_points.txt +2 -0
  5. fastapi_fullstack-0.1.7.dist-info/licenses/LICENSE +21 -0
  6. fastapi_gen/__init__.py +3 -0
  7. fastapi_gen/cli.py +442 -0
  8. fastapi_gen/config.py +356 -0
  9. fastapi_gen/generator.py +207 -0
  10. fastapi_gen/prompts.py +874 -0
  11. fastapi_gen/template/VARIABLES.md +276 -0
  12. fastapi_gen/template/cookiecutter.json +93 -0
  13. fastapi_gen/template/hooks/post_gen_project.py +355 -0
  14. fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example +56 -0
  15. fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
  16. fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +109 -0
  17. fastapi_gen/template/{{cookiecutter.project_slug}}/AGENTS.md +55 -0
  18. fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +99 -0
  19. fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +315 -0
  20. fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +768 -0
  21. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
  22. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example +155 -0
  23. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
  24. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
  25. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
  26. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
  27. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
  28. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
  29. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
  30. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +447 -0
  31. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +23 -0
  32. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +226 -0
  33. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langchain_assistant.py +226 -0
  34. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/prompts.py +10 -0
  35. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
  36. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
  37. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
  38. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +541 -0
  39. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +98 -0
  40. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
  41. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
  42. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
  43. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +902 -0
  44. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
  45. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +498 -0
  46. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
  47. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
  48. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
  49. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
  50. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
  51. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
  52. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
  53. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
  54. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
  55. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
  56. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
  57. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
  58. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
  59. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
  60. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
  61. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
  62. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +267 -0
  63. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
  64. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
  65. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
  66. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
  67. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
  68. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
  69. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
  70. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
  71. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
  72. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
  73. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
  74. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
  75. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
  76. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
  77. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
  78. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
  79. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +130 -0
  80. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +334 -0
  81. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
  82. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
  83. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
  84. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
  85. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +838 -0
  86. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
  87. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
  88. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
  89. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
  90. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
  91. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
  92. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +192 -0
  93. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
  94. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
  95. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
  96. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
  97. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
  98. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
  99. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +850 -0
  100. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
  101. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
  102. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
  103. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
  104. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
  105. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
  106. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
  107. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
  108. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
  109. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
  110. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
  111. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
  112. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
  113. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +180 -0
  114. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
  115. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
  116. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
  117. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
  118. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
  119. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
  120. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
  121. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
  122. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
  123. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_admin.py +890 -0
  124. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +261 -0
  125. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
  126. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
  127. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
  128. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
  129. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
  130. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
  131. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
  132. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
  133. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
  134. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
  135. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +435 -0
  136. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
  137. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/adding_features.md +132 -0
  138. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/architecture.md +63 -0
  139. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/patterns.md +161 -0
  140. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/testing.md +120 -0
  141. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.dockerignore +40 -0
  142. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
  143. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
  144. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
  145. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
  146. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
  147. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +648 -0
  148. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
  149. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
  150. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
  151. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
  152. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
  153. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
  154. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
  155. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
  156. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +69 -0
  157. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
  158. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
  159. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/layout.tsx +11 -0
  160. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/login/page.tsx +5 -0
  161. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/register/page.tsx +5 -0
  162. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/chat/page.tsx +48 -0
  163. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/dashboard/page.tsx +99 -0
  164. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/layout.tsx +17 -0
  165. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/profile/page.tsx +152 -0
  166. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/auth/callback/page.tsx +113 -0
  167. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/layout.tsx +46 -0
  168. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/page.tsx +73 -0
  169. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
  170. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
  171. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
  172. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
  173. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
  174. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
  175. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
  176. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
  177. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
  178. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
  179. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +323 -0
  180. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +22 -0
  181. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
  182. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
  183. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
  184. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
  185. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +234 -0
  186. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +72 -0
  187. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +328 -0
  188. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/copy-button.tsx +46 -0
  189. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +11 -0
  190. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/local-conversation-sidebar.tsx +295 -0
  191. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/markdown-content.tsx +167 -0
  192. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +79 -0
  193. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
  194. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +79 -0
  195. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
  196. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
  197. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
  198. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +65 -0
  199. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
  200. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +82 -0
  201. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
  202. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
  203. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +105 -0
  204. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
  205. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
  206. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +56 -0
  207. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
  208. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +13 -0
  209. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
  210. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
  211. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/sheet.tsx +109 -0
  212. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +7 -0
  213. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
  214. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
  215. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +181 -0
  216. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-local-chat.ts +165 -0
  217. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
  218. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +37 -0
  219. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
  220. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
  221. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
  222. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
  223. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
  224. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +31 -0
  225. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
  226. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +64 -0
  227. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-sidebar-store.ts +17 -0
  228. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
  229. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
  230. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +9 -0
  231. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/local-chat-store.ts +255 -0
  232. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/sidebar-store.ts +17 -0
  233. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
  234. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
  235. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
  236. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +83 -0
  237. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
  238. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
  239. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
  240. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
  241. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.setup.ts +56 -0
@@ -0,0 +1,447 @@
1
+ {%- if cookiecutter.enable_admin_panel and cookiecutter.use_postgresql %}
2
+ """SQLAdmin configuration with automatic model discovery."""
3
+
4
+ from typing import Any, ClassVar
5
+
6
+ from sqlalchemy import String, inspect
7
+ from sqlalchemy.engine import Engine
8
+ from sqlalchemy.orm import DeclarativeBase
9
+ from sqladmin import Admin, ModelView
10
+ {%- if cookiecutter.admin_require_auth %}
11
+ from sqladmin.authentication import AuthenticationBackend
12
+ from starlette.requests import Request
13
+ {%- endif %}
14
+
15
+ from app.core.config import settings
16
+ {%- if cookiecutter.admin_require_auth %}
17
+ from app.core.security import verify_password
18
+ {%- endif %}
19
+ from app.db.base import Base
20
+ from app.db.models.user import User
21
+ {%- if cookiecutter.enable_session_management %}
22
+ from app.db.models.session import Session
23
+ {%- endif %}
24
+ {%- if cookiecutter.include_example_crud %}
25
+ from app.db.models.item import Item
26
+ {%- endif %}
27
+ {%- if cookiecutter.enable_conversation_persistence %}
28
+ from app.db.models.conversation import Conversation, Message, ToolCall
29
+ {%- endif %}
30
+ {%- if cookiecutter.enable_webhooks %}
31
+ from app.db.models.webhook import Webhook, WebhookDelivery
32
+ {%- endif %}
33
+
34
+
35
+ # Columns that should be excluded from forms (sensitive data)
36
+ SENSITIVE_COLUMN_PATTERNS: list[str] = [
37
+ "password",
38
+ "hashed_password",
39
+ "secret",
40
+ "token",
41
+ "api_key",
42
+ "refresh_token",
43
+ ]
44
+
45
+ # Columns that should be searchable by default (string columns)
46
+ SEARCHABLE_COLUMN_TYPES: tuple[type, ...] = (String,)
47
+
48
+ # Columns that are auto-generated and should be excluded from create/edit forms
49
+ AUTO_GENERATED_COLUMNS: list[str] = [
50
+ "created_at",
51
+ "updated_at",
52
+ ]
53
+
54
+ # Model icons mapping (model name -> Font Awesome icon)
55
+ MODEL_ICONS: dict[str, str] = {
56
+ "User": "fa-solid fa-user",
57
+ "Session": "fa-solid fa-key",
58
+ "Item": "fa-solid fa-box",
59
+ "Conversation": "fa-solid fa-comments",
60
+ "Message": "fa-solid fa-message",
61
+ "ToolCall": "fa-solid fa-wrench",
62
+ "Webhook": "fa-solid fa-link",
63
+ "WebhookDelivery": "fa-solid fa-paper-plane",
64
+ }
65
+
66
+
67
+ def discover_models(base: type[DeclarativeBase]) -> list[type]:
68
+ """Discover all SQLAlchemy models registered with the given Base.
69
+
70
+ Args:
71
+ base: The SQLAlchemy DeclarativeBase class.
72
+
73
+ Returns:
74
+ List of model classes that inherit from the Base.
75
+ """
76
+ return [mapper.class_ for mapper in base.registry.mappers]
77
+
78
+
79
+ def get_model_columns(model: type) -> list[str]:
80
+ """Get all column names from a SQLAlchemy model.
81
+
82
+ Args:
83
+ model: The SQLAlchemy model class.
84
+
85
+ Returns:
86
+ List of column names.
87
+ """
88
+ mapper = inspect(model)
89
+ return [column.key for column in mapper.columns]
90
+
91
+
92
+ def get_searchable_columns(model: type) -> list[str]:
93
+ """Get columns suitable for searching (String type columns).
94
+
95
+ Args:
96
+ model: The SQLAlchemy model class.
97
+
98
+ Returns:
99
+ List of searchable column names.
100
+ """
101
+ mapper = inspect(model)
102
+ searchable = []
103
+ for column in mapper.columns:
104
+ # Include String columns that are not sensitive
105
+ is_searchable_type = isinstance(column.type, SEARCHABLE_COLUMN_TYPES)
106
+ is_sensitive = any(pattern in column.key.lower() for pattern in SENSITIVE_COLUMN_PATTERNS)
107
+ if is_searchable_type and not is_sensitive:
108
+ searchable.append(column.key)
109
+ return searchable
110
+
111
+
112
+ def get_sortable_columns(model: type) -> list[str]:
113
+ """Get columns suitable for sorting.
114
+
115
+ Args:
116
+ model: The SQLAlchemy model class.
117
+
118
+ Returns:
119
+ List of sortable column names.
120
+ """
121
+ mapper = inspect(model)
122
+ return [column.key for column in mapper.columns]
123
+
124
+
125
+ def get_form_excluded_columns(model: type) -> list[str]:
126
+ """Get columns that should be excluded from create/edit forms.
127
+
128
+ Excludes sensitive columns and auto-generated columns.
129
+
130
+ Args:
131
+ model: The SQLAlchemy model class.
132
+
133
+ Returns:
134
+ List of column names to exclude from forms.
135
+ """
136
+ excluded = []
137
+ for column_name in get_model_columns(model):
138
+ # Exclude sensitive columns
139
+ if any(pattern in column_name.lower() for pattern in SENSITIVE_COLUMN_PATTERNS):
140
+ excluded.append(column_name)
141
+ # Exclude auto-generated columns
142
+ elif column_name in AUTO_GENERATED_COLUMNS:
143
+ excluded.append(column_name)
144
+ return excluded
145
+
146
+
147
+ def pluralize(name: str) -> str:
148
+ """Simple pluralization for model names.
149
+
150
+ Args:
151
+ name: Singular name.
152
+
153
+ Returns:
154
+ Pluralized name.
155
+ """
156
+ if name.endswith("y"):
157
+ return name[:-1] + "ies"
158
+ elif name.endswith("s") or name.endswith("x") or name.endswith("ch") or name.endswith("sh"):
159
+ return name + "es"
160
+ return name + "s"
161
+
162
+
163
+ def create_model_admin(
164
+ model: type,
165
+ *,
166
+ name: str | None = None,
167
+ name_plural: str | None = None,
168
+ icon: str | None = None,
169
+ column_list: list[Any] | None = None,
170
+ column_searchable_list: list[Any] | None = None,
171
+ column_sortable_list: list[Any] | None = None,
172
+ form_excluded_columns: list[Any] | None = None,
173
+ can_create: bool = True,
174
+ can_edit: bool = True,
175
+ can_delete: bool = True,
176
+ can_view_details: bool = True,
177
+ ) -> type[ModelView]:
178
+ """Dynamically create a ModelView class for a SQLAlchemy model.
179
+
180
+ Args:
181
+ model: The SQLAlchemy model class.
182
+ name: Display name (defaults to model class name).
183
+ name_plural: Plural display name (defaults to auto-pluralized name).
184
+ icon: Font Awesome icon class.
185
+ column_list: Columns to display in list view.
186
+ column_searchable_list: Columns to enable search on.
187
+ column_sortable_list: Columns to enable sorting on.
188
+ form_excluded_columns: Columns to exclude from forms.
189
+ can_create: Allow creating new records.
190
+ can_edit: Allow editing records.
191
+ can_delete: Allow deleting records.
192
+ can_view_details: Allow viewing record details.
193
+
194
+ Returns:
195
+ A dynamically created ModelView subclass.
196
+ """
197
+ import types
198
+
199
+ model_name = model.__name__
200
+
201
+ # Use provided values or generate defaults
202
+ _name = name or model_name
203
+ _name_plural = name_plural or pluralize(_name)
204
+ _icon = icon or MODEL_ICONS.get(model_name, "fa-solid fa-database")
205
+
206
+ # Get column attributes from the model
207
+ _column_list = column_list
208
+ if _column_list is None:
209
+ columns = get_model_columns(model)
210
+ _column_list = [getattr(model, col) for col in columns if hasattr(model, col)]
211
+
212
+ _column_searchable_list = column_searchable_list
213
+ if _column_searchable_list is None:
214
+ searchable = get_searchable_columns(model)
215
+ _column_searchable_list = [getattr(model, col) for col in searchable if hasattr(model, col)]
216
+
217
+ _column_sortable_list = column_sortable_list
218
+ if _column_sortable_list is None:
219
+ sortable = get_sortable_columns(model)
220
+ _column_sortable_list = [getattr(model, col) for col in sortable if hasattr(model, col)]
221
+
222
+ _form_excluded_columns = form_excluded_columns
223
+ if _form_excluded_columns is None:
224
+ excluded = get_form_excluded_columns(model)
225
+ _form_excluded_columns = [getattr(model, col) for col in excluded if hasattr(model, col)]
226
+
227
+ # Create class attributes in the exec_body callback
228
+ def exec_body(ns: dict[str, Any]) -> None:
229
+ ns["name"] = _name
230
+ ns["name_plural"] = _name_plural
231
+ ns["icon"] = _icon
232
+ ns["column_list"] = _column_list
233
+ ns["column_searchable_list"] = _column_searchable_list
234
+ ns["column_sortable_list"] = _column_sortable_list
235
+ ns["form_excluded_columns"] = _form_excluded_columns
236
+ ns["can_create"] = can_create
237
+ ns["can_edit"] = can_edit
238
+ ns["can_delete"] = can_delete
239
+ ns["can_view_details"] = can_view_details
240
+ # Add ClassVar type hints for sqladmin compatibility
241
+ ns["__annotations__"] = {
242
+ "column_list": ClassVar,
243
+ "column_searchable_list": ClassVar,
244
+ "column_sortable_list": ClassVar,
245
+ "form_excluded_columns": ClassVar,
246
+ "can_create": ClassVar,
247
+ "can_edit": ClassVar,
248
+ "can_delete": ClassVar,
249
+ "can_view_details": ClassVar,
250
+ }
251
+
252
+ # Create the class using types.new_class to properly pass model kwarg to metaclass
253
+ class_name = f"{model_name}Admin"
254
+ admin_class = types.new_class(
255
+ class_name,
256
+ (ModelView,),
257
+ {"model": model}, # Pass model to metaclass
258
+ exec_body,
259
+ )
260
+
261
+ return admin_class # type: ignore[return-value]
262
+
263
+
264
+ def register_models_auto(
265
+ admin: Admin,
266
+ base: type[DeclarativeBase],
267
+ *,
268
+ exclude_models: list[type] | None = None,
269
+ custom_configs: dict[type, dict[str, Any]] | None = None,
270
+ ) -> list[type[ModelView]]:
271
+ """Auto-discover and register all models with the admin panel.
272
+
273
+ Args:
274
+ admin: The SQLAdmin instance.
275
+ base: The SQLAlchemy DeclarativeBase class.
276
+ exclude_models: Models to exclude from auto-registration.
277
+ custom_configs: Custom configuration overrides per model.
278
+
279
+ Returns:
280
+ List of registered ModelView classes.
281
+ """
282
+ exclude_models = exclude_models or []
283
+ custom_configs = custom_configs or {}
284
+
285
+ registered_views: list[type[ModelView]] = []
286
+ models = discover_models(base)
287
+
288
+ for model in models:
289
+ if model in exclude_models:
290
+ continue
291
+
292
+ # Get custom config for this model if provided
293
+ config = custom_configs.get(model, {})
294
+
295
+ # Create and register the admin view
296
+ admin_class = create_model_admin(model, **config)
297
+ admin.add_view(admin_class)
298
+ registered_views.append(admin_class)
299
+
300
+ return registered_views
301
+
302
+
303
+ # SQLAdmin requires a synchronous engine
304
+ _sync_engine: Engine | None = None
305
+
306
+
307
+ def get_sync_engine() -> Engine:
308
+ """Get or create the synchronous engine for SQLAdmin."""
309
+ global _sync_engine
310
+ if _sync_engine is None:
311
+ from sqlalchemy import create_engine
312
+
313
+ _sync_engine = create_engine(settings.DATABASE_URL_SYNC, echo=settings.DEBUG)
314
+ return _sync_engine
315
+
316
+
317
+ {%- if cookiecutter.admin_require_auth %}
318
+
319
+
320
+ class AdminAuth(AuthenticationBackend):
321
+ """Admin panel authentication backend.
322
+
323
+ Requires superuser credentials to access the admin panel.
324
+ """
325
+
326
+ async def login(self, request: Request) -> bool:
327
+ """Validate admin login credentials."""
328
+ form = await request.form()
329
+ email = form.get("username")
330
+ password = form.get("password")
331
+
332
+ if not email or not password:
333
+ return False
334
+
335
+ # Get user from database
336
+ from sqlalchemy.orm import Session as DBSession
337
+
338
+ with DBSession(get_sync_engine()) as session:
339
+ user = session.query(User).filter(User.email == email).first()
340
+
341
+ if (
342
+ user
343
+ and verify_password(str(password), user.hashed_password)
344
+ and user.is_superuser
345
+ ):
346
+ # Store user info in session
347
+ request.session["admin_user_id"] = str(user.id)
348
+ request.session["admin_email"] = user.email
349
+ return True
350
+
351
+ return False
352
+
353
+ async def logout(self, request: Request) -> bool:
354
+ """Clear admin session."""
355
+ request.session.clear()
356
+ return True
357
+
358
+ async def authenticate(self, request: Request) -> bool:
359
+ """Check if user is authenticated."""
360
+ admin_user_id = request.session.get("admin_user_id")
361
+ if not admin_user_id:
362
+ return False
363
+
364
+ # Verify user still exists and is superuser
365
+ from sqlalchemy.orm import Session as DBSession
366
+
367
+ with DBSession(get_sync_engine()) as session:
368
+ user = session.query(User).filter(User.id == admin_user_id).first()
369
+ if user and user.is_superuser and user.is_active:
370
+ return True
371
+
372
+ # User no longer valid, clear session
373
+ request.session.clear()
374
+ return False
375
+ {%- endif %}
376
+
377
+
378
+ CUSTOM_MODEL_CONFIGS: dict[type, dict[str, Any]] = {
379
+ User: {
380
+ "icon": "fa-solid fa-user",
381
+ "form_excluded_columns": [User.hashed_password, User.created_at, User.updated_at],
382
+ },
383
+ {%- if cookiecutter.enable_session_management %}
384
+ Session: {
385
+ "icon": "fa-solid fa-key",
386
+ "form_excluded_columns": [Session.refresh_token_hash],
387
+ "can_create": False, # Sessions are created via login
388
+ },
389
+ {%- endif %}
390
+ {%- if cookiecutter.enable_conversation_persistence %}
391
+ ToolCall: {
392
+ "icon": "fa-solid fa-wrench",
393
+ "can_create": False, # Tool calls are created by the agent
394
+ },
395
+ {%- endif %}
396
+ {%- if cookiecutter.enable_webhooks %}
397
+ Webhook: {
398
+ "icon": "fa-solid fa-link",
399
+ "form_excluded_columns": [Webhook.secret],
400
+ },
401
+ WebhookDelivery: {
402
+ "icon": "fa-solid fa-paper-plane",
403
+ "can_create": False, # Deliveries are created by webhook dispatch
404
+ "can_edit": False,
405
+ },
406
+ {%- endif %}
407
+ }
408
+
409
+
410
+ def setup_admin(app) -> Admin:
411
+ """Setup SQLAdmin for the FastAPI app with automatic model discovery.
412
+
413
+ Automatically discovers all SQLAlchemy models from the Base registry
414
+ and creates admin views for them with sensible defaults.
415
+
416
+ Custom configurations can be provided in CUSTOM_MODEL_CONFIGS to override
417
+ default behavior for specific models.
418
+ """
419
+ sync_engine = get_sync_engine()
420
+
421
+ {%- if cookiecutter.admin_require_auth %}
422
+ authentication_backend = AdminAuth(secret_key=settings.SECRET_KEY)
423
+ admin = Admin(
424
+ app,
425
+ sync_engine,
426
+ title="{{ cookiecutter.project_name }} Admin",
427
+ authentication_backend=authentication_backend,
428
+ )
429
+ {%- else %}
430
+ admin = Admin(
431
+ app,
432
+ sync_engine,
433
+ title="{{ cookiecutter.project_name }} Admin",
434
+ )
435
+ {%- endif %}
436
+
437
+ # Auto-register all models from Base with custom configs
438
+ register_models_auto(
439
+ admin,
440
+ Base,
441
+ custom_configs=CUSTOM_MODEL_CONFIGS,
442
+ )
443
+
444
+ return admin
445
+ {%- else %}
446
+ """Admin panel - not configured."""
447
+ {%- endif %}
@@ -0,0 +1,23 @@
1
+ {%- if cookiecutter.enable_ai_agent and cookiecutter.use_pydantic_ai %}
2
+ """AI Agents module using PydanticAI.
3
+
4
+ This module contains agents that handle AI-powered interactions.
5
+ Tools are defined in the tools/ subdirectory.
6
+ """
7
+
8
+ from app.agents.assistant import AssistantAgent, Deps
9
+
10
+ __all__ = ["AssistantAgent", "Deps"]
11
+ {%- elif cookiecutter.enable_ai_agent and cookiecutter.use_langchain %}
12
+ """AI Agents module using LangChain.
13
+
14
+ This module contains agents that handle AI-powered interactions.
15
+ Tools are defined in the tools/ subdirectory.
16
+ """
17
+
18
+ from app.agents.langchain_assistant import AgentContext, AgentState, LangChainAssistant
19
+
20
+ __all__ = ["LangChainAssistant", "AgentContext", "AgentState"]
21
+ {%- else %}
22
+ """AI Agents - not configured."""
23
+ {%- endif %}
@@ -0,0 +1,226 @@
1
+ {%- if cookiecutter.enable_ai_agent and cookiecutter.use_pydantic_ai %}
2
+ """Assistant agent with PydanticAI.
3
+
4
+ The main conversational agent that can be extended with custom tools.
5
+ """
6
+
7
+ import logging
8
+ from dataclasses import dataclass, field
9
+ from typing import Any
10
+
11
+ from pydantic_ai import Agent, RunContext
12
+ from pydantic_ai.messages import (
13
+ ModelRequest,
14
+ ModelResponse,
15
+ SystemPromptPart,
16
+ TextPart,
17
+ UserPromptPart,
18
+ )
19
+ {%- if cookiecutter.use_openai %}
20
+ from pydantic_ai.models.openai import OpenAIChatModel
21
+ from pydantic_ai.providers.openai import OpenAIProvider
22
+ {%- endif %}
23
+ {%- if cookiecutter.use_anthropic %}
24
+ from pydantic_ai.models.anthropic import AnthropicModel
25
+ {%- endif %}
26
+ {%- if cookiecutter.use_openrouter %}
27
+ from pydantic_ai.models.openrouter import OpenRouterModel
28
+ from pydantic_ai.providers.openrouter import OpenRouterProvider
29
+ {%- endif %}
30
+ from pydantic_ai.settings import ModelSettings
31
+
32
+ from app.agents.prompts import DEFAULT_SYSTEM_PROMPT
33
+ from app.agents.tools import get_current_datetime
34
+ from app.core.config import settings
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+
39
+ @dataclass
40
+ class Deps:
41
+ """Dependencies for the assistant agent.
42
+
43
+ These are passed to tools via RunContext.
44
+ """
45
+
46
+ user_id: str | None = None
47
+ user_name: str | None = None
48
+ metadata: dict[str, Any] = field(default_factory=dict)
49
+
50
+
51
+ class AssistantAgent:
52
+ """Assistant agent wrapper for conversational AI.
53
+
54
+ Encapsulates agent creation and execution with tool support.
55
+ """
56
+
57
+ def __init__(
58
+ self,
59
+ model_name: str | None = None,
60
+ temperature: float | None = None,
61
+ system_prompt: str | None = None,
62
+ ):
63
+ self.model_name = model_name or settings.AI_MODEL
64
+ self.temperature = temperature or settings.AI_TEMPERATURE
65
+ self.system_prompt = system_prompt or DEFAULT_SYSTEM_PROMPT
66
+ self._agent: Agent[Deps, str] | None = None
67
+
68
+ def _create_agent(self) -> Agent[Deps, str]:
69
+ """Create and configure the PydanticAI agent."""
70
+ {%- if cookiecutter.use_openai %}
71
+ model = OpenAIChatModel(
72
+ self.model_name,
73
+ provider=OpenAIProvider(api_key=settings.OPENAI_API_KEY),
74
+ )
75
+ {%- endif %}
76
+ {%- if cookiecutter.use_anthropic %}
77
+ model = AnthropicModel(
78
+ self.model_name,
79
+ api_key=settings.ANTHROPIC_API_KEY,
80
+ )
81
+ {%- endif %}
82
+ {%- if cookiecutter.use_openrouter %}
83
+ model = OpenRouterModel(
84
+ self.model_name,
85
+ provider=OpenRouterProvider(api_key=settings.OPENROUTER_API_KEY),
86
+ )
87
+ {%- endif %}
88
+
89
+ agent = Agent[Deps, str](
90
+ model=model,
91
+ model_settings=ModelSettings(temperature=self.temperature),
92
+ system_prompt=self.system_prompt,
93
+ )
94
+
95
+ self._register_tools(agent)
96
+
97
+ return agent
98
+
99
+ def _register_tools(self, agent: Agent[Deps, str]) -> None:
100
+ """Register all tools on the agent."""
101
+
102
+ @agent.tool
103
+ async def current_datetime(ctx: RunContext[Deps]) -> str:
104
+ """Get the current date and time.
105
+
106
+ Use this tool when you need to know the current date or time.
107
+ """
108
+ return get_current_datetime()
109
+
110
+ @property
111
+ def agent(self) -> Agent[Deps, str]:
112
+ """Get or create the agent instance."""
113
+ if self._agent is None:
114
+ self._agent = self._create_agent()
115
+ return self._agent
116
+
117
+ async def run(
118
+ self,
119
+ user_input: str,
120
+ history: list[dict[str, str]] | None = None,
121
+ deps: Deps | None = None,
122
+ ) -> tuple[str, list[Any], Deps]:
123
+ """Run agent and return the output along with tool call events.
124
+
125
+ Args:
126
+ user_input: User's message.
127
+ history: Conversation history as list of {"role": "...", "content": "..."}.
128
+ deps: Optional dependencies. If not provided, a new Deps will be created.
129
+
130
+ Returns:
131
+ Tuple of (output_text, tool_events, deps).
132
+ """
133
+ model_history: list[ModelRequest | ModelResponse] = []
134
+
135
+ for msg in history or []:
136
+ if msg["role"] == "user":
137
+ model_history.append(ModelRequest(parts=[UserPromptPart(content=msg["content"])]))
138
+ elif msg["role"] == "assistant":
139
+ model_history.append(ModelResponse(parts=[TextPart(content=msg["content"])]))
140
+ elif msg["role"] == "system":
141
+ model_history.append(ModelRequest(parts=[SystemPromptPart(content=msg["content"])]))
142
+
143
+ agent_deps = deps if deps is not None else Deps()
144
+
145
+ logger.info(f"Running agent with user input: {user_input[:100]}...")
146
+ result = await self.agent.run(user_input, deps=agent_deps, message_history=model_history)
147
+
148
+ tool_events: list[Any] = []
149
+ for message in result.all_messages():
150
+ if hasattr(message, "parts"):
151
+ for part in message.parts:
152
+ if hasattr(part, "tool_name"):
153
+ tool_events.append(part)
154
+
155
+ logger.info(f"Agent run complete. Output length: {len(result.output)} chars")
156
+
157
+ return result.output, tool_events, agent_deps
158
+
159
+ async def iter(
160
+ self,
161
+ user_input: str,
162
+ history: list[dict[str, str]] | None = None,
163
+ deps: Deps | None = None,
164
+ ):
165
+ """Stream agent execution with full event access.
166
+
167
+ Args:
168
+ user_input: User's message.
169
+ history: Conversation history.
170
+ deps: Optional dependencies.
171
+
172
+ Yields:
173
+ Agent events for streaming responses.
174
+ """
175
+ model_history: list[ModelRequest | ModelResponse] = []
176
+
177
+ for msg in history or []:
178
+ if msg["role"] == "user":
179
+ model_history.append(ModelRequest(parts=[UserPromptPart(content=msg["content"])]))
180
+ elif msg["role"] == "assistant":
181
+ model_history.append(ModelResponse(parts=[TextPart(content=msg["content"])]))
182
+ elif msg["role"] == "system":
183
+ model_history.append(ModelRequest(parts=[SystemPromptPart(content=msg["content"])]))
184
+
185
+ agent_deps = deps if deps is not None else Deps()
186
+
187
+ async with self.agent.iter(
188
+ user_input,
189
+ deps=agent_deps,
190
+ message_history=model_history,
191
+ ) as run:
192
+ async for event in run:
193
+ yield event
194
+
195
+
196
+ def get_agent() -> AssistantAgent:
197
+ """Factory function to create an AssistantAgent.
198
+
199
+ Returns:
200
+ Configured AssistantAgent instance.
201
+ """
202
+ return AssistantAgent()
203
+
204
+
205
+ async def run_agent(
206
+ user_input: str,
207
+ history: list[dict[str, str]],
208
+ deps: Deps | None = None,
209
+ ) -> tuple[str, list[Any], Deps]:
210
+ """Run agent and return the output along with tool call events.
211
+
212
+ This is a convenience function for backwards compatibility.
213
+
214
+ Args:
215
+ user_input: User's message.
216
+ history: Conversation history.
217
+ deps: Optional dependencies.
218
+
219
+ Returns:
220
+ Tuple of (output_text, tool_events, deps).
221
+ """
222
+ agent = get_agent()
223
+ return await agent.run(user_input, history, deps)
224
+ {%- else %}
225
+ """PydanticAI Assistant agent - not configured."""
226
+ {%- endif %}