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