insforge 1.3.0 → 1.4.8

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 (269) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/auth/package.json +5 -3
  3. package/auth/src/lib/broadcastService.ts +115 -117
  4. package/auth/src/lib/insforge.ts +8 -0
  5. package/auth/src/main.tsx +2 -4
  6. package/auth/src/pages/SignInPage.tsx +60 -60
  7. package/auth/src/pages/SignUpPage.tsx +60 -60
  8. package/auth/src/pages/VerifyEmailPage.tsx +18 -0
  9. package/auth/tsconfig.json +2 -1
  10. package/backend/package.json +10 -6
  11. package/backend/src/api/middlewares/rate-limiters.ts +127 -127
  12. package/backend/src/api/routes/ai/index.routes.ts +475 -468
  13. package/backend/src/api/routes/auth/index.routes.ts +85 -32
  14. package/backend/src/api/routes/auth/oauth.routes.ts +11 -6
  15. package/backend/src/api/routes/database/index.routes.ts +2 -0
  16. package/backend/src/api/routes/database/records.routes.ts +39 -175
  17. package/backend/src/api/routes/database/rpc.routes.ts +69 -0
  18. package/backend/src/api/routes/deployments/index.routes.ts +192 -0
  19. package/backend/src/api/routes/docs/index.routes.ts +3 -2
  20. package/backend/src/api/routes/email/index.routes.ts +35 -35
  21. package/backend/src/api/routes/functions/index.routes.ts +3 -3
  22. package/backend/src/api/routes/metadata/index.routes.ts +26 -0
  23. package/backend/src/api/routes/webhooks/index.routes.ts +109 -0
  24. package/backend/src/infra/database/database.manager.ts +0 -10
  25. package/backend/src/infra/database/migrations/018_schema-rework.sql +441 -0
  26. package/backend/src/infra/database/migrations/019_create-deployments-table.sql +36 -0
  27. package/backend/src/infra/database/migrations/020_add-audio-modality.sql +11 -0
  28. package/backend/src/infra/database/migrations/bootstrap/bootstrap-migrations.js +103 -0
  29. package/backend/src/infra/security/token.manager.ts +1 -4
  30. package/backend/src/providers/ai/openrouter.provider.ts +12 -3
  31. package/backend/src/providers/database/base.provider.ts +39 -0
  32. package/backend/src/providers/database/cloud.provider.ts +159 -0
  33. package/backend/src/providers/deployments/vercel.provider.ts +516 -0
  34. package/backend/src/server.ts +19 -7
  35. package/backend/src/services/ai/ai-config.service.ts +6 -6
  36. package/backend/src/services/ai/ai-model.service.ts +60 -60
  37. package/backend/src/services/ai/ai-usage.service.ts +7 -7
  38. package/backend/src/services/ai/chat-completion.service.ts +415 -220
  39. package/backend/src/services/ai/helpers.ts +64 -64
  40. package/backend/src/services/ai/index.ts +13 -13
  41. package/backend/src/services/auth/auth-config.service.ts +4 -4
  42. package/backend/src/services/auth/auth-otp.service.ts +6 -6
  43. package/backend/src/services/auth/auth.service.ts +134 -74
  44. package/backend/src/services/auth/index.ts +4 -4
  45. package/backend/src/services/auth/oauth-config.service.ts +12 -12
  46. package/backend/src/services/database/database-advance.service.ts +19 -55
  47. package/backend/src/services/database/database-table.service.ts +38 -85
  48. package/backend/src/services/database/postgrest-proxy.service.ts +165 -0
  49. package/backend/src/services/deployments/deployment.service.ts +693 -0
  50. package/backend/src/services/functions/function.service.ts +61 -41
  51. package/backend/src/services/logs/audit.service.ts +10 -10
  52. package/backend/src/services/secrets/secret.service.ts +101 -27
  53. package/backend/src/services/storage/storage.service.ts +30 -30
  54. package/backend/src/services/usage/usage.service.ts +6 -6
  55. package/backend/src/types/ai.ts +8 -0
  56. package/backend/src/types/auth.ts +5 -1
  57. package/backend/src/types/database.ts +2 -0
  58. package/backend/src/types/deployments.ts +33 -0
  59. package/backend/src/types/storage.ts +1 -1
  60. package/backend/src/types/webhooks.ts +45 -0
  61. package/backend/src/utils/cookies.ts +34 -35
  62. package/backend/src/utils/environment.ts +0 -14
  63. package/backend/src/utils/s3-config-loader.ts +64 -64
  64. package/backend/src/utils/seed.ts +334 -301
  65. package/backend/src/utils/sql-parser.ts +126 -0
  66. package/backend/src/utils/utils.ts +114 -114
  67. package/backend/src/utils/validations.ts +10 -10
  68. package/backend/tests/local/test-rpc.sh +141 -0
  69. package/backend/tests/local/test-secrets.sh +1 -1
  70. package/backend/tests/manual/test-ai-model-plugins.sh +258 -0
  71. package/backend/tests/manual/test-rawsql-modes.sh +24 -24
  72. package/backend/tests/unit/database-advance.test.ts +326 -0
  73. package/backend/tests/unit/helpers.test.ts +2 -2
  74. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +13 -10
  75. package/docker-compose.prod.yml +1 -1
  76. package/docker-compose.yml +1 -1
  77. package/docs/agent-docs/deployment.md +79 -0
  78. package/docs/changelog.mdx +165 -72
  79. package/docs/core-concepts/ai/architecture.mdx +1 -23
  80. package/docs/core-concepts/ai/sdk.mdx +26 -1
  81. package/docs/core-concepts/authentication/architecture.mdx +6 -8
  82. package/docs/core-concepts/authentication/sdk.mdx +387 -91
  83. package/docs/core-concepts/authentication/ui-components/customization.mdx +460 -256
  84. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +50 -24
  85. package/docs/core-concepts/authentication/ui-components/react-router.mdx +18 -19
  86. package/docs/core-concepts/authentication/ui-components/react.mdx +26 -19
  87. package/docs/core-concepts/database/architecture.mdx +58 -21
  88. package/docs/core-concepts/database/pgvector.mdx +138 -0
  89. package/docs/core-concepts/database/sdk.mdx +17 -17
  90. package/docs/core-concepts/deployments/architecture.mdx +152 -0
  91. package/docs/core-concepts/email/architecture.mdx +4 -2
  92. package/docs/core-concepts/functions/architecture.mdx +1 -1
  93. package/docs/core-concepts/functions/sdk.mdx +0 -1
  94. package/docs/core-concepts/realtime/architecture.mdx +1 -1
  95. package/docs/core-concepts/storage/architecture.mdx +1 -1
  96. package/docs/core-concepts/storage/sdk.mdx +25 -25
  97. package/docs/docs.json +14 -6
  98. package/docs/favicon.png +0 -0
  99. package/docs/favicon.svg +3 -18
  100. package/docs/images/changelog/dec-2025/apple-oauth.mp4 +0 -0
  101. package/docs/images/changelog/dec-2025/moreModels.png +0 -0
  102. package/docs/images/changelog/dec-2025/multi-region.webp +0 -0
  103. package/docs/images/changelog/dec-2025/postgres-connection.webp +0 -0
  104. package/docs/images/changelog/dec-2025/realtime2.png +0 -0
  105. package/docs/images/mcp-setup/CC-MCP-1.mp4 +0 -0
  106. package/docs/images/mcp-setup/CC-MCP-2.mp4 +0 -0
  107. package/docs/images/mcp-setup/Cursor-MCP-1.mp4 +0 -0
  108. package/docs/images/mcp-setup/Cursor-MCP-2.mp4 +0 -0
  109. package/docs/images/mcp-setup/Cursor-MCP-3.mp4 +0 -0
  110. package/docs/images/mcp-setup/claude-code-connect.png +0 -0
  111. package/docs/images/mcp-setup/cline-1.png +0 -0
  112. package/docs/images/mcp-setup/cline-2.png +0 -0
  113. package/docs/images/mcp-setup/cline-3.png +0 -0
  114. package/docs/images/mcp-setup/connect-project.png +0 -0
  115. package/docs/images/mcp-setup/copilot-1.png +0 -0
  116. package/docs/images/mcp-setup/copilot-2.png +0 -0
  117. package/docs/images/mcp-setup/copilot-3.png +0 -0
  118. package/docs/images/mcp-setup/mcp-json-1.png +0 -0
  119. package/docs/images/mcp-setup/mcp-json-2.png +0 -0
  120. package/docs/images/mcp-setup/qoder-1.png +0 -0
  121. package/docs/images/mcp-setup/qoder-2.png +0 -0
  122. package/docs/images/mcp-setup/roocode-1.png +0 -0
  123. package/docs/images/mcp-setup/roocode-2.png +0 -0
  124. package/docs/images/mcp-setup/trae-1.png +0 -0
  125. package/docs/images/mcp-setup/trae-2.png +0 -0
  126. package/docs/images/mcp-setup/trae-3.png +0 -0
  127. package/docs/images/mcp-setup/trae-4.png +0 -0
  128. package/docs/images/mcp-setup/trae-5.png +0 -0
  129. package/docs/images/mcp-setup/windsurf-1.png +0 -0
  130. package/docs/images/mcp-setup/windsurf-2.png +0 -0
  131. package/docs/insforge-instructions-sdk.md +7 -3
  132. package/docs/introduction.mdx +9 -8
  133. package/docs/mcp-setup.mdx +332 -0
  134. package/docs/oauth-server.mdx +563 -0
  135. package/docs/partnership.mdx +79 -10
  136. package/docs/quickstart.mdx +1 -1
  137. package/docs/vscode-extension.mdx +74 -0
  138. package/eslint.config.js +1 -0
  139. package/examples/response-examples.md +1 -1
  140. package/frontend/package.json +1 -1
  141. package/frontend/src/App.tsx +8 -3
  142. package/frontend/src/assets/logos/antigravity.svg +1 -0
  143. package/frontend/src/assets/logos/copilot.svg +10 -0
  144. package/frontend/src/assets/logos/deepseek.svg +139 -0
  145. package/frontend/src/assets/logos/kiro.svg +9 -0
  146. package/frontend/src/assets/logos/qoder.svg +4 -0
  147. package/frontend/src/assets/logos/qwen.svg +15 -0
  148. package/frontend/src/components/CodeBlock.tsx +2 -2
  149. package/frontend/src/components/ConnectCTA.tsx +3 -2
  150. package/frontend/src/components/datagrid/DataGrid.tsx +90 -62
  151. package/frontend/src/components/datagrid/datagridTypes.tsx +2 -1
  152. package/frontend/src/components/datagrid/index.ts +1 -1
  153. package/frontend/src/components/index.ts +0 -1
  154. package/frontend/src/components/layout/AppHeader.tsx +4 -27
  155. package/frontend/src/components/layout/AppSidebar.tsx +85 -100
  156. package/frontend/src/components/layout/Layout.tsx +34 -32
  157. package/frontend/src/components/layout/PrimaryMenu.tsx +12 -4
  158. package/frontend/src/components/radix/Select.tsx +151 -151
  159. package/frontend/src/features/ai/components/AIConfigCard.tsx +200 -200
  160. package/frontend/src/features/ai/components/AIEmptyState.tsx +23 -23
  161. package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +102 -101
  162. package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -135
  163. package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -51
  164. package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -118
  165. package/frontend/src/features/ai/components/index.ts +6 -6
  166. package/frontend/src/features/ai/helpers.ts +147 -141
  167. package/frontend/src/features/ai/pages/AIPage.tsx +166 -166
  168. package/frontend/src/features/auth/components/AuthPreview.tsx +96 -96
  169. package/frontend/src/features/auth/components/UsersDataGrid.tsx +55 -31
  170. package/frontend/src/features/auth/components/index.ts +5 -5
  171. package/frontend/src/features/auth/pages/AuthMethodsPage.tsx +275 -275
  172. package/frontend/src/features/dashboard/pages/DashboardPage.tsx +1 -1
  173. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +0 -2
  174. package/frontend/src/features/database/components/ForeignKeyCell.tsx +38 -11
  175. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +18 -8
  176. package/frontend/src/features/database/components/LinkRecordModal.tsx +61 -13
  177. package/frontend/src/features/database/components/RecordFormField.tsx +1 -1
  178. package/frontend/src/features/database/components/TableSidebar.tsx +0 -3
  179. package/frontend/src/features/database/components/TablesEmptyState.tsx +1 -1
  180. package/frontend/src/features/database/components/TemplatePreview.tsx +1 -2
  181. package/frontend/src/features/database/constants.ts +16 -28
  182. package/frontend/src/features/database/hooks/useCSVImport.ts +3 -2
  183. package/frontend/src/features/database/hooks/useRawSQL.ts +3 -2
  184. package/frontend/src/features/database/hooks/useTables.ts +5 -7
  185. package/frontend/src/features/database/pages/FunctionsPage.tsx +0 -5
  186. package/frontend/src/features/database/pages/IndexesPage.tsx +0 -5
  187. package/frontend/src/features/database/pages/PoliciesPage.tsx +0 -5
  188. package/frontend/src/features/database/pages/SQLEditorPage.tsx +2 -2
  189. package/frontend/src/features/database/pages/TriggersPage.tsx +0 -5
  190. package/frontend/src/features/database/services/advance.service.ts +1 -15
  191. package/frontend/src/features/database/services/record.service.ts +4 -20
  192. package/frontend/src/features/database/services/table.service.ts +1 -4
  193. package/frontend/src/features/database/templates/ai-chatbot.ts +6 -6
  194. package/frontend/src/features/database/templates/ecommerce-platform.ts +2 -2
  195. package/frontend/src/features/database/templates/instagram-clone.ts +10 -10
  196. package/frontend/src/features/database/templates/notion-clone.ts +8 -8
  197. package/frontend/src/features/database/templates/reddit-clone.ts +10 -10
  198. package/frontend/src/features/deployments/components/DeploymentRow.tsx +93 -0
  199. package/frontend/src/features/deployments/components/DeploymentsEmptyState.tsx +15 -0
  200. package/frontend/src/features/deployments/hooks/useDeployments.ts +157 -0
  201. package/frontend/src/features/deployments/pages/DeploymentsPage.tsx +318 -0
  202. package/frontend/src/features/deployments/services/deployments.service.ts +63 -0
  203. package/frontend/src/features/functions/components/FunctionRow.tsx +72 -72
  204. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -56
  205. package/frontend/src/features/functions/components/SecretRow.tsx +3 -3
  206. package/frontend/src/features/functions/components/index.ts +5 -5
  207. package/frontend/src/features/functions/hooks/useFunctions.ts +5 -4
  208. package/frontend/src/features/functions/hooks/useSecrets.ts +6 -9
  209. package/frontend/src/features/functions/pages/SecretsPage.tsx +118 -118
  210. package/frontend/src/features/functions/services/function.service.ts +8 -25
  211. package/frontend/src/features/functions/services/secret.service.ts +23 -41
  212. package/frontend/src/features/login/pages/CloudLoginPage.tsx +125 -118
  213. package/frontend/src/features/logs/components/LogDetailPanel.tsx +41 -0
  214. package/frontend/src/features/logs/components/LogsDataGrid.tsx +32 -1
  215. package/frontend/src/features/logs/components/index.ts +1 -0
  216. package/frontend/src/features/logs/pages/LogsPage.tsx +36 -6
  217. package/frontend/src/features/onboard/components/ApiCredentialsSection.tsx +59 -0
  218. package/frontend/src/features/onboard/components/ConnectionStringSection.tsx +180 -0
  219. package/frontend/src/features/onboard/components/McpConnectionSection.tsx +159 -0
  220. package/frontend/src/features/onboard/components/OnboardingController.tsx +68 -0
  221. package/frontend/src/features/onboard/components/OnboardingModal.tsx +121 -267
  222. package/frontend/src/features/onboard/components/ShowPasswordButton.tsx +21 -0
  223. package/frontend/src/features/onboard/components/index.ts +9 -4
  224. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +1 -1
  225. package/frontend/src/features/onboard/components/mcp/QoderDeeplinkGenerator.tsx +36 -0
  226. package/frontend/src/features/onboard/components/mcp/helpers.tsx +123 -98
  227. package/frontend/src/features/onboard/components/mcp/index.ts +4 -3
  228. package/frontend/src/features/onboard/index.ts +17 -13
  229. package/frontend/src/features/settings/pages/SettingsPage.tsx +349 -0
  230. package/frontend/src/features/visualizer/components/AuthNode.tsx +4 -4
  231. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +21 -8
  232. package/frontend/src/features/visualizer/pages/VisualizerPage.tsx +10 -1
  233. package/frontend/src/index.css +249 -249
  234. package/frontend/src/lib/contexts/ModalContext.tsx +35 -0
  235. package/frontend/src/lib/hooks/useMetadata.ts +45 -1
  236. package/frontend/src/lib/hooks/useModal.tsx +2 -0
  237. package/frontend/src/lib/routing/AppRoutes.tsx +103 -99
  238. package/frontend/src/lib/services/metadata.service.ts +20 -3
  239. package/frontend/src/lib/utils/menuItems.ts +223 -207
  240. package/frontend/src/lib/utils/utils.ts +196 -196
  241. package/functions/server.ts +315 -315
  242. package/functions/worker-template.js +1 -1
  243. package/openapi/ai.yaml +115 -5
  244. package/openapi/auth.yaml +97 -17
  245. package/openapi/logs.yaml +0 -2
  246. package/openapi/metadata.yaml +0 -2
  247. package/openapi/records.yaml +21 -21
  248. package/openapi/tables.yaml +1 -2
  249. package/package.json +1 -1
  250. package/shared-schemas/package.json +1 -1
  251. package/shared-schemas/src/ai-api.schema.ts +251 -143
  252. package/shared-schemas/src/ai.schema.ts +63 -63
  253. package/shared-schemas/src/auth-api.schema.ts +34 -6
  254. package/shared-schemas/src/auth.schema.ts +17 -10
  255. package/shared-schemas/src/cloud-events.schema.ts +26 -0
  256. package/shared-schemas/src/deployments-api.schema.ts +55 -0
  257. package/shared-schemas/src/deployments.schema.ts +30 -0
  258. package/shared-schemas/src/docs.schema.ts +8 -2
  259. package/shared-schemas/src/email-api.schema.ts +30 -30
  260. package/shared-schemas/src/functions-api.schema.ts +13 -4
  261. package/shared-schemas/src/functions.schema.ts +1 -1
  262. package/shared-schemas/src/index.ts +22 -18
  263. package/shared-schemas/src/metadata.schema.ts +30 -4
  264. package/shared-schemas/src/secrets-api.schema.ts +44 -0
  265. package/shared-schemas/src/secrets.schema.ts +15 -0
  266. package/zeabur/README.md +13 -0
  267. package/zeabur/template.yml +20 -51
  268. package/backend/src/types/profile.ts +0 -55
  269. package/frontend/src/components/ProjectInfoModal.tsx +0 -128
@@ -1,141 +1,147 @@
1
- import {
2
- ModalitySchema,
3
- AIModelSchema,
4
- AIConfigurationWithUsageSchema,
5
- } from '@insforge/shared-schemas';
6
- export interface ModelOption {
7
- id: string;
8
- modelId: string;
9
- modelName: string;
10
- providerName: string;
11
- logo: React.ComponentType<React.SVGProps<SVGSVGElement>> | undefined;
12
- inputModality: ModalitySchema[];
13
- outputModality: ModalitySchema[];
14
- priceLevel?: number;
15
- usageStats?: {
16
- totalRequests: number;
17
- };
18
- systemPrompt?: string | null;
19
- }
20
-
21
- import { Type, Image } from 'lucide-react';
22
- import GrokIcon from '@/assets/logos/grok.svg?react';
23
- import GeminiIcon from '@/assets/logos/gemini.svg?react';
24
- import ClaudeIcon from '@/assets/logos/claude_code.svg?react';
25
- import OpenAIIcon from '@/assets/logos/openai.svg?react';
26
- import AmazonIcon from '@/assets/logos/amazon.svg?react';
27
-
28
- export const getModalityIcon = (
29
- modality: ModalitySchema
30
- ): React.FunctionComponent<React.SVGProps<SVGSVGElement>> => {
31
- switch (modality) {
32
- case 'text':
33
- return Type;
34
- case 'image':
35
- return Image;
36
- // case 'audio':
37
- // return Mic;
38
- // case 'video':
39
- // return Video;
40
- // case 'file':
41
- // return File;
42
- default:
43
- return Type;
44
- }
45
- };
46
-
47
- export const formatTokenCount = (count: number): string => {
48
- if (count >= 1000000) {
49
- return `${(count / 1000000).toFixed(1)}M`;
50
- } else if (count >= 1000) {
51
- return `${(count / 1000).toFixed(1)}K`;
52
- }
53
- return count.toString();
54
- };
55
-
56
- export const getProviderDisplayName = (providerId: string): string => {
57
- const providerMap: Record<string, string> = {
58
- openai: 'OpenAI',
59
- anthropic: 'Anthropic',
60
- google: 'Google',
61
- openrouter: 'OpenRouter',
62
- azure: 'Azure',
63
- amazon: 'Amazon',
64
- 'x-ai': 'xAI',
65
- huggingface: 'HuggingFace',
66
- };
67
-
68
- return (
69
- providerMap[providerId.toLowerCase()] ||
70
- providerId.charAt(0).toUpperCase() + providerId.slice(1)
71
- );
72
- };
73
-
74
- export const getProviderLogo = (
75
- providerId: string
76
- ): React.FunctionComponent<React.SVGProps<SVGSVGElement>> | undefined => {
77
- const logoMap: Record<string, React.FunctionComponent<React.SVGProps<SVGSVGElement>>> = {
78
- anthropic: ClaudeIcon,
79
- openai: OpenAIIcon,
80
- google: GeminiIcon,
81
- 'x-ai': GrokIcon,
82
- amazon: AmazonIcon,
83
- };
84
- return logoMap[providerId];
85
- };
86
-
87
- // Helper function to filter AI models based on selected modalities
88
- export const filterModelsByModalities = (
89
- models: AIModelSchema[],
90
- selectedInputModalities: ModalitySchema[],
91
- selectedOutputModalities: ModalitySchema[]
92
- ): AIModelSchema[] => {
93
- if (!models?.length) {
94
- return [];
95
- }
96
-
97
- return models.filter((model) => {
98
- const inputModalities = new Set(model.inputModality);
99
- const outputModalities = new Set(model.outputModality);
100
- return (
101
- selectedInputModalities.every((m) => inputModalities.has(m)) &&
102
- selectedOutputModalities.every((m) => outputModalities.has(m))
103
- );
104
- });
105
- };
106
-
107
- // Helper function to get friendly model name from model ID
108
- export const getFriendlyModelName = (rawModelName: string): string => {
109
- // Convert kebab-case to Title Case
110
- return rawModelName
111
- .split('-')
112
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
113
- .join(' ');
114
- };
115
-
116
- export function toModelOption(model: AIModelSchema | AIConfigurationWithUsageSchema): ModelOption {
117
- const [rawProviderId, rawModelName] = model.modelId.split('/');
118
-
119
- return {
120
- ...model,
121
- modelName: getFriendlyModelName(rawModelName),
122
- providerName: getProviderDisplayName(rawProviderId),
123
- logo: getProviderLogo(rawProviderId),
124
- };
125
- }
126
-
127
- // Sort models with configured ones at the end
128
- export const sortModelsByConfigurationStatus = (
129
- models: ModelOption[],
130
- configuredModelIds: string[]
131
- ): ModelOption[] => {
132
- return [...models].sort((a, b) => {
133
- const aConfigured = configuredModelIds.includes(a.modelId);
134
- const bConfigured = configuredModelIds.includes(b.modelId);
135
-
136
- if (aConfigured === bConfigured) {
137
- return 0;
138
- }
139
- return aConfigured ? 1 : -1;
140
- });
141
- };
1
+ import {
2
+ ModalitySchema,
3
+ AIModelSchema,
4
+ AIConfigurationWithUsageSchema,
5
+ } from '@insforge/shared-schemas';
6
+ export interface ModelOption {
7
+ id: string;
8
+ modelId: string;
9
+ modelName: string;
10
+ providerName: string;
11
+ logo: React.ComponentType<React.SVGProps<SVGSVGElement>> | undefined;
12
+ inputModality: ModalitySchema[];
13
+ outputModality: ModalitySchema[];
14
+ priceLevel?: number;
15
+ usageStats?: {
16
+ totalRequests: number;
17
+ };
18
+ systemPrompt?: string | null;
19
+ }
20
+
21
+ import { Type, Image, Mic } from 'lucide-react';
22
+ import GrokIcon from '@/assets/logos/grok.svg?react';
23
+ import GeminiIcon from '@/assets/logos/gemini.svg?react';
24
+ import ClaudeIcon from '@/assets/logos/claude_code.svg?react';
25
+ import OpenAIIcon from '@/assets/logos/openai.svg?react';
26
+ import AmazonIcon from '@/assets/logos/amazon.svg?react';
27
+ import DeepseekIcon from '@/assets/logos/deepseek.svg?react';
28
+ import QwenIcon from '@/assets/logos/qwen.svg?react';
29
+
30
+ export const getModalityIcon = (
31
+ modality: ModalitySchema
32
+ ): React.FunctionComponent<React.SVGProps<SVGSVGElement>> => {
33
+ switch (modality) {
34
+ case 'text':
35
+ return Type;
36
+ case 'image':
37
+ return Image;
38
+ case 'audio':
39
+ return Mic;
40
+ // case 'video':
41
+ // return Video;
42
+ // case 'file':
43
+ // return File;
44
+ default:
45
+ return Type;
46
+ }
47
+ };
48
+
49
+ export const formatTokenCount = (count: number): string => {
50
+ if (count >= 1000000) {
51
+ return `${(count / 1000000).toFixed(1)}M`;
52
+ } else if (count >= 1000) {
53
+ return `${(count / 1000).toFixed(1)}K`;
54
+ }
55
+ return count.toString();
56
+ };
57
+
58
+ export const getProviderDisplayName = (providerId: string): string => {
59
+ const providerMap: Record<string, string> = {
60
+ openai: 'OpenAI',
61
+ anthropic: 'Anthropic',
62
+ google: 'Google',
63
+ openrouter: 'OpenRouter',
64
+ azure: 'Azure',
65
+ amazon: 'Amazon',
66
+ 'x-ai': 'xAI',
67
+ huggingface: 'HuggingFace',
68
+ deepseek: 'DeepSeek',
69
+ qwen: 'Qwen',
70
+ };
71
+
72
+ return (
73
+ providerMap[providerId.toLowerCase()] ||
74
+ providerId.charAt(0).toUpperCase() + providerId.slice(1)
75
+ );
76
+ };
77
+
78
+ export const getProviderLogo = (
79
+ providerId: string
80
+ ): React.FunctionComponent<React.SVGProps<SVGSVGElement>> | undefined => {
81
+ const logoMap: Record<string, React.FunctionComponent<React.SVGProps<SVGSVGElement>>> = {
82
+ anthropic: ClaudeIcon,
83
+ openai: OpenAIIcon,
84
+ google: GeminiIcon,
85
+ 'x-ai': GrokIcon,
86
+ amazon: AmazonIcon,
87
+ deepseek: DeepseekIcon,
88
+ qwen: QwenIcon,
89
+ };
90
+ return logoMap[providerId];
91
+ };
92
+
93
+ // Helper function to filter AI models based on selected modalities
94
+ export const filterModelsByModalities = (
95
+ models: AIModelSchema[],
96
+ selectedInputModalities: ModalitySchema[],
97
+ selectedOutputModalities: ModalitySchema[]
98
+ ): AIModelSchema[] => {
99
+ if (!models?.length) {
100
+ return [];
101
+ }
102
+
103
+ return models.filter((model) => {
104
+ const inputModalities = new Set(model.inputModality);
105
+ const outputModalities = new Set(model.outputModality);
106
+ return (
107
+ selectedInputModalities.every((m) => inputModalities.has(m)) &&
108
+ selectedOutputModalities.every((m) => outputModalities.has(m))
109
+ );
110
+ });
111
+ };
112
+
113
+ // Helper function to get friendly model name from model ID
114
+ export const getFriendlyModelName = (rawModelName: string): string => {
115
+ // Convert kebab-case to Title Case
116
+ return rawModelName
117
+ .split('-')
118
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
119
+ .join(' ');
120
+ };
121
+
122
+ export function toModelOption(model: AIModelSchema | AIConfigurationWithUsageSchema): ModelOption {
123
+ const [rawProviderId, rawModelName] = model.modelId.split('/');
124
+
125
+ return {
126
+ ...model,
127
+ modelName: getFriendlyModelName(rawModelName),
128
+ providerName: getProviderDisplayName(rawProviderId),
129
+ logo: getProviderLogo(rawProviderId),
130
+ };
131
+ }
132
+
133
+ // Sort models with configured ones at the end
134
+ export const sortModelsByConfigurationStatus = (
135
+ models: ModelOption[],
136
+ configuredModelIds: string[]
137
+ ): ModelOption[] => {
138
+ return [...models].sort((a, b) => {
139
+ const aConfigured = configuredModelIds.includes(a.modelId);
140
+ const bConfigured = configuredModelIds.includes(b.modelId);
141
+
142
+ if (aConfigured === bConfigured) {
143
+ return 0;
144
+ }
145
+ return aConfigured ? 1 : -1;
146
+ });
147
+ };
@@ -1,166 +1,166 @@
1
- import { useState } from 'react';
2
- import { Plus, Loader2 } from 'lucide-react';
3
- import { Button, ConfirmDialog } from '@/components';
4
- import { useAIConfigs } from '../hooks/useAIConfigs';
5
- import { useAIRemainingCredits } from '../hooks/useAIUsage';
6
- import {
7
- CreateAIConfigurationRequest,
8
- UpdateAIConfigurationRequest,
9
- } from '@insforge/shared-schemas';
10
- import { useConfirm } from '@/lib/hooks/useConfirm';
11
- import {
12
- AIEmptyState,
13
- ModelSelectionDialog,
14
- SystemPromptDialog,
15
- AIModelCard,
16
- } from '@/features/ai/components';
17
- import { isInsForgeCloudProject } from '@/lib/utils/utils';
18
-
19
- export default function AIPage() {
20
- const {
21
- configurationOptions,
22
- isLoadingConfigurations,
23
- createConfiguration,
24
- updateConfiguration,
25
- deleteConfiguration,
26
- } = useAIConfigs();
27
-
28
- const { data: credits, error: getAICreditsError } =
29
- useAIRemainingCredits(!isInsForgeCloudProject());
30
-
31
- const { confirm, confirmDialogProps } = useConfirm();
32
-
33
- // Format credits display
34
- const formatCredits = (remaining: number) => {
35
- if (remaining >= 1000) {
36
- return `${(remaining / 1000).toFixed(1)}K`;
37
- }
38
- return remaining.toFixed(2);
39
- };
40
-
41
- const [modelSelectionOpen, setModelSelectionOpen] = useState(false);
42
- const [systemPromptOpen, setSystemPromptOpen] = useState(false);
43
- const [editingConfigId, setEditingConfigId] = useState<string | undefined>();
44
-
45
- const handleEdit = (id: string) => {
46
- setEditingConfigId(id);
47
- setSystemPromptOpen(true);
48
- };
49
-
50
- const handleDelete = async (id: string) => {
51
- const shouldDelete = await confirm({
52
- title: 'Delete AI Configuration',
53
- description:
54
- 'Are you certain you wish to remove this AI Integration? This action is irreversible.',
55
- confirmText: 'Delete',
56
- destructive: true,
57
- });
58
-
59
- if (shouldDelete) {
60
- deleteConfiguration(id);
61
- }
62
- };
63
-
64
- const handleCreate = () => {
65
- setModelSelectionOpen(true);
66
- };
67
-
68
- const handleModelSelectionSuccess = (configData: CreateAIConfigurationRequest) => {
69
- createConfiguration({
70
- inputModality: configData.inputModality,
71
- outputModality: configData.outputModality,
72
- provider: configData.provider,
73
- modelId: configData.modelId,
74
- });
75
- };
76
-
77
- // Derive the editing config from the ID
78
- const editingConfig = configurationOptions.find((c) => c.id === editingConfigId);
79
-
80
- const handleSystemPromptSuccess = (configData: UpdateAIConfigurationRequest) => {
81
- if (editingConfigId) {
82
- updateConfiguration({
83
- id: editingConfigId,
84
- data: {
85
- systemPrompt: configData.systemPrompt || null,
86
- },
87
- });
88
- }
89
- };
90
-
91
- return (
92
- <div className="flex h-full bg-white dark:bg-neutral-800 pt-8 pb-6">
93
- <div className="max-w-[1080px] mx-auto flex-1 flex flex-col gap-6 overflow-hidden">
94
- {/* Header Section */}
95
- <div className="w-full flex items-start justify-between">
96
- <div className="flex flex-col items-start gap-2">
97
- <div className="flex items-center gap-3">
98
- <h1 className="text-xl font-semibold text-black dark:text-white">AI Integration</h1>
99
- {credits?.remaining && (
100
- <span className="text-sm font-normal text-neutral-700 dark:text-emerald-300 mt-[2.5px]">
101
- {formatCredits(credits.remaining)} credit{credits.remaining !== 1 ? 's' : ''} left
102
- </span>
103
- )}
104
- </div>
105
- <p className="text-sm text-neutral-500 dark:text-neutral-400">
106
- Your models are ready — build LLM-powered features or add more integrations.
107
- </p>
108
- </div>
109
- <Button
110
- 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"
111
- onClick={handleCreate}
112
- >
113
- <Plus className="w-5 h-5" />
114
- New Integration
115
- </Button>
116
- </div>
117
-
118
- {/* Content Section */}
119
- <div className="flex-1 overflow-auto">
120
- {getAICreditsError ? (
121
- <AIEmptyState title="Configuration Error" description={getAICreditsError.message} />
122
- ) : isLoadingConfigurations ? (
123
- <div className="flex-1 flex items-center justify-center h-full">
124
- <Loader2 className="w-8 h-8 animate-spin text-gray-400" />
125
- </div>
126
- ) : configurationOptions.length ? (
127
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
128
- {configurationOptions.map((modelOption) => (
129
- <AIModelCard
130
- key={modelOption.id}
131
- config={modelOption}
132
- mode="configured"
133
- onEdit={handleEdit}
134
- onDelete={() => void handleDelete(modelOption.id)}
135
- />
136
- ))}
137
- </div>
138
- ) : (
139
- <AIEmptyState
140
- title="No AI Integration Yet"
141
- description="Add your first integration to get started"
142
- />
143
- )}
144
- </div>
145
- </div>
146
-
147
- {/* Model Selection Dialog */}
148
- <ModelSelectionDialog
149
- open={modelSelectionOpen}
150
- onOpenChange={setModelSelectionOpen}
151
- onSuccess={handleModelSelectionSuccess}
152
- />
153
-
154
- {/* System Prompt Dialog */}
155
- <SystemPromptDialog
156
- open={systemPromptOpen}
157
- onOpenChange={setSystemPromptOpen}
158
- initialSystemPrompt={editingConfig?.systemPrompt}
159
- onSuccess={handleSystemPromptSuccess}
160
- />
161
-
162
- {/* Confirm Dialog */}
163
- <ConfirmDialog {...confirmDialogProps} />
164
- </div>
165
- );
166
- }
1
+ import { useState } from 'react';
2
+ import { Plus, Loader2 } from 'lucide-react';
3
+ import { Button, ConfirmDialog } from '@/components';
4
+ import { useAIConfigs } from '../hooks/useAIConfigs';
5
+ import { useAIRemainingCredits } from '../hooks/useAIUsage';
6
+ import {
7
+ CreateAIConfigurationRequest,
8
+ UpdateAIConfigurationRequest,
9
+ } from '@insforge/shared-schemas';
10
+ import { useConfirm } from '@/lib/hooks/useConfirm';
11
+ import {
12
+ AIEmptyState,
13
+ ModelSelectionDialog,
14
+ SystemPromptDialog,
15
+ AIModelCard,
16
+ } from '@/features/ai/components';
17
+ import { isInsForgeCloudProject } from '@/lib/utils/utils';
18
+
19
+ export default function AIPage() {
20
+ const {
21
+ configurationOptions,
22
+ isLoadingConfigurations,
23
+ createConfiguration,
24
+ updateConfiguration,
25
+ deleteConfiguration,
26
+ } = useAIConfigs();
27
+
28
+ const { data: credits, error: getAICreditsError } =
29
+ useAIRemainingCredits(!isInsForgeCloudProject());
30
+
31
+ const { confirm, confirmDialogProps } = useConfirm();
32
+
33
+ // Format credits display
34
+ const formatCredits = (remaining: number) => {
35
+ if (remaining >= 1000) {
36
+ return `${(remaining / 1000).toFixed(1)}K`;
37
+ }
38
+ return remaining.toFixed(2);
39
+ };
40
+
41
+ const [modelSelectionOpen, setModelSelectionOpen] = useState(false);
42
+ const [systemPromptOpen, setSystemPromptOpen] = useState(false);
43
+ const [editingConfigId, setEditingConfigId] = useState<string | undefined>();
44
+
45
+ const handleEdit = (id: string) => {
46
+ setEditingConfigId(id);
47
+ setSystemPromptOpen(true);
48
+ };
49
+
50
+ const handleDelete = async (id: string) => {
51
+ const shouldDelete = await confirm({
52
+ title: 'Delete AI Configuration',
53
+ description:
54
+ 'Are you certain you wish to remove this AI Integration? This action is irreversible.',
55
+ confirmText: 'Delete',
56
+ destructive: true,
57
+ });
58
+
59
+ if (shouldDelete) {
60
+ deleteConfiguration(id);
61
+ }
62
+ };
63
+
64
+ const handleCreate = () => {
65
+ setModelSelectionOpen(true);
66
+ };
67
+
68
+ const handleModelSelectionSuccess = (configData: CreateAIConfigurationRequest) => {
69
+ createConfiguration({
70
+ inputModality: configData.inputModality,
71
+ outputModality: configData.outputModality,
72
+ provider: configData.provider,
73
+ modelId: configData.modelId,
74
+ });
75
+ };
76
+
77
+ // Derive the editing config from the ID
78
+ const editingConfig = configurationOptions.find((c) => c.id === editingConfigId);
79
+
80
+ const handleSystemPromptSuccess = (configData: UpdateAIConfigurationRequest) => {
81
+ if (editingConfigId) {
82
+ updateConfiguration({
83
+ id: editingConfigId,
84
+ data: {
85
+ systemPrompt: configData.systemPrompt || null,
86
+ },
87
+ });
88
+ }
89
+ };
90
+
91
+ return (
92
+ <div className="flex h-full bg-white dark:bg-neutral-800 pt-8 pb-6">
93
+ <div className="max-w-[1080px] mx-auto flex-1 flex flex-col gap-6 overflow-hidden">
94
+ {/* Header Section */}
95
+ <div className="w-full flex items-start justify-between">
96
+ <div className="flex flex-col items-start gap-2">
97
+ <div className="flex items-center gap-3">
98
+ <h1 className="text-xl font-semibold text-black dark:text-white">AI Integration</h1>
99
+ {credits?.remaining && (
100
+ <span className="text-sm font-normal text-neutral-700 dark:text-emerald-300 mt-[2.5px]">
101
+ {formatCredits(credits.remaining)} credit{credits.remaining !== 1 ? 's' : ''} left
102
+ </span>
103
+ )}
104
+ </div>
105
+ <p className="text-sm text-neutral-500 dark:text-neutral-400">
106
+ Your models are ready — build LLM-powered features or add more integrations.
107
+ </p>
108
+ </div>
109
+ <Button
110
+ 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"
111
+ onClick={handleCreate}
112
+ >
113
+ <Plus className="w-5 h-5" />
114
+ New Integration
115
+ </Button>
116
+ </div>
117
+
118
+ {/* Content Section */}
119
+ <div className="flex-1 overflow-auto">
120
+ {getAICreditsError ? (
121
+ <AIEmptyState title="Configuration Error" description={getAICreditsError.message} />
122
+ ) : isLoadingConfigurations ? (
123
+ <div className="flex-1 flex items-center justify-center h-full">
124
+ <Loader2 className="w-8 h-8 animate-spin text-gray-400" />
125
+ </div>
126
+ ) : configurationOptions.length ? (
127
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
128
+ {configurationOptions.map((modelOption) => (
129
+ <AIModelCard
130
+ key={modelOption.id}
131
+ config={modelOption}
132
+ mode="configured"
133
+ onEdit={handleEdit}
134
+ onDelete={() => void handleDelete(modelOption.id)}
135
+ />
136
+ ))}
137
+ </div>
138
+ ) : (
139
+ <AIEmptyState
140
+ title="No AI Integration Yet"
141
+ description="Add your first integration to get started"
142
+ />
143
+ )}
144
+ </div>
145
+ </div>
146
+
147
+ {/* Model Selection Dialog */}
148
+ <ModelSelectionDialog
149
+ open={modelSelectionOpen}
150
+ onOpenChange={setModelSelectionOpen}
151
+ onSuccess={handleModelSelectionSuccess}
152
+ />
153
+
154
+ {/* System Prompt Dialog */}
155
+ <SystemPromptDialog
156
+ open={systemPromptOpen}
157
+ onOpenChange={setSystemPromptOpen}
158
+ initialSystemPrompt={editingConfig?.systemPrompt}
159
+ onSuccess={handleSystemPromptSuccess}
160
+ />
161
+
162
+ {/* Confirm Dialog */}
163
+ <ConfirmDialog {...confirmDialogProps} />
164
+ </div>
165
+ );
166
+ }