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