@nextclaw/ui 0.6.3 → 0.6.5
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/CHANGELOG.md +12 -0
- package/dist/assets/{ChannelsList-Bga6n85j.js → ChannelsList-DiX7ssoj.js} +1 -1
- package/dist/assets/ChatPage-CEEbtfae.js +34 -0
- package/dist/assets/{DocBrowser-dv57PRp5.js → DocBrowser-CkDugJv3.js} +1 -1
- package/dist/assets/{MarketplacePage-j6p73Hjo.js → MarketplacePage-Cxt223yL.js} +1 -1
- package/dist/assets/{ModelConfig-BiKSDp5h.js → ModelConfig-BRRJU2mk.js} +1 -1
- package/dist/assets/{ProvidersList-B7ZfRUkD.js → ProvidersList-Bl-OMAwu.js} +1 -1
- package/dist/assets/{RuntimeConfig-Bpt9UNb6.js → RuntimeConfig-C0wurlgT.js} +1 -1
- package/dist/assets/{SecretsConfig-Ds00G-_O.js → SecretsConfig-C_3yJm1I.js} +2 -2
- package/dist/assets/{SessionsConfig-Mjet4opU.js → SessionsConfig-D13xFf5J.js} +1 -1
- package/dist/assets/{card-C7JJ5BGA.js → card-sDEXo_R4.js} +1 -1
- package/dist/assets/index-6azf0otP.css +1 -0
- package/dist/assets/index-DP9xXu9V.js +2 -0
- package/dist/assets/{label-DHJKdaUl.js → label-DhIqXnKx.js} +1 -1
- package/dist/assets/{logos-fPO_amyL.js → logos-B9Qiy9wo.js} +1 -1
- package/dist/assets/{page-layout-CF0JQsWW.js → page-layout-CC6kzYFp.js} +1 -1
- package/dist/assets/{switch-C1hgy-fE.js → switch-DFPJEEVz.js} +1 -1
- package/dist/assets/{tabs-custom-OyoLf5ZM.js → tabs-custom-C_1pBckQ.js} +1 -1
- package/dist/assets/{useConfig-D_G46zbo.js → useConfig-q7QqH3JB.js} +3 -3
- package/dist/assets/{useConfirmDialog-_0u6i3cI.js → useConfirmDialog-BNkeOTcY.js} +1 -1
- package/dist/assets/{vendor-Ylg6Wdt_.js → vendor-Dj2ULvht.js} +61 -56
- package/dist/index.html +3 -3
- package/package.json +1 -1
- package/src/components/chat/ChatConversationPanel.tsx +33 -0
- package/src/components/chat/ChatInputBar.tsx +32 -4
- package/src/components/chat/ChatThread.tsx +151 -10
- package/src/index.css +226 -29
- package/src/lib/i18n.ts +2 -0
- package/dist/assets/ChatPage-B-Yk3kkv.js +0 -32
- package/dist/assets/index-BiJ2xs5X.css +0 -1
- package/dist/assets/index-Cb9xiqC5.js +0 -2
package/dist/index.html
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>NextClaw - 系统配置</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-DP9xXu9V.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-Dj2ULvht.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-6azf0otP.css">
|
|
12
12
|
</head>
|
|
13
13
|
|
|
14
14
|
<body>
|
package/package.json
CHANGED
|
@@ -40,6 +40,34 @@ type ChatConversationPanelProps = {
|
|
|
40
40
|
queuedCount: number;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
function ChatConversationSkeleton() {
|
|
44
|
+
return (
|
|
45
|
+
<section className="flex-1 min-h-0 flex flex-col overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
|
|
46
|
+
<div className="flex-1 min-h-0 overflow-y-auto custom-scrollbar">
|
|
47
|
+
<div className="mx-auto w-full max-w-[min(1120px,100%)] px-6 py-5">
|
|
48
|
+
<div className="space-y-4">
|
|
49
|
+
<div className="h-6 w-48 animate-pulse rounded bg-gray-200" />
|
|
50
|
+
<div className="h-24 w-[78%] animate-pulse rounded-2xl bg-gray-200/80" />
|
|
51
|
+
<div className="h-20 w-[62%] animate-pulse rounded-2xl bg-gray-200/80" />
|
|
52
|
+
<div className="h-28 w-[84%] animate-pulse rounded-2xl bg-gray-200/80" />
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="border-t border-gray-200/80 bg-white p-4">
|
|
57
|
+
<div className="mx-auto w-full max-w-[min(1120px,100%)]">
|
|
58
|
+
<div className="rounded-2xl border border-gray-200 bg-white shadow-card p-4">
|
|
59
|
+
<div className="h-16 w-full animate-pulse rounded-xl bg-gray-200/80" />
|
|
60
|
+
<div className="mt-3 flex items-center justify-between">
|
|
61
|
+
<div className="h-8 w-36 animate-pulse rounded-lg bg-gray-200/80" />
|
|
62
|
+
<div className="h-8 w-20 animate-pulse rounded-lg bg-gray-200/80" />
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</section>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
43
71
|
export function ChatConversationPanel({
|
|
44
72
|
isProviderStateResolved,
|
|
45
73
|
modelOptions,
|
|
@@ -82,6 +110,10 @@ export function ChatConversationPanel({
|
|
|
82
110
|
!isAwaitingAssistantOutput &&
|
|
83
111
|
!streamingAssistantText.trim();
|
|
84
112
|
|
|
113
|
+
if (!isProviderStateResolved) {
|
|
114
|
+
return <ChatConversationSkeleton />;
|
|
115
|
+
}
|
|
116
|
+
|
|
85
117
|
return (
|
|
86
118
|
<section className="flex-1 min-h-0 flex flex-col overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
|
|
87
119
|
{/* Minimal top bar - only shown when session is active */}
|
|
@@ -134,6 +166,7 @@ export function ChatConversationPanel({
|
|
|
134
166
|
|
|
135
167
|
{/* Enhanced input bar */}
|
|
136
168
|
<ChatInputBar
|
|
169
|
+
isProviderStateResolved={isProviderStateResolved}
|
|
137
170
|
draft={draft}
|
|
138
171
|
onDraftChange={onDraftChange}
|
|
139
172
|
onSend={onSend}
|
|
@@ -13,6 +13,7 @@ export type ChatModelOption = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
type ChatInputBarProps = {
|
|
16
|
+
isProviderStateResolved: boolean;
|
|
16
17
|
draft: string;
|
|
17
18
|
onDraftChange: (value: string) => void;
|
|
18
19
|
onSend: () => Promise<void> | void;
|
|
@@ -33,6 +34,7 @@ type ChatInputBarProps = {
|
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
export function ChatInputBar({
|
|
37
|
+
isProviderStateResolved,
|
|
36
38
|
draft,
|
|
37
39
|
onDraftChange,
|
|
38
40
|
onSend,
|
|
@@ -52,7 +54,9 @@ export function ChatInputBar({
|
|
|
52
54
|
onSelectedSkillsChange
|
|
53
55
|
}: ChatInputBarProps) {
|
|
54
56
|
const hasModelOptions = modelOptions.length > 0;
|
|
55
|
-
const
|
|
57
|
+
const isModelOptionsLoading = !isProviderStateResolved && !hasModelOptions;
|
|
58
|
+
const isModelOptionsEmpty = isProviderStateResolved && !hasModelOptions;
|
|
59
|
+
const inputDisabled = (isModelOptionsLoading || isModelOptionsEmpty) && !isSending;
|
|
56
60
|
const selectedModelOption = modelOptions.find((option) => option.value === selectedModel);
|
|
57
61
|
const resolvedStopHint =
|
|
58
62
|
stopDisabledReason === '__preparing__'
|
|
@@ -86,10 +90,24 @@ export function ChatInputBar({
|
|
|
86
90
|
void onSend();
|
|
87
91
|
}
|
|
88
92
|
}}
|
|
89
|
-
placeholder={
|
|
93
|
+
placeholder={
|
|
94
|
+
isModelOptionsLoading
|
|
95
|
+
? ''
|
|
96
|
+
: hasModelOptions
|
|
97
|
+
? t('chatInputPlaceholder')
|
|
98
|
+
: t('chatModelNoOptions')
|
|
99
|
+
}
|
|
90
100
|
className="w-full min-h-[68px] max-h-[220px] resize-y bg-transparent outline-none text-sm px-4 py-3 text-gray-800 placeholder:text-gray-400"
|
|
91
101
|
/>
|
|
92
|
-
{
|
|
102
|
+
{isModelOptionsLoading && (
|
|
103
|
+
<div className="px-4 pb-2">
|
|
104
|
+
<div className="inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-2">
|
|
105
|
+
<span className="h-3 w-28 animate-pulse rounded bg-gray-200" />
|
|
106
|
+
<span className="h-3 w-16 animate-pulse rounded bg-gray-200" />
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
{isModelOptionsEmpty && (
|
|
93
111
|
<div className="px-4 pb-2">
|
|
94
112
|
<div className="inline-flex items-center gap-2 rounded-lg border border-amber-200 bg-amber-50 px-3 py-1.5 text-xs text-amber-800">
|
|
95
113
|
<span>{t('chatModelNoOptions')}</span>
|
|
@@ -147,13 +165,23 @@ export function ChatInputBar({
|
|
|
147
165
|
{selectedModelOption.providerLabel}/{selectedModelOption.modelLabel}
|
|
148
166
|
</span>
|
|
149
167
|
</div>
|
|
168
|
+
) : isModelOptionsLoading ? (
|
|
169
|
+
<div className="h-3 w-24 animate-pulse rounded bg-gray-200" />
|
|
150
170
|
) : (
|
|
151
171
|
<SelectValue placeholder={t('chatSelectModel')} />
|
|
152
172
|
)}
|
|
153
173
|
</SelectTrigger>
|
|
154
174
|
<SelectContent className="w-[320px]">
|
|
155
175
|
{modelOptions.length === 0 && (
|
|
156
|
-
|
|
176
|
+
isModelOptionsLoading ? (
|
|
177
|
+
<div className="space-y-2 px-3 py-2">
|
|
178
|
+
<div className="h-3 w-36 animate-pulse rounded bg-gray-200" />
|
|
179
|
+
<div className="h-3 w-28 animate-pulse rounded bg-gray-200" />
|
|
180
|
+
<div className="h-3 w-32 animate-pulse rounded bg-gray-200" />
|
|
181
|
+
</div>
|
|
182
|
+
) : (
|
|
183
|
+
<div className="px-3 py-2 text-xs text-gray-500">{t('chatModelNoOptions')}</div>
|
|
184
|
+
)
|
|
157
185
|
)}
|
|
158
186
|
{modelOptions.map((option) => (
|
|
159
187
|
<SelectItem key={option.value} value={option.value} className="py-2">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo, type ReactNode } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
|
|
2
2
|
import type { SessionEventView, SessionMessageView } from '@/api/types';
|
|
3
3
|
import { cn } from '@/lib/utils';
|
|
4
4
|
import {
|
|
@@ -11,10 +11,9 @@ import {
|
|
|
11
11
|
type ToolCard
|
|
12
12
|
} from '@/lib/chat-message';
|
|
13
13
|
import { formatDateTime, t } from '@/lib/i18n';
|
|
14
|
-
import ReactMarkdown from 'react-markdown';
|
|
15
|
-
import rehypeSanitize from 'rehype-sanitize';
|
|
14
|
+
import ReactMarkdown, { type Components } from 'react-markdown';
|
|
16
15
|
import remarkGfm from 'remark-gfm';
|
|
17
|
-
import { Bot, Clock3, FileSearch, Globe, Search, SendHorizontal, Terminal, User, Wrench } from 'lucide-react';
|
|
16
|
+
import { Bot, Check, Clock3, Copy, FileSearch, Globe, Search, SendHorizontal, Terminal, User, Wrench } from 'lucide-react';
|
|
18
17
|
|
|
19
18
|
type ChatThreadProps = {
|
|
20
19
|
events: SessionEventView[];
|
|
@@ -24,6 +23,8 @@ type ChatThreadProps = {
|
|
|
24
23
|
|
|
25
24
|
const MARKDOWN_MAX_CHARS = 140_000;
|
|
26
25
|
const TOOL_OUTPUT_PREVIEW_MAX = 220;
|
|
26
|
+
const CODE_LANGUAGE_REGEX = /language-([a-z0-9-]+)/i;
|
|
27
|
+
const SAFE_LINK_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);
|
|
27
28
|
|
|
28
29
|
type WorkflowToolCard = ToolCard & {
|
|
29
30
|
_workflowStep?: number;
|
|
@@ -36,6 +37,91 @@ function trimMarkdown(value: string): string {
|
|
|
36
37
|
return `${value.slice(0, MARKDOWN_MAX_CHARS)}\n\n…`;
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
function flattenNodeText(value: ReactNode): string {
|
|
41
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
42
|
+
return String(value);
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(value)) {
|
|
45
|
+
return value.map(flattenNodeText).join('');
|
|
46
|
+
}
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeCodeText(value: ReactNode): string {
|
|
51
|
+
const content = flattenNodeText(value);
|
|
52
|
+
return content.endsWith('\n') ? content.slice(0, -1) : content;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveCodeLanguage(className?: string): string {
|
|
56
|
+
const match = className ? CODE_LANGUAGE_REGEX.exec(className) : null;
|
|
57
|
+
return match?.[1]?.toLowerCase() || 'text';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveSafeHref(href?: string): string | null {
|
|
61
|
+
if (!href) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (href.startsWith('#') || href.startsWith('/') || href.startsWith('./') || href.startsWith('../')) {
|
|
65
|
+
return href;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const url = new URL(href);
|
|
69
|
+
return SAFE_LINK_PROTOCOLS.has(url.protocol) ? href : null;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isExternalHref(href: string): boolean {
|
|
76
|
+
return /^https?:\/\//i.test(href);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function MarkdownCodeBlock({ className, children }: { className?: string; children: ReactNode }) {
|
|
80
|
+
const [copied, setCopied] = useState(false);
|
|
81
|
+
const language = useMemo(() => resolveCodeLanguage(className), [className]);
|
|
82
|
+
const codeText = useMemo(() => normalizeCodeText(children), [children]);
|
|
83
|
+
|
|
84
|
+
const handleCopy = useCallback(async () => {
|
|
85
|
+
if (!codeText || typeof navigator === 'undefined' || !navigator.clipboard?.writeText) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
await navigator.clipboard.writeText(codeText);
|
|
90
|
+
setCopied(true);
|
|
91
|
+
} catch {
|
|
92
|
+
setCopied(false);
|
|
93
|
+
}
|
|
94
|
+
}, [codeText]);
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!copied || typeof window === 'undefined') {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const timer = window.setTimeout(() => setCopied(false), 1300);
|
|
101
|
+
return () => window.clearTimeout(timer);
|
|
102
|
+
}, [copied]);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div className="chat-codeblock">
|
|
106
|
+
<div className="chat-codeblock-toolbar">
|
|
107
|
+
<span className="chat-codeblock-language">{language}</span>
|
|
108
|
+
<button
|
|
109
|
+
type="button"
|
|
110
|
+
className="chat-codeblock-copy"
|
|
111
|
+
onClick={handleCopy}
|
|
112
|
+
aria-label={copied ? t('chatCodeCopied') : t('chatCodeCopy')}
|
|
113
|
+
>
|
|
114
|
+
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
|
|
115
|
+
<span>{copied ? t('chatCodeCopied') : t('chatCodeCopy')}</span>
|
|
116
|
+
</button>
|
|
117
|
+
</div>
|
|
118
|
+
<pre>
|
|
119
|
+
<code className={className}>{codeText}</code>
|
|
120
|
+
</pre>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
39
125
|
function roleTitle(role: ChatRole): string {
|
|
40
126
|
if (role === 'user') return t('chatRoleUser');
|
|
41
127
|
if (role === 'assistant') return t('chatRoleAssistant');
|
|
@@ -91,16 +177,71 @@ function RoleAvatar({ role }: { role: ChatRole }) {
|
|
|
91
177
|
|
|
92
178
|
function MarkdownBlock({ text, role }: { text: string; role: ChatRole }) {
|
|
93
179
|
const isUser = role === 'user';
|
|
180
|
+
const markdownComponents = useMemo<Components>(() => ({
|
|
181
|
+
a: ({ href, children, ...props }) => {
|
|
182
|
+
const safeHref = resolveSafeHref(href);
|
|
183
|
+
if (!safeHref) {
|
|
184
|
+
return <span className="chat-link-invalid">{children}</span>;
|
|
185
|
+
}
|
|
186
|
+
const external = isExternalHref(safeHref);
|
|
187
|
+
return (
|
|
188
|
+
<a
|
|
189
|
+
{...props}
|
|
190
|
+
href={safeHref}
|
|
191
|
+
target={external ? '_blank' : undefined}
|
|
192
|
+
rel={external ? 'noreferrer noopener' : undefined}
|
|
193
|
+
>
|
|
194
|
+
{children}
|
|
195
|
+
</a>
|
|
196
|
+
);
|
|
197
|
+
},
|
|
198
|
+
table: ({ children, ...props }) => (
|
|
199
|
+
<div className="chat-table-wrap">
|
|
200
|
+
<table {...props}>{children}</table>
|
|
201
|
+
</div>
|
|
202
|
+
),
|
|
203
|
+
input: ({ type, checked, ...props }) => {
|
|
204
|
+
if (type !== 'checkbox') {
|
|
205
|
+
return <input {...props} type={type} />;
|
|
206
|
+
}
|
|
207
|
+
return (
|
|
208
|
+
<input
|
|
209
|
+
{...props}
|
|
210
|
+
type="checkbox"
|
|
211
|
+
checked={checked}
|
|
212
|
+
readOnly
|
|
213
|
+
disabled
|
|
214
|
+
className="chat-task-checkbox"
|
|
215
|
+
/>
|
|
216
|
+
);
|
|
217
|
+
},
|
|
218
|
+
img: ({ src, alt, ...props }) => {
|
|
219
|
+
const safeSrc = resolveSafeHref(src);
|
|
220
|
+
if (!safeSrc) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return <img {...props} src={safeSrc} alt={alt || ''} loading="lazy" decoding="async" />;
|
|
224
|
+
},
|
|
225
|
+
code: ({ className, children, ...props }) => {
|
|
226
|
+
const plainText = String(children ?? '');
|
|
227
|
+
const isInlineCode = !className && !plainText.includes('\n');
|
|
228
|
+
if (isInlineCode) {
|
|
229
|
+
return (
|
|
230
|
+
<code {...props} className={cn('chat-inline-code', className)}>
|
|
231
|
+
{children}
|
|
232
|
+
</code>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return <MarkdownCodeBlock className={className}>{children}</MarkdownCodeBlock>;
|
|
236
|
+
}
|
|
237
|
+
}), []);
|
|
238
|
+
|
|
94
239
|
return (
|
|
95
240
|
<div className={cn('chat-markdown', isUser ? 'chat-markdown-user' : 'chat-markdown-assistant')}>
|
|
96
241
|
<ReactMarkdown
|
|
242
|
+
skipHtml
|
|
97
243
|
remarkPlugins={[remarkGfm]}
|
|
98
|
-
|
|
99
|
-
components={{
|
|
100
|
-
a: ({ ...props }) => (
|
|
101
|
-
<a {...props} target="_blank" rel="noreferrer noopener" />
|
|
102
|
-
)
|
|
103
|
-
}}
|
|
244
|
+
components={markdownComponents}
|
|
104
245
|
>
|
|
105
246
|
{trimMarkdown(text)}
|
|
106
247
|
</ReactMarkdown>
|
package/src/index.css
CHANGED
|
@@ -184,70 +184,267 @@
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
.chat-markdown {
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
--md-text: #0f172a;
|
|
188
|
+
--md-muted: #334155;
|
|
189
|
+
--md-border: rgba(148, 163, 184, 0.34);
|
|
190
|
+
--md-link: hsl(var(--primary));
|
|
191
|
+
--md-inline-code-bg: rgba(15, 23, 42, 0.08);
|
|
192
|
+
--md-inline-code-color: #1e293b;
|
|
193
|
+
--md-blockquote-border: rgba(14, 165, 233, 0.45);
|
|
194
|
+
--md-blockquote-bg: rgba(14, 165, 233, 0.08);
|
|
195
|
+
--md-code-surface: #0b1220;
|
|
196
|
+
--md-code-toolbar-bg: rgba(15, 23, 42, 0.56);
|
|
197
|
+
--md-code-toolbar-border: rgba(148, 163, 184, 0.3);
|
|
198
|
+
--md-code-text: #e5e7eb;
|
|
199
|
+
--md-table-head-bg: rgba(148, 163, 184, 0.14);
|
|
200
|
+
|
|
201
|
+
color: var(--md-text);
|
|
202
|
+
font-size: 0.925rem;
|
|
203
|
+
line-height: 1.72;
|
|
189
204
|
word-break: break-word;
|
|
190
205
|
}
|
|
191
206
|
|
|
207
|
+
.chat-markdown-user {
|
|
208
|
+
--md-text: rgba(255, 255, 255, 0.97);
|
|
209
|
+
--md-muted: rgba(219, 234, 254, 0.92);
|
|
210
|
+
--md-border: rgba(191, 219, 254, 0.35);
|
|
211
|
+
--md-link: #dbeafe;
|
|
212
|
+
--md-inline-code-bg: rgba(15, 23, 42, 0.35);
|
|
213
|
+
--md-inline-code-color: #eff6ff;
|
|
214
|
+
--md-blockquote-border: rgba(191, 219, 254, 0.8);
|
|
215
|
+
--md-blockquote-bg: rgba(15, 23, 42, 0.2);
|
|
216
|
+
--md-code-surface: rgba(2, 6, 23, 0.92);
|
|
217
|
+
--md-code-toolbar-bg: rgba(2, 6, 23, 0.88);
|
|
218
|
+
--md-code-toolbar-border: rgba(191, 219, 254, 0.26);
|
|
219
|
+
--md-code-text: #e2e8f0;
|
|
220
|
+
--md-table-head-bg: rgba(191, 219, 254, 0.16);
|
|
221
|
+
}
|
|
222
|
+
|
|
192
223
|
.chat-markdown > * + * {
|
|
193
|
-
margin-top: 0.
|
|
224
|
+
margin-top: 0.72rem;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.chat-markdown p,
|
|
228
|
+
.chat-markdown li,
|
|
229
|
+
.chat-markdown blockquote {
|
|
230
|
+
color: var(--md-text);
|
|
194
231
|
}
|
|
195
232
|
|
|
196
233
|
.chat-markdown h1,
|
|
197
234
|
.chat-markdown h2,
|
|
198
|
-
.chat-markdown h3
|
|
199
|
-
|
|
235
|
+
.chat-markdown h3,
|
|
236
|
+
.chat-markdown h4,
|
|
237
|
+
.chat-markdown h5,
|
|
238
|
+
.chat-markdown h6 {
|
|
239
|
+
margin: 0.2rem 0 0;
|
|
200
240
|
font-weight: 700;
|
|
241
|
+
line-height: 1.35;
|
|
242
|
+
letter-spacing: -0.01em;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.chat-markdown h1 {
|
|
246
|
+
font-size: 1.22rem;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.chat-markdown h2 {
|
|
250
|
+
font-size: 1.12rem;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.chat-markdown h3 {
|
|
254
|
+
font-size: 1.02rem;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.chat-markdown h4,
|
|
258
|
+
.chat-markdown h5,
|
|
259
|
+
.chat-markdown h6 {
|
|
260
|
+
font-size: 0.96rem;
|
|
201
261
|
}
|
|
202
262
|
|
|
203
263
|
.chat-markdown ul,
|
|
204
264
|
.chat-markdown ol {
|
|
205
|
-
|
|
265
|
+
margin: 0.35rem 0;
|
|
266
|
+
padding-left: 1.35rem;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.chat-markdown li + li {
|
|
270
|
+
margin-top: 0.25rem;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.chat-markdown li > p {
|
|
274
|
+
margin: 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.chat-markdown ul.contains-task-list,
|
|
278
|
+
.chat-markdown ol.contains-task-list {
|
|
279
|
+
list-style: none;
|
|
280
|
+
padding-left: 0.2rem;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.chat-markdown li.task-list-item {
|
|
284
|
+
display: flex;
|
|
285
|
+
align-items: flex-start;
|
|
286
|
+
gap: 0.52rem;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.chat-markdown .chat-task-checkbox {
|
|
290
|
+
position: relative;
|
|
291
|
+
top: 0.2rem;
|
|
292
|
+
width: 0.92rem;
|
|
293
|
+
height: 0.92rem;
|
|
294
|
+
cursor: default;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.chat-markdown hr {
|
|
298
|
+
border: 0;
|
|
299
|
+
border-top: 1px solid var(--md-border);
|
|
300
|
+
margin: 0.92rem 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.chat-markdown a {
|
|
304
|
+
color: var(--md-link);
|
|
305
|
+
text-decoration-line: underline;
|
|
306
|
+
text-decoration-thickness: 1.2px;
|
|
307
|
+
text-underline-offset: 2px;
|
|
308
|
+
word-break: break-all;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.chat-markdown .chat-link-invalid {
|
|
312
|
+
color: var(--md-muted);
|
|
313
|
+
text-decoration-line: underline;
|
|
314
|
+
text-decoration-style: dotted;
|
|
206
315
|
}
|
|
207
316
|
|
|
208
317
|
.chat-markdown code {
|
|
209
318
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
210
|
-
font-size: 0.
|
|
319
|
+
font-size: 0.82rem;
|
|
211
320
|
}
|
|
212
321
|
|
|
322
|
+
.chat-markdown .chat-inline-code,
|
|
213
323
|
.chat-markdown :not(pre) > code {
|
|
214
|
-
padding: 0.1rem 0.
|
|
215
|
-
border-radius: 0.
|
|
216
|
-
background:
|
|
324
|
+
padding: 0.1rem 0.35rem;
|
|
325
|
+
border-radius: 0.4rem;
|
|
326
|
+
background: var(--md-inline-code-bg);
|
|
327
|
+
color: var(--md-inline-code-color);
|
|
328
|
+
border: 1px solid var(--md-border);
|
|
329
|
+
font-weight: 520;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.chat-codeblock {
|
|
333
|
+
margin: 0.2rem 0 0.1rem;
|
|
334
|
+
border: 1px solid var(--md-code-toolbar-border);
|
|
335
|
+
border-radius: 0.78rem;
|
|
336
|
+
overflow: hidden;
|
|
337
|
+
background: var(--md-code-surface);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.chat-codeblock-toolbar {
|
|
341
|
+
display: flex;
|
|
342
|
+
align-items: center;
|
|
343
|
+
justify-content: space-between;
|
|
344
|
+
gap: 0.5rem;
|
|
345
|
+
padding: 0.43rem 0.58rem;
|
|
346
|
+
background: var(--md-code-toolbar-bg);
|
|
347
|
+
border-bottom: 1px solid var(--md-code-toolbar-border);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.chat-codeblock-language {
|
|
351
|
+
font-size: 0.68rem;
|
|
352
|
+
font-weight: 700;
|
|
353
|
+
text-transform: uppercase;
|
|
354
|
+
letter-spacing: 0.06em;
|
|
355
|
+
color: var(--md-code-text);
|
|
356
|
+
opacity: 0.8;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.chat-codeblock-copy {
|
|
360
|
+
display: inline-flex;
|
|
361
|
+
align-items: center;
|
|
362
|
+
gap: 0.3rem;
|
|
363
|
+
font-size: 0.7rem;
|
|
364
|
+
font-weight: 600;
|
|
365
|
+
color: var(--md-code-text);
|
|
366
|
+
border: 1px solid var(--md-code-toolbar-border);
|
|
367
|
+
border-radius: 0.48rem;
|
|
368
|
+
padding: 0.18rem 0.36rem;
|
|
369
|
+
background: transparent;
|
|
370
|
+
cursor: pointer;
|
|
371
|
+
transition: background-color 0.15s ease, border-color 0.15s ease;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.chat-codeblock-copy:hover {
|
|
375
|
+
background: rgba(148, 163, 184, 0.2);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.chat-codeblock pre {
|
|
379
|
+
margin: 0;
|
|
380
|
+
padding: 0.75rem 0.82rem 0.8rem;
|
|
381
|
+
overflow-x: auto;
|
|
382
|
+
background: transparent;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.chat-codeblock code {
|
|
386
|
+
display: block;
|
|
387
|
+
min-width: max-content;
|
|
388
|
+
white-space: pre;
|
|
389
|
+
line-height: 1.58;
|
|
390
|
+
color: var(--md-code-text);
|
|
217
391
|
}
|
|
218
392
|
|
|
219
|
-
.chat-
|
|
393
|
+
.chat-table-wrap {
|
|
394
|
+
margin: 0.15rem 0;
|
|
395
|
+
border: 1px solid var(--md-border);
|
|
396
|
+
border-radius: 0.72rem;
|
|
220
397
|
overflow-x: auto;
|
|
221
|
-
border-radius: 0.65rem;
|
|
222
|
-
padding: 0.65rem 0.75rem;
|
|
223
|
-
background: rgba(15, 23, 42, 0.9);
|
|
224
|
-
color: #e2e8f0;
|
|
225
398
|
}
|
|
226
399
|
|
|
227
400
|
.chat-markdown table {
|
|
228
401
|
width: 100%;
|
|
229
|
-
|
|
230
|
-
|
|
402
|
+
min-width: 420px;
|
|
403
|
+
border-collapse: separate;
|
|
404
|
+
border-spacing: 0;
|
|
405
|
+
font-size: 0.82rem;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.chat-markdown thead {
|
|
409
|
+
background: var(--md-table-head-bg);
|
|
231
410
|
}
|
|
232
411
|
|
|
233
412
|
.chat-markdown th,
|
|
234
413
|
.chat-markdown td {
|
|
235
|
-
|
|
236
|
-
|
|
414
|
+
padding: 0.45rem 0.58rem;
|
|
415
|
+
border-bottom: 1px solid var(--md-border);
|
|
416
|
+
text-align: left;
|
|
417
|
+
vertical-align: top;
|
|
237
418
|
}
|
|
238
419
|
|
|
239
|
-
.chat-markdown
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
color: rgba(30, 41, 59, 0.85);
|
|
420
|
+
.chat-markdown th + th,
|
|
421
|
+
.chat-markdown td + td {
|
|
422
|
+
border-left: 1px solid var(--md-border);
|
|
243
423
|
}
|
|
244
424
|
|
|
245
|
-
.chat-markdown-
|
|
246
|
-
|
|
247
|
-
text-decoration: underline;
|
|
425
|
+
.chat-markdown tbody tr:last-child td {
|
|
426
|
+
border-bottom: none;
|
|
248
427
|
}
|
|
249
428
|
|
|
250
|
-
.chat-markdown
|
|
251
|
-
|
|
252
|
-
|
|
429
|
+
.chat-markdown th {
|
|
430
|
+
font-weight: 650;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.chat-markdown strong {
|
|
434
|
+
font-weight: 650;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.chat-markdown img {
|
|
438
|
+
max-width: 100%;
|
|
439
|
+
border-radius: 0.75rem;
|
|
440
|
+
border: 1px solid var(--md-border);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.chat-markdown blockquote {
|
|
444
|
+
margin: 0.2rem 0;
|
|
445
|
+
border-left: 3px solid var(--md-blockquote-border);
|
|
446
|
+
border-radius: 0 0.55rem 0.55rem 0;
|
|
447
|
+
padding: 0.45rem 0.72rem;
|
|
448
|
+
background: var(--md-blockquote-bg);
|
|
449
|
+
color: var(--md-muted);
|
|
253
450
|
}
|
package/src/lib/i18n.ts
CHANGED
|
@@ -525,6 +525,8 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
525
525
|
chatToolOutput: { zh: '查看输出', en: 'View Output' },
|
|
526
526
|
chatToolNoOutput: { zh: '无输出(执行完成)', en: 'No output (completed)' },
|
|
527
527
|
chatReasoning: { zh: '查看推理内容', en: 'Show reasoning' },
|
|
528
|
+
chatCodeCopy: { zh: '复制代码', en: 'Copy' },
|
|
529
|
+
chatCodeCopied: { zh: '已复制', en: 'Copied' },
|
|
528
530
|
|
|
529
531
|
// Chat Sidebar (unified)
|
|
530
532
|
chatSidebarNewTask: { zh: '新任务', en: 'New Task' },
|