@trainly/react 1.1.3 → 1.3.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 +587 -0
- package/dist/TrainlyProvider.js +174 -3
- package/dist/api/TrainlyClient.d.ts +4 -1
- package/dist/api/TrainlyClient.js +183 -0
- package/dist/components/TrainlyFileManager.d.ts +8 -0
- package/dist/components/TrainlyFileManager.js +304 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/types.d.ts +51 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -86,6 +86,593 @@ 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
|
+
|
|
149
|
+
## 📚 **Detailed File Management Documentation**
|
|
150
|
+
|
|
151
|
+
### **1. Listing Files**
|
|
152
|
+
|
|
153
|
+
Get all files uploaded to the user's permanent subchat:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
import { useTrainly } from "@trainly/react";
|
|
157
|
+
|
|
158
|
+
function FileList() {
|
|
159
|
+
const { listFiles } = useTrainly();
|
|
160
|
+
|
|
161
|
+
const handleListFiles = async () => {
|
|
162
|
+
try {
|
|
163
|
+
const result = await listFiles();
|
|
164
|
+
|
|
165
|
+
console.log(`Total files: ${result.total_files}`);
|
|
166
|
+
console.log(`Total storage: ${formatBytes(result.total_size_bytes)}`);
|
|
167
|
+
|
|
168
|
+
result.files.forEach((file) => {
|
|
169
|
+
console.log(`📄 ${file.filename}`);
|
|
170
|
+
console.log(` Size: ${formatBytes(file.size_bytes)}`);
|
|
171
|
+
console.log(` Chunks: ${file.chunk_count}`);
|
|
172
|
+
console.log(
|
|
173
|
+
` Uploaded: ${new Date(parseInt(file.upload_date)).toLocaleDateString()}`,
|
|
174
|
+
);
|
|
175
|
+
console.log(` ID: ${file.file_id}`);
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error("Failed to list files:", error);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
return <button onClick={handleListFiles}>List My Files</button>;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Response Structure:**
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
interface FileListResult {
|
|
190
|
+
success: boolean;
|
|
191
|
+
files: FileInfo[];
|
|
192
|
+
total_files: number;
|
|
193
|
+
total_size_bytes: number;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
interface FileInfo {
|
|
197
|
+
file_id: string; // Unique identifier for deletion
|
|
198
|
+
filename: string; // Original filename
|
|
199
|
+
upload_date: string; // Unix timestamp (milliseconds)
|
|
200
|
+
size_bytes: number; // File size in bytes
|
|
201
|
+
chunk_count: number; // Number of text chunks created
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### **2. Bulk Upload Files**
|
|
206
|
+
|
|
207
|
+
Upload multiple files at once (up to 10 files per request):
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { useTrainly } from "@trainly/react";
|
|
211
|
+
|
|
212
|
+
function BulkFileUpload() {
|
|
213
|
+
const { bulkUploadFiles } = useTrainly();
|
|
214
|
+
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
|
215
|
+
|
|
216
|
+
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
217
|
+
const files = Array.from(event.target.files || []);
|
|
218
|
+
if (files.length > 10) {
|
|
219
|
+
alert("Maximum 10 files allowed per bulk upload");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
setSelectedFiles(files);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const handleBulkUpload = async () => {
|
|
226
|
+
if (selectedFiles.length === 0) return;
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const result = await bulkUploadFiles(selectedFiles);
|
|
230
|
+
|
|
231
|
+
console.log(`Bulk upload completed: ${result.message}`);
|
|
232
|
+
console.log(
|
|
233
|
+
`Successful: ${result.successful_uploads}/${result.total_files}`,
|
|
234
|
+
);
|
|
235
|
+
console.log(`Total size: ${formatBytes(result.total_size_bytes)}`);
|
|
236
|
+
|
|
237
|
+
// Review individual file results
|
|
238
|
+
result.results.forEach((fileResult) => {
|
|
239
|
+
if (fileResult.success) {
|
|
240
|
+
console.log(`✅ ${fileResult.filename} - ${fileResult.message}`);
|
|
241
|
+
} else {
|
|
242
|
+
console.log(`❌ ${fileResult.filename} - ${fileResult.error}`);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Clear selection after successful upload
|
|
247
|
+
setSelectedFiles([]);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error("Bulk upload failed:", error);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<div>
|
|
255
|
+
<input
|
|
256
|
+
type="file"
|
|
257
|
+
multiple
|
|
258
|
+
accept=".pdf,.txt,.docx"
|
|
259
|
+
onChange={handleFileSelect}
|
|
260
|
+
/>
|
|
261
|
+
|
|
262
|
+
{selectedFiles.length > 0 && (
|
|
263
|
+
<div>
|
|
264
|
+
<p>Selected files: {selectedFiles.length}</p>
|
|
265
|
+
<ul>
|
|
266
|
+
{selectedFiles.map((file, index) => (
|
|
267
|
+
<li key={index}>
|
|
268
|
+
{file.name} ({formatBytes(file.size)})
|
|
269
|
+
</li>
|
|
270
|
+
))}
|
|
271
|
+
</ul>
|
|
272
|
+
<button onClick={handleBulkUpload}>
|
|
273
|
+
Upload {selectedFiles.length} Files
|
|
274
|
+
</button>
|
|
275
|
+
</div>
|
|
276
|
+
)}
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Helper function for formatting file sizes
|
|
282
|
+
function formatBytes(bytes: number): string {
|
|
283
|
+
if (bytes === 0) return "0 Bytes";
|
|
284
|
+
const k = 1024;
|
|
285
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
286
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
287
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Bulk Upload Features:**
|
|
292
|
+
|
|
293
|
+
- ✅ **Efficient**: Upload up to 10 files in a single API call
|
|
294
|
+
- ✅ **Detailed Results**: Individual success/failure status for each file
|
|
295
|
+
- ✅ **Error Resilience**: Partial failures don't stop other files
|
|
296
|
+
- ✅ **Progress Tracking**: Total size and success metrics
|
|
297
|
+
- ✅ **Automatic Retry**: Token refresh handling built-in
|
|
298
|
+
|
|
299
|
+
### **3. Deleting Files**
|
|
300
|
+
|
|
301
|
+
Remove a specific file and free up storage space:
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
import { useTrainly } from "@trainly/react";
|
|
305
|
+
|
|
306
|
+
function FileDeleter() {
|
|
307
|
+
const { deleteFile, listFiles } = useTrainly();
|
|
308
|
+
|
|
309
|
+
const handleDeleteFile = async (fileId: string, filename: string) => {
|
|
310
|
+
// Always confirm before deletion
|
|
311
|
+
const confirmed = confirm(
|
|
312
|
+
`Delete "${filename}"? This will permanently remove the file and cannot be undone.`,
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
if (!confirmed) return;
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
const result = await deleteFile(fileId);
|
|
319
|
+
|
|
320
|
+
console.log(`✅ ${result.message}`);
|
|
321
|
+
console.log(`🗑️ Deleted: ${result.filename}`);
|
|
322
|
+
console.log(`💾 Storage freed: ${formatBytes(result.size_bytes_freed)}`);
|
|
323
|
+
console.log(`📊 Chunks removed: ${result.chunks_deleted}`);
|
|
324
|
+
|
|
325
|
+
// Optionally refresh file list
|
|
326
|
+
await listFiles();
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.error("Failed to delete file:", error);
|
|
329
|
+
alert(`Failed to delete file: ${error.message}`);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// Example: Delete first file
|
|
334
|
+
const deleteFirstFile = async () => {
|
|
335
|
+
const files = await listFiles();
|
|
336
|
+
if (files.files.length > 0) {
|
|
337
|
+
const firstFile = files.files[0];
|
|
338
|
+
await handleDeleteFile(firstFile.file_id, firstFile.filename);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
return <button onClick={deleteFirstFile}>Delete First File</button>;
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Response Structure:**
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
interface FileDeleteResult {
|
|
350
|
+
success: boolean;
|
|
351
|
+
message: string; // Human-readable success message
|
|
352
|
+
file_id: string; // ID of deleted file
|
|
353
|
+
filename: string; // Name of deleted file
|
|
354
|
+
chunks_deleted: number; // Number of chunks removed
|
|
355
|
+
size_bytes_freed: number; // Storage space freed up
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### **4. Pre-built File Manager Component**
|
|
360
|
+
|
|
361
|
+
Use the ready-made component for complete file management:
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
import { TrainlyFileManager } from "@trainly/react";
|
|
365
|
+
|
|
366
|
+
function MyApp() {
|
|
367
|
+
return (
|
|
368
|
+
<TrainlyFileManager
|
|
369
|
+
// Optional: Custom CSS class
|
|
370
|
+
className="my-custom-styles"
|
|
371
|
+
// Callback when file is deleted
|
|
372
|
+
onFileDeleted={(fileId, filename) => {
|
|
373
|
+
console.log(`File deleted: ${filename} (ID: ${fileId})`);
|
|
374
|
+
// Update your app state, show notification, etc.
|
|
375
|
+
}}
|
|
376
|
+
// Error handling callback
|
|
377
|
+
onError={(error) => {
|
|
378
|
+
console.error("File operation failed:", error);
|
|
379
|
+
// Show user-friendly error message
|
|
380
|
+
alert(`Error: ${error.message}`);
|
|
381
|
+
}}
|
|
382
|
+
// Show upload button in component
|
|
383
|
+
showUploadButton={true}
|
|
384
|
+
// Maximum file size in MB
|
|
385
|
+
maxFileSize={5}
|
|
386
|
+
/>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Component Features:**
|
|
392
|
+
|
|
393
|
+
- 📋 **File List**: Shows all files with metadata
|
|
394
|
+
- 🔄 **Auto-Refresh**: Updates after uploads/deletions
|
|
395
|
+
- ⚠️ **Confirmation**: Asks before deleting files
|
|
396
|
+
- 📊 **Storage Stats**: Shows total files and storage used
|
|
397
|
+
- 🎨 **Styled**: Clean, professional appearance
|
|
398
|
+
- 📱 **Responsive**: Works on mobile and desktop
|
|
399
|
+
|
|
400
|
+
### **5. Complete Integration Example**
|
|
401
|
+
|
|
402
|
+
Here's a full example showing all file operations together:
|
|
403
|
+
|
|
404
|
+
```tsx
|
|
405
|
+
import React from "react";
|
|
406
|
+
import { useAuth } from "@clerk/nextjs"; // or your OAuth provider
|
|
407
|
+
import { useTrainly, TrainlyFileManager } from "@trainly/react";
|
|
408
|
+
|
|
409
|
+
export function CompleteFileExample() {
|
|
410
|
+
const { getToken } = useAuth();
|
|
411
|
+
const {
|
|
412
|
+
ask,
|
|
413
|
+
upload,
|
|
414
|
+
listFiles,
|
|
415
|
+
deleteFile,
|
|
416
|
+
connectWithOAuthToken,
|
|
417
|
+
isConnected,
|
|
418
|
+
} = useTrainly();
|
|
419
|
+
|
|
420
|
+
const [files, setFiles] = React.useState([]);
|
|
421
|
+
const [storageUsed, setStorageUsed] = React.useState(0);
|
|
422
|
+
|
|
423
|
+
// Connect to Trainly on mount
|
|
424
|
+
React.useEffect(() => {
|
|
425
|
+
async function connect() {
|
|
426
|
+
const token = await getToken();
|
|
427
|
+
if (token) {
|
|
428
|
+
await connectWithOAuthToken(token);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
connect();
|
|
432
|
+
}, []);
|
|
433
|
+
|
|
434
|
+
// Load files when connected
|
|
435
|
+
React.useEffect(() => {
|
|
436
|
+
if (isConnected) {
|
|
437
|
+
refreshFiles();
|
|
438
|
+
}
|
|
439
|
+
}, [isConnected]);
|
|
440
|
+
|
|
441
|
+
const refreshFiles = async () => {
|
|
442
|
+
try {
|
|
443
|
+
const result = await listFiles();
|
|
444
|
+
setFiles(result.files);
|
|
445
|
+
setStorageUsed(result.total_size_bytes);
|
|
446
|
+
} catch (error) {
|
|
447
|
+
console.error("Failed to load files:", error);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const handleBulkDelete = async () => {
|
|
452
|
+
if (files.length === 0) {
|
|
453
|
+
alert("No files to delete");
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const confirmed = confirm(
|
|
458
|
+
`Delete ALL ${files.length} files? This cannot be undone.`,
|
|
459
|
+
);
|
|
460
|
+
if (!confirmed) return;
|
|
461
|
+
|
|
462
|
+
let deletedCount = 0;
|
|
463
|
+
let totalFreed = 0;
|
|
464
|
+
|
|
465
|
+
for (const file of files) {
|
|
466
|
+
try {
|
|
467
|
+
const result = await deleteFile(file.file_id);
|
|
468
|
+
deletedCount++;
|
|
469
|
+
totalFreed += result.size_bytes_freed;
|
|
470
|
+
console.log(`Deleted: ${result.filename}`);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
console.error(`Failed to delete ${file.filename}:`, error);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
alert(`Deleted ${deletedCount} files, freed ${formatBytes(totalFreed)}`);
|
|
477
|
+
await refreshFiles();
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const formatBytes = (bytes) => {
|
|
481
|
+
if (bytes === 0) return "0 Bytes";
|
|
482
|
+
const k = 1024;
|
|
483
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
484
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
485
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
if (!isConnected) {
|
|
489
|
+
return <div>Connecting to Trainly...</div>;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return (
|
|
493
|
+
<div style={{ maxWidth: "800px", margin: "0 auto", padding: "20px" }}>
|
|
494
|
+
<h1>📁 My File Workspace</h1>
|
|
495
|
+
|
|
496
|
+
{/* Storage Overview */}
|
|
497
|
+
<div
|
|
498
|
+
style={{
|
|
499
|
+
background: "#f8fafc",
|
|
500
|
+
padding: "20px",
|
|
501
|
+
borderRadius: "8px",
|
|
502
|
+
marginBottom: "20px",
|
|
503
|
+
}}
|
|
504
|
+
>
|
|
505
|
+
<h3>Storage Overview</h3>
|
|
506
|
+
<p>
|
|
507
|
+
<strong>{files.length} files</strong> using{" "}
|
|
508
|
+
<strong>{formatBytes(storageUsed)}</strong>
|
|
509
|
+
</p>
|
|
510
|
+
<div style={{ display: "flex", gap: "10px", marginTop: "10px" }}>
|
|
511
|
+
<button onClick={refreshFiles}>🔄 Refresh</button>
|
|
512
|
+
<button
|
|
513
|
+
onClick={handleBulkDelete}
|
|
514
|
+
disabled={files.length === 0}
|
|
515
|
+
style={{ background: "#dc2626", color: "white" }}
|
|
516
|
+
>
|
|
517
|
+
🗑️ Delete All Files
|
|
518
|
+
</button>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
|
|
522
|
+
{/* File Manager Component */}
|
|
523
|
+
<TrainlyFileManager
|
|
524
|
+
onFileDeleted={(fileId, filename) => {
|
|
525
|
+
console.log(`File deleted: ${filename}`);
|
|
526
|
+
// Update local state
|
|
527
|
+
setFiles((prev) => prev.filter((f) => f.file_id !== fileId));
|
|
528
|
+
refreshFiles(); // Refresh to get accurate totals
|
|
529
|
+
}}
|
|
530
|
+
onError={(error) => {
|
|
531
|
+
alert(`Error: ${error.message}`);
|
|
532
|
+
}}
|
|
533
|
+
showUploadButton={true}
|
|
534
|
+
maxFileSize={5}
|
|
535
|
+
/>
|
|
536
|
+
|
|
537
|
+
{/* AI Integration */}
|
|
538
|
+
<div
|
|
539
|
+
style={{
|
|
540
|
+
marginTop: "30px",
|
|
541
|
+
padding: "20px",
|
|
542
|
+
background: "#f0f9ff",
|
|
543
|
+
borderRadius: "8px",
|
|
544
|
+
}}
|
|
545
|
+
>
|
|
546
|
+
<h3>🤖 Ask AI About Your Files</h3>
|
|
547
|
+
<button
|
|
548
|
+
onClick={async () => {
|
|
549
|
+
const answer = await ask(
|
|
550
|
+
"What files do I have? Give me a summary of each.",
|
|
551
|
+
);
|
|
552
|
+
alert(`AI Response:\n\n${answer}`);
|
|
553
|
+
}}
|
|
554
|
+
style={{ background: "#059669", color: "white" }}
|
|
555
|
+
>
|
|
556
|
+
Get File Summary from AI
|
|
557
|
+
</button>
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### **6. Error Handling Best Practices**
|
|
565
|
+
|
|
566
|
+
```tsx
|
|
567
|
+
import { useTrainly } from "@trainly/react";
|
|
568
|
+
|
|
569
|
+
function RobustFileManager() {
|
|
570
|
+
const { deleteFile, listFiles } = useTrainly();
|
|
571
|
+
|
|
572
|
+
const safeDeleteFile = async (fileId: string, filename: string) => {
|
|
573
|
+
try {
|
|
574
|
+
// 1. Confirm with user
|
|
575
|
+
const confirmed = confirm(`Delete "${filename}"?`);
|
|
576
|
+
if (!confirmed) return;
|
|
577
|
+
|
|
578
|
+
// 2. Attempt deletion
|
|
579
|
+
const result = await deleteFile(fileId);
|
|
580
|
+
|
|
581
|
+
// 3. Success feedback
|
|
582
|
+
console.log(`✅ Success: ${result.message}`);
|
|
583
|
+
return result;
|
|
584
|
+
} catch (error) {
|
|
585
|
+
// 4. Handle specific error types
|
|
586
|
+
if (error.message.includes("404")) {
|
|
587
|
+
alert("File not found - it may have already been deleted");
|
|
588
|
+
} else if (error.message.includes("401")) {
|
|
589
|
+
alert("Authentication expired - please refresh the page");
|
|
590
|
+
} else {
|
|
591
|
+
alert(`Failed to delete file: ${error.message}`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
console.error("Delete error:", error);
|
|
595
|
+
throw error;
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const safeListFiles = async () => {
|
|
600
|
+
try {
|
|
601
|
+
return await listFiles();
|
|
602
|
+
} catch (error) {
|
|
603
|
+
console.error("List files error:", error);
|
|
604
|
+
|
|
605
|
+
if (error.message.includes("V1 mode")) {
|
|
606
|
+
alert("File management requires V1 OAuth authentication");
|
|
607
|
+
} else {
|
|
608
|
+
alert(`Failed to load files: ${error.message}`);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return { success: false, files: [], total_files: 0, total_size_bytes: 0 };
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
return (
|
|
616
|
+
<div>
|
|
617
|
+
<button onClick={() => safeListFiles()}>Safe List Files</button>
|
|
618
|
+
<button onClick={() => safeDeleteFile("file_123", "example.pdf")}>
|
|
619
|
+
Safe Delete Example
|
|
620
|
+
</button>
|
|
621
|
+
</div>
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### **7. TypeScript Support**
|
|
627
|
+
|
|
628
|
+
Full TypeScript definitions included:
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
// Import types for better development experience
|
|
632
|
+
import type {
|
|
633
|
+
FileInfo,
|
|
634
|
+
FileListResult,
|
|
635
|
+
FileDeleteResult,
|
|
636
|
+
TrainlyFileManagerProps,
|
|
637
|
+
} from "@trainly/react";
|
|
638
|
+
|
|
639
|
+
// Type-safe file operations
|
|
640
|
+
const handleTypedFileOps = async () => {
|
|
641
|
+
const fileList: FileListResult = await listFiles();
|
|
642
|
+
const deleteResult: FileDeleteResult = await deleteFile("file_123");
|
|
643
|
+
|
|
644
|
+
// Full IntelliSense support
|
|
645
|
+
console.log(deleteResult.size_bytes_freed);
|
|
646
|
+
console.log(fileList.total_size_bytes);
|
|
647
|
+
};
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### **8. Security & Privacy Notes**
|
|
651
|
+
|
|
652
|
+
- 🔒 **V1 Only**: File management only works with V1 Trusted Issuer authentication
|
|
653
|
+
- 👤 **User Isolation**: Users can only see and delete their own files
|
|
654
|
+
- 🛡️ **No Raw Access**: Developers never see file content, only AI responses
|
|
655
|
+
- 📊 **Privacy-Safe Analytics**: Storage tracking without exposing user data
|
|
656
|
+
- ⚠️ **Permanent Deletion**: Deleted files cannot be recovered
|
|
657
|
+
- 🔐 **OAuth Required**: Must be authenticated with valid OAuth token
|
|
658
|
+
|
|
659
|
+
### **9. Storage Management**
|
|
660
|
+
|
|
661
|
+
File operations automatically update storage analytics:
|
|
662
|
+
|
|
663
|
+
```tsx
|
|
664
|
+
// Storage is tracked automatically
|
|
665
|
+
const result = await deleteFile(fileId);
|
|
666
|
+
console.log(`Freed ${result.size_bytes_freed} bytes`);
|
|
667
|
+
|
|
668
|
+
// Check total storage
|
|
669
|
+
const files = await listFiles();
|
|
670
|
+
console.log(`Using ${files.total_size_bytes} bytes total`);
|
|
671
|
+
|
|
672
|
+
// Parent app analytics are updated automatically
|
|
673
|
+
// (visible in Trainly dashboard for developers)
|
|
674
|
+
```
|
|
675
|
+
|
|
89
676
|
---
|
|
90
677
|
|
|
91
678
|
## 🚀 Original Quick Start (Legacy)
|