feat: add terminal detachment functionality
implement terminal cleanup when closing tabs or unmounting component remove unused actionRunner prop across components delete unused file-watcher utility
This commit is contained in:
@@ -25,7 +25,6 @@ import ChatAlert from './ChatAlert';
|
||||
import type { ModelInfo } from '~/lib/modules/llm/types';
|
||||
import ProgressCompilation from './ProgressCompilation';
|
||||
import type { ProgressAnnotation } from '~/types/context';
|
||||
import type { ActionRunner } from '~/lib/runtime/action-runner';
|
||||
import { SupabaseChatAlert } from '~/components/chat/SupabaseAlert';
|
||||
import { expoUrlAtom } from '~/lib/stores/qrCodeStore';
|
||||
import { useStore } from '@nanostores/react';
|
||||
@@ -71,7 +70,6 @@ interface BaseChatProps {
|
||||
deployAlert?: DeployAlert;
|
||||
clearDeployAlert?: () => void;
|
||||
data?: JSONValue[] | undefined;
|
||||
actionRunner?: ActionRunner;
|
||||
chatMode?: 'discuss' | 'build';
|
||||
setChatMode?: (mode: 'discuss' | 'build') => void;
|
||||
append?: (message: Message) => void;
|
||||
@@ -116,7 +114,6 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
supabaseAlert,
|
||||
clearSupabaseAlert,
|
||||
data,
|
||||
actionRunner,
|
||||
chatMode,
|
||||
setChatMode,
|
||||
append,
|
||||
@@ -483,12 +480,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
</div>
|
||||
<ClientOnly>
|
||||
{() => (
|
||||
<Workbench
|
||||
actionRunner={actionRunner ?? ({} as ActionRunner)}
|
||||
chatStarted={chatStarted}
|
||||
isStreaming={isStreaming}
|
||||
setSelectedElement={setSelectedElement}
|
||||
/>
|
||||
<Workbench chatStarted={chatStarted} isStreaming={isStreaming} setSelectedElement={setSelectedElement} />
|
||||
)}
|
||||
</ClientOnly>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { diffLines, type Change } from 'diff';
|
||||
import { getHighlighter } from 'shiki';
|
||||
import '~/styles/diff-view.css';
|
||||
import { diffFiles, extractRelativePath } from '~/utils/diff';
|
||||
import { ActionRunner } from '~/lib/runtime/action-runner';
|
||||
import type { FileHistory } from '~/types/actions';
|
||||
import { getLanguageFromExtension } from '~/utils/getLanguageFromExtension';
|
||||
import { themeStore } from '~/lib/stores/theme';
|
||||
@@ -664,7 +663,6 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }
|
||||
interface DiffViewProps {
|
||||
fileHistory: Record<string, FileHistory>;
|
||||
setFileHistory: React.Dispatch<React.SetStateAction<Record<string, FileHistory>>>;
|
||||
actionRunner: ActionRunner;
|
||||
}
|
||||
|
||||
export const DiffView = memo(({ fileHistory, setFileHistory }: DiffViewProps) => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { memo, useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Popover, Transition } from '@headlessui/react';
|
||||
import { diffLines, type Change } from 'diff';
|
||||
import { ActionRunner } from '~/lib/runtime/action-runner';
|
||||
import { getLanguageFromExtension } from '~/utils/getLanguageFromExtension';
|
||||
import type { FileHistory } from '~/types/actions';
|
||||
import { DiffView } from './DiffView';
|
||||
@@ -32,7 +31,6 @@ import type { ElementInfo } from './Inspector';
|
||||
interface WorkspaceProps {
|
||||
chatStarted?: boolean;
|
||||
isStreaming?: boolean;
|
||||
actionRunner: ActionRunner;
|
||||
metadata?: {
|
||||
gitUrl?: string;
|
||||
};
|
||||
@@ -281,7 +279,7 @@ const FileModifiedDropdown = memo(
|
||||
);
|
||||
|
||||
export const Workbench = memo(
|
||||
({ chatStarted, isStreaming, actionRunner, metadata, updateChatMestaData, setSelectedElement }: WorkspaceProps) => {
|
||||
({ chatStarted, isStreaming, metadata, updateChatMestaData, setSelectedElement }: WorkspaceProps) => {
|
||||
renderLogger.trace('Workbench');
|
||||
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
@@ -486,7 +484,7 @@ export const Workbench = memo(
|
||||
initial={{ x: '100%' }}
|
||||
animate={{ x: selectedView === 'diff' ? '0%' : selectedView === 'code' ? '100%' : '-100%' }}
|
||||
>
|
||||
<DiffView fileHistory={fileHistory} setFileHistory={setFileHistory} actionRunner={actionRunner} />
|
||||
<DiffView fileHistory={fileHistory} setFileHistory={setFileHistory} />
|
||||
</View>
|
||||
<View initial={{ x: '100%' }} animate={{ x: selectedView === 'preview' ? '0%' : '100%' }}>
|
||||
<Preview setSelectedElement={setSelectedElement} />
|
||||
|
||||
@@ -10,6 +10,7 @@ const logger = createScopedLogger('Terminal');
|
||||
|
||||
export interface TerminalRef {
|
||||
reloadStyles: () => void;
|
||||
getTerminal: () => XTerm | undefined;
|
||||
}
|
||||
|
||||
export interface TerminalProps {
|
||||
@@ -80,6 +81,9 @@ export const Terminal = memo(
|
||||
const terminal = terminalRef.current!;
|
||||
terminal.options.theme = getTerminalTheme(readonly ? { cursor: '#00000000' } : {});
|
||||
},
|
||||
getTerminal: () => {
|
||||
return terminalRef.current;
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export const TerminalTabs = memo(() => {
|
||||
const terminalToggledByShortcut = useRef(false);
|
||||
|
||||
const [activeTerminal, setActiveTerminal] = useState(0);
|
||||
const [terminalCount, setTerminalCount] = useState(1);
|
||||
const [terminalCount, setTerminalCount] = useState(0);
|
||||
|
||||
const addTerminal = () => {
|
||||
if (terminalCount < MAX_TERMINALS) {
|
||||
@@ -32,6 +32,48 @@ export const TerminalTabs = memo(() => {
|
||||
}
|
||||
};
|
||||
|
||||
const closeTerminal = (index: number) => {
|
||||
if (index === 0) {
|
||||
return;
|
||||
} // Can't close bolt terminal
|
||||
|
||||
const terminalRef = terminalRefs.current[index];
|
||||
|
||||
if (terminalRef?.getTerminal) {
|
||||
const terminal = terminalRef.getTerminal();
|
||||
|
||||
if (terminal) {
|
||||
workbenchStore.detachTerminal(terminal);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the terminal from refs
|
||||
terminalRefs.current.splice(index, 1);
|
||||
|
||||
// Adjust terminal count and active terminal
|
||||
setTerminalCount(terminalCount - 1);
|
||||
|
||||
if (activeTerminal === index) {
|
||||
setActiveTerminal(Math.max(0, index - 1));
|
||||
} else if (activeTerminal > index) {
|
||||
setActiveTerminal(activeTerminal - 1);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
terminalRefs.current.forEach((ref, index) => {
|
||||
if (index > 0 && ref?.getTerminal) {
|
||||
const terminal = ref.getTerminal();
|
||||
|
||||
if (terminal) {
|
||||
workbenchStore.detachTerminal(terminal);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const { current: terminal } = terminalPanelRef;
|
||||
|
||||
@@ -125,6 +167,15 @@ export const TerminalTabs = memo(() => {
|
||||
>
|
||||
<div className="i-ph:terminal-window-duotone text-lg" />
|
||||
Terminal {terminalCount > 1 && index}
|
||||
<button
|
||||
className="bg-transparent text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary hover:bg-transparent rounded"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
closeTerminal(index);
|
||||
}}
|
||||
>
|
||||
<div className="i-ph:x text-xs" />
|
||||
</button>
|
||||
</button>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user