insforge 0.3.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 (395) hide show
  1. package/.dockerignore +58 -0
  2. package/.env.example +49 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.yml +79 -0
  6. package/.github/copilot-instructions.md +147 -0
  7. package/.github/workflows/build-image.yml +65 -0
  8. package/.github/workflows/ci-premerge-check.yml +24 -0
  9. package/.github/workflows/deploy-aws.yml +130 -0
  10. package/.github/workflows/lint-and-format.yml +33 -0
  11. package/.prettierignore +65 -0
  12. package/.prettierrc +9 -0
  13. package/CHANGELOG.md +3 -0
  14. package/CONTRIBUTING.md +126 -0
  15. package/Dockerfile +27 -0
  16. package/GITHUB_OAUTH_SETUP.md +49 -0
  17. package/GOOGLE_OAUTH_SETUP.md +148 -0
  18. package/LICENSE +201 -0
  19. package/README.md +134 -0
  20. package/assets/Dark.svg +23 -0
  21. package/assets/archDiagram.png +0 -0
  22. package/assets/banner.png +0 -0
  23. package/assets/mcpInstallv2.png +0 -0
  24. package/assets/sampleResponse.png +0 -0
  25. package/assets/signin.png +0 -0
  26. package/assets/userflow.png +0 -0
  27. package/backend/migrations/000_create-base-tables.sql +142 -0
  28. package/backend/migrations/001_create-helper-functions.sql +41 -0
  29. package/backend/migrations/002_rename-auth-tables.sql +30 -0
  30. package/backend/migrations/003_create-users-table.sql +56 -0
  31. package/backend/migrations/004_add-reload-postgrest-func.sql +24 -0
  32. package/backend/migrations/005_enable-project-admin-modify-users.sql +30 -0
  33. package/backend/migrations/006_modify-ai-usage-table.sql +25 -0
  34. package/backend/migrations/007_drop-metadata-table.sql +2 -0
  35. package/backend/migrations/008_add-system-tables.sql +77 -0
  36. package/backend/migrations/009_add-function-secrets.sql +24 -0
  37. package/backend/migrations/010_modify-ai-config-modalities.sql +93 -0
  38. package/backend/migrations/011_refactor-secrets-table.sql +15 -0
  39. package/backend/migrations/012_add-storage-uploaded-by.sql +8 -0
  40. package/backend/package.json +75 -0
  41. package/backend/src/api/middleware/auth.ts +240 -0
  42. package/backend/src/api/middleware/error.ts +231 -0
  43. package/backend/src/api/middleware/upload.ts +59 -0
  44. package/backend/src/api/routes/agent.ts +29 -0
  45. package/backend/src/api/routes/ai.ts +472 -0
  46. package/backend/src/api/routes/auth.oauth.ts +482 -0
  47. package/backend/src/api/routes/auth.ts +386 -0
  48. package/backend/src/api/routes/database.advance.ts +275 -0
  49. package/backend/src/api/routes/database.records.ts +246 -0
  50. package/backend/src/api/routes/database.tables.ts +161 -0
  51. package/backend/src/api/routes/docs.ts +66 -0
  52. package/backend/src/api/routes/functions.ts +183 -0
  53. package/backend/src/api/routes/logs.ts +150 -0
  54. package/backend/src/api/routes/metadata.ts +160 -0
  55. package/backend/src/api/routes/openapi.ts +82 -0
  56. package/backend/src/api/routes/secrets.ts +199 -0
  57. package/backend/src/api/routes/storage.ts +547 -0
  58. package/backend/src/api/routes/usage.ts +96 -0
  59. package/backend/src/core/ai/chat.ts +207 -0
  60. package/backend/src/core/ai/client.ts +242 -0
  61. package/backend/src/core/ai/config.ts +187 -0
  62. package/backend/src/core/ai/image.ts +156 -0
  63. package/backend/src/core/ai/model.ts +117 -0
  64. package/backend/src/core/ai/usage.ts +290 -0
  65. package/backend/src/core/auth/auth.ts +781 -0
  66. package/backend/src/core/auth/oauth.ts +398 -0
  67. package/backend/src/core/database/advance.ts +1074 -0
  68. package/backend/src/core/database/manager.ts +178 -0
  69. package/backend/src/core/database/table.ts +772 -0
  70. package/backend/src/core/documentation/agent.ts +689 -0
  71. package/backend/src/core/documentation/openapi.ts +856 -0
  72. package/backend/src/core/functions/functions.ts +310 -0
  73. package/backend/src/core/logs/analytics.ts +76 -0
  74. package/backend/src/core/logs/audit.ts +255 -0
  75. package/backend/src/core/logs/providers/base.provider.ts +83 -0
  76. package/backend/src/core/logs/providers/cloudwatch.provider.ts +510 -0
  77. package/backend/src/core/logs/providers/localdb.provider.ts +246 -0
  78. package/backend/src/core/secrets/encryption.ts +58 -0
  79. package/backend/src/core/secrets/secrets.ts +410 -0
  80. package/backend/src/core/socket/socket.ts +388 -0
  81. package/backend/src/core/socket/types.ts +79 -0
  82. package/backend/src/core/storage/storage.ts +923 -0
  83. package/backend/src/server.ts +288 -0
  84. package/backend/src/types/ai.ts +46 -0
  85. package/backend/src/types/auth.ts +90 -0
  86. package/backend/src/types/database.ts +136 -0
  87. package/backend/src/types/error-constants.ts +86 -0
  88. package/backend/src/types/logs.ts +47 -0
  89. package/backend/src/types/profile.ts +55 -0
  90. package/backend/src/types/storage.ts +23 -0
  91. package/backend/src/utils/cloud-token.ts +39 -0
  92. package/backend/src/utils/constants.ts +1 -0
  93. package/backend/src/utils/environment.ts +35 -0
  94. package/backend/src/utils/helpers.ts +49 -0
  95. package/backend/src/utils/logger.ts +13 -0
  96. package/backend/src/utils/response.ts +62 -0
  97. package/backend/src/utils/seed.ts +205 -0
  98. package/backend/src/utils/sql-parser.ts +63 -0
  99. package/backend/src/utils/uuid.ts +9 -0
  100. package/backend/src/utils/validations.ts +129 -0
  101. package/backend/tests/README.md +134 -0
  102. package/backend/tests/cleanup-all-test-data.sh +231 -0
  103. package/backend/tests/cloud/test-s3-multitenant.sh +132 -0
  104. package/backend/tests/local/comprehensive-curl-tests.sh +156 -0
  105. package/backend/tests/local/test-auth-router.sh +144 -0
  106. package/backend/tests/local/test-database-router.sh +222 -0
  107. package/backend/tests/local/test-e2e.sh +241 -0
  108. package/backend/tests/local/test-fk-errors.sh +97 -0
  109. package/backend/tests/local/test-id-field.sh +201 -0
  110. package/backend/tests/local/test-public-bucket.sh +265 -0
  111. package/backend/tests/local/test-secrets.sh +248 -0
  112. package/backend/tests/local/test-serverless-functions.sh.disabled +325 -0
  113. package/backend/tests/local/test-traditional-rest.sh +209 -0
  114. package/backend/tests/manual/README.md +51 -0
  115. package/backend/tests/manual/create-large-table-simple.sql +11 -0
  116. package/backend/tests/manual/seed-large-table.sql +101 -0
  117. package/backend/tests/manual/setup-large-table-extras.sql +34 -0
  118. package/backend/tests/manual/test-better-auth.sh +303 -0
  119. package/backend/tests/manual/test-bulk-upsert.sh +410 -0
  120. package/backend/tests/manual/test-database-advance.sh +297 -0
  121. package/backend/tests/manual/test-postgrest-stability.sh +192 -0
  122. package/backend/tests/manual/test-rawsql-export-import.sh +412 -0
  123. package/backend/tests/manual/test-universal-storage.sh +264 -0
  124. package/backend/tests/manual/test-users.sql +18 -0
  125. package/backend/tests/run-all-tests.sh +140 -0
  126. package/backend/tests/setup.ts +22 -0
  127. package/backend/tests/test-config.sh +303 -0
  128. package/backend/tsconfig.json +23 -0
  129. package/backend/tsup.config.ts +18 -0
  130. package/backend/vitest.config.ts +22 -0
  131. package/docker-compose.prod.yml +145 -0
  132. package/docker-compose.yml +167 -0
  133. package/docker-init/db/db-init.sql +125 -0
  134. package/docker-init/db/jwt.sql +5 -0
  135. package/docker-init/db/logs.sql +9 -0
  136. package/docker-init/db/postgresql.conf +17 -0
  137. package/docs/deprecated/insforge-auth-api.md +215 -0
  138. package/docs/deprecated/insforge-auth-sdk.md +100 -0
  139. package/docs/deprecated/insforge-db-api.md +359 -0
  140. package/docs/deprecated/insforge-db-sdk.md +140 -0
  141. package/docs/deprecated/insforge-debug-sdk.md +157 -0
  142. package/docs/deprecated/insforge-debug.md +65 -0
  143. package/docs/deprecated/insforge-instructions.md +124 -0
  144. package/docs/deprecated/insforge-project.md +118 -0
  145. package/docs/deprecated/insforge-storage-api.md +279 -0
  146. package/docs/deprecated/insforge-storage-sdk.md +159 -0
  147. package/docs/insforge-instructions-sdk.md +407 -0
  148. package/eslint.config.js +317 -0
  149. package/examples/oauth/frontend-oauth-example.html +251 -0
  150. package/examples/response-examples.md +444 -0
  151. package/frontend/README.md +112 -0
  152. package/frontend/components.json +17 -0
  153. package/frontend/index.html +13 -0
  154. package/frontend/package.json +63 -0
  155. package/frontend/public/favicon.ico +0 -0
  156. package/frontend/src/App.tsx +106 -0
  157. package/frontend/src/assets/icons/checkbox_checked.svg +6 -0
  158. package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -0
  159. package/frontend/src/assets/icons/checked.svg +3 -0
  160. package/frontend/src/assets/icons/error.svg +3 -0
  161. package/frontend/src/assets/icons/pencil.svg +4 -0
  162. package/frontend/src/assets/icons/refresh.svg +4 -0
  163. package/frontend/src/assets/icons/step_active.svg +3 -0
  164. package/frontend/src/assets/icons/step_inactive.svg +11 -0
  165. package/frontend/src/assets/icons/warning.svg +3 -0
  166. package/frontend/src/assets/logos/amazon.svg +1 -0
  167. package/frontend/src/assets/logos/claude_code.svg +3 -0
  168. package/frontend/src/assets/logos/cline.svg +6 -0
  169. package/frontend/src/assets/logos/cursor.svg +20 -0
  170. package/frontend/src/assets/logos/discord.svg +9 -0
  171. package/frontend/src/assets/logos/gemini.svg +19 -0
  172. package/frontend/src/assets/logos/github.svg +5 -0
  173. package/frontend/src/assets/logos/google.svg +13 -0
  174. package/frontend/src/assets/logos/grok.svg +10 -0
  175. package/frontend/src/assets/logos/insforge_dark.svg +15 -0
  176. package/frontend/src/assets/logos/insforge_light.svg +15 -0
  177. package/frontend/src/assets/logos/openai.svg +10 -0
  178. package/frontend/src/assets/logos/roo_code.svg +9 -0
  179. package/frontend/src/assets/logos/trae.svg +3 -0
  180. package/frontend/src/assets/logos/windsurf.svg +10 -0
  181. package/frontend/src/components/ButtonWithLoading.tsx +27 -0
  182. package/frontend/src/components/Checkbox.tsx +61 -0
  183. package/frontend/src/components/CodeBlock.tsx +32 -0
  184. package/frontend/src/components/ConfirmDialog.tsx +96 -0
  185. package/frontend/src/components/CopyButton.tsx +69 -0
  186. package/frontend/src/components/DeleteActionButton.tsx +42 -0
  187. package/frontend/src/components/EmptyState.tsx +41 -0
  188. package/frontend/src/components/ErrorState.tsx +35 -0
  189. package/frontend/src/components/FeatureSidebar.tsx +126 -0
  190. package/frontend/src/components/FeatureSidebarItem.tsx +101 -0
  191. package/frontend/src/components/JsonHighlight.tsx +61 -0
  192. package/frontend/src/components/LoadingState.tsx +16 -0
  193. package/frontend/src/components/PaginationControls.tsx +54 -0
  194. package/frontend/src/components/PromptDialog.tsx +68 -0
  195. package/frontend/src/components/SearchInput.tsx +90 -0
  196. package/frontend/src/components/SelectionClearButton.tsx +26 -0
  197. package/frontend/src/components/Stepper.tsx +139 -0
  198. package/frontend/src/components/ThemeToggle.tsx +58 -0
  199. package/frontend/src/components/TypeBadge.tsx +20 -0
  200. package/frontend/src/components/datagrid/DataGrid.tsx +264 -0
  201. package/frontend/src/components/datagrid/DefaultCellRenderer.tsx +114 -0
  202. package/frontend/src/components/datagrid/IdCell.tsx +44 -0
  203. package/frontend/src/components/datagrid/SortableHeader.tsx +74 -0
  204. package/frontend/src/components/datagrid/cell-editors/BooleanCellEditor.tsx +54 -0
  205. package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +483 -0
  206. package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +362 -0
  207. package/frontend/src/components/datagrid/cell-editors/TextCellEditor.tsx +38 -0
  208. package/frontend/src/components/datagrid/cell-editors/index.ts +14 -0
  209. package/frontend/src/components/datagrid/cell-editors/types.ts +43 -0
  210. package/frontend/src/components/datagrid/datagridTypes.tsx +72 -0
  211. package/frontend/src/components/datagrid/index.tsx +20 -0
  212. package/frontend/src/components/index.ts +39 -0
  213. package/frontend/src/components/layout/AppHeader.tsx +146 -0
  214. package/frontend/src/components/layout/AppSidebar.tsx +190 -0
  215. package/frontend/src/components/layout/CloudLayout.tsx +95 -0
  216. package/frontend/src/components/layout/Layout.tsx +43 -0
  217. package/frontend/src/components/radix/Alert.tsx +45 -0
  218. package/frontend/src/components/radix/AlertDialog.tsx +115 -0
  219. package/frontend/src/components/radix/Avatar.tsx +45 -0
  220. package/frontend/src/components/radix/Badge.tsx +33 -0
  221. package/frontend/src/components/radix/Button.tsx +50 -0
  222. package/frontend/src/components/radix/Card.tsx +58 -0
  223. package/frontend/src/components/radix/Dialog.tsx +98 -0
  224. package/frontend/src/components/radix/DropdownMenu.tsx +185 -0
  225. package/frontend/src/components/radix/Form.tsx +167 -0
  226. package/frontend/src/components/radix/Input.tsx +22 -0
  227. package/frontend/src/components/radix/Label.tsx +19 -0
  228. package/frontend/src/components/radix/Popover.tsx +29 -0
  229. package/frontend/src/components/radix/ScrollArea.tsx +44 -0
  230. package/frontend/src/components/radix/Select.tsx +151 -0
  231. package/frontend/src/components/radix/Separator.tsx +26 -0
  232. package/frontend/src/components/radix/Sheet.tsx +119 -0
  233. package/frontend/src/components/radix/Skeleton.tsx +7 -0
  234. package/frontend/src/components/radix/Switch.tsx +29 -0
  235. package/frontend/src/components/radix/Tabs.tsx +50 -0
  236. package/frontend/src/components/radix/Textarea.tsx +21 -0
  237. package/frontend/src/components/radix/Tooltip.tsx +28 -0
  238. package/frontend/src/features/ai/components/AIConfigCard.tsx +154 -0
  239. package/frontend/src/features/ai/components/AIConfigDialog.tsx +76 -0
  240. package/frontend/src/features/ai/components/AIConfigForm.tsx +222 -0
  241. package/frontend/src/features/ai/components/AIEmptyState.tsx +18 -0
  242. package/frontend/src/features/ai/components/fields/ModalityField.tsx +87 -0
  243. package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +134 -0
  244. package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +33 -0
  245. package/frontend/src/features/ai/helpers.ts +155 -0
  246. package/frontend/src/features/ai/hooks/useAIConfigs.ts +221 -0
  247. package/frontend/src/features/ai/hooks/useAIUsage.ts +77 -0
  248. package/frontend/src/features/ai/page/AIPage.tsx +178 -0
  249. package/frontend/src/features/ai/services/ai.service.ts +148 -0
  250. package/frontend/src/features/auth/components/AddOAuthDialog.tsx +106 -0
  251. package/frontend/src/features/auth/components/AuthMethodTab.tsx +238 -0
  252. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +303 -0
  253. package/frontend/src/features/auth/components/OAuthEmptyState.tsx +15 -0
  254. package/frontend/src/features/auth/components/UserFormDialog.tsx +248 -0
  255. package/frontend/src/features/auth/components/UsersDataGrid.tsx +183 -0
  256. package/frontend/src/features/auth/components/UsersTab.tsx +114 -0
  257. package/frontend/src/features/auth/hooks/useOAuthConfig.ts +129 -0
  258. package/frontend/src/features/auth/hooks/useUsers.ts +57 -0
  259. package/frontend/src/features/auth/index.ts +9 -0
  260. package/frontend/src/features/auth/page/AuthenticationPage.tsx +169 -0
  261. package/frontend/src/features/auth/services/auth.service.ts +112 -0
  262. package/frontend/src/features/auth/services/oauth.service.ts +49 -0
  263. package/frontend/src/features/dashboard/page/DashboardPage.tsx +194 -0
  264. package/frontend/src/features/database/components/ColumnTypeSelect.tsx +64 -0
  265. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +282 -0
  266. package/frontend/src/features/database/components/ForeignKeyCell.tsx +187 -0
  267. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +378 -0
  268. package/frontend/src/features/database/components/LinkRecordModal.tsx +288 -0
  269. package/frontend/src/features/database/components/RecordFormDialog.tsx +164 -0
  270. package/frontend/src/features/database/components/RecordFormField.tsx +568 -0
  271. package/frontend/src/features/database/components/TableEmptyState.tsx +21 -0
  272. package/frontend/src/features/database/components/TableForm.tsx +656 -0
  273. package/frontend/src/features/database/components/TableFormColumn.tsx +137 -0
  274. package/frontend/src/features/database/components/TableListSkeleton.tsx +9 -0
  275. package/frontend/src/features/database/components/TableSidebar.tsx +47 -0
  276. package/frontend/src/features/database/constants.ts +26 -0
  277. package/frontend/src/features/database/helpers.ts +125 -0
  278. package/frontend/src/features/database/hooks/UseLinkModal.tsx +78 -0
  279. package/frontend/src/features/database/index.ts +12 -0
  280. package/frontend/src/features/database/page/DatabasePage.tsx +626 -0
  281. package/frontend/src/features/database/schema.ts +25 -0
  282. package/frontend/src/features/database/services/database.service.ts +216 -0
  283. package/frontend/src/features/functions/components/FunctionEmptyState.tsx +15 -0
  284. package/frontend/src/features/functions/components/FunctionRow.tsx +71 -0
  285. package/frontend/src/features/functions/components/FunctionViewer.tsx +46 -0
  286. package/frontend/src/features/functions/components/FunctionsContent.tsx +88 -0
  287. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -0
  288. package/frontend/src/features/functions/components/SecretEmptyState.tsx +23 -0
  289. package/frontend/src/features/functions/components/SecretRow.tsx +68 -0
  290. package/frontend/src/features/functions/components/SecretsContent.tsx +120 -0
  291. package/frontend/src/features/functions/hooks/useFunctions.ts +106 -0
  292. package/frontend/src/features/functions/page/FunctionsPage.tsx +28 -0
  293. package/frontend/src/features/functions/services/functions.service.ts +48 -0
  294. package/frontend/src/features/login/components/AuthErrorBoundary.tsx +87 -0
  295. package/frontend/src/features/login/components/PrivateRoute.tsx +24 -0
  296. package/frontend/src/features/login/page/CloudLoginPage.tsx +93 -0
  297. package/frontend/src/features/login/page/LoginPage.tsx +174 -0
  298. package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +313 -0
  299. package/frontend/src/features/logs/components/LogsTable.tsx +199 -0
  300. package/frontend/src/features/logs/hooks/useAuditLogs.ts +39 -0
  301. package/frontend/src/features/logs/index.ts +5 -0
  302. package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +530 -0
  303. package/frontend/src/features/logs/page/AuditsPage.tsx +192 -0
  304. package/frontend/src/features/logs/services/log.service.ts +171 -0
  305. package/frontend/src/features/metadata/hooks/useMetadata.ts +53 -0
  306. package/frontend/src/features/metadata/index.ts +0 -0
  307. package/frontend/src/features/metadata/page/MetadataPage.tsx +136 -0
  308. package/frontend/src/features/metadata/services/metadata.service.ts +17 -0
  309. package/frontend/src/features/onboard/components/CompletionCard.tsx +41 -0
  310. package/frontend/src/features/onboard/components/OnboardButton.tsx +84 -0
  311. package/frontend/src/features/onboard/components/StepContent.tsx +91 -0
  312. package/frontend/src/features/onboard/components/TestConnectionStep.tsx +53 -0
  313. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +35 -0
  314. package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +144 -0
  315. package/frontend/src/features/onboard/components/mcp/index.ts +4 -0
  316. package/frontend/src/features/onboard/components/mcp/mcp-helper.tsx +98 -0
  317. package/frontend/src/features/onboard/index.ts +3 -0
  318. package/frontend/src/features/onboard/page/OnBoardPage.tsx +104 -0
  319. package/frontend/src/features/onboard/types.ts +8 -0
  320. package/frontend/src/features/secrets/hooks/useSecrets.ts +139 -0
  321. package/frontend/src/features/secrets/services/secrets.service.ts +57 -0
  322. package/frontend/src/features/storage/components/BucketEmptyState.tsx +19 -0
  323. package/frontend/src/features/storage/components/BucketFormDialog.tsx +194 -0
  324. package/frontend/src/features/storage/components/BucketListSkeleton.tsx +17 -0
  325. package/frontend/src/features/storage/components/FilePreviewDialog.tsx +287 -0
  326. package/frontend/src/features/storage/components/StorageDataGrid.tsx +239 -0
  327. package/frontend/src/features/storage/components/StorageManager.tsx +236 -0
  328. package/frontend/src/features/storage/components/StorageSidebar.tsx +44 -0
  329. package/frontend/src/features/storage/components/UploadToast.tsx +46 -0
  330. package/frontend/src/features/storage/index.ts +3 -0
  331. package/frontend/src/features/storage/page/StoragePage.tsx +553 -0
  332. package/frontend/src/features/storage/services/storage.service.ts +144 -0
  333. package/frontend/src/features/visualizer/components/AuthNode.tsx +107 -0
  334. package/frontend/src/features/visualizer/components/BucketNode.tsx +34 -0
  335. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +359 -0
  336. package/frontend/src/features/visualizer/components/TableNode.tsx +152 -0
  337. package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +24 -0
  338. package/frontend/src/features/visualizer/components/index.ts +5 -0
  339. package/frontend/src/features/visualizer/page/VisualizerPage.tsx +127 -0
  340. package/frontend/src/index.css +248 -0
  341. package/frontend/src/lib/api/client.ts +163 -0
  342. package/frontend/src/lib/contexts/AuthContext.tsx +157 -0
  343. package/frontend/src/lib/contexts/OnboardStepContext.tsx +68 -0
  344. package/frontend/src/lib/contexts/SocketContext.tsx +303 -0
  345. package/frontend/src/lib/contexts/ThemeContext.tsx +125 -0
  346. package/frontend/src/lib/hooks/useAuth.ts +4 -0
  347. package/frontend/src/lib/hooks/useConfirm.ts +55 -0
  348. package/frontend/src/lib/hooks/useInterval.ts +27 -0
  349. package/frontend/src/lib/hooks/useMediaQuery.ts +59 -0
  350. package/frontend/src/lib/hooks/useOnboardingCompletion.ts +29 -0
  351. package/frontend/src/lib/hooks/usePagination.ts +27 -0
  352. package/frontend/src/lib/hooks/useTimeout.ts +27 -0
  353. package/frontend/src/lib/hooks/useToast.tsx +229 -0
  354. package/frontend/src/lib/utils/constants.ts +38 -0
  355. package/frontend/src/lib/utils/utils.ts +165 -0
  356. package/frontend/src/lib/utils/validation-schemas.ts +126 -0
  357. package/frontend/src/main.tsx +16 -0
  358. package/frontend/src/rdg.css +194 -0
  359. package/frontend/src/vite-env.d.ts +12 -0
  360. package/frontend/tailwind.config.js +97 -0
  361. package/frontend/tsconfig.json +26 -0
  362. package/frontend/tsconfig.node.json +10 -0
  363. package/frontend/vite.config.ts +37 -0
  364. package/frontend/vitest.config.ts +36 -0
  365. package/functions/deno.json +25 -0
  366. package/functions/server.ts +290 -0
  367. package/functions/worker-template.js +126 -0
  368. package/openapi/ai.yaml +689 -0
  369. package/openapi/auth.yaml +563 -0
  370. package/openapi/functions.yaml +476 -0
  371. package/openapi/health.yaml +30 -0
  372. package/openapi/logs.yaml +224 -0
  373. package/openapi/metadata.yaml +178 -0
  374. package/openapi/records.yaml +382 -0
  375. package/openapi/secrets.yaml +371 -0
  376. package/openapi/storage.yaml +876 -0
  377. package/openapi/tables.yaml +464 -0
  378. package/package.json +88 -0
  379. package/shared-schemas/package.json +31 -0
  380. package/shared-schemas/src/ai-api.schema.ts +167 -0
  381. package/shared-schemas/src/ai.schema.ts +54 -0
  382. package/shared-schemas/src/auth-api.schema.ts +193 -0
  383. package/shared-schemas/src/auth.schema.ts +94 -0
  384. package/shared-schemas/src/database-api.schema.ts +259 -0
  385. package/shared-schemas/src/database.schema.ts +69 -0
  386. package/shared-schemas/src/functions-api.schema.ts +25 -0
  387. package/shared-schemas/src/functions.schema.ts +16 -0
  388. package/shared-schemas/src/index.ts +13 -0
  389. package/shared-schemas/src/logs-api.schema.ts +49 -0
  390. package/shared-schemas/src/logs.schema.ts +14 -0
  391. package/shared-schemas/src/metadata.schema.ts +56 -0
  392. package/shared-schemas/src/storage-api.schema.ts +65 -0
  393. package/shared-schemas/src/storage.schema.ts +19 -0
  394. package/shared-schemas/tsconfig.json +21 -0
  395. package/tsconfig.json +8 -0
@@ -0,0 +1,155 @@
1
+ import { OpenRouterModel, ModalitySchema } from '@insforge/shared-schemas';
2
+
3
+ // Type for pricing information from OpenRouter model
4
+ type ModelPricing = {
5
+ prompt: string;
6
+ completion: string;
7
+ image?: string;
8
+ request?: string;
9
+ webSearch?: string;
10
+ internalReasoning?: string;
11
+ inputCacheRead?: string;
12
+ inputCacheWrite?: string;
13
+ };
14
+
15
+ export type ModelPriceLevel = 'FREE' | '$' | '$$' | '$$$';
16
+
17
+ export interface ModelOption {
18
+ value: string;
19
+ label: string;
20
+ company: string;
21
+ priceLevel: ModelPriceLevel;
22
+ priceColor: string;
23
+ logo: React.ComponentType<React.SVGProps<SVGSVGElement>> | undefined;
24
+ }
25
+
26
+ import { Type, Image } from 'lucide-react';
27
+ import GrokIcon from '@/assets/logos/grok.svg?react';
28
+ import GeminiIcon from '@/assets/logos/gemini.svg?react';
29
+ import ClaudeIcon from '@/assets/logos/claude_code.svg?react';
30
+ import OpenAIIcon from '@/assets/logos/openai.svg?react';
31
+ import AmazonIcon from '@/assets/logos/amazon.svg?react';
32
+
33
+ export const getModalityIcon = (
34
+ modality: ModalitySchema
35
+ ): React.FunctionComponent<React.SVGProps<SVGSVGElement>> => {
36
+ switch (modality) {
37
+ case 'text':
38
+ return Type;
39
+ case 'image':
40
+ return Image;
41
+ // case 'audio':
42
+ // return Mic;
43
+ // case 'video':
44
+ // return Video;
45
+ // case 'file':
46
+ // return File;
47
+ default:
48
+ return Type;
49
+ }
50
+ };
51
+
52
+ export const formatTokenCount = (count: number): string => {
53
+ if (count >= 1000000) {
54
+ return `${(count / 1000000).toFixed(1)}M`;
55
+ } else if (count >= 1000) {
56
+ return `${(count / 1000).toFixed(1)}K`;
57
+ }
58
+ return count.toString();
59
+ };
60
+
61
+ export const getProviderDisplayName = (providerId: string): string => {
62
+ const providerMap: Record<string, string> = {
63
+ openai: 'OpenAI',
64
+ anthropic: 'Anthropic',
65
+ google: 'Google',
66
+ openrouter: 'OpenRouter',
67
+ azure: 'Azure',
68
+ amazon: 'Amazon',
69
+ xai: 'xAI',
70
+ huggingface: 'HuggingFace',
71
+ };
72
+
73
+ return (
74
+ providerMap[providerId.toLowerCase()] ||
75
+ providerId.charAt(0).toUpperCase() + providerId.slice(1)
76
+ );
77
+ };
78
+
79
+ export const getProviderLogo = (
80
+ providerId: string
81
+ ): React.FunctionComponent<React.SVGProps<SVGSVGElement>> | undefined => {
82
+ const logoMap: Record<string, React.FunctionComponent<React.SVGProps<SVGSVGElement>>> = {
83
+ anthropic: ClaudeIcon,
84
+ openai: OpenAIIcon,
85
+ google: GeminiIcon,
86
+ xai: GrokIcon,
87
+ amazon: AmazonIcon,
88
+ };
89
+ return logoMap[providerId];
90
+ };
91
+
92
+ // Calculate price level based on pricing data
93
+ export const calculatePriceLevel = (
94
+ pricing: ModelPricing | undefined | null
95
+ ): { level: ModelPriceLevel; color: string } => {
96
+ if (!pricing) {
97
+ return { level: 'FREE', color: 'text-green-400' };
98
+ }
99
+
100
+ // Check if it's free
101
+ if (pricing.prompt === '0' && pricing.completion === '0') {
102
+ return { level: 'FREE', color: 'text-green-400' };
103
+ }
104
+
105
+ // Calculate average cost per 1M tokens (prompt + completion)
106
+ // Convert from per-token to per-1M-tokens
107
+ const promptCostPerToken = parseFloat(pricing.prompt) || 0;
108
+ const completionCostPerToken = parseFloat(pricing.completion) || 0;
109
+ const promptCostPer1M = promptCostPerToken * 1000000;
110
+ const completionCostPer1M = completionCostPerToken * 1000000;
111
+ const avgCostPer1M = (promptCostPer1M + completionCostPer1M) / 2;
112
+
113
+ // Adjusted thresholds based on actual pricing data and user feedback
114
+ if (avgCostPer1M <= 3) {
115
+ return { level: '$', color: 'text-green-400' };
116
+ } // ≤$3/1M tokens (Haiku, Gemini Flash, etc.)
117
+ if (avgCostPer1M <= 15) {
118
+ return { level: '$$', color: 'text-amber-400' };
119
+ } // ≤$15/1M tokens (GPT-4o, Claude Sonnet, etc.)
120
+ return { level: '$$$', color: 'text-red-400' }; // >$15/1M tokens (Claude Opus, etc.)
121
+ };
122
+
123
+ // Helper function to filter AI models based on selected modalities
124
+ export const filterModelsByModalities = (
125
+ models: OpenRouterModel[],
126
+ selectedInputModalities: ModalitySchema[],
127
+ selectedOutputModalities: ModalitySchema[]
128
+ ): OpenRouterModel[] => {
129
+ if (!models?.length) {
130
+ return [];
131
+ }
132
+
133
+ return models
134
+ .filter((model) => {
135
+ const inputModalities = new Set(model.architecture?.inputModalities || []);
136
+ const outputModalities = new Set(model.architecture?.outputModalities || []);
137
+ return (
138
+ selectedInputModalities.every((m) => inputModalities.has(m)) &&
139
+ selectedOutputModalities.every((m) => outputModalities.has(m))
140
+ );
141
+ })
142
+ .sort((a, b) => a.name.localeCompare(b.name));
143
+ };
144
+
145
+ // Helper function to get friendly model name from model ID
146
+ export const getFriendlyModelName = (modelId: string): string => {
147
+ // Extract the model name part (after the last slash)
148
+ const modelName = modelId.split('/').pop() || modelId;
149
+
150
+ // Convert kebab-case to Title Case
151
+ return modelName
152
+ .split('-')
153
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
154
+ .join(' ');
155
+ };
@@ -0,0 +1,221 @@
1
+ import { useMemo, useCallback } from 'react';
2
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
3
+ import { aiService } from '@/features/ai/services/ai.service';
4
+ import { authService } from '@/features/auth/services/auth.service';
5
+ import {
6
+ ListModelsResponse,
7
+ AIConfigurationWithUsageSchema,
8
+ CreateAIConfigurationRequest,
9
+ UpdateAIConfigurationRequest,
10
+ ModalitySchema,
11
+ type OpenRouterModel,
12
+ } from '@insforge/shared-schemas';
13
+ import { useToast } from '@/lib/hooks/useToast';
14
+ import {
15
+ getProviderLogo,
16
+ calculatePriceLevel,
17
+ getProviderDisplayName,
18
+ filterModelsByModalities,
19
+ type ModelOption,
20
+ } from '../helpers';
21
+
22
+ interface UseAIConfigsOptions {
23
+ enabled?: boolean;
24
+ }
25
+
26
+ export function useAIConfigs(options: UseAIConfigsOptions = {}) {
27
+ const { enabled = true } = options;
28
+ const queryClient = useQueryClient();
29
+ const { showToast } = useToast();
30
+
31
+ // Fetch AI models configuration
32
+ const {
33
+ data: modelsData,
34
+ isLoading,
35
+ error,
36
+ refetch,
37
+ } = useQuery<ListModelsResponse>({
38
+ queryKey: ['ai-models'],
39
+ queryFn: () => aiService.getModels(),
40
+ enabled: enabled,
41
+ staleTime: 5 * 60 * 1000, // Cache for 5 minutes
42
+ });
43
+
44
+ // Fetch AI configurations list
45
+ const {
46
+ data: configurations,
47
+ isLoading: isLoadingConfigurations,
48
+ error: configurationsError,
49
+ refetch: refetchConfigurations,
50
+ } = useQuery<AIConfigurationWithUsageSchema[]>({
51
+ queryKey: ['ai-configurations'],
52
+ queryFn: () => aiService.listConfigurations(),
53
+ enabled: enabled,
54
+ staleTime: 5 * 60 * 1000, // Cache for 5 minutes
55
+ });
56
+
57
+ // Fetch anonymous token (shared across all configs)
58
+ const { data: anonTokenData, isLoading: isLoadingAnonToken } = useQuery({
59
+ queryKey: ['anon-token'],
60
+ queryFn: () => authService.generateAnonToken(),
61
+ enabled: enabled,
62
+ staleTime: 30 * 60 * 1000, // Cache for 30 minutes since token never expires
63
+ });
64
+
65
+ // Create configuration mutation
66
+ const createConfigurationMutation = useMutation({
67
+ mutationFn: (data: CreateAIConfigurationRequest) => aiService.createConfiguration(data),
68
+ onSuccess: () => {
69
+ void queryClient.invalidateQueries({ queryKey: ['ai-configurations'] });
70
+ showToast('AI configuration created successfully', 'success');
71
+ },
72
+ onError: (error: Error) => {
73
+ showToast(`Failed to create configuration: ${error.message}`, 'error');
74
+ },
75
+ });
76
+
77
+ // Update configuration mutation
78
+ const updateConfigurationMutation = useMutation({
79
+ mutationFn: ({ id, data }: { id: string; data: UpdateAIConfigurationRequest }) =>
80
+ aiService.updateConfiguration(id, data),
81
+ onSuccess: () => {
82
+ void queryClient.invalidateQueries({ queryKey: ['ai-configurations'] });
83
+ showToast('AI configuration updated successfully', 'success');
84
+ },
85
+ onError: (error: Error) => {
86
+ showToast(`Failed to update configuration: ${error.message}`, 'error');
87
+ },
88
+ });
89
+
90
+ // Delete configuration mutation
91
+ const deleteConfigurationMutation = useMutation({
92
+ mutationFn: (id: string) => aiService.deleteConfiguration(id),
93
+ onSuccess: () => {
94
+ void queryClient.invalidateQueries({ queryKey: ['ai-configurations'] });
95
+ showToast('AI configuration deleted successfully', 'success');
96
+ },
97
+ onError: (error: Error) => {
98
+ showToast(`Failed to delete configuration: ${error.message}`, 'error');
99
+ },
100
+ });
101
+
102
+ // Extract configured providers (memoized to maintain referential stability)
103
+ const configuredTextProviders = useMemo(
104
+ () => modelsData?.text?.filter((p) => p.configured) || [],
105
+ [modelsData?.text]
106
+ );
107
+ const configuredImageProviders = useMemo(
108
+ () => modelsData?.image?.filter((p) => p.configured) || [],
109
+ [modelsData?.image]
110
+ );
111
+
112
+ // Extract unconfigured providers
113
+ const unconfiguredTextProviders = modelsData?.text?.filter((p) => !p.configured) || [];
114
+ const unconfiguredImageProviders = modelsData?.image?.filter((p) => !p.configured) || [];
115
+
116
+ // Get all available models (flat list)
117
+ const allTextModels = modelsData?.text?.flatMap((p) => p.models) || [];
118
+ const allImageModels = modelsData?.image?.flatMap((p) => p.models) || [];
119
+
120
+ // All configured models from all providers (flattened with deduplication)
121
+ const allConfiguredModels = useMemo(() => {
122
+ const uniqueModels = new Map<string, OpenRouterModel>();
123
+
124
+ [...configuredTextProviders, ...configuredImageProviders].forEach((provider) => {
125
+ provider.models.forEach((model) => {
126
+ // Only add if we haven't seen this model.id before
127
+ if (!uniqueModels.has(model.id)) {
128
+ uniqueModels.set(model.id, model);
129
+ }
130
+ });
131
+ });
132
+
133
+ return Array.from(uniqueModels.values());
134
+ }, [configuredTextProviders, configuredImageProviders]);
135
+
136
+ // Helper function to get filtered and processed models
137
+ const getFilteredModels = useCallback(
138
+ (inputModality: ModalitySchema[], outputModality: ModalitySchema[]): ModelOption[] => {
139
+ const filteredRawModels = filterModelsByModalities(
140
+ allConfiguredModels,
141
+ inputModality,
142
+ outputModality
143
+ );
144
+
145
+ return filteredRawModels.map((model) => {
146
+ const companyId = model.id.split('/')[0];
147
+ const priceInfo = calculatePriceLevel(model.pricing);
148
+
149
+ return {
150
+ value: model.id,
151
+ label: model.name,
152
+ company: getProviderDisplayName(companyId),
153
+ priceLevel: priceInfo.level,
154
+ priceColor: priceInfo.color,
155
+ logo: getProviderLogo(companyId),
156
+ };
157
+ });
158
+ },
159
+ [allConfiguredModels]
160
+ );
161
+
162
+ // Check if any providers are configured
163
+ const hasConfiguredTextProviders = configuredTextProviders.length > 0;
164
+ const hasConfiguredImageProviders = configuredImageProviders.length > 0;
165
+ const hasAnyConfiguration = hasConfiguredTextProviders || hasConfiguredImageProviders;
166
+
167
+ return {
168
+ // Raw data
169
+ modelsData,
170
+ isLoading,
171
+ error,
172
+
173
+ // Providers by type
174
+ textProviders: modelsData?.text || [],
175
+ imageProviders: modelsData?.image || [],
176
+
177
+ // Configured providers
178
+ configuredTextProviders,
179
+ configuredImageProviders,
180
+ allConfiguredModels,
181
+
182
+ // Unconfigured providers
183
+ unconfiguredTextProviders,
184
+ unconfiguredImageProviders,
185
+
186
+ // Models lists
187
+ allTextModels,
188
+ allImageModels,
189
+
190
+ // Status checks
191
+ hasConfiguredTextProviders,
192
+ hasConfiguredImageProviders,
193
+ hasAnyConfiguration,
194
+
195
+ // Configurations data
196
+ configurations: configurations || [],
197
+ isLoadingConfigurations,
198
+ configurationsError,
199
+
200
+ // Anonymous token data
201
+ anonKey: anonTokenData?.accessToken,
202
+ isLoadingAnonToken,
203
+
204
+ // Configuration mutations
205
+ createConfiguration: createConfigurationMutation.mutate,
206
+ updateConfiguration: updateConfigurationMutation.mutate,
207
+ deleteConfiguration: deleteConfigurationMutation.mutate,
208
+
209
+ // Mutation states
210
+ isCreating: createConfigurationMutation.isPending,
211
+ isUpdating: updateConfigurationMutation.isPending,
212
+ isDeleting: deleteConfigurationMutation.isPending,
213
+
214
+ // Operations
215
+ refetch,
216
+ refetchConfigurations,
217
+
218
+ // Helper functions
219
+ getFilteredModels,
220
+ };
221
+ }
@@ -0,0 +1,77 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { aiService } from '@/features/ai/services/ai.service';
3
+ import {
4
+ AIUsageSummarySchema,
5
+ AIUsageRecordSchema,
6
+ ListAIUsageResponse,
7
+ } from '@insforge/shared-schemas';
8
+
9
+ interface UseAIUsageSummaryOptions {
10
+ configId?: string;
11
+ startDate?: string;
12
+ endDate?: string;
13
+ enabled?: boolean;
14
+ }
15
+
16
+ export function useAIUsageSummary(options: UseAIUsageSummaryOptions = {}) {
17
+ const { configId, startDate, endDate, enabled = true } = options;
18
+
19
+ return useQuery<AIUsageSummarySchema>({
20
+ queryKey: ['ai-usage-summary', configId, startDate, endDate],
21
+ queryFn: () => aiService.getUsageSummary({ configId, startDate, endDate }),
22
+ enabled: enabled,
23
+ staleTime: 60 * 1000, // Cache for 1 minute
24
+ });
25
+ }
26
+
27
+ interface UseAIUsageRecordsOptions {
28
+ startDate?: string;
29
+ endDate?: string;
30
+ limit?: string;
31
+ offset?: string;
32
+ enabled?: boolean;
33
+ }
34
+
35
+ export function useAIUsageRecords(options: UseAIUsageRecordsOptions = {}) {
36
+ const { startDate, endDate, limit = '50', offset = '0', enabled = true } = options;
37
+
38
+ return useQuery<ListAIUsageResponse>({
39
+ queryKey: ['ai-usage-records', startDate, endDate, limit, offset],
40
+ queryFn: () => aiService.getUsageRecords({ startDate, endDate, limit, offset }),
41
+ enabled: enabled,
42
+ staleTime: 60 * 1000, // Cache for 1 minute
43
+ });
44
+ }
45
+
46
+ interface UseAIConfigUsageOptions {
47
+ configId: string;
48
+ startDate?: string;
49
+ endDate?: string;
50
+ enabled?: boolean;
51
+ }
52
+
53
+ export function useAIConfigUsage(options: UseAIConfigUsageOptions) {
54
+ const { configId, startDate, endDate, enabled = true } = options;
55
+
56
+ return useQuery<AIUsageRecordSchema[]>({
57
+ queryKey: ['ai-config-usage', configId, startDate, endDate],
58
+ queryFn: () => aiService.getConfigUsageRecords(configId, { startDate, endDate }),
59
+ enabled: enabled && !!configId,
60
+ staleTime: 60 * 1000, // Cache for 1 minute
61
+ });
62
+ }
63
+
64
+ export function useAIRemainingCredits(enabled = true) {
65
+ return useQuery<{
66
+ usage: number;
67
+ limit: number | null;
68
+ remaining: number | null;
69
+ }>({
70
+ queryKey: ['ai-remaining-credits'],
71
+ queryFn: () => aiService.getRemainingCredits(),
72
+ enabled: enabled,
73
+ staleTime: 30 * 1000, // Cache for 30 seconds
74
+ refetchInterval: 60 * 1000, // Refetch every minute
75
+ retry: false,
76
+ });
77
+ }
@@ -0,0 +1,178 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Plus, Loader2 } from 'lucide-react';
3
+ import { Button } from '@/components/radix/Button';
4
+ import { ConfirmDialog } from '@/components/ConfirmDialog';
5
+ import { useAIConfigs } from '../hooks/useAIConfigs';
6
+ import { useAIRemainingCredits } from '../hooks/useAIUsage';
7
+ import {
8
+ AIConfigurationWithUsageSchema,
9
+ CreateAIConfigurationRequest,
10
+ UpdateAIConfigurationRequest,
11
+ } from '@insforge/shared-schemas';
12
+ import { useConfirm } from '@/lib/hooks/useConfirm';
13
+ import { useToast } from '@/lib/hooks/useToast';
14
+ import { AIConfigDialog } from '@/features/ai/components/AIConfigDialog';
15
+ import { AIModelCard } from '@/features/ai/components/AIConfigCard';
16
+ import AIEmptyState from '@/features/ai/components/AIEmptyState';
17
+ import { getProviderLogo } from '../helpers';
18
+
19
+ export default function AIPage() {
20
+ const {
21
+ configurations,
22
+ isLoadingConfigurations,
23
+ createConfiguration,
24
+ updateConfiguration,
25
+ deleteConfiguration,
26
+ } = useAIConfigs();
27
+
28
+ const { data: credits, error: getAICreditsError } = useAIRemainingCredits();
29
+
30
+ const { confirm, confirmDialogProps } = useConfirm();
31
+ const { showToast } = useToast();
32
+
33
+ // Handle AI credits error
34
+ useEffect(() => {
35
+ if (getAICreditsError) {
36
+ console.error('Failed to fetch AI credits:', getAICreditsError);
37
+ const errorMessage = getAICreditsError.message || 'Failed to load AI credits';
38
+ showToast(errorMessage, 'error');
39
+ }
40
+ }, [getAICreditsError, showToast]);
41
+
42
+ // Format credits display
43
+ const formatCredits = (remaining: number) => {
44
+ if (remaining >= 1000) {
45
+ return `${(remaining / 1000).toFixed(1)}K`;
46
+ }
47
+ return remaining.toFixed(2);
48
+ };
49
+
50
+ const [dialogOpen, setDialogOpen] = useState(false);
51
+ const [dialogMode, setDialogMode] = useState<'create' | 'edit'>('create');
52
+ const [editingConfig, setEditingConfig] = useState<AIConfigurationWithUsageSchema | undefined>();
53
+
54
+ const handleEdit = (id: string) => {
55
+ const config = configurations.find((c) => c.id === id);
56
+ if (config) {
57
+ setEditingConfig(config);
58
+ setDialogMode('edit');
59
+ setDialogOpen(true);
60
+ }
61
+ };
62
+
63
+ const handleDelete = async (id: string) => {
64
+ const shouldDelete = await confirm({
65
+ title: 'Delete AI Configuration',
66
+ description:
67
+ 'Are you certain you wish to remove this AI Integration? This action is irreversible.',
68
+ confirmText: 'Delete',
69
+ destructive: true,
70
+ });
71
+
72
+ if (shouldDelete) {
73
+ deleteConfiguration(id);
74
+ }
75
+ };
76
+
77
+ const handleCreate = () => {
78
+ setEditingConfig(undefined);
79
+ setDialogMode('create');
80
+ setDialogOpen(true);
81
+ };
82
+
83
+ const handleDialogSuccess = (
84
+ configData: CreateAIConfigurationRequest | UpdateAIConfigurationRequest
85
+ ) => {
86
+ if (dialogMode === 'create') {
87
+ const createData = configData as CreateAIConfigurationRequest;
88
+ createConfiguration({
89
+ inputModality: createData.inputModality,
90
+ outputModality: createData.outputModality,
91
+ provider: createData.provider,
92
+ modelId: createData.modelId,
93
+ systemPrompt: createData.systemPrompt,
94
+ });
95
+ } else if (editingConfig) {
96
+ const updateData = configData as UpdateAIConfigurationRequest;
97
+ updateConfiguration({
98
+ id: editingConfig.id,
99
+ data: {
100
+ systemPrompt: updateData.systemPrompt || null,
101
+ },
102
+ });
103
+ }
104
+ setDialogOpen(false);
105
+ };
106
+
107
+ return (
108
+ <div className="flex h-full bg-bg-gray dark:bg-neutral-800 pt-8 pb-6">
109
+ <div className="max-w-[1080px] mx-auto flex-1 flex flex-col gap-6 overflow-hidden">
110
+ {/* Header Section */}
111
+ <div className="w-full flex items-start justify-between">
112
+ <div className="flex flex-col items-start gap-2">
113
+ <div className="flex items-center gap-3">
114
+ <h1 className="text-xl font-semibold text-black dark:text-white">AI Integration</h1>
115
+ {credits?.remaining && (
116
+ <span className="text-sm font-normal text-emerald-500 dark:text-emerald-400 mt-[2.5px]">
117
+ {formatCredits(credits.remaining)} credit{credits.remaining !== 1 ? 's' : ''} left
118
+ </span>
119
+ )}
120
+ </div>
121
+ <p className="text-sm text-neutral-500 dark:text-neutral-400">
122
+ Copy prompt to your agent and the AI Model below will be integrated automatically.
123
+ </p>
124
+ </div>
125
+ <Button
126
+ className="h-9 py-2 pl-2 pr-3 text-sm font-medium gap-2 dark:text-white dark:bg-neutral-700 dark:hover:bg-neutral-600"
127
+ onClick={handleCreate}
128
+ >
129
+ <Plus className="w-5 h-5" />
130
+ New Integration
131
+ </Button>
132
+ </div>
133
+
134
+ {/* Content Section */}
135
+ <div className="flex-1 overflow-auto">
136
+ {isLoadingConfigurations ? (
137
+ <div className="flex-1 flex items-center justify-center h-full">
138
+ <Loader2 className="w-8 h-8 animate-spin text-gray-400" />
139
+ </div>
140
+ ) : configurations.length > 0 ? (
141
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
142
+ {configurations.map((config) => {
143
+ const providerLogo = getProviderLogo(config.modelId.split('/')[0]);
144
+ const extendedConfig = {
145
+ ...config,
146
+ logo: providerLogo,
147
+ };
148
+
149
+ return (
150
+ <AIModelCard
151
+ key={config.id}
152
+ config={extendedConfig}
153
+ onEdit={handleEdit}
154
+ onDelete={() => void handleDelete(config.id)}
155
+ />
156
+ );
157
+ })}
158
+ </div>
159
+ ) : (
160
+ <AIEmptyState />
161
+ )}
162
+ </div>
163
+ </div>
164
+
165
+ {/* AI Configuration Dialog */}
166
+ <AIConfigDialog
167
+ open={dialogOpen}
168
+ onOpenChange={setDialogOpen}
169
+ mode={dialogMode}
170
+ editingConfig={editingConfig}
171
+ onSuccess={handleDialogSuccess}
172
+ />
173
+
174
+ {/* Confirm Dialog */}
175
+ <ConfirmDialog {...confirmDialogProps} />
176
+ </div>
177
+ );
178
+ }