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.
- package/.dockerignore +58 -0
- package/.env.example +49 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +79 -0
- package/.github/copilot-instructions.md +147 -0
- package/.github/workflows/build-image.yml +65 -0
- package/.github/workflows/ci-premerge-check.yml +24 -0
- package/.github/workflows/deploy-aws.yml +130 -0
- package/.github/workflows/lint-and-format.yml +33 -0
- package/.prettierignore +65 -0
- package/.prettierrc +9 -0
- package/CHANGELOG.md +3 -0
- package/CONTRIBUTING.md +126 -0
- package/Dockerfile +27 -0
- package/GITHUB_OAUTH_SETUP.md +49 -0
- package/GOOGLE_OAUTH_SETUP.md +148 -0
- package/LICENSE +201 -0
- package/README.md +134 -0
- package/assets/Dark.svg +23 -0
- package/assets/archDiagram.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/mcpInstallv2.png +0 -0
- package/assets/sampleResponse.png +0 -0
- package/assets/signin.png +0 -0
- package/assets/userflow.png +0 -0
- package/backend/migrations/000_create-base-tables.sql +142 -0
- package/backend/migrations/001_create-helper-functions.sql +41 -0
- package/backend/migrations/002_rename-auth-tables.sql +30 -0
- package/backend/migrations/003_create-users-table.sql +56 -0
- package/backend/migrations/004_add-reload-postgrest-func.sql +24 -0
- package/backend/migrations/005_enable-project-admin-modify-users.sql +30 -0
- package/backend/migrations/006_modify-ai-usage-table.sql +25 -0
- package/backend/migrations/007_drop-metadata-table.sql +2 -0
- package/backend/migrations/008_add-system-tables.sql +77 -0
- package/backend/migrations/009_add-function-secrets.sql +24 -0
- package/backend/migrations/010_modify-ai-config-modalities.sql +93 -0
- package/backend/migrations/011_refactor-secrets-table.sql +15 -0
- package/backend/migrations/012_add-storage-uploaded-by.sql +8 -0
- package/backend/package.json +75 -0
- package/backend/src/api/middleware/auth.ts +240 -0
- package/backend/src/api/middleware/error.ts +231 -0
- package/backend/src/api/middleware/upload.ts +59 -0
- package/backend/src/api/routes/agent.ts +29 -0
- package/backend/src/api/routes/ai.ts +472 -0
- package/backend/src/api/routes/auth.oauth.ts +482 -0
- package/backend/src/api/routes/auth.ts +386 -0
- package/backend/src/api/routes/database.advance.ts +275 -0
- package/backend/src/api/routes/database.records.ts +246 -0
- package/backend/src/api/routes/database.tables.ts +161 -0
- package/backend/src/api/routes/docs.ts +66 -0
- package/backend/src/api/routes/functions.ts +183 -0
- package/backend/src/api/routes/logs.ts +150 -0
- package/backend/src/api/routes/metadata.ts +160 -0
- package/backend/src/api/routes/openapi.ts +82 -0
- package/backend/src/api/routes/secrets.ts +199 -0
- package/backend/src/api/routes/storage.ts +547 -0
- package/backend/src/api/routes/usage.ts +96 -0
- package/backend/src/core/ai/chat.ts +207 -0
- package/backend/src/core/ai/client.ts +242 -0
- package/backend/src/core/ai/config.ts +187 -0
- package/backend/src/core/ai/image.ts +156 -0
- package/backend/src/core/ai/model.ts +117 -0
- package/backend/src/core/ai/usage.ts +290 -0
- package/backend/src/core/auth/auth.ts +781 -0
- package/backend/src/core/auth/oauth.ts +398 -0
- package/backend/src/core/database/advance.ts +1074 -0
- package/backend/src/core/database/manager.ts +178 -0
- package/backend/src/core/database/table.ts +772 -0
- package/backend/src/core/documentation/agent.ts +689 -0
- package/backend/src/core/documentation/openapi.ts +856 -0
- package/backend/src/core/functions/functions.ts +310 -0
- package/backend/src/core/logs/analytics.ts +76 -0
- package/backend/src/core/logs/audit.ts +255 -0
- package/backend/src/core/logs/providers/base.provider.ts +83 -0
- package/backend/src/core/logs/providers/cloudwatch.provider.ts +510 -0
- package/backend/src/core/logs/providers/localdb.provider.ts +246 -0
- package/backend/src/core/secrets/encryption.ts +58 -0
- package/backend/src/core/secrets/secrets.ts +410 -0
- package/backend/src/core/socket/socket.ts +388 -0
- package/backend/src/core/socket/types.ts +79 -0
- package/backend/src/core/storage/storage.ts +923 -0
- package/backend/src/server.ts +288 -0
- package/backend/src/types/ai.ts +46 -0
- package/backend/src/types/auth.ts +90 -0
- package/backend/src/types/database.ts +136 -0
- package/backend/src/types/error-constants.ts +86 -0
- package/backend/src/types/logs.ts +47 -0
- package/backend/src/types/profile.ts +55 -0
- package/backend/src/types/storage.ts +23 -0
- package/backend/src/utils/cloud-token.ts +39 -0
- package/backend/src/utils/constants.ts +1 -0
- package/backend/src/utils/environment.ts +35 -0
- package/backend/src/utils/helpers.ts +49 -0
- package/backend/src/utils/logger.ts +13 -0
- package/backend/src/utils/response.ts +62 -0
- package/backend/src/utils/seed.ts +205 -0
- package/backend/src/utils/sql-parser.ts +63 -0
- package/backend/src/utils/uuid.ts +9 -0
- package/backend/src/utils/validations.ts +129 -0
- package/backend/tests/README.md +134 -0
- package/backend/tests/cleanup-all-test-data.sh +231 -0
- package/backend/tests/cloud/test-s3-multitenant.sh +132 -0
- package/backend/tests/local/comprehensive-curl-tests.sh +156 -0
- package/backend/tests/local/test-auth-router.sh +144 -0
- package/backend/tests/local/test-database-router.sh +222 -0
- package/backend/tests/local/test-e2e.sh +241 -0
- package/backend/tests/local/test-fk-errors.sh +97 -0
- package/backend/tests/local/test-id-field.sh +201 -0
- package/backend/tests/local/test-public-bucket.sh +265 -0
- package/backend/tests/local/test-secrets.sh +248 -0
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -0
- package/backend/tests/local/test-traditional-rest.sh +209 -0
- package/backend/tests/manual/README.md +51 -0
- package/backend/tests/manual/create-large-table-simple.sql +11 -0
- package/backend/tests/manual/seed-large-table.sql +101 -0
- package/backend/tests/manual/setup-large-table-extras.sql +34 -0
- package/backend/tests/manual/test-better-auth.sh +303 -0
- package/backend/tests/manual/test-bulk-upsert.sh +410 -0
- package/backend/tests/manual/test-database-advance.sh +297 -0
- package/backend/tests/manual/test-postgrest-stability.sh +192 -0
- package/backend/tests/manual/test-rawsql-export-import.sh +412 -0
- package/backend/tests/manual/test-universal-storage.sh +264 -0
- package/backend/tests/manual/test-users.sql +18 -0
- package/backend/tests/run-all-tests.sh +140 -0
- package/backend/tests/setup.ts +22 -0
- package/backend/tests/test-config.sh +303 -0
- package/backend/tsconfig.json +23 -0
- package/backend/tsup.config.ts +18 -0
- package/backend/vitest.config.ts +22 -0
- package/docker-compose.prod.yml +145 -0
- package/docker-compose.yml +167 -0
- package/docker-init/db/db-init.sql +125 -0
- package/docker-init/db/jwt.sql +5 -0
- package/docker-init/db/logs.sql +9 -0
- package/docker-init/db/postgresql.conf +17 -0
- package/docs/deprecated/insforge-auth-api.md +215 -0
- package/docs/deprecated/insforge-auth-sdk.md +100 -0
- package/docs/deprecated/insforge-db-api.md +359 -0
- package/docs/deprecated/insforge-db-sdk.md +140 -0
- package/docs/deprecated/insforge-debug-sdk.md +157 -0
- package/docs/deprecated/insforge-debug.md +65 -0
- package/docs/deprecated/insforge-instructions.md +124 -0
- package/docs/deprecated/insforge-project.md +118 -0
- package/docs/deprecated/insforge-storage-api.md +279 -0
- package/docs/deprecated/insforge-storage-sdk.md +159 -0
- package/docs/insforge-instructions-sdk.md +407 -0
- package/eslint.config.js +317 -0
- package/examples/oauth/frontend-oauth-example.html +251 -0
- package/examples/response-examples.md +444 -0
- package/frontend/README.md +112 -0
- package/frontend/components.json +17 -0
- package/frontend/index.html +13 -0
- package/frontend/package.json +63 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/src/App.tsx +106 -0
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -0
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -0
- package/frontend/src/assets/icons/checked.svg +3 -0
- package/frontend/src/assets/icons/error.svg +3 -0
- package/frontend/src/assets/icons/pencil.svg +4 -0
- package/frontend/src/assets/icons/refresh.svg +4 -0
- package/frontend/src/assets/icons/step_active.svg +3 -0
- package/frontend/src/assets/icons/step_inactive.svg +11 -0
- package/frontend/src/assets/icons/warning.svg +3 -0
- package/frontend/src/assets/logos/amazon.svg +1 -0
- package/frontend/src/assets/logos/claude_code.svg +3 -0
- package/frontend/src/assets/logos/cline.svg +6 -0
- package/frontend/src/assets/logos/cursor.svg +20 -0
- package/frontend/src/assets/logos/discord.svg +9 -0
- package/frontend/src/assets/logos/gemini.svg +19 -0
- package/frontend/src/assets/logos/github.svg +5 -0
- package/frontend/src/assets/logos/google.svg +13 -0
- package/frontend/src/assets/logos/grok.svg +10 -0
- package/frontend/src/assets/logos/insforge_dark.svg +15 -0
- package/frontend/src/assets/logos/insforge_light.svg +15 -0
- package/frontend/src/assets/logos/openai.svg +10 -0
- package/frontend/src/assets/logos/roo_code.svg +9 -0
- package/frontend/src/assets/logos/trae.svg +3 -0
- package/frontend/src/assets/logos/windsurf.svg +10 -0
- package/frontend/src/components/ButtonWithLoading.tsx +27 -0
- package/frontend/src/components/Checkbox.tsx +61 -0
- package/frontend/src/components/CodeBlock.tsx +32 -0
- package/frontend/src/components/ConfirmDialog.tsx +96 -0
- package/frontend/src/components/CopyButton.tsx +69 -0
- package/frontend/src/components/DeleteActionButton.tsx +42 -0
- package/frontend/src/components/EmptyState.tsx +41 -0
- package/frontend/src/components/ErrorState.tsx +35 -0
- package/frontend/src/components/FeatureSidebar.tsx +126 -0
- package/frontend/src/components/FeatureSidebarItem.tsx +101 -0
- package/frontend/src/components/JsonHighlight.tsx +61 -0
- package/frontend/src/components/LoadingState.tsx +16 -0
- package/frontend/src/components/PaginationControls.tsx +54 -0
- package/frontend/src/components/PromptDialog.tsx +68 -0
- package/frontend/src/components/SearchInput.tsx +90 -0
- package/frontend/src/components/SelectionClearButton.tsx +26 -0
- package/frontend/src/components/Stepper.tsx +139 -0
- package/frontend/src/components/ThemeToggle.tsx +58 -0
- package/frontend/src/components/TypeBadge.tsx +20 -0
- package/frontend/src/components/datagrid/DataGrid.tsx +264 -0
- package/frontend/src/components/datagrid/DefaultCellRenderer.tsx +114 -0
- package/frontend/src/components/datagrid/IdCell.tsx +44 -0
- package/frontend/src/components/datagrid/SortableHeader.tsx +74 -0
- package/frontend/src/components/datagrid/cell-editors/BooleanCellEditor.tsx +54 -0
- package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +483 -0
- package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +362 -0
- package/frontend/src/components/datagrid/cell-editors/TextCellEditor.tsx +38 -0
- package/frontend/src/components/datagrid/cell-editors/index.ts +14 -0
- package/frontend/src/components/datagrid/cell-editors/types.ts +43 -0
- package/frontend/src/components/datagrid/datagridTypes.tsx +72 -0
- package/frontend/src/components/datagrid/index.tsx +20 -0
- package/frontend/src/components/index.ts +39 -0
- package/frontend/src/components/layout/AppHeader.tsx +146 -0
- package/frontend/src/components/layout/AppSidebar.tsx +190 -0
- package/frontend/src/components/layout/CloudLayout.tsx +95 -0
- package/frontend/src/components/layout/Layout.tsx +43 -0
- package/frontend/src/components/radix/Alert.tsx +45 -0
- package/frontend/src/components/radix/AlertDialog.tsx +115 -0
- package/frontend/src/components/radix/Avatar.tsx +45 -0
- package/frontend/src/components/radix/Badge.tsx +33 -0
- package/frontend/src/components/radix/Button.tsx +50 -0
- package/frontend/src/components/radix/Card.tsx +58 -0
- package/frontend/src/components/radix/Dialog.tsx +98 -0
- package/frontend/src/components/radix/DropdownMenu.tsx +185 -0
- package/frontend/src/components/radix/Form.tsx +167 -0
- package/frontend/src/components/radix/Input.tsx +22 -0
- package/frontend/src/components/radix/Label.tsx +19 -0
- package/frontend/src/components/radix/Popover.tsx +29 -0
- package/frontend/src/components/radix/ScrollArea.tsx +44 -0
- package/frontend/src/components/radix/Select.tsx +151 -0
- package/frontend/src/components/radix/Separator.tsx +26 -0
- package/frontend/src/components/radix/Sheet.tsx +119 -0
- package/frontend/src/components/radix/Skeleton.tsx +7 -0
- package/frontend/src/components/radix/Switch.tsx +29 -0
- package/frontend/src/components/radix/Tabs.tsx +50 -0
- package/frontend/src/components/radix/Textarea.tsx +21 -0
- package/frontend/src/components/radix/Tooltip.tsx +28 -0
- package/frontend/src/features/ai/components/AIConfigCard.tsx +154 -0
- package/frontend/src/features/ai/components/AIConfigDialog.tsx +76 -0
- package/frontend/src/features/ai/components/AIConfigForm.tsx +222 -0
- package/frontend/src/features/ai/components/AIEmptyState.tsx +18 -0
- package/frontend/src/features/ai/components/fields/ModalityField.tsx +87 -0
- package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +134 -0
- package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +33 -0
- package/frontend/src/features/ai/helpers.ts +155 -0
- package/frontend/src/features/ai/hooks/useAIConfigs.ts +221 -0
- package/frontend/src/features/ai/hooks/useAIUsage.ts +77 -0
- package/frontend/src/features/ai/page/AIPage.tsx +178 -0
- package/frontend/src/features/ai/services/ai.service.ts +148 -0
- package/frontend/src/features/auth/components/AddOAuthDialog.tsx +106 -0
- package/frontend/src/features/auth/components/AuthMethodTab.tsx +238 -0
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +303 -0
- package/frontend/src/features/auth/components/OAuthEmptyState.tsx +15 -0
- package/frontend/src/features/auth/components/UserFormDialog.tsx +248 -0
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +183 -0
- package/frontend/src/features/auth/components/UsersTab.tsx +114 -0
- package/frontend/src/features/auth/hooks/useOAuthConfig.ts +129 -0
- package/frontend/src/features/auth/hooks/useUsers.ts +57 -0
- package/frontend/src/features/auth/index.ts +9 -0
- package/frontend/src/features/auth/page/AuthenticationPage.tsx +169 -0
- package/frontend/src/features/auth/services/auth.service.ts +112 -0
- package/frontend/src/features/auth/services/oauth.service.ts +49 -0
- package/frontend/src/features/dashboard/page/DashboardPage.tsx +194 -0
- package/frontend/src/features/database/components/ColumnTypeSelect.tsx +64 -0
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +282 -0
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +187 -0
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +378 -0
- package/frontend/src/features/database/components/LinkRecordModal.tsx +288 -0
- package/frontend/src/features/database/components/RecordFormDialog.tsx +164 -0
- package/frontend/src/features/database/components/RecordFormField.tsx +568 -0
- package/frontend/src/features/database/components/TableEmptyState.tsx +21 -0
- package/frontend/src/features/database/components/TableForm.tsx +656 -0
- package/frontend/src/features/database/components/TableFormColumn.tsx +137 -0
- package/frontend/src/features/database/components/TableListSkeleton.tsx +9 -0
- package/frontend/src/features/database/components/TableSidebar.tsx +47 -0
- package/frontend/src/features/database/constants.ts +26 -0
- package/frontend/src/features/database/helpers.ts +125 -0
- package/frontend/src/features/database/hooks/UseLinkModal.tsx +78 -0
- package/frontend/src/features/database/index.ts +12 -0
- package/frontend/src/features/database/page/DatabasePage.tsx +626 -0
- package/frontend/src/features/database/schema.ts +25 -0
- package/frontend/src/features/database/services/database.service.ts +216 -0
- package/frontend/src/features/functions/components/FunctionEmptyState.tsx +15 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +71 -0
- package/frontend/src/features/functions/components/FunctionViewer.tsx +46 -0
- package/frontend/src/features/functions/components/FunctionsContent.tsx +88 -0
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -0
- package/frontend/src/features/functions/components/SecretEmptyState.tsx +23 -0
- package/frontend/src/features/functions/components/SecretRow.tsx +68 -0
- package/frontend/src/features/functions/components/SecretsContent.tsx +120 -0
- package/frontend/src/features/functions/hooks/useFunctions.ts +106 -0
- package/frontend/src/features/functions/page/FunctionsPage.tsx +28 -0
- package/frontend/src/features/functions/services/functions.service.ts +48 -0
- package/frontend/src/features/login/components/AuthErrorBoundary.tsx +87 -0
- package/frontend/src/features/login/components/PrivateRoute.tsx +24 -0
- package/frontend/src/features/login/page/CloudLoginPage.tsx +93 -0
- package/frontend/src/features/login/page/LoginPage.tsx +174 -0
- package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +313 -0
- package/frontend/src/features/logs/components/LogsTable.tsx +199 -0
- package/frontend/src/features/logs/hooks/useAuditLogs.ts +39 -0
- package/frontend/src/features/logs/index.ts +5 -0
- package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +530 -0
- package/frontend/src/features/logs/page/AuditsPage.tsx +192 -0
- package/frontend/src/features/logs/services/log.service.ts +171 -0
- package/frontend/src/features/metadata/hooks/useMetadata.ts +53 -0
- package/frontend/src/features/metadata/index.ts +0 -0
- package/frontend/src/features/metadata/page/MetadataPage.tsx +136 -0
- package/frontend/src/features/metadata/services/metadata.service.ts +17 -0
- package/frontend/src/features/onboard/components/CompletionCard.tsx +41 -0
- package/frontend/src/features/onboard/components/OnboardButton.tsx +84 -0
- package/frontend/src/features/onboard/components/StepContent.tsx +91 -0
- package/frontend/src/features/onboard/components/TestConnectionStep.tsx +53 -0
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +35 -0
- package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +144 -0
- package/frontend/src/features/onboard/components/mcp/index.ts +4 -0
- package/frontend/src/features/onboard/components/mcp/mcp-helper.tsx +98 -0
- package/frontend/src/features/onboard/index.ts +3 -0
- package/frontend/src/features/onboard/page/OnBoardPage.tsx +104 -0
- package/frontend/src/features/onboard/types.ts +8 -0
- package/frontend/src/features/secrets/hooks/useSecrets.ts +139 -0
- package/frontend/src/features/secrets/services/secrets.service.ts +57 -0
- package/frontend/src/features/storage/components/BucketEmptyState.tsx +19 -0
- package/frontend/src/features/storage/components/BucketFormDialog.tsx +194 -0
- package/frontend/src/features/storage/components/BucketListSkeleton.tsx +17 -0
- package/frontend/src/features/storage/components/FilePreviewDialog.tsx +287 -0
- package/frontend/src/features/storage/components/StorageDataGrid.tsx +239 -0
- package/frontend/src/features/storage/components/StorageManager.tsx +236 -0
- package/frontend/src/features/storage/components/StorageSidebar.tsx +44 -0
- package/frontend/src/features/storage/components/UploadToast.tsx +46 -0
- package/frontend/src/features/storage/index.ts +3 -0
- package/frontend/src/features/storage/page/StoragePage.tsx +553 -0
- package/frontend/src/features/storage/services/storage.service.ts +144 -0
- package/frontend/src/features/visualizer/components/AuthNode.tsx +107 -0
- package/frontend/src/features/visualizer/components/BucketNode.tsx +34 -0
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +359 -0
- package/frontend/src/features/visualizer/components/TableNode.tsx +152 -0
- package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +24 -0
- package/frontend/src/features/visualizer/components/index.ts +5 -0
- package/frontend/src/features/visualizer/page/VisualizerPage.tsx +127 -0
- package/frontend/src/index.css +248 -0
- package/frontend/src/lib/api/client.ts +163 -0
- package/frontend/src/lib/contexts/AuthContext.tsx +157 -0
- package/frontend/src/lib/contexts/OnboardStepContext.tsx +68 -0
- package/frontend/src/lib/contexts/SocketContext.tsx +303 -0
- package/frontend/src/lib/contexts/ThemeContext.tsx +125 -0
- package/frontend/src/lib/hooks/useAuth.ts +4 -0
- package/frontend/src/lib/hooks/useConfirm.ts +55 -0
- package/frontend/src/lib/hooks/useInterval.ts +27 -0
- package/frontend/src/lib/hooks/useMediaQuery.ts +59 -0
- package/frontend/src/lib/hooks/useOnboardingCompletion.ts +29 -0
- package/frontend/src/lib/hooks/usePagination.ts +27 -0
- package/frontend/src/lib/hooks/useTimeout.ts +27 -0
- package/frontend/src/lib/hooks/useToast.tsx +229 -0
- package/frontend/src/lib/utils/constants.ts +38 -0
- package/frontend/src/lib/utils/utils.ts +165 -0
- package/frontend/src/lib/utils/validation-schemas.ts +126 -0
- package/frontend/src/main.tsx +16 -0
- package/frontend/src/rdg.css +194 -0
- package/frontend/src/vite-env.d.ts +12 -0
- package/frontend/tailwind.config.js +97 -0
- package/frontend/tsconfig.json +26 -0
- package/frontend/tsconfig.node.json +10 -0
- package/frontend/vite.config.ts +37 -0
- package/frontend/vitest.config.ts +36 -0
- package/functions/deno.json +25 -0
- package/functions/server.ts +290 -0
- package/functions/worker-template.js +126 -0
- package/openapi/ai.yaml +689 -0
- package/openapi/auth.yaml +563 -0
- package/openapi/functions.yaml +476 -0
- package/openapi/health.yaml +30 -0
- package/openapi/logs.yaml +224 -0
- package/openapi/metadata.yaml +178 -0
- package/openapi/records.yaml +382 -0
- package/openapi/secrets.yaml +371 -0
- package/openapi/storage.yaml +876 -0
- package/openapi/tables.yaml +464 -0
- package/package.json +88 -0
- package/shared-schemas/package.json +31 -0
- package/shared-schemas/src/ai-api.schema.ts +167 -0
- package/shared-schemas/src/ai.schema.ts +54 -0
- package/shared-schemas/src/auth-api.schema.ts +193 -0
- package/shared-schemas/src/auth.schema.ts +94 -0
- package/shared-schemas/src/database-api.schema.ts +259 -0
- package/shared-schemas/src/database.schema.ts +69 -0
- package/shared-schemas/src/functions-api.schema.ts +25 -0
- package/shared-schemas/src/functions.schema.ts +16 -0
- package/shared-schemas/src/index.ts +13 -0
- package/shared-schemas/src/logs-api.schema.ts +49 -0
- package/shared-schemas/src/logs.schema.ts +14 -0
- package/shared-schemas/src/metadata.schema.ts +56 -0
- package/shared-schemas/src/storage-api.schema.ts +65 -0
- package/shared-schemas/src/storage.schema.ts +19 -0
- package/shared-schemas/tsconfig.json +21 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
|
|
3
|
+
import { AIUsageService } from './usage';
|
|
4
|
+
import { AIConfigService } from './config';
|
|
5
|
+
import { AIClientService } from './client';
|
|
6
|
+
import type {
|
|
7
|
+
AIConfigurationSchema,
|
|
8
|
+
ImageGenerationRequest,
|
|
9
|
+
ImageGenerationResponse,
|
|
10
|
+
} from '@insforge/shared-schemas';
|
|
11
|
+
import logger from '@/utils/logger.js';
|
|
12
|
+
import { OpenRouterImageMessage } from '@/types/ai';
|
|
13
|
+
|
|
14
|
+
export class ImageService {
|
|
15
|
+
private static aiUsageService = new AIUsageService();
|
|
16
|
+
private static aiConfigService = new AIConfigService();
|
|
17
|
+
private static aiCredentialsService = AIClientService.getInstance();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate model and get config
|
|
21
|
+
*/
|
|
22
|
+
private static async validateAndGetConfig(
|
|
23
|
+
modelId: string
|
|
24
|
+
): Promise<AIConfigurationSchema | null> {
|
|
25
|
+
const aiConfig = await ImageService.aiConfigService.findByModelId(modelId);
|
|
26
|
+
if (!aiConfig) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Model ${modelId} is not enabled. Please contact your administrator to enable this model.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return aiConfig;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generate images using the specified model
|
|
36
|
+
* @param options - Image generation options
|
|
37
|
+
*/
|
|
38
|
+
static async generate(options: ImageGenerationRequest): Promise<ImageGenerationResponse> {
|
|
39
|
+
// Get the client (handles validation and initialization automatically)
|
|
40
|
+
const client = await this.aiCredentialsService.getClient();
|
|
41
|
+
|
|
42
|
+
// Validate model and get config
|
|
43
|
+
const aiConfig = await ImageService.validateAndGetConfig(options.model);
|
|
44
|
+
|
|
45
|
+
const model = options.model;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Concatenate system prompt with user prompt if it exists
|
|
49
|
+
// This is because OpenRouter image models don't properly handle system messages
|
|
50
|
+
let finalPrompt = options.prompt;
|
|
51
|
+
if (aiConfig?.systemPrompt) {
|
|
52
|
+
finalPrompt = `${aiConfig.systemPrompt}\n\n${options.prompt}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Build content for the message
|
|
56
|
+
const userContent = options.images?.length
|
|
57
|
+
? [
|
|
58
|
+
{ type: 'text', text: finalPrompt },
|
|
59
|
+
...options.images.map((image) => ({
|
|
60
|
+
type: 'image_url',
|
|
61
|
+
image_url: { url: image.url },
|
|
62
|
+
})),
|
|
63
|
+
]
|
|
64
|
+
: finalPrompt;
|
|
65
|
+
|
|
66
|
+
// Build the request - OpenRouter extends OpenAI's API with additional fields
|
|
67
|
+
const request = {
|
|
68
|
+
model,
|
|
69
|
+
messages: [
|
|
70
|
+
{
|
|
71
|
+
role: 'user',
|
|
72
|
+
content: userContent,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
stream: false, // Explicitly disable streaming
|
|
76
|
+
// OpenRouter-specific field for image generation
|
|
77
|
+
modalities: ['text', 'image'],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Use OpenRouter's standard chat completions API
|
|
81
|
+
// Cast the extended request to the base OpenAI type for the SDK call
|
|
82
|
+
const response = (await client.chat.completions.create(
|
|
83
|
+
request as OpenAI.Chat.ChatCompletionCreateParamsNonStreaming
|
|
84
|
+
)) as OpenAI.Chat.ChatCompletion;
|
|
85
|
+
|
|
86
|
+
// Initialize the result
|
|
87
|
+
const result: ImageGenerationResponse = {
|
|
88
|
+
images: [],
|
|
89
|
+
metadata: {
|
|
90
|
+
model: model,
|
|
91
|
+
usage: response.usage
|
|
92
|
+
? {
|
|
93
|
+
promptTokens: response.usage.prompt_tokens || 0,
|
|
94
|
+
completionTokens: response.usage.completion_tokens || 0,
|
|
95
|
+
totalTokens: response.usage.total_tokens || 0,
|
|
96
|
+
}
|
|
97
|
+
: undefined,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Process the OpenAI-compatible response
|
|
102
|
+
if (response.choices && response.choices.length > 0) {
|
|
103
|
+
for (const choice of response.choices) {
|
|
104
|
+
const message = choice.message;
|
|
105
|
+
|
|
106
|
+
// Extract text content if present (for multimodal responses)
|
|
107
|
+
if (message.content) {
|
|
108
|
+
result.text = message.content;
|
|
109
|
+
// Use text as revised prompt if available
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// OpenRouter adds an 'images' field to the assistant message for image generation
|
|
113
|
+
// Cast the message to include the extended OpenRouter fields
|
|
114
|
+
const extendedMessage = message as typeof message & {
|
|
115
|
+
images?: OpenRouterImageMessage[];
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Check for images in the OpenRouter format
|
|
119
|
+
if (extendedMessage.images && Array.isArray(extendedMessage.images)) {
|
|
120
|
+
for (const image of extendedMessage.images) {
|
|
121
|
+
if (image.type === 'image_url' && image.image_url?.url) {
|
|
122
|
+
result.images.push({
|
|
123
|
+
type: 'imageUrl',
|
|
124
|
+
imageUrl: image.image_url?.url,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Track usage if config is available
|
|
133
|
+
if (aiConfig?.id) {
|
|
134
|
+
// Pass token usage information if available
|
|
135
|
+
const inputTokens = result.metadata?.usage?.promptTokens;
|
|
136
|
+
const outputTokens = result.metadata?.usage?.completionTokens;
|
|
137
|
+
|
|
138
|
+
await ImageService.aiUsageService.trackImageGenerationUsage(
|
|
139
|
+
aiConfig.id,
|
|
140
|
+
result.images.length,
|
|
141
|
+
undefined, // image resolution not available from OpenRouter
|
|
142
|
+
inputTokens,
|
|
143
|
+
outputTokens,
|
|
144
|
+
options.model
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
logger.error('Image generation error', { error });
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Failed to generate image: ${error instanceof Error ? error.message : String(error)}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { isCloudEnvironment } from '@/utils/environment';
|
|
2
|
+
import { AIClientService } from './client';
|
|
3
|
+
import type { RawOpenRouterModel } from '@/types/ai';
|
|
4
|
+
import type { OpenRouterModel } from '@insforge/shared-schemas';
|
|
5
|
+
|
|
6
|
+
export interface ModelProviderInfo {
|
|
7
|
+
provider: string;
|
|
8
|
+
configured: boolean;
|
|
9
|
+
models: OpenRouterModel[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ListModelsResponse {
|
|
13
|
+
text: ModelProviderInfo[];
|
|
14
|
+
image: ModelProviderInfo[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class AIModelService {
|
|
18
|
+
/**
|
|
19
|
+
* Get all available AI models
|
|
20
|
+
* Fetches from cloud API if in cloud environment, otherwise from OpenRouter directly
|
|
21
|
+
*/
|
|
22
|
+
static async getModels(): Promise<ListModelsResponse> {
|
|
23
|
+
const credentialsService = AIClientService.getInstance();
|
|
24
|
+
const configured = credentialsService.isConfigured();
|
|
25
|
+
|
|
26
|
+
if (!configured) {
|
|
27
|
+
return {
|
|
28
|
+
text: [
|
|
29
|
+
{
|
|
30
|
+
provider: 'openrouter',
|
|
31
|
+
configured: false,
|
|
32
|
+
models: [],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
image: [
|
|
36
|
+
{
|
|
37
|
+
provider: 'openrouter',
|
|
38
|
+
configured: false,
|
|
39
|
+
models: [],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get API key from credentials service
|
|
46
|
+
const apiKey = await credentialsService.getApiKey();
|
|
47
|
+
|
|
48
|
+
// Determine the API endpoint based on environment
|
|
49
|
+
const apiUrl = isCloudEnvironment()
|
|
50
|
+
? 'https://api.insforge.dev/ai/v1/models'
|
|
51
|
+
: 'https://openrouter.ai/api/v1/models/user';
|
|
52
|
+
|
|
53
|
+
// Fetch models from the appropriate endpoint
|
|
54
|
+
const response = await fetch(apiUrl, {
|
|
55
|
+
headers: {
|
|
56
|
+
Authorization: `Bearer ${apiKey}`,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`Failed to fetch models: ${response.statusText}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const data = (await response.json()) as { data: RawOpenRouterModel[] };
|
|
65
|
+
const models = data.data || [];
|
|
66
|
+
|
|
67
|
+
const textModels: OpenRouterModel[] = [];
|
|
68
|
+
const imageModels: OpenRouterModel[] = [];
|
|
69
|
+
|
|
70
|
+
for (const model of models) {
|
|
71
|
+
// Classify based on output modality
|
|
72
|
+
const transformedModel: OpenRouterModel = {
|
|
73
|
+
...model,
|
|
74
|
+
architecture: model.architecture
|
|
75
|
+
? {
|
|
76
|
+
inputModalities: model.architecture.input_modalities || [],
|
|
77
|
+
outputModalities: model.architecture.output_modalities || [],
|
|
78
|
+
tokenizer: model.architecture.tokenizer || '',
|
|
79
|
+
instructType: model.architecture.instruct_type || '',
|
|
80
|
+
}
|
|
81
|
+
: undefined,
|
|
82
|
+
topProvider: model.topProvider
|
|
83
|
+
? {
|
|
84
|
+
isModerated: model.topProvider.is_moderated,
|
|
85
|
+
contextLength: model.topProvider.context_length,
|
|
86
|
+
maxCompletionTokens: model.topProvider.max_completion_tokens,
|
|
87
|
+
}
|
|
88
|
+
: undefined,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
if (model.architecture?.output_modalities?.includes('image')) {
|
|
92
|
+
imageModels.push(transformedModel);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (model.architecture?.output_modalities?.includes('text')) {
|
|
96
|
+
textModels.push(transformedModel);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
text: [
|
|
102
|
+
{
|
|
103
|
+
provider: 'openrouter',
|
|
104
|
+
configured: true,
|
|
105
|
+
models: textModels,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
image: [
|
|
109
|
+
{
|
|
110
|
+
provider: 'openrouter',
|
|
111
|
+
configured: true,
|
|
112
|
+
models: imageModels,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
import { DatabaseManager } from '@/core/database/manager.js';
|
|
3
|
+
import logger from '@/utils/logger.js';
|
|
4
|
+
import type {
|
|
5
|
+
AIUsageDataSchema,
|
|
6
|
+
AIUsageRecordSchema,
|
|
7
|
+
AIUsageSummarySchema,
|
|
8
|
+
ListAIUsageResponse,
|
|
9
|
+
} from '@insforge/shared-schemas';
|
|
10
|
+
|
|
11
|
+
export class AIUsageService {
|
|
12
|
+
private pool: Pool | null = null;
|
|
13
|
+
|
|
14
|
+
private getPool(): Pool {
|
|
15
|
+
if (!this.pool) {
|
|
16
|
+
this.pool = DatabaseManager.getInstance().getPool();
|
|
17
|
+
}
|
|
18
|
+
return this.pool;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async trackUsage(data: AIUsageDataSchema): Promise<{ id: string }> {
|
|
22
|
+
const client = await this.getPool().connect();
|
|
23
|
+
try {
|
|
24
|
+
const result = await client.query(
|
|
25
|
+
`INSERT INTO _ai_usage (config_id, input_tokens, output_tokens, image_count, image_resolution)
|
|
26
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
27
|
+
RETURNING id`,
|
|
28
|
+
[
|
|
29
|
+
data.configId,
|
|
30
|
+
data.inputTokens || null,
|
|
31
|
+
data.outputTokens || null,
|
|
32
|
+
data.imageCount || null,
|
|
33
|
+
data.imageResolution || null,
|
|
34
|
+
]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
logger.info('AI usage tracked', {
|
|
38
|
+
id: result.rows[0].id,
|
|
39
|
+
configId: data.configId,
|
|
40
|
+
inputTokens: data.inputTokens,
|
|
41
|
+
outputTokens: data.outputTokens,
|
|
42
|
+
imageCount: data.imageCount,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return { id: result.rows[0].id };
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.error('Failed to track AI usage', { error, data });
|
|
48
|
+
throw new Error('Failed to track AI usage');
|
|
49
|
+
} finally {
|
|
50
|
+
client.release();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async trackChatUsage(
|
|
55
|
+
configId: string,
|
|
56
|
+
inputTokens?: number,
|
|
57
|
+
outputTokens?: number,
|
|
58
|
+
modelId?: string
|
|
59
|
+
): Promise<{ id: string }> {
|
|
60
|
+
const totalTokens = (inputTokens || 0) + (outputTokens || 0);
|
|
61
|
+
|
|
62
|
+
const client = await this.getPool().connect();
|
|
63
|
+
try {
|
|
64
|
+
const usageResult = await client.query(
|
|
65
|
+
`INSERT INTO _ai_usage (config_id, input_tokens, output_tokens, model_id)
|
|
66
|
+
VALUES ($1, $2, $3, $4)
|
|
67
|
+
RETURNING id`,
|
|
68
|
+
[configId, inputTokens || null, outputTokens || null, modelId || null]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
logger.info('Chat usage tracked', {
|
|
72
|
+
id: usageResult.rows[0].id,
|
|
73
|
+
configId,
|
|
74
|
+
inputTokens,
|
|
75
|
+
outputTokens,
|
|
76
|
+
totalTokens,
|
|
77
|
+
modelId,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return { id: usageResult.rows[0].id };
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.error('Failed to track chat usage', { error, configId });
|
|
83
|
+
throw new Error('Failed to track chat usage');
|
|
84
|
+
} finally {
|
|
85
|
+
client.release();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async trackImageGenerationUsage(
|
|
90
|
+
configId: string,
|
|
91
|
+
imageCount: number,
|
|
92
|
+
imageResolution?: string,
|
|
93
|
+
inputTokens?: number,
|
|
94
|
+
outputTokens?: number,
|
|
95
|
+
modelId?: string
|
|
96
|
+
): Promise<{ id: string }> {
|
|
97
|
+
const client = await this.getPool().connect();
|
|
98
|
+
try {
|
|
99
|
+
const usageResult = await client.query(
|
|
100
|
+
`INSERT INTO _ai_usage (config_id, image_count, image_resolution, input_tokens, output_tokens, model_id)
|
|
101
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
102
|
+
RETURNING id`,
|
|
103
|
+
[
|
|
104
|
+
configId,
|
|
105
|
+
imageCount,
|
|
106
|
+
imageResolution || null,
|
|
107
|
+
inputTokens || null,
|
|
108
|
+
outputTokens || null,
|
|
109
|
+
modelId || null,
|
|
110
|
+
]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
logger.info('Image usage tracked', {
|
|
114
|
+
id: usageResult.rows[0].id,
|
|
115
|
+
configId,
|
|
116
|
+
imageCount,
|
|
117
|
+
imageResolution,
|
|
118
|
+
inputTokens,
|
|
119
|
+
outputTokens,
|
|
120
|
+
modelId,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return { id: usageResult.rows[0].id };
|
|
124
|
+
} catch (error) {
|
|
125
|
+
logger.error('Failed to track image usage', { error, configId });
|
|
126
|
+
throw new Error('Failed to track image usage');
|
|
127
|
+
} finally {
|
|
128
|
+
client.release();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getUsageByConfig(
|
|
133
|
+
configId: string,
|
|
134
|
+
startDate?: Date,
|
|
135
|
+
endDate?: Date
|
|
136
|
+
): Promise<AIUsageRecordSchema[]> {
|
|
137
|
+
const client = await this.getPool().connect();
|
|
138
|
+
try {
|
|
139
|
+
let query = `
|
|
140
|
+
SELECT id, config_id as "configId", input_tokens as "inputTokens",
|
|
141
|
+
output_tokens as "outputTokens", image_count as "imageCount",
|
|
142
|
+
image_resolution as "imageResolution", created_at as "createdAt"
|
|
143
|
+
FROM _ai_usage
|
|
144
|
+
WHERE config_id = $1
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
const params: (string | Date)[] = [configId];
|
|
148
|
+
|
|
149
|
+
if (startDate) {
|
|
150
|
+
params.push(startDate);
|
|
151
|
+
query += ` AND created_at >= $${params.length}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (endDate) {
|
|
155
|
+
params.push(endDate);
|
|
156
|
+
query += ` AND created_at <= $${params.length}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
query += ' ORDER BY created_at DESC';
|
|
160
|
+
|
|
161
|
+
const result = await client.query(query, params);
|
|
162
|
+
|
|
163
|
+
return result.rows;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
logger.error('Failed to fetch usage by config', { error, configId });
|
|
166
|
+
throw new Error('Failed to fetch usage records');
|
|
167
|
+
} finally {
|
|
168
|
+
client.release();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getUsageSummary(
|
|
173
|
+
configId?: string,
|
|
174
|
+
startDate?: Date,
|
|
175
|
+
endDate?: Date
|
|
176
|
+
): Promise<AIUsageSummarySchema> {
|
|
177
|
+
const client = await this.getPool().connect();
|
|
178
|
+
try {
|
|
179
|
+
let query = `
|
|
180
|
+
SELECT
|
|
181
|
+
COALESCE(SUM(input_tokens), 0) as "totalInputTokens",
|
|
182
|
+
COALESCE(SUM(output_tokens), 0) as "totalOutputTokens",
|
|
183
|
+
COALESCE(SUM(COALESCE(input_tokens, 0) + COALESCE(output_tokens, 0)), 0) as "totalTokens",
|
|
184
|
+
COALESCE(SUM(image_count), 0) as "totalImageCount",
|
|
185
|
+
COUNT(*) as "totalRequests"
|
|
186
|
+
FROM _ai_usage
|
|
187
|
+
WHERE 1=1
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
const params: (string | Date)[] = [];
|
|
191
|
+
|
|
192
|
+
if (configId) {
|
|
193
|
+
params.push(configId);
|
|
194
|
+
query += ` AND config_id = $${params.length}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (startDate) {
|
|
198
|
+
params.push(startDate);
|
|
199
|
+
query += ` AND created_at >= $${params.length}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (endDate) {
|
|
203
|
+
params.push(endDate);
|
|
204
|
+
query += ` AND created_at <= $${params.length}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const result = await client.query(query, params);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
totalInputTokens: parseInt(result.rows[0].totalInputTokens),
|
|
211
|
+
totalOutputTokens: parseInt(result.rows[0].totalOutputTokens),
|
|
212
|
+
totalTokens: parseInt(result.rows[0].totalTokens),
|
|
213
|
+
totalImageCount: parseInt(result.rows[0].totalImageCount),
|
|
214
|
+
totalRequests: parseInt(result.rows[0].totalRequests),
|
|
215
|
+
};
|
|
216
|
+
} catch (error) {
|
|
217
|
+
logger.error('Failed to fetch usage summary', { error, configId });
|
|
218
|
+
throw new Error('Failed to fetch usage summary');
|
|
219
|
+
} finally {
|
|
220
|
+
client.release();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async getAllUsage(
|
|
225
|
+
startDate?: Date,
|
|
226
|
+
endDate?: Date,
|
|
227
|
+
limit?: number,
|
|
228
|
+
offset?: number
|
|
229
|
+
): Promise<ListAIUsageResponse> {
|
|
230
|
+
const client = await this.getPool().connect();
|
|
231
|
+
try {
|
|
232
|
+
let query = `
|
|
233
|
+
SELECT
|
|
234
|
+
u.id,
|
|
235
|
+
u.config_id as "configId",
|
|
236
|
+
u.input_tokens as "inputTokens",
|
|
237
|
+
u.output_tokens as "outputTokens",
|
|
238
|
+
u.image_count as "imageCount",
|
|
239
|
+
u.image_resolution as "imageResolution",
|
|
240
|
+
u.created_at as "createdAt",
|
|
241
|
+
u.model_id as "modelId",
|
|
242
|
+
COALESCE(u.model_id, c.model_id) as "model",
|
|
243
|
+
c.provider,
|
|
244
|
+
c.modality
|
|
245
|
+
FROM _ai_usage u
|
|
246
|
+
LEFT JOIN _ai_configs c ON u.config_id = c.id
|
|
247
|
+
WHERE 1=1
|
|
248
|
+
`;
|
|
249
|
+
|
|
250
|
+
const params: (string | Date | number)[] = [];
|
|
251
|
+
|
|
252
|
+
if (startDate) {
|
|
253
|
+
params.push(startDate);
|
|
254
|
+
query += ` AND u.created_at >= $${params.length}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (endDate) {
|
|
258
|
+
params.push(endDate);
|
|
259
|
+
query += ` AND u.created_at <= $${params.length}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const countQuery = `SELECT COUNT(*) as total FROM (${query}) as subquery`;
|
|
263
|
+
const countResult = await client.query(countQuery, params);
|
|
264
|
+
|
|
265
|
+
query += ' ORDER BY u.created_at DESC';
|
|
266
|
+
|
|
267
|
+
if (limit) {
|
|
268
|
+
params.push(limit);
|
|
269
|
+
query += ` LIMIT $${params.length}`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (offset) {
|
|
273
|
+
params.push(offset);
|
|
274
|
+
query += ` OFFSET $${params.length}`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const result = await client.query(query, params);
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
records: result.rows,
|
|
281
|
+
total: parseInt(countResult.rows[0].total),
|
|
282
|
+
};
|
|
283
|
+
} catch (error) {
|
|
284
|
+
logger.error('Failed to fetch all usage records', { error });
|
|
285
|
+
throw new Error('Failed to fetch usage records');
|
|
286
|
+
} finally {
|
|
287
|
+
client.release();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|