enhance: improve header colors, streaming markdown, scroll preservation
- Header: use colored values (API cyan, model green, dir yellow, etc.) - Markdown: render progressively during streaming (not just after completion) - Streaming cursor appended to last rendered line - Scroll: preserve user scroll offset during streaming token updates - Previously reset to 0 on every token, now keeps user position - All process info (tool calls, thinking) persists after completion Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -158,37 +158,48 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
|
|||||||
" │ CODE │─╯ ",
|
" │ CODE │─╯ ",
|
||||||
" ╰─┬────┬─╯ "
|
" ╰─┬────┬─╯ "
|
||||||
};
|
};
|
||||||
// 右侧信息
|
|
||||||
String[] info = {
|
|
||||||
"",
|
|
||||||
"Welcome!",
|
|
||||||
"API: " + baseUrl,
|
|
||||||
"Protocol: " + provider.toUpperCase() + " Model: " + model,
|
|
||||||
"Work Dir: " + System.getProperty("user.dir", "."),
|
|
||||||
"Tools: " + toolCount + " | Commands: " + cmdCount
|
|
||||||
};
|
|
||||||
|
|
||||||
// 构建双列文本行
|
|
||||||
int logoWidth = 19;
|
int logoWidth = 19;
|
||||||
int sepWidth = 3; // " │ "
|
int sepWidth = 3; // " │ "
|
||||||
int rightWidth = Math.max(0, w - 4 - logoWidth - sepWidth - 2); // 4=border+padding
|
int rightWidth = Math.max(0, w - 4 - logoWidth - sepWidth - 2);
|
||||||
|
|
||||||
|
// 构建右侧信息行(带颜色高亮)
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Renderable[] rightTexts = {
|
||||||
|
Text.of(""),
|
||||||
|
Text.of("Welcome!").bold(),
|
||||||
|
Text.of(
|
||||||
|
Text.of("API: ").dimmed(),
|
||||||
|
Text.of(baseUrl).color(Color.BRIGHT_CYAN)
|
||||||
|
),
|
||||||
|
Text.of(
|
||||||
|
Text.of("Protocol: ").dimmed(),
|
||||||
|
Text.of(provider.toUpperCase()).color(Color.BRIGHT_GREEN),
|
||||||
|
Text.of(" Model: ").dimmed(),
|
||||||
|
Text.of(model).color(Color.BRIGHT_GREEN)
|
||||||
|
),
|
||||||
|
Text.of(
|
||||||
|
Text.of("Work Dir: ").dimmed(),
|
||||||
|
Text.of(System.getProperty("user.dir", ".")).color(Color.BRIGHT_YELLOW)
|
||||||
|
),
|
||||||
|
Text.of(
|
||||||
|
Text.of("Tools: ").dimmed(),
|
||||||
|
Text.of(String.valueOf(toolCount)).color(Color.BRIGHT_CYAN),
|
||||||
|
Text.of(" │ Commands: ").dimmed(),
|
||||||
|
Text.of(String.valueOf(cmdCount)).color(Color.BRIGHT_CYAN)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
List<Renderable> rows = new ArrayList<>();
|
List<Renderable> rows = new ArrayList<>();
|
||||||
int maxRows = Math.max(logo.length, info.length);
|
int maxRows = Math.max(logo.length, rightTexts.length);
|
||||||
for (int i = 0; i < maxRows; i++) {
|
for (int i = 0; i < maxRows; i++) {
|
||||||
String left = i < logo.length ? logo[i] : "";
|
String left = i < logo.length ? logo[i] : "";
|
||||||
String right = i < info.length ? info[i] : "";
|
|
||||||
// 补齐左侧
|
|
||||||
if (left.length() < logoWidth) left = left + " ".repeat(logoWidth - left.length());
|
if (left.length() < logoWidth) left = left + " ".repeat(logoWidth - left.length());
|
||||||
// 截断右侧
|
Renderable rightPart = i < rightTexts.length ? rightTexts[i] : Text.of("");
|
||||||
if (right.length() > rightWidth) right = right.substring(0, rightWidth);
|
|
||||||
// 右侧补齐
|
|
||||||
right = right + " ".repeat(Math.max(0, rightWidth - right.length()));
|
|
||||||
|
|
||||||
rows.add(Text.of(
|
rows.add(Text.of(
|
||||||
Text.of(left).color(Color.BRIGHT_CYAN),
|
Text.of(left).color(Color.BRIGHT_CYAN),
|
||||||
Text.of(" │ ").dimmed(),
|
Text.of(" │ ").dimmed(),
|
||||||
i == 1 ? Text.of(right).bold() : Text.of(right).dimmed()
|
rightPart
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,34 +270,18 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
|
|||||||
yield lines;
|
yield lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m.streaming()) {
|
// 始终使用 Markdown 渲染(流式和完成都渲染)
|
||||||
// 已完成的消息 — 使用 Markdown 渲染
|
List<Renderable> mdLines = MarkdownToText.convert(text);
|
||||||
List<Renderable> mdLines = MarkdownToText.convert(text);
|
// 流式时在最后一行追加光标
|
||||||
for (int i = 0; i < mdLines.size(); i++) {
|
if (m.streaming() && !mdLines.isEmpty()) {
|
||||||
if (i == 0) {
|
Renderable lastLine = mdLines.getLast();
|
||||||
lines.add(Text.of(Text.of("● ").color(Color.BRIGHT_CYAN), mdLines.get(i)));
|
mdLines.set(mdLines.size() - 1, Text.of(lastLine, Text.of("▌").color(Color.BRIGHT_CYAN)));
|
||||||
} else {
|
}
|
||||||
lines.add(Text.of(Text.of(" "), mdLines.get(i)));
|
for (int i = 0; i < mdLines.size(); i++) {
|
||||||
}
|
if (i == 0) {
|
||||||
}
|
lines.add(Text.of(Text.of("● ").color(Color.BRIGHT_CYAN), mdLines.get(i)));
|
||||||
} else {
|
} else {
|
||||||
// 流式中 — 直接按行显示带光标
|
lines.add(Text.of(Text.of(" "), mdLines.get(i)));
|
||||||
String[] textLines = text.split("\n", -1);
|
|
||||||
for (int i = 0; i < textLines.length; i++) {
|
|
||||||
String line = textLines[i];
|
|
||||||
String displayLine = (m.streaming() && i == textLines.length - 1)
|
|
||||||
? line + "▌" : line;
|
|
||||||
if (i == 0) {
|
|
||||||
lines.add(Text.of(
|
|
||||||
Text.of("● ").color(Color.BRIGHT_CYAN),
|
|
||||||
Text.of(displayLine).color(Color.WHITE)
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
lines.add(Text.of(
|
|
||||||
Text.of(" ").dimmed(),
|
|
||||||
Text.of(displayLine).color(Color.WHITE)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield lines;
|
yield lines;
|
||||||
@@ -698,8 +693,9 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
|
|||||||
msgs.add(new AssistantMsg(token, true));
|
msgs.add(new AssistantMsg(token, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保留用户的滚动偏移(如果用户手动滚动过则不自动归零)
|
||||||
setState(new TuiState(s.inputText, Collections.unmodifiableList(msgs),
|
setState(new TuiState(s.inputText, Collections.unmodifiableList(msgs),
|
||||||
0, s.thinking, s.thinkingText));
|
s.scrollOffset, s.thinking, s.thinkingText));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,7 +708,7 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
|
|||||||
if (!msgs.isEmpty() && msgs.getLast() instanceof AssistantMsg am && am.streaming()) {
|
if (!msgs.isEmpty() && msgs.getLast() instanceof AssistantMsg am && am.streaming()) {
|
||||||
msgs.set(msgs.size() - 1, am.finish());
|
msgs.set(msgs.size() - 1, am.finish());
|
||||||
setState(new TuiState(s.inputText, Collections.unmodifiableList(msgs),
|
setState(new TuiState(s.inputText, Collections.unmodifiableList(msgs),
|
||||||
0, s.thinking, s.thinkingText));
|
s.scrollOffset, s.thinking, s.thinkingText));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user