asasvirtuais 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 (86) hide show
  1. package/README.md +78 -0
  2. package/actions/draw.ts +110 -0
  3. package/components/OAuthCard.tsx +346 -0
  4. package/components/icons.tsx +11 -0
  5. package/components/markdown.tsx +18 -0
  6. package/components/stack/list.tsx +21 -0
  7. package/components/stack/menu.tsx +40 -0
  8. package/components/stack/nav.tsx +39 -0
  9. package/components/table/fixed.tsx +59 -0
  10. package/components/table/key-value.tsx +19 -0
  11. package/components/ui/color-mode.tsx +108 -0
  12. package/components/ui/provider.tsx +15 -0
  13. package/components/ui/toaster.tsx +43 -0
  14. package/components/ui/tooltip.tsx +46 -0
  15. package/hooks/useBoolean.tsx +11 -0
  16. package/hooks/useForwardAs.tsx +29 -0
  17. package/hooks/useHash copy.tsx +27 -0
  18. package/hooks/useHash.tsx +27 -0
  19. package/hooks/useIsMobile.tsx +6 -0
  20. package/hooks/useOAuthTokens.ts +97 -0
  21. package/hooks/useOpenRouterModels.ts +80 -0
  22. package/lib/auth0.ts +11 -0
  23. package/lib/blob.ts +3 -0
  24. package/lib/client-token-storage.ts +216 -0
  25. package/lib/oauth-tokens.ts +85 -0
  26. package/lib/react/context.tsx +20 -0
  27. package/lib/react/index.ts +1 -0
  28. package/lib/tools.ts +375 -0
  29. package/next-env.d.ts +5 -0
  30. package/next.config.ts +23 -0
  31. package/package.json +72 -0
  32. package/packages/blob.ts +97 -0
  33. package/packages/chat/components/chat/feed/index.tsx +76 -0
  34. package/packages/chat/components/chat/feed/story.tsx +18 -0
  35. package/packages/chat/components/chat/index.tsx +16 -0
  36. package/packages/chat/components/chat/story.tsx +74 -0
  37. package/packages/chat/components/debug/index.tsx +54 -0
  38. package/packages/chat/components/header/index.tsx +14 -0
  39. package/packages/chat/components/header/menu/index.tsx +63 -0
  40. package/packages/chat/components/header/story.tsx +33 -0
  41. package/packages/chat/components/header/title/index.tsx +35 -0
  42. package/packages/chat/components/index.ts +13 -0
  43. package/packages/chat/components/input/index.tsx +17 -0
  44. package/packages/chat/components/input/menu/index.tsx +35 -0
  45. package/packages/chat/components/input/send.tsx +21 -0
  46. package/packages/chat/components/input/story.tsx +35 -0
  47. package/packages/chat/components/input/textarea/index.tsx +20 -0
  48. package/packages/chat/components/message/file.tsx +103 -0
  49. package/packages/chat/components/message/menu/index.tsx +26 -0
  50. package/packages/chat/components/message/story.tsx +49 -0
  51. package/packages/chat/components/messages/index.tsx +23 -0
  52. package/packages/chat/components/messages/story.tsx +11 -0
  53. package/packages/chat/components/ui/prose.tsx +263 -0
  54. package/packages/chat/edit-message.tsx +49 -0
  55. package/packages/chat/header.tsx +118 -0
  56. package/packages/chat/index.ts +14 -0
  57. package/packages/chat/input.tsx +89 -0
  58. package/packages/chat/message-menu.tsx +57 -0
  59. package/packages/chat/message.tsx +44 -0
  60. package/packages/chat/messages.tsx +44 -0
  61. package/packages/chat/model-selector.tsx +172 -0
  62. package/packages/chat/scenarios.tsx +68 -0
  63. package/packages/chat/settings.tsx +98 -0
  64. package/packages/chat/temperature-slider.tsx +67 -0
  65. package/packages/chat/tool-results.tsx +32 -0
  66. package/packages/crud/core.ts +75 -0
  67. package/packages/crud/fetcher.ts +64 -0
  68. package/packages/crud/index.ts +2 -0
  69. package/packages/crud/next.ts +128 -0
  70. package/packages/crud/react.tsx +365 -0
  71. package/packages/env.ts +8 -0
  72. package/packages/fields.tsx +157 -0
  73. package/packages/firebase.ts +13 -0
  74. package/packages/firestore.ts +51 -0
  75. package/packages/form.tsx +66 -0
  76. package/packages/next.ts +64 -0
  77. package/packages/openrouter.ts +4 -0
  78. package/packages/react/context.tsx +21 -0
  79. package/packages/react/crud.tsx +372 -0
  80. package/packages/react/hooks.ts +90 -0
  81. package/packages/react/store.tsx +20 -0
  82. package/packages/replit-db.ts +219 -0
  83. package/packages/wretch.ts +22 -0
  84. package/packages/yaml.ts +163 -0
  85. package/pnpm-workspace.yaml +4 -0
  86. package/server/db.ts +15 -0
@@ -0,0 +1,219 @@
1
+ import { DatabaseInterface, TableInterface, Query } from './crud'
2
+ import z from 'zod'
3
+ import { feathers } from '@feathersjs/feathers'
4
+ import { KnexService } from '@feathersjs/knex'
5
+ import knex from 'knex'
6
+
7
+ if (!process.env.DATABASE_URL) {
8
+ throw new Error(
9
+ "DATABASE_URL must be set. Did you forget to provision a database?",
10
+ )
11
+ }
12
+
13
+ // Initialize Knex with PostgreSQL connection
14
+ const db = knex({
15
+ client: 'pg',
16
+ connection: process.env.DATABASE_URL,
17
+ pool: {
18
+ min: 2,
19
+ max: 10
20
+ }
21
+ })
22
+
23
+ // Initialize FeathersJS app
24
+ const app = feathers()
25
+
26
+ // Cache for FeathersJS services to avoid recreation
27
+ const serviceCache = new Map<string, KnexService>()
28
+
29
+ // Ensure table exists in the database
30
+ async function ensureTableExists(tableName: string) {
31
+ try {
32
+ const exists = await db.schema.hasTable(tableName)
33
+ if (!exists) {
34
+ await db.schema.createTable(tableName, (table) => {
35
+ table.uuid('id').primary().defaultTo(db.raw('gen_random_uuid()'))
36
+ table.jsonb('data').notNullable()
37
+ table.timestamp('created_at').defaultTo(db.fn.now())
38
+ table.timestamp('updated_at').defaultTo(db.fn.now())
39
+ })
40
+ console.log(`[REPLIT-DB] Created table ${tableName}`)
41
+ }
42
+ } catch (error) {
43
+ console.warn(`[REPLIT-DB] Could not ensure table ${tableName} exists:`, error)
44
+ }
45
+ }
46
+
47
+ // Get or create FeathersJS service for a table
48
+ function getService(tableName: string): KnexService {
49
+ if (!serviceCache.has(tableName)) {
50
+ const service = new KnexService({
51
+ Model: db,
52
+ name: tableName,
53
+ id: 'id',
54
+ paginate: false // Disable pagination for simpler API
55
+ })
56
+ serviceCache.set(tableName, service)
57
+ app.use(tableName, service)
58
+ }
59
+ return serviceCache.get(tableName)!
60
+ }
61
+
62
+ export function replitDbInterface<Schema extends DatabaseInterface, T extends keyof Schema & string>(defaultTable?: T): TableInterface<z.infer<Schema[T]['readable']>, z.infer<Schema[T]['writable']>> {
63
+ type Readable = z.infer<Schema[T]['readable']>
64
+ type Writable = z.infer<Schema[T]['writable']>
65
+
66
+ return {
67
+ async find({ table = defaultTable, id }) {
68
+ const tableName = table as string
69
+ await ensureTableExists(tableName)
70
+ const service = getService(tableName)
71
+
72
+ try {
73
+ const result = await service.get(id)
74
+ return { id: result.id, ...result.data } as Readable
75
+ } catch (error) {
76
+ throw new Error(`Record not found in ${tableName} with id ${id}`)
77
+ }
78
+ },
79
+
80
+ async list({ table = defaultTable, query: q = {} }) {
81
+ const tableName = table as string
82
+ await ensureTableExists(tableName)
83
+
84
+ // Build the query using Knex directly for proper JSONB handling
85
+ let knexQuery = db.select('*').from(tableName)
86
+
87
+ if (q) {
88
+ // Handle meta operators
89
+ if (q.$limit) {
90
+ knexQuery = knexQuery.limit(q.$limit)
91
+ }
92
+ if (q.$skip) {
93
+ knexQuery = knexQuery.offset(q.$skip)
94
+ }
95
+ if (q.$sort) {
96
+ for (const [field, direction] of Object.entries(q.$sort)) {
97
+ const order = direction === 1 ? 'asc' : 'desc'
98
+ knexQuery = knexQuery.orderByRaw(`data->>'${field}' ${order}`)
99
+ }
100
+ }
101
+
102
+ // Apply all conditions using a recursive function
103
+ knexQuery = applyConditions(knexQuery, q)
104
+ }
105
+
106
+ const results = await knexQuery
107
+ return results.map(row => ({ id: row.id, ...row.data } as unknown as Readable))
108
+ },
109
+
110
+ async create({ table = defaultTable, data }) {
111
+ const tableName = table as string
112
+ await ensureTableExists(tableName)
113
+ const service = getService(tableName)
114
+
115
+ const result = await service.create({
116
+ data: data as any,
117
+ })
118
+
119
+ console.log(`[REPLIT-DB] Record created in ${tableName} with id ${result.id}`)
120
+ return { id: result.id, ...data } as Readable
121
+ },
122
+
123
+ async update({ table = defaultTable, id, data }) {
124
+ const tableName = table as string
125
+ await ensureTableExists(tableName)
126
+ const service = getService(tableName)
127
+
128
+ // Get current record to merge data
129
+ const current = await service.get(id)
130
+ const mergedData = { ...current.data, ...data }
131
+
132
+ const result = await service.patch(id, {
133
+ data: mergedData,
134
+ updated_at: new Date()
135
+ })
136
+
137
+ console.log(`[REPLIT-DB] Record updated in ${tableName} with id ${id}`)
138
+ return { id: result.id, ...result.data } as Readable
139
+ },
140
+
141
+ async remove({ table = defaultTable, id }) {
142
+ const tableName = table as string
143
+ await ensureTableExists(tableName)
144
+ const service = getService(tableName)
145
+
146
+ // Get the record before deletion
147
+ const current = await service.get(id)
148
+ const data = { id: current.id, ...current.data } as Readable
149
+
150
+ await service.remove(id)
151
+
152
+ console.log(`[REPLIT-DB] Record removed from ${tableName} with id ${id}`)
153
+ return data
154
+ },
155
+ }
156
+ }
157
+
158
+ // Recursive function to apply query conditions with proper operator handling
159
+ function applyConditions(query: any, conditions: any): any {
160
+ for (const [key, value] of Object.entries(conditions)) {
161
+ if (key.startsWith('$')) {
162
+ // Handle compound operators
163
+ if (key === '$or' && Array.isArray(value)) {
164
+ query = query.where(function() {
165
+ for (const orCondition of value) {
166
+ this.orWhere(function() {
167
+ applyConditions(this, orCondition)
168
+ })
169
+ }
170
+ })
171
+ } else if (key === '$and' && Array.isArray(value)) {
172
+ for (const andCondition of value) {
173
+ query = query.where(function() {
174
+ applyConditions(this, andCondition)
175
+ })
176
+ }
177
+ }
178
+ // Skip other meta operators like $limit, $skip, $sort - they're handled elsewhere
179
+ } else {
180
+ // Handle field conditions
181
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
182
+ // Handle operators like $ne, $in, etc.
183
+ for (const [op, opValue] of Object.entries(value)) {
184
+ switch (op) {
185
+ case '$ne':
186
+ query = query.whereRaw(`data->>'${key}' != ?`, [opValue])
187
+ break
188
+ case '$in':
189
+ if (Array.isArray(opValue)) {
190
+ query = query.whereRaw(`data->>'${key}' = ANY(?)`, [opValue])
191
+ }
192
+ break
193
+ case '$nin':
194
+ if (Array.isArray(opValue)) {
195
+ query = query.whereRaw(`data->>'${key}' != ALL(?)`, [opValue])
196
+ }
197
+ break
198
+ case '$lt':
199
+ query = query.whereRaw(`(data->>'${key}')::numeric < ?`, [opValue])
200
+ break
201
+ case '$lte':
202
+ query = query.whereRaw(`(data->>'${key}')::numeric <= ?`, [opValue])
203
+ break
204
+ case '$gt':
205
+ query = query.whereRaw(`(data->>'${key}')::numeric > ?`, [opValue])
206
+ break
207
+ case '$gte':
208
+ query = query.whereRaw(`(data->>'${key}')::numeric >= ?`, [opValue])
209
+ break
210
+ }
211
+ }
212
+ } else {
213
+ // Simple equality
214
+ query = query.whereRaw(`data->>'${key}' = ?`, [value])
215
+ }
216
+ }
217
+ }
218
+ return query
219
+ }
@@ -0,0 +1,22 @@
1
+ import { FetchLike } from 'wretch'
2
+
3
+ export function ratePerSec(max: number, delay: number = 1000) {
4
+ let count : number = 0
5
+
6
+ return (next: FetchLike) => {
7
+
8
+ const check: FetchLike = async (url, opts) => {
9
+ if (count < max) {
10
+ count++
11
+ try {
12
+ return await next(url, opts)
13
+ } finally {
14
+ setTimeout(() => count--, delay)
15
+ }
16
+ } else {
17
+ return new Promise((resolve) => setTimeout(() => resolve(check(url, opts)), delay))
18
+ }
19
+ }
20
+ return check
21
+ }
22
+ }
@@ -0,0 +1,163 @@
1
+ import fs from 'fs'
2
+ import { writeFile } from 'fs/promises'
3
+ import YAML from 'yaml'
4
+ import { generateId } from 'ai'
5
+ import { DatabaseInterface, TableInterface } from './crud'
6
+ import z from 'zod'
7
+
8
+ export class Mutex {
9
+ private promise : Promise<any> = Promise.resolve()
10
+
11
+ async run<T>(fn: () => Promise<T>): Promise<T> {
12
+ const result = this.promise.then(fn)
13
+ this.promise = result.catch(() => {}) // Don't break chain on errors
14
+ return result
15
+ }
16
+ }
17
+
18
+ export const fileMutex = new Mutex()
19
+
20
+ export function yamlInterface<Schema extends DatabaseInterface, T extends keyof Schema & string>(defaultTable: string, databasePath = 'database'): TableInterface<z.infer<Schema[T]['readable']>, z.infer<Schema[T]['writable']>>{
21
+ type Readable = z.infer<Schema[T]['readable']>
22
+ type Writable = z.infer<Schema[T]['writable']>
23
+ const tablePath = (name: string) => `${databasePath}/${name}`
24
+ const recordPath = (table: string, id: string) => `${tablePath(table)}/${id}.yaml`
25
+ const readRecord = (table: string, id: string) => {
26
+ const path = recordPath(table, id)
27
+ if ( ! fs.existsSync(path) )
28
+ throw new Error(`Record not found: ${path}`)
29
+ const file = fs.readFileSync(path, 'utf8')
30
+ return YAML.parse(file) as Readable
31
+ }
32
+ return {
33
+ async find({ table = defaultTable, id }) {
34
+ return readRecord(table as string, id)
35
+ },
36
+ async list({ table = defaultTable, query }) {
37
+ const path = tablePath(table as string)
38
+ if (!fs.existsSync(path)) {
39
+ // If the table directory doesn't exist, return an empty array.
40
+ return []
41
+ }
42
+ const files = fs.readdirSync(path)
43
+ let records: Readable[] = []
44
+ for (const file of files) {
45
+ if (file.endsWith('.yaml')) {
46
+ const id = file.slice(0, -5)
47
+ try {
48
+ const record = readRecord(table, id)
49
+ records.push(record)
50
+ } catch (error) {
51
+ console.error(`Error reading record ${id} from table ${table}:`, error)
52
+ }
53
+ }
54
+ }
55
+
56
+ if (query) {
57
+ const { $limit, $skip, $sort, $select, ...filters } = query
58
+
59
+ // Apply filters
60
+ if (Object.keys(filters).length > 0) {
61
+ records = records.filter(record => {
62
+ return Object.entries(filters).every(([key, value]) => {
63
+ const recordValue = record[key as keyof typeof record]
64
+ if (typeof value === 'object' && value !== null) {
65
+ // Handle operators within a field
66
+ return Object.entries(value).every(([op, opValue]) => {
67
+ switch (op) {
68
+ case '$ne':
69
+ return recordValue != opValue
70
+ case '$in':
71
+ return Array.isArray(opValue) && opValue.includes(recordValue)
72
+ case '$nin':
73
+ return Array.isArray(opValue) && !opValue.includes(recordValue)
74
+ case '$lt':
75
+ // @ts-expect-error
76
+ return recordValue < opValue
77
+ case '$lte':
78
+ // @ts-expect-error
79
+ return recordValue <= opValue
80
+ case '$gt':
81
+ // @ts-expect-error
82
+ return recordValue > opValue
83
+ case '$gte':
84
+ // @ts-expect-error
85
+ return recordValue >= opValue
86
+ case '$search':
87
+ return typeof recordValue === 'string' && recordValue.toLowerCase().includes(String(opValue).toLowerCase())
88
+ default:
89
+ return true // Unknown operators are ignored
90
+ }
91
+ })
92
+ }
93
+ return recordValue === value
94
+ })
95
+ })
96
+ }
97
+
98
+ // Apply sorting
99
+ if ($sort) {
100
+ const sortKeys = Object.keys($sort) as (keyof Readable)[]
101
+ if (sortKeys.length > 0) {
102
+ const key = sortKeys[0]
103
+ const direction = $sort[key] === 1 ? 1 : -1
104
+ records.sort((a, b) => {
105
+ if (a[key] < b[key]) return -1 * direction
106
+ if (a[key] > b[key]) return 1 * direction
107
+ return 0
108
+ })
109
+ }
110
+ }
111
+
112
+ // Apply pagination
113
+ const skip = $skip || 0
114
+ const limit = $limit || records.length
115
+ records = records.slice(skip, skip + limit)
116
+
117
+ // Apply projection
118
+ if ($select) {
119
+ records = records.map(record => {
120
+ // @ts-expect-error
121
+ const selectedRecord: Partial<Readable> = { id: (record).id }
122
+ // @ts-expect-error
123
+ ($select as (keyof Readable)[]).forEach(key => {
124
+ // @ts-expect-error
125
+ selectedRecord[key] = record[key]
126
+ })
127
+ return selectedRecord as Readable
128
+ })
129
+ }
130
+ }
131
+
132
+ return records
133
+ },
134
+ async create({table = defaultTable, ...props}) {
135
+ props.data
136
+ const path = tablePath(table as string)
137
+ if ( ! fs.existsSync(path ) )
138
+ fs.mkdirSync(path, { recursive: true })
139
+ const id = generateId()
140
+ const record = { id, ...props.data }
141
+ const filePath = recordPath(table as string, id)
142
+ await fileMutex.run(() => writeFile(filePath, YAML.stringify(record), 'utf8'))
143
+ return record as Readable
144
+ },
145
+ async update({table = defaultTable, id, ...props}) {
146
+ const record = readRecord(table as string, id)
147
+ const updatedRecord = { ...record, ...props.data }
148
+ await fileMutex.run(() => writeFile(recordPath(table as string, id), YAML.stringify(updatedRecord), 'utf8'))
149
+ return updatedRecord as Readable
150
+ },
151
+ async remove({table = defaultTable, id}) {
152
+ const path = recordPath(table as string, id)
153
+ const data = readRecord(table as string, id)
154
+ if ( ! fs.existsSync(path) )
155
+ throw new Error(`Record not found: ${path}`)
156
+ fs.unlinkSync(path)
157
+ return {
158
+ id,
159
+ ...data
160
+ }
161
+ },
162
+ }
163
+ }
@@ -0,0 +1,4 @@
1
+ packages:
2
+ - 'packages/*'
3
+ - 'app'
4
+ - 'server'
package/server/db.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { Pool, neonConfig } from '@neondatabase/serverless';
2
+ import { drizzle } from 'drizzle-orm/neon-serverless';
3
+ import ws from "ws";
4
+ import * as schema from "@shared/schema";
5
+
6
+ neonConfig.webSocketConstructor = ws;
7
+
8
+ if (!process.env.DATABASE_URL) {
9
+ throw new Error(
10
+ "DATABASE_URL must be set. Did you forget to provision a database?",
11
+ );
12
+ }
13
+
14
+ export const pool = new Pool({ connectionString: process.env.DATABASE_URL });
15
+ export const db = drizzle({ client: pool, schema });