@doderasoftware/restify-ai 0.1.0-beta.6 โ†’ 0.1.0-beta.8

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.
Files changed (56) hide show
  1. package/README.md +855 -262
  2. package/dist/components/AiAvatar.vue.d.ts.map +1 -1
  3. package/dist/components/AiChatDrawer.vue.d.ts +3 -3
  4. package/dist/components/AiChatDrawer.vue.d.ts.map +1 -1
  5. package/dist/components/AiEmptyState.vue.d.ts.map +1 -1
  6. package/dist/components/ChatInput.vue.d.ts.map +1 -1
  7. package/dist/components/ChatMessage.vue.d.ts.map +1 -1
  8. package/dist/components/ChatMessageActions.vue.d.ts.map +1 -1
  9. package/dist/components/ErrorBoundary.vue.d.ts +74 -0
  10. package/dist/components/ErrorBoundary.vue.d.ts.map +1 -0
  11. package/dist/components/MentionList.vue.d.ts.map +1 -1
  12. package/dist/components/UserAvatar.vue.d.ts.map +1 -1
  13. package/dist/components/drawer/ConfirmDialog.vue.d.ts.map +1 -1
  14. package/dist/components/drawer/DrawerHeader.vue.d.ts.map +1 -1
  15. package/dist/components/drawer/DrawerMessageList.vue.d.ts.map +1 -1
  16. package/dist/components/drawer/SetupGuide.vue.d.ts.map +1 -1
  17. package/dist/components/index.d.ts +1 -0
  18. package/dist/components/index.d.ts.map +1 -1
  19. package/dist/components/input/AttachmentsPreview.vue.d.ts.map +1 -1
  20. package/dist/components/input/InputActions.vue.d.ts.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/restify-ai.js +1191 -1135
  24. package/dist/restify-ai.umd.cjs +42 -42
  25. package/dist/store/index.d.ts +2 -0
  26. package/dist/store/index.d.ts.map +1 -0
  27. package/dist/store/storage.d.ts +30 -0
  28. package/dist/store/storage.d.ts.map +1 -0
  29. package/dist/store/store.d.ts +247 -0
  30. package/dist/store/store.d.ts.map +1 -0
  31. package/dist/store/utils.d.ts +23 -0
  32. package/dist/store/utils.d.ts.map +1 -0
  33. package/dist/store.d.ts +1 -246
  34. package/dist/store.d.ts.map +1 -1
  35. package/dist/style.css +1 -1
  36. package/dist/types/api.d.ts +43 -0
  37. package/dist/types/api.d.ts.map +1 -0
  38. package/dist/types/chat.d.ts +56 -0
  39. package/dist/types/chat.d.ts.map +1 -0
  40. package/dist/types/config.d.ts +167 -0
  41. package/dist/types/config.d.ts.map +1 -0
  42. package/dist/types/index.d.ts +15 -599
  43. package/dist/types/index.d.ts.map +1 -1
  44. package/dist/types/mentions.d.ts +34 -0
  45. package/dist/types/mentions.d.ts.map +1 -0
  46. package/dist/types/setup.d.ts +13 -0
  47. package/dist/types/setup.d.ts.map +1 -0
  48. package/dist/types/slots.d.ts +33 -0
  49. package/dist/types/slots.d.ts.map +1 -0
  50. package/dist/types/suggestions.d.ts +32 -0
  51. package/dist/types/suggestions.d.ts.map +1 -0
  52. package/dist/types/texts.d.ts +56 -0
  53. package/dist/types/texts.d.ts.map +1 -0
  54. package/dist/types/ui.d.ts +101 -0
  55. package/dist/types/ui.d.ts.map +1 -0
  56. package/package.json +11 -3
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
  [![npm version](https://img.shields.io/npm/v/@doderasoftware/restify-ai.svg)](https://www.npmjs.com/package/@doderasoftware/restify-ai)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![Vue 3](https://img.shields.io/badge/Vue-3.x-brightgreen.svg)](https://vuejs.org/)
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
9
- [![TailwindCSS](https://img.shields.io/badge/TailwindCSS-3.x-38bdf8.svg)](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 (users, documents, etc.)
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 Tailwind CSS classes
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 (Cmd/Ctrl+G)
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 agents
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 tailwindcss@^3.3.0
44
+ npm install vue@^3.3.0 pinia@^2.1.0
45
45
  ```
46
46
 
47
47
  ## ๐Ÿš€ Quick Start
48
48
 
49
- ### 1. Configure Tailwind CSS
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 "@doderasoftware/restify-ai/styles"
53
+ import '@doderasoftware/restify-ai/styles'
69
54
  ```
70
55
 
71
- ### 3. Register the Plugin
56
+ ### 2. Create Plugin Configuration
72
57
 
73
58
  ```typescript
74
59
  // plugins/restifyAi.ts
75
- import { RestifyAiPlugin } from "@doderasoftware/restify-ai"
76
- import type { App } from "vue"
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: "/api/ai/ask",
82
- uploadFile: "/api/ai/upload",
83
- quota: "/api/ai/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 "vue"
94
- import { createPinia } from "pinia"
95
- import App from "./App.vue"
96
- import { setupRestifyAi } from "./plugins/restifyAi"
97
- import "@doderasoftware/restify-ai/styles"
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("#app")
90
+ app.mount('#app')
103
91
  ```
104
92
 
105
93
  ### 4. Add the Component
106
94
 
107
95
  ```vue
108
96
  <template>
109
- <div>
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 { ref } from "vue"
119
- import { AiChatDrawer } from "@doderasoftware/restify-ai"
101
+ import { AiChatDrawer, useRestifyAiStore } from '@doderasoftware/restify-ai'
120
102
 
121
- const showChat = ref(false)
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
- import { useAiDrawerShortcut } from "@doderasoftware/restify-ai"
110
+ // In your layout or App.vue
111
+ import { useAiDrawerShortcut } from '@doderasoftware/restify-ai'
129
112
 
130
- // Enable Cmd/Ctrl+G to toggle drawer
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
- app.use(RestifyAiPlugin, {
140
- // REQUIRED
141
- endpoints: {
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
- ## ๐ŸŽจ UI Customization
210
-
211
- The `:ui` prop allows complete control over every element's styling:
212
-
213
- ```vue
214
- <AiChatDrawer
215
- v-model="isOpen"
216
- :ui="{
217
- backdrop: 'bg-black/50 backdrop-blur-sm',
218
- drawer: 'shadow-2xl',
219
- panel: 'bg-gray-50 dark:bg-gray-900',
220
- header: 'border-b-2 border-blue-500',
221
- newChatButton: 'bg-gradient-to-r from-blue-500 to-purple-500',
222
- }"
223
- />
224
- ```
225
-
226
- ### Available UI Keys - AiChatDrawer
227
-
228
- | Key | Description |
229
- |-----|-------------|
230
- | `backdrop` | Backdrop overlay |
231
- | `drawer` | Drawer container |
232
- | `panel` | Inner panel |
233
- | `header` | Header container |
234
- | `headerTitle` | Header title text |
235
- | `body` | Chat body area |
236
- | `footer` | Footer container |
237
- | `newChatButton` | New chat button |
238
- | `errorContainer` | Error message container |
239
- | `retryButton` | Retry button |
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
- ### Available UI Keys - ChatInput
162
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
163
+ // RETRY CONFIGURATION
164
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
165
+
166
+ retry: {
167
+ maxRetries: 3,
168
+ retryDelay: 1000,
169
+ shouldRetry: (error, attempt) => attempt < 3,
170
+ },
242
171
 
243
- | Key | Description |
244
- |-----|-------------|
245
- | `root` | Input root container |
246
- | `textarea` | Textarea element |
247
- | `sendButton` | Send button |
248
- | `attachButton` | Attachment button |
249
- | `suggestionsDropdown` | Suggestions dropdown |
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
- ### Available UI Keys - ChatMessage
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
- | Key | Description |
254
- |-----|-------------|
255
- | `root` | Message root container |
256
- | `userBubble` | User message bubble |
257
- | `assistantBubble` | Assistant message bubble |
258
- | `content` | Message content |
259
- | `loadingDots` | Loading animation dots |
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 (for fixed headers) |
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
- | `confirmClose` | `boolean` | `true` | Confirm before clearing |
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` | `{ quota, isFullscreen, onNewChat, onClose }` | Custom 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` | `{ message, isUser, isLoading }` | Custom message bubble |
293
- | `input` | `{ modelValue, sending, onSubmit }` | Custom input area |
388
+ | `message` | `MessageSlotProps` | Custom message bubble |
389
+ | `input` | `InputSlotProps` | Custom input area |
390
+ | `context-link` | - | Custom context link below input |
294
391
 
295
- ```vue
296
- <AiChatDrawer v-model="isOpen">
297
- <template #header="{ quota, isFullscreen, onNewChat, onClose }">
298
- <MyCustomHeader />
299
- </template>
300
- <template #empty-state="{ suggestions, onClick }">
301
- <MyCustomEmptyState />
302
- </template>
303
- <template #message="{ message, isUser, isLoading }">
304
- <MyCustomMessage />
305
- </template>
306
- <template #input="{ modelValue, sending, onSubmit }">
307
- <MyCustomInput />
308
- </template>
309
- </AiChatDrawer>
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 "@doderasoftware/restify-ai"
424
+ import { useRestifyAiStore } from '@doderasoftware/restify-ai'
318
425
 
319
426
  const store = useRestifyAiStore()
320
427
 
321
- // State
322
- store.chatHistory // ChatMessage[]
323
- store.loading // boolean
324
- store.sending // boolean
325
- store.quota // { limit, used, remaining }
326
- store.error // { message, failedQuestion, timestamp }
327
-
328
- // Actions
329
- store.toggleChat()
330
- store.sendMessage(payload)
331
- store.cancelRequest()
332
- store.retryLastMessage()
333
- store.clearChatHistory()
334
- store.fetchQuota()
335
- store.uploadFile(file)
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
- Enable keyboard shortcuts to toggle the chat drawer:
473
+ Toggle drawer with keyboard shortcut:
343
474
 
344
475
  ```typescript
345
- import { useAiDrawerShortcut } from "@doderasoftware/restify-ai"
476
+ import { useAiDrawerShortcut } from '@doderasoftware/restify-ai'
346
477
 
347
- // Uses store directly (recommended)
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
- Provide page context to the AI for smarter responses:
484
+ Set page context for AI suggestions:
358
485
 
359
486
  ```typescript
360
- import { usePageAiContext } from "@doderasoftware/restify-ai"
487
+ import { usePageAiContext } from '@doderasoftware/restify-ai'
361
488
 
362
- usePageAiContext({
363
- pageType: "invoice-detail",
364
- entityId: route.params.id,
365
- entityType: "invoice",
366
- metadata: { customerName: invoice.value?.customer?.name },
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
- ## ๐Ÿ”Œ Backend Integration
499
+ ### useAiContext
371
500
 
372
- This package is designed to work with [Laravel Restify](https://laravel-restify.com):
501
+ Programmatic context control:
373
502
 
374
- ```php
375
- // routes/api.php
376
- Route::middleware("auth:sanctum")->group(function () {
377
- Route::post("/ai/ask", [AiController::class, "ask"]);
378
- Route::post("/ai/upload", [AiController::class, "upload"]);
379
- Route::get("/ai/quota", [AiController::class, "quota"]);
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
- ### Expected Request/Response Formats
516
+ ### useAiSuggestions
384
517
 
385
- **Ask Endpoint (SSE Stream):**
518
+ Get suggestions for current context:
386
519
 
387
520
  ```typescript
388
- // Request
389
- {
390
- question: string
391
- history: Array<{ role: string, message: string }>
392
- stream: true
393
- files?: Array<{ id: string, name: string }>
394
- mentions?: Array<{ id: string, type: string, name: string }>
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
- // Response: Server-Sent Events (OpenAI format)
398
- data: {"choices":[{"delta":{"content":"Hello"}}]}
399
- data: {"choices":[{"delta":{"content":" world"}}]}
400
- data: [DONE]
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
- **Upload Endpoint:**
709
+ ### Default Suggestions
404
710
 
405
711
  ```typescript
406
- // Response
407
- { id: string, name: string, url: string, type: string, size: number }
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
- **Quota Endpoint:**
731
+ ### SuggestionProvider Interface
411
732
 
412
733
  ```typescript
413
- // Response
414
- { limit: number, used: number, remaining: number }
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
- ## ๐Ÿ“ TypeScript
757
+ ## ๐ŸŽจ UI Customization
418
758
 
419
- Full TypeScript support with exported types:
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
- RestifyAiConfig,
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
- } from "@doderasoftware/restify-ai"
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
- ## ๐ŸŒ Browser Support
1029
+ ## ๐Ÿค Requirements
437
1030
 
438
- - Chrome 80+
439
- - Firefox 75+
440
- - Safari 13+
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)