react-native-ai-hooks 0.2.0 → 0.3.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/package.json +1 -1
- package/src/hooks/useAICode.ts +206 -0
- package/src/hooks/useAISummarize.ts +158 -0
- package/src/hooks/useAITranslate.ts +207 -0
- package/src/index.ts +4 -1
package/package.json
CHANGED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseAICodeOptions {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
system?: string;
|
|
7
|
+
maxTokens?: number;
|
|
8
|
+
temperature?: number;
|
|
9
|
+
defaultLanguage?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface GenerateCodeInput {
|
|
13
|
+
prompt: string;
|
|
14
|
+
language?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ExplainCodeInput {
|
|
18
|
+
code: string;
|
|
19
|
+
language?: string;
|
|
20
|
+
focus?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface UseAICodeReturn {
|
|
24
|
+
language: string;
|
|
25
|
+
generatedCode: string;
|
|
26
|
+
explanation: string;
|
|
27
|
+
isLoading: boolean;
|
|
28
|
+
error: string | null;
|
|
29
|
+
setLanguage: (language: string) => void;
|
|
30
|
+
generateCode: (input: GenerateCodeInput) => Promise<string | null>;
|
|
31
|
+
explainCode: (input: ExplainCodeInput) => Promise<string | null>;
|
|
32
|
+
clearCodeState: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ClaudeTextBlock {
|
|
36
|
+
type?: string;
|
|
37
|
+
text?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ClaudeApiResult {
|
|
41
|
+
content?: ClaudeTextBlock[];
|
|
42
|
+
error?: {
|
|
43
|
+
message?: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getClaudeTextContent(data: unknown): string {
|
|
48
|
+
const content = (data as ClaudeApiResult)?.content;
|
|
49
|
+
if (!Array.isArray(content)) {
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return content
|
|
54
|
+
.filter(item => item?.type === 'text' && typeof item.text === 'string')
|
|
55
|
+
.map(item => item.text as string)
|
|
56
|
+
.join('\n')
|
|
57
|
+
.trim();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useAICode(options: UseAICodeOptions): UseAICodeReturn {
|
|
61
|
+
const [language, setLanguage] = useState(options.defaultLanguage || 'typescript');
|
|
62
|
+
const [generatedCode, setGeneratedCode] = useState('');
|
|
63
|
+
const [explanation, setExplanation] = useState('');
|
|
64
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
65
|
+
const [error, setError] = useState<string | null>(null);
|
|
66
|
+
|
|
67
|
+
const isMountedRef = useRef(true);
|
|
68
|
+
|
|
69
|
+
const clearCodeState = useCallback(() => {
|
|
70
|
+
setGeneratedCode('');
|
|
71
|
+
setExplanation('');
|
|
72
|
+
setError(null);
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
const sendClaudeRequest = useCallback(
|
|
76
|
+
async (prompt: string) => {
|
|
77
|
+
const apiResponse = await fetch('https://api.anthropic.com/v1/messages', {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
'x-api-key': options.apiKey,
|
|
82
|
+
'anthropic-version': '2023-06-01',
|
|
83
|
+
},
|
|
84
|
+
body: JSON.stringify({
|
|
85
|
+
model: options.model || 'claude-sonnet-4-20250514',
|
|
86
|
+
max_tokens: options.maxTokens ?? 1800,
|
|
87
|
+
temperature: options.temperature ?? 0.2,
|
|
88
|
+
system:
|
|
89
|
+
options.system ||
|
|
90
|
+
'You are an expert software engineer. Produce practical, correct code and clear explanations.',
|
|
91
|
+
messages: [{ role: 'user', content: prompt }],
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const data = (await apiResponse.json()) as ClaudeApiResult;
|
|
96
|
+
if (!apiResponse.ok) {
|
|
97
|
+
throw new Error(data?.error?.message || `Claude API error: ${apiResponse.status}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const text = getClaudeTextContent(data);
|
|
101
|
+
if (!text) {
|
|
102
|
+
throw new Error('No content returned by Claude API.');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return text;
|
|
106
|
+
},
|
|
107
|
+
[options.apiKey, options.maxTokens, options.model, options.system, options.temperature],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const generateCode = useCallback(
|
|
111
|
+
async (input: GenerateCodeInput) => {
|
|
112
|
+
const taskPrompt = input.prompt.trim();
|
|
113
|
+
const selectedLanguage = (input.language || language).trim();
|
|
114
|
+
|
|
115
|
+
if (!taskPrompt) {
|
|
116
|
+
setError('No code generation prompt provided.');
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!options.apiKey) {
|
|
121
|
+
setError('Missing Claude API key.');
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
setIsLoading(true);
|
|
126
|
+
setError(null);
|
|
127
|
+
setLanguage(selectedLanguage);
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const prompt = [
|
|
131
|
+
`Generate ${selectedLanguage} code for the following request:`,
|
|
132
|
+
taskPrompt,
|
|
133
|
+
'Return runnable code and include brief usage notes only when necessary.',
|
|
134
|
+
].join('\n');
|
|
135
|
+
|
|
136
|
+
const result = await sendClaudeRequest(prompt);
|
|
137
|
+
setGeneratedCode(result);
|
|
138
|
+
return result;
|
|
139
|
+
} catch (err) {
|
|
140
|
+
const message = (err as Error).message || 'Failed to generate code';
|
|
141
|
+
setError(message);
|
|
142
|
+
return null;
|
|
143
|
+
} finally {
|
|
144
|
+
if (isMountedRef.current) {
|
|
145
|
+
setIsLoading(false);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
[language, options.apiKey, sendClaudeRequest],
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const explainCode = useCallback(
|
|
153
|
+
async (input: ExplainCodeInput) => {
|
|
154
|
+
const code = input.code.trim();
|
|
155
|
+
const selectedLanguage = (input.language || language).trim();
|
|
156
|
+
|
|
157
|
+
if (!code) {
|
|
158
|
+
setError('No code provided for explanation.');
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!options.apiKey) {
|
|
163
|
+
setError('Missing Claude API key.');
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setIsLoading(true);
|
|
168
|
+
setError(null);
|
|
169
|
+
setLanguage(selectedLanguage);
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const prompt = [
|
|
173
|
+
`Explain the following ${selectedLanguage} code.`,
|
|
174
|
+
input.focus ? `Focus: ${input.focus}` : 'Focus: logic, structure, and potential pitfalls.',
|
|
175
|
+
'Code:',
|
|
176
|
+
code,
|
|
177
|
+
].join('\n');
|
|
178
|
+
|
|
179
|
+
const result = await sendClaudeRequest(prompt);
|
|
180
|
+
setExplanation(result);
|
|
181
|
+
return result;
|
|
182
|
+
} catch (err) {
|
|
183
|
+
const message = (err as Error).message || 'Failed to explain code';
|
|
184
|
+
setError(message);
|
|
185
|
+
return null;
|
|
186
|
+
} finally {
|
|
187
|
+
if (isMountedRef.current) {
|
|
188
|
+
setIsLoading(false);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
[language, options.apiKey, sendClaudeRequest],
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
language,
|
|
197
|
+
generatedCode,
|
|
198
|
+
explanation,
|
|
199
|
+
isLoading,
|
|
200
|
+
error,
|
|
201
|
+
setLanguage,
|
|
202
|
+
generateCode,
|
|
203
|
+
explainCode,
|
|
204
|
+
clearCodeState,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
type SummaryLength = 'short' | 'medium' | 'long';
|
|
4
|
+
|
|
5
|
+
interface UseAISummarizeOptions {
|
|
6
|
+
apiKey: string;
|
|
7
|
+
model?: string;
|
|
8
|
+
system?: string;
|
|
9
|
+
maxTokens?: number;
|
|
10
|
+
temperature?: number;
|
|
11
|
+
defaultLength?: SummaryLength;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface SummarizeInput {
|
|
15
|
+
text: string;
|
|
16
|
+
length?: SummaryLength;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface UseAISummarizeReturn {
|
|
20
|
+
summary: string;
|
|
21
|
+
length: SummaryLength;
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
error: string | null;
|
|
24
|
+
setLength: (length: SummaryLength) => void;
|
|
25
|
+
summarizeText: (input: SummarizeInput) => Promise<string | null>;
|
|
26
|
+
clearSummary: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ClaudeTextBlock {
|
|
30
|
+
type?: string;
|
|
31
|
+
text?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ClaudeApiResult {
|
|
35
|
+
content?: ClaudeTextBlock[];
|
|
36
|
+
error?: {
|
|
37
|
+
message?: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getClaudeTextContent(data: unknown): string {
|
|
42
|
+
const content = (data as ClaudeApiResult)?.content;
|
|
43
|
+
if (!Array.isArray(content)) {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return content
|
|
48
|
+
.filter(item => item?.type === 'text' && typeof item.text === 'string')
|
|
49
|
+
.map(item => item.text as string)
|
|
50
|
+
.join('\n')
|
|
51
|
+
.trim();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function lengthInstruction(length: SummaryLength): string {
|
|
55
|
+
if (length === 'short') {
|
|
56
|
+
return 'Produce a concise summary in 2-4 bullet points.';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (length === 'long') {
|
|
60
|
+
return 'Produce a detailed summary with key points, context, and implications in 3-5 short paragraphs.';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return 'Produce a balanced summary in 1-2 paragraphs plus a short bullet list of key takeaways.';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function useAISummarize(options: UseAISummarizeOptions): UseAISummarizeReturn {
|
|
67
|
+
const [summary, setSummary] = useState('');
|
|
68
|
+
const [length, setLength] = useState<SummaryLength>(options.defaultLength || 'medium');
|
|
69
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
70
|
+
const [error, setError] = useState<string | null>(null);
|
|
71
|
+
|
|
72
|
+
const isMountedRef = useRef(true);
|
|
73
|
+
|
|
74
|
+
const clearSummary = useCallback(() => {
|
|
75
|
+
setSummary('');
|
|
76
|
+
setError(null);
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
const summarizeText = useCallback(
|
|
80
|
+
async (input: SummarizeInput) => {
|
|
81
|
+
const text = input.text.trim();
|
|
82
|
+
const selectedLength = input.length || length;
|
|
83
|
+
|
|
84
|
+
if (!text) {
|
|
85
|
+
setError('No text provided for summarization.');
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!options.apiKey) {
|
|
90
|
+
setError('Missing Claude API key.');
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setIsLoading(true);
|
|
95
|
+
setError(null);
|
|
96
|
+
setLength(selectedLength);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const prompt = [
|
|
100
|
+
'Summarize the following text.',
|
|
101
|
+
lengthInstruction(selectedLength),
|
|
102
|
+
'Text:',
|
|
103
|
+
text,
|
|
104
|
+
].join('\n');
|
|
105
|
+
|
|
106
|
+
const apiResponse = await fetch('https://api.anthropic.com/v1/messages', {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-Type': 'application/json',
|
|
110
|
+
'x-api-key': options.apiKey,
|
|
111
|
+
'anthropic-version': '2023-06-01',
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
model: options.model || 'claude-sonnet-4-20250514',
|
|
115
|
+
max_tokens: options.maxTokens ?? 1200,
|
|
116
|
+
temperature: options.temperature ?? 0.3,
|
|
117
|
+
system:
|
|
118
|
+
options.system ||
|
|
119
|
+
'You are an expert summarization assistant. Keep summaries faithful to source text and avoid fabrications.',
|
|
120
|
+
messages: [{ role: 'user', content: prompt }],
|
|
121
|
+
}),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const data = (await apiResponse.json()) as ClaudeApiResult;
|
|
125
|
+
if (!apiResponse.ok) {
|
|
126
|
+
throw new Error(data?.error?.message || `Claude API error: ${apiResponse.status}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const result = getClaudeTextContent(data);
|
|
130
|
+
if (!result) {
|
|
131
|
+
throw new Error('No summary returned by Claude API.');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setSummary(result);
|
|
135
|
+
return result;
|
|
136
|
+
} catch (err) {
|
|
137
|
+
const message = (err as Error).message || 'Failed to summarize text';
|
|
138
|
+
setError(message);
|
|
139
|
+
return null;
|
|
140
|
+
} finally {
|
|
141
|
+
if (isMountedRef.current) {
|
|
142
|
+
setIsLoading(false);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
[length, options.apiKey, options.maxTokens, options.model, options.system, options.temperature],
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
summary,
|
|
151
|
+
length,
|
|
152
|
+
isLoading,
|
|
153
|
+
error,
|
|
154
|
+
setLength,
|
|
155
|
+
summarizeText,
|
|
156
|
+
clearSummary,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseAITranslateOptions {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
system?: string;
|
|
7
|
+
maxTokens?: number;
|
|
8
|
+
temperature?: number;
|
|
9
|
+
initialTargetLanguage?: string;
|
|
10
|
+
autoTranslate?: boolean;
|
|
11
|
+
debounceMs?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface TranslationResult {
|
|
15
|
+
translatedText: string;
|
|
16
|
+
detectedSourceLanguage: string;
|
|
17
|
+
targetLanguage: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface UseAITranslateReturn {
|
|
21
|
+
sourceText: string;
|
|
22
|
+
translatedText: string;
|
|
23
|
+
detectedSourceLanguage: string;
|
|
24
|
+
targetLanguage: string;
|
|
25
|
+
isTranslating: boolean;
|
|
26
|
+
error: string | null;
|
|
27
|
+
setSourceText: (text: string) => void;
|
|
28
|
+
setTargetLanguage: (language: string) => void;
|
|
29
|
+
translateText: (overrideText?: string) => Promise<TranslationResult | null>;
|
|
30
|
+
clearTranslation: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ClaudeTextBlock {
|
|
34
|
+
type?: string;
|
|
35
|
+
text?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ClaudeApiResult {
|
|
39
|
+
content?: ClaudeTextBlock[];
|
|
40
|
+
error?: {
|
|
41
|
+
message?: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getClaudeTextContent(data: unknown): string {
|
|
46
|
+
const content = (data as ClaudeApiResult)?.content;
|
|
47
|
+
if (!Array.isArray(content)) {
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return content
|
|
52
|
+
.filter(item => item?.type === 'text' && typeof item.text === 'string')
|
|
53
|
+
.map(item => item.text as string)
|
|
54
|
+
.join('\n')
|
|
55
|
+
.trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseJsonFromText<T>(text: string): T {
|
|
59
|
+
const trimmed = text.trim();
|
|
60
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
61
|
+
const candidate = fenced?.[1]?.trim() || trimmed;
|
|
62
|
+
return JSON.parse(candidate) as T;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function useAITranslate(options: UseAITranslateOptions): UseAITranslateReturn {
|
|
66
|
+
const [sourceText, setSourceText] = useState('');
|
|
67
|
+
const [translatedText, setTranslatedText] = useState('');
|
|
68
|
+
const [detectedSourceLanguage, setDetectedSourceLanguage] = useState('');
|
|
69
|
+
const [targetLanguage, setTargetLanguage] = useState(options.initialTargetLanguage || 'English');
|
|
70
|
+
const [isTranslating, setIsTranslating] = useState(false);
|
|
71
|
+
const [error, setError] = useState<string | null>(null);
|
|
72
|
+
|
|
73
|
+
const isMountedRef = useRef(true);
|
|
74
|
+
|
|
75
|
+
const clearTranslation = useCallback(() => {
|
|
76
|
+
setSourceText('');
|
|
77
|
+
setTranslatedText('');
|
|
78
|
+
setDetectedSourceLanguage('');
|
|
79
|
+
setError(null);
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
82
|
+
const translateText = useCallback(
|
|
83
|
+
async (overrideText?: string) => {
|
|
84
|
+
const textToTranslate = (overrideText ?? sourceText).trim();
|
|
85
|
+
|
|
86
|
+
if (!textToTranslate) {
|
|
87
|
+
setError('No text to translate.');
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!options.apiKey) {
|
|
92
|
+
setError('Missing Claude API key.');
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setIsTranslating(true);
|
|
97
|
+
setError(null);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const prompt = [
|
|
101
|
+
'Detect source language and translate text.',
|
|
102
|
+
'Return JSON only using this schema:',
|
|
103
|
+
'{"detectedSourceLanguage":"string","targetLanguage":"string","translatedText":"string"}',
|
|
104
|
+
`Target language: ${targetLanguage}`,
|
|
105
|
+
'Text:',
|
|
106
|
+
textToTranslate,
|
|
107
|
+
].join('\n');
|
|
108
|
+
|
|
109
|
+
const apiResponse = await fetch('https://api.anthropic.com/v1/messages', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
'Content-Type': 'application/json',
|
|
113
|
+
'x-api-key': options.apiKey,
|
|
114
|
+
'anthropic-version': '2023-06-01',
|
|
115
|
+
},
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
model: options.model || 'claude-sonnet-4-20250514',
|
|
118
|
+
max_tokens: options.maxTokens ?? 800,
|
|
119
|
+
temperature: options.temperature ?? 0.2,
|
|
120
|
+
system:
|
|
121
|
+
options.system ||
|
|
122
|
+
'You are a translation assistant. Always detect source language and translate accurately. Return valid JSON only.',
|
|
123
|
+
messages: [{ role: 'user', content: prompt }],
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const data = (await apiResponse.json()) as ClaudeApiResult;
|
|
128
|
+
if (!apiResponse.ok) {
|
|
129
|
+
throw new Error(data?.error?.message || `Claude API error: ${apiResponse.status}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const text = getClaudeTextContent(data);
|
|
133
|
+
if (!text) {
|
|
134
|
+
throw new Error('No translation returned by Claude API.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const parsed = parseJsonFromText<{
|
|
138
|
+
translatedText?: string;
|
|
139
|
+
detectedSourceLanguage?: string;
|
|
140
|
+
targetLanguage?: string;
|
|
141
|
+
}>(text);
|
|
142
|
+
|
|
143
|
+
const result: TranslationResult = {
|
|
144
|
+
translatedText: parsed?.translatedText?.trim() || '',
|
|
145
|
+
detectedSourceLanguage: parsed?.detectedSourceLanguage?.trim() || 'Unknown',
|
|
146
|
+
targetLanguage: parsed?.targetLanguage?.trim() || targetLanguage,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (!result.translatedText) {
|
|
150
|
+
throw new Error('Invalid translation format returned by Claude API.');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
setTranslatedText(result.translatedText);
|
|
154
|
+
setDetectedSourceLanguage(result.detectedSourceLanguage);
|
|
155
|
+
return result;
|
|
156
|
+
} catch (err) {
|
|
157
|
+
const message = (err as Error).message || 'Failed to translate text';
|
|
158
|
+
setError(message);
|
|
159
|
+
return null;
|
|
160
|
+
} finally {
|
|
161
|
+
if (isMountedRef.current) {
|
|
162
|
+
setIsTranslating(false);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
[options.apiKey, options.maxTokens, options.model, options.system, options.temperature, sourceText, targetLanguage],
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
isMountedRef.current = true;
|
|
171
|
+
return () => {
|
|
172
|
+
isMountedRef.current = false;
|
|
173
|
+
};
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (options.autoTranslate === false) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const text = sourceText.trim();
|
|
182
|
+
if (!text) {
|
|
183
|
+
setTranslatedText('');
|
|
184
|
+
setDetectedSourceLanguage('');
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const timer = setTimeout(() => {
|
|
189
|
+
translateText(text).catch(() => undefined);
|
|
190
|
+
}, options.debounceMs ?? 500);
|
|
191
|
+
|
|
192
|
+
return () => clearTimeout(timer);
|
|
193
|
+
}, [options.autoTranslate, options.debounceMs, sourceText, targetLanguage, translateText]);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
sourceText,
|
|
197
|
+
translatedText,
|
|
198
|
+
detectedSourceLanguage,
|
|
199
|
+
targetLanguage,
|
|
200
|
+
isTranslating,
|
|
201
|
+
error,
|
|
202
|
+
setSourceText,
|
|
203
|
+
setTargetLanguage,
|
|
204
|
+
translateText,
|
|
205
|
+
clearTranslation,
|
|
206
|
+
};
|
|
207
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,4 +2,7 @@ export { useAIChat } from './hooks/useAIChat';
|
|
|
2
2
|
export { useAIStream } from './hooks/useAIStream';
|
|
3
3
|
export { useImageAnalysis } from './hooks/useImageAnalysis';
|
|
4
4
|
export { useAIForm } from './hooks/useAIForm';
|
|
5
|
-
export { useAIVoice } from './hooks/useAIVoice';
|
|
5
|
+
export { useAIVoice } from './hooks/useAIVoice';
|
|
6
|
+
export { useAITranslate } from './hooks/useAITranslate';
|
|
7
|
+
export { useAISummarize } from './hooks/useAISummarize';
|
|
8
|
+
export { useAICode } from './hooks/useAICode';
|