@geenius/adapters 0.1.0

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 (151) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.github/CODEOWNERS +1 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
  5. package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  6. package/.github/dependabot.yml +11 -0
  7. package/.github/workflows/ci.yml +23 -0
  8. package/.github/workflows/release.yml +29 -0
  9. package/.nvmrc +1 -0
  10. package/.project/ACCOUNT.yaml +4 -0
  11. package/.project/IDEAS.yaml +7 -0
  12. package/.project/PROJECT.yaml +11 -0
  13. package/.project/ROADMAP.yaml +15 -0
  14. package/CHANGELOG.md +11 -0
  15. package/CODE_OF_CONDUCT.md +16 -0
  16. package/CONTRIBUTING.md +26 -0
  17. package/LICENSE +21 -0
  18. package/README.md +202 -0
  19. package/SECURITY.md +15 -0
  20. package/SUPPORT.md +8 -0
  21. package/package.json +51 -0
  22. package/packages/convex/README.md +64 -0
  23. package/packages/convex/package.json +42 -0
  24. package/packages/convex/src/adapter.ts +39 -0
  25. package/packages/convex/src/index.ts +19 -0
  26. package/packages/convex/src/mutations.ts +142 -0
  27. package/packages/convex/src/queries.ts +106 -0
  28. package/packages/convex/src/schema.ts +54 -0
  29. package/packages/convex/src/types.ts +20 -0
  30. package/packages/convex/tsconfig.json +11 -0
  31. package/packages/convex/tsup.config.ts +10 -0
  32. package/packages/react/README.md +1 -0
  33. package/packages/react/package.json +45 -0
  34. package/packages/react/src/components/AdapterCard.tsx +49 -0
  35. package/packages/react/src/components/AdapterConfigForm.tsx +118 -0
  36. package/packages/react/src/components/AdapterList.tsx +84 -0
  37. package/packages/react/src/components/AdapterStatusBadge.tsx +30 -0
  38. package/packages/react/src/components/index.ts +4 -0
  39. package/packages/react/src/hooks/index.ts +75 -0
  40. package/packages/react/src/index.tsx +44 -0
  41. package/packages/react/src/pages/AdapterDetailPage.tsx +133 -0
  42. package/packages/react/src/pages/AdaptersPage.tsx +111 -0
  43. package/packages/react/src/pages/index.ts +2 -0
  44. package/packages/react/src/provider/AdapterProvider.tsx +115 -0
  45. package/packages/react/src/provider/index.ts +2 -0
  46. package/packages/react/tsconfig.json +18 -0
  47. package/packages/react/tsup.config.ts +10 -0
  48. package/packages/react-css/README.md +1 -0
  49. package/packages/react-css/package.json +44 -0
  50. package/packages/react-css/src/adapters.css +1576 -0
  51. package/packages/react-css/src/components/AdapterCard.tsx +34 -0
  52. package/packages/react-css/src/components/AdapterConfigForm.tsx +63 -0
  53. package/packages/react-css/src/components/AdapterList.tsx +40 -0
  54. package/packages/react-css/src/components/AdapterStatusBadge.tsx +21 -0
  55. package/packages/react-css/src/components/index.ts +4 -0
  56. package/packages/react-css/src/hooks/index.ts +75 -0
  57. package/packages/react-css/src/index.tsx +25 -0
  58. package/packages/react-css/src/pages/AdapterDetailPage.tsx +133 -0
  59. package/packages/react-css/src/pages/AdaptersPage.tsx +111 -0
  60. package/packages/react-css/src/pages/index.ts +2 -0
  61. package/packages/react-css/src/provider/AdapterProvider.tsx +115 -0
  62. package/packages/react-css/src/provider/index.ts +2 -0
  63. package/packages/react-css/src/styles.css +494 -0
  64. package/packages/react-css/tsconfig.json +19 -0
  65. package/packages/react-css/tsup.config.ts +2 -0
  66. package/packages/shared/README.md +1 -0
  67. package/packages/shared/package.json +39 -0
  68. package/packages/shared/src/__tests__/adapters.test.ts +545 -0
  69. package/packages/shared/src/admin/index.ts +2 -0
  70. package/packages/shared/src/admin/interface.ts +34 -0
  71. package/packages/shared/src/admin/localStorage.ts +109 -0
  72. package/packages/shared/src/ai/anthropic.ts +123 -0
  73. package/packages/shared/src/ai/cloudflare-gateway.ts +130 -0
  74. package/packages/shared/src/ai/gemini.ts +181 -0
  75. package/packages/shared/src/ai/index.ts +14 -0
  76. package/packages/shared/src/ai/interface.ts +11 -0
  77. package/packages/shared/src/ai/localStorage.ts +78 -0
  78. package/packages/shared/src/ai/ollama.ts +143 -0
  79. package/packages/shared/src/ai/openai.ts +120 -0
  80. package/packages/shared/src/ai/vercel-ai.ts +101 -0
  81. package/packages/shared/src/auth/better-auth.ts +118 -0
  82. package/packages/shared/src/auth/clerk.ts +151 -0
  83. package/packages/shared/src/auth/convex-auth.ts +125 -0
  84. package/packages/shared/src/auth/index.ts +10 -0
  85. package/packages/shared/src/auth/interface.ts +17 -0
  86. package/packages/shared/src/auth/localStorage.ts +125 -0
  87. package/packages/shared/src/auth/supabase-auth.ts +136 -0
  88. package/packages/shared/src/config.ts +57 -0
  89. package/packages/shared/src/constants.ts +122 -0
  90. package/packages/shared/src/db/convex.ts +146 -0
  91. package/packages/shared/src/db/index.ts +10 -0
  92. package/packages/shared/src/db/interface.ts +13 -0
  93. package/packages/shared/src/db/localStorage.ts +91 -0
  94. package/packages/shared/src/db/mongodb.ts +125 -0
  95. package/packages/shared/src/db/neon.ts +171 -0
  96. package/packages/shared/src/db/supabase.ts +158 -0
  97. package/packages/shared/src/index.ts +117 -0
  98. package/packages/shared/src/payments/index.ts +4 -0
  99. package/packages/shared/src/payments/interface.ts +11 -0
  100. package/packages/shared/src/payments/localStorage.ts +81 -0
  101. package/packages/shared/src/payments/stripe.ts +177 -0
  102. package/packages/shared/src/storage/convex.ts +113 -0
  103. package/packages/shared/src/storage/index.ts +14 -0
  104. package/packages/shared/src/storage/interface.ts +11 -0
  105. package/packages/shared/src/storage/localStorage.ts +95 -0
  106. package/packages/shared/src/storage/minio.ts +47 -0
  107. package/packages/shared/src/storage/r2.ts +123 -0
  108. package/packages/shared/src/storage/s3.ts +128 -0
  109. package/packages/shared/src/storage/supabase-storage.ts +116 -0
  110. package/packages/shared/src/storage/uploadthing.ts +126 -0
  111. package/packages/shared/src/styles/adapters.css +494 -0
  112. package/packages/shared/src/tier-gate.ts +119 -0
  113. package/packages/shared/src/types.ts +162 -0
  114. package/packages/shared/tsconfig.json +18 -0
  115. package/packages/shared/tsup.config.ts +9 -0
  116. package/packages/shared/vitest.config.ts +14 -0
  117. package/packages/solidjs/README.md +1 -0
  118. package/packages/solidjs/package.json +44 -0
  119. package/packages/solidjs/src/components/AdapterCard.tsx +24 -0
  120. package/packages/solidjs/src/components/AdapterConfigForm.tsx +54 -0
  121. package/packages/solidjs/src/components/AdapterList.tsx +28 -0
  122. package/packages/solidjs/src/components/AdapterStatusBadge.tsx +20 -0
  123. package/packages/solidjs/src/components/index.ts +4 -0
  124. package/packages/solidjs/src/index.tsx +17 -0
  125. package/packages/solidjs/src/pages/AdapterDetailPage.tsx +38 -0
  126. package/packages/solidjs/src/pages/AdaptersPage.tsx +39 -0
  127. package/packages/solidjs/src/pages/index.ts +2 -0
  128. package/packages/solidjs/src/primitives/index.ts +78 -0
  129. package/packages/solidjs/src/provider/AdapterProvider.tsx +62 -0
  130. package/packages/solidjs/src/provider/index.ts +2 -0
  131. package/packages/solidjs/tsconfig.json +20 -0
  132. package/packages/solidjs/tsup.config.ts +10 -0
  133. package/packages/solidjs-css/README.md +1 -0
  134. package/packages/solidjs-css/package.json +43 -0
  135. package/packages/solidjs-css/src/adapters.css +1576 -0
  136. package/packages/solidjs-css/src/components/AdapterCard.tsx +43 -0
  137. package/packages/solidjs-css/src/components/AdapterConfigForm.tsx +119 -0
  138. package/packages/solidjs-css/src/components/AdapterList.tsx +68 -0
  139. package/packages/solidjs-css/src/components/AdapterStatusBadge.tsx +24 -0
  140. package/packages/solidjs-css/src/components/index.ts +8 -0
  141. package/packages/solidjs-css/src/index.tsx +30 -0
  142. package/packages/solidjs-css/src/pages/AdapterDetailPage.tsx +107 -0
  143. package/packages/solidjs-css/src/pages/AdaptersPage.tsx +94 -0
  144. package/packages/solidjs-css/src/pages/index.ts +4 -0
  145. package/packages/solidjs-css/src/primitives/index.ts +1 -0
  146. package/packages/solidjs-css/src/provider/AdapterProvider.tsx +61 -0
  147. package/packages/solidjs-css/src/provider/index.ts +2 -0
  148. package/packages/solidjs-css/tsconfig.json +20 -0
  149. package/packages/solidjs-css/tsup.config.ts +2 -0
  150. package/pnpm-workspace.yaml +2 -0
  151. package/tsconfig.json +17 -0
@@ -0,0 +1,19 @@
1
+ // @geenius-adapters/convex — Barrel
2
+
3
+ export { default as schema } from './schema'
4
+
5
+ export {
6
+ registerAdapter,
7
+ updateAdapterStatus,
8
+ updateAdapter,
9
+ removeAdapter,
10
+ } from './mutations'
11
+
12
+ export {
13
+ listAdapters,
14
+ listAdaptersByDomain,
15
+ getAdapter,
16
+ getActiveAdapter,
17
+ getAdapterStatusSummary,
18
+ getAdapterHealthLogs,
19
+ } from './queries'
@@ -0,0 +1,142 @@
1
+ // @geenius-adapters/convex — Mutations
2
+
3
+ import { mutation } from './_generated/server'
4
+ import { v } from 'convex/values'
5
+
6
+ const domainValidator = v.union(
7
+ v.literal('db'), v.literal('auth'), v.literal('payments'),
8
+ v.literal('ai'), v.literal('storage'), v.literal('admin'),
9
+ )
10
+
11
+ const statusValidator = v.union(
12
+ v.literal('connected'), v.literal('disconnected'),
13
+ v.literal('error'), v.literal('initializing'),
14
+ )
15
+
16
+ /**
17
+ * Register or update an adapter for a given domain.
18
+ */
19
+ export const registerAdapter = mutation({
20
+ args: {
21
+ domain: domainValidator,
22
+ provider: v.string(),
23
+ config: v.object({
24
+ provider: v.string(),
25
+ apiKey: v.optional(v.string()),
26
+ baseUrl: v.optional(v.string()),
27
+ options: v.optional(v.any()),
28
+ }),
29
+ createdBy: v.optional(v.string()),
30
+ },
31
+ handler: async (ctx, args) => {
32
+ // Check if adapter already exists for this domain
33
+ const existing = await ctx.db
34
+ .query('adapter_registry')
35
+ .withIndex('by_domain_provider', (q) => q.eq('domain', args.domain).eq('provider', args.provider))
36
+ .first()
37
+
38
+ const now = Date.now()
39
+
40
+ if (existing) {
41
+ await ctx.db.patch(existing._id, {
42
+ config: args.config,
43
+ status: 'initializing',
44
+ updatedAt: now,
45
+ })
46
+ return existing._id
47
+ }
48
+
49
+ return await ctx.db.insert('adapter_registry', {
50
+ domain: args.domain,
51
+ provider: args.provider,
52
+ status: 'initializing',
53
+ config: args.config,
54
+ connectedAt: undefined,
55
+ lastHealthCheck: undefined,
56
+ errorMessage: undefined,
57
+ createdBy: args.createdBy,
58
+ createdAt: now,
59
+ updatedAt: now,
60
+ })
61
+ },
62
+ })
63
+
64
+ /**
65
+ * Update an adapter's status.
66
+ */
67
+ export const updateAdapterStatus = mutation({
68
+ args: {
69
+ adapterId: v.id('adapter_registry'),
70
+ status: statusValidator,
71
+ latency: v.optional(v.number()),
72
+ errorMessage: v.optional(v.string()),
73
+ },
74
+ handler: async (ctx, args) => {
75
+ const now = Date.now()
76
+ const update: Record<string, unknown> = {
77
+ status: args.status,
78
+ lastHealthCheck: now,
79
+ updatedAt: now,
80
+ }
81
+
82
+ if (args.status === 'connected') {
83
+ update.connectedAt = now
84
+ update.errorMessage = undefined
85
+ }
86
+ if (args.status === 'error' && args.errorMessage) {
87
+ update.errorMessage = args.errorMessage
88
+ }
89
+
90
+ await ctx.db.patch(args.adapterId, update)
91
+
92
+ // Log health check
93
+ await ctx.db.insert('adapter_health_logs', {
94
+ adapterId: args.adapterId,
95
+ domain: (await ctx.db.get(args.adapterId))!.domain,
96
+ status: args.status,
97
+ latency: args.latency,
98
+ errorMessage: args.errorMessage,
99
+ timestamp: now,
100
+ })
101
+ },
102
+ })
103
+
104
+ /**
105
+ * Update adapter configuration.
106
+ */
107
+ export const updateAdapter = mutation({
108
+ args: {
109
+ adapterId: v.id('adapter_registry'),
110
+ config: v.object({
111
+ provider: v.string(),
112
+ apiKey: v.optional(v.string()),
113
+ baseUrl: v.optional(v.string()),
114
+ options: v.optional(v.any()),
115
+ }),
116
+ },
117
+ handler: async (ctx, args) => {
118
+ await ctx.db.patch(args.adapterId, {
119
+ config: args.config,
120
+ status: 'initializing',
121
+ updatedAt: Date.now(),
122
+ })
123
+ },
124
+ })
125
+
126
+ /**
127
+ * Remove an adapter from the registry.
128
+ */
129
+ export const removeAdapter = mutation({
130
+ args: { adapterId: v.id('adapter_registry') },
131
+ handler: async (ctx, args) => {
132
+ // Remove health logs
133
+ const logs = await ctx.db
134
+ .query('adapter_health_logs')
135
+ .withIndex('by_adapter', (q) => q.eq('adapterId', args.adapterId))
136
+ .collect()
137
+ for (const log of logs) {
138
+ await ctx.db.delete(log._id)
139
+ }
140
+ await ctx.db.delete(args.adapterId)
141
+ },
142
+ })
@@ -0,0 +1,106 @@
1
+ // @geenius-adapters/convex — Queries
2
+
3
+ import { query } from './_generated/server'
4
+ import { v } from 'convex/values'
5
+
6
+ /**
7
+ * List all registered adapters.
8
+ */
9
+ export const listAdapters = query({
10
+ args: {},
11
+ handler: async (ctx) => {
12
+ return await ctx.db.query('adapter_registry').collect()
13
+ },
14
+ })
15
+
16
+ /**
17
+ * List adapters by domain.
18
+ */
19
+ export const listAdaptersByDomain = query({
20
+ args: {
21
+ domain: v.union(
22
+ v.literal('db'), v.literal('auth'), v.literal('payments'),
23
+ v.literal('ai'), v.literal('storage'), v.literal('admin'),
24
+ ),
25
+ },
26
+ handler: async (ctx, args) => {
27
+ return await ctx.db
28
+ .query('adapter_registry')
29
+ .withIndex('by_domain', (q) => q.eq('domain', args.domain))
30
+ .collect()
31
+ },
32
+ })
33
+
34
+ /**
35
+ * Get a single adapter by ID.
36
+ */
37
+ export const getAdapter = query({
38
+ args: { adapterId: v.id('adapter_registry') },
39
+ handler: async (ctx, args) => {
40
+ return await ctx.db.get(args.adapterId)
41
+ },
42
+ })
43
+
44
+ /**
45
+ * Get the active adapter for a specific domain.
46
+ * Returns the first connected adapter, or the first adapter if none connected.
47
+ */
48
+ export const getActiveAdapter = query({
49
+ args: {
50
+ domain: v.union(
51
+ v.literal('db'), v.literal('auth'), v.literal('payments'),
52
+ v.literal('ai'), v.literal('storage'), v.literal('admin'),
53
+ ),
54
+ },
55
+ handler: async (ctx, args) => {
56
+ const adapters = await ctx.db
57
+ .query('adapter_registry')
58
+ .withIndex('by_domain', (q) => q.eq('domain', args.domain))
59
+ .collect()
60
+
61
+ return adapters.find((a) => a.status === 'connected') ?? adapters[0] ?? null
62
+ },
63
+ })
64
+
65
+ /**
66
+ * Get adapter status summary — one entry per domain.
67
+ */
68
+ export const getAdapterStatusSummary = query({
69
+ args: {},
70
+ handler: async (ctx) => {
71
+ const all = await ctx.db.query('adapter_registry').collect()
72
+ const domains = ['db', 'auth', 'payments', 'ai', 'storage', 'admin'] as const
73
+
74
+ return domains.map((domain) => {
75
+ const domainAdapters = all.filter((a) => a.domain === domain)
76
+ const connected = domainAdapters.find((a) => a.status === 'connected')
77
+ return {
78
+ domain,
79
+ provider: connected?.provider ?? domainAdapters[0]?.provider ?? null,
80
+ status: connected?.status ?? domainAdapters[0]?.status ?? 'disconnected',
81
+ count: domainAdapters.length,
82
+ }
83
+ })
84
+ },
85
+ })
86
+
87
+ /**
88
+ * Get health logs for a specific adapter.
89
+ */
90
+ export const getAdapterHealthLogs = query({
91
+ args: {
92
+ adapterId: v.id('adapter_registry'),
93
+ limit: v.optional(v.number()),
94
+ },
95
+ handler: async (ctx, args) => {
96
+ const q = ctx.db
97
+ .query('adapter_health_logs')
98
+ .withIndex('by_adapter', (q) => q.eq('adapterId', args.adapterId))
99
+ .order('desc')
100
+
101
+ if (args.limit) {
102
+ return await q.take(args.limit)
103
+ }
104
+ return await q.take(50)
105
+ },
106
+ })
@@ -0,0 +1,54 @@
1
+ // @geenius-adapters/convex — Schema
2
+
3
+ import { defineSchema, defineTable } from 'convex/server'
4
+ import { v } from 'convex/values'
5
+
6
+ export default defineSchema({
7
+ // ─── Adapter Registry ──────────────────────────────────────
8
+ adapter_registry: defineTable({
9
+ domain: v.union(
10
+ v.literal('db'), v.literal('auth'), v.literal('payments'),
11
+ v.literal('ai'), v.literal('storage'), v.literal('admin'),
12
+ ),
13
+ provider: v.string(),
14
+ status: v.union(
15
+ v.literal('connected'), v.literal('disconnected'),
16
+ v.literal('error'), v.literal('initializing'),
17
+ ),
18
+ config: v.object({
19
+ provider: v.string(),
20
+ apiKey: v.optional(v.string()),
21
+ baseUrl: v.optional(v.string()),
22
+ options: v.optional(v.any()),
23
+ }),
24
+ connectedAt: v.optional(v.number()),
25
+ lastHealthCheck: v.optional(v.number()),
26
+ errorMessage: v.optional(v.string()),
27
+ // Metadata
28
+ createdBy: v.optional(v.string()),
29
+ createdAt: v.number(),
30
+ updatedAt: v.number(),
31
+ })
32
+ .index('by_domain', ['domain'])
33
+ .index('by_status', ['status'])
34
+ .index('by_domain_provider', ['domain', 'provider']),
35
+
36
+ // ─── Adapter Health Logs ───────────────────────────────────
37
+ adapter_health_logs: defineTable({
38
+ adapterId: v.id('adapter_registry'),
39
+ domain: v.union(
40
+ v.literal('db'), v.literal('auth'), v.literal('payments'),
41
+ v.literal('ai'), v.literal('storage'), v.literal('admin'),
42
+ ),
43
+ status: v.union(
44
+ v.literal('connected'), v.literal('disconnected'),
45
+ v.literal('error'), v.literal('initializing'),
46
+ ),
47
+ latency: v.optional(v.number()),
48
+ errorMessage: v.optional(v.string()),
49
+ timestamp: v.number(),
50
+ })
51
+ .index('by_adapter', ['adapterId'])
52
+ .index('by_domain', ['domain'])
53
+ .index('by_timestamp', ['timestamp']),
54
+ })
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Configuration for Convex adapter
3
+ */
4
+ export interface ConvexAdapterConfig {
5
+ deploymentUrl?: string
6
+ apiKey?: string
7
+ }
8
+
9
+ /**
10
+ * Interface for Convex database adapter
11
+ */
12
+ export interface ConvexDbAdapter {
13
+ query: (queryFn: string, args?: Record<string, unknown>) => Promise<unknown>
14
+ mutation: (
15
+ mutationFn: string,
16
+ args?: Record<string, unknown>,
17
+ ) => Promise<unknown>
18
+ isReady: () => boolean
19
+ disconnect: () => Promise<void>
20
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true
9
+ },
10
+ "include": ["src"]
11
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs', 'esm'],
6
+ dts: true,
7
+ clean: true,
8
+ sourcemap: true,
9
+ external: ['convex'],
10
+ })
@@ -0,0 +1 @@
1
+ # ✦ @geenius-adapters/react\n\n> Geenius Adapters — React hooks and providers\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius-adapters/react\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-adapters/react';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@geenius-adapters/react",
3
+ "version": "0.1.0",
4
+ "description": "Geenius Adapters — React hooks and providers",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsup",
17
+ "lint": "tsc --noEmit",
18
+ "test": "vitest run",
19
+ "clean": "rm -rf dist"
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=18.0.0"
29
+ },
30
+ "dependencies": {
31
+ "@geenius-adapters/shared": "workspace:*"
32
+ },
33
+ "devDependencies": {
34
+ "@types/react": "^19.0.0",
35
+ "react": "^19.2.4",
36
+ "tsup": "^8.5.1",
37
+ "typescript": "~6.0.2",
38
+ "vitest": "^4.1.0"
39
+ },
40
+ "author": "Antigravity HQ",
41
+ "license": "MIT",
42
+ "engines": {
43
+ "node": ">=20.0.0"
44
+ }
45
+ }
@@ -0,0 +1,49 @@
1
+ // @geenius-adapters/react — AdapterCard
2
+
3
+ import type { AdapterDomain, AdapterStatusInfo } from '@geenius-adapters/shared'
4
+ import { DOMAIN_LABELS, DOMAIN_ICONS, DOMAIN_DESCRIPTIONS } from '@geenius-adapters/shared'
5
+ import { AdapterStatusBadge } from './AdapterStatusBadge'
6
+
7
+ export interface AdapterCardProps {
8
+ domain: AdapterDomain
9
+ status: AdapterStatusInfo
10
+ onClick?: () => void
11
+ className?: string
12
+ }
13
+
14
+ export function AdapterCard({ domain, status, onClick, className = '' }: AdapterCardProps) {
15
+ const label = DOMAIN_LABELS[domain]
16
+ const icon = DOMAIN_ICONS[domain]
17
+ const desc = DOMAIN_DESCRIPTIONS[domain]
18
+
19
+ return (
20
+ <button
21
+ type="button"
22
+ onClick={onClick}
23
+ className={`group flex w-full items-start gap-4 rounded-2xl border border-white/10 bg-white/[0.03] p-5 text-left backdrop-blur-sm transition-all hover:border-white/15 hover:bg-white/[0.05] ${className}`}
24
+ aria-label={`${label} adapter — ${status.status}`}
25
+ >
26
+ <div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-xl bg-white/5 text-2xl transition-transform group-hover:scale-105">
27
+ {icon}
28
+ </div>
29
+ <div className="min-w-0 flex-1">
30
+ <div className="flex items-center justify-between gap-2">
31
+ <h3 className="truncate text-sm font-bold text-white/90">{label}</h3>
32
+ <AdapterStatusBadge status={status.status} />
33
+ </div>
34
+ <p className="mt-0.5 text-xs text-white/40 line-clamp-2">{desc}</p>
35
+ {status.provider && status.provider !== 'none' && (
36
+ <div className="mt-2 flex items-center gap-2">
37
+ <span className="rounded-md bg-white/5 px-2 py-0.5 text-[0.625rem] font-semibold text-white/50">{status.provider}</span>
38
+ {status.latency && (
39
+ <span className="text-[0.625rem] text-white/30">{status.latency}ms</span>
40
+ )}
41
+ </div>
42
+ )}
43
+ {status.error && (
44
+ <p className="mt-1.5 truncate text-[0.625rem] text-red-400/80">{status.error}</p>
45
+ )}
46
+ </div>
47
+ </button>
48
+ )
49
+ }
@@ -0,0 +1,118 @@
1
+ // @geenius-adapters/react — AdapterConfigForm
2
+
3
+ import { useState } from 'react'
4
+ import type { AdapterDomain, DomainAdapterConfig } from '@geenius-adapters/shared'
5
+ import { DOMAIN_LABELS, getProvidersForDomain } from '@geenius-adapters/shared'
6
+
7
+ export interface AdapterConfigFormProps {
8
+ domain: AdapterDomain
9
+ initialConfig?: DomainAdapterConfig
10
+ onSave: (config: DomainAdapterConfig) => Promise<void> | void
11
+ onCancel?: () => void
12
+ className?: string
13
+ }
14
+
15
+ export function AdapterConfigForm({ domain, initialConfig, onSave, onCancel, className = '' }: AdapterConfigFormProps) {
16
+ const providers = getProvidersForDomain(domain)
17
+ const [provider, setProvider] = useState(initialConfig?.provider ?? providers[0]?.id ?? '')
18
+ const [apiKey, setApiKey] = useState(initialConfig?.apiKey ?? '')
19
+ const [baseUrl, setBaseUrl] = useState(initialConfig?.baseUrl ?? '')
20
+ const [isSaving, setIsSaving] = useState(false)
21
+ const [error, setError] = useState<string | null>(null)
22
+
23
+ const selectedMeta = providers.find((p) => p.id === provider)
24
+ const needsApiKey = !['localStorage', 'noop'].includes(provider)
25
+ const needsBaseUrl = ['ollama', 'minio', 'neon', 'supabase'].includes(provider)
26
+
27
+ const handleSubmit = async (e: React.FormEvent) => {
28
+ e.preventDefault()
29
+ setError(null)
30
+ if (!provider) { setError('Select a provider'); return }
31
+ if (needsApiKey && !apiKey.trim()) { setError('API key is required'); return }
32
+ setIsSaving(true)
33
+ try {
34
+ await onSave({ provider, apiKey: apiKey || undefined, baseUrl: baseUrl || undefined })
35
+ } catch (e) {
36
+ setError(e instanceof Error ? e.message : 'Failed to save')
37
+ } finally {
38
+ setIsSaving(false)
39
+ }
40
+ }
41
+
42
+ return (
43
+ <form onSubmit={handleSubmit} className={`space-y-5 rounded-2xl border border-white/10 bg-white/[0.03] p-6 backdrop-blur-sm ${className}`} noValidate>
44
+ <div>
45
+ <h3 className="text-lg font-bold text-white/90">Configure {DOMAIN_LABELS[domain]}</h3>
46
+ <p className="mt-1 text-xs text-white/40">Select and configure your preferred provider.</p>
47
+ </div>
48
+
49
+ {error && <div className="rounded-lg border border-red-500/20 bg-red-500/10 px-4 py-3 text-sm text-red-400" role="alert">{error}</div>}
50
+
51
+ <div>
52
+ <label htmlFor={`adapter-${domain}-provider`} className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-white/40">Provider</label>
53
+ <div className="grid gap-2 sm:grid-cols-2">
54
+ {providers.map((p) => (
55
+ <button
56
+ key={p.id}
57
+ type="button"
58
+ onClick={() => setProvider(p.id)}
59
+ className={`flex flex-col rounded-xl border p-3 text-left transition-all ${
60
+ provider === p.id
61
+ ? 'border-indigo-500/30 bg-indigo-500/10'
62
+ : 'border-white/10 bg-white/[0.02] hover:bg-white/[0.04]'
63
+ }`}
64
+ >
65
+ <span className="text-xs font-semibold text-white/80">{p.name}</span>
66
+ <span className="mt-0.5 text-[0.625rem] text-white/40">{p.description}</span>
67
+ <span className={`mt-1 self-start rounded px-1.5 py-0.5 text-[0.5625rem] font-bold uppercase ${
68
+ p.tier === 'pronto' ? 'bg-emerald-500/10 text-emerald-400' : p.tier === 'mvp' ? 'bg-blue-500/10 text-blue-400' : 'bg-purple-500/10 text-purple-400'
69
+ }`}>{p.tier}</span>
70
+ </button>
71
+ ))}
72
+ </div>
73
+ </div>
74
+
75
+ {needsApiKey && (
76
+ <div>
77
+ <label htmlFor={`adapter-${domain}-apiKey`} className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-white/40">API Key</label>
78
+ <input
79
+ id={`adapter-${domain}-apiKey`}
80
+ type="password"
81
+ value={apiKey}
82
+ onChange={(e) => setApiKey(e.target.value)}
83
+ placeholder={`Enter ${selectedMeta?.name ?? provider} API key`}
84
+ autoComplete="off"
85
+ className="w-full rounded-xl border border-white/10 bg-white/[0.02] px-4 py-3 text-sm text-white/90 placeholder:text-white/20 focus:border-indigo-500/50 focus:outline-none focus:ring-2 focus:ring-indigo-500/20"
86
+ />
87
+ </div>
88
+ )}
89
+
90
+ {needsBaseUrl && (
91
+ <div>
92
+ <label htmlFor={`adapter-${domain}-url`} className="mb-1.5 block text-xs font-semibold uppercase tracking-wider text-white/40">Base URL</label>
93
+ <input
94
+ id={`adapter-${domain}-url`}
95
+ type="url"
96
+ value={baseUrl}
97
+ onChange={(e) => setBaseUrl(e.target.value)}
98
+ placeholder={`https://...`}
99
+ className="w-full rounded-xl border border-white/10 bg-white/[0.02] px-4 py-3 text-sm text-white/90 placeholder:text-white/20 focus:border-indigo-500/50 focus:outline-none focus:ring-2 focus:ring-indigo-500/20"
100
+ />
101
+ </div>
102
+ )}
103
+
104
+ <div className="flex items-center justify-end gap-3 pt-2">
105
+ {onCancel && (
106
+ <button type="button" onClick={onCancel} className="rounded-xl px-5 py-2.5 text-sm font-semibold text-white/50 hover:text-white/70">Cancel</button>
107
+ )}
108
+ <button
109
+ type="submit"
110
+ disabled={isSaving || !provider}
111
+ className="flex items-center gap-2 rounded-xl bg-indigo-500 px-6 py-2.5 text-sm font-semibold text-white hover:bg-indigo-400 disabled:opacity-50"
112
+ >
113
+ {isSaving ? 'Saving…' : 'Save Configuration'}
114
+ </button>
115
+ </div>
116
+ </form>
117
+ )
118
+ }
@@ -0,0 +1,84 @@
1
+ // @geenius-adapters/react — AdapterList
2
+
3
+ import { useState, useMemo } from 'react'
4
+ import { useAdapters } from '../hooks'
5
+ import { AdapterCard } from './AdapterCard'
6
+ import { ADAPTER_DOMAINS, DOMAIN_LABELS } from '@geenius-adapters/shared'
7
+ import type { AdapterDomain, AdapterStatusType } from '@geenius-adapters/shared'
8
+
9
+ export interface AdapterListProps {
10
+ onSelect?: (domain: AdapterDomain) => void
11
+ filterStatus?: AdapterStatusType
12
+ className?: string
13
+ }
14
+
15
+ export function AdapterList({ onSelect, filterStatus, className = '' }: AdapterListProps) {
16
+ const { statuses, isLoading } = useAdapters()
17
+ const [search, setSearch] = useState('')
18
+
19
+ const filteredDomains = useMemo(() => {
20
+ let domains = [...ADAPTER_DOMAINS]
21
+ if (search) {
22
+ const q = search.toLowerCase()
23
+ domains = domains.filter((d) => {
24
+ const label = DOMAIN_LABELS[d].toLowerCase()
25
+ const provider = statuses[d]?.provider?.toLowerCase() ?? ''
26
+ return label.includes(q) || provider.includes(q) || d.includes(q)
27
+ })
28
+ }
29
+ if (filterStatus) {
30
+ domains = domains.filter((d) => statuses[d]?.status === filterStatus)
31
+ }
32
+ return domains
33
+ }, [search, filterStatus, statuses])
34
+
35
+ if (isLoading) {
36
+ return (
37
+ <div className={`space-y-3 ${className}`} role="status" aria-label="Loading adapters">
38
+ {[1, 2, 3, 4].map((i) => (
39
+ <div key={i} className="animate-pulse rounded-2xl border border-white/5 bg-white/[0.02] p-5">
40
+ <div className="flex items-start gap-4">
41
+ <div className="h-12 w-12 rounded-xl bg-white/10" />
42
+ <div className="flex-1 space-y-2">
43
+ <div className="h-3 w-24 rounded bg-white/10" />
44
+ <div className="h-2 w-48 rounded bg-white/5" />
45
+ </div>
46
+ </div>
47
+ </div>
48
+ ))}
49
+ </div>
50
+ )
51
+ }
52
+
53
+ return (
54
+ <div className={className}>
55
+ <div className="mb-4">
56
+ <input
57
+ type="text"
58
+ value={search}
59
+ onChange={(e) => setSearch(e.target.value)}
60
+ placeholder="Search adapters..."
61
+ aria-label="Search adapters"
62
+ className="w-full rounded-xl border border-white/10 bg-white/[0.02] px-4 py-2.5 text-sm text-white/90 placeholder:text-white/20 focus:border-indigo-500/50 focus:outline-none focus:ring-2 focus:ring-indigo-500/20"
63
+ />
64
+ </div>
65
+
66
+ {filteredDomains.length === 0 ? (
67
+ <div className="rounded-xl border border-white/5 bg-white/[0.01] p-8 text-center">
68
+ <p className="text-sm text-white/40">{search ? 'No adapters match your search' : 'No adapters configured'}</p>
69
+ </div>
70
+ ) : (
71
+ <div className="grid gap-3 sm:grid-cols-2" role="list" aria-label="Adapter list">
72
+ {filteredDomains.map((domain) => (
73
+ <AdapterCard
74
+ key={domain}
75
+ domain={domain}
76
+ status={statuses[domain]}
77
+ onClick={() => onSelect?.(domain)}
78
+ />
79
+ ))}
80
+ </div>
81
+ )}
82
+ </div>
83
+ )
84
+ }