@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,158 @@
|
|
|
1
|
+
// @geenius-ai/react — src/hooks/useAI.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Core AI hook — generate text/image/audio/video with loading state.
|
|
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 placeholder for optional Convex action references.
|
|
12
|
+
// Ensures hooks are always called unconditionally (React Rules of Hooks).
|
|
13
|
+
const _noop = (() => Promise.resolve(null)) as any
|
|
14
|
+
|
|
15
|
+
export interface UseAIOptions {
|
|
16
|
+
/** Convex action reference for text generation (api.ai.generateText) */
|
|
17
|
+
generateTextAction?: any
|
|
18
|
+
/** Convex action reference for image generation */
|
|
19
|
+
generateImageAction?: any
|
|
20
|
+
/** Convex action reference for audio generation */
|
|
21
|
+
generateAudioAction?: any
|
|
22
|
+
/** Convex action reference for transcription */
|
|
23
|
+
transcribeAudioAction?: any
|
|
24
|
+
/** Convex action reference for video generation */
|
|
25
|
+
generateVideoAction?: any
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UseAIReturn {
|
|
29
|
+
generateText: (args: {
|
|
30
|
+
model: string
|
|
31
|
+
messages: any[]
|
|
32
|
+
temperature?: number
|
|
33
|
+
maxTokens?: number
|
|
34
|
+
caller?: string
|
|
35
|
+
}) => Promise<string>
|
|
36
|
+
generateImage: (prompt: string, model?: string) => Promise<string>
|
|
37
|
+
generateAudio: (prompt: string, voice?: string) => Promise<string>
|
|
38
|
+
transcribeAudio: (audio: string) => Promise<string>
|
|
39
|
+
generateVideo: (prompt: string) => Promise<string>
|
|
40
|
+
isLoading: boolean
|
|
41
|
+
error: string | null
|
|
42
|
+
lastResult: string | null
|
|
43
|
+
lastType: AIGenerationType | null
|
|
44
|
+
clearError: () => void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function useAI(options: UseAIOptions = {}): UseAIReturn {
|
|
48
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
49
|
+
const [error, setError] = useState<string | null>(null)
|
|
50
|
+
const [lastResult, setLastResult] = useState<string | null>(null)
|
|
51
|
+
const [lastType, setLastType] = useState<AIGenerationType | null>(null)
|
|
52
|
+
|
|
53
|
+
// Always call hooks unconditionally; gate actual invocations on options presence
|
|
54
|
+
const textAction = useAction(options.generateTextAction ?? _noop)
|
|
55
|
+
const imageAction = useAction(options.generateImageAction ?? _noop)
|
|
56
|
+
const audioAction = useAction(options.generateAudioAction ?? _noop)
|
|
57
|
+
const transcribeAction = useAction(options.transcribeAudioAction ?? _noop)
|
|
58
|
+
const videoAction = useAction(options.generateVideoAction ?? _noop)
|
|
59
|
+
|
|
60
|
+
const generateText = useCallback(async (args: {
|
|
61
|
+
model: string; messages: any[]; temperature?: number; maxTokens?: number; caller?: string
|
|
62
|
+
}) => {
|
|
63
|
+
if (!options.generateTextAction) throw new Error('generateTextAction not provided')
|
|
64
|
+
setIsLoading(true)
|
|
65
|
+
setError(null)
|
|
66
|
+
try {
|
|
67
|
+
const result = await textAction(args)
|
|
68
|
+
setLastResult(result)
|
|
69
|
+
setLastType('text')
|
|
70
|
+
return result
|
|
71
|
+
} catch (err) {
|
|
72
|
+
const msg = err instanceof Error ? err.message : 'Text generation failed'
|
|
73
|
+
setError(msg)
|
|
74
|
+
throw err
|
|
75
|
+
} finally {
|
|
76
|
+
setIsLoading(false)
|
|
77
|
+
}
|
|
78
|
+
}, [textAction])
|
|
79
|
+
|
|
80
|
+
const generateImage = useCallback(async (prompt: string, model?: string) => {
|
|
81
|
+
if (!options.generateImageAction) throw new Error('generateImageAction not provided')
|
|
82
|
+
setIsLoading(true)
|
|
83
|
+
setError(null)
|
|
84
|
+
try {
|
|
85
|
+
const result = await imageAction({ prompt, model })
|
|
86
|
+
setLastResult(result)
|
|
87
|
+
setLastType('image')
|
|
88
|
+
return result
|
|
89
|
+
} catch (err) {
|
|
90
|
+
const msg = err instanceof Error ? err.message : 'Image generation failed'
|
|
91
|
+
setError(msg)
|
|
92
|
+
throw err
|
|
93
|
+
} finally {
|
|
94
|
+
setIsLoading(false)
|
|
95
|
+
}
|
|
96
|
+
}, [imageAction])
|
|
97
|
+
|
|
98
|
+
const generateAudio = useCallback(async (prompt: string, voice?: string) => {
|
|
99
|
+
if (!options.generateAudioAction) throw new Error('generateAudioAction not provided')
|
|
100
|
+
setIsLoading(true)
|
|
101
|
+
setError(null)
|
|
102
|
+
try {
|
|
103
|
+
const result = await audioAction({ prompt, voice })
|
|
104
|
+
setLastResult(result)
|
|
105
|
+
setLastType('audio')
|
|
106
|
+
return result
|
|
107
|
+
} catch (err) {
|
|
108
|
+
const msg = err instanceof Error ? err.message : 'Audio generation failed'
|
|
109
|
+
setError(msg)
|
|
110
|
+
throw err
|
|
111
|
+
} finally {
|
|
112
|
+
setIsLoading(false)
|
|
113
|
+
}
|
|
114
|
+
}, [audioAction])
|
|
115
|
+
|
|
116
|
+
const transcribeAudio = useCallback(async (audio: string) => {
|
|
117
|
+
if (!options.transcribeAudioAction) throw new Error('transcribeAudioAction not provided')
|
|
118
|
+
setIsLoading(true)
|
|
119
|
+
setError(null)
|
|
120
|
+
try {
|
|
121
|
+
const result = await transcribeAction({ audio })
|
|
122
|
+
setLastResult(result)
|
|
123
|
+
setLastType('transcription')
|
|
124
|
+
return result
|
|
125
|
+
} catch (err) {
|
|
126
|
+
const msg = err instanceof Error ? err.message : 'Transcription failed'
|
|
127
|
+
setError(msg)
|
|
128
|
+
throw err
|
|
129
|
+
} finally {
|
|
130
|
+
setIsLoading(false)
|
|
131
|
+
}
|
|
132
|
+
}, [transcribeAction])
|
|
133
|
+
|
|
134
|
+
const generateVideo = useCallback(async (prompt: string) => {
|
|
135
|
+
if (!options.generateVideoAction) throw new Error('generateVideoAction not provided')
|
|
136
|
+
setIsLoading(true)
|
|
137
|
+
setError(null)
|
|
138
|
+
try {
|
|
139
|
+
const result = await videoAction({ prompt })
|
|
140
|
+
setLastResult(result)
|
|
141
|
+
setLastType('video')
|
|
142
|
+
return result
|
|
143
|
+
} catch (err) {
|
|
144
|
+
const msg = err instanceof Error ? err.message : 'Video generation failed'
|
|
145
|
+
setError(msg)
|
|
146
|
+
throw err
|
|
147
|
+
} finally {
|
|
148
|
+
setIsLoading(false)
|
|
149
|
+
}
|
|
150
|
+
}, [videoAction])
|
|
151
|
+
|
|
152
|
+
const clearError = useCallback(() => setError(null), [])
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
generateText, generateImage, generateAudio, transcribeAudio, generateVideo,
|
|
156
|
+
isLoading, error, lastResult, lastType, clearError,
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @geenius-ai/react — src/hooks/useAILogs.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook for querying AI logs with filters.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useQuery } from 'convex/react'
|
|
8
|
+
import type { AILogEntry, AIRequestStatus } from '@geenius-ai/shared'
|
|
9
|
+
|
|
10
|
+
export interface UseAILogsOptions {
|
|
11
|
+
/** Query reference: api.ai.listLogs */
|
|
12
|
+
listLogsQuery: any
|
|
13
|
+
filters?: {
|
|
14
|
+
model?: string
|
|
15
|
+
provider?: string
|
|
16
|
+
status?: AIRequestStatus
|
|
17
|
+
caller?: string
|
|
18
|
+
}
|
|
19
|
+
limit?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UseAILogsReturn {
|
|
23
|
+
logs: AILogEntry[]
|
|
24
|
+
isLoading: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useAILogs(options: UseAILogsOptions): UseAILogsReturn {
|
|
28
|
+
const logs = useQuery(options.listLogsQuery, {
|
|
29
|
+
model: options.filters?.model,
|
|
30
|
+
provider: options.filters?.provider,
|
|
31
|
+
status: options.filters?.status,
|
|
32
|
+
caller: options.filters?.caller,
|
|
33
|
+
limit: options.limit ?? 50,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
logs: (logs ?? []) as AILogEntry[],
|
|
38
|
+
isLoading: logs === undefined,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// @geenius-ai/react — src/hooks/useAIModels.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook for managing AI models and providers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useQuery, useMutation } from 'convex/react'
|
|
8
|
+
import { useCallback } from 'react'
|
|
9
|
+
import type { AIModel } from '@geenius-ai/shared'
|
|
10
|
+
|
|
11
|
+
// Stable no-op for optional Convex mutation references (Rules of Hooks)
|
|
12
|
+
const _noop = (() => Promise.resolve(null)) as any
|
|
13
|
+
|
|
14
|
+
export interface UseAIModelsOptions {
|
|
15
|
+
/** Query reference: api.ai.listModels */
|
|
16
|
+
listModelsQuery: any
|
|
17
|
+
/** Mutation reference: api.ai.upsertModel */
|
|
18
|
+
upsertModelMutation?: any
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UseAIModelsReturn {
|
|
22
|
+
models: AIModel[]
|
|
23
|
+
isLoading: boolean
|
|
24
|
+
upsertModel: (model: Partial<AIModel> & { id: string; provider: string }) => Promise<void>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useAIModels(options: UseAIModelsOptions): UseAIModelsReturn {
|
|
28
|
+
const models = useQuery(options.listModelsQuery, {})
|
|
29
|
+
// Always call useMutation unconditionally (React Rules of Hooks)
|
|
30
|
+
const upsertMutation = useMutation(options.upsertModelMutation ?? _noop)
|
|
31
|
+
|
|
32
|
+
const upsertModel = useCallback(async (
|
|
33
|
+
model: Partial<AIModel> & { id: string; provider: string },
|
|
34
|
+
) => {
|
|
35
|
+
if (!options.upsertModelMutation) throw new Error('upsertModelMutation not provided')
|
|
36
|
+
await upsertMutation({
|
|
37
|
+
model: model.id,
|
|
38
|
+
provider: model.provider,
|
|
39
|
+
displayName: model.displayName,
|
|
40
|
+
inputCostPer1k: model.inputCostPer1k ?? 0,
|
|
41
|
+
outputCostPer1k: model.outputCostPer1k ?? 0,
|
|
42
|
+
capabilities: model.capabilities,
|
|
43
|
+
contextWindow: model.contextWindow,
|
|
44
|
+
isActive: model.isActive,
|
|
45
|
+
})
|
|
46
|
+
}, [upsertMutation, options.upsertModelMutation])
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
models: (models ?? []) as AIModel[],
|
|
50
|
+
isLoading: models === undefined,
|
|
51
|
+
upsertModel,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// @geenius-ai/react — src/hooks/useChat.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Chat conversation hook — manages messages, sends messages,
|
|
5
|
+
* and tracks conversation state.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useCallback, useRef, useEffect } from 'react'
|
|
9
|
+
import { useQuery, useMutation, useAction, skipToken } from 'convex/react'
|
|
10
|
+
import type { AIChatMessage, AIMessageRole } from '@geenius-ai/shared'
|
|
11
|
+
|
|
12
|
+
// Stable no-op for optional Convex mutation/action refs (React Rules of Hooks)
|
|
13
|
+
const _noop = (() => Promise.resolve(null)) as any
|
|
14
|
+
|
|
15
|
+
export interface UseChatOptions {
|
|
16
|
+
conversationId?: string
|
|
17
|
+
/** Query reference: api.ai.getConversation */
|
|
18
|
+
getConversationQuery?: any
|
|
19
|
+
/** Query reference: api.ai.listMessages */
|
|
20
|
+
listMessagesQuery?: any
|
|
21
|
+
/** Mutation reference: api.ai.sendMessage */
|
|
22
|
+
sendMessageMutation?: any
|
|
23
|
+
/** Action reference: api.ai.generateText (for AI response) */
|
|
24
|
+
generateTextAction?: any
|
|
25
|
+
/** Mutation reference: api.ai.createConversation */
|
|
26
|
+
createConversationMutation?: any
|
|
27
|
+
model?: string
|
|
28
|
+
systemPrompt?: string
|
|
29
|
+
onNewConversation?: (id: string) => void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UseChatReturn {
|
|
33
|
+
messages: AIChatMessage[]
|
|
34
|
+
isLoading: boolean
|
|
35
|
+
isSending: boolean
|
|
36
|
+
error: string | null
|
|
37
|
+
sendMessage: (content: string) => Promise<void>
|
|
38
|
+
conversationId: string | null
|
|
39
|
+
clearError: () => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useChat(options: UseChatOptions = {}): UseChatReturn {
|
|
43
|
+
const [localMessages, setLocalMessages] = useState<AIChatMessage[]>([])
|
|
44
|
+
const [isSending, setIsSending] = useState(false)
|
|
45
|
+
const [error, setError] = useState<string | null>(null)
|
|
46
|
+
const [conversationId, setConversationId] = useState<string | null>(options.conversationId ?? null)
|
|
47
|
+
|
|
48
|
+
// Always call Convex hooks unconditionally (React Rules of Hooks).
|
|
49
|
+
// skipToken skips the query when no ref or no conversationId is available.
|
|
50
|
+
const convexMessages = useQuery(
|
|
51
|
+
options.listMessagesQuery ?? skipToken,
|
|
52
|
+
options.listMessagesQuery && conversationId ? { conversationId } : skipToken,
|
|
53
|
+
)
|
|
54
|
+
const sendMutation = useMutation(options.sendMessageMutation ?? _noop)
|
|
55
|
+
const createConversation = useMutation(options.createConversationMutation ?? _noop)
|
|
56
|
+
const textAction = useAction(options.generateTextAction ?? _noop)
|
|
57
|
+
|
|
58
|
+
const messages = convexMessages ?? localMessages
|
|
59
|
+
|
|
60
|
+
const sendMessage = useCallback(async (content: string) => {
|
|
61
|
+
setIsSending(true)
|
|
62
|
+
setError(null)
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
let activeConversationId = conversationId
|
|
66
|
+
|
|
67
|
+
// Create conversation if needed
|
|
68
|
+
if (!activeConversationId && options.createConversationMutation) {
|
|
69
|
+
activeConversationId = await createConversation({
|
|
70
|
+
title: content.substring(0, 100),
|
|
71
|
+
model: options.model ?? 'gpt-4o',
|
|
72
|
+
systemPrompt: options.systemPrompt,
|
|
73
|
+
})
|
|
74
|
+
setConversationId(activeConversationId)
|
|
75
|
+
if (activeConversationId) options.onNewConversation?.(activeConversationId)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Add user message
|
|
79
|
+
if (options.sendMessageMutation && activeConversationId) {
|
|
80
|
+
await sendMutation({ conversationId: activeConversationId, content })
|
|
81
|
+
} else {
|
|
82
|
+
// Local-only mode
|
|
83
|
+
const userMsg: AIChatMessage = {
|
|
84
|
+
id: `msg-${Date.now()}`,
|
|
85
|
+
conversationId: activeConversationId ?? 'local',
|
|
86
|
+
userId: 'local',
|
|
87
|
+
role: 'user',
|
|
88
|
+
content,
|
|
89
|
+
createdAt: Date.now(),
|
|
90
|
+
}
|
|
91
|
+
setLocalMessages(prev => [...prev, userMsg])
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Generate AI response if action provided
|
|
95
|
+
if (options.generateTextAction) {
|
|
96
|
+
const aiResponse = await textAction({
|
|
97
|
+
model: options.model ?? 'gpt-4o',
|
|
98
|
+
messages: [
|
|
99
|
+
...(options.systemPrompt
|
|
100
|
+
? [{ role: 'system', content: options.systemPrompt }]
|
|
101
|
+
: []),
|
|
102
|
+
...(messages as AIChatMessage[]).map((m: AIChatMessage) => ({ role: m.role, content: m.content })),
|
|
103
|
+
{ role: 'user', content },
|
|
104
|
+
],
|
|
105
|
+
caller: 'chat',
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// If local-only, add the response too
|
|
109
|
+
if (!options.sendMessageMutation) {
|
|
110
|
+
const assistantMsg: AIChatMessage = {
|
|
111
|
+
id: `msg-${Date.now()}-ai`,
|
|
112
|
+
conversationId: activeConversationId ?? 'local',
|
|
113
|
+
userId: 'ai',
|
|
114
|
+
role: 'assistant',
|
|
115
|
+
content: aiResponse,
|
|
116
|
+
model: options.model ?? 'gpt-4o',
|
|
117
|
+
createdAt: Date.now(),
|
|
118
|
+
}
|
|
119
|
+
setLocalMessages(prev => [...prev, assistantMsg])
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
const msg = err instanceof Error ? err.message : 'Failed to send message'
|
|
124
|
+
setError(msg)
|
|
125
|
+
} finally {
|
|
126
|
+
setIsSending(false)
|
|
127
|
+
}
|
|
128
|
+
}, [conversationId, sendMutation, createConversation, textAction, messages, options])
|
|
129
|
+
|
|
130
|
+
const clearError = useCallback(() => setError(null), [])
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
messages: messages as AIChatMessage[],
|
|
134
|
+
isLoading: convexMessages === undefined && !!options.listMessagesQuery,
|
|
135
|
+
isSending,
|
|
136
|
+
error,
|
|
137
|
+
sendMessage,
|
|
138
|
+
conversationId,
|
|
139
|
+
clearError,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// @geenius-ai/react — src/hooks/useContentManager.ts
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react'
|
|
4
|
+
import type {
|
|
5
|
+
ContentAction,
|
|
6
|
+
ContentTone,
|
|
7
|
+
ContentType,
|
|
8
|
+
ContentGenerateOptions,
|
|
9
|
+
ContentResult,
|
|
10
|
+
ContentTemplate,
|
|
11
|
+
} from '@geenius-ai/shared'
|
|
12
|
+
|
|
13
|
+
export interface UseContentManagerOptions {
|
|
14
|
+
/** Convex action for content generation */
|
|
15
|
+
generateFn: (options: ContentGenerateOptions) => Promise<ContentResult>
|
|
16
|
+
/** Default content type */
|
|
17
|
+
defaultType?: ContentType
|
|
18
|
+
/** Default tone */
|
|
19
|
+
defaultTone?: ContentTone
|
|
20
|
+
/** Default model */
|
|
21
|
+
defaultModel?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseContentManagerReturn {
|
|
25
|
+
/** Generate content */
|
|
26
|
+
generate: (input: string, options?: Partial<ContentGenerateOptions>) => Promise<ContentResult>
|
|
27
|
+
/** Rewrite content */
|
|
28
|
+
rewrite: (text: string, instructions?: string) => Promise<ContentResult>
|
|
29
|
+
/** Translate content */
|
|
30
|
+
translate: (text: string, language: string) => Promise<ContentResult>
|
|
31
|
+
/** Summarize content */
|
|
32
|
+
summarize: (text: string) => Promise<ContentResult>
|
|
33
|
+
/** Generate variations */
|
|
34
|
+
variations: (text: string, count?: number) => Promise<ContentResult>
|
|
35
|
+
/** Change tone */
|
|
36
|
+
changeTone: (text: string, tone: ContentTone) => Promise<ContentResult>
|
|
37
|
+
/** Proofread */
|
|
38
|
+
proofread: (text: string) => Promise<ContentResult>
|
|
39
|
+
/** Current result */
|
|
40
|
+
result: ContentResult | null
|
|
41
|
+
/** Loading state */
|
|
42
|
+
isGenerating: boolean
|
|
43
|
+
/** Error state */
|
|
44
|
+
error: Error | null
|
|
45
|
+
/** Clear state */
|
|
46
|
+
reset: () => void
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function useContentManager(options: UseContentManagerOptions): UseContentManagerReturn {
|
|
50
|
+
const { generateFn, defaultType = 'text', defaultTone, defaultModel } = options
|
|
51
|
+
const [result, setResult] = useState<ContentResult | null>(null)
|
|
52
|
+
const [isGenerating, setIsGenerating] = useState(false)
|
|
53
|
+
const [error, setError] = useState<Error | null>(null)
|
|
54
|
+
|
|
55
|
+
const execute = useCallback(async (action: ContentAction, input: string, extra?: Partial<ContentGenerateOptions>) => {
|
|
56
|
+
setIsGenerating(true)
|
|
57
|
+
setError(null)
|
|
58
|
+
try {
|
|
59
|
+
const res = await generateFn({
|
|
60
|
+
action,
|
|
61
|
+
input,
|
|
62
|
+
type: extra?.type ?? defaultType,
|
|
63
|
+
tone: extra?.tone ?? defaultTone,
|
|
64
|
+
model: extra?.model ?? defaultModel,
|
|
65
|
+
...extra,
|
|
66
|
+
})
|
|
67
|
+
setResult(res)
|
|
68
|
+
return res
|
|
69
|
+
} catch (err) {
|
|
70
|
+
const e = err instanceof Error ? err : new Error(String(err))
|
|
71
|
+
setError(e)
|
|
72
|
+
throw e
|
|
73
|
+
} finally {
|
|
74
|
+
setIsGenerating(false)
|
|
75
|
+
}
|
|
76
|
+
}, [generateFn, defaultType, defaultTone, defaultModel])
|
|
77
|
+
|
|
78
|
+
const generate = useCallback((input: string, opts?: Partial<ContentGenerateOptions>) =>
|
|
79
|
+
execute('generate', input, opts), [execute])
|
|
80
|
+
|
|
81
|
+
const rewrite = useCallback((text: string, instructions?: string) =>
|
|
82
|
+
execute('rewrite', text, { instructions }), [execute])
|
|
83
|
+
|
|
84
|
+
const translate = useCallback((text: string, language: string) =>
|
|
85
|
+
execute('translate', text, { language }), [execute])
|
|
86
|
+
|
|
87
|
+
const summarize = useCallback((text: string) =>
|
|
88
|
+
execute('summarize', text), [execute])
|
|
89
|
+
|
|
90
|
+
const variations = useCallback((text: string, count: number = 3) =>
|
|
91
|
+
execute('variations', text, { variations: count }), [execute])
|
|
92
|
+
|
|
93
|
+
const changeTone = useCallback((text: string, tone: ContentTone) =>
|
|
94
|
+
execute('change-tone', text, { tone }), [execute])
|
|
95
|
+
|
|
96
|
+
const proofread = useCallback((text: string) =>
|
|
97
|
+
execute('proofread', text), [execute])
|
|
98
|
+
|
|
99
|
+
const reset = useCallback(() => {
|
|
100
|
+
setResult(null)
|
|
101
|
+
setError(null)
|
|
102
|
+
}, [])
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
generate, rewrite, translate, summarize, variations, changeTone, proofread,
|
|
106
|
+
result, isGenerating, error, reset,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// @geenius-ai/react — src/hooks/useImageGeneration.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Image generation hook (DALL-E, SDXL, Flux, etc.)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useCallback } from 'react'
|
|
8
|
+
import { useAction } from 'convex/react'
|
|
9
|
+
|
|
10
|
+
export interface GeneratedImage {
|
|
11
|
+
url: string
|
|
12
|
+
prompt: string
|
|
13
|
+
model: string
|
|
14
|
+
timestamp: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UseImageGenerationOptions {
|
|
18
|
+
generateImageAction: any
|
|
19
|
+
defaultModel?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UseImageGenerationReturn {
|
|
23
|
+
generate: (prompt: string, options?: {
|
|
24
|
+
model?: string
|
|
25
|
+
negativePrompt?: string
|
|
26
|
+
size?: string
|
|
27
|
+
quality?: 'standard' | 'hd'
|
|
28
|
+
style?: 'natural' | 'vivid'
|
|
29
|
+
n?: number
|
|
30
|
+
seed?: number
|
|
31
|
+
}) => Promise<string>
|
|
32
|
+
images: GeneratedImage[]
|
|
33
|
+
isGenerating: boolean
|
|
34
|
+
error: string | null
|
|
35
|
+
clearImages: () => void
|
|
36
|
+
clearError: () => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useImageGeneration(options: UseImageGenerationOptions): UseImageGenerationReturn {
|
|
40
|
+
const [images, setImages] = useState<GeneratedImage[]>([])
|
|
41
|
+
const [isGenerating, setIsGenerating] = useState(false)
|
|
42
|
+
const [error, setError] = useState<string | null>(null)
|
|
43
|
+
const action = useAction(options.generateImageAction)
|
|
44
|
+
|
|
45
|
+
const generate = useCallback(async (prompt: string, opts?: {
|
|
46
|
+
model?: string; negativePrompt?: string; size?: string
|
|
47
|
+
quality?: 'standard' | 'hd'; style?: 'natural' | 'vivid'; n?: number; seed?: number
|
|
48
|
+
}) => {
|
|
49
|
+
setIsGenerating(true)
|
|
50
|
+
setError(null)
|
|
51
|
+
try {
|
|
52
|
+
const model = opts?.model ?? options.defaultModel ?? 'dall-e-3'
|
|
53
|
+
const result = await action({
|
|
54
|
+
prompt, model,
|
|
55
|
+
negativePrompt: opts?.negativePrompt,
|
|
56
|
+
size: opts?.size ?? '1024x1024',
|
|
57
|
+
quality: opts?.quality,
|
|
58
|
+
style: opts?.style,
|
|
59
|
+
n: opts?.n ?? 1,
|
|
60
|
+
seed: opts?.seed,
|
|
61
|
+
})
|
|
62
|
+
const img: GeneratedImage = {
|
|
63
|
+
url: result.startsWith('http') ? result : `data:image/png;base64,${result}`,
|
|
64
|
+
prompt, model, timestamp: Date.now(),
|
|
65
|
+
}
|
|
66
|
+
setImages(prev => [img, ...prev])
|
|
67
|
+
return img.url
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const msg = err instanceof Error ? err.message : 'Image generation failed'
|
|
70
|
+
setError(msg)
|
|
71
|
+
throw err
|
|
72
|
+
} finally {
|
|
73
|
+
setIsGenerating(false)
|
|
74
|
+
}
|
|
75
|
+
}, [action, options.defaultModel])
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
generate, images, isGenerating, error,
|
|
79
|
+
clearImages: () => setImages([]),
|
|
80
|
+
clearError: () => setError(null),
|
|
81
|
+
}
|
|
82
|
+
}
|