diff --git a/web-app/src/containers/ChatInput.tsx b/web-app/src/containers/ChatInput.tsx index 9460ed98f..f82d17f52 100644 --- a/web-app/src/containers/ChatInput.tsx +++ b/web-app/src/containers/ChatInput.tsx @@ -47,23 +47,23 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { const [isFocused, setIsFocused] = useState(false) const [rows, setRows] = useState(1) const serviceHub = useServiceHub() - const { - streamingContent, - abortControllers, - loadingModel, - tools, - cancelToolCall, - } = useAppState() - const { prompt, setPrompt } = usePrompt() - const { currentThreadId } = useThreads() + const streamingContent = useAppState((state) => state.streamingContent) + const abortControllers = useAppState((state) => state.abortControllers) + const loadingModel = useAppState((state) => state.loadingModel) + const tools = useAppState((state) => state.tools) + const cancelToolCall = useAppState((state) => state.cancelToolCall) + const prompt = usePrompt((state) => state.prompt) + const setPrompt = usePrompt((state) => state.setPrompt) + const currentThreadId = useThreads((state) => state.currentThreadId) const { t } = useTranslation() const { spellCheckChatInput } = useGeneralSetting() useTools() const maxRows = 10 - const { selectedModel, selectedProvider } = useModelProvider() - const { sendMessage } = useChat() + const selectedModel = useModelProvider((state) => state.selectedModel) + const selectedProvider = useModelProvider((state) => state.selectedProvider) + const sendMessage = useChat() const [message, setMessage] = useState('') const [dropdownToolsAvailable, setDropdownToolsAvailable] = useState(false) const [tooltipToolsAvailable, setTooltipToolsAvailable] = useState(false) diff --git a/web-app/src/containers/GenerateResponseButton.tsx b/web-app/src/containers/GenerateResponseButton.tsx index 0d1ab339e..9f6df11f8 100644 --- a/web-app/src/containers/GenerateResponseButton.tsx +++ b/web-app/src/containers/GenerateResponseButton.tsx @@ -12,7 +12,7 @@ export const GenerateResponseButton = ({ threadId }: { threadId: string }) => { messages: state.messages[threadId], })) ) - const { sendMessage } = useChat() + const sendMessage = useChat() const generateAIResponse = () => { const latestUserMessage = messages[messages.length - 1] if ( diff --git a/web-app/src/containers/StreamingContent.tsx b/web-app/src/containers/StreamingContent.tsx index 573dc29c9..57aebe61e 100644 --- a/web-app/src/containers/StreamingContent.tsx +++ b/web-app/src/containers/StreamingContent.tsx @@ -21,7 +21,7 @@ function extractReasoningSegment(text: string) { // Use memo with no dependencies to allow re-renders when props change // Avoid duplicate reasoning segments after tool calls export const StreamingContent = memo(({ threadId }: Props) => { - const { streamingContent } = useAppState() + const streamingContent = useAppState((state) => state.streamingContent) const { getMessages } = useMessages() const messages = getMessages(threadId) @@ -68,6 +68,7 @@ export const StreamingContent = memo(({ threadId }: Props) => { }} {...streamingContent} isLastMessage={true} + streamingThread={streamingContent.thread_id} showAssistant={ messages.length > 0 ? messages[messages.length - 1].role !== 'assistant' diff --git a/web-app/src/containers/ThinkingBlock.tsx b/web-app/src/containers/ThinkingBlock.tsx index 9afc75164..68ab8644f 100644 --- a/web-app/src/containers/ThinkingBlock.tsx +++ b/web-app/src/containers/ThinkingBlock.tsx @@ -27,12 +27,15 @@ const useThinkingStore = create((set) => ({ })) const ThinkingBlock = ({ id, text }: Props) => { - const { thinkingState, setThinkingState } = useThinkingStore() + const thinkingState = useThinkingStore((state) => state.thinkingState) + const setThinkingState = useThinkingStore((state) => state.setThinkingState) const isStreaming = useAppState((state) => !!state.streamingContent) const { t } = useTranslation() // Check for thinking formats const hasThinkTag = text.includes('') && !text.includes('') - const hasAnalysisChannel = text.includes('<|channel|>analysis<|message|>') && !text.includes('<|start|>assistant<|channel|>final<|message|>') + const hasAnalysisChannel = + text.includes('<|channel|>analysis<|message|>') && + !text.includes('<|start|>assistant<|channel|>final<|message|>') const loading = (hasThinkTag || hasAnalysisChannel) && isStreaming const isExpanded = thinkingState[id] ?? (loading ? true : false) const handleClick = () => { @@ -48,7 +51,7 @@ const ThinkingBlock = ({ id, text }: Props) => { .replace(/<\|start\|>assistant<\|channel\|>final<\|message\|>/g, '') .replace(/assistant<\|channel\|>final<\|message\|>/g, '') .replace(/<\|channel\|>/g, '') // remove any remaining channel markers - .replace(/<\|message\|>/g, '') // remove any remaining message markers + .replace(/<\|message\|>/g, '') // remove any remaining message markers .replace(/<\|start\|>/g, '') // remove any remaining start markers .trim() } diff --git a/web-app/src/containers/ThreadContent.tsx b/web-app/src/containers/ThreadContent.tsx index 5239bd5d9..79b38c565 100644 --- a/web-app/src/containers/ThreadContent.tsx +++ b/web-app/src/containers/ThreadContent.tsx @@ -68,6 +68,7 @@ export const ThreadContent = memo( isLastMessage?: boolean index?: number showAssistant?: boolean + streamingThread?: string streamTools?: any contextOverflowModal?: React.ReactNode | null @@ -75,7 +76,7 @@ export const ThreadContent = memo( } ) => { const { t } = useTranslation() - const { selectedModel } = useModelProvider() + const selectedModel = useModelProvider((state) => state.selectedModel) // Use useMemo to stabilize the components prop const linkComponents = useMemo( @@ -132,8 +133,9 @@ export const ThreadContent = memo( return { reasoningSegment: undefined, textSegment: text } }, [text]) - const { getMessages, deleteMessage } = useMessages() - const { sendMessage } = useChat() + const getMessages = useMessages((state) => state.getMessages) + const deleteMessage = useMessages((state) => state.deleteMessage) + const sendMessage = useChat() const regenerate = useCallback(() => { // Only regenerate assistant message is allowed diff --git a/web-app/src/containers/ThreadList.tsx b/web-app/src/containers/ThreadList.tsx index 112f41b2d..672fc3ebc 100644 --- a/web-app/src/containers/ThreadList.tsx +++ b/web-app/src/containers/ThreadList.tsx @@ -46,14 +46,16 @@ const SortableItem = memo(({ thread }: { thread: Thread }) => { } = useSortable({ id: thread.id, disabled: true }) const isSmallScreen = useSmallScreen() - const { setLeftPanel } = useLeftPanel() + const setLeftPanel = useLeftPanel(state => state.setLeftPanel) const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, } - const { toggleFavorite, deleteThread, renameThread } = useThreads() + const toggleFavorite = useThreads((state) => state.toggleFavorite) + const deleteThread = useThreads((state) => state.deleteThread) + const renameThread = useThreads((state) => state.renameThread) const { t } = useTranslation() const [openDropdown, setOpenDropdown] = useState(false) const navigate = useNavigate() diff --git a/web-app/src/containers/TokenSpeedIndicator.tsx b/web-app/src/containers/TokenSpeedIndicator.tsx index 704a78995..7638c0804 100644 --- a/web-app/src/containers/TokenSpeedIndicator.tsx +++ b/web-app/src/containers/TokenSpeedIndicator.tsx @@ -2,6 +2,7 @@ import { memo } from 'react' import { useAppState } from '@/hooks/useAppState' import { toNumber } from '@/utils/number' import { Gauge } from 'lucide-react' +import { memo } from 'react' interface TokenSpeedIndicatorProps { metadata?: Record @@ -41,4 +42,4 @@ export const TokenSpeedIndicator = memo(({ ) }) -export default TokenSpeedIndicator +export default memo(TokenSpeedIndicator) diff --git a/web-app/src/containers/dialogs/ErrorDialog.tsx b/web-app/src/containers/dialogs/ErrorDialog.tsx index cd6ca879a..27a6c8bf2 100644 --- a/web-app/src/containers/dialogs/ErrorDialog.tsx +++ b/web-app/src/containers/dialogs/ErrorDialog.tsx @@ -16,7 +16,8 @@ import { useAppState } from '@/hooks/useAppState' export default function ErrorDialog() { const { t } = useTranslation() - const { errorMessage, setErrorMessage } = useAppState() + const errorMessage = useAppState((state) => state.errorMessage) + const setErrorMessage = useAppState((state) => state.setErrorMessage) const [isCopying, setIsCopying] = useState(false) const [isDetailExpanded, setIsDetailExpanded] = useState(true) diff --git a/web-app/src/hooks/useChat.ts b/web-app/src/hooks/useChat.ts index c20989360..7d0458b48 100644 --- a/web-app/src/hooks/useChat.ts +++ b/web-app/src/hooks/useChat.ts @@ -82,7 +82,7 @@ export const useChat = () => { const selectedAssistant = assistants.find((a) => a.id === currentAssistant?.id) || assistants[0] - const getCurrentThread = useCallback(async () => { + const getCurrentThread = useCallback(async (prompt: string) => { let currentThread = retrieveThread() if (!currentThread) { @@ -225,7 +225,7 @@ export const useChat = () => { dataUrl: string }> ) => { - const activeThread = await getCurrentThread() + const activeThread = await getCurrentThread(message) resetTokenSpeed() let activeProvider = currentProviderId @@ -570,5 +570,5 @@ export const useChat = () => { ] ) - return useMemo(() => ({ sendMessage }), [sendMessage]) + return useMemo(() => (sendMessage), [sendMessage]) } diff --git a/web-app/src/providers/DataProvider.tsx b/web-app/src/providers/DataProvider.tsx index a734cd39f..2b94276a4 100644 --- a/web-app/src/providers/DataProvider.tsx +++ b/web-app/src/providers/DataProvider.tsx @@ -38,13 +38,18 @@ export function DataProvider() { verboseLogs, proxyTimeout, } = useLocalApiServer() - const { setServerStatus } = useAppState() + const setServerStatus = useAppState((state) => state.setServerStatus) useEffect(() => { console.log('Initializing DataProvider...') serviceHub.providers().getProviders().then(setProviders) - serviceHub.mcp().getMCPConfig().then((data) => setServers(data.mcpServers ?? {})) - serviceHub.assistants().getAssistants() + serviceHub + .mcp() + .getMCPConfig() + .then((data) => setServers(data.mcpServers ?? {})) + serviceHub + .assistants() + .getAssistants() .then((data) => { // Only update assistants if we have valid data if (data && Array.isArray(data) && data.length > 0) { @@ -61,14 +66,18 @@ export function DataProvider() { }, [serviceHub]) useEffect(() => { - serviceHub.threads().fetchThreads().then((threads) => { - setThreads(threads) - threads.forEach((thread) => - serviceHub.messages().fetchMessages(thread.id).then((messages) => - setMessages(thread.id, messages) + serviceHub + .threads() + .fetchThreads() + .then((threads) => { + setThreads(threads) + threads.forEach((thread) => + serviceHub + .messages() + .fetchMessages(thread.id) + .then((messages) => setMessages(thread.id, messages)) ) - ) - }) + }) }, [serviceHub, setThreads, setMessages]) // Check for app updates @@ -157,7 +166,9 @@ export function DataProvider() { setServerStatus('pending') // Start the model first - serviceHub.models().startModel(modelToStart.provider, modelToStart.model) + serviceHub + .models() + .startModel(modelToStart.provider, modelToStart.model) .then(() => { console.log(`Model ${modelToStart.model} started successfully`) diff --git a/web-app/src/routes/settings/mcp-servers.tsx b/web-app/src/routes/settings/mcp-servers.tsx index 0b95cf7ce..242d4f217 100644 --- a/web-app/src/routes/settings/mcp-servers.tsx +++ b/web-app/src/routes/settings/mcp-servers.tsx @@ -132,7 +132,7 @@ function MCPServersDesktop() { const [loadingServers, setLoadingServers] = useState<{ [key: string]: boolean }>({}) - const { setErrorMessage } = useAppState() + const setErrorMessage = useAppState((state) => state.setErrorMessage) const handleOpenDialog = (serverKey?: string) => { if (serverKey) {