@doderasoftware/restify-ai 0.1.0-beta.5 โ†’ 0.1.0-beta.7

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