@doderasoftware/restify-ai 0.1.0-beta.3 → 0.1.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +523 -406
- package/dist/restify-ai.js +462 -456
- package/dist/restify-ai.umd.cjs +11 -11
- package/dist/store.d.ts.map +1 -1
- package/package.json +21 -9
package/README.md
CHANGED
|
@@ -1,514 +1,631 @@
|
|
|
1
|
-
|
|
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>
|
|
2
22
|
|
|
3
|
-
|
|
23
|
+
---
|
|
4
24
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# npm
|
|
70
|
+
npm install @doderasoftware/restify-ai
|
|
8
71
|
|
|
9
|
-
|
|
72
|
+
# yarn
|
|
73
|
+
yarn add @doderasoftware/restify-ai
|
|
10
74
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 |
|
|
75
|
+
# pnpm
|
|
76
|
+
pnpm add @doderasoftware/restify-ai
|
|
77
|
+
```
|
|
27
78
|
|
|
28
|
-
|
|
79
|
+
### Peer Dependencies
|
|
29
80
|
|
|
30
|
-
|
|
81
|
+
```bash
|
|
82
|
+
npm install vue@^3.3.0 pinia@^2.1.0 tailwindcss@^3.3.0
|
|
83
|
+
```
|
|
31
84
|
|
|
32
|
-
|
|
33
|
-
npm install @doderasoftware/restify-ai
|
|
34
|
-
# or
|
|
35
|
-
pnpm add @doderasoftware/restify-ai
|
|
36
|
-
\`\`\`
|
|
85
|
+
## Quick Start
|
|
37
86
|
|
|
38
|
-
|
|
87
|
+
### 1. Configure Tailwind CSS
|
|
39
88
|
|
|
40
|
-
|
|
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
|
+
```
|
|
41
101
|
|
|
42
|
-
|
|
102
|
+
### 2. Import Styles
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// main.ts
|
|
106
|
+
import "@doderasoftware/restify-ai/styles"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3. Register the Plugin
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// plugins/restifyAi.ts
|
|
113
|
+
import { RestifyAiPlugin } from "@doderasoftware/restify-ai"
|
|
114
|
+
import type { App } from "vue"
|
|
115
|
+
|
|
116
|
+
export function setupRestifyAi(app: App) {
|
|
117
|
+
app.use(RestifyAiPlugin, {
|
|
118
|
+
endpoints: {
|
|
119
|
+
ask: "/api/ai/ask",
|
|
120
|
+
uploadFile: "/api/ai/upload",
|
|
121
|
+
quota: "/api/ai/quota",
|
|
122
|
+
},
|
|
123
|
+
getAuthToken: () => localStorage.getItem("token"),
|
|
124
|
+
baseUrl: import.meta.env.VITE_API_URL,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
```
|
|
43
128
|
|
|
44
|
-
|
|
129
|
+
```typescript
|
|
45
130
|
// main.ts
|
|
46
|
-
import { createApp } from
|
|
47
|
-
import { createPinia } from
|
|
48
|
-
import
|
|
49
|
-
import
|
|
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"
|
|
50
136
|
|
|
51
137
|
const app = createApp(App)
|
|
52
138
|
app.use(createPinia())
|
|
139
|
+
setupRestifyAi(app)
|
|
140
|
+
app.mount("#app")
|
|
141
|
+
```
|
|
53
142
|
|
|
54
|
-
|
|
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
|
-
|
|
63
|
-
app.mount('#app')
|
|
64
|
-
\`\`\`
|
|
143
|
+
### 4. Add the Component
|
|
65
144
|
|
|
66
|
-
|
|
67
|
-
<!-- App.vue -->
|
|
145
|
+
```vue
|
|
68
146
|
<template>
|
|
69
|
-
<
|
|
70
|
-
|
|
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>
|
|
71
153
|
</template>
|
|
72
154
|
|
|
73
|
-
<script setup>
|
|
74
|
-
import { ref } from
|
|
75
|
-
import { AiChatDrawer } from
|
|
155
|
+
<script setup lang="ts">
|
|
156
|
+
import { ref } from "vue"
|
|
157
|
+
import { AiChatDrawer } from "@doderasoftware/restify-ai"
|
|
76
158
|
|
|
77
159
|
const showChat = ref(false)
|
|
78
160
|
</script>
|
|
79
|
-
|
|
161
|
+
```
|
|
80
162
|
|
|
81
|
-
|
|
163
|
+
### 5. Enable Keyboard Shortcut
|
|
82
164
|
|
|
83
|
-
|
|
165
|
+
```typescript
|
|
166
|
+
import { useAiDrawerShortcut } from "@doderasoftware/restify-ai"
|
|
84
167
|
|
|
85
|
-
|
|
168
|
+
// Enable Cmd/Ctrl+G to toggle drawer
|
|
169
|
+
useAiDrawerShortcut()
|
|
170
|
+
```
|
|
86
171
|
|
|
87
|
-
|
|
88
|
-
|--------|------|-------------|
|
|
89
|
-
| \`endpoints.ask\` | \`string\` | SSE streaming endpoint |
|
|
90
|
-
| \`getAuthToken\` | \`() => string \| Promise<string>\` | Auth token getter |
|
|
172
|
+
## Configuration
|
|
91
173
|
|
|
92
|
-
###
|
|
174
|
+
### Full Configuration Options
|
|
93
175
|
|
|
94
|
-
|
|
95
|
-
{
|
|
96
|
-
|
|
176
|
+
```typescript
|
|
177
|
+
app.use(RestifyAiPlugin, {
|
|
178
|
+
// REQUIRED
|
|
97
179
|
endpoints: {
|
|
98
|
-
ask:
|
|
99
|
-
|
|
100
|
-
|
|
180
|
+
ask: "/api/ai/ask",
|
|
181
|
+
uploadFile: "/api/ai/upload",
|
|
182
|
+
quota: "/api/ai/quota",
|
|
101
183
|
},
|
|
102
|
-
getAuthToken: () =>
|
|
103
|
-
getCustomHeaders: () => ({
|
|
104
|
-
'X-CSRF-TOKEN': csrfToken,
|
|
105
|
-
'X-App-Version': '1.0.0',
|
|
106
|
-
}),
|
|
107
|
-
}
|
|
108
|
-
\`\`\`
|
|
184
|
+
getAuthToken: () => localStorage.getItem("auth_token"),
|
|
109
185
|
|
|
110
|
-
|
|
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,
|
|
111
191
|
|
|
112
|
-
|
|
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
|
-
\`\`\`
|
|
192
|
+
// RETRY
|
|
193
|
+
retry: { maxRetries: 3, retryDelay: 1000 },
|
|
128
194
|
|
|
129
|
-
|
|
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
|
+
},
|
|
130
202
|
|
|
131
|
-
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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",
|
|
137
220
|
},
|
|
138
|
-
}
|
|
139
|
-
\`\`\`
|
|
140
221
|
|
|
141
|
-
|
|
222
|
+
// LIMITS
|
|
223
|
+
chatHistoryLimit: 50,
|
|
224
|
+
maxAttachments: 5,
|
|
225
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
142
226
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
\`\`\`
|
|
227
|
+
// FEATURES
|
|
228
|
+
keyboardShortcut: "cmd+g",
|
|
229
|
+
enableSupportMode: true,
|
|
170
230
|
|
|
171
|
-
|
|
231
|
+
// CUSTOM COMPONENTS
|
|
232
|
+
assistantAvatar: CustomAvatarComponent,
|
|
233
|
+
userAvatar: () => currentUser.value?.avatarUrl,
|
|
172
234
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
drawerStateKey: 'my_drawer_state', // localStorage key
|
|
181
|
-
}
|
|
182
|
-
\`\`\`
|
|
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
|
+
```
|
|
183
242
|
|
|
184
|
-
|
|
243
|
+
## Component API
|
|
185
244
|
|
|
186
|
-
|
|
187
|
-
{
|
|
188
|
-
keyboardShortcut: 'cmd+g', // null to disable
|
|
189
|
-
enableSupportMode: true, // Support request toggle
|
|
190
|
-
}
|
|
191
|
-
\`\`\`
|
|
245
|
+
### AiChatDrawer Props
|
|
192
246
|
|
|
193
|
-
|
|
247
|
+
| Prop | Type | Default | Description |
|
|
248
|
+
|------|------|---------|-------------|
|
|
249
|
+
| `modelValue` | `boolean` | required | Controls drawer visibility (v-model) |
|
|
250
|
+
| `width` | `string` | `"600px"` | Drawer width |
|
|
251
|
+
| `fullscreenWidth` | `string` | `"90vw"` | Width when fullscreen |
|
|
252
|
+
| `topOffset` | `string` | `"0"` | Top offset (for fixed headers) |
|
|
253
|
+
| `position` | `"left" \| "right"` | `"right"` | Drawer position |
|
|
254
|
+
| `showBackdrop` | `boolean` | `false` | Show backdrop overlay |
|
|
255
|
+
| `closeOnEscape` | `boolean` | `true` | Close on Escape key |
|
|
256
|
+
| `showQuota` | `boolean` | `true` | Show quota display |
|
|
257
|
+
| `confirmClose` | `boolean` | `true` | Confirm before clearing |
|
|
258
|
+
| `ui` | `AiChatDrawerUI` | `{}` | Custom CSS classes |
|
|
259
|
+
| `texts` | `AiChatDrawerTexts` | `{}` | Custom text labels |
|
|
260
|
+
|
|
261
|
+
### Events
|
|
194
262
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
263
|
+
| Event | Payload | Description |
|
|
264
|
+
|-------|---------|-------------|
|
|
265
|
+
| `update:modelValue` | `boolean` | Drawer state changed |
|
|
266
|
+
| `close` | - | Drawer closed |
|
|
267
|
+
| `contact-support` | - | Support mode activated |
|
|
268
|
+
| `new-chat` | - | New chat started |
|
|
269
|
+
|
|
270
|
+
### Slots
|
|
271
|
+
|
|
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:
|
|
292
|
+
|
|
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
|
|
307
|
+
|
|
308
|
+
```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
|
|
199
320
|
}
|
|
200
|
-
\`\`\`
|
|
201
321
|
|
|
202
|
-
|
|
322
|
+
interface ChatInputUI {
|
|
323
|
+
root?: string
|
|
324
|
+
textarea?: string
|
|
325
|
+
sendButton?: string
|
|
326
|
+
attachButton?: string
|
|
327
|
+
suggestionsDropdown?: string
|
|
328
|
+
}
|
|
203
329
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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),
|
|
330
|
+
interface ChatMessageUI {
|
|
331
|
+
root?: string
|
|
332
|
+
userBubble?: string
|
|
333
|
+
assistantBubble?: string
|
|
334
|
+
content?: string
|
|
335
|
+
loadingDots?: string
|
|
225
336
|
}
|
|
226
|
-
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Store API
|
|
340
|
+
|
|
341
|
+
Access the Pinia store for advanced use cases:
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { useRestifyAiStore } from "@doderasoftware/restify-ai"
|
|
345
|
+
|
|
346
|
+
const store = useRestifyAiStore()
|
|
347
|
+
|
|
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)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Composables
|
|
366
|
+
|
|
367
|
+
### useAiDrawerShortcut
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { useAiDrawerShortcut } from "@doderasoftware/restify-ai"
|
|
371
|
+
|
|
372
|
+
// Uses store directly (recommended)
|
|
373
|
+
useAiDrawerShortcut()
|
|
374
|
+
|
|
375
|
+
// Or pass a ref
|
|
376
|
+
const drawerRef = ref(false)
|
|
377
|
+
useAiDrawerShortcut(drawerRef)
|
|
378
|
+
```
|
|
227
379
|
|
|
228
|
-
###
|
|
380
|
+
### usePageAiContext
|
|
229
381
|
|
|
230
|
-
|
|
382
|
+
```typescript
|
|
383
|
+
import { usePageAiContext } from "@doderasoftware/restify-ai"
|
|
384
|
+
|
|
385
|
+
usePageAiContext({
|
|
386
|
+
pageType: "invoice-detail",
|
|
387
|
+
entityId: route.params.id,
|
|
388
|
+
entityType: "invoice",
|
|
389
|
+
metadata: { customerName: invoice.value?.customer?.name },
|
|
390
|
+
})
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Backend Integration
|
|
394
|
+
|
|
395
|
+
### Laravel Restify Setup
|
|
396
|
+
|
|
397
|
+
This package is designed to work with [Laravel Restify](https://laravel-restify.com):
|
|
398
|
+
|
|
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
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Expected Request/Response Formats
|
|
409
|
+
|
|
410
|
+
**Ask Endpoint (SSE Stream):**
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
// Request
|
|
231
414
|
{
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
},
|
|
238
|
-
translate: (key, params) => i18n.t(key, params),
|
|
239
|
-
can: (permission) => user.hasPermission(permission),
|
|
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 }>
|
|
240
420
|
}
|
|
241
|
-
\`\`\`
|
|
242
421
|
|
|
243
|
-
|
|
422
|
+
// Response: Server-Sent Events (OpenAI format)
|
|
423
|
+
data: {"choices":[{"delta":{"content":"Hello"}}]}
|
|
424
|
+
data: {"choices":[{"delta":{"content":" world"}}]}
|
|
425
|
+
data: [DONE]
|
|
426
|
+
```
|
|
244
427
|
|
|
245
|
-
|
|
428
|
+
**Upload Endpoint:**
|
|
246
429
|
|
|
247
|
-
|
|
430
|
+
```typescript
|
|
431
|
+
{ id: string, name: string, url: string, type: string, size: number }
|
|
432
|
+
```
|
|
248
433
|
|
|
249
|
-
|
|
434
|
+
**Quota Endpoint:**
|
|
250
435
|
|
|
251
|
-
|
|
436
|
+
```typescript
|
|
437
|
+
{ limit: number, used: number, remaining: number }
|
|
438
|
+
```
|
|
252
439
|
|
|
253
|
-
|
|
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
|
|
440
|
+
## TypeScript Support
|
|
274
441
|
|
|
275
|
-
|
|
276
|
-
|-------|---------|-------------|
|
|
277
|
-
| \`close\` | - | Drawer closed |
|
|
278
|
-
| \`contact-support\` | - | Support mode triggered |
|
|
279
|
-
| \`new-chat\` | - | New chat started |
|
|
280
|
-
|
|
281
|
-
#### Slots
|
|
282
|
-
|
|
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.
|
|
442
|
+
Full TypeScript support with exported types:
|
|
306
443
|
|
|
307
|
-
|
|
444
|
+
```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"
|
|
457
|
+
```
|
|
308
458
|
|
|
309
|
-
##
|
|
459
|
+
## Browser Support
|
|
310
460
|
|
|
311
|
-
|
|
461
|
+
- Chrome 80+
|
|
462
|
+
- Firefox 75+
|
|
463
|
+
- Safari 13+
|
|
464
|
+
- Edge 80+
|
|
312
465
|
|
|
313
|
-
|
|
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
|
-
\`\`\`
|
|
466
|
+
## Contributing
|
|
326
467
|
|
|
327
|
-
|
|
468
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
328
469
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
- \`AiEmptyStateUI\` - grid, suggestionCard, title, etc.
|
|
333
|
-
- \`MentionListUI\` - item, itemSelected, groupHeader, etc.
|
|
470
|
+
## License
|
|
471
|
+
|
|
472
|
+
[MIT](LICENSE) - [BinarCode](https://binarcode.com)
|
|
334
473
|
|
|
335
474
|
---
|
|
336
475
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
\`\`\`
|
|
355
|
-
|
|
356
|
-
### Store Actions
|
|
357
|
-
|
|
358
|
-
\`\`\`typescript
|
|
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>
|
|
485
|
+
|
|
486
|
+
## Store API
|
|
487
|
+
|
|
488
|
+
Access the Pinia store for advanced use cases:
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
import { useRestifyAiStore } from "@doderasoftware/restify-ai"
|
|
492
|
+
|
|
359
493
|
const store = useRestifyAiStore()
|
|
360
494
|
|
|
361
|
-
|
|
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)
|
|
362
505
|
store.cancelRequest()
|
|
363
|
-
store.
|
|
506
|
+
store.retryLastMessage()
|
|
364
507
|
store.clearChatHistory()
|
|
365
|
-
store.clearError()
|
|
366
508
|
store.fetchQuota()
|
|
367
|
-
store.
|
|
368
|
-
|
|
509
|
+
store.uploadFile(file)
|
|
510
|
+
```
|
|
369
511
|
|
|
370
|
-
|
|
512
|
+
## Composables
|
|
371
513
|
|
|
372
|
-
|
|
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
|
-
\`\`\`
|
|
514
|
+
### useAiDrawerShortcut
|
|
381
515
|
|
|
382
|
-
|
|
516
|
+
```typescript
|
|
517
|
+
import { useAiDrawerShortcut } from "@doderasoftware/restify-ai"
|
|
383
518
|
|
|
384
|
-
|
|
519
|
+
// Uses store directly (recommended)
|
|
520
|
+
useAiDrawerShortcut()
|
|
385
521
|
|
|
386
|
-
|
|
522
|
+
// Or pass a ref
|
|
523
|
+
const drawerRef = ref(false)
|
|
524
|
+
useAiDrawerShortcut(drawerRef)
|
|
525
|
+
```
|
|
387
526
|
|
|
388
|
-
|
|
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
|
-
\`\`\`
|
|
527
|
+
### usePageAiContext
|
|
398
528
|
|
|
399
|
-
|
|
529
|
+
```typescript
|
|
530
|
+
import { usePageAiContext } from "@doderasoftware/restify-ai"
|
|
400
531
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
:
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
\`\`\`
|
|
532
|
+
usePageAiContext({
|
|
533
|
+
pageType: "invoice-detail",
|
|
534
|
+
entityId: route.params.id,
|
|
535
|
+
entityType: "invoice",
|
|
536
|
+
metadata: { customerName: invoice.value?.customer?.name },
|
|
537
|
+
})
|
|
538
|
+
```
|
|
409
539
|
|
|
410
|
-
|
|
540
|
+
## Backend Integration
|
|
411
541
|
|
|
412
|
-
|
|
413
|
-
// Plugin config
|
|
414
|
-
{ keyboardShortcut: 'cmd+shift+a' }
|
|
542
|
+
### Laravel Restify Setup
|
|
415
543
|
|
|
416
|
-
|
|
417
|
-
const { toggle } = useAiDrawerShortcut()
|
|
418
|
-
\`\`\`
|
|
544
|
+
This package is designed to work with [Laravel Restify](https://laravel-restify.com):
|
|
419
545
|
|
|
420
|
-
|
|
546
|
+
```php
|
|
547
|
+
// 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
|
+
});
|
|
553
|
+
```
|
|
421
554
|
|
|
422
|
-
|
|
555
|
+
### Expected Request/Response Formats
|
|
423
556
|
|
|
424
|
-
|
|
557
|
+
**Ask Endpoint (SSE Stream):**
|
|
425
558
|
|
|
426
|
-
|
|
427
|
-
//
|
|
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)
|
|
559
|
+
```typescript
|
|
560
|
+
// Request
|
|
435
561
|
{
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
]);
|
|
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 }>
|
|
450
567
|
}
|
|
451
|
-
\`\`\`
|
|
452
568
|
|
|
453
|
-
|
|
569
|
+
// Response: Server-Sent Events (OpenAI format)
|
|
570
|
+
data: {"choices":[{"delta":{"content":"Hello"}}]}
|
|
571
|
+
data: {"choices":[{"delta":{"content":" world"}}]}
|
|
572
|
+
data: [DONE]
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
**Upload Endpoint:**
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
{ id: string, name: string, url: string, type: string, size: number }
|
|
579
|
+
```
|
|
454
580
|
|
|
455
|
-
|
|
581
|
+
**Quota Endpoint:**
|
|
456
582
|
|
|
457
|
-
|
|
583
|
+
```typescript
|
|
584
|
+
{ limit: number, used: number, remaining: number }
|
|
585
|
+
```
|
|
458
586
|
|
|
459
|
-
|
|
587
|
+
## TypeScript Support
|
|
588
|
+
|
|
589
|
+
Full TypeScript support with exported types:
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
460
592
|
import type {
|
|
461
593
|
ChatMessage,
|
|
462
594
|
ChatAttachment,
|
|
463
595
|
Mention,
|
|
596
|
+
ChatQuota,
|
|
597
|
+
RestifyAiConfig,
|
|
464
598
|
MentionProvider,
|
|
465
599
|
SuggestionProvider,
|
|
466
600
|
AISuggestion,
|
|
467
|
-
RestifyAiConfig,
|
|
468
|
-
ChatQuota,
|
|
469
|
-
ChatError,
|
|
470
|
-
// UI types
|
|
471
601
|
AiChatDrawerUI,
|
|
472
|
-
ChatInputUI,
|
|
473
|
-
ChatMessageUI,
|
|
474
|
-
// Text types
|
|
475
602
|
AiChatDrawerTexts,
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
HistoryLimitConfig,
|
|
479
|
-
LoadingTextConfig,
|
|
480
|
-
RetryConfig,
|
|
481
|
-
// Hook types
|
|
482
|
-
BeforeSendHook,
|
|
483
|
-
AfterResponseHook,
|
|
484
|
-
StreamParserFunction,
|
|
485
|
-
} from '@doderasoftware/restify-ai'
|
|
486
|
-
\`\`\`
|
|
603
|
+
} from "@doderasoftware/restify-ai"
|
|
604
|
+
```
|
|
487
605
|
|
|
488
|
-
|
|
606
|
+
## Browser Support
|
|
489
607
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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 |
|
|
608
|
+
- Chrome 80+
|
|
609
|
+
- Firefox 75+
|
|
610
|
+
- Safari 13+
|
|
611
|
+
- Edge 80+
|
|
509
612
|
|
|
510
|
-
|
|
613
|
+
## Contributing
|
|
511
614
|
|
|
512
|
-
|
|
615
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
616
|
+
|
|
617
|
+
## License
|
|
618
|
+
|
|
619
|
+
[MIT](LICENSE) - [BinarCode](https://binarcode.com)
|
|
620
|
+
|
|
621
|
+
---
|
|
513
622
|
|
|
514
|
-
|
|
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>
|