diff --git a/app/src/main/java/ai/javaclaw/JavaClawApplication.java b/app/src/main/java/ai/javaclaw/JavaClawApplication.java index e396ce6..0f80245 100644 --- a/app/src/main/java/ai/javaclaw/JavaClawApplication.java +++ b/app/src/main/java/ai/javaclaw/JavaClawApplication.java @@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.DefaultApplicationArguments; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @@ -44,12 +45,16 @@ public void run(ApplicationArguments args) throws Exception { @EventListener public void on(ConfigurationChangedEvent configurationChangedEvent) { - ApplicationArguments args = applicationContext.getBean(ApplicationArguments.class); + Thread thread = new Thread(() -> { try { + ApplicationArguments args = new DefaultApplicationArguments(); + if(applicationContext != null){ + args = applicationContext.getBean(ApplicationArguments.class); + applicationContext.close(); + } Thread.sleep(2000); - applicationContext.close(); applicationContext = SpringApplication.run(JavaClawApplication.class, args.getSourceArgs()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/app/src/main/java/ai/javaclaw/api/AgentsController.java b/app/src/main/java/ai/javaclaw/api/AgentsController.java new file mode 100644 index 0000000..b4dc403 --- /dev/null +++ b/app/src/main/java/ai/javaclaw/api/AgentsController.java @@ -0,0 +1,54 @@ +package ai.javaclaw.api; + +import ai.javaclaw.agents.AgentRegistry; +import ai.javaclaw.agents.AgentWorkspaceResolver; +import ai.javaclaw.agents.ConfiguredAgent; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import java.nio.file.Path; +import java.util.List; + +@Controller +public class AgentsController { + + private final AgentRegistry agentRegistry; + private final AgentWorkspaceResolver agentWorkspaceResolver; + + public AgentsController(AgentRegistry agentRegistry, AgentWorkspaceResolver agentWorkspaceResolver) { + this.agentRegistry = agentRegistry; + this.agentWorkspaceResolver = agentWorkspaceResolver; + } + + @GetMapping("/agents") + public String agents(Model model) { + List agents = agentRegistry.getAgents().stream() + .map(configuredAgent -> new AgentView( + configuredAgent.id(), + configuredAgent.provider(), + configuredAgent.model(), + resolveWorkspace(configuredAgent), + configuredAgent.id().equals(agentRegistry.getDefaultAgentId()) + )) + .toList(); + + model.addAttribute("agents", agents); + model.addAttribute("hasAgents", !agents.isEmpty()); + return "agents"; + } + + private String resolveWorkspace(ConfiguredAgent configuredAgent) { + Path workspacePath = agentWorkspaceResolver.resolveWorkspacePath(configuredAgent.workspacePath(), configuredAgent.id()); + return workspacePath.toString(); + } + + public record AgentView( + String id, + String provider, + String model, + String workspace, + boolean isDefault + ) { + } +} diff --git a/app/src/main/java/ai/javaclaw/chat/ChatChannel.java b/app/src/main/java/ai/javaclaw/chat/ChatChannel.java index 2050c31..2c2f539 100644 --- a/app/src/main/java/ai/javaclaw/chat/ChatChannel.java +++ b/app/src/main/java/ai/javaclaw/chat/ChatChannel.java @@ -1,6 +1,8 @@ package ai.javaclaw.chat; import ai.javaclaw.agent.Agent; +import ai.javaclaw.agents.AgentConversationId; +import ai.javaclaw.agents.AgentRegistry; import ai.javaclaw.channels.Channel; import ai.javaclaw.channels.ChannelMessageReceivedEvent; import ai.javaclaw.channels.ChannelRegistry; @@ -36,13 +38,15 @@ public class ChatChannel implements Channel { private static final Logger log = LoggerFactory.getLogger(ChatChannel.class); private final Agent agent; + private final AgentRegistry agentRegistry; private final ChannelRegistry channelRegistry; private final ChatMemoryRepository chatMemoryRepository; private final ConcurrentLinkedQueue pendingMessages = new ConcurrentLinkedQueue<>(); private final AtomicReference wsSession = new AtomicReference<>(); - public ChatChannel(Agent agent, ChannelRegistry channelRegistry, ChatMemoryRepository chatMemoryRepository) { + public ChatChannel(Agent agent, AgentRegistry agentRegistry, ChannelRegistry channelRegistry, ChatMemoryRepository chatMemoryRepository) { this.agent = agent; + this.agentRegistry = agentRegistry; this.channelRegistry = channelRegistry; this.chatMemoryRepository = chatMemoryRepository; channelRegistry.registerChannel(this); @@ -96,10 +100,24 @@ public void sendMessage(String message) { /** * Returns all known conversation IDs, always with "web" first. */ - public List conversationIds() { + public List agentIds() { + List ids = agentRegistry.getAgents().stream().map(ai.javaclaw.agents.ConfiguredAgent::id).toList(); + if (!ids.isEmpty()) { + return ids; + } + return List.of(agentRegistry.getDefaultAgentId()); + } + + public String defaultAgentId() { + return agentRegistry.getDefaultAgentId(); + } + + public List conversationIds(String agentId) { List result = new ArrayList<>(); result.add("web"); chatMemoryRepository.findConversationIds().stream() + .filter(id -> agentId.equals(AgentConversationId.agentId(id))) + .map(AgentConversationId::rawConversationId) .filter(id -> !id.equals("web")) .forEach(result::add); return result; @@ -109,8 +127,8 @@ public List conversationIds() { * Loads conversation history for the given conversationId as HTML bubbles. * Returns a single welcome bubble if no history exists yet. */ - public List loadHistoryAsHtml(String conversationId) { - List history = chatMemoryRepository.findByConversationId(conversationId); + public List loadHistoryAsHtml(String agentId, String conversationId) { + List history = chatMemoryRepository.findByConversationId(AgentConversationId.scoped(agentId, conversationId)); if (history.isEmpty()) { return List.of(ChatHtml.agentBubble("Hi! I'm your JavaClaw assistant. How can I help you today?")); } @@ -125,9 +143,9 @@ public List loadHistoryAsHtml(String conversationId) { /** * Handles a chat message from the web UI for the given conversationId. */ - public String chat(String conversationId, String message) { + public String chat(String agentId, String conversationId, String message) { channelRegistry.publishMessageReceivedEvent(new ChannelMessageReceivedEvent(getName(), message)); - return agent.respondTo(conversationId, message); + return agent.respondTo(agentId, conversationId, message); } private static String buildBackgroundMessageHtml(String text) { diff --git a/app/src/main/java/ai/javaclaw/chat/ChatHtml.java b/app/src/main/java/ai/javaclaw/chat/ChatHtml.java index c6416c8..b4a2121 100644 --- a/app/src/main/java/ai/javaclaw/chat/ChatHtml.java +++ b/app/src/main/java/ai/javaclaw/chat/ChatHtml.java @@ -34,11 +34,11 @@ public static String typingDots() { """; } - public static String chatInputArea(String conversationId) { + public static String chatInputArea(String agentId, String conversationId) { if ("web".equals(conversationId)) { return """
+ hx-vals='js:{"type": "userMessage", "agentId": document.getElementById("agent-select") ? document.getElementById("agent-select").value : "%s", "conversationId": document.getElementById("channel-select") ? document.getElementById("channel-select").value : "web"}'>