@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.
- package/LICENSE +1 -1
- package/dist/ai/ai-chat-composer.d.ts +2 -0
- package/dist/ai/ai-chat-composer.js +47 -0
- package/dist/ai/ai-chat-composer.mjs +45 -0
- package/dist/ai/ai-markdown.d.ts +2 -0
- package/dist/ai/ai-markdown.js +36 -0
- package/dist/ai/ai-markdown.mjs +34 -0
- package/dist/ai/ai-message-actions.d.ts +2 -0
- package/dist/ai/ai-message-actions.js +14 -0
- package/dist/ai/ai-message-actions.mjs +12 -0
- package/dist/ai/ai-message-bubble.d.ts +2 -0
- package/dist/ai/ai-message-bubble.js +66 -0
- package/dist/ai/ai-message-bubble.mjs +64 -0
- package/dist/ai/ai-message-content.d.ts +2 -0
- package/dist/ai/ai-message-content.js +63 -0
- package/dist/ai/ai-message-content.mjs +61 -0
- package/dist/ai/ai-message-list.d.ts +2 -0
- package/dist/ai/ai-message-list.js +24 -0
- package/dist/ai/ai-message-list.mjs +22 -0
- package/dist/ai/ai-message-meta.d.ts +2 -0
- package/dist/ai/ai-message-meta.js +38 -0
- package/dist/ai/ai-message-meta.mjs +36 -0
- package/dist/ai/ai-status-indicator.d.ts +2 -0
- package/dist/ai/ai-status-indicator.js +51 -0
- package/dist/ai/ai-status-indicator.mjs +49 -0
- package/dist/ai/index.d.ts +11 -0
- package/dist/ai/index.js +33 -0
- package/dist/ai/index.mjs +11 -0
- package/dist/ai/types.d.ts +110 -0
- package/dist/ai/use-ai-conversation.d.ts +13 -0
- package/dist/ai/use-ai-conversation.js +276 -0
- package/dist/ai/use-ai-conversation.mjs +274 -0
- package/dist/clerk/clerk-organization-client.js +2 -2
- package/dist/clerk/clerk-organization-client.mjs +2 -2
- package/dist/clerk/clerk-page-generator.d.ts +1 -1
- package/dist/clerk/clerk-user-client.js +2 -2
- package/dist/clerk/clerk-user-client.mjs +2 -2
- package/dist/clerk/fingerprint/fingerprint-provider.js +9 -9
- package/dist/clerk/fingerprint/fingerprint-provider.mjs +9 -9
- package/dist/fuma/base/custom-header.js +4 -4
- package/dist/fuma/base/custom-header.mjs +4 -4
- package/dist/fuma/mdx/banner.js +3 -3
- package/dist/fuma/mdx/banner.mjs +3 -3
- package/dist/fuma/mdx/fuma-github-info.js +3 -3
- package/dist/fuma/mdx/fuma-github-info.mjs +3 -3
- package/dist/fuma/mdx/gradient-button.js +3 -3
- package/dist/fuma/mdx/gradient-button.mjs +3 -3
- package/dist/fuma/mdx/index.d.ts +1 -0
- package/dist/fuma/mdx/index.js +2 -0
- package/dist/fuma/mdx/index.mjs +1 -0
- package/dist/fuma/mdx/markdown-component-map.d.ts +3 -0
- package/dist/fuma/mdx/markdown-component-map.js +73 -0
- package/dist/fuma/mdx/markdown-component-map.mjs +71 -0
- package/dist/fuma/mdx/mermaid.d.ts +2 -1
- package/dist/fuma/mdx/mermaid.js +130 -6
- package/dist/fuma/mdx/mermaid.mjs +130 -6
- package/dist/fuma/mdx/toc-base.js +4 -4
- package/dist/fuma/mdx/toc-base.mjs +4 -4
- package/dist/fuma/mdx/trophy-card.js +2 -2
- package/dist/fuma/mdx/trophy-card.mjs +2 -2
- package/dist/fuma/mdx/zia-card.js +3 -3
- package/dist/fuma/mdx/zia-card.mjs +3 -3
- package/dist/fuma/mdx/zia-file.js +3 -3
- package/dist/fuma/mdx/zia-file.mjs +3 -3
- package/dist/main/ads-alert-dialog.js +2 -2
- package/dist/main/ads-alert-dialog.mjs +2 -2
- package/dist/main/credit/credit-nav-button.js +2 -2
- package/dist/main/credit/credit-nav-button.mjs +2 -2
- package/dist/main/credit/credit-overview-client.js +4 -4
- package/dist/main/credit/credit-overview-client.mjs +4 -4
- package/dist/main/footer.js +2 -2
- package/dist/main/footer.mjs +2 -2
- package/dist/main/go-to-top.js +2 -2
- package/dist/main/go-to-top.mjs +2 -2
- package/dist/main/hero-media.d.ts +14 -0
- package/dist/main/hero-media.js +12 -0
- package/dist/main/hero-media.mjs +10 -0
- package/dist/main/hero-section.d.ts +10 -0
- package/dist/main/hero-section.js +11 -0
- package/dist/main/hero-section.mjs +9 -0
- package/dist/main/index.d.ts +3 -0
- package/dist/main/index.js +6 -0
- package/dist/main/index.mjs +3 -0
- package/dist/main/info-tooltip.d.ts +8 -0
- package/dist/main/info-tooltip.js +48 -0
- package/dist/main/info-tooltip.mjs +46 -0
- package/dist/main/pill-select/x-pill-select.js +2 -2
- package/dist/main/pill-select/x-pill-select.mjs +2 -2
- package/dist/main/pill-select/x-token-input.js +2 -2
- package/dist/main/pill-select/x-token-input.mjs +2 -2
- package/dist/main/x-button.js +3 -3
- package/dist/main/x-button.mjs +3 -3
- package/package.json +16 -3
- package/src/ai/ai-chat-composer.tsx +187 -0
- package/src/ai/ai-markdown.tsx +45 -0
- package/src/ai/ai-message-actions.tsx +16 -0
- package/src/ai/ai-message-bubble.tsx +138 -0
- package/src/ai/ai-message-content.tsx +149 -0
- package/src/ai/ai-message-list.tsx +59 -0
- package/src/ai/ai-message-meta.tsx +56 -0
- package/src/ai/ai-status-indicator.tsx +61 -0
- package/src/ai/index.ts +13 -0
- package/src/ai/types.ts +131 -0
- package/src/ai/use-ai-conversation.ts +422 -0
- package/src/clerk/clerk-organization-client.tsx +5 -5
- package/src/clerk/clerk-page-generator.tsx +1 -1
- package/src/clerk/clerk-user-client.tsx +4 -4
- package/src/clerk/fingerprint/fingerprint-provider.tsx +34 -22
- package/src/fuma/base/custom-header.tsx +5 -5
- package/src/fuma/mdx/banner.tsx +3 -3
- package/src/fuma/mdx/fuma-github-info.tsx +4 -4
- package/src/fuma/mdx/gradient-button.tsx +3 -3
- package/src/fuma/mdx/index.ts +2 -1
- package/src/fuma/mdx/markdown-component-map.tsx +174 -0
- package/src/fuma/mdx/mermaid.tsx +145 -10
- package/src/fuma/mdx/toc-base.tsx +5 -5
- package/src/fuma/mdx/trophy-card.tsx +2 -2
- package/src/fuma/mdx/zia-card.tsx +3 -3
- package/src/fuma/mdx/zia-file.tsx +3 -3
- package/src/main/ads-alert-dialog.tsx +5 -5
- package/src/main/credit/credit-nav-button.tsx +3 -3
- package/src/main/credit/credit-overview-client.tsx +15 -7
- package/src/main/features.tsx +5 -3
- package/src/main/footer.tsx +4 -5
- package/src/main/go-to-top.tsx +2 -2
- package/src/main/hero-media.tsx +53 -0
- package/src/main/hero-section.tsx +36 -0
- package/src/main/index.ts +5 -0
- package/src/main/info-tooltip.tsx +99 -0
- package/src/main/language-detector.tsx +4 -4
- package/src/main/pill-select/x-pill-select.tsx +2 -2
- package/src/main/pill-select/x-token-input.tsx +2 -2
- 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 {
|
|
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={<
|
|
31
|
+
labelIcon={<D8Icon />}
|
|
32
32
|
/>
|
|
33
33
|
<OrganizationSwitcher.OrganizationProfilePage
|
|
34
|
-
labelIcon={<
|
|
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={<
|
|
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
|
+
}
|
|
@@ -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 {
|
|
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={<
|
|
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={<
|
|
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 {
|
|
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: <
|
|
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: <
|
|
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: <
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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={<
|
|
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={<
|
|
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={<
|
|
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={<
|
|
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={<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
240
|
+
<LanguagesIcon className="size-5" />
|
|
241
241
|
<LanguageToggleText />
|
|
242
|
-
<
|
|
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
|
-
<
|
|
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) => (
|