@trainly/react 1.0.3 → 1.1.2

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
@@ -1,10 +1,94 @@
1
1
  # @trainly/react
2
2
 
3
- **Dead simple RAG integration for React apps**
3
+ **Dead simple RAG integration for React apps with V1 OAuth Authentication**
4
4
 
5
- Go from `npm install` to working AI in under 5 minutes. No backend required, no complex setup, just install and use.
5
+ Go from `npm install` to working AI in under 5 minutes. Now supports direct OAuth integration with **permanent user subchats** and complete privacy protection.
6
6
 
7
- ## 🚀 Quick Start
7
+ ## 🆕 **NEW: V1 Trusted Issuer Authentication**
8
+
9
+ Use your existing OAuth provider (Clerk, Auth0, Cognito) directly with Trainly! Users get permanent private workspaces, and developers never see raw files or queries.
10
+
11
+ ### V1 Quick Start
12
+
13
+ #### 1. Install
14
+
15
+ ```bash
16
+ npm install @trainly/react
17
+ ```
18
+
19
+ #### 2. Register Your OAuth App (One-time)
20
+
21
+ ```bash
22
+ curl -X POST "http://localhost:8000/v1/console/apps/register" \
23
+ -H "X-Admin-Token: admin_dev_token_123" \
24
+ -F "app_name=My App" \
25
+ -F "issuer=https://clerk.myapp.com" \
26
+ -F 'allowed_audiences=["my-clerk-frontend-api"]'
27
+ ```
28
+
29
+ Save the `app_id` from the response!
30
+
31
+ #### 3. Setup with V1 (Clerk Example)
32
+
33
+ ```tsx
34
+ // app/layout.tsx
35
+ import { ClerkProvider } from "@clerk/nextjs";
36
+ import { TrainlyProvider } from "@trainly/react";
37
+
38
+ export default function RootLayout({ children }) {
39
+ return (
40
+ <html>
41
+ <body>
42
+ <ClerkProvider>
43
+ <TrainlyProvider appId="your_app_id_from_step_2">
44
+ {children}
45
+ </TrainlyProvider>
46
+ </ClerkProvider>
47
+ </body>
48
+ </html>
49
+ );
50
+ }
51
+ ```
52
+
53
+ #### 4. Use with OAuth Authentication
54
+
55
+ ```tsx
56
+ // Any component
57
+ import { useAuth } from "@clerk/nextjs";
58
+ import { useTrainly } from "@trainly/react";
59
+
60
+ function MyComponent() {
61
+ const { getToken } = useAuth();
62
+ const { ask, connectWithOAuthToken } = useTrainly();
63
+
64
+ React.useEffect(() => {
65
+ async function setupTrainly() {
66
+ const idToken = await getToken();
67
+ await connectWithOAuthToken(idToken);
68
+ }
69
+ setupTrainly();
70
+ }, []);
71
+
72
+ const handleClick = async () => {
73
+ const answer = await ask("What files do I have?");
74
+ console.log(answer); // AI response from user's permanent private subchat!
75
+ };
76
+
77
+ return <button onClick={handleClick}>Ask My AI</button>;
78
+ }
79
+ ```
80
+
81
+ ## 🔒 **V1 Benefits**
82
+
83
+ - ✅ **Permanent User Data**: Same user = same private subchat forever
84
+ - ✅ **Complete Privacy**: Developer never sees user files or queries
85
+ - ✅ **Any OAuth Provider**: Clerk, Auth0, Cognito, Firebase, custom OIDC
86
+ - ✅ **Zero Migration**: Works with your existing OAuth setup
87
+ - ✅ **Simple Integration**: Just add `appId` and use `connectWithOAuthToken()`
88
+
89
+ ---
90
+
91
+ ## 🚀 Original Quick Start (Legacy)
8
92
 
9
93
  ### 1. Install
10
94
 
@@ -111,20 +195,45 @@ function App() {
111
195
  ### Authentication Modes
112
196
 
113
197
  ```tsx
114
- // Mode 1: App Secret (recommended for multi-user apps)
198
+ // Mode 1: V1 Trusted Issuer (NEW - recommended for OAuth apps)
199
+ <TrainlyProvider appId="app_v1_12345" /> // Register via console API first
200
+
201
+ // Mode 2: App Secret (legacy - for multi-user apps)
115
202
  <TrainlyProvider appSecret="as_secret_123" />
116
203
 
117
- // Mode 2: With user context
204
+ // Mode 3: With user context (legacy)
118
205
  <TrainlyProvider
119
206
  appSecret="as_secret_123"
120
207
  userId="user_123"
121
208
  userEmail="user@example.com"
122
209
  />
123
210
 
124
- // Mode 3: Direct API key (simple apps)
211
+ // Mode 4: Direct API key (legacy - simple apps)
125
212
  <TrainlyProvider apiKey="tk_chat_id_key" />
126
213
  ```
127
214
 
215
+ ### V1 OAuth Provider Examples
216
+
217
+ ```tsx
218
+ // With Clerk
219
+ <TrainlyProvider
220
+ appId="app_v1_clerk_123"
221
+ baseUrl="https://api.trainly.com"
222
+ />
223
+
224
+ // With Auth0
225
+ <TrainlyProvider
226
+ appId="app_v1_auth0_456"
227
+ baseUrl="https://api.trainly.com"
228
+ />
229
+
230
+ // With AWS Cognito
231
+ <TrainlyProvider
232
+ appId="app_v1_cognito_789"
233
+ baseUrl="https://api.trainly.com"
234
+ />
235
+ ```
236
+
128
237
  ### Component Customization
129
238
 
130
239
  ```tsx
@@ -177,6 +286,9 @@ const {
177
286
  askWithCitations: (question: string) => Promise<{answer: string, citations: Citation[]}>,
178
287
  upload: (file: File) => Promise<UploadResult>,
179
288
 
289
+ // NEW: V1 Authentication
290
+ connectWithOAuthToken: (idToken: string) => Promise<void>,
291
+
180
292
  // State
181
293
  isLoading: boolean,
182
294
  isConnected: boolean,
@@ -198,22 +310,31 @@ const {
198
310
  ```tsx
199
311
  interface TrainlyProviderProps {
200
312
  children: React.ReactNode;
201
- appSecret?: string; // App secret from Trainly dashboard
202
- apiKey?: string; // Direct API key (alternative to appSecret)
313
+ appId?: string; // NEW: V1 app ID from console registration
314
+ appSecret?: string; // Legacy: App secret from Trainly dashboard
315
+ apiKey?: string; // Legacy: Direct API key (alternative to appSecret)
203
316
  baseUrl?: string; // Custom API URL (defaults to trainly.com)
204
- userId?: string; // Your app's user ID
205
- userEmail?: string; // Your app's user email
317
+ userId?: string; // Legacy: Your app's user ID
318
+ userEmail?: string; // Legacy: Your app's user email
206
319
  }
207
320
  ```
208
321
 
209
322
  ## 🔍 Examples
210
323
 
211
- Check out the `/examples` folder for complete implementations:
324
+ See complete implementation examples in the [API Documentation](https://trainly.com/docs/v1-authentication).
325
+
326
+ ## 🆚 **V1 vs Legacy Comparison**
327
+
328
+ | Feature | V1 Trusted Issuer | Legacy App Secret |
329
+ | -------------- | -------------------------------- | ------------------------- |
330
+ | **User Auth** | Your OAuth provider | Trainly OAuth flow |
331
+ | **User Data** | Permanent private subchat | Temporary or shared |
332
+ | **Privacy** | Complete (dev can't see files) | Limited |
333
+ | **Setup** | Register once, use OAuth tokens | Generate app secrets |
334
+ | **Migration** | Zero (uses existing OAuth) | Requires auth integration |
335
+ | **Permanence** | Same user = same subchat forever | Depends on implementation |
212
336
 
213
- - **Simple Chat App** - Drop-in components
214
- - **Custom Implementation** - Build your own UI
215
- - **Multi-user App** - User-specific workspaces
216
- - **File-focused App** - Document analysis focus
337
+ **Recommendation**: Use V1 for new apps and consider migrating existing apps for better privacy and user experience.
217
338
 
218
339
  ## 🛠️ Development
219
340
 
@@ -4,9 +4,12 @@ export interface TrainlyProviderProps {
4
4
  children: React.ReactNode;
5
5
  appSecret?: string;
6
6
  apiKey?: string;
7
+ appId?: string;
7
8
  baseUrl?: string;
8
9
  userId?: string;
9
10
  userEmail?: string;
11
+ getToken?: () => Promise<string | null>;
10
12
  }
11
- export declare function TrainlyProvider({ children, appSecret, apiKey, baseUrl, userId, userEmail, }: TrainlyProviderProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function TrainlyProvider({ children, appSecret, apiKey, appId, // NEW: For V1 authentication
14
+ baseUrl, userId, userEmail, getToken, }: TrainlyProviderProps): import("react/jsx-runtime").JSX.Element;
12
15
  export declare function useTrainlyContext(): TrainlyContextValue;
@@ -50,11 +50,14 @@ import { TrainlyClient } from "./api/TrainlyClient";
50
50
  var TrainlyContext = React.createContext(undefined);
51
51
  export function TrainlyProvider(_a) {
52
52
  var _this = this;
53
- var children = _a.children, appSecret = _a.appSecret, apiKey = _a.apiKey, _b = _a.baseUrl, baseUrl = _b === void 0 ? "http://localhost:8000" : _b, userId = _a.userId, userEmail = _a.userEmail;
53
+ var children = _a.children, appSecret = _a.appSecret, apiKey = _a.apiKey, appId = _a.appId, // NEW: For V1 authentication
54
+ _b = _a.baseUrl, // NEW: For V1 authentication
55
+ baseUrl = _b === void 0 ? "http://localhost:8000" : _b, userId = _a.userId, userEmail = _a.userEmail, getToken = _a.getToken;
54
56
  var client = React.useState(function () {
55
57
  return new TrainlyClient({
56
58
  appSecret: appSecret,
57
59
  apiKey: apiKey,
60
+ appId: appId, // NEW: Pass appId to client
58
61
  baseUrl: baseUrl,
59
62
  userId: userId,
60
63
  userEmail: userEmail,
@@ -97,36 +100,94 @@ export function TrainlyProvider(_a) {
97
100
  }
98
101
  });
99
102
  }); };
100
- var ask = function (question) { return __awaiter(_this, void 0, void 0, function () {
101
- var response, err_2, error_1;
103
+ // NEW: V1 OAuth Token connection method
104
+ var connectWithOAuthToken = function (idToken) { return __awaiter(_this, void 0, void 0, function () {
105
+ var err_2;
102
106
  return __generator(this, function (_a) {
103
107
  switch (_a.label) {
104
108
  case 0:
105
109
  _a.trys.push([0, 2, 3, 4]);
106
110
  setIsLoading(true);
107
111
  setError(null);
112
+ return [4 /*yield*/, client.connectWithOAuthToken(idToken)];
113
+ case 1:
114
+ _a.sent();
115
+ setIsConnected(true);
116
+ return [3 /*break*/, 4];
117
+ case 2:
118
+ err_2 = _a.sent();
119
+ setError({
120
+ code: "V1_CONNECTION_FAILED",
121
+ message: "Failed to connect with OAuth token",
122
+ details: err_2,
123
+ });
124
+ setIsConnected(false);
125
+ throw err_2;
126
+ case 3:
127
+ setIsLoading(false);
128
+ return [7 /*endfinally*/];
129
+ case 4: return [2 /*return*/];
130
+ }
131
+ });
132
+ }); };
133
+ var ask = function (question) { return __awaiter(_this, void 0, void 0, function () {
134
+ var response, err_3, errorMessage, newToken, response, refreshError_1, error_1;
135
+ return __generator(this, function (_a) {
136
+ switch (_a.label) {
137
+ case 0:
138
+ _a.trys.push([0, 2, 10, 11]);
139
+ setIsLoading(true);
140
+ setError(null);
108
141
  return [4 /*yield*/, client.ask(question)];
109
142
  case 1:
110
143
  response = _a.sent();
111
144
  return [2 /*return*/, response.answer];
112
145
  case 2:
113
- err_2 = _a.sent();
146
+ err_3 = _a.sent();
147
+ errorMessage = err_3 instanceof Error ? err_3.message : String(err_3);
148
+ if (!(getToken &&
149
+ appId &&
150
+ (errorMessage.includes("401") ||
151
+ errorMessage.includes("authentication") ||
152
+ errorMessage.includes("Unauthorized")))) return [3 /*break*/, 9];
153
+ _a.label = 3;
154
+ case 3:
155
+ _a.trys.push([3, 8, , 9]);
156
+ console.log("🔄 Token expired, refreshing...");
157
+ return [4 /*yield*/, getToken()];
158
+ case 4:
159
+ newToken = _a.sent();
160
+ if (!newToken) return [3 /*break*/, 7];
161
+ return [4 /*yield*/, client.connectWithOAuthToken(newToken)];
162
+ case 5:
163
+ _a.sent();
164
+ return [4 /*yield*/, client.ask(question)];
165
+ case 6:
166
+ response = _a.sent();
167
+ console.log("✅ Query succeeded after token refresh");
168
+ return [2 /*return*/, response.answer];
169
+ case 7: return [3 /*break*/, 9];
170
+ case 8:
171
+ refreshError_1 = _a.sent();
172
+ console.error("❌ Token refresh failed:", refreshError_1);
173
+ return [3 /*break*/, 9];
174
+ case 9:
114
175
  error_1 = {
115
176
  code: "QUERY_FAILED",
116
177
  message: "Failed to get answer",
117
- details: err_2,
178
+ details: err_3,
118
179
  };
119
180
  setError(error_1);
120
181
  throw error_1;
121
- case 3:
182
+ case 10:
122
183
  setIsLoading(false);
123
184
  return [7 /*endfinally*/];
124
- case 4: return [2 /*return*/];
185
+ case 11: return [2 /*return*/];
125
186
  }
126
187
  });
127
188
  }); };
128
189
  var askWithCitations = function (question) { return __awaiter(_this, void 0, void 0, function () {
129
- var response, err_3, error_2;
190
+ var response, err_4, error_2;
130
191
  return __generator(this, function (_a) {
131
192
  switch (_a.label) {
132
193
  case 0:
@@ -141,11 +202,11 @@ export function TrainlyProvider(_a) {
141
202
  citations: response.citations || [],
142
203
  }];
143
204
  case 2:
144
- err_3 = _a.sent();
205
+ err_4 = _a.sent();
145
206
  error_2 = {
146
207
  code: "QUERY_FAILED",
147
208
  message: "Failed to get answer with citations",
148
- details: err_3,
209
+ details: err_4,
149
210
  };
150
211
  setError(error_2);
151
212
  throw error_2;
@@ -157,11 +218,11 @@ export function TrainlyProvider(_a) {
157
218
  });
158
219
  }); };
159
220
  var upload = function (file) { return __awaiter(_this, void 0, void 0, function () {
160
- var result, err_4, error_3;
221
+ var result, err_5, errorMessage, newToken, result, refreshError_2, error_3;
161
222
  return __generator(this, function (_a) {
162
223
  switch (_a.label) {
163
224
  case 0:
164
- _a.trys.push([0, 2, 3, 4]);
225
+ _a.trys.push([0, 2, 10, 11]);
165
226
  setIsLoading(true);
166
227
  setError(null);
167
228
  return [4 /*yield*/, client.upload(file)];
@@ -169,23 +230,51 @@ export function TrainlyProvider(_a) {
169
230
  result = _a.sent();
170
231
  return [2 /*return*/, result];
171
232
  case 2:
172
- err_4 = _a.sent();
233
+ err_5 = _a.sent();
234
+ errorMessage = err_5 instanceof Error ? err_5.message : String(err_5);
235
+ if (!(getToken &&
236
+ appId &&
237
+ (errorMessage.includes("401") ||
238
+ errorMessage.includes("authentication") ||
239
+ errorMessage.includes("Unauthorized")))) return [3 /*break*/, 9];
240
+ _a.label = 3;
241
+ case 3:
242
+ _a.trys.push([3, 8, , 9]);
243
+ console.log("🔄 Token expired during upload, refreshing...");
244
+ return [4 /*yield*/, getToken()];
245
+ case 4:
246
+ newToken = _a.sent();
247
+ if (!newToken) return [3 /*break*/, 7];
248
+ return [4 /*yield*/, client.connectWithOAuthToken(newToken)];
249
+ case 5:
250
+ _a.sent();
251
+ return [4 /*yield*/, client.upload(file)];
252
+ case 6:
253
+ result = _a.sent();
254
+ console.log("✅ Upload succeeded after token refresh");
255
+ return [2 /*return*/, result];
256
+ case 7: return [3 /*break*/, 9];
257
+ case 8:
258
+ refreshError_2 = _a.sent();
259
+ console.error("❌ Token refresh failed:", refreshError_2);
260
+ return [3 /*break*/, 9];
261
+ case 9:
173
262
  error_3 = {
174
263
  code: "UPLOAD_FAILED",
175
264
  message: "Failed to upload file",
176
- details: err_4,
265
+ details: err_5,
177
266
  };
178
267
  setError(error_3);
179
268
  throw error_3;
180
- case 3:
269
+ case 10:
181
270
  setIsLoading(false);
182
271
  return [7 /*endfinally*/];
183
- case 4: return [2 /*return*/];
272
+ case 11: return [2 /*return*/];
184
273
  }
185
274
  });
186
275
  }); };
187
276
  var sendMessage = function (content) { return __awaiter(_this, void 0, void 0, function () {
188
- var userMessage, response, assistantMessage_1, err_5;
277
+ var userMessage, response, assistantMessage_1, err_6;
189
278
  return __generator(this, function (_a) {
190
279
  switch (_a.label) {
191
280
  case 0:
@@ -212,9 +301,9 @@ export function TrainlyProvider(_a) {
212
301
  setMessages(function (prev) { return __spreadArray(__spreadArray([], prev, true), [assistantMessage_1], false); });
213
302
  return [3 /*break*/, 4];
214
303
  case 3:
215
- err_5 = _a.sent();
304
+ err_6 = _a.sent();
216
305
  // Error is already set by askWithCitations
217
- console.error("Failed to send message:", err_5);
306
+ console.error("Failed to send message:", err_6);
218
307
  return [3 /*break*/, 4];
219
308
  case 4: return [2 /*return*/];
220
309
  }
@@ -227,6 +316,7 @@ export function TrainlyProvider(_a) {
227
316
  ask: ask,
228
317
  askWithCitations: askWithCitations,
229
318
  upload: upload,
319
+ connectWithOAuthToken: connectWithOAuthToken, // NEW: V1 OAuth connection method
230
320
  isLoading: isLoading,
231
321
  isConnected: isConnected,
232
322
  error: error,
@@ -7,7 +7,13 @@ export declare class TrainlyClient {
7
7
  private config;
8
8
  private scopedToken;
9
9
  private currentUserId;
10
+ private isV1Mode;
10
11
  constructor(config: TrainlyConfig);
12
+ /**
13
+ * NEW: Connect using V1 Trusted Issuer authentication with OAuth ID token
14
+ * This method allows users to authenticate directly with their OAuth provider tokens
15
+ */
16
+ connectWithOAuthToken(idToken: string): Promise<void>;
11
17
  connect(): Promise<void>;
12
18
  ask(question: string, options?: {
13
19
  includeCitations?: boolean;
@@ -49,8 +49,51 @@ var TrainlyClient = /** @class */ (function () {
49
49
  function TrainlyClient(config) {
50
50
  this.scopedToken = null;
51
51
  this.currentUserId = null;
52
+ this.isV1Mode = false;
52
53
  this.config = config;
53
54
  }
55
+ /**
56
+ * NEW: Connect using V1 Trusted Issuer authentication with OAuth ID token
57
+ * This method allows users to authenticate directly with their OAuth provider tokens
58
+ */
59
+ TrainlyClient.prototype.connectWithOAuthToken = function (idToken) {
60
+ return __awaiter(this, void 0, void 0, function () {
61
+ var response, error, profile;
62
+ return __generator(this, function (_a) {
63
+ switch (_a.label) {
64
+ case 0:
65
+ if (!this.config.appId) {
66
+ throw new Error("appId is required for V1 authentication.");
67
+ }
68
+ // For V1, we use the ID token directly - no need to provision
69
+ this.scopedToken = idToken;
70
+ this.isV1Mode = true;
71
+ return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/me/profile"), {
72
+ headers: {
73
+ Authorization: "Bearer ".concat(idToken),
74
+ "X-App-ID": this.config.appId,
75
+ },
76
+ })];
77
+ case 1:
78
+ response = _a.sent();
79
+ if (!!response.ok) return [3 /*break*/, 3];
80
+ return [4 /*yield*/, response.json()];
81
+ case 2:
82
+ error = _a.sent();
83
+ throw new Error("V1 authentication failed: ".concat(error.detail || response.statusText));
84
+ case 3: return [4 /*yield*/, response.json()];
85
+ case 4:
86
+ profile = _a.sent();
87
+ this.currentUserId = profile.user_id;
88
+ console.log("✅ Connected to Trainly with V1 Trusted Issuer authentication");
89
+ console.log("\uD83D\uDCCB User ID: ".concat(profile.user_id));
90
+ console.log("\uD83D\uDCAC Chat ID: ".concat(profile.chat_id));
91
+ console.log("\uD83D\uDD12 OAuth Provider: ".concat(profile.issuer));
92
+ return [2 /*return*/];
93
+ }
94
+ });
95
+ });
96
+ };
54
97
  TrainlyClient.prototype.connect = function () {
55
98
  return __awaiter(this, void 0, void 0, function () {
56
99
  var response, error, data;
@@ -99,14 +142,42 @@ var TrainlyClient = /** @class */ (function () {
99
142
  };
100
143
  TrainlyClient.prototype.ask = function (question_1) {
101
144
  return __awaiter(this, arguments, void 0, function (question, options) {
102
- var url, headers, body, response, error, data;
145
+ var response_1, error, data_1, url, headers, body, response, error, data;
103
146
  if (options === void 0) { options = {}; }
104
147
  return __generator(this, function (_a) {
105
148
  switch (_a.label) {
106
149
  case 0:
107
150
  if (!this.scopedToken) {
108
- throw new Error("Not connected. Call connect() first.");
151
+ throw new Error("Not connected. Call connect() or connectWithOAuthToken() first.");
109
152
  }
153
+ if (!(this.isV1Mode && this.config.appId)) return [3 /*break*/, 5];
154
+ return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/me/chats/query"), {
155
+ method: "POST",
156
+ headers: {
157
+ Authorization: "Bearer ".concat(this.scopedToken),
158
+ "X-App-ID": this.config.appId,
159
+ "Content-Type": "application/x-www-form-urlencoded",
160
+ },
161
+ body: new URLSearchParams({
162
+ messages: JSON.stringify([{ role: "user", content: question }]),
163
+ response_tokens: "150",
164
+ }),
165
+ })];
166
+ case 1:
167
+ response_1 = _a.sent();
168
+ if (!!response_1.ok) return [3 /*break*/, 3];
169
+ return [4 /*yield*/, response_1.json()];
170
+ case 2:
171
+ error = _a.sent();
172
+ throw new Error("V1 query failed: ".concat(error.detail || response_1.statusText));
173
+ case 3: return [4 /*yield*/, response_1.json()];
174
+ case 4:
175
+ data_1 = _a.sent();
176
+ return [2 /*return*/, {
177
+ answer: data_1.answer,
178
+ citations: data_1.citations || [],
179
+ }];
180
+ case 5:
110
181
  url = this.config.apiKey
111
182
  ? "".concat(this.config.baseUrl, "/v1/").concat(this.extractChatId(), "/answer_question")
112
183
  : "".concat(this.config.baseUrl, "/v1/privacy/query");
@@ -129,15 +200,15 @@ var TrainlyClient = /** @class */ (function () {
129
200
  headers: headers,
130
201
  body: JSON.stringify(body),
131
202
  })];
132
- case 1:
203
+ case 6:
133
204
  response = _a.sent();
134
- if (!!response.ok) return [3 /*break*/, 3];
205
+ if (!!response.ok) return [3 /*break*/, 8];
135
206
  return [4 /*yield*/, response.json()];
136
- case 2:
207
+ case 7:
137
208
  error = _a.sent();
138
209
  throw new Error("Query failed: ".concat(error.detail || response.statusText));
139
- case 3: return [4 /*yield*/, response.json()];
140
- case 4:
210
+ case 8: return [4 /*yield*/, response.json()];
211
+ case 9:
141
212
  data = _a.sent();
142
213
  return [2 /*return*/, {
143
214
  answer: data.answer,
@@ -149,20 +220,21 @@ var TrainlyClient = /** @class */ (function () {
149
220
  };
150
221
  TrainlyClient.prototype.upload = function (file) {
151
222
  return __awaiter(this, void 0, void 0, function () {
152
- var formData, response, error, presignedResponse, error, _a, upload_url, upload_headers, formData, uploadResponse;
223
+ var formData, response, error, data, formData, response, error, presignedResponse, error, _a, upload_url, upload_headers, formData, uploadResponse;
153
224
  return __generator(this, function (_b) {
154
225
  switch (_b.label) {
155
226
  case 0:
156
227
  if (!this.scopedToken) {
157
- throw new Error("Not connected. Call connect() first.");
228
+ throw new Error("Not connected. Call connect() or connectWithOAuthToken() first.");
158
229
  }
159
- if (!this.config.apiKey) return [3 /*break*/, 4];
230
+ if (!(this.isV1Mode && this.config.appId)) return [3 /*break*/, 5];
160
231
  formData = new FormData();
161
232
  formData.append("file", file);
162
- return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/").concat(this.extractChatId(), "/upload_file"), {
233
+ return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/me/chats/files/upload"), {
163
234
  method: "POST",
164
235
  headers: {
165
- Authorization: "Bearer ".concat(this.config.apiKey),
236
+ Authorization: "Bearer ".concat(this.scopedToken),
237
+ "X-App-ID": this.config.appId,
166
238
  },
167
239
  body: formData,
168
240
  })];
@@ -171,15 +243,42 @@ var TrainlyClient = /** @class */ (function () {
171
243
  if (!!response.ok) return [3 /*break*/, 3];
172
244
  return [4 /*yield*/, response.json()];
173
245
  case 2:
246
+ error = _b.sent();
247
+ throw new Error("V1 upload failed: ".concat(error.detail || response.statusText));
248
+ case 3: return [4 /*yield*/, response.json()];
249
+ case 4:
250
+ data = _b.sent();
251
+ return [2 /*return*/, {
252
+ success: data.success,
253
+ filename: data.filename,
254
+ size: data.size_bytes,
255
+ message: data.message || "File uploaded to your permanent private subchat",
256
+ }];
257
+ case 5:
258
+ if (!this.config.apiKey) return [3 /*break*/, 9];
259
+ formData = new FormData();
260
+ formData.append("file", file);
261
+ return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/").concat(this.extractChatId(), "/upload_file"), {
262
+ method: "POST",
263
+ headers: {
264
+ Authorization: "Bearer ".concat(this.config.apiKey),
265
+ },
266
+ body: formData,
267
+ })];
268
+ case 6:
269
+ response = _b.sent();
270
+ if (!!response.ok) return [3 /*break*/, 8];
271
+ return [4 /*yield*/, response.json()];
272
+ case 7:
174
273
  error = _b.sent();
175
274
  throw new Error("Upload failed: ".concat(error.detail || response.statusText));
176
- case 3: return [2 /*return*/, {
275
+ case 8: return [2 /*return*/, {
177
276
  success: true,
178
277
  filename: file.name,
179
278
  size: file.size,
180
279
  message: "File uploaded successfully",
181
280
  }];
182
- case 4: return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/privacy/upload/presigned-url"), {
281
+ case 9: return [4 /*yield*/, fetch("".concat(this.config.baseUrl, "/v1/privacy/upload/presigned-url"), {
183
282
  method: "POST",
184
283
  headers: {
185
284
  "Content-Type": "application/json",
@@ -193,15 +292,15 @@ var TrainlyClient = /** @class */ (function () {
193
292
  file_type: file.type,
194
293
  }),
195
294
  })];
196
- case 5:
295
+ case 10:
197
296
  presignedResponse = _b.sent();
198
- if (!!presignedResponse.ok) return [3 /*break*/, 7];
297
+ if (!!presignedResponse.ok) return [3 /*break*/, 12];
199
298
  return [4 /*yield*/, presignedResponse.json()];
200
- case 6:
299
+ case 11:
201
300
  error = _b.sent();
202
301
  throw new Error("Failed to get upload URL: ".concat(error.detail || presignedResponse.statusText));
203
- case 7: return [4 /*yield*/, presignedResponse.json()];
204
- case 8:
302
+ case 12: return [4 /*yield*/, presignedResponse.json()];
303
+ case 13:
205
304
  _a = _b.sent(), upload_url = _a.upload_url, upload_headers = _a.upload_headers;
206
305
  formData = new FormData();
207
306
  formData.append("file", file);
@@ -210,7 +309,7 @@ var TrainlyClient = /** @class */ (function () {
210
309
  body: formData,
211
310
  headers: __assign({}, upload_headers),
212
311
  })];
213
- case 9:
312
+ case 14:
214
313
  uploadResponse = _b.sent();
215
314
  if (!uploadResponse.ok) {
216
315
  throw new Error("Failed to upload file");
@@ -87,7 +87,7 @@ export function TrainlyChat(_a) {
87
87
  var file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
88
88
  if (file) {
89
89
  // This would trigger upload and add a system message
90
- console.log("File selected:", file.name);
90
+ // File selected for upload
91
91
  }
92
92
  };
93
93
  var baseClasses = "\n flex flex-col border border-gray-200 rounded-lg overflow-hidden\n ".concat(theme === "dark" ? "bg-gray-900 text-white border-gray-700" : "bg-white text-gray-900", "\n ").concat(className, "\n ");
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export interface TrainlyConfig {
2
2
  appSecret?: string;
3
3
  apiKey?: string;
4
+ appId?: string;
4
5
  baseUrl?: string;
5
6
  userId?: string;
6
7
  userEmail?: string;
@@ -9,6 +10,7 @@ export interface TrainlyProviderProps {
9
10
  children: React.ReactNode;
10
11
  appSecret?: string;
11
12
  apiKey?: string;
13
+ appId?: string;
12
14
  baseUrl?: string;
13
15
  userId?: string;
14
16
  userEmail?: string;
@@ -44,6 +46,7 @@ export interface TrainlyContextValue {
44
46
  citations: Citation[];
45
47
  }>;
46
48
  upload: (file: File) => Promise<UploadResult>;
49
+ connectWithOAuthToken: (idToken: string) => Promise<void>;
47
50
  isLoading: boolean;
48
51
  isConnected: boolean;
49
52
  error: TrainlyError | null;
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * const handleQuestion = async () => {
10
10
  * const answer = await ask("What is photosynthesis?");
11
- * console.log(answer);
11
+ * // Handle the answer response
12
12
  * };
13
13
  *
14
14
  * return <button onClick={handleQuestion}>Ask AI</button>;
@@ -9,7 +9,7 @@ import { useTrainlyContext } from "./TrainlyProvider";
9
9
  *
10
10
  * const handleQuestion = async () => {
11
11
  * const answer = await ask("What is photosynthesis?");
12
- * console.log(answer);
12
+ * // Handle the answer response
13
13
  * };
14
14
  *
15
15
  * return <button onClick={handleQuestion}>Ask AI</button>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@trainly/react",
3
- "version": "1.0.3",
4
- "description": "Dead simple RAG integration for React apps",
3
+ "version": "1.1.2",
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",
7
7
  "scripts": {
@@ -18,6 +18,11 @@
18
18
  "documents",
19
19
  "react",
20
20
  "trainly",
21
+ "oauth",
22
+ "authentication",
23
+ "privacy",
24
+ "clerk",
25
+ "auth0",
21
26
  "retrieval-augmented-generation",
22
27
  "semantic-search",
23
28
  "document-chat"