diff --git a/web/src/components/canvas/canvas-batch-table-node.tsx b/web/src/components/canvas/canvas-batch-table-node.tsx
index fdb1b2b17..0265be90e 100644
--- a/web/src/components/canvas/canvas-batch-table-node.tsx
+++ b/web/src/components/canvas/canvas-batch-table-node.tsx
@@ -359,7 +359,7 @@ function BatchReferenceHandle({ column, index, theme, commonStyle, onConnectStar
}, []);
return (
-
diff --git a/web/src/components/canvas/canvas-cloud-agent-chat-ui.tsx b/web/src/components/canvas/canvas-cloud-agent-chat-ui.tsx
index 2ae9b5ddf..5d8f61160 100644
--- a/web/src/components/canvas/canvas-cloud-agent-chat-ui.tsx
+++ b/web/src/components/canvas/canvas-cloud-agent-chat-ui.tsx
@@ -1051,6 +1051,7 @@ export function AgentChatComposer({
whileTap={canSubmit && !reducedMotion ? { scale: 0.9, y: 1 } : undefined}
animate={stopping && !reducedMotion ? { scale: [1, 0.94, 1] } : { scale: 1, rotate: 0 }}
transition={sending && !reducedMotion ? { duration: 0.42, ease: "easeOut" } : { type: "spring", stiffness: 420, damping: 24 }}
+ data-icon-only
className="agent-composer-send grid size-7 shrink-0 place-items-center rounded-full p-0 outline-none transition-[background-color,box-shadow,color,transform] duration-200 focus-visible:ring-2 focus-visible:ring-current/35 disabled:cursor-not-allowed"
style={{
background: canSubmit || sending ? theme.accent.primary : theme.spatial.surface,
diff --git a/web/src/components/canvas/canvas-cloud-agent-panel.tsx b/web/src/components/canvas/canvas-cloud-agent-panel.tsx
index 3cc7bea46..9d7470930 100644
--- a/web/src/components/canvas/canvas-cloud-agent-panel.tsx
+++ b/web/src/components/canvas/canvas-cloud-agent-panel.tsx
@@ -1038,10 +1038,7 @@ function formatContextBytes(bytes: number | undefined) {
}
function AgentContextRing({ view }: { view: AgentContextUsageView }) {
- const reducedMotion = useReducedMotion();
const [open, setOpen] = useState(false);
- const radius = 15;
- const circumference = 2 * Math.PI * radius;
const marker = view.compactRatio && view.compactRatio > 0 && view.compactRatio < 1 ? view.compactRatio : undefined;
const percent = view.ratio === undefined ? view.label : `${Math.round(view.ratio * 100)}%`;
const meterLabel = view.ratio === undefined ? "—" : percent;
@@ -1147,20 +1144,15 @@ function AgentContextRing({ view }: { view: AgentContextUsageView }) {
title="查看上下文用量"
onPointerDown={(event) => event.stopPropagation()}
>
-
-
+
+ {marker ? : null}
{meterLabel}
diff --git a/web/src/components/canvas/canvas-cloud-agent.css b/web/src/components/canvas/canvas-cloud-agent.css
index 7b3a9f863..272e5acbe 100644
--- a/web/src/components/canvas/canvas-cloud-agent.css
+++ b/web/src/components/canvas/canvas-cloud-agent.css
@@ -51,26 +51,26 @@
}
.agent-context-ring {
+ --agent-context-ring-color: var(--agent-accent, currentColor);
position: relative;
display: inline-flex;
flex: none;
align-items: center;
- gap: 7px;
- min-width: 68px;
- height: 32px;
- padding: 3px 8px 3px 4px;
- border: 1px solid color-mix(in srgb, var(--agent-ink) 10%, transparent);
+ gap: 6px;
+ min-width: 64px;
+ height: 30px;
+ padding: 2px 7px 2px 3px;
+ border: 0;
border-radius: 999px;
- background: color-mix(in srgb, var(--agent-ink) 4%, transparent);
+ background: color-mix(in srgb, var(--agent-ink) 5%, transparent);
color: var(--agent-caption);
cursor: pointer;
- transition: border-color 160ms ease, background-color 160ms ease, color 160ms ease;
+ transition: background-color 160ms ease, color 160ms ease;
}
.agent-context-ring:hover,
.agent-context-ring[aria-expanded="true"] {
- border-color: color-mix(in srgb, var(--agent-accent) 48%, transparent);
- background: var(--agent-surface-raised);
+ background: color-mix(in srgb, var(--agent-ink) 9%, transparent);
color: var(--agent-ink);
}
@@ -79,45 +79,61 @@
outline-offset: 2px;
}
-.agent-context-ring-visual,
-.agent-context-ring-visual svg {
+.agent-context-ring-visual {
+ position: relative;
display: block;
- width: 25px;
- height: 25px;
+ width: 26px;
+ height: 26px;
flex: none;
+ overflow: hidden;
+ border-radius: 50%;
+ background: conic-gradient(
+ from -90deg,
+ var(--agent-context-ring-color) 0 var(--agent-context-progress),
+ color-mix(in srgb, var(--agent-ink) 11%, transparent) var(--agent-context-progress) 100%
+ );
}
-.agent-context-ring-visual svg {
- transform: rotate(-90deg);
-}
-
-.agent-context-ring circle {
- fill: none;
- stroke-linecap: round;
-}
-
-.agent-context-ring-track {
- stroke: color-mix(in srgb, var(--agent-ink) 18%, transparent);
- stroke-width: 2.4;
-}
-
-.agent-context-ring-value {
- stroke: var(--agent-accent, currentColor);
- stroke-width: 2.4;
- transition: stroke-dashoffset 240ms ease;
+.agent-context-ring-visual::after {
+ position: absolute;
+ inset: 5px;
+ border-radius: 50%;
+ background: color-mix(in srgb, var(--agent-surface-raised) 92%, transparent);
+ content: "";
}
.agent-context-ring-marker {
- fill: var(--agent-ink);
- stroke: none;
+ position: absolute;
+ inset: 2px;
+ z-index: 1;
+ border-radius: 50%;
+ transform: rotate(var(--agent-context-marker-angle));
}
-.agent-context-ring.is-watch .agent-context-ring-value { stroke: #d9973b; }
-.agent-context-ring.is-compress .agent-context-ring-value,
-.agent-context-ring.is-compacting .agent-context-ring-value { stroke: #d4534e; }
-.agent-context-ring.is-compacting .agent-context-ring-visual svg { animation: agent-context-spin 1.4s linear infinite; }
-.agent-context-ring.is-unknown .agent-context-ring-value,
-.agent-context-ring.is-stale .agent-context-ring-value { stroke-dasharray: 4 6; }
+.agent-context-ring-marker::after {
+ position: absolute;
+ top: -1px;
+ left: 50%;
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: var(--agent-context-ring-color);
+ content: "";
+ transform: translateX(-50%);
+}
+
+.agent-context-ring.is-watch {
+ --agent-context-ring-color: #d9973b;
+}
+
+.agent-context-ring.is-compress,
+.agent-context-ring.is-compacting {
+ --agent-context-ring-color: #d4534e;
+}
+
+.agent-context-ring.is-compacting .agent-context-ring-visual { animation: agent-context-spin 1.4s linear infinite; }
+.agent-context-ring.is-unknown .agent-context-ring-visual,
+.agent-context-ring.is-stale .agent-context-ring-visual { opacity: 0.72; }
.agent-context-meter-copy {
display: grid;
@@ -267,7 +283,6 @@
border-radius: inherit;
background: #8b93ff;
background: linear-gradient(90deg, color-mix(in srgb, var(--agent-accent, #8b93ff) 58%, white), var(--agent-accent, #8b93ff));
- box-shadow: 0 0 12px color-mix(in srgb, var(--agent-accent, #8b93ff) 48%, transparent);
transition: width 240ms ease;
}
@@ -359,8 +374,8 @@
}
@media (prefers-reduced-motion: reduce) {
- .agent-context-ring.is-compacting .agent-context-ring-visual svg { animation: none; }
- .agent-context-ring-value,
+ .agent-context-ring.is-compacting .agent-context-ring-visual { animation: none; }
+ .agent-context-ring-visual,
.agent-context-progress > span { transition: none; }
}
diff --git a/web/src/components/canvas/canvas-node-emotion-panel.tsx b/web/src/components/canvas/canvas-node-emotion-panel.tsx
index ba1f50288..e61b98cd0 100644
--- a/web/src/components/canvas/canvas-node-emotion-panel.tsx
+++ b/web/src/components/canvas/canvas-node-emotion-panel.tsx
@@ -173,7 +173,7 @@ function EmotionPad({ preset, onChange }: { preset: CanvasEmotionPreset; onChang
const distance = pointer ? Math.hypot(pointer.x - column, pointer.y - row) : 4;
const proximity = dragging ? Math.max(0, 1 - distance / 1.7) : 0;
return (
- onChange(item)}>
+ onChange(item)}>
void openAnnouncements()}
aria-label={unreadCount ? `系统公告,${unreadCount} 条未读` : "系统公告"}
title="系统公告"
+ data-icon-only={showLabel ? undefined : true}
>
diff --git a/web/src/components/ui/aceternity/floating-dock.tsx b/web/src/components/ui/aceternity/floating-dock.tsx
index d636bd069..1a79e7d99 100644
--- a/web/src/components/ui/aceternity/floating-dock.tsx
+++ b/web/src/components/ui/aceternity/floating-dock.tsx
@@ -259,6 +259,7 @@ function DockCommandButton({ command, mouseX, metrics, motionEnabled, compact, s
type="button"
aria-label={command.label}
title={nativeTitle}
+ data-icon-only
aria-expanded={command.expands ? command.active || undefined : undefined}
aria-pressed={command.expands ? undefined : command.active || undefined}
disabled={command.disabled}
diff --git a/web/src/components/ui/base/buttons/icon-button.tsx b/web/src/components/ui/base/buttons/icon-button.tsx
index 47b353f5d..f6594db59 100644
--- a/web/src/components/ui/base/buttons/icon-button.tsx
+++ b/web/src/components/ui/base/buttons/icon-button.tsx
@@ -19,7 +19,12 @@ import { cn } from "@/lib/utils";
* 明暗:全部颜色走 token;active 按压底用 bg-surface-active,明暗自动适配。
*/
export const iconButtonVariants = cva(
- ["inline-flex shrink-0 items-center justify-center rounded-md", "outline-none focus-visible:outline-none data-[input-modality=keyboard]:focus-visible:ring-2 data-[input-modality=keyboard]:focus-visible:ring-ring", "disabled:pointer-events-none disabled:opacity-45", "motion-safe:active:scale-[0.96]"],
+ [
+ "inline-flex shrink-0 items-center justify-center rounded-md",
+ "outline-none focus-visible:outline-none data-[input-modality=keyboard]:focus-visible:ring-2 data-[input-modality=keyboard]:focus-visible:ring-ring",
+ "disabled:pointer-events-none disabled:opacity-45",
+ "motion-safe:active:scale-[0.96]",
+ ],
{
variants: {
variant: {
diff --git a/web/src/components/ui/base/buttons/tool-button.tsx b/web/src/components/ui/base/buttons/tool-button.tsx
index 92eaff387..31ca61b63 100644
--- a/web/src/components/ui/base/buttons/tool-button.tsx
+++ b/web/src/components/ui/base/buttons/tool-button.tsx
@@ -21,7 +21,12 @@ import { cn } from "@/lib/utils";
* 明暗:全部颜色走 token;active 用按压底语义 token,明暗自动适配。
*/
export const toolButtonVariants = cva(
- ["inline-flex select-none items-center justify-center gap-1.5 rounded-md px-3", "font-medium transition-colors", "outline-none focus-visible:outline-none data-[input-modality=keyboard]:focus-visible:ring-2 data-[input-modality=keyboard]:focus-visible:ring-ring", "disabled:pointer-events-none disabled:opacity-45"],
+ [
+ "inline-flex select-none items-center justify-center gap-1.5 rounded-md px-3",
+ "font-medium transition-colors",
+ "outline-none focus-visible:outline-none data-[input-modality=keyboard]:focus-visible:ring-2 data-[input-modality=keyboard]:focus-visible:ring-ring",
+ "disabled:pointer-events-none disabled:opacity-45",
+ ],
{
variants: {
variant: {
diff --git a/web/src/components/ui/base/select/select.tsx b/web/src/components/ui/base/select/select.tsx
index f610240c3..37ca1c952 100644
--- a/web/src/components/ui/base/select/select.tsx
+++ b/web/src/components/ui/base/select/select.tsx
@@ -56,7 +56,7 @@ export const Select = forwardRef(function Select({
onKeyDown?.(event);
}}
onFocus={(event) => {
- setInputModality((current) => current === "unknown" ? "keyboard" : current);
+ setInputModality((current) => (current === "unknown" ? "keyboard" : current));
onFocus?.(event);
}}
onBlur={(event) => {
diff --git a/web/src/pages/canvas/canvas-project-world-layers.tsx b/web/src/pages/canvas/canvas-project-world-layers.tsx
index 13c14541c..a1f71bf97 100644
--- a/web/src/pages/canvas/canvas-project-world-layers.tsx
+++ b/web/src/pages/canvas/canvas-project-world-layers.tsx
@@ -245,6 +245,7 @@ function BatchConnectionHandle({ scale, count, active, onPointerDown }: { scale:
svg:only-child)) {
+/* 带文字的按钮不要贴着左边缘。纯图标按钮保持正方形,不额外加水平内边距。
+ :has 只能识别 svg 是直接唯一子元素的情况;图标被动画 span、角标、sr-only 等
+ wrapper 包住时检测不到,这类按钮用 data-icon-only 显式声明豁免。 */
+:where(button:not(.ant-btn-icon-only):not([data-icon-only]), .ant-btn:not(.ant-btn-icon-only):not([data-icon-only])):not(:has(> svg:only-child)) {
padding-inline: max(12px, var(--space-3));
}
diff --git a/web/src/styles/unified-inputs.css b/web/src/styles/unified-inputs.css
index 1a0a20e9a..29e74f148 100644
--- a/web/src/styles/unified-inputs.css
+++ b/web/src/styles/unified-inputs.css
@@ -20,19 +20,14 @@
--unified-input-addon: color-mix(in srgb, var(--foreground) 4%, transparent);
}
-:where(
- .ant-input:not(textarea),
- .ant-input-affix-wrapper,
- .ant-input-number,
- .ant-input-number-affix-wrapper,
- .ant-input-group-wrapper .ant-input-group-addon,
- .ant-otp-input
-) {
+:where(.ant-input:not(textarea), .ant-input-affix-wrapper, .ant-input-number, .ant-input-number-affix-wrapper, .ant-input-group-wrapper .ant-input-group-addon, .ant-otp-input) {
border-color: transparent !important;
border-radius: var(--r-lg) !important;
background: var(--unified-input-surface) !important;
box-shadow: none !important;
- transition: background-color var(--motion-state, 180ms) ease, color var(--motion-state, 180ms) ease !important;
+ transition:
+ background-color var(--motion-state, 180ms) ease,
+ color var(--motion-state, 180ms) ease !important;
}
:where(textarea.ant-input, .ant-input-textarea textarea.ant-input) {
@@ -53,25 +48,14 @@
box-shadow: none !important;
}
-:where(
- .ant-input:focus,
- .ant-input-focused,
- .ant-input-affix-wrapper-focused,
- .ant-input-number-focused,
- .ant-input-number-affix-wrapper-focused
-) {
+:where(.ant-input:focus, .ant-input-focused, .ant-input-affix-wrapper-focused, .ant-input-number-focused, .ant-input-number-affix-wrapper-focused) {
border-color: transparent !important;
background: var(--unified-input-surface-focus) !important;
outline: none !important;
box-shadow: none !important;
}
-:where(
- .ant-input-affix-wrapper > .ant-input,
- .ant-input-number .ant-input-number-input,
- .ant-input-number-affix-wrapper .ant-input-number-input,
- .ant-input-search .ant-input
-) {
+:where(.ant-input-affix-wrapper > .ant-input, .ant-input-number .ant-input-number-input, .ant-input-number-affix-wrapper .ant-input-number-input, .ant-input-search .ant-input) {
border: 0 !important;
border-radius: 0 !important;
background: transparent !important;
@@ -98,40 +82,22 @@
}
/* 外壳已经承担视觉时,内部输入不再形成第二层线条。 */
-:where(
- .ant-input-affix-wrapper .ant-input,
- .ant-input-group .ant-input,
- .ant-input-group .ant-input-affix-wrapper,
- .ant-otp .ant-otp-input-wrapper .ant-input
-) {
+:where(.ant-input-affix-wrapper .ant-input, .ant-input-group .ant-input, .ant-input-group .ant-input-affix-wrapper, .ant-otp .ant-otp-input-wrapper .ant-input) {
border-color: transparent !important;
background: transparent !important;
box-shadow: none !important;
}
/* 错误和警告仍需要可识别的状态,但不再叠加发光描边。 */
-:where(
- .ant-input-status-error:not(.ant-input-disabled),
- .ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled),
- .ant-input-number-status-error:not(.ant-input-number-disabled)
-) {
+:where(.ant-input-status-error:not(.ant-input-disabled), .ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled), .ant-input-number-status-error:not(.ant-input-number-disabled)) {
background: color-mix(in srgb, var(--destructive, #ef4444) 10%, transparent) !important;
}
-:where(
- .ant-input-status-warning:not(.ant-input-disabled),
- .ant-input-affix-wrapper-status-warning:not(.ant-input-affix-wrapper-disabled),
- .ant-input-number-status-warning:not(.ant-input-number-disabled)
-) {
+:where(.ant-input-status-warning:not(.ant-input-disabled), .ant-input-affix-wrapper-status-warning:not(.ant-input-affix-wrapper-disabled), .ant-input-number-status-warning:not(.ant-input-number-disabled)) {
background: color-mix(in srgb, var(--warning, #f59e0b) 12%, transparent) !important;
}
-:where(
- .ant-input-disabled,
- .ant-input-affix-wrapper-disabled,
- .ant-input-number-disabled,
- .ant-input-number-affix-wrapper-disabled
-) {
+:where(.ant-input-disabled, .ant-input-affix-wrapper-disabled, .ant-input-number-disabled, .ant-input-number-affix-wrapper-disabled) {
background: color-mix(in srgb, var(--foreground) 4%, transparent) !important;
color: color-mix(in srgb, var(--foreground) 42%, transparent) !important;
box-shadow: none !important;