fix: CLI交互样式优化 - 参考Claude Code/Copilot CLI效果

- 移除输入框横线(readLine阻塞限制,无法实现上下同时显示)
- ● 前缀移至 onStreamStart 回调(避免被 spinner 覆盖)
- 每段 AI 文本都获得独立的 ● 前缀(工具调用后续文也有)
- 工具 START 事件确保换行后再渲染(避免与AI文本混行)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pull/1/head
liuzh 1 month ago
parent 3c852d4446
commit 86a47282cf
  1. 33
      src/main/java/com/claudecode/repl/ReplSession.java

@ -102,6 +102,10 @@ public class ReplSession {
switch (event.phase()) { switch (event.phase()) {
case START -> { case START -> {
spinner.stop(); spinner.stop();
// 如果前面的流式文本没有换行结尾,先换行
if (!streamNewLine) {
out.println();
}
toolStatusRenderer.renderStart(event.toolName(), event.arguments()); toolStatusRenderer.renderStart(event.toolName(), event.arguments());
streamNewLine = true; // 工具渲染输出以 println 结尾 streamNewLine = true; // 工具渲染输出以 println 结尾
} }
@ -112,8 +116,16 @@ public class ReplSession {
} }
}); });
// 流式输出第一个 token 到达时停止 spinner // 流式输出第一个 token 到达时:停止 spinner → 打印 ● 前缀
agentLoop.setOnStreamStart(() -> spinner.stop()); agentLoop.setOnStreamStart(() -> {
spinner.stop();
// 每个流式迭代开始时打印 ● 前缀(工具调用后的续文也会获得新的 ●)
if (streamNewLine) {
out.println(); // 与前面的输出之间留一个空行
}
out.print(AnsiStyle.BRIGHT_CYAN + " ● " + AnsiStyle.RESET);
streamNewLine = false;
});
agentLoop.setOnAssistantMessage(text -> { agentLoop.setOnAssistantMessage(text -> {
// 阻塞模式回调:流式模式下由 onToken 实时输出,此回调不触发 // 阻塞模式回调:流式模式下由 onToken 实时输出,此回调不触发
@ -199,17 +211,9 @@ public class ReplSession {
statusLine.enable(providerInfo.model(), agentLoop.getTokenTracker()); statusLine.enable(providerInfo.model(), agentLoop.getTokenTracker());
} }
// 输入框横线宽度
int termWidth = terminal.getWidth();
int lineWidth = termWidth > 10 ? termWidth - 2 : 78;
String inputLine = AnsiStyle.DIM + "─".repeat(lineWidth) + AnsiStyle.RESET;
CommandContext cmdContext = new CommandContext(agentLoop, toolRegistry, commandRegistry, out, () -> running = false); CommandContext cmdContext = new CommandContext(agentLoop, toolRegistry, commandRegistry, out, () -> running = false);
while (running) { while (running) {
// 输入框上横线
out.println(inputLine);
String input; String input;
try { try {
input = reader.readLine(prompt).strip(); input = reader.readLine(prompt).strip();
@ -221,9 +225,6 @@ public class ReplSession {
break; break;
} }
// 输入框下横线
out.println(inputLine);
if (input.isEmpty()) { if (input.isEmpty()) {
continue; continue;
} }
@ -331,14 +332,10 @@ public class ReplSession {
// Agent 循环(流式输出) // Agent 循环(流式输出)
try { try {
spinner.start("Thinking..."); spinner.start("Thinking...");
out.println(); // 换行准备输出区域 streamNewLine = true; // spinner 停止后 onStreamStart 会打印 ● 前缀
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
// AI 回复前的 ● 标识(不换行,后续流式文本紧跟其后)
out.print(AnsiStyle.BRIGHT_CYAN + " ● " + AnsiStyle.RESET);
streamNewLine = false; // 重置换行跟踪
// 流式回调:逐 token 输出到终端,自动在每行开头加缩进 // 流式回调:逐 token 输出到终端,自动在每行开头加缩进
String response = agentLoop.runStreaming(input, token -> { String response = agentLoop.runStreaming(input, token -> {
for (int i = 0; i < token.length(); i++) { for (int i = 0; i < token.length(); i++) {

Loading…
Cancel
Save