mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat: 添加会话关闭功能及确认对话框,更新相关国际化文本
This commit is contained in:
@@ -22,10 +22,21 @@ import {
|
||||
Loader2,
|
||||
FileText,
|
||||
ArrowLeft,
|
||||
LogOut,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { TerminalInstance } from './TerminalInstance';
|
||||
import { FilePreview } from '@/components/shared/FilePreview';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
} from '@/components/ui/AlertDialog';
|
||||
import {
|
||||
useTerminalGridStore,
|
||||
selectTerminalGridPanes,
|
||||
@@ -33,7 +44,6 @@ import {
|
||||
} from '@/stores/terminalGridStore';
|
||||
import {
|
||||
useSessionManagerStore,
|
||||
selectGroups,
|
||||
selectTerminalMetas,
|
||||
} from '@/stores/sessionManagerStore';
|
||||
import {
|
||||
@@ -86,7 +96,6 @@ export function TerminalPane({ paneId }: TerminalPaneProps) {
|
||||
const isFileMode = displayMode === 'file' && filePath;
|
||||
|
||||
// Session data
|
||||
const groups = useSessionManagerStore(selectGroups);
|
||||
const terminalMetas = useSessionManagerStore(selectTerminalMetas);
|
||||
const sessions = useCliSessionStore((s) => s.sessions);
|
||||
|
||||
@@ -94,10 +103,13 @@ export function TerminalPane({ paneId }: TerminalPaneProps) {
|
||||
const pauseSession = useSessionManagerStore((s) => s.pauseSession);
|
||||
const resumeSession = useSessionManagerStore((s) => s.resumeSession);
|
||||
const restartSession = useSessionManagerStore((s) => s.restartSession);
|
||||
const closeSession = useSessionManagerStore((s) => s.closeSession);
|
||||
|
||||
// Action loading states
|
||||
const [isRestarting, setIsRestarting] = useState(false);
|
||||
const [isTogglingPause, setIsTogglingPause] = useState(false);
|
||||
const [isClosingSession, setIsClosingSession] = useState(false);
|
||||
const [isCloseConfirmOpen, setIsCloseConfirmOpen] = useState(false);
|
||||
|
||||
// File content for preview mode
|
||||
const { content: fileContent, isLoading: isFileLoading, error: fileError } = useFileContent(filePath, {
|
||||
@@ -118,14 +130,15 @@ export function TerminalPane({ paneId }: TerminalPaneProps) {
|
||||
const alertCount = meta?.alertCount ?? 0;
|
||||
|
||||
// Build session options for dropdown
|
||||
// Use sessions from cliSessionStore directly (all sessions, not just grouped ones)
|
||||
const sessionOptions = useMemo(() => {
|
||||
const allSessionIds = groups.flatMap((g) => g.sessionIds);
|
||||
const allSessionIds = Object.keys(sessions);
|
||||
return allSessionIds.map((sid) => {
|
||||
const s = sessions[sid];
|
||||
const name = s ? (s.tool ? `${s.tool} - ${s.shellKind}` : s.shellKind) : sid;
|
||||
return { id: sid, name };
|
||||
});
|
||||
}, [groups, sessions]);
|
||||
}, [sessions]);
|
||||
|
||||
// Handlers
|
||||
const handleFocus = useCallback(() => {
|
||||
@@ -141,7 +154,17 @@ export function TerminalPane({ paneId }: TerminalPaneProps) {
|
||||
}, [paneId, splitPane]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
// If pane has an active session, show confirmation dialog
|
||||
if (sessionId) {
|
||||
setIsCloseConfirmOpen(true);
|
||||
} else {
|
||||
closePane(paneId);
|
||||
}
|
||||
}, [paneId, sessionId, closePane]);
|
||||
|
||||
const handleCloseConfirm = useCallback(() => {
|
||||
closePane(paneId);
|
||||
setIsCloseConfirmOpen(false);
|
||||
}, [paneId, closePane]);
|
||||
|
||||
const handleSessionChange = useCallback(
|
||||
@@ -189,6 +212,20 @@ export function TerminalPane({ paneId }: TerminalPaneProps) {
|
||||
}
|
||||
}, [sessionId, isTogglingPause, status, pauseSession, resumeSession]);
|
||||
|
||||
const handleCloseSession = useCallback(async () => {
|
||||
if (!sessionId || isClosingSession) return;
|
||||
setIsClosingSession(true);
|
||||
try {
|
||||
await closeSession(sessionId);
|
||||
// Clear the pane's session after closing
|
||||
assignSession(paneId, null);
|
||||
} catch (error) {
|
||||
console.error('[TerminalPane] Close session failed:', error);
|
||||
} finally {
|
||||
setIsClosingSession(false);
|
||||
}
|
||||
}, [sessionId, isClosingSession, closeSession, paneId, assignSession]);
|
||||
|
||||
// Handle back to terminal from file preview
|
||||
const handleBackToTerminal = useCallback(() => {
|
||||
setPaneDisplayMode(paneId, 'terminal');
|
||||
@@ -329,6 +366,24 @@ export function TerminalPane({ paneId }: TerminalPaneProps) {
|
||||
>
|
||||
<Eraser className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
{/* Close session button */}
|
||||
<button
|
||||
onClick={handleCloseSession}
|
||||
disabled={isClosingSession}
|
||||
className={cn(
|
||||
'p-1 rounded hover:bg-muted transition-colors',
|
||||
isClosingSession
|
||||
? 'text-muted-foreground/50'
|
||||
: 'text-muted-foreground hover:text-destructive'
|
||||
)}
|
||||
title={formatMessage({ id: 'terminalDashboard.pane.closeSession' })}
|
||||
>
|
||||
{isClosingSession ? (
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||
) : (
|
||||
<LogOut className="w-3.5 h-3.5" />
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{alertCount > 0 && !isFileMode && (
|
||||
@@ -381,6 +436,28 @@ export function TerminalPane({ paneId }: TerminalPaneProps) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Close Pane Confirmation Dialog */}
|
||||
<AlertDialog open={isCloseConfirmOpen} onOpenChange={setIsCloseConfirmOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{formatMessage({ id: 'terminalDashboard.pane.closeConfirmTitle' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{formatMessage({ id: 'terminalDashboard.pane.closeConfirmMessage' })}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
{formatMessage({ id: 'common.actions.cancel' })}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleCloseConfirm}>
|
||||
{formatMessage({ id: 'terminalDashboard.pane.closeConfirmAction' })}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -227,6 +227,7 @@
|
||||
"turns": "turns",
|
||||
"perTurnView": "Per-Turn View",
|
||||
"concatenatedView": "Concatenated View",
|
||||
"nativeView": "Native View",
|
||||
"userPrompt": "User Prompt",
|
||||
"assistantResponse": "Assistant Response",
|
||||
"errors": "Errors",
|
||||
|
||||
@@ -147,11 +147,15 @@
|
||||
"splitVertical": "Split Down",
|
||||
"clearTerminal": "Clear Terminal",
|
||||
"closePane": "Close Pane",
|
||||
"closeSession": "Close Session",
|
||||
"linkedIssue": "Linked Issue",
|
||||
"restart": "Restart Session",
|
||||
"pause": "Pause Session",
|
||||
"resume": "Resume Session",
|
||||
"backToTerminal": "Back to terminal"
|
||||
"backToTerminal": "Back to terminal",
|
||||
"closeConfirmTitle": "Close Pane?",
|
||||
"closeConfirmMessage": "The session will still exist and can be restored from the session tree.",
|
||||
"closeConfirmAction": "Close Pane"
|
||||
},
|
||||
"tabBar": {
|
||||
"noTabs": "No terminal sessions"
|
||||
|
||||
@@ -227,6 +227,7 @@
|
||||
"turns": "回合",
|
||||
"perTurnView": "分回合视图",
|
||||
"concatenatedView": "连接视图",
|
||||
"nativeView": "原生视图",
|
||||
"userPrompt": "用户提示词",
|
||||
"assistantResponse": "助手响应",
|
||||
"errors": "错误",
|
||||
|
||||
@@ -147,11 +147,15 @@
|
||||
"splitVertical": "向下分割",
|
||||
"clearTerminal": "清屏",
|
||||
"closePane": "关闭窗格",
|
||||
"closeSession": "关闭会话",
|
||||
"linkedIssue": "关联问题",
|
||||
"restart": "重启会话",
|
||||
"pause": "暂停会话",
|
||||
"resume": "恢复会话",
|
||||
"backToTerminal": "返回终端"
|
||||
"backToTerminal": "返回终端",
|
||||
"closeConfirmTitle": "关闭窗格?",
|
||||
"closeConfirmMessage": "会话仍将保留,可从会话树中恢复。",
|
||||
"closeConfirmAction": "关闭窗格"
|
||||
},
|
||||
"tabBar": {
|
||||
"noTabs": "暂无终端会话"
|
||||
|
||||
@@ -285,6 +285,13 @@ export const useSessionManagerStore = create<SessionManagerStore>()(
|
||||
shellKind: session.shellKind,
|
||||
};
|
||||
|
||||
// Map shellKind to preferredShell for API
|
||||
const mapShellKind = (kind: string): 'bash' | 'pwsh' | 'cmd' => {
|
||||
if (kind === 'pwsh' || kind === 'powershell') return 'pwsh';
|
||||
if (kind === 'cmd') return 'cmd';
|
||||
return 'bash'; // 'git-bash', 'wsl-bash', or fallback
|
||||
};
|
||||
|
||||
try {
|
||||
// Close existing session
|
||||
await closeCliSession(terminalId, projectPath ?? undefined);
|
||||
@@ -293,7 +300,7 @@ export const useSessionManagerStore = create<SessionManagerStore>()(
|
||||
const result = await createCliSession(
|
||||
{
|
||||
workingDir: sessionConfig.workingDir,
|
||||
preferredShell: sessionConfig.shellKind === 'powershell' ? 'pwsh' : 'bash',
|
||||
preferredShell: mapShellKind(sessionConfig.shellKind),
|
||||
tool: sessionConfig.tool,
|
||||
model: sessionConfig.model,
|
||||
resumeKey: sessionConfig.resumeKey,
|
||||
@@ -325,6 +332,35 @@ export const useSessionManagerStore = create<SessionManagerStore>()(
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
closeSession: async (terminalId: string) => {
|
||||
const projectPath = selectProjectPath(useWorkflowStore.getState());
|
||||
const cliStore = useCliSessionStore.getState();
|
||||
|
||||
try {
|
||||
// Call backend API to terminate PTY session
|
||||
await closeCliSession(terminalId, projectPath ?? undefined);
|
||||
|
||||
// Remove session from cliSessionStore
|
||||
cliStore.removeSession(terminalId);
|
||||
|
||||
// Remove terminal meta
|
||||
set(
|
||||
(state) => {
|
||||
const nextMetas = { ...state.terminalMetas };
|
||||
delete nextMetas[terminalId];
|
||||
return { terminalMetas: nextMetas };
|
||||
},
|
||||
false,
|
||||
'closeSession'
|
||||
);
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) {
|
||||
console.error('[SessionManager] closeSession error:', error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
}),
|
||||
{ name: 'SessionManagerStore' }
|
||||
)
|
||||
|
||||
@@ -89,6 +89,8 @@ export interface SessionManagerActions {
|
||||
resumeSession: (terminalId: string) => Promise<void>;
|
||||
/** Restart a terminal session (close and recreate with same config) */
|
||||
restartSession: (terminalId: string) => Promise<void>;
|
||||
/** Close and terminate a terminal session permanently */
|
||||
closeSession: (terminalId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export type SessionManagerStore = SessionManagerState & SessionManagerActions;
|
||||
|
||||
@@ -280,8 +280,10 @@ export class CliSessionManager {
|
||||
|
||||
} else {
|
||||
// Legacy shell session: spawn bash/pwsh
|
||||
const preferredShell = options.preferredShell ?? 'bash';
|
||||
const picked = pickShell(preferredShell);
|
||||
// Note: 'cmd' is for CLI tools only, for legacy shells we default to bash
|
||||
const shellPreference = options.preferredShell ?? 'bash';
|
||||
const preferredShell = shellPreference === 'cmd' ? 'bash' : shellPreference;
|
||||
const picked = pickShell(preferredShell as 'bash' | 'pwsh');
|
||||
shellKind = picked.shellKind;
|
||||
file = picked.file;
|
||||
args = picked.args;
|
||||
|
||||
Reference in New Issue
Block a user