@semiont/react-ui 0.4.13 → 0.4.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -12
- package/dist/KnowledgeBaseSessionContext-CpYaCbnC.d.mts +174 -0
- package/dist/{PdfAnnotationCanvas.client-CW6SKH2U.mjs → PdfAnnotationCanvas.client-CHDCGQBR.mjs} +3 -3
- package/dist/{chunk-HNZOXH4L.mjs → chunk-OZICDVH7.mjs} +5 -3
- package/dist/chunk-OZICDVH7.mjs.map +1 -0
- package/dist/chunk-R2U7P4TK.mjs +865 -0
- package/dist/chunk-R2U7P4TK.mjs.map +1 -0
- package/dist/{chunk-BQJWOK4C.mjs → chunk-VN5NY4SN.mjs} +9 -8
- package/dist/chunk-VN5NY4SN.mjs.map +1 -0
- package/dist/index.d.mts +147 -171
- package/dist/index.mjs +2215 -1961
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +13 -62
- package/dist/test-utils.mjs +40 -21
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +5 -3
- package/src/components/ProtectedErrorBoundary.tsx +95 -0
- package/src/components/Toolbar.tsx +13 -13
- package/src/components/__tests__/ProtectedErrorBoundary.test.tsx +197 -0
- package/src/components/modals/PermissionDeniedModal.tsx +140 -0
- package/src/components/modals/ReferenceWizardModal.tsx +3 -2
- package/src/components/modals/SessionExpiredModal.tsx +101 -0
- package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +150 -0
- package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +115 -0
- package/src/components/resource/AnnotationHistory.tsx +5 -6
- package/src/components/resource/HistoryEvent.tsx +7 -7
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +33 -34
- package/src/components/resource/__tests__/HistoryEvent.test.tsx +17 -19
- package/src/components/resource/__tests__/event-formatting.test.ts +70 -94
- package/src/components/resource/event-formatting.ts +56 -56
- package/src/components/resource/panels/CollaborationPanel.tsx +9 -1
- package/src/components/resource/panels/ReferenceEntry.tsx +7 -5
- package/src/components/resource/panels/ResourceInfoPanel.tsx +8 -6
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +12 -12
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +1 -0
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +1 -1
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +4 -4
- package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +5 -10
- package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +23 -54
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +6 -6
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +7 -19
- package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +1 -1
- package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +18 -44
- package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +6 -6
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +31 -26
- package/src/styles/patterns/panels-base.css +12 -0
- package/dist/TranslationManager-CudgH3gw.d.mts +0 -107
- package/dist/chunk-BQJWOK4C.mjs.map +0 -1
- package/dist/chunk-HNZOXH4L.mjs.map +0 -1
- package/dist/chunk-OL5UST25.mjs +0 -413
- package/dist/chunk-OL5UST25.mjs.map +0 -1
- /package/dist/{PdfAnnotationCanvas.client-CW6SKH2U.mjs.map → PdfAnnotationCanvas.client-CHDCGQBR.mjs.map} +0 -0
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import {
|
|
3
|
+
en_default
|
|
4
|
+
} from "./chunk-HVMAGUFA.mjs";
|
|
5
|
+
import {
|
|
6
|
+
__glob
|
|
7
|
+
} from "./chunk-D4GAAQMM.mjs";
|
|
8
|
+
|
|
9
|
+
// src/contexts/knowledge-base-session/storage.ts
|
|
10
|
+
var SESSION_PREFIX = "semiont.session.";
|
|
11
|
+
var STORAGE_KEY = "semiont.knowledgeBases";
|
|
12
|
+
var ACTIVE_KEY = "semiont.activeKnowledgeBaseId";
|
|
13
|
+
var REFRESH_BEFORE_EXP_MS = 5 * 60 * 1e3;
|
|
14
|
+
function sessionKey(kbId) {
|
|
15
|
+
return `${SESSION_PREFIX}${kbId}`;
|
|
16
|
+
}
|
|
17
|
+
function getStoredSession(kbId) {
|
|
18
|
+
const raw = localStorage.getItem(sessionKey(kbId));
|
|
19
|
+
if (!raw) return null;
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
if (parsed && typeof parsed.access === "string" && typeof parsed.refresh === "string") {
|
|
23
|
+
return { access: parsed.access, refresh: parsed.refresh };
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
function setStoredSession(kbId, session) {
|
|
30
|
+
localStorage.setItem(sessionKey(kbId), JSON.stringify(session));
|
|
31
|
+
}
|
|
32
|
+
function clearStoredSession(kbId) {
|
|
33
|
+
localStorage.removeItem(sessionKey(kbId));
|
|
34
|
+
}
|
|
35
|
+
function parseJwtExpiry(token) {
|
|
36
|
+
try {
|
|
37
|
+
const parts = token.split(".");
|
|
38
|
+
if (parts.length !== 3 || !parts[1]) return null;
|
|
39
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
40
|
+
if (!payload.exp) return null;
|
|
41
|
+
return new Date(payload.exp * 1e3);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function isJwtExpired(token) {
|
|
47
|
+
const expiry = parseJwtExpiry(token);
|
|
48
|
+
if (!expiry) return true;
|
|
49
|
+
return expiry.getTime() < Date.now();
|
|
50
|
+
}
|
|
51
|
+
function migrateLegacyEntry(entry) {
|
|
52
|
+
if (entry.host !== void 0) return entry;
|
|
53
|
+
try {
|
|
54
|
+
const url = new URL(entry.backendUrl);
|
|
55
|
+
return {
|
|
56
|
+
id: entry.id,
|
|
57
|
+
label: entry.label,
|
|
58
|
+
host: url.hostname,
|
|
59
|
+
port: parseInt(url.port, 10) || (url.protocol === "https:" ? 443 : 80),
|
|
60
|
+
protocol: url.protocol === "https:" ? "https" : "http",
|
|
61
|
+
email: ""
|
|
62
|
+
};
|
|
63
|
+
} catch {
|
|
64
|
+
return {
|
|
65
|
+
id: entry.id,
|
|
66
|
+
label: entry.label || "Unknown",
|
|
67
|
+
host: "localhost",
|
|
68
|
+
port: 4e3,
|
|
69
|
+
protocol: "http",
|
|
70
|
+
email: ""
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function loadKnowledgeBases() {
|
|
75
|
+
try {
|
|
76
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
77
|
+
if (!raw) return [];
|
|
78
|
+
const entries = JSON.parse(raw);
|
|
79
|
+
return entries.map(migrateLegacyEntry);
|
|
80
|
+
} catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function saveKnowledgeBases(knowledgeBases) {
|
|
85
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(knowledgeBases));
|
|
86
|
+
}
|
|
87
|
+
function defaultProtocol(host) {
|
|
88
|
+
return host === "localhost" || host === "127.0.0.1" ? "http" : "https";
|
|
89
|
+
}
|
|
90
|
+
function kbBackendUrl(kb) {
|
|
91
|
+
return `${kb.protocol}://${kb.host}:${kb.port}`;
|
|
92
|
+
}
|
|
93
|
+
function getKbSessionStatus(kbId) {
|
|
94
|
+
const stored = getStoredSession(kbId);
|
|
95
|
+
if (!stored) return "signed-out";
|
|
96
|
+
return isJwtExpired(stored.access) ? "expired" : "authenticated";
|
|
97
|
+
}
|
|
98
|
+
function generateKbId() {
|
|
99
|
+
return crypto.randomUUID();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/contexts/knowledge-base-session/notify.ts
|
|
103
|
+
var activeOnSessionExpired = null;
|
|
104
|
+
var activeOnPermissionDenied = null;
|
|
105
|
+
function notifySessionExpired(message) {
|
|
106
|
+
activeOnSessionExpired?.(message);
|
|
107
|
+
}
|
|
108
|
+
function notifyPermissionDenied(message) {
|
|
109
|
+
activeOnPermissionDenied?.(message);
|
|
110
|
+
}
|
|
111
|
+
function registerAuthNotifyHandlers(handlers) {
|
|
112
|
+
activeOnSessionExpired = handlers.onSessionExpired;
|
|
113
|
+
activeOnPermissionDenied = handlers.onPermissionDenied;
|
|
114
|
+
return () => {
|
|
115
|
+
activeOnSessionExpired = null;
|
|
116
|
+
activeOnPermissionDenied = null;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/contexts/KnowledgeBaseSessionContext.tsx
|
|
121
|
+
import {
|
|
122
|
+
createContext,
|
|
123
|
+
useCallback,
|
|
124
|
+
useContext,
|
|
125
|
+
useEffect,
|
|
126
|
+
useMemo,
|
|
127
|
+
useRef,
|
|
128
|
+
useState
|
|
129
|
+
} from "react";
|
|
130
|
+
import { SemiontApiClient as SemiontApiClient2, APIError } from "@semiont/api-client";
|
|
131
|
+
import { baseUrl as baseUrl2, EventBus as EventBus2, accessToken } from "@semiont/core";
|
|
132
|
+
|
|
133
|
+
// src/contexts/knowledge-base-session/refresh.ts
|
|
134
|
+
import { SemiontApiClient } from "@semiont/api-client";
|
|
135
|
+
import { baseUrl, EventBus, refreshToken as makeRefreshToken } from "@semiont/core";
|
|
136
|
+
var inFlightRefreshes = /* @__PURE__ */ new Map();
|
|
137
|
+
async function performRefresh(kb) {
|
|
138
|
+
const existing = inFlightRefreshes.get(kb.id);
|
|
139
|
+
if (existing) return existing;
|
|
140
|
+
const promise = (async () => {
|
|
141
|
+
const stored = getStoredSession(kb.id);
|
|
142
|
+
if (!stored) return null;
|
|
143
|
+
const client = new SemiontApiClient({
|
|
144
|
+
baseUrl: baseUrl(kbBackendUrl(kb)),
|
|
145
|
+
eventBus: new EventBus()
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
const response = await client.refreshToken(makeRefreshToken(stored.refresh));
|
|
149
|
+
const newAccess = response.access_token;
|
|
150
|
+
if (!newAccess) return null;
|
|
151
|
+
setStoredSession(kb.id, { access: newAccess, refresh: stored.refresh });
|
|
152
|
+
return newAccess;
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
})();
|
|
157
|
+
inFlightRefreshes.set(kb.id, promise);
|
|
158
|
+
try {
|
|
159
|
+
return await promise;
|
|
160
|
+
} finally {
|
|
161
|
+
inFlightRefreshes.delete(kb.id);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/contexts/KnowledgeBaseSessionContext.tsx
|
|
166
|
+
import { jsx } from "react/jsx-runtime";
|
|
167
|
+
var KnowledgeBaseSessionContext = createContext(void 0);
|
|
168
|
+
function KnowledgeBaseSessionProvider({ children }) {
|
|
169
|
+
const [knowledgeBases, setKnowledgeBases] = useState(() => loadKnowledgeBases());
|
|
170
|
+
const [activeKnowledgeBaseId, setActiveKnowledgeBaseId] = useState(() => {
|
|
171
|
+
const saved = localStorage.getItem(ACTIVE_KEY);
|
|
172
|
+
const loaded = loadKnowledgeBases();
|
|
173
|
+
if (saved && loaded.some((kb) => kb.id === saved)) return saved;
|
|
174
|
+
return loaded[0]?.id ?? null;
|
|
175
|
+
});
|
|
176
|
+
const [session, setSession] = useState(null);
|
|
177
|
+
const [isLoading, setIsLoading] = useState(() => {
|
|
178
|
+
const id = activeKnowledgeBaseId;
|
|
179
|
+
if (!id) return false;
|
|
180
|
+
const stored = getStoredSession(id);
|
|
181
|
+
if (!stored) return false;
|
|
182
|
+
return !isJwtExpired(stored.access) || stored.refresh != null;
|
|
183
|
+
});
|
|
184
|
+
const [sessionExpiredAt, setSessionExpiredAt] = useState(null);
|
|
185
|
+
const [sessionExpiredMessage, setSessionExpiredMessage] = useState(null);
|
|
186
|
+
const [permissionDeniedAt, setPermissionDeniedAt] = useState(null);
|
|
187
|
+
const [permissionDeniedMessage, setPermissionDeniedMessage] = useState(null);
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
saveKnowledgeBases(knowledgeBases);
|
|
190
|
+
}, [knowledgeBases]);
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (activeKnowledgeBaseId) {
|
|
193
|
+
localStorage.setItem(ACTIVE_KEY, activeKnowledgeBaseId);
|
|
194
|
+
} else {
|
|
195
|
+
localStorage.removeItem(ACTIVE_KEY);
|
|
196
|
+
}
|
|
197
|
+
}, [activeKnowledgeBaseId]);
|
|
198
|
+
const activeKnowledgeBase = useMemo(
|
|
199
|
+
() => knowledgeBases.find((kb) => kb.id === activeKnowledgeBaseId) ?? null,
|
|
200
|
+
[knowledgeBases, activeKnowledgeBaseId]
|
|
201
|
+
);
|
|
202
|
+
const activeKbRef = useRef(activeKnowledgeBase);
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
activeKbRef.current = activeKnowledgeBase;
|
|
205
|
+
}, [activeKnowledgeBase]);
|
|
206
|
+
const proactiveRefreshTimerRef = useRef(null);
|
|
207
|
+
const scheduleProactiveRefresh = useCallback((accessTokenStr) => {
|
|
208
|
+
if (proactiveRefreshTimerRef.current) {
|
|
209
|
+
clearTimeout(proactiveRefreshTimerRef.current);
|
|
210
|
+
proactiveRefreshTimerRef.current = null;
|
|
211
|
+
}
|
|
212
|
+
const expiresAt = parseJwtExpiry(accessTokenStr);
|
|
213
|
+
if (!expiresAt) return;
|
|
214
|
+
const refreshAt = expiresAt.getTime() - REFRESH_BEFORE_EXP_MS;
|
|
215
|
+
const delay = Math.max(0, refreshAt - Date.now());
|
|
216
|
+
proactiveRefreshTimerRef.current = setTimeout(() => {
|
|
217
|
+
proactiveRefreshTimerRef.current = null;
|
|
218
|
+
refreshActiveRef.current?.();
|
|
219
|
+
}, delay);
|
|
220
|
+
}, []);
|
|
221
|
+
const refreshActiveRef = useRef(null);
|
|
222
|
+
const refreshActive = useCallback(async () => {
|
|
223
|
+
const kb = activeKbRef.current;
|
|
224
|
+
if (!kb) return null;
|
|
225
|
+
const newAccess = await performRefresh(kb);
|
|
226
|
+
if (newAccess) {
|
|
227
|
+
setSession((prev) => prev ? { ...prev, token: newAccess } : prev);
|
|
228
|
+
scheduleProactiveRefresh(newAccess);
|
|
229
|
+
} else {
|
|
230
|
+
setSession(null);
|
|
231
|
+
clearStoredSession(kb.id);
|
|
232
|
+
setSessionExpiredMessage("Your session has expired. Please sign in again.");
|
|
233
|
+
setSessionExpiredAt(Date.now());
|
|
234
|
+
if (proactiveRefreshTimerRef.current) {
|
|
235
|
+
clearTimeout(proactiveRefreshTimerRef.current);
|
|
236
|
+
proactiveRefreshTimerRef.current = null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return newAccess;
|
|
240
|
+
}, [scheduleProactiveRefresh]);
|
|
241
|
+
refreshActiveRef.current = refreshActive;
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
if (!activeKnowledgeBase) {
|
|
244
|
+
setSession(null);
|
|
245
|
+
setIsLoading(false);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const stored = getStoredSession(activeKnowledgeBase.id);
|
|
249
|
+
if (!stored) {
|
|
250
|
+
setSession(null);
|
|
251
|
+
setIsLoading(false);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
let cancelled = false;
|
|
255
|
+
const validate = async (tokenToUse) => {
|
|
256
|
+
const client = new SemiontApiClient2({
|
|
257
|
+
baseUrl: baseUrl2(kbBackendUrl(activeKnowledgeBase)),
|
|
258
|
+
eventBus: new EventBus2()
|
|
259
|
+
});
|
|
260
|
+
try {
|
|
261
|
+
const data = await client.getMe({ auth: accessToken(tokenToUse) });
|
|
262
|
+
if (cancelled) return;
|
|
263
|
+
setSession({ token: tokenToUse, user: data });
|
|
264
|
+
scheduleProactiveRefresh(tokenToUse);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (cancelled) return;
|
|
267
|
+
setSession(null);
|
|
268
|
+
if (error instanceof APIError && error.status === 401) {
|
|
269
|
+
const refreshed = await performRefresh(activeKnowledgeBase);
|
|
270
|
+
if (cancelled) return;
|
|
271
|
+
if (refreshed) {
|
|
272
|
+
return validate(refreshed);
|
|
273
|
+
}
|
|
274
|
+
clearStoredSession(activeKnowledgeBase.id);
|
|
275
|
+
setSessionExpiredMessage("Your session has expired. Please sign in again.");
|
|
276
|
+
setSessionExpiredAt(Date.now());
|
|
277
|
+
}
|
|
278
|
+
} finally {
|
|
279
|
+
if (!cancelled) setIsLoading(false);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
setIsLoading(true);
|
|
283
|
+
if (isJwtExpired(stored.access)) {
|
|
284
|
+
(async () => {
|
|
285
|
+
const refreshed = await performRefresh(activeKnowledgeBase);
|
|
286
|
+
if (cancelled) return;
|
|
287
|
+
if (refreshed) {
|
|
288
|
+
await validate(refreshed);
|
|
289
|
+
} else {
|
|
290
|
+
setSession(null);
|
|
291
|
+
clearStoredSession(activeKnowledgeBase.id);
|
|
292
|
+
setIsLoading(false);
|
|
293
|
+
}
|
|
294
|
+
})();
|
|
295
|
+
} else {
|
|
296
|
+
validate(stored.access);
|
|
297
|
+
}
|
|
298
|
+
return () => {
|
|
299
|
+
cancelled = true;
|
|
300
|
+
};
|
|
301
|
+
}, [activeKnowledgeBase, scheduleProactiveRefresh]);
|
|
302
|
+
useEffect(() => {
|
|
303
|
+
return () => {
|
|
304
|
+
if (proactiveRefreshTimerRef.current) {
|
|
305
|
+
clearTimeout(proactiveRefreshTimerRef.current);
|
|
306
|
+
proactiveRefreshTimerRef.current = null;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}, []);
|
|
310
|
+
useEffect(() => {
|
|
311
|
+
if (!activeKnowledgeBaseId) return;
|
|
312
|
+
const watchKey = sessionKey(activeKnowledgeBaseId);
|
|
313
|
+
const handler = (e) => {
|
|
314
|
+
if (e.key !== watchKey) return;
|
|
315
|
+
if (!e.newValue) {
|
|
316
|
+
setSession(null);
|
|
317
|
+
if (proactiveRefreshTimerRef.current) {
|
|
318
|
+
clearTimeout(proactiveRefreshTimerRef.current);
|
|
319
|
+
proactiveRefreshTimerRef.current = null;
|
|
320
|
+
}
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
const parsed = JSON.parse(e.newValue);
|
|
325
|
+
if (typeof parsed.access === "string") {
|
|
326
|
+
setSession((prev) => prev ? { ...prev, token: parsed.access } : prev);
|
|
327
|
+
scheduleProactiveRefresh(parsed.access);
|
|
328
|
+
}
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
window.addEventListener("storage", handler);
|
|
333
|
+
return () => window.removeEventListener("storage", handler);
|
|
334
|
+
}, [activeKnowledgeBaseId, scheduleProactiveRefresh]);
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
return registerAuthNotifyHandlers({
|
|
337
|
+
onSessionExpired: (message) => {
|
|
338
|
+
setSessionExpiredMessage(message ?? "Your session has expired. Please sign in again.");
|
|
339
|
+
setSessionExpiredAt(Date.now());
|
|
340
|
+
setSession(null);
|
|
341
|
+
if (activeKnowledgeBaseId) {
|
|
342
|
+
clearStoredSession(activeKnowledgeBaseId);
|
|
343
|
+
}
|
|
344
|
+
if (proactiveRefreshTimerRef.current) {
|
|
345
|
+
clearTimeout(proactiveRefreshTimerRef.current);
|
|
346
|
+
proactiveRefreshTimerRef.current = null;
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
onPermissionDenied: (message) => {
|
|
350
|
+
setPermissionDeniedMessage(message ?? "You do not have permission to perform this action.");
|
|
351
|
+
setPermissionDeniedAt(Date.now());
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}, [activeKnowledgeBaseId]);
|
|
355
|
+
const addKnowledgeBase = useCallback((input, access, refresh) => {
|
|
356
|
+
const kb = { id: generateKbId(), ...input };
|
|
357
|
+
setStoredSession(kb.id, { access, refresh });
|
|
358
|
+
setKnowledgeBases((prev) => [...prev, kb]);
|
|
359
|
+
setActiveKnowledgeBaseId(kb.id);
|
|
360
|
+
return kb;
|
|
361
|
+
}, []);
|
|
362
|
+
const removeKnowledgeBase = useCallback((id) => {
|
|
363
|
+
clearStoredSession(id);
|
|
364
|
+
setKnowledgeBases((prev) => {
|
|
365
|
+
const remaining = prev.filter((kb) => kb.id !== id);
|
|
366
|
+
setActiveKnowledgeBaseId((activeId) => activeId === id ? remaining[0]?.id ?? null : activeId);
|
|
367
|
+
return remaining;
|
|
368
|
+
});
|
|
369
|
+
}, []);
|
|
370
|
+
const setActiveKnowledgeBase = useCallback((id) => {
|
|
371
|
+
setActiveKnowledgeBaseId(id);
|
|
372
|
+
}, []);
|
|
373
|
+
const updateKnowledgeBase = useCallback((id, updates) => {
|
|
374
|
+
setKnowledgeBases((prev) => prev.map((kb) => kb.id === id ? { ...kb, ...updates } : kb));
|
|
375
|
+
}, []);
|
|
376
|
+
const signIn = useCallback((id, access, refresh) => {
|
|
377
|
+
setStoredSession(id, { access, refresh });
|
|
378
|
+
setKnowledgeBases((prev) => prev.map((kb) => kb.id === id ? { ...kb } : kb));
|
|
379
|
+
setActiveKnowledgeBaseId(id);
|
|
380
|
+
}, []);
|
|
381
|
+
const signOut = useCallback((id) => {
|
|
382
|
+
clearStoredSession(id);
|
|
383
|
+
setActiveKnowledgeBaseId((activeId) => {
|
|
384
|
+
if (activeId === id) {
|
|
385
|
+
setSession(null);
|
|
386
|
+
if (proactiveRefreshTimerRef.current) {
|
|
387
|
+
clearTimeout(proactiveRefreshTimerRef.current);
|
|
388
|
+
proactiveRefreshTimerRef.current = null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return activeId;
|
|
392
|
+
});
|
|
393
|
+
setKnowledgeBases((prev) => [...prev]);
|
|
394
|
+
}, []);
|
|
395
|
+
const acknowledgeSessionExpired = useCallback(() => {
|
|
396
|
+
setSessionExpiredAt(null);
|
|
397
|
+
setSessionExpiredMessage(null);
|
|
398
|
+
}, []);
|
|
399
|
+
const acknowledgePermissionDenied = useCallback(() => {
|
|
400
|
+
setPermissionDeniedAt(null);
|
|
401
|
+
setPermissionDeniedMessage(null);
|
|
402
|
+
}, []);
|
|
403
|
+
const [, setTick] = useState(0);
|
|
404
|
+
useEffect(() => {
|
|
405
|
+
const interval = setInterval(() => setTick((t) => t + 1), 3e4);
|
|
406
|
+
return () => clearInterval(interval);
|
|
407
|
+
}, []);
|
|
408
|
+
const value = useMemo(() => {
|
|
409
|
+
const user = session?.user ?? null;
|
|
410
|
+
const token = session?.token ?? null;
|
|
411
|
+
const expiresAt = token ? parseJwtExpiry(token) : null;
|
|
412
|
+
return {
|
|
413
|
+
knowledgeBases,
|
|
414
|
+
activeKnowledgeBase,
|
|
415
|
+
session,
|
|
416
|
+
isLoading,
|
|
417
|
+
user,
|
|
418
|
+
token,
|
|
419
|
+
isAuthenticated: !!session,
|
|
420
|
+
hasValidBackendToken: !!token,
|
|
421
|
+
isFullyAuthenticated: !!session,
|
|
422
|
+
displayName: user?.name ?? user?.email?.split("@")[0] ?? "User",
|
|
423
|
+
avatarUrl: user?.image ?? null,
|
|
424
|
+
userDomain: user?.domain || user?.email?.split("@")[1],
|
|
425
|
+
isAdmin: user?.isAdmin ?? false,
|
|
426
|
+
isModerator: user?.isModerator ?? false,
|
|
427
|
+
expiresAt,
|
|
428
|
+
sessionExpiredAt,
|
|
429
|
+
sessionExpiredMessage,
|
|
430
|
+
permissionDeniedAt,
|
|
431
|
+
permissionDeniedMessage,
|
|
432
|
+
addKnowledgeBase,
|
|
433
|
+
removeKnowledgeBase,
|
|
434
|
+
setActiveKnowledgeBase,
|
|
435
|
+
updateKnowledgeBase,
|
|
436
|
+
signIn,
|
|
437
|
+
signOut,
|
|
438
|
+
refreshActive,
|
|
439
|
+
acknowledgeSessionExpired,
|
|
440
|
+
acknowledgePermissionDenied
|
|
441
|
+
};
|
|
442
|
+
}, [
|
|
443
|
+
knowledgeBases,
|
|
444
|
+
activeKnowledgeBase,
|
|
445
|
+
session,
|
|
446
|
+
isLoading,
|
|
447
|
+
sessionExpiredAt,
|
|
448
|
+
sessionExpiredMessage,
|
|
449
|
+
permissionDeniedAt,
|
|
450
|
+
permissionDeniedMessage,
|
|
451
|
+
addKnowledgeBase,
|
|
452
|
+
removeKnowledgeBase,
|
|
453
|
+
setActiveKnowledgeBase,
|
|
454
|
+
updateKnowledgeBase,
|
|
455
|
+
signIn,
|
|
456
|
+
signOut,
|
|
457
|
+
refreshActive,
|
|
458
|
+
acknowledgeSessionExpired,
|
|
459
|
+
acknowledgePermissionDenied
|
|
460
|
+
]);
|
|
461
|
+
return /* @__PURE__ */ jsx(KnowledgeBaseSessionContext.Provider, { value, children });
|
|
462
|
+
}
|
|
463
|
+
function useKnowledgeBaseSession() {
|
|
464
|
+
const ctx = useContext(KnowledgeBaseSessionContext);
|
|
465
|
+
if (!ctx) {
|
|
466
|
+
throw new Error(
|
|
467
|
+
"useKnowledgeBaseSession requires KnowledgeBaseSessionProvider. This component is rendered outside the auth boundary. Move it into a protected layout."
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
return ctx;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/components/Toast.tsx
|
|
474
|
+
import React2, { useEffect as useEffect2, useState as useState2 } from "react";
|
|
475
|
+
import { createPortal } from "react-dom";
|
|
476
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
477
|
+
var icons = {
|
|
478
|
+
success: /* @__PURE__ */ jsx2("svg", { className: "semiont-toast-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }),
|
|
479
|
+
error: /* @__PURE__ */ jsx2("svg", { className: "semiont-toast-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }),
|
|
480
|
+
warning: /* @__PURE__ */ jsx2("svg", { className: "semiont-toast-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
|
|
481
|
+
info: /* @__PURE__ */ jsx2("svg", { className: "semiont-toast-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) })
|
|
482
|
+
};
|
|
483
|
+
function Toast({ toast, onClose }) {
|
|
484
|
+
useEffect2(() => {
|
|
485
|
+
const timer = setTimeout(() => {
|
|
486
|
+
onClose(toast.id);
|
|
487
|
+
}, toast.duration || 3e3);
|
|
488
|
+
return () => clearTimeout(timer);
|
|
489
|
+
}, [toast, onClose]);
|
|
490
|
+
return /* @__PURE__ */ jsxs(
|
|
491
|
+
"div",
|
|
492
|
+
{
|
|
493
|
+
className: "semiont-toast",
|
|
494
|
+
"data-variant": toast.type,
|
|
495
|
+
role: "alert",
|
|
496
|
+
children: [
|
|
497
|
+
/* @__PURE__ */ jsx2("div", { className: "semiont-toast-icon-wrapper", children: icons[toast.type] }),
|
|
498
|
+
/* @__PURE__ */ jsx2("p", { className: "semiont-toast-message", children: toast.message }),
|
|
499
|
+
/* @__PURE__ */ jsx2(
|
|
500
|
+
"button",
|
|
501
|
+
{
|
|
502
|
+
onClick: () => onClose(toast.id),
|
|
503
|
+
className: "semiont-toast-close",
|
|
504
|
+
"aria-label": "Close",
|
|
505
|
+
children: /* @__PURE__ */ jsx2("svg", { className: "semiont-toast-close-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
|
|
506
|
+
}
|
|
507
|
+
)
|
|
508
|
+
]
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
function ToastContainer({ toasts, onClose }) {
|
|
513
|
+
const [mounted, setMounted] = useState2(false);
|
|
514
|
+
useEffect2(() => {
|
|
515
|
+
setMounted(true);
|
|
516
|
+
return () => setMounted(false);
|
|
517
|
+
}, []);
|
|
518
|
+
if (!mounted) return null;
|
|
519
|
+
return createPortal(
|
|
520
|
+
/* @__PURE__ */ jsx2("div", { className: "semiont-toast-container", children: toasts.map((toast) => /* @__PURE__ */ jsx2(Toast, { toast, onClose }, toast.id)) }),
|
|
521
|
+
document.body
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
var ToastContext = React2.createContext(void 0);
|
|
525
|
+
function ToastProvider({ children }) {
|
|
526
|
+
const [toasts, setToasts] = useState2([]);
|
|
527
|
+
const showToast = React2.useCallback((message, type = "info", duration) => {
|
|
528
|
+
const id = Date.now().toString();
|
|
529
|
+
const newToast = duration !== void 0 ? { id, message, type, duration } : { id, message, type };
|
|
530
|
+
setToasts((prev) => [...prev, newToast]);
|
|
531
|
+
}, []);
|
|
532
|
+
const showSuccess = React2.useCallback((message, duration) => showToast(message, "success", duration), [showToast]);
|
|
533
|
+
const showError = React2.useCallback((message, duration) => showToast(message, "error", duration), [showToast]);
|
|
534
|
+
const showWarning = React2.useCallback((message, duration) => showToast(message, "warning", duration), [showToast]);
|
|
535
|
+
const showInfo = React2.useCallback((message, duration) => showToast(message, "info", duration), [showToast]);
|
|
536
|
+
const handleClose = React2.useCallback((id) => {
|
|
537
|
+
setToasts((prev) => prev.filter((toast) => toast.id !== id));
|
|
538
|
+
}, []);
|
|
539
|
+
const contextValue = React2.useMemo(
|
|
540
|
+
() => ({ showToast, showSuccess, showError, showWarning, showInfo }),
|
|
541
|
+
[showToast, showSuccess, showError, showWarning, showInfo]
|
|
542
|
+
);
|
|
543
|
+
return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: contextValue, children: [
|
|
544
|
+
children,
|
|
545
|
+
/* @__PURE__ */ jsx2(ToastContainer, { toasts, onClose: handleClose })
|
|
546
|
+
] });
|
|
547
|
+
}
|
|
548
|
+
function useToast() {
|
|
549
|
+
const context = React2.useContext(ToastContext);
|
|
550
|
+
if (context === void 0) {
|
|
551
|
+
throw new Error("useToast must be used within a ToastProvider");
|
|
552
|
+
}
|
|
553
|
+
return context;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/contexts/OpenResourcesContext.tsx
|
|
557
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
558
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
559
|
+
var OpenResourcesContext = createContext2(void 0);
|
|
560
|
+
function OpenResourcesProvider({
|
|
561
|
+
openResourcesManager,
|
|
562
|
+
children
|
|
563
|
+
}) {
|
|
564
|
+
return /* @__PURE__ */ jsx3(OpenResourcesContext.Provider, { value: openResourcesManager, children });
|
|
565
|
+
}
|
|
566
|
+
function useOpenResources() {
|
|
567
|
+
const context = useContext2(OpenResourcesContext);
|
|
568
|
+
if (context === void 0) {
|
|
569
|
+
throw new Error("useOpenResources must be used within an OpenResourcesProvider");
|
|
570
|
+
}
|
|
571
|
+
return context;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/contexts/TranslationContext.tsx
|
|
575
|
+
import { createContext as createContext3, useContext as useContext3, useState as useState3, useEffect as useEffect3, useMemo as useMemo2 } from "react";
|
|
576
|
+
import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
|
|
577
|
+
|
|
578
|
+
// import("../../translations/**/*.json") in src/contexts/TranslationContext.tsx
|
|
579
|
+
var globImport_translations_json = __glob({
|
|
580
|
+
"../../translations/ar.json": () => import("./ar-R4CRNXEF.mjs"),
|
|
581
|
+
"../../translations/bn.json": () => import("./bn-CZKGRHTA.mjs"),
|
|
582
|
+
"../../translations/cs.json": () => import("./cs-4WIB2IHH.mjs"),
|
|
583
|
+
"../../translations/da.json": () => import("./da-JWYEUYPX.mjs"),
|
|
584
|
+
"../../translations/de.json": () => import("./de-GWUQZGER.mjs"),
|
|
585
|
+
"../../translations/el.json": () => import("./el-DM2GT7P5.mjs"),
|
|
586
|
+
"../../translations/en.json": () => import("./en-IUV4ZXKH.mjs"),
|
|
587
|
+
"../../translations/es.json": () => import("./es-6LVQIM3D.mjs"),
|
|
588
|
+
"../../translations/fa.json": () => import("./fa-IRUJY3QI.mjs"),
|
|
589
|
+
"../../translations/fi.json": () => import("./fi-53FBOEVT.mjs"),
|
|
590
|
+
"../../translations/fr.json": () => import("./fr-Q5KY7QL6.mjs"),
|
|
591
|
+
"../../translations/he.json": () => import("./he-HJNKULBY.mjs"),
|
|
592
|
+
"../../translations/hi.json": () => import("./hi-UYZ4X6CR.mjs"),
|
|
593
|
+
"../../translations/id.json": () => import("./id-UAQMH6U2.mjs"),
|
|
594
|
+
"../../translations/it.json": () => import("./it-C7QEBNFA.mjs"),
|
|
595
|
+
"../../translations/ja.json": () => import("./ja-THS6AOSJ.mjs"),
|
|
596
|
+
"../../translations/ko.json": () => import("./ko-XKK3TWQG.mjs"),
|
|
597
|
+
"../../translations/ms.json": () => import("./ms-GSK7LIF7.mjs"),
|
|
598
|
+
"../../translations/nl.json": () => import("./nl-KUBWITGY.mjs"),
|
|
599
|
+
"../../translations/no.json": () => import("./no-ECWZUHT6.mjs"),
|
|
600
|
+
"../../translations/pl.json": () => import("./pl-PLVWSZWS.mjs"),
|
|
601
|
+
"../../translations/pt.json": () => import("./pt-AL74ZTKB.mjs"),
|
|
602
|
+
"../../translations/ro.json": () => import("./ro-WTPHLHGS.mjs"),
|
|
603
|
+
"../../translations/sv.json": () => import("./sv-QCLI7SG4.mjs"),
|
|
604
|
+
"../../translations/th.json": () => import("./th-WCKVZU6U.mjs"),
|
|
605
|
+
"../../translations/tr.json": () => import("./tr-2CAFS2XS.mjs"),
|
|
606
|
+
"../../translations/uk.json": () => import("./uk-TDE4JLCY.mjs"),
|
|
607
|
+
"../../translations/vi.json": () => import("./vi-KKXZ4PCX.mjs"),
|
|
608
|
+
"../../translations/zh.json": () => import("./zh-VH4XN5PV.mjs")
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// src/contexts/TranslationContext.tsx
|
|
612
|
+
var TranslationContext = createContext3(null);
|
|
613
|
+
var translationCache = /* @__PURE__ */ new Map();
|
|
614
|
+
function processPluralFormat(text, params) {
|
|
615
|
+
const pluralMatch = text.match(/\{(\w+),\s*plural,\s*/);
|
|
616
|
+
if (!pluralMatch) {
|
|
617
|
+
return text;
|
|
618
|
+
}
|
|
619
|
+
const paramName = pluralMatch[1];
|
|
620
|
+
const count = params[paramName];
|
|
621
|
+
if (count === void 0) {
|
|
622
|
+
return text;
|
|
623
|
+
}
|
|
624
|
+
let startPos = pluralMatch[0].length;
|
|
625
|
+
let braceCount = 1;
|
|
626
|
+
let endPos = startPos;
|
|
627
|
+
for (let i = startPos; i < text.length; i++) {
|
|
628
|
+
if (text[i] === "{") braceCount++;
|
|
629
|
+
else if (text[i] === "}") {
|
|
630
|
+
braceCount--;
|
|
631
|
+
if (braceCount === 0) {
|
|
632
|
+
endPos = i;
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const pluralCases = text.substring(startPos, endPos);
|
|
638
|
+
const cases = {};
|
|
639
|
+
const caseRegex = /(?:=(\d+)|(\w+))\s*\{([^}]+)\}/g;
|
|
640
|
+
let caseMatch;
|
|
641
|
+
while ((caseMatch = caseRegex.exec(pluralCases)) !== null) {
|
|
642
|
+
const [, exactNumber, keyword, textContent] = caseMatch;
|
|
643
|
+
const key = exactNumber !== void 0 ? `=${exactNumber}` : keyword;
|
|
644
|
+
cases[key] = textContent;
|
|
645
|
+
}
|
|
646
|
+
const exactMatch = cases[`=${count}`];
|
|
647
|
+
if (exactMatch !== void 0) {
|
|
648
|
+
const result = exactMatch.replace(/#/g, String(count));
|
|
649
|
+
return text.substring(0, pluralMatch.index) + result + text.substring(endPos + 1);
|
|
650
|
+
}
|
|
651
|
+
const otherCase = cases["other"];
|
|
652
|
+
if (otherCase !== void 0) {
|
|
653
|
+
const result = otherCase.replace(/#/g, String(count));
|
|
654
|
+
return text.substring(0, pluralMatch.index) + result + text.substring(endPos + 1);
|
|
655
|
+
}
|
|
656
|
+
return text;
|
|
657
|
+
}
|
|
658
|
+
var AVAILABLE_LOCALES = [
|
|
659
|
+
"ar",
|
|
660
|
+
// Arabic
|
|
661
|
+
"bn",
|
|
662
|
+
// Bengali
|
|
663
|
+
"cs",
|
|
664
|
+
// Czech
|
|
665
|
+
"da",
|
|
666
|
+
// Danish
|
|
667
|
+
"de",
|
|
668
|
+
// German
|
|
669
|
+
"el",
|
|
670
|
+
// Greek
|
|
671
|
+
"en",
|
|
672
|
+
// English
|
|
673
|
+
"es",
|
|
674
|
+
// Spanish
|
|
675
|
+
"fa",
|
|
676
|
+
// Persian/Farsi
|
|
677
|
+
"fi",
|
|
678
|
+
// Finnish
|
|
679
|
+
"fr",
|
|
680
|
+
// French
|
|
681
|
+
"he",
|
|
682
|
+
// Hebrew
|
|
683
|
+
"hi",
|
|
684
|
+
// Hindi
|
|
685
|
+
"id",
|
|
686
|
+
// Indonesian
|
|
687
|
+
"it",
|
|
688
|
+
// Italian
|
|
689
|
+
"ja",
|
|
690
|
+
// Japanese
|
|
691
|
+
"ko",
|
|
692
|
+
// Korean
|
|
693
|
+
"ms",
|
|
694
|
+
// Malay
|
|
695
|
+
"nl",
|
|
696
|
+
// Dutch
|
|
697
|
+
"no",
|
|
698
|
+
// Norwegian
|
|
699
|
+
"pl",
|
|
700
|
+
// Polish
|
|
701
|
+
"pt",
|
|
702
|
+
// Portuguese
|
|
703
|
+
"ro",
|
|
704
|
+
// Romanian
|
|
705
|
+
"sv",
|
|
706
|
+
// Swedish
|
|
707
|
+
"th",
|
|
708
|
+
// Thai
|
|
709
|
+
"tr",
|
|
710
|
+
// Turkish
|
|
711
|
+
"uk",
|
|
712
|
+
// Ukrainian
|
|
713
|
+
"vi",
|
|
714
|
+
// Vietnamese
|
|
715
|
+
"zh"
|
|
716
|
+
// Chinese
|
|
717
|
+
];
|
|
718
|
+
async function loadTranslations(locale) {
|
|
719
|
+
if (translationCache.has(locale)) {
|
|
720
|
+
return translationCache.get(locale);
|
|
721
|
+
}
|
|
722
|
+
if (locale === "en") {
|
|
723
|
+
translationCache.set("en", en_default);
|
|
724
|
+
return en_default;
|
|
725
|
+
}
|
|
726
|
+
try {
|
|
727
|
+
const translations = await globImport_translations_json(`../../translations/${locale}.json`);
|
|
728
|
+
const translationData = translations.default || translations;
|
|
729
|
+
translationCache.set(locale, translationData);
|
|
730
|
+
return translationData;
|
|
731
|
+
} catch (error) {
|
|
732
|
+
console.error(`Failed to load translations for locale: ${locale}`, error);
|
|
733
|
+
return en_default;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
var defaultTranslationManager = {
|
|
737
|
+
t: (namespace, key, params) => {
|
|
738
|
+
const translations = en_default;
|
|
739
|
+
const translation = translations[namespace]?.[key];
|
|
740
|
+
if (!translation) {
|
|
741
|
+
console.warn(`Translation not found for ${namespace}.${key}`);
|
|
742
|
+
return `${namespace}.${key}`;
|
|
743
|
+
}
|
|
744
|
+
if (params && typeof translation === "string") {
|
|
745
|
+
let result = translation;
|
|
746
|
+
result = processPluralFormat(result, params);
|
|
747
|
+
Object.entries(params).forEach(([paramKey, paramValue]) => {
|
|
748
|
+
result = result.replace(new RegExp(`\\{${paramKey}\\}`, "g"), String(paramValue));
|
|
749
|
+
});
|
|
750
|
+
return result;
|
|
751
|
+
}
|
|
752
|
+
return translation;
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
function TranslationProvider({
|
|
756
|
+
translationManager,
|
|
757
|
+
locale,
|
|
758
|
+
loadingComponent = null,
|
|
759
|
+
children
|
|
760
|
+
}) {
|
|
761
|
+
const [loadedTranslations, setLoadedTranslations] = useState3(null);
|
|
762
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
763
|
+
useEffect3(() => {
|
|
764
|
+
if (locale && !translationManager) {
|
|
765
|
+
setIsLoading(true);
|
|
766
|
+
loadTranslations(locale).then((translations) => {
|
|
767
|
+
setLoadedTranslations(translations);
|
|
768
|
+
setIsLoading(false);
|
|
769
|
+
}).catch((error) => {
|
|
770
|
+
console.error("Failed to load translations:", error);
|
|
771
|
+
setLoadedTranslations(en_default);
|
|
772
|
+
setIsLoading(false);
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}, [locale, translationManager]);
|
|
776
|
+
const localeManager = useMemo2(() => {
|
|
777
|
+
if (!loadedTranslations) return null;
|
|
778
|
+
return {
|
|
779
|
+
t: (namespace, key, params) => {
|
|
780
|
+
const translation = loadedTranslations[namespace]?.[key];
|
|
781
|
+
if (!translation) {
|
|
782
|
+
console.warn(`Translation not found for ${namespace}.${key} in locale ${locale}`);
|
|
783
|
+
return `${namespace}.${key}`;
|
|
784
|
+
}
|
|
785
|
+
if (params && typeof translation === "string") {
|
|
786
|
+
let result = translation;
|
|
787
|
+
result = processPluralFormat(result, params);
|
|
788
|
+
Object.entries(params).forEach(([paramKey, paramValue]) => {
|
|
789
|
+
result = result.replace(new RegExp(`\\{${paramKey}\\}`, "g"), String(paramValue));
|
|
790
|
+
});
|
|
791
|
+
return result;
|
|
792
|
+
}
|
|
793
|
+
return translation;
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}, [loadedTranslations, locale]);
|
|
797
|
+
if (translationManager) {
|
|
798
|
+
return /* @__PURE__ */ jsx4(TranslationContext.Provider, { value: translationManager, children });
|
|
799
|
+
}
|
|
800
|
+
if (locale && isLoading) {
|
|
801
|
+
return /* @__PURE__ */ jsx4(Fragment, { children: loadingComponent });
|
|
802
|
+
}
|
|
803
|
+
if (locale && localeManager) {
|
|
804
|
+
return /* @__PURE__ */ jsx4(TranslationContext.Provider, { value: localeManager, children });
|
|
805
|
+
}
|
|
806
|
+
return /* @__PURE__ */ jsx4(TranslationContext.Provider, { value: defaultTranslationManager, children });
|
|
807
|
+
}
|
|
808
|
+
function useTranslations(namespace) {
|
|
809
|
+
const context = useContext3(TranslationContext);
|
|
810
|
+
if (!context) {
|
|
811
|
+
return (key, params) => {
|
|
812
|
+
const translations = en_default;
|
|
813
|
+
const translation = translations[namespace]?.[key];
|
|
814
|
+
if (!translation) {
|
|
815
|
+
console.warn(`Translation not found for ${namespace}.${key}`);
|
|
816
|
+
return `${namespace}.${key}`;
|
|
817
|
+
}
|
|
818
|
+
if (params && typeof translation === "string") {
|
|
819
|
+
let result = translation;
|
|
820
|
+
result = processPluralFormat(result, params);
|
|
821
|
+
Object.entries(params).forEach(([paramKey, paramValue]) => {
|
|
822
|
+
result = result.replace(new RegExp(`\\{${paramKey}\\}`, "g"), String(paramValue));
|
|
823
|
+
});
|
|
824
|
+
return result;
|
|
825
|
+
}
|
|
826
|
+
return translation;
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
return (key, params) => context.t(namespace, key, params);
|
|
830
|
+
}
|
|
831
|
+
function usePreloadTranslations() {
|
|
832
|
+
return {
|
|
833
|
+
preload: async (locale) => {
|
|
834
|
+
try {
|
|
835
|
+
await loadTranslations(locale);
|
|
836
|
+
return true;
|
|
837
|
+
} catch (error) {
|
|
838
|
+
console.error(`Failed to preload translations for ${locale}:`, error);
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
},
|
|
842
|
+
isLoaded: (locale) => translationCache.has(locale)
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
export {
|
|
847
|
+
defaultProtocol,
|
|
848
|
+
kbBackendUrl,
|
|
849
|
+
getKbSessionStatus,
|
|
850
|
+
notifySessionExpired,
|
|
851
|
+
notifyPermissionDenied,
|
|
852
|
+
KnowledgeBaseSessionContext,
|
|
853
|
+
KnowledgeBaseSessionProvider,
|
|
854
|
+
useKnowledgeBaseSession,
|
|
855
|
+
ToastContainer,
|
|
856
|
+
ToastProvider,
|
|
857
|
+
useToast,
|
|
858
|
+
OpenResourcesProvider,
|
|
859
|
+
useOpenResources,
|
|
860
|
+
AVAILABLE_LOCALES,
|
|
861
|
+
TranslationProvider,
|
|
862
|
+
useTranslations,
|
|
863
|
+
usePreloadTranslations
|
|
864
|
+
};
|
|
865
|
+
//# sourceMappingURL=chunk-R2U7P4TK.mjs.map
|