@lastbrain/ai-ui-core 1.0.3
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 +135 -0
- package/dist/client/createClient.d.ts +9 -0
- package/dist/client/createClient.d.ts.map +1 -0
- package/dist/client/createClient.js +179 -0
- package/dist/errors/errorCodes.d.ts +13 -0
- package/dist/errors/errorCodes.d.ts.map +1 -0
- package/dist/errors/errorCodes.js +13 -0
- package/dist/errors/normalizeError.d.ts +8 -0
- package/dist/errors/normalizeError.d.ts.map +1 -0
- package/dist/errors/normalizeError.js +79 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/route-handlers/nextjs/gateway.d.ts +12 -0
- package/dist/route-handlers/nextjs/gateway.d.ts.map +1 -0
- package/dist/route-handlers/nextjs/gateway.js +54 -0
- package/dist/route-handlers/nextjs/gateway.ts +93 -0
- package/dist/types/index.d.ts +96 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/package.json +65 -0
- package/src/client/createClient.ts +241 -0
- package/src/errors/errorCodes.ts +12 -0
- package/src/errors/normalizeError.ts +107 -0
- package/src/index.ts +4 -0
- package/src/route-handlers/nextjs/gateway.ts +93 -0
- package/src/types/index.ts +103 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# @lastbrain/ai-ui-core
|
|
2
|
+
|
|
3
|
+
Framework-agnostic core library for LastBrain AI UI Kit.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- HTTP client with retry logic and authentication
|
|
8
|
+
- TypeScript types for AI models, responses, and status
|
|
9
|
+
- Error normalization and handling
|
|
10
|
+
- Response parsing utilities
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @lastbrain/ai-ui-core @lastbrain/ai-ui-react
|
|
16
|
+
# or
|
|
17
|
+
pnpm add @lastbrain/ai-ui-core @lastbrain/ai-ui-react
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 🔐 Secure Integration Guide
|
|
21
|
+
|
|
22
|
+
**⚠️ NEVER expose your API key in client code!**
|
|
23
|
+
|
|
24
|
+
### Step 1: Add your LastBrain API key to `.env.local`
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
LB_API_KEY=lb_your_api_key_here
|
|
28
|
+
LB_BASE_URL=https://ai.lastbrain.io # Optional, defaults to this
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Step 2: Create a secure API proxy route
|
|
32
|
+
|
|
33
|
+
**For Next.js App Router** - Create `app/api/lastbrain/[...path]/route.ts`:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
export { GET, POST } from "@lastbrain/ai-ui-core/route-handlers/nextjs/gateway";
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
That's it! The package handles all the proxying securely to `https://ai.lastbrain.io/api/public/v1/*`.
|
|
40
|
+
|
|
41
|
+
### Step 3: Use the React components
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
"use client";
|
|
45
|
+
import { AiProvider, AiTextarea } from "@lastbrain/ai-ui-react";
|
|
46
|
+
|
|
47
|
+
export default function MyPage() {
|
|
48
|
+
return (
|
|
49
|
+
<AiProvider baseUrl="/api/lastbrain">
|
|
50
|
+
<div className="p-6 space-y-4">
|
|
51
|
+
<h1 className="text-xl font-bold">AI UI Demo</h1>
|
|
52
|
+
|
|
53
|
+
<AiTextarea
|
|
54
|
+
placeholder="Describe what you want to generate..."
|
|
55
|
+
onValue={(content) => console.log("AI response:", content)}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
</AiProvider>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Complete Example
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# 1. Install packages
|
|
67
|
+
pnpm add @lastbrain/ai-ui-core @lastbrain/ai-ui-react
|
|
68
|
+
|
|
69
|
+
# 2. Add to .env.local
|
|
70
|
+
echo "LB_API_KEY=lb_your_key" >> .env.local
|
|
71
|
+
|
|
72
|
+
# 3. Create route file
|
|
73
|
+
mkdir -p app/api/lastbrain/\[...path\]
|
|
74
|
+
cat > app/api/lastbrain/\[...path\]/route.ts << 'EOF'
|
|
75
|
+
export { GET, POST } from "@lastbrain/ai-ui-core/route-handlers/nextjs/gateway";
|
|
76
|
+
EOF
|
|
77
|
+
|
|
78
|
+
# 4. Use in your components (see Step 3 above)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Architecture
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
Your App → /api/lastbrain → Gateway (with LB_API_KEY) → https://ai.lastbrain.io/api/public/v1
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The gateway automatically:
|
|
88
|
+
|
|
89
|
+
- Adds your API key in Authorization header
|
|
90
|
+
- Proxies all requests to LastBrain API
|
|
91
|
+
- Keeps your API key secure server-side
|
|
92
|
+
|
|
93
|
+
## Direct Usage (Advanced)
|
|
94
|
+
|
|
95
|
+
For custom implementations without React components:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { createClient, ErrorCode } from "@lastbrain/ai-ui-core";
|
|
99
|
+
|
|
100
|
+
// ⚠️ Only use this server-side!
|
|
101
|
+
const client = createClient({
|
|
102
|
+
baseUrl: "https://api.lastbrain.io",
|
|
103
|
+
apiKeyId: "lb_your_api_key",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Get models
|
|
107
|
+
const models = await client.getModels();
|
|
108
|
+
|
|
109
|
+
// Generate text
|
|
110
|
+
const result = await client.generateText({
|
|
111
|
+
model: "openai/gpt-4o-mini",
|
|
112
|
+
prompt: "Write a story",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Get status
|
|
116
|
+
const status = await client.getStatus();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Security Best Practices
|
|
120
|
+
|
|
121
|
+
✅ **DO:**
|
|
122
|
+
|
|
123
|
+
- Store API keys in environment variables (`.env.local`)
|
|
124
|
+
- Create server-side proxy routes
|
|
125
|
+
- Use `baseUrl="/api/lastbrain"` in `AiProvider`
|
|
126
|
+
|
|
127
|
+
❌ **DON'T:**
|
|
128
|
+
|
|
129
|
+
- Hardcode API keys in client components
|
|
130
|
+
- Pass `apiKeyId` directly in browser code
|
|
131
|
+
- Expose `.env.local` in version control
|
|
132
|
+
|
|
133
|
+
## API Reference
|
|
134
|
+
|
|
135
|
+
See the [full documentation](https://docs.lastbrain.com/ai-ui-kit/core) for detailed API reference.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ClientConfig, ModelRef, AiTextRequest, AiTextResponse, AiImageRequest, AiImageResponse, AiEmbedRequest, AiEmbedResponse, AiStatus } from "../types";
|
|
2
|
+
export declare function createClient(config: ClientConfig): {
|
|
3
|
+
getModels: () => Promise<ModelRef[]>;
|
|
4
|
+
generateText: (req: AiTextRequest) => Promise<AiTextResponse>;
|
|
5
|
+
generateImage: (req: AiImageRequest) => Promise<AiImageResponse>;
|
|
6
|
+
embed: (req: AiEmbedRequest) => Promise<AiEmbedResponse>;
|
|
7
|
+
getStatus: () => Promise<AiStatus>;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=createClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createClient.d.ts","sourceRoot":"","sources":["../../src/client/createClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,cAAc,EACd,cAAc,EACd,eAAe,EACf,cAAc,EACd,eAAe,EACf,QAAQ,EACT,MAAM,UAAU,CAAC;AA6DlB,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY;qBAsBnB,OAAO,CAAC,QAAQ,EAAE,CAAC;wBAmCd,aAAa,KAAG,OAAO,CAAC,cAAc,CAAC;yBAmCtC,cAAc,KAAG,OAAO,CAAC,eAAe,CAAC;iBAmCjD,cAAc,KAAG,OAAO,CAAC,eAAe,CAAC;qBAkBvC,OAAO,CAAC,QAAQ,CAAC;EAwB9C"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { normalizeError } from "../errors/normalizeError";
|
|
2
|
+
const DEFAULT_TIMEOUT = 60000;
|
|
3
|
+
const DEFAULT_RETRIES = 3;
|
|
4
|
+
const INITIAL_RETRY_DELAY = 1000;
|
|
5
|
+
async function sleep(ms) {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
async function fetchWithRetry(url, options, retryConfig) {
|
|
9
|
+
let lastError;
|
|
10
|
+
for (let attempt = 0; attempt <= retryConfig.retries; attempt++) {
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(url, options);
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
const errorData = await response.json().catch(() => ({}));
|
|
15
|
+
const error = new Error(errorData.message || `HTTP ${response.status}`);
|
|
16
|
+
error.status = response.status;
|
|
17
|
+
error.response = { status: response.status };
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
return await response.json();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
lastError = error;
|
|
24
|
+
const isRetryable = error.name === "AbortError" ||
|
|
25
|
+
error.code === "ECONNREFUSED" ||
|
|
26
|
+
error.code === "ENOTFOUND" ||
|
|
27
|
+
error.message?.includes("fetch failed") ||
|
|
28
|
+
(error.status && error.status >= 500);
|
|
29
|
+
if (isRetryable && attempt < retryConfig.retries) {
|
|
30
|
+
const delay = retryConfig.delay * Math.pow(2, attempt);
|
|
31
|
+
await sleep(delay);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw lastError;
|
|
38
|
+
}
|
|
39
|
+
export function createClient(config) {
|
|
40
|
+
const timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
41
|
+
const retries = config.retries ?? DEFAULT_RETRIES;
|
|
42
|
+
function createHeaders() {
|
|
43
|
+
const headers = {
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
};
|
|
46
|
+
console.log("[APIKEY] config.apiKeyId:", config.apiKeyId);
|
|
47
|
+
if (config.apiKeyId) {
|
|
48
|
+
headers.Authorization = `Bearer ${config.apiKeyId}`;
|
|
49
|
+
}
|
|
50
|
+
return headers;
|
|
51
|
+
}
|
|
52
|
+
function createAbortSignal() {
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
setTimeout(() => controller.abort(), timeout);
|
|
55
|
+
return controller.signal;
|
|
56
|
+
}
|
|
57
|
+
async function getModels() {
|
|
58
|
+
try {
|
|
59
|
+
const url = `${config.baseUrl}/provider`;
|
|
60
|
+
const response = await fetchWithRetry(url, {
|
|
61
|
+
method: "GET",
|
|
62
|
+
headers: createHeaders(),
|
|
63
|
+
signal: createAbortSignal(),
|
|
64
|
+
}, { retries, delay: INITIAL_RETRY_DELAY });
|
|
65
|
+
// Transform response: extract all models from providers array
|
|
66
|
+
if (response.providers && Array.isArray(response.providers)) {
|
|
67
|
+
const allModels = [];
|
|
68
|
+
for (const provider of response.providers) {
|
|
69
|
+
if (provider.models && Array.isArray(provider.models)) {
|
|
70
|
+
allModels.push(...provider.models);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return allModels;
|
|
74
|
+
}
|
|
75
|
+
// Fallback: if response is already a flat array
|
|
76
|
+
if (Array.isArray(response)) {
|
|
77
|
+
return response;
|
|
78
|
+
}
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
throw normalizeError(error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function generateText(req) {
|
|
86
|
+
try {
|
|
87
|
+
const url = `${config.baseUrl}/text-ai`;
|
|
88
|
+
const response = await fetchWithRetry(url, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: createHeaders(),
|
|
91
|
+
body: JSON.stringify(req),
|
|
92
|
+
signal: createAbortSignal(),
|
|
93
|
+
}, { retries, delay: INITIAL_RETRY_DELAY });
|
|
94
|
+
// Track prompt usage if promptId is provided
|
|
95
|
+
if (req.promptId) {
|
|
96
|
+
try {
|
|
97
|
+
await fetch(`${config.baseUrl}/track-usage`, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: createHeaders(),
|
|
100
|
+
body: JSON.stringify({ promptId: req.promptId }),
|
|
101
|
+
}).catch(() => {
|
|
102
|
+
// Silently fail - don't block the response
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (_error) {
|
|
106
|
+
// Ignore tracking errors
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return response;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw normalizeError(error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function generateImage(req) {
|
|
116
|
+
try {
|
|
117
|
+
const url = `${config.baseUrl}/image-ai`;
|
|
118
|
+
const response = await fetchWithRetry(url, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: createHeaders(),
|
|
121
|
+
body: JSON.stringify(req),
|
|
122
|
+
signal: createAbortSignal(),
|
|
123
|
+
}, { retries, delay: INITIAL_RETRY_DELAY });
|
|
124
|
+
// Track prompt usage if promptId is provided
|
|
125
|
+
if (req.promptId) {
|
|
126
|
+
try {
|
|
127
|
+
await fetch(`${config.baseUrl}/track-usage`, {
|
|
128
|
+
method: "POST",
|
|
129
|
+
headers: createHeaders(),
|
|
130
|
+
body: JSON.stringify({ promptId: req.promptId }),
|
|
131
|
+
}).catch(() => {
|
|
132
|
+
// Silently fail - don't block the response
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
// Ignore tracking errors
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return response;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw normalizeError(error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function embed(req) {
|
|
146
|
+
try {
|
|
147
|
+
const url = `${config.baseUrl}/ai/embed`;
|
|
148
|
+
return await fetchWithRetry(url, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: createHeaders(),
|
|
151
|
+
body: JSON.stringify(req),
|
|
152
|
+
signal: createAbortSignal(),
|
|
153
|
+
}, { retries, delay: INITIAL_RETRY_DELAY });
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
throw normalizeError(error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function getStatus() {
|
|
160
|
+
try {
|
|
161
|
+
const url = `${config.baseUrl}/status`;
|
|
162
|
+
return await fetchWithRetry(url, {
|
|
163
|
+
method: "GET",
|
|
164
|
+
headers: createHeaders(),
|
|
165
|
+
signal: createAbortSignal(),
|
|
166
|
+
}, { retries, delay: INITIAL_RETRY_DELAY });
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
throw normalizeError(error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
getModels,
|
|
174
|
+
generateText,
|
|
175
|
+
generateImage,
|
|
176
|
+
embed,
|
|
177
|
+
getStatus,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare enum ErrorCode {
|
|
2
|
+
NO_API_KEY = "NO_API_KEY",
|
|
3
|
+
MODEL_DISABLED = "MODEL_DISABLED",
|
|
4
|
+
PRICING_UNAVAILABLE = "PRICING_UNAVAILABLE",
|
|
5
|
+
INSUFFICIENT_TOKENS = "INSUFFICIENT_TOKENS",
|
|
6
|
+
PROVIDER_DOWN = "PROVIDER_DOWN",
|
|
7
|
+
BAD_REQUEST = "BAD_REQUEST",
|
|
8
|
+
UNAUTHORIZED = "UNAUTHORIZED",
|
|
9
|
+
NETWORK_ERROR = "NETWORK_ERROR",
|
|
10
|
+
TIMEOUT = "TIMEOUT",
|
|
11
|
+
UNKNOWN = "UNKNOWN"
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=errorCodes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorCodes.d.ts","sourceRoot":"","sources":["../../src/errors/errorCodes.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,UAAU,eAAe;IACzB,cAAc,mBAAmB;IACjC,mBAAmB,wBAAwB;IAC3C,mBAAmB,wBAAwB;IAC3C,aAAa,kBAAkB;IAC/B,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,aAAa,kBAAkB;IAC/B,OAAO,YAAY;IACnB,OAAO,YAAY;CACpB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export var ErrorCode;
|
|
2
|
+
(function (ErrorCode) {
|
|
3
|
+
ErrorCode["NO_API_KEY"] = "NO_API_KEY";
|
|
4
|
+
ErrorCode["MODEL_DISABLED"] = "MODEL_DISABLED";
|
|
5
|
+
ErrorCode["PRICING_UNAVAILABLE"] = "PRICING_UNAVAILABLE";
|
|
6
|
+
ErrorCode["INSUFFICIENT_TOKENS"] = "INSUFFICIENT_TOKENS";
|
|
7
|
+
ErrorCode["PROVIDER_DOWN"] = "PROVIDER_DOWN";
|
|
8
|
+
ErrorCode["BAD_REQUEST"] = "BAD_REQUEST";
|
|
9
|
+
ErrorCode["UNAUTHORIZED"] = "UNAUTHORIZED";
|
|
10
|
+
ErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
11
|
+
ErrorCode["TIMEOUT"] = "TIMEOUT";
|
|
12
|
+
ErrorCode["UNKNOWN"] = "UNKNOWN";
|
|
13
|
+
})(ErrorCode || (ErrorCode = {}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeError.d.ts","sourceRoot":"","sources":["../../src/errors/normalizeError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,GAAG,GAAG,eAAe,CAkG1D"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ErrorCode } from "./errorCodes";
|
|
2
|
+
export function normalizeError(error) {
|
|
3
|
+
if (error?.name === "AbortError" || error?.message?.includes("timeout")) {
|
|
4
|
+
return {
|
|
5
|
+
code: ErrorCode.TIMEOUT,
|
|
6
|
+
message: "Request timeout",
|
|
7
|
+
status: 408,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
if (error?.code === "ENOTFOUND" ||
|
|
11
|
+
error?.code === "ECONNREFUSED" ||
|
|
12
|
+
error?.message?.includes("fetch failed")) {
|
|
13
|
+
return {
|
|
14
|
+
code: ErrorCode.NETWORK_ERROR,
|
|
15
|
+
message: "Network error",
|
|
16
|
+
status: 0,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const status = error?.status || error?.response?.status;
|
|
20
|
+
if (status === 401 || status === 403) {
|
|
21
|
+
return {
|
|
22
|
+
code: ErrorCode.UNAUTHORIZED,
|
|
23
|
+
message: error?.message || "Unauthorized",
|
|
24
|
+
status,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (status === 400) {
|
|
28
|
+
return {
|
|
29
|
+
code: ErrorCode.BAD_REQUEST,
|
|
30
|
+
message: error?.message || "Bad request",
|
|
31
|
+
status,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (status === 503) {
|
|
35
|
+
return {
|
|
36
|
+
code: ErrorCode.PROVIDER_DOWN,
|
|
37
|
+
message: error?.message || "Provider unavailable",
|
|
38
|
+
status,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const errorMessage = error?.message?.toLowerCase() || "";
|
|
42
|
+
if (errorMessage.includes("no api key") ||
|
|
43
|
+
errorMessage.includes("api key required")) {
|
|
44
|
+
return {
|
|
45
|
+
code: ErrorCode.NO_API_KEY,
|
|
46
|
+
message: error?.message || "No API key provided",
|
|
47
|
+
status,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (errorMessage.includes("model disabled") ||
|
|
51
|
+
errorMessage.includes("model not available")) {
|
|
52
|
+
return {
|
|
53
|
+
code: ErrorCode.MODEL_DISABLED,
|
|
54
|
+
message: error?.message || "Model disabled",
|
|
55
|
+
status,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (errorMessage.includes("pricing unavailable") ||
|
|
59
|
+
errorMessage.includes("pricing not found")) {
|
|
60
|
+
return {
|
|
61
|
+
code: ErrorCode.PRICING_UNAVAILABLE,
|
|
62
|
+
message: error?.message || "Pricing unavailable",
|
|
63
|
+
status,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (errorMessage.includes("insufficient tokens") ||
|
|
67
|
+
errorMessage.includes("not enough tokens")) {
|
|
68
|
+
return {
|
|
69
|
+
code: ErrorCode.INSUFFICIENT_TOKENS,
|
|
70
|
+
message: error?.message || "Insufficient tokens",
|
|
71
|
+
status,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
code: ErrorCode.UNKNOWN,
|
|
76
|
+
message: error?.message || "Unknown error",
|
|
77
|
+
status,
|
|
78
|
+
};
|
|
79
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NextRequest } from "next/server";
|
|
2
|
+
export declare function GET(request: NextRequest, context: {
|
|
3
|
+
params: Promise<{
|
|
4
|
+
path: string[];
|
|
5
|
+
}>;
|
|
6
|
+
}): Promise<any>;
|
|
7
|
+
export declare function POST(request: NextRequest, context: {
|
|
8
|
+
params: Promise<{
|
|
9
|
+
path: string[];
|
|
10
|
+
}>;
|
|
11
|
+
}): Promise<any>;
|
|
12
|
+
//# sourceMappingURL=gateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../../src/route-handlers/nextjs/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAgB,MAAM,aAAa,CAAC;AAwExD,wBAAsB,GAAG,CACvB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CAAE,gBAGjD;AAED,wBAAsB,IAAI,CACxB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CAAE,gBAGjD"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
/**
|
|
3
|
+
* Next.js App Router gateway for LastBrain AI API
|
|
4
|
+
*
|
|
5
|
+
* Usage in app/api/lastbrain/[...path]/route.ts:
|
|
6
|
+
* export { GET, POST } from "@lastbrain/ai-ui-core/route-handlers/nextjs/gateway";
|
|
7
|
+
*
|
|
8
|
+
* This proxies requests to https://ai.lastbrain.io/api/public/v1/[...path]
|
|
9
|
+
*/
|
|
10
|
+
const LB_API_KEY = process.env.LB_API_KEY;
|
|
11
|
+
const LB_BASE_URL = process.env.LB_BASE_URL || "https://ai.lastbrain.io";
|
|
12
|
+
if (!LB_API_KEY) {
|
|
13
|
+
console.warn("⚠️ LB_API_KEY not found in environment variables. AI features will not work.");
|
|
14
|
+
}
|
|
15
|
+
async function handleRequest(request, context) {
|
|
16
|
+
if (!LB_API_KEY) {
|
|
17
|
+
return NextResponse.json({ error: "LB_API_KEY not configured" }, { status: 500 });
|
|
18
|
+
}
|
|
19
|
+
const params = await context.params;
|
|
20
|
+
const path = params.path.join("/");
|
|
21
|
+
const url = `${LB_BASE_URL}/api/public/v1/${path}`;
|
|
22
|
+
try {
|
|
23
|
+
const headers = {
|
|
24
|
+
Authorization: `Bearer ${LB_API_KEY}`,
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
};
|
|
27
|
+
let body;
|
|
28
|
+
if (request.method === "POST") {
|
|
29
|
+
const json = await request.json();
|
|
30
|
+
body = JSON.stringify(json);
|
|
31
|
+
}
|
|
32
|
+
const response = await fetch(url, {
|
|
33
|
+
method: request.method,
|
|
34
|
+
headers,
|
|
35
|
+
body,
|
|
36
|
+
});
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
const errorText = await response.text();
|
|
39
|
+
return NextResponse.json({ error: errorText || "API request failed" }, { status: response.status });
|
|
40
|
+
}
|
|
41
|
+
const data = await response.json();
|
|
42
|
+
return NextResponse.json(data);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error("LastBrain API gateway error:", error);
|
|
46
|
+
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function GET(request, context) {
|
|
50
|
+
return handleRequest(request, context);
|
|
51
|
+
}
|
|
52
|
+
export async function POST(request, context) {
|
|
53
|
+
return handleRequest(request, context);
|
|
54
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Next.js App Router gateway for LastBrain AI API
|
|
5
|
+
*
|
|
6
|
+
* Usage in app/api/lastbrain/[...path]/route.ts:
|
|
7
|
+
* export { GET, POST } from "@lastbrain/ai-ui-core/route-handlers/nextjs/gateway";
|
|
8
|
+
*
|
|
9
|
+
* This proxies requests to https://ai.lastbrain.io/api/public/v1/[...path]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const LB_API_KEY = process.env.LB_API_KEY;
|
|
13
|
+
const LB_BASE_URL =
|
|
14
|
+
process.env.LB_BASE_URL || "https://ai.lastbrain.io/api/public/v1";
|
|
15
|
+
|
|
16
|
+
if (!LB_API_KEY) {
|
|
17
|
+
console.warn(
|
|
18
|
+
"⚠️ LB_API_KEY not found in environment variables. AI features will not work."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function handleRequest(
|
|
23
|
+
request: NextRequest,
|
|
24
|
+
context: { params: Promise<{ path: string[] }> }
|
|
25
|
+
) {
|
|
26
|
+
if (!LB_API_KEY) {
|
|
27
|
+
return NextResponse.json(
|
|
28
|
+
{ error: "LB_API_KEY not configured" },
|
|
29
|
+
{ status: 500 }
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const params = await context.params;
|
|
34
|
+
const rawPath = params.path.join("/");
|
|
35
|
+
// Remove leading slash to avoid double slash
|
|
36
|
+
const path = rawPath.startsWith("/") ? rawPath.slice(1) : rawPath;
|
|
37
|
+
const url = `${LB_BASE_URL}/${path}`;
|
|
38
|
+
console.log("[Gateway] Proxying to:", url);
|
|
39
|
+
console.log(
|
|
40
|
+
"[Gateway] Bearer token:",
|
|
41
|
+
LB_API_KEY ? `${LB_API_KEY.substring(0, 10)}...` : "MISSING"
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const headers: HeadersInit = {
|
|
46
|
+
Authorization: `Bearer ${LB_API_KEY}`,
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
let body: string | undefined;
|
|
51
|
+
if (request.method === "POST") {
|
|
52
|
+
const json = await request.json();
|
|
53
|
+
body = JSON.stringify(json);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
method: request.method,
|
|
58
|
+
headers,
|
|
59
|
+
body,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const errorText = await response.text();
|
|
64
|
+
return NextResponse.json(
|
|
65
|
+
{ error: errorText || "API request failed" },
|
|
66
|
+
{ status: response.status }
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
return NextResponse.json(data);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error("LastBrain API gateway error:", error);
|
|
74
|
+
return NextResponse.json(
|
|
75
|
+
{ error: "Internal server error" },
|
|
76
|
+
{ status: 500 }
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function GET(
|
|
82
|
+
request: NextRequest,
|
|
83
|
+
context: { params: Promise<{ path: string[] }> }
|
|
84
|
+
) {
|
|
85
|
+
return handleRequest(request, context);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function POST(
|
|
89
|
+
request: NextRequest,
|
|
90
|
+
context: { params: Promise<{ path: string[] }> }
|
|
91
|
+
) {
|
|
92
|
+
return handleRequest(request, context);
|
|
93
|
+
}
|