@ifc-lite/viewer 1.17.4 → 1.18.0

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 (196) hide show
  1. package/.turbo/turbo-build.log +20 -17
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/CHANGELOG.md +630 -0
  4. package/DESKTOP_CONTRACT_VERSION +1 -1
  5. package/dist/assets/{basketViewActivator-BmnNtVfZ.js → basketViewActivator-Cm1QEk_R.js} +1 -1
  6. package/dist/assets/drawing-2d-DoxKMqbO.js +257 -0
  7. package/dist/assets/{exporters-ChAtBmlj.js → exporters-B_OBqIyD.js} +3479 -2845
  8. package/dist/assets/{geometry.worker-BQ0rzNo-.js → geometry.worker-xHHy-9DV.js} +1 -1
  9. package/dist/assets/ids-DQ5jY0E8.js +1 -0
  10. package/dist/assets/ifc-lite_bg-ADjKXSms.wasm +0 -0
  11. package/dist/assets/{index-Co8E2-FE.js → index-BKq-M3Mk.js} +55873 -40593
  12. package/dist/assets/index-COnQRuqY.css +1 -0
  13. package/dist/assets/{native-bridge-BRvbckFQ.js → native-bridge-SHXiQwFW.js} +104 -104
  14. package/dist/assets/sandbox-jez21HtV.js +9627 -0
  15. package/dist/assets/{server-client-BV8zHZ7Y.js → server-client-ncOQVNso.js} +1 -1
  16. package/dist/assets/{wasm-bridge-g01g7T9b.js → wasm-bridge-DyfBSB8z.js} +1 -1
  17. package/dist/index.html +8 -7
  18. package/index.html +1 -0
  19. package/package.json +13 -13
  20. package/src/App.tsx +16 -2
  21. package/src/apache-arrow.d.ts +30 -0
  22. package/src/components/viewer/AddElementPanel.tsx +758 -0
  23. package/src/components/viewer/BulkPropertyEditor.tsx +7 -0
  24. package/src/components/viewer/CesiumOverlay.tsx +62 -19
  25. package/src/components/viewer/ChatPanel.tsx +259 -93
  26. package/src/components/viewer/CommandPalette.tsx +56 -7
  27. package/src/components/viewer/EntityContextMenu.tsx +168 -4
  28. package/src/components/viewer/ExportChangesButton.tsx +25 -5
  29. package/src/components/viewer/ExportDialog.tsx +19 -1
  30. package/src/components/viewer/MainToolbar.tsx +73 -13
  31. package/src/components/viewer/PropertiesPanel.tsx +237 -23
  32. package/src/components/viewer/SearchInline.tsx +669 -0
  33. package/src/components/viewer/SearchModal.filter.builder.tsx +766 -0
  34. package/src/components/viewer/SearchModal.filter.tsx +514 -0
  35. package/src/components/viewer/SearchModal.text.tsx +388 -0
  36. package/src/components/viewer/SearchModal.tsx +235 -0
  37. package/src/components/viewer/SettingsPage.tsx +252 -101
  38. package/src/components/viewer/ThemeSwitch.tsx +63 -7
  39. package/src/components/viewer/ToolOverlays.tsx +5 -0
  40. package/src/components/viewer/ViewerLayout.tsx +25 -4
  41. package/src/components/viewer/Viewport.tsx +25 -3
  42. package/src/components/viewer/ViewportContainer.tsx +51 -64
  43. package/src/components/viewer/ViewportOverlays.tsx +5 -2
  44. package/src/components/viewer/annotations/AnnotationDropInput.tsx +203 -0
  45. package/src/components/viewer/annotations/AnnotationLayer.tsx +287 -0
  46. package/src/components/viewer/annotations/AnnotationPin.tsx +90 -0
  47. package/src/components/viewer/annotations/AnnotationPopover.tsx +296 -0
  48. package/src/components/viewer/bcf/BCFTopicDetail.tsx +4 -4
  49. package/src/components/viewer/chat/ModelSelector.tsx +90 -54
  50. package/src/components/viewer/lists/ListPanel.tsx +14 -21
  51. package/src/components/viewer/properties/GeoreferencingPanel.tsx +113 -51
  52. package/src/components/viewer/properties/LocationMap.tsx +9 -7
  53. package/src/components/viewer/properties/ModelMetadataPanel.tsx +1 -1
  54. package/src/components/viewer/properties/RawStepCard.tsx +332 -0
  55. package/src/components/viewer/properties/RawStepRow.tsx +261 -0
  56. package/src/components/viewer/properties/ScheduleCard.tsx +224 -0
  57. package/src/components/viewer/properties/TaskEditCard.tsx +510 -0
  58. package/src/components/viewer/properties/raw-step-format.ts +193 -0
  59. package/src/components/viewer/schedule/AnimationSettingsPopover.tsx +542 -0
  60. package/src/components/viewer/schedule/GanttDependencyArrows.tsx +89 -0
  61. package/src/components/viewer/schedule/GanttDragTooltip.tsx +48 -0
  62. package/src/components/viewer/schedule/GanttEmptyState.tsx +97 -0
  63. package/src/components/viewer/schedule/GanttPanel.tsx +295 -0
  64. package/src/components/viewer/schedule/GanttTaskBar.tsx +199 -0
  65. package/src/components/viewer/schedule/GanttTaskTree.tsx +250 -0
  66. package/src/components/viewer/schedule/GanttTimeline.tsx +305 -0
  67. package/src/components/viewer/schedule/GanttToolbar.tsx +406 -0
  68. package/src/components/viewer/schedule/GenerateAdvancedPanel.tsx +147 -0
  69. package/src/components/viewer/schedule/GenerateScheduleDialog.tsx +392 -0
  70. package/src/components/viewer/schedule/HeightStrategyPanel.tsx +120 -0
  71. package/src/components/viewer/schedule/generate-schedule.test.ts +439 -0
  72. package/src/components/viewer/schedule/generate-schedule.ts +648 -0
  73. package/src/components/viewer/schedule/schedule-animator.test.ts +452 -0
  74. package/src/components/viewer/schedule/schedule-animator.ts +488 -0
  75. package/src/components/viewer/schedule/schedule-selection.test.ts +148 -0
  76. package/src/components/viewer/schedule/schedule-selection.ts +163 -0
  77. package/src/components/viewer/schedule/schedule-utils.ts +223 -0
  78. package/src/components/viewer/schedule/useConstructionSequence.ts +156 -0
  79. package/src/components/viewer/schedule/useGanttBarDrag.test.ts +90 -0
  80. package/src/components/viewer/schedule/useGanttBarDrag.ts +305 -0
  81. package/src/components/viewer/schedule/useGanttSelection3DHighlight.ts +152 -0
  82. package/src/components/viewer/schedule/useOverlayCompositor.ts +108 -0
  83. package/src/components/viewer/selectionHandlers.ts +446 -0
  84. package/src/components/viewer/tools/AddElementOverlay.tsx +540 -0
  85. package/src/components/viewer/tools/SectionCapControls.tsx +237 -0
  86. package/src/components/viewer/tools/SectionPanel.tsx +39 -18
  87. package/src/components/viewer/useAnimationLoop.ts +9 -1
  88. package/src/components/viewer/useDuplicateShortcut.ts +77 -0
  89. package/src/components/viewer/useMouseControls.ts +9 -1
  90. package/src/components/viewer/useRenderUpdates.ts +1 -1
  91. package/src/hooks/ids/idsDataAccessor.ts +60 -24
  92. package/src/hooks/ingest/viewerModelIngest.ts +7 -2
  93. package/src/hooks/useIfcFederation.ts +326 -71
  94. package/src/hooks/useIfcLoader.ts +23 -10
  95. package/src/hooks/useKeyboardShortcuts.ts +25 -0
  96. package/src/hooks/useSandbox.ts +1 -1
  97. package/src/hooks/useSearchIndex.ts +125 -0
  98. package/src/hooks/useViewControls.ts +13 -5
  99. package/src/index.css +550 -10
  100. package/src/lib/desktop-entitlement.ts +2 -4
  101. package/src/lib/geo/cesium-bridge.ts +15 -7
  102. package/src/lib/geo/effective-georef.test.ts +73 -0
  103. package/src/lib/geo/effective-georef.ts +111 -0
  104. package/src/lib/geo/reproject.ts +105 -19
  105. package/src/lib/llm/byok-guard.test.ts +77 -0
  106. package/src/lib/llm/byok-guard.ts +39 -0
  107. package/src/lib/llm/free-models.test.ts +0 -6
  108. package/src/lib/llm/models.ts +104 -42
  109. package/src/lib/llm/stream-client.ts +74 -110
  110. package/src/lib/llm/stream-direct.test.ts +130 -0
  111. package/src/lib/llm/stream-direct.ts +316 -0
  112. package/src/lib/llm/system-prompt.test.ts +14 -0
  113. package/src/lib/llm/system-prompt.ts +102 -1
  114. package/src/lib/llm/types.ts +20 -2
  115. package/src/lib/recent-files.ts +38 -4
  116. package/src/lib/scripts/templates/bim-globals.d.ts +136 -114
  117. package/src/lib/scripts/templates/construction-schedule.ts +223 -0
  118. package/src/lib/scripts/templates.ts +7 -0
  119. package/src/lib/search/common-ifc-types.ts +36 -0
  120. package/src/lib/search/filter-evaluate.test.ts +537 -0
  121. package/src/lib/search/filter-evaluate.ts +610 -0
  122. package/src/lib/search/filter-rules.test.ts +119 -0
  123. package/src/lib/search/filter-rules.ts +198 -0
  124. package/src/lib/search/filter-schema.test.ts +233 -0
  125. package/src/lib/search/filter-schema.ts +146 -0
  126. package/src/lib/search/recent-searches.test.ts +116 -0
  127. package/src/lib/search/recent-searches.ts +93 -0
  128. package/src/lib/search/result-export.test.ts +101 -0
  129. package/src/lib/search/result-export.ts +104 -0
  130. package/src/lib/search/saved-filters.test.ts +118 -0
  131. package/src/lib/search/saved-filters.ts +154 -0
  132. package/src/lib/search/tier0-scan.test.ts +196 -0
  133. package/src/lib/search/tier0-scan.ts +237 -0
  134. package/src/lib/search/tier1-index.test.ts +242 -0
  135. package/src/lib/search/tier1-index.ts +448 -0
  136. package/src/main.tsx +1 -10
  137. package/src/sdk/adapters/export-adapter.test.ts +434 -1
  138. package/src/sdk/adapters/export-adapter.ts +404 -1
  139. package/src/sdk/adapters/export-schedule-splice.test.ts +127 -0
  140. package/src/sdk/adapters/export-schedule-splice.ts +87 -0
  141. package/src/sdk/adapters/model-compat.ts +8 -2
  142. package/src/sdk/adapters/schedule-adapter.ts +73 -0
  143. package/src/sdk/adapters/store-adapter.ts +201 -0
  144. package/src/sdk/adapters/visibility-adapter.ts +3 -0
  145. package/src/sdk/local-backend.ts +16 -8
  146. package/src/services/api-keys.ts +73 -0
  147. package/src/services/desktop-export.ts +3 -1
  148. package/src/services/desktop-native-metadata.ts +41 -18
  149. package/src/services/file-dialog.ts +4 -1
  150. package/src/services/tauri-modules.d.ts +25 -0
  151. package/src/store/basketVisibleSet.ts +3 -0
  152. package/src/store/constants.ts +20 -2
  153. package/src/store/globalId.ts +4 -1
  154. package/src/store/index.ts +82 -6
  155. package/src/store/slices/addElementMeshes.ts +365 -0
  156. package/src/store/slices/addElementSlice.ts +275 -0
  157. package/src/store/slices/annotationsSlice.test.ts +133 -0
  158. package/src/store/slices/annotationsSlice.ts +251 -0
  159. package/src/store/slices/cesiumSlice.ts +5 -0
  160. package/src/store/slices/chatSlice.test.ts +6 -76
  161. package/src/store/slices/chatSlice.ts +17 -58
  162. package/src/store/slices/dataSlice.test.ts +23 -4
  163. package/src/store/slices/dataSlice.ts +1 -1
  164. package/src/store/slices/modelSlice.test.ts +67 -9
  165. package/src/store/slices/modelSlice.ts +39 -7
  166. package/src/store/slices/mutationSlice.ts +964 -3
  167. package/src/store/slices/overlayCompositor.test.ts +164 -0
  168. package/src/store/slices/overlaySlice.test.ts +93 -0
  169. package/src/store/slices/overlaySlice.ts +151 -0
  170. package/src/store/slices/pinboardSlice.test.ts +6 -1
  171. package/src/store/slices/playbackSlice.ts +128 -0
  172. package/src/store/slices/schedule-edit-helpers.test.ts +97 -0
  173. package/src/store/slices/schedule-edit-helpers.ts +179 -0
  174. package/src/store/slices/scheduleSlice.test.ts +694 -0
  175. package/src/store/slices/scheduleSlice.ts +1330 -0
  176. package/src/store/slices/searchSlice.test.ts +342 -0
  177. package/src/store/slices/searchSlice.ts +341 -0
  178. package/src/store/slices/sectionSlice.test.ts +87 -7
  179. package/src/store/slices/sectionSlice.ts +151 -5
  180. package/src/store/slices/selectionSlice.test.ts +46 -0
  181. package/src/store/slices/selectionSlice.ts +20 -0
  182. package/src/store/slices/uiSlice.ts +28 -5
  183. package/src/store/types.ts +26 -0
  184. package/src/store.ts +14 -0
  185. package/src/utils/nativeSpatialDataStore.ts +4 -1
  186. package/src/utils/viewportUtils.ts +7 -2
  187. package/src/vite-env.d.ts +0 -4
  188. package/dist/assets/drawing-2d-gWfpdfYe.js +0 -257
  189. package/dist/assets/ids-B4jTqB1O.js +0 -1
  190. package/dist/assets/ifc-lite_bg-BX4E7TX8.wasm +0 -0
  191. package/dist/assets/index-DckuDqlv.css +0 -1
  192. package/dist/assets/sandbox-DZiNLNMk.js +0 -5933
  193. package/src/components/viewer/UpgradePage.tsx +0 -71
  194. package/src/lib/desktop/ClerkDesktopEntitlementSync.tsx +0 -175
  195. package/src/lib/llm/ClerkChatSync.tsx +0 -74
  196. package/src/lib/llm/clerk-auth.ts +0 -62
@@ -1,71 +0,0 @@
1
- /* This Source Code Form is subject to the terms of the Mozilla Public
2
- * License, v. 2.0. If a copy of the MPL was not distributed with this
3
- * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
-
5
- import { PricingTable, SignInButton, SignedIn, SignedOut } from '@clerk/clerk-react';
6
- import { useEffect, useMemo } from 'react';
7
- import { ArrowLeft } from 'lucide-react';
8
- import { Button } from '@/components/ui/button';
9
- import { useViewerStore } from '@/store';
10
- import { hasDesktopPro } from '@/lib/desktop-product';
11
- import { navigateToPath } from '@/services/app-navigation';
12
-
13
- export function UpgradePage() {
14
- const desktopEntitlement = useViewerStore((s) => s.desktopEntitlement);
15
- const hasPro = hasDesktopPro(desktopEntitlement);
16
- const returnTo = useMemo(() => {
17
- const params = new URLSearchParams(window.location.search);
18
- const candidate = params.get('returnTo');
19
- return candidate && candidate.startsWith('/') ? candidate : '/';
20
- }, []);
21
-
22
- const navigateBack = () => {
23
- navigateToPath(returnTo, { replace: true });
24
- };
25
-
26
- // Automatically return to the previous app view once upgrade is active.
27
- useEffect(() => {
28
- if (!hasPro) return;
29
- const timer = window.setTimeout(() => {
30
- navigateBack();
31
- }, 400);
32
- return () => window.clearTimeout(timer);
33
- }, [hasPro]);
34
-
35
- return (
36
- <div className="min-h-screen bg-background text-foreground">
37
- <div className="mx-auto w-full max-w-5xl px-6 py-8">
38
- <div className="mb-6 flex items-center justify-between">
39
- <Button variant="ghost" size="sm" onClick={navigateBack}>
40
- <ArrowLeft className="mr-2 h-4 w-4" />
41
- Back to Viewer
42
- </Button>
43
- </div>
44
-
45
- <div className="rounded-lg border bg-card p-6 shadow-sm">
46
- <h1 className="text-2xl font-semibold">Upgrade to Pro</h1>
47
- <p className="mt-2 text-sm text-muted-foreground">
48
- Free keeps the core desktop viewer available. Pro unlocks advanced desktop features, plus the AI assistant with the same monthly limits and routing used on web.
49
- </p>
50
-
51
- <div className="mt-6">
52
- <SignedOut>
53
- <div className="flex items-center justify-center py-12">
54
- <SignInButton
55
- mode="modal"
56
- fallbackRedirectUrl={returnTo}
57
- forceRedirectUrl={returnTo}
58
- >
59
- <Button>Sign in to continue</Button>
60
- </SignInButton>
61
- </div>
62
- </SignedOut>
63
- <SignedIn>
64
- <PricingTable />
65
- </SignedIn>
66
- </div>
67
- </div>
68
- </div>
69
- </div>
70
- );
71
- }
@@ -1,175 +0,0 @@
1
- /* This Source Code Form is subject to the terms of the Mozilla Public
2
- * License, v. 2.0. If a copy of the MPL was not distributed with this
3
- * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
-
5
- import { useEffect } from 'react';
6
- import { useAuth, useUser } from '@clerk/clerk-react';
7
- import { getDefaultDesktopEntitlement, type DesktopEntitlement } from '@/lib/desktop-product';
8
- import { resolveDesktopEntitlement } from '@/lib/desktop-entitlement';
9
- import { useViewerStore } from '@/store';
10
- import { DESKTOP_ENTITLEMENT_REFRESH_EVENT } from './desktopEntitlementEvents';
11
-
12
- /**
13
- * Sync Clerk auth + entitlement state into the viewer store.
14
- * Desktop plan state is authoritative here; chat inherits only the AI-specific permission.
15
- */
16
- const ENTITLEMENT_REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1000;
17
-
18
- function entitlementsEqual(left: DesktopEntitlement, right: DesktopEntitlement): boolean {
19
- return left.tier === right.tier
20
- && left.status === right.status
21
- && left.source === right.source
22
- && left.userId === right.userId
23
- && left.validatedAt === right.validatedAt
24
- && left.graceUntil === right.graceUntil
25
- && left.trialEndsAt === right.trialEndsAt;
26
- }
27
-
28
- export function ClerkDesktopEntitlementSync() {
29
- const { isLoaded, isSignedIn, userId, getToken, has } = useAuth();
30
- const { user } = useUser();
31
- const setChatAuthToken = useViewerStore((s) => s.setChatAuthToken);
32
- const switchChatUserContext = useViewerStore((s) => s.switchChatUserContext);
33
- const setDesktopEntitlement = useViewerStore((s) => s.setDesktopEntitlement);
34
-
35
- useEffect(() => {
36
- if (!isLoaded) return;
37
-
38
- if (!isSignedIn) {
39
- const state = useViewerStore.getState();
40
- const next = (
41
- state.desktopEntitlement.tier === 'pro'
42
- && state.desktopEntitlement.graceUntil
43
- && state.desktopEntitlement.graceUntil > Date.now()
44
- )
45
- ? {
46
- ...state.desktopEntitlement,
47
- status: 'grace_offline' as const,
48
- source: 'cached' as const,
49
- }
50
- : {
51
- ...getDefaultDesktopEntitlement(),
52
- status: 'signed_out' as const,
53
- source: 'cached' as const,
54
- };
55
- if (!entitlementsEqual(state.desktopEntitlement, next)) {
56
- setDesktopEntitlement(next);
57
- }
58
- if (next.status === 'grace_offline') {
59
- switchChatUserContext(next.userId, false, {
60
- clearPersistedCurrent: false,
61
- restoreMessages: true,
62
- });
63
- } else {
64
- switchChatUserContext(null, false, {
65
- clearPersistedCurrent: state.chatStorageUserId !== null,
66
- restoreMessages: false,
67
- });
68
- }
69
- setChatAuthToken(null);
70
- return;
71
- }
72
-
73
- let cancelled = false;
74
-
75
- const syncAuth = async () => {
76
- try {
77
- const token = await getToken({ skipCache: true });
78
- const { entitlement, aiAssistantEnabled } = resolveDesktopEntitlement({
79
- userId: userId ?? null,
80
- token,
81
- has,
82
- publicMetadata: (user?.publicMetadata ?? null) as Record<string, unknown> | null,
83
- });
84
-
85
- if (cancelled) {
86
- return;
87
- }
88
-
89
- const state = useViewerStore.getState();
90
- if (!entitlementsEqual(state.desktopEntitlement, entitlement)) {
91
- setDesktopEntitlement(entitlement);
92
- }
93
- if (state.chatStorageUserId !== (userId ?? null) || state.chatHasPro !== aiAssistantEnabled) {
94
- switchChatUserContext(userId ?? null, aiAssistantEnabled, {
95
- clearPersistedCurrent: state.chatStorageUserId !== null && state.chatStorageUserId !== userId,
96
- restoreMessages: true,
97
- });
98
- }
99
- setChatAuthToken(token ?? null);
100
- } catch {
101
- if (cancelled) {
102
- return;
103
- }
104
-
105
- const state = useViewerStore.getState();
106
- if (
107
- state.desktopEntitlement.tier === 'pro'
108
- && state.desktopEntitlement.graceUntil
109
- && state.desktopEntitlement.graceUntil > Date.now()
110
- ) {
111
- const next = {
112
- ...state.desktopEntitlement,
113
- status: 'grace_offline',
114
- source: 'cached',
115
- } satisfies DesktopEntitlement;
116
- if (!entitlementsEqual(state.desktopEntitlement, next)) {
117
- setDesktopEntitlement(next);
118
- }
119
- if (state.chatStorageUserId !== (userId ?? null) || state.chatHasPro !== true) {
120
- switchChatUserContext(userId ?? null, true, {
121
- clearPersistedCurrent: false,
122
- restoreMessages: true,
123
- });
124
- }
125
- return;
126
- }
127
-
128
- const next = {
129
- ...getDefaultDesktopEntitlement(),
130
- userId: userId ?? null,
131
- status: 'signed_out' as const,
132
- source: 'cached' as const,
133
- };
134
- if (!entitlementsEqual(state.desktopEntitlement, next)) {
135
- setDesktopEntitlement(next);
136
- }
137
- }
138
- };
139
-
140
- void syncAuth();
141
- const timer = window.setInterval(() => {
142
- void syncAuth();
143
- }, ENTITLEMENT_REFRESH_INTERVAL_MS);
144
- const onWindowFocus = () => {
145
- void syncAuth();
146
- };
147
- const onManualRefresh = (event: Event) => {
148
- const detail = (event as CustomEvent<{ resolve: () => void; reject: (error?: unknown) => void }>).detail;
149
- void syncAuth()
150
- .then(() => detail?.resolve())
151
- .catch((error) => detail?.reject(error));
152
- };
153
- window.addEventListener('focus', onWindowFocus);
154
- window.addEventListener(DESKTOP_ENTITLEMENT_REFRESH_EVENT, onManualRefresh);
155
-
156
- return () => {
157
- cancelled = true;
158
- window.clearInterval(timer);
159
- window.removeEventListener('focus', onWindowFocus);
160
- window.removeEventListener(DESKTOP_ENTITLEMENT_REFRESH_EVENT, onManualRefresh);
161
- };
162
- }, [
163
- getToken,
164
- has,
165
- isLoaded,
166
- isSignedIn,
167
- setChatAuthToken,
168
- setDesktopEntitlement,
169
- switchChatUserContext,
170
- user?.publicMetadata,
171
- userId,
172
- ]);
173
-
174
- return null;
175
- }
@@ -1,74 +0,0 @@
1
- /* This Source Code Form is subject to the terms of the Mozilla Public
2
- * License, v. 2.0. If a copy of the MPL was not distributed with this
3
- * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
-
5
- import { useEffect } from 'react';
6
- import { useAuth } from '@clerk/clerk-react';
7
- import { useViewerStore } from '@/store';
8
-
9
- /**
10
- * Sync Clerk session state into chat store for authenticated LLM requests.
11
- * Keeps chat UX decoupled from auth/billing details.
12
- */
13
- export function ClerkChatSync() {
14
- const { isLoaded, isSignedIn, userId, getToken, has } = useAuth();
15
- const setChatAuthToken = useViewerStore((s) => s.setChatAuthToken);
16
- const switchChatUserContext = useViewerStore((s) => s.switchChatUserContext);
17
- const currentChatUserId = useViewerStore((s) => s.chatStorageUserId);
18
- const currentChatHasPro = useViewerStore((s) => s.chatHasPro);
19
-
20
- useEffect(() => {
21
- if (!isLoaded) return;
22
-
23
- if (!isSignedIn) {
24
- switchChatUserContext(null, false, {
25
- clearPersistedCurrent: currentChatUserId !== null,
26
- restoreMessages: false,
27
- });
28
- setChatAuthToken(null);
29
- return;
30
- }
31
-
32
- let cancelled = false;
33
-
34
- const syncAuth = async () => {
35
- try {
36
- const token = await getToken({ skipCache: true });
37
- const proPlan = has?.({ plan: 'pro' }) ?? false;
38
- const proFeature = has?.({ feature: 'pro_models' }) ?? false;
39
- const nextHasPro = proPlan || proFeature;
40
- if (!cancelled) {
41
- // Avoid resetting chat usage/messages on routine token refreshes for
42
- // the same signed-in user. Only switch context when identity or
43
- // entitlement actually changes.
44
- if (currentChatUserId !== (userId ?? null) || currentChatHasPro !== nextHasPro) {
45
- switchChatUserContext(userId ?? null, nextHasPro, {
46
- clearPersistedCurrent: currentChatUserId !== null && currentChatUserId !== userId,
47
- restoreMessages: true,
48
- });
49
- }
50
- if (token) {
51
- setChatAuthToken(token);
52
- }
53
- }
54
- } catch {
55
- if (!cancelled) {
56
- // Preserve the current signed-in chat context on transient token
57
- // refresh failures. Explicit sign-out is handled above.
58
- }
59
- }
60
- };
61
-
62
- void syncAuth();
63
- // Keep short-lived JWTs fresh so chat/usage polling doesn't reuse expired tokens.
64
- const timer = window.setInterval(() => {
65
- void syncAuth();
66
- }, 15_000);
67
- return () => {
68
- cancelled = true;
69
- window.clearInterval(timer);
70
- };
71
- }, [currentChatHasPro, currentChatUserId, getToken, has, isLoaded, isSignedIn, setChatAuthToken, switchChatUserContext, userId]);
72
-
73
- return null;
74
- }
@@ -1,62 +0,0 @@
1
- /* This Source Code Form is subject to the terms of the Mozilla Public
2
- * License, v. 2.0. If a copy of the MPL was not distributed with this
3
- * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
-
5
- /**
6
- * Auth helpers for the LLM chat integration.
7
- *
8
- * Setup:
9
- * 1. Install: pnpm add @clerk/clerk-react
10
- * 2. Set VITE_CLERK_PUBLISHABLE_KEY in .env
11
- * 3. Create plans in dashboard:
12
- * - Free plan (slug: 'free', features: ['llm_chat', 'free_models'])
13
- * - Pro plan (slug: 'pro', $8/month, features: ['llm_chat', 'free_models', 'pro_models'])
14
- * 4. Wrap app with <ClerkProvider>
15
- *
16
- * Usage in components:
17
- *
18
- * ```tsx
19
- * import { useAuth, useUser, Protect } from '@clerk/clerk-react';
20
- *
21
- * const { has } = useAuth();
22
- * const hasPro = has?.({ feature: 'pro_models' }) ?? false;
23
- *
24
- * const { getToken } = useAuth();
25
- * const token = await getToken();
26
- * ```
27
- */
28
-
29
- /**
30
- * Subscription tiers and their features.
31
- */
32
- export const SUBSCRIPTION_PLANS = {
33
- free: {
34
- slug: 'free',
35
- name: 'Free',
36
- features: ['llm_chat', 'free_models'],
37
- description: 'AI chat with free models',
38
- },
39
- pro: {
40
- slug: 'pro',
41
- name: 'Pro',
42
- features: ['llm_chat', 'free_models', 'pro_models'],
43
- description: 'All models with monthly credits ($8/month)',
44
- },
45
- } as const;
46
-
47
- /**
48
- * Feature flags that map to plan features.
49
- */
50
- export const FEATURES = {
51
- LLM_CHAT: 'llm_chat',
52
- FREE_MODELS: 'free_models',
53
- PRO_MODELS: 'pro_models',
54
- } as const;
55
-
56
- /**
57
- * Check if auth is configured (publishable key present).
58
- * When not configured, the chat works in anonymous free-tier mode.
59
- */
60
- export function isClerkConfigured(): boolean {
61
- return Boolean(import.meta.env.VITE_CLERK_PUBLISHABLE_KEY);
62
- }