feat: Add auto-connect and auto-start toggles for sessions
- Add global settings system (autoConnect, autoStart) with localStorage persistence - Auto-connect: Automatically establish WebSocket connections on page load - Auto-start: Automatically start Claude sessions after connecting (requires auto-connect) - Add two new toggles in Sidebar under Working Directory section - Auto-start toggle is disabled/grayed out when auto-connect is off - Disabling auto-connect also disables auto-start automatically 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,8 @@ export function Sidebar({ open, onToggle }) {
|
||||
stopClaudeSession,
|
||||
clearMessages,
|
||||
updateSessionConfig,
|
||||
settings,
|
||||
updateSettings,
|
||||
} = useSessionManager();
|
||||
|
||||
const { user, authEnabled, logout, isAdmin } = useAuth();
|
||||
@@ -102,6 +104,22 @@ export function Sidebar({ open, onToggle }) {
|
||||
updateSessionConfig(focusedSessionId, { resumeOnStart: !resumeSession });
|
||||
}, [focusedSessionId, resumeSession, updateSessionConfig]);
|
||||
|
||||
// Handle autoConnect toggle
|
||||
const handleToggleAutoConnect = useCallback(() => {
|
||||
const newAutoConnect = !settings?.autoConnect;
|
||||
// If disabling autoConnect, also disable autoStart
|
||||
if (!newAutoConnect) {
|
||||
updateSettings({ autoConnect: false, autoStart: false });
|
||||
} else {
|
||||
updateSettings({ autoConnect: true });
|
||||
}
|
||||
}, [settings?.autoConnect, updateSettings]);
|
||||
|
||||
// Handle autoStart toggle
|
||||
const handleToggleAutoStart = useCallback(() => {
|
||||
updateSettings({ autoStart: !settings?.autoStart });
|
||||
}, [settings?.autoStart, updateSettings]);
|
||||
|
||||
// Browse directories on host
|
||||
const browsePath = useCallback(async (path) => {
|
||||
if (!currentHost) return;
|
||||
@@ -320,7 +338,7 @@ export function Sidebar({ open, onToggle }) {
|
||||
)}
|
||||
|
||||
{/* Resume toggle */}
|
||||
<div className="pt-2">
|
||||
<div className="pt-2 space-y-3">
|
||||
<label className="flex items-center gap-3 cursor-pointer group">
|
||||
<div
|
||||
onClick={handleToggleResume}
|
||||
@@ -340,6 +358,49 @@ export function Sidebar({ open, onToggle }) {
|
||||
Resume previous session
|
||||
</span>
|
||||
</label>
|
||||
|
||||
{/* Auto-connect toggle */}
|
||||
<label className="flex items-center gap-3 cursor-pointer group">
|
||||
<div
|
||||
onClick={handleToggleAutoConnect}
|
||||
className={`
|
||||
relative w-10 h-5 rounded-full transition-colors
|
||||
${settings?.autoConnect ? 'bg-orange-600' : 'bg-dark-700'}
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
absolute top-0.5 w-4 h-4 rounded-full bg-white shadow transition-transform
|
||||
${settings?.autoConnect ? 'translate-x-5' : 'translate-x-0.5'}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-dark-300 group-hover:text-dark-200">
|
||||
Auto-connect on load
|
||||
</span>
|
||||
</label>
|
||||
|
||||
{/* Auto-start toggle (only works if auto-connect is enabled) */}
|
||||
<label className={`flex items-center gap-3 cursor-pointer group ${!settings?.autoConnect ? 'opacity-50' : ''}`}>
|
||||
<div
|
||||
onClick={() => settings?.autoConnect && handleToggleAutoStart()}
|
||||
className={`
|
||||
relative w-10 h-5 rounded-full transition-colors
|
||||
${settings?.autoStart && settings?.autoConnect ? 'bg-orange-600' : 'bg-dark-700'}
|
||||
${!settings?.autoConnect ? 'cursor-not-allowed' : ''}
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
absolute top-0.5 w-4 h-4 rounded-full bg-white shadow transition-transform
|
||||
${settings?.autoStart && settings?.autoConnect ? 'translate-x-5' : 'translate-x-0.5'}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-dark-300 group-hover:text-dark-200">
|
||||
Auto-start sessions
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,8 +8,28 @@ function getWsUrl() {
|
||||
}
|
||||
|
||||
const SESSIONS_STORAGE_KEY = 'claude-webui-sessions';
|
||||
const SETTINGS_STORAGE_KEY = 'claude-webui-settings';
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
// Load global settings from localStorage
|
||||
function loadSettings() {
|
||||
try {
|
||||
const stored = localStorage.getItem(SETTINGS_STORAGE_KEY);
|
||||
return stored ? JSON.parse(stored) : { autoConnect: true, autoStart: false };
|
||||
} catch {
|
||||
return { autoConnect: true, autoStart: false };
|
||||
}
|
||||
}
|
||||
|
||||
// Save global settings to localStorage
|
||||
function saveSettings(settings) {
|
||||
try {
|
||||
localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(settings));
|
||||
} catch (e) {
|
||||
console.error('Failed to save settings:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate unique session ID
|
||||
function generateSessionId() {
|
||||
return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
@@ -64,6 +84,9 @@ export function SessionProvider({ children }) {
|
||||
// Tab order (array of session IDs)
|
||||
const [tabOrder, setTabOrder] = useState([]);
|
||||
|
||||
// Global settings (autoConnect, etc.)
|
||||
const [settings, setSettings] = useState(() => loadSettings());
|
||||
|
||||
// WebSocket refs keyed by session ID
|
||||
const wsRefs = useRef({});
|
||||
|
||||
@@ -74,9 +97,14 @@ export function SessionProvider({ children }) {
|
||||
const sessionsRef = useRef(sessions);
|
||||
sessionsRef.current = sessions;
|
||||
|
||||
// Ref to current settings (for stable callbacks)
|
||||
const settingsRef = useRef(settings);
|
||||
settingsRef.current = settings;
|
||||
|
||||
// Track if initial load is done (for auto-connecting restored sessions)
|
||||
const initialLoadDone = useRef(false);
|
||||
const sessionsToConnect = useRef([]);
|
||||
const sessionsToAutoStart = useRef(new Set()); // Sessions that should auto-start after connecting
|
||||
|
||||
// Load sessions from localStorage on mount
|
||||
useEffect(() => {
|
||||
@@ -690,16 +718,73 @@ export function SessionProvider({ children }) {
|
||||
};
|
||||
}, [updateSession, handleWsMessage]);
|
||||
|
||||
// Auto-connect restored sessions
|
||||
// Auto-connect restored sessions (only if autoConnect is enabled)
|
||||
useEffect(() => {
|
||||
if (sessionsToConnect.current.length > 0) {
|
||||
if (sessionsToConnect.current.length > 0 && settings.autoConnect) {
|
||||
const toConnect = [...sessionsToConnect.current];
|
||||
sessionsToConnect.current = [];
|
||||
|
||||
// Mark sessions for auto-start if autoStart is also enabled
|
||||
if (settings.autoStart) {
|
||||
toConnect.forEach(sessionId => {
|
||||
sessionsToAutoStart.current.add(sessionId);
|
||||
});
|
||||
}
|
||||
|
||||
toConnect.forEach(sessionId => {
|
||||
connectSession(sessionId);
|
||||
});
|
||||
}
|
||||
}, [connectSession]);
|
||||
}, [connectSession, settings.autoConnect, settings.autoStart]);
|
||||
|
||||
// Auto-start sessions after they connect (if marked for auto-start)
|
||||
useEffect(() => {
|
||||
if (sessionsToAutoStart.current.size === 0) return;
|
||||
|
||||
// Check each session marked for auto-start
|
||||
sessionsToAutoStart.current.forEach(sessionId => {
|
||||
const session = sessions[sessionId];
|
||||
// If session is connected but not yet active, start it
|
||||
if (session?.connected && !session?.active) {
|
||||
console.log(`[${sessionId}] Auto-starting session`);
|
||||
sessionsToAutoStart.current.delete(sessionId);
|
||||
|
||||
// Use the websocket directly to start the session
|
||||
const ws = wsRefs.current[sessionId];
|
||||
if (ws?.readyState === WebSocket.OPEN) {
|
||||
// Load history first if resumeOnStart is enabled
|
||||
const startSession = async () => {
|
||||
if (session.resumeOnStart) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/history/${encodeURIComponent(session.project)}?host=${session.host}`,
|
||||
{ credentials: 'include' }
|
||||
);
|
||||
const data = await res.json();
|
||||
if (data.messages && Array.isArray(data.messages)) {
|
||||
setSessionMessages(prev => ({
|
||||
...prev,
|
||||
[sessionId]: data.messages,
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load history:', e);
|
||||
}
|
||||
}
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
type: 'start_session',
|
||||
project: session.project,
|
||||
resume: session.resumeOnStart,
|
||||
host: session.host,
|
||||
}));
|
||||
};
|
||||
|
||||
startSession();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [sessions]);
|
||||
|
||||
// Disconnect WebSocket for a session
|
||||
const disconnectSession = useCallback((sessionId) => {
|
||||
@@ -955,6 +1040,15 @@ export function SessionProvider({ children }) {
|
||||
updateSession(sessionId, config);
|
||||
}, [updateSession]);
|
||||
|
||||
// Update global settings (autoConnect, etc.)
|
||||
const updateSettings = useCallback((newSettings) => {
|
||||
setSettings(prev => {
|
||||
const updated = { ...prev, ...newSettings };
|
||||
saveSettings(updated);
|
||||
return updated;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Memoize focused session to prevent unnecessary re-renders
|
||||
const focusedSession = useMemo(() => {
|
||||
return focusedSessionId ? sessions[focusedSessionId] : null;
|
||||
@@ -969,6 +1063,7 @@ export function SessionProvider({ children }) {
|
||||
splitSessions,
|
||||
focusedSessionId,
|
||||
focusedSession,
|
||||
settings,
|
||||
|
||||
// Session management
|
||||
createSession,
|
||||
@@ -976,6 +1071,7 @@ export function SessionProvider({ children }) {
|
||||
removeSession,
|
||||
renameSession,
|
||||
updateSessionConfig,
|
||||
updateSettings,
|
||||
|
||||
// Focus & view
|
||||
setFocusedSessionId,
|
||||
@@ -1001,8 +1097,8 @@ export function SessionProvider({ children }) {
|
||||
changePermissionMode,
|
||||
respondToPermission,
|
||||
}), [
|
||||
sessions, sessionMessages, tabOrder, splitSessions, focusedSessionId, focusedSession,
|
||||
createSession, closeSession, removeSession, renameSession, updateSessionConfig,
|
||||
sessions, sessionMessages, tabOrder, splitSessions, focusedSessionId, focusedSession, settings,
|
||||
createSession, closeSession, removeSession, renameSession, updateSessionConfig, updateSettings,
|
||||
setFocusedSessionId, markAsRead, reorderTabs, addToSplit, removeFromSplit, clearSplit,
|
||||
connectSession, disconnectSession, startClaudeSession, stopClaudeSession,
|
||||
sendMessage, stopGeneration, clearMessages, setCompacting, changePermissionMode, respondToPermission,
|
||||
|
||||
Reference in New Issue
Block a user