@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.
@@ -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;;;;;;EAWvD"}
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;AAElC,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,CAuL1B"}
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 && effectiveOptions.baseUrl.includes("/api/lastbrain");
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":"AAKA,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,CAgL7C"}
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"}
@@ -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 si on est dans un contexte externe (proxy nécessaire)
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
- : `/api/ai/public/prompts?${params}`;
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
- : `/api/ai/auth/prompts?${params}`;
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 response = await fetch(endpoint);
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
- setPrompts(data.prompts || []);
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}/ai/auth/prompts`
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: { "Content-Type": "application/json" },
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
- : "/api/ai/auth/prompts";
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: { "Content-Type": "application/json" },
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}/ai/auth/prompts?id=${id}`
107
- : `/api/ai/auth/prompts?id=${id}`;
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}/ai/auth/prompts/stats`
130
- : "/api/ai/auth/prompts/stats";
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: { "Content-Type": "application/json" },
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
@@ -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;AAGxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,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,CA4Bf;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,CAqCA;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,MAAM,EAAE,CAAC,CAqCnB"}
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 response = await fetch(`${baseUrl}/api/public/v1/ai/user/models/toggle`, {
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 si on est dans un contexte externe (proxy nécessaire)
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/auth/ai-models-available` // /api/lastbrain/ai/auth/ai-models-available
38
- : `/api/public/v1/ai/models/available`; // → /api/public/v1/ai/models/available
39
- console.log("[getAvailableModels] isExternalProxy:", isExternalProxy, "endpoint:", endpoint);
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 seulement si pas de proxy externe (appel direct)
42
- if (!isExternalProxy && apiKey) {
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 si on est dans un contexte externe (proxy nécessaire)
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/auth/user-models` // /api/lastbrain/ai/auth/user-models
65
- : `/api/public/v1/ai/user/models`; // → /api/public/v1/ai/user/models
66
- console.log("[getUserModels] isExternalProxy:", isExternalProxy, "endpoint:", endpoint);
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 seulement si pas de proxy externe (appel direct)
69
- if (!isExternalProxy && apiKey) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/ai-ui-react",
3
- "version": "1.0.20",
3
+ "version": "1.0.23",
4
4
  "description": "Headless React components for LastBrain AI UI Kit",
5
5
  "private": false,
6
6
  "type": "module",
@@ -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 && effectiveOptions.baseUrl.includes("/api/lastbrain");
218
+ effectiveOptions.baseUrl &&
219
+ effectiveOptions.baseUrl.includes("/api/lastbrain");
191
220
 
192
221
  console.log("[useModelManagement] useEffect triggered:", {
193
222
  autoFetch,
@@ -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 si on est dans un contexte externe (proxy nécessaire)
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
- : `/api/ai/public/prompts?${params}`;
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
- : `/api/ai/auth/prompts?${params}`;
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 response = await fetch(endpoint);
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
- setPrompts(data.prompts || []);
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}/ai/auth/prompts`
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: { "Content-Type": "application/json" },
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
- : "/api/ai/auth/prompts";
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: { "Content-Type": "application/json" },
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}/ai/auth/prompts?id=${id}`
173
- : `/api/ai/auth/prompts?id=${id}`;
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}/ai/auth/prompts/stats`
201
- : "/api/ai/auth/prompts/stats";
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: { "Content-Type": "application/json" },
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
@@ -26,6 +26,7 @@ export * from "./components/AiStatusButton";
26
26
 
27
27
  // Utils
28
28
  export * from "./utils/modelManagement";
29
+ export * from "./utils/cache";
29
30
 
30
31
  // Examples
31
32
  export * from "./examples/AiImageGenerator";
@@ -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 response = await fetch(
30
- `${baseUrl}/api/public/v1/ai/user/models/toggle`,
31
- {
32
- method: "PUT",
33
- headers,
34
- body: JSON.stringify({
35
- model_id: modelId,
36
- is_active: isActive,
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 si on est dans un contexte externe (proxy nécessaire)
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/auth/ai-models-available` // /api/lastbrain/ai/auth/ai-models-available
70
- : `/api/public/v1/ai/models/available`; // → /api/public/v1/ai/models/available
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 seulement si pas de proxy externe (appel direct)
82
- if (!isExternalProxy && apiKey) {
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 si on est dans un contexte externe (proxy nécessaire)
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/auth/user-models` // /api/lastbrain/ai/auth/user-models
114
- : `/api/public/v1/ai/user/models`; // → /api/public/v1/ai/user/models
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 seulement si pas de proxy externe (appel direct)
126
- if (!isExternalProxy && apiKey) {
146
+ // Ajouter la clé API pour les appels publics directs
147
+ if (isPublicApi && apiKey) {
127
148
  headers["Authorization"] = `Bearer ${apiKey}`;
128
149
  }
129
150