@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,122 @@
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
+ export interface UseModelTestOptions {
12
+ /** Action reference for text: api.ai.generateText */
13
+ generateTextAction?: any
14
+ /** Action reference for image: api.ai.generateImage */
15
+ generateImageAction?: any
16
+ /** Action reference for audio: api.ai.generateAudio */
17
+ generateAudioAction?: any
18
+ /** Action reference for transcription: api.ai.transcribeAudio */
19
+ transcribeAudioAction?: any
20
+ /** Action reference for video: api.ai.generateVideo */
21
+ generateVideoAction?: any
22
+ }
23
+
24
+ export interface ModelTestResult {
25
+ model: string
26
+ type: AIGenerationType
27
+ result: string
28
+ durationMs: number
29
+ timestamp: number
30
+ error?: string
31
+ }
32
+
33
+ export interface UseModelTestReturn {
34
+ runTest: (model: string, prompt: string, type?: AIGenerationType) => Promise<ModelTestResult>
35
+ runBatchTest: (models: string[], prompt: string) => Promise<ModelTestResult[]>
36
+ results: ModelTestResult[]
37
+ isRunning: boolean
38
+ clearResults: () => void
39
+ }
40
+
41
+ export function useModelTest(options: UseModelTestOptions = {}): UseModelTestReturn {
42
+ const [results, setResults] = useState<ModelTestResult[]>([])
43
+ const [isRunning, setIsRunning] = useState(false)
44
+
45
+ const textAction = options.generateTextAction ? useAction(options.generateTextAction) : null
46
+ const imageAction = options.generateImageAction ? useAction(options.generateImageAction) : null
47
+ const audioAction = options.generateAudioAction ? useAction(options.generateAudioAction) : null
48
+ const transcribeAction = options.transcribeAudioAction ? useAction(options.transcribeAudioAction) : null
49
+ const videoAction = options.generateVideoAction ? useAction(options.generateVideoAction) : null
50
+
51
+ const runTest = useCallback(async (
52
+ model: string,
53
+ prompt: string,
54
+ type: AIGenerationType = 'text',
55
+ ): Promise<ModelTestResult> => {
56
+ setIsRunning(true)
57
+ const start = Date.now()
58
+
59
+ try {
60
+ let result = ''
61
+ switch (type) {
62
+ case 'text':
63
+ if (!textAction) throw new Error('generateTextAction not provided')
64
+ result = await textAction({
65
+ model,
66
+ messages: [{ role: 'user', content: prompt }],
67
+ caller: 'model-test',
68
+ })
69
+ break
70
+ case 'image':
71
+ if (!imageAction) throw new Error('generateImageAction not provided')
72
+ result = await imageAction({ prompt, model })
73
+ break
74
+ case 'audio':
75
+ if (!audioAction) throw new Error('generateAudioAction not provided')
76
+ result = await audioAction({ prompt })
77
+ break
78
+ case 'video':
79
+ if (!videoAction) throw new Error('generateVideoAction not provided')
80
+ result = await videoAction({ prompt })
81
+ break
82
+ default:
83
+ throw new Error(`Unsupported test type: ${type}`)
84
+ }
85
+
86
+ const testResult: ModelTestResult = {
87
+ model, type, result, durationMs: Date.now() - start, timestamp: Date.now(),
88
+ }
89
+ setResults(prev => [...prev, testResult])
90
+ return testResult
91
+ } catch (err) {
92
+ const testResult: ModelTestResult = {
93
+ model, type, result: '',
94
+ durationMs: Date.now() - start,
95
+ timestamp: Date.now(),
96
+ error: err instanceof Error ? err.message : 'Test failed',
97
+ }
98
+ setResults(prev => [...prev, testResult])
99
+ return testResult
100
+ } finally {
101
+ setIsRunning(false)
102
+ }
103
+ }, [textAction, imageAction, audioAction, videoAction])
104
+
105
+ const runBatchTest = useCallback(async (
106
+ models: string[],
107
+ prompt: string,
108
+ ): Promise<ModelTestResult[]> => {
109
+ setIsRunning(true)
110
+ const batchResults: ModelTestResult[] = []
111
+ for (const model of models) {
112
+ const result = await runTest(model, prompt)
113
+ batchResults.push(result)
114
+ }
115
+ setIsRunning(false)
116
+ return batchResults
117
+ }, [runTest])
118
+
119
+ const clearResults = useCallback(() => setResults([]), [])
120
+
121
+ return { runTest, runBatchTest, results, isRunning, clearResults }
122
+ }
@@ -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
+ }