@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,161 @@
1
+ // @geenius-ai/react — src/hooks/useMemory.ts
2
+
3
+ import { useState, useCallback, useRef } from 'react'
4
+ import type {
5
+ MemoryEntry,
6
+ MemoryQuery,
7
+ MemoryNamespace,
8
+ MemoryImportance,
9
+ MemoryType,
10
+ } from '@geenius-ai/shared'
11
+
12
+ export interface UseMemoryOptions {
13
+ /** Convex mutation to store a memory */
14
+ storeFn: (entry: Omit<MemoryEntry, 'id' | 'accessCount' | 'lastAccessedAt' | 'createdAt' | 'updatedAt'>) => Promise<MemoryEntry>
15
+ /** Convex query to search memories */
16
+ searchFn: (query: MemoryQuery) => Promise<MemoryEntry[]>
17
+ /** Convex mutation to delete a memory */
18
+ deleteFn: (id: string) => Promise<void>
19
+ /** Convex mutation to clear memories */
20
+ clearFn: (namespace: MemoryNamespace, scopeId: string) => Promise<void>
21
+ /** Default namespace */
22
+ defaultNamespace?: MemoryNamespace
23
+ /** Default scope ID (e.g., current user ID) */
24
+ defaultScopeId?: string
25
+ }
26
+
27
+ export interface UseMemoryReturn {
28
+ /** Store a memory */
29
+ store: (key: string, value: string, options?: {
30
+ namespace?: MemoryNamespace
31
+ type?: MemoryType
32
+ importance?: MemoryImportance
33
+ metadata?: Record<string, unknown>
34
+ scopeId?: string
35
+ }) => Promise<MemoryEntry>
36
+ /** Recall a specific memory by key */
37
+ recall: (key: string, namespace?: MemoryNamespace) => Promise<MemoryEntry | null>
38
+ /** Search memories */
39
+ search: (query: MemoryQuery) => Promise<MemoryEntry[]>
40
+ /** Delete a memory */
41
+ remove: (id: string) => Promise<void>
42
+ /** Clear all memories in a namespace/scope */
43
+ clear: (namespace?: MemoryNamespace) => Promise<void>
44
+ /** Currently loaded memories */
45
+ memories: MemoryEntry[]
46
+ /** Loading state */
47
+ isLoading: boolean
48
+ /** Error state */
49
+ error: Error | null
50
+ }
51
+
52
+ export function useMemory(options: UseMemoryOptions): UseMemoryReturn {
53
+ const {
54
+ storeFn, searchFn, deleteFn, clearFn,
55
+ defaultNamespace = 'user',
56
+ defaultScopeId = '',
57
+ } = options
58
+
59
+ const [memories, setMemories] = useState<MemoryEntry[]>([])
60
+ const [isLoading, setIsLoading] = useState(false)
61
+ const [error, setError] = useState<Error | null>(null)
62
+
63
+ const store = useCallback(async (
64
+ key: string,
65
+ value: string,
66
+ opts?: {
67
+ namespace?: MemoryNamespace
68
+ type?: MemoryType
69
+ importance?: MemoryImportance
70
+ metadata?: Record<string, unknown>
71
+ scopeId?: string
72
+ },
73
+ ) => {
74
+ setIsLoading(true)
75
+ setError(null)
76
+ try {
77
+ const entry = await storeFn({
78
+ namespace: opts?.namespace ?? defaultNamespace,
79
+ type: opts?.type ?? 'fact',
80
+ importance: opts?.importance ?? 'medium',
81
+ key,
82
+ value,
83
+ metadata: opts?.metadata,
84
+ scopeId: opts?.scopeId ?? defaultScopeId,
85
+ })
86
+ setMemories(prev => [...prev, entry])
87
+ return entry
88
+ } catch (err) {
89
+ const e = err instanceof Error ? err : new Error(String(err))
90
+ setError(e)
91
+ throw e
92
+ } finally {
93
+ setIsLoading(false)
94
+ }
95
+ }, [storeFn, defaultNamespace, defaultScopeId])
96
+
97
+ const recall = useCallback(async (key: string, namespace?: MemoryNamespace) => {
98
+ setIsLoading(true)
99
+ setError(null)
100
+ try {
101
+ const results = await searchFn({
102
+ namespace: namespace ?? defaultNamespace,
103
+ key,
104
+ scopeId: defaultScopeId,
105
+ limit: 1,
106
+ })
107
+ return results.length > 0 ? results[0] : null
108
+ } catch (err) {
109
+ const e = err instanceof Error ? err : new Error(String(err))
110
+ setError(e)
111
+ throw e
112
+ } finally {
113
+ setIsLoading(false)
114
+ }
115
+ }, [searchFn, defaultNamespace, defaultScopeId])
116
+
117
+ const search = useCallback(async (query: MemoryQuery) => {
118
+ setIsLoading(true)
119
+ setError(null)
120
+ try {
121
+ const results = await searchFn({
122
+ scopeId: defaultScopeId,
123
+ ...query,
124
+ })
125
+ setMemories(results)
126
+ return results
127
+ } catch (err) {
128
+ const e = err instanceof Error ? err : new Error(String(err))
129
+ setError(e)
130
+ throw e
131
+ } finally {
132
+ setIsLoading(false)
133
+ }
134
+ }, [searchFn, defaultScopeId])
135
+
136
+ const remove = useCallback(async (id: string) => {
137
+ setError(null)
138
+ try {
139
+ await deleteFn(id)
140
+ setMemories(prev => prev.filter(m => m.id !== id))
141
+ } catch (err) {
142
+ const e = err instanceof Error ? err : new Error(String(err))
143
+ setError(e)
144
+ throw e
145
+ }
146
+ }, [deleteFn])
147
+
148
+ const clear = useCallback(async (namespace?: MemoryNamespace) => {
149
+ setError(null)
150
+ try {
151
+ await clearFn(namespace ?? defaultNamespace, defaultScopeId)
152
+ setMemories([])
153
+ } catch (err) {
154
+ const e = err instanceof Error ? err : new Error(String(err))
155
+ setError(e)
156
+ throw e
157
+ }
158
+ }, [clearFn, defaultNamespace, defaultScopeId])
159
+
160
+ return { store, recall, search, remove, clear, memories, isLoading, error }
161
+ }
@@ -0,0 +1,126 @@
1
+ // @geenius-ai/react — src/hooks/useModelTest.ts
2
+
3
+ /**
4
+ * Hook for testing AI models with prompts.
5
+ */
6
+
7
+ import { useState, useCallback } from 'react'
8
+ import { useAction } from 'convex/react'
9
+ import type { AIGenerationType } from '@geenius-ai/shared'
10
+
11
+ // Stable no-op for optional Convex action references (React Rules of Hooks)
12
+ const _noop = (() => Promise.resolve(null)) as any
13
+
14
+ export interface UseModelTestOptions {
15
+ /** Action reference for text: api.ai.generateText */
16
+ generateTextAction?: any
17
+ /** Action reference for image: api.ai.generateImage */
18
+ generateImageAction?: any
19
+ /** Action reference for audio: api.ai.generateAudio */
20
+ generateAudioAction?: any
21
+ /** Action reference for transcription: api.ai.transcribeAudio */
22
+ transcribeAudioAction?: any
23
+ /** Action reference for video: api.ai.generateVideo */
24
+ generateVideoAction?: any
25
+ }
26
+
27
+ export interface ModelTestResult {
28
+ model: string
29
+ type: AIGenerationType
30
+ result: string
31
+ durationMs: number
32
+ timestamp: number
33
+ error?: string
34
+ }
35
+
36
+ export interface UseModelTestReturn {
37
+ runTest: (model: string, prompt: string, type?: AIGenerationType) => Promise<ModelTestResult>
38
+ runBatchTest: (models: string[], prompt: string) => Promise<ModelTestResult[]>
39
+ results: ModelTestResult[]
40
+ isRunning: boolean
41
+ clearResults: () => void
42
+ }
43
+
44
+ export function useModelTest(options: UseModelTestOptions = {}): UseModelTestReturn {
45
+ const [results, setResults] = useState<ModelTestResult[]>([])
46
+ const [isRunning, setIsRunning] = useState(false)
47
+
48
+ // Always call hooks unconditionally; gate invocations on options presence
49
+ const textAction = useAction(options.generateTextAction ?? _noop)
50
+ const imageAction = useAction(options.generateImageAction ?? _noop)
51
+ const audioAction = useAction(options.generateAudioAction ?? _noop)
52
+ const transcribeAction = useAction(options.transcribeAudioAction ?? _noop)
53
+ const videoAction = useAction(options.generateVideoAction ?? _noop)
54
+
55
+ const runTest = useCallback(async (
56
+ model: string,
57
+ prompt: string,
58
+ type: AIGenerationType = 'text',
59
+ ): Promise<ModelTestResult> => {
60
+ setIsRunning(true)
61
+ const start = Date.now()
62
+
63
+ try {
64
+ let result = ''
65
+ switch (type) {
66
+ case 'text':
67
+ if (!options.generateTextAction) throw new Error('generateTextAction not provided')
68
+ result = await textAction({
69
+ model,
70
+ messages: [{ role: 'user', content: prompt }],
71
+ caller: 'model-test',
72
+ })
73
+ break
74
+ case 'image':
75
+ if (!options.generateImageAction) throw new Error('generateImageAction not provided')
76
+ result = await imageAction({ prompt, model })
77
+ break
78
+ case 'audio':
79
+ if (!options.generateAudioAction) throw new Error('generateAudioAction not provided')
80
+ result = await audioAction({ prompt })
81
+ break
82
+ case 'video':
83
+ if (!options.generateVideoAction) throw new Error('generateVideoAction not provided')
84
+ result = await videoAction({ prompt })
85
+ break
86
+ default:
87
+ throw new Error(`Unsupported test type: ${type}`)
88
+ }
89
+
90
+ const testResult: ModelTestResult = {
91
+ model, type, result, durationMs: Date.now() - start, timestamp: Date.now(),
92
+ }
93
+ setResults(prev => [...prev, testResult])
94
+ return testResult
95
+ } catch (err) {
96
+ const testResult: ModelTestResult = {
97
+ model, type, result: '',
98
+ durationMs: Date.now() - start,
99
+ timestamp: Date.now(),
100
+ error: err instanceof Error ? err.message : 'Test failed',
101
+ }
102
+ setResults(prev => [...prev, testResult])
103
+ return testResult
104
+ } finally {
105
+ setIsRunning(false)
106
+ }
107
+ }, [textAction, imageAction, audioAction, videoAction])
108
+
109
+ const runBatchTest = useCallback(async (
110
+ models: string[],
111
+ prompt: string,
112
+ ): Promise<ModelTestResult[]> => {
113
+ setIsRunning(true)
114
+ const batchResults: ModelTestResult[] = []
115
+ for (const model of models) {
116
+ const result = await runTest(model, prompt)
117
+ batchResults.push(result)
118
+ }
119
+ setIsRunning(false)
120
+ return batchResults
121
+ }, [runTest])
122
+
123
+ const clearResults = useCallback(() => setResults([]), [])
124
+
125
+ return { runTest, runBatchTest, results, isRunning, clearResults }
126
+ }
@@ -0,0 +1,203 @@
1
+ // @geenius-ai/react — src/hooks/useRealtimeAudio.ts
2
+
3
+ /**
4
+ * Realtime audio conversation hook — OpenAI Realtime API.
5
+ * Uses WebSocket for bidirectional audio streaming.
6
+ */
7
+
8
+ import { useState, useCallback, useRef, useEffect } from 'react'
9
+ import type { AIRealtimeConfig, AIRealtimeEvent } from '@geenius-ai/shared'
10
+
11
+ export interface UseRealtimeAudioOptions extends AIRealtimeConfig {
12
+ /** WebSocket URL for the realtime API session */
13
+ wsUrl?: string
14
+ /** Gets a session token/URL from your backend */
15
+ getSessionAction?: any
16
+ /** Called when AI responds with text */
17
+ onResponse?: (text: string) => void
18
+ /** Called when user's speech is transcribed */
19
+ onTranscript?: (text: string) => void
20
+ /** Called on any realtime event */
21
+ onEvent?: (event: AIRealtimeEvent) => void
22
+ }
23
+
24
+ export interface UseRealtimeAudioReturn {
25
+ connect: () => Promise<void>
26
+ disconnect: () => void
27
+ isConnected: boolean
28
+ isSpeaking: boolean
29
+ isListening: boolean
30
+ error: string | null
31
+ transcript: string
32
+ response: string
33
+ clearError: () => void
34
+ }
35
+
36
+ export function useRealtimeAudio(options: UseRealtimeAudioOptions): UseRealtimeAudioReturn {
37
+ const [isConnected, setIsConnected] = useState(false)
38
+ const [isSpeaking, setIsSpeaking] = useState(false)
39
+ const [isListening, setIsListening] = useState(false)
40
+ const [error, setError] = useState<string | null>(null)
41
+ const [transcript, setTranscript] = useState('')
42
+ const [response, setResponse] = useState('')
43
+ const wsRef = useRef<WebSocket | null>(null)
44
+ const audioContextRef = useRef<AudioContext | null>(null)
45
+ const streamRef = useRef<MediaStream | null>(null)
46
+ const processorRef = useRef<ScriptProcessorNode | null>(null)
47
+
48
+ const connect = useCallback(async () => {
49
+ try {
50
+ setError(null)
51
+
52
+ // Get the WebSocket URL from backend if action provided
53
+ let wsUrl = options.wsUrl ?? 'wss://api.openai.com/v1/realtime'
54
+ if (options.getSessionAction) {
55
+ const session = await options.getSessionAction({
56
+ model: options.model ?? 'gpt-4o-realtime-preview',
57
+ voice: options.voice ?? 'alloy',
58
+ instructions: options.instructions,
59
+ })
60
+ wsUrl = session.url ?? wsUrl
61
+ }
62
+
63
+ // Connect WebSocket
64
+ const ws = new WebSocket(wsUrl)
65
+ wsRef.current = ws
66
+
67
+ ws.onopen = () => {
68
+ setIsConnected(true)
69
+
70
+ // Send session config
71
+ ws.send(JSON.stringify({
72
+ type: 'session.update',
73
+ session: {
74
+ model: options.model ?? 'gpt-4o-realtime-preview',
75
+ voice: options.voice ?? 'alloy',
76
+ instructions: options.instructions ?? 'You are a helpful assistant.',
77
+ input_audio_format: options.inputAudioFormat ?? 'pcm16',
78
+ output_audio_format: options.outputAudioFormat ?? 'pcm16',
79
+ turn_detection: options.turnDetection ?? {
80
+ type: 'server_vad',
81
+ threshold: 0.5,
82
+ prefix_padding_ms: 300,
83
+ silence_duration_ms: 500,
84
+ },
85
+ tools: options.tools ?? [],
86
+ },
87
+ }))
88
+
89
+ // Start capturing microphone audio
90
+ startAudioCapture(ws)
91
+ }
92
+
93
+ ws.onmessage = (event) => {
94
+ const data = JSON.parse(event.data) as AIRealtimeEvent
95
+ options.onEvent?.(data)
96
+
97
+ switch (data.type) {
98
+ case 'input_audio_buffer.speech_started':
99
+ setIsSpeaking(true)
100
+ break
101
+ case 'input_audio_buffer.speech_stopped':
102
+ setIsSpeaking(false)
103
+ break
104
+ case 'response.text.delta':
105
+ setResponse(prev => prev + data.delta)
106
+ break
107
+ case 'response.text.done':
108
+ options.onResponse?.(data.text)
109
+ setResponse('')
110
+ break
111
+ case 'response.audio.delta':
112
+ playAudioDelta(data.delta)
113
+ break
114
+ case 'error':
115
+ setError(data.error.message)
116
+ break
117
+ }
118
+ }
119
+
120
+ ws.onerror = () => setError('WebSocket connection error')
121
+ ws.onclose = () => {
122
+ setIsConnected(false)
123
+ setIsListening(false)
124
+ stopAudioCapture()
125
+ }
126
+
127
+ } catch (err) {
128
+ setError(err instanceof Error ? err.message : 'Failed to connect')
129
+ }
130
+ }, [options])
131
+
132
+ const startAudioCapture = useCallback(async (ws: WebSocket) => {
133
+ try {
134
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 24000, channelCount: 1 } })
135
+ streamRef.current = stream
136
+ const audioCtx = new AudioContext({ sampleRate: 24000 })
137
+ audioContextRef.current = audioCtx
138
+ const source = audioCtx.createMediaStreamSource(stream)
139
+ const processor = audioCtx.createScriptProcessor(4096, 1, 1)
140
+ processorRef.current = processor
141
+
142
+ processor.onaudioprocess = (e) => {
143
+ if (ws.readyState !== WebSocket.OPEN) return
144
+ const inputData = e.inputBuffer.getChannelData(0)
145
+ const pcm16 = new Int16Array(inputData.length)
146
+ for (let i = 0; i < inputData.length; i++) {
147
+ const s = Math.max(-1, Math.min(1, inputData[i]!))
148
+ pcm16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF
149
+ }
150
+ const bytes = new Uint8Array(pcm16.buffer)
151
+ let binary = ''
152
+ for (let i = 0; i < bytes.length; i++) {
153
+ binary += String.fromCharCode(bytes[i]!)
154
+ }
155
+ ws.send(JSON.stringify({
156
+ type: 'input_audio_buffer.append',
157
+ audio: btoa(binary),
158
+ }))
159
+ }
160
+
161
+ source.connect(processor)
162
+ processor.connect(audioCtx.destination)
163
+ setIsListening(true)
164
+ } catch (err) {
165
+ setError(err instanceof Error ? err.message : 'Microphone access denied')
166
+ }
167
+ }, [])
168
+
169
+ const stopAudioCapture = useCallback(() => {
170
+ processorRef.current?.disconnect()
171
+ processorRef.current = null
172
+ streamRef.current?.getTracks().forEach(t => t.stop())
173
+ streamRef.current = null
174
+ audioContextRef.current?.close()
175
+ audioContextRef.current = null
176
+ setIsListening(false)
177
+ }, [])
178
+
179
+ const playAudioDelta = useCallback((_delta: string) => {
180
+ // In production, queue PCM16 deltas and pipe to AudioWorklet
181
+ // Simplified: uses browser Audio element with base64 chunks
182
+ // Full implementation would use AudioContext + AudioWorkletNode
183
+ }, [])
184
+
185
+ const disconnect = useCallback(() => {
186
+ wsRef.current?.close()
187
+ wsRef.current = null
188
+ stopAudioCapture()
189
+ setIsConnected(false)
190
+ }, [stopAudioCapture])
191
+
192
+ // Cleanup on unmount
193
+ useEffect(() => {
194
+ return () => { disconnect() }
195
+ }, [])
196
+
197
+ return {
198
+ connect, disconnect,
199
+ isConnected, isSpeaking, isListening,
200
+ error, transcript, response,
201
+ clearError: () => setError(null),
202
+ }
203
+ }
@@ -0,0 +1,114 @@
1
+ // @geenius-ai/react — src/hooks/useSkills.ts
2
+
3
+ import { useState, useCallback, useMemo } from 'react'
4
+ import type {
5
+ SkillDefinition,
6
+ SkillContext,
7
+ SkillResult,
8
+ SkillCategory,
9
+ } from '@geenius-ai/shared'
10
+ import { BUILT_IN_SKILLS } from '@geenius-ai/shared'
11
+
12
+ export interface UseSkillsOptions {
13
+ /** Convex action to execute a skill */
14
+ executeFn: (context: SkillContext) => Promise<SkillResult>
15
+ /** Additional custom skills to register */
16
+ customSkills?: SkillDefinition[]
17
+ /** Default model */
18
+ defaultModel?: string
19
+ /** User ID for personalization */
20
+ userId?: string
21
+ }
22
+
23
+ export interface UseSkillsReturn {
24
+ /** List all available skills */
25
+ skills: SkillDefinition[]
26
+ /** List skills by category */
27
+ byCategory: (category: SkillCategory) => SkillDefinition[]
28
+ /** Search skills */
29
+ search: (query: string) => SkillDefinition[]
30
+ /** Get a skill by ID */
31
+ getSkill: (id: string) => SkillDefinition | undefined
32
+ /** Execute a skill */
33
+ execute: (skillId: string, params: Record<string, unknown>, context?: Record<string, unknown>) => Promise<SkillResult>
34
+ /** Last result */
35
+ result: SkillResult | null
36
+ /** Loading state */
37
+ isExecuting: boolean
38
+ /** Error */
39
+ error: Error | null
40
+ /** Reset state */
41
+ reset: () => void
42
+ }
43
+
44
+ export function useSkills(options: UseSkillsOptions): UseSkillsReturn {
45
+ const { executeFn, customSkills = [], defaultModel, userId } = options
46
+ const [result, setResult] = useState<SkillResult | null>(null)
47
+ const [isExecuting, setIsExecuting] = useState(false)
48
+ const [error, setError] = useState<Error | null>(null)
49
+
50
+ const allSkills = useMemo(() => {
51
+ const map = new Map<string, SkillDefinition>()
52
+ for (const skill of Object.values(BUILT_IN_SKILLS)) map.set(skill.id, skill)
53
+ for (const skill of customSkills) map.set(skill.id, skill)
54
+ return Array.from(map.values())
55
+ }, [customSkills])
56
+
57
+ const byCategory = useCallback((category: SkillCategory) =>
58
+ allSkills.filter(s => s.category === category), [allSkills])
59
+
60
+ const searchSkills = useCallback((query: string) => {
61
+ const q = query.toLowerCase()
62
+ return allSkills.filter(s =>
63
+ s.name.toLowerCase().includes(q) ||
64
+ s.description.toLowerCase().includes(q) ||
65
+ s.tags?.some(t => t.toLowerCase().includes(q))
66
+ )
67
+ }, [allSkills])
68
+
69
+ const getSkill = useCallback((id: string) =>
70
+ allSkills.find(s => s.id === id), [allSkills])
71
+
72
+ const execute = useCallback(async (
73
+ skillId: string,
74
+ params: Record<string, unknown>,
75
+ context?: Record<string, unknown>,
76
+ ) => {
77
+ setIsExecuting(true)
78
+ setError(null)
79
+ try {
80
+ const res = await executeFn({
81
+ skillId,
82
+ params,
83
+ userId,
84
+ context,
85
+ model: defaultModel,
86
+ })
87
+ setResult(res)
88
+ return res
89
+ } catch (err) {
90
+ const e = err instanceof Error ? err : new Error(String(err))
91
+ setError(e)
92
+ throw e
93
+ } finally {
94
+ setIsExecuting(false)
95
+ }
96
+ }, [executeFn, userId, defaultModel])
97
+
98
+ const reset = useCallback(() => {
99
+ setResult(null)
100
+ setError(null)
101
+ }, [])
102
+
103
+ return {
104
+ skills: allSkills,
105
+ byCategory,
106
+ search: searchSkills,
107
+ getSkill,
108
+ execute,
109
+ result,
110
+ isExecuting,
111
+ error,
112
+ reset,
113
+ }
114
+ }
@@ -0,0 +1,99 @@
1
+ // @geenius-ai/react — src/hooks/useTextToSpeech.ts
2
+
3
+ /**
4
+ * TTS hook — OpenAI TTS, ElevenLabs, etc.
5
+ */
6
+
7
+ import { useState, useCallback, useRef } from 'react'
8
+ import { useAction } from 'convex/react'
9
+
10
+ export interface UseTextToSpeechOptions {
11
+ generateAudioAction: any
12
+ defaultVoice?: string
13
+ defaultModel?: string
14
+ autoPlay?: boolean
15
+ }
16
+
17
+ export interface UseTextToSpeechReturn {
18
+ speak: (text: string, options?: {
19
+ voice?: string; model?: string; speed?: number
20
+ voiceSettings?: {
21
+ stability?: number; similarityBoost?: number; style?: number
22
+ }
23
+ }) => Promise<string>
24
+ stop: () => void
25
+ isSpeaking: boolean
26
+ isGenerating: boolean
27
+ error: string | null
28
+ audioUrl: string | null
29
+ clearError: () => void
30
+ }
31
+
32
+ export function useTextToSpeech(options: UseTextToSpeechOptions): UseTextToSpeechReturn {
33
+ const [isGenerating, setIsGenerating] = useState(false)
34
+ const [isSpeaking, setIsSpeaking] = useState(false)
35
+ const [error, setError] = useState<string | null>(null)
36
+ const [audioUrl, setAudioUrl] = useState<string | null>(null)
37
+ const audioRef = useRef<HTMLAudioElement | null>(null)
38
+ const action = useAction(options.generateAudioAction)
39
+
40
+ const speak = useCallback(async (text: string, opts?: {
41
+ voice?: string; model?: string; speed?: number
42
+ voiceSettings?: { stability?: number; similarityBoost?: number; style?: number }
43
+ }) => {
44
+ setIsGenerating(true)
45
+ setError(null)
46
+ try {
47
+ const base64 = await action({
48
+ prompt: text,
49
+ voice: opts?.voice ?? options.defaultVoice ?? 'alloy',
50
+ model: opts?.model ?? options.defaultModel,
51
+ speed: opts?.speed,
52
+ voiceSettings: opts?.voiceSettings,
53
+ })
54
+
55
+ const url = base64.startsWith('http')
56
+ ? base64
57
+ : `data:audio/mp3;base64,${base64}`
58
+
59
+ setAudioUrl(url)
60
+
61
+ // Auto-play if enabled
62
+ if (options.autoPlay !== false) {
63
+ if (audioRef.current) {
64
+ audioRef.current.pause()
65
+ }
66
+ const audio = new Audio(url)
67
+ audioRef.current = audio
68
+ setIsSpeaking(true)
69
+ audio.onended = () => setIsSpeaking(false)
70
+ audio.onerror = () => {
71
+ setIsSpeaking(false)
72
+ setError('Audio playback failed')
73
+ }
74
+ await audio.play()
75
+ }
76
+
77
+ return url
78
+ } catch (err) {
79
+ const msg = err instanceof Error ? err.message : 'TTS generation failed'
80
+ setError(msg)
81
+ throw err
82
+ } finally {
83
+ setIsGenerating(false)
84
+ }
85
+ }, [action, options])
86
+
87
+ const stop = useCallback(() => {
88
+ if (audioRef.current) {
89
+ audioRef.current.pause()
90
+ audioRef.current = null
91
+ }
92
+ setIsSpeaking(false)
93
+ }, [])
94
+
95
+ return {
96
+ speak, stop, isSpeaking, isGenerating, error, audioUrl,
97
+ clearError: () => setError(null),
98
+ }
99
+ }