@trainly/react 1.3.1 → 1.4.1

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 CHANGED
@@ -86,9 +86,58 @@ function MyComponent() {
86
86
  - ✅ **Zero Migration**: Works with your existing OAuth setup
87
87
  - ✅ **Simple Integration**: Just add `appId` and use `connectWithOAuthToken()`
88
88
 
89
- ## 📁 **NEW: File Management**
89
+ ## 🏷️ **NEW: Custom Scopes (Zero Config)**
90
90
 
91
- Now users can manage their uploaded files directly:
91
+ Tag your documents with custom attributes for powerful filtering and organization:
92
+
93
+ ```tsx
94
+ import { useTrainly } from "@trainly/react";
95
+
96
+ function MyApp() {
97
+ const { upload, ask } = useTrainly();
98
+
99
+ // 1. Upload with scopes - use any keys you want!
100
+ await upload(file, {
101
+ playlist_id: "xyz123",
102
+ workspace_id: "acme_corp",
103
+ project: "alpha",
104
+ });
105
+
106
+ // 2. Query with scope filters - only get results from matching documents
107
+ const answer = await ask("What are the key features?", {
108
+ scope_filters: { playlist_id: "xyz123" },
109
+ });
110
+ // ☝️ Only searches documents with playlist_id="xyz123"
111
+
112
+ // Query with multiple filters
113
+ const answer2 = await ask("Show me updates", {
114
+ scope_filters: {
115
+ workspace_id: "acme_corp",
116
+ project: "alpha",
117
+ },
118
+ });
119
+ // ☝️ Only searches documents matching ALL specified scopes
120
+
121
+ // Query everything (no filters)
122
+ const answer3 = await ask("What do I have?");
123
+ // ☝️ Searches ALL user's documents
124
+ }
125
+ ```
126
+
127
+ **No setup required!** Just pass any key-value pairs - perfect for multi-tenant apps, playlist systems, workspace organization, and more.
128
+
129
+ **Use Cases:**
130
+
131
+ - 🎵 **Playlist Apps**: Filter by `playlist_id` to query specific playlists
132
+ - 🏢 **Multi-Tenant SaaS**: Filter by `tenant_id` or `workspace_id`
133
+ - 📁 **Project Management**: Filter by `project_id` or `team_id`
134
+ - 👥 **User Segmentation**: Filter by `user_tier`, `department`, etc.
135
+
136
+ [📖 Full Scopes Guide](./SCOPES_GUIDE.md)
137
+
138
+ ## 📁 **File Management**
139
+
140
+ Users can manage their uploaded files directly:
92
141
 
93
142
  ```tsx
94
143
  import { useTrainly, TrainlyFileManager } from "@trainly/react";
@@ -142,6 +191,67 @@ function MyApp() {
142
191
  - 📋 **List Files**: View all uploaded documents with metadata
143
192
  - 🗑️ **Delete Files**: Remove files and free up storage space
144
193
  - 📊 **Storage Analytics**: Track file sizes and storage usage
194
+
195
+ ## 🏷️ **NEW in v1.4.0: Custom Scopes**
196
+
197
+ Tag your documents with custom attributes for powerful data segmentation!
198
+
199
+ ```tsx
200
+ import { useTrainly } from "@trainly/react";
201
+
202
+ function PlaylistUploader({ playlistId }) {
203
+ const { upload } = useTrainly();
204
+
205
+ const handleUpload = async (file: File) => {
206
+ // Upload with custom scope values
207
+ await upload(file, {
208
+ playlist_id: playlistId,
209
+ user_id: currentUser.id,
210
+ is_public: false,
211
+ });
212
+ };
213
+
214
+ return (
215
+ <input type="file" onChange={(e) => handleUpload(e.target.files[0])} />
216
+ );
217
+ }
218
+ ```
219
+
220
+ ### Scope Features
221
+
222
+ - 🎯 **Data Segmentation**: Keep playlists, workspaces, or projects separate
223
+ - ⚡ **Faster Queries**: Filter at database level before vector search
224
+ - 🔒 **Complete Isolation**: Multi-tenant apps with full data privacy
225
+ - 🎨 **Flexible**: Define any custom attributes you need
226
+
227
+ ### With TrainlyUpload Component
228
+
229
+ ```tsx
230
+ <TrainlyUpload
231
+ variant="drag-drop"
232
+ scopeValues={{
233
+ playlist_id: "playlist_123",
234
+ workspace_id: "workspace_456",
235
+ }}
236
+ onUpload={(files) => console.log("Uploaded with scopes!")}
237
+ />
238
+ ```
239
+
240
+ ### Complete Documentation
241
+
242
+ See **[SCOPES_GUIDE.md](./SCOPES_GUIDE.md)** for:
243
+
244
+ - Complete API reference
245
+ - Real-world examples
246
+ - Advanced patterns
247
+ - Testing & debugging
248
+ - Migration guide
249
+
250
+ **Quick Reference:**
251
+
252
+ - `upload(file, scopeValues)` - Upload with scopes
253
+ - `bulkUploadFiles(files, scopeValues)` - Bulk upload with scopes
254
+ - `<TrainlyUpload scopeValues={{...}} />` - Component with scopes
145
255
  - 🔄 **Auto-Refresh**: File list updates after uploads/deletions
146
256
  - 🎨 **Pre-built UI**: `TrainlyFileManager` component with styling
147
257
  - 🔒 **Privacy-First**: Only works in V1 mode with OAuth authentication
@@ -333,7 +333,7 @@ export function TrainlyProvider(_a) {
333
333
  }
334
334
  });
335
335
  }); };
336
- var upload = function (file) { return __awaiter(_this, void 0, void 0, function () {
336
+ var upload = function (file, scopeValues) { return __awaiter(_this, void 0, void 0, function () {
337
337
  var result, err_5, errorMessage, newToken, result, refreshError_2, error_5;
338
338
  return __generator(this, function (_a) {
339
339
  switch (_a.label) {
@@ -341,7 +341,7 @@ export function TrainlyProvider(_a) {
341
341
  _a.trys.push([0, 2, 10, 11]);
342
342
  setIsLoading(true);
343
343
  setError(null);
344
- return [4 /*yield*/, client.upload(file)];
344
+ return [4 /*yield*/, client.upload(file, scopeValues)];
345
345
  case 1:
346
346
  result = _a.sent();
347
347
  return [2 /*return*/, result];
@@ -364,7 +364,7 @@ export function TrainlyProvider(_a) {
364
364
  return [4 /*yield*/, client.connectWithOAuthToken(newToken)];
365
365
  case 5:
366
366
  _a.sent();
367
- return [4 /*yield*/, client.upload(file)];
367
+ return [4 /*yield*/, client.upload(file, scopeValues)];
368
368
  case 6:
369
369
  result = _a.sent();
370
370
  console.log("✅ Upload succeeded after token refresh");
@@ -389,7 +389,7 @@ export function TrainlyProvider(_a) {
389
389
  }
390
390
  });
391
391
  }); };
392
- var bulkUploadFiles = function (files) { return __awaiter(_this, void 0, void 0, function () {
392
+ var bulkUploadFiles = function (files, scopeValues) { return __awaiter(_this, void 0, void 0, function () {
393
393
  var result, err_6, errorMessage, newToken, result, refreshError_3, error_6;
394
394
  return __generator(this, function (_a) {
395
395
  switch (_a.label) {
@@ -397,7 +397,7 @@ export function TrainlyProvider(_a) {
397
397
  _a.trys.push([0, 2, 10, 11]);
398
398
  setIsLoading(true);
399
399
  setError(null);
400
- return [4 /*yield*/, client.bulkUploadFiles(files)];
400
+ return [4 /*yield*/, client.bulkUploadFiles(files, scopeValues)];
401
401
  case 1:
402
402
  result = _a.sent();
403
403
  return [2 /*return*/, result];
@@ -420,7 +420,7 @@ export function TrainlyProvider(_a) {
420
420
  return [4 /*yield*/, client.connectWithOAuthToken(newToken)];
421
421
  case 5:
422
422
  _a.sent();
423
- return [4 /*yield*/, client.bulkUploadFiles(files)];
423
+ return [4 /*yield*/, client.bulkUploadFiles(files, scopeValues)];
424
424
  case 6:
425
425
  result = _a.sent();
426
426
  console.log("✅ Bulk upload succeeded after token refresh");
@@ -17,9 +17,10 @@ export declare class TrainlyClient {
17
17
  connect(): Promise<void>;
18
18
  ask(question: string, options?: {
19
19
  includeCitations?: boolean;
20
+ scope_filters?: Record<string, string | number | boolean>;
20
21
  }): Promise<QueryResponse>;
21
- upload(file: File): Promise<UploadResult>;
22
- bulkUploadFiles(files: File[]): Promise<BulkUploadResult>;
22
+ upload(file: File, scopeValues?: Record<string, string | number | boolean>): Promise<UploadResult>;
23
+ bulkUploadFiles(files: File[], scopeValues?: Record<string, string | number | boolean>): Promise<BulkUploadResult>;
23
24
  listFiles(): Promise<FileListResult>;
24
25
  deleteFile(fileId: string): Promise<FileDeleteResult>;
25
26
  private extractChatId;
@@ -142,7 +142,7 @@ var TrainlyClient = /** @class */ (function () {
142
142
  };
143
143
  TrainlyClient.prototype.ask = function (question_1) {
144
144
  return __awaiter(this, arguments, void 0, function (question, options) {
145
- var response_1, error, data_1, url, headers, body, response, error, data;
145
+ var params, response_1, error, data_1, url, headers, body, response, error, data;
146
146
  if (options === void 0) { options = {}; }
147
147
  return __generator(this, function (_a) {
148
148
  switch (_a.label) {
@@ -151,6 +151,15 @@ var TrainlyClient = /** @class */ (function () {
151
151
  throw new Error("Not connected. Call connect() or connectWithOAuthToken() first.");
152
152
  }
153
153
  if (!(this.isV1Mode && this.config.appId)) return [3 /*break*/, 5];
154
+ params = {
155
+ messages: JSON.stringify([{ role: "user", content: question }]),
156
+ response_tokens: "150",
157
+ };
158
+ // Add scope filters if provided
159
+ if (options.scope_filters &&
160
+ Object.keys(options.scope_filters).length > 0) {
161
+ params.scope_filters = JSON.stringify(options.scope_filters);
162
+ }
154
163
  return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/me/chats/query"), {
155
164
  method: "POST",
156
165
  headers: {
@@ -158,10 +167,7 @@ var TrainlyClient = /** @class */ (function () {
158
167
  "X-App-ID": this.config.appId,
159
168
  "Content-Type": "application/x-www-form-urlencoded",
160
169
  },
161
- body: new URLSearchParams({
162
- messages: JSON.stringify([{ role: "user", content: question }]),
163
- response_tokens: "150",
164
- }),
170
+ body: new URLSearchParams(params),
165
171
  })];
166
172
  case 1:
167
173
  response_1 = _a.sent();
@@ -218,7 +224,7 @@ var TrainlyClient = /** @class */ (function () {
218
224
  });
219
225
  });
220
226
  };
221
- TrainlyClient.prototype.upload = function (file) {
227
+ TrainlyClient.prototype.upload = function (file, scopeValues) {
222
228
  return __awaiter(this, void 0, void 0, function () {
223
229
  var formData, response, error, data, formData, response, error, presignedResponse, error, _a, upload_url, upload_headers, formData, uploadResponse;
224
230
  return __generator(this, function (_b) {
@@ -230,6 +236,10 @@ var TrainlyClient = /** @class */ (function () {
230
236
  if (!(this.isV1Mode && this.config.appId)) return [3 /*break*/, 5];
231
237
  formData = new FormData();
232
238
  formData.append("file", file);
239
+ // Add scope values if provided
240
+ if (scopeValues && Object.keys(scopeValues).length > 0) {
241
+ formData.append("scope_values", JSON.stringify(scopeValues));
242
+ }
233
243
  return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/me/chats/files/upload"), {
234
244
  method: "POST",
235
245
  headers: {
@@ -258,6 +268,10 @@ var TrainlyClient = /** @class */ (function () {
258
268
  if (!this.config.apiKey) return [3 /*break*/, 9];
259
269
  formData = new FormData();
260
270
  formData.append("file", file);
271
+ // Add scope values if provided
272
+ if (scopeValues && Object.keys(scopeValues).length > 0) {
273
+ formData.append("scope_values", JSON.stringify(scopeValues));
274
+ }
261
275
  return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/").concat(this.extractChatId(), "/upload_file"), {
262
276
  method: "POST",
263
277
  headers: {
@@ -324,7 +338,7 @@ var TrainlyClient = /** @class */ (function () {
324
338
  });
325
339
  });
326
340
  };
327
- TrainlyClient.prototype.bulkUploadFiles = function (files) {
341
+ TrainlyClient.prototype.bulkUploadFiles = function (files, scopeValues) {
328
342
  return __awaiter(this, void 0, void 0, function () {
329
343
  var formData_1, response, error, data, results, successful_uploads, total_size_bytes, _i, files_1, file, uploadResult, error_1;
330
344
  return __generator(this, function (_a) {
@@ -345,6 +359,10 @@ var TrainlyClient = /** @class */ (function () {
345
359
  files.forEach(function (file) {
346
360
  formData_1.append("files", file);
347
361
  });
362
+ // Add scope values if provided
363
+ if (scopeValues && Object.keys(scopeValues).length > 0) {
364
+ formData_1.append("scope_values", JSON.stringify(scopeValues));
365
+ }
348
366
  return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/me/chats/files/upload-bulk"), {
349
367
  method: "POST",
350
368
  headers: {
@@ -386,7 +404,7 @@ var TrainlyClient = /** @class */ (function () {
386
404
  _a.label = 7;
387
405
  case 7:
388
406
  _a.trys.push([7, 9, , 10]);
389
- return [4 /*yield*/, this.upload(file)];
407
+ return [4 /*yield*/, this.upload(file, scopeValues)];
390
408
  case 8:
391
409
  uploadResult = _a.sent();
392
410
  results.push({
@@ -6,5 +6,6 @@ export interface TrainlyUploadProps {
6
6
  className?: string;
7
7
  onUpload?: (files: File[]) => void;
8
8
  onError?: (error: string) => void;
9
+ scopeValues?: Record<string, string | number | boolean>;
9
10
  }
10
- export declare function TrainlyUpload({ variant, accept, maxSize, multiple, className, onUpload, onError, }: TrainlyUploadProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function TrainlyUpload({ variant, accept, maxSize, multiple, className, onUpload, onError, scopeValues, }: TrainlyUploadProps): import("react/jsx-runtime").JSX.Element;
@@ -40,7 +40,7 @@ import * as React from "react";
40
40
  import { useTrainly } from "../useTrainly";
41
41
  export function TrainlyUpload(_a) {
42
42
  var _this = this;
43
- var _b = _a.variant, variant = _b === void 0 ? "drag-drop" : _b, _c = _a.accept, accept = _c === void 0 ? ".pdf,.doc,.docx,.txt,.md" : _c, _d = _a.maxSize, maxSize = _d === void 0 ? "10MB" : _d, _e = _a.multiple, multiple = _e === void 0 ? false : _e, _f = _a.className, className = _f === void 0 ? "" : _f, onUpload = _a.onUpload, onError = _a.onError;
43
+ var _b = _a.variant, variant = _b === void 0 ? "drag-drop" : _b, _c = _a.accept, accept = _c === void 0 ? ".pdf,.doc,.docx,.txt,.md" : _c, _d = _a.maxSize, maxSize = _d === void 0 ? "10MB" : _d, _e = _a.multiple, multiple = _e === void 0 ? false : _e, _f = _a.className, className = _f === void 0 ? "" : _f, onUpload = _a.onUpload, onError = _a.onError, scopeValues = _a.scopeValues;
44
44
  var _g = useTrainly(), upload = _g.upload, isLoading = _g.isLoading;
45
45
  var _h = React.useState(false), isDragOver = _h[0], setIsDragOver = _h[1];
46
46
  var fileInputRef = React.useRef(null);
@@ -70,7 +70,7 @@ export function TrainlyUpload(_a) {
70
70
  case 2:
71
71
  if (!(_a < fileArray_2.length)) return [3 /*break*/, 5];
72
72
  file = fileArray_2[_a];
73
- return [4 /*yield*/, upload(file)];
73
+ return [4 /*yield*/, upload(file, scopeValues)];
74
74
  case 3:
75
75
  _b.sent();
76
76
  _b.label = 4;
package/dist/types.d.ts CHANGED
@@ -93,10 +93,10 @@ export interface TrainlyContextValue {
93
93
  answer: string;
94
94
  citations: Citation[];
95
95
  }>;
96
- upload: (file: File) => Promise<UploadResult>;
96
+ upload: (file: File, scopeValues?: Record<string, string | number | boolean>) => Promise<UploadResult>;
97
97
  listFiles: () => Promise<FileListResult>;
98
98
  deleteFile: (fileId: string) => Promise<FileDeleteResult>;
99
- bulkUploadFiles: (files: File[]) => Promise<BulkUploadResult>;
99
+ bulkUploadFiles: (files: File[], scopeValues?: Record<string, string | number | boolean>) => Promise<BulkUploadResult>;
100
100
  connectWithOAuthToken: (idToken: string) => Promise<void>;
101
101
  isLoading: boolean;
102
102
  isConnected: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@trainly/react",
3
- "version": "1.3.1",
4
- "description": "Dead simple RAG integration for React apps with OAuth authentication",
3
+ "version": "1.4.1",
4
+ "description": "Dead simple RAG integration for React apps with OAuth authentication and custom scopes",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
@@ -28,7 +28,11 @@
28
28
  "document-chat",
29
29
  "file-management",
30
30
  "file-upload",
31
- "file-deletion"
31
+ "file-deletion",
32
+ "custom-scopes",
33
+ "scope-filtering",
34
+ "data-segmentation",
35
+ "multi-tenant"
32
36
  ],
33
37
  "author": "Trainly",
34
38
  "license": "MIT",