Compare commits
4 Commits
eaf270766f
...
aff972b218
| Author | SHA1 | Date | |
|---|---|---|---|
| aff972b218 | |||
| 41305ae2a8 | |||
| b03d158ab6 | |||
| 1e83bc9f68 |
@ -443,12 +443,32 @@ def start_background_jobs():
|
|||||||
_load_last_active_cache()
|
_load_last_active_cache()
|
||||||
socketio.start_background_task(idle_reaper_loop)
|
socketio.start_background_task(idle_reaper_loop)
|
||||||
|
|
||||||
|
TITLE_DEBUG_DIR = Path(LOGS_DIR).expanduser().resolve() / "title_debug"
|
||||||
|
TITLE_DEBUG_FILE = TITLE_DEBUG_DIR / "title_generation.log"
|
||||||
|
|
||||||
|
|
||||||
|
def _title_debug_log(message: str, **extra: Any) -> None:
|
||||||
|
try:
|
||||||
|
TITLE_DEBUG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
payload = {
|
||||||
|
"ts": datetime.now().isoformat(timespec="milliseconds"),
|
||||||
|
"message": str(message),
|
||||||
|
}
|
||||||
|
if extra:
|
||||||
|
payload["extra"] = extra
|
||||||
|
with TITLE_DEBUG_FILE.open("a", encoding="utf-8") as f:
|
||||||
|
f.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def _generate_title_async(user_message: str) -> Optional[str]:
|
async def _generate_title_async(user_message: str) -> Optional[str]:
|
||||||
"""使用快速模型生成对话标题。"""
|
"""使用快速模型生成对话标题。"""
|
||||||
if not user_message:
|
if not user_message:
|
||||||
|
_title_debug_log("skip_empty_user_message")
|
||||||
return None
|
return None
|
||||||
client = DeepSeekClient(thinking_mode=False, web_mode=True)
|
client = DeepSeekClient(thinking_mode=False, web_mode=True)
|
||||||
|
_title_debug_log("start_generate_title", user_message_preview=str(user_message)[:200], user_message_len=len(str(user_message)))
|
||||||
try:
|
try:
|
||||||
prompt_text = TITLE_PROMPT_PATH.read_text(encoding="utf-8")
|
prompt_text = TITLE_PROMPT_PATH.read_text(encoding="utf-8")
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -466,11 +486,17 @@ async def _generate_title_async(user_message: str) -> Optional[str]:
|
|||||||
try:
|
try:
|
||||||
content = resp.get("choices", [{}])[0].get("message", {}).get("content")
|
content = resp.get("choices", [{}])[0].get("message", {}).get("content")
|
||||||
if content:
|
if content:
|
||||||
return " ".join(str(content).strip().split())
|
normalized = " ".join(str(content).strip().split())
|
||||||
|
_title_debug_log("title_api_success", title_preview=normalized[:200], title_len=len(normalized))
|
||||||
|
return normalized
|
||||||
|
_title_debug_log("title_api_empty_content", resp_preview=str(resp)[:500])
|
||||||
except Exception:
|
except Exception:
|
||||||
|
_title_debug_log("title_api_parse_error", resp_preview=str(resp)[:500])
|
||||||
continue
|
continue
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug_log(f"[TitleGen] 生成标题异常: {exc}")
|
debug_log(f"[TitleGen] 生成标题异常: {exc}")
|
||||||
|
_title_debug_log("title_api_exception", error=str(exc))
|
||||||
|
_title_debug_log("title_api_no_result")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -482,6 +508,7 @@ def generate_conversation_title_background(web_terminal: WebTerminal, conversati
|
|||||||
async def _runner():
|
async def _runner():
|
||||||
title = await _generate_title_async(user_message)
|
title = await _generate_title_async(user_message)
|
||||||
if not title:
|
if not title:
|
||||||
|
_title_debug_log("title_not_generated", conversation_id=conversation_id, username=username)
|
||||||
return
|
return
|
||||||
# 限长,避免标题过长
|
# 限长,避免标题过长
|
||||||
safe_title = title[:80]
|
safe_title = title[:80]
|
||||||
@ -490,8 +517,11 @@ def generate_conversation_title_background(web_terminal: WebTerminal, conversati
|
|||||||
ok = web_terminal.context_manager.conversation_manager.update_conversation_title(conversation_id, safe_title)
|
ok = web_terminal.context_manager.conversation_manager.update_conversation_title(conversation_id, safe_title)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug_log(f"[TitleGen] 保存标题失败: {exc}")
|
debug_log(f"[TitleGen] 保存标题失败: {exc}")
|
||||||
|
_title_debug_log("title_save_exception", error=str(exc), conversation_id=conversation_id)
|
||||||
if not ok:
|
if not ok:
|
||||||
|
_title_debug_log("title_save_failed", conversation_id=conversation_id, safe_title=safe_title)
|
||||||
return
|
return
|
||||||
|
_title_debug_log("title_save_success", conversation_id=conversation_id, safe_title=safe_title)
|
||||||
try:
|
try:
|
||||||
socketio.emit('conversation_changed', {
|
socketio.emit('conversation_changed', {
|
||||||
'conversation_id': conversation_id,
|
'conversation_id': conversation_id,
|
||||||
@ -503,11 +533,13 @@ def generate_conversation_title_background(web_terminal: WebTerminal, conversati
|
|||||||
}, room=f"user_{username}")
|
}, room=f"user_{username}")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug_log(f"[TitleGen] 推送标题更新失败: {exc}")
|
debug_log(f"[TitleGen] 推送标题更新失败: {exc}")
|
||||||
|
_title_debug_log("title_emit_exception", error=str(exc), conversation_id=conversation_id, username=username)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
asyncio.run(_runner())
|
asyncio.run(_runner())
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug_log(f"[TitleGen] 任务执行失败: {exc}")
|
debug_log(f"[TitleGen] 任务执行失败: {exc}")
|
||||||
|
_title_debug_log("title_background_runner_exception", error=str(exc), conversation_id=conversation_id, username=username)
|
||||||
|
|
||||||
def cache_monitor_snapshot(execution_id: Optional[str], stage: str, snapshot: Optional[Dict[str, Any]]):
|
def cache_monitor_snapshot(execution_id: Optional[str], stage: str, snapshot: Optional[Dict[str, Any]]):
|
||||||
"""缓存工具执行前/后的文件快照。"""
|
"""缓存工具执行前/后的文件快照。"""
|
||||||
|
|||||||
@ -54,7 +54,7 @@ def _dispatch_runtime_mode_notice(terminal: WebTerminal, username: str, text: st
|
|||||||
if not notice:
|
if not notice:
|
||||||
return
|
return
|
||||||
|
|
||||||
# 若有运行任务,优先入队到该任务(下一轮工具后注入),确保会进入后续 API 请求上下文。
|
# 仅在“有运行中的任务”时注入 [系统通知|notify],空闲期切换不写入 user 消息。
|
||||||
try:
|
try:
|
||||||
from .tasks import task_manager
|
from .tasks import task_manager
|
||||||
|
|
||||||
@ -64,39 +64,21 @@ def _dispatch_runtime_mode_notice(terminal: WebTerminal, username: str, text: st
|
|||||||
]
|
]
|
||||||
if current_conv:
|
if current_conv:
|
||||||
running_tasks.sort(key=lambda r: 0 if r.conversation_id == current_conv else 1)
|
running_tasks.sort(key=lambda r: 0 if r.conversation_id == current_conv else 1)
|
||||||
if running_tasks:
|
if not running_tasks:
|
||||||
target = running_tasks[0]
|
debug_log(f"[RuntimeMode] idle switch ignored notify injection: {notice}")
|
||||||
result = task_manager.enqueue_runtime_guidance(
|
return
|
||||||
username=username,
|
|
||||||
task_id=target.task_id,
|
|
||||||
message=notice,
|
|
||||||
source="notify",
|
|
||||||
)
|
|
||||||
if result.get("success"):
|
|
||||||
return
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 无运行任务时,直接写入当前会话并广播前端显示(来源固定为 notify)。
|
target = running_tasks[0]
|
||||||
from .chat_flow_task_support import inject_runtime_user_message
|
result = task_manager.enqueue_runtime_guidance(
|
||||||
|
username=username,
|
||||||
conversation_id = getattr(getattr(terminal, "context_manager", None), "current_conversation_id", None)
|
task_id=target.task_id,
|
||||||
|
message=notice,
|
||||||
def _emit(event: str, payload: Dict[str, Any]) -> None:
|
source="notify",
|
||||||
try:
|
)
|
||||||
socketio.emit(event, payload, room=f"user_{username}")
|
if not result.get("success"):
|
||||||
except Exception:
|
debug_log(f"[RuntimeMode] enqueue runtime guidance failed: {result}")
|
||||||
pass
|
except Exception as exc:
|
||||||
|
debug_log(f"[RuntimeMode] dispatch notice failed: {exc}")
|
||||||
inject_runtime_user_message(
|
|
||||||
web_terminal=terminal,
|
|
||||||
messages=None,
|
|
||||||
text=notice,
|
|
||||||
source="notify",
|
|
||||||
sender=_emit,
|
|
||||||
conversation_id=conversation_id,
|
|
||||||
inline=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
@chat_bp.route('/api/thinking-mode', methods=['POST'])
|
@chat_bp.route('/api/thinking-mode', methods=['POST'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
|
|||||||
@ -1,13 +1,57 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from core.web_terminal import WebTerminal
|
from core.web_terminal import WebTerminal
|
||||||
|
from config import LOGS_DIR
|
||||||
from utils.api_client import DeepSeekClient
|
from utils.api_client import DeepSeekClient
|
||||||
|
|
||||||
|
TITLE_DEBUG_DIR = Path(LOGS_DIR).expanduser().resolve() / "title_debug"
|
||||||
|
TITLE_DEBUG_FILE = TITLE_DEBUG_DIR / "title_generation.log"
|
||||||
|
|
||||||
|
|
||||||
|
def _title_debug_log(message: str, **extra: Any) -> None:
|
||||||
|
try:
|
||||||
|
TITLE_DEBUG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
payload = {
|
||||||
|
"ts": datetime.now().isoformat(timespec="milliseconds"),
|
||||||
|
"message": str(message),
|
||||||
|
}
|
||||||
|
if extra:
|
||||||
|
payload["extra"] = extra
|
||||||
|
with TITLE_DEBUG_FILE.open("a", encoding="utf-8") as f:
|
||||||
|
f.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _env_optional(name: str) -> Optional[str]:
|
||||||
|
value = os.environ.get(name)
|
||||||
|
if value is None:
|
||||||
|
env_path = Path(__file__).resolve().parents[1] / ".env"
|
||||||
|
if env_path.exists():
|
||||||
|
try:
|
||||||
|
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
key, val = line.split("=", 1)
|
||||||
|
if key.strip() == name:
|
||||||
|
value = val.strip().strip('"').strip("'")
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
value = None
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
value = value.strip()
|
||||||
|
return value or None
|
||||||
|
|
||||||
|
|
||||||
async def _generate_title_async(
|
async def _generate_title_async(
|
||||||
user_message: str,
|
user_message: str,
|
||||||
@ -16,9 +60,29 @@ async def _generate_title_async(
|
|||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""使用快速模型生成对话标题。"""
|
"""使用快速模型生成对话标题。"""
|
||||||
if not user_message:
|
if not user_message:
|
||||||
|
_title_debug_log("skip_empty_user_message")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
client = DeepSeekClient(thinking_mode=False, web_mode=True)
|
client = DeepSeekClient(thinking_mode=False, web_mode=True)
|
||||||
|
title_base = _env_optional("AGENT_TITLE_API_BASE_URL")
|
||||||
|
title_key = _env_optional("AGENT_TITLE_API_KEY")
|
||||||
|
title_model = _env_optional("AGENT_TITLE_MODEL_ID")
|
||||||
|
if title_base:
|
||||||
|
client.fast_api_config["base_url"] = title_base
|
||||||
|
client.api_base_url = title_base
|
||||||
|
if title_key:
|
||||||
|
client.fast_api_config["api_key"] = title_key
|
||||||
|
client.api_key = title_key
|
||||||
|
if title_model:
|
||||||
|
client.fast_api_config["model_id"] = title_model
|
||||||
|
client.model_id = title_model
|
||||||
|
_title_debug_log("start_generate_title", user_message_preview=str(user_message)[:200], user_message_len=len(str(user_message)))
|
||||||
|
_title_debug_log(
|
||||||
|
"title_api_config",
|
||||||
|
base_url=client.fast_api_config.get("base_url"),
|
||||||
|
model_id=client.fast_api_config.get("model_id"),
|
||||||
|
has_api_key=bool(client.fast_api_config.get("api_key")),
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
prompt_text = Path(title_prompt_path).read_text(encoding="utf-8")
|
prompt_text = Path(title_prompt_path).read_text(encoding="utf-8")
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -38,11 +102,17 @@ async def _generate_title_async(
|
|||||||
try:
|
try:
|
||||||
content = resp.get("choices", [{}])[0].get("message", {}).get("content")
|
content = resp.get("choices", [{}])[0].get("message", {}).get("content")
|
||||||
if content:
|
if content:
|
||||||
return " ".join(str(content).strip().split())
|
normalized = " ".join(str(content).strip().split())
|
||||||
|
_title_debug_log("title_api_success", title_preview=normalized[:200], title_len=len(normalized))
|
||||||
|
return normalized
|
||||||
|
_title_debug_log("title_api_empty_content", resp_preview=str(resp)[:500])
|
||||||
except Exception:
|
except Exception:
|
||||||
|
_title_debug_log("title_api_parse_error", resp_preview=str(resp)[:500])
|
||||||
continue
|
continue
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug_logger(f"[TitleGen] 生成标题异常: {exc}")
|
debug_logger(f"[TitleGen] 生成标题异常: {exc}")
|
||||||
|
_title_debug_log("title_api_exception", error=str(exc))
|
||||||
|
_title_debug_log("title_api_no_result")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -62,6 +132,7 @@ def generate_conversation_title_background(
|
|||||||
async def _runner():
|
async def _runner():
|
||||||
title = await _generate_title_async(user_message, title_prompt_path, debug_logger)
|
title = await _generate_title_async(user_message, title_prompt_path, debug_logger)
|
||||||
if not title:
|
if not title:
|
||||||
|
_title_debug_log("title_not_generated", conversation_id=conversation_id, username=username)
|
||||||
return
|
return
|
||||||
|
|
||||||
safe_title = title[:80]
|
safe_title = title[:80]
|
||||||
@ -70,8 +141,11 @@ def generate_conversation_title_background(
|
|||||||
ok = web_terminal.context_manager.conversation_manager.update_conversation_title(conversation_id, safe_title)
|
ok = web_terminal.context_manager.conversation_manager.update_conversation_title(conversation_id, safe_title)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug_logger(f"[TitleGen] 保存标题失败: {exc}")
|
debug_logger(f"[TitleGen] 保存标题失败: {exc}")
|
||||||
|
_title_debug_log("title_save_exception", error=str(exc), conversation_id=conversation_id)
|
||||||
if not ok:
|
if not ok:
|
||||||
|
_title_debug_log("title_save_failed", conversation_id=conversation_id, safe_title=safe_title)
|
||||||
return
|
return
|
||||||
|
_title_debug_log("title_save_success", conversation_id=conversation_id, safe_title=safe_title)
|
||||||
|
|
||||||
# 添加标题更新事件到任务事件流(用于轮询机制)
|
# 添加标题更新事件到任务事件流(用于轮询机制)
|
||||||
try:
|
try:
|
||||||
@ -91,6 +165,7 @@ def generate_conversation_title_background(
|
|||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug_logger(f"[TitleGen] 添加任务事件失败: {exc}")
|
debug_logger(f"[TitleGen] 添加任务事件失败: {exc}")
|
||||||
|
_title_debug_log("title_task_event_exception", error=str(exc), conversation_id=conversation_id, username=username)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
socketio_instance.emit(
|
socketio_instance.emit(
|
||||||
@ -105,11 +180,13 @@ def generate_conversation_title_background(
|
|||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug_logger(f"[TitleGen] 推送标题更新失败: {exc}")
|
debug_logger(f"[TitleGen] 推送标题更新失败: {exc}")
|
||||||
|
_title_debug_log("title_emit_exception", error=str(exc), conversation_id=conversation_id, username=username)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
asyncio.run(_runner())
|
asyncio.run(_runner())
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
debug_logger(f"[TitleGen] 任务执行失败: {exc}")
|
debug_logger(f"[TitleGen] 任务执行失败: {exc}")
|
||||||
|
_title_debug_log("title_background_runner_exception", error=str(exc), conversation_id=conversation_id, username=username)
|
||||||
|
|
||||||
|
|
||||||
def get_thinking_state(terminal: WebTerminal) -> Dict[str, Any]:
|
def get_thinking_state(terminal: WebTerminal) -> Dict[str, Any]:
|
||||||
|
|||||||
@ -420,6 +420,14 @@ export const uiMethods = {
|
|||||||
if (Number(this.composerDraftFetchSeq || 0) !== fetchSeq) {
|
if (Number(this.composerDraftFetchSeq || 0) !== fetchSeq) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.taskInProgress || this.composerBusy) {
|
||||||
|
debugLog('[UI] 跳过输入草稿恢复(任务运行中)', {
|
||||||
|
reason,
|
||||||
|
taskInProgress: !!this.taskInProgress,
|
||||||
|
composerBusy: !!this.composerBusy
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const content = this.normalizeComposerDraftContent(payload?.data?.content || '');
|
const content = this.normalizeComposerDraftContent(payload?.data?.content || '');
|
||||||
this.composerDraftLastSyncedContent = content;
|
this.composerDraftLastSyncedContent = content;
|
||||||
this.composerDraftDirty = false;
|
this.composerDraftDirty = false;
|
||||||
|
|||||||
@ -81,7 +81,12 @@ export const watchers = {
|
|||||||
newValue,
|
newValue,
|
||||||
skipConversationHistoryReload: this.skipConversationHistoryReload
|
skipConversationHistoryReload: this.skipConversationHistoryReload
|
||||||
});
|
});
|
||||||
if (oldValue !== newValue && typeof this.restoreComposerDraftState === 'function') {
|
if (
|
||||||
|
oldValue !== newValue &&
|
||||||
|
!this.taskInProgress &&
|
||||||
|
!this.composerBusy &&
|
||||||
|
typeof this.restoreComposerDraftState === 'function'
|
||||||
|
) {
|
||||||
this.restoreComposerDraftState(
|
this.restoreComposerDraftState(
|
||||||
`watch-conversation-id:${oldValue || 'none'}->${newValue || 'none'}`
|
`watch-conversation-id:${oldValue || 'none'}->${newValue || 'none'}`
|
||||||
);
|
);
|
||||||
|
|||||||
@ -388,7 +388,12 @@ const composerInputKey = computed(
|
|||||||
|
|
||||||
const runtimeQueuedMessagesForRender = computed(() => {
|
const runtimeQueuedMessagesForRender = computed(() => {
|
||||||
const list = Array.isArray(props.runtimeQueuedMessages) ? props.runtimeQueuedMessages : [];
|
const list = Array.isArray(props.runtimeQueuedMessages) ? props.runtimeQueuedMessages : [];
|
||||||
return list.filter((item) => item && item.id && item.text);
|
return list
|
||||||
|
.filter((item) => item && item.id && item.text)
|
||||||
|
.map((item) => ({
|
||||||
|
...item,
|
||||||
|
text: String(item.text || '').replace(/[\r\n\u2028\u2029]+/g, ' ')
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
const RUNTIME_QUEUE_ANIM_DURATION = 300;
|
const RUNTIME_QUEUE_ANIM_DURATION = 300;
|
||||||
@ -432,6 +437,7 @@ const stopRuntimeQueueAnimation = (el: HTMLElement) => {
|
|||||||
}
|
}
|
||||||
el.style.removeProperty('transition');
|
el.style.removeProperty('transition');
|
||||||
el.style.removeProperty('transform');
|
el.style.removeProperty('transform');
|
||||||
|
el.style.removeProperty('pointer-events');
|
||||||
};
|
};
|
||||||
|
|
||||||
const playRuntimeQueueTransform = (
|
const playRuntimeQueueTransform = (
|
||||||
@ -563,10 +569,7 @@ const spawnRuntimeQueueLeaveGhost = (id: string) => {
|
|||||||
|
|
||||||
const animateRuntimeQueueEnter = (el: HTMLElement) => {
|
const animateRuntimeQueueEnter = (el: HTMLElement) => {
|
||||||
const offset = resolveRuntimeQueueOffset(el);
|
const offset = resolveRuntimeQueueOffset(el);
|
||||||
el.style.pointerEvents = 'none';
|
playRuntimeQueueTransform(el, offset, 0);
|
||||||
playRuntimeQueueTransform(el, offset, 0, () => {
|
|
||||||
el.style.removeProperty('pointer-events');
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onBeforeUpdate(() => {
|
onBeforeUpdate(() => {
|
||||||
|
|||||||
@ -58,14 +58,16 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: var(--runtime-queue-z, 1);
|
z-index: var(--runtime-queue-z, 1);
|
||||||
min-height: 34px;
|
min-height: 34px;
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 6px 10px 6px 12px;
|
padding: 6px 10px 6px 12px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: var(--runtime-queue-bg);
|
background: var(--runtime-queue-bg);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.runtime-queue-item--top {
|
.runtime-queue-item--top {
|
||||||
@ -74,7 +76,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.runtime-queue-item__text {
|
.runtime-queue-item__text {
|
||||||
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
display: block;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
color: var(--claude-text);
|
color: var(--claude-text);
|
||||||
@ -84,6 +88,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.runtime-queue-item__action {
|
.runtime-queue-item__action {
|
||||||
|
flex-shrink: 0;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--claude-text-secondary);
|
color: var(--claude-text-secondary);
|
||||||
@ -92,6 +97,8 @@
|
|||||||
padding: 3px 4px;
|
padding: 3px 4px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.runtime-queue-item__action:hover {
|
.runtime-queue-item__action:hover {
|
||||||
|
|||||||
@ -14,6 +14,15 @@ except ImportError:
|
|||||||
sys.path.insert(0, str(project_root))
|
sys.path.insert(0, str(project_root))
|
||||||
from config import LOGS_DIR, LOG_LEVEL, LOG_FORMAT
|
from config import LOGS_DIR, LOG_LEVEL, LOG_FORMAT
|
||||||
|
|
||||||
|
def _env_flag(name: str, default: bool = False) -> bool:
|
||||||
|
value = str(os.environ.get(name, "")).strip().lower()
|
||||||
|
if not value:
|
||||||
|
return default
|
||||||
|
return value in {"1", "true", "yes", "on"}
|
||||||
|
|
||||||
|
|
||||||
|
_FILE_LOG_ENABLED = _env_flag("AGENT_FILE_LOG_ENABLED", default=False)
|
||||||
|
|
||||||
def setup_logger(name: str, log_file: str = None) -> logging.Logger:
|
def setup_logger(name: str, log_file: str = None) -> logging.Logger:
|
||||||
"""
|
"""
|
||||||
设置日志记录器
|
设置日志记录器
|
||||||
@ -40,21 +49,22 @@ def setup_logger(name: str, log_file: str = None) -> logging.Logger:
|
|||||||
console_handler.setFormatter(formatter)
|
console_handler.setFormatter(formatter)
|
||||||
logger.addHandler(console_handler)
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
# 文件处理器
|
# 文件处理器(默认关闭,避免在工作区生成 agent_debug/agent_*.log)
|
||||||
if log_file:
|
if _FILE_LOG_ENABLED:
|
||||||
file_path = Path(LOGS_DIR) / log_file
|
if log_file:
|
||||||
else:
|
file_path = Path(LOGS_DIR) / log_file
|
||||||
# 默认日志文件
|
else:
|
||||||
today = datetime.now().strftime("%Y%m%d")
|
# 默认日志文件
|
||||||
file_path = Path(LOGS_DIR) / f"agent_{today}.log"
|
today = datetime.now().strftime("%Y%m%d")
|
||||||
|
file_path = Path(LOGS_DIR) / f"agent_{today}.log"
|
||||||
|
|
||||||
# 确保日志目录存在
|
# 确保日志目录存在
|
||||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
file_handler = logging.FileHandler(file_path, encoding='utf-8')
|
file_handler = logging.FileHandler(file_path, encoding='utf-8')
|
||||||
file_handler.setLevel(getattr(logging, LOG_LEVEL))
|
file_handler.setLevel(getattr(logging, LOG_LEVEL))
|
||||||
file_handler.setFormatter(formatter)
|
file_handler.setFormatter(formatter)
|
||||||
logger.addHandler(file_handler)
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user