@libreapps/react 1.1.1

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.
File without changes
@@ -0,0 +1,105 @@
1
+ "use client"
2
+
3
+ import { useState, useCallback, useRef } from 'react'
4
+ import { useLibreApps } from '../components/LibreAppsProvider'
5
+ import type { Message } from '../components/LibreAppsProvider'
6
+
7
+ export interface UseMessageOptions {
8
+ threadId?: string
9
+ onSuccess?: (message: Message) => void
10
+ onError?: (error: Error) => void
11
+ autoRetry?: boolean
12
+ maxRetries?: number
13
+ retryDelay?: number
14
+ }
15
+
16
+ export interface UseMessageReturn {
17
+ sendMessage: (content: string) => Promise<Message>
18
+ sendMessageWithAttachments: (content: string, attachments: File[]) => Promise<Message>
19
+ isLoading: boolean
20
+ error: Error | null
21
+ lastMessage: Message | null
22
+ clearError: () => void
23
+ retry: () => Promise<void>
24
+ }
25
+
26
+ export function useMessage(options: UseMessageOptions = {}): UseMessageReturn {
27
+ const {
28
+ threadId,
29
+ onSuccess,
30
+ onError,
31
+ autoRetry = false,
32
+ maxRetries = 3,
33
+ retryDelay = 1000
34
+ } = options
35
+
36
+ const { sendMessage: sendToAPI, activeThreadId } = useLibreApps()
37
+ const [isLoading, setIsLoading] = useState(false)
38
+ const [error, setError] = useState<Error | null>(null)
39
+ const [lastMessage, setLastMessage] = useState<Message | null>(null)
40
+ const lastContentRef = useRef<string>('')
41
+ const retryCountRef = useRef(0)
42
+
43
+ const sendMessage = useCallback(async (content: string): Promise<Message> => {
44
+ setIsLoading(true)
45
+ setError(null)
46
+ lastContentRef.current = content
47
+ retryCountRef.current = 0
48
+
49
+ try {
50
+ const targetThread = threadId || activeThreadId
51
+ if (!targetThread) {
52
+ throw new Error('No thread available')
53
+ }
54
+
55
+ const message = await sendToAPI(content, targetThread)
56
+ setLastMessage(message)
57
+ onSuccess?.(message)
58
+ return message
59
+ } catch (err) {
60
+ const error = err instanceof Error ? err : new Error('Failed to send message')
61
+ setError(error)
62
+ onError?.(error)
63
+
64
+ if (autoRetry && retryCountRef.current < maxRetries) {
65
+ retryCountRef.current++
66
+ setTimeout(() => retry(), retryDelay * retryCountRef.current)
67
+ }
68
+
69
+ throw error
70
+ } finally {
71
+ setIsLoading(false)
72
+ }
73
+ }, [sendToAPI, threadId, activeThreadId, onSuccess, onError, autoRetry, maxRetries, retryDelay])
74
+
75
+ const sendMessageWithAttachments = useCallback(async (
76
+ content: string,
77
+ attachments: File[]
78
+ ): Promise<Message> => {
79
+ // TODO: Implement file upload and attachment handling
80
+ // For now, we'll just append file names to the content
81
+ const attachmentInfo = attachments.map(f => `[Attached: ${f.name}]`).join(' ')
82
+ const enrichedContent = `${content}\n${attachmentInfo}`
83
+ return sendMessage(enrichedContent)
84
+ }, [sendMessage])
85
+
86
+ const clearError = useCallback(() => {
87
+ setError(null)
88
+ }, [])
89
+
90
+ const retry = useCallback(async () => {
91
+ if (lastContentRef.current) {
92
+ await sendMessage(lastContentRef.current)
93
+ }
94
+ }, [sendMessage])
95
+
96
+ return {
97
+ sendMessage,
98
+ sendMessageWithAttachments,
99
+ isLoading,
100
+ error,
101
+ lastMessage,
102
+ clearError,
103
+ retry
104
+ }
105
+ }
File without changes
@@ -0,0 +1,161 @@
1
+ "use client"
2
+
3
+ import { useState, useCallback, useRef, useEffect } from 'react'
4
+ import { useLibreApps } from '../components/LibreAppsProvider'
5
+ import type { Message } from '../components/LibreAppsProvider'
6
+
7
+ export interface UseStreamingOptions {
8
+ threadId?: string
9
+ onChunk?: (chunk: string) => void
10
+ onComplete?: (message: Message) => void
11
+ onError?: (error: Error) => void
12
+ bufferSize?: number
13
+ throttleMs?: number
14
+ }
15
+
16
+ export interface UseStreamingReturn {
17
+ streamMessage: (content: string) => Promise<void>
18
+ isStreaming: boolean
19
+ currentMessage: string
20
+ error: Error | null
21
+ stopStreaming: () => void
22
+ clearMessage: () => void
23
+ progress: number // 0-100
24
+ }
25
+
26
+ export function useStreaming(options: UseStreamingOptions = {}): UseStreamingReturn {
27
+ const {
28
+ threadId,
29
+ onChunk,
30
+ onComplete,
31
+ onError,
32
+ bufferSize = 1,
33
+ throttleMs = 0
34
+ } = options
35
+
36
+ const { streamMessage: streamFromAPI, activeThreadId, isStreaming: globalStreaming } = useLibreApps()
37
+ const [isStreaming, setIsStreaming] = useState(false)
38
+ const [currentMessage, setCurrentMessage] = useState('')
39
+ const [error, setError] = useState<Error | null>(null)
40
+ const [progress, setProgress] = useState(0)
41
+ const abortControllerRef = useRef<AbortController | null>(null)
42
+ const bufferRef = useRef<string[]>([])
43
+ const lastUpdateRef = useRef<number>(Date.now())
44
+
45
+ const streamMessage = useCallback(async (content: string): Promise<void> => {
46
+ setIsStreaming(true)
47
+ setError(null)
48
+ setCurrentMessage('')
49
+ setProgress(0)
50
+ bufferRef.current = []
51
+
52
+ // Create abort controller
53
+ abortControllerRef.current = new AbortController()
54
+
55
+ try {
56
+ const targetThread = threadId || activeThreadId
57
+ if (!targetThread) {
58
+ throw new Error('No thread available')
59
+ }
60
+
61
+ const stream = streamFromAPI(content, targetThread)
62
+ let totalChunks = 0
63
+ let estimatedTotal = 100 // Start with estimate, will adjust
64
+
65
+ for await (const message of stream) {
66
+ if (abortControllerRef.current?.signal.aborted) {
67
+ break
68
+ }
69
+
70
+ totalChunks++
71
+
72
+ // Extract string content from message
73
+ const chunk = typeof message.content === 'string' ? message.content : ''
74
+
75
+ // Buffer management
76
+ bufferRef.current.push(chunk)
77
+
78
+ // Throttling
79
+ const now = Date.now()
80
+ const shouldUpdate = (
81
+ bufferRef.current.length >= bufferSize ||
82
+ (throttleMs > 0 && now - lastUpdateRef.current >= throttleMs)
83
+ )
84
+
85
+ if (shouldUpdate) {
86
+ const bufferedContent = bufferRef.current.join('')
87
+ setCurrentMessage(prev => prev + bufferedContent)
88
+ onChunk?.(bufferedContent)
89
+ bufferRef.current = []
90
+ lastUpdateRef.current = now
91
+ }
92
+
93
+ // Update progress (estimate based on typical response length)
94
+ const estimatedProgress = Math.min(95, (totalChunks / estimatedTotal) * 100)
95
+ setProgress(estimatedProgress)
96
+
97
+ // Adjust estimate based on response speed
98
+ if (totalChunks > 10 && totalChunks % 10 === 0) {
99
+ estimatedTotal = Math.max(estimatedTotal, totalChunks * 1.5)
100
+ }
101
+ }
102
+
103
+ // Flush remaining buffer
104
+ if (bufferRef.current.length > 0) {
105
+ const remainingContent = bufferRef.current.join('')
106
+ setCurrentMessage(prev => prev + remainingContent)
107
+ onChunk?.(remainingContent)
108
+ bufferRef.current = []
109
+ }
110
+
111
+ setProgress(100)
112
+
113
+ // Create complete message
114
+ const completeMessage: Message = {
115
+ id: `stream-${Date.now()}`,
116
+ role: 'assistant',
117
+ content: currentMessage,
118
+ timestamp: new Date(),
119
+ threadId: targetThread
120
+ }
121
+
122
+ onComplete?.(completeMessage)
123
+ } catch (err) {
124
+ const error = err instanceof Error ? err : new Error('Streaming failed')
125
+ setError(error)
126
+ onError?.(error)
127
+ } finally {
128
+ setIsStreaming(false)
129
+ abortControllerRef.current = null
130
+ }
131
+ }, [streamFromAPI, threadId, activeThreadId, onChunk, onComplete, onError, bufferSize, throttleMs, currentMessage])
132
+
133
+ const stopStreaming = useCallback(() => {
134
+ if (abortControllerRef.current) {
135
+ abortControllerRef.current.abort()
136
+ setIsStreaming(false)
137
+ }
138
+ }, [])
139
+
140
+ const clearMessage = useCallback(() => {
141
+ setCurrentMessage('')
142
+ setProgress(0)
143
+ }, [])
144
+
145
+ // Sync with global streaming state
146
+ useEffect(() => {
147
+ if (!globalStreaming && isStreaming) {
148
+ setIsStreaming(false)
149
+ }
150
+ }, [globalStreaming, isStreaming])
151
+
152
+ return {
153
+ streamMessage,
154
+ isStreaming,
155
+ currentMessage,
156
+ error,
157
+ stopStreaming,
158
+ clearMessage,
159
+ progress
160
+ }
161
+ }
File without changes
File without changes
File without changes
package/src/index.ts ADDED
@@ -0,0 +1,40 @@
1
+ // @libreapps/react - React package for building AI-powered applications
2
+ // with generative UI and natural language interactions
3
+
4
+ // Core Provider and Context
5
+ export {
6
+ LibreAppsProvider,
7
+ useLibreApps,
8
+ type LibreAppsProviderProps,
9
+ type LibreAppsContextValue,
10
+ type LibreAppsComponent,
11
+ type LibreAppsTool,
12
+ type Message,
13
+ type Thread,
14
+ type ToolCall
15
+ } from './components/LibreAppsProvider'
16
+
17
+ // Hooks
18
+ export { useMessage, type UseMessageOptions, type UseMessageReturn } from './hooks/useMessage'
19
+ export { useStreaming, type UseStreamingOptions, type UseStreamingReturn } from './hooks/useStreaming'
20
+ // TODO: Implement the following hooks
21
+ // export { useThread } from './hooks/useThread'
22
+ // export { useComponent } from './hooks/useComponent'
23
+ // export { useTool } from './hooks/useTool'
24
+ // export { useSuggestions } from './hooks/useSuggestions'
25
+ // export { useModelConfig } from './hooks/useModelConfig'
26
+ // export { useMCP } from './hooks/useMCP'
27
+ // export { useGenerativeUI } from './hooks/useGenerativeUI'
28
+ // export { useAuth } from './hooks/useAuth'
29
+ // export { useAttachments } from './hooks/useAttachments'
30
+
31
+ // Utilities
32
+ export { cn } from './utils/cn'
33
+ export { generateId } from './utils/id'
34
+ export { parseStream } from './utils/stream'
35
+
36
+ // Types
37
+ export * from './types'
38
+
39
+ // Version
40
+ export const VERSION = '1.0.0'
@@ -0,0 +1,25 @@
1
+ // Re-export all types from hooks
2
+ export * from '../hooks/types'
3
+
4
+ // Additional common types
5
+ export interface Suggestion {
6
+ id: string
7
+ text: string
8
+ metadata?: Record<string, any>
9
+ }
10
+
11
+ export interface Model {
12
+ id: string
13
+ name: string
14
+ description: string
15
+ parameters?: Record<string, any>
16
+ }
17
+
18
+ export interface Attachment {
19
+ id: string
20
+ name: string
21
+ size: number
22
+ type: string
23
+ url?: string
24
+ file?: File
25
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,6 @@
1
+ import { nanoid } from 'nanoid'
2
+
3
+ export function generateId(prefix?: string): string {
4
+ const id = nanoid()
5
+ return prefix ? `${prefix}-${id}` : id
6
+ }
@@ -0,0 +1,33 @@
1
+ export async function parseStream(stream: ReadableStream<Uint8Array>) {
2
+ const reader = stream.getReader()
3
+ const decoder = new TextDecoder()
4
+ const chunks: string[] = []
5
+
6
+ while (true) {
7
+ const { done, value } = await reader.read()
8
+ if (done) break
9
+
10
+ const chunk = decoder.decode(value)
11
+ chunks.push(chunk)
12
+ }
13
+
14
+ return chunks.join('')
15
+ }
16
+
17
+ export function* streamToGenerator<T>(stream: ReadableStream<T>) {
18
+ const reader = stream.getReader()
19
+
20
+ async function* generator() {
21
+ try {
22
+ while (true) {
23
+ const { done, value } = await reader.read()
24
+ if (done) break
25
+ yield value
26
+ }
27
+ } finally {
28
+ reader.releaseLock()
29
+ }
30
+ }
31
+
32
+ return generator()
33
+ }