Removed drafts and agentIsReady in ChatInput (#5366)

This commit is contained in:
Zane
2025-10-30 14:04:53 -07:00
committed by GitHub
parent c39775ea44
commit e586e13d9d
5 changed files with 10 additions and 148 deletions
-7
View File
@@ -131,18 +131,11 @@ vi.mock('./contexts/ChatContext', () => ({
hasActiveSession: false,
setRecipe: vi.fn(),
clearRecipe: vi.fn(),
draft: '',
setDraft: vi.fn(),
clearDraft: vi.fn(),
contextKey: 'hub',
}),
DEFAULT_CHAT_TITLE: 'New Chat', // Keep this from HEAD
}));
vi.mock('./contexts/DraftContext', () => ({
DraftProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
}));
vi.mock('./components/ui/ConfirmationModal', () => ({
ConfirmationModal: () => null,
}));
+6 -9
View File
@@ -27,7 +27,6 @@ import SchedulesView from './components/schedule/SchedulesView';
import ProviderSettings from './components/settings/providers/ProviderSettingsPage';
import { AppLayout } from './components/Layout/AppLayout';
import { ChatProvider } from './contexts/ChatContext';
import { DraftProvider } from './contexts/DraftContext';
import 'react-toastify/dist/ReactToastify.css';
import { useConfig } from './components/ConfigContext';
@@ -638,13 +637,11 @@ export function AppInner() {
export default function App() {
return (
<DraftProvider>
<ModelAndProviderProvider>
<HashRouter>
<AppInner />
</HashRouter>
<AnnouncementModal />
</ModelAndProviderProvider>
</DraftProvider>
<ModelAndProviderProvider>
<HashRouter>
<AppInner />
</HashRouter>
<AnnouncementModal />
</ModelAndProviderProvider>
);
}
+4 -66
View File
@@ -20,7 +20,6 @@ import { WaveformVisualizer } from './WaveformVisualizer';
import { toastError } from '../toasts';
import MentionPopover, { FileItemWithMatch } from './MentionPopover';
import { useDictationSettings } from '../hooks/useDictationSettings';
import { useChatContext } from '../contexts/ChatContext';
import { COST_TRACKING_ENABLED, VOICE_DICTATION_ELEVENLABS_ENABLED } from '../updates';
import { CostTracker } from './bottom_menu/CostTracker';
import { DroppedFile, useFileDrop } from '../hooks/useFileDrop';
@@ -142,20 +141,8 @@ export default function ChatInput({
const { getCurrentModelAndProvider, currentModel, currentProvider } = useModelAndProvider();
const [tokenLimit, setTokenLimit] = useState<number>(TOKEN_LIMIT_DEFAULT);
const [isTokenLimitLoaded, setIsTokenLimitLoaded] = useState(false);
// Draft functionality - get chat context and global draft context
// We need to handle the case where ChatInput is used without ChatProvider (e.g., in Hub)
const chatContext = useChatContext(); // This should always be available now
const agentIsReady = chatContext === null || chatContext.agentWaitingMessage === null;
const draftLoadedRef = useRef(false);
const [diagnosticsOpen, setDiagnosticsOpen] = useState(false);
// Debug logging for draft context
useEffect(() => {
// Debug logging removed - draft functionality is working correctly
}, [chatContext?.contextKey, chatContext?.draft, chatContext]);
// Save queue state (paused/interrupted) to storage
useEffect(() => {
try {
@@ -281,9 +268,6 @@ export default function ChatInput({
setValue(initialValue);
setDisplayValue(initialValue);
// Reset draft loaded flag when initialValue changes
draftLoadedRef.current = false;
// Use a functional update to get the current pastedImages
// and perform cleanup. This avoids needing pastedImages in the deps.
setPastedImages((currentPastedImages) => {
@@ -313,38 +297,6 @@ export default function ChatInput({
}
}, [recipeAccepted, initialPrompt, messages.length]);
// Draft functionality - load draft if no initial value or recipe
useEffect(() => {
// Reset draft loaded flag when context changes
draftLoadedRef.current = false;
}, [chatContext?.contextKey]);
useEffect(() => {
// Only load draft once and if conditions are met
if (!initialValue && !recipe && !draftLoadedRef.current && chatContext) {
const draftText = chatContext.draft || '';
if (draftText) {
setDisplayValue(draftText);
setValue(draftText);
}
// Always mark as loaded after checking, regardless of whether we found a draft
draftLoadedRef.current = true;
}
}, [chatContext, initialValue, recipe]);
// Save draft when user types (debounced)
const debouncedSaveDraft = useMemo(
() =>
debounce((value: string) => {
if (chatContext && chatContext.setDraft) {
chatContext.setDraft(value);
}
}, 500), // Save draft after 500ms of no typing
[chatContext]
);
// State to track if the IME is composing (i.e., in the middle of Japanese IME input)
const [isComposing, setIsComposing] = useState(false);
const [historyIndex, setHistoryIndex] = useState(-1);
@@ -608,13 +560,9 @@ export default function ChatInput({
const val = evt.target.value;
const cursorPosition = evt.target.selectionStart;
setDisplayValue(val); // Update display immediately
updateValue(val); // Update actual value immediately for better responsiveness
debouncedSaveDraft(val); // Save draft with debounce
// Mark that the user has typed something
setDisplayValue(val);
updateValue(val);
setHasUserTyped(true);
// Check for @ mention
checkForMention(val, cursorPosition, evt.target);
};
@@ -769,9 +717,8 @@ export default function ChatInput({
useEffect(() => {
return () => {
debouncedAutosize.cancel?.();
debouncedSaveDraft.cancel?.();
};
}, [debouncedAutosize, debouncedSaveDraft]);
}, [debouncedAutosize]);
// Handlers for composition events, which are crucial for proper IME behavior
const handleCompositionStart = () => {
@@ -910,7 +857,6 @@ export default function ChatInput({
const canSubmit =
!isLoading &&
agentIsReady &&
(displayValue.trim() ||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
allDroppedFiles.some((file) => !file.error && !file.isLoading));
@@ -964,11 +910,6 @@ export default function ChatInput({
setIsInGlobalHistory(false);
setHasUserTyped(false);
// Clear draft when message is sent
if (chatContext && chatContext.clearDraft) {
chatContext.clearDraft();
}
// Clear both parent and local dropped files after processing
if (onFilesProcessed && droppedFiles.length > 0) {
onFilesProcessed();
@@ -980,7 +921,6 @@ export default function ChatInput({
},
[
allDroppedFiles,
chatContext,
displayValue,
droppedFiles.length,
handleSubmit,
@@ -1066,7 +1006,6 @@ export default function ChatInput({
e.preventDefault();
const canSubmit =
!isLoading &&
agentIsReady &&
(displayValue.trim() ||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
allDroppedFiles.some((file) => !file.error && !file.isLoading));
@@ -1120,7 +1059,6 @@ export default function ChatInput({
isAnyDroppedFileLoading ||
isRecording ||
isTranscribing ||
!agentIsReady ||
isExtensionsLoading;
// Queue management functions - no storage persistence, only in-memory
@@ -1372,7 +1310,7 @@ export default function ChatInput({
? 'Recording...'
: isTranscribing
? 'Transcribing...'
: (chatContext?.agentWaitingMessage ?? 'Send')}
: 'Send'}
</p>
</TooltipContent>
</Tooltip>
-22
View File
@@ -1,7 +1,6 @@
import React, { createContext, useContext, ReactNode } from 'react';
import { ChatType } from '../types/chat';
import { Recipe } from '../recipe';
import { useDraftContext } from './DraftContext';
// TODO(Douwe): We should not need this anymore
export const DEFAULT_CHAT_TITLE = 'New Chat';
@@ -13,10 +12,6 @@ interface ChatContextType {
hasActiveSession: boolean;
setRecipe: (recipe: Recipe | null) => void;
clearRecipe: () => void;
// Draft functionality
draft: string;
setDraft: (draft: string) => void;
clearDraft: () => void;
// Context identification
contextKey: string; // 'hub' or 'pair-{sessionId}'
agentWaitingMessage: string | null;
@@ -39,19 +34,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
agentWaitingMessage,
contextKey = 'hub',
}) => {
const draftContext = useDraftContext();
// Draft functionality using the app-level DraftContext
const draft = draftContext.getDraft(contextKey);
const setDraft = (newDraft: string) => {
draftContext.setDraft(contextKey, newDraft);
};
const clearDraft = () => {
draftContext.clearDraft(contextKey);
};
const resetChat = () => {
setChat({
sessionId: '',
@@ -61,7 +43,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
recipe: null,
recipeParameterValues: null,
});
clearDraft();
};
const setRecipe = (recipe: Recipe | null) => {
@@ -88,9 +69,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
hasActiveSession,
setRecipe,
clearRecipe,
draft,
setDraft,
clearDraft,
contextKey,
agentWaitingMessage,
};
-44
View File
@@ -1,44 +0,0 @@
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface DraftContextType {
getDraft: (contextKey: string) => string;
setDraft: (contextKey: string, draft: string) => void;
clearDraft: (contextKey: string) => void;
}
const DraftContext = createContext<DraftContextType | undefined>(undefined);
export const DraftProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
// Store all drafts by contextKey
const [drafts, setDrafts] = useState<Record<string, string>>({});
const getDraft = (contextKey: string): string => {
return drafts[contextKey] || '';
};
const setDraft = (contextKey: string, draft: string) => {
setDrafts((prev) => ({ ...prev, [contextKey]: draft }));
};
const clearDraft = (contextKey: string) => {
setDrafts((prev) => {
const newDrafts = { ...prev };
delete newDrafts[contextKey];
return newDrafts;
});
};
return (
<DraftContext.Provider value={{ getDraft, setDraft, clearDraft }}>
{children}
</DraftContext.Provider>
);
};
export const useDraftContext = (): DraftContextType => {
const context = useContext(DraftContext);
if (context === undefined) {
throw new Error('useDraftContext must be used within a DraftProvider');
}
return context;
};