@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 +136 -15
- package/dist/TrainlyProvider.d.ts +4 -1
- package/dist/TrainlyProvider.js +109 -19
- package/dist/api/TrainlyClient.d.ts +6 -0
- package/dist/api/TrainlyClient.js +119 -20
- package/dist/components/TrainlyChat.js +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/useTrainly.d.ts +1 -1
- package/dist/useTrainly.js +1 -1
- package/package.json +7 -2
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.
|
|
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
|
-
##
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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;
|
package/dist/TrainlyProvider.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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:
|
|
178
|
+
details: err_3,
|
|
118
179
|
};
|
|
119
180
|
setError(error_1);
|
|
120
181
|
throw error_1;
|
|
121
|
-
case
|
|
182
|
+
case 10:
|
|
122
183
|
setIsLoading(false);
|
|
123
184
|
return [7 /*endfinally*/];
|
|
124
|
-
case
|
|
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,
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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:
|
|
265
|
+
details: err_5,
|
|
177
266
|
};
|
|
178
267
|
setError(error_3);
|
|
179
268
|
throw error_3;
|
|
180
|
-
case
|
|
269
|
+
case 10:
|
|
181
270
|
setIsLoading(false);
|
|
182
271
|
return [7 /*endfinally*/];
|
|
183
|
-
case
|
|
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,
|
|
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
|
-
|
|
304
|
+
err_6 = _a.sent();
|
|
216
305
|
// Error is already set by askWithCitations
|
|
217
|
-
console.error("Failed to send message:",
|
|
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
|
|
203
|
+
case 6:
|
|
133
204
|
response = _a.sent();
|
|
134
|
-
if (!!response.ok) return [3 /*break*/,
|
|
205
|
+
if (!!response.ok) return [3 /*break*/, 8];
|
|
135
206
|
return [4 /*yield*/, response.json()];
|
|
136
|
-
case
|
|
207
|
+
case 7:
|
|
137
208
|
error = _a.sent();
|
|
138
209
|
throw new Error("Query failed: ".concat(error.detail || response.statusText));
|
|
139
|
-
case
|
|
140
|
-
case
|
|
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.
|
|
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/
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
295
|
+
case 10:
|
|
197
296
|
presignedResponse = _b.sent();
|
|
198
|
-
if (!!presignedResponse.ok) return [3 /*break*/,
|
|
297
|
+
if (!!presignedResponse.ok) return [3 /*break*/, 12];
|
|
199
298
|
return [4 /*yield*/, presignedResponse.json()];
|
|
200
|
-
case
|
|
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
|
|
204
|
-
case
|
|
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
|
|
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
|
-
|
|
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;
|
package/dist/useTrainly.d.ts
CHANGED
package/dist/useTrainly.js
CHANGED
|
@@ -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
|
-
*
|
|
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.
|
|
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"
|