release: v6.1.0 - TUI 体验全面升级 + 背景自定义

TUI 界面改进:
- 全面中文化: CommandPalette/HelpPanel/Footer/Chatbox 边框标题等所有用户可见英文汉化
- 辅助中心 (Ctrl+P/F9): 原"设置"重命名, 含 6 项可切换设置
- 独立帮助浮层 (? 键): 重写帮助页面逻辑, 全中文, 带滑入淡入动画
- 背景切换: 辅助中心 [5] 预设背景, [6] 自定义背景; 新增 /bg 命令支持图片提取主色调
- 换行修复: Ctrl+J 作为主推换行键 (Windows 终端不区分 Shift+Enter)
- 引擎切换修复: 解决 call_from_thread 在主线程崩溃的 RuntimeError

技术改进:
- 文本工具调用解析: 检测模型用 <tool_call> 标签返回的工具调用
- 网络工具优化: network_tools.py 和 ai_search.py 改进请求头/重试/编码
- 流式输出修复: append_chunk 的 refresh 改为 layout=True
- 取消响应修复: 引擎层 _cancelled 标志 + TUI 层 worker 身份跟踪

融合 v6.0.0 工作:
- 保留 terminal 模拟器/operit 桥接/深度思考/工作模式/记忆模式等 v6.0.0 全部功能
- 在此基础上叠加本会话的 TUI 改进
This commit is contained in:
shuiyu1123 committed 2026-06-29 13:00:19 +08:00
1 parent b5f638cffc
commit de15b54b19
30 files changed
+3267 -934

No files matched your search

+26 -19
View File
@@ -189,6 +189,13 @@ class WorkflowEngine:
self.state_dir = os.path.join(self.workflows_dir, ".state")
os.makedirs(self.state_dir, exist_ok=True)
def _out(self, msg):
"""TUI 兼容输出"""
if self.cli and getattr(self.cli, 'tui_output_callback', None):
self.cli.tui_output_callback(msg)
else:
print(msg)
def run(self, path: str, args: dict = None) -> str:
"""执行工作流"""
if not os.path.exists(path):
@@ -226,8 +233,8 @@ class WorkflowEngine:
'started_at': context['workflow']['started_at'],
})
print(f"\n{Fore.CYAN}🔄 工作流 '{wf_name}' 开始 (ID: {wf_id}){Style.RESET_ALL}")
print(f" 共 {len(steps)} 个步骤\n")
self._out(f"\n{Fore.CYAN}🔄 工作流 '{wf_name}' 开始 (ID: {wf_id}){Style.RESET_ALL}")
self._out(f" 共 {len(steps)} 个步骤\n")
failed = False
for i, step in enumerate(steps):
@@ -240,7 +247,7 @@ class WorkflowEngine:
if condition:
resolved_condition = _resolve_vars(condition, context)
if not self._eval_condition(resolved_condition):
print(f" {Fore.YELLOW}⏭ [{step_name}] 条件不满足,跳过{Style.RESET_ALL}")
self._out(f" {Fore.YELLOW}⏭ [{step_name}] 条件不满足,跳过{Style.RESET_ALL}")
context['steps'][step_name] = {
'output': '', 'exit_code': 0, 'skipped': True
}
@@ -249,7 +256,7 @@ class WorkflowEngine:
# 解析步骤参数
resolved_step = _resolve_value(step, context)
print(f" {Fore.CYAN}▶ [{step_name}] {step_type}{Style.RESET_ALL}")
self._out(f" {Fore.CYAN}▶ [{step_name}] {step_type}{Style.RESET_ALL}")
# 更新状态
self._save_state(wf_id, {
@@ -268,29 +275,29 @@ class WorkflowEngine:
if result.get('exit_code', 0) == 0:
output_preview = str(result.get('output', ''))[:100]
print(f" {Fore.GREEN}✓ [{step_name}] 完成{Style.RESET_ALL}"
self._out(f" {Fore.GREEN}✓ [{step_name}] 完成{Style.RESET_ALL}"
f"{' → ' + output_preview if output_preview else ''}")
else:
failed = True
print(f" {Fore.RED}✗ [{step_name}] 失败 (exit={result.get('exit_code')}){Style.RESET_ALL}")
print(f" {result.get('error', result.get('output', ''))[:200]}")
self._out(f" {Fore.RED}✗ [{step_name}] 失败 (exit={result.get('exit_code')}){Style.RESET_ALL}")
self._out(f" {result.get('error', result.get('output', ''))[:200]}")
if not continue_on_error:
# 检查 on_fail 分支
on_fail = resolved_step.get('on_fail')
if on_fail:
print(f" {Fore.YELLOW}→ 执行 on_fail 分支{Style.RESET_ALL}")
self._out(f" {Fore.YELLOW}→ 执行 on_fail 分支{Style.RESET_ALL}")
self._execute_on_fail(on_fail, context)
break
else:
print(f" {Fore.YELLOW}⚠ continue_on_error=true,继续{Style.RESET_ALL}")
self._out(f" {Fore.YELLOW}⚠ continue_on_error=true,继续{Style.RESET_ALL}")
except Exception as e:
failed = True
context['steps'][step_name] = {
'output': '', 'exit_code': 1, 'error': str(e)
}
print(f" {Fore.RED}✗ [{step_name}] 异常: {e}{Style.RESET_ALL}")
self._out(f" {Fore.RED}✗ [{step_name}] 异常: {e}{Style.RESET_ALL}")
if not continue_on_error:
break
@@ -310,7 +317,7 @@ class WorkflowEngine:
})
icon = '✅' if not failed else '❌'
print(f"\n{icon} 工作流 '{wf_name}' {status} (ID: {wf_id})")
self._out(f"\n{icon} 工作流 '{wf_name}' {status} (ID: {wf_id})")
return f"{icon} 工作流 '{wf_name}' {status}"
def resume(self, wf_id: str) -> str:
@@ -335,7 +342,7 @@ class WorkflowEngine:
start_from = state.get('current_step', 0)
context = state.get('context', {})
print(f"\n{Fore.CYAN}🔄 恢复工作流 '{wf.get('name', wf_id)}' 从步骤 {start_from + 1}{Style.RESET_ALL}")
self._out(f"\n{Fore.CYAN}🔄 恢复工作流 '{wf.get('name', wf_id)}' 从步骤 {start_from + 1}{Style.RESET_ALL}")
failed = False
for i in range(start_from, len(steps)):
@@ -345,23 +352,23 @@ class WorkflowEngine:
continue_on_error = step.get('continue_on_error', False)
resolved_step = _resolve_value(step, context)
print(f" {Fore.CYAN}▶ [{step_name}] {step_type}{Style.RESET_ALL}")
self._out(f" {Fore.CYAN}▶ [{step_name}] {step_type}{Style.RESET_ALL}")
try:
result = self._execute_step(resolved_step, context)
context['steps'][step_name] = result
if result.get('exit_code', 0) == 0:
print(f" {Fore.GREEN}✓ [{step_name}] 完成{Style.RESET_ALL}")
self._out(f" {Fore.GREEN}✓ [{step_name}] 完成{Style.RESET_ALL}")
else:
failed = True
print(f" {Fore.RED}✗ [{step_name}] 失败{Style.RESET_ALL}")
self._out(f" {Fore.RED}✗ [{step_name}] 失败{Style.RESET_ALL}")
if not continue_on_error:
break
except Exception as e:
failed = True
context['steps'][step_name] = {'output': '', 'exit_code': 1, 'error': str(e)}
print(f" {Fore.RED}✗ [{step_name}] 异常: {e}{Style.RESET_ALL}")
self._out(f" {Fore.RED}✗ [{step_name}] 异常: {e}{Style.RESET_ALL}")
if not continue_on_error:
break
@@ -553,7 +560,7 @@ class WorkflowEngine:
"""打印步骤"""
message = step.get('message', step.get('text', ''))
resolved = _resolve_vars(str(message), context)
print(f" 💬 {resolved}")
self._out(f" 💬 {resolved}")
return {'output': resolved, 'exit_code': 0}
def _exec_wait_step(self, step: dict, context: dict) -> dict:
@@ -622,7 +629,7 @@ class WorkflowEngine:
def _execute_on_fail(self, on_fail, context: dict):
"""执行 on_fail 处理"""
if isinstance(on_fail, str):
print(f" 💬 {on_fail}")
self._out(f" 💬 {on_fail}")
elif isinstance(on_fail, list):
for step in on_fail:
self._execute_step(step, context)
@@ -636,7 +643,7 @@ class WorkflowEngine:
with open(path, 'w', encoding='utf-8') as f:
json.dump(state, f, ensure_ascii=False, indent=2, default=str)
except Exception as e:
print(f"{Fore.YELLOW}⚠ 保存状态失败: {e}{Style.RESET_ALL}")
self._out(f"{Fore.YELLOW}⚠ 保存状态失败: {e}{Style.RESET_ALL}")
def _load_state(self, wf_id: str) -> dict:
"""加载工作流状态"""