@trainly/react 1.1.3 → 1.2.0

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,6 +86,66 @@ 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**
90
+
91
+ Now users can manage their uploaded files directly:
92
+
93
+ ```tsx
94
+ import { useTrainly, TrainlyFileManager } from "@trainly/react";
95
+
96
+ function MyApp() {
97
+ const { listFiles, deleteFile, upload } = useTrainly();
98
+
99
+ // List all user's files
100
+ const handleListFiles = async () => {
101
+ const result = await listFiles();
102
+ console.log(
103
+ `${result.total_files} files, ${result.total_size_bytes} bytes total`,
104
+ );
105
+ result.files.forEach((file) => {
106
+ console.log(
107
+ `${file.filename}: ${file.size_bytes} bytes, ${file.chunk_count} chunks`,
108
+ );
109
+ });
110
+ };
111
+
112
+ // Delete a specific file
113
+ const handleDeleteFile = async (fileId) => {
114
+ const result = await deleteFile(fileId);
115
+ console.log(
116
+ `Deleted ${result.filename}, freed ${result.size_bytes_freed} bytes`,
117
+ );
118
+ };
119
+
120
+ return (
121
+ <div>
122
+ <button onClick={handleListFiles}>List My Files</button>
123
+
124
+ {/* Pre-built file manager component */}
125
+ <TrainlyFileManager
126
+ onFileDeleted={(fileId, filename) => {
127
+ console.log(`File deleted: ${filename}`);
128
+ }}
129
+ onError={(error) => {
130
+ console.error("File operation failed:", error);
131
+ }}
132
+ showUploadButton={true}
133
+ maxFileSize={5} // MB
134
+ />
135
+ </div>
136
+ );
137
+ }
138
+ ```
139
+
140
+ ### File Management Features
141
+
142
+ - 📋 **List Files**: View all uploaded documents with metadata
143
+ - 🗑️ **Delete Files**: Remove files and free up storage space
144
+ - 📊 **Storage Analytics**: Track file sizes and storage usage
145
+ - 🔄 **Auto-Refresh**: File list updates after uploads/deletions
146
+ - 🎨 **Pre-built UI**: `TrainlyFileManager` component with styling
147
+ - 🔒 **Privacy-First**: Only works in V1 mode with OAuth authentication
148
+
89
149
  ---
90
150
 
91
151
  ## 🚀 Original Quick Start (Legacy)
@@ -389,8 +389,120 @@ export function TrainlyProvider(_a) {
389
389
  }
390
390
  });
391
391
  }); };
392
+ var listFiles = function () { return __awaiter(_this, void 0, void 0, function () {
393
+ var result, err_6, errorMessage, newToken, result, refreshError_3, error_6;
394
+ return __generator(this, function (_a) {
395
+ switch (_a.label) {
396
+ case 0:
397
+ _a.trys.push([0, 2, 10, 11]);
398
+ setIsLoading(true);
399
+ setError(null);
400
+ return [4 /*yield*/, client.listFiles()];
401
+ case 1:
402
+ result = _a.sent();
403
+ return [2 /*return*/, result];
404
+ case 2:
405
+ err_6 = _a.sent();
406
+ errorMessage = err_6 instanceof Error ? err_6.message : String(err_6);
407
+ if (!(getToken &&
408
+ appId &&
409
+ (errorMessage.includes("401") ||
410
+ errorMessage.includes("authentication") ||
411
+ errorMessage.includes("Unauthorized")))) return [3 /*break*/, 9];
412
+ _a.label = 3;
413
+ case 3:
414
+ _a.trys.push([3, 8, , 9]);
415
+ console.log("🔄 Token expired during file listing, refreshing...");
416
+ return [4 /*yield*/, getToken()];
417
+ case 4:
418
+ newToken = _a.sent();
419
+ if (!newToken) return [3 /*break*/, 7];
420
+ return [4 /*yield*/, client.connectWithOAuthToken(newToken)];
421
+ case 5:
422
+ _a.sent();
423
+ return [4 /*yield*/, client.listFiles()];
424
+ case 6:
425
+ result = _a.sent();
426
+ console.log("✅ File listing succeeded after token refresh");
427
+ return [2 /*return*/, result];
428
+ case 7: return [3 /*break*/, 9];
429
+ case 8:
430
+ refreshError_3 = _a.sent();
431
+ console.error("❌ Token refresh failed:", refreshError_3);
432
+ return [3 /*break*/, 9];
433
+ case 9:
434
+ error_6 = {
435
+ code: "LIST_FILES_FAILED",
436
+ message: "Failed to list files",
437
+ details: err_6,
438
+ };
439
+ setError(error_6);
440
+ throw error_6;
441
+ case 10:
442
+ setIsLoading(false);
443
+ return [7 /*endfinally*/];
444
+ case 11: return [2 /*return*/];
445
+ }
446
+ });
447
+ }); };
448
+ var deleteFile = function (fileId) { return __awaiter(_this, void 0, void 0, function () {
449
+ var result, err_7, errorMessage, newToken, result, refreshError_4, error_7;
450
+ return __generator(this, function (_a) {
451
+ switch (_a.label) {
452
+ case 0:
453
+ _a.trys.push([0, 2, 10, 11]);
454
+ setIsLoading(true);
455
+ setError(null);
456
+ return [4 /*yield*/, client.deleteFile(fileId)];
457
+ case 1:
458
+ result = _a.sent();
459
+ return [2 /*return*/, result];
460
+ case 2:
461
+ err_7 = _a.sent();
462
+ errorMessage = err_7 instanceof Error ? err_7.message : String(err_7);
463
+ if (!(getToken &&
464
+ appId &&
465
+ (errorMessage.includes("401") ||
466
+ errorMessage.includes("authentication") ||
467
+ errorMessage.includes("Unauthorized")))) return [3 /*break*/, 9];
468
+ _a.label = 3;
469
+ case 3:
470
+ _a.trys.push([3, 8, , 9]);
471
+ console.log("🔄 Token expired during file deletion, refreshing...");
472
+ return [4 /*yield*/, getToken()];
473
+ case 4:
474
+ newToken = _a.sent();
475
+ if (!newToken) return [3 /*break*/, 7];
476
+ return [4 /*yield*/, client.connectWithOAuthToken(newToken)];
477
+ case 5:
478
+ _a.sent();
479
+ return [4 /*yield*/, client.deleteFile(fileId)];
480
+ case 6:
481
+ result = _a.sent();
482
+ console.log("✅ File deletion succeeded after token refresh");
483
+ return [2 /*return*/, result];
484
+ case 7: return [3 /*break*/, 9];
485
+ case 8:
486
+ refreshError_4 = _a.sent();
487
+ console.error("❌ Token refresh failed:", refreshError_4);
488
+ return [3 /*break*/, 9];
489
+ case 9:
490
+ error_7 = {
491
+ code: "DELETE_FILE_FAILED",
492
+ message: "Failed to delete file",
493
+ details: err_7,
494
+ };
495
+ setError(error_7);
496
+ throw error_7;
497
+ case 10:
498
+ setIsLoading(false);
499
+ return [7 /*endfinally*/];
500
+ case 11: return [2 /*return*/];
501
+ }
502
+ });
503
+ }); };
392
504
  var sendMessage = function (content) { return __awaiter(_this, void 0, void 0, function () {
393
- var userMessage, response, assistantMessage_1, err_6;
505
+ var userMessage, response, assistantMessage_1, err_8;
394
506
  return __generator(this, function (_a) {
395
507
  switch (_a.label) {
396
508
  case 0:
@@ -417,9 +529,9 @@ export function TrainlyProvider(_a) {
417
529
  setMessages(function (prev) { return __spreadArray(__spreadArray([], prev, true), [assistantMessage_1], false); });
418
530
  return [3 /*break*/, 4];
419
531
  case 3:
420
- err_6 = _a.sent();
532
+ err_8 = _a.sent();
421
533
  // Error is already set by askWithCitations
422
- console.error("Failed to send message:", err_6);
534
+ console.error("Failed to send message:", err_8);
423
535
  return [3 /*break*/, 4];
424
536
  case 4: return [2 /*return*/];
425
537
  }
@@ -432,6 +544,8 @@ export function TrainlyProvider(_a) {
432
544
  ask: ask,
433
545
  askWithCitations: askWithCitations,
434
546
  upload: upload,
547
+ listFiles: listFiles, // NEW: File management methods
548
+ deleteFile: deleteFile,
435
549
  connectWithOAuthToken: connectWithOAuthToken, // NEW: V1 OAuth connection method
436
550
  isLoading: isLoading,
437
551
  isConnected: isConnected,
@@ -1,4 +1,4 @@
1
- import { TrainlyConfig, Citation, UploadResult } from "../types";
1
+ import { TrainlyConfig, Citation, UploadResult, FileListResult, FileDeleteResult } from "../types";
2
2
  interface QueryResponse {
3
3
  answer: string;
4
4
  citations?: Citation[];
@@ -19,6 +19,8 @@ export declare class TrainlyClient {
19
19
  includeCitations?: boolean;
20
20
  }): Promise<QueryResponse>;
21
21
  upload(file: File): Promise<UploadResult>;
22
+ listFiles(): Promise<FileListResult>;
23
+ deleteFile(fileId: string): Promise<FileDeleteResult>;
22
24
  private extractChatId;
23
25
  private generateAnonymousId;
24
26
  }
@@ -324,6 +324,81 @@ var TrainlyClient = /** @class */ (function () {
324
324
  });
325
325
  });
326
326
  };
327
+ TrainlyClient.prototype.listFiles = function () {
328
+ return __awaiter(this, void 0, void 0, function () {
329
+ var response, error, data;
330
+ return __generator(this, function (_a) {
331
+ switch (_a.label) {
332
+ case 0:
333
+ if (!this.scopedToken) {
334
+ throw new Error("Not connected. Call connect() or connectWithOAuthToken() first.");
335
+ }
336
+ if (!(this.isV1Mode && this.config.appId)) return [3 /*break*/, 5];
337
+ return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/me/chats/files"), {
338
+ method: "GET",
339
+ headers: {
340
+ Authorization: "Bearer ".concat(this.scopedToken),
341
+ "X-App-ID": this.config.appId,
342
+ },
343
+ })];
344
+ case 1:
345
+ response = _a.sent();
346
+ if (!!response.ok) return [3 /*break*/, 3];
347
+ return [4 /*yield*/, response.json()];
348
+ case 2:
349
+ error = _a.sent();
350
+ throw new Error("V1 list files failed: ".concat(error.detail || response.statusText));
351
+ case 3: return [4 /*yield*/, response.json()];
352
+ case 4:
353
+ data = _a.sent();
354
+ return [2 /*return*/, data];
355
+ case 5:
356
+ // For other modes, this functionality is not yet implemented
357
+ // as it requires chat-specific API endpoints
358
+ throw new Error("File listing is currently only available in V1 Trusted Issuer mode");
359
+ }
360
+ });
361
+ });
362
+ };
363
+ TrainlyClient.prototype.deleteFile = function (fileId) {
364
+ return __awaiter(this, void 0, void 0, function () {
365
+ var response, error, data;
366
+ return __generator(this, function (_a) {
367
+ switch (_a.label) {
368
+ case 0:
369
+ if (!this.scopedToken) {
370
+ throw new Error("Not connected. Call connect() or connectWithOAuthToken() first.");
371
+ }
372
+ if (!fileId) {
373
+ throw new Error("File ID is required");
374
+ }
375
+ if (!(this.isV1Mode && this.config.appId)) return [3 /*break*/, 5];
376
+ return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/me/chats/files/").concat(encodeURIComponent(fileId)), {
377
+ method: "DELETE",
378
+ headers: {
379
+ Authorization: "Bearer ".concat(this.scopedToken),
380
+ "X-App-ID": this.config.appId,
381
+ },
382
+ })];
383
+ case 1:
384
+ response = _a.sent();
385
+ if (!!response.ok) return [3 /*break*/, 3];
386
+ return [4 /*yield*/, response.json()];
387
+ case 2:
388
+ error = _a.sent();
389
+ throw new Error("V1 delete file failed: ".concat(error.detail || response.statusText));
390
+ case 3: return [4 /*yield*/, response.json()];
391
+ case 4:
392
+ data = _a.sent();
393
+ return [2 /*return*/, data];
394
+ case 5:
395
+ // For other modes, this functionality is not yet implemented
396
+ // as it requires chat-specific API endpoints
397
+ throw new Error("File deletion is currently only available in V1 Trusted Issuer mode");
398
+ }
399
+ });
400
+ });
401
+ };
327
402
  TrainlyClient.prototype.extractChatId = function () {
328
403
  if (!this.config.apiKey) {
329
404
  throw new Error("API key not provided");
@@ -0,0 +1,8 @@
1
+ export interface TrainlyFileManagerProps {
2
+ className?: string;
3
+ onFileDeleted?: (fileId: string, filename: string) => void;
4
+ onError?: (error: Error) => void;
5
+ showUploadButton?: boolean;
6
+ maxFileSize?: number;
7
+ }
8
+ export declare function TrainlyFileManager({ className, onFileDeleted, onError, showUploadButton, maxFileSize, }: TrainlyFileManagerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,304 @@
1
+ "use client";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
24
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
50
+ import * as React from "react";
51
+ import { useTrainly } from "../useTrainly";
52
+ export function TrainlyFileManager(_a) {
53
+ var _this = this;
54
+ var _b = _a.className, className = _b === void 0 ? "" : _b, onFileDeleted = _a.onFileDeleted, onError = _a.onError, _c = _a.showUploadButton, showUploadButton = _c === void 0 ? true : _c, _d = _a.maxFileSize, maxFileSize = _d === void 0 ? 5 : _d;
55
+ var _e = useTrainly(), listFiles = _e.listFiles, deleteFile = _e.deleteFile, upload = _e.upload, isLoading = _e.isLoading, error = _e.error;
56
+ var _f = React.useState([]), files = _f[0], setFiles = _f[1];
57
+ var _g = React.useState(false), isLoadingFiles = _g[0], setIsLoadingFiles = _g[1];
58
+ var _h = React.useState(null), isDeletingFile = _h[0], setIsDeletingFile = _h[1];
59
+ var _j = React.useState(false), isUploading = _j[0], setIsUploading = _j[1];
60
+ var fileInputRef = React.useRef(null);
61
+ // Load files on component mount
62
+ React.useEffect(function () {
63
+ loadFiles();
64
+ }, []);
65
+ var loadFiles = function () { return __awaiter(_this, void 0, void 0, function () {
66
+ var result, err_1, error_1;
67
+ return __generator(this, function (_a) {
68
+ switch (_a.label) {
69
+ case 0:
70
+ _a.trys.push([0, 2, 3, 4]);
71
+ setIsLoadingFiles(true);
72
+ return [4 /*yield*/, listFiles()];
73
+ case 1:
74
+ result = _a.sent();
75
+ setFiles(result.files);
76
+ return [3 /*break*/, 4];
77
+ case 2:
78
+ err_1 = _a.sent();
79
+ error_1 = err_1 instanceof Error ? err_1 : new Error(String(err_1));
80
+ console.error("Failed to load files:", error_1);
81
+ onError === null || onError === void 0 ? void 0 : onError(error_1);
82
+ return [3 /*break*/, 4];
83
+ case 3:
84
+ setIsLoadingFiles(false);
85
+ return [7 /*endfinally*/];
86
+ case 4: return [2 /*return*/];
87
+ }
88
+ });
89
+ }); };
90
+ var handleDeleteFile = function (fileId, filename) { return __awaiter(_this, void 0, void 0, function () {
91
+ var result, err_2, error_2;
92
+ return __generator(this, function (_a) {
93
+ switch (_a.label) {
94
+ case 0:
95
+ if (!confirm("Are you sure you want to delete \"".concat(filename, "\"? This action cannot be undone."))) {
96
+ return [2 /*return*/];
97
+ }
98
+ _a.label = 1;
99
+ case 1:
100
+ _a.trys.push([1, 3, 4, 5]);
101
+ setIsDeletingFile(fileId);
102
+ return [4 /*yield*/, deleteFile(fileId)];
103
+ case 2:
104
+ result = _a.sent();
105
+ // Remove file from local state
106
+ setFiles(function (prev) { return prev.filter(function (f) { return f.file_id !== fileId; }); });
107
+ // Notify parent component
108
+ onFileDeleted === null || onFileDeleted === void 0 ? void 0 : onFileDeleted(fileId, filename);
109
+ console.log("File deleted: ".concat(result.message));
110
+ return [3 /*break*/, 5];
111
+ case 3:
112
+ err_2 = _a.sent();
113
+ error_2 = err_2 instanceof Error ? err_2 : new Error(String(err_2));
114
+ console.error("Failed to delete file:", error_2);
115
+ onError === null || onError === void 0 ? void 0 : onError(error_2);
116
+ return [3 /*break*/, 5];
117
+ case 4:
118
+ setIsDeletingFile(null);
119
+ return [7 /*endfinally*/];
120
+ case 5: return [2 /*return*/];
121
+ }
122
+ });
123
+ }); };
124
+ var handleFileUpload = function (event) { return __awaiter(_this, void 0, void 0, function () {
125
+ var file, error_3, result, err_3, error_4;
126
+ var _a;
127
+ return __generator(this, function (_b) {
128
+ switch (_b.label) {
129
+ case 0:
130
+ file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
131
+ if (!file)
132
+ return [2 /*return*/];
133
+ // Check file size
134
+ if (file.size > maxFileSize * 1024 * 1024) {
135
+ error_3 = new Error("File size must be less than ".concat(maxFileSize, "MB"));
136
+ onError === null || onError === void 0 ? void 0 : onError(error_3);
137
+ return [2 /*return*/];
138
+ }
139
+ _b.label = 1;
140
+ case 1:
141
+ _b.trys.push([1, 5, 6, 7]);
142
+ setIsUploading(true);
143
+ return [4 /*yield*/, upload(file)];
144
+ case 2:
145
+ result = _b.sent();
146
+ if (!result.success) return [3 /*break*/, 4];
147
+ // Reload files to show the new upload
148
+ return [4 /*yield*/, loadFiles()];
149
+ case 3:
150
+ // Reload files to show the new upload
151
+ _b.sent();
152
+ console.log("File uploaded: ".concat(result.filename));
153
+ _b.label = 4;
154
+ case 4: return [3 /*break*/, 7];
155
+ case 5:
156
+ err_3 = _b.sent();
157
+ error_4 = err_3 instanceof Error ? err_3 : new Error(String(err_3));
158
+ console.error("Failed to upload file:", error_4);
159
+ onError === null || onError === void 0 ? void 0 : onError(error_4);
160
+ return [3 /*break*/, 7];
161
+ case 6:
162
+ setIsUploading(false);
163
+ // Clear the input
164
+ if (fileInputRef.current) {
165
+ fileInputRef.current.value = "";
166
+ }
167
+ return [7 /*endfinally*/];
168
+ case 7: return [2 /*return*/];
169
+ }
170
+ });
171
+ }); };
172
+ var formatFileSize = function (bytes) {
173
+ if (bytes === 0)
174
+ return "0 Bytes";
175
+ var k = 1024;
176
+ var sizes = ["Bytes", "KB", "MB", "GB"];
177
+ var i = Math.floor(Math.log(bytes) / Math.log(k));
178
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
179
+ };
180
+ var formatDate = function (dateString) {
181
+ try {
182
+ var date = new Date(parseInt(dateString));
183
+ return date.toLocaleDateString() + " " + date.toLocaleTimeString();
184
+ }
185
+ catch (_a) {
186
+ return dateString;
187
+ }
188
+ };
189
+ var totalSize = files.reduce(function (sum, file) { return sum + file.size_bytes; }, 0);
190
+ var styles = {
191
+ container: {
192
+ border: "1px solid #e5e7eb",
193
+ borderRadius: "8px",
194
+ padding: "16px",
195
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif",
196
+ },
197
+ header: {
198
+ display: "flex",
199
+ justifyContent: "space-between",
200
+ alignItems: "flex-start",
201
+ marginBottom: "16px",
202
+ },
203
+ title: {
204
+ margin: "0 0 4px 0",
205
+ fontSize: "18px",
206
+ fontWeight: "600",
207
+ color: "#1f2937",
208
+ },
209
+ totalSize: {
210
+ margin: "0",
211
+ fontSize: "14px",
212
+ color: "#6b7280",
213
+ },
214
+ uploadButton: {
215
+ backgroundColor: "#3b82f6",
216
+ color: "white",
217
+ border: "none",
218
+ borderRadius: "6px",
219
+ padding: "8px 16px",
220
+ fontSize: "14px",
221
+ cursor: "pointer",
222
+ transition: "background-color 0.2s",
223
+ },
224
+ uploadButtonDisabled: {
225
+ backgroundColor: "#9ca3af",
226
+ cursor: "not-allowed",
227
+ },
228
+ error: {
229
+ backgroundColor: "#fef2f2",
230
+ color: "#dc2626",
231
+ padding: "12px",
232
+ borderRadius: "6px",
233
+ marginBottom: "16px",
234
+ fontSize: "14px",
235
+ },
236
+ loading: {
237
+ textAlign: "center",
238
+ padding: "32px",
239
+ color: "#6b7280",
240
+ fontSize: "14px",
241
+ },
242
+ emptyState: {
243
+ textAlign: "center",
244
+ padding: "32px",
245
+ color: "#6b7280",
246
+ },
247
+ emptyStateText: {
248
+ margin: "0 0 8px 0",
249
+ fontSize: "14px",
250
+ },
251
+ fileItem: {
252
+ display: "flex",
253
+ justifyContent: "space-between",
254
+ alignItems: "center",
255
+ padding: "12px",
256
+ border: "1px solid #f3f4f6",
257
+ borderRadius: "6px",
258
+ backgroundColor: "#f9fafb",
259
+ marginBottom: "8px",
260
+ transition: "background-color 0.2s",
261
+ },
262
+ fileName: {
263
+ fontWeight: "500",
264
+ color: "#1f2937",
265
+ fontSize: "14px",
266
+ marginBottom: "4px",
267
+ },
268
+ fileMeta: {
269
+ fontSize: "12px",
270
+ color: "#6b7280",
271
+ },
272
+ deleteButton: {
273
+ backgroundColor: "#dc2626",
274
+ color: "white",
275
+ border: "none",
276
+ borderRadius: "4px",
277
+ padding: "6px 12px",
278
+ fontSize: "12px",
279
+ cursor: "pointer",
280
+ transition: "background-color 0.2s",
281
+ },
282
+ deleteButtonDisabled: {
283
+ backgroundColor: "#9ca3af",
284
+ cursor: "not-allowed",
285
+ },
286
+ };
287
+ return (_jsxs("div", { className: className, style: styles.container, children: [_jsxs("div", { style: styles.header, children: [_jsxs("div", { children: [_jsxs("h3", { style: styles.title, children: ["Your Files (", files.length, ")"] }), _jsxs("p", { style: styles.totalSize, children: ["Total: ", formatFileSize(totalSize)] })] }), showUploadButton && (_jsxs("div", { children: [_jsx("input", { ref: fileInputRef, type: "file", onChange: handleFileUpload, disabled: isUploading || isLoading, accept: ".pdf,.docx,.txt,.md,.csv,.json,.html,.xml,.yaml,.yml,.js,.py,.java,.cpp,.c,.h,.cs,.php,.rb,.sh,.bat,.ps1", style: { display: "none" } }), _jsx("button", { onClick: function () { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, disabled: isUploading || isLoading, style: __assign(__assign({}, styles.uploadButton), (isUploading || isLoading
288
+ ? styles.uploadButtonDisabled
289
+ : {})), children: isUploading ? "Uploading..." : "Upload File" })] }))] }), error && _jsx("div", { style: styles.error, children: error.message }), isLoadingFiles ? (_jsx("div", { style: styles.loading, children: "Loading files..." })) : files.length === 0 ? (_jsxs("div", { style: styles.emptyState, children: [_jsx("p", { style: styles.emptyStateText, children: "No files uploaded yet." }), showUploadButton && (_jsx("p", { style: styles.emptyStateText, children: "Click \"Upload File\" to add your first document." }))] })) : (_jsx("div", { children: files.map(function (file) { return (_jsxs("div", { style: styles.fileItem, onMouseEnter: function (e) {
290
+ e.currentTarget.style.backgroundColor = "#f3f4f6";
291
+ }, onMouseLeave: function (e) {
292
+ e.currentTarget.style.backgroundColor = "#f9fafb";
293
+ }, children: [_jsxs("div", { children: [_jsx("div", { style: styles.fileName, children: file.filename }), _jsxs("div", { style: styles.fileMeta, children: [formatFileSize(file.size_bytes), " \u2022 ", file.chunk_count, " chunks \u2022 ", formatDate(file.upload_date)] })] }), _jsx("div", { children: _jsx("button", { onClick: function () { return handleDeleteFile(file.file_id, file.filename); }, disabled: isDeletingFile === file.file_id || isLoading, style: __assign(__assign({}, styles.deleteButton), (isDeletingFile === file.file_id || isLoading
294
+ ? styles.deleteButtonDisabled
295
+ : {})), onMouseEnter: function (e) {
296
+ if (!e.currentTarget.disabled) {
297
+ e.currentTarget.style.backgroundColor = "#b91c1c";
298
+ }
299
+ }, onMouseLeave: function (e) {
300
+ if (!e.currentTarget.disabled) {
301
+ e.currentTarget.style.backgroundColor = "#dc2626";
302
+ }
303
+ }, children: isDeletingFile === file.file_id ? "Deleting..." : "Delete" }) })] }, file.file_id)); }) }))] }));
304
+ }
package/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export { useTrainly } from "./useTrainly";
3
3
  export { TrainlyChat } from "./components/TrainlyChat";
4
4
  export { TrainlyUpload } from "./components/TrainlyUpload";
5
5
  export { TrainlyStatus } from "./components/TrainlyStatus";
6
- export type { TrainlyProviderProps, TrainlyConfig, ChatMessage, Citation, UploadResult, TrainlyError, } from "./types";
6
+ export { TrainlyFileManager } from "./components/TrainlyFileManager";
7
+ export type { TrainlyProviderProps, TrainlyConfig, ChatMessage, Citation, UploadResult, FileInfo, FileListResult, FileDeleteResult, TrainlyError, TrainlyFileManagerProps, } from "./types";
package/dist/index.js CHANGED
@@ -5,3 +5,4 @@ export { useTrainly } from "./useTrainly";
5
5
  export { TrainlyChat } from "./components/TrainlyChat";
6
6
  export { TrainlyUpload } from "./components/TrainlyUpload";
7
7
  export { TrainlyStatus } from "./components/TrainlyStatus";
8
+ export { TrainlyFileManager } from "./components/TrainlyFileManager";
package/dist/types.d.ts CHANGED
@@ -34,11 +34,39 @@ export interface UploadResult {
34
34
  size: number;
35
35
  message?: string;
36
36
  }
37
+ export interface FileInfo {
38
+ file_id: string;
39
+ filename: string;
40
+ upload_date: string;
41
+ size_bytes: number;
42
+ chunk_count: number;
43
+ }
44
+ export interface FileListResult {
45
+ success: boolean;
46
+ files: FileInfo[];
47
+ total_files: number;
48
+ total_size_bytes: number;
49
+ }
50
+ export interface FileDeleteResult {
51
+ success: boolean;
52
+ message: string;
53
+ file_id: string;
54
+ filename: string;
55
+ chunks_deleted: number;
56
+ size_bytes_freed: number;
57
+ }
37
58
  export interface TrainlyError {
38
59
  code: string;
39
60
  message: string;
40
61
  details?: any;
41
62
  }
63
+ export interface TrainlyFileManagerProps {
64
+ className?: string;
65
+ onFileDeleted?: (fileId: string, filename: string) => void;
66
+ onError?: (error: Error) => void;
67
+ showUploadButton?: boolean;
68
+ maxFileSize?: number;
69
+ }
42
70
  export interface TrainlyContextValue {
43
71
  ask: (question: string) => Promise<string>;
44
72
  askWithCitations: (question: string) => Promise<{
@@ -46,6 +74,8 @@ export interface TrainlyContextValue {
46
74
  citations: Citation[];
47
75
  }>;
48
76
  upload: (file: File) => Promise<UploadResult>;
77
+ listFiles: () => Promise<FileListResult>;
78
+ deleteFile: (fileId: string) => Promise<FileDeleteResult>;
49
79
  connectWithOAuthToken: (idToken: string) => Promise<void>;
50
80
  isLoading: boolean;
51
81
  isConnected: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trainly/react",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "Dead simple RAG integration for React apps with OAuth authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,7 +25,10 @@
25
25
  "auth0",
26
26
  "retrieval-augmented-generation",
27
27
  "semantic-search",
28
- "document-chat"
28
+ "document-chat",
29
+ "file-management",
30
+ "file-upload",
31
+ "file-deletion"
29
32
  ],
30
33
  "author": "Trainly",
31
34
  "license": "MIT",