fix(ui,runtime,search): merge runtime/frontend stabilization into main
This commit is contained in:
parent
e955ef73e0
commit
cd82c6d658
@ -9,8 +9,16 @@ AGENT_THINKING_API_BASE_URL=
|
||||
AGENT_THINKING_API_KEY=
|
||||
AGENT_THINKING_MODEL_ID=
|
||||
|
||||
# 对话标题生成模型(可选,留空则回退到基础推理模型)
|
||||
AGENT_TITLE_API_BASE_URL=
|
||||
AGENT_TITLE_API_KEY=
|
||||
AGENT_TITLE_MODEL_ID=
|
||||
|
||||
# 第三方搜索(可选)
|
||||
AGENT_TAVILY_API_KEY=
|
||||
AGENT_TAVILY_API_KEY_2=
|
||||
AGENT_TAVILY_API_KEY_BACKUP=
|
||||
# 使用哪个 Tavily 环境变量请在 config/search.py 里改 TAVILY_API_KEY_ENV_NAME
|
||||
|
||||
# 每轮最大响应 token(可选)
|
||||
AGENT_DEFAULT_RESPONSE_MAX_TOKENS=32768
|
||||
|
||||
@ -35,6 +35,7 @@ def _load_dotenv():
|
||||
_load_dotenv()
|
||||
|
||||
from . import api as _api
|
||||
from . import search as _search
|
||||
from . import paths as _paths
|
||||
from . import limits as _limits
|
||||
from . import terminal as _terminal
|
||||
@ -51,6 +52,7 @@ from . import custom_tools as _custom_tools
|
||||
from . import mcp as _mcp
|
||||
|
||||
from .api import *
|
||||
from .search import *
|
||||
from .paths import *
|
||||
from .limits import *
|
||||
from .terminal import *
|
||||
@ -67,7 +69,7 @@ from .custom_tools import *
|
||||
from .mcp import *
|
||||
|
||||
__all__ = []
|
||||
for module in (_api, _paths, _limits, _terminal, _conversation, _security, _ui, _memory, _ocr, _todo, _auth, _uploads, _sub_agent, _custom_tools, _mcp):
|
||||
for module in (_api, _search, _paths, _limits, _terminal, _conversation, _security, _ui, _memory, _ocr, _todo, _auth, _uploads, _sub_agent, _custom_tools, _mcp):
|
||||
__all__ += getattr(module, "__all__", [])
|
||||
|
||||
del _api, _paths, _limits, _terminal, _conversation, _security, _ui, _memory, _ocr, _todo, _auth, _uploads, _sub_agent, _custom_tools, _mcp
|
||||
del _api, _search, _paths, _limits, _terminal, _conversation, _security, _ui, _memory, _ocr, _todo, _auth, _uploads, _sub_agent, _custom_tools, _mcp
|
||||
|
||||
@ -19,8 +19,12 @@ THINKING_API_BASE_URL = _env("AGENT_THINKING_API_BASE_URL", API_BASE_URL)
|
||||
THINKING_API_KEY = _env("AGENT_THINKING_API_KEY", API_KEY)
|
||||
THINKING_MODEL_ID = _env("AGENT_THINKING_MODEL_ID", MODEL_ID)
|
||||
|
||||
# Tavily 搜索(可选)
|
||||
TAVILY_API_KEY = _env("AGENT_TAVILY_API_KEY", "")
|
||||
from .search import TAVILY_API_KEY
|
||||
|
||||
# 对话标题生成模型(可选,留空则回退到基础快速模型)
|
||||
TITLE_API_BASE_URL = _env("AGENT_TITLE_API_BASE_URL", API_BASE_URL)
|
||||
TITLE_API_KEY = _env("AGENT_TITLE_API_KEY", API_KEY)
|
||||
TITLE_MODEL_ID = _env("AGENT_TITLE_MODEL_ID", MODEL_ID)
|
||||
|
||||
# 默认响应 token 限制
|
||||
DEFAULT_RESPONSE_MAX_TOKENS = int(os.environ.get("AGENT_DEFAULT_RESPONSE_MAX_TOKENS", "32768"))
|
||||
@ -29,9 +33,11 @@ __all__ = [
|
||||
"API_BASE_URL",
|
||||
"API_KEY",
|
||||
"MODEL_ID",
|
||||
"TAVILY_API_KEY",
|
||||
"DEFAULT_RESPONSE_MAX_TOKENS",
|
||||
"THINKING_API_BASE_URL",
|
||||
"THINKING_API_KEY",
|
||||
"THINKING_MODEL_ID",
|
||||
"TITLE_API_BASE_URL",
|
||||
"TITLE_API_KEY",
|
||||
"TITLE_MODEL_ID",
|
||||
]
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
"max_output_tokens": 32768,
|
||||
"thinkmode_status": {
|
||||
"type": "param_toggle",
|
||||
"model_id": "kimi-k2.5",
|
||||
"model_id": "kimi-k2.6",
|
||||
"fast_extra_parameter": {
|
||||
"thinking": {
|
||||
"type": "disabled"
|
||||
|
||||
17
config/search.py
Normal file
17
config/search.py
Normal file
@ -0,0 +1,17 @@
|
||||
"""搜索相关配置。"""
|
||||
|
||||
import os
|
||||
|
||||
# 选择 Tavily 使用哪个环境变量中的密钥。
|
||||
# 默认保持兼容:仍使用 AGENT_TAVILY_API_KEY。
|
||||
# 你可以改成例如:AGENT_TAVILY_API_KEY_2 / AGENT_TAVILY_API_KEY_BACKUP
|
||||
TAVILY_API_KEY_ENV_NAME = "AGENT_TAVILY_API_KEY_2"
|
||||
|
||||
# 实际生效的 Tavily 密钥
|
||||
TAVILY_API_KEY = os.environ.get(TAVILY_API_KEY_ENV_NAME, "")
|
||||
|
||||
__all__ = [
|
||||
"TAVILY_API_KEY_ENV_NAME",
|
||||
"TAVILY_API_KEY",
|
||||
]
|
||||
|
||||
@ -8,6 +8,7 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from config import TITLE_API_BASE_URL, TITLE_API_KEY, TITLE_MODEL_ID
|
||||
from core.web_terminal import WebTerminal
|
||||
from config import LOGS_DIR
|
||||
from utils.api_client import DeepSeekClient
|
||||
|
||||
@ -236,6 +236,7 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
|
||||
|
||||
# 执行每个工具
|
||||
pending_runtime_mode_notices: List[str] = []
|
||||
last_completed_tool_call_id: Optional[str] = None
|
||||
for tool_call in tool_calls:
|
||||
# 检查停止标志
|
||||
client_stop_info = get_stop_flag(client_sid, username)
|
||||
@ -1137,6 +1138,7 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
|
||||
"name": function_name,
|
||||
"content": tool_message_content
|
||||
})
|
||||
last_completed_tool_call_id = tool_call_id
|
||||
|
||||
if not pending_runtime_mode_notices:
|
||||
try:
|
||||
@ -1150,28 +1152,34 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
|
||||
if tool_failed:
|
||||
mark_force_thinking(web_terminal, reason=f"{function_name}_failed")
|
||||
|
||||
# 同一轮全部 tool_call 完成后,再统一 poll 子智能体/后台命令并注入通知,
|
||||
# 避免在 assistant.tool_calls 与对应 tool 消息序列之间插入 user 消息导致 API 报错。
|
||||
debug_log("[SubAgent] poll updates after all tool_calls finished")
|
||||
await process_sub_agent_updates(
|
||||
messages=messages,
|
||||
inline=True,
|
||||
after_tool_call_id=None,
|
||||
web_terminal=web_terminal,
|
||||
sender=sender,
|
||||
debug_log=debug_log,
|
||||
maybe_mark_failure_from_message=maybe_mark_failure_from_message,
|
||||
)
|
||||
debug_log("[BgCmdDebug] poll background command updates after all tool_calls finished")
|
||||
await process_background_command_updates(
|
||||
messages=messages,
|
||||
inline=True,
|
||||
after_tool_call_id=None,
|
||||
web_terminal=web_terminal,
|
||||
sender=sender,
|
||||
debug_log=debug_log,
|
||||
maybe_mark_failure_from_message=maybe_mark_failure_from_message,
|
||||
)
|
||||
# 子智能体/后台指令完成通知:必须等待同一轮全部 tool_call 都完成后再注入,
|
||||
# 避免在 assistant.tool_calls 与未完成的 tool 结果之间插入 system 消息导致 API 报错。
|
||||
if last_completed_tool_call_id:
|
||||
debug_log(
|
||||
f"[SubAgent] after all tools finished -> poll updates inline after_tool_call_id={last_completed_tool_call_id}"
|
||||
)
|
||||
await process_sub_agent_updates(
|
||||
messages=messages,
|
||||
inline=True,
|
||||
after_tool_call_id=last_completed_tool_call_id,
|
||||
web_terminal=web_terminal,
|
||||
sender=sender,
|
||||
debug_log=debug_log,
|
||||
maybe_mark_failure_from_message=maybe_mark_failure_from_message,
|
||||
)
|
||||
debug_log(
|
||||
"[BgCmdDebug] after all tools finished -> poll background command updates "
|
||||
f"inline after_tool_call_id={last_completed_tool_call_id}"
|
||||
)
|
||||
await process_background_command_updates(
|
||||
messages=messages,
|
||||
inline=True,
|
||||
after_tool_call_id=last_completed_tool_call_id,
|
||||
web_terminal=web_terminal,
|
||||
sender=sender,
|
||||
debug_log=debug_log,
|
||||
maybe_mark_failure_from_message=maybe_mark_failure_from_message,
|
||||
)
|
||||
|
||||
# 运行期模式通知:必须等待同一轮全部 tool_call 都完成后再注入,
|
||||
# 避免在 assistant.tool_calls 与对应 tool 消息之间插入 user 消息导致 API 报错。
|
||||
|
||||
@ -160,12 +160,36 @@ export const resourceMethods = {
|
||||
this.executionModeDirectUntil = status.execution_mode.direct_until ?? null;
|
||||
this.executionModeEnabled =
|
||||
typeof status.execution_mode_enabled === 'boolean' ? status.execution_mode_enabled : true;
|
||||
if (typeof this.scheduleExecutionModeExpirySync === 'function') {
|
||||
this.scheduleExecutionModeExpirySync(this.executionModeDirectUntil);
|
||||
}
|
||||
const currentMode = this.currentExecutionMode;
|
||||
const parseDirectUntilMs = (value: any): number => {
|
||||
if (!value) return 0;
|
||||
if (typeof value === 'number') {
|
||||
return value > 1e12 ? value : value * 1000;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isNaN(parsed) ? 0 : parsed;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const prevDirectUntilMs = parseDirectUntilMs(prevDirectUntil);
|
||||
const fallbackLikelyByExpiry = !!prevDirectUntilMs && Date.now() >= prevDirectUntilMs - 1200;
|
||||
const directExpiredToSandbox =
|
||||
prevExecutionMode === 'direct' &&
|
||||
currentMode === 'sandbox' &&
|
||||
!!prevDirectUntil;
|
||||
prevExecutionMode === 'direct' && currentMode === 'sandbox' && fallbackLikelyByExpiry;
|
||||
if (directExpiredToSandbox) {
|
||||
if (this.currentPermissionMode === 'unrestricted') {
|
||||
if (typeof pendingModes.permission_mode === 'string' && pendingModes.permission_mode) {
|
||||
this.currentPermissionMode = pendingModes.permission_mode;
|
||||
} else {
|
||||
this.currentPermissionMode = 'approval';
|
||||
}
|
||||
}
|
||||
if (typeof this.fetchPermissionMode === 'function') {
|
||||
Promise.resolve(this.fetchPermissionMode()).catch(() => {});
|
||||
}
|
||||
try {
|
||||
if (this.executionModeAutoFallbackToastId) {
|
||||
this.uiDismissToast(this.executionModeAutoFallbackToastId);
|
||||
@ -173,7 +197,7 @@ export const resourceMethods = {
|
||||
} catch (_err) {}
|
||||
this.executionModeAutoFallbackToastId = this.uiPushToast({
|
||||
title: '执行环境已自动回退',
|
||||
message: '完全访问权限已到期,当前已切换为沙箱执行环境。',
|
||||
message: '完全访问权限已到期,已自动切回受限权限并切换为沙箱执行环境。',
|
||||
type: 'warning',
|
||||
duration: null
|
||||
});
|
||||
|
||||
@ -84,12 +84,7 @@ function isRuntimeModeNoticePayload(data: any): boolean {
|
||||
return false;
|
||||
}
|
||||
const meta = data.metadata || {};
|
||||
return !!(
|
||||
data.runtime_guidance ||
|
||||
meta.runtime_guidance ||
|
||||
data.runtime_mode_notice ||
|
||||
meta.runtime_mode_notice
|
||||
);
|
||||
return !!(data.runtime_mode_notice || meta.runtime_mode_notice);
|
||||
}
|
||||
|
||||
function resolveUserMessageSource(data: any): string {
|
||||
@ -1723,15 +1718,22 @@ export const taskPollingMethods = {
|
||||
this.streamingMessage = false;
|
||||
this.stopRequested = false;
|
||||
} else {
|
||||
// 运行期/空闲期由系统注入的 user 消息(runtime_guidance / sub_agent / background_command / notify 等)
|
||||
// 现已统一为前缀 [系统通知|...] 的 user 消息,前端按普通 user 渲染。
|
||||
this.chatAddUserMessage(
|
||||
message,
|
||||
incomingImages,
|
||||
incomingVideos,
|
||||
incomingMediaRefs,
|
||||
resolveUserMessageSource(data)
|
||||
);
|
||||
const source = resolveUserMessageSource(data);
|
||||
// 仅在对话运行期间展示 runtime_mode_notice;其余自动消息按来源正常显示,
|
||||
// 保持与后端历史重建一致(guidance/notify/sub_agent/background_command)。
|
||||
if (isRuntimeModeNotice && !(this.taskInProgress || this.streamingMessage)) {
|
||||
debugLog('[TaskPolling] 空闲态跳过 runtime_mode_notice 用户通知', {
|
||||
messagePreview: message.slice(0, 80)
|
||||
});
|
||||
} else {
|
||||
this.chatAddUserMessage(
|
||||
message,
|
||||
incomingImages,
|
||||
incomingVideos,
|
||||
incomingMediaRefs,
|
||||
source
|
||||
);
|
||||
}
|
||||
}
|
||||
if (data?.sub_agent_notice) {
|
||||
if (typeof data?.has_running_sub_agents === 'boolean') {
|
||||
|
||||
@ -1212,6 +1212,7 @@ export const uiMethods = {
|
||||
this.executionModeDirectUntil = state.direct_until ?? null;
|
||||
this.executionModeTtlSeconds =
|
||||
typeof state.ttl_seconds === 'number' ? state.ttl_seconds : this.executionModeTtlSeconds;
|
||||
this.scheduleExecutionModeExpirySync(this.executionModeDirectUntil);
|
||||
this.uiPushToast({
|
||||
title: '执行环境已更新',
|
||||
message: payload?.message || '已立即生效',
|
||||
@ -1253,6 +1254,8 @@ export const uiMethods = {
|
||||
|
||||
async fetchExecutionMode() {
|
||||
try {
|
||||
const prevExecutionMode = this.currentExecutionMode;
|
||||
const prevDirectUntil = this.executionModeDirectUntil;
|
||||
const response = await fetch('/api/execution-mode');
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok || !payload?.success) {
|
||||
@ -1268,11 +1271,78 @@ export const uiMethods = {
|
||||
if (typeof state.ttl_seconds === 'number') {
|
||||
this.executionModeTtlSeconds = state.ttl_seconds;
|
||||
}
|
||||
this.scheduleExecutionModeExpirySync(this.executionModeDirectUntil);
|
||||
const parseDirectUntilMs = (value) => {
|
||||
if (!value) return 0;
|
||||
if (typeof value === 'number') {
|
||||
return value > 1e12 ? value : value * 1000;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isNaN(parsed) ? 0 : parsed;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const prevDirectUntilMs = parseDirectUntilMs(prevDirectUntil);
|
||||
const fallbackLikelyByExpiry = !!prevDirectUntilMs && Date.now() >= prevDirectUntilMs - 1200;
|
||||
const directExpiredToSandbox =
|
||||
prevExecutionMode === 'direct' &&
|
||||
this.currentExecutionMode === 'sandbox' &&
|
||||
fallbackLikelyByExpiry;
|
||||
if (directExpiredToSandbox) {
|
||||
if (this.currentPermissionMode === 'unrestricted') {
|
||||
this.currentPermissionMode = 'approval';
|
||||
}
|
||||
Promise.resolve(this.fetchPermissionMode()).catch(() => {});
|
||||
try {
|
||||
if (this.executionModeAutoFallbackToastId) {
|
||||
this.uiDismissToast(this.executionModeAutoFallbackToastId);
|
||||
}
|
||||
} catch (_err) {}
|
||||
this.executionModeAutoFallbackToastId = this.uiPushToast({
|
||||
title: '执行环境已自动回退',
|
||||
message: '完全访问权限已到期,已自动切回受限权限并切换为沙箱执行环境。',
|
||||
type: 'warning',
|
||||
duration: null
|
||||
});
|
||||
}
|
||||
} catch (_error) {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
|
||||
clearExecutionModeExpirySyncTimer() {
|
||||
if (this.executionModeExpirySyncTimer) {
|
||||
clearTimeout(this.executionModeExpirySyncTimer);
|
||||
this.executionModeExpirySyncTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
scheduleExecutionModeExpirySync(directUntil) {
|
||||
this.clearExecutionModeExpirySyncTimer();
|
||||
if (!this.executionModeEnabled || this.currentExecutionMode !== 'direct' || !directUntil) {
|
||||
return;
|
||||
}
|
||||
let untilMs = 0;
|
||||
if (typeof directUntil === 'number') {
|
||||
untilMs = directUntil > 1e12 ? directUntil : directUntil * 1000;
|
||||
} else if (typeof directUntil === 'string') {
|
||||
const parsed = Date.parse(directUntil);
|
||||
if (!Number.isNaN(parsed)) {
|
||||
untilMs = parsed;
|
||||
}
|
||||
}
|
||||
if (!untilMs) {
|
||||
return;
|
||||
}
|
||||
const delayMs = Math.max(250, untilMs - Date.now() + 250);
|
||||
this.executionModeExpirySyncTimer = window.setTimeout(async () => {
|
||||
this.executionModeExpirySyncTimer = null;
|
||||
await this.fetchExecutionMode();
|
||||
await this.fetchPermissionMode();
|
||||
}, delayMs);
|
||||
},
|
||||
|
||||
async openPathAuthorizationDialog() {
|
||||
if (!this.executionModeEnabled) return;
|
||||
this.pathAuthorizationDialogOpen = true;
|
||||
|
||||
@ -104,6 +104,7 @@ export function dataState() {
|
||||
executionModeDirectUntil: null,
|
||||
executionModeAutoFallbackToastId: null,
|
||||
executionModeTtlSeconds: 600,
|
||||
executionModeExpirySyncTimer: null,
|
||||
pathAuthorizationDialogOpen: false,
|
||||
pathAuthorizationMode: 'writable',
|
||||
pathAuthorizationWritableDraft: '',
|
||||
|
||||
@ -338,11 +338,32 @@ function normalizeShowTagsPlugin() {
|
||||
};
|
||||
}
|
||||
|
||||
function wrapMarkdownTablesPlugin() {
|
||||
return (tree: any) => {
|
||||
visit(tree, 'element', (node: any, index: number | undefined, parent: any) => {
|
||||
if (!node || node.tagName !== 'table' || !parent || typeof index !== 'number') return;
|
||||
if (parent?.tagName === 'div' && Array.isArray(parent?.properties?.className)) {
|
||||
const classes = parent.properties.className as string[];
|
||||
if (classes.includes('md-table-scroll')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
parent.children[index] = {
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
properties: { className: ['md-table-scroll'], 'data-md-table-scroll': '1' },
|
||||
children: [node]
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const sanitizedSchema: Record<string, any> = {
|
||||
...defaultSchema,
|
||||
tagNames: [...(defaultSchema.tagNames || []), 'show_html', 'show_image', 'show-html', 'show-image'],
|
||||
attributes: {
|
||||
...(defaultSchema.attributes || {}),
|
||||
div: [...((defaultSchema.attributes || {}).div || []), 'className', 'data-md-table-scroll'],
|
||||
show_html: ['ratio', 'js', 'data-encoded', 'data-partial'],
|
||||
show_image: ['src', 'alt', 'title', 'width', 'height'],
|
||||
'show-html': ['ratio', 'js', 'data-encoded', 'data-partial'],
|
||||
@ -357,6 +378,7 @@ const markdownProcessor = unified()
|
||||
.use(remarkRehype, { allowDangerousHtml: true })
|
||||
.use(rehypeRaw)
|
||||
.use(normalizeShowTagsPlugin)
|
||||
.use(wrapMarkdownTablesPlugin)
|
||||
.use(rehypeSanitize, sanitizedSchema)
|
||||
.use(rehypeStringify, { allowDangerousHtml: false });
|
||||
|
||||
|
||||
@ -74,6 +74,7 @@
|
||||
.messages-area {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-anchor: none;
|
||||
padding: 24px;
|
||||
padding-top: 30px;
|
||||
@ -412,6 +413,7 @@
|
||||
|
||||
.message-block {
|
||||
margin-bottom: 24px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.message-block.message-block--sub-agent-notice {
|
||||
@ -1084,6 +1086,9 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
|
||||
|
||||
.text-output .text-content {
|
||||
padding: 0 20px 0 15px;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.text-output pre {
|
||||
@ -1093,7 +1098,8 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
|
||||
line-height: 1.55;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
scrollbar-gutter: stable;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.text-output pre code {
|
||||
@ -1106,15 +1112,58 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
|
||||
}
|
||||
|
||||
.text-output .text-content table {
|
||||
width: 100%;
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 16px 0;
|
||||
margin: 0;
|
||||
background: var(--theme-surface-soft);
|
||||
}
|
||||
|
||||
.text-output .text-content .md-table-scroll,
|
||||
.text-output .text-content [data-md-table-scroll='1'] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 16px 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border: 1px solid var(--claude-border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: var(--theme-surface-soft);
|
||||
box-shadow: var(--theme-shadow-soft);
|
||||
}
|
||||
|
||||
.text-output .text-content .md-table-scroll > table,
|
||||
.text-output .text-content [data-md-table-scroll='1'] > table {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.text-output .text-content .md-table-scroll::-webkit-scrollbar,
|
||||
.text-output .text-content [data-md-table-scroll='1']::-webkit-scrollbar {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.text-output .text-content .md-table-scroll::-webkit-scrollbar-track,
|
||||
.text-output .text-content [data-md-table-scroll='1']::-webkit-scrollbar-track {
|
||||
background: color-mix(in srgb, var(--theme-surface-muted, #d1d5db) 70%, transparent);
|
||||
border-radius: 999px;
|
||||
margin: 0 8px 6px;
|
||||
}
|
||||
|
||||
.text-output .text-content .md-table-scroll::-webkit-scrollbar-thumb,
|
||||
.text-output .text-content [data-md-table-scroll='1']::-webkit-scrollbar-thumb {
|
||||
background: color-mix(in srgb, var(--claude-text-secondary, #6b7280) 55%, transparent);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.text-output .text-content .md-table-scroll,
|
||||
.text-output .text-content [data-md-table-scroll='1'] {
|
||||
scrollbar-color: color-mix(in srgb, var(--claude-text-secondary, #6b7280) 55%, transparent)
|
||||
color-mix(in srgb, var(--theme-surface-muted, #d1d5db) 70%, transparent);
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.text-output .text-content thead {
|
||||
background: var(--theme-chip-bg);
|
||||
}
|
||||
@ -1251,7 +1300,8 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
|
||||
line-height: 1.55;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
scrollbar-gutter: stable;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.code-block-wrapper pre code {
|
||||
|
||||
@ -396,10 +396,13 @@
|
||||
}
|
||||
|
||||
.todo-panel {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding: 16px 20px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-agent-panel {
|
||||
@ -412,6 +415,22 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 左侧四个工作面板统一支持纵向滚动 */
|
||||
.file-tree,
|
||||
.todo-panel,
|
||||
.sub-agent-panel {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.file-tree::-webkit-scrollbar,
|
||||
.todo-panel::-webkit-scrollbar,
|
||||
.sub-agent-panel::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.sub-agent-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user