feat(ui): refine gem-blue acrylic palette and focus borders
Derive the gem-blue palette with OKLCH tonal and complementary-color rules while preserving acrylic opacity, blur and surface hierarchy. Darker focus tokens exposed stacked field rings, fallback outlines and persistent basemap selection rings. Keep fallback focus styles in the base layer and use one themed field boundary or inset card stroke. Preserve keyboard focus, invalid states and native input semantics. Add browser regression coverage for pointer and keyboard focus, all basemap presentations, narrow layouts and rendered contrast. Validation in an isolated checkout: pnpm lint, pnpm typecheck, pnpm test (393 tests), focused browser suites and pnpm build.
This commit is contained in:
@@ -28,7 +28,7 @@ export function AgentCollapsedRail({
|
||||
aria-label={expandLabel}
|
||||
title={expandLabel}
|
||||
onClick={onExpand}
|
||||
className="agent-rail-item surface-control group relative flex h-14 w-full items-center justify-center rounded-xl border hover:bg-white"
|
||||
className="agent-rail-item surface-control group relative flex h-14 w-full items-center justify-center rounded-xl border hover:bg-[var(--surface-reading)]"
|
||||
>
|
||||
<AgentPersona className="h-11 w-11" state={personaState} />
|
||||
<span className="agent-panel-primary-icon absolute bottom-0.5 right-0.5 grid h-5 w-5 place-items-center rounded-md border-0! shadow-xs transition-transform group-hover:translate-x-0.5">
|
||||
|
||||
@@ -373,7 +373,7 @@ export function AgentCommandPanel({
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="min-w-0">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<p className="shrink-0 text-xs font-semibold uppercase text-slate-500">
|
||||
<p className="shrink-0 text-xs font-semibold uppercase text-[var(--ink-muted)]">
|
||||
对话主题
|
||||
</p>
|
||||
<StatusBadge
|
||||
@@ -587,8 +587,8 @@ export function AgentCommandPanel({
|
||||
className={cn(
|
||||
"agent-panel-band relative",
|
||||
floatingPresentation
|
||||
? "agent-panel-floating-acrylic agent-panel-composer absolute bottom-3 z-20 rounded-2xl p-1"
|
||||
: "border-t border-slate-200 p-3"
|
||||
? "form-field agent-panel-floating-acrylic agent-panel-composer absolute bottom-3 z-20 rounded-2xl p-1"
|
||||
: "border-t border-[var(--surface-divider)] p-3"
|
||||
)}
|
||||
>
|
||||
{!hasConversation ? (
|
||||
@@ -608,7 +608,7 @@ export function AgentCommandPanel({
|
||||
key={suggestion}
|
||||
suggestion={suggestion}
|
||||
onClick={submitPrompt}
|
||||
className="surface-reading max-w-full border border-slate-200 text-slate-600 hover:bg-blue-50 hover:text-blue-700"
|
||||
className="surface-reading max-w-full border border-[var(--surface-divider)] text-slate-600 hover:bg-[var(--action-soft)] hover:text-[var(--action-blue-hover)]"
|
||||
>
|
||||
<span className="truncate">{suggestion}</span>
|
||||
</Suggestion>
|
||||
@@ -617,7 +617,7 @@ export function AgentCommandPanel({
|
||||
</div>
|
||||
) : null}
|
||||
<PromptInput
|
||||
className="agent-panel-control overflow-hidden rounded-2xl shadow-xs [&>[data-slot=input-group]]:rounded-[inherit] [&>[data-slot=input-group]]:border-0 [&>[data-slot=input-group]]:shadow-none [&>[data-slot=input-group]]:ring-0!"
|
||||
className="form-field agent-panel-control overflow-hidden rounded-2xl shadow-xs [&>[data-slot=input-group]]:rounded-[inherit] [&>[data-slot=input-group]]:border-0 [&>[data-slot=input-group]]:shadow-none"
|
||||
onSubmit={(message) => {
|
||||
const nextPrompt = message.text.trim();
|
||||
if (nextPrompt) {
|
||||
@@ -638,7 +638,7 @@ export function AgentCommandPanel({
|
||||
</PromptInputBody>
|
||||
<PromptInputFooter className="justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
{!qaOnly ? <ApprovalModeSelect presentation={presentation} value={approvalMode} disabled={streaming} onValueChange={onApprovalModeChange} /> : <span className="px-1 text-[11px] text-slate-500">任务协作问答</span>}
|
||||
{!qaOnly ? <ApprovalModeSelect presentation={presentation} value={approvalMode} disabled={streaming} onValueChange={onApprovalModeChange} /> : <span className="px-1 text-[11px] text-[var(--ink-muted)]">任务协作问答</span>}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<AgentModelSelect
|
||||
@@ -712,7 +712,7 @@ function VoiceInputButton({
|
||||
"group relative grid h-8 w-8 shrink-0 place-items-center overflow-visible rounded-lg border after:absolute after:-inset-1 after:content-['']",
|
||||
"border-transparent bg-transparent shadow-none",
|
||||
"transition-[color,background-color,border-color,box-shadow,transform] duration-200",
|
||||
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-600/25 focus-visible:ring-offset-1",
|
||||
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[var(--action-focus-ring)] focus-visible:ring-offset-1",
|
||||
"disabled:cursor-not-allowed disabled:opacity-45",
|
||||
isListening
|
||||
? "status-tone-danger bg-[var(--status-soft)] text-[var(--status-foreground)] [@media(hover:hover)]:hover:border-[var(--status-border)] [@media(hover:hover)]:hover:brightness-95"
|
||||
@@ -822,7 +822,7 @@ function ApprovalModeSelect({
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="shrink-0 gap-1.5 border-transparent bg-transparent px-1.5 text-xs text-slate-600 shadow-none hover:bg-slate-50"
|
||||
className="shrink-0 gap-1.5 border-transparent bg-transparent px-1.5 text-xs text-slate-600 shadow-none hover:bg-[var(--surface-reading)]"
|
||||
disabled={disabled || !onValueChange}
|
||||
aria-label="权限批准模式"
|
||||
>
|
||||
@@ -842,28 +842,28 @@ function ApprovalModeSelect({
|
||||
sideOffset={8}
|
||||
className="agent-panel-overlay agent-panel-control w-[220px] rounded-xl p-2 shadow-lg"
|
||||
>
|
||||
<DropdownMenuLabel className="px-2 pb-2 pt-1 text-xs text-slate-500">
|
||||
<DropdownMenuLabel className="px-2 pb-2 pt-1 text-xs text-[var(--ink-muted)]">
|
||||
权限批准模式
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
className={cn(
|
||||
"items-start rounded-lg border border-transparent px-2.5 py-2 focus:bg-slate-50",
|
||||
requestApproval && "border-blue-200 bg-blue-50 focus:bg-blue-50"
|
||||
"items-start rounded-lg border border-transparent px-2.5 py-2 focus:bg-[var(--surface-reading)]",
|
||||
requestApproval && "border-[var(--surface-border)] bg-[var(--action-soft)] focus:bg-[var(--action-soft)]"
|
||||
)}
|
||||
onSelect={() => onValueChange?.("request")}
|
||||
>
|
||||
<Shield className="mt-0.5 text-blue-600" aria-hidden="true" />
|
||||
<Shield className="mt-0.5 text-[var(--action-blue-hover)]" aria-hidden="true" />
|
||||
<span>
|
||||
<span className="block text-sm font-semibold text-slate-800">请求批准</span>
|
||||
<span className="block text-xs leading-5 text-slate-500">工具权限逐次确认</span>
|
||||
<span className="block text-xs leading-5 text-[var(--ink-muted)]">工具权限逐次确认</span>
|
||||
</span>
|
||||
{requestApproval ? (
|
||||
<CheckCircle2 className="ml-auto mt-0.5 text-blue-600" aria-hidden="true" />
|
||||
<CheckCircle2 className="ml-auto mt-0.5 text-[var(--action-blue-hover)]" aria-hidden="true" />
|
||||
) : null}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className={cn(
|
||||
"mt-1 items-start rounded-lg border border-transparent px-2.5 py-2 focus:bg-slate-50",
|
||||
"mt-1 items-start rounded-lg border border-transparent px-2.5 py-2 focus:bg-[var(--surface-reading)]",
|
||||
!requestApproval &&
|
||||
"status-tone-success border-[var(--status-border)] bg-[var(--status-soft)] focus:bg-[var(--status-soft)]"
|
||||
)}
|
||||
@@ -875,7 +875,7 @@ function ApprovalModeSelect({
|
||||
/>
|
||||
<span>
|
||||
<span className="block text-sm font-semibold text-slate-800">始终允许</span>
|
||||
<span className="block text-xs leading-5 text-slate-500">自动允许本轮权限请求</span>
|
||||
<span className="block text-xs leading-5 text-[var(--ink-muted)]">自动允许本轮权限请求</span>
|
||||
</span>
|
||||
{!requestApproval ? (
|
||||
<CheckCircle2
|
||||
@@ -993,7 +993,7 @@ function AgentModelSelect({
|
||||
|
||||
if (!models.length) {
|
||||
return (
|
||||
<span className="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-slate-500">模型</span>
|
||||
<span className="shrink-0 rounded-md px-2 py-1 text-xs font-medium text-[var(--ink-muted)]">模型</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1004,7 +1004,7 @@ function AgentModelSelect({
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-12 shrink-0 justify-between gap-1 border-transparent bg-transparent px-1 text-xs text-slate-600 shadow-none hover:bg-slate-50 sm:w-24 sm:gap-1.5 sm:px-1.5"
|
||||
className="w-12 shrink-0 justify-between gap-1 border-transparent bg-transparent px-1 text-xs text-slate-600 shadow-none hover:bg-[var(--surface-reading)] sm:w-24 sm:gap-1.5 sm:px-1.5"
|
||||
disabled={!onValueChange}
|
||||
aria-label="选择 Agent 模型"
|
||||
aria-description={`当前模型:${selectedModel?.label ?? "模型"}`}
|
||||
@@ -1024,7 +1024,7 @@ function AgentModelSelect({
|
||||
sideOffset={8}
|
||||
className="agent-panel-overlay agent-panel-control w-[230px] rounded-xl p-2 shadow-lg"
|
||||
>
|
||||
<DropdownMenuLabel className="px-2 pb-2 pt-1 text-xs text-slate-500">
|
||||
<DropdownMenuLabel className="px-2 pb-2 pt-1 text-xs text-[var(--ink-muted)]">
|
||||
模型选择
|
||||
</DropdownMenuLabel>
|
||||
<div className="space-y-1">
|
||||
@@ -1034,10 +1034,10 @@ function AgentModelSelect({
|
||||
<DropdownMenuItem
|
||||
key={model.id}
|
||||
className={cn(
|
||||
"rounded-lg border border-transparent px-2.5 py-2 focus:bg-slate-50",
|
||||
"rounded-lg border border-transparent px-2.5 py-2 focus:bg-[var(--surface-reading)]",
|
||||
selected && (presentation === "desktop-dock"
|
||||
? "border-blue-200 bg-blue-50 focus:bg-blue-50"
|
||||
: "border-violet-200 bg-violet-50 focus:bg-violet-50")
|
||||
? "border-[var(--surface-border)] bg-[var(--action-soft)] focus:bg-[var(--action-soft)]"
|
||||
: "border-[var(--surface-border)] bg-[var(--action-soft)] focus:bg-[var(--action-soft)]")
|
||||
)}
|
||||
onSelect={() => onValueChange?.(model.id)}
|
||||
>
|
||||
@@ -1048,12 +1048,12 @@ function AgentModelSelect({
|
||||
<span className="block truncate text-sm font-semibold text-slate-800">
|
||||
{model.label}
|
||||
</span>
|
||||
<span className="block truncate text-xs leading-4 text-slate-500">
|
||||
<span className="block truncate text-xs leading-4 text-[var(--ink-muted)]">
|
||||
{model.description}
|
||||
</span>
|
||||
</span>
|
||||
{selected ? (
|
||||
<CheckCircle2 size={15} className={presentation === "desktop-dock" ? "text-blue-600" : "text-violet-600"} aria-hidden="true" />
|
||||
<CheckCircle2 size={15} className={presentation === "desktop-dock" ? "text-[var(--action-blue-hover)]" : "text-[var(--action-blue-hover)]"} aria-hidden="true" />
|
||||
) : null}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
@@ -1099,7 +1099,7 @@ function ExpertModelIcon({ size }: { size: number }) {
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
className="text-violet-600"
|
||||
className="text-[var(--action-blue-hover)]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
@@ -1116,21 +1116,21 @@ function AgentReadyMark() {
|
||||
<g className="agent-ready-orbit">
|
||||
<path
|
||||
d="M384.1536 952.1664a38.4 38.4 0 0 1-49.3568 22.528 498.3808 498.3808 0 0 1-284.928-273.92 38.4 38.4 0 0 1 70.8608-29.6448 421.5808 421.5808 0 0 0 240.896 231.6288 38.4 38.4 0 0 1 22.528 49.408zM952.1152 384.9728a38.4 38.4 0 0 1-49.4592-22.528 421.5296 421.5296 0 0 0-234.1376-241.5104 38.4 38.4 0 0 1 29.184-71.0656 498.3296 498.3296 0 0 1 276.8896 285.696 38.4 38.4 0 0 1-22.528 49.408z"
|
||||
fill="#CE75FF"
|
||||
fill="var(--palette-brand)"
|
||||
/>
|
||||
<path
|
||||
d="M981.248 768.0512a42.6496 42.6496 0 1 1-85.2992 0 42.6496 42.6496 0 0 1 85.2992 0zM127.9488 256.0512a42.6496 42.6496 0 1 1-85.3504 0 42.6496 42.6496 0 0 1 85.3504 0z"
|
||||
fill="#F62E76"
|
||||
fill="var(--palette-champagne)"
|
||||
/>
|
||||
<path
|
||||
d="M810.496 938.8544a42.6496 42.6496 0 1 1-85.2992 0 42.6496 42.6496 0 0 1 85.3504 0zM298.496 85.504a42.6496 42.6496 0 1 1-85.2992 0 42.6496 42.6496 0 0 1 85.3504 0z"
|
||||
fill="#CD88FF"
|
||||
fill="var(--palette-sand)"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
className="agent-ready-core"
|
||||
d="M511.9488 276.736l-27.8528 114.7392A126.0544 126.0544 0 0 1 391.3216 484.352l-114.7904 27.8528 114.7904 27.8016a126.0544 126.0544 0 0 1 92.7744 92.8256L512 747.52l27.8016-114.7392a126.0544 126.0544 0 0 1 92.8256-92.8256l114.7392-27.8016-114.7392-27.8528a126.0544 126.0544 0 0 1-92.8256-92.8256L512 276.736z m55.6544-62.1568c-14.1312-58.368-97.1776-58.368-111.36 0L417.28 375.296a57.344 57.344 0 0 1-42.1888 42.1888l-160.6656 38.912c-58.4192 14.1824-58.4192 97.28 0 111.4112l160.6656 38.9632c20.8384 5.12 37.12 21.3504 42.1888 42.1888l38.9632 160.7168c14.1824 58.368 97.2288 58.368 111.36 0l38.9632-160.7168a57.344 57.344 0 0 1 42.1888-42.1888l160.7168-38.912c58.368-14.1824 58.368-97.28 0-111.4112l-160.7168-38.9632a57.344 57.344 0 0 1-42.1888-42.1888l-38.912-160.7168z"
|
||||
fill="#F3E2FF"
|
||||
fill="var(--action-selection-soft)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -1200,8 +1200,8 @@ function AgentEmptyState({ compact = false }: { compact?: boolean }) {
|
||||
key={label}
|
||||
className={cn(
|
||||
"agent-capability flex min-h-20 items-center gap-3 px-3.5 py-3 text-left",
|
||||
index % 2 === 0 && "border-r border-slate-300/80",
|
||||
index < 2 && "border-b border-slate-300/80"
|
||||
index % 2 === 0 && "border-r border-[var(--surface-border)]/80",
|
||||
index < 2 && "border-b border-[var(--surface-border)]/80"
|
||||
)}
|
||||
>
|
||||
<span className="agent-capability-icon grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-[#f9fbfe] text-[#334155] shadow-[inset_0_0_0_1px_rgba(148,163,184,0.28)]">
|
||||
@@ -1312,7 +1312,7 @@ function BackendMessageList({
|
||||
<p className="whitespace-pre-wrap">{message.content}</p>
|
||||
)
|
||||
) : streaming && index === messages.length - 1 ? (
|
||||
<span className="text-slate-500">正在生成...</span>
|
||||
<span className="text-[var(--ink-muted)]">正在生成...</span>
|
||||
) : null}
|
||||
{message.role === "assistant" && !qaOnly ? (
|
||||
<AgentMessageDetails
|
||||
@@ -1466,7 +1466,7 @@ function AgentSpeechMessage({
|
||||
key="speech-selection-action"
|
||||
type="button"
|
||||
data-agent-presentation={presentation}
|
||||
className="agent-panel-overlay agent-panel-floating-acrylic agent-speech-selection-action pointer-events-auto inline-flex h-10 items-center gap-2 rounded-xl px-2.5 pr-3 text-xs font-semibold text-slate-800 transition-colors hover:text-blue-700 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-600/25"
|
||||
className="agent-panel-overlay agent-panel-floating-acrylic agent-speech-selection-action pointer-events-auto inline-flex h-10 items-center gap-2 rounded-xl px-2.5 pr-3 text-xs font-semibold text-slate-800 transition-colors hover:text-[var(--action-blue-hover)] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[var(--action-focus-ring)]"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
@@ -1480,7 +1480,7 @@ function AgentSpeechMessage({
|
||||
onClick={speakFromSelection}
|
||||
>
|
||||
<span
|
||||
className="grid h-6 w-6 shrink-0 place-items-center rounded-lg bg-blue-50/75 text-blue-600"
|
||||
className="grid h-6 w-6 shrink-0 place-items-center rounded-lg bg-[var(--action-soft)]/75 text-[var(--action-blue-hover)]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Volume2 size={15} />
|
||||
@@ -1495,7 +1495,7 @@ function AgentSpeechMessage({
|
||||
: null}
|
||||
|
||||
{isSupported && !disabled ? (
|
||||
<div className="mt-2 flex min-h-8 items-center gap-1 border-t border-slate-100 pt-1.5">
|
||||
<div className="mt-2 flex min-h-8 items-center gap-1 border-t border-[var(--surface-divider)] pt-1.5">
|
||||
{speechState === "idle" ? (
|
||||
<SpeechIconButton
|
||||
label="语音播放"
|
||||
@@ -1507,7 +1507,7 @@ function AgentSpeechMessage({
|
||||
{speechState === "loading" ? (
|
||||
<>
|
||||
<SpeechIconButton label="正在生成语音" disabled>
|
||||
<LoaderCircle size={15} className="animate-spin text-blue-600" aria-hidden="true" />
|
||||
<LoaderCircle size={15} className="animate-spin text-[var(--action-blue-hover)]" aria-hidden="true" />
|
||||
</SpeechIconButton>
|
||||
<SpeechIconButton label="停止语音播放" tone="danger" onClick={onStop}>
|
||||
<Square size={14} fill="currentColor" aria-hidden="true" />
|
||||
@@ -1534,7 +1534,7 @@ function AgentSpeechMessage({
|
||||
</SpeechIconButton>
|
||||
</>
|
||||
) : null}
|
||||
<span className="text-[11px] text-slate-400">
|
||||
<span className="text-[11px] text-[var(--ink-muted)]">
|
||||
{speechState === "idle"
|
||||
? "语音播放"
|
||||
: speechState === "loading"
|
||||
@@ -1572,8 +1572,8 @@ function SpeechIconButton({
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"shrink-0 text-slate-500 hover:bg-slate-100 disabled:cursor-wait",
|
||||
tone === "primary" && "text-blue-600",
|
||||
"shrink-0 text-[var(--ink-muted)] hover:bg-[var(--surface-control)] disabled:cursor-wait",
|
||||
tone === "primary" && "text-[var(--action-blue-hover)]",
|
||||
tone === "danger" && "status-tone-danger text-[var(--status-foreground)]"
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -75,7 +75,7 @@ export function AgentHistoryPanel({
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-semibold text-slate-950">历史记录</p>
|
||||
<p className="mt-0.5 text-xs text-slate-500">
|
||||
<p className="mt-0.5 text-xs text-[var(--ink-muted)]">
|
||||
{sessions.length ? `${sessions.length} 个会话` : "暂无历史会话"}
|
||||
</p>
|
||||
</div>
|
||||
@@ -103,7 +103,7 @@ export function AgentHistoryPanel({
|
||||
{loading && sessions.length === 0 ? (
|
||||
<motion.div
|
||||
key="history-loading"
|
||||
className="agent-history-state flex items-center gap-2 rounded-xl px-3 py-3 text-xs font-semibold text-slate-500"
|
||||
className="agent-history-state flex items-center gap-2 rounded-xl px-3 py-3 text-xs font-semibold text-[var(--ink-muted)]"
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
@@ -128,7 +128,7 @@ export function AgentHistoryPanel({
|
||||
className={cn(
|
||||
"agent-history-item grid w-full grid-cols-[1fr_auto] gap-2 rounded-xl px-3 py-2 text-left transition-colors",
|
||||
active && !editing
|
||||
? "agent-history-item-active border border-blue-200"
|
||||
? "agent-history-item-active border border-[var(--surface-border)]"
|
||||
: "border border-transparent"
|
||||
)}
|
||||
initial="initial"
|
||||
@@ -153,7 +153,7 @@ export function AgentHistoryPanel({
|
||||
>
|
||||
删除“{session.title}”?
|
||||
</span>
|
||||
<span className="mt-0.5 block truncate text-xs text-slate-500">
|
||||
<span className="mt-0.5 block truncate text-xs text-[var(--ink-muted)]">
|
||||
删除后无法从历史记录中恢复
|
||||
</span>
|
||||
</motion.div>
|
||||
@@ -172,7 +172,7 @@ export function AgentHistoryPanel({
|
||||
>
|
||||
<Input
|
||||
autoFocus
|
||||
className="agent-history-rename-input h-8 rounded-lg bg-white text-xs"
|
||||
className="agent-history-rename-input h-8 rounded-lg bg-[var(--surface-reading)] text-xs"
|
||||
value={draftTitle}
|
||||
disabled={saving}
|
||||
onChange={(event) => setDraftTitle(event.currentTarget.value)}
|
||||
@@ -201,7 +201,7 @@ export function AgentHistoryPanel({
|
||||
>
|
||||
{session.title}
|
||||
</span>
|
||||
<span className="mt-0.5 block truncate text-xs text-slate-500">
|
||||
<span className="mt-0.5 block truncate text-xs text-[var(--ink-muted)]">
|
||||
{formatSessionUpdatedAt(session.updatedAt)}
|
||||
</span>
|
||||
</motion.button>
|
||||
@@ -214,7 +214,7 @@ export function AgentHistoryPanel({
|
||||
{itemLoading || saving || deleting ? (
|
||||
<Loader2
|
||||
size={13}
|
||||
className="animate-spin text-blue-600"
|
||||
className="animate-spin text-[var(--action-blue-hover)]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
@@ -289,7 +289,7 @@ export function AgentHistoryPanel({
|
||||
title="删除对话"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
className="text-slate-500 hover:bg-red-50 hover:text-red-600"
|
||||
className="text-[var(--ink-muted)] hover:bg-red-50 hover:text-red-600"
|
||||
disabled={itemLoading || deleting || !onDeleteSession}
|
||||
onClick={() => {
|
||||
cancelRename();
|
||||
@@ -307,7 +307,7 @@ export function AgentHistoryPanel({
|
||||
) : (
|
||||
<motion.div
|
||||
key="history-empty"
|
||||
className="agent-history-state rounded-xl px-3 py-3 text-xs text-slate-500"
|
||||
className="agent-history-state rounded-xl px-3 py-3 text-xs text-[var(--ink-muted)]"
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
|
||||
@@ -13,6 +13,9 @@ import { AnimatePresence, motion } from "motion/react";
|
||||
import { useId, useState, type ReactNode } from "react";
|
||||
import { cn } from "@/shared/ui/cn";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Checkbox } from "@/shared/ui/checkbox";
|
||||
import { Radio } from "@/shared/ui/radio";
|
||||
import { Textarea } from "@/shared/ui/textarea";
|
||||
import {
|
||||
StatusBadge,
|
||||
StatusDot,
|
||||
@@ -218,9 +221,9 @@ function ProgressList({ progress, running }: { progress: AgentChatProgress[]; ru
|
||||
return (
|
||||
<div className="agent-panel-nested rounded-xl p-2.5" data-agent-progress>
|
||||
<div className="flex min-h-8 items-center gap-2 px-0.5">
|
||||
<ListTree size={15} className="shrink-0 text-blue-600" aria-hidden="true" />
|
||||
<ListTree size={15} className="shrink-0 text-[var(--action-blue-hover)]" aria-hidden="true" />
|
||||
<span className="min-w-0 flex-1 truncate text-xs font-semibold text-slate-700">执行进度</span>
|
||||
<span className="flex shrink-0 items-center gap-1.5 text-xs font-medium text-slate-500">
|
||||
<span className="flex shrink-0 items-center gap-1.5 text-xs font-medium text-[var(--ink-muted)]">
|
||||
{running ? (
|
||||
<StatusDot tone="info" activity="live" aria-hidden="true" />
|
||||
) : null}
|
||||
@@ -236,8 +239,8 @@ function ProgressList({ progress, running }: { progress: AgentChatProgress[]; ru
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"-my-1 -mr-1.5 grid h-10 w-10 shrink-0 place-items-center rounded-md text-slate-400 transition-colors [@media(hover:hover)]:hover:text-blue-600",
|
||||
expanded && "text-blue-500"
|
||||
"-my-1 -mr-1.5 grid h-10 w-10 shrink-0 place-items-center rounded-md text-[var(--ink-muted)] transition-colors [@media(hover:hover)]:hover:text-[var(--action-blue-hover)]",
|
||||
expanded && "text-[var(--action-blue-hover)]"
|
||||
)}
|
||||
aria-controls={listId}
|
||||
aria-expanded={expanded}
|
||||
@@ -296,7 +299,7 @@ function ProgressList({ progress, running }: { progress: AgentChatProgress[]; ru
|
||||
{item.detail ? (
|
||||
<span
|
||||
className={cn(
|
||||
"block leading-5 text-slate-500",
|
||||
"block leading-5 text-[var(--ink-muted)]",
|
||||
compact ? "truncate" : "break-words"
|
||||
)}
|
||||
title={item.detail}
|
||||
@@ -312,7 +315,7 @@ function ProgressList({ progress, running }: { progress: AgentChatProgress[]; ru
|
||||
className={cn(
|
||||
"min-w-12 pt-0.5 text-right text-xs font-semibold",
|
||||
item.status === "running"
|
||||
? "text-blue-600"
|
||||
? "text-[var(--action-blue-hover)]"
|
||||
: [
|
||||
`status-tone-${progressStatusMeta[item.status].tone}`,
|
||||
"text-[var(--status-foreground)]"
|
||||
@@ -429,7 +432,7 @@ function PermissionRequestCard({
|
||||
<p className="truncate font-semibold text-slate-800" title={permission.target ?? permission.permission}>
|
||||
{getPermissionTitle(permission)}
|
||||
</p>
|
||||
<p className="mt-0.5 truncate font-mono text-xs text-slate-500" title={target}>
|
||||
<p className="mt-0.5 truncate font-mono text-xs text-[var(--ink-muted)]" title={target}>
|
||||
{target}
|
||||
</p>
|
||||
{permission.error ? (
|
||||
@@ -470,7 +473,7 @@ function PermissionRequestCard({
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-8 border-blue-200 bg-blue-50 px-2 text-xs text-blue-700 hover:bg-blue-100"
|
||||
className="h-8 border-[var(--surface-border)] bg-[var(--action-soft)] px-2 text-xs text-[var(--action-blue-hover)] hover:bg-[var(--action-selection-soft)]"
|
||||
disabled={disabled}
|
||||
onClick={() => reply("always")}
|
||||
>
|
||||
@@ -506,7 +509,7 @@ function QuestionList({
|
||||
onRejectQuestion?: (request: AgentQuestionRequest) => Promise<void> | void;
|
||||
}) {
|
||||
return (
|
||||
<AgentNestedBlock icon={HelpCircle} iconClassName="text-blue-600" title="补充信息" floating>
|
||||
<AgentNestedBlock icon={HelpCircle} iconClassName="text-[var(--action-blue-hover)]" title="补充信息" floating>
|
||||
<motion.div className="flex flex-col gap-1.5" variants={agentPanelListVariants} animate="animate">
|
||||
<AnimatePresence initial={false} mode="popLayout">
|
||||
{questions.slice(-3).map((request) => (
|
||||
@@ -587,7 +590,7 @@ function QuestionRequestCard({
|
||||
<p className="status-tone-danger mt-2 line-clamp-2 text-xs leading-4 text-[var(--status-foreground)]">{request.error}</p>
|
||||
) : null}
|
||||
{request.answers?.length ? (
|
||||
<p className="mt-2 truncate text-slate-500" title={request.answers.map((answer) => answer.join("、")).join(";")}>
|
||||
<p className="mt-2 truncate text-[var(--ink-muted)]" title={request.answers.map((answer) => answer.join("、")).join(";")}>
|
||||
已答:{request.answers.map((answer) => answer.join("、")).join(";")}
|
||||
</p>
|
||||
) : null}
|
||||
@@ -617,7 +620,7 @@ function QuestionRequestCard({
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-8 border-slate-200 bg-white px-2 text-xs text-slate-600 hover:bg-slate-50"
|
||||
className="h-8 border-[var(--surface-divider)] bg-[var(--surface-reading)] px-2 text-xs text-slate-600 hover:bg-[var(--surface-reading)]"
|
||||
disabled={submitting || !onRejectQuestion}
|
||||
onClick={reject}
|
||||
>
|
||||
@@ -686,21 +689,20 @@ function QuestionInput({
|
||||
<div className="space-y-1">
|
||||
{question.options.map((option) => {
|
||||
const checked = value.selected.includes(option.label);
|
||||
const Choice = question.multiple ? Checkbox : Radio;
|
||||
return (
|
||||
<label
|
||||
key={option.label}
|
||||
className={cn(
|
||||
"flex cursor-pointer items-start gap-2 rounded-md border px-2 py-1.5 transition-colors",
|
||||
checked
|
||||
? "border-blue-300 bg-blue-50/70 text-blue-700"
|
||||
: "border-slate-200/80 text-slate-600 hover:border-slate-300 hover:bg-slate-50/70",
|
||||
? "border-[var(--palette-brand)] bg-[var(--action-soft)]/70 text-[var(--action-blue-hover)]"
|
||||
: "border-[var(--surface-divider)]/80 text-slate-600 hover:border-[var(--surface-border)] hover:bg-[var(--surface-reading)]/70",
|
||||
disabled && "cursor-default opacity-60"
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type={question.multiple ? "checkbox" : "radio"}
|
||||
<Choice
|
||||
name={name}
|
||||
className="mt-0.5 h-3.5 w-3.5 accent-blue-600"
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
onChange={(event) => toggleOption(option.label, event.currentTarget.checked)}
|
||||
@@ -708,7 +710,7 @@ function QuestionInput({
|
||||
<span className="min-w-0">
|
||||
<span className="block font-semibold text-slate-700">{option.label}</span>
|
||||
{option.description ? (
|
||||
<span className="block leading-4 text-slate-500">{option.description}</span>
|
||||
<span className="block leading-4 text-[var(--ink-muted)]">{option.description}</span>
|
||||
) : null}
|
||||
</span>
|
||||
</label>
|
||||
@@ -719,27 +721,24 @@ function QuestionInput({
|
||||
className={cn(
|
||||
"flex cursor-pointer items-center gap-2 rounded-md border px-2 py-2 transition-colors",
|
||||
value.customSelected
|
||||
? "border-blue-300 bg-blue-50/70 text-blue-700"
|
||||
: "border-slate-200/80 text-slate-600 hover:border-slate-300 hover:bg-slate-50/70",
|
||||
? "border-[var(--palette-brand)] bg-[var(--action-soft)]/70 text-[var(--action-blue-hover)]"
|
||||
: "border-[var(--surface-divider)]/80 text-slate-600 hover:border-[var(--surface-border)] hover:bg-[var(--surface-reading)]/70",
|
||||
disabled && "cursor-default opacity-60"
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type={question.multiple ? "checkbox" : "radio"}
|
||||
name={name}
|
||||
className="h-3.5 w-3.5 accent-blue-600"
|
||||
checked={value.customSelected}
|
||||
disabled={disabled}
|
||||
onChange={(event) => toggleCustom(event.currentTarget.checked)}
|
||||
/>
|
||||
{question.multiple ? (
|
||||
<Checkbox name={name} checked={value.customSelected} disabled={disabled} onChange={(event) => toggleCustom(event.currentTarget.checked)} />
|
||||
) : (
|
||||
<Radio name={name} checked={value.customSelected} disabled={disabled} onChange={(event) => toggleCustom(event.currentTarget.checked)} />
|
||||
)}
|
||||
<span className="font-semibold text-slate-700">自定义回答</span>
|
||||
</label>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{showCustomInput ? (
|
||||
<textarea
|
||||
className="min-h-16 w-full resize-none rounded-md border border-slate-200/80 bg-transparent px-2 py-1.5 text-xs leading-5 text-slate-700 outline-hidden transition focus:border-blue-300 focus:ring-2 focus:ring-blue-100 disabled:opacity-60"
|
||||
<Textarea
|
||||
className="min-h-16 resize-none border-[var(--surface-divider)]/80 px-2 py-1.5 text-xs leading-5 text-slate-700 shadow-none md:text-xs disabled:opacity-60"
|
||||
aria-label="自定义回答内容"
|
||||
placeholder="输入自定义回答"
|
||||
value={value.custom}
|
||||
|
||||
@@ -85,7 +85,7 @@ function AgentBriefMetric({
|
||||
return (
|
||||
<div className="agent-brief-metric min-w-0 rounded-xl border px-2 py-2">
|
||||
<dt className="flex items-center gap-1.5 text-xs font-semibold text-slate-600">
|
||||
<span className="text-blue-600">{icon}</span>
|
||||
<span className="text-[var(--action-blue-hover)]">{icon}</span>
|
||||
{label}
|
||||
</dt>
|
||||
<dd className="mt-1 truncate text-xs font-semibold text-slate-900" title={value}>
|
||||
|
||||
@@ -44,7 +44,7 @@ function useElementVisibility() {
|
||||
|
||||
function PersonaFallback() {
|
||||
return (
|
||||
<span className="grid h-full w-full place-items-center rounded-full border border-slate-300/70 bg-slate-100 text-slate-500">
|
||||
<span className="grid h-full w-full place-items-center rounded-full border border-[var(--surface-border)]/70 bg-[var(--surface-control)] text-[var(--ink-muted)]">
|
||||
<Bot className="h-2/5 w-2/5" aria-hidden="true" />
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -50,15 +50,15 @@ function AgentUiResultCard({ result }: { result: AgentUiResult }) {
|
||||
<div className="agent-panel-message w-full rounded-2xl p-3 text-slate-700 shadow-xs">
|
||||
<div className="mb-3 flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="grid h-8 w-8 shrink-0 place-items-center rounded-xl bg-blue-100 text-blue-700">
|
||||
<span className="grid h-8 w-8 shrink-0 place-items-center rounded-xl bg-[var(--action-selection-soft)] text-[var(--action-blue-hover)]">
|
||||
{getEnvelopeIcon(envelope)}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-semibold text-slate-950">{getEnvelopeTitle(envelope)}</p>
|
||||
<p className="text-xs text-slate-500">{getSurfaceLabel(envelope.surface)}</p>
|
||||
<p className="text-xs text-[var(--ink-muted)]">{getSurfaceLabel(envelope.surface)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="shrink-0 rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-600">
|
||||
<span className="shrink-0 rounded-full bg-[var(--surface-control)] px-2 py-1 text-xs font-semibold text-slate-600">
|
||||
agent-ui@1
|
||||
</span>
|
||||
</div>
|
||||
@@ -85,7 +85,7 @@ function SafeChartEnvelope({ envelope }: { envelope: Extract<UIEnvelope, { kind:
|
||||
<p className="truncate text-sm font-semibold text-slate-950" title={spec.title}>
|
||||
{spec.title ?? "Agent 图表"}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-slate-500">
|
||||
<p className="mt-1 text-xs text-[var(--ink-muted)]">
|
||||
{spec.chartType === "bar" ? "柱状图" : "折线图"}
|
||||
{spec.yAxisName ? ` · ${spec.yAxisName}` : ""}
|
||||
</p>
|
||||
@@ -100,7 +100,7 @@ function SafeChartEnvelope({ envelope }: { envelope: Extract<UIEnvelope, { kind:
|
||||
</div>
|
||||
</div>
|
||||
<SafeChartSvg spec={spec} data={data} />
|
||||
{spec.xAxisName ? <p className="mt-2 text-center text-xs text-slate-500">{spec.xAxisName}</p> : null}
|
||||
{spec.xAxisName ? <p className="mt-2 text-center text-xs text-[var(--ink-muted)]">{spec.xAxisName}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -302,7 +302,7 @@ function PropRows({ props, labels }: { props: unknown; labels: Record<string, st
|
||||
<dl className="grid grid-cols-2 gap-2">
|
||||
{rows.map((row) => (
|
||||
<div key={row.label} className="surface-reading min-w-0 rounded-lg px-2.5 py-2">
|
||||
<dt className="truncate text-xs text-slate-500">{row.label}</dt>
|
||||
<dt className="truncate text-xs text-[var(--ink-muted)]">{row.label}</dt>
|
||||
<dd className="mt-1 truncate text-sm font-semibold text-slate-900" title={row.value}>
|
||||
{row.value}
|
||||
</dd>
|
||||
@@ -381,7 +381,7 @@ function chartStroke(index: number) {
|
||||
}
|
||||
|
||||
function chartColorClass(index: number) {
|
||||
return ["bg-blue-600", "bg-teal-600", "bg-orange-500", "bg-sky-700"][index % 4] ?? "bg-blue-600";
|
||||
return ["bg-[var(--action-blue)]", "bg-teal-600", "bg-orange-500", "bg-sky-700"][index % 4] ?? "bg-[var(--action-blue)]";
|
||||
}
|
||||
|
||||
function formatChartNumber(value: number) {
|
||||
|
||||
Reference in New Issue
Block a user