@windrun-huaiin/third-ui 20.0.0 → 21.0.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 (54) hide show
  1. package/LICENSE +1 -1
  2. package/dist/clerk/clerk-page-generator.d.ts +0 -8
  3. package/dist/clerk/fingerprint/fingerprint-provider.js +58 -49
  4. package/dist/clerk/fingerprint/fingerprint-provider.mjs +58 -49
  5. package/dist/fuma/fuma-page-genarator.d.ts +2 -2
  6. package/dist/fuma/fuma-page-genarator.js +21 -8
  7. package/dist/fuma/fuma-page-genarator.mjs +21 -8
  8. package/dist/fuma/llm-copy-handler.js +3 -2
  9. package/dist/fuma/llm-copy-handler.mjs +3 -2
  10. package/dist/fuma/mdx/index.d.ts +1 -0
  11. package/dist/fuma/mdx/index.js +3 -0
  12. package/dist/fuma/mdx/index.mjs +1 -0
  13. package/dist/fuma/mdx/math.d.ts +17 -0
  14. package/dist/fuma/mdx/math.js +60 -0
  15. package/dist/fuma/mdx/math.mjs +57 -0
  16. package/dist/fuma/mdx/zia-card.js +1 -1
  17. package/dist/fuma/mdx/zia-card.mjs +1 -1
  18. package/dist/main/{ads-alert-dialog.d.ts → alert-dialog/ads-alert-dialog.d.ts} +1 -1
  19. package/dist/main/alert-dialog/ads-alert-dialog.js +24 -0
  20. package/dist/main/alert-dialog/ads-alert-dialog.mjs +22 -0
  21. package/dist/main/alert-dialog/confirm-dialog.d.ts +15 -0
  22. package/dist/main/alert-dialog/confirm-dialog.js +40 -0
  23. package/dist/main/alert-dialog/confirm-dialog.mjs +38 -0
  24. package/dist/main/alert-dialog/dialog-styles.d.ts +14 -0
  25. package/dist/main/alert-dialog/dialog-styles.js +35 -0
  26. package/dist/main/alert-dialog/dialog-styles.mjs +20 -0
  27. package/dist/main/alert-dialog/high-priority-confirm-dialog.d.ts +12 -0
  28. package/dist/main/alert-dialog/high-priority-confirm-dialog.js +23 -0
  29. package/dist/main/alert-dialog/high-priority-confirm-dialog.mjs +21 -0
  30. package/dist/main/alert-dialog/index.d.ts +4 -0
  31. package/dist/main/alert-dialog/info-dialog.d.ts +13 -0
  32. package/dist/main/alert-dialog/info-dialog.js +50 -0
  33. package/dist/main/alert-dialog/info-dialog.mjs +48 -0
  34. package/dist/main/index.d.ts +1 -1
  35. package/dist/main/index.js +7 -1
  36. package/dist/main/index.mjs +4 -1
  37. package/package.json +4 -4
  38. package/src/clerk/clerk-page-generator.tsx +0 -9
  39. package/src/clerk/fingerprint/fingerprint-provider.tsx +155 -62
  40. package/src/fuma/fuma-page-genarator.tsx +26 -9
  41. package/src/fuma/llm-copy-handler.ts +3 -3
  42. package/src/fuma/mdx/index.ts +1 -0
  43. package/src/fuma/mdx/math.tsx +130 -0
  44. package/src/fuma/mdx/zia-card.tsx +1 -0
  45. package/src/main/{ads-alert-dialog.tsx → alert-dialog/ads-alert-dialog.tsx} +46 -29
  46. package/src/main/alert-dialog/confirm-dialog.tsx +131 -0
  47. package/src/main/alert-dialog/dialog-styles.ts +73 -0
  48. package/src/main/alert-dialog/high-priority-confirm-dialog.tsx +94 -0
  49. package/src/main/alert-dialog/index.ts +7 -0
  50. package/src/main/alert-dialog/info-dialog.tsx +139 -0
  51. package/src/main/index.ts +1 -1
  52. package/src/main/language-detector.tsx +0 -8
  53. package/dist/main/ads-alert-dialog.js +0 -21
  54. package/dist/main/ads-alert-dialog.mjs +0 -19
@@ -4,6 +4,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { Settings2Icon, CoinsIcon, GiftIcon, LightbulbIcon, ShieldUserIcon, XIcon, FingerprintIcon, GemIcon, DatabaseZapIcon, BellIcon, RefreshCcwIcon } from '@windrun-huaiin/base-ui/icons';
5
5
  import { themeIconColor, themeButtonGradientClass, themeButtonGradientHoverClass } from '@windrun-huaiin/base-ui/lib';
6
6
  import { cn } from '@windrun-huaiin/lib/utils';
7
+ import { useMessages } from 'next-intl';
7
8
  import { createContext, useContext, useState, useRef, useEffect, useMemo } from 'react';
8
9
  import { useFingerprint } from './use-fingerprint.mjs';
9
10
  import { CopyableText } from '@windrun-huaiin/base-ui/ui';
@@ -12,6 +13,10 @@ import { getOrCreateDebugFingerprintOverride, regenerateDebugFingerprintOverride
12
13
  import { FINGERPRINT_SOURCE_REFER } from './fingerprint-shared.mjs';
13
14
 
14
15
  const FingerprintContext = createContext(undefined);
16
+ function useFingerprintStatusTranslations() {
17
+ const messages = useMessages();
18
+ return messages.fingerprint;
19
+ }
15
20
  /**
16
21
  * Fingerprint Provider Component
17
22
  * 为应用提供fingerprint和匿名用户管理功能
@@ -51,6 +56,7 @@ function withFingerprint(Component, config) {
51
56
  * 组件:显示用户状态和积分信息(用于调试)
52
57
  */
53
58
  function FingerprintStatus() {
59
+ const translations = useFingerprintStatusTranslations();
54
60
  const { fingerprintId, xUser, xCredit, xSubscription, error, clearError, } = useFingerprintContext();
55
61
  const [isOpen, setIsOpen] = useState(false);
56
62
  const [panelMode, setPanelMode] = useState('info');
@@ -90,7 +96,7 @@ function FingerprintStatus() {
90
96
  return [
91
97
  {
92
98
  key: 'paid',
93
- label: 'Paid',
99
+ label: translations.creditBuckets.paid,
94
100
  icon: jsx(Settings2Icon, { className: "size-4 text-green-500 dark:text-green-300" }),
95
101
  balance: xCredit.balancePaid,
96
102
  total: xCredit.totalPaidLimit,
@@ -99,7 +105,7 @@ function FingerprintStatus() {
99
105
  },
100
106
  {
101
107
  key: 'oneTimePaid',
102
- label: 'OneTimePaid',
108
+ label: translations.creditBuckets.oneTimePaid,
103
109
  icon: jsx(CoinsIcon, { className: "size-4 text-amber-500 dark:text-amber-300" }),
104
110
  balance: xCredit.balanceOneTimePaid,
105
111
  total: xCredit.totalOneTimePaidLimit,
@@ -108,7 +114,7 @@ function FingerprintStatus() {
108
114
  },
109
115
  {
110
116
  key: 'free',
111
- label: 'Free',
117
+ label: translations.creditBuckets.free,
112
118
  icon: jsx(GiftIcon, { className: "size-4 text-purple-500 dark:text-purple-300" }),
113
119
  balance: xCredit.balanceFree,
114
120
  total: xCredit.totalFreeLimit,
@@ -116,15 +122,15 @@ function FingerprintStatus() {
116
122
  end: xCredit.freeEnd,
117
123
  },
118
124
  ];
119
- }, [xCredit]);
125
+ }, [translations.creditBuckets.free, translations.creditBuckets.oneTimePaid, translations.creditBuckets.paid, xCredit]);
120
126
  const subscriptionStatus = useMemo(() => {
121
127
  var _a, _b;
122
128
  if (!xSubscription) {
123
129
  return {
124
- status: 'Never',
130
+ status: translations.placeholders.subscriptionStatusNever,
125
131
  priceName: '--',
126
132
  creditsAllocated: '--',
127
- period: 'Unavailable',
133
+ period: translations.placeholders.subscriptionPeriodUnavailable,
128
134
  };
129
135
  }
130
136
  return {
@@ -133,9 +139,9 @@ function FingerprintStatus() {
133
139
  creditsAllocated: typeof xSubscription.creditsAllocated === 'number'
134
140
  ? formatNumber(xSubscription.creditsAllocated)
135
141
  : '--',
136
- period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd),
142
+ period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd, translations),
137
143
  };
138
- }, [xSubscription]);
144
+ }, [translations.placeholders.subscriptionPeriodUnavailable, translations.placeholders.subscriptionStatusNever, xSubscription]);
139
145
  const userStatus = (xUser === null || xUser === void 0 ? void 0 : xUser.status) || '--';
140
146
  const totalCredits = formatNumber(xCredit === null || xCredit === void 0 ? void 0 : xCredit.totalBalance);
141
147
  const subStatus = subscriptionStatus.status;
@@ -143,22 +149,22 @@ function FingerprintStatus() {
143
149
  const runContextParallelInitTest = () => __awaiter(this, void 0, void 0, function* () {
144
150
  const debugFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : getOrCreateDebugFingerprintOverride();
145
151
  if (!debugFingerprintId) {
146
- setTestResult('Test fingerprint override is not ready yet.');
152
+ setTestResult(translations.messages.testFingerprintNotReady);
147
153
  return;
148
154
  }
149
155
  setActiveDebugFingerprintId(debugFingerprintId);
150
156
  setIsRunningTest(true);
151
- setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
157
+ setTestResult(tpl(translations.messages.runningFrontendPreventionTest, { fingerprintId: debugFingerprintId }));
152
158
  try {
153
159
  yield Promise.all([
154
160
  initializeDebugAnonymousUser(debugFingerprintId),
155
161
  initializeDebugAnonymousUser(debugFingerprintId),
156
162
  initializeDebugAnonymousUser(debugFingerprintId),
157
163
  ]);
158
- setTestResult(`Frontend Prevention Test finished. Active test fingerprint: ${debugFingerprintId}`);
164
+ setTestResult(tpl(translations.messages.frontendPreventionTestFinished, { fingerprintId: debugFingerprintId }));
159
165
  }
160
166
  catch (testError) {
161
- setTestResult(`Frontend Prevention Test failed: ${formatErrorMessage(testError)}`);
167
+ setTestResult(tpl(translations.messages.frontendPreventionTestFailed, { error: formatErrorMessage(testError, translations) }));
162
168
  }
163
169
  finally {
164
170
  setIsRunningTest(false);
@@ -178,7 +184,7 @@ function FingerprintStatus() {
178
184
  });
179
185
  if (!response.ok) {
180
186
  const errorData = yield response.json().catch(() => ({}));
181
- throw new Error(errorData.error || 'Failed to initialize anonymous user');
187
+ throw new Error(errorData.error || translations.messages.failedToInitializeAnonymousUser);
182
188
  }
183
189
  yield response.json().catch(() => ({}));
184
190
  }
@@ -189,12 +195,12 @@ function FingerprintStatus() {
189
195
  const runRawParallelPostTest = () => __awaiter(this, void 0, void 0, function* () {
190
196
  const normalizedFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : getOrCreateDebugFingerprintOverride();
191
197
  if (!normalizedFingerprintId) {
192
- setTestResult('Test fingerprint override is not ready yet.');
198
+ setTestResult(translations.messages.testFingerprintNotReady);
193
199
  return;
194
200
  }
195
201
  setActiveDebugFingerprintId(normalizedFingerprintId);
196
202
  setIsRunningTest(true);
197
- setTestResult(`Running Backend Idempotency Test with fingerprint: ${normalizedFingerprintId}`);
203
+ setTestResult(tpl(translations.messages.runningBackendIdempotencyTest, { fingerprintId: normalizedFingerprintId }));
198
204
  try {
199
205
  const fingerprintHeaders = yield createFingerprintHeaders();
200
206
  const requests = Array.from({ length: 3 }, () => fetch('/api/user/anonymous/init', {
@@ -225,16 +231,16 @@ function FingerprintStatus() {
225
231
  .filter((payload) => !payload.ok)
226
232
  .map((payload) => `${payload.status}${payload.error ? `:${payload.error}` : ''}`);
227
233
  setTestResult([
228
- `Backend Idempotency Test done.`,
229
- `created=${createdUserIds.length}`,
230
- `reused=${reusedUserIds.length}`,
231
- `failed=${failedStatuses.length}`,
232
- `createdUserIds=[${createdUserIds.join(', ')}]`,
233
- failedStatuses.length > 0 ? `failedStatuses=[${failedStatuses.join(', ')}]` : null,
234
+ translations.messages.backendIdempotencyTestDone,
235
+ tpl(translations.messages.createdCount, { count: createdUserIds.length }),
236
+ tpl(translations.messages.reusedCount, { count: reusedUserIds.length }),
237
+ tpl(translations.messages.failedCount, { count: failedStatuses.length }),
238
+ tpl(translations.messages.createdUserIds, { value: createdUserIds.join(', ') }),
239
+ failedStatuses.length > 0 ? tpl(translations.messages.failedStatuses, { value: failedStatuses.join(', ') }) : null,
234
240
  ].filter(Boolean).join('\n'));
235
241
  }
236
242
  catch (testError) {
237
- setTestResult(`Backend Idempotency Test failed: ${formatErrorMessage(testError)}`);
243
+ setTestResult(tpl(translations.messages.backendIdempotencyTestFailed, { error: formatErrorMessage(testError, translations) }));
238
244
  }
239
245
  finally {
240
246
  setIsRunningTest(false);
@@ -243,31 +249,31 @@ function FingerprintStatus() {
243
249
  const regenerateTestFingerprint = () => {
244
250
  const nextFingerprintId = regenerateDebugFingerprintOverride();
245
251
  setActiveDebugFingerprintId(nextFingerprintId);
246
- setTestResult(`Generated test fingerprint override: ${nextFingerprintId}`);
252
+ setTestResult(tpl(translations.messages.generatedTestFingerprintOverride, { fingerprintId: nextFingerprintId }));
247
253
  };
248
- return (jsxs(Fragment, { children: [!isOpen && (jsx("button", { onClick: handleToggle, type: "button", "aria-label": "Fingerprint debug panel", className: cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', themeButtonGradientClass, themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsx(LightbulbIcon, { className: "size-6 text-white" }) })), isOpen && (jsxs(Fragment, { children: [jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxs("div", { ref: modalRef, className: cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsx("header", { className: "mb-4", children: jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxs("div", { className: cn("flex items-center gap-2 text-base font-bold tracking-wider", themeIconColor), children: [jsx(ShieldUserIcon, { className: "size-4" }), "Fingerprint Debug Panel"] }), jsxs("div", { className: "flex items-center gap-2", children: [jsxs("button", { type: "button", onClick: () => setPanelMode((prev) => prev === 'info' ? 'test' : 'info'), className: cn('inline-flex items-center gap-2 rounded-full border px-2 py-1 text-[11px] font-semibold shadow-sm transition-all duration-200', panelMode === 'test'
254
+ return (jsxs(Fragment, { children: [!isOpen && (jsx("button", { onClick: handleToggle, type: "button", "aria-label": translations.panel.toggleAriaLabel, className: cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', themeButtonGradientClass, themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsx(LightbulbIcon, { className: "size-6 text-white" }) })), isOpen && (jsxs(Fragment, { children: [jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxs("div", { ref: modalRef, className: cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsx("header", { className: "mb-4", children: jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxs("div", { className: cn("flex items-center gap-2 text-base font-bold tracking-wider", themeIconColor), children: [jsx(ShieldUserIcon, { className: "size-4" }), translations.panel.title] }), jsxs("div", { className: "flex items-center gap-2", children: [jsxs("button", { type: "button", onClick: () => setPanelMode((prev) => prev === 'info' ? 'test' : 'info'), className: cn('inline-flex items-center gap-2 rounded-full border px-2 py-1 text-[11px] font-semibold shadow-sm transition-all duration-200', panelMode === 'test'
249
255
  ? cn('border-transparent text-white', themeButtonGradientClass, themeButtonGradientHoverClass)
250
- : themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsx("span", { children: "Concurrent Test" }), jsx("span", { className: cn('relative inline-flex h-5 w-9 items-center rounded-full transition-colors', panelMode === 'test'
256
+ : themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsx("span", { children: translations.panel.testModeLabel }), jsx("span", { className: cn('relative inline-flex h-5 w-9 items-center rounded-full transition-colors', panelMode === 'test'
251
257
  ? 'bg-white/25'
252
- : 'bg-slate-300 dark:bg-slate-700'), children: jsx("span", { className: cn('inline-block size-4 rounded-full shadow-sm transition-transform', panelMode === 'test' ? 'bg-white' : 'bg-white dark:bg-slate-100', panelMode === 'test' ? 'translate-x-4' : 'translate-x-0.5') }) })] }), jsx("button", { type: "button", "aria-label": "Close fingerprint panel", className: "rounded-full p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white", onClick: () => setIsOpen(false), children: jsx(XIcon, { className: "size-4" }) })] })] }) }), jsxs("section", { className: "space-y-1", children: [panelMode === 'info' ? (jsxs(Fragment, { children: [jsx(PanelSection, { icon: jsx(FingerprintIcon, { className: "size-4" }), title: "User", rightInfo: jsx(StatusTag, { value: userStatus }), items: [
253
- { label: 'UserID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
254
- { label: 'NickName', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
255
- { label: 'FingerprintID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
256
- { label: 'ClerkUserID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
257
- { label: 'Email', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
258
- { label: 'StripeCusID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
259
- { label: 'CreatedAt', value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
260
- ] }), jsxs("div", { className: "space-y-2 rounded-xl border border-slate-200/70 bg-white/80 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/50", children: [jsx(PanelHeader, { icon: jsx(GemIcon, { className: "size-4" }), title: "Credits Info", rightInfo: jsx("span", { className: cn("font-semibold", themeIconColor), children: totalCredits }) }), jsx("div", { className: "space-y-3", children: creditBuckets.length > 0 ? (creditBuckets.map((bucket) => {
258
+ : 'bg-slate-300 dark:bg-slate-700'), children: jsx("span", { className: cn('inline-block size-4 rounded-full shadow-sm transition-transform', panelMode === 'test' ? 'bg-white' : 'bg-white dark:bg-slate-100', panelMode === 'test' ? 'translate-x-4' : 'translate-x-0.5') }) })] }), jsx("button", { type: "button", "aria-label": translations.panel.closeAriaLabel, className: "rounded-full p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white", onClick: () => setIsOpen(false), children: jsx(XIcon, { className: "size-4" }) })] })] }) }), jsxs("section", { className: "space-y-1", children: [panelMode === 'info' ? (jsxs(Fragment, { children: [jsx(PanelSection, { icon: jsx(FingerprintIcon, { className: "size-4" }), title: translations.sections.user, rightInfo: jsx(StatusTag, { value: userStatus, translations: translations }), items: [
259
+ { label: translations.labels.userId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
260
+ { label: translations.labels.nickName, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
261
+ { label: translations.labels.fingerprintId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
262
+ { label: translations.labels.clerkUserId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
263
+ { label: translations.labels.email, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
264
+ { label: translations.labels.stripeCusId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
265
+ { label: translations.labels.createdAt, value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
266
+ ] }), jsxs("div", { className: "space-y-2 rounded-xl border border-slate-200/70 bg-white/80 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/50", children: [jsx(PanelHeader, { icon: jsx(GemIcon, { className: "size-4" }), title: translations.sections.creditsInfo, rightInfo: jsx("span", { className: cn("font-semibold", themeIconColor), children: totalCredits }) }), jsx("div", { className: "space-y-3", children: creditBuckets.length > 0 ? (creditBuckets.map((bucket) => {
261
267
  const percent = Math.round(computeProgress(bucket.balance, bucket.total) * 100);
262
- return (jsxs("div", { className: "rounded-lg border border-slate-200/70 bg-white/70 p-3 dark:border-white/10 dark:bg-slate-900/40", children: [jsxs("div", { className: "flex items-center justify-between text-xs font-medium text-slate-600 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center gap-1.5", children: [bucket.icon, jsx("span", { children: bucket.label })] }), jsxs("span", { className: "font-semibold text-slate-700 dark:text-slate-100", children: [formatNumber(bucket.balance), " / ", formatNumber(bucket.total)] })] }), jsx("div", { className: "mt-2 h-1.5 w-full rounded-full bg-slate-200 dark:bg-slate-800", children: jsx("div", { className: "h-full rounded-full bg-linear-to-r from-purple-500 via-pink-500 to-rose-400 transition-[width]", style: { width: `${percent}%` } }) }), jsxs("div", { className: "mt-2 flex items-center justify-between text-[11px] text-slate-500 dark:text-slate-400", children: [jsx("span", { children: formatRangeText(bucket.start, bucket.end) }), jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
263
- })) : (jsx(EmptyPlaceholder, { label: "No Credits Yet", icon: jsx(DatabaseZapIcon, { className: "size-4" }) })) })] }), jsx(PanelSection, { icon: jsx(BellIcon, { className: "size-4" }), title: "Subscription", rightInfo: jsx(StatusTag, { value: subStatus }), items: [
264
- { label: 'Plan', value: subscriptionStatus.priceName },
265
- { label: 'Period', value: subscriptionStatus.period },
266
- { label: 'Allocated', value: subscriptionStatus.creditsAllocated },
267
- { label: 'SubID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
268
- { label: 'OrderID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
269
- { label: 'PriceID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
270
- ] })] })) : (jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsx(PanelHeader, { icon: jsx(DatabaseZapIcon, { className: "size-4" }), title: "Concurrent Base Info", rightInfo: jsx(StatusTag, { value: isRunningTest ? 'pending' : 'idle' }) }), jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Real Browser" }), jsx(CopyableText, { text: fingerprintId || '' })] }), jsxs("div", { className: "space-y-1", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Test Override" }), jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsx("div", { className: "min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", children: jsx(CopyableText, { text: activeDebugFingerprintId || '' }) }), jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": "Generate new test fingerprint", className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsx(RefreshCcwIcon, { className: "size-4" }) })] })] })] }), jsxs("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: [jsx("button", { type: "button", disabled: isRunningTest, onClick: runContextParallelInitTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', themedGhostButtonClass), children: "Frontend Prevention Test" }), jsx("button", { type: "button", disabled: isRunningTest, onClick: runRawParallelPostTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', 'border-transparent', themeButtonGradientClass, themeButtonGradientHoverClass), children: "Backend Idempotency Test" })] }), jsx("div", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50/80 p-3 dark:border-white/10 dark:bg-slate-950/50", children: jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-5 text-slate-600 dark:text-slate-300", children: testResult || 'No test executed yet.' }) })] })), error && (jsxs("div", { className: "flex items-start justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsxs("div", { className: "flex items-start gap-2", children: [jsx(XIcon, { className: "mt-0.5 size-4 shrink-0" }), jsx("span", { children: error })] }), jsx("button", { type: "button", "aria-label": "Dismiss error", onClick: clearError, className: "shrink-0 rounded-full p-1 text-amber-500 transition hover:bg-amber-100 hover:text-amber-700 dark:text-amber-200 dark:hover:bg-amber-500/10 dark:hover:text-amber-100", children: jsx(XIcon, { className: "size-4" }) })] }))] })] })] }))] }));
268
+ return (jsxs("div", { className: "rounded-lg border border-slate-200/70 bg-white/70 p-3 dark:border-white/10 dark:bg-slate-900/40", children: [jsxs("div", { className: "flex items-center justify-between text-xs font-medium text-slate-600 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center gap-1.5", children: [bucket.icon, jsx("span", { children: bucket.label })] }), jsxs("span", { className: "font-semibold text-slate-700 dark:text-slate-100", children: [formatNumber(bucket.balance), " / ", formatNumber(bucket.total)] })] }), jsx("div", { className: "mt-2 h-1.5 w-full rounded-full bg-slate-200 dark:bg-slate-800", children: jsx("div", { className: "h-full rounded-full bg-linear-to-r from-purple-500 via-pink-500 to-rose-400 transition-[width]", style: { width: `${percent}%` } }) }), jsxs("div", { className: "mt-2 flex items-center justify-between text-[11px] text-slate-500 dark:text-slate-400", children: [jsx("span", { children: formatRangeText(bucket.start, bucket.end, translations) }), jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
269
+ })) : (jsx(EmptyPlaceholder, { label: translations.placeholders.noCreditsYet, icon: jsx(DatabaseZapIcon, { className: "size-4" }) })) })] }), jsx(PanelSection, { icon: jsx(BellIcon, { className: "size-4" }), title: translations.sections.subscription, rightInfo: jsx(StatusTag, { value: subStatus, translations: translations }), items: [
270
+ { label: translations.labels.plan, value: subscriptionStatus.priceName },
271
+ { label: translations.labels.period, value: subscriptionStatus.period },
272
+ { label: translations.labels.allocated, value: subscriptionStatus.creditsAllocated },
273
+ { label: translations.labels.subId, value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
274
+ { label: translations.labels.orderId, value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
275
+ { label: translations.labels.priceId, value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
276
+ ] })] })) : (jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsx(PanelHeader, { icon: jsx(DatabaseZapIcon, { className: "size-4" }), title: translations.sections.concurrentBaseInfo, rightInfo: jsx(StatusTag, { value: isRunningTest ? translations.status.pending : translations.status.idle, translations: translations }) }), jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: translations.labels.realBrowser }), jsx(CopyableText, { text: fingerprintId || '' })] }), jsxs("div", { className: "space-y-1", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: translations.labels.testOverride }), jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsx("div", { className: "min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", children: jsx(CopyableText, { text: activeDebugFingerprintId || '' }) }), jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": translations.actions.generateNewTestFingerprintAriaLabel, className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsx(RefreshCcwIcon, { className: "size-4" }) })] })] })] }), jsxs("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: [jsx("button", { type: "button", disabled: isRunningTest, onClick: runContextParallelInitTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', themedGhostButtonClass), children: translations.actions.frontendPreventionTest }), jsx("button", { type: "button", disabled: isRunningTest, onClick: runRawParallelPostTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', 'border-transparent', themeButtonGradientClass, themeButtonGradientHoverClass), children: translations.actions.backendIdempotencyTest })] }), jsx("div", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50/80 p-3 dark:border-white/10 dark:bg-slate-950/50", children: jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-5 text-slate-600 dark:text-slate-300", children: testResult || translations.placeholders.noTestExecutedYet }) })] })), error && (jsxs("div", { className: "flex items-start justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsxs("div", { className: "flex items-start gap-2", children: [jsx(XIcon, { className: "mt-0.5 size-4 shrink-0" }), jsx("span", { children: error })] }), jsx("button", { type: "button", "aria-label": translations.actions.dismissErrorAriaLabel, onClick: clearError, className: "shrink-0 rounded-full p-1 text-amber-500 transition hover:bg-amber-100 hover:text-amber-700 dark:text-amber-200 dark:hover:bg-amber-500/10 dark:hover:text-amber-100", children: jsx(XIcon, { className: "size-4" }) })] }))] })] })] }))] }));
271
277
  }
272
278
  /* ==================== 新增辅助组件 ==================== */
273
279
  // 标题行:左侧图标+标题,右侧信息(右对齐)
@@ -294,11 +300,11 @@ function computeProgress(balance, total) {
294
300
  return 0;
295
301
  return Math.min(Math.max(ratio, 0), 1);
296
302
  }
297
- function formatRangeText(start, end) {
303
+ function formatRangeText(start, end, translations) {
298
304
  const safeStart = start && start.trim() ? start : '';
299
305
  const safeEnd = end && end.trim() ? end : '';
300
306
  if (!safeStart && !safeEnd) {
301
- return 'No records';
307
+ return translations.placeholders.noRecords;
302
308
  }
303
309
  if (!safeStart) {
304
310
  return safeEnd;
@@ -308,9 +314,9 @@ function formatRangeText(start, end) {
308
314
  }
309
315
  return `${safeStart} - ${safeEnd}`;
310
316
  }
311
- function StatusTag({ value }) {
317
+ function StatusTag({ value, translations, }) {
312
318
  if (!value)
313
- return jsx("span", { className: "text-slate-400", children: "None" });
319
+ return jsx("span", { className: "text-slate-400", children: translations.placeholders.none });
314
320
  const normalized = value.toLowerCase();
315
321
  const colorMap = {
316
322
  // 绿色:正常/活跃
@@ -333,14 +339,17 @@ function StatusTag({ value }) {
333
339
  const badgeClass = colorMap[normalized] || defaultColor;
334
340
  return (jsx("span", { className: cn('inline-block rounded-full px-2 py-0.5 text-xs capitalize font-medium', badgeClass), children: value }));
335
341
  }
336
- function formatErrorMessage(error) {
342
+ function formatErrorMessage(error, translations) {
337
343
  if (error instanceof Error) {
338
344
  return error.message;
339
345
  }
340
346
  if (typeof error === 'string') {
341
347
  return error;
342
348
  }
343
- return 'Unknown error';
349
+ return translations.placeholders.unknownError;
350
+ }
351
+ function tpl(template, values) {
352
+ return template.replace(/\{(\w+)\}/g, (_, key) => { var _a; return String((_a = values[key]) !== null && _a !== void 0 ? _a : ''); });
344
353
  }
345
354
 
346
355
  export { FingerprintProvider, FingerprintStatus, useFingerprintContext, useFingerprintContextSafe, withFingerprint };
@@ -2,7 +2,7 @@ import { ReactNode, ReactElement } from 'react';
2
2
  import type { LLMCopyButtonProps, LLMCopyButton } from '@third-ui/fuma/mdx/toc-base';
3
3
  interface FumaPageParams {
4
4
  sourceKey: string;
5
- mdxContentSource: any;
5
+ mdxContentSource: any | (() => Promise<any>);
6
6
  getMDXComponents: () => any;
7
7
  mdxSourceDir: string;
8
8
  githubBaseUrl?: string;
@@ -25,7 +25,7 @@ export declare function createFumaPage({ sourceKey, mdxContentSource, getMDXComp
25
25
  slug?: string[];
26
26
  }>;
27
27
  }) => Promise<import("react/jsx-runtime").JSX.Element>;
28
- generateStaticParams: () => any;
28
+ generateStaticParams: () => Promise<any>;
29
29
  generateMetadata: (props: {
30
30
  params: Promise<{
31
31
  slug?: string[];
@@ -9,36 +9,49 @@ var lib = require('@windrun-huaiin/lib');
9
9
  var tocClerkPortable = require('./mdx/toc-clerk-portable.js');
10
10
 
11
11
  function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSourceDir, githubBaseUrl, copyButtonComponent, siteIcon, FallbackPage, supportedLocales = ['en'], showBreadcrumb = true, showTableOfContent = true, showTableOfContentPopover = false, localePrefixAsNeeded = true, defaultLocale = 'en', }) {
12
+ const getSource = () => tslib.__awaiter(this, void 0, void 0, function* () {
13
+ if (typeof mdxContentSource === 'function') {
14
+ return yield mdxContentSource();
15
+ }
16
+ return mdxContentSource;
17
+ });
12
18
  const Page = function Page(_a) {
13
19
  return tslib.__awaiter(this, arguments, void 0, function* ({ params }) {
14
- var _b, _c, _d;
20
+ var _b, _c, _d, _e;
15
21
  const { slug, locale } = yield params;
16
- const page$1 = mdxContentSource.getPage(slug, locale);
22
+ const source = yield getSource();
23
+ const page$1 = source.getPage(slug, locale);
17
24
  if (!page$1) {
18
- console.log('[FumaPage] missing page', { slug, locale, available: (_d = (_c = (_b = mdxContentSource.pageTree) === null || _b === void 0 ? void 0 : _b[locale]) === null || _c === void 0 ? void 0 : _c.children) === null || _d === void 0 ? void 0 : _d.map((c) => c.url) });
25
+ console.log('[FumaPage] missing page', { slug, locale, available: (_d = (_c = (_b = source.pageTree) === null || _b === void 0 ? void 0 : _b[locale]) === null || _c === void 0 ? void 0 : _c.children) === null || _d === void 0 ? void 0 : _d.map((c) => c.url) });
19
26
  return jsxRuntime.jsx(FallbackPage, { siteIcon: siteIcon });
20
27
  }
21
28
  const path = githubBaseUrl ? `${mdxSourceDir}/${page$1.path}` : undefined;
22
29
  const tocFooterElement = (jsxRuntime.jsx(tocFooterWrapper.TocFooterWrapper, { lastModified: page$1.data.date, copyButtonComponent: copyButtonComponent
23
30
  ? React.cloneElement(copyButtonComponent, { sourceKey })
24
31
  : undefined, editPath: path, githubBaseUrl: githubBaseUrl }));
25
- const MDX = page$1.data.body;
32
+ const content = typeof page$1.data.load === 'function'
33
+ ? yield page$1.data.load(getMDXComponents())
34
+ : {
35
+ body: yield page$1.data.body({ components: getMDXComponents() }),
36
+ toc: (_e = page$1.data.toc) !== null && _e !== void 0 ? _e : [],
37
+ };
26
38
  return (jsxRuntime.jsxs(page.DocsPage, { breadcrumb: { enabled: showBreadcrumb }, tableOfContent: {
27
39
  enabled: showTableOfContent,
28
40
  single: false,
29
- component: (jsxRuntime.jsx(tocClerkPortable.PortableClerkTOC, { toc: page$1.data.toc, footer: tocFooterElement })),
41
+ component: (jsxRuntime.jsx(tocClerkPortable.PortableClerkTOC, { toc: content.toc, footer: tocFooterElement })),
30
42
  }, tableOfContentPopover: {
31
43
  enabled: false,
32
- }, toc: page$1.data.toc, article: { className: 'max-sm:pb-16' }, children: [jsxRuntime.jsx(page.DocsTitle, { children: page$1.data.title }), jsxRuntime.jsx(page.DocsDescription, { className: "mb-2", children: page$1.data.description }), jsxRuntime.jsx(page.DocsBody, { className: "text-fd-foreground/80", children: jsxRuntime.jsx(MDX, { components: getMDXComponents() }) })] }));
44
+ }, toc: content.toc, article: { className: 'max-sm:pb-16' }, children: [jsxRuntime.jsx(page.DocsTitle, { children: page$1.data.title }), jsxRuntime.jsx(page.DocsDescription, { className: "mb-2", children: page$1.data.description }), jsxRuntime.jsx(page.DocsBody, { className: "text-fd-foreground/80", children: content.body })] }));
33
45
  });
34
46
  };
35
47
  function generateStaticParams() {
36
- return mdxContentSource.generateParams('slug', 'locale');
48
+ return getSource().then((source) => source.generateParams('slug', 'locale'));
37
49
  }
38
50
  function generateMetadata(props) {
39
51
  return tslib.__awaiter(this, void 0, void 0, function* () {
40
52
  const { slug, locale } = yield props.params;
41
- const page = mdxContentSource.getPage(slug, locale);
53
+ const source = yield getSource();
54
+ const page = source.getPage(slug, locale);
42
55
  if (!page) {
43
56
  return {
44
57
  title: '404 - Page Not Found',
@@ -7,36 +7,49 @@ import { getAsNeededLocalizedUrl } from '@windrun-huaiin/lib';
7
7
  import { PortableClerkTOC } from './mdx/toc-clerk-portable.mjs';
8
8
 
9
9
  function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSourceDir, githubBaseUrl, copyButtonComponent, siteIcon, FallbackPage, supportedLocales = ['en'], showBreadcrumb = true, showTableOfContent = true, showTableOfContentPopover = false, localePrefixAsNeeded = true, defaultLocale = 'en', }) {
10
+ const getSource = () => __awaiter(this, void 0, void 0, function* () {
11
+ if (typeof mdxContentSource === 'function') {
12
+ return yield mdxContentSource();
13
+ }
14
+ return mdxContentSource;
15
+ });
10
16
  const Page = function Page(_a) {
11
17
  return __awaiter(this, arguments, void 0, function* ({ params }) {
12
- var _b, _c, _d;
18
+ var _b, _c, _d, _e;
13
19
  const { slug, locale } = yield params;
14
- const page = mdxContentSource.getPage(slug, locale);
20
+ const source = yield getSource();
21
+ const page = source.getPage(slug, locale);
15
22
  if (!page) {
16
- console.log('[FumaPage] missing page', { slug, locale, available: (_d = (_c = (_b = mdxContentSource.pageTree) === null || _b === void 0 ? void 0 : _b[locale]) === null || _c === void 0 ? void 0 : _c.children) === null || _d === void 0 ? void 0 : _d.map((c) => c.url) });
23
+ console.log('[FumaPage] missing page', { slug, locale, available: (_d = (_c = (_b = source.pageTree) === null || _b === void 0 ? void 0 : _b[locale]) === null || _c === void 0 ? void 0 : _c.children) === null || _d === void 0 ? void 0 : _d.map((c) => c.url) });
17
24
  return jsx(FallbackPage, { siteIcon: siteIcon });
18
25
  }
19
26
  const path = githubBaseUrl ? `${mdxSourceDir}/${page.path}` : undefined;
20
27
  const tocFooterElement = (jsx(TocFooterWrapper, { lastModified: page.data.date, copyButtonComponent: copyButtonComponent
21
28
  ? cloneElement(copyButtonComponent, { sourceKey })
22
29
  : undefined, editPath: path, githubBaseUrl: githubBaseUrl }));
23
- const MDX = page.data.body;
30
+ const content = typeof page.data.load === 'function'
31
+ ? yield page.data.load(getMDXComponents())
32
+ : {
33
+ body: yield page.data.body({ components: getMDXComponents() }),
34
+ toc: (_e = page.data.toc) !== null && _e !== void 0 ? _e : [],
35
+ };
24
36
  return (jsxs(DocsPage, { breadcrumb: { enabled: showBreadcrumb }, tableOfContent: {
25
37
  enabled: showTableOfContent,
26
38
  single: false,
27
- component: (jsx(PortableClerkTOC, { toc: page.data.toc, footer: tocFooterElement })),
39
+ component: (jsx(PortableClerkTOC, { toc: content.toc, footer: tocFooterElement })),
28
40
  }, tableOfContentPopover: {
29
41
  enabled: false,
30
- }, toc: page.data.toc, article: { className: 'max-sm:pb-16' }, children: [jsx(DocsTitle, { children: page.data.title }), jsx(DocsDescription, { className: "mb-2", children: page.data.description }), jsx(DocsBody, { className: "text-fd-foreground/80", children: jsx(MDX, { components: getMDXComponents() }) })] }));
42
+ }, toc: content.toc, article: { className: 'max-sm:pb-16' }, children: [jsx(DocsTitle, { children: page.data.title }), jsx(DocsDescription, { className: "mb-2", children: page.data.description }), jsx(DocsBody, { className: "text-fd-foreground/80", children: content.body })] }));
31
43
  });
32
44
  };
33
45
  function generateStaticParams() {
34
- return mdxContentSource.generateParams('slug', 'locale');
46
+ return getSource().then((source) => source.generateParams('slug', 'locale'));
35
47
  }
36
48
  function generateMetadata(props) {
37
49
  return __awaiter(this, void 0, void 0, function* () {
38
50
  const { slug, locale } = yield props.params;
39
- const page = mdxContentSource.getPage(slug, locale);
51
+ const source = yield getSource();
52
+ const page = source.getPage(slug, locale);
40
53
  if (!page) {
41
54
  return {
42
55
  title: '404 - Page Not Found',
@@ -11,6 +11,7 @@ var llmUtils = require('@windrun-huaiin/lib/llm-utils');
11
11
  */
12
12
  function LLMCopyHandler(options) {
13
13
  return tslib.__awaiter(this, void 0, void 0, function* () {
14
+ var _a, _b, _c, _d;
14
15
  const { sourceDir, dataSource, requestedPath, locale } = options;
15
16
  // log received parameters
16
17
  console.log(`[LLMCopy] Received, locale=${locale}, path=${requestedPath}`);
@@ -28,8 +29,8 @@ function LLMCopyHandler(options) {
28
29
  console.error(`[LLMCopy] file path information missing in page data for locale=${locale}, path=${requestedPath}`);
29
30
  return { error: 'Page file path information missing', status: 500 };
30
31
  }
31
- const title = page.title;
32
- const description = page.description;
32
+ const title = (_b = (_a = page.data) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : page.title;
33
+ const description = (_d = (_c = page.data) === null || _c === void 0 ? void 0 : _c.description) !== null && _d !== void 0 ? _d : page.description;
33
34
  const relativeMdxFilePath = page.path;
34
35
  const absoluteFilePath = nodePath.join(process.cwd(), sourceDir, relativeMdxFilePath);
35
36
  console.log(`[LLMCopy] Attempting to read MDX content from: ${absoluteFilePath}`);
@@ -9,6 +9,7 @@ import { getLLMText } from '@windrun-huaiin/lib/llm-utils';
9
9
  */
10
10
  function LLMCopyHandler(options) {
11
11
  return __awaiter(this, void 0, void 0, function* () {
12
+ var _a, _b, _c, _d;
12
13
  const { sourceDir, dataSource, requestedPath, locale } = options;
13
14
  // log received parameters
14
15
  console.log(`[LLMCopy] Received, locale=${locale}, path=${requestedPath}`);
@@ -26,8 +27,8 @@ function LLMCopyHandler(options) {
26
27
  console.error(`[LLMCopy] file path information missing in page data for locale=${locale}, path=${requestedPath}`);
27
28
  return { error: 'Page file path information missing', status: 500 };
28
29
  }
29
- const title = page.title;
30
- const description = page.description;
30
+ const title = (_b = (_a = page.data) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : page.title;
31
+ const description = (_d = (_c = page.data) === null || _c === void 0 ? void 0 : _c.description) !== null && _d !== void 0 ? _d : page.description;
31
32
  const relativeMdxFilePath = page.path;
32
33
  const absoluteFilePath = nodePath.join(process.cwd(), sourceDir, relativeMdxFilePath);
33
34
  console.log(`[LLMCopy] Attempting to read MDX content from: ${absoluteFilePath}`);
@@ -12,3 +12,4 @@ export * from './toc-clerk-portable';
12
12
  export * from './banner';
13
13
  export * from './suno-embed';
14
14
  export * from './markdown-component-map';
15
+ export * from './math';
@@ -15,6 +15,7 @@ var tocClerkPortable = require('./toc-clerk-portable.js');
15
15
  var banner = require('./banner.js');
16
16
  var sunoEmbed = require('./suno-embed.js');
17
17
  var markdownComponentMap = require('./markdown-component-map.js');
18
+ var math = require('./math.js');
18
19
 
19
20
 
20
21
 
@@ -38,3 +39,5 @@ exports.PortableClerkTOCScrollArea = tocClerkPortable.PortableClerkTOCScrollArea
38
39
  exports.Banner = banner.Banner;
39
40
  exports.SunoEmbed = sunoEmbed.SunoEmbed;
40
41
  exports.baseMarkdownComponents = markdownComponentMap.baseMarkdownComponents;
42
+ exports.InlineMath = math.InlineMath;
43
+ exports.MathBlock = math.MathBlock;
@@ -13,3 +13,4 @@ export { PortableClerkTOC, PortableClerkTOCItems, PortableClerkTOCPopover, Porta
13
13
  export { Banner } from './banner.mjs';
14
14
  export { SunoEmbed } from './suno-embed.mjs';
15
15
  export { baseMarkdownComponents } from './markdown-component-map.mjs';
16
+ export { InlineMath, MathBlock } from './math.mjs';
@@ -0,0 +1,17 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react';
2
+ type MathSourceProps = {
3
+ children?: ReactNode;
4
+ math?: string;
5
+ formula?: string;
6
+ };
7
+ type Align = 'left' | 'center' | 'right';
8
+ export type MathBlockProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & MathSourceProps & {
9
+ title?: ReactNode;
10
+ titleAlign?: Align;
11
+ };
12
+ export type InlineMathProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children'> & MathSourceProps & {
13
+ align?: Align;
14
+ };
15
+ export declare function MathBlock({ title, titleAlign, children, math, formula, className, ...props }: MathBlockProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare function InlineMath({ children, math, formula, align, className, ...props }: InlineMathProps): import("react/jsx-runtime").JSX.Element;
17
+ export {};
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ var tslib = require('tslib');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var katex = require('katex');
6
+ var utils = require('@windrun-huaiin/lib/utils');
7
+
8
+ const alignClassMap = {
9
+ left: 'text-left justify-start',
10
+ center: 'text-center justify-center',
11
+ right: 'text-right justify-end',
12
+ };
13
+ const textAlignClassMap = {
14
+ left: 'text-left',
15
+ center: 'text-center',
16
+ right: 'text-right',
17
+ };
18
+ function getMathSource({ children, math, formula }) {
19
+ if (typeof math === 'string' && math.trim() !== '')
20
+ return math.trim();
21
+ if (typeof formula === 'string' && formula.trim() !== '')
22
+ return formula.trim();
23
+ if (typeof children === 'string' && children.trim() !== '')
24
+ return children.trim();
25
+ if (Array.isArray(children)) {
26
+ const text = children
27
+ .map((item) => (typeof item === 'string' ? item : ''))
28
+ .join('')
29
+ .trim();
30
+ if (text !== '')
31
+ return text;
32
+ }
33
+ return '';
34
+ }
35
+ function renderMath(source, displayMode) {
36
+ if (source === '')
37
+ return '';
38
+ return katex.renderToString(source, {
39
+ displayMode,
40
+ throwOnError: false,
41
+ output: 'html',
42
+ strict: 'ignore',
43
+ trust: false,
44
+ });
45
+ }
46
+ function MathBlock(_a) {
47
+ var { title, titleAlign = 'center', children, math, formula, className } = _a, props = tslib.__rest(_a, ["title", "titleAlign", "children", "math", "formula", "className"]);
48
+ const source = getMathSource({ children, math, formula });
49
+ const html = renderMath(source, true);
50
+ return (jsxRuntime.jsxs("div", Object.assign({}, props, { className: utils.cn('not-prose my-6 overflow-x-auto rounded-xl border bg-fd-card p-4 text-fd-card-foreground', className), children: [title ? (jsxRuntime.jsx("div", { className: utils.cn('mb-3 text-sm font-medium text-fd-muted-foreground', alignClassMap[titleAlign].split(' ')[0]), children: title })) : null, html ? (jsxRuntime.jsx("div", { className: "min-w-fit [&_.katex-display]:my-0 [&_.katex-display]:overflow-x-auto [&_.katex-display]:overflow-y-hidden", dangerouslySetInnerHTML: { __html: html } })) : (jsxRuntime.jsx("div", { className: "text-sm text-fd-muted-foreground", children: "Empty math block." }))] })));
51
+ }
52
+ function InlineMath(_a) {
53
+ var { children, math, formula, align = 'center', className } = _a, props = tslib.__rest(_a, ["children", "math", "formula", "align", "className"]);
54
+ const source = getMathSource({ children, math, formula });
55
+ const html = renderMath(source, false);
56
+ return (jsxRuntime.jsx("span", Object.assign({}, props, { className: utils.cn('mx-1 inline-flex max-w-full align-middle rounded-md bg-neutral-200 px-2 py-0.5 text-sm leading-none dark:bg-white/20 [&_.katex]:text-inherit', textAlignClassMap[align], className), children: jsxRuntime.jsx("span", { dangerouslySetInnerHTML: { __html: html } }) })));
57
+ }
58
+
59
+ exports.InlineMath = InlineMath;
60
+ exports.MathBlock = MathBlock;