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

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