diff --git a/src/backend/core/evolution/agent_profile.py b/src/backend/core/evolution/agent_profile.py index 6afcbf3..7d73981 100644 --- a/src/backend/core/evolution/agent_profile.py +++ b/src/backend/core/evolution/agent_profile.py @@ -90,8 +90,13 @@ # out-of-range value hides the fact that something proposed it. MIN_BUDGET_MULTIPLIER = 0.25 MAX_BUDGET_MULTIPLIER = 3.0 +# 0 is the default and means "no turn cap" — the main agent is unbounded unless +# a published profile deliberately fences it. Any other value must land in the +# band below; the band starts at 3 because a 1-2 turn main agent cannot finish a +# single tool round-trip. MIN_REACT_TURNS = 3 MAX_REACT_TURNS = 80 +UNBOUNDED_REACT_TURNS = 0 MIN_MEMORY_BUDGET_MS = 100 MAX_MEMORY_BUDGET_MS = 5000 MIN_LOOP_ATTEMPTS = 2 @@ -178,7 +183,10 @@ class AgentProfile: subagent_routes: List[SubagentRoute] = field(default_factory=list) memory_budget_ms: int = 600 - max_react_turns: int = 50 + # 0 = 不限(默认)。See UNBOUNDED_REACT_TURNS: the main agent's round ceiling + # was removed, so a turn budget here is an opt-in narrowing a published + # profile performs deliberately, not a floor every profile inherits. + max_react_turns: int = UNBOUNDED_REACT_TURNS budget_multiplier: float = 1.0 reviewer_checkpoints: List[str] = field(default_factory=list) intervention_rules: List[InterventionRule] = field(default_factory=list) @@ -225,7 +233,10 @@ def from_dict(cls, raw: Dict[str, Any]) -> "AgentProfile": SubagentRoute.from_dict(r) for r in (raw.get("subagent_routes") or []) ], memory_budget_ms=int(raw.get("memory_budget_ms") or 600), - max_react_turns=int(raw.get("max_react_turns") or 20), + # Missing key → unbounded, not an invented ceiling: a profile + # published for unrelated reasons (tool allowlist, memory budget) + # must not silently re-fence the main loop. + max_react_turns=int(raw.get("max_react_turns") or UNBOUNDED_REACT_TURNS), budget_multiplier=float(raw.get("budget_multiplier") or 1.0), reviewer_checkpoints=[str(c) for c in (raw.get("reviewer_checkpoints") or [])], intervention_rules=[ @@ -253,11 +264,10 @@ def builtin_profile() -> AgentProfile: version="v0", tool_allowlist=None, memory_budget_ms=600, - # The main agent's existing cap, reproduced exactly. Installing profile - # resolution must change nothing until a profile is published; a - # "sensible default" here would be a behaviour change disguised as a - # refactor. - max_react_turns=50, + # The main agent's runtime default, reproduced exactly — which is now + # "no cap". This field only bounds the loop when a published profile + # sets it on purpose. + max_react_turns=UNBOUNDED_REACT_TURNS, budget_multiplier=1.0, loop_max_attempts_per_requirement=6, loop_strategy_change_after=2, @@ -323,9 +333,12 @@ def validate_profile( f"预算倍数 {profile.budget_multiplier} 超出 " f"[{MIN_BUDGET_MULTIPLIER}, {MAX_BUDGET_MULTIPLIER}]" ) - if not (MIN_REACT_TURNS <= profile.max_react_turns <= MAX_REACT_TURNS): + if profile.max_react_turns != UNBOUNDED_REACT_TURNS and not ( + MIN_REACT_TURNS <= profile.max_react_turns <= MAX_REACT_TURNS + ): problems.append( - f"最大轮数 {profile.max_react_turns} 超出 [{MIN_REACT_TURNS}, {MAX_REACT_TURNS}]" + f"最大轮数 {profile.max_react_turns} 超出 " + f"[{MIN_REACT_TURNS}, {MAX_REACT_TURNS}]({UNBOUNDED_REACT_TURNS} 表示不限)" ) if not (MIN_MEMORY_BUDGET_MS <= profile.memory_budget_ms <= MAX_MEMORY_BUDGET_MS): problems.append( diff --git a/src/backend/core/llm/agent_factory.py b/src/backend/core/llm/agent_factory.py index 1511123..eb8884a 100644 --- a/src/backend/core/llm/agent_factory.py +++ b/src/backend/core/llm/agent_factory.py @@ -80,6 +80,7 @@ "确认后系统会自动逐条执行并把结果实时推送给用户,无需你重复调用。\n" "若请求确实只针对单一对象/单一概念,再走普通回答即可。\n" ) + from orchestration.registry import AgentSpec load_dotenv() @@ -92,6 +93,32 @@ _HTTP_MCP_FAIL_COOLDOWN_S = 60.0 +def _render_turn_budget_hint(max_iters: int) -> str: + """Tell an agent that *does* have a turn budget how to spend it. + + Stating the budget up front is cheaper than shouting about it at the end: + the wrap-up reminder (:class:`IterBudgetReminderMiddleware`) can only ask a + loop that already burned its rounds one tool at a time to salvage + something, while this changes how the rounds get spent in the first place. + Parallel fan-out within one round is the lever — a round is one reasoning + step, not one tool call, so the budget binds serial round-trips, never the + total number of tool calls. + + Only rendered where a bound actually exists (sub-agents, turbo, custom + agents, an explicit operator cap). The main agent is unbounded and must not + be told otherwise — a false scarcity claim would make it cut work short. + """ + return ( + "\n\n## 轮次预算\n" + f"本次运行最多 {max_iters} 轮「推理 → 工具调用」。一轮里可以同时发起任意多个" + "工具调用,所以受限的是串行往返次数,不是工具调用总数。\n" + "- 先想清楚需要哪些信息,再**在同一轮里并行发起**全部彼此独立的调用" + "(一次读多个文件、一次检索多个关键词),不要一轮只调一个。\n" + "- 只有后一步的参数确实要等前一步的结果时,才分成下一轮。\n" + "- 同一个操作连续失败两次就换思路或如实报告,不要用剩余轮次反复重试。\n" + ) + + def _effective_mcp_server_keys( cfg, agent_spec: Optional[AgentSpec], @@ -2053,13 +2080,29 @@ def _build_toolkit() -> Toolkit: # toolkit.get_agent_skill_prompt(), so no separate hook is needed. # ── Resolve agent name and max_iters ── - _DEFAULT_MAIN_ITERS = 50 + # + # **The main agent has no turn cap.** A fixed round ceiling bounds the wrong + # axis: what a long task actually exhausts is context, not rounds, and any + # number picked here is simultaneously too low for report-scale work and too + # high to catch a genuine runaway. Neither of the two things a cap was + # supposed to buy needs it: + # - runaway protection lives in the chat-run watchdog, which can see + # wall-clock and output (CHAT_RUN_INACTIVITY_TIMEOUT_SEC / + # CHAT_RUN_MAX_AGE_SEC / CHAT_RUN_HARD_MAX_AGE_SEC reap silent and + # immortal runs regardless of how many rounds they took); + # - context exhaustion is handled by compaction. + # ``_UNBOUNDED_ITERS`` is a loop backstop, not a budget — AgentScope's + # ReActConfig needs an int, and this one sits far above any real turn. + # + # Bounded budgets survive only where the bound is a deliberate contract: + # sub-agents (a delegated task that must come back), turbo's quick-lookup + # cap, a custom agent's own ``max_iters``, a published profile's turn + # budget, and the CHAT_MAIN_MAX_ITERS opt-in for operators who do want the + # main agent fenced. + _UNBOUNDED_ITERS = 100_000 _DEFAULT_SUBAGENT_ITERS = 10 _agent_name = "hugagent_agent" - # The active profile's turn budget governs the main agent. The built-in - # profile carries the same 50 this used, so resolution is a no-op until a - # profile is published. - _max_iters = profile.max_react_turns if user_agent is None else _DEFAULT_MAIN_ITERS + _max_iters = _UNBOUNDED_ITERS if max_iters is not None: _max_iters = max_iters elif user_agent is not None: @@ -2067,15 +2110,20 @@ def _build_toolkit() -> Toolkit: f"subagent_{user_agent.agent_id}" if isolated else f"agent_{user_agent.agent_id}" ) _max_iters = user_agent.max_iters or ( - _DEFAULT_SUBAGENT_ITERS if isolated else _DEFAULT_MAIN_ITERS + _DEFAULT_SUBAGENT_ITERS if isolated else _UNBOUNDED_ITERS ) elif isolated: _max_iters = _DEFAULT_SUBAGENT_ITERS else: - # Main-agent env override for long-running tasks. Wins over the profile - # value: the profile validation range tops out at 80 turns, far below - # what e.g. a multi-hour report-generation run needs (container restart - # required to change, like all env config). + # Both main-agent caps are opt-in and absent by default: the built-in + # profile now carries 0 ("no cap"), so profile resolution only bounds the + # loop when someone publishes a profile that deliberately sets a turn + # budget. The env override still wins over it — the profile validation + # range tops out at 80 turns, far below what e.g. a multi-hour + # report-generation run needs (container restart required to change, + # like all env config). + if profile.max_react_turns: + _max_iters = profile.max_react_turns from core.config.settings import _env, _int _env_iters = _int(_env("CHAT_MAIN_MAX_ITERS"), 0) @@ -2096,6 +2144,13 @@ def _build_toolkit() -> Toolkit: # else: 开了代码执行位且模式没配上限 → 不套极速的检索档硬顶(默认 4 轮 # 会把跑代码的任务掐死),按 profile/env 的常规上限走。 + # Budget spending policy, injected only where a budget exists. Appended last + # so it sees the final number after every narrowing above (turbo included); + # skipped without tools, where "spend your rounds on parallel calls" has + # nothing to describe. + if _max_iters < _UNBOUNDED_ITERS and not disable_tools: + system_prompt += _render_turn_budget_hint(_max_iters) + # ── Create the Agent (AgentScope 2.0) ── # Note: long_term_memory is not passed — mem0 is fully stripped from the SSE # main path (manual non-blocking pipeline). diff --git a/src/backend/tests/evolution/test_policies_and_memory_ops.py b/src/backend/tests/evolution/test_policies_and_memory_ops.py index e394e56..854d810 100644 --- a/src/backend/tests/evolution/test_policies_and_memory_ops.py +++ b/src/backend/tests/evolution/test_policies_and_memory_ops.py @@ -22,8 +22,13 @@ def test_builtin_profile_reproduces_todays_constants(): policy = PL.policy_from_profile(AP.builtin_profile()) assert policy.max_attempts_per_requirement == 6 assert policy.strategy_change_after == 2 - # The main ReAct agent's existing cap, unchanged. - assert AP.builtin_profile().max_react_turns == 50 + # The main ReAct agent has no turn cap; the built-in profile must not + # reintroduce one behind the runtime's back. + assert AP.builtin_profile().max_react_turns == AP.UNBOUNDED_REACT_TURNS + # A profile published without an explicit turn budget stays unbounded too — + # a missing key must not become an invented ceiling. + unspecified = AP.AgentProfile.from_dict({"profile_id": "p"}) + assert unspecified.max_react_turns == AP.UNBOUNDED_REACT_TURNS def test_longer_stall_escalates_rather_than_repeating_the_mild_response(): diff --git a/src/backend/tests/test_iter_budget_middleware.py b/src/backend/tests/test_iter_budget_middleware.py index 3c2897e..4475d0c 100644 --- a/src/backend/tests/test_iter_budget_middleware.py +++ b/src/backend/tests/test_iter_budget_middleware.py @@ -143,3 +143,39 @@ def test_force_text_kill_switch(monkeypatch): agent = _fake_agent(max_iters=10, cur_iter=9) out = _force(mw, agent, {"tool_choice": None}) assert out.get("tool_choice") is None + + +# ── Turn-budget hint (static policy, injected only where a budget exists) ── + + +def test_turn_budget_hint_states_the_actual_number(): + from core.llm.agent_factory import _render_turn_budget_hint + + hint = _render_turn_budget_hint(12) + assert "12 轮" in hint + # The point of the hint is the parallel fan-out strategy, not the scary + # number — one that only announces a limit makes the model cautious + # rather than efficient. + assert "并行" in hint + + +def test_turn_budget_hint_is_byte_stable(): + """Prefix-cache safety: same budget → same bytes, every request.""" + from core.llm.agent_factory import _render_turn_budget_hint + + assert _render_turn_budget_hint(30) == _render_turn_budget_hint(30) + + +def test_unbounded_profile_is_valid_and_bad_bounds_are_not(): + from core.evolution import agent_profile as AP + + unbounded = AP.builtin_profile() + assert unbounded.max_react_turns == AP.UNBOUNDED_REACT_TURNS + ok, problems = AP.validate_profile(unbounded) + assert ok, problems + + fenced = AP.builtin_profile() + fenced.max_react_turns = 1 # below MIN_REACT_TURNS, and not the 0 sentinel + ok, problems = AP.validate_profile(fenced) + assert ok is False + assert any("最大轮数" in p for p in problems) diff --git a/src/frontend/src/styles/chat.css b/src/frontend/src/styles/chat.css index 64653bc..9bb1e57 100644 --- a/src/frontend/src/styles/chat.css +++ b/src/frontend/src/styles/chat.css @@ -598,6 +598,21 @@ color:transparent; animation:jx-kf-shimmer 1.6s linear infinite; } +/* 深色下把流光方向掰回来:上面那条渐变是「slate-400 打底 + slate-600 高光」, + 靠"把字压暗"做高光——浅色底成立,暗底上就完全反了,高光处 (#475569) 对比度不到 2, + 于是「深度拥抱中…」会朝着看不见的方向脉动。深色档改成暗→亮,高光才是最亮的一点。 */ +:root[data-theme="dark"] .jx-turnStatus-label{ + background-image:linear-gradient(90deg, + color-mix(in srgb, var(--color-text) 52%, transparent) 0%, + color-mix(in srgb, var(--color-text) 96%, transparent) 50%, + color-mix(in srgb, var(--color-text) 52%, transparent) 100%); + background-size:200% 100%; + /* ⚠️ 必须用 background-image 而不是 background 简写:简写会把 background-clip 一并 + 重置回 border-box,这行字正是靠 clip:text 把渐变裁进字形的,重置后整行会变成一块 + 实心灰方块、文字消失(已踩)。这两行是保险,即便将来改回简写也不至于塌掉。 */ + -webkit-background-clip:text; + background-clip:text; +} .jx-turnStatus-clock{ margin-left:2px; font-size:12px; @@ -605,6 +620,10 @@ font-variant-numeric:tabular-nums; color:var(--color-text-placeholder); } +/* 与折叠摘要同理:placeholder 令牌在深色档只有 2.65 对比度,计时数字看不清 */ +:root[data-theme="dark"] .jx-turnStatus-clock{ + color:var(--color-text-tertiary); +} /* ── 上传文件预览卡片(输入区 & 用户消息) ──────── */ @@ -4162,8 +4181,8 @@ textarea.jx-composer{ .jx-deploySwitcherMenu { position: absolute; top: calc(100% + 6px); left: 50%; transform: translateX(-50%); min-width: 288px; z-index: 50; padding: 6px; - background: var(--color-bg-white, #fff); - border: 1px solid var(--color-border, #e5e6eb); + background: var(--color-bg-elevated); + border: 1px solid var(--color-border); border-radius: 12px; box-shadow: 0 8px 28px rgba(0,0,0,.14); } .jx-deploySwitcherGroup { @@ -4187,7 +4206,7 @@ textarea.jx-composer{ /* Local permissions modal (ticket #06) */ .jx-localPermOverlay { position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,.35); display: flex; align-items: center; justify-content: center; } -.jx-localPermModal { width: 520px; max-width: 92vw; max-height: 82vh; overflow: auto; background: var(--color-bg-white, #fff); color: var(--color-text, #1d2129); border-radius: 14px; box-shadow: 0 16px 48px rgba(0,0,0,.24); } +.jx-localPermModal { width: 520px; max-width: 92vw; max-height: 82vh; overflow: auto; background: var(--color-bg-elevated); color: var(--color-text); border-radius: 14px; box-shadow: 0 16px 48px rgba(0,0,0,.24); } .jx-localPermHead { display: flex; align-items: center; justify-content: space-between; padding: 16px 18px; font-size: 16px; font-weight: 600; border-bottom: 1px solid var(--color-border, #e5e6eb); } .jx-localPermClose { border: 0; background: transparent; font-size: 22px; line-height: 1; cursor: pointer; color: var(--color-text-tertiary, #86909c); } .jx-localPermSection { padding: 14px 18px; border-bottom: 1px solid var(--color-border, var(--color-border)); } @@ -4201,7 +4220,7 @@ textarea.jx-composer{ .jx-localPermAdd { margin-top: 8px; border: 1px dashed var(--color-border, #c9cdd4); background: transparent; color: var(--color-primary, #165dff); border-radius: 8px; padding: 7px 12px; cursor: pointer; font-size: 13px; } .jx-localPermPolicyRow { display: flex; align-items: center; justify-content: space-between; padding: 7px 0; } .jx-localPermPolicyLabel { font-size: 13px; } -.jx-localPermSelect { font-size: 13px; padding: 4px 8px; border-radius: 6px; border: 1px solid var(--color-border, #c9cdd4); background: var(--color-bg-white, #fff); color: var(--color-text, #1d2129); } +.jx-localPermSelect { font-size: 13px; padding: 4px 8px; border-radius: 6px; border: 1px solid var(--color-border); background: var(--color-bg-container); color: var(--color-text); } /* 项目框旁的本机操作权限档胶囊(桌面壳本机模式) */ .jx-approvalPillBtn .jx-approvalPillIcon { font-size: 13px; line-height: 1; flex-shrink: 0; } .jx-approvalMenu .ant-dropdown-menu { min-width: 248px; } diff --git a/src/frontend/src/styles/code-highlight.css b/src/frontend/src/styles/code-highlight.css new file mode 100644 index 0000000..633c7b5 --- /dev/null +++ b/src/frontend/src/styles/code-highlight.css @@ -0,0 +1,112 @@ +/* ══════════════════════════════════════════════════════════════════════════ + highlight.js 深色调色板(github-dark) + + App.tsx 全局 `import 'highlight.js/styles/github.css'` 引入的是**固定浅色**主题, + 其中 `.hljs{color:#24292e;background:#ffffff}` 会给 元素本身刷一块白底深字, + 盖住外层容器(气泡 pre / 工具卡展开体 / JSON 块)已经做好的深色底 —— 深色模式下 + 凡是渲染代码块的地方都会出现「白块 + 黑字」。 + + 这里按 github-dark 的官方色板重写同一批 token 类,选择器统一带 `:root[data-theme="dark"]` + 前缀:特异性 (0,2,0) 高于 github.css 的 (0,1,0),因此不依赖两份 CSS 的注入先后顺序。 + 底色一律 transparent —— 让容器自己的语义令牌底透出来,代码块才能和卡片融为一体。 + ══════════════════════════════════════════════════════════════════════════ */ + +:root[data-theme="dark"] .hljs{ + color:#C9D1D9; + background:transparent; +} + +/* prettylights-syntax-keyword */ +:root[data-theme="dark"] .hljs-doctag, +:root[data-theme="dark"] .hljs-keyword, +:root[data-theme="dark"] .hljs-meta .hljs-keyword, +:root[data-theme="dark"] .hljs-template-tag, +:root[data-theme="dark"] .hljs-template-variable, +:root[data-theme="dark"] .hljs-type, +:root[data-theme="dark"] .hljs-variable.language_{ + color:#FF7B72; +} + +/* prettylights-syntax-entity */ +:root[data-theme="dark"] .hljs-title, +:root[data-theme="dark"] .hljs-title.class_, +:root[data-theme="dark"] .hljs-title.class_.inherited__, +:root[data-theme="dark"] .hljs-title.function_{ + color:#D2A8FF; +} + +/* prettylights-syntax-constant */ +:root[data-theme="dark"] .hljs-attr, +:root[data-theme="dark"] .hljs-attribute, +:root[data-theme="dark"] .hljs-literal, +:root[data-theme="dark"] .hljs-meta, +:root[data-theme="dark"] .hljs-number, +:root[data-theme="dark"] .hljs-operator, +:root[data-theme="dark"] .hljs-variable, +:root[data-theme="dark"] .hljs-selector-attr, +:root[data-theme="dark"] .hljs-selector-class, +:root[data-theme="dark"] .hljs-selector-id{ + color:#79C0FF; +} + +/* prettylights-syntax-string */ +:root[data-theme="dark"] .hljs-regexp, +:root[data-theme="dark"] .hljs-string, +:root[data-theme="dark"] .hljs-meta .hljs-string{ + color:#A5D6FF; +} + +/* prettylights-syntax-variable */ +:root[data-theme="dark"] .hljs-built_in, +:root[data-theme="dark"] .hljs-symbol{ + color:#FFA657; +} + +/* prettylights-syntax-comment */ +:root[data-theme="dark"] .hljs-comment, +:root[data-theme="dark"] .hljs-code, +:root[data-theme="dark"] .hljs-formula{ + color:#8B949E; +} + +/* prettylights-syntax-entity-tag */ +:root[data-theme="dark"] .hljs-name, +:root[data-theme="dark"] .hljs-quote, +:root[data-theme="dark"] .hljs-selector-tag, +:root[data-theme="dark"] .hljs-selector-pseudo{ + color:#7EE787; +} + +:root[data-theme="dark"] .hljs-subst{ + color:#C9D1D9; +} + +/* prettylights-syntax-markup-heading */ +:root[data-theme="dark"] .hljs-section{ + color:#58A6FF; + font-weight:bold; +} + +/* prettylights-syntax-markup-list */ +:root[data-theme="dark"] .hljs-bullet{ + color:#F2CC60; +} + +:root[data-theme="dark"] .hljs-emphasis{ + color:#C9D1D9; + font-style:italic; +} +:root[data-theme="dark"] .hljs-strong{ + color:#C9D1D9; + font-weight:bold; +} + +/* diff 增删行:底色本身就是深色,直接沿用 github-dark */ +:root[data-theme="dark"] .hljs-addition{ + color:#AFF5B4; + background-color:#033A16; +} +:root[data-theme="dark"] .hljs-deletion{ + color:#FFDCD7; + background-color:#67060C; +} diff --git a/src/frontend/src/styles/index.ts b/src/frontend/src/styles/index.ts index 2e6ed32..56a6a0a 100644 --- a/src/frontend/src/styles/index.ts +++ b/src/frontend/src/styles/index.ts @@ -4,6 +4,7 @@ import './sidebar.css'; import './search-modal.css'; import './chat.css'; import './tool.css'; +import './code-highlight.css'; import './catalog.css'; import './kb-wiki.css'; import './common.css'; diff --git a/src/frontend/src/styles/mcp.css b/src/frontend/src/styles/mcp.css index da763e0..9d06734 100644 --- a/src/frontend/src/styles/mcp.css +++ b/src/frontend/src/styles/mcp.css @@ -136,7 +136,7 @@ padding: 16px; border: 1px solid var(--border); border-radius: 12px; - background: var(--surface, #fff); + background: var(--color-bg-container); cursor: pointer; transition: border-color 160ms ease, box-shadow 160ms ease; } diff --git a/src/frontend/src/styles/tool.css b/src/frontend/src/styles/tool.css index 306345e..7281cbe 100644 --- a/src/frontend/src/styles/tool.css +++ b/src/frontend/src/styles/tool.css @@ -1,3 +1,26 @@ +/* ══════════════════════════════════════════════════════════════════════════ + 工具卡专属口音色(全局语义令牌之外的两族,浅/深各一档) + + · indigo —— 企业画像卡片族(搜索卡、时间线、专利榜)的靛蓝口音,浅色值 #4338CA + 与历史硬编码 rgb(67,56,202) 完全相同,故 color-mix 出来的 tint 像素等价。 + · 状态深字 —— 「深字 + 浅底」徽章配对色(成功/失败/警告)。浅色档保留原设计的深字, + 深色档翻转成浅字,否则暗底上是一坨几乎不可见的墨绿/暗红。 + ══════════════════════════════════════════════════════════════════════════ */ +:root{ + --jx-tool-indigo:#4338CA; + --jx-tool-ok-text:#166534; + --jx-tool-err-text:#991B1B; + --jx-tool-warn-text:#92400E; + --jx-tool-warn-strong:#D97706; +} +:root[data-theme="dark"]{ + --jx-tool-indigo:#9C95FF; + --jx-tool-ok-text:#6EE7A8; + --jx-tool-err-text:#FF9E9E; + --jx-tool-warn-text:#F0B86A; + --jx-tool-warn-strong:#F5B24A; +} + /* ══════════════════════════════════════════════════════════════════════════ Unified LobeHub-style Tool Call Row (.jx-tcr-*) Compact single-line: ✓ prefix + bold value + (count) + ▼/▶ @@ -23,10 +46,10 @@ cursor: pointer; } .jx-tcr-header[role="button"]:hover { - background: rgba(0, 0, 0, 0.03); + background: color-mix(in srgb, var(--color-text) 3%, transparent); } .jx-tcr-header[role="button"]:focus-visible { - outline: 2px solid rgba(18, 109, 255, 0.30); + outline: 2px solid color-mix(in srgb, var(--color-primary) 30%, transparent); outline-offset: 1px; } @@ -65,7 +88,7 @@ height: 7px; flex-shrink: 0; border-radius: 50%; - box-shadow: inset 0 0 0 1.4px rgba(15, 23, 42, 0.3); + box-shadow: inset 0 0 0 1.4px color-mix(in srgb, var(--color-text) 30%, transparent); } /* ── Label: prefix + chip value + count (NOT flex:1 — arrow sits next to text) ── */ @@ -83,15 +106,15 @@ max-width: calc(100% - 40px); } .jx-tcr-prefix { - color: rgba(0, 0, 0, 0.65); + color: color-mix(in srgb, var(--color-text) 65%, transparent); flex-shrink: 0; } /* "正在准备调用工具…" 等待文字流光 — 单色灰阶,参数缓冲空窗期的活性信号 */ .jx-tcr-prefix--shimmer { background: linear-gradient(90deg, - rgba(15, 23, 42, 0.40) 0%, - rgba(15, 23, 42, 0.88) 50%, - rgba(15, 23, 42, 0.40) 100%); + color-mix(in srgb, var(--color-text) 40%, transparent) 0%, + color-mix(in srgb, var(--color-text) 88%, transparent) 50%, + color-mix(in srgb, var(--color-text) 40%, transparent) 100%); background-size: 200% 100%; -webkit-background-clip: text; background-clip: text; @@ -103,9 +126,9 @@ .jx-tcr-value { display: inline-block; padding: 0 6px; - background: rgba(0, 0, 0, 0.045); + background: color-mix(in srgb, var(--color-text) 4.5%, transparent); border-radius: 4px; - color: rgba(0, 0, 0, 0.82); + color: color-mix(in srgb, var(--color-text) 82%, transparent); font-weight: 400; max-width: 100%; overflow: hidden; @@ -113,7 +136,7 @@ white-space: nowrap; } .jx-tcr-count { - color: rgba(0, 0, 0, 0.45); + color: color-mix(in srgb, var(--color-text) 45%, transparent); flex-shrink: 0; } @@ -126,7 +149,7 @@ flex-shrink: 0; border-style: solid; border-width: 4px 0 4px 5px; - border-color: transparent transparent transparent rgba(0, 0, 0, 0.35); + border-color: transparent transparent transparent color-mix(in srgb, var(--color-text) 35%, transparent); transition: transform 0.18s ease, border-color 0.15s; margin-left: 2px; } @@ -134,7 +157,7 @@ transform: rotate(90deg); } .jx-tcr-header[role="button"]:hover .jx-tcr-arrow { - border-left-color: rgba(0, 0, 0, 0.55); + border-left-color: color-mix(in srgb, var(--color-text) 55%, transparent); } /* ── Expanded body (uses .jx-expandWrap from variables.css) ── */ @@ -189,7 +212,7 @@ --ce-code-bg: var(--color-bg-gray); --ce-code-border: var(--color-border); --ce-line-num: var(--color-text-placeholder); - --ce-line-border: rgba(71, 84, 103, 0.12); + --ce-line-border: color-mix(in srgb, var(--color-text-secondary) 12%, transparent); border: 1px solid var(--color-border); border-radius: var(--radius-sm); overflow: hidden; @@ -211,10 +234,10 @@ color: var(--color-text-secondary) !important; } .jx-tcr-liveCode .jx-ce-codeWrap::-webkit-scrollbar-thumb { - background: rgba(71, 84, 103, 0.16); + background: color-mix(in srgb, var(--color-text-secondary) 16%, transparent); } .jx-tcr-liveCode .jx-ce-codeWrap::-webkit-scrollbar-thumb:hover { - background: rgba(71, 84, 103, 0.28); + background: color-mix(in srgb, var(--color-text-secondary) 28%, transparent); } /* ── Elapsed timer (running tool card + pending indicator) ── */ @@ -295,7 +318,7 @@ transition: background 0.15s ease; } .jx-trs-head:hover { - background: rgba(15, 23, 42, 0.035); + background: color-mix(in srgb, var(--color-text) 3.5%, transparent); } .jx-trs-head:focus-visible { outline: 2px solid color-mix(in srgb, var(--color-text) 18%, transparent); @@ -324,15 +347,15 @@ border-radius: 50%; background: radial-gradient( circle at 50% 50%, - rgba(18, 109, 255, 0.14) 0%, - rgba(18, 109, 255, 0) 70% + color-mix(in srgb, var(--color-primary) 14%, transparent) 0%, + transparent 70% ); } .jx-trs-mark--success::before { background: radial-gradient( circle at 50% 50%, - rgba(15, 23, 42, 0.10) 0%, - rgba(15, 23, 42, 0) 70% + color-mix(in srgb, var(--color-text) 10%, transparent) 0%, + transparent 70% ); } /* One-shot expanding ring the moment the batch settles */ @@ -423,7 +446,7 @@ font-size: 13px; } .jx-trs-body .jx-tcr-value { - background: rgba(15, 23, 42, 0.04); + background: color-mix(in srgb, var(--color-text) 4%, transparent); color: color-mix(in srgb, var(--color-text) 70%, transparent); } @@ -444,7 +467,7 @@ align-items: center; gap: 6px; padding: 5px 10px; - background: rgba(248, 250, 252, 0.9); + background: color-mix(in srgb, var(--color-bg-gray) 90%, transparent); border: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent); border-radius: 7px; overflow: hidden; @@ -479,7 +502,7 @@ padding-bottom: 6px; -webkit-overflow-scrolling: touch; scrollbar-width: thin; - scrollbar-color: rgba(100, 116, 139, 0.20) transparent; + scrollbar-color: var(--scrollbar-thumb-hover) transparent; } .jx-tr-searchCardsWrap::-webkit-scrollbar { height: 4px; @@ -488,7 +511,7 @@ background: transparent; } .jx-tr-searchCardsWrap::-webkit-scrollbar-thumb { - background: rgba(100, 116, 139, 0.20); + background: var(--scrollbar-thumb-hover); border-radius: 3px; } @@ -517,7 +540,7 @@ box-shadow: 0 1px 4px rgba(15, 23, 42, 0.04); } .jx-tr-searchCard:hover { - border-color: rgba(18, 109, 255, 0.25); + border-color: color-mix(in srgb, var(--color-primary) 25%, transparent); box-shadow: 0 4px 12px rgba(18, 109, 255, 0.10); transform: translateY(-1px); text-decoration: none; @@ -631,7 +654,7 @@ font-size:13px; } .jx-trp-close:hover{ - background:rgba(15,23,42,.07); + background:color-mix(in srgb, var(--color-text) 7%, transparent); color:color-mix(in srgb, var(--color-text) 80%, transparent); } .jx-trp-body{ @@ -649,14 +672,14 @@ cursor:pointer; } .jx-toolCallItem--panel .jx-toolCallHeader{ - background:rgba(248,250,252,.6); + background:color-mix(in srgb, var(--color-bg-gray) 60%, transparent); } .jx-toolCallItem--panel.active .jx-toolCallHeader{ - background:rgba(18,109,255,.07); - border-color:rgba(18,109,255,.22); + background:color-mix(in srgb, var(--color-primary) 7%, transparent); + border-color:color-mix(in srgb, var(--color-primary) 22%, transparent); } .jx-toolCallItem--panel.active{ - border-color:rgba(18,109,255,.22); + border-color:color-mix(in srgb, var(--color-primary) 22%, transparent); } .jx-panelOpenIcon{ color:color-mix(in srgb, var(--color-text) 36%, transparent); @@ -673,8 +696,8 @@ /* Tool Calls Section */ .jx-toolCallsSection{ - background:linear-gradient(180deg, rgba(239,246,255,.92) 0%, color-mix(in srgb, var(--color-bg-container) 96%, transparent) 100%); - border:1px solid rgba(18,109,255,.18); + background:linear-gradient(180deg, color-mix(in srgb, var(--color-primary-light) 92%, transparent) 0%, color-mix(in srgb, var(--color-bg-container) 96%, transparent) 100%); + border:1px solid color-mix(in srgb, var(--color-primary) 18%, transparent); border-radius:14px; padding:10px; box-shadow:0 10px 24px rgba(18,109,255,.08); @@ -685,7 +708,7 @@ gap:8px; font-weight:900; font-size:13px; - color:rgba(67, 56, 202, 0.90); + color:color-mix(in srgb, var(--jx-tool-indigo) 90%, transparent); margin-bottom:8px; user-select:none; } @@ -702,7 +725,7 @@ } .jx-toolCallItem{ background:var(--color-bg-container); - border:1px solid rgba(18,109,255,.14); + border:1px solid color-mix(in srgb, var(--color-primary) 14%, transparent); border-radius:12px; box-shadow:0 6px 18px rgba(17,24,39,.04); overflow:clip; @@ -717,10 +740,10 @@ user-select:none; } .jx-toolCallHeader:hover{ - background:rgba(18,109,255,.08); + background:color-mix(in srgb, var(--color-primary) 8%, transparent); } .jx-toolCallHeader:focus-visible{ - outline:2px solid rgba(18,109,255,.35); + outline:2px solid color-mix(in srgb, var(--color-primary) 35%, transparent); outline-offset:-2px; } .jx-toolCallTitle{ @@ -761,16 +784,16 @@ } .jx-toolCallStatusTag.running{ color:var(--color-primary); - background:rgba(18,109,255,.16); - border-color:rgba(18,109,255,.32); + background:color-mix(in srgb, var(--color-primary) 16%, transparent); + border-color:color-mix(in srgb, var(--color-primary) 32%, transparent); } .jx-toolCallStatusTag.success{ - color:#166534; + color:var(--jx-tool-ok-text); background:rgba(34,197,94,.16); border-color:rgba(34,197,94,.32); } .jx-toolCallStatusTag.error{ - color:#991b1b; + color:var(--jx-tool-err-text); background:rgba(239,68,68,.14); border-color:rgba(239,68,68,.26); } @@ -1070,7 +1093,7 @@ .jx-toolCallBody{ padding:10px 12px 12px 12px; - border-top:1px solid rgba(18,109,255,.14); + border-top:1px solid color-mix(in srgb, var(--color-primary) 14%, transparent); -webkit-user-select:text; user-select:text; } @@ -1080,12 +1103,12 @@ .jx-toolSectionLabel{ font-weight:700; font-size:12px; - color:rgba(18, 109, 255, 0.92); + color:color-mix(in srgb, var(--color-primary) 92%, transparent); margin-bottom:6px; } .jx-toolCode{ - background:rgba(248, 250, 252, 1); - border:1px solid rgba(17, 24, 39, 0.10); + background:var(--color-bg-gray); + border:1px solid color-mix(in srgb, var(--color-text) 10%, transparent); border-radius:8px; padding:10px; font-size:12px; @@ -1098,9 +1121,9 @@ color:color-mix(in srgb, var(--color-text) 92%, transparent); } .jx-toolCode::-webkit-scrollbar{width:8px; height:8px} -.jx-toolCode::-webkit-scrollbar-track{background:rgba(248, 250, 252, 1); border-radius:4px} -.jx-toolCode::-webkit-scrollbar-thumb{background:rgba(100, 116, 139, 0.35); border-radius:4px} -.jx-toolCode::-webkit-scrollbar-thumb:hover{background:rgba(100, 116, 139, 0.50)} +.jx-toolCode::-webkit-scrollbar-track{background:var(--color-bg-gray); border-radius:4px} +.jx-toolCode::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb-drag); border-radius:4px} +.jx-toolCode::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-drag)} /* ── 工具结果结构化渲染(Tool Result Renderers)────────────────── */ @@ -1132,29 +1155,29 @@ padding:4px 8px; border-radius:4px; margin-bottom:8px; - background:rgba(248,250,252,1); + background:var(--color-bg-gray); color:color-mix(in srgb, var(--color-text) 72%, transparent); } -.jx-tr-dbHeader.success{color:color-mix(in srgb, var(--color-text) 55%, transparent);background:rgba(15,23,42,.035);border:1px solid color-mix(in srgb, var(--color-text) 7%, transparent);} -.jx-tr-dbHeader.error{color:color-mix(in srgb, var(--color-text) 72%, transparent);background:rgba(15,23,42,.05);border:1px solid color-mix(in srgb, var(--color-text) 12%, transparent);} +.jx-tr-dbHeader.success{color:color-mix(in srgb, var(--color-text) 55%, transparent);background:color-mix(in srgb, var(--color-text) 3.5%, transparent);border:1px solid color-mix(in srgb, var(--color-text) 7%, transparent);} +.jx-tr-dbHeader.error{color:color-mix(in srgb, var(--color-text) 72%, transparent);background:color-mix(in srgb, var(--color-text) 5%, transparent);border:1px solid color-mix(in srgb, var(--color-text) 12%, transparent);} .jx-tr-dbText{font-size:12px;line-height:1.65;white-space:pre-wrap;color:color-mix(in srgb, var(--color-text) 78%, transparent);} -.jx-tr-dbText.error{color:#991b1b;} +.jx-tr-dbText.error{color:var(--jx-tool-err-text);} -.jx-tr-tableWrap{overflow-x:auto;border-radius:6px;border:1px solid rgba(18,109,255,.14);} +.jx-tr-tableWrap{overflow-x:auto;border-radius:6px;border:1px solid color-mix(in srgb, var(--color-primary) 14%, transparent);} .jx-tr-table{width:100%;border-collapse:collapse;font-size:12px;line-height:1.4;} .jx-tr-table th{ - background:rgba(18,109,255,.07); + background:color-mix(in srgb, var(--color-primary) 7%, transparent); color:var(--color-primary); font-weight:700; padding:6px 10px; text-align:left; - border-bottom:1px solid rgba(18,109,255,.16); + border-bottom:1px solid color-mix(in srgb, var(--color-primary) 16%, transparent); white-space:nowrap; font-size:11.5px; } .jx-tr-table td{ padding:5px 10px; - border-bottom:1px solid rgba(18,109,255,.08); + border-bottom:1px solid color-mix(in srgb, var(--color-primary) 8%, transparent); color:color-mix(in srgb, var(--color-text) 82%, transparent); max-width:180px; overflow:hidden; @@ -1162,14 +1185,14 @@ white-space:nowrap; } .jx-tr-table tr:last-child td{border-bottom:none;} -.jx-tr-table tr:nth-child(even) td{background:rgba(248,250,252,.6);} -.jx-tr-moreHint{font-size:11px;color:color-mix(in srgb, var(--color-text) 38%, transparent);padding:6px 10px;text-align:center;border-top:1px solid rgba(18,109,255,.08);} +.jx-tr-table tr:nth-child(even) td{background:color-mix(in srgb, var(--color-bg-gray) 60%, transparent);} +.jx-tr-moreHint{font-size:11px;color:color-mix(in srgb, var(--color-text) 38%, transparent);padding:6px 10px;text-align:center;border-top:1px solid color-mix(in srgb, var(--color-primary) 8%, transparent);} /* ── 知识库检索 ─────────────────────── */ .jx-tr-kbList{display:flex;flex-direction:column;} .jx-tr-kbItem{ padding:8px 4px; - border-bottom:1px solid rgba(18,109,255,.09); + border-bottom:1px solid color-mix(in srgb, var(--color-primary) 9%, transparent); border-radius:4px; } .jx-tr-kbItem:last-child{border-bottom:none;} @@ -1177,7 +1200,7 @@ cursor:pointer; transition:background .15s; } -.jx-tr-kbItem--clickable:hover{background:rgba(18,109,255,.04);} +.jx-tr-kbItem--clickable:hover{background:color-mix(in srgb, var(--color-primary) 4%, transparent);} .jx-tr-kbDocName{ font-size:12.5px; font-weight:700; @@ -1216,7 +1239,7 @@ padding:0 5px; border-radius:4px; background:var(--color-primary-light); - border:1px solid rgba(18,109,255,.28); + border:1px solid color-mix(in srgb, var(--color-primary) 28%, transparent); color:var(--color-primary); font-size:10px; font-weight:600; @@ -1237,7 +1260,7 @@ .jx-tr-kbScore{ font-size:10px; - color:rgba(18,109,255,.55); + color:color-mix(in srgb, var(--color-primary) 55%, transparent); margin-top:2px; } @@ -1261,7 +1284,7 @@ text-decoration:none !important; color:inherit; } -.jx-tr-searchItem--link:hover{background:rgba(18,109,255,.04);} +.jx-tr-searchItem--link:hover{background:color-mix(in srgb, var(--color-primary) 4%, transparent);} .jx-tr-searchHeader{display:flex;align-items:center;gap:6px;margin-bottom:4px;} .jx-tr-searchTitle{ font-size:12.5px; @@ -1278,9 +1301,9 @@ font-size:10px; padding:1px 7px; border-radius:999px; - background:rgba(18,109,255,.10); + background:color-mix(in srgb, var(--color-primary) 10%, transparent); color:var(--color-primary); - border:1px solid rgba(18,109,255,.22); + border:1px solid color-mix(in srgb, var(--color-primary) 22%, transparent); white-space:nowrap; } @@ -1308,7 +1331,7 @@ align-items:center; gap:8px; font-size:12px; - color:#166534; + color:var(--jx-tool-ok-text); padding:8px 10px; background:rgba(34,197,94,.07); border:1px solid rgba(34,197,94,.20); @@ -1330,7 +1353,7 @@ max-height:320px; overflow:auto; padding:8px 10px; - background:rgba(15,23,42,.025); + background:color-mix(in srgb, var(--color-text) 2.5%, transparent); border:1px solid color-mix(in srgb, var(--color-text) 6%, transparent); border-radius:6px; } @@ -1344,14 +1367,14 @@ color:var(--color-text-tertiary); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break:break-all; - background:rgba(15,23,42,.03); + background:color-mix(in srgb, var(--color-text) 3%, transparent); border-radius:4px; padding:2px 6px; } .jx-tr-filePreview{ margin:0; padding:8px 10px; - background:rgba(15,23,42,.025); + background:color-mix(in srgb, var(--color-text) 2.5%, transparent); border:1px solid color-mix(in srgb, var(--color-text) 6%, transparent); border-radius:6px; font-size:12px; @@ -1384,8 +1407,8 @@ font-size:12px; color:color-mix(in srgb, var(--color-text) 76%, transparent); padding:4px 8px; - background:rgba(248,250,252,.95); - border:1px solid rgba(18,109,255,.12); + background:color-mix(in srgb, var(--color-bg-gray) 95%, transparent); + border:1px solid color-mix(in srgb, var(--color-primary) 12%, transparent); border-radius:4px; overflow:hidden; text-overflow:ellipsis; @@ -1394,8 +1417,8 @@ /* ── 通用 JSON 展示(兜底) ─────────── */ .jx-tr-jsonBlock{ - background:rgba(248,250,252,1); - border:1px solid rgba(17,24,39,.09); + background:var(--color-bg-gray); + border:1px solid color-mix(in srgb, var(--color-text) 9%, transparent); border-radius:6px; padding:10px; font-size:11.5px; @@ -1409,8 +1432,8 @@ white-space:pre; } .jx-tr-jsonBlock::-webkit-scrollbar{width:6px;height:6px;} -.jx-tr-jsonBlock::-webkit-scrollbar-track{background:rgba(248,250,252,1);} -.jx-tr-jsonBlock::-webkit-scrollbar-thumb{background:rgba(100,116,139,.30);border-radius:3px;} +.jx-tr-jsonBlock::-webkit-scrollbar-track{background:var(--color-bg-gray);} +.jx-tr-jsonBlock::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb-drag);border-radius:3px;} /* Thinking Section — 旧版兼容样式(历史消息) */ @@ -1526,14 +1549,14 @@ overflow-y:auto; } .jx-thinkingContent::-webkit-scrollbar{width:5px} -.jx-thinkingContent::-webkit-scrollbar-thumb{background:rgba(148,163,184,.60);border-radius:3px} +.jx-thinkingContent::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb-drag);border-radius:3px} /* 思考中旋转动画 */ .jx-thinkingSpinner{ width:12px; height:12px; border:2px solid color-mix(in srgb, var(--color-text-placeholder) 50%, transparent); - border-top-color:rgba(71,85,105,.86); + border-top-color:color-mix(in srgb, var(--color-text-secondary) 86%, transparent); border-radius:50%; animation:jxThinkSpin .8s linear infinite; flex-shrink:0; @@ -1566,6 +1589,13 @@ from{opacity:0;transform:scale(.85);} to {opacity:1;transform:scale(1);} } +/* 深色下把「已完成」定格标记提亮一档。 + 它是一张单一色值(slate-400 #94A3B8)铺满的 PNG,CSS 改不了图里的颜色,只能上滤镜; + ×1.15 后约 #AABBD4,与旁边的浅色文字同一亮度带,仍明显区别于运行中的品牌蓝动图。 + ⚠️ 只作用于 --done:运行中的动图保持原样,不要给 .jx-brandLoader 本体加滤镜。 */ +:root[data-theme="dark"] .jx-brandLoader--done{ + filter:brightness(1.15); +} /* ── Inline summary row ────────────────── */ .jx-inlineSummary{ @@ -1602,6 +1632,16 @@ .jx-inlineSummary:hover .jx-inlineSummaryArrow{ color:color-mix(in srgb, var(--color-text-tertiary) 75%, transparent); } +/* 深色下这行「已深度思考 N 秒 / 已调用 N 个工具」的折叠摘要要抬一档。 + 浅色档用 --color-text-placeholder 是对的(安静、不抢正文);但深色档该令牌是 #5F6B7D, + 本职是输入框占位符,拿来当说明文字压在暗底上只有 2.65 的对比度(箭头 1.94),低于可读下限。 + 换成 --color-text-tertiary 后约 4.9 / 3.5,仍是最安静的一档,但看得清。浅色不动。 */ +:root[data-theme="dark"] .jx-inlineSummaryText{ + color:color-mix(in srgb, var(--color-text-tertiary) 92%, transparent); +} +:root[data-theme="dark"] .jx-inlineSummaryArrow{ + color:color-mix(in srgb, var(--color-text-tertiary) 70%, transparent); +} /* ── Tool timeline (Canvas panel body) ─── */ .jx-toolTimeline{ @@ -1616,7 +1656,7 @@ position:absolute; left:5px;top:8px;bottom:8px; width:2px; - background:rgba(148,163,184,.22); + background:color-mix(in srgb, var(--color-text-placeholder) 42%, transparent); border-radius:1px; } .jx-toolTimelineStep{ @@ -1630,7 +1670,7 @@ transition:background .12s; } .jx-toolTimelineStep:hover{ - background:rgba(241,245,249,.7); + background:color-mix(in srgb, var(--color-bg-gray) 70%, transparent); } .jx-toolTimelineStep.active{ background:rgba(59,130,246,.06); @@ -1717,7 +1757,7 @@ /* ── Card Container ── */ .jx-ce-card{ background:var(--color-bg-container); - border:1px solid rgba(18,109,255,.14); + border:1px solid color-mix(in srgb, var(--color-primary) 14%, transparent); border-radius:12px; box-shadow:0 2px 8px rgba(17,24,39,.03); overflow:hidden; @@ -1738,7 +1778,7 @@ user-select:none; } .jx-ce-cardHeader:hover{ - background:rgba(18,109,255,.04); + background:color-mix(in srgb, var(--color-primary) 4%, transparent); } .jx-ce-cardHeaderLeft{ display:flex; @@ -1796,7 +1836,7 @@ opacity:1; } .jx-ce-cardActionBtn:hover{ - background:rgba(18,109,255,.08); + background:color-mix(in srgb, var(--color-primary) 8%, transparent); color:var(--color-primary); } .jx-ce-cardArrow{ @@ -1807,7 +1847,7 @@ /* Card body */ .jx-ce-cardBody{ - border-top:1px solid rgba(18,109,255,.08); + border-top:1px solid color-mix(in srgb, var(--color-primary) 8%, transparent); padding:10px 12px 12px; display:flex; flex-direction:column; @@ -1819,9 +1859,9 @@ .jx-cap{ --ce-mono:'Menlo','Consolas','Monaco','Liberation Mono','Courier New',monospace; --ce-code-bg:var(--color-bg-layout); - --ce-code-border:rgba(18,109,255,.10); - --ce-line-num:#b0b8c4; - --ce-line-border:rgba(18,109,255,.06); + --ce-code-border:color-mix(in srgb, var(--color-primary) 10%, transparent); + --ce-line-num:var(--color-text-placeholder); + --ce-line-border:color-mix(in srgb, var(--color-primary) 6%, transparent); } /* ── Code Section ── */ @@ -1838,7 +1878,7 @@ align-items:center; justify-content:space-between; padding:5px 10px; - background:rgba(18,109,255,.03); + background:color-mix(in srgb, var(--color-primary) 3%, transparent); border-bottom:1px solid var(--ce-line-border); min-height:30px; } @@ -1888,7 +1928,7 @@ transition:background .12s,color .12s; } .jx-ce-barBtn:hover{ - background:rgba(18,109,255,.08); + background:color-mix(in srgb, var(--color-primary) 8%, transparent); color:var(--color-primary); } .jx-ce-barBtn--copied{ @@ -1905,8 +1945,8 @@ overflow:auto; } /* width set by global variables.css (6px for code areas); only override color */ -.jx-ce-codeWrap::-webkit-scrollbar-thumb{background:rgba(18,109,255,.12);} -.jx-ce-codeWrap::-webkit-scrollbar-thumb:hover{background:rgba(18,109,255,.22);} +.jx-ce-codeWrap::-webkit-scrollbar-thumb{background:color-mix(in srgb, var(--color-primary) 12%, transparent);} +.jx-ce-codeWrap::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb, var(--color-primary) 22%, transparent);} /* Line numbers */ .jx-ce-lineNums{ @@ -2029,7 +2069,7 @@ } .jx-ce-stdout::-webkit-scrollbar{width:5px;} .jx-ce-stdout::-webkit-scrollbar-track{background:transparent;} -.jx-ce-stdout::-webkit-scrollbar-thumb{background:rgba(0,0,0,.06);border-radius:3px;} +.jx-ce-stdout::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:3px;} .jx-ce-stderr{ margin:0; @@ -2069,7 +2109,7 @@ } .jx-ce-imageCard:hover{ box-shadow:var(--shadow-card-hover); - border-color:rgba(18,109,255,.18); + border-color:color-mix(in srgb, var(--color-primary) 18%, transparent); } .jx-ce-imageThumb{ display:block; @@ -2170,7 +2210,7 @@ opacity:1; } .jx-ce-filePreviewBtn:hover{ - background:rgba(18,109,255,.08); + background:color-mix(in srgb, var(--color-primary) 8%, transparent); color:var(--color-primary); } @@ -2223,7 +2263,7 @@ transition:background .12s; } .jx-ms-listItem:hover{ - background:rgba(18,109,255,.04); + background:color-mix(in srgb, var(--color-primary) 4%, transparent); } .jx-ms-fileIcon{ font-size:13px; @@ -2266,7 +2306,7 @@ padding:4px 6px; border-radius:6px; } -.jx-ms-msgItem--user{ background:rgba(18,109,255,.04); } +.jx-ms-msgItem--user{ background:color-mix(in srgb, var(--color-primary) 4%, transparent); } .jx-ms-msgRole{ font-size:11px; font-weight:600;