@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.
@@ -0,0 +1,96 @@
1
+ export interface ModelRef {
2
+ id: string;
3
+ name: string;
4
+ type: "text" | "language" | "image" | "embed";
5
+ provider: string;
6
+ tags?: string[];
7
+ pricing?: {
8
+ inputTokens?: number;
9
+ outputTokens?: number;
10
+ imageTokens?: number;
11
+ };
12
+ }
13
+ export interface AiTextRequest {
14
+ model: string;
15
+ prompt: string;
16
+ context?: string;
17
+ editMode?: boolean;
18
+ inputValue?: string;
19
+ system?: string;
20
+ maxTokens?: number;
21
+ temperature?: number;
22
+ promptId?: string;
23
+ actionType?: "generate-text" | "generate-recipe-text" | "autocomplete";
24
+ }
25
+ export interface AiTextResponse {
26
+ text: string;
27
+ debitTokens: number;
28
+ requestId: string;
29
+ }
30
+ export interface AiImageRequest {
31
+ model: string;
32
+ prompt: string;
33
+ context?: string;
34
+ size?: string;
35
+ n?: number;
36
+ promptId?: string;
37
+ }
38
+ export interface AiImageResponse {
39
+ url: string;
40
+ debitTokens: number;
41
+ requestId: string;
42
+ }
43
+ export interface AiEmbedRequest {
44
+ model: string;
45
+ input: string;
46
+ }
47
+ export interface AiEmbedResponse {
48
+ embedding: number[];
49
+ embeddings?: number[][];
50
+ debitTokens?: number;
51
+ requestId?: string;
52
+ }
53
+ export interface AiStatus {
54
+ request_id: string;
55
+ api_key: {
56
+ id: string;
57
+ name: string;
58
+ prefix: string;
59
+ env: string;
60
+ scopes: string[];
61
+ rate_limit_rpm: number;
62
+ daily_token_limit: number | null;
63
+ created_at: string;
64
+ };
65
+ balance: {
66
+ purchased: number;
67
+ quota: number;
68
+ total: number;
69
+ };
70
+ quota: {
71
+ plan: string;
72
+ effective_quota: number;
73
+ used_quota: number;
74
+ remaining_quota: number;
75
+ period_start: string | null;
76
+ period_end: string | null;
77
+ is_active: boolean;
78
+ };
79
+ settings: {
80
+ mode: string;
81
+ max_tokens_per_call: number | null;
82
+ };
83
+ storage: {
84
+ db_mb: number;
85
+ files_mb: number;
86
+ total_mb: number;
87
+ allocated_mb: number;
88
+ };
89
+ }
90
+ export interface ClientConfig {
91
+ baseUrl: string;
92
+ apiKeyId: string;
93
+ timeout?: number;
94
+ retries?: number;
95
+ }
96
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@lastbrain/ai-ui-core",
3
+ "version": "1.0.3",
4
+ "description": "Framework-agnostic core library for LastBrain AI UI Kit",
5
+ "private": false,
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./route-handlers/nextjs/gateway": {
17
+ "types": "./dist/route-handlers/nextjs/gateway.ts",
18
+ "import": "./dist/route-handlers/nextjs/gateway.ts",
19
+ "require": "./dist/route-handlers/nextjs/gateway.ts",
20
+ "default": "./dist/route-handlers/nextjs/gateway.ts"
21
+ }
22
+ },
23
+ "release": {
24
+ "type": "public"
25
+ },
26
+ "keywords": [
27
+ "ai",
28
+ "lastbrain",
29
+ "core",
30
+ "utilities",
31
+ "typescript",
32
+ "framework-agnostic"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/lastpublication/starter.git",
37
+ "directory": "packages/ai-ui-core"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "src"
45
+ ],
46
+ "sideEffects": false,
47
+ "dependencies": {},
48
+ "devDependencies": {
49
+ "typescript": "^5.4.0",
50
+ "@types/node": "^20.0.0"
51
+ },
52
+ "peerDependencies": {
53
+ "next": ">=14.0.0"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "next": {
57
+ "optional": true
58
+ }
59
+ },
60
+ "scripts": {
61
+ "dev": "tsc -p tsconfig.json --watch",
62
+ "build": "tsc -p tsconfig.json && mkdir -p dist/route-handlers/nextjs && cp src/route-handlers/nextjs/gateway.ts dist/route-handlers/nextjs/gateway.ts",
63
+ "lint": "eslint ."
64
+ }
65
+ }
@@ -0,0 +1,241 @@
1
+ import type {
2
+ ClientConfig,
3
+ ModelRef,
4
+ AiTextRequest,
5
+ AiTextResponse,
6
+ AiImageRequest,
7
+ AiImageResponse,
8
+ AiEmbedRequest,
9
+ AiEmbedResponse,
10
+ AiStatus,
11
+ } from "../types";
12
+ import { normalizeError } from "../errors/normalizeError";
13
+
14
+ const DEFAULT_TIMEOUT = 60000;
15
+ const DEFAULT_RETRIES = 3;
16
+ const INITIAL_RETRY_DELAY = 1000;
17
+
18
+ interface RetryConfig {
19
+ retries: number;
20
+ delay: number;
21
+ }
22
+
23
+ async function sleep(ms: number): Promise<void> {
24
+ return new Promise((resolve) => setTimeout(resolve, ms));
25
+ }
26
+
27
+ async function fetchWithRetry<T>(
28
+ url: string,
29
+ options: RequestInit,
30
+ retryConfig: RetryConfig
31
+ ): Promise<T> {
32
+ let lastError: any;
33
+
34
+ for (let attempt = 0; attempt <= retryConfig.retries; attempt++) {
35
+ try {
36
+ const response = await fetch(url, options);
37
+
38
+ if (!response.ok) {
39
+ const errorData = await response.json().catch(() => ({}));
40
+ const error: any = new Error(
41
+ errorData.message || `HTTP ${response.status}`
42
+ );
43
+ error.status = response.status;
44
+ error.response = { status: response.status };
45
+ throw error;
46
+ }
47
+
48
+ return await response.json();
49
+ } catch (error: any) {
50
+ lastError = error;
51
+
52
+ const isRetryable =
53
+ error.name === "AbortError" ||
54
+ error.code === "ECONNREFUSED" ||
55
+ error.code === "ENOTFOUND" ||
56
+ error.message?.includes("fetch failed") ||
57
+ (error.status && error.status >= 500);
58
+
59
+ if (isRetryable && attempt < retryConfig.retries) {
60
+ const delay = retryConfig.delay * Math.pow(2, attempt);
61
+ await sleep(delay);
62
+ continue;
63
+ }
64
+
65
+ throw error;
66
+ }
67
+ }
68
+
69
+ throw lastError;
70
+ }
71
+
72
+ export function createClient(config: ClientConfig) {
73
+ const timeout = config.timeout ?? DEFAULT_TIMEOUT;
74
+ const retries = config.retries ?? DEFAULT_RETRIES;
75
+
76
+ function createHeaders(): Record<string, string> {
77
+ const headers: Record<string, string> = {
78
+ "Content-Type": "application/json",
79
+ };
80
+ console.log("[APIKEY] config.apiKeyId:", config.apiKeyId);
81
+ if (config.apiKeyId) {
82
+ headers.Authorization = `Bearer ${config.apiKeyId}`;
83
+ }
84
+
85
+ return headers;
86
+ }
87
+
88
+ function createAbortSignal(): AbortSignal {
89
+ const controller = new AbortController();
90
+ setTimeout(() => controller.abort(), timeout);
91
+ return controller.signal;
92
+ }
93
+
94
+ async function getModels(): Promise<ModelRef[]> {
95
+ try {
96
+ const url = `${config.baseUrl}/provider`;
97
+ const response = await fetchWithRetry<any>(
98
+ url,
99
+ {
100
+ method: "GET",
101
+ headers: createHeaders(),
102
+ signal: createAbortSignal(),
103
+ },
104
+ { retries, delay: INITIAL_RETRY_DELAY }
105
+ );
106
+
107
+ // Transform response: extract all models from providers array
108
+ if (response.providers && Array.isArray(response.providers)) {
109
+ const allModels: ModelRef[] = [];
110
+ for (const provider of response.providers) {
111
+ if (provider.models && Array.isArray(provider.models)) {
112
+ allModels.push(...provider.models);
113
+ }
114
+ }
115
+ return allModels;
116
+ }
117
+
118
+ // Fallback: if response is already a flat array
119
+ if (Array.isArray(response)) {
120
+ return response;
121
+ }
122
+
123
+ return [];
124
+ } catch (error) {
125
+ throw normalizeError(error);
126
+ }
127
+ }
128
+
129
+ async function generateText(req: AiTextRequest): Promise<AiTextResponse> {
130
+ try {
131
+ const url = `${config.baseUrl}/text-ai`;
132
+ const response = await fetchWithRetry<AiTextResponse>(
133
+ url,
134
+ {
135
+ method: "POST",
136
+ headers: createHeaders(),
137
+ body: JSON.stringify(req),
138
+ signal: createAbortSignal(),
139
+ },
140
+ { retries, delay: INITIAL_RETRY_DELAY }
141
+ );
142
+
143
+ // Track prompt usage if promptId is provided
144
+ if (req.promptId) {
145
+ try {
146
+ await fetch(`${config.baseUrl}/track-usage`, {
147
+ method: "POST",
148
+ headers: createHeaders(),
149
+ body: JSON.stringify({ promptId: req.promptId }),
150
+ }).catch(() => {
151
+ // Silently fail - don't block the response
152
+ });
153
+ } catch (_error) {
154
+ // Ignore tracking errors
155
+ }
156
+ }
157
+
158
+ return response;
159
+ } catch (error) {
160
+ throw normalizeError(error);
161
+ }
162
+ }
163
+
164
+ async function generateImage(req: AiImageRequest): Promise<AiImageResponse> {
165
+ try {
166
+ const url = `${config.baseUrl}/image-ai`;
167
+ const response = await fetchWithRetry<AiImageResponse>(
168
+ url,
169
+ {
170
+ method: "POST",
171
+ headers: createHeaders(),
172
+ body: JSON.stringify(req),
173
+ signal: createAbortSignal(),
174
+ },
175
+ { retries, delay: INITIAL_RETRY_DELAY }
176
+ );
177
+
178
+ // Track prompt usage if promptId is provided
179
+ if (req.promptId) {
180
+ try {
181
+ await fetch(`${config.baseUrl}/track-usage`, {
182
+ method: "POST",
183
+ headers: createHeaders(),
184
+ body: JSON.stringify({ promptId: req.promptId }),
185
+ }).catch(() => {
186
+ // Silently fail - don't block the response
187
+ });
188
+ } catch (error) {
189
+ // Ignore tracking errors
190
+ }
191
+ }
192
+
193
+ return response;
194
+ } catch (error) {
195
+ throw normalizeError(error);
196
+ }
197
+ }
198
+
199
+ async function embed(req: AiEmbedRequest): Promise<AiEmbedResponse> {
200
+ try {
201
+ const url = `${config.baseUrl}/ai/embed`;
202
+ return await fetchWithRetry<AiEmbedResponse>(
203
+ url,
204
+ {
205
+ method: "POST",
206
+ headers: createHeaders(),
207
+ body: JSON.stringify(req),
208
+ signal: createAbortSignal(),
209
+ },
210
+ { retries, delay: INITIAL_RETRY_DELAY }
211
+ );
212
+ } catch (error) {
213
+ throw normalizeError(error);
214
+ }
215
+ }
216
+
217
+ async function getStatus(): Promise<AiStatus> {
218
+ try {
219
+ const url = `${config.baseUrl}/status`;
220
+ return await fetchWithRetry<AiStatus>(
221
+ url,
222
+ {
223
+ method: "GET",
224
+ headers: createHeaders(),
225
+ signal: createAbortSignal(),
226
+ },
227
+ { retries, delay: INITIAL_RETRY_DELAY }
228
+ );
229
+ } catch (error) {
230
+ throw normalizeError(error);
231
+ }
232
+ }
233
+
234
+ return {
235
+ getModels,
236
+ generateText,
237
+ generateImage,
238
+ embed,
239
+ getStatus,
240
+ };
241
+ }
@@ -0,0 +1,12 @@
1
+ export 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
+ }
@@ -0,0 +1,107 @@
1
+ import { ErrorCode } from "./errorCodes";
2
+
3
+ export interface NormalizedError {
4
+ code: ErrorCode;
5
+ message: string;
6
+ status?: number;
7
+ }
8
+
9
+ export function normalizeError(error: any): NormalizedError {
10
+ if (error?.name === "AbortError" || error?.message?.includes("timeout")) {
11
+ return {
12
+ code: ErrorCode.TIMEOUT,
13
+ message: "Request timeout",
14
+ status: 408,
15
+ };
16
+ }
17
+
18
+ if (
19
+ error?.code === "ENOTFOUND" ||
20
+ error?.code === "ECONNREFUSED" ||
21
+ error?.message?.includes("fetch failed")
22
+ ) {
23
+ return {
24
+ code: ErrorCode.NETWORK_ERROR,
25
+ message: "Network error",
26
+ status: 0,
27
+ };
28
+ }
29
+
30
+ const status = error?.status || error?.response?.status;
31
+
32
+ if (status === 401 || status === 403) {
33
+ return {
34
+ code: ErrorCode.UNAUTHORIZED,
35
+ message: error?.message || "Unauthorized",
36
+ status,
37
+ };
38
+ }
39
+
40
+ if (status === 400) {
41
+ return {
42
+ code: ErrorCode.BAD_REQUEST,
43
+ message: error?.message || "Bad request",
44
+ status,
45
+ };
46
+ }
47
+
48
+ if (status === 503) {
49
+ return {
50
+ code: ErrorCode.PROVIDER_DOWN,
51
+ message: error?.message || "Provider unavailable",
52
+ status,
53
+ };
54
+ }
55
+
56
+ const errorMessage = error?.message?.toLowerCase() || "";
57
+
58
+ if (
59
+ errorMessage.includes("no api key") ||
60
+ errorMessage.includes("api key required")
61
+ ) {
62
+ return {
63
+ code: ErrorCode.NO_API_KEY,
64
+ message: error?.message || "No API key provided",
65
+ status,
66
+ };
67
+ }
68
+
69
+ if (
70
+ errorMessage.includes("model disabled") ||
71
+ errorMessage.includes("model not available")
72
+ ) {
73
+ return {
74
+ code: ErrorCode.MODEL_DISABLED,
75
+ message: error?.message || "Model disabled",
76
+ status,
77
+ };
78
+ }
79
+
80
+ if (
81
+ errorMessage.includes("pricing unavailable") ||
82
+ errorMessage.includes("pricing not found")
83
+ ) {
84
+ return {
85
+ code: ErrorCode.PRICING_UNAVAILABLE,
86
+ message: error?.message || "Pricing unavailable",
87
+ status,
88
+ };
89
+ }
90
+
91
+ if (
92
+ errorMessage.includes("insufficient tokens") ||
93
+ errorMessage.includes("not enough tokens")
94
+ ) {
95
+ return {
96
+ code: ErrorCode.INSUFFICIENT_TOKENS,
97
+ message: error?.message || "Insufficient tokens",
98
+ status,
99
+ };
100
+ }
101
+
102
+ return {
103
+ code: ErrorCode.UNKNOWN,
104
+ message: error?.message || "Unknown error",
105
+ status,
106
+ };
107
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./types";
2
+ export * from "./errors/errorCodes";
3
+ export * from "./errors/normalizeError";
4
+ export * from "./client/createClient";
@@ -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
+ }