@doderasoftware/restify-ai 0.2.0-beta.1 โ 0.2.0-beta.3
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/README.md +445 -31
- package/dist/components/AiChatDrawer.vue.d.ts +25 -3
- package/dist/components/AiChatDrawer.vue.d.ts.map +1 -1
- package/dist/components/ChatInput.vue.d.ts +10 -0
- package/dist/components/ChatInput.vue.d.ts.map +1 -1
- package/dist/components/ChatMessage.vue.d.ts.map +1 -1
- package/dist/components/drawer/ConversationHistory.vue.d.ts +3 -0
- package/dist/components/drawer/ConversationHistory.vue.d.ts.map +1 -0
- package/dist/components/drawer/DrawerHeader.vue.d.ts +4 -0
- package/dist/components/drawer/DrawerHeader.vue.d.ts.map +1 -1
- package/dist/components/drawer/DrawerMessageList.vue.d.ts.map +1 -1
- package/dist/components/icons/IconHistory.vue.d.ts +3 -0
- package/dist/components/icons/IconHistory.vue.d.ts.map +1 -0
- package/dist/components/icons/IconMicrophone.vue.d.ts +3 -0
- package/dist/components/icons/IconMicrophone.vue.d.ts.map +1 -0
- package/dist/components/icons/IconMicrophoneOff.vue.d.ts +3 -0
- package/dist/components/icons/IconMicrophoneOff.vue.d.ts.map +1 -0
- package/dist/components/icons/index.d.ts +3 -0
- package/dist/components/icons/index.d.ts.map +1 -1
- package/dist/components/input/AudioWave.vue.d.ts +29 -0
- package/dist/components/input/AudioWave.vue.d.ts.map +1 -0
- package/dist/components/input/InputActions.vue.d.ts +24 -3
- package/dist/components/input/InputActions.vue.d.ts.map +1 -1
- package/dist/composables/useChatInput.d.ts +2 -0
- package/dist/composables/useChatInput.d.ts.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/restify-ai.js +2965 -2391
- package/dist/restify-ai.umd.cjs +52 -52
- package/dist/store/storage.d.ts +10 -1
- package/dist/store/storage.d.ts.map +1 -1
- package/dist/store/store.d.ts +42 -0
- package/dist/store/store.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/chat.d.ts +12 -0
- package/dist/types/chat.d.ts.map +1 -1
- package/dist/types/config.d.ts +28 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/texts.d.ts +8 -0
- package/dist/types/texts.d.ts.map +1 -1
- package/dist/types/ui.d.ts +11 -3
- package/dist/types/ui.d.ts.map +1 -1
- package/package.json +10 -8
- package/tailwind.config.cjs +1 -30
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ A production-ready AI chatbot component for Vue 3 with real-time SSE streaming,
|
|
|
15
15
|
|
|
16
16
|
- ๐ **Real-time SSE Streaming** - Smooth character-by-character response streaming
|
|
17
17
|
- ๐ **File Attachments** - Upload and process documents, images, and more
|
|
18
|
+
- ๐ค **Audio Input** - Voice recording with visual feedback and wave animation
|
|
18
19
|
- ๐ฅ **@Mentions System** - Reference entities from your application (employees, jobs, projects, etc.)
|
|
19
20
|
- ๐ก **Context-Aware Suggestions** - Smart prompts based on current page/route
|
|
20
21
|
- ๐ฌ **Chat History** - Persistent conversation memory with configurable limits
|
|
@@ -251,10 +252,13 @@ export function setupRestifyAi(app: App) {
|
|
|
251
252
|
},
|
|
252
253
|
|
|
253
254
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
254
|
-
// LIMITS
|
|
255
|
+
// LIMITS & AI MODEL
|
|
255
256
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
256
257
|
|
|
257
258
|
chatHistoryLimit: 20, // Maximum user messages per conversation
|
|
259
|
+
model: 'gpt-4', // AI model to use (passed to backend)
|
|
260
|
+
temperature: 0.7, // AI temperature (0-1)
|
|
261
|
+
maxTokens: 2048, // Maximum tokens per response
|
|
258
262
|
maxAttachments: 5,
|
|
259
263
|
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
260
264
|
acceptedFileTypes: 'image/*,.pdf,.txt,.doc,.docx,.xls,.xlsx,.csv',
|
|
@@ -272,6 +276,11 @@ export function setupRestifyAi(app: App) {
|
|
|
272
276
|
|
|
273
277
|
keyboardShortcut: 'mod+g', // 'mod' = Cmd on Mac, Ctrl on Windows
|
|
274
278
|
enableSupportMode: true,
|
|
279
|
+
useQuota: true, // Enable quota management
|
|
280
|
+
useHeadersRateLimiter: true, // Use rate limit headers instead of quota endpoint
|
|
281
|
+
useConversationId: true, // Enable conversation ID tracking
|
|
282
|
+
useConversationHistory: true, // Enable conversation history sidebar
|
|
283
|
+
maxConversationHistory: 10, // Max conversations to store (default: 10)
|
|
275
284
|
canToggle: () => true,
|
|
276
285
|
|
|
277
286
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -291,6 +300,7 @@ export function setupRestifyAi(app: App) {
|
|
|
291
300
|
onResponseReceived: (message) => console.log('Response:', message),
|
|
292
301
|
onDrawerToggle: (isOpen) => console.log('Drawer:', isOpen),
|
|
293
302
|
onNewChat: () => console.log('New chat started'),
|
|
303
|
+
onSetupComplete: () => console.log('Setup completed'),
|
|
294
304
|
|
|
295
305
|
// Stream lifecycle hooks
|
|
296
306
|
onStreamStart: () => console.log('Stream started'),
|
|
@@ -318,7 +328,7 @@ export function setupRestifyAi(app: App) {
|
|
|
318
328
|
| `topOffset` | `string` | `"0"` | Top offset for fixed headers |
|
|
319
329
|
| `position` | `"left" \| "right"` | `"right"` | Drawer position |
|
|
320
330
|
| `showBackdrop` | `boolean` | `false` | Show backdrop overlay |
|
|
321
|
-
| `closeOnBackdropClick` | `boolean` | `
|
|
331
|
+
| `closeOnBackdropClick` | `boolean` | `true` | Close when clicking backdrop |
|
|
322
332
|
| `closeOnEscape` | `boolean` | `true` | Close on Escape key |
|
|
323
333
|
| `showQuota` | `boolean` | `true` | Show quota display (API usage remaining) |
|
|
324
334
|
| `showMessageCount` | `boolean` | `true` | Show message count badge (X/20 format) |
|
|
@@ -327,6 +337,9 @@ export function setupRestifyAi(app: App) {
|
|
|
327
337
|
| `showCloseButton` | `boolean` | `true` | Show close button |
|
|
328
338
|
| `showNewChatButton` | `boolean` | `true` | Show new chat button |
|
|
329
339
|
| `confirmClose` | `boolean` | `true` | Confirm before clearing history |
|
|
340
|
+
| `enableAudioInput` | `boolean` | `false` | Enable voice recording button |
|
|
341
|
+
| `isRecording` | `boolean` | `false` | Audio recording state (controlled) |
|
|
342
|
+
| `inputValue` | `string` | `''` | Input text value (v-model:inputValue) |
|
|
330
343
|
| `autoFetchQuota` | `boolean` | `true` | Auto-fetch quota when opened |
|
|
331
344
|
| `historyLimit` | `HistoryLimitConfig` | - | History limit configuration |
|
|
332
345
|
| `loadingText` | `LoadingTextConfig` | - | Loading text configuration |
|
|
@@ -369,14 +382,84 @@ function handleContactSupport() {
|
|
|
369
382
|
</script>
|
|
370
383
|
```
|
|
371
384
|
|
|
385
|
+
### Audio Input Example
|
|
386
|
+
|
|
387
|
+
Enable voice recording with the `enableAudioInput` prop and handle the recording state:
|
|
388
|
+
|
|
389
|
+
```vue
|
|
390
|
+
<template>
|
|
391
|
+
<AiChatDrawer
|
|
392
|
+
v-model="aiStore.showChat"
|
|
393
|
+
v-model:input-value="audioInput"
|
|
394
|
+
:enable-audio-input="true"
|
|
395
|
+
:is-recording="isRecording"
|
|
396
|
+
@toggle-audio-recording="toggleRecording"
|
|
397
|
+
/>
|
|
398
|
+
</template>
|
|
399
|
+
|
|
400
|
+
<script setup lang="ts">
|
|
401
|
+
import { ref } from 'vue'
|
|
402
|
+
import { AiChatDrawer, useRestifyAiStore } from '@doderasoftware/restify-ai'
|
|
403
|
+
|
|
404
|
+
const aiStore = useRestifyAiStore()
|
|
405
|
+
const isRecording = ref(false)
|
|
406
|
+
const audioInput = ref('')
|
|
407
|
+
let mediaRecorder: MediaRecorder | null = null
|
|
408
|
+
|
|
409
|
+
async function toggleRecording() {
|
|
410
|
+
if (isRecording.value) {
|
|
411
|
+
// Stop recording
|
|
412
|
+
mediaRecorder?.stop()
|
|
413
|
+
isRecording.value = false
|
|
414
|
+
} else {
|
|
415
|
+
// Start recording
|
|
416
|
+
try {
|
|
417
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
|
418
|
+
mediaRecorder = new MediaRecorder(stream)
|
|
419
|
+
|
|
420
|
+
const chunks: Blob[] = []
|
|
421
|
+
mediaRecorder.ondataavailable = (e) => chunks.push(e.data)
|
|
422
|
+
mediaRecorder.onstop = async () => {
|
|
423
|
+
const audioBlob = new Blob(chunks, { type: 'audio/webm' })
|
|
424
|
+
// Process the audio (e.g., send to transcription API)
|
|
425
|
+
const transcription = await transcribeAudio(audioBlob)
|
|
426
|
+
audioInput.value = transcription
|
|
427
|
+
stream.getTracks().forEach(track => track.stop())
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
mediaRecorder.start()
|
|
431
|
+
isRecording.value = true
|
|
432
|
+
} catch (error) {
|
|
433
|
+
console.error('Failed to start recording:', error)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function transcribeAudio(blob: Blob): Promise<string> {
|
|
439
|
+
// Implement your transcription logic here
|
|
440
|
+
// e.g., send to OpenAI Whisper, Google Speech-to-Text, etc.
|
|
441
|
+
return 'Transcribed text...'
|
|
442
|
+
}
|
|
443
|
+
</script>
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Features:**
|
|
447
|
+
- ๐ค Microphone button appears when `enableAudioInput` is enabled
|
|
448
|
+
- ๐ Animated wave indicator shows when recording is active
|
|
449
|
+
- ๐ด Visual feedback with red styling during recording
|
|
450
|
+
- ๐จ Customizable via `audioButton`, `audioButtonRecording`, and `audioRecordingIndicator` UI classes
|
|
451
|
+
- ๐ Text labels customizable via `startRecording`, `stopRecording`, and `recording` texts
|
|
452
|
+
|
|
372
453
|
## ๐ก Events
|
|
373
454
|
|
|
374
455
|
| Event | Payload | Description |
|
|
375
456
|
|-------|---------|-------------|
|
|
376
457
|
| `update:modelValue` | `boolean` | Drawer state changed |
|
|
458
|
+
| `update:inputValue` | `string` | Input text value changed |
|
|
377
459
|
| `close` | - | Drawer was closed |
|
|
378
460
|
| `contact-support` | - | Support mode activated |
|
|
379
461
|
| `new-chat` | - | New chat started |
|
|
462
|
+
| `toggle-audio-recording` | - | Audio recording toggled |
|
|
380
463
|
|
|
381
464
|
## ๐ฐ Slots
|
|
382
465
|
|
|
@@ -385,7 +468,7 @@ function handleContactSupport() {
|
|
|
385
468
|
| `header` | `HeaderSlotProps` | Custom header content |
|
|
386
469
|
| `quota` | `{ quota: ChatQuota }` | Custom quota display |
|
|
387
470
|
| `setup` | - | Custom setup guide |
|
|
388
|
-
| `empty-state` | `
|
|
471
|
+
| `empty-state` | `EmptyStateSlotProps` | Custom empty state |
|
|
389
472
|
| `message` | `MessageSlotProps` | Custom message bubble |
|
|
390
473
|
| `input` | `InputSlotProps` | Custom input area |
|
|
391
474
|
| `context-link` | - | Custom context link below input |
|
|
@@ -403,6 +486,11 @@ interface HeaderSlotProps {
|
|
|
403
486
|
onToggleFullscreen: () => void
|
|
404
487
|
}
|
|
405
488
|
|
|
489
|
+
interface EmptyStateSlotProps {
|
|
490
|
+
suggestions: AISuggestion[]
|
|
491
|
+
onSuggestionClick: (suggestion: AISuggestion) => void
|
|
492
|
+
}
|
|
493
|
+
|
|
406
494
|
interface MessageSlotProps {
|
|
407
495
|
message: ChatMessage
|
|
408
496
|
isUser: boolean
|
|
@@ -430,15 +518,22 @@ const store = useRestifyAiStore()
|
|
|
430
518
|
// STATE
|
|
431
519
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
432
520
|
|
|
521
|
+
store.chatHistoryLimit // number - Maximum messages allowed
|
|
433
522
|
store.chatHistory // ChatMessage[] - All messages
|
|
523
|
+
store.uploadedFiles // Record<string, ChatAttachment> - Uploaded files by ID
|
|
434
524
|
store.showChat // boolean - Drawer visibility
|
|
435
525
|
store.sending // boolean - Message being sent
|
|
436
526
|
store.loading // boolean - Loading state
|
|
437
527
|
store.isFullscreen // boolean - Fullscreen mode
|
|
438
528
|
store.quota // { limit, used, remaining }
|
|
439
|
-
store.error // { message, failedQuestion, failedAttachments, timestamp }
|
|
529
|
+
store.error // { message, failedQuestion, failedAttachments, timestamp, quotaExceeded }
|
|
440
530
|
store.supportRequestMode // boolean - Support mode active
|
|
441
531
|
store.pageContext // PageContext | null - Current page context
|
|
532
|
+
store.setupState // SetupState - Setup wizard state
|
|
533
|
+
store.conversationId // string | null - Current conversation ID from backend
|
|
534
|
+
store.conversationName // string | null - Current conversation name
|
|
535
|
+
store.conversationHistory // ConversationHistoryItem[] - List of saved conversations
|
|
536
|
+
store.showHistorySidebar // boolean - History sidebar visibility
|
|
442
537
|
|
|
443
538
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
444
539
|
// GETTERS
|
|
@@ -464,7 +559,21 @@ store.toggleSupportMode() // Toggle support mode
|
|
|
464
559
|
store.fetchQuota() // Fetch quota from server
|
|
465
560
|
store.uploadFile(file) // Upload file
|
|
466
561
|
store.setPageContext(context) // Set page context
|
|
467
|
-
store.scrollToBottom()
|
|
562
|
+
store.scrollToBottom(delay?) // Scroll chat to bottom
|
|
563
|
+
|
|
564
|
+
// Conversation History Actions
|
|
565
|
+
store.toggleHistorySidebar() // Toggle history sidebar
|
|
566
|
+
store.loadConversation(id) // Load a saved conversation
|
|
567
|
+
store.renameConversation(id, title) // Rename a conversation
|
|
568
|
+
store.deleteConversation(id) // Delete a saved conversation
|
|
569
|
+
store.saveCurrentConversation() // Save current conversation
|
|
570
|
+
|
|
571
|
+
// Setup Mode Actions
|
|
572
|
+
store.startSetupMode() // Start setup wizard
|
|
573
|
+
store.setSetupStep(step) // Set current setup step
|
|
574
|
+
store.testConnection() // Test backend connection
|
|
575
|
+
store.completeSetup() // Complete setup
|
|
576
|
+
store.skipSetup() // Skip setup wizard
|
|
468
577
|
```
|
|
469
578
|
|
|
470
579
|
## ๐ช Composables
|
|
@@ -521,7 +630,44 @@ Get suggestions for current context:
|
|
|
521
630
|
```typescript
|
|
522
631
|
import { useAiSuggestions } from '@doderasoftware/restify-ai'
|
|
523
632
|
|
|
524
|
-
const { suggestions, resolvePrompt } = useAiSuggestions()
|
|
633
|
+
const { suggestions, hasContextualSuggestions, resolvePrompt } = useAiSuggestions()
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### useLoadingText
|
|
637
|
+
|
|
638
|
+
Manage dynamic loading text messages:
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
import { useLoadingText } from '@doderasoftware/restify-ai'
|
|
642
|
+
|
|
643
|
+
const {
|
|
644
|
+
loadingMessage,
|
|
645
|
+
startLoadingText,
|
|
646
|
+
resetLoadingText
|
|
647
|
+
} = useLoadingText(
|
|
648
|
+
() => isSending.value,
|
|
649
|
+
() => ({
|
|
650
|
+
messages: ['Thinking...', 'Analyzing...', 'Crafting response...'],
|
|
651
|
+
intervals: [0, 2000, 5000]
|
|
652
|
+
})
|
|
653
|
+
)
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### useHistoryLimit
|
|
657
|
+
|
|
658
|
+
Handle chat history limits with warnings:
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
import { useHistoryLimit } from '@doderasoftware/restify-ai'
|
|
662
|
+
|
|
663
|
+
const historyLimit = useHistoryLimit({
|
|
664
|
+
getHistoryLength: () => chatHistory.length,
|
|
665
|
+
getStoreLimit: () => store.chatHistoryLimit,
|
|
666
|
+
getConfig: () => historyLimitConfig,
|
|
667
|
+
getTexts: () => texts,
|
|
668
|
+
onStartNewChat: () => store.clearChatHistory(),
|
|
669
|
+
onNewChatEmit: () => emit('new-chat'),
|
|
670
|
+
})
|
|
525
671
|
```
|
|
526
672
|
|
|
527
673
|
## ๐ท๏ธ Mention Providers
|
|
@@ -757,26 +903,41 @@ interface AISuggestion {
|
|
|
757
903
|
|
|
758
904
|
## ๐จ UI Customization
|
|
759
905
|
|
|
760
|
-
Override CSS classes for any component
|
|
906
|
+
Override CSS classes for any component. The `ui` prop on `AiChatDrawer` allows you to customize all components in one place - it extends all child component UI interfaces, so you can customize the drawer, input, messages, empty state, and mentions all from a single object.
|
|
761
907
|
|
|
762
908
|
```vue
|
|
763
909
|
<AiChatDrawer
|
|
764
910
|
v-model="isOpen"
|
|
765
911
|
:ui="{
|
|
912
|
+
// Drawer customization
|
|
766
913
|
backdrop: 'bg-black/50 backdrop-blur-sm',
|
|
767
914
|
drawer: 'shadow-2xl',
|
|
768
915
|
panel: 'bg-gray-50 dark:bg-gray-900',
|
|
769
916
|
header: 'border-b-2 border-primary-500',
|
|
770
917
|
body: 'custom-scrollbar',
|
|
771
918
|
footer: 'border-t border-gray-200',
|
|
919
|
+
|
|
920
|
+
// ChatInput customization (automatically passed down)
|
|
921
|
+
textarea: 'rounded-xl',
|
|
922
|
+
sendButton: 'bg-blue-500 hover:bg-blue-600',
|
|
923
|
+
|
|
924
|
+
// ChatMessage customization (automatically passed down)
|
|
925
|
+
userBubble: 'bg-blue-500 text-white',
|
|
926
|
+
assistantBubble: 'bg-gray-100 dark:bg-gray-800',
|
|
927
|
+
|
|
928
|
+
// AiEmptyState customization (automatically passed down)
|
|
929
|
+
suggestionCard: 'border-2 border-primary-500',
|
|
772
930
|
}"
|
|
773
931
|
/>
|
|
774
932
|
```
|
|
775
933
|
|
|
776
934
|
### AiChatDrawerUI
|
|
777
935
|
|
|
936
|
+
The main UI interface that combines all component UI interfaces. Pass this to the `ui` prop to customize all components:
|
|
937
|
+
|
|
778
938
|
```typescript
|
|
779
|
-
interface AiChatDrawerUI {
|
|
939
|
+
interface AiChatDrawerUI extends ChatInputUI, ChatMessageUI, AiEmptyStateUI, MentionListUI {
|
|
940
|
+
// Drawer-specific
|
|
780
941
|
backdrop?: string // Backdrop overlay
|
|
781
942
|
drawer?: string // Main drawer container
|
|
782
943
|
panel?: string // Inner panel
|
|
@@ -786,17 +947,24 @@ interface AiChatDrawerUI {
|
|
|
786
947
|
headerActionButton?: string // Header buttons
|
|
787
948
|
body?: string // Messages container
|
|
788
949
|
footer?: string // Footer container
|
|
950
|
+
|
|
951
|
+
// Dialogs
|
|
952
|
+
closeConfirmModal?: string // Confirm modal
|
|
953
|
+
closeConfirmButton?: string // Confirm button
|
|
954
|
+
cancelButton?: string // Cancel button
|
|
955
|
+
historyLimitModal?: string // History limit modal
|
|
956
|
+
historyLimitButton?: string // History limit button
|
|
957
|
+
|
|
958
|
+
// Header elements
|
|
789
959
|
quotaDisplay?: string // Quota display
|
|
790
960
|
messageCountBadge?: string // Message count badge
|
|
791
961
|
newChatButton?: string // New chat button
|
|
962
|
+
|
|
963
|
+
// Error display
|
|
792
964
|
errorContainer?: string // Error container
|
|
793
965
|
errorMessage?: string // Error message
|
|
794
966
|
retryButton?: string // Retry button
|
|
795
|
-
|
|
796
|
-
closeConfirmButton?: string // Confirm button
|
|
797
|
-
cancelButton?: string // Cancel button
|
|
798
|
-
historyLimitModal?: string // History limit modal
|
|
799
|
-
historyLimitButton?: string // History limit button
|
|
967
|
+
contactSupportButton?: string // Contact support button
|
|
800
968
|
}
|
|
801
969
|
```
|
|
802
970
|
|
|
@@ -824,6 +992,10 @@ interface ChatInputUI {
|
|
|
824
992
|
suggestionItem?: string // Suggestion item
|
|
825
993
|
suggestionItemSelected?: string // Selected suggestion
|
|
826
994
|
contextLink?: string // Context link
|
|
995
|
+
// Audio input
|
|
996
|
+
audioButton?: string // Audio button
|
|
997
|
+
audioButtonRecording?: string // Audio button when recording
|
|
998
|
+
audioRecordingIndicator?: string // Recording indicator
|
|
827
999
|
}
|
|
828
1000
|
```
|
|
829
1001
|
|
|
@@ -847,6 +1019,66 @@ interface ChatMessageUI {
|
|
|
847
1019
|
}
|
|
848
1020
|
```
|
|
849
1021
|
|
|
1022
|
+
### AiEmptyStateUI
|
|
1023
|
+
|
|
1024
|
+
```typescript
|
|
1025
|
+
interface AiEmptyStateUI {
|
|
1026
|
+
root?: string // Root container
|
|
1027
|
+
content?: string // Content container
|
|
1028
|
+
header?: string // Header container
|
|
1029
|
+
badge?: string // AI badge
|
|
1030
|
+
title?: string // Title
|
|
1031
|
+
description?: string // Description
|
|
1032
|
+
grid?: string // Suggestions grid
|
|
1033
|
+
suggestionCard?: string // Suggestion card
|
|
1034
|
+
suggestionIconContainer?: string // Icon container
|
|
1035
|
+
suggestionIcon?: string // Icon
|
|
1036
|
+
suggestionTitle?: string // Suggestion title
|
|
1037
|
+
suggestionDescription?: string // Suggestion description
|
|
1038
|
+
}
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
### MentionListUI
|
|
1042
|
+
|
|
1043
|
+
```typescript
|
|
1044
|
+
interface MentionListUI {
|
|
1045
|
+
root?: string // Root container
|
|
1046
|
+
container?: string // List container
|
|
1047
|
+
groupHeader?: string // Group header
|
|
1048
|
+
item?: string // Item
|
|
1049
|
+
itemSelected?: string // Selected item
|
|
1050
|
+
itemIcon?: string // Item icon
|
|
1051
|
+
itemContent?: string // Item content
|
|
1052
|
+
itemName?: string // Item name
|
|
1053
|
+
itemSubtitle?: string // Item subtitle
|
|
1054
|
+
}
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
### AiAvatarUI / UserAvatarUI
|
|
1058
|
+
|
|
1059
|
+
```typescript
|
|
1060
|
+
interface AiAvatarUI {
|
|
1061
|
+
container?: string // Container
|
|
1062
|
+
icon?: string // Icon
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
interface UserAvatarUI {
|
|
1066
|
+
container?: string // Container
|
|
1067
|
+
icon?: string // Icon
|
|
1068
|
+
}
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
### ChatMessageActionsUI
|
|
1072
|
+
|
|
1073
|
+
```typescript
|
|
1074
|
+
interface ChatMessageActionsUI {
|
|
1075
|
+
container?: string // Container
|
|
1076
|
+
button?: string // Action button
|
|
1077
|
+
copyButton?: string // Copy button
|
|
1078
|
+
successState?: string // Success state
|
|
1079
|
+
}
|
|
1080
|
+
```
|
|
1081
|
+
|
|
850
1082
|
## ๐ TypeScript Types
|
|
851
1083
|
|
|
852
1084
|
All types are exported:
|
|
@@ -866,6 +1098,8 @@ import type {
|
|
|
866
1098
|
ChatAttachment,
|
|
867
1099
|
ChatRole,
|
|
868
1100
|
SubmitPayload,
|
|
1101
|
+
Mention,
|
|
1102
|
+
ConversationHistoryItem,
|
|
869
1103
|
|
|
870
1104
|
// Context
|
|
871
1105
|
PageContext,
|
|
@@ -873,7 +1107,8 @@ import type {
|
|
|
873
1107
|
// Providers
|
|
874
1108
|
MentionProvider,
|
|
875
1109
|
MentionItem,
|
|
876
|
-
|
|
1110
|
+
MentionContext,
|
|
1111
|
+
MentionParseResult,
|
|
877
1112
|
SuggestionProvider,
|
|
878
1113
|
AISuggestion,
|
|
879
1114
|
|
|
@@ -881,17 +1116,30 @@ import type {
|
|
|
881
1116
|
HistoryLimitConfig,
|
|
882
1117
|
LoadingTextConfig,
|
|
883
1118
|
|
|
1119
|
+
// Setup
|
|
1120
|
+
SetupStep,
|
|
1121
|
+
SetupState,
|
|
1122
|
+
|
|
1123
|
+
// Store
|
|
1124
|
+
AiStoreState,
|
|
1125
|
+
|
|
884
1126
|
// UI Customization
|
|
885
1127
|
AiChatDrawerUI,
|
|
886
1128
|
ChatInputUI,
|
|
887
1129
|
ChatMessageUI,
|
|
888
1130
|
AiEmptyStateUI,
|
|
889
1131
|
MentionListUI,
|
|
1132
|
+
AiAvatarUI,
|
|
1133
|
+
UserAvatarUI,
|
|
1134
|
+
ChatMessageActionsUI,
|
|
890
1135
|
|
|
891
1136
|
// Text Customization
|
|
892
1137
|
AiChatDrawerTexts,
|
|
893
1138
|
ChatInputTexts,
|
|
894
1139
|
ChatMessageTexts,
|
|
1140
|
+
AiEmptyStateTexts,
|
|
1141
|
+
MentionListTexts,
|
|
1142
|
+
ChatMessageActionsTexts,
|
|
895
1143
|
|
|
896
1144
|
// Slot Props
|
|
897
1145
|
HeaderSlotProps,
|
|
@@ -899,15 +1147,26 @@ import type {
|
|
|
899
1147
|
InputSlotProps,
|
|
900
1148
|
EmptyStateSlotProps,
|
|
901
1149
|
|
|
902
|
-
// Hooks
|
|
1150
|
+
// API Hooks
|
|
1151
|
+
AiRequestPayload,
|
|
1152
|
+
AiStreamChunk,
|
|
903
1153
|
BeforeSendHook,
|
|
904
1154
|
AfterResponseHook,
|
|
905
1155
|
OnStreamStartHook,
|
|
906
1156
|
OnStreamEndHook,
|
|
907
1157
|
OnStreamChunkHook,
|
|
908
1158
|
StreamParserFunction,
|
|
1159
|
+
RequestInterceptor,
|
|
1160
|
+
ResponseInterceptor,
|
|
909
1161
|
RetryConfig,
|
|
1162
|
+
|
|
1163
|
+
// Functions
|
|
1164
|
+
TranslateFunction,
|
|
1165
|
+
PermissionCheckFunction,
|
|
910
1166
|
} from '@doderasoftware/restify-ai'
|
|
1167
|
+
|
|
1168
|
+
// Constants
|
|
1169
|
+
import { ChatRoles } from '@doderasoftware/restify-ai'
|
|
911
1170
|
```
|
|
912
1171
|
|
|
913
1172
|
## ๐ Backend Integration
|
|
@@ -924,25 +1183,101 @@ Route::post('/ask', [AiController::class, 'ask']);
|
|
|
924
1183
|
**Request:**
|
|
925
1184
|
```json
|
|
926
1185
|
{
|
|
927
|
-
"
|
|
1186
|
+
"message": "Who is available today?",
|
|
928
1187
|
"history": [
|
|
929
|
-
{ "role": "user", "
|
|
930
|
-
{ "role": "assistant", "
|
|
1188
|
+
{ "role": "user", "content": "Hello" },
|
|
1189
|
+
{ "role": "assistant", "content": "Hi! How can I help?" }
|
|
931
1190
|
],
|
|
932
1191
|
"stream": true,
|
|
933
|
-
"files": [{ "id": "file-123", "name": "report.pdf" }],
|
|
1192
|
+
"files": [{ "id": "file-123", "name": "report.pdf", "backendFileId": 456 }],
|
|
1193
|
+
"attachment_ids": [456],
|
|
934
1194
|
"mentions": [{ "id": "emp-1", "type": "employee", "name": "John Doe" }],
|
|
935
|
-
"contact_support": false
|
|
1195
|
+
"contact_support": false,
|
|
1196
|
+
"conversation_id": "conv-abc123"
|
|
936
1197
|
}
|
|
937
1198
|
```
|
|
938
1199
|
|
|
1200
|
+
> **Note:** The `conversation_id` field is only sent when `useConversationId: true` is configured. The `attachment_ids` array contains backend file IDs returned from the upload endpoint.
|
|
1201
|
+
|
|
939
1202
|
**Response (SSE):**
|
|
940
1203
|
```
|
|
1204
|
+
event: start
|
|
1205
|
+
data: {"conversation_id":"conv-abc123"}
|
|
1206
|
+
|
|
941
1207
|
data: {"choices":[{"delta":{"content":"Based on"}}]}
|
|
942
1208
|
data: {"choices":[{"delta":{"content":" the schedule..."}}]}
|
|
943
1209
|
data: [DONE]
|
|
944
1210
|
```
|
|
945
1211
|
|
|
1212
|
+
### Conversation ID Tracking
|
|
1213
|
+
|
|
1214
|
+
When `useConversationId: true` is enabled in the plugin configuration, the package will:
|
|
1215
|
+
|
|
1216
|
+
1. **Receive** the conversation ID from the backend via the `start` SSE event
|
|
1217
|
+
2. **Store** it in the Pinia store (`store.conversationId`)
|
|
1218
|
+
3. **Send** it back with subsequent requests in the same conversation
|
|
1219
|
+
4. **Clear** it when starting a new chat
|
|
1220
|
+
|
|
1221
|
+
This is useful for:
|
|
1222
|
+
- Tracking conversations across multiple requests
|
|
1223
|
+
- Maintaining context on the backend
|
|
1224
|
+
- Analytics and logging
|
|
1225
|
+
- Multi-turn conversation management
|
|
1226
|
+
|
|
1227
|
+
### Conversation History
|
|
1228
|
+
|
|
1229
|
+
When `useConversationHistory: true` is enabled, users can access a sidebar showing their previous conversations:
|
|
1230
|
+
|
|
1231
|
+
**Features:**
|
|
1232
|
+
- ๐ **View past conversations** - Browse through saved chats
|
|
1233
|
+
- โ๏ธ **Rename conversations** - Give meaningful titles to chats
|
|
1234
|
+
- ๐๏ธ **Delete conversations** - Remove unwanted history
|
|
1235
|
+
- ๐ **Switch conversations** - Seamlessly load previous chats
|
|
1236
|
+
- ๐พ **Automatic saving** - Conversations save automatically
|
|
1237
|
+
|
|
1238
|
+
**Configuration:**
|
|
1239
|
+
```typescript
|
|
1240
|
+
app.use(RestifyAiPlugin, {
|
|
1241
|
+
useConversationId: true, // Required for history
|
|
1242
|
+
useConversationHistory: true, // Enable history sidebar
|
|
1243
|
+
maxConversationHistory: 10, // Max conversations to keep (default: 10)
|
|
1244
|
+
})
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1247
|
+
**How it works:**
|
|
1248
|
+
1. When `useConversationHistory` is enabled, a history button appears in the drawer header
|
|
1249
|
+
2. Clicking the button opens a sidebar showing past conversations
|
|
1250
|
+
3. Conversations are automatically saved to `localStorage` when completed
|
|
1251
|
+
4. Each conversation shows: title (from first message or backend), date, message count
|
|
1252
|
+
5. Users can rename, delete, or switch between conversations
|
|
1253
|
+
|
|
1254
|
+
**Backend Integration:**
|
|
1255
|
+
|
|
1256
|
+
The backend can provide a conversation name via the `start` SSE event:
|
|
1257
|
+
|
|
1258
|
+
```
|
|
1259
|
+
event: start
|
|
1260
|
+
data: {"conversation_id":"conv-abc123","conversation_name":"Budget Analysis Q1"}
|
|
1261
|
+
```
|
|
1262
|
+
|
|
1263
|
+
If no name is provided, the title defaults to a truncated version of the first user message.
|
|
1264
|
+
|
|
1265
|
+
**Storage:**
|
|
1266
|
+
- Conversation list: `restify_ai_conversation_history`
|
|
1267
|
+
- Conversation messages: `restify_ai_conv_{conversationId}`
|
|
1268
|
+
- Current conversation ID: `restify_ai_current_conversation`
|
|
1269
|
+
|
|
1270
|
+
**ConversationHistoryItem Interface:**
|
|
1271
|
+
```typescript
|
|
1272
|
+
interface ConversationHistoryItem {
|
|
1273
|
+
id: string // Conversation ID from backend
|
|
1274
|
+
title: string // Display title
|
|
1275
|
+
createdAt: number // Timestamp when created
|
|
1276
|
+
updatedAt: number // Timestamp of last update
|
|
1277
|
+
messageCount: number // Number of messages
|
|
1278
|
+
}
|
|
1279
|
+
```
|
|
1280
|
+
|
|
946
1281
|
### Quota Endpoint
|
|
947
1282
|
|
|
948
1283
|
```php
|
|
@@ -961,6 +1296,27 @@ Route::get('/ai/quota', [AiController::class, 'quota']);
|
|
|
961
1296
|
}
|
|
962
1297
|
```
|
|
963
1298
|
|
|
1299
|
+
### Rate Limiting via Headers
|
|
1300
|
+
|
|
1301
|
+
As an alternative to the quota endpoint, you can enable headers-based rate limiting. When `useHeadersRateLimiter` is enabled, the package will extract rate limit information directly from response headers instead of making separate quota endpoint calls.
|
|
1302
|
+
|
|
1303
|
+
**Configuration:**
|
|
1304
|
+
```typescript
|
|
1305
|
+
app.use(RestifyAi, {
|
|
1306
|
+
useQuota: true,
|
|
1307
|
+
useHeadersRateLimiter: true, // Enable headers-based rate limiting
|
|
1308
|
+
})
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
**Required Response Headers:**
|
|
1312
|
+
|
|
1313
|
+
Your chat endpoint should return these headers with each response:
|
|
1314
|
+
|
|
1315
|
+
```
|
|
1316
|
+
x-ratelimit-limit: 100 // Total allowed requests
|
|
1317
|
+
x-ratelimit-remaining: 75 // Remaining requests
|
|
1318
|
+
```
|
|
1319
|
+
|
|
964
1320
|
### Upload Endpoint
|
|
965
1321
|
|
|
966
1322
|
```php
|
|
@@ -972,15 +1328,41 @@ Route::post('/ai/upload', [AiController::class, 'upload']);
|
|
|
972
1328
|
```json
|
|
973
1329
|
{
|
|
974
1330
|
"data": {
|
|
975
|
-
"id":
|
|
1331
|
+
"id": 456,
|
|
976
1332
|
"name": "document.pdf",
|
|
977
1333
|
"url": "/storage/uploads/document.pdf",
|
|
978
1334
|
"type": "application/pdf",
|
|
979
|
-
"size": 102400
|
|
1335
|
+
"size": 102400,
|
|
1336
|
+
"extracted_text": "Optional extracted text content..."
|
|
980
1337
|
}
|
|
981
1338
|
}
|
|
982
1339
|
```
|
|
983
1340
|
|
|
1341
|
+
### Backend File ID Support
|
|
1342
|
+
|
|
1343
|
+
When files are uploaded, the backend can return an ID that will be automatically tracked and sent with subsequent requests. This enables the backend to reference pre-processed files.
|
|
1344
|
+
|
|
1345
|
+
**Supported ID field names in upload response:**
|
|
1346
|
+
- `id` (recommended)
|
|
1347
|
+
- `file_id`
|
|
1348
|
+
- `attachment_id`
|
|
1349
|
+
- `openai_file_id`
|
|
1350
|
+
|
|
1351
|
+
The package automatically:
|
|
1352
|
+
1. **Stores** the backend file ID when received from upload response
|
|
1353
|
+
2. **Includes** all backend file IDs in the `attachment_ids` array in ask requests
|
|
1354
|
+
3. **Preserves** the ID through the entire conversation
|
|
1355
|
+
|
|
1356
|
+
**Example flow:**
|
|
1357
|
+
```
|
|
1358
|
+
1. User uploads file.pdf
|
|
1359
|
+
2. Backend returns: { "id": 456, "name": "file.pdf", ... }
|
|
1360
|
+
3. Package stores backendFileId: 456
|
|
1361
|
+
4. User sends message with attachment
|
|
1362
|
+
5. Request includes: { "attachment_ids": [456], "files": [...] }
|
|
1363
|
+
6. Backend can use attachment_ids to reference stored/processed files
|
|
1364
|
+
```
|
|
1365
|
+
|
|
984
1366
|
## โจ๏ธ Keyboard Shortcuts
|
|
985
1367
|
|
|
986
1368
|
| Shortcut | Action |
|
|
@@ -1002,7 +1384,17 @@ Chat history persists in `sessionStorage` by default:
|
|
|
1002
1384
|
|
|
1003
1385
|
```typescript
|
|
1004
1386
|
// Components
|
|
1005
|
-
export {
|
|
1387
|
+
export {
|
|
1388
|
+
AiChatDrawer,
|
|
1389
|
+
AiEmptyState,
|
|
1390
|
+
ChatInput,
|
|
1391
|
+
ChatMessage,
|
|
1392
|
+
ChatMessageActions,
|
|
1393
|
+
MentionList,
|
|
1394
|
+
AiAvatar,
|
|
1395
|
+
UserAvatar,
|
|
1396
|
+
ErrorBoundary,
|
|
1397
|
+
} from './components'
|
|
1006
1398
|
|
|
1007
1399
|
// Store
|
|
1008
1400
|
export { useRestifyAiStore } from './store'
|
|
@@ -1010,20 +1402,42 @@ export { useRestifyAiStore } from './store'
|
|
|
1010
1402
|
// Composables
|
|
1011
1403
|
export {
|
|
1012
1404
|
useAiDrawerShortcut,
|
|
1405
|
+
useKeyboardShortcut,
|
|
1013
1406
|
usePageAiContext,
|
|
1014
1407
|
useAiContext,
|
|
1015
|
-
useAiSuggestions
|
|
1408
|
+
useAiSuggestions,
|
|
1409
|
+
useMentionParsing,
|
|
1410
|
+
useChatMarkdown,
|
|
1411
|
+
useChatScroll,
|
|
1412
|
+
useChatErrorHandling,
|
|
1413
|
+
useLoadingText,
|
|
1414
|
+
useHistoryLimit,
|
|
1415
|
+
useSuggestionFilter,
|
|
1416
|
+
useAutoScroll,
|
|
1417
|
+
formatMentionsForApi,
|
|
1418
|
+
groupMentionsByType,
|
|
1016
1419
|
} from './composables'
|
|
1017
1420
|
|
|
1018
1421
|
// Config
|
|
1019
1422
|
export {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1423
|
+
RestifyAiPlugin,
|
|
1424
|
+
getRestifyAiConfig,
|
|
1425
|
+
setRestifyAiConfig,
|
|
1426
|
+
getLabel,
|
|
1427
|
+
getConfigValue,
|
|
1428
|
+
isConfigured,
|
|
1429
|
+
defaultLabels,
|
|
1025
1430
|
} from './config'
|
|
1026
1431
|
|
|
1432
|
+
// Suggestions
|
|
1433
|
+
export {
|
|
1434
|
+
registerSuggestionProvider,
|
|
1435
|
+
getSuggestionsForPath,
|
|
1436
|
+
} from './suggestions'
|
|
1437
|
+
|
|
1438
|
+
// Utilities
|
|
1439
|
+
export { isImageFile, formatFileSize } from './utils'
|
|
1440
|
+
|
|
1027
1441
|
// Types
|
|
1028
1442
|
export * from './types'
|
|
1029
1443
|
```
|
|
@@ -1039,7 +1453,7 @@ export * from './types'
|
|
|
1039
1453
|
- [๐ Laravel Restify Documentation](https://laravel-restify.com)
|
|
1040
1454
|
- [๐ฆ npm Package](https://www.npmjs.com/package/@doderasoftware/restify-ai)
|
|
1041
1455
|
- [๐ข BinarCode](https://binarcode.com)
|
|
1042
|
-
- [๐ป GitHub](https://github.com/BinarCode/
|
|
1456
|
+
- [๐ป GitHub](https://github.com/BinarCode/restify-chatbot)
|
|
1043
1457
|
|
|
1044
1458
|
## ๐ License
|
|
1045
1459
|
|
|
@@ -1047,4 +1461,4 @@ MIT ยฉ [BinarCode](https://binarcode.com)
|
|
|
1047
1461
|
|
|
1048
1462
|
---
|
|
1049
1463
|
|
|
1050
|
-
Built with โค๏ธ by the [BinarCode](https://binarcode.com) team
|
|
1464
|
+
Built with โค๏ธ by the [BinarCode](https://binarcode.com) team.
|