Files
Netcatty/application/state/useSessionState.ts
T
bincxz 885d837fda feat: Implement agents architecture and core functionality
- Added agents overview documentation outlining the project structure and roles.
- Implemented `useSessionState` for managing terminal sessions and workspaces.
- Developed `useSettingsState` for handling user settings and theme management.
- Created `useVaultState` for managing hosts, SSH keys, snippets, and custom groups.
- Introduced domain logic for host normalization and sanitization.
- Defined models for Host, SSHKey, Snippet, TerminalSession, and Workspace.
- Implemented workspace management functions including creation, insertion, and pruning.
- Established local storage adapter for persistent data management.
- Integrated Gemini AI service for terminal simulation and command generation.
- Developed sync service for backing up and restoring configuration to/from GitHub Gists.
2025-12-08 01:29:49 +08:00

206 lines
6.8 KiB
TypeScript

import { MouseEvent, useMemo, useState } from 'react';
import { Host, TerminalSession, Workspace } from '../../domain/models';
import {
createWorkspaceFromSessions as createWorkspaceEntity,
insertPaneIntoWorkspace,
pruneWorkspaceNode,
SplitHint,
updateWorkspaceSplitSizes,
} from '../../domain/workspace';
export const useSessionState = () => {
const [sessions, setSessions] = useState<TerminalSession[]>([]);
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
const [activeTabId, setActiveTabId] = useState<string>('vault');
const [draggingSessionId, setDraggingSessionId] = useState<string | null>(null);
const [workspaceRenameTarget, setWorkspaceRenameTarget] = useState<Workspace | null>(null);
const [workspaceRenameValue, setWorkspaceRenameValue] = useState('');
const createLocalTerminal = () => {
const sessionId = crypto.randomUUID();
const localHostId = `local-${sessionId}`;
const newSession: TerminalSession = {
id: sessionId,
hostId: localHostId,
hostLabel: 'Local Terminal',
hostname: 'localhost',
username: 'local',
status: 'connecting',
};
setSessions(prev => [...prev, newSession]);
setActiveTabId(sessionId);
};
const connectToHost = (host: Host) => {
const newSession: TerminalSession = {
id: crypto.randomUUID(),
hostId: host.id,
hostLabel: host.label,
hostname: host.hostname,
username: host.username,
status: 'connecting',
};
setSessions(prev => [...prev, newSession]);
setActiveTabId(newSession.id);
};
const updateSessionStatus = (sessionId: string, status: TerminalSession['status']) => {
setSessions(prev => prev.map(s => s.id === sessionId ? { ...s, status } : s));
};
const closeSession = (sessionId: string, e?: MouseEvent) => {
e?.stopPropagation();
const targetSession = sessions.find(s => s.id === sessionId);
const workspaceId = targetSession?.workspaceId;
let removedWorkspaceId: string | null = null;
let nextWorkspaces = workspaces;
if (workspaceId) {
nextWorkspaces = workspaces
.map(ws => {
if (ws.id !== workspaceId) return ws;
const pruned = pruneWorkspaceNode(ws.root, sessionId);
if (!pruned) {
removedWorkspaceId = ws.id;
return null;
}
return { ...ws, root: pruned };
})
.filter((ws): ws is Workspace => Boolean(ws));
}
const remainingSessions = sessions.filter(s => s.id !== sessionId);
setWorkspaces(nextWorkspaces);
setSessions(remainingSessions);
const fallbackWorkspace = nextWorkspaces[nextWorkspaces.length - 1];
const fallbackSolo = remainingSessions.filter(s => !s.workspaceId).slice(-1)[0];
const setFallback = () => {
if (fallbackWorkspace) setActiveTabId(fallbackWorkspace.id);
else if (fallbackSolo) setActiveTabId(fallbackSolo.id);
else setActiveTabId('vault');
};
if (activeTabId === sessionId) {
if (fallbackSolo) setActiveTabId(fallbackSolo.id);
else setFallback();
} else if (removedWorkspaceId && activeTabId === removedWorkspaceId) {
setFallback();
} else if (workspaceId && activeTabId === workspaceId && !nextWorkspaces.find(w => w.id === workspaceId)) {
setFallback();
}
};
const closeWorkspace = (workspaceId: string) => {
const remainingWorkspaces = workspaces.filter(w => w.id !== workspaceId);
const remainingSessions = sessions.filter(s => s.workspaceId !== workspaceId);
setWorkspaces(remainingWorkspaces);
setSessions(remainingSessions);
if (activeTabId === workspaceId) {
const remainingOrphans = remainingSessions.filter(s => !s.workspaceId);
if (remainingWorkspaces.length > 0) {
setActiveTabId(remainingWorkspaces[remainingWorkspaces.length - 1].id);
} else if (remainingOrphans.length > 0) {
setActiveTabId(remainingOrphans[remainingOrphans.length - 1].id);
} else {
setActiveTabId('vault');
}
}
};
const startWorkspaceRename = (workspaceId: string) => {
const target = workspaces.find(w => w.id === workspaceId);
if (!target) return;
setWorkspaceRenameTarget(target);
setWorkspaceRenameValue(target.title);
};
const submitWorkspaceRename = () => {
const name = workspaceRenameValue.trim();
if (!name || !workspaceRenameTarget) return;
setWorkspaces(prev => prev.map(w => w.id === workspaceRenameTarget.id ? { ...w, title: name } : w));
setWorkspaceRenameTarget(null);
setWorkspaceRenameValue('');
};
const resetWorkspaceRename = () => {
setWorkspaceRenameTarget(null);
setWorkspaceRenameValue('');
};
const createWorkspaceFromSessions = (
baseSessionId: string,
joiningSessionId: string,
hint: SplitHint
) => {
if (!hint || baseSessionId === joiningSessionId) return;
const base = sessions.find(s => s.id === baseSessionId);
const joining = sessions.find(s => s.id === joiningSessionId);
if (!base || !joining || base.workspaceId || joining.workspaceId) return;
const newWorkspace = createWorkspaceEntity(baseSessionId, joiningSessionId, hint);
setWorkspaces(prev => [...prev, newWorkspace]);
setSessions(prev => prev.map(s => {
if (s.id === baseSessionId || s.id === joiningSessionId) {
return { ...s, workspaceId: newWorkspace.id };
}
return s;
}));
setActiveTabId(newWorkspace.id);
};
const addSessionToWorkspace = (
workspaceId: string,
sessionId: string,
hint: SplitHint
) => {
const targetWorkspace = workspaces.find(w => w.id === workspaceId);
if (!targetWorkspace || !hint) return;
const session = sessions.find(s => s.id === sessionId);
if (!session || session.workspaceId) return;
setWorkspaces(prev => prev.map(ws => {
if (ws.id !== workspaceId) return ws;
return { ...ws, root: insertPaneIntoWorkspace(ws.root, sessionId, hint) };
}));
setSessions(prev => prev.map(s => s.id === sessionId ? { ...s, workspaceId } : s));
setActiveTabId(workspaceId);
};
const updateSplitSizes = (workspaceId: string, splitId: string, sizes: number[]) => {
setWorkspaces(prev => prev.map(ws => {
if (ws.id !== workspaceId) return ws;
return { ...ws, root: updateWorkspaceSplitSizes(ws.root, splitId, sizes) };
}));
};
const orphanSessions = useMemo(() => sessions.filter(s => !s.workspaceId), [sessions]);
return {
sessions,
workspaces,
activeTabId,
setActiveTabId,
draggingSessionId,
setDraggingSessionId,
workspaceRenameTarget,
workspaceRenameValue,
setWorkspaceRenameValue,
startWorkspaceRename,
submitWorkspaceRename,
resetWorkspaceRename,
createLocalTerminal,
connectToHost,
closeSession,
closeWorkspace,
updateSessionStatus,
createWorkspaceFromSessions,
addSessionToWorkspace,
updateSplitSizes,
orphanSessions,
};
};