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,207 @@
1
+ import OpenAI from 'openai';
2
+ import { AIUsageService } from './usage';
3
+ import { AIConfigService } from './config';
4
+ import { AIClientService } from './client';
5
+ import type {
6
+ AIConfigurationSchema,
7
+ ChatCompletionResponse,
8
+ ChatMessageSchema,
9
+ } from '@insforge/shared-schemas';
10
+ import logger from '@/utils/logger.js';
11
+ import { ChatCompletionOptions } from '@/types/ai';
12
+
13
+ export class ChatService {
14
+ private aiUsageService = new AIUsageService();
15
+ private aiConfigService = new AIConfigService();
16
+ private aiCredentialsService = AIClientService.getInstance();
17
+
18
+ /**
19
+ * Format messages for OpenAI API with multimodal support
20
+ */
21
+ private formatMessages(
22
+ messages: ChatMessageSchema[],
23
+ systemPrompt?: string
24
+ ): OpenAI.Chat.ChatCompletionMessageParam[] {
25
+ const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [];
26
+
27
+ // Add system message if provided
28
+ if (systemPrompt) {
29
+ formattedMessages.push({ role: 'system', content: systemPrompt });
30
+ }
31
+
32
+ // Format conversation messages
33
+ for (const msg of messages) {
34
+ // Check if message has images
35
+ if (msg.images && msg.images.length > 0) {
36
+ // Build multimodal content array
37
+ const content = [
38
+ { type: 'text', text: msg.content },
39
+ ...msg.images.map((image) => ({
40
+ type: 'image_url',
41
+ image_url: { url: image.url },
42
+ })),
43
+ ];
44
+
45
+ formattedMessages.push({
46
+ role: msg.role as 'system' | 'user' | 'assistant',
47
+ content,
48
+ } as OpenAI.Chat.ChatCompletionMessageParam);
49
+ } else {
50
+ // Simple text message
51
+ formattedMessages.push({
52
+ role: msg.role as 'system' | 'user' | 'assistant',
53
+ content: msg.content,
54
+ });
55
+ }
56
+ }
57
+
58
+ return formattedMessages;
59
+ }
60
+
61
+ /**
62
+ * Validate model and get config
63
+ */
64
+ async validateAndGetConfig(modelId: string): Promise<AIConfigurationSchema | null> {
65
+ const aiConfig = await this.aiConfigService.findByModelId(modelId);
66
+ if (!aiConfig) {
67
+ throw new Error(
68
+ `Model ${modelId} is not enabled. Please contact your administrator to enable this model.`
69
+ );
70
+ }
71
+ return aiConfig;
72
+ }
73
+
74
+ /**
75
+ * Send a chat message to the specified model
76
+ * @param messages - Array of messages for conversation
77
+ * @param options - Chat options including model, temperature, etc.
78
+ */
79
+ async chat(
80
+ messages: ChatMessageSchema[],
81
+ options: ChatCompletionOptions
82
+ ): Promise<ChatCompletionResponse> {
83
+ try {
84
+ // Get the client (handles validation and initialization automatically)
85
+ const client = await this.aiCredentialsService.getClient();
86
+
87
+ // Validate model and get config
88
+ const aiConfig = await this.validateAndGetConfig(options.model);
89
+
90
+ // Apply system prompt from config if available
91
+ const formattedMessages = this.formatMessages(messages, aiConfig?.systemPrompt);
92
+
93
+ const response = await client.chat.completions.create({
94
+ model: options.model,
95
+ messages: formattedMessages,
96
+ temperature: options.temperature ?? 0.7,
97
+ max_tokens: options.maxTokens ?? 4096,
98
+ top_p: options.topP,
99
+ stream: false,
100
+ });
101
+
102
+ // Extract token usage if available
103
+ const tokenUsage = response.usage
104
+ ? {
105
+ promptTokens: response.usage.prompt_tokens,
106
+ completionTokens: response.usage.completion_tokens,
107
+ totalTokens: response.usage.total_tokens,
108
+ }
109
+ : undefined;
110
+
111
+ // Track usage if config is available
112
+ if (aiConfig?.id && tokenUsage) {
113
+ await this.aiUsageService.trackChatUsage(
114
+ aiConfig.id,
115
+ tokenUsage.promptTokens,
116
+ tokenUsage.completionTokens,
117
+ options.model
118
+ );
119
+ }
120
+
121
+ return {
122
+ text: response.choices[0]?.message?.content || '',
123
+ metadata: {
124
+ model: options.model,
125
+ ...tokenUsage,
126
+ },
127
+ };
128
+ } catch (error) {
129
+ logger.error('Chat error', { error });
130
+ throw new Error(
131
+ `Failed to get response: ${error instanceof Error ? error.message : String(error)}`
132
+ );
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Stream a chat response
138
+ * @param messages - Array of messages for conversation
139
+ * @param options - Chat options including model, temperature, etc.
140
+ */
141
+ async *streamChat(
142
+ messages: ChatMessageSchema[],
143
+ options: ChatCompletionOptions
144
+ ): AsyncGenerator<{
145
+ chunk?: string;
146
+ tokenUsage?: { promptTokens?: number; completionTokens?: number; totalTokens?: number };
147
+ }> {
148
+ try {
149
+ // Get the client (handles validation and initialization automatically)
150
+ const client = await this.aiCredentialsService.getClient();
151
+
152
+ // Validate model and get config
153
+ const aiConfig = await this.validateAndGetConfig(options.model);
154
+
155
+ // Apply system prompt from config if available
156
+ const formattedMessages = this.formatMessages(messages, aiConfig?.systemPrompt);
157
+
158
+ const stream = await client.chat.completions.create({
159
+ model: options.model,
160
+ messages: formattedMessages,
161
+ temperature: options.temperature ?? 0.7,
162
+ max_tokens: options.maxTokens ?? 4096,
163
+ top_p: options.topP,
164
+ stream: true,
165
+ });
166
+
167
+ const tokenUsage = {
168
+ promptTokens: 0,
169
+ completionTokens: 0,
170
+ totalTokens: 0,
171
+ };
172
+
173
+ for await (const chunk of stream) {
174
+ const content = chunk.choices[0]?.delta?.content;
175
+ if (content) {
176
+ yield { chunk: content };
177
+ }
178
+
179
+ // Check if this chunk contains usage data
180
+ if (chunk.usage) {
181
+ // Accumulate tokens instead of replacing
182
+ tokenUsage.promptTokens += chunk.usage.prompt_tokens || 0;
183
+ tokenUsage.completionTokens += chunk.usage.completion_tokens || 0;
184
+ tokenUsage.totalTokens += chunk.usage.total_tokens || 0;
185
+
186
+ // Yield the accumulated usage
187
+ yield { tokenUsage: { ...tokenUsage } };
188
+ }
189
+ }
190
+
191
+ // Track usage after streaming completes
192
+ if (aiConfig?.id && tokenUsage.totalTokens > 0) {
193
+ await this.aiUsageService.trackChatUsage(
194
+ aiConfig.id,
195
+ tokenUsage.promptTokens,
196
+ tokenUsage.completionTokens,
197
+ options.model
198
+ );
199
+ }
200
+ } catch (error) {
201
+ logger.error('Streaming error', { error });
202
+ throw new Error(
203
+ `Failed to stream response: ${error instanceof Error ? error.message : String(error)}`
204
+ );
205
+ }
206
+ }
207
+ }
@@ -0,0 +1,242 @@
1
+ import OpenAI from 'openai';
2
+ import jwt from 'jsonwebtoken';
3
+ import { isCloudEnvironment } from '@/utils/environment';
4
+ import { AppError } from '@/api/middleware/error';
5
+ import { ERROR_CODES } from '@/types/error-constants';
6
+
7
+ interface CloudCredentialsResponse {
8
+ openrouter?: {
9
+ api_key: string;
10
+ limit?: number;
11
+ expired_at?: string | null;
12
+ usage?: number;
13
+ limit_remaining?: number;
14
+ };
15
+ }
16
+
17
+ interface CloudCredentials {
18
+ apiKey: string;
19
+ limitRemaining?: number;
20
+ expiredAt?: Date | null;
21
+ }
22
+
23
+ interface OpenRouterKeyInfo {
24
+ data: {
25
+ label: string;
26
+ usage: number;
27
+ limit: number | null;
28
+ is_free_tier: boolean;
29
+ };
30
+ }
31
+
32
+ interface OpenRouterLimitation {
33
+ label: string;
34
+ limit: number | null;
35
+ usage: number;
36
+ is_provisioning_key: boolean;
37
+ limit_remaining: number | null;
38
+ is_free_tier: boolean;
39
+ rate_limit: {
40
+ requests: number;
41
+ interval: string;
42
+ note: string;
43
+ };
44
+ }
45
+
46
+ export class AIClientService {
47
+ private static instance: AIClientService;
48
+ private cloudCredentials: CloudCredentials | undefined;
49
+ private openRouterClient: OpenAI | null = null;
50
+ private currentApiKey: string | undefined;
51
+
52
+ private constructor() {}
53
+
54
+ static getInstance(): AIClientService {
55
+ if (!AIClientService.instance) {
56
+ AIClientService.instance = new AIClientService();
57
+ }
58
+ return AIClientService.instance;
59
+ }
60
+
61
+ /**
62
+ * Create or recreate the OpenAI client with the given API key
63
+ */
64
+ private createClient(apiKey: string): OpenAI {
65
+ this.currentApiKey = apiKey;
66
+ return new OpenAI({
67
+ baseURL: 'https://openrouter.ai/api/v1',
68
+ apiKey,
69
+ defaultHeaders: {
70
+ 'HTTP-Referer': 'https://insforge.dev',
71
+ 'X-Title': 'InsForge',
72
+ },
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Get OpenRouter API key based on environment
78
+ * In cloud environment: fetches from cloud API with JWT authentication
79
+ * In local environment: returns from environment variable
80
+ */
81
+ async getApiKey(): Promise<string> {
82
+ if (isCloudEnvironment()) {
83
+ if (
84
+ this.cloudCredentials &&
85
+ (!this.cloudCredentials.expiredAt || new Date() <= this.cloudCredentials.expiredAt)
86
+ ) {
87
+ return this.cloudCredentials.apiKey;
88
+ } else {
89
+ return await this.fetchCloudApiKey();
90
+ }
91
+ }
92
+
93
+ const apiKey = process.env.OPENROUTER_API_KEY;
94
+ if (!apiKey) {
95
+ throw new AppError(
96
+ 'OPENROUTER_API_KEY not found in environment variables',
97
+ 500,
98
+ ERROR_CODES.AI_INVALID_API_KEY
99
+ );
100
+ }
101
+ return apiKey;
102
+ }
103
+
104
+ /**
105
+ * Get the OpenAI client, creating or updating it as needed
106
+ * This is the main method services should use
107
+ */
108
+ async getClient(): Promise<OpenAI> {
109
+ if (!this.openRouterClient) {
110
+ this.openRouterClient = this.createClient(await this.getApiKey());
111
+ return this.openRouterClient;
112
+ }
113
+ if (isCloudEnvironment()) {
114
+ const apiKey = await this.getApiKey();
115
+ if (this.currentApiKey !== apiKey) {
116
+ this.openRouterClient = this.createClient(apiKey);
117
+ }
118
+ }
119
+ return this.openRouterClient;
120
+ }
121
+
122
+ /**
123
+ * Check if AI services are properly configured
124
+ */
125
+ isConfigured(): boolean {
126
+ if (isCloudEnvironment()) {
127
+ return true;
128
+ }
129
+ return !!process.env.OPENROUTER_API_KEY;
130
+ }
131
+
132
+ /**
133
+ * Get remaining credits for the current API key from OpenRouter
134
+ */
135
+ async getRemainingCredits(): Promise<{
136
+ usage: number;
137
+ limit: number | null;
138
+ remaining: number | null;
139
+ }> {
140
+ try {
141
+ const apiKey = await this.getApiKey();
142
+
143
+ if (isCloudEnvironment()) {
144
+ // Use InsForge API for cloud environment
145
+ const response = await fetch(
146
+ `https://api.insforge.dev/ai/v1/limitations?credential=${encodeURIComponent(apiKey)}`,
147
+ {
148
+ method: 'GET',
149
+ }
150
+ );
151
+
152
+ if (!response.ok) {
153
+ throw new Error(`Failed to fetch key info: ${response.statusText}`);
154
+ }
155
+
156
+ const result = (await response.json()) as { data: OpenRouterLimitation };
157
+ const keyInfo = result.data;
158
+
159
+ return {
160
+ usage: keyInfo.usage,
161
+ limit: keyInfo.limit,
162
+ remaining: keyInfo.limit_remaining,
163
+ };
164
+ } else {
165
+ // Use OpenRouter API for local environment
166
+ const response = await fetch('https://openrouter.ai/api/v1/key', {
167
+ method: 'GET',
168
+ headers: {
169
+ Authorization: `Bearer ${apiKey}`,
170
+ },
171
+ });
172
+
173
+ if (!response.ok) {
174
+ throw new AppError(
175
+ `Invalid OpenRouter API Key`,
176
+ 500,
177
+ ERROR_CODES.AI_INVALID_API_KEY,
178
+ 'Check your OpenRouter key and try again.'
179
+ );
180
+ }
181
+
182
+ const keyInfo = (await response.json()) as OpenRouterKeyInfo;
183
+
184
+ return {
185
+ usage: keyInfo.data.usage,
186
+ limit: keyInfo.data.limit,
187
+ remaining: keyInfo.data.limit !== null ? keyInfo.data.limit - keyInfo.data.usage : null,
188
+ };
189
+ }
190
+ } catch (error) {
191
+ console.error('Failed to fetch remaining credits:', error);
192
+ throw error;
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Fetch API key from cloud service
198
+ */
199
+ private async fetchCloudApiKey(): Promise<string> {
200
+ try {
201
+ const projectId = process.env.PROJECT_ID;
202
+ if (!projectId) {
203
+ throw new Error('PROJECT_ID not found in environment variables');
204
+ }
205
+
206
+ const jwtSecret = process.env.JWT_SECRET;
207
+ if (!jwtSecret) {
208
+ throw new Error('JWT_SECRET not found in environment variables');
209
+ }
210
+
211
+ // Sign a token for authentication
212
+ const token = jwt.sign({ projectId }, jwtSecret, { expiresIn: '1h' });
213
+
214
+ // Fetch API key from cloud service with sign token as query parameter
215
+ const response = await fetch(
216
+ `https://api.insforge.dev/ai/v1/credentials/${projectId}?sign=${token}`
217
+ );
218
+
219
+ if (!response.ok) {
220
+ throw new Error(`Failed to fetch cloud API key: ${response.statusText}`);
221
+ }
222
+
223
+ const data = (await response.json()) as CloudCredentialsResponse;
224
+
225
+ // Extract API key from the openrouter object in response
226
+ if (!data.openrouter?.api_key) {
227
+ throw new Error('Invalid response: missing openrouter API Key');
228
+ }
229
+
230
+ // Store credentials with metadata
231
+ this.cloudCredentials = {
232
+ apiKey: data.openrouter.api_key,
233
+ limitRemaining: data.openrouter.limit_remaining,
234
+ expiredAt: data.openrouter.expired_at ? new Date(data.openrouter.expired_at) : null,
235
+ };
236
+ return data.openrouter.api_key;
237
+ } catch (error) {
238
+ console.error('Failed to fetch cloud API key:', error);
239
+ throw error;
240
+ }
241
+ }
242
+ }
@@ -0,0 +1,187 @@
1
+ import { Pool } from 'pg';
2
+ import { DatabaseManager } from '@/core/database/manager.js';
3
+ import logger from '@/utils/logger.js';
4
+ import { AIConfigurationSchema, AIConfigurationWithUsageSchema } from '@insforge/shared-schemas';
5
+
6
+ export class AIConfigService {
7
+ private pool: Pool | null = null;
8
+
9
+ private getPool(): Pool {
10
+ if (!this.pool) {
11
+ this.pool = DatabaseManager.getInstance().getPool();
12
+ }
13
+ return this.pool;
14
+ }
15
+
16
+ async create(
17
+ inputModality: string[],
18
+ outputModality: string[],
19
+ provider: string,
20
+ modelId: string,
21
+ systemPrompt?: string
22
+ ): Promise<{ id: string }> {
23
+ const client = await this.getPool().connect();
24
+ try {
25
+ const result = await client.query(
26
+ `INSERT INTO _ai_configs (input_modality, output_modality, provider, model_id, system_prompt)
27
+ VALUES ($1, $2, $3, $4, $5)
28
+ RETURNING id`,
29
+ [inputModality, outputModality, provider, modelId, systemPrompt || null]
30
+ );
31
+
32
+ logger.info('AI configuration created', { id: result.rows[0].id });
33
+ return { id: result.rows[0].id };
34
+ } catch (error) {
35
+ logger.error('Failed to create AI configuration', { error });
36
+ throw new Error('Failed to create AI configuration');
37
+ } finally {
38
+ client.release();
39
+ }
40
+ }
41
+
42
+ async findAll(): Promise<AIConfigurationWithUsageSchema[]> {
43
+ const client = await this.getPool().connect();
44
+ try {
45
+ // Use a single query with aggregation to get configs with usage stats
46
+ const result = await client.query(
47
+ `SELECT
48
+ c.id,
49
+ c.input_modality as "inputModality",
50
+ c.output_modality as "outputModality",
51
+ c.provider,
52
+ c.model_id as "modelId",
53
+ c.system_prompt as "systemPrompt",
54
+ COALESCE(SUM(u.input_tokens), 0)::INTEGER as "totalInputTokens",
55
+ COALESCE(SUM(u.output_tokens), 0)::INTEGER as "totalOutputTokens",
56
+ COALESCE(SUM(u.input_tokens + u.output_tokens), 0)::INTEGER as "totalTokens",
57
+ COALESCE(SUM(u.image_count), 0)::INTEGER as "totalImageCount",
58
+ COALESCE(COUNT(u.id), 0)::INTEGER as "totalRequests"
59
+ FROM _ai_configs c
60
+ LEFT JOIN _ai_usage u ON c.id = u.config_id
61
+ GROUP BY c.id, c.input_modality, c.output_modality, c.provider, c.model_id, c.system_prompt, c.created_at
62
+ ORDER BY c.created_at DESC`
63
+ );
64
+
65
+ return result.rows.map((row) => ({
66
+ id: row.id,
67
+ inputModality: row.inputModality,
68
+ outputModality: row.outputModality,
69
+ provider: row.provider,
70
+ modelId: row.modelId,
71
+ systemPrompt: row.systemPrompt,
72
+ usageStats: {
73
+ totalInputTokens: row.totalInputTokens,
74
+ totalOutputTokens: row.totalOutputTokens,
75
+ totalTokens: row.totalTokens,
76
+ totalImageCount: row.totalImageCount,
77
+ totalRequests: row.totalRequests,
78
+ },
79
+ }));
80
+ } catch (error) {
81
+ logger.error('Failed to fetch AI configurations with usage', { error });
82
+ throw new Error('Failed to fetch AI configurations');
83
+ } finally {
84
+ client.release();
85
+ }
86
+ }
87
+
88
+ async update(id: string, systemPrompt: string | null): Promise<boolean> {
89
+ const client = await this.getPool().connect();
90
+ try {
91
+ const result = await client.query(
92
+ `UPDATE _ai_configs
93
+ SET system_prompt = $1, updated_at = NOW()
94
+ WHERE id = $2`,
95
+ [systemPrompt, id]
96
+ );
97
+
98
+ const success = (result.rowCount ?? 0) > 0;
99
+ if (success) {
100
+ logger.info('AI configuration updated', { id });
101
+ }
102
+ return success;
103
+ } catch (error) {
104
+ logger.error('Failed to update AI configuration', { error, id });
105
+ throw new Error('Failed to update AI configuration');
106
+ } finally {
107
+ client.release();
108
+ }
109
+ }
110
+
111
+ async delete(id: string): Promise<boolean> {
112
+ const client = await this.getPool().connect();
113
+ try {
114
+ const result = await client.query('DELETE FROM _ai_configs WHERE id = $1', [id]);
115
+
116
+ const success = (result.rowCount ?? 0) > 0;
117
+ if (success) {
118
+ logger.info('AI configuration deleted', { id });
119
+ }
120
+ return success;
121
+ } catch (error) {
122
+ logger.error('Failed to delete AI configuration', { error, id });
123
+ throw new Error('Failed to delete AI configuration');
124
+ } finally {
125
+ client.release();
126
+ }
127
+ }
128
+
129
+ async findByModelId(modelId: string): Promise<AIConfigurationSchema | null> {
130
+ const client = await this.getPool().connect();
131
+ try {
132
+ const result = await client.query(
133
+ `SELECT id, input_modality as "inputModality", output_modality as "outputModality", provider, model_id as "modelId", system_prompt as "systemPrompt", created_at, updated_at
134
+ FROM _ai_configs
135
+ WHERE model_id = $1`,
136
+ [modelId]
137
+ );
138
+
139
+ if (result.rows.length === 0) {
140
+ return null;
141
+ }
142
+
143
+ const row = result.rows[0];
144
+ return {
145
+ id: row.id,
146
+ inputModality: row.inputModality,
147
+ outputModality: row.outputModality,
148
+ provider: row.provider,
149
+ modelId: row.modelId,
150
+ systemPrompt: row.systemPrompt,
151
+ };
152
+ } catch (error) {
153
+ logger.error('Failed to fetch AI configuration by modelId', {
154
+ error,
155
+ modelId,
156
+ });
157
+ throw new Error('Failed to fetch AI configuration');
158
+ } finally {
159
+ client.release();
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Get AI metadata
165
+ */
166
+ async getMetadata(): Promise<{
167
+ models: Array<{ inputModality: string[]; outputModality: string[]; modelId: string }>;
168
+ }> {
169
+ try {
170
+ const configs = await this.findAll();
171
+
172
+ // Map configs to simplified model metadata
173
+ const models = configs.map((config) => ({
174
+ inputModality: config.inputModality,
175
+ outputModality: config.outputModality,
176
+ modelId: config.modelId,
177
+ }));
178
+
179
+ return { models };
180
+ } catch (error) {
181
+ logger.error('Failed to get AI metadata', {
182
+ error: error instanceof Error ? error.message : String(error),
183
+ });
184
+ return { models: [] };
185
+ }
186
+ }
187
+ }