Removed drafts and agentIsReady in ChatInput (#5366)
This commit is contained in:
@@ -131,18 +131,11 @@ vi.mock('./contexts/ChatContext', () => ({
|
|||||||
hasActiveSession: false,
|
hasActiveSession: false,
|
||||||
setRecipe: vi.fn(),
|
setRecipe: vi.fn(),
|
||||||
clearRecipe: vi.fn(),
|
clearRecipe: vi.fn(),
|
||||||
draft: '',
|
|
||||||
setDraft: vi.fn(),
|
|
||||||
clearDraft: vi.fn(),
|
|
||||||
contextKey: 'hub',
|
contextKey: 'hub',
|
||||||
}),
|
}),
|
||||||
DEFAULT_CHAT_TITLE: 'New Chat', // Keep this from HEAD
|
DEFAULT_CHAT_TITLE: 'New Chat', // Keep this from HEAD
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./contexts/DraftContext', () => ({
|
|
||||||
DraftProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock('./components/ui/ConfirmationModal', () => ({
|
vi.mock('./components/ui/ConfirmationModal', () => ({
|
||||||
ConfirmationModal: () => null,
|
ConfirmationModal: () => null,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import SchedulesView from './components/schedule/SchedulesView';
|
|||||||
import ProviderSettings from './components/settings/providers/ProviderSettingsPage';
|
import ProviderSettings from './components/settings/providers/ProviderSettingsPage';
|
||||||
import { AppLayout } from './components/Layout/AppLayout';
|
import { AppLayout } from './components/Layout/AppLayout';
|
||||||
import { ChatProvider } from './contexts/ChatContext';
|
import { ChatProvider } from './contexts/ChatContext';
|
||||||
import { DraftProvider } from './contexts/DraftContext';
|
|
||||||
|
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
import { useConfig } from './components/ConfigContext';
|
import { useConfig } from './components/ConfigContext';
|
||||||
@@ -638,13 +637,11 @@ export function AppInner() {
|
|||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<DraftProvider>
|
<ModelAndProviderProvider>
|
||||||
<ModelAndProviderProvider>
|
<HashRouter>
|
||||||
<HashRouter>
|
<AppInner />
|
||||||
<AppInner />
|
</HashRouter>
|
||||||
</HashRouter>
|
<AnnouncementModal />
|
||||||
<AnnouncementModal />
|
</ModelAndProviderProvider>
|
||||||
</ModelAndProviderProvider>
|
|
||||||
</DraftProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import { WaveformVisualizer } from './WaveformVisualizer';
|
|||||||
import { toastError } from '../toasts';
|
import { toastError } from '../toasts';
|
||||||
import MentionPopover, { FileItemWithMatch } from './MentionPopover';
|
import MentionPopover, { FileItemWithMatch } from './MentionPopover';
|
||||||
import { useDictationSettings } from '../hooks/useDictationSettings';
|
import { useDictationSettings } from '../hooks/useDictationSettings';
|
||||||
import { useChatContext } from '../contexts/ChatContext';
|
|
||||||
import { COST_TRACKING_ENABLED, VOICE_DICTATION_ELEVENLABS_ENABLED } from '../updates';
|
import { COST_TRACKING_ENABLED, VOICE_DICTATION_ELEVENLABS_ENABLED } from '../updates';
|
||||||
import { CostTracker } from './bottom_menu/CostTracker';
|
import { CostTracker } from './bottom_menu/CostTracker';
|
||||||
import { DroppedFile, useFileDrop } from '../hooks/useFileDrop';
|
import { DroppedFile, useFileDrop } from '../hooks/useFileDrop';
|
||||||
@@ -142,20 +141,8 @@ export default function ChatInput({
|
|||||||
const { getCurrentModelAndProvider, currentModel, currentProvider } = useModelAndProvider();
|
const { getCurrentModelAndProvider, currentModel, currentProvider } = useModelAndProvider();
|
||||||
const [tokenLimit, setTokenLimit] = useState<number>(TOKEN_LIMIT_DEFAULT);
|
const [tokenLimit, setTokenLimit] = useState<number>(TOKEN_LIMIT_DEFAULT);
|
||||||
const [isTokenLimitLoaded, setIsTokenLimitLoaded] = useState(false);
|
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);
|
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
|
// Save queue state (paused/interrupted) to storage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
@@ -281,9 +268,6 @@ export default function ChatInput({
|
|||||||
setValue(initialValue);
|
setValue(initialValue);
|
||||||
setDisplayValue(initialValue);
|
setDisplayValue(initialValue);
|
||||||
|
|
||||||
// Reset draft loaded flag when initialValue changes
|
|
||||||
draftLoadedRef.current = false;
|
|
||||||
|
|
||||||
// Use a functional update to get the current pastedImages
|
// Use a functional update to get the current pastedImages
|
||||||
// and perform cleanup. This avoids needing pastedImages in the deps.
|
// and perform cleanup. This avoids needing pastedImages in the deps.
|
||||||
setPastedImages((currentPastedImages) => {
|
setPastedImages((currentPastedImages) => {
|
||||||
@@ -313,38 +297,6 @@ export default function ChatInput({
|
|||||||
}
|
}
|
||||||
}, [recipeAccepted, initialPrompt, messages.length]);
|
}, [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)
|
// State to track if the IME is composing (i.e., in the middle of Japanese IME input)
|
||||||
const [isComposing, setIsComposing] = useState(false);
|
const [isComposing, setIsComposing] = useState(false);
|
||||||
const [historyIndex, setHistoryIndex] = useState(-1);
|
const [historyIndex, setHistoryIndex] = useState(-1);
|
||||||
@@ -608,13 +560,9 @@ export default function ChatInput({
|
|||||||
const val = evt.target.value;
|
const val = evt.target.value;
|
||||||
const cursorPosition = evt.target.selectionStart;
|
const cursorPosition = evt.target.selectionStart;
|
||||||
|
|
||||||
setDisplayValue(val); // Update display immediately
|
setDisplayValue(val);
|
||||||
updateValue(val); // Update actual value immediately for better responsiveness
|
updateValue(val);
|
||||||
debouncedSaveDraft(val); // Save draft with debounce
|
|
||||||
// Mark that the user has typed something
|
|
||||||
setHasUserTyped(true);
|
setHasUserTyped(true);
|
||||||
|
|
||||||
// Check for @ mention
|
|
||||||
checkForMention(val, cursorPosition, evt.target);
|
checkForMention(val, cursorPosition, evt.target);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -769,9 +717,8 @@ export default function ChatInput({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
debouncedAutosize.cancel?.();
|
debouncedAutosize.cancel?.();
|
||||||
debouncedSaveDraft.cancel?.();
|
|
||||||
};
|
};
|
||||||
}, [debouncedAutosize, debouncedSaveDraft]);
|
}, [debouncedAutosize]);
|
||||||
|
|
||||||
// Handlers for composition events, which are crucial for proper IME behavior
|
// Handlers for composition events, which are crucial for proper IME behavior
|
||||||
const handleCompositionStart = () => {
|
const handleCompositionStart = () => {
|
||||||
@@ -910,7 +857,6 @@ export default function ChatInput({
|
|||||||
|
|
||||||
const canSubmit =
|
const canSubmit =
|
||||||
!isLoading &&
|
!isLoading &&
|
||||||
agentIsReady &&
|
|
||||||
(displayValue.trim() ||
|
(displayValue.trim() ||
|
||||||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
|
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
|
||||||
allDroppedFiles.some((file) => !file.error && !file.isLoading));
|
allDroppedFiles.some((file) => !file.error && !file.isLoading));
|
||||||
@@ -964,11 +910,6 @@ export default function ChatInput({
|
|||||||
setIsInGlobalHistory(false);
|
setIsInGlobalHistory(false);
|
||||||
setHasUserTyped(false);
|
setHasUserTyped(false);
|
||||||
|
|
||||||
// Clear draft when message is sent
|
|
||||||
if (chatContext && chatContext.clearDraft) {
|
|
||||||
chatContext.clearDraft();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear both parent and local dropped files after processing
|
// Clear both parent and local dropped files after processing
|
||||||
if (onFilesProcessed && droppedFiles.length > 0) {
|
if (onFilesProcessed && droppedFiles.length > 0) {
|
||||||
onFilesProcessed();
|
onFilesProcessed();
|
||||||
@@ -980,7 +921,6 @@ export default function ChatInput({
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
allDroppedFiles,
|
allDroppedFiles,
|
||||||
chatContext,
|
|
||||||
displayValue,
|
displayValue,
|
||||||
droppedFiles.length,
|
droppedFiles.length,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@@ -1066,7 +1006,6 @@ export default function ChatInput({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const canSubmit =
|
const canSubmit =
|
||||||
!isLoading &&
|
!isLoading &&
|
||||||
agentIsReady &&
|
|
||||||
(displayValue.trim() ||
|
(displayValue.trim() ||
|
||||||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
|
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
|
||||||
allDroppedFiles.some((file) => !file.error && !file.isLoading));
|
allDroppedFiles.some((file) => !file.error && !file.isLoading));
|
||||||
@@ -1120,7 +1059,6 @@ export default function ChatInput({
|
|||||||
isAnyDroppedFileLoading ||
|
isAnyDroppedFileLoading ||
|
||||||
isRecording ||
|
isRecording ||
|
||||||
isTranscribing ||
|
isTranscribing ||
|
||||||
!agentIsReady ||
|
|
||||||
isExtensionsLoading;
|
isExtensionsLoading;
|
||||||
|
|
||||||
// Queue management functions - no storage persistence, only in-memory
|
// Queue management functions - no storage persistence, only in-memory
|
||||||
@@ -1372,7 +1310,7 @@ export default function ChatInput({
|
|||||||
? 'Recording...'
|
? 'Recording...'
|
||||||
: isTranscribing
|
: isTranscribing
|
||||||
? 'Transcribing...'
|
? 'Transcribing...'
|
||||||
: (chatContext?.agentWaitingMessage ?? 'Send')}
|
: 'Send'}
|
||||||
</p>
|
</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { createContext, useContext, ReactNode } from 'react';
|
import React, { createContext, useContext, ReactNode } from 'react';
|
||||||
import { ChatType } from '../types/chat';
|
import { ChatType } from '../types/chat';
|
||||||
import { Recipe } from '../recipe';
|
import { Recipe } from '../recipe';
|
||||||
import { useDraftContext } from './DraftContext';
|
|
||||||
|
|
||||||
// TODO(Douwe): We should not need this anymore
|
// TODO(Douwe): We should not need this anymore
|
||||||
export const DEFAULT_CHAT_TITLE = 'New Chat';
|
export const DEFAULT_CHAT_TITLE = 'New Chat';
|
||||||
@@ -13,10 +12,6 @@ interface ChatContextType {
|
|||||||
hasActiveSession: boolean;
|
hasActiveSession: boolean;
|
||||||
setRecipe: (recipe: Recipe | null) => void;
|
setRecipe: (recipe: Recipe | null) => void;
|
||||||
clearRecipe: () => void;
|
clearRecipe: () => void;
|
||||||
// Draft functionality
|
|
||||||
draft: string;
|
|
||||||
setDraft: (draft: string) => void;
|
|
||||||
clearDraft: () => void;
|
|
||||||
// Context identification
|
// Context identification
|
||||||
contextKey: string; // 'hub' or 'pair-{sessionId}'
|
contextKey: string; // 'hub' or 'pair-{sessionId}'
|
||||||
agentWaitingMessage: string | null;
|
agentWaitingMessage: string | null;
|
||||||
@@ -39,19 +34,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|||||||
agentWaitingMessage,
|
agentWaitingMessage,
|
||||||
contextKey = 'hub',
|
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 = () => {
|
const resetChat = () => {
|
||||||
setChat({
|
setChat({
|
||||||
sessionId: '',
|
sessionId: '',
|
||||||
@@ -61,7 +43,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|||||||
recipe: null,
|
recipe: null,
|
||||||
recipeParameterValues: null,
|
recipeParameterValues: null,
|
||||||
});
|
});
|
||||||
clearDraft();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const setRecipe = (recipe: Recipe | null) => {
|
const setRecipe = (recipe: Recipe | null) => {
|
||||||
@@ -88,9 +69,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|||||||
hasActiveSession,
|
hasActiveSession,
|
||||||
setRecipe,
|
setRecipe,
|
||||||
clearRecipe,
|
clearRecipe,
|
||||||
draft,
|
|
||||||
setDraft,
|
|
||||||
clearDraft,
|
|
||||||
contextKey,
|
contextKey,
|
||||||
agentWaitingMessage,
|
agentWaitingMessage,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user