@doderasoftware/restify-ai 0.1.0-beta.1 โ 0.1.0-beta.11
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 +937 -401
- package/dist/components/AiAvatar.vue.d.ts.map +1 -1
- package/dist/components/AiChatDrawer.vue.d.ts +7 -3
- package/dist/components/AiChatDrawer.vue.d.ts.map +1 -1
- package/dist/components/AiEmptyState.vue.d.ts.map +1 -1
- package/dist/components/ChatInput.vue.d.ts +3 -1
- package/dist/components/ChatInput.vue.d.ts.map +1 -1
- package/dist/components/ChatMessage.vue.d.ts.map +1 -1
- package/dist/components/ChatMessageActions.vue.d.ts.map +1 -1
- package/dist/components/ErrorBoundary.vue.d.ts +74 -0
- package/dist/components/ErrorBoundary.vue.d.ts.map +1 -0
- package/dist/components/MentionList.vue.d.ts.map +1 -1
- package/dist/components/UserAvatar.vue.d.ts.map +1 -1
- package/dist/components/drawer/ConfirmDialog.vue.d.ts.map +1 -1
- 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/drawer/SetupGuide.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/input/AttachmentsPreview.vue.d.ts.map +1 -1
- package/dist/components/input/InputActions.vue.d.ts.map +1 -1
- package/dist/composables/useKeyboardShortcut.d.ts +9 -5
- package/dist/composables/useKeyboardShortcut.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/restify-ai.js +2159 -1984
- package/dist/restify-ai.umd.cjs +52 -52
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/storage.d.ts +30 -0
- package/dist/store/storage.d.ts.map +1 -0
- package/dist/store/store.d.ts +246 -0
- package/dist/store/store.d.ts.map +1 -0
- package/dist/store/utils.d.ts +23 -0
- package/dist/store/utils.d.ts.map +1 -0
- package/dist/store.d.ts +1 -246
- package/dist/store.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/api.d.ts +45 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/chat.d.ts +56 -0
- package/dist/types/chat.d.ts.map +1 -0
- package/dist/types/config.d.ts +170 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/index.d.ts +15 -599
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/mentions.d.ts +34 -0
- package/dist/types/mentions.d.ts.map +1 -0
- package/dist/types/setup.d.ts +13 -0
- package/dist/types/setup.d.ts.map +1 -0
- package/dist/types/slots.d.ts +33 -0
- package/dist/types/slots.d.ts.map +1 -0
- package/dist/types/suggestions.d.ts +32 -0
- package/dist/types/suggestions.d.ts.map +1 -0
- package/dist/types/texts.d.ts +57 -0
- package/dist/types/texts.d.ts.map +1 -0
- package/dist/types/ui.d.ts +102 -0
- package/dist/types/ui.d.ts.map +1 -0
- package/package.json +31 -11
package/README.md
CHANGED
|
@@ -1,514 +1,1050 @@
|
|
|
1
1
|
# @doderasoftware/restify-ai
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A production-ready AI chatbot component for Vue 3 with real-time SSE streaming, file attachments, @mentions, and seamless [Laravel Restify](https://laravel-restify.com) integration.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@doderasoftware/restify-ai)
|
|
6
|
-
[](https://vuejs.org/)
|
|
5
|
+
[](https://www.npmjs.com/package/@doderasoftware/restify-ai)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://vuejs.org/)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
|
|
10
|
+
**๐ [Laravel Restify](https://laravel-restify.com) | ๐ฆ [npm](https://www.npmjs.com/package/@doderasoftware/restify-ai) | ๐ข [BinarCode](https://binarcode.com)**
|
|
8
11
|
|
|
9
12
|
---
|
|
10
13
|
|
|
11
14
|
## โจ Features
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
- ๐ **Real-time SSE Streaming** - Smooth character-by-character response streaming
|
|
17
|
+
- ๐ **File Attachments** - Upload and process documents, images, and more
|
|
18
|
+
- ๐ฅ **@Mentions System** - Reference entities from your application (employees, jobs, projects, etc.)
|
|
19
|
+
- ๐ก **Context-Aware Suggestions** - Smart prompts based on current page/route
|
|
20
|
+
- ๐ฌ **Chat History** - Persistent conversation memory with configurable limits
|
|
21
|
+
- ๐ **Markdown Rendering** - Beautiful formatting with syntax highlighting
|
|
22
|
+
- ๐ **Quota Management** - Track and display API usage limits
|
|
23
|
+
- ๐จ **Fully Customizable** - Override any style with CSS classes
|
|
24
|
+
- ๐ **Dark Mode Support** - Automatic dark/light theme detection
|
|
25
|
+
- ๐ฑ **Responsive Design** - Works on desktop, tablet, and mobile
|
|
26
|
+
- โจ๏ธ **Keyboard Shortcuts** - Quick access with configurable shortcuts
|
|
27
|
+
- ๐ณ **Fullscreen Mode** - Expandable chat interface
|
|
28
|
+
- ๐ฏ **TypeScript First** - Full type definitions included
|
|
29
|
+
- ๐๏ธ **Pinia Integration** - State management built-in
|
|
30
|
+
- ๐ง **Slot-Based Customization** - Override any component section
|
|
31
|
+
- ๐ **i18n Ready** - Full internationalization support
|
|
32
|
+
- ๐ **Support Mode** - Route conversations to human support
|
|
33
|
+
- ๐ **Retry Logic** - Automatic retry with configurable backoff
|
|
29
34
|
|
|
30
35
|
## ๐ฆ Installation
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
```bash
|
|
33
38
|
npm install @doderasoftware/restify-ai
|
|
34
|
-
|
|
35
|
-
pnpm add @doderasoftware/restify-ai
|
|
36
|
-
\`\`\`
|
|
39
|
+
```
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
### Peer Dependencies
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
```bash
|
|
44
|
+
npm install vue@^3.3.0 pinia@^2.1.0
|
|
45
|
+
```
|
|
41
46
|
|
|
42
47
|
## ๐ Quick Start
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
### 1. Import Styles
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// main.ts
|
|
53
|
+
import '@doderasoftware/restify-ai/styles'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Create Plugin Configuration
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// plugins/restifyAi.ts
|
|
60
|
+
import type { App } from 'vue'
|
|
61
|
+
import { RestifyAiPlugin } from '@doderasoftware/restify-ai'
|
|
62
|
+
import '@doderasoftware/restify-ai/styles'
|
|
63
|
+
|
|
64
|
+
export function setupRestifyAi(app: App) {
|
|
65
|
+
app.use(RestifyAiPlugin, {
|
|
66
|
+
endpoints: {
|
|
67
|
+
ask: '/ask',
|
|
68
|
+
uploadFile: '/ai/upload',
|
|
69
|
+
quota: '/ai/quota',
|
|
70
|
+
},
|
|
71
|
+
baseUrl: import.meta.env.VITE_API_URL,
|
|
72
|
+
getAuthToken: () => localStorage.getItem('token'),
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Register in Main
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
45
80
|
// main.ts
|
|
46
81
|
import { createApp } from 'vue'
|
|
47
82
|
import { createPinia } from 'pinia'
|
|
48
|
-
import
|
|
83
|
+
import App from './App.vue'
|
|
84
|
+
import { setupRestifyAi } from './plugins/restifyAi'
|
|
49
85
|
import '@doderasoftware/restify-ai/styles'
|
|
50
86
|
|
|
51
87
|
const app = createApp(App)
|
|
52
88
|
app.use(createPinia())
|
|
53
|
-
|
|
54
|
-
app.use(RestifyAiPlugin, {
|
|
55
|
-
endpoints: {
|
|
56
|
-
ask: '/api/ai/ask', // Required - SSE streaming endpoint
|
|
57
|
-
quota: '/api/ai/quota', // Optional - quota endpoint
|
|
58
|
-
uploadFile: '/api/ai/upload', // Optional - file upload
|
|
59
|
-
},
|
|
60
|
-
getAuthToken: () => localStorage.getItem('token'),
|
|
61
|
-
})
|
|
62
|
-
|
|
89
|
+
setupRestifyAi(app)
|
|
63
90
|
app.mount('#app')
|
|
64
|
-
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 4. Add the Component
|
|
65
94
|
|
|
66
|
-
|
|
67
|
-
<!-- App.vue -->
|
|
95
|
+
```vue
|
|
68
96
|
<template>
|
|
69
|
-
<AiChatDrawer v-model="showChat" />
|
|
70
|
-
<button @click="showChat = true">Open AI Chat</button>
|
|
97
|
+
<AiChatDrawer v-model="aiStore.showChat" top-offset="50px" />
|
|
71
98
|
</template>
|
|
72
99
|
|
|
73
|
-
<script setup>
|
|
74
|
-
import {
|
|
75
|
-
import { AiChatDrawer } from '@doderasoftware/restify-ai'
|
|
100
|
+
<script setup lang="ts">
|
|
101
|
+
import { AiChatDrawer, useRestifyAiStore } from '@doderasoftware/restify-ai'
|
|
76
102
|
|
|
77
|
-
const
|
|
103
|
+
const aiStore = useRestifyAiStore()
|
|
78
104
|
</script>
|
|
79
|
-
|
|
105
|
+
```
|
|
80
106
|
|
|
81
|
-
|
|
107
|
+
### 5. Enable Keyboard Shortcut (Optional)
|
|
82
108
|
|
|
83
|
-
|
|
109
|
+
```typescript
|
|
110
|
+
// In your layout or App.vue
|
|
111
|
+
import { useAiDrawerShortcut } from '@doderasoftware/restify-ai'
|
|
84
112
|
|
|
85
|
-
|
|
113
|
+
useAiDrawerShortcut() // Enables Cmd/Ctrl+G to toggle
|
|
114
|
+
```
|
|
86
115
|
|
|
87
|
-
|
|
88
|
-
|--------|------|-------------|
|
|
89
|
-
| \`endpoints.ask\` | \`string\` | SSE streaming endpoint |
|
|
90
|
-
| \`getAuthToken\` | \`() => string \| Promise<string>\` | Auth token getter |
|
|
116
|
+
## โ๏ธ Configuration
|
|
91
117
|
|
|
92
|
-
###
|
|
118
|
+
### Full Configuration Options
|
|
93
119
|
|
|
94
|
-
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
```typescript
|
|
121
|
+
import type { App } from 'vue'
|
|
122
|
+
import { RestifyAiPlugin } from '@doderasoftware/restify-ai'
|
|
123
|
+
import type { MentionProvider, SuggestionProvider, AISuggestion } from '@doderasoftware/restify-ai'
|
|
124
|
+
|
|
125
|
+
export function setupRestifyAi(app: App) {
|
|
126
|
+
app.use(RestifyAiPlugin, {
|
|
127
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
128
|
+
// REQUIRED
|
|
129
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
130
|
+
|
|
131
|
+
endpoints: {
|
|
132
|
+
ask: '/ask', // SSE streaming endpoint
|
|
133
|
+
uploadFile: '/ai/upload', // File upload endpoint
|
|
134
|
+
quota: '/ai/quota', // Quota fetch endpoint
|
|
135
|
+
},
|
|
136
|
+
getAuthToken: () => localStorage.getItem('token'),
|
|
137
|
+
|
|
138
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
139
|
+
// API CONFIGURATION
|
|
140
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
141
|
+
|
|
142
|
+
baseUrl: 'https://api.example.com',
|
|
143
|
+
|
|
144
|
+
// Custom headers for every request
|
|
145
|
+
getCustomHeaders: () => ({
|
|
146
|
+
'X-Tenant-ID': getTenantId(),
|
|
147
|
+
'Accept-Language': getCurrentLocale(),
|
|
148
|
+
}),
|
|
149
|
+
|
|
150
|
+
// Transform request payload before sending
|
|
151
|
+
buildRequest: (payload) => ({
|
|
152
|
+
...payload,
|
|
153
|
+
customField: 'value',
|
|
154
|
+
}),
|
|
155
|
+
|
|
156
|
+
// Parse SSE stream content (default handles OpenAI format)
|
|
157
|
+
parseStreamContent: (data) => {
|
|
158
|
+
const parsed = JSON.parse(data)
|
|
159
|
+
return parsed.choices?.[0]?.delta?.content || null
|
|
160
|
+
},
|
|
109
161
|
|
|
110
|
-
|
|
162
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
163
|
+
// RETRY CONFIGURATION
|
|
164
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
165
|
+
|
|
166
|
+
retry: {
|
|
167
|
+
maxRetries: 3,
|
|
168
|
+
retryDelay: 1000,
|
|
169
|
+
shouldRetry: (error, attempt) => attempt < 3,
|
|
170
|
+
},
|
|
111
171
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
172
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
173
|
+
// INTERNATIONALIZATION
|
|
174
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
175
|
+
|
|
176
|
+
// Translate function for i18n integration
|
|
177
|
+
translate: (key, params) => i18n.t(key, params),
|
|
178
|
+
|
|
179
|
+
// Permission check function
|
|
180
|
+
can: (permission) => userCan(permission),
|
|
181
|
+
|
|
182
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
183
|
+
// LABELS (all customizable)
|
|
184
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
185
|
+
|
|
186
|
+
labels: {
|
|
187
|
+
title: 'AI Assistant',
|
|
188
|
+
aiName: 'AI Assistant',
|
|
189
|
+
you: 'You',
|
|
190
|
+
newChat: 'New chat',
|
|
191
|
+
placeholder: 'Ask me anything...',
|
|
192
|
+
inputPlaceholder: 'Ask me anything...',
|
|
193
|
+
supportPlaceholder: 'Describe your issue...',
|
|
194
|
+
loadingText: 'Gathering data...',
|
|
195
|
+
analyzingText: 'Analyzing...',
|
|
196
|
+
craftingText: 'Crafting response...',
|
|
197
|
+
quotaRemaining: 'questions remaining',
|
|
198
|
+
noQuota: 'No AI credit available',
|
|
199
|
+
contactSupport: 'Contact Support',
|
|
200
|
+
close: 'Close',
|
|
201
|
+
minimize: 'Minimize',
|
|
202
|
+
fullscreen: 'Fullscreen',
|
|
203
|
+
exitFullscreen: 'Exit fullscreen',
|
|
204
|
+
copyToClipboard: 'Copy to clipboard',
|
|
205
|
+
copied: 'Content copied to clipboard',
|
|
206
|
+
showMore: 'Show more',
|
|
207
|
+
showLess: 'Show less',
|
|
208
|
+
retry: 'Retry',
|
|
209
|
+
attachFiles: 'Attach files',
|
|
210
|
+
emptyStateTitle: 'How can I help you today?',
|
|
211
|
+
emptyStateDescription: 'Ask questions or get help with tasks',
|
|
212
|
+
keyboardShortcutHint: 'Press โG to toggle',
|
|
213
|
+
sendMessage: 'Send message',
|
|
214
|
+
attachFile: 'Attach file',
|
|
215
|
+
closeConfirmTitle: 'Close chat window?',
|
|
216
|
+
closeConfirmMessage: 'You will lose the conversation.',
|
|
217
|
+
confirmClose: 'Yes, close it',
|
|
218
|
+
cancel: 'Cancel',
|
|
219
|
+
toggleSupportMode: 'Contact Support',
|
|
220
|
+
exitSupportMode: 'Exit Support Mode',
|
|
221
|
+
historyLimitReachedTitle: 'Conversation Limit Reached',
|
|
222
|
+
historyLimitReachedMessage: 'Maximum messages reached.',
|
|
223
|
+
startNewChat: 'New Chat',
|
|
224
|
+
},
|
|
128
225
|
|
|
129
|
-
|
|
226
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
227
|
+
// MENTION PROVIDERS
|
|
228
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
229
|
+
|
|
230
|
+
mentionProviders: createMentionProviders(),
|
|
231
|
+
|
|
232
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
233
|
+
// SUGGESTION PROVIDERS
|
|
234
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
235
|
+
|
|
236
|
+
suggestionProviders: createSuggestionProviders(),
|
|
237
|
+
defaultSuggestions: getDefaultSuggestions(),
|
|
238
|
+
|
|
239
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
240
|
+
// THEME
|
|
241
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
242
|
+
|
|
243
|
+
theme: {
|
|
244
|
+
primaryColor: '#3b82f6',
|
|
245
|
+
primaryLightColor: '#60a5fa',
|
|
246
|
+
userBubbleColor: '#3b82f6',
|
|
247
|
+
userTextColor: '#ffffff',
|
|
248
|
+
borderColor: '#e5e7eb',
|
|
249
|
+
drawerWidth: '600px',
|
|
250
|
+
drawerFullscreenWidth: '90vw',
|
|
251
|
+
},
|
|
130
252
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
253
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
254
|
+
// LIMITS
|
|
255
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
256
|
+
|
|
257
|
+
chatHistoryLimit: 20, // Maximum user messages per conversation
|
|
258
|
+
maxAttachments: 5,
|
|
259
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
260
|
+
acceptedFileTypes: 'image/*,.pdf,.txt,.doc,.docx,.xls,.xlsx,.csv',
|
|
261
|
+
|
|
262
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
263
|
+
// STORAGE
|
|
264
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
265
|
+
|
|
266
|
+
chatHistoryKey: 'app_chat_history',
|
|
267
|
+
drawerStateKey: 'app_chat_drawer_open',
|
|
268
|
+
|
|
269
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
270
|
+
// FEATURES
|
|
271
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
272
|
+
|
|
273
|
+
keyboardShortcut: 'mod+g', // 'mod' = Cmd on Mac, Ctrl on Windows
|
|
274
|
+
enableSupportMode: true,
|
|
275
|
+
canToggle: () => true,
|
|
276
|
+
|
|
277
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
278
|
+
// AVATARS
|
|
279
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
280
|
+
|
|
281
|
+
assistantAvatar: CustomAiAvatarComponent,
|
|
282
|
+
userAvatar: () => authStore.profile?.avatar || null,
|
|
283
|
+
|
|
284
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
285
|
+
// CALLBACKS
|
|
286
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
287
|
+
|
|
288
|
+
onError: (error) => console.error('AI Error:', error),
|
|
289
|
+
onQuotaFetched: (quota) => console.log('Quota:', quota),
|
|
290
|
+
onMessageSent: (message) => analytics.track('ai_message_sent'),
|
|
291
|
+
onResponseReceived: (message) => console.log('Response:', message),
|
|
292
|
+
onDrawerToggle: (isOpen) => console.log('Drawer:', isOpen),
|
|
293
|
+
onNewChat: () => console.log('New chat started'),
|
|
294
|
+
|
|
295
|
+
// Stream lifecycle hooks
|
|
296
|
+
onStreamStart: () => console.log('Stream started'),
|
|
297
|
+
onStreamEnd: (fullMessage) => console.log('Stream ended'),
|
|
298
|
+
onStreamChunk: (chunk) => console.log('Chunk:', chunk),
|
|
299
|
+
beforeSend: (payload) => payload,
|
|
300
|
+
afterResponse: (message) => {},
|
|
301
|
+
|
|
302
|
+
// File upload hooks
|
|
303
|
+
onFileUploadStart: (file) => {},
|
|
304
|
+
onFileUploadProgress: (file, progress) => {},
|
|
305
|
+
onFileUploadComplete: (file) => {},
|
|
306
|
+
onFileUploadError: (file, error) => {},
|
|
307
|
+
})
|
|
138
308
|
}
|
|
139
|
-
|
|
309
|
+
```
|
|
140
310
|
|
|
141
|
-
|
|
311
|
+
## ๐ AiChatDrawer Props
|
|
142
312
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
313
|
+
| Prop | Type | Default | Description |
|
|
314
|
+
|------|------|---------|-------------|
|
|
315
|
+
| `modelValue` | `boolean` | required | Controls drawer visibility (v-model) |
|
|
316
|
+
| `width` | `string` | `"600px"` | Drawer width |
|
|
317
|
+
| `fullscreenWidth` | `string` | `"90vw"` | Width when in fullscreen mode |
|
|
318
|
+
| `topOffset` | `string` | `"0"` | Top offset for fixed headers |
|
|
319
|
+
| `position` | `"left" \| "right"` | `"right"` | Drawer position |
|
|
320
|
+
| `showBackdrop` | `boolean` | `false` | Show backdrop overlay |
|
|
321
|
+
| `closeOnBackdropClick` | `boolean` | `false` | Close when clicking backdrop |
|
|
322
|
+
| `closeOnEscape` | `boolean` | `true` | Close on Escape key |
|
|
323
|
+
| `showQuota` | `boolean` | `true` | Show quota display (API usage remaining) |
|
|
324
|
+
| `showMessageCount` | `boolean` | `true` | Show message count badge (X/20 format) |
|
|
325
|
+
| `showFullscreenToggle` | `boolean` | `true` | Show fullscreen button |
|
|
326
|
+
| `showMinimizeButton` | `boolean` | `true` | Show minimize button |
|
|
327
|
+
| `showCloseButton` | `boolean` | `true` | Show close button |
|
|
328
|
+
| `showNewChatButton` | `boolean` | `true` | Show new chat button |
|
|
329
|
+
| `confirmClose` | `boolean` | `true` | Confirm before clearing history |
|
|
330
|
+
| `autoFetchQuota` | `boolean` | `true` | Auto-fetch quota when opened |
|
|
331
|
+
| `historyLimit` | `HistoryLimitConfig` | - | History limit configuration |
|
|
332
|
+
| `loadingText` | `LoadingTextConfig` | - | Loading text configuration |
|
|
333
|
+
| `ui` | `AiChatDrawerUI` | `{}` | Custom CSS classes |
|
|
334
|
+
| `texts` | `AiChatDrawerTexts` | `{}` | Custom text labels |
|
|
335
|
+
|
|
336
|
+
### Component Usage Example
|
|
337
|
+
|
|
338
|
+
```vue
|
|
339
|
+
<template>
|
|
340
|
+
<AiChatDrawer
|
|
341
|
+
v-model="aiStore.showChat"
|
|
342
|
+
top-offset="50px"
|
|
343
|
+
:show-backdrop="false"
|
|
344
|
+
:confirm-close="true"
|
|
345
|
+
:show-quota="true"
|
|
346
|
+
@contact-support="handleContactSupport"
|
|
347
|
+
>
|
|
348
|
+
<template #context-link>
|
|
349
|
+
<p class="text-center text-xs text-gray-500">
|
|
350
|
+
For accurate results, provide
|
|
351
|
+
<router-link to="/settings/ai-context" class="text-primary-600 hover:underline">
|
|
352
|
+
company context
|
|
353
|
+
</router-link>
|
|
354
|
+
</p>
|
|
355
|
+
</template>
|
|
356
|
+
</AiChatDrawer>
|
|
357
|
+
</template>
|
|
170
358
|
|
|
171
|
-
|
|
359
|
+
<script setup lang="ts">
|
|
360
|
+
import { AiChatDrawer, useRestifyAiStore, useAiDrawerShortcut } from '@doderasoftware/restify-ai'
|
|
172
361
|
|
|
173
|
-
|
|
174
|
-
{
|
|
175
|
-
chatHistoryLimit: 15, // Max messages
|
|
176
|
-
maxAttachments: 5, // Max files per message
|
|
177
|
-
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
178
|
-
acceptedFileTypes: 'image/*,.pdf,.txt',
|
|
179
|
-
chatHistoryKey: 'my_chat_history', // sessionStorage key
|
|
180
|
-
drawerStateKey: 'my_drawer_state', // localStorage key
|
|
181
|
-
}
|
|
182
|
-
\`\`\`
|
|
362
|
+
const aiStore = useRestifyAiStore()
|
|
183
363
|
|
|
184
|
-
|
|
364
|
+
useAiDrawerShortcut()
|
|
185
365
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
keyboardShortcut: 'cmd+g', // null to disable
|
|
189
|
-
enableSupportMode: true, // Support request toggle
|
|
366
|
+
function handleContactSupport() {
|
|
367
|
+
// Handle support request
|
|
190
368
|
}
|
|
191
|
-
|
|
369
|
+
</script>
|
|
370
|
+
```
|
|
192
371
|
|
|
193
|
-
|
|
372
|
+
## ๐ก Events
|
|
194
373
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
374
|
+
| Event | Payload | Description |
|
|
375
|
+
|-------|---------|-------------|
|
|
376
|
+
| `update:modelValue` | `boolean` | Drawer state changed |
|
|
377
|
+
| `close` | - | Drawer was closed |
|
|
378
|
+
| `contact-support` | - | Support mode activated |
|
|
379
|
+
| `new-chat` | - | New chat started |
|
|
201
380
|
|
|
202
|
-
|
|
381
|
+
## ๐ฐ Slots
|
|
203
382
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
383
|
+
| Slot | Props | Description |
|
|
384
|
+
|------|-------|-------------|
|
|
385
|
+
| `header` | `HeaderSlotProps` | Custom header content |
|
|
386
|
+
| `quota` | `{ quota: ChatQuota }` | Custom quota display |
|
|
387
|
+
| `setup` | - | Custom setup guide |
|
|
388
|
+
| `empty-state` | `{ suggestions, onClick }` | Custom empty state |
|
|
389
|
+
| `message` | `MessageSlotProps` | Custom message bubble |
|
|
390
|
+
| `input` | `InputSlotProps` | Custom input area |
|
|
391
|
+
| `context-link` | - | Custom context link below input |
|
|
392
|
+
|
|
393
|
+
### Slot Props Types
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
interface HeaderSlotProps {
|
|
397
|
+
quota: ChatQuota
|
|
398
|
+
isFullscreen: boolean
|
|
399
|
+
hasHistory: boolean
|
|
400
|
+
onNewChat: () => void
|
|
401
|
+
onClose: () => void
|
|
402
|
+
onMinimize: () => void
|
|
403
|
+
onToggleFullscreen: () => void
|
|
225
404
|
}
|
|
226
|
-
\`\`\`
|
|
227
405
|
|
|
228
|
-
|
|
406
|
+
interface MessageSlotProps {
|
|
407
|
+
message: ChatMessage
|
|
408
|
+
isUser: boolean
|
|
409
|
+
isLoading: boolean
|
|
410
|
+
isStreaming: boolean
|
|
411
|
+
}
|
|
229
412
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
// 40+ customizable labels
|
|
237
|
-
},
|
|
238
|
-
translate: (key, params) => i18n.t(key, params),
|
|
239
|
-
can: (permission) => user.hasPermission(permission),
|
|
413
|
+
interface InputSlotProps {
|
|
414
|
+
modelValue: string
|
|
415
|
+
sending: boolean
|
|
416
|
+
disabled: boolean
|
|
417
|
+
onSubmit: (payload: SubmitPayload) => void
|
|
418
|
+
onCancel: () => void
|
|
240
419
|
}
|
|
241
|
-
|
|
420
|
+
```
|
|
242
421
|
|
|
243
|
-
|
|
422
|
+
## ๐ช Store API
|
|
244
423
|
|
|
245
|
-
|
|
424
|
+
```typescript
|
|
425
|
+
import { useRestifyAiStore } from '@doderasoftware/restify-ai'
|
|
246
426
|
|
|
247
|
-
|
|
427
|
+
const store = useRestifyAiStore()
|
|
248
428
|
|
|
249
|
-
|
|
429
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
430
|
+
// STATE
|
|
431
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
432
|
+
|
|
433
|
+
store.chatHistory // ChatMessage[] - All messages
|
|
434
|
+
store.showChat // boolean - Drawer visibility
|
|
435
|
+
store.sending // boolean - Message being sent
|
|
436
|
+
store.loading // boolean - Loading state
|
|
437
|
+
store.isFullscreen // boolean - Fullscreen mode
|
|
438
|
+
store.quota // { limit, used, remaining }
|
|
439
|
+
store.error // { message, failedQuestion, failedAttachments, timestamp }
|
|
440
|
+
store.supportRequestMode // boolean - Support mode active
|
|
441
|
+
store.pageContext // PageContext | null - Current page context
|
|
442
|
+
|
|
443
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
444
|
+
// GETTERS
|
|
445
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
446
|
+
|
|
447
|
+
store.hasMessages // boolean - Has any messages
|
|
448
|
+
store.isInSetupMode // boolean - In setup mode
|
|
449
|
+
store.canChat // boolean - Can send messages
|
|
450
|
+
|
|
451
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
452
|
+
// ACTIONS
|
|
453
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
454
|
+
|
|
455
|
+
store.toggleDrawer() // Toggle drawer visibility
|
|
456
|
+
store.openDrawer() // Open drawer
|
|
457
|
+
store.closeDrawer() // Close drawer
|
|
458
|
+
store.askQuestion(question, attachments?, mentions?, isSupportRequest?)
|
|
459
|
+
store.cancelRequest() // Cancel current request
|
|
460
|
+
store.retry() // Retry failed message
|
|
461
|
+
store.clearChatHistory() // Clear all messages
|
|
462
|
+
store.clearError() // Clear error state
|
|
463
|
+
store.toggleSupportMode() // Toggle support mode
|
|
464
|
+
store.fetchQuota() // Fetch quota from server
|
|
465
|
+
store.uploadFile(file) // Upload file
|
|
466
|
+
store.setPageContext(context) // Set page context
|
|
467
|
+
store.scrollToBottom() // Scroll chat to bottom
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## ๐ช Composables
|
|
471
|
+
|
|
472
|
+
### useAiDrawerShortcut
|
|
473
|
+
|
|
474
|
+
Toggle drawer with keyboard shortcut:
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
import { useAiDrawerShortcut } from '@doderasoftware/restify-ai'
|
|
478
|
+
|
|
479
|
+
// Uses store's showChat state directly
|
|
480
|
+
useAiDrawerShortcut()
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### usePageAiContext
|
|
484
|
+
|
|
485
|
+
Set page context for AI suggestions:
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
import { usePageAiContext } from '@doderasoftware/restify-ai'
|
|
489
|
+
|
|
490
|
+
// Simple usage
|
|
491
|
+
usePageAiContext('invoices')
|
|
492
|
+
|
|
493
|
+
// With dynamic metadata
|
|
494
|
+
usePageAiContext('employee-detail', {
|
|
495
|
+
employeeId: computed(() => route.params.id),
|
|
496
|
+
employeeName: computed(() => employee.value?.name),
|
|
497
|
+
})
|
|
498
|
+
```
|
|
250
499
|
|
|
251
|
-
|
|
500
|
+
### useAiContext
|
|
252
501
|
|
|
253
|
-
|
|
254
|
-
|------|------|---------|-------------|
|
|
255
|
-
| \`v-model\` | \`boolean\` | - | Drawer visibility |
|
|
256
|
-
| \`width\` | \`string\` | \`'600px'\` | Drawer width |
|
|
257
|
-
| \`fullscreenWidth\` | \`string\` | \`'90vw'\` | Fullscreen width |
|
|
258
|
-
| \`position\` | \`'left' \| 'right'\` | \`'right'\` | Drawer position |
|
|
259
|
-
| \`showBackdrop\` | \`boolean\` | \`false\` | Show backdrop overlay |
|
|
260
|
-
| \`closeOnBackdropClick\` | \`boolean\` | \`false\` | Close on backdrop click |
|
|
261
|
-
| \`closeOnEscape\` | \`boolean\` | \`true\` | Close on Escape key |
|
|
262
|
-
| \`showQuota\` | \`boolean\` | \`true\` | Show quota display |
|
|
263
|
-
| \`showFullscreenToggle\` | \`boolean\` | \`true\` | Show fullscreen button |
|
|
264
|
-
| \`showMinimizeButton\` | \`boolean\` | \`true\` | Show minimize button |
|
|
265
|
-
| \`showCloseButton\` | \`boolean\` | \`true\` | Show close button |
|
|
266
|
-
| \`showNewChatButton\` | \`boolean\` | \`true\` | Show new chat button |
|
|
267
|
-
| \`confirmClose\` | \`boolean\` | \`true\` | Confirm before closing |
|
|
268
|
-
| \`ui\` | \`AiChatDrawerUI\` | - | Custom CSS classes |
|
|
269
|
-
| \`texts\` | \`AiChatDrawerTexts\` | - | Custom text labels |
|
|
270
|
-
| \`historyLimit\` | \`HistoryLimitConfig\` | - | History limit config |
|
|
271
|
-
| \`loadingText\` | \`LoadingTextConfig\` | - | Dynamic loading text |
|
|
272
|
-
|
|
273
|
-
#### Events
|
|
502
|
+
Programmatic context control:
|
|
274
503
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
| \`close\` | - | Drawer closed |
|
|
278
|
-
| \`contact-support\` | - | Support mode triggered |
|
|
279
|
-
| \`new-chat\` | - | New chat started |
|
|
504
|
+
```typescript
|
|
505
|
+
import { useAiContext } from '@doderasoftware/restify-ai'
|
|
280
506
|
|
|
281
|
-
|
|
507
|
+
const { setContext, updateContext, clearContext, context } = useAiContext()
|
|
282
508
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
| \`setup\` | - | Custom setup guide |
|
|
291
|
-
| \`context-link\` | - | Link above input |
|
|
292
|
-
|
|
293
|
-
### Other Components
|
|
294
|
-
|
|
295
|
-
| Component | Description |
|
|
296
|
-
|-----------|-------------|
|
|
297
|
-
| \`<ChatInput>\` | Message input with attachments & mentions |
|
|
298
|
-
| \`<ChatMessage>\` | Single message display |
|
|
299
|
-
| \`<AiEmptyState>\` | Empty state with suggestions |
|
|
300
|
-
| \`<MentionList>\` | @mention dropdown |
|
|
301
|
-
| \`<AiAvatar>\` | AI avatar icon |
|
|
302
|
-
| \`<UserAvatar>\` | User avatar icon |
|
|
303
|
-
| \`<ChatMessageActions>\` | Copy/action buttons |
|
|
304
|
-
|
|
305
|
-
All components accept \`:ui\` and \`:texts\` props for full customization.
|
|
509
|
+
setContext({
|
|
510
|
+
pageType: 'dashboard',
|
|
511
|
+
entityType: 'report',
|
|
512
|
+
entityId: '123',
|
|
513
|
+
metadata: { period: 'Q4' }
|
|
514
|
+
})
|
|
515
|
+
```
|
|
306
516
|
|
|
307
|
-
|
|
517
|
+
### useAiSuggestions
|
|
308
518
|
|
|
309
|
-
|
|
519
|
+
Get suggestions for current context:
|
|
310
520
|
|
|
311
|
-
|
|
521
|
+
```typescript
|
|
522
|
+
import { useAiSuggestions } from '@doderasoftware/restify-ai'
|
|
312
523
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
:ui="{
|
|
316
|
-
drawer: 'my-drawer-class',
|
|
317
|
-
header: 'my-header-class',
|
|
318
|
-
body: 'my-body-class',
|
|
319
|
-
}"
|
|
320
|
-
:texts="{
|
|
321
|
-
title: 'My AI',
|
|
322
|
-
placeholder: 'Ask anything...',
|
|
323
|
-
}"
|
|
324
|
-
/>
|
|
325
|
-
\`\`\`
|
|
524
|
+
const { suggestions, resolvePrompt } = useAiSuggestions()
|
|
525
|
+
```
|
|
326
526
|
|
|
327
|
-
|
|
527
|
+
## ๐ท๏ธ Mention Providers
|
|
328
528
|
|
|
329
|
-
|
|
330
|
-
- \`ChatInputUI\` - root, textarea, sendButton, attachButton, etc.
|
|
331
|
-
- \`ChatMessageUI\` - userBubble, assistantBubble, loadingDots, etc.
|
|
332
|
-
- \`AiEmptyStateUI\` - grid, suggestionCard, title, etc.
|
|
333
|
-
- \`MentionListUI\` - item, itemSelected, groupHeader, etc.
|
|
529
|
+
Enable @mentions to reference entities from your application:
|
|
334
530
|
|
|
335
|
-
|
|
531
|
+
```typescript
|
|
532
|
+
import type { MentionProvider, MentionItem } from '@doderasoftware/restify-ai'
|
|
336
533
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
534
|
+
function createMentionProviders(): MentionProvider[] {
|
|
535
|
+
return [
|
|
536
|
+
{
|
|
537
|
+
type: 'employee',
|
|
538
|
+
label: 'Team Members',
|
|
539
|
+
iconClass: 'text-primary',
|
|
540
|
+
priority: 10,
|
|
541
|
+
|
|
542
|
+
// Search function - can be sync or async
|
|
543
|
+
search: (query: string): MentionItem[] => {
|
|
544
|
+
const employeeStore = useEmployeeStore()
|
|
545
|
+
const employees = employeeStore.allEmployees || []
|
|
546
|
+
|
|
547
|
+
if (!query) {
|
|
548
|
+
return employees.slice(0, 5).map(emp => ({
|
|
549
|
+
id: emp.id,
|
|
550
|
+
type: 'employee',
|
|
551
|
+
attributes: emp.attributes,
|
|
552
|
+
}))
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const lowerQuery = query.toLowerCase()
|
|
556
|
+
return employees
|
|
557
|
+
.filter((emp) => {
|
|
558
|
+
const firstName = emp.attributes?.first_name?.toLowerCase() || ''
|
|
559
|
+
const lastName = emp.attributes?.last_name?.toLowerCase() || ''
|
|
560
|
+
return firstName.includes(lowerQuery) || lastName.includes(lowerQuery)
|
|
561
|
+
})
|
|
562
|
+
.slice(0, 5)
|
|
563
|
+
.map(emp => ({
|
|
564
|
+
id: emp.id,
|
|
565
|
+
type: 'employee',
|
|
566
|
+
attributes: emp.attributes,
|
|
567
|
+
}))
|
|
568
|
+
},
|
|
569
|
+
|
|
570
|
+
// Display formatting
|
|
571
|
+
getDisplayName: (item: MentionItem): string => {
|
|
572
|
+
const firstName = item.attributes?.first_name || ''
|
|
573
|
+
const lastName = item.attributes?.last_name || ''
|
|
574
|
+
return `${firstName} ${lastName}`.trim()
|
|
575
|
+
},
|
|
576
|
+
|
|
577
|
+
getSubtitle: (item: MentionItem): string | null => {
|
|
578
|
+
return item.attributes?.position || null
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
buildMentionText: (item: MentionItem): string => {
|
|
582
|
+
const firstName = item.attributes?.first_name || ''
|
|
583
|
+
const lastName = item.attributes?.last_name || ''
|
|
584
|
+
return `@${firstName} ${lastName}`.trim()
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
type: 'job',
|
|
589
|
+
label: 'Jobs',
|
|
590
|
+
iconClass: 'text-blue-500',
|
|
591
|
+
routes: ['/hiring/jobs'], // Only show on these routes
|
|
592
|
+
priority: 5,
|
|
593
|
+
search: async (query: string): Promise<MentionItem[]> => {
|
|
594
|
+
const response = await api.get('/jobs', { params: { search: query }})
|
|
595
|
+
return response.data.map(job => ({
|
|
596
|
+
id: job.id,
|
|
597
|
+
type: 'job',
|
|
598
|
+
attributes: job,
|
|
599
|
+
}))
|
|
600
|
+
},
|
|
601
|
+
getDisplayName: (item) => item.attributes?.title || 'Untitled',
|
|
602
|
+
getSubtitle: (item) => item.attributes?.location || null,
|
|
603
|
+
},
|
|
604
|
+
]
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### MentionProvider Interface
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
interface MentionProvider {
|
|
612
|
+
type: string // Unique type identifier
|
|
613
|
+
label: string // Display label for group
|
|
614
|
+
icon?: Component // Icon component
|
|
615
|
+
iconClass?: string // Icon CSS classes
|
|
616
|
+
routes?: string[] // Limit to specific routes
|
|
617
|
+
priority?: number // Sort order (higher = first)
|
|
618
|
+
search: (query: string) => Promise<MentionItem[]> | MentionItem[]
|
|
619
|
+
getDisplayName?: (item: MentionItem) => string
|
|
620
|
+
getSubtitle?: (item: MentionItem) => string | null
|
|
621
|
+
buildMentionText?: (item: MentionItem) => string
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
interface MentionItem {
|
|
625
|
+
id: string
|
|
626
|
+
type: string
|
|
627
|
+
name?: string
|
|
628
|
+
label?: string
|
|
629
|
+
title?: string
|
|
630
|
+
attributes?: Record<string, any> | null
|
|
631
|
+
relationships?: Record<string, any> | null
|
|
632
|
+
}
|
|
633
|
+
```
|
|
355
634
|
|
|
356
|
-
|
|
635
|
+
## ๐ก Suggestion Providers
|
|
357
636
|
|
|
358
|
-
|
|
359
|
-
const store = useRestifyAiStore()
|
|
637
|
+
Context-aware suggestions based on current page:
|
|
360
638
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
store.clearChatHistory()
|
|
365
|
-
store.clearError()
|
|
366
|
-
store.fetchQuota()
|
|
367
|
-
store.toggleSupportMode()
|
|
368
|
-
\`\`\`
|
|
369
|
-
|
|
370
|
-
### Store State
|
|
371
|
-
|
|
372
|
-
\`\`\`typescript
|
|
373
|
-
store.chatHistory // ChatMessage[]
|
|
374
|
-
store.sending // boolean
|
|
375
|
-
store.error // ChatError
|
|
376
|
-
store.quota // ChatQuota
|
|
377
|
-
store.showChat // boolean
|
|
378
|
-
store.isFullscreen // boolean
|
|
379
|
-
store.supportRequestMode // boolean
|
|
380
|
-
\`\`\`
|
|
639
|
+
```typescript
|
|
640
|
+
import type { SuggestionProvider, AISuggestion, PageContext } from '@doderasoftware/restify-ai'
|
|
641
|
+
import { UserGroupIcon, ChartBarIcon, CalendarDaysIcon } from '@heroicons/vue/24/outline'
|
|
381
642
|
|
|
382
|
-
|
|
643
|
+
function createSuggestionProviders(): SuggestionProvider[] {
|
|
644
|
+
return [
|
|
645
|
+
{
|
|
646
|
+
id: 'employees',
|
|
647
|
+
routes: ['/employees'],
|
|
648
|
+
priority: 10,
|
|
649
|
+
|
|
650
|
+
getSuggestions: (context: PageContext): AISuggestion[] => {
|
|
651
|
+
const isDetailView = context.entityId
|
|
652
|
+
const employeeName = context.metadata?.employeeName
|
|
653
|
+
|
|
654
|
+
if (isDetailView && employeeName) {
|
|
655
|
+
// Suggestions for employee detail page
|
|
656
|
+
return [
|
|
657
|
+
{
|
|
658
|
+
id: 'performance-review',
|
|
659
|
+
title: 'Prepare Evaluation',
|
|
660
|
+
description: 'Performance review points',
|
|
661
|
+
icon: ChartBarIcon,
|
|
662
|
+
gradientClass: 'bg-gradient-to-br from-violet-500/10 to-purple-500/10',
|
|
663
|
+
prompt: `Help me prepare a performance review for ${employeeName}.`,
|
|
664
|
+
permission: 'manageEmployees',
|
|
665
|
+
},
|
|
666
|
+
]
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Suggestions for employee list
|
|
670
|
+
return [
|
|
671
|
+
{
|
|
672
|
+
id: 'find-available',
|
|
673
|
+
title: 'Who is Available?',
|
|
674
|
+
description: 'See who is not on leave',
|
|
675
|
+
icon: CalendarDaysIcon,
|
|
676
|
+
gradientClass: 'bg-gradient-to-br from-teal-500/10 to-emerald-500/10',
|
|
677
|
+
prompt: 'Who is available today and not on leave?',
|
|
678
|
+
permission: 'manageEmployees',
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
id: 'team-analytics',
|
|
682
|
+
title: 'Team Analysis',
|
|
683
|
+
description: 'Team structure insights',
|
|
684
|
+
icon: ChartBarIcon,
|
|
685
|
+
gradientClass: 'bg-gradient-to-br from-amber-500/10 to-orange-500/10',
|
|
686
|
+
prompt: 'Give me an overview of our team structure.',
|
|
687
|
+
permission: 'manageEmployees',
|
|
688
|
+
},
|
|
689
|
+
]
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
id: 'hiring',
|
|
694
|
+
routes: ['/hiring/jobs', '/hiring/candidates'],
|
|
695
|
+
priority: 10,
|
|
696
|
+
getSuggestions: (): AISuggestion[] => [
|
|
697
|
+
{
|
|
698
|
+
id: 'open-positions',
|
|
699
|
+
title: 'Open Positions',
|
|
700
|
+
description: 'List all open job positions',
|
|
701
|
+
icon: UserGroupIcon,
|
|
702
|
+
prompt: 'Show me all currently open job positions.',
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
},
|
|
706
|
+
]
|
|
707
|
+
}
|
|
708
|
+
```
|
|
383
709
|
|
|
384
|
-
|
|
710
|
+
### Default Suggestions
|
|
385
711
|
|
|
386
|
-
|
|
712
|
+
```typescript
|
|
713
|
+
function getDefaultSuggestions(): AISuggestion[] {
|
|
714
|
+
return [
|
|
715
|
+
{
|
|
716
|
+
id: 'help',
|
|
717
|
+
title: 'How can you help?',
|
|
718
|
+
description: 'Learn what I can do',
|
|
719
|
+
prompt: 'What can you help me with?',
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
id: 'contact-support',
|
|
723
|
+
title: 'Contact Support',
|
|
724
|
+
description: 'Get help from a human',
|
|
725
|
+
prompt: 'I need to contact support',
|
|
726
|
+
isSupportRequest: true,
|
|
727
|
+
},
|
|
728
|
+
]
|
|
729
|
+
}
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### SuggestionProvider Interface
|
|
733
|
+
|
|
734
|
+
```typescript
|
|
735
|
+
interface SuggestionProvider {
|
|
736
|
+
id: string // Unique identifier
|
|
737
|
+
routes?: string[] // Route patterns to match
|
|
738
|
+
matcher?: (path: string, context: PageContext | null) => boolean
|
|
739
|
+
getSuggestions: (context: PageContext) => AISuggestion[]
|
|
740
|
+
extractContext?: (path: string) => Record<string, any>
|
|
741
|
+
priority?: number // Higher = first
|
|
742
|
+
}
|
|
387
743
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
744
|
+
interface AISuggestion {
|
|
745
|
+
id: string
|
|
746
|
+
title: string
|
|
747
|
+
description?: string
|
|
748
|
+
icon?: Component
|
|
749
|
+
className?: string
|
|
750
|
+
gradientClass?: string
|
|
751
|
+
prompt: string | ((context: PageContext) => string)
|
|
752
|
+
permission?: string
|
|
753
|
+
category?: string
|
|
754
|
+
isSupportRequest?: boolean
|
|
755
|
+
}
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
## ๐จ UI Customization
|
|
398
759
|
|
|
399
|
-
|
|
760
|
+
Override CSS classes for any component:
|
|
400
761
|
|
|
401
|
-
|
|
762
|
+
```vue
|
|
402
763
|
<AiChatDrawer
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
764
|
+
v-model="isOpen"
|
|
765
|
+
:ui="{
|
|
766
|
+
backdrop: 'bg-black/50 backdrop-blur-sm',
|
|
767
|
+
drawer: 'shadow-2xl',
|
|
768
|
+
panel: 'bg-gray-50 dark:bg-gray-900',
|
|
769
|
+
header: 'border-b-2 border-primary-500',
|
|
770
|
+
body: 'custom-scrollbar',
|
|
771
|
+
footer: 'border-t border-gray-200',
|
|
406
772
|
}"
|
|
407
773
|
/>
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
###
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
//
|
|
434
|
-
public function ask(Request $request)
|
|
435
|
-
{
|
|
436
|
-
return response()->stream(function () use ($request) {
|
|
437
|
-
// Stream OpenAI-format chunks
|
|
438
|
-
echo "data: " . json_encode([
|
|
439
|
-
'choices' => [['delta' => ['content' => 'Hello']]]
|
|
440
|
-
]) . "\n\n";
|
|
441
|
-
ob_flush();
|
|
442
|
-
flush();
|
|
443
|
-
|
|
444
|
-
echo "data: [DONE]\n\n";
|
|
445
|
-
}, 200, [
|
|
446
|
-
'Content-Type' => 'text/event-stream',
|
|
447
|
-
'Cache-Control' => 'no-cache',
|
|
448
|
-
'X-Accel-Buffering' => 'no',
|
|
449
|
-
]);
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### AiChatDrawerUI
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
interface AiChatDrawerUI {
|
|
780
|
+
backdrop?: string // Backdrop overlay
|
|
781
|
+
drawer?: string // Main drawer container
|
|
782
|
+
panel?: string // Inner panel
|
|
783
|
+
header?: string // Header container
|
|
784
|
+
headerTitle?: string // Header title
|
|
785
|
+
headerActions?: string // Header actions
|
|
786
|
+
headerActionButton?: string // Header buttons
|
|
787
|
+
body?: string // Messages container
|
|
788
|
+
footer?: string // Footer container
|
|
789
|
+
quotaDisplay?: string // Quota display
|
|
790
|
+
messageCountBadge?: string // Message count badge
|
|
791
|
+
newChatButton?: string // New chat button
|
|
792
|
+
errorContainer?: string // Error container
|
|
793
|
+
errorMessage?: string // Error message
|
|
794
|
+
retryButton?: string // Retry button
|
|
795
|
+
closeConfirmModal?: string // Confirm modal
|
|
796
|
+
closeConfirmButton?: string // Confirm button
|
|
797
|
+
cancelButton?: string // Cancel button
|
|
798
|
+
historyLimitModal?: string // History limit modal
|
|
799
|
+
historyLimitButton?: string // History limit button
|
|
450
800
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### ChatInputUI
|
|
804
|
+
|
|
805
|
+
```typescript
|
|
806
|
+
interface ChatInputUI {
|
|
807
|
+
root?: string // Root container
|
|
808
|
+
form?: string // Form wrapper
|
|
809
|
+
inputContainer?: string // Input container
|
|
810
|
+
inputWrapper?: string // Input border wrapper
|
|
811
|
+
textarea?: string // Textarea element
|
|
812
|
+
attachButton?: string // Attach button
|
|
813
|
+
sendButton?: string // Send button
|
|
814
|
+
sendButtonActive?: string // Send active state
|
|
815
|
+
sendButtonDisabled?: string // Send disabled state
|
|
816
|
+
stopButton?: string // Stop button
|
|
817
|
+
supportToggle?: string // Support toggle
|
|
818
|
+
supportBadge?: string // Support badge
|
|
819
|
+
attachmentsContainer?: string // Attachments container
|
|
820
|
+
attachmentItem?: string // Attachment item
|
|
821
|
+
attachmentThumbnail?: string // Attachment thumbnail
|
|
822
|
+
attachmentRemove?: string // Remove button
|
|
823
|
+
suggestionsDropdown?: string // Suggestions dropdown
|
|
824
|
+
suggestionItem?: string // Suggestion item
|
|
825
|
+
suggestionItemSelected?: string // Selected suggestion
|
|
826
|
+
contextLink?: string // Context link
|
|
827
|
+
}
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
### ChatMessageUI
|
|
831
|
+
|
|
832
|
+
```typescript
|
|
833
|
+
interface ChatMessageUI {
|
|
834
|
+
root?: string // Root container
|
|
835
|
+
userMessage?: string // User message container
|
|
836
|
+
userBubble?: string // User bubble
|
|
837
|
+
userAvatar?: string // User avatar
|
|
838
|
+
assistantMessage?: string // Assistant container
|
|
839
|
+
assistantBubble?: string // Assistant bubble
|
|
840
|
+
loadingIndicator?: string // Loading indicator
|
|
841
|
+
loadingDots?: string // Loading dots
|
|
842
|
+
content?: string // Content wrapper
|
|
843
|
+
attachmentsContainer?: string // Attachments
|
|
844
|
+
attachmentItem?: string // Attachment item
|
|
845
|
+
actionsContainer?: string // Actions container
|
|
846
|
+
showMoreButton?: string // Show more button
|
|
847
|
+
}
|
|
848
|
+
```
|
|
454
849
|
|
|
455
850
|
## ๐ TypeScript Types
|
|
456
851
|
|
|
457
852
|
All types are exported:
|
|
458
853
|
|
|
459
|
-
|
|
854
|
+
```typescript
|
|
460
855
|
import type {
|
|
856
|
+
// Core Config
|
|
857
|
+
RestifyAiConfig,
|
|
858
|
+
RestifyAiEndpoints,
|
|
859
|
+
RestifyAiLabels,
|
|
860
|
+
RestifyAiTheme,
|
|
861
|
+
|
|
862
|
+
// Chat Types
|
|
461
863
|
ChatMessage,
|
|
864
|
+
ChatQuota,
|
|
865
|
+
ChatError,
|
|
462
866
|
ChatAttachment,
|
|
463
|
-
|
|
867
|
+
ChatRole,
|
|
868
|
+
SubmitPayload,
|
|
869
|
+
|
|
870
|
+
// Context
|
|
871
|
+
PageContext,
|
|
872
|
+
|
|
873
|
+
// Providers
|
|
464
874
|
MentionProvider,
|
|
875
|
+
MentionItem,
|
|
876
|
+
Mention,
|
|
465
877
|
SuggestionProvider,
|
|
466
878
|
AISuggestion,
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
879
|
+
|
|
880
|
+
// History
|
|
881
|
+
HistoryLimitConfig,
|
|
882
|
+
LoadingTextConfig,
|
|
883
|
+
|
|
884
|
+
// UI Customization
|
|
471
885
|
AiChatDrawerUI,
|
|
472
886
|
ChatInputUI,
|
|
473
887
|
ChatMessageUI,
|
|
474
|
-
|
|
888
|
+
AiEmptyStateUI,
|
|
889
|
+
MentionListUI,
|
|
890
|
+
|
|
891
|
+
// Text Customization
|
|
475
892
|
AiChatDrawerTexts,
|
|
476
893
|
ChatInputTexts,
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
894
|
+
ChatMessageTexts,
|
|
895
|
+
|
|
896
|
+
// Slot Props
|
|
897
|
+
HeaderSlotProps,
|
|
898
|
+
MessageSlotProps,
|
|
899
|
+
InputSlotProps,
|
|
900
|
+
EmptyStateSlotProps,
|
|
901
|
+
|
|
902
|
+
// Hooks
|
|
482
903
|
BeforeSendHook,
|
|
483
904
|
AfterResponseHook,
|
|
905
|
+
OnStreamStartHook,
|
|
906
|
+
OnStreamEndHook,
|
|
907
|
+
OnStreamChunkHook,
|
|
484
908
|
StreamParserFunction,
|
|
909
|
+
RetryConfig,
|
|
485
910
|
} from '@doderasoftware/restify-ai'
|
|
486
|
-
|
|
911
|
+
```
|
|
487
912
|
|
|
488
|
-
|
|
913
|
+
## ๐ Backend Integration
|
|
489
914
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
| Feature | Configuration |
|
|
493
|
-
|---------|---------------|
|
|
494
|
-
| SSE Streaming | Built-in, automatic |
|
|
495
|
-
| Keyboard Shortcut | \`keyboardShortcut: 'cmd+g'\` |
|
|
496
|
-
| @Mentions | \`mentionProviders: [...]\` |
|
|
497
|
-
| Route Suggestions | \`suggestionProviders: [...]\` |
|
|
498
|
-
| File Attachments | \`endpoints.uploadFile\` + \`maxAttachments\` |
|
|
499
|
-
| Support Mode | \`enableSupportMode: true\` |
|
|
500
|
-
| Custom Headers | \`getCustomHeaders: () => ({...})\` |
|
|
501
|
-
| Auth Token | \`getAuthToken: () => token\` |
|
|
502
|
-
| Base URL | \`baseUrl: 'https://api.example.com'\` |
|
|
503
|
-
| Error Handling | \`onError: (err) => {...}\` |
|
|
504
|
-
| Retry Logic | \`retry: { maxRetries: 3 }\` |
|
|
505
|
-
| UI Customization | \`:ui\` prop on all components |
|
|
506
|
-
| Text/i18n | \`:texts\` prop + \`labels\` config |
|
|
507
|
-
| History Limit | \`:history-limit\` prop |
|
|
508
|
-
| Loading Messages | \`:loading-text\` prop |
|
|
915
|
+
This package is designed for [Laravel Restify](https://laravel-restify.com) backends:
|
|
509
916
|
|
|
510
|
-
|
|
917
|
+
### Ask Endpoint (SSE Stream)
|
|
918
|
+
|
|
919
|
+
```php
|
|
920
|
+
// routes/api.php
|
|
921
|
+
Route::post('/ask', [AiController::class, 'ask']);
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
**Request:**
|
|
925
|
+
```json
|
|
926
|
+
{
|
|
927
|
+
"question": "Who is available today?",
|
|
928
|
+
"history": [
|
|
929
|
+
{ "role": "user", "message": "Hello" },
|
|
930
|
+
{ "role": "assistant", "message": "Hi! How can I help?" }
|
|
931
|
+
],
|
|
932
|
+
"stream": true,
|
|
933
|
+
"files": [{ "id": "file-123", "name": "report.pdf" }],
|
|
934
|
+
"mentions": [{ "id": "emp-1", "type": "employee", "name": "John Doe" }],
|
|
935
|
+
"contact_support": false
|
|
936
|
+
}
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
**Response (SSE):**
|
|
940
|
+
```
|
|
941
|
+
data: {"choices":[{"delta":{"content":"Based on"}}]}
|
|
942
|
+
data: {"choices":[{"delta":{"content":" the schedule..."}}]}
|
|
943
|
+
data: [DONE]
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
### Quota Endpoint
|
|
947
|
+
|
|
948
|
+
```php
|
|
949
|
+
// routes/api.php
|
|
950
|
+
Route::get('/ai/quota', [AiController::class, 'quota']);
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
**Response:**
|
|
954
|
+
```json
|
|
955
|
+
{
|
|
956
|
+
"data": {
|
|
957
|
+
"limit": 100,
|
|
958
|
+
"used": 25,
|
|
959
|
+
"remaining": 75
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
### Upload Endpoint
|
|
965
|
+
|
|
966
|
+
```php
|
|
967
|
+
// routes/api.php
|
|
968
|
+
Route::post('/ai/upload', [AiController::class, 'upload']);
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
**Response:**
|
|
972
|
+
```json
|
|
973
|
+
{
|
|
974
|
+
"data": {
|
|
975
|
+
"id": "file-123",
|
|
976
|
+
"name": "document.pdf",
|
|
977
|
+
"url": "/storage/uploads/document.pdf",
|
|
978
|
+
"type": "application/pdf",
|
|
979
|
+
"size": 102400
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
## โจ๏ธ Keyboard Shortcuts
|
|
985
|
+
|
|
986
|
+
| Shortcut | Action |
|
|
987
|
+
|----------|--------|
|
|
988
|
+
| `โ/Ctrl + G` | Toggle drawer (configurable) |
|
|
989
|
+
| `Escape` | Close drawer |
|
|
990
|
+
| `Enter` | Send message |
|
|
991
|
+
| `Shift + Enter` | New line |
|
|
992
|
+
|
|
993
|
+
## ๐พ Session Storage
|
|
994
|
+
|
|
995
|
+
Chat history persists in `sessionStorage` by default:
|
|
996
|
+
|
|
997
|
+
- Key: `restify_ai_chat_history` (configurable via `chatHistoryKey`)
|
|
998
|
+
- Cleared on browser close
|
|
999
|
+
- Persists across page navigation
|
|
1000
|
+
|
|
1001
|
+
## ๐ฆ Package Exports
|
|
1002
|
+
|
|
1003
|
+
```typescript
|
|
1004
|
+
// Components
|
|
1005
|
+
export { AiChatDrawer } from './components'
|
|
1006
|
+
|
|
1007
|
+
// Store
|
|
1008
|
+
export { useRestifyAiStore } from './store'
|
|
1009
|
+
|
|
1010
|
+
// Composables
|
|
1011
|
+
export {
|
|
1012
|
+
useAiDrawerShortcut,
|
|
1013
|
+
usePageAiContext,
|
|
1014
|
+
useAiContext,
|
|
1015
|
+
useAiSuggestions
|
|
1016
|
+
} from './composables'
|
|
1017
|
+
|
|
1018
|
+
// Config
|
|
1019
|
+
export {
|
|
1020
|
+
initRestifyAi,
|
|
1021
|
+
getConfig,
|
|
1022
|
+
getLabels,
|
|
1023
|
+
getUI,
|
|
1024
|
+
RestifyAiPlugin
|
|
1025
|
+
} from './config'
|
|
1026
|
+
|
|
1027
|
+
// Types
|
|
1028
|
+
export * from './types'
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
## ๐ค Requirements
|
|
1032
|
+
|
|
1033
|
+
- **Vue 3.3+**
|
|
1034
|
+
- **Pinia 2.1+**
|
|
1035
|
+
- A backend implementing the streaming chat API (e.g., [Laravel Restify](https://laravel-restify.com))
|
|
1036
|
+
|
|
1037
|
+
## ๐ Links
|
|
1038
|
+
|
|
1039
|
+
- [๐ Laravel Restify Documentation](https://laravel-restify.com)
|
|
1040
|
+
- [๐ฆ npm Package](https://www.npmjs.com/package/@doderasoftware/restify-ai)
|
|
1041
|
+
- [๐ข BinarCode](https://binarcode.com)
|
|
1042
|
+
- [๐ป GitHub](https://github.com/BinarCode/laravel-restify)
|
|
511
1043
|
|
|
512
1044
|
## ๐ License
|
|
513
1045
|
|
|
514
|
-
MIT ยฉ [
|
|
1046
|
+
MIT ยฉ [BinarCode](https://binarcode.com)
|
|
1047
|
+
|
|
1048
|
+
---
|
|
1049
|
+
|
|
1050
|
+
Built with โค๏ธ by the [BinarCode](https://binarcode.com) team ยท Published by [Dodera Software](https://doderasoft.com)
|