Files
kandev/apps/web/components/session-commands.tsx
T
2026-02-16 22:54:28 +00:00

192 lines
5.7 KiB
TypeScript

'use client';
import { useCallback, useMemo } from 'react';
import {
IconPlayerStop,
IconFileTextSpark,
IconGitCommit,
IconArrowUp,
IconArrowDown,
IconGitPullRequest,
IconGitBranch,
IconGitMerge,
IconBrowser,
IconTerminal2,
IconFileText,
IconFileDiff,
} from '@tabler/icons-react';
import { useRegisterCommands } from '@/hooks/use-register-commands';
import { useGitOperations } from '@/hooks/use-git-operations';
import { useGitWithFeedback } from '@/hooks/use-git-with-feedback';
import { usePanelActions } from '@/hooks/use-panel-actions';
import { useVcsDialogs } from '@/components/vcs/vcs-dialogs';
import { getWebSocketClient } from '@/lib/ws/connection';
import type { CommandItem } from '@/lib/commands/types';
type SessionCommandsProps = {
sessionId: string | null;
baseBranch?: string;
isAgentRunning?: boolean;
hasWorktree?: boolean;
};
export function SessionCommands({ sessionId, baseBranch, isAgentRunning, hasWorktree }: SessionCommandsProps) {
const git = useGitOperations(sessionId);
const panels = usePanelActions();
const { openCommitDialog, openPRDialog } = useVcsDialogs();
const cancelTurn = useCallback(async () => {
if (!sessionId) return;
const client = getWebSocketClient();
if (!client) return;
try {
await client.request('agent.cancel', { session_id: sessionId }, 15000);
} catch (error) {
console.error('Failed to cancel agent turn:', error);
}
}, [sessionId]);
const gitWithFeedback = useGitWithFeedback();
const runGitWithFeedback = useCallback(
async (
operation: () => Promise<{ success: boolean; output: string; error?: string }>,
operationName: string
) => {
panels.addChanges();
await gitWithFeedback(operation, operationName);
},
[panels, gitWithFeedback]
);
const commands = useMemo<CommandItem[]>(() => {
if (!sessionId) return [];
const PAGE_PRIORITY = 0;
const items: CommandItem[] = [];
// Session — conditional
if (isAgentRunning) {
items.push({
id: 'session-cancel',
label: 'Cancel Turn',
group: 'Session',
icon: <IconPlayerStop className="size-3.5" />,
keywords: ['cancel', 'stop', 'turn'],
action: cancelTurn,
});
}
items.push({
id: 'session-plan-mode',
label: 'Toggle Plan Mode',
group: 'Session',
icon: <IconFileTextSpark className="size-3.5" />,
keywords: ['plan', 'mode', 'toggle'],
action: () => panels.addPlan(),
});
// Git — only when worktree is set up
if (hasWorktree) {
items.push(
{
id: 'git-commit',
label: 'Commit Changes',
group: 'Git',
icon: <IconGitCommit className="size-3.5" />,
keywords: ['commit', 'git', 'save'],
action: openCommitDialog,
},
{
id: 'git-push',
label: 'Push',
group: 'Git',
icon: <IconArrowUp className="size-3.5" />,
keywords: ['push', 'git', 'upload'],
action: () => runGitWithFeedback(() => git.push(), 'Push'),
},
{
id: 'git-pull',
label: 'Pull',
group: 'Git',
icon: <IconArrowDown className="size-3.5" />,
keywords: ['pull', 'git', 'download', 'fetch'],
action: () => runGitWithFeedback(() => git.pull(), 'Pull'),
},
{
id: 'git-create-pr',
label: 'Create PR',
group: 'Git',
icon: <IconGitPullRequest className="size-3.5" />,
keywords: ['pull request', 'pr', 'git'],
action: openPRDialog,
},
{
id: 'git-rebase',
label: 'Rebase',
group: 'Git',
icon: <IconGitBranch className="size-3.5" />,
keywords: ['rebase', 'git', 'branch'],
action: () => {
const target = baseBranch?.replace(/^origin\//, '') || 'main';
return runGitWithFeedback(() => git.rebase(target), 'Rebase');
},
},
{
id: 'git-merge',
label: 'Merge',
group: 'Git',
icon: <IconGitMerge className="size-3.5" />,
keywords: ['merge', 'git', 'branch'],
action: () => {
const target = baseBranch?.replace(/^origin\//, '') || 'main';
return runGitWithFeedback(() => git.merge(target), 'Merge');
},
},
);
}
// Panels — always available on session page
items.push(
{
id: 'panel-browser',
label: 'Add Browser Panel',
group: 'Panels',
icon: <IconBrowser className="size-3.5" />,
keywords: ['browser', 'preview', 'web'],
action: () => panels.addBrowser(),
},
{
id: 'panel-terminal',
label: 'Add Terminal Panel',
group: 'Panels',
icon: <IconTerminal2 className="size-3.5" />,
keywords: ['terminal', 'shell', 'console'],
action: () => panels.addTerminal(),
},
{
id: 'panel-plan',
label: 'Add Plan Panel',
group: 'Panels',
icon: <IconFileText className="size-3.5" />,
keywords: ['plan', 'document'],
action: () => panels.addPlan(),
},
{
id: 'panel-changes',
label: 'Add Changes Panel',
group: 'Panels',
icon: <IconFileDiff className="size-3.5" />,
keywords: ['changes', 'diff', 'git'],
action: () => panels.addChanges(),
},
);
return items.map((cmd) => ({ ...cmd, priority: PAGE_PRIORITY }));
}, [sessionId, git, panels, cancelTurn, baseBranch, isAgentRunning, hasWorktree, openCommitDialog, openPRDialog, runGitWithFeedback]);
useRegisterCommands(commands);
return null;
}