@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.
@@ -0,0 +1,508 @@
1
+ "use client"
2
+
3
+ import React, { createContext, useContext, useCallback, useEffect, useMemo, useState } from 'react'
4
+ import { nanoid } from 'nanoid'
5
+ import { z } from 'zod'
6
+
7
+ // Types
8
+ export interface LibreAppsComponent {
9
+ name: string
10
+ component: React.ComponentType<any>
11
+ description?: string
12
+ parameters?: z.ZodType<any>
13
+ generateUI?: (params: any) => React.ReactElement
14
+ }
15
+
16
+ export interface LibreAppsTool {
17
+ name: string
18
+ description: string
19
+ parameters: z.ZodType<any>
20
+ execute: (params: any) => Promise<any>
21
+ }
22
+
23
+ export interface Message {
24
+ id: string
25
+ role: 'user' | 'assistant' | 'system' | 'tool'
26
+ content: string | React.ReactElement
27
+ timestamp: Date
28
+ threadId: string
29
+ toolCalls?: ToolCall[]
30
+ metadata?: Record<string, any>
31
+ }
32
+
33
+ export interface ToolCall {
34
+ id: string
35
+ name: string
36
+ arguments: any
37
+ result?: any
38
+ status: 'pending' | 'running' | 'completed' | 'failed'
39
+ }
40
+
41
+ export interface Thread {
42
+ id: string
43
+ messages: Message[]
44
+ createdAt: Date
45
+ updatedAt: Date
46
+ metadata?: Record<string, any>
47
+ }
48
+
49
+ export interface LibreAppsContextValue {
50
+ // Configuration
51
+ apiKey?: string
52
+ apiUrl?: string
53
+ model?: string
54
+
55
+ // Components & Tools
56
+ components: Map<string, LibreAppsComponent>
57
+ tools: Map<string, LibreAppsTool>
58
+
59
+ // Thread Management
60
+ threads: Map<string, Thread>
61
+ activeThreadId?: string
62
+
63
+ // Message Operations
64
+ sendMessage: (content: string, threadId?: string) => Promise<Message>
65
+ streamMessage: (content: string, threadId?: string) => AsyncGenerator<Message>
66
+
67
+ // Thread Operations
68
+ createThread: (metadata?: Record<string, any>) => Thread
69
+ switchThread: (threadId: string) => void
70
+ deleteThread: (threadId: string) => void
71
+
72
+ // Component Operations
73
+ registerComponent: (component: LibreAppsComponent) => void
74
+ unregisterComponent: (name: string) => void
75
+ renderComponent: (name: string, props: any) => React.ReactElement | null
76
+
77
+ // Tool Operations
78
+ registerTool: (tool: LibreAppsTool) => void
79
+ unregisterTool: (name: string) => void
80
+ executeTool: (name: string, params: any) => Promise<any>
81
+
82
+ // State Management
83
+ isStreaming: boolean
84
+ responseStage?: 'thinking' | 'generating' | 'tool-calling' | 'completed'
85
+ error?: Error
86
+ }
87
+
88
+ const LibreAppsContext = createContext<LibreAppsContextValue | undefined>(undefined)
89
+
90
+ export interface LibreAppsProviderProps {
91
+ children: React.ReactNode
92
+ apiKey?: string
93
+ apiUrl?: string
94
+ model?: string
95
+ components?: LibreAppsComponent[]
96
+ tools?: LibreAppsTool[]
97
+ initialMessages?: Message[]
98
+ onMessage?: (message: Message) => void
99
+ onError?: (error: Error) => void
100
+ enableStreaming?: boolean
101
+ enableMCP?: boolean
102
+ mcpServers?: string[]
103
+ }
104
+
105
+ export function LibreAppsProvider({
106
+ children,
107
+ apiKey,
108
+ apiUrl = 'https://api.libreapps.com/v1',
109
+ model = 'gpt-4-turbo-preview',
110
+ components: initialComponents = [],
111
+ tools: initialTools = [],
112
+ initialMessages = [],
113
+ onMessage,
114
+ onError,
115
+ enableStreaming = true,
116
+ // @ts-expect-error - MCP integration coming soon
117
+ enableMCP = false, // eslint-disable-line @typescript-eslint/no-unused-vars
118
+ // @ts-expect-error - MCP server support coming soon
119
+ mcpServers = [] // eslint-disable-line @typescript-eslint/no-unused-vars
120
+ }: LibreAppsProviderProps) {
121
+ // State
122
+ const [components] = useState(() => new Map(initialComponents.map(c => [c.name, c])))
123
+ const [tools] = useState(() => new Map(initialTools.map(t => [t.name, t])))
124
+ const [threads, setThreads] = useState<Map<string, Thread>>(new Map())
125
+ const [activeThreadId, setActiveThreadId] = useState<string>()
126
+ const [isStreaming, setIsStreaming] = useState(false)
127
+ const [responseStage, setResponseStage] = useState<LibreAppsContextValue['responseStage']>()
128
+ const [error, setError] = useState<Error>()
129
+
130
+ // Initialize default thread
131
+ useEffect(() => {
132
+ const defaultThread = createThread({ name: 'default' })
133
+ if (initialMessages.length > 0) {
134
+ defaultThread.messages = initialMessages
135
+ }
136
+ setActiveThreadId(defaultThread.id)
137
+ }, [])
138
+
139
+ // Thread Operations
140
+ const createThread = useCallback((metadata?: Record<string, any>): Thread => {
141
+ const thread: Thread = {
142
+ id: nanoid(),
143
+ messages: [],
144
+ createdAt: new Date(),
145
+ updatedAt: new Date(),
146
+ metadata
147
+ }
148
+ setThreads(prev => new Map(prev).set(thread.id, thread))
149
+ return thread
150
+ }, [])
151
+
152
+ const switchThread = useCallback((threadId: string) => {
153
+ if (threads.has(threadId)) {
154
+ setActiveThreadId(threadId)
155
+ } else {
156
+ throw new Error(`Thread ${threadId} not found`)
157
+ }
158
+ }, [threads])
159
+
160
+ const deleteThread = useCallback((threadId: string) => {
161
+ setThreads(prev => {
162
+ const next = new Map(prev)
163
+ next.delete(threadId)
164
+ return next
165
+ })
166
+ if (activeThreadId === threadId) {
167
+ const remainingThreads = Array.from(threads.keys()).filter(id => id !== threadId)
168
+ setActiveThreadId(remainingThreads[0])
169
+ }
170
+ }, [activeThreadId, threads])
171
+
172
+ // Message Operations
173
+ const sendMessage = useCallback(async (content: string, threadId?: string): Promise<Message> => {
174
+ const targetThreadId = threadId || activeThreadId
175
+ if (!targetThreadId) {
176
+ throw new Error('No active thread')
177
+ }
178
+
179
+ const thread = threads.get(targetThreadId)
180
+ if (!thread) {
181
+ throw new Error(`Thread ${targetThreadId} not found`)
182
+ }
183
+
184
+ // Create user message
185
+ const userMessage: Message = {
186
+ id: nanoid(),
187
+ role: 'user',
188
+ content,
189
+ timestamp: new Date(),
190
+ threadId: targetThreadId
191
+ }
192
+
193
+ // Add to thread
194
+ thread.messages.push(userMessage)
195
+ thread.updatedAt = new Date()
196
+ setThreads(new Map(threads))
197
+
198
+ // Notify callback
199
+ onMessage?.(userMessage)
200
+
201
+ // Send to API
202
+ setIsStreaming(true)
203
+ setResponseStage('thinking')
204
+
205
+ try {
206
+ const response = await fetch(`${apiUrl}/chat/completions`, {
207
+ method: 'POST',
208
+ headers: {
209
+ 'Content-Type': 'application/json',
210
+ 'Authorization': `Bearer ${apiKey}`
211
+ },
212
+ body: JSON.stringify({
213
+ model,
214
+ messages: thread.messages.map(m => ({
215
+ role: m.role,
216
+ content: typeof m.content === 'string' ? m.content : 'Component rendered'
217
+ })),
218
+ tools: Array.from(tools.values()).map(t => ({
219
+ type: 'function',
220
+ function: {
221
+ name: t.name,
222
+ description: t.description,
223
+ parameters: t.parameters
224
+ }
225
+ })),
226
+ stream: enableStreaming
227
+ })
228
+ })
229
+
230
+ if (!response.ok) {
231
+ throw new Error(`API error: ${response.statusText}`)
232
+ }
233
+
234
+ setResponseStage('generating')
235
+
236
+ // Handle response
237
+ const data = await response.json()
238
+ const assistantMessage: Message = {
239
+ id: nanoid(),
240
+ role: 'assistant',
241
+ content: data.choices[0].message.content,
242
+ timestamp: new Date(),
243
+ threadId: targetThreadId
244
+ }
245
+
246
+ // Handle tool calls
247
+ if (data.choices[0].message.tool_calls) {
248
+ setResponseStage('tool-calling')
249
+ assistantMessage.toolCalls = await Promise.all(
250
+ data.choices[0].message.tool_calls.map(async (call: any) => {
251
+ const tool = tools.get(call.function.name)
252
+ if (!tool) {
253
+ return {
254
+ id: call.id,
255
+ name: call.function.name,
256
+ arguments: call.function.arguments,
257
+ status: 'failed',
258
+ result: `Tool ${call.function.name} not found`
259
+ }
260
+ }
261
+
262
+ try {
263
+ const result = await tool.execute(JSON.parse(call.function.arguments))
264
+ return {
265
+ id: call.id,
266
+ name: call.function.name,
267
+ arguments: call.function.arguments,
268
+ status: 'completed',
269
+ result
270
+ }
271
+ } catch (error) {
272
+ return {
273
+ id: call.id,
274
+ name: call.function.name,
275
+ arguments: call.function.arguments,
276
+ status: 'failed',
277
+ result: error instanceof Error ? error.message : 'Unknown error'
278
+ }
279
+ }
280
+ })
281
+ )
282
+ }
283
+
284
+ // Add assistant message
285
+ thread.messages.push(assistantMessage)
286
+ thread.updatedAt = new Date()
287
+ setThreads(new Map(threads))
288
+
289
+ // Notify callback
290
+ onMessage?.(assistantMessage)
291
+
292
+ setResponseStage('completed')
293
+ return assistantMessage
294
+ } catch (err) {
295
+ const error = err instanceof Error ? err : new Error('Unknown error')
296
+ setError(error)
297
+ onError?.(error)
298
+ throw error
299
+ } finally {
300
+ setIsStreaming(false)
301
+ setResponseStage(undefined)
302
+ }
303
+ }, [apiKey, apiUrl, model, activeThreadId, threads, tools, onMessage, onError, enableStreaming])
304
+
305
+ // Streaming Message Operation
306
+ const streamMessage = useCallback(async function* (
307
+ content: string,
308
+ threadId?: string
309
+ ): AsyncGenerator<Message> {
310
+ const targetThreadId = threadId || activeThreadId
311
+ if (!targetThreadId) {
312
+ throw new Error('No active thread')
313
+ }
314
+
315
+ const thread = threads.get(targetThreadId)
316
+ if (!thread) {
317
+ throw new Error(`Thread ${targetThreadId} not found`)
318
+ }
319
+
320
+ // Create user message
321
+ const userMessage: Message = {
322
+ id: nanoid(),
323
+ role: 'user',
324
+ content,
325
+ timestamp: new Date(),
326
+ threadId: targetThreadId
327
+ }
328
+
329
+ thread.messages.push(userMessage)
330
+ yield userMessage
331
+
332
+ // Stream from API
333
+ setIsStreaming(true)
334
+ setResponseStage('thinking')
335
+
336
+ try {
337
+ const response = await fetch(`${apiUrl}/chat/completions`, {
338
+ method: 'POST',
339
+ headers: {
340
+ 'Content-Type': 'application/json',
341
+ 'Authorization': `Bearer ${apiKey}`
342
+ },
343
+ body: JSON.stringify({
344
+ model,
345
+ messages: thread.messages.map(m => ({
346
+ role: m.role,
347
+ content: typeof m.content === 'string' ? m.content : 'Component rendered'
348
+ })),
349
+ stream: true
350
+ })
351
+ })
352
+
353
+ if (!response.ok) {
354
+ throw new Error(`API error: ${response.statusText}`)
355
+ }
356
+
357
+ setResponseStage('generating')
358
+
359
+ const reader = response.body?.getReader()
360
+ if (!reader) {
361
+ throw new Error('No response body')
362
+ }
363
+
364
+ const decoder = new TextDecoder()
365
+ let assistantMessage: Message = {
366
+ id: nanoid(),
367
+ role: 'assistant',
368
+ content: '',
369
+ timestamp: new Date(),
370
+ threadId: targetThreadId
371
+ }
372
+
373
+ while (true) {
374
+ const { done, value } = await reader.read()
375
+ if (done) break
376
+
377
+ const chunk = decoder.decode(value)
378
+ const lines = chunk.split('\n').filter(line => line.trim() !== '')
379
+
380
+ for (const line of lines) {
381
+ if (line.startsWith('data: ')) {
382
+ const data = line.slice(6)
383
+ if (data === '[DONE]') continue
384
+
385
+ try {
386
+ const parsed = JSON.parse(data)
387
+ if (parsed.choices?.[0]?.delta?.content) {
388
+ assistantMessage.content += parsed.choices[0].delta.content
389
+ yield { ...assistantMessage }
390
+ }
391
+ } catch (e) {
392
+ console.error('Error parsing SSE data:', e)
393
+ }
394
+ }
395
+ }
396
+ }
397
+
398
+ thread.messages.push(assistantMessage)
399
+ thread.updatedAt = new Date()
400
+ setThreads(new Map(threads))
401
+
402
+ setResponseStage('completed')
403
+ } finally {
404
+ setIsStreaming(false)
405
+ setResponseStage(undefined)
406
+ }
407
+ }, [apiKey, apiUrl, model, activeThreadId, threads])
408
+
409
+ // Component Operations
410
+ const registerComponent = useCallback((component: LibreAppsComponent) => {
411
+ components.set(component.name, component)
412
+ }, [components])
413
+
414
+ const unregisterComponent = useCallback((name: string) => {
415
+ components.delete(name)
416
+ }, [components])
417
+
418
+ const renderComponent = useCallback((name: string, props: any): React.ReactElement | null => {
419
+ const component = components.get(name)
420
+ if (!component) return null
421
+
422
+ if (component.generateUI) {
423
+ return component.generateUI(props)
424
+ }
425
+
426
+ const Component = component.component
427
+ return <Component {...props} />
428
+ }, [components])
429
+
430
+ // Tool Operations
431
+ const registerTool = useCallback((tool: LibreAppsTool) => {
432
+ tools.set(tool.name, tool)
433
+ }, [tools])
434
+
435
+ const unregisterTool = useCallback((name: string) => {
436
+ tools.delete(name)
437
+ }, [tools])
438
+
439
+ const executeTool = useCallback(async (name: string, params: any): Promise<any> => {
440
+ const tool = tools.get(name)
441
+ if (!tool) {
442
+ throw new Error(`Tool ${name} not found`)
443
+ }
444
+ return tool.execute(params)
445
+ }, [tools])
446
+
447
+ // Context value
448
+ const contextValue = useMemo<LibreAppsContextValue>(() => ({
449
+ apiKey,
450
+ apiUrl,
451
+ model,
452
+ components,
453
+ tools,
454
+ threads,
455
+ activeThreadId,
456
+ sendMessage,
457
+ streamMessage,
458
+ createThread,
459
+ switchThread,
460
+ deleteThread,
461
+ registerComponent,
462
+ unregisterComponent,
463
+ renderComponent,
464
+ registerTool,
465
+ unregisterTool,
466
+ executeTool,
467
+ isStreaming,
468
+ responseStage,
469
+ error
470
+ }), [
471
+ apiKey,
472
+ apiUrl,
473
+ model,
474
+ components,
475
+ tools,
476
+ threads,
477
+ activeThreadId,
478
+ sendMessage,
479
+ streamMessage,
480
+ createThread,
481
+ switchThread,
482
+ deleteThread,
483
+ registerComponent,
484
+ unregisterComponent,
485
+ renderComponent,
486
+ registerTool,
487
+ unregisterTool,
488
+ executeTool,
489
+ isStreaming,
490
+ responseStage,
491
+ error
492
+ ])
493
+
494
+ return (
495
+ <LibreAppsContext.Provider value={contextValue}>
496
+ {children}
497
+ </LibreAppsContext.Provider>
498
+ )
499
+ }
500
+
501
+ // Hook to use LibreApps context
502
+ export function useLibreApps() {
503
+ const context = useContext(LibreAppsContext)
504
+ if (!context) {
505
+ throw new Error('useLibreApps must be used within LibreAppsProvider')
506
+ }
507
+ return context
508
+ }
@@ -0,0 +1,39 @@
1
+ // Core LibreApps hooks for AI interactions
2
+ export { useLibreApps } from '../components/LibreAppsProvider'
3
+ export { useMessage } from './useMessage'
4
+ // export { useThread } from './useThread'
5
+ // export { useComponent } from './useComponent'
6
+ // export { useTool } from './useTool'
7
+ export { useStreaming } from './useStreaming'
8
+ // export { useSuggestions } from './useSuggestions'
9
+ // export { useModelConfig } from './useModelConfig'
10
+ // export { useMCP } from './useMCP'
11
+ // export { useGenerativeUI } from './useGenerativeUI'
12
+ // export { useAuth } from './useAuth'
13
+ // export { useAttachments } from './useAttachments'
14
+
15
+ // Type exports
16
+ export type {
17
+ UseMessageOptions,
18
+ UseMessageReturn,
19
+ // UseThreadOptions,
20
+ // UseThreadReturn,
21
+ // UseComponentOptions,
22
+ // UseComponentReturn,
23
+ // UseToolOptions,
24
+ // UseToolReturn,
25
+ UseStreamingOptions,
26
+ UseStreamingReturn,
27
+ // UseSuggestionsOptions,
28
+ // UseSuggestionsReturn,
29
+ // UseModelConfigOptions,
30
+ // UseModelConfigReturn,
31
+ // UseMCPOptions,
32
+ // UseMCPReturn,
33
+ // UseGenerativeUIOptions,
34
+ // UseGenerativeUIReturn,
35
+ // UseAuthOptions,
36
+ // UseAuthReturn,
37
+ // UseAttachmentsOptions,
38
+ // UseAttachmentsReturn
39
+ } from './types'
@@ -0,0 +1,162 @@
1
+ // Hook type definitions
2
+
3
+ export interface UseMessageOptions {
4
+ threadId?: string
5
+ onSuccess?: (message: any) => void
6
+ onError?: (error: Error) => void
7
+ autoRetry?: boolean
8
+ maxRetries?: number
9
+ retryDelay?: number
10
+ }
11
+
12
+ export interface UseMessageReturn {
13
+ sendMessage: (content: string) => Promise<any>
14
+ sendMessageWithAttachments: (content: string, attachments: File[]) => Promise<any>
15
+ isLoading: boolean
16
+ error: Error | null
17
+ lastMessage: any | null
18
+ clearError: () => void
19
+ retry: () => Promise<void>
20
+ }
21
+
22
+ export interface UseStreamingOptions {
23
+ threadId?: string
24
+ onChunk?: (chunk: string) => void
25
+ onComplete?: (message: any) => void
26
+ onError?: (error: Error) => void
27
+ bufferSize?: number
28
+ throttleMs?: number
29
+ }
30
+
31
+ export interface UseStreamingReturn {
32
+ streamMessage: (content: string) => Promise<void>
33
+ isStreaming: boolean
34
+ currentMessage: string
35
+ error: Error | null
36
+ stopStreaming: () => void
37
+ clearMessage: () => void
38
+ progress: number
39
+ }
40
+
41
+ export interface UseThreadOptions {
42
+ maxThreads?: number
43
+ autoArchive?: boolean
44
+ }
45
+
46
+ export interface UseThreadReturn {
47
+ threads: Map<string, any>
48
+ activeThread: any | undefined
49
+ createThread: (metadata?: Record<string, any>) => any
50
+ switchThread: (threadId: string) => void
51
+ deleteThread: (threadId: string) => void
52
+ updateThreadMetadata: (threadId: string, metadata: Record<string, any>) => void
53
+ getThreadMessages: (threadId: string) => any[]
54
+ clearThread: (threadId: string) => void
55
+ }
56
+
57
+ export interface UseComponentOptions {
58
+ lazy?: boolean
59
+ }
60
+
61
+ export interface UseComponentReturn {
62
+ registerComponent: (component: any) => void
63
+ unregisterComponent: (name: string) => void
64
+ renderComponent: (name: string, props: any) => React.ReactElement | null
65
+ getComponent: (name: string) => any | undefined
66
+ hasComponent: (name: string) => boolean
67
+ listComponents: () => string[]
68
+ }
69
+
70
+ export interface UseToolOptions {
71
+ timeout?: number
72
+ }
73
+
74
+ export interface UseToolReturn {
75
+ executeTool: (name: string, params: any) => Promise<any>
76
+ registerTool: (tool: any) => void
77
+ unregisterTool: (name: string) => void
78
+ isExecuting: boolean
79
+ lastResult: any | null
80
+ error: Error | null
81
+ }
82
+
83
+ export interface UseSuggestionsOptions {
84
+ maxSuggestions?: number
85
+ autoRefresh?: boolean
86
+ refreshInterval?: number
87
+ }
88
+
89
+ export interface UseSuggestionsReturn {
90
+ suggestions: any[]
91
+ getSuggestions: (options?: any) => Promise<void>
92
+ acceptSuggestion: (suggestion: any) => void
93
+ dismissSuggestion: (id: string) => void
94
+ isLoading: boolean
95
+ }
96
+
97
+ export interface UseModelConfigOptions {
98
+ defaultModel?: string
99
+ }
100
+
101
+ export interface UseModelConfigReturn {
102
+ currentModel: string
103
+ availableModels: any[]
104
+ switchModel: (modelId: string) => void
105
+ updateParameters: (params: any) => void
106
+ resetToDefaults: () => void
107
+ }
108
+
109
+ export interface UseMCPOptions {
110
+ autoConnect?: boolean
111
+ }
112
+
113
+ export interface UseMCPReturn {
114
+ connectServer: (config: any) => Promise<void>
115
+ disconnectServer: (id: string) => void
116
+ connectedServers: any[]
117
+ executeServerTool: (serverId: string, tool: string, params: any) => Promise<any>
118
+ getServerResources: (serverId: string) => any[]
119
+ isConnected: (serverId: string) => boolean
120
+ }
121
+
122
+ export interface UseGenerativeUIOptions {
123
+ cacheResults?: boolean
124
+ }
125
+
126
+ export interface UseGenerativeUIReturn {
127
+ generateUI: (options: any) => Promise<React.ReactElement>
128
+ isGenerating: boolean
129
+ generatedComponents: React.ReactElement[]
130
+ clearComponents: () => void
131
+ saveComponent: (name: string, component: React.ReactElement) => void
132
+ }
133
+
134
+ export interface UseAuthOptions {
135
+ provider?: string
136
+ }
137
+
138
+ export interface UseAuthReturn {
139
+ user: any | null
140
+ isAuthenticated: boolean
141
+ isLoading: boolean
142
+ signIn: (options?: any) => Promise<void>
143
+ signOut: () => Promise<void>
144
+ getToken: () => Promise<string | null>
145
+ refreshToken: () => Promise<void>
146
+ }
147
+
148
+ export interface UseAttachmentsOptions {
149
+ maxSize?: number
150
+ allowedTypes?: string[]
151
+ autoUpload?: boolean
152
+ }
153
+
154
+ export interface UseAttachmentsReturn {
155
+ attachments: any[]
156
+ addAttachment: (file: File) => Promise<any>
157
+ removeAttachment: (id: string) => void
158
+ clearAttachments: () => void
159
+ uploadAttachment: (attachment: any) => Promise<void>
160
+ isUploading: boolean
161
+ uploadProgress: number
162
+ }
File without changes
File without changes
File without changes
File without changes