refactor: Restructure files and folders to better mimic feature-based architecture

This commit is contained in:
Haileyesus
2026-02-11 18:11:06 +03:00
parent fadbcc8259
commit 7f45540dbe
34 changed files with 124 additions and 233 deletions

View File

@@ -0,0 +1,372 @@
import React, { useCallback, useEffect, useRef } from 'react';
import QuickSettingsPanel from '../../QuickSettingsPanel';
import { useTasksSettings } from '../../../contexts/TasksSettingsContext';
import { useTranslation } from 'react-i18next';
import ChatMessagesPane from './ChatMessagesPane';
import ChatComposer from './ChatComposer';
import type { ChatInterfaceProps } from '../types';
import { useChatProviderState } from '../hooks/useChatProviderState';
import { useChatSessionState } from '../hooks/useChatSessionState';
import { useChatRealtimeHandlers } from '../hooks/useChatRealtimeHandlers';
import { useChatComposerState } from '../hooks/useChatComposerState';
import type { Provider } from '../types';
type PendingViewSession = {
sessionId: string | null;
startedAt: number;
};
function ChatInterface({
selectedProject,
selectedSession,
ws,
sendMessage,
latestMessage,
onFileOpen,
onInputFocusChange,
onSessionActive,
onSessionInactive,
onSessionProcessing,
onSessionNotProcessing,
processingSessions,
onReplaceTemporarySession,
onNavigateToSession,
onShowSettings,
autoExpandTools,
showRawParameters,
showThinking,
autoScrollToBottom,
sendByCtrlEnter,
externalMessageUpdate,
onShowAllTasks,
}: ChatInterfaceProps) {
const { tasksEnabled, isTaskMasterInstalled } = useTasksSettings();
const { t } = useTranslation('chat');
const streamBufferRef = useRef('');
const streamTimerRef = useRef<number | null>(null);
const pendingViewSessionRef = useRef<PendingViewSession | null>(null);
const resetStreamingState = useCallback(() => {
if (streamTimerRef.current) {
clearTimeout(streamTimerRef.current);
streamTimerRef.current = null;
}
streamBufferRef.current = '';
}, []);
const {
provider,
setProvider,
cursorModel,
setCursorModel,
claudeModel,
setClaudeModel,
codexModel,
setCodexModel,
permissionMode,
pendingPermissionRequests,
setPendingPermissionRequests,
cyclePermissionMode,
} = useChatProviderState({
selectedSession,
});
const {
chatMessages,
setChatMessages,
isLoading,
setIsLoading,
currentSessionId,
setCurrentSessionId,
sessionMessages,
setSessionMessages,
isLoadingSessionMessages,
isLoadingMoreMessages,
hasMoreMessages,
totalMessages,
isSystemSessionChange,
setIsSystemSessionChange,
canAbortSession,
setCanAbortSession,
isUserScrolledUp,
setIsUserScrolledUp,
tokenBudget,
setTokenBudget,
visibleMessageCount,
visibleMessages,
loadEarlierMessages,
claudeStatus,
setClaudeStatus,
createDiff,
scrollContainerRef,
scrollToBottom,
handleScroll,
} = useChatSessionState({
selectedProject,
selectedSession,
ws,
sendMessage,
autoScrollToBottom,
externalMessageUpdate,
processingSessions,
resetStreamingState,
pendingViewSessionRef,
});
const {
input,
setInput,
textareaRef,
inputHighlightRef,
isTextareaExpanded,
thinkingMode,
setThinkingMode,
slashCommandsCount,
filteredCommands,
frequentCommands,
commandQuery,
showCommandMenu,
selectedCommandIndex,
resetCommandMenuState,
handleCommandSelect,
handleToggleCommandMenu,
showFileDropdown,
filteredFiles,
selectedFileIndex,
renderInputWithMentions,
selectFile,
attachedImages,
setAttachedImages,
uploadingImages,
imageErrors,
getRootProps,
getInputProps,
isDragActive,
openImagePicker,
handleSubmit,
handleInputChange,
handleKeyDown,
handlePaste,
handleTextareaClick,
handleTextareaInput,
syncInputOverlayScroll,
handleClearInput,
handleAbortSession,
handleTranscript,
handlePermissionDecision,
handleGrantToolPermission,
handleInputFocusChange,
} = useChatComposerState({
selectedProject,
selectedSession,
currentSessionId,
provider,
permissionMode,
cyclePermissionMode,
cursorModel,
claudeModel,
codexModel,
isLoading,
canAbortSession,
tokenBudget,
sendMessage,
sendByCtrlEnter,
onSessionActive,
onInputFocusChange,
onFileOpen,
onShowSettings,
pendingViewSessionRef,
scrollToBottom,
setChatMessages,
setSessionMessages,
setIsLoading,
setCanAbortSession,
setClaudeStatus,
setIsUserScrolledUp,
setPendingPermissionRequests,
});
useChatRealtimeHandlers({
latestMessage,
provider,
selectedProject,
selectedSession,
currentSessionId,
setCurrentSessionId,
setChatMessages,
setIsLoading,
setCanAbortSession,
setClaudeStatus,
setTokenBudget,
setIsSystemSessionChange,
setPendingPermissionRequests,
pendingViewSessionRef,
streamBufferRef,
streamTimerRef,
onSessionInactive,
onSessionProcessing,
onSessionNotProcessing,
onReplaceTemporarySession,
onNavigateToSession,
});
useEffect(() => {
if (!isLoading || !canAbortSession) {
return;
}
const handleGlobalEscape = (event: KeyboardEvent) => {
if (event.key !== 'Escape' || event.repeat || event.defaultPrevented) {
return;
}
event.preventDefault();
handleAbortSession();
};
document.addEventListener('keydown', handleGlobalEscape, { capture: true });
return () => {
document.removeEventListener('keydown', handleGlobalEscape, { capture: true });
};
}, [canAbortSession, handleAbortSession, isLoading]);
useEffect(() => {
const processingSessionId = selectedSession?.id || currentSessionId;
if (processingSessionId && isLoading && onSessionProcessing) {
onSessionProcessing(processingSessionId);
}
}, [currentSessionId, isLoading, onSessionProcessing, selectedSession?.id]);
useEffect(() => {
return () => {
resetStreamingState();
};
}, [resetStreamingState]);
if (!selectedProject) {
return (
<div className="flex items-center justify-center h-full">
<div className="text-center text-gray-500 dark:text-gray-400">
<p>Select a project to start chatting with Claude</p>
</div>
</div>
);
}
return (
<>
<div className="h-full flex flex-col">
<ChatMessagesPane
scrollContainerRef={scrollContainerRef}
onWheel={handleScroll}
onTouchMove={handleScroll}
isLoadingSessionMessages={isLoadingSessionMessages}
chatMessages={chatMessages}
selectedSession={selectedSession}
currentSessionId={currentSessionId}
provider={provider}
setProvider={(nextProvider) => setProvider(nextProvider as Provider)}
textareaRef={textareaRef}
claudeModel={claudeModel}
setClaudeModel={setClaudeModel}
cursorModel={cursorModel}
setCursorModel={setCursorModel}
codexModel={codexModel}
setCodexModel={setCodexModel}
tasksEnabled={tasksEnabled}
isTaskMasterInstalled={isTaskMasterInstalled}
onShowAllTasks={onShowAllTasks}
setInput={setInput}
isLoadingMoreMessages={isLoadingMoreMessages}
hasMoreMessages={hasMoreMessages}
totalMessages={totalMessages}
sessionMessagesCount={sessionMessages.length}
visibleMessageCount={visibleMessageCount}
visibleMessages={visibleMessages}
loadEarlierMessages={loadEarlierMessages}
createDiff={createDiff}
onFileOpen={onFileOpen}
onShowSettings={onShowSettings}
onGrantToolPermission={handleGrantToolPermission}
autoExpandTools={autoExpandTools}
showRawParameters={showRawParameters}
showThinking={showThinking}
selectedProject={selectedProject}
isLoading={isLoading}
/>
<ChatComposer
pendingPermissionRequests={pendingPermissionRequests}
handlePermissionDecision={handlePermissionDecision}
handleGrantToolPermission={handleGrantToolPermission}
claudeStatus={claudeStatus}
isLoading={isLoading}
onAbortSession={handleAbortSession}
provider={provider}
permissionMode={permissionMode}
onModeSwitch={cyclePermissionMode}
thinkingMode={thinkingMode}
setThinkingMode={setThinkingMode}
tokenBudget={tokenBudget}
slashCommandsCount={slashCommandsCount}
onToggleCommandMenu={handleToggleCommandMenu}
hasInput={Boolean(input.trim())}
onClearInput={handleClearInput}
isUserScrolledUp={isUserScrolledUp}
hasMessages={chatMessages.length > 0}
onScrollToBottom={scrollToBottom}
onSubmit={handleSubmit}
isDragActive={isDragActive}
attachedImages={attachedImages}
onRemoveImage={(index) =>
setAttachedImages((previous) =>
previous.filter((_, currentIndex) => currentIndex !== index),
)
}
uploadingImages={uploadingImages}
imageErrors={imageErrors}
showFileDropdown={showFileDropdown}
filteredFiles={filteredFiles}
selectedFileIndex={selectedFileIndex}
onSelectFile={selectFile}
filteredCommands={filteredCommands}
selectedCommandIndex={selectedCommandIndex}
onCommandSelect={handleCommandSelect}
onCloseCommandMenu={resetCommandMenuState}
isCommandMenuOpen={showCommandMenu}
frequentCommands={commandQuery ? [] : frequentCommands}
getRootProps={getRootProps as (...args: unknown[]) => Record<string, unknown>}
getInputProps={getInputProps as (...args: unknown[]) => Record<string, unknown>}
openImagePicker={openImagePicker}
inputHighlightRef={inputHighlightRef}
renderInputWithMentions={renderInputWithMentions}
textareaRef={textareaRef}
input={input}
onInputChange={handleInputChange}
onTextareaClick={handleTextareaClick}
onTextareaKeyDown={handleKeyDown}
onTextareaPaste={handlePaste}
onTextareaScrollSync={syncInputOverlayScroll}
onTextareaInput={handleTextareaInput}
onInputFocusChange={handleInputFocusChange}
placeholder={t('input.placeholder', {
provider:
provider === 'cursor'
? t('messageTypes.cursor')
: provider === 'codex'
? t('messageTypes.codex')
: t('messageTypes.claude'),
})}
isTextareaExpanded={isTextareaExpanded}
sendByCtrlEnter={sendByCtrlEnter}
onTranscript={handleTranscript}
/>
</div>
<QuickSettingsPanel />
</>
);
}
export default React.memo(ChatInterface);