UNPKG

@flavoai/fastfold

Version:

Flavo frontend package

445 lines 14.5 kB
/** * FastFold AI Client Hooks * React hooks for AI functionality with streaming support */ import { useState, useCallback, useRef, useEffect } from 'react'; // ============================================================================ // HTTP HELPERS // ============================================================================ let baseUrl = '/api'; export function configureAIClient(config) { if (config.baseUrl) { baseUrl = config.baseUrl; } } async function postJSON(url, data) { const response = await fetch(`${baseUrl}${url}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`AI request failed: ${errorText}`); } const result = await response.json(); if (!result.success) { throw new Error(result.error || 'AI request failed'); } return result.data; } // ============================================================================ // AI CHAT HOOK // ============================================================================ /** * 💬 useAIChat - Chat with AI with streaming support * * @example * const { messages, input, handleSubmit, isLoading, setInput } = useAIChat({ * api: '/api/ai/chat', * system: 'You are a helpful assistant', * onFinish: (response) => console.log('Done:', response), * }); * * // In your component: * <form onSubmit={handleSubmit}> * <input value={input} onChange={(e) => setInput(e.target.value)} /> * <button type="submit" disabled={isLoading}>Send</button> * </form> * {messages.map((m, i) => <div key={i}>{m.role}: {m.content}</div>)} */ export function useAIChat(options = {}) { const { api = '/ai/chat', model, provider, system, temperature, maxTokens, initialMessages = [], onChunk, onFinish, onError, } = options; const [messages, setMessages] = useState(initialMessages); const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const abortControllerRef = useRef(null); const append = useCallback(async (message) => { const newMessages = [...messages, message]; setMessages(newMessages); if (message.role === 'user') { setIsLoading(true); setError(null); try { const response = await postJSON(api, { messages: system ? [{ role: 'system', content: system }, ...newMessages] : newMessages, model, provider, temperature, maxTokens, }); const assistantMessage = { role: 'assistant', content: response.text, }; setMessages(prev => [...prev, assistantMessage]); onFinish?.(response); return response; } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); setError(error); onError?.(error); throw error; } finally { setIsLoading(false); } } return null; }, [messages, api, model, provider, system, temperature, maxTokens, onFinish, onError]); const handleSubmit = useCallback(async (e) => { e?.preventDefault(); if (!input.trim() || isLoading) return; const userMessage = { role: 'user', content: input.trim() }; setInput(''); await append(userMessage); }, [input, isLoading, append]); const stop = useCallback(() => { abortControllerRef.current?.abort(); setIsLoading(false); }, []); const reload = useCallback(async () => { if (messages.length === 0) return; // Find last user message const lastUserIndex = messages.findLastIndex(m => m.role === 'user'); if (lastUserIndex === -1) return; // Remove messages from last user message onwards const newMessages = messages.slice(0, lastUserIndex); setMessages(newMessages); // Re-send the last user message const lastUserMessage = messages[lastUserIndex]; await append(lastUserMessage); }, [messages, append]); const clear = useCallback(() => { setMessages(initialMessages); setInput(''); setError(null); }, [initialMessages]); return { messages, input, setInput, handleSubmit, append, reload, stop, clear, isLoading, error, }; } // ============================================================================ // AI STREAM HOOK // ============================================================================ /** * 🌊 useAIStream - Stream text generation with SSE * * @example * const { text, isStreaming, generate, stop } = useAIStream({ * api: '/api/ai/stream', * onChunk: (chunk) => console.log('Chunk:', chunk), * onFinish: (text) => console.log('Final:', text), * }); * * // Start streaming * await generate({ prompt: 'Tell me a story' }); */ export function useAIStream(options = {}) { const { api = '/ai/stream', model, provider, system, temperature, maxTokens, onChunk, onFinish, onError, } = options; const [text, setText] = useState(''); const [isStreaming, setIsStreaming] = useState(false); const [error, setError] = useState(null); const abortControllerRef = useRef(null); const generate = useCallback(async (params) => { setIsStreaming(true); setError(null); setText(''); abortControllerRef.current = new AbortController(); try { const response = await fetch(`${baseUrl}${api}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...params, model, provider, system, temperature, maxTokens, }), signal: abortControllerRef.current.signal, }); if (!response.ok) { throw new Error(`Stream request failed: ${response.status}`); } const reader = response.body?.getReader(); if (!reader) { throw new Error('No response body'); } const decoder = new TextDecoder(); let fullText = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { try { const data = JSON.parse(line.slice(6)); if (data.text) { fullText += data.text; setText(fullText); onChunk?.(data.text); } if (data.done) { onFinish?.(fullText); } if (data.error) { throw new Error(data.error); } } catch (e) { // Skip invalid JSON lines } } } } return fullText; } catch (err) { if (err.name === 'AbortError') { return text; } const error = err instanceof Error ? err : new Error(String(err)); setError(error); onError?.(error); throw error; } finally { setIsStreaming(false); } }, [api, model, provider, system, temperature, maxTokens, onChunk, onFinish, onError, text]); const stop = useCallback(() => { abortControllerRef.current?.abort(); setIsStreaming(false); }, []); const clear = useCallback(() => { setText(''); setError(null); }, []); // Cleanup on unmount useEffect(() => { return () => { abortControllerRef.current?.abort(); }; }, []); return { text, isStreaming, error, generate, stop, clear, }; } // ============================================================================ // AI OBJECT HOOK // ============================================================================ /** * 🎯 useAIObject - Generate structured objects with AI * * @example * const { object, generate, isLoading, error } = useAIObject({ * schema: { * type: 'object', * properties: { * name: { type: 'string' }, * age: { type: 'number' }, * }, * required: ['name', 'age'], * }, * onFinish: (obj) => console.log('Generated:', obj), * }); * * // Generate object * await generate({ prompt: 'Generate a random person' }); */ export function useAIObject(options) { const { api = '/ai/generate', schema, model, provider, temperature, maxTokens, system, onFinish, onError, } = options; const [object, setObject] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const generate = useCallback(async (params) => { setIsLoading(true); setError(null); try { const response = await postJSON(api, { ...params, schema, model, provider, temperature, maxTokens, system, }); setObject(response.object); onFinish?.(response.object); return response.object; } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); setError(error); onError?.(error); throw error; } finally { setIsLoading(false); } }, [api, schema, model, provider, temperature, maxTokens, system, onFinish, onError]); const clear = useCallback(() => { setObject(null); setError(null); }, []); return { object, isLoading, error, generate, clear, }; } // ============================================================================ // AI COMPLETION HOOK // ============================================================================ /** * ✍️ useAICompletion - Simple text completion * * @example * const { text, complete, isLoading } = useAICompletion({ * model: 'gpt-4o', * }); * * const result = await complete('Translate to French: Hello world'); */ export function useAICompletion(options = {}) { const { api = '/ai/complete', model, provider, temperature, maxTokens, onFinish, onError, } = options; const [text, setText] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const complete = useCallback(async (prompt) => { setIsLoading(true); setError(null); try { const response = await postJSON(api, { prompt, model, provider, temperature, maxTokens, }); setText(response.text); onFinish?.(response.text); return response.text; } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); setError(error); onError?.(error); throw error; } finally { setIsLoading(false); } }, [api, model, provider, temperature, maxTokens, onFinish, onError]); const clear = useCallback(() => { setText(''); setError(null); }, []); return { text, isLoading, error, complete, clear, }; } // ============================================================================ // AI EMBED HOOK // ============================================================================ /** * 🔢 useAIEmbed - Generate embeddings * * @example * const { embed, embedMany, isLoading } = useAIEmbed(); * * const embedding = await embed('Hello world'); * const embeddings = await embedMany(['Hello', 'World']); */ export function useAIEmbed(options = {}) { const { api = '/ai/embed', model, provider, } = options; const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const embed = useCallback(async (text) => { setIsLoading(true); setError(null); try { const response = await postJSON(api, { text, model, provider, }); return response.embedding; } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); setError(error); throw error; } finally { setIsLoading(false); } }, [api, model, provider]); const embedMany = useCallback(async (texts) => { setIsLoading(true); setError(null); try { const response = await postJSON(api, { texts, model, provider, }); return response.embeddings; } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); setError(error); throw error; } finally { setIsLoading(false); } }, [api, model, provider]); return { isLoading, error, embed, embedMany, }; } export default { useAIChat, useAIStream, useAIObject, useAICompletion, useAIEmbed, }; //# sourceMappingURL=ai.js.map