@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.
- package/.changeset/config.json +11 -0
- package/.env.example +2 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +23 -0
- package/.github/workflows/release.yml +29 -0
- package/.node-version +1 -0
- package/.nvmrc +1 -0
- package/.prettierrc +7 -0
- package/.project/ACCOUNT.yaml +4 -0
- package/.project/IDEAS.yaml +7 -0
- package/.project/PROJECT.yaml +11 -0
- package/.project/ROADMAP.yaml +15 -0
- package/CHANGELOG.md +15 -0
- package/CODE_OF_CONDUCT.md +26 -0
- package/CONTRIBUTING.md +61 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/SECURITY.md +18 -0
- package/SUPPORT.md +14 -0
- package/package.json +75 -0
- package/packages/convex/package.json +42 -0
- package/packages/convex/src/index.ts +8 -0
- package/packages/convex/src/mutations/messages.ts +29 -0
- package/packages/convex/src/queries/messages.ts +24 -0
- package/packages/convex/src/schema.ts +20 -0
- package/packages/convex/tsconfig.json +11 -0
- package/packages/convex/tsup.config.ts +17 -0
- package/packages/react/README.md +1 -0
- package/packages/react/package.json +60 -0
- package/packages/react/src/components/AILogTable.tsx +90 -0
- package/packages/react/src/components/ChatWindow.tsx +118 -0
- package/packages/react/src/components/GenerationCard.tsx +73 -0
- package/packages/react/src/components/ImageGenerator.tsx +103 -0
- package/packages/react/src/components/ModelSelector.tsx +44 -0
- package/packages/react/src/components/ModelTestRunner.tsx +148 -0
- package/packages/react/src/components/VoiceSelector.tsx +51 -0
- package/packages/react/src/components/index.ts +9 -0
- package/packages/react/src/hooks/index.ts +12 -0
- package/packages/react/src/hooks/useAI.ts +158 -0
- package/packages/react/src/hooks/useAILogs.ts +40 -0
- package/packages/react/src/hooks/useAIModels.ts +53 -0
- package/packages/react/src/hooks/useChat.ts +141 -0
- package/packages/react/src/hooks/useContentManager.ts +108 -0
- package/packages/react/src/hooks/useImageGeneration.ts +82 -0
- package/packages/react/src/hooks/useMemory.ts +161 -0
- package/packages/react/src/hooks/useModelTest.ts +126 -0
- package/packages/react/src/hooks/useRealtimeAudio.ts +203 -0
- package/packages/react/src/hooks/useSkills.ts +114 -0
- package/packages/react/src/hooks/useTextToSpeech.ts +99 -0
- package/packages/react/src/hooks/useTranscription.ts +119 -0
- package/packages/react/src/hooks/useVideoGeneration.ts +79 -0
- package/packages/react/src/index.ts +42 -0
- package/packages/react/src/pages/AILogsPage.tsx +98 -0
- package/packages/react/src/pages/ChatPage.tsx +42 -0
- package/packages/react/src/pages/ModelTestPage.tsx +33 -0
- package/packages/react/src/pages/index.ts +5 -0
- package/packages/react/tsconfig.json +26 -0
- package/packages/react/tsup.config.ts +22 -0
- package/packages/react-css/README.md +1 -0
- package/packages/react-css/package.json +45 -0
- package/packages/react-css/src/ai.css +857 -0
- package/packages/react-css/src/components/AILogTable.tsx +90 -0
- package/packages/react-css/src/components/ChatWindow.tsx +118 -0
- package/packages/react-css/src/components/GenerationCard.tsx +73 -0
- package/packages/react-css/src/components/ImageGenerator.tsx +103 -0
- package/packages/react-css/src/components/ModelSelector.tsx +44 -0
- package/packages/react-css/src/components/ModelTestRunner.tsx +148 -0
- package/packages/react-css/src/components/VoiceSelector.tsx +51 -0
- package/packages/react-css/src/components/index.ts +9 -0
- package/packages/react-css/src/hooks/index.ts +12 -0
- package/packages/react-css/src/hooks/useAI.ts +153 -0
- package/packages/react-css/src/hooks/useAILogs.ts +40 -0
- package/packages/react-css/src/hooks/useAIModels.ts +51 -0
- package/packages/react-css/src/hooks/useChat.ts +145 -0
- package/packages/react-css/src/hooks/useContentManager.ts +108 -0
- package/packages/react-css/src/hooks/useImageGeneration.ts +82 -0
- package/packages/react-css/src/hooks/useMemory.ts +161 -0
- package/packages/react-css/src/hooks/useModelTest.ts +122 -0
- package/packages/react-css/src/hooks/useRealtimeAudio.ts +203 -0
- package/packages/react-css/src/hooks/useSkills.ts +114 -0
- package/packages/react-css/src/hooks/useTextToSpeech.ts +99 -0
- package/packages/react-css/src/hooks/useTranscription.ts +119 -0
- package/packages/react-css/src/hooks/useVideoGeneration.ts +79 -0
- package/packages/react-css/src/index.ts +35 -0
- package/packages/react-css/src/pages/AILogsPage.tsx +98 -0
- package/packages/react-css/src/pages/ChatPage.tsx +42 -0
- package/packages/react-css/src/pages/ModelTestPage.tsx +33 -0
- package/packages/react-css/src/pages/index.ts +5 -0
- package/packages/react-css/src/styles.css +127 -0
- package/packages/react-css/tsconfig.json +26 -0
- package/packages/react-css/tsup.config.ts +2 -0
- package/packages/shared/README.md +1 -0
- package/packages/shared/package.json +71 -0
- package/packages/shared/src/__tests__/ai.test.ts +67 -0
- package/packages/shared/src/ai-client.ts +243 -0
- package/packages/shared/src/config.ts +235 -0
- package/packages/shared/src/content.ts +249 -0
- package/packages/shared/src/convex/helpers.ts +163 -0
- package/packages/shared/src/convex/index.ts +16 -0
- package/packages/shared/src/convex/schemas.ts +146 -0
- package/packages/shared/src/convex/validators.ts +136 -0
- package/packages/shared/src/index.ts +107 -0
- package/packages/shared/src/memory.ts +197 -0
- package/packages/shared/src/providers/base.ts +103 -0
- package/packages/shared/src/providers/elevenlabs.ts +155 -0
- package/packages/shared/src/providers/index.ts +28 -0
- package/packages/shared/src/providers/openai-compatible.ts +286 -0
- package/packages/shared/src/providers/registry.ts +113 -0
- package/packages/shared/src/providers/replicate-fal.ts +230 -0
- package/packages/shared/src/skills.ts +273 -0
- package/packages/shared/src/types.ts +501 -0
- package/packages/shared/tsconfig.json +25 -0
- package/packages/shared/tsup.config.ts +22 -0
- package/packages/shared/vitest.config.ts +4 -0
- package/packages/solidjs/README.md +1 -0
- package/packages/solidjs/package.json +59 -0
- package/packages/solidjs/src/components/ChatWindow.tsx +78 -0
- package/packages/solidjs/src/components/GenerationCard.tsx +62 -0
- package/packages/solidjs/src/components/ModelTestRunner.tsx +119 -0
- package/packages/solidjs/src/components/index.ts +5 -0
- package/packages/solidjs/src/index.ts +32 -0
- package/packages/solidjs/src/pages/ChatPage.tsx +22 -0
- package/packages/solidjs/src/pages/ModelTestPage.tsx +22 -0
- package/packages/solidjs/src/pages/index.ts +4 -0
- package/packages/solidjs/src/primitives/createAI.ts +79 -0
- package/packages/solidjs/src/primitives/createChat.ts +100 -0
- package/packages/solidjs/src/primitives/createContentManager.ts +61 -0
- package/packages/solidjs/src/primitives/createImageGeneration.ts +46 -0
- package/packages/solidjs/src/primitives/createMemory.ts +127 -0
- package/packages/solidjs/src/primitives/createModelTest.ts +89 -0
- package/packages/solidjs/src/primitives/createSkills.ts +83 -0
- package/packages/solidjs/src/primitives/createTextToSpeech.ts +56 -0
- package/packages/solidjs/src/primitives/createVideoGeneration.ts +46 -0
- package/packages/solidjs/src/primitives/index.ts +8 -0
- package/packages/solidjs/tsconfig.json +27 -0
- package/packages/solidjs/tsup.config.ts +21 -0
- package/packages/solidjs-css/README.md +1 -0
- package/packages/solidjs-css/package.json +44 -0
- package/packages/solidjs-css/src/ai.css +857 -0
- package/packages/solidjs-css/src/components/ChatWindow.tsx +78 -0
- package/packages/solidjs-css/src/components/GenerationCard.tsx +62 -0
- package/packages/solidjs-css/src/components/ModelTestRunner.tsx +119 -0
- package/packages/solidjs-css/src/components/index.ts +5 -0
- package/packages/solidjs-css/src/index.ts +26 -0
- package/packages/solidjs-css/src/pages/ChatPage.tsx +22 -0
- package/packages/solidjs-css/src/pages/ModelTestPage.tsx +22 -0
- package/packages/solidjs-css/src/pages/index.ts +4 -0
- package/packages/solidjs-css/src/primitives/createAI.ts +79 -0
- package/packages/solidjs-css/src/primitives/createChat.ts +100 -0
- package/packages/solidjs-css/src/primitives/createContentManager.ts +61 -0
- package/packages/solidjs-css/src/primitives/createImageGeneration.ts +46 -0
- package/packages/solidjs-css/src/primitives/createMemory.ts +127 -0
- package/packages/solidjs-css/src/primitives/createModelTest.ts +89 -0
- package/packages/solidjs-css/src/primitives/createSkills.ts +83 -0
- package/packages/solidjs-css/src/primitives/createTextToSpeech.ts +56 -0
- package/packages/solidjs-css/src/primitives/createVideoGeneration.ts +46 -0
- package/packages/solidjs-css/src/primitives/index.ts +1 -0
- package/packages/solidjs-css/src/styles.css +127 -0
- package/packages/solidjs-css/tsconfig.json +27 -0
- package/packages/solidjs-css/tsup.config.ts +2 -0
- 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
|
+
}
|