@windrun-huaiin/third-ui 15.1.0 → 16.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 (133) hide show
  1. package/LICENSE +1 -1
  2. package/dist/ai/ai-chat-composer.d.ts +2 -0
  3. package/dist/ai/ai-chat-composer.js +47 -0
  4. package/dist/ai/ai-chat-composer.mjs +45 -0
  5. package/dist/ai/ai-markdown.d.ts +2 -0
  6. package/dist/ai/ai-markdown.js +36 -0
  7. package/dist/ai/ai-markdown.mjs +34 -0
  8. package/dist/ai/ai-message-actions.d.ts +2 -0
  9. package/dist/ai/ai-message-actions.js +14 -0
  10. package/dist/ai/ai-message-actions.mjs +12 -0
  11. package/dist/ai/ai-message-bubble.d.ts +2 -0
  12. package/dist/ai/ai-message-bubble.js +66 -0
  13. package/dist/ai/ai-message-bubble.mjs +64 -0
  14. package/dist/ai/ai-message-content.d.ts +2 -0
  15. package/dist/ai/ai-message-content.js +63 -0
  16. package/dist/ai/ai-message-content.mjs +61 -0
  17. package/dist/ai/ai-message-list.d.ts +2 -0
  18. package/dist/ai/ai-message-list.js +24 -0
  19. package/dist/ai/ai-message-list.mjs +22 -0
  20. package/dist/ai/ai-message-meta.d.ts +2 -0
  21. package/dist/ai/ai-message-meta.js +38 -0
  22. package/dist/ai/ai-message-meta.mjs +36 -0
  23. package/dist/ai/ai-status-indicator.d.ts +2 -0
  24. package/dist/ai/ai-status-indicator.js +51 -0
  25. package/dist/ai/ai-status-indicator.mjs +49 -0
  26. package/dist/ai/index.d.ts +11 -0
  27. package/dist/ai/index.js +33 -0
  28. package/dist/ai/index.mjs +11 -0
  29. package/dist/ai/types.d.ts +110 -0
  30. package/dist/ai/use-ai-conversation.d.ts +13 -0
  31. package/dist/ai/use-ai-conversation.js +276 -0
  32. package/dist/ai/use-ai-conversation.mjs +274 -0
  33. package/dist/clerk/clerk-organization-client.js +2 -2
  34. package/dist/clerk/clerk-organization-client.mjs +2 -2
  35. package/dist/clerk/clerk-page-generator.d.ts +1 -1
  36. package/dist/clerk/clerk-user-client.js +2 -2
  37. package/dist/clerk/clerk-user-client.mjs +2 -2
  38. package/dist/clerk/fingerprint/fingerprint-provider.js +9 -9
  39. package/dist/clerk/fingerprint/fingerprint-provider.mjs +9 -9
  40. package/dist/fuma/base/custom-header.js +4 -4
  41. package/dist/fuma/base/custom-header.mjs +4 -4
  42. package/dist/fuma/mdx/banner.js +3 -3
  43. package/dist/fuma/mdx/banner.mjs +3 -3
  44. package/dist/fuma/mdx/fuma-github-info.js +3 -3
  45. package/dist/fuma/mdx/fuma-github-info.mjs +3 -3
  46. package/dist/fuma/mdx/gradient-button.js +3 -3
  47. package/dist/fuma/mdx/gradient-button.mjs +3 -3
  48. package/dist/fuma/mdx/index.d.ts +1 -0
  49. package/dist/fuma/mdx/index.js +2 -0
  50. package/dist/fuma/mdx/index.mjs +1 -0
  51. package/dist/fuma/mdx/markdown-component-map.d.ts +3 -0
  52. package/dist/fuma/mdx/markdown-component-map.js +73 -0
  53. package/dist/fuma/mdx/markdown-component-map.mjs +71 -0
  54. package/dist/fuma/mdx/mermaid.d.ts +2 -1
  55. package/dist/fuma/mdx/mermaid.js +130 -6
  56. package/dist/fuma/mdx/mermaid.mjs +130 -6
  57. package/dist/fuma/mdx/toc-base.js +4 -4
  58. package/dist/fuma/mdx/toc-base.mjs +4 -4
  59. package/dist/fuma/mdx/trophy-card.js +2 -2
  60. package/dist/fuma/mdx/trophy-card.mjs +2 -2
  61. package/dist/fuma/mdx/zia-card.js +3 -3
  62. package/dist/fuma/mdx/zia-card.mjs +3 -3
  63. package/dist/fuma/mdx/zia-file.js +3 -3
  64. package/dist/fuma/mdx/zia-file.mjs +3 -3
  65. package/dist/main/ads-alert-dialog.js +2 -2
  66. package/dist/main/ads-alert-dialog.mjs +2 -2
  67. package/dist/main/credit/credit-nav-button.js +2 -2
  68. package/dist/main/credit/credit-nav-button.mjs +2 -2
  69. package/dist/main/credit/credit-overview-client.js +4 -4
  70. package/dist/main/credit/credit-overview-client.mjs +4 -4
  71. package/dist/main/footer.js +2 -2
  72. package/dist/main/footer.mjs +2 -2
  73. package/dist/main/go-to-top.js +2 -2
  74. package/dist/main/go-to-top.mjs +2 -2
  75. package/dist/main/hero-media.d.ts +14 -0
  76. package/dist/main/hero-media.js +12 -0
  77. package/dist/main/hero-media.mjs +10 -0
  78. package/dist/main/hero-section.d.ts +10 -0
  79. package/dist/main/hero-section.js +11 -0
  80. package/dist/main/hero-section.mjs +9 -0
  81. package/dist/main/index.d.ts +3 -0
  82. package/dist/main/index.js +6 -0
  83. package/dist/main/index.mjs +3 -0
  84. package/dist/main/info-tooltip.d.ts +8 -0
  85. package/dist/main/info-tooltip.js +48 -0
  86. package/dist/main/info-tooltip.mjs +46 -0
  87. package/dist/main/pill-select/x-pill-select.js +2 -2
  88. package/dist/main/pill-select/x-pill-select.mjs +2 -2
  89. package/dist/main/pill-select/x-token-input.js +2 -2
  90. package/dist/main/pill-select/x-token-input.mjs +2 -2
  91. package/dist/main/x-button.js +3 -3
  92. package/dist/main/x-button.mjs +3 -3
  93. package/package.json +16 -3
  94. package/src/ai/ai-chat-composer.tsx +187 -0
  95. package/src/ai/ai-markdown.tsx +45 -0
  96. package/src/ai/ai-message-actions.tsx +16 -0
  97. package/src/ai/ai-message-bubble.tsx +138 -0
  98. package/src/ai/ai-message-content.tsx +149 -0
  99. package/src/ai/ai-message-list.tsx +59 -0
  100. package/src/ai/ai-message-meta.tsx +56 -0
  101. package/src/ai/ai-status-indicator.tsx +61 -0
  102. package/src/ai/index.ts +13 -0
  103. package/src/ai/types.ts +131 -0
  104. package/src/ai/use-ai-conversation.ts +422 -0
  105. package/src/clerk/clerk-organization-client.tsx +5 -5
  106. package/src/clerk/clerk-page-generator.tsx +1 -1
  107. package/src/clerk/clerk-user-client.tsx +4 -4
  108. package/src/clerk/fingerprint/fingerprint-provider.tsx +34 -22
  109. package/src/fuma/base/custom-header.tsx +5 -5
  110. package/src/fuma/mdx/banner.tsx +3 -3
  111. package/src/fuma/mdx/fuma-github-info.tsx +4 -4
  112. package/src/fuma/mdx/gradient-button.tsx +3 -3
  113. package/src/fuma/mdx/index.ts +2 -1
  114. package/src/fuma/mdx/markdown-component-map.tsx +174 -0
  115. package/src/fuma/mdx/mermaid.tsx +145 -10
  116. package/src/fuma/mdx/toc-base.tsx +5 -5
  117. package/src/fuma/mdx/trophy-card.tsx +2 -2
  118. package/src/fuma/mdx/zia-card.tsx +3 -3
  119. package/src/fuma/mdx/zia-file.tsx +3 -3
  120. package/src/main/ads-alert-dialog.tsx +5 -5
  121. package/src/main/credit/credit-nav-button.tsx +3 -3
  122. package/src/main/credit/credit-overview-client.tsx +15 -7
  123. package/src/main/features.tsx +5 -3
  124. package/src/main/footer.tsx +4 -5
  125. package/src/main/go-to-top.tsx +2 -2
  126. package/src/main/hero-media.tsx +53 -0
  127. package/src/main/hero-section.tsx +36 -0
  128. package/src/main/index.ts +5 -0
  129. package/src/main/info-tooltip.tsx +99 -0
  130. package/src/main/language-detector.tsx +4 -4
  131. package/src/main/pill-select/x-pill-select.tsx +2 -2
  132. package/src/main/pill-select/x-token-input.tsx +2 -2
  133. package/src/main/x-button.tsx +4 -4
@@ -0,0 +1,422 @@
1
+ 'use client';
2
+
3
+ import {
4
+ type AIErrorPayload,
5
+ type AIRuntimeRequest,
6
+ type AIStreamEvent,
7
+ type ConversationMessage,
8
+ } from '@windrun-huaiin/contracts/ai';
9
+ import { startTransition, useRef, useState } from 'react';
10
+ import type {
11
+ AIMessageRuntimeMetadata,
12
+ AIConversationOptions,
13
+ AIConversationState,
14
+ SendMessageInput,
15
+ } from './types';
16
+
17
+ function createId(prefix: string) {
18
+ try {
19
+ return `${prefix}-${crypto.randomUUID()}`;
20
+ } catch {
21
+ return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
22
+ }
23
+ }
24
+
25
+ function getAssistantMessageText(message: ConversationMessage) {
26
+ return message.parts
27
+ .flatMap((part) => (part.type === 'text' ? [part.text] : []))
28
+ .join('');
29
+ }
30
+
31
+ function createUserMessage(input: SendMessageInput): ConversationMessage {
32
+ return {
33
+ id: createId('user'),
34
+ role: 'user',
35
+ parts: [{ type: 'text', text: input.text }],
36
+ createdAt: Date.now(),
37
+ metadata: input.metadata,
38
+ };
39
+ }
40
+
41
+ function getRuntimeMetadata(message: ConversationMessage): AIMessageRuntimeMetadata {
42
+ const metadata = message.metadata?.aiRuntime;
43
+ if (!metadata || typeof metadata !== 'object') {
44
+ return {};
45
+ }
46
+
47
+ return metadata as AIMessageRuntimeMetadata;
48
+ }
49
+
50
+ function withRuntimeMetadata(
51
+ message: ConversationMessage,
52
+ nextMetadata: AIMessageRuntimeMetadata,
53
+ ): ConversationMessage {
54
+ return {
55
+ ...message,
56
+ metadata: {
57
+ ...(message.metadata ?? {}),
58
+ aiRuntime: {
59
+ ...getRuntimeMetadata(message),
60
+ ...nextMetadata,
61
+ },
62
+ },
63
+ };
64
+ }
65
+
66
+ function createAssistantPlaceholder(requestStartedAt: number): ConversationMessage {
67
+ return {
68
+ id: createId('assistant'),
69
+ role: 'assistant',
70
+ parts: [{ type: 'text', text: '' }],
71
+ status: 'streaming',
72
+ createdAt: Date.now(),
73
+ metadata: {
74
+ aiRuntime: {
75
+ requestStartedAt,
76
+ },
77
+ },
78
+ };
79
+ }
80
+
81
+ function defaultTransport(endpoint: string) {
82
+ return async (input: AIRuntimeRequest, signal: AbortSignal) => {
83
+ return fetch(endpoint, {
84
+ method: 'POST',
85
+ signal,
86
+ headers: {
87
+ 'Content-Type': 'application/json',
88
+ },
89
+ body: JSON.stringify(input),
90
+ });
91
+ };
92
+ }
93
+
94
+ function updateAssistantText(
95
+ messages: ConversationMessage[],
96
+ messageId: string,
97
+ textDelta: string,
98
+ ): ConversationMessage[] {
99
+ return messages.map((message): ConversationMessage => {
100
+ if (message.id !== messageId) {
101
+ return message;
102
+ }
103
+
104
+ const currentText = getAssistantMessageText(message);
105
+ const runtimeMetadata = getRuntimeMetadata(message);
106
+ const now = Date.now();
107
+
108
+ return withRuntimeMetadata(
109
+ {
110
+ ...message,
111
+ parts: [{ type: 'text', text: currentText + textDelta }],
112
+ status: 'streaming',
113
+ },
114
+ runtimeMetadata.firstTokenAt
115
+ ? {}
116
+ : {
117
+ firstTokenAt: now,
118
+ firstTokenMs: runtimeMetadata.requestStartedAt
119
+ ? now - runtimeMetadata.requestStartedAt
120
+ : undefined,
121
+ },
122
+ );
123
+ });
124
+ }
125
+
126
+ function updateMessageStarted(
127
+ messages: ConversationMessage[],
128
+ placeholderId: string,
129
+ messageId: string,
130
+ ): ConversationMessage[] {
131
+ return messages.map((message): ConversationMessage => {
132
+ if (message.id !== placeholderId) {
133
+ return message;
134
+ }
135
+
136
+ return withRuntimeMetadata(
137
+ {
138
+ ...message,
139
+ id: messageId,
140
+ },
141
+ {
142
+ streamStartedAt: Date.now(),
143
+ },
144
+ );
145
+ });
146
+ }
147
+
148
+ function updateMessageStatus(
149
+ messages: ConversationMessage[],
150
+ messageId: string,
151
+ status: ConversationMessage['status'],
152
+ ): ConversationMessage[] {
153
+ return messages.map((message): ConversationMessage => {
154
+ if (message.id !== messageId) {
155
+ return message;
156
+ }
157
+
158
+ const now = Date.now();
159
+ const runtimeMetadata = getRuntimeMetadata(message);
160
+
161
+ return withRuntimeMetadata(
162
+ {
163
+ ...message,
164
+ status,
165
+ },
166
+ {
167
+ completedAt: now,
168
+ totalMs: runtimeMetadata.requestStartedAt
169
+ ? now - runtimeMetadata.requestStartedAt
170
+ : undefined,
171
+ },
172
+ );
173
+ });
174
+ }
175
+
176
+ function applyErrorToLatestAssistant(
177
+ messages: ConversationMessage[],
178
+ error: AIErrorPayload,
179
+ ) {
180
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
181
+ const message = messages[index];
182
+ if (message.role !== 'assistant') {
183
+ continue;
184
+ }
185
+
186
+ const nextMessages = [...messages];
187
+ const now = Date.now();
188
+ const runtimeMetadata = getRuntimeMetadata(message);
189
+ nextMessages[index] = withRuntimeMetadata(
190
+ {
191
+ ...message,
192
+ status: error.status,
193
+ failureReason: error.failureReason,
194
+ errorMessage: error.error,
195
+ upstreamStatusCode: error.upstreamStatusCode,
196
+ },
197
+ {
198
+ completedAt: now,
199
+ totalMs: runtimeMetadata.requestStartedAt
200
+ ? now - runtimeMetadata.requestStartedAt
201
+ : undefined,
202
+ },
203
+ );
204
+ return nextMessages;
205
+ }
206
+
207
+ return messages;
208
+ }
209
+
210
+ async function consumeEventStream(
211
+ response: Response,
212
+ onEvent: (event: AIStreamEvent) => void,
213
+ ) {
214
+ if (!response.body) {
215
+ throw new Error('Missing response body');
216
+ }
217
+
218
+ const reader = response.body.getReader();
219
+ const decoder = new TextDecoder();
220
+ let buffer = '';
221
+
222
+ for (;;) {
223
+ const chunk = await reader.read();
224
+ if (chunk.done) {
225
+ break;
226
+ }
227
+
228
+ buffer += decoder.decode(chunk.value, { stream: true });
229
+ const frames = buffer.split('\n\n');
230
+ buffer = frames.pop() ?? '';
231
+
232
+ for (const frame of frames) {
233
+ const line = frame
234
+ .split('\n')
235
+ .find((item) => item.startsWith('data: '));
236
+
237
+ if (!line) {
238
+ continue;
239
+ }
240
+
241
+ onEvent(JSON.parse(line.slice(6)) as AIStreamEvent);
242
+ }
243
+ }
244
+ }
245
+
246
+ export function useAIConversation(options: AIConversationOptions) {
247
+ const transport = options.transport ?? defaultTransport(options.endpoint);
248
+ const [state, setState] = useState<AIConversationState>({
249
+ sessionId: options.initialSessionId,
250
+ messages: options.initialMessages ?? [],
251
+ isStreaming: false,
252
+ });
253
+ const abortRef = useRef<AbortController | null>(null);
254
+
255
+ const sendMessage = async (input: SendMessageInput) => {
256
+ const text = input.text.trim();
257
+ if (!text || state.isStreaming) {
258
+ return;
259
+ }
260
+
261
+ const userMessage = createUserMessage(input);
262
+ const requestStartedAt = Date.now();
263
+ const assistantMessage = createAssistantPlaceholder(requestStartedAt);
264
+
265
+ startTransition(() => {
266
+ setState((current) => ({
267
+ ...current,
268
+ messages: [...current.messages, userMessage, assistantMessage],
269
+ isStreaming: true,
270
+ error: undefined,
271
+ }));
272
+ });
273
+
274
+ const controller = new AbortController();
275
+ abortRef.current = controller;
276
+
277
+ try {
278
+ const response = await transport(
279
+ {
280
+ sessionId: state.sessionId,
281
+ messages: [...state.messages, userMessage],
282
+ modelName: options.modelName,
283
+ metadata: {
284
+ ...(options.metadata ?? {}),
285
+ ...(input.metadata ?? {}),
286
+ },
287
+ },
288
+ controller.signal,
289
+ );
290
+
291
+ if (!response.ok) {
292
+ const payload = (await response.json()) as AIErrorPayload;
293
+ throw new Error(payload.error || 'AI request failed');
294
+ }
295
+
296
+ await consumeEventStream(response, (event) => {
297
+ options.onEvent?.(event);
298
+
299
+ startTransition(() => {
300
+ setState((current) => {
301
+ if (event.type === 'message_started') {
302
+ return {
303
+ ...current,
304
+ messages: updateMessageStarted(current.messages, assistantMessage.id, event.messageId),
305
+ };
306
+ }
307
+
308
+ if (event.type === 'text_delta') {
309
+ return {
310
+ ...current,
311
+ messages: updateAssistantText(current.messages, event.messageId, event.text),
312
+ };
313
+ }
314
+
315
+ if (event.type === 'part') {
316
+ return {
317
+ ...current,
318
+ messages: current.messages.map((message): ConversationMessage => {
319
+ if (message.id !== event.messageId) {
320
+ return message;
321
+ }
322
+
323
+ return {
324
+ ...message,
325
+ parts: [...message.parts, event.part],
326
+ };
327
+ }),
328
+ };
329
+ }
330
+
331
+ if (event.type === 'message_completed') {
332
+ return {
333
+ ...current,
334
+ isStreaming: false,
335
+ messages: updateMessageStatus(current.messages, event.messageId, 'completed'),
336
+ };
337
+ }
338
+
339
+ if (event.type === 'error') {
340
+ return {
341
+ ...current,
342
+ isStreaming: false,
343
+ error: event.error.error,
344
+ messages: applyErrorToLatestAssistant(current.messages, event.error),
345
+ };
346
+ }
347
+
348
+ return current;
349
+ });
350
+ });
351
+ });
352
+
353
+ startTransition(() => {
354
+ setState((current) => ({
355
+ ...current,
356
+ isStreaming: false,
357
+ }));
358
+ });
359
+ } catch (error) {
360
+ const nextError = error instanceof Error ? error : new Error('AI request failed');
361
+ options.onError?.(nextError);
362
+
363
+ startTransition(() => {
364
+ setState((current) => ({
365
+ ...current,
366
+ isStreaming: false,
367
+ error: nextError.message,
368
+ messages: applyErrorToLatestAssistant(current.messages, {
369
+ error: nextError.message,
370
+ status: controller.signal.aborted ? 'request_aborted' : 'failed',
371
+ failureReason: 'unknown',
372
+ upstreamStatusCode: controller.signal.aborted ? 499 : 500,
373
+ }),
374
+ }));
375
+ });
376
+ } finally {
377
+ abortRef.current = null;
378
+ }
379
+ };
380
+
381
+ const stopGeneration = () => {
382
+ abortRef.current?.abort();
383
+ };
384
+
385
+ const resetConversation = () => {
386
+ startTransition(() => {
387
+ setState({
388
+ sessionId: options.initialSessionId,
389
+ messages: options.initialMessages ?? [],
390
+ isStreaming: false,
391
+ });
392
+ });
393
+ };
394
+
395
+ const removeMessage = (messageId: string) => {
396
+ startTransition(() => {
397
+ setState((current) => ({
398
+ ...current,
399
+ messages: current.messages.filter((message) => message.id !== messageId),
400
+ }));
401
+ });
402
+ };
403
+
404
+ const loadConversation = (messages: ConversationMessage[], sessionId?: string) => {
405
+ startTransition(() => {
406
+ setState({
407
+ sessionId,
408
+ messages,
409
+ isStreaming: false,
410
+ });
411
+ });
412
+ };
413
+
414
+ return {
415
+ ...state,
416
+ sendMessage,
417
+ stopGeneration,
418
+ resetConversation,
419
+ removeMessage,
420
+ loadConversation,
421
+ };
422
+ }
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { OrganizationSwitcher } from '@clerk/nextjs';
4
- import { globalLucideIcons as icons } from '@windrun-huaiin/base-ui/components/server';
4
+ import { D8Icon, ReceiptTextIcon, ShieldUserIcon } from '@windrun-huaiin/base-ui/icons';
5
5
 
6
6
  interface ClerkOrganizationData {
7
7
  homepage: string;
@@ -28,17 +28,17 @@ export function ClerkOrganizationClient({ data }: { data: ClerkOrganizationData
28
28
  <OrganizationSwitcher.OrganizationProfilePage
29
29
  label={data.homepage}
30
30
  url="/"
31
- labelIcon={<icons.D8 />}
31
+ labelIcon={<D8Icon />}
32
32
  />
33
33
  <OrganizationSwitcher.OrganizationProfilePage
34
- labelIcon={<icons.ReceiptText />}
34
+ labelIcon={<ReceiptTextIcon />}
35
35
  label={data.terms}
36
36
  url={`/${data.locale}/legal/terms`}
37
37
  >
38
38
  </OrganizationSwitcher.OrganizationProfilePage>
39
39
 
40
40
  <OrganizationSwitcher.OrganizationProfilePage
41
- labelIcon={<icons.ShieldUser />}
41
+ labelIcon={<ShieldUserIcon />}
42
42
  label={data.privacy}
43
43
  url={`/${data.locale}/legal/privacy`}
44
44
  >
@@ -47,4 +47,4 @@ export function ClerkOrganizationClient({ data }: { data: ClerkOrganizationData
47
47
  </div>
48
48
  </div>
49
49
  );
50
- }
50
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @license
3
3
  * MIT License
4
- * Copyright (c) 2025 D8ger
4
+ * Copyright (c) 2026 D8ger
5
5
  *
6
6
  * This source code is licensed under the MIT license found in the
7
7
  * LICENSE file in the root directory of this source tree.
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState } from 'react';
4
4
  import { ClerkLoaded, ClerkLoading, SignInButton, UserButton, useAuth } from "@clerk/nextjs";
5
- import { globalLucideIcons as icons } from '@windrun-huaiin/base-ui/components/server';
5
+ import { ReceiptTextIcon, ShieldUserIcon } from '@windrun-huaiin/base-ui/icons';
6
6
  import { themeButtonGradientClass } from '@windrun-huaiin/base-ui/lib';
7
7
  import { SignUpButtonWithFingerprint } from './signup-button-with-fingerprint-client';
8
8
 
@@ -70,12 +70,12 @@ export function ClerkUserClient({ data }: { data: ClerkUserData }) {
70
70
  <UserButton.MenuItems>
71
71
  <UserButton.Action label="manageAccount" />
72
72
  <UserButton.Link
73
- labelIcon={<icons.ReceiptText className="size-4 fill-none stroke-(--clerk-icon-stroke-color)" />}
73
+ labelIcon={<ReceiptTextIcon className="size-4 fill-none stroke-(--clerk-icon-stroke-color)" />}
74
74
  label={data.terms}
75
75
  href={`/${data.locale}/legal/terms`}>
76
76
  </UserButton.Link>
77
77
  <UserButton.Link
78
- labelIcon={<icons.ShieldUser className="size-4 fill-none stroke-(--clerk-icon-stroke-color)" />}
78
+ labelIcon={<ShieldUserIcon className="size-4 fill-none stroke-(--clerk-icon-stroke-color)" />}
79
79
  label={data.privacy}
80
80
  href={`/${data.locale}/legal/privacy`}>
81
81
  </UserButton.Link>
@@ -86,4 +86,4 @@ export function ClerkUserClient({ data }: { data: ClerkUserData }) {
86
86
  </ClerkLoaded>
87
87
  </div>
88
88
  );
89
- }
89
+ }
@@ -1,6 +1,18 @@
1
1
  'use client';
2
2
 
3
- import { globalLucideIcons as icons } from '@windrun-huaiin/base-ui/components/server';
3
+ import {
4
+ BellIcon,
5
+ CoinsIcon,
6
+ DatabaseZapIcon,
7
+ FingerprintIcon,
8
+ GemIcon,
9
+ GiftIcon,
10
+ LightbulbIcon,
11
+ RefreshCcwIcon,
12
+ Settings2Icon,
13
+ ShieldUserIcon,
14
+ XIcon,
15
+ } from '@windrun-huaiin/base-ui/icons';
4
16
  import { themeButtonGradientClass, themeButtonGradientHoverClass, themeIconColor } from '@windrun-huaiin/base-ui/lib';
5
17
  import { cn } from '@windrun-huaiin/lib/utils';
6
18
  import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
@@ -125,7 +137,7 @@ export function FingerprintStatus() {
125
137
  {
126
138
  key: 'paid',
127
139
  label: 'Paid',
128
- icon: <icons.Settings2 className="size-4 text-green-500 dark:text-green-300" />,
140
+ icon: <Settings2Icon className="size-4 text-green-500 dark:text-green-300" />,
129
141
  balance: xCredit.balancePaid,
130
142
  total: xCredit.totalPaidLimit,
131
143
  start: xCredit.paidStart,
@@ -134,7 +146,7 @@ export function FingerprintStatus() {
134
146
  {
135
147
  key: 'oneTimePaid',
136
148
  label: 'OneTimePaid',
137
- icon: <icons.Coins className="size-4 text-amber-500 dark:text-amber-300" />,
149
+ icon: <CoinsIcon className="size-4 text-amber-500 dark:text-amber-300" />,
138
150
  balance: xCredit.balanceOneTimePaid,
139
151
  total: xCredit.totalOneTimePaidLimit,
140
152
  start: xCredit.oneTimePaidStart,
@@ -143,7 +155,7 @@ export function FingerprintStatus() {
143
155
  {
144
156
  key: 'free',
145
157
  label: 'Free',
146
- icon: <icons.Gift className="size-4 text-purple-500 dark:text-purple-300" />,
158
+ icon: <GiftIcon className="size-4 text-purple-500 dark:text-purple-300" />,
147
159
  balance: xCredit.balanceFree,
148
160
  total: xCredit.totalFreeLimit,
149
161
  start: xCredit.freeStart,
@@ -314,14 +326,14 @@ export function FingerprintStatus() {
314
326
  onClick={handleToggle}
315
327
  type="button"
316
328
  aria-label="Fingerprint debug panel"
317
- className={cn(
318
- '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',
319
- themeButtonGradientClass,
320
- themeButtonGradientHoverClass,
321
- 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300',
322
- )}
323
- >
324
- <icons.Lightbulb className="size-6 text-white" />
329
+ className={cn(
330
+ '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',
331
+ themeButtonGradientClass,
332
+ themeButtonGradientHoverClass,
333
+ 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300',
334
+ )}
335
+ >
336
+ <LightbulbIcon className="size-6 text-white" />
325
337
  </button>
326
338
  )}
327
339
 
@@ -341,7 +353,7 @@ export function FingerprintStatus() {
341
353
  <header className="mb-4">
342
354
  <div className="flex items-start justify-between gap-3">
343
355
  <div className={cn("flex items-center gap-2 text-base font-bold tracking-wider", themeIconColor)}>
344
- <icons.ShieldUser className="size-4" />
356
+ <ShieldUserIcon className="size-4" />
345
357
  Fingerprint Debug Panel
346
358
  </div>
347
359
  <div className="flex items-center gap-2">
@@ -380,7 +392,7 @@ export function FingerprintStatus() {
380
392
  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"
381
393
  onClick={() => setIsOpen(false)}
382
394
  >
383
- <icons.X className="size-4" />
395
+ <XIcon className="size-4" />
384
396
  </button>
385
397
  </div>
386
398
  </div>
@@ -390,7 +402,7 @@ export function FingerprintStatus() {
390
402
  {panelMode === 'info' ? (
391
403
  <>
392
404
  <PanelSection
393
- icon={<icons.Fingerprint className="size-4" />}
405
+ icon={<FingerprintIcon className="size-4" />}
394
406
  title="User"
395
407
  rightInfo={<StatusTag value={userStatus} />}
396
408
  items={[
@@ -406,7 +418,7 @@ export function FingerprintStatus() {
406
418
 
407
419
  <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">
408
420
  <PanelHeader
409
- icon={<icons.Gem className="size-4" />}
421
+ icon={<GemIcon className="size-4" />}
410
422
  title="Credits Info"
411
423
  rightInfo={<span className={cn("font-semibold", themeIconColor)}>{totalCredits}</span>}
412
424
  />
@@ -439,13 +451,13 @@ export function FingerprintStatus() {
439
451
  );
440
452
  })
441
453
  ) : (
442
- <EmptyPlaceholder label="No Credits Yet" icon={<icons.DatabaseZap className="size-4" />} />
454
+ <EmptyPlaceholder label="No Credits Yet" icon={<DatabaseZapIcon className="size-4" />} />
443
455
  )}
444
456
  </div>
445
457
  </div>
446
458
 
447
459
  <PanelSection
448
- icon={<icons.Bell className="size-4" />}
460
+ icon={<BellIcon className="size-4" />}
449
461
  title="Subscription"
450
462
  rightInfo={<StatusTag value={subStatus} />}
451
463
  items={[
@@ -461,7 +473,7 @@ export function FingerprintStatus() {
461
473
  ) : (
462
474
  <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">
463
475
  <PanelHeader
464
- icon={<icons.DatabaseZap className="size-4" />}
476
+ icon={<DatabaseZapIcon className="size-4" />}
465
477
  title="Concurrent Base Info"
466
478
  rightInfo={<StatusTag value={isRunningTest ? 'pending' : 'idle'} />}
467
479
  />
@@ -484,7 +496,7 @@ export function FingerprintStatus() {
484
496
  aria-label="Generate new test fingerprint"
485
497
  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"
486
498
  >
487
- <icons.RefreshCcw className="size-4" />
499
+ <RefreshCcwIcon className="size-4" />
488
500
  </button>
489
501
  </div>
490
502
  </div>
@@ -528,7 +540,7 @@ export function FingerprintStatus() {
528
540
  {error && (
529
541
  <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">
530
542
  <div className="flex items-start gap-2">
531
- <icons.X className="mt-0.5 size-4 shrink-0" />
543
+ <XIcon className="mt-0.5 size-4 shrink-0" />
532
544
  <span>{error}</span>
533
545
  </div>
534
546
  <button
@@ -537,7 +549,7 @@ export function FingerprintStatus() {
537
549
  onClick={clearError}
538
550
  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"
539
551
  >
540
- <icons.X className="size-4" />
552
+ <XIcon className="size-4" />
541
553
  </button>
542
554
  </div>
543
555
  )}
@@ -9,8 +9,8 @@ import {
9
9
  useState,
10
10
  } from 'react';
11
11
  import { cva } from 'class-variance-authority';
12
+ import { ChevronDownIcon, LanguagesIcon } from '@windrun-huaiin/base-ui/icons';
12
13
  import Link from 'fumadocs-core/link';
13
- import { globalLucideIcons as icons } from '@windrun-huaiin/base-ui/components/server';
14
14
  import { HomeLayoutProps } from 'fumadocs-ui/layouts/home';
15
15
  import {
16
16
  BaseLinkItem,
@@ -185,7 +185,7 @@ export function CustomHomeHeader({
185
185
  : null,
186
186
  i18n: i18n ? (
187
187
  <CompactLanguageToggle>
188
- <icons.Languages className="size-5" />
188
+ <LanguagesIcon className="size-5" />
189
189
  </CompactLanguageToggle>
190
190
  ) : null,
191
191
  secondary: desktopSecondaryDisplayItems.length ? (
@@ -237,9 +237,9 @@ export function CustomHomeHeader({
237
237
  separator: <div role="separator" className="flex-1" />,
238
238
  i18n: i18n ? (
239
239
  <CompactLanguageToggle>
240
- <icons.Languages className="size-5" />
240
+ <LanguagesIcon className="size-5" />
241
241
  <LanguageToggleText />
242
- <icons.ChevronDown className="size-3 text-fd-muted-foreground" />
242
+ <ChevronDownIcon className="size-3 text-fd-muted-foreground" />
243
243
  </CompactLanguageToggle>
244
244
  ) : null,
245
245
  theme:
@@ -268,7 +268,7 @@ export function CustomHomeHeader({
268
268
  )}
269
269
  enableHover={nav.enableHoverToOpen}
270
270
  >
271
- <icons.ChevronDown className="transition-transform duration-300 group-data-[state=open]:rotate-180" />
271
+ <ChevronDownIcon className="transition-transform duration-300 group-data-[state=open]:rotate-180" />
272
272
  </MenuTrigger>
273
273
  <MenuContent className="sm:flex-row sm:items-center sm:justify-end">
274
274
  {primaryMenuItems.map((item, i) => (