@lastbrain/ai-ui-react 1.0.20 → 1.0.23
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/dist/hooks/useAiClient.d.ts.map +1 -1
- package/dist/hooks/useAiClient.js +8 -0
- package/dist/hooks/useModelManagement.d.ts.map +1 -1
- package/dist/hooks/useModelManagement.js +20 -1
- package/dist/hooks/usePrompts.d.ts.map +1 -1
- package/dist/hooks/usePrompts.js +100 -18
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/utils/cache.d.ts +32 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +86 -0
- package/dist/utils/modelManagement.d.ts.map +1 -1
- package/dist/utils/modelManagement.js +29 -13
- package/package.json +1 -1
- package/src/hooks/useAiClient.ts +9 -0
- package/src/hooks/useModelManagement.ts +30 -1
- package/src/hooks/usePrompts.ts +119 -18
- package/src/index.ts +1 -0
- package/src/utils/cache.ts +111 -0
- package/src/utils/modelManagement.ts +42 -21
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAiClient.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiClient.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB;;;;;;
|
|
1
|
+
{"version":3,"file":"useAiClient.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiClient.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB;;;;;;EAoBvD"}
|
|
@@ -7,6 +7,14 @@ export function useAiClient(options) {
|
|
|
7
7
|
const client = useMemo(() => {
|
|
8
8
|
const baseUrl = options?.baseUrl || context.baseUrl;
|
|
9
9
|
const apiKeyId = options?.apiKeyId || context.apiKeyId;
|
|
10
|
+
console.log("[useAiClient] Creating client with:", {
|
|
11
|
+
contextBaseUrl: context.baseUrl,
|
|
12
|
+
contextApiKeyId: context.apiKeyId,
|
|
13
|
+
optionsBaseUrl: options?.baseUrl,
|
|
14
|
+
optionsApiKeyId: options?.apiKeyId,
|
|
15
|
+
finalBaseUrl: baseUrl,
|
|
16
|
+
finalApiKeyId: apiKeyId,
|
|
17
|
+
});
|
|
10
18
|
return createClient({ baseUrl, apiKeyId });
|
|
11
19
|
}, [options?.baseUrl, options?.apiKeyId, context.baseUrl, context.apiKeyId]);
|
|
12
20
|
return client;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useModelManagement.d.ts","sourceRoot":"","sources":["../../src/hooks/useModelManagement.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"useModelManagement.d.ts","sourceRoot":"","sources":["../../src/hooks/useModelManagement.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AASlC,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IACnE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACjD;AAED,MAAM,WAAW,wBAAwB;IAEvC,eAAe,EAAE,OAAO,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAGrB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAGvC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,eAAe,EAAE,MAAM,OAAO,EAAE,CAAC;IACjC,iBAAiB,EAAE,MAAM,OAAO,EAAE,CAAC;CACpC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,yBAA8B,GACtC,wBAAwB,CA6M1B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useCallback, useEffect } from "react";
|
|
2
2
|
import { useAiContext } from "../context/AiProvider";
|
|
3
3
|
import { toggleUserModel, getAvailableModels, getUserModels, } from "../utils/modelManagement";
|
|
4
|
+
import { getCached, setCache, isRateLimited, getRateLimitResetTime, } from "../utils/cache";
|
|
4
5
|
/**
|
|
5
6
|
* Hook pour gérer les modèles IA d'un utilisateur
|
|
6
7
|
*/
|
|
@@ -21,6 +22,21 @@ export function useModelManagement(options = {}) {
|
|
|
21
22
|
try {
|
|
22
23
|
setLoading(true);
|
|
23
24
|
setError(null);
|
|
25
|
+
// Check cache first (5 minutes TTL for models)
|
|
26
|
+
const cacheKey = `models_${category || "all"}`;
|
|
27
|
+
const cached = getCached(cacheKey, 300000);
|
|
28
|
+
if (cached) {
|
|
29
|
+
console.log("[useModelManagement] Using cached models");
|
|
30
|
+
setAvailableModels(cached);
|
|
31
|
+
setLoading(false);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Check rate limit (max 5 calls per minute)
|
|
35
|
+
if (isRateLimited("models_fetch", 5, 60000)) {
|
|
36
|
+
const resetTime = getRateLimitResetTime("models_fetch");
|
|
37
|
+
const resetSeconds = Math.ceil(resetTime / 1000);
|
|
38
|
+
throw new Error(`Rate limit exceeded. Please wait ${resetSeconds}s before trying again.`);
|
|
39
|
+
}
|
|
24
40
|
console.log("[useModelManagement] Fetching available models with options:", {
|
|
25
41
|
apiKey: effectiveOptions.apiKey
|
|
26
42
|
? effectiveOptions.apiKey.substring(0, 10) + "..."
|
|
@@ -33,6 +49,8 @@ export function useModelManagement(options = {}) {
|
|
|
33
49
|
? models.filter((m) => m.category === category)
|
|
34
50
|
: models;
|
|
35
51
|
setAvailableModels(filteredModels);
|
|
52
|
+
// Cache the results
|
|
53
|
+
setCache(cacheKey, filteredModels);
|
|
36
54
|
console.log("[useModelManagement] Set filtered models:", filteredModels.length);
|
|
37
55
|
}
|
|
38
56
|
catch (err) {
|
|
@@ -113,7 +131,8 @@ export function useModelManagement(options = {}) {
|
|
|
113
131
|
}, [availableModels, userModels]);
|
|
114
132
|
// Auto-fetch au mount - si autoFetch ET (apiKey OU baseUrl avec proxy externe)
|
|
115
133
|
useEffect(() => {
|
|
116
|
-
const isExternalProxy = effectiveOptions.baseUrl &&
|
|
134
|
+
const isExternalProxy = effectiveOptions.baseUrl &&
|
|
135
|
+
effectiveOptions.baseUrl.includes("/api/lastbrain");
|
|
117
136
|
console.log("[useModelManagement] useEffect triggered:", {
|
|
118
137
|
autoFetch,
|
|
119
138
|
hasApiKey: !!effectiveOptions.apiKey,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePrompts.d.ts","sourceRoot":"","sources":["../../src/hooks/usePrompts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePrompts.d.ts","sourceRoot":"","sources":["../../src/hooks/usePrompts.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAa,SAAQ,MAAM;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,CAAC,OAAO,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,YAAY,EAAE,CACZ,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,GAAG,YAAY,GAAG,YAAY,CAAC,KACnD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5B,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5E,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,aAAa,EAAE,CACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,KAClC,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAED,wBAAgB,UAAU,IAAI,gBAAgB,CA8Q7C"}
|
package/dist/hooks/usePrompts.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useState, useCallback } from "react";
|
|
3
3
|
import { useAiContext } from "../context/AiProvider";
|
|
4
|
+
import { getCached, setCache, clearCache, isRateLimited, getRateLimitResetTime, } from "../utils/cache";
|
|
4
5
|
export function usePrompts() {
|
|
5
6
|
const { apiKeyId, baseUrl } = useAiContext();
|
|
6
7
|
const [prompts, setPrompts] = useState([]);
|
|
@@ -10,30 +11,69 @@ export function usePrompts() {
|
|
|
10
11
|
try {
|
|
11
12
|
setLoading(true);
|
|
12
13
|
setError(null);
|
|
14
|
+
// Generate cache key based on options
|
|
15
|
+
const cacheKey = `prompts_${JSON.stringify(options || {})}`;
|
|
16
|
+
// Check cache first (60 seconds TTL)
|
|
17
|
+
const cached = getCached(cacheKey, 60000);
|
|
18
|
+
if (cached) {
|
|
19
|
+
console.log("[usePrompts] Using cached data");
|
|
20
|
+
setPrompts(cached);
|
|
21
|
+
setLoading(false);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Check rate limit (max 10 calls per minute)
|
|
25
|
+
if (isRateLimited("prompts_fetch", 10, 60000)) {
|
|
26
|
+
const resetTime = getRateLimitResetTime("prompts_fetch");
|
|
27
|
+
const resetSeconds = Math.ceil(resetTime / 1000);
|
|
28
|
+
throw new Error(`Rate limit exceeded. Please wait ${resetSeconds}s before trying again.`);
|
|
29
|
+
}
|
|
13
30
|
const params = new URLSearchParams();
|
|
14
31
|
if (options?.type)
|
|
15
32
|
params.append("type", options.type);
|
|
16
33
|
if (options?.favorite !== undefined)
|
|
17
34
|
params.append("favorite", String(options.favorite));
|
|
18
|
-
// Déterminer
|
|
35
|
+
// Déterminer le contexte:
|
|
36
|
+
// - isExternalProxy: app externe via proxy /api/lastbrain
|
|
37
|
+
// - isPublicApi: app consommatrice avec API key via /api/public/v1
|
|
38
|
+
// - isAuthInternal: app interne avec session via /api/ai/auth
|
|
19
39
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
40
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
20
41
|
// Use public route without auth if public=true, otherwise use auth route
|
|
21
42
|
let endpoint;
|
|
22
43
|
if (options?.public) {
|
|
23
44
|
endpoint = isExternalProxy
|
|
24
45
|
? `${baseUrl}/ai/public/prompts?${params}`
|
|
25
|
-
:
|
|
46
|
+
: isPublicApi
|
|
47
|
+
? `${baseUrl}/prompts?${params}`
|
|
48
|
+
: baseUrl
|
|
49
|
+
? `${baseUrl}/public/prompts?${params}`
|
|
50
|
+
: `/api/ai/public/prompts?${params}`;
|
|
26
51
|
}
|
|
27
52
|
else {
|
|
28
53
|
endpoint = isExternalProxy
|
|
29
54
|
? `${baseUrl}/ai/auth/prompts?${params}`
|
|
30
|
-
:
|
|
55
|
+
: isPublicApi
|
|
56
|
+
? `${baseUrl}/prompts?${params}`
|
|
57
|
+
: baseUrl
|
|
58
|
+
? `${baseUrl}/auth/prompts?${params}`
|
|
59
|
+
: `/api/ai/auth/prompts?${params}`;
|
|
31
60
|
}
|
|
32
61
|
console.log("[usePrompts] Fetching prompts from:", endpoint);
|
|
33
|
-
const
|
|
62
|
+
const headers = {};
|
|
63
|
+
// Ajouter l'API key pour les appels publics directs (pas de proxy externe ni auth interne)
|
|
64
|
+
if (isPublicApi && apiKeyId) {
|
|
65
|
+
headers["Authorization"] = `Bearer ${apiKeyId}`;
|
|
66
|
+
}
|
|
67
|
+
const response = await fetch(endpoint, {
|
|
68
|
+
headers,
|
|
69
|
+
credentials: isExternalProxy ? "include" : "same-origin",
|
|
70
|
+
});
|
|
34
71
|
const data = await response.json();
|
|
35
72
|
if (response.ok) {
|
|
36
|
-
|
|
73
|
+
const promptsData = data.prompts || [];
|
|
74
|
+
setPrompts(promptsData);
|
|
75
|
+
// Cache the results
|
|
76
|
+
setCache(cacheKey, promptsData);
|
|
37
77
|
}
|
|
38
78
|
else {
|
|
39
79
|
setError(data.error || "Failed to fetch prompts");
|
|
@@ -51,15 +91,22 @@ export function usePrompts() {
|
|
|
51
91
|
setError(null);
|
|
52
92
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
53
93
|
const endpoint = isExternalProxy
|
|
54
|
-
? `${baseUrl}/
|
|
94
|
+
? `${baseUrl}/prompts` // Proxy handles auth routes
|
|
55
95
|
: "/api/ai/auth/prompts";
|
|
96
|
+
const headers = { "Content-Type": "application/json" };
|
|
97
|
+
if (!isExternalProxy && apiKeyId) {
|
|
98
|
+
headers["Authorization"] = `Bearer ${apiKeyId}`;
|
|
99
|
+
}
|
|
56
100
|
const response = await fetch(endpoint, {
|
|
57
101
|
method: "POST",
|
|
58
|
-
headers
|
|
102
|
+
headers,
|
|
59
103
|
body: JSON.stringify(data),
|
|
104
|
+
credentials: isExternalProxy ? "include" : "same-origin",
|
|
60
105
|
});
|
|
61
106
|
const result = await response.json();
|
|
62
107
|
if (response.ok) {
|
|
108
|
+
// Invalidate cache after creating a prompt
|
|
109
|
+
clearCache();
|
|
63
110
|
return result.prompt;
|
|
64
111
|
}
|
|
65
112
|
else {
|
|
@@ -71,21 +118,33 @@ export function usePrompts() {
|
|
|
71
118
|
setError(err instanceof Error ? err.message : "Unknown error");
|
|
72
119
|
return null;
|
|
73
120
|
}
|
|
74
|
-
}, [baseUrl]);
|
|
121
|
+
}, [baseUrl, apiKeyId]);
|
|
75
122
|
const updatePrompt = useCallback(async (id, data) => {
|
|
76
123
|
try {
|
|
77
124
|
setError(null);
|
|
78
125
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
126
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
79
127
|
const endpoint = isExternalProxy
|
|
80
128
|
? `${baseUrl}/ai/auth/prompts`
|
|
81
|
-
:
|
|
129
|
+
: isPublicApi
|
|
130
|
+
? `${baseUrl}/prompts`
|
|
131
|
+
: baseUrl
|
|
132
|
+
? `${baseUrl}/auth/prompts`
|
|
133
|
+
: "/api/ai/auth/prompts";
|
|
134
|
+
const headers = { "Content-Type": "application/json" };
|
|
135
|
+
if (isPublicApi && apiKeyId) {
|
|
136
|
+
headers["Authorization"] = `Bearer ${apiKeyId}`;
|
|
137
|
+
}
|
|
82
138
|
const response = await fetch(endpoint, {
|
|
83
139
|
method: "PUT",
|
|
84
|
-
headers
|
|
140
|
+
headers,
|
|
85
141
|
body: JSON.stringify({ id, ...data }),
|
|
142
|
+
credentials: isExternalProxy ? "include" : "same-origin",
|
|
86
143
|
});
|
|
87
144
|
const result = await response.json();
|
|
88
145
|
if (response.ok) {
|
|
146
|
+
// Invalidate cache after updating a prompt
|
|
147
|
+
clearCache();
|
|
89
148
|
return result.prompt;
|
|
90
149
|
}
|
|
91
150
|
else {
|
|
@@ -97,19 +156,32 @@ export function usePrompts() {
|
|
|
97
156
|
setError(err instanceof Error ? err.message : "Unknown error");
|
|
98
157
|
return null;
|
|
99
158
|
}
|
|
100
|
-
}, [baseUrl]);
|
|
159
|
+
}, [baseUrl, apiKeyId]);
|
|
101
160
|
const deletePrompt = useCallback(async (id) => {
|
|
102
161
|
try {
|
|
103
162
|
setError(null);
|
|
104
163
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
164
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
105
165
|
const endpoint = isExternalProxy
|
|
106
|
-
? `${baseUrl}/
|
|
107
|
-
:
|
|
166
|
+
? `${baseUrl}/prompts?id=${id}` // Proxy handles auth routes
|
|
167
|
+
: isPublicApi
|
|
168
|
+
? `${baseUrl}/prompts?id=${id}`
|
|
169
|
+
: baseUrl
|
|
170
|
+
? `${baseUrl}/auth/prompts?id=${id}`
|
|
171
|
+
: `/api/ai/auth/prompts?id=${id}`;
|
|
172
|
+
const headers = {};
|
|
173
|
+
if (isPublicApi && apiKeyId) {
|
|
174
|
+
headers["Authorization"] = `Bearer ${apiKeyId}`;
|
|
175
|
+
}
|
|
108
176
|
const response = await fetch(endpoint, {
|
|
109
177
|
method: "DELETE",
|
|
178
|
+
headers,
|
|
179
|
+
credentials: isExternalProxy ? "include" : "same-origin",
|
|
110
180
|
});
|
|
111
181
|
const result = await response.json();
|
|
112
182
|
if (response.ok) {
|
|
183
|
+
// Invalidate cache after deleting a prompt
|
|
184
|
+
clearCache();
|
|
113
185
|
return true;
|
|
114
186
|
}
|
|
115
187
|
else {
|
|
@@ -121,23 +193,33 @@ export function usePrompts() {
|
|
|
121
193
|
setError(err instanceof Error ? err.message : "Unknown error");
|
|
122
194
|
return false;
|
|
123
195
|
}
|
|
124
|
-
}, [baseUrl]);
|
|
196
|
+
}, [baseUrl, apiKeyId]);
|
|
125
197
|
const incrementStat = useCallback(async (promptId, statType) => {
|
|
126
198
|
try {
|
|
127
199
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
200
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
128
201
|
const endpoint = isExternalProxy
|
|
129
|
-
? `${baseUrl}/
|
|
130
|
-
:
|
|
202
|
+
? `${baseUrl}/prompts/stats` // Proxy handles auth routes internally
|
|
203
|
+
: isPublicApi
|
|
204
|
+
? `${baseUrl}/prompts/stats`
|
|
205
|
+
: baseUrl
|
|
206
|
+
? `${baseUrl}/auth/prompts/stats`
|
|
207
|
+
: "/api/ai/auth/prompts/stats";
|
|
208
|
+
const headers = { "Content-Type": "application/json" };
|
|
209
|
+
if (isPublicApi && apiKeyId) {
|
|
210
|
+
headers["Authorization"] = `Bearer ${apiKeyId}`;
|
|
211
|
+
}
|
|
131
212
|
await fetch(endpoint, {
|
|
132
213
|
method: "POST",
|
|
133
|
-
headers
|
|
214
|
+
headers,
|
|
134
215
|
body: JSON.stringify({ prompt_id: promptId, stat_type: statType }),
|
|
216
|
+
credentials: isExternalProxy ? "include" : "same-origin",
|
|
135
217
|
});
|
|
136
218
|
}
|
|
137
219
|
catch (err) {
|
|
138
220
|
// Silent fail for stats
|
|
139
221
|
}
|
|
140
|
-
}, [baseUrl]);
|
|
222
|
+
}, [baseUrl, apiKeyId]);
|
|
141
223
|
return {
|
|
142
224
|
prompts,
|
|
143
225
|
loading,
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from "./components/AiImageButton";
|
|
|
17
17
|
export * from "./components/AiSettingsButton";
|
|
18
18
|
export * from "./components/AiStatusButton";
|
|
19
19
|
export * from "./utils/modelManagement";
|
|
20
|
+
export * from "./utils/cache";
|
|
20
21
|
export * from "./examples/AiImageGenerator";
|
|
21
22
|
export * from "./examples/AiPromptPanelAdvanced";
|
|
22
23
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,SAAS,CAAC;AAGxB,cAAc,sBAAsB,CAAC;AAGrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAG3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAG5C,cAAc,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,SAAS,CAAC;AAGxB,cAAc,sBAAsB,CAAC;AAGrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAG3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAG5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,eAAe,CAAC;AAG9B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export * from "./components/AiSettingsButton";
|
|
|
22
22
|
export * from "./components/AiStatusButton";
|
|
23
23
|
// Utils
|
|
24
24
|
export * from "./utils/modelManagement";
|
|
25
|
+
export * from "./utils/cache";
|
|
25
26
|
// Examples
|
|
26
27
|
export * from "./examples/AiImageGenerator";
|
|
27
28
|
export * from "./examples/AiPromptPanelAdvanced";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple cache and rate limiting utilities for API calls
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get cached data if valid
|
|
6
|
+
*/
|
|
7
|
+
export declare function getCached<T>(key: string, maxAgeMs?: number): T | null;
|
|
8
|
+
/**
|
|
9
|
+
* Set cached data
|
|
10
|
+
*/
|
|
11
|
+
export declare function setCache<T>(key: string, data: T): void;
|
|
12
|
+
/**
|
|
13
|
+
* Clear cache for a specific key or all cache
|
|
14
|
+
*/
|
|
15
|
+
export declare function clearCache(key?: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Check if rate limit is exceeded
|
|
18
|
+
* @param key - Unique identifier for the rate limit
|
|
19
|
+
* @param maxCalls - Maximum number of calls allowed
|
|
20
|
+
* @param windowMs - Time window in milliseconds
|
|
21
|
+
* @returns true if rate limit is exceeded
|
|
22
|
+
*/
|
|
23
|
+
export declare function isRateLimited(key: string, maxCalls?: number, windowMs?: number): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Get remaining time before rate limit reset
|
|
26
|
+
*/
|
|
27
|
+
export declare function getRateLimitResetTime(key: string): number;
|
|
28
|
+
/**
|
|
29
|
+
* Reset rate limit for a key
|
|
30
|
+
*/
|
|
31
|
+
export declare function resetRateLimit(key?: string): void;
|
|
32
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAeH;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAc,GAAG,CAAC,GAAG,IAAI,CAW5E;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAKtD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAM7C;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAW,EACrB,QAAQ,GAAE,MAAc,GACvB,OAAO,CAuBT;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAMjD"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple cache and rate limiting utilities for API calls
|
|
3
|
+
*/
|
|
4
|
+
const cache = new Map();
|
|
5
|
+
const rateLimits = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Get cached data if valid
|
|
8
|
+
*/
|
|
9
|
+
export function getCached(key, maxAgeMs = 60000) {
|
|
10
|
+
const entry = cache.get(key);
|
|
11
|
+
if (!entry)
|
|
12
|
+
return null;
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
if (now - entry.timestamp > maxAgeMs) {
|
|
15
|
+
cache.delete(key);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return entry.data;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Set cached data
|
|
22
|
+
*/
|
|
23
|
+
export function setCache(key, data) {
|
|
24
|
+
cache.set(key, {
|
|
25
|
+
data,
|
|
26
|
+
timestamp: Date.now(),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Clear cache for a specific key or all cache
|
|
31
|
+
*/
|
|
32
|
+
export function clearCache(key) {
|
|
33
|
+
if (key) {
|
|
34
|
+
cache.delete(key);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
cache.clear();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check if rate limit is exceeded
|
|
42
|
+
* @param key - Unique identifier for the rate limit
|
|
43
|
+
* @param maxCalls - Maximum number of calls allowed
|
|
44
|
+
* @param windowMs - Time window in milliseconds
|
|
45
|
+
* @returns true if rate limit is exceeded
|
|
46
|
+
*/
|
|
47
|
+
export function isRateLimited(key, maxCalls = 10, windowMs = 60000) {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const entry = rateLimits.get(key);
|
|
50
|
+
if (!entry || now > entry.resetAt) {
|
|
51
|
+
// No entry or expired, create new
|
|
52
|
+
rateLimits.set(key, {
|
|
53
|
+
count: 1,
|
|
54
|
+
resetAt: now + windowMs,
|
|
55
|
+
});
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (entry.count >= maxCalls) {
|
|
59
|
+
console.warn(`[Rate Limit] Exceeded ${maxCalls} calls for "${key}". Reset at ${new Date(entry.resetAt).toLocaleTimeString()}`);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
// Increment counter
|
|
63
|
+
entry.count++;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get remaining time before rate limit reset
|
|
68
|
+
*/
|
|
69
|
+
export function getRateLimitResetTime(key) {
|
|
70
|
+
const entry = rateLimits.get(key);
|
|
71
|
+
if (!entry)
|
|
72
|
+
return 0;
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
return Math.max(0, entry.resetAt - now);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Reset rate limit for a key
|
|
78
|
+
*/
|
|
79
|
+
export function resetRateLimit(key) {
|
|
80
|
+
if (key) {
|
|
81
|
+
rateLimits.delete(key);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
rateLimits.clear();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modelManagement.d.ts","sourceRoot":"","sources":["../../src/utils/modelManagement.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,OAAO,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"modelManagement.d.ts","sourceRoot":"","sources":["../../src/utils/modelManagement.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,OAAO,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CACR,KAAK,CAAC;IACJ,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CACH,CA6CA;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,MAAM,EAAE,CAAC,CA6CnB"}
|
|
@@ -13,7 +13,13 @@ export async function toggleUserModel(modelId, isActive, options = {}) {
|
|
|
13
13
|
if (apiKey) {
|
|
14
14
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
15
15
|
}
|
|
16
|
-
const
|
|
16
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
17
|
+
const endpoint = isPublicApi
|
|
18
|
+
? `${baseUrl}/ai/user/models/toggle`
|
|
19
|
+
: baseUrl
|
|
20
|
+
? `${baseUrl}/auth/ai-models-toggle`
|
|
21
|
+
: `/api/ai/auth/ai-models-toggle`;
|
|
22
|
+
const response = await fetch(endpoint, {
|
|
17
23
|
method: "PUT",
|
|
18
24
|
headers,
|
|
19
25
|
body: JSON.stringify({
|
|
@@ -31,15 +37,20 @@ export async function toggleUserModel(modelId, isActive, options = {}) {
|
|
|
31
37
|
*/
|
|
32
38
|
export async function getAvailableModels(options = {}) {
|
|
33
39
|
const { apiKey, baseUrl = "" } = options;
|
|
34
|
-
// Déterminer
|
|
40
|
+
// Déterminer le contexte
|
|
35
41
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
42
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
36
43
|
const endpoint = isExternalProxy
|
|
37
|
-
? `${baseUrl}/ai/
|
|
38
|
-
:
|
|
39
|
-
|
|
44
|
+
? `${baseUrl}/ai/models/available` // Proxy routes to public API
|
|
45
|
+
: isPublicApi
|
|
46
|
+
? `${baseUrl}/ai/models/available` // → /api/public/v1/ai/models/available
|
|
47
|
+
: baseUrl
|
|
48
|
+
? `${baseUrl}/auth/ai-models-available` // → /api/ai/auth/ai-models-available
|
|
49
|
+
: `/api/ai/auth/ai-models-available`; // fallback
|
|
50
|
+
console.log("[getAvailableModels] isExternalProxy:", isExternalProxy, "isPublicApi:", isPublicApi, "endpoint:", endpoint);
|
|
40
51
|
const headers = {};
|
|
41
|
-
// Ajouter la clé API
|
|
42
|
-
if (
|
|
52
|
+
// Ajouter la clé API pour les appels publics directs
|
|
53
|
+
if (isPublicApi && apiKey) {
|
|
43
54
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
44
55
|
}
|
|
45
56
|
const response = await fetch(endpoint, {
|
|
@@ -58,15 +69,20 @@ export async function getAvailableModels(options = {}) {
|
|
|
58
69
|
*/
|
|
59
70
|
export async function getUserModels(options = {}) {
|
|
60
71
|
const { apiKey, baseUrl = "" } = options;
|
|
61
|
-
// Déterminer
|
|
72
|
+
// Déterminer le contexte
|
|
62
73
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
74
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
63
75
|
const endpoint = isExternalProxy
|
|
64
|
-
? `${baseUrl}/ai/
|
|
65
|
-
:
|
|
66
|
-
|
|
76
|
+
? `${baseUrl}/ai/user/models` // Proxy routes to public API
|
|
77
|
+
: isPublicApi
|
|
78
|
+
? `${baseUrl}/ai/user/models` // → /api/public/v1/ai/user/models
|
|
79
|
+
: baseUrl
|
|
80
|
+
? `${baseUrl}/auth/user-models` // → /api/ai/auth/user-models
|
|
81
|
+
: `/api/ai/auth/user-models`; // fallback
|
|
82
|
+
console.log("[getUserModels] isExternalProxy:", isExternalProxy, "isPublicApi:", isPublicApi, "endpoint:", endpoint);
|
|
67
83
|
const headers = {};
|
|
68
|
-
// Ajouter la clé API
|
|
69
|
-
if (
|
|
84
|
+
// Ajouter la clé API pour les appels publics directs
|
|
85
|
+
if (isPublicApi && apiKey) {
|
|
70
86
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
71
87
|
}
|
|
72
88
|
const response = await fetch(endpoint, {
|
package/package.json
CHANGED
package/src/hooks/useAiClient.ts
CHANGED
|
@@ -16,6 +16,15 @@ export function useAiClient(options?: UseAiClientOptions) {
|
|
|
16
16
|
const baseUrl = options?.baseUrl || context.baseUrl;
|
|
17
17
|
const apiKeyId = options?.apiKeyId || context.apiKeyId;
|
|
18
18
|
|
|
19
|
+
console.log("[useAiClient] Creating client with:", {
|
|
20
|
+
contextBaseUrl: context.baseUrl,
|
|
21
|
+
contextApiKeyId: context.apiKeyId,
|
|
22
|
+
optionsBaseUrl: options?.baseUrl,
|
|
23
|
+
optionsApiKeyId: options?.apiKeyId,
|
|
24
|
+
finalBaseUrl: baseUrl,
|
|
25
|
+
finalApiKeyId: apiKeyId,
|
|
26
|
+
});
|
|
27
|
+
|
|
19
28
|
return createClient({ baseUrl, apiKeyId });
|
|
20
29
|
}, [options?.baseUrl, options?.apiKeyId, context.baseUrl, context.apiKeyId]);
|
|
21
30
|
|
|
@@ -6,6 +6,13 @@ import {
|
|
|
6
6
|
getUserModels,
|
|
7
7
|
type ModelToggleOptions,
|
|
8
8
|
} from "../utils/modelManagement";
|
|
9
|
+
import {
|
|
10
|
+
getCached,
|
|
11
|
+
setCache,
|
|
12
|
+
clearCache,
|
|
13
|
+
isRateLimited,
|
|
14
|
+
getRateLimitResetTime,
|
|
15
|
+
} from "../utils/cache";
|
|
9
16
|
|
|
10
17
|
export interface AIModel {
|
|
11
18
|
id: string;
|
|
@@ -67,6 +74,25 @@ export function useModelManagement(
|
|
|
67
74
|
setLoading(true);
|
|
68
75
|
setError(null);
|
|
69
76
|
|
|
77
|
+
// Check cache first (5 minutes TTL for models)
|
|
78
|
+
const cacheKey = `models_${category || "all"}`;
|
|
79
|
+
const cached = getCached<AIModel[]>(cacheKey, 300000);
|
|
80
|
+
if (cached) {
|
|
81
|
+
console.log("[useModelManagement] Using cached models");
|
|
82
|
+
setAvailableModels(cached);
|
|
83
|
+
setLoading(false);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check rate limit (max 5 calls per minute)
|
|
88
|
+
if (isRateLimited("models_fetch", 5, 60000)) {
|
|
89
|
+
const resetTime = getRateLimitResetTime("models_fetch");
|
|
90
|
+
const resetSeconds = Math.ceil(resetTime / 1000);
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Rate limit exceeded. Please wait ${resetSeconds}s before trying again.`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
70
96
|
console.log(
|
|
71
97
|
"[useModelManagement] Fetching available models with options:",
|
|
72
98
|
{
|
|
@@ -85,6 +111,8 @@ export function useModelManagement(
|
|
|
85
111
|
: models;
|
|
86
112
|
|
|
87
113
|
setAvailableModels(filteredModels);
|
|
114
|
+
// Cache the results
|
|
115
|
+
setCache(cacheKey, filteredModels);
|
|
88
116
|
console.log(
|
|
89
117
|
"[useModelManagement] Set filtered models:",
|
|
90
118
|
filteredModels.length
|
|
@@ -187,7 +215,8 @@ export function useModelManagement(
|
|
|
187
215
|
// Auto-fetch au mount - si autoFetch ET (apiKey OU baseUrl avec proxy externe)
|
|
188
216
|
useEffect(() => {
|
|
189
217
|
const isExternalProxy =
|
|
190
|
-
effectiveOptions.baseUrl &&
|
|
218
|
+
effectiveOptions.baseUrl &&
|
|
219
|
+
effectiveOptions.baseUrl.includes("/api/lastbrain");
|
|
191
220
|
|
|
192
221
|
console.log("[useModelManagement] useEffect triggered:", {
|
|
193
222
|
autoFetch,
|
package/src/hooks/usePrompts.ts
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback } from "react";
|
|
4
4
|
import { useAiContext } from "../context/AiProvider";
|
|
5
|
+
import {
|
|
6
|
+
getCached,
|
|
7
|
+
setCache,
|
|
8
|
+
clearCache,
|
|
9
|
+
isRateLimited,
|
|
10
|
+
getRateLimitResetTime,
|
|
11
|
+
} from "../utils/cache";
|
|
5
12
|
|
|
6
13
|
export interface Prompt {
|
|
7
14
|
id: string;
|
|
@@ -58,34 +65,79 @@ export function usePrompts(): UsePromptsReturn {
|
|
|
58
65
|
setLoading(true);
|
|
59
66
|
setError(null);
|
|
60
67
|
|
|
68
|
+
// Generate cache key based on options
|
|
69
|
+
const cacheKey = `prompts_${JSON.stringify(options || {})}`;
|
|
70
|
+
|
|
71
|
+
// Check cache first (60 seconds TTL)
|
|
72
|
+
const cached = getCached<Prompt[] | PublicPrompt[]>(cacheKey, 60000);
|
|
73
|
+
if (cached) {
|
|
74
|
+
console.log("[usePrompts] Using cached data");
|
|
75
|
+
setPrompts(cached);
|
|
76
|
+
setLoading(false);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check rate limit (max 10 calls per minute)
|
|
81
|
+
if (isRateLimited("prompts_fetch", 10, 60000)) {
|
|
82
|
+
const resetTime = getRateLimitResetTime("prompts_fetch");
|
|
83
|
+
const resetSeconds = Math.ceil(resetTime / 1000);
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Rate limit exceeded. Please wait ${resetSeconds}s before trying again.`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
61
89
|
const params = new URLSearchParams();
|
|
62
90
|
if (options?.type) params.append("type", options.type);
|
|
63
91
|
if (options?.favorite !== undefined)
|
|
64
92
|
params.append("favorite", String(options.favorite));
|
|
65
93
|
|
|
66
|
-
// Déterminer
|
|
94
|
+
// Déterminer le contexte:
|
|
95
|
+
// - isExternalProxy: app externe via proxy /api/lastbrain
|
|
96
|
+
// - isPublicApi: app consommatrice avec API key via /api/public/v1
|
|
97
|
+
// - isAuthInternal: app interne avec session via /api/ai/auth
|
|
67
98
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
99
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
68
100
|
|
|
69
101
|
// Use public route without auth if public=true, otherwise use auth route
|
|
70
102
|
let endpoint: string;
|
|
71
103
|
if (options?.public) {
|
|
72
104
|
endpoint = isExternalProxy
|
|
73
105
|
? `${baseUrl}/ai/public/prompts?${params}`
|
|
74
|
-
:
|
|
106
|
+
: isPublicApi
|
|
107
|
+
? `${baseUrl}/prompts?${params}`
|
|
108
|
+
: baseUrl
|
|
109
|
+
? `${baseUrl}/public/prompts?${params}`
|
|
110
|
+
: `/api/ai/public/prompts?${params}`;
|
|
75
111
|
} else {
|
|
76
112
|
endpoint = isExternalProxy
|
|
77
113
|
? `${baseUrl}/ai/auth/prompts?${params}`
|
|
78
|
-
:
|
|
114
|
+
: isPublicApi
|
|
115
|
+
? `${baseUrl}/prompts?${params}`
|
|
116
|
+
: baseUrl
|
|
117
|
+
? `${baseUrl}/auth/prompts?${params}`
|
|
118
|
+
: `/api/ai/auth/prompts?${params}`;
|
|
79
119
|
}
|
|
80
120
|
|
|
81
121
|
console.log("[usePrompts] Fetching prompts from:", endpoint);
|
|
82
122
|
|
|
83
|
-
const
|
|
123
|
+
const headers: HeadersInit = {};
|
|
124
|
+
// Ajouter l'API key pour les appels publics directs (pas de proxy externe ni auth interne)
|
|
125
|
+
if (isPublicApi && apiKeyId) {
|
|
126
|
+
headers["Authorization"] = `Bearer ${apiKeyId}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const response = await fetch(endpoint, {
|
|
130
|
+
headers,
|
|
131
|
+
credentials: isExternalProxy ? "include" : "same-origin",
|
|
132
|
+
});
|
|
84
133
|
|
|
85
134
|
const data = await response.json();
|
|
86
135
|
|
|
87
136
|
if (response.ok) {
|
|
88
|
-
|
|
137
|
+
const promptsData = data.prompts || [];
|
|
138
|
+
setPrompts(promptsData);
|
|
139
|
+
// Cache the results
|
|
140
|
+
setCache(cacheKey, promptsData);
|
|
89
141
|
} else {
|
|
90
142
|
setError(data.error || "Failed to fetch prompts");
|
|
91
143
|
}
|
|
@@ -105,18 +157,26 @@ export function usePrompts(): UsePromptsReturn {
|
|
|
105
157
|
|
|
106
158
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
107
159
|
const endpoint = isExternalProxy
|
|
108
|
-
? `${baseUrl}/
|
|
160
|
+
? `${baseUrl}/prompts` // Proxy handles auth routes
|
|
109
161
|
: "/api/ai/auth/prompts";
|
|
110
162
|
|
|
163
|
+
const headers: HeadersInit = { "Content-Type": "application/json" };
|
|
164
|
+
if (!isExternalProxy && apiKeyId) {
|
|
165
|
+
headers["Authorization"] = `Bearer ${apiKeyId}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
111
168
|
const response = await fetch(endpoint, {
|
|
112
169
|
method: "POST",
|
|
113
|
-
headers
|
|
170
|
+
headers,
|
|
114
171
|
body: JSON.stringify(data),
|
|
172
|
+
credentials: isExternalProxy ? "include" : "same-origin",
|
|
115
173
|
});
|
|
116
174
|
|
|
117
175
|
const result = await response.json();
|
|
118
176
|
|
|
119
177
|
if (response.ok) {
|
|
178
|
+
// Invalidate cache after creating a prompt
|
|
179
|
+
clearCache();
|
|
120
180
|
return result.prompt;
|
|
121
181
|
} else {
|
|
122
182
|
setError(result.error || "Failed to create prompt");
|
|
@@ -127,7 +187,7 @@ export function usePrompts(): UsePromptsReturn {
|
|
|
127
187
|
return null;
|
|
128
188
|
}
|
|
129
189
|
},
|
|
130
|
-
[baseUrl]
|
|
190
|
+
[baseUrl, apiKeyId]
|
|
131
191
|
);
|
|
132
192
|
|
|
133
193
|
const updatePrompt = useCallback(
|
|
@@ -136,19 +196,33 @@ export function usePrompts(): UsePromptsReturn {
|
|
|
136
196
|
setError(null);
|
|
137
197
|
|
|
138
198
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
199
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
200
|
+
|
|
139
201
|
const endpoint = isExternalProxy
|
|
140
202
|
? `${baseUrl}/ai/auth/prompts`
|
|
141
|
-
:
|
|
203
|
+
: isPublicApi
|
|
204
|
+
? `${baseUrl}/prompts`
|
|
205
|
+
: baseUrl
|
|
206
|
+
? `${baseUrl}/auth/prompts`
|
|
207
|
+
: "/api/ai/auth/prompts";
|
|
208
|
+
|
|
209
|
+
const headers: HeadersInit = { "Content-Type": "application/json" };
|
|
210
|
+
if (isPublicApi && apiKeyId) {
|
|
211
|
+
headers["Authorization"] = `Bearer ${apiKeyId}`;
|
|
212
|
+
}
|
|
142
213
|
|
|
143
214
|
const response = await fetch(endpoint, {
|
|
144
215
|
method: "PUT",
|
|
145
|
-
headers
|
|
216
|
+
headers,
|
|
146
217
|
body: JSON.stringify({ id, ...data }),
|
|
218
|
+
credentials: isExternalProxy ? "include" : "same-origin",
|
|
147
219
|
});
|
|
148
220
|
|
|
149
221
|
const result = await response.json();
|
|
150
222
|
|
|
151
223
|
if (response.ok) {
|
|
224
|
+
// Invalidate cache after updating a prompt
|
|
225
|
+
clearCache();
|
|
152
226
|
return result.prompt;
|
|
153
227
|
} else {
|
|
154
228
|
setError(result.error || "Failed to update prompt");
|
|
@@ -159,7 +233,7 @@ export function usePrompts(): UsePromptsReturn {
|
|
|
159
233
|
return null;
|
|
160
234
|
}
|
|
161
235
|
},
|
|
162
|
-
[baseUrl]
|
|
236
|
+
[baseUrl, apiKeyId]
|
|
163
237
|
);
|
|
164
238
|
|
|
165
239
|
const deletePrompt = useCallback(
|
|
@@ -168,17 +242,32 @@ export function usePrompts(): UsePromptsReturn {
|
|
|
168
242
|
setError(null);
|
|
169
243
|
|
|
170
244
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
245
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
246
|
+
|
|
171
247
|
const endpoint = isExternalProxy
|
|
172
|
-
? `${baseUrl}/
|
|
173
|
-
:
|
|
248
|
+
? `${baseUrl}/prompts?id=${id}` // Proxy handles auth routes
|
|
249
|
+
: isPublicApi
|
|
250
|
+
? `${baseUrl}/prompts?id=${id}`
|
|
251
|
+
: baseUrl
|
|
252
|
+
? `${baseUrl}/auth/prompts?id=${id}`
|
|
253
|
+
: `/api/ai/auth/prompts?id=${id}`;
|
|
254
|
+
|
|
255
|
+
const headers: HeadersInit = {};
|
|
256
|
+
if (isPublicApi && apiKeyId) {
|
|
257
|
+
headers["Authorization"] = `Bearer ${apiKeyId}`;
|
|
258
|
+
}
|
|
174
259
|
|
|
175
260
|
const response = await fetch(endpoint, {
|
|
176
261
|
method: "DELETE",
|
|
262
|
+
headers,
|
|
263
|
+
credentials: isExternalProxy ? "include" : "same-origin",
|
|
177
264
|
});
|
|
178
265
|
|
|
179
266
|
const result = await response.json();
|
|
180
267
|
|
|
181
268
|
if (response.ok) {
|
|
269
|
+
// Invalidate cache after deleting a prompt
|
|
270
|
+
clearCache();
|
|
182
271
|
return true;
|
|
183
272
|
} else {
|
|
184
273
|
setError(result.error || "Failed to delete prompt");
|
|
@@ -189,27 +278,39 @@ export function usePrompts(): UsePromptsReturn {
|
|
|
189
278
|
return false;
|
|
190
279
|
}
|
|
191
280
|
},
|
|
192
|
-
[baseUrl]
|
|
281
|
+
[baseUrl, apiKeyId]
|
|
193
282
|
);
|
|
194
283
|
|
|
195
284
|
const incrementStat = useCallback(
|
|
196
285
|
async (promptId: string, statType: "views" | "used" | "picked") => {
|
|
197
286
|
try {
|
|
198
287
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
288
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
289
|
+
|
|
199
290
|
const endpoint = isExternalProxy
|
|
200
|
-
? `${baseUrl}/
|
|
201
|
-
:
|
|
291
|
+
? `${baseUrl}/prompts/stats` // Proxy handles auth routes internally
|
|
292
|
+
: isPublicApi
|
|
293
|
+
? `${baseUrl}/prompts/stats`
|
|
294
|
+
: baseUrl
|
|
295
|
+
? `${baseUrl}/auth/prompts/stats`
|
|
296
|
+
: "/api/ai/auth/prompts/stats";
|
|
297
|
+
|
|
298
|
+
const headers: HeadersInit = { "Content-Type": "application/json" };
|
|
299
|
+
if (isPublicApi && apiKeyId) {
|
|
300
|
+
headers["Authorization"] = `Bearer ${apiKeyId}`;
|
|
301
|
+
}
|
|
202
302
|
|
|
203
303
|
await fetch(endpoint, {
|
|
204
304
|
method: "POST",
|
|
205
|
-
headers
|
|
305
|
+
headers,
|
|
206
306
|
body: JSON.stringify({ prompt_id: promptId, stat_type: statType }),
|
|
307
|
+
credentials: isExternalProxy ? "include" : "same-origin",
|
|
207
308
|
});
|
|
208
309
|
} catch (err) {
|
|
209
310
|
// Silent fail for stats
|
|
210
311
|
}
|
|
211
312
|
},
|
|
212
|
-
[baseUrl]
|
|
313
|
+
[baseUrl, apiKeyId]
|
|
213
314
|
);
|
|
214
315
|
|
|
215
316
|
return {
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple cache and rate limiting utilities for API calls
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
interface CacheEntry<T> {
|
|
6
|
+
data: T;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface RateLimitEntry {
|
|
11
|
+
count: number;
|
|
12
|
+
resetAt: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const cache = new Map<string, CacheEntry<any>>();
|
|
16
|
+
const rateLimits = new Map<string, RateLimitEntry>();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get cached data if valid
|
|
20
|
+
*/
|
|
21
|
+
export function getCached<T>(key: string, maxAgeMs: number = 60000): T | null {
|
|
22
|
+
const entry = cache.get(key);
|
|
23
|
+
if (!entry) return null;
|
|
24
|
+
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
if (now - entry.timestamp > maxAgeMs) {
|
|
27
|
+
cache.delete(key);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return entry.data;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Set cached data
|
|
36
|
+
*/
|
|
37
|
+
export function setCache<T>(key: string, data: T): void {
|
|
38
|
+
cache.set(key, {
|
|
39
|
+
data,
|
|
40
|
+
timestamp: Date.now(),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Clear cache for a specific key or all cache
|
|
46
|
+
*/
|
|
47
|
+
export function clearCache(key?: string): void {
|
|
48
|
+
if (key) {
|
|
49
|
+
cache.delete(key);
|
|
50
|
+
} else {
|
|
51
|
+
cache.clear();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if rate limit is exceeded
|
|
57
|
+
* @param key - Unique identifier for the rate limit
|
|
58
|
+
* @param maxCalls - Maximum number of calls allowed
|
|
59
|
+
* @param windowMs - Time window in milliseconds
|
|
60
|
+
* @returns true if rate limit is exceeded
|
|
61
|
+
*/
|
|
62
|
+
export function isRateLimited(
|
|
63
|
+
key: string,
|
|
64
|
+
maxCalls: number = 10,
|
|
65
|
+
windowMs: number = 60000
|
|
66
|
+
): boolean {
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const entry = rateLimits.get(key);
|
|
69
|
+
|
|
70
|
+
if (!entry || now > entry.resetAt) {
|
|
71
|
+
// No entry or expired, create new
|
|
72
|
+
rateLimits.set(key, {
|
|
73
|
+
count: 1,
|
|
74
|
+
resetAt: now + windowMs,
|
|
75
|
+
});
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (entry.count >= maxCalls) {
|
|
80
|
+
console.warn(
|
|
81
|
+
`[Rate Limit] Exceeded ${maxCalls} calls for "${key}". Reset at ${new Date(entry.resetAt).toLocaleTimeString()}`
|
|
82
|
+
);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Increment counter
|
|
87
|
+
entry.count++;
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get remaining time before rate limit reset
|
|
93
|
+
*/
|
|
94
|
+
export function getRateLimitResetTime(key: string): number {
|
|
95
|
+
const entry = rateLimits.get(key);
|
|
96
|
+
if (!entry) return 0;
|
|
97
|
+
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
return Math.max(0, entry.resetAt - now);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Reset rate limit for a key
|
|
104
|
+
*/
|
|
105
|
+
export function resetRateLimit(key?: string): void {
|
|
106
|
+
if (key) {
|
|
107
|
+
rateLimits.delete(key);
|
|
108
|
+
} else {
|
|
109
|
+
rateLimits.clear();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -26,17 +26,22 @@ export async function toggleUserModel(
|
|
|
26
26
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
30
|
+
|
|
31
|
+
const endpoint = isPublicApi
|
|
32
|
+
? `${baseUrl}/ai/user/models/toggle`
|
|
33
|
+
: baseUrl
|
|
34
|
+
? `${baseUrl}/auth/ai-models-toggle`
|
|
35
|
+
: `/api/ai/auth/ai-models-toggle`;
|
|
36
|
+
|
|
37
|
+
const response = await fetch(endpoint, {
|
|
38
|
+
method: "PUT",
|
|
39
|
+
headers,
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
model_id: modelId,
|
|
42
|
+
is_active: isActive,
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
40
45
|
|
|
41
46
|
if (!response.ok) {
|
|
42
47
|
const error = await response.text();
|
|
@@ -63,23 +68,31 @@ export async function getAvailableModels(
|
|
|
63
68
|
> {
|
|
64
69
|
const { apiKey, baseUrl = "" } = options;
|
|
65
70
|
|
|
66
|
-
// Déterminer
|
|
71
|
+
// Déterminer le contexte
|
|
67
72
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
73
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
74
|
+
|
|
68
75
|
const endpoint = isExternalProxy
|
|
69
|
-
? `${baseUrl}/ai/
|
|
70
|
-
:
|
|
76
|
+
? `${baseUrl}/ai/models/available` // Proxy routes to public API
|
|
77
|
+
: isPublicApi
|
|
78
|
+
? `${baseUrl}/ai/models/available` // → /api/public/v1/ai/models/available
|
|
79
|
+
: baseUrl
|
|
80
|
+
? `${baseUrl}/auth/ai-models-available` // → /api/ai/auth/ai-models-available
|
|
81
|
+
: `/api/ai/auth/ai-models-available`; // fallback
|
|
71
82
|
|
|
72
83
|
console.log(
|
|
73
84
|
"[getAvailableModels] isExternalProxy:",
|
|
74
85
|
isExternalProxy,
|
|
86
|
+
"isPublicApi:",
|
|
87
|
+
isPublicApi,
|
|
75
88
|
"endpoint:",
|
|
76
89
|
endpoint
|
|
77
90
|
);
|
|
78
91
|
|
|
79
92
|
const headers: Record<string, string> = {};
|
|
80
93
|
|
|
81
|
-
// Ajouter la clé API
|
|
82
|
-
if (
|
|
94
|
+
// Ajouter la clé API pour les appels publics directs
|
|
95
|
+
if (isPublicApi && apiKey) {
|
|
83
96
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
84
97
|
}
|
|
85
98
|
|
|
@@ -107,23 +120,31 @@ export async function getUserModels(
|
|
|
107
120
|
): Promise<string[]> {
|
|
108
121
|
const { apiKey, baseUrl = "" } = options;
|
|
109
122
|
|
|
110
|
-
// Déterminer
|
|
123
|
+
// Déterminer le contexte
|
|
111
124
|
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
125
|
+
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
126
|
+
|
|
112
127
|
const endpoint = isExternalProxy
|
|
113
|
-
? `${baseUrl}/ai/
|
|
114
|
-
:
|
|
128
|
+
? `${baseUrl}/ai/user/models` // Proxy routes to public API
|
|
129
|
+
: isPublicApi
|
|
130
|
+
? `${baseUrl}/ai/user/models` // → /api/public/v1/ai/user/models
|
|
131
|
+
: baseUrl
|
|
132
|
+
? `${baseUrl}/auth/user-models` // → /api/ai/auth/user-models
|
|
133
|
+
: `/api/ai/auth/user-models`; // fallback
|
|
115
134
|
|
|
116
135
|
console.log(
|
|
117
136
|
"[getUserModels] isExternalProxy:",
|
|
118
137
|
isExternalProxy,
|
|
138
|
+
"isPublicApi:",
|
|
139
|
+
isPublicApi,
|
|
119
140
|
"endpoint:",
|
|
120
141
|
endpoint
|
|
121
142
|
);
|
|
122
143
|
|
|
123
144
|
const headers: Record<string, string> = {};
|
|
124
145
|
|
|
125
|
-
// Ajouter la clé API
|
|
126
|
-
if (
|
|
146
|
+
// Ajouter la clé API pour les appels publics directs
|
|
147
|
+
if (isPublicApi && apiKey) {
|
|
127
148
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
128
149
|
}
|
|
129
150
|
|