feat: Phase1 项目骨架 - Maven项目结构、核心AgentLoop、6个工具、命令系统、控制台渲染、REPL会话
实现内容: - pom.xml: JDK25 + Spring AI 2.0.0-M4 + JLine3 + Picocli - core/AgentLoop: 基于ChatModel的显式工具循环(非ChatClient) - tool/: Tool接口 + ToolRegistry + ToolCallbackAdapter(适配Spring AI) - tool/impl/: BashTool, FileReadTool, FileWriteTool, FileEditTool, GlobTool, GrepTool - command/: SlashCommand接口 + CommandRegistry + /help, /clear, /exit - console/: AnsiStyle, BannerPrinter, ToolStatusRenderer, ThinkingRenderer, SpinnerAnimation, MarkdownRenderer - context/: SystemPromptBuilder, ClaudeMdLoader(多级CLAUDE.md加载) - repl/ReplSession: REPL主循环(Scanner降级方案) - config/AppConfig: Spring Bean装配 - application.yml: Anthropic/OpenAI双模型配置 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package com.claudecode.console;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
/**
|
||||
* 加载动画(Spinner)—— 对应 claude-code/src/components/Spinner.tsx。
|
||||
* <p>
|
||||
* 在等待 AI 响应时显示旋转动画。
|
||||
*/
|
||||
public class SpinnerAnimation {
|
||||
|
||||
private static final String[] FRAMES = {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"};
|
||||
private static final int INTERVAL_MS = 80;
|
||||
|
||||
private final PrintStream out;
|
||||
private volatile boolean running;
|
||||
private Thread thread;
|
||||
private String message = "Thinking";
|
||||
|
||||
public SpinnerAnimation(PrintStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/** 启动 spinner */
|
||||
public void start(String message) {
|
||||
if (running) return;
|
||||
this.message = message;
|
||||
this.running = true;
|
||||
|
||||
thread = Thread.ofVirtual().name("spinner").start(() -> {
|
||||
int idx = 0;
|
||||
while (running) {
|
||||
out.print(AnsiStyle.clearLine());
|
||||
out.print(AnsiStyle.CYAN + " " + FRAMES[idx % FRAMES.length]
|
||||
+ " " + AnsiStyle.RESET + AnsiStyle.dim(this.message));
|
||||
out.flush();
|
||||
idx++;
|
||||
try {
|
||||
Thread.sleep(INTERVAL_MS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 清除 spinner 行
|
||||
out.print(AnsiStyle.clearLine());
|
||||
out.flush();
|
||||
});
|
||||
}
|
||||
|
||||
/** 停止 spinner */
|
||||
public void stop() {
|
||||
running = false;
|
||||
if (thread != null) {
|
||||
thread.interrupt();
|
||||
try {
|
||||
thread.join(200);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 更新消息 */
|
||||
public void updateMessage(String newMessage) {
|
||||
this.message = newMessage;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user