@windrun-huaiin/third-ui 15.1.1 → 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/index.d.ts +1 -0
- package/dist/main/index.js +2 -0
- package/dist/main/index.mjs +1 -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/index.ts +2 -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,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ConversationMessage } from '@windrun-huaiin/contracts/ai';
|
|
4
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
5
|
+
import { useEffect, useRef } from 'react';
|
|
6
|
+
import type { AIMessageListProps } from './types';
|
|
7
|
+
import { AIMessageBubble } from './ai-message-bubble';
|
|
8
|
+
|
|
9
|
+
export function AIMessageList({
|
|
10
|
+
messages,
|
|
11
|
+
className,
|
|
12
|
+
contentClassName,
|
|
13
|
+
emptyText = 'No messages yet.',
|
|
14
|
+
emptyState,
|
|
15
|
+
autoScroll = true,
|
|
16
|
+
scrollBehavior = 'smooth',
|
|
17
|
+
renderMessage,
|
|
18
|
+
}: AIMessageListProps) {
|
|
19
|
+
const bottomRef = useRef<HTMLDivElement | null>(null);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!autoScroll) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
bottomRef.current?.scrollIntoView({ behavior: scrollBehavior, block: 'end' });
|
|
27
|
+
}, [autoScroll, messages, scrollBehavior]);
|
|
28
|
+
|
|
29
|
+
const content = messages.length === 0
|
|
30
|
+
? (
|
|
31
|
+
emptyState ?? (
|
|
32
|
+
<div className="rounded-2xl border border-dashed border-border p-6 text-sm text-muted-foreground">
|
|
33
|
+
{emptyText}
|
|
34
|
+
</div>
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
: (
|
|
38
|
+
messages.map((message: ConversationMessage) => (
|
|
39
|
+
<div key={message.id}>
|
|
40
|
+
{renderMessage ? renderMessage(message) : <AIMessageBubble message={message} />}
|
|
41
|
+
</div>
|
|
42
|
+
))
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className={cn('min-h-0 flex-1 overflow-y-auto', className)}>
|
|
47
|
+
<div
|
|
48
|
+
className={cn(
|
|
49
|
+
'mx-auto flex min-h-full w-full max-w-5xl flex-col gap-5 px-1',
|
|
50
|
+
messages.length === 0 ? 'justify-center' : 'justify-end',
|
|
51
|
+
contentClassName,
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
{content}
|
|
55
|
+
<div ref={bottomRef} />
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
4
|
+
import { AIStatusIndicator } from './ai-status-indicator';
|
|
5
|
+
import type { AIMessageMetaProps, AIMessageRuntimeMetadata } from './types';
|
|
6
|
+
|
|
7
|
+
function getRuntimeMetadata(message: AIMessageMetaProps['message']): AIMessageRuntimeMetadata {
|
|
8
|
+
const metadata = message.metadata?.aiRuntime;
|
|
9
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return metadata as AIMessageRuntimeMetadata;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function formatDuration(durationMs?: number) {
|
|
17
|
+
if (durationMs === undefined || Number.isNaN(durationMs)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (durationMs < 1000) {
|
|
22
|
+
return `${durationMs}ms`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return `${(durationMs / 1000).toFixed(2)}s`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatTime(createdAt: number) {
|
|
29
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
30
|
+
hour: '2-digit',
|
|
31
|
+
minute: '2-digit',
|
|
32
|
+
}).format(createdAt);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function AIMessageMeta({
|
|
36
|
+
message,
|
|
37
|
+
className,
|
|
38
|
+
showTime = true,
|
|
39
|
+
showStatus = true,
|
|
40
|
+
showRuntime = true,
|
|
41
|
+
showFailureReason = true,
|
|
42
|
+
}: AIMessageMetaProps) {
|
|
43
|
+
const runtime = getRuntimeMetadata(message);
|
|
44
|
+
const firstToken = formatDuration(runtime.firstTokenMs);
|
|
45
|
+
const total = formatDuration(runtime.totalMs);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className={cn('flex flex-wrap items-center gap-2 text-[11px] text-muted-foreground', className)}>
|
|
49
|
+
{showTime ? <span>{formatTime(message.createdAt)}</span> : null}
|
|
50
|
+
{showStatus ? <AIStatusIndicator message={message} /> : null}
|
|
51
|
+
{showRuntime && firstToken ? <span>First Token {firstToken}</span> : null}
|
|
52
|
+
{showRuntime && total ? <span>Total {total}</span> : null}
|
|
53
|
+
{showFailureReason && message.failureReason ? <span>Reason {message.failureReason}</span> : null}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
4
|
+
import type { AIStatusIndicatorProps } from './types';
|
|
5
|
+
|
|
6
|
+
function getLabel(status?: string) {
|
|
7
|
+
switch (status) {
|
|
8
|
+
case 'streaming':
|
|
9
|
+
return 'Streaming';
|
|
10
|
+
case 'completed':
|
|
11
|
+
return 'Completed';
|
|
12
|
+
case 'stopped':
|
|
13
|
+
return 'Stopped';
|
|
14
|
+
case 'timeout':
|
|
15
|
+
return 'Timeout';
|
|
16
|
+
case 'request_aborted':
|
|
17
|
+
return 'Aborted';
|
|
18
|
+
case 'failed':
|
|
19
|
+
return 'Failed';
|
|
20
|
+
default:
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getStatusClassName(status?: string) {
|
|
26
|
+
switch (status) {
|
|
27
|
+
case 'completed':
|
|
28
|
+
return 'border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-900/60 dark:bg-emerald-950/40 dark:text-emerald-300';
|
|
29
|
+
case 'streaming':
|
|
30
|
+
return 'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-900/60 dark:bg-sky-950/40 dark:text-sky-300';
|
|
31
|
+
case 'stopped':
|
|
32
|
+
return 'border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-900/60 dark:bg-rose-950/40 dark:text-rose-300';
|
|
33
|
+
case 'timeout':
|
|
34
|
+
return 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/60 dark:bg-amber-950/40 dark:text-amber-300';
|
|
35
|
+
case 'request_aborted':
|
|
36
|
+
return 'border-orange-200 bg-orange-50 text-orange-700 dark:border-orange-900/60 dark:bg-orange-950/40 dark:text-orange-300';
|
|
37
|
+
case 'failed':
|
|
38
|
+
return 'border-red-200 bg-red-50 text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-300';
|
|
39
|
+
default:
|
|
40
|
+
return 'border-border bg-background text-muted-foreground';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function AIStatusIndicator({ message, className }: AIStatusIndicatorProps) {
|
|
45
|
+
const label = getLabel(message.status);
|
|
46
|
+
if (!label) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<span
|
|
52
|
+
className={cn(
|
|
53
|
+
'inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] uppercase tracking-[0.12em]',
|
|
54
|
+
getStatusClassName(message.status),
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{label}
|
|
59
|
+
</span>
|
|
60
|
+
);
|
|
61
|
+
}
|
package/src/ai/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export { getMessageText, getTextParts, type ConversationMessage, type MessagePart } from '@windrun-huaiin/contracts/ai';
|
|
4
|
+
export * from './types';
|
|
5
|
+
export * from './use-ai-conversation';
|
|
6
|
+
export * from './ai-chat-composer';
|
|
7
|
+
export * from './ai-message-list';
|
|
8
|
+
export * from './ai-message-bubble';
|
|
9
|
+
export * from './ai-markdown';
|
|
10
|
+
export * from './ai-message-content';
|
|
11
|
+
export * from './ai-message-meta';
|
|
12
|
+
export * from './ai-message-actions';
|
|
13
|
+
export * from './ai-status-indicator';
|
package/src/ai/types.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AIRuntimeRequest,
|
|
3
|
+
AIStreamEvent,
|
|
4
|
+
ConversationMessage,
|
|
5
|
+
MessagePart,
|
|
6
|
+
} from '@windrun-huaiin/contracts/ai';
|
|
7
|
+
import type { ComponentType, ReactNode, RefObject } from 'react';
|
|
8
|
+
|
|
9
|
+
export type AIConversationTransport = (
|
|
10
|
+
input: AIRuntimeRequest,
|
|
11
|
+
signal: AbortSignal,
|
|
12
|
+
) => Promise<Response>;
|
|
13
|
+
|
|
14
|
+
export type AIConversationState = {
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
messages: ConversationMessage[];
|
|
17
|
+
isStreaming: boolean;
|
|
18
|
+
error?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type AIConversationOptions = {
|
|
22
|
+
endpoint: string;
|
|
23
|
+
initialSessionId?: string;
|
|
24
|
+
initialMessages?: ConversationMessage[];
|
|
25
|
+
modelName?: string;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
transport?: AIConversationTransport;
|
|
28
|
+
onEvent?: (event: AIStreamEvent) => void;
|
|
29
|
+
onError?: (error: Error) => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type SendMessageInput = {
|
|
33
|
+
text: string;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type AIChatComposerProps = {
|
|
38
|
+
value: string;
|
|
39
|
+
onChange: (value: string) => void;
|
|
40
|
+
onSubmit: () => void;
|
|
41
|
+
onStop?: () => void;
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
isStreaming?: boolean;
|
|
44
|
+
placeholder?: string;
|
|
45
|
+
className?: string;
|
|
46
|
+
leftSlot?: ReactNode;
|
|
47
|
+
attachments?: ReactNode;
|
|
48
|
+
helper?: ReactNode;
|
|
49
|
+
submitLabel?: string;
|
|
50
|
+
stopLabel?: string;
|
|
51
|
+
minHeight?: number;
|
|
52
|
+
maxHeight?: number;
|
|
53
|
+
submitOnEnter?: boolean;
|
|
54
|
+
shellClassName?: string;
|
|
55
|
+
textareaClassName?: string;
|
|
56
|
+
submitControl?: ReactNode;
|
|
57
|
+
stopControl?: ReactNode;
|
|
58
|
+
textareaRef?: RefObject<HTMLTextAreaElement | null>;
|
|
59
|
+
secondaryActions?: ReactNode;
|
|
60
|
+
actionLayout?: 'inline' | 'stacked';
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type AIMessageBubbleProps = {
|
|
64
|
+
message: ConversationMessage;
|
|
65
|
+
className?: string;
|
|
66
|
+
cardClassName?: string;
|
|
67
|
+
contentClassName?: string;
|
|
68
|
+
footerClassName?: string;
|
|
69
|
+
maxWidthClassName?: string;
|
|
70
|
+
showRoleLabel?: boolean;
|
|
71
|
+
markdownComponents?: AIMarkdownComponentMap;
|
|
72
|
+
showFooter?: boolean;
|
|
73
|
+
renderContent?: (message: ConversationMessage) => ReactNode;
|
|
74
|
+
renderMeta?: (message: ConversationMessage) => ReactNode;
|
|
75
|
+
renderActions?: (message: ConversationMessage) => ReactNode;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type AIMessageListProps = {
|
|
79
|
+
messages: ConversationMessage[];
|
|
80
|
+
className?: string;
|
|
81
|
+
contentClassName?: string;
|
|
82
|
+
emptyText?: string;
|
|
83
|
+
emptyState?: ReactNode;
|
|
84
|
+
autoScroll?: boolean;
|
|
85
|
+
scrollBehavior?: ScrollBehavior;
|
|
86
|
+
renderMessage?: (message: ConversationMessage) => ReactNode;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type AIStatusIndicatorProps = {
|
|
90
|
+
message: ConversationMessage;
|
|
91
|
+
className?: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type AIMessageContentProps = {
|
|
95
|
+
message: ConversationMessage;
|
|
96
|
+
className?: string;
|
|
97
|
+
markdownComponents?: AIMarkdownComponentMap;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type AIMessageMetaProps = {
|
|
101
|
+
message: ConversationMessage;
|
|
102
|
+
className?: string;
|
|
103
|
+
showTime?: boolean;
|
|
104
|
+
showStatus?: boolean;
|
|
105
|
+
showRuntime?: boolean;
|
|
106
|
+
showFailureReason?: boolean;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type AIMessageActionsProps = {
|
|
110
|
+
className?: string;
|
|
111
|
+
children?: ReactNode;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export type AIMarkdownComponentMap = Record<string, ComponentType<any>>;
|
|
115
|
+
|
|
116
|
+
export type AIMarkdownProps = {
|
|
117
|
+
content: string;
|
|
118
|
+
className?: string;
|
|
119
|
+
components?: AIMarkdownComponentMap;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export type TextMessagePart = Extract<MessagePart, { type: 'text' }>;
|
|
123
|
+
|
|
124
|
+
export type AIMessageRuntimeMetadata = {
|
|
125
|
+
requestStartedAt?: number;
|
|
126
|
+
streamStartedAt?: number;
|
|
127
|
+
firstTokenAt?: number;
|
|
128
|
+
firstTokenMs?: number;
|
|
129
|
+
completedAt?: number;
|
|
130
|
+
totalMs?: number;
|
|
131
|
+
};
|