diff --git a/bin/hermit b/bin/hermit index 31559b7d115e..6dbd60cceb4e 100755 --- a/bin/hermit +++ b/bin/hermit @@ -17,7 +17,7 @@ if [ -z "${HERMIT_STATE_DIR}" ]; then esac fi -export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}" +export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://d1abdrezunyhdp.cloudfront.net/square}" HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")" export HERMIT_CHANNEL export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit} @@ -26,7 +26,7 @@ if [ ! -x "${HERMIT_EXE}" ]; then echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2 INSTALL_SCRIPT="$(mktemp)" # This value must match that of the install script - INSTALL_SCRIPT_SHA256="09ed936378857886fd4a7a4878c0f0c7e3d839883f39ca8b4f2f242e3126e1c6" + INSTALL_SCRIPT_SHA256="d9774f75517f9a6d9e371daae9991cdb9fbbc390101b47c3fb2f6876d9094bab" if [ "${INSTALL_SCRIPT_SHA256}" = "BYPASS" ]; then curl -fsSL "${HERMIT_DIST_URL}/install.sh" -o "${INSTALL_SCRIPT}" else diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 14c72ac5692d..2ff4ec8cf5e8 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -13,6 +13,7 @@ import { GoosehintsModal } from './components/GoosehintsModal'; import { type ExtensionConfig } from './extensions'; import { type Recipe } from './recipe'; import AnnouncementModal from './components/AnnouncementModal'; +import { ChatInputProvider } from './components/ChatInputContext'; import ChatView from './components/ChatView'; import SuspenseLoader from './suspense-loader'; @@ -112,6 +113,8 @@ const getInitialView = (): ViewConfig => { }; }; +import { ChatInputProvider } from './components/ChatInputContext'; + export default function App() { const [fatalError, setFatalError] = useState(null); const [modalVisible, setModalVisible] = useState(false); @@ -509,8 +512,9 @@ export default function App() { ); return ( - - + + `relative min-h-16 mb-4 p-2 rounded-lg @@ -613,5 +617,6 @@ export default function App() { )} + ); } diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx index 528e4d4273ec..caf2c46c30c4 100644 --- a/ui/desktop/src/components/ChatInput.tsx +++ b/ui/desktop/src/components/ChatInput.tsx @@ -11,6 +11,7 @@ import { useWhisper } from '../hooks/useWhisper'; import { WaveformVisualizer } from './WaveformVisualizer'; import { toastError } from '../toasts'; import MentionPopover, { FileItemWithMatch } from './MentionPopover'; +import { useChatInput } from './ChatInputContext'; interface PastedImage { id: string; @@ -63,7 +64,8 @@ export default function ChatInput({ sessionCosts, }: ChatInputProps) { const [_value, setValue] = useState(initialValue); - const [displayValue, setDisplayValue] = useState(initialValue); // For immediate visual feedback + const { inputValue, setInputValue } = useChatInput(); + const [displayValue, setDisplayValue] = useState(inputValue || initialValue); // Prioritize context value const [isFocused, setIsFocused] = useState(false); const [pastedImages, setPastedImages] = useState([]); const [mentionPopover, setMentionPopover] = useState<{ @@ -114,10 +116,24 @@ export default function ChatInput({ }, }); - // Update internal value when initialValue changes + // Update context when display value changes useEffect(() => { - setValue(initialValue); - setDisplayValue(initialValue); + setInputValue(displayValue); + }, [displayValue, setInputValue]); + + // Update display value when context value changes + useEffect(() => { + if (inputValue !== displayValue) { + setDisplayValue(inputValue); + } + }, [inputValue]); + + // Initialize display value from context or initialValue on mount + useEffect(() => { + // Prioritize context value over initialValue + const valueToUse = inputValue || initialValue; + setValue(valueToUse); + setDisplayValue(valueToUse); // Use a functional update to get the current pastedImages // and perform cleanup. This avoids needing pastedImages in the deps. @@ -184,8 +200,13 @@ export default function ChatInput({ useEffect(() => { if (textAreaRef.current) { textAreaRef.current.focus(); + // Set cursor to end of text if there's persisted content + if (displayValue) { + const length = displayValue.length; + textAreaRef.current.setSelectionRange(length, length); + } } - }, []); + }, [displayValue]); const minHeight = '1rem'; const maxHeight = 10 * 24; @@ -472,6 +493,7 @@ export default function ChatInput({ setDisplayValue(''); setValue(''); + setInputValue(''); setPastedImages([]); setHistoryIndex(-1); setSavedInput(''); diff --git a/ui/desktop/src/components/ChatInputContext.tsx b/ui/desktop/src/components/ChatInputContext.tsx new file mode 100644 index 000000000000..fa111e1093a1 --- /dev/null +++ b/ui/desktop/src/components/ChatInputContext.tsx @@ -0,0 +1,26 @@ +import React, { createContext, useContext, useState } from 'react'; + +interface ChatInputContextType { + inputValue: string; + setInputValue: (value: string) => void; +} + +const ChatInputContext = createContext(undefined); + +export function ChatInputProvider({ children }: { children: React.ReactNode }) { + const [inputValue, setInputValue] = useState(''); + + return ( + + {children} + + ); +} + +export function useChatInput() { + const context = useContext(ChatInputContext); + if (context === undefined) { + throw new Error('useChatInput must be used within a ChatInputProvider'); + } + return context; +}