@geenius/ai 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 (165) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.env.example +2 -0
  3. package/.github/CODEOWNERS +1 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
  6. package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  7. package/.github/dependabot.yml +11 -0
  8. package/.github/workflows/ci.yml +23 -0
  9. package/.github/workflows/release.yml +29 -0
  10. package/.node-version +1 -0
  11. package/.nvmrc +1 -0
  12. package/.prettierrc +7 -0
  13. package/.project/ACCOUNT.yaml +4 -0
  14. package/.project/IDEAS.yaml +7 -0
  15. package/.project/PROJECT.yaml +11 -0
  16. package/.project/ROADMAP.yaml +15 -0
  17. package/CHANGELOG.md +15 -0
  18. package/CODE_OF_CONDUCT.md +26 -0
  19. package/CONTRIBUTING.md +61 -0
  20. package/LICENSE +21 -0
  21. package/README.md +1 -0
  22. package/SECURITY.md +18 -0
  23. package/SUPPORT.md +14 -0
  24. package/package.json +75 -0
  25. package/packages/convex/package.json +42 -0
  26. package/packages/convex/src/index.ts +8 -0
  27. package/packages/convex/src/mutations/messages.ts +29 -0
  28. package/packages/convex/src/queries/messages.ts +24 -0
  29. package/packages/convex/src/schema.ts +20 -0
  30. package/packages/convex/tsconfig.json +11 -0
  31. package/packages/convex/tsup.config.ts +17 -0
  32. package/packages/react/README.md +1 -0
  33. package/packages/react/package.json +60 -0
  34. package/packages/react/src/components/AILogTable.tsx +90 -0
  35. package/packages/react/src/components/ChatWindow.tsx +118 -0
  36. package/packages/react/src/components/GenerationCard.tsx +73 -0
  37. package/packages/react/src/components/ImageGenerator.tsx +103 -0
  38. package/packages/react/src/components/ModelSelector.tsx +44 -0
  39. package/packages/react/src/components/ModelTestRunner.tsx +148 -0
  40. package/packages/react/src/components/VoiceSelector.tsx +51 -0
  41. package/packages/react/src/components/index.ts +9 -0
  42. package/packages/react/src/hooks/index.ts +12 -0
  43. package/packages/react/src/hooks/useAI.ts +158 -0
  44. package/packages/react/src/hooks/useAILogs.ts +40 -0
  45. package/packages/react/src/hooks/useAIModels.ts +53 -0
  46. package/packages/react/src/hooks/useChat.ts +141 -0
  47. package/packages/react/src/hooks/useContentManager.ts +108 -0
  48. package/packages/react/src/hooks/useImageGeneration.ts +82 -0
  49. package/packages/react/src/hooks/useMemory.ts +161 -0
  50. package/packages/react/src/hooks/useModelTest.ts +126 -0
  51. package/packages/react/src/hooks/useRealtimeAudio.ts +203 -0
  52. package/packages/react/src/hooks/useSkills.ts +114 -0
  53. package/packages/react/src/hooks/useTextToSpeech.ts +99 -0
  54. package/packages/react/src/hooks/useTranscription.ts +119 -0
  55. package/packages/react/src/hooks/useVideoGeneration.ts +79 -0
  56. package/packages/react/src/index.ts +42 -0
  57. package/packages/react/src/pages/AILogsPage.tsx +98 -0
  58. package/packages/react/src/pages/ChatPage.tsx +42 -0
  59. package/packages/react/src/pages/ModelTestPage.tsx +33 -0
  60. package/packages/react/src/pages/index.ts +5 -0
  61. package/packages/react/tsconfig.json +26 -0
  62. package/packages/react/tsup.config.ts +22 -0
  63. package/packages/react-css/README.md +1 -0
  64. package/packages/react-css/package.json +45 -0
  65. package/packages/react-css/src/ai.css +857 -0
  66. package/packages/react-css/src/components/AILogTable.tsx +90 -0
  67. package/packages/react-css/src/components/ChatWindow.tsx +118 -0
  68. package/packages/react-css/src/components/GenerationCard.tsx +73 -0
  69. package/packages/react-css/src/components/ImageGenerator.tsx +103 -0
  70. package/packages/react-css/src/components/ModelSelector.tsx +44 -0
  71. package/packages/react-css/src/components/ModelTestRunner.tsx +148 -0
  72. package/packages/react-css/src/components/VoiceSelector.tsx +51 -0
  73. package/packages/react-css/src/components/index.ts +9 -0
  74. package/packages/react-css/src/hooks/index.ts +12 -0
  75. package/packages/react-css/src/hooks/useAI.ts +153 -0
  76. package/packages/react-css/src/hooks/useAILogs.ts +40 -0
  77. package/packages/react-css/src/hooks/useAIModels.ts +51 -0
  78. package/packages/react-css/src/hooks/useChat.ts +145 -0
  79. package/packages/react-css/src/hooks/useContentManager.ts +108 -0
  80. package/packages/react-css/src/hooks/useImageGeneration.ts +82 -0
  81. package/packages/react-css/src/hooks/useMemory.ts +161 -0
  82. package/packages/react-css/src/hooks/useModelTest.ts +122 -0
  83. package/packages/react-css/src/hooks/useRealtimeAudio.ts +203 -0
  84. package/packages/react-css/src/hooks/useSkills.ts +114 -0
  85. package/packages/react-css/src/hooks/useTextToSpeech.ts +99 -0
  86. package/packages/react-css/src/hooks/useTranscription.ts +119 -0
  87. package/packages/react-css/src/hooks/useVideoGeneration.ts +79 -0
  88. package/packages/react-css/src/index.ts +35 -0
  89. package/packages/react-css/src/pages/AILogsPage.tsx +98 -0
  90. package/packages/react-css/src/pages/ChatPage.tsx +42 -0
  91. package/packages/react-css/src/pages/ModelTestPage.tsx +33 -0
  92. package/packages/react-css/src/pages/index.ts +5 -0
  93. package/packages/react-css/src/styles.css +127 -0
  94. package/packages/react-css/tsconfig.json +26 -0
  95. package/packages/react-css/tsup.config.ts +2 -0
  96. package/packages/shared/README.md +1 -0
  97. package/packages/shared/package.json +71 -0
  98. package/packages/shared/src/__tests__/ai.test.ts +67 -0
  99. package/packages/shared/src/ai-client.ts +243 -0
  100. package/packages/shared/src/config.ts +235 -0
  101. package/packages/shared/src/content.ts +249 -0
  102. package/packages/shared/src/convex/helpers.ts +163 -0
  103. package/packages/shared/src/convex/index.ts +16 -0
  104. package/packages/shared/src/convex/schemas.ts +146 -0
  105. package/packages/shared/src/convex/validators.ts +136 -0
  106. package/packages/shared/src/index.ts +107 -0
  107. package/packages/shared/src/memory.ts +197 -0
  108. package/packages/shared/src/providers/base.ts +103 -0
  109. package/packages/shared/src/providers/elevenlabs.ts +155 -0
  110. package/packages/shared/src/providers/index.ts +28 -0
  111. package/packages/shared/src/providers/openai-compatible.ts +286 -0
  112. package/packages/shared/src/providers/registry.ts +113 -0
  113. package/packages/shared/src/providers/replicate-fal.ts +230 -0
  114. package/packages/shared/src/skills.ts +273 -0
  115. package/packages/shared/src/types.ts +501 -0
  116. package/packages/shared/tsconfig.json +25 -0
  117. package/packages/shared/tsup.config.ts +22 -0
  118. package/packages/shared/vitest.config.ts +4 -0
  119. package/packages/solidjs/README.md +1 -0
  120. package/packages/solidjs/package.json +59 -0
  121. package/packages/solidjs/src/components/ChatWindow.tsx +78 -0
  122. package/packages/solidjs/src/components/GenerationCard.tsx +62 -0
  123. package/packages/solidjs/src/components/ModelTestRunner.tsx +119 -0
  124. package/packages/solidjs/src/components/index.ts +5 -0
  125. package/packages/solidjs/src/index.ts +32 -0
  126. package/packages/solidjs/src/pages/ChatPage.tsx +22 -0
  127. package/packages/solidjs/src/pages/ModelTestPage.tsx +22 -0
  128. package/packages/solidjs/src/pages/index.ts +4 -0
  129. package/packages/solidjs/src/primitives/createAI.ts +79 -0
  130. package/packages/solidjs/src/primitives/createChat.ts +100 -0
  131. package/packages/solidjs/src/primitives/createContentManager.ts +61 -0
  132. package/packages/solidjs/src/primitives/createImageGeneration.ts +46 -0
  133. package/packages/solidjs/src/primitives/createMemory.ts +127 -0
  134. package/packages/solidjs/src/primitives/createModelTest.ts +89 -0
  135. package/packages/solidjs/src/primitives/createSkills.ts +83 -0
  136. package/packages/solidjs/src/primitives/createTextToSpeech.ts +56 -0
  137. package/packages/solidjs/src/primitives/createVideoGeneration.ts +46 -0
  138. package/packages/solidjs/src/primitives/index.ts +8 -0
  139. package/packages/solidjs/tsconfig.json +27 -0
  140. package/packages/solidjs/tsup.config.ts +21 -0
  141. package/packages/solidjs-css/README.md +1 -0
  142. package/packages/solidjs-css/package.json +44 -0
  143. package/packages/solidjs-css/src/ai.css +857 -0
  144. package/packages/solidjs-css/src/components/ChatWindow.tsx +78 -0
  145. package/packages/solidjs-css/src/components/GenerationCard.tsx +62 -0
  146. package/packages/solidjs-css/src/components/ModelTestRunner.tsx +119 -0
  147. package/packages/solidjs-css/src/components/index.ts +5 -0
  148. package/packages/solidjs-css/src/index.ts +26 -0
  149. package/packages/solidjs-css/src/pages/ChatPage.tsx +22 -0
  150. package/packages/solidjs-css/src/pages/ModelTestPage.tsx +22 -0
  151. package/packages/solidjs-css/src/pages/index.ts +4 -0
  152. package/packages/solidjs-css/src/primitives/createAI.ts +79 -0
  153. package/packages/solidjs-css/src/primitives/createChat.ts +100 -0
  154. package/packages/solidjs-css/src/primitives/createContentManager.ts +61 -0
  155. package/packages/solidjs-css/src/primitives/createImageGeneration.ts +46 -0
  156. package/packages/solidjs-css/src/primitives/createMemory.ts +127 -0
  157. package/packages/solidjs-css/src/primitives/createModelTest.ts +89 -0
  158. package/packages/solidjs-css/src/primitives/createSkills.ts +83 -0
  159. package/packages/solidjs-css/src/primitives/createTextToSpeech.ts +56 -0
  160. package/packages/solidjs-css/src/primitives/createVideoGeneration.ts +46 -0
  161. package/packages/solidjs-css/src/primitives/index.ts +1 -0
  162. package/packages/solidjs-css/src/styles.css +127 -0
  163. package/packages/solidjs-css/tsconfig.json +27 -0
  164. package/packages/solidjs-css/tsup.config.ts +2 -0
  165. package/pnpm-workspace.yaml +2 -0
@@ -0,0 +1,249 @@
1
+ // @geenius-ai/shared — src/content.ts
2
+
3
+ /**
4
+ * Content management types and helpers.
5
+ * Supports AI-powered content generation, rewriting, translation, and templating.
6
+ */
7
+
8
+ import type { AIProviderType } from './types'
9
+
10
+ // ============================================================================
11
+ // Content Types
12
+ // ============================================================================
13
+
14
+ export type ContentType =
15
+ | 'text'
16
+ | 'email'
17
+ | 'social-post'
18
+ | 'product-description'
19
+ | 'blog-post'
20
+ | 'landing-page'
21
+ | 'notification'
22
+ | 'seo-meta'
23
+ | 'ad-copy'
24
+ | 'custom'
25
+
26
+ export type ContentAction =
27
+ | 'generate'
28
+ | 'rewrite'
29
+ | 'translate'
30
+ | 'summarize'
31
+ | 'expand'
32
+ | 'shorten'
33
+ | 'change-tone'
34
+ | 'extract'
35
+ | 'proofread'
36
+ | 'variations'
37
+
38
+ export type ContentTone =
39
+ | 'professional'
40
+ | 'casual'
41
+ | 'friendly'
42
+ | 'formal'
43
+ | 'persuasive'
44
+ | 'technical'
45
+ | 'humorous'
46
+ | 'empathetic'
47
+
48
+ export interface ContentTemplate {
49
+ id: string
50
+ name: string
51
+ type: ContentType
52
+ description?: string
53
+ systemPrompt: string
54
+ userPromptTemplate: string
55
+ /** Expected output fields */
56
+ outputFields?: ContentOutputField[]
57
+ /** Default tone */
58
+ tone?: ContentTone
59
+ /** Max tokens for generation */
60
+ maxTokens?: number
61
+ /** Tags for filtering */
62
+ tags?: string[]
63
+ }
64
+
65
+ export interface ContentOutputField {
66
+ name: string
67
+ type: 'string' | 'string[]' | 'number'
68
+ description?: string
69
+ required?: boolean
70
+ }
71
+
72
+ export interface ContentItem {
73
+ id: string
74
+ type: ContentType
75
+ action: ContentAction
76
+ input: string
77
+ output: string
78
+ templateId?: string
79
+ model?: string
80
+ provider?: AIProviderType
81
+ tone?: ContentTone
82
+ language?: string
83
+ metadata?: Record<string, unknown>
84
+ tokens?: number
85
+ costUsd?: number
86
+ createdAt: number
87
+ }
88
+
89
+ export interface ContentSchema {
90
+ /** Zod/Yup schema for the content fields */
91
+ fields: ContentFieldMeta[]
92
+ /** Context for AI generation */
93
+ context?: string
94
+ }
95
+
96
+ export interface ContentFieldMeta {
97
+ name: string
98
+ type: 'string' | 'number' | 'boolean' | 'array' | 'object'
99
+ label?: string
100
+ /** AI hint for content generation */
101
+ hint?: string
102
+ /** Max length */
103
+ maxLength?: number
104
+ /** Allowed values */
105
+ enum?: string[]
106
+ required?: boolean
107
+ }
108
+
109
+ // ============================================================================
110
+ // Content Generation Options
111
+ // ============================================================================
112
+
113
+ export interface ContentGenerateOptions {
114
+ action: ContentAction
115
+ /** Input text to transform, or prompt for generation */
116
+ input: string
117
+ type?: ContentType
118
+ tone?: ContentTone
119
+ /** Target language for translation */
120
+ language?: string
121
+ /** Number of variations to generate */
122
+ variations?: number
123
+ /** Template to use */
124
+ templateId?: string
125
+ /** Custom instructions */
126
+ instructions?: string
127
+ /** Max output length */
128
+ maxLength?: number
129
+ /** Model override */
130
+ model?: string
131
+ /** Schema context for structured generation */
132
+ schema?: ContentSchema
133
+ }
134
+
135
+ export interface ContentResult {
136
+ content: string
137
+ /** For multi-variation generation */
138
+ alternatives?: string[]
139
+ action: ContentAction
140
+ type: ContentType
141
+ model: string
142
+ tokens?: number
143
+ costUsd?: number
144
+ durationMs: number
145
+ }
146
+
147
+ // ============================================================================
148
+ // Built-in Templates
149
+ // ============================================================================
150
+
151
+ export const BUILT_IN_TEMPLATES: Record<string, ContentTemplate> = {
152
+ 'product-description': {
153
+ id: 'product-description',
154
+ name: 'Product Description',
155
+ type: 'product-description',
156
+ description: 'Generate a compelling product description',
157
+ systemPrompt: 'You are a skilled copywriter specializing in product descriptions. Write clear, persuasive descriptions that highlight key features and benefits.',
158
+ userPromptTemplate: 'Write a product description for: {{input}}\n\n{{#instructions}}Additional instructions: {{instructions}}{{/instructions}}',
159
+ outputFields: [
160
+ { name: 'headline', type: 'string', required: true },
161
+ { name: 'description', type: 'string', required: true },
162
+ { name: 'bulletPoints', type: 'string[]' },
163
+ ],
164
+ tone: 'persuasive',
165
+ maxTokens: 500,
166
+ tags: ['ecommerce', 'marketing'],
167
+ },
168
+ 'email-subject': {
169
+ id: 'email-subject',
170
+ name: 'Email Subject Line',
171
+ type: 'email',
172
+ description: 'Generate email subject lines',
173
+ systemPrompt: 'You are an email marketing expert. Generate compelling subject lines that drive opens.',
174
+ userPromptTemplate: 'Generate 5 email subject lines for: {{input}}',
175
+ outputFields: [
176
+ { name: 'subjects', type: 'string[]', required: true },
177
+ ],
178
+ tone: 'persuasive',
179
+ maxTokens: 200,
180
+ tags: ['email', 'marketing'],
181
+ },
182
+ 'social-post': {
183
+ id: 'social-post',
184
+ name: 'Social Media Post',
185
+ type: 'social-post',
186
+ description: 'Generate a social media post',
187
+ systemPrompt: 'You are a social media manager. Write engaging posts that drive engagement.',
188
+ userPromptTemplate: 'Write a social media post about: {{input}}\n\nPlatform: {{platform}}\nTone: {{tone}}',
189
+ tone: 'casual',
190
+ maxTokens: 300,
191
+ tags: ['social', 'marketing'],
192
+ },
193
+ 'seo-meta': {
194
+ id: 'seo-meta',
195
+ name: 'SEO Meta Tags',
196
+ type: 'seo-meta',
197
+ description: 'Generate SEO meta title and description',
198
+ systemPrompt: 'You are an SEO expert. Generate optimized meta tags.',
199
+ userPromptTemplate: 'Generate SEO meta title (60 chars max) and meta description (155 chars max) for: {{input}}',
200
+ outputFields: [
201
+ { name: 'title', type: 'string', required: true },
202
+ { name: 'description', type: 'string', required: true },
203
+ ],
204
+ tone: 'professional',
205
+ maxTokens: 200,
206
+ tags: ['seo'],
207
+ },
208
+ }
209
+
210
+ // ============================================================================
211
+ // Helpers
212
+ // ============================================================================
213
+
214
+ /**
215
+ * Build a prompt from a content template and variables.
216
+ */
217
+ export function buildContentPrompt(
218
+ template: ContentTemplate,
219
+ vars: Record<string, string>,
220
+ ): { systemPrompt: string; userPrompt: string } {
221
+ let userPrompt = template.userPromptTemplate
222
+ for (const [key, value] of Object.entries(vars)) {
223
+ // Handle simple {{var}} replacement
224
+ userPrompt = userPrompt.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value)
225
+ }
226
+ // Remove unmatched conditionals {{#...}}...{{/...}}
227
+ userPrompt = userPrompt.replace(/\{\{#\w+\}\}.*?\{\{\/\w+\}\}/gs, '')
228
+ return { systemPrompt: template.systemPrompt, userPrompt: userPrompt.trim() }
229
+ }
230
+
231
+ /**
232
+ * Build a system prompt for a content action.
233
+ */
234
+ export function buildActionSystemPrompt(action: ContentAction, tone?: ContentTone): string {
235
+ const toneStr = tone ? ` Use a ${tone} tone.` : ''
236
+ const prompts: Record<ContentAction, string> = {
237
+ generate: `You are a skilled content writer. Generate high-quality content as requested.${toneStr}`,
238
+ rewrite: `You are an expert editor. Rewrite the provided content to improve clarity, flow, and impact.${toneStr}`,
239
+ translate: `You are a professional translator. Translate the content accurately while preserving tone and meaning.`,
240
+ summarize: `You are an expert at summarization. Provide a clear, concise summary of the given content.`,
241
+ expand: `You are a content writer. Expand the given content with more detail, examples, and depth.${toneStr}`,
242
+ shorten: `You are an editor. Shorten the content while preserving the key message and important details.`,
243
+ 'change-tone': `You are a skilled writer. Rewrite the content in the specified tone while preserving the meaning.${toneStr}`,
244
+ extract: `You are a data extraction expert. Extract the requested information from the provided content.`,
245
+ proofread: `You are a proofreader. Fix grammar, spelling, punctuation, and style issues. Return the corrected text.`,
246
+ variations: `You are a creative writer. Generate multiple distinct variations of the given content.${toneStr}`,
247
+ }
248
+ return prompts[action]
249
+ }
@@ -0,0 +1,163 @@
1
+ // @geenius-ai/shared — src/convex/helpers.ts
2
+
3
+ /**
4
+ * Convex backend helper functions for AI operations.
5
+ * Apps use these inside their own Convex actions/mutations.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * // convex/ai.ts
10
+ * import { action, internalMutation, query } from './_generated/server'
11
+ * import { generateTextArgs, saveLogArgs, listLogsArgs } from '@geenius-ai/shared/convex'
12
+ * import { performAICall, createLogEntry } from '@geenius-ai/shared/convex'
13
+ *
14
+ * export const generateText = action({
15
+ * args: generateTextArgs,
16
+ * handler: async (ctx, args) => {
17
+ * return await performAICall(ctx, args)
18
+ * },
19
+ * })
20
+ * ```
21
+ */
22
+
23
+ import type {
24
+ AIConfig,
25
+ AIGenerateTextOptions,
26
+ AIGenerationResult,
27
+ AILogEntry,
28
+ AIRequestStatus,
29
+ AIGenerationType,
30
+ } from '../types'
31
+ import { defineAIConfig } from '../config'
32
+ import { resolveProviderForModel, generateTextWithRetries } from '../providers/registry'
33
+ import { extractPromptText } from '../providers/base'
34
+
35
+ /**
36
+ * Perform an AI text generation call with logging.
37
+ * This is the high-level function apps call from Convex actions.
38
+ */
39
+ export async function performAICall(
40
+ ctx: { runMutation?: (fn: any, args: any) => Promise<any>; runQuery?: (fn: any, args: any) => Promise<any> },
41
+ args: AIGenerateTextOptions,
42
+ options: {
43
+ config?: AIConfig
44
+ saveLogFn?: any // Reference to the internal mutation for saving logs
45
+ getModelCostFn?: any // Reference to the internal query for model costs
46
+ getApiKey?: (envVar: string) => string | undefined
47
+ } = {},
48
+ ): Promise<AIGenerationResult> {
49
+ const config = options.config ?? defineAIConfig()
50
+ const getApiKey = options.getApiKey ?? ((envVar: string) =>
51
+ (globalThis as any).process?.env?.[envVar]
52
+ )
53
+
54
+ const { model, messages, caller = 'unknown' } = args
55
+ const provider = resolveProviderForModel(config, model, getApiKey)
56
+ const promptInfo = extractPromptText(messages)
57
+ const requestId = `req-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`
58
+ const timestamp = Date.now()
59
+
60
+ let result: AIGenerationResult | undefined
61
+ let status: AIRequestStatus = 'success'
62
+ let errorMessage: string | undefined
63
+ let httpStatus = 200
64
+
65
+ try {
66
+ result = await generateTextWithRetries(provider, args, config.retries)
67
+ return result
68
+ } catch (err) {
69
+ status = 'error'
70
+ errorMessage = err instanceof Error ? err.message : 'Unknown error'
71
+ // Try to extract HTTP status from error message
72
+ const statusMatch = errorMessage.match(/\((\d{3})\)/)
73
+ if (statusMatch) httpStatus = parseInt(statusMatch[1]!, 10)
74
+ throw err
75
+ } finally {
76
+ const durationMs = Date.now() - timestamp
77
+
78
+ // Calculate costs
79
+ let inputCostUsd: number | undefined
80
+ let outputCostUsd: number | undefined
81
+ let totalCostUsd: number | undefined
82
+
83
+ const modelConfig = config.models.find(m => m.id === model)
84
+ if (modelConfig && result?.tokens) {
85
+ inputCostUsd = (result.tokens.prompt / 1000) * modelConfig.inputCostPer1k
86
+ outputCostUsd = (result.tokens.completion / 1000) * modelConfig.outputCostPer1k
87
+ totalCostUsd = inputCostUsd + outputCostUsd
88
+ }
89
+
90
+ // Save log if mutation reference provided
91
+ if (options.saveLogFn && ctx.runMutation) {
92
+ try {
93
+ await ctx.runMutation(options.saveLogFn, {
94
+ requestId,
95
+ model,
96
+ provider: provider.type,
97
+ caller,
98
+ type: 'text' as string,
99
+ timestamp,
100
+ durationMs,
101
+ systemPrompt: promptInfo.systemPrompt.substring(0, config.logging.maxPromptLength),
102
+ userPrompt: promptInfo.userPromptText.substring(0, config.logging.maxPromptLength),
103
+ hasImage: promptInfo.hasImage,
104
+ imageSizeBytes: promptInfo.imageSizeBytes || undefined,
105
+ temperature: args.temperature,
106
+ maxTokens: args.maxTokens,
107
+ requestBodySize: JSON.stringify(args).length,
108
+ status,
109
+ httpStatus,
110
+ responseContent: (result?.content ?? '').substring(0, config.logging.maxResponseLength),
111
+ responseSize: result?.content?.length ?? 0,
112
+ finishReason: result?.finishReason,
113
+ promptTokens: result?.tokens?.prompt,
114
+ completionTokens: result?.tokens?.completion,
115
+ totalTokens: result?.tokens?.total,
116
+ errorMessage,
117
+ inputCostUsd,
118
+ outputCostUsd,
119
+ totalCostUsd,
120
+ })
121
+ } catch (logErr) {
122
+ console.error('[geenius-ai] Failed to save log:', logErr)
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Build an AI log entry object (without saving).
130
+ * Useful for custom logging flows.
131
+ */
132
+ export function createLogEntry(
133
+ args: Partial<AILogEntry> & { requestId: string; model: string; timestamp: number },
134
+ ): AILogEntry {
135
+ return {
136
+ requestId: args.requestId,
137
+ model: args.model,
138
+ provider: args.provider ?? 'openai',
139
+ caller: args.caller ?? 'unknown',
140
+ type: args.type ?? 'text',
141
+ timestamp: args.timestamp,
142
+ durationMs: args.durationMs ?? 0,
143
+ systemPrompt: args.systemPrompt ?? '',
144
+ userPrompt: args.userPrompt ?? '',
145
+ hasImage: args.hasImage ?? false,
146
+ imageSizeBytes: args.imageSizeBytes,
147
+ temperature: args.temperature,
148
+ maxTokens: args.maxTokens,
149
+ requestBodySize: args.requestBodySize ?? 0,
150
+ status: args.status ?? 'success',
151
+ httpStatus: args.httpStatus ?? 200,
152
+ responseContent: args.responseContent ?? '',
153
+ responseSize: args.responseSize ?? 0,
154
+ finishReason: args.finishReason,
155
+ promptTokens: args.promptTokens,
156
+ completionTokens: args.completionTokens,
157
+ totalTokens: args.totalTokens,
158
+ errorMessage: args.errorMessage,
159
+ inputCostUsd: args.inputCostUsd,
160
+ outputCostUsd: args.outputCostUsd,
161
+ totalCostUsd: args.totalCostUsd,
162
+ }
163
+ }
@@ -0,0 +1,16 @@
1
+ // @geenius-ai/shared — src/convex/index.ts
2
+
3
+ // Schemas
4
+ export { aiTables, aiLogsTable, aiModelCostsTable, aiProvidersTable, aiConversationsTable, aiMessagesTable } from './schemas'
5
+
6
+ // Validators
7
+ export {
8
+ generateTextArgs, generateImageArgs, generateAudioArgs,
9
+ transcribeAudioArgs, generateVideoArgs, testModelArgs,
10
+ saveLogArgs, listLogsArgs,
11
+ upsertModelArgs,
12
+ createConversationArgs, updateConversationArgs, sendMessageArgs,
13
+ } from './validators'
14
+
15
+ // Helpers
16
+ export { performAICall, createLogEntry } from './helpers'
@@ -0,0 +1,146 @@
1
+ // @geenius-ai/shared — src/convex/schemas.ts
2
+
3
+ /**
4
+ * Convex table schemas for the AI component.
5
+ * Apps merge these into their schema.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * // convex/schema.ts
10
+ * import { defineSchema } from 'convex/server'
11
+ * import { aiTables } from '@geenius-ai/shared/convex'
12
+ *
13
+ * export default defineSchema({
14
+ * ...aiTables,
15
+ * // your other tables
16
+ * })
17
+ * ```
18
+ */
19
+
20
+ import { defineTable } from 'convex/server'
21
+ import { v } from 'convex/values'
22
+
23
+ // ============================================================================
24
+ // AI Log Entry — every API call is recorded
25
+ // ============================================================================
26
+
27
+ export const aiLogsTable = defineTable({
28
+ requestId: v.string(),
29
+ model: v.string(),
30
+ provider: v.string(),
31
+ caller: v.string(),
32
+ type: v.string(), // text, image, audio, video, transcription
33
+ timestamp: v.number(),
34
+ durationMs: v.number(),
35
+ systemPrompt: v.string(),
36
+ userPrompt: v.string(),
37
+ hasImage: v.boolean(),
38
+ imageSizeBytes: v.optional(v.number()),
39
+ temperature: v.optional(v.number()),
40
+ maxTokens: v.optional(v.number()),
41
+ requestBodySize: v.number(),
42
+ status: v.union(v.literal('success'), v.literal('error')),
43
+ httpStatus: v.number(),
44
+ responseContent: v.string(),
45
+ responseSize: v.number(),
46
+ finishReason: v.optional(v.string()),
47
+ promptTokens: v.optional(v.number()),
48
+ completionTokens: v.optional(v.number()),
49
+ totalTokens: v.optional(v.number()),
50
+ errorMessage: v.optional(v.string()),
51
+ inputCostUsd: v.optional(v.number()),
52
+ outputCostUsd: v.optional(v.number()),
53
+ totalCostUsd: v.optional(v.number()),
54
+ })
55
+ .index('by_timestamp', ['timestamp'])
56
+ .index('by_model', ['model', 'timestamp'])
57
+ .index('by_provider', ['provider', 'timestamp'])
58
+ .index('by_caller', ['caller', 'timestamp'])
59
+ .index('by_status', ['status', 'timestamp'])
60
+
61
+ // ============================================================================
62
+ // Model Cost Registry
63
+ // ============================================================================
64
+
65
+ export const aiModelCostsTable = defineTable({
66
+ model: v.string(),
67
+ provider: v.string(),
68
+ displayName: v.optional(v.string()),
69
+ inputCostPer1k: v.number(),
70
+ outputCostPer1k: v.number(),
71
+ capabilities: v.optional(v.array(v.string())),
72
+ contextWindow: v.optional(v.number()),
73
+ isActive: v.boolean(),
74
+ updatedAt: v.number(),
75
+ })
76
+ .index('by_model', ['model'])
77
+ .index('by_provider', ['provider'])
78
+
79
+ // ============================================================================
80
+ // Provider Configuration
81
+ // ============================================================================
82
+
83
+ export const aiProvidersTable = defineTable({
84
+ name: v.string(),
85
+ type: v.string(),
86
+ baseUrl: v.string(),
87
+ apiKeyEnvVar: v.string(),
88
+ isActive: v.boolean(),
89
+ models: v.array(v.string()),
90
+ defaultModel: v.optional(v.string()),
91
+ updatedAt: v.number(),
92
+ })
93
+ .index('by_type', ['type'])
94
+ .index('by_name', ['name'])
95
+
96
+ // ============================================================================
97
+ // Chat Conversations
98
+ // ============================================================================
99
+
100
+ export const aiConversationsTable = defineTable({
101
+ userId: v.string(),
102
+ title: v.string(),
103
+ model: v.string(),
104
+ systemPrompt: v.optional(v.string()),
105
+ status: v.union(v.literal('active'), v.literal('archived')),
106
+ favorite: v.boolean(),
107
+ tags: v.array(v.string()),
108
+ folderId: v.optional(v.string()),
109
+ messageCount: v.number(),
110
+ createdAt: v.number(),
111
+ updatedAt: v.number(),
112
+ })
113
+ .index('by_userId', ['userId'])
114
+ .index('by_userId_model', ['userId', 'model'])
115
+ .index('by_userId_status', ['userId', 'status'])
116
+ .index('by_userId_favorite', ['userId', 'favorite'])
117
+
118
+ // ============================================================================
119
+ // Chat Messages
120
+ // ============================================================================
121
+
122
+ export const aiMessagesTable = defineTable({
123
+ conversationId: v.id('aiConversations'),
124
+ userId: v.string(),
125
+ role: v.union(v.literal('system'), v.literal('user'), v.literal('assistant'), v.literal('tool')),
126
+ content: v.string(),
127
+ model: v.optional(v.string()),
128
+ tokens: v.optional(v.number()),
129
+ durationMs: v.optional(v.number()),
130
+ createdAt: v.number(),
131
+ })
132
+ .index('by_conversationId', ['conversationId'])
133
+ .index('by_conversationId_createdAt', ['conversationId', 'createdAt'])
134
+ .index('by_userId', ['userId'])
135
+
136
+ // ============================================================================
137
+ // Combined table export
138
+ // ============================================================================
139
+
140
+ export const aiTables = {
141
+ aiLogs: aiLogsTable,
142
+ aiModelCosts: aiModelCostsTable,
143
+ aiProviders: aiProvidersTable,
144
+ aiConversations: aiConversationsTable,
145
+ aiMessages: aiMessagesTable,
146
+ }