diff --git a/ui/desktop/src/App.test.tsx b/ui/desktop/src/App.test.tsx
index 1955821280d4..92313dfcf8cc 100644
--- a/ui/desktop/src/App.test.tsx
+++ b/ui/desktop/src/App.test.tsx
@@ -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,
}));
diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx
index 5ae00641abc1..90199c4dd45b 100644
--- a/ui/desktop/src/App.tsx
+++ b/ui/desktop/src/App.tsx
@@ -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 (
-
-
-
-
-
-
-
-
+
+
+
+
+
+
);
}
diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx
index 863c7f65b5b7..63da447e0ce2 100644
--- a/ui/desktop/src/components/ChatInput.tsx
+++ b/ui/desktop/src/components/ChatInput.tsx
@@ -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(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'}
diff --git a/ui/desktop/src/contexts/ChatContext.tsx b/ui/desktop/src/contexts/ChatContext.tsx
index eb0537574cef..49ec41495295 100644
--- a/ui/desktop/src/contexts/ChatContext.tsx
+++ b/ui/desktop/src/contexts/ChatContext.tsx
@@ -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 = ({
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 = ({
recipe: null,
recipeParameterValues: null,
});
- clearDraft();
};
const setRecipe = (recipe: Recipe | null) => {
@@ -88,9 +69,6 @@ export const ChatProvider: React.FC = ({
hasActiveSession,
setRecipe,
clearRecipe,
- draft,
- setDraft,
- clearDraft,
contextKey,
agentWaitingMessage,
};
diff --git a/ui/desktop/src/contexts/DraftContext.tsx b/ui/desktop/src/contexts/DraftContext.tsx
deleted file mode 100644
index 9238f54bfd9f..000000000000
--- a/ui/desktop/src/contexts/DraftContext.tsx
+++ /dev/null
@@ -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(undefined);
-
-export const DraftProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
- // Store all drafts by contextKey
- const [drafts, setDrafts] = useState>({});
-
- 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 (
-
- {children}
-
- );
-};
-
-export const useDraftContext = (): DraftContextType => {
- const context = useContext(DraftContext);
- if (context === undefined) {
- throw new Error('useDraftContext must be used within a DraftProvider');
- }
- return context;
-};