feat: Add multi-host config and dynamic project scanning

Backend:
- Load hosts from config/hosts.json
- New /api/hosts endpoint listing available hosts
- Dynamic project scanning with configurable depth
- Support for local and SSH hosts (SSH execution coming next)

Frontend (by Web-UI Claude):
- Slash commands: /clear, /help, /export, /scroll, /new, /info
- Chat export as Markdown

Config:
- hosts.json defines hosts with connection info and base paths
- hosts.example.json as template (real config is gitignored)
- Each host has name, description, color, icon, basePaths

Next steps:
- SSH command execution for remote hosts
- Frontend host selector UI
- Multi-agent collaboration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-15 22:11:22 +01:00
parent 52792268fa
commit 38ab89932a
7 changed files with 354 additions and 21 deletions

View File

@@ -1,10 +1,89 @@
import { useState, useRef, useEffect } from 'react';
import { useState, useRef, useEffect, useCallback } from 'react';
import { useClaudeSession } from './hooks/useClaudeSession';
import { MessageList } from './components/MessageList';
import { ChatInput } from './components/ChatInput';
import { Sidebar } from './components/Sidebar';
import { Header } from './components/Header';
// Slash command definitions
const SLASH_COMMANDS = {
clear: {
description: 'Clear chat history (UI only)',
execute: ({ clearMessages, addSystemMessage }) => {
clearMessages();
addSystemMessage('Chat cleared');
}
},
help: {
description: 'Show available commands',
execute: ({ addSystemMessage }) => {
const helpText = Object.entries(SLASH_COMMANDS)
.map(([cmd, { description }]) => `/${cmd} - ${description}`)
.join('\n');
addSystemMessage(`Available commands:\n${helpText}`);
}
},
export: {
description: 'Export chat as Markdown',
execute: ({ messages, addSystemMessage }) => {
const markdown = messages.map(m => {
if (m.type === 'user') return `**You:** ${m.content}`;
if (m.type === 'assistant') return `**Claude:** ${m.content}`;
if (m.type === 'tool_use') return `> Tool: ${m.tool}`;
if (m.type === 'system') return `_${m.content}_`;
return '';
}).filter(Boolean).join('\n\n');
const blob = new Blob([markdown], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `claude-chat-${new Date().toISOString().slice(0,10)}.md`;
a.click();
URL.revokeObjectURL(url);
addSystemMessage('Chat exported as Markdown');
}
},
scroll: {
description: 'Scroll to top or bottom (/scroll top|bottom)',
execute: ({ args, addSystemMessage }) => {
const direction = args[0] || 'bottom';
const container = document.querySelector('.overflow-y-auto');
if (container) {
if (direction === 'top') {
container.scrollTo({ top: 0, behavior: 'smooth' });
} else {
container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
}
}
addSystemMessage(`Scrolled to ${direction}`);
}
},
new: {
description: 'Start a new session (clears history)',
execute: ({ clearMessages, stopSession, startSession, selectedProject, addSystemMessage }) => {
stopSession();
clearMessages();
setTimeout(() => {
startSession(selectedProject, false); // false = don't resume
}, 500);
addSystemMessage('Starting new session...');
}
},
info: {
description: 'Show session info',
execute: ({ connected, sessionActive, currentProject, messages, addSystemMessage }) => {
const info = [
`Connected: ${connected ? 'Yes' : 'No'}`,
`Session: ${sessionActive ? 'Active' : 'Inactive'}`,
`Project: ${currentProject || 'None'}`,
`Messages: ${messages.length}`
].join('\n');
addSystemMessage(info);
}
}
};
function App() {
const {
connected,
@@ -17,21 +96,67 @@ function App() {
sendMessage,
stopSession,
clearMessages,
setError
setError,
setMessages
} = useClaudeSession();
const [selectedProject, setSelectedProject] = useState('/projects/claude-web-ui');
const [sidebarOpen, setSidebarOpen] = useState(true);
const [resumeSession, setResumeSession] = useState(true);
// Add system message helper
const addSystemMessage = useCallback((content) => {
setMessages(prev => [...prev, {
type: 'system',
content,
timestamp: Date.now()
}]);
}, [setMessages]);
const handleStartSession = () => {
startSession(selectedProject, resumeSession);
};
const handleSendMessage = (message) => {
if (message.trim()) {
sendMessage(message);
// Handle slash commands
const handleCommand = useCallback((command, args) => {
const cmd = SLASH_COMMANDS[command.toLowerCase()];
if (cmd) {
cmd.execute({
clearMessages,
addSystemMessage,
messages,
stopSession,
startSession,
selectedProject,
connected,
sessionActive,
currentProject,
args
});
return true;
}
return false;
}, [clearMessages, addSystemMessage, messages, stopSession, startSession, selectedProject, connected, sessionActive, currentProject]);
const handleSendMessage = (message) => {
if (!message.trim()) return;
// Check for slash command
if (message.startsWith('/')) {
const parts = message.slice(1).split(' ');
const command = parts[0];
const args = parts.slice(1);
if (handleCommand(command, args)) {
return; // Command handled
} else {
addSystemMessage(`Unknown command: /${command}. Type /help for available commands.`);
return;
}
}
// Regular message
sendMessage(message);
};
return (