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,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
|
+
}
|