@doderasoftware/restify-ai 0.1.0-beta.1 โ†’ 0.1.0-beta.10

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