con un clic
con un clic
跨语言代码库健康治理与自动优化系统。基于学术研究和实战经验,系统性检测 LLM 生成代码的结构性缺陷。完整流程:SCAN → DIAGNOSE → FIX → HARDEN。适用于任何语言的项目。
Sage 到 Tink UI 框架的完全重构指南,包含迁移步骤、文件清单、架构设计
Sage 版本管理规范,包含语义化版本、CHANGELOG、发布流程
Sage Agent 执行引擎开发指南,涵盖 UnifiedExecutor、Subagent、Lifecycle 管理
Sage 项目整体架构设计指南,基于 Claude Code、Crush 最佳实践的融合方案
Sage 独有的检查点系统设计,包含状态快照、文件追踪、回滚恢复机制
| name | sage-ui-design |
| description | Sage CLI UI 设计规范,参考 Claude Code 的终端显示模式,包含对齐、颜色、图标等设计标准 |
| when_to_use | 当修改 UI 显示、输出格式、颜色方案、动画效果时使用 |
| allowed_tools | ["Read","Grep","Glob","Edit","Write","Bash"] |
| user_invocable | true |
| priority | 90 |
参考 Claude Code 的终端 UI 设计,确保一致的用户体验。
所有内容从同一列开始,无前导空格
● 助手回复内容从这里开始
换行后的内容与上一行文字对齐(2空格缩进)
● Read(README.md)
└ Read 341 lines
✱ Thinking... (ctrl+c to interrupt)
关键规则:
● 从第 0 列开始(无前导空格)└ 树形符号,缩进 2 空格| 元素 | 颜色 | Rust 实现 | 说明 |
|---|---|---|---|
用户输入提示 > | 橙色/棕色 | .truecolor(204, 120, 50) 或 .yellow() | 用户输入区域标识 |
助手回复图标 ● | 亮白色 | .bright_white() | 内容消息标识 |
工具图标 ● | 亮蓝色 | .bright_blue() | 工具调用标识 |
| 工具名称 | 亮白色加粗 | .bright_white().bold() | 突出工具名 |
| 工具参数 | 暗淡灰色 | .dimmed() | 次要信息 |
工具结果符号 └ | 暗淡灰色 | .dimmed() | 树形结构 |
| 工具结果内容 | 暗淡灰色 | .dimmed() | 次要输出 |
| Thinking 动画 | 颜色可配置 | .bright_blue().bold() 等 | 根据状态变化 |
Thinking 完成 ✓ | 亮青色暗淡 | .bright_cyan().dimmed() | 完成状态 |
| 错误信息 | 红色 | .red() | 错误提示 |
| 成功信息 | 绿色 | .green() | 成功提示 |
| 警告信息 | 黄色 | .yellow() | 警告提示 |
// animation.rs 中的颜色映射
let colored_output = match color {
"blue" => format!("{} {}", frame, message).bright_blue().bold(),
"green" => format!("{} {}", frame, message).bright_green().bold(),
"yellow" => format!("{} {}", frame, message).bright_yellow().bold(),
"red" => format!("{} {}", frame, message).bright_red().bold(),
"cyan" => format!("{} {}", frame, message).bright_cyan().bold(),
"magenta" => format!("{} {}", frame, message).bright_magenta().bold(),
_ => format!("{} {}", frame, message).bright_white().bold(),
};
colored crateuse colored::Colorize;
// 基础颜色
"text".red() // 红色
"text".green() // 绿色
"text".yellow() // 黄色
"text".blue() // 蓝色
"text".cyan() // 青色
"text".magenta() // 品红
// 亮色变体
"text".bright_white() // 亮白色
"text".bright_blue() // 亮蓝色
"text".bright_cyan() // 亮青色
// 样式修饰
"text".bold() // 加粗
"text".dimmed() // 暗淡
"text".italic() // 斜体
// 组合使用
"text".bright_white().bold() // 亮白加粗
"text".bright_cyan().dimmed() // 亮青暗淡
// 自定义 RGB 颜色
"text".truecolor(204, 120, 50) // 橙色/棕色
// 核心图标(sage-core/src/ui/icons.rs)
pub const MESSAGE: &str = "●"; // 助手消息
pub const RESULT: &str = "└"; // 工具结果(树形)
pub const COGITATE: &str = "✱"; // Thinking 状态
pub const CHECKMARK: &str = "✓"; // 完成状态
pub const PROMPT: &str = "❯"; // 用户输入提示
● 消息内容第一行
消息内容第二行(2空格缩进)
消息内容第三行
实现:
fn on_content_start(&self) {
print!("{} ", Icons::message().bright_white());
}
fn on_content_chunk(&self, chunk: &str) {
// 换行后添加2空格缩进
let indented = chunk.replace('\n', "\n ");
print!("{}", indented);
}
● Read(README.md)
└ Read 341 lines
● Bash(git status)
└ On branch main
Your branch is up to date
● Task(探索代码库结构)
└ Found 15 files...
工具动画显示格式:
◐ Running (3.2s) · Read
◐ Running (5.1s) · 探索代码库结构
注意:
{spinner} Running ({elapsed}s) · {detail}实现:
fn on_tool_start(&self, name: &str, params: &str) {
println!();
print!("{} {}", Icons::message().bright_white(), name.bright_white().bold());
if !params.is_empty() {
println!("({})", params.dimmed());
}
}
fn on_tool_result(&self, output: &str) {
// 结果缩进2空格,多行内容额外缩进
let indented = output.replace('\n', "\n ");
println!(" {} {}", Icons::result().dimmed(), indented.dimmed());
}
进行中:
✱ Thinking... (ctrl+c to interrupt · thinking)
完成后:
✓ Thought for 2.1s
实现:
// 动画运行时(animation.rs)
async fn run_animation(...) {
let frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
// 显示: "⠋ Thinking (2.3s)"
print!("\r{} {} ({:.1}s)", frame, message, elapsed);
}
// 停止时显示完成状态
pub async fn stop_animation(&self) {
let completion_msg = format!("✓ Thought for {:.1}s", elapsed);
println!("{}", completion_msg.bright_cyan().dimmed());
}
| 文件 | 职责 |
|---|---|
sage-core/src/ui/icons.rs | 图标定义 |
sage-core/src/ui/animation.rs | Thinking 动画 |
sage-core/src/output/strategy.rs | 输出策略(Streaming/Batch/JSON) |
sage-cli/src/ui/nerd_console.rs | CLI 控制台 |
修改 UI 时,确保:
└ 符号 + 2 空格缩进检查 on_content_chunk 是否正确处理换行:
let indented = chunk.replace('\n', "\n "); // 2空格
Unicode 图标在不同终端可能有不同宽度。使用 unicode-width crate 计算实际宽度:
use unicode_width::UnicodeWidthStr;
let width = icon.width();
# 运行简单测试
sage "你好"
# 检查多行输出
sage "列出这个项目的主要功能"
# 检查工具调用显示
sage "读取 README.md"
Claude Code 使用 React/Ink 实现终端 UI:
// 统一的 FlexBox 布局
<FlexBox flexDirection="column" width="100%">
<Text>{content}</Text>
</FlexBox>
// 工具显示
<FlexBox marginTop={1}>
<Text>● </Text>
<Text bold>{toolName}</Text>
<Text dimColor>({params})</Text>
</FlexBox>
<FlexBox paddingLeft={2}>
<Text dimColor>└ {result}</Text>
</FlexBox>
Sage 使用 Rust 直接输出到终端,需要手动管理缩进和对齐。
LLM API (SSE) → SseDecoder → StreamChunk → LlmOrchestrator → OutputStrategy → Terminal
SseDecoder (sage-core/src/llm/sse_decoder/mod.rs)
\n\n 分割事件Provider (sage-core/src/llm/providers/*.rs)
StreamChunkcontent_block_delta 事件中的 text_deltaLlmOrchestrator (sage-core/src/agent/unified/llm_orchestrator.rs)
output_strategy.on_content_chunk() 显示每个 chunkOutputStrategy (sage-core/src/output/strategy.rs)
StreamingOutput: 实时显示每个 chunkBatchOutput: 收集所有内容后一次显示// llm_orchestrator.rs - stream_chat_with_animation_stop
loop {
select! {
chunk_opt = stream.next() => {
match chunk_opt {
Some(Ok(chunk)) => {
if let Some(ref chunk_content) = chunk.content {
if !chunk_content.is_empty() {
// 停止动画(仅第一次)
if !animation_stopped {
event_manager.stop_animation().await;
animation_stopped = true;
}
// 开始内容显示
if !has_content {
output_strategy.on_content_start();
has_content = true;
}
// 实时显示 chunk
output_strategy.on_content_chunk(chunk_content);
content.push_str(chunk_content);
}
}
}
// ...
}
}
}
}
// 在 on_content_chunk 中添加调试
fn on_content_chunk(&self, chunk: &str) {
tracing::debug!("Received chunk: {} bytes", chunk.len());
let indented = chunk.replace('\n', "\n ");
print!("{}", indented);
let _ = io::stdout().flush();
}