@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.
Files changed (52) hide show
  1. package/README.md +18 -12
  2. package/dist/KnowledgeBaseSessionContext-CpYaCbnC.d.mts +174 -0
  3. package/dist/{PdfAnnotationCanvas.client-CW6SKH2U.mjs → PdfAnnotationCanvas.client-CHDCGQBR.mjs} +3 -3
  4. package/dist/{chunk-HNZOXH4L.mjs → chunk-OZICDVH7.mjs} +5 -3
  5. package/dist/chunk-OZICDVH7.mjs.map +1 -0
  6. package/dist/chunk-R2U7P4TK.mjs +865 -0
  7. package/dist/chunk-R2U7P4TK.mjs.map +1 -0
  8. package/dist/{chunk-BQJWOK4C.mjs → chunk-VN5NY4SN.mjs} +9 -8
  9. package/dist/chunk-VN5NY4SN.mjs.map +1 -0
  10. package/dist/index.d.mts +147 -171
  11. package/dist/index.mjs +2215 -1961
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/test-utils.d.mts +13 -62
  14. package/dist/test-utils.mjs +40 -21
  15. package/dist/test-utils.mjs.map +1 -1
  16. package/package.json +5 -3
  17. package/src/components/ProtectedErrorBoundary.tsx +95 -0
  18. package/src/components/Toolbar.tsx +13 -13
  19. package/src/components/__tests__/ProtectedErrorBoundary.test.tsx +197 -0
  20. package/src/components/modals/PermissionDeniedModal.tsx +140 -0
  21. package/src/components/modals/ReferenceWizardModal.tsx +3 -2
  22. package/src/components/modals/SessionExpiredModal.tsx +101 -0
  23. package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +150 -0
  24. package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +115 -0
  25. package/src/components/resource/AnnotationHistory.tsx +5 -6
  26. package/src/components/resource/HistoryEvent.tsx +7 -7
  27. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +33 -34
  28. package/src/components/resource/__tests__/HistoryEvent.test.tsx +17 -19
  29. package/src/components/resource/__tests__/event-formatting.test.ts +70 -94
  30. package/src/components/resource/event-formatting.ts +56 -56
  31. package/src/components/resource/panels/CollaborationPanel.tsx +9 -1
  32. package/src/components/resource/panels/ReferenceEntry.tsx +7 -5
  33. package/src/components/resource/panels/ResourceInfoPanel.tsx +8 -6
  34. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +12 -12
  35. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +1 -0
  36. package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +1 -1
  37. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +4 -4
  38. package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +5 -10
  39. package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +23 -54
  40. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +6 -6
  41. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +7 -19
  42. package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +1 -1
  43. package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +18 -44
  44. package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +6 -6
  45. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +31 -26
  46. package/src/styles/patterns/panels-base.css +12 -0
  47. package/dist/TranslationManager-CudgH3gw.d.mts +0 -107
  48. package/dist/chunk-BQJWOK4C.mjs.map +0 -1
  49. package/dist/chunk-HNZOXH4L.mjs.map +0 -1
  50. package/dist/chunk-OL5UST25.mjs +0 -413
  51. package/dist/chunk-OL5UST25.mjs.map +0 -1
  52. /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