feat: Optimize state updates in server and model checks

- Added shallow equality guard for `connectedServers` state to prevent redundant updates when the fetched server list hasn't changed.
- Updated error handling for server fetch to only clear the state when it actually contains data.
- Introduced `newHasActiveModels` variable and conditional updater for `hasActiveModels` to avoid unnecessary state changes.
- Adjusted error handling for active model fetch to only set `hasActiveModels` to `false` when the current state differs.

These changes reduce needless re‑renders and improve component performance.
This commit is contained in:
Akarshan 2025-10-10 20:25:17 +05:30
parent 45d57dd34d
commit 31f9501d8e
No known key found for this signature in database
GPG Key ID: D75C9634A870665F

View File

@ -50,7 +50,11 @@ import { PlatformFeatures } from '@/lib/platform/const'
import { PlatformFeature } from '@/lib/platform/types' import { PlatformFeature } from '@/lib/platform/types'
import { useAnalytic } from '@/hooks/useAnalytic' import { useAnalytic } from '@/hooks/useAnalytic'
import posthog from 'posthog-js' import posthog from 'posthog-js'
import { Attachment, createImageAttachment, createDocumentAttachment } from '@/types/attachment' import {
Attachment,
createImageAttachment,
createDocumentAttachment,
} from '@/types/attachment'
type ChatInputProps = { type ChatInputProps = {
className?: string className?: string
@ -123,10 +127,19 @@ const ChatInput = ({
const checkConnectedServers = async () => { const checkConnectedServers = async () => {
try { try {
const servers = await serviceHub.mcp().getConnectedServers() const servers = await serviceHub.mcp().getConnectedServers()
setConnectedServers(servers) // Only update state if the servers list has actually changed
setConnectedServers((prev) => {
if (JSON.stringify(prev) === JSON.stringify(servers)) {
return prev
}
return servers
})
} catch (error) { } catch (error) {
console.error('Failed to get connected servers:', error) console.error('Failed to get connected servers:', error)
setConnectedServers([]) setConnectedServers((prev) => {
if (prev.length === 0) return prev
return []
})
} }
} }
@ -148,10 +161,22 @@ const ChatInput = ({
const hasMatchingActiveModel = activeModels.some( const hasMatchingActiveModel = activeModels.some(
(model) => String(model) === selectedModel?.id (model) => String(model) === selectedModel?.id
) )
setHasActiveModels(activeModels.length > 0 && hasMatchingActiveModel) const newHasActiveModels =
activeModels.length > 0 && hasMatchingActiveModel
// Only update state if the value has actually changed
setHasActiveModels((prev) => {
if (prev === newHasActiveModels) {
return prev
}
return newHasActiveModels
})
} catch (error) { } catch (error) {
console.error('Failed to get active models:', error) console.error('Failed to get active models:', error)
setHasActiveModels(false) setHasActiveModels((prev) => {
if (prev === false) return prev
return false
})
} }
} }
@ -327,7 +352,19 @@ const ChatInput = ({
filters: [ filters: [
{ {
name: 'Documents', name: 'Documents',
extensions: ['pdf', 'docx', 'txt', 'md', 'csv', 'xlsx', 'xls', 'ods', 'pptx', 'html', 'htm'], extensions: [
'pdf',
'docx',
'txt',
'md',
'csv',
'xlsx',
'xls',
'ods',
'pptx',
'html',
'htm',
],
}, },
], ],
}) })
@ -437,7 +474,9 @@ const ChatInput = ({
console.error('Failed to ingest document:', error) console.error('Failed to ingest document:', error)
// Remove failed document // Remove failed document
setAttachments((prev) => setAttachments((prev) =>
prev.filter((a) => !(a.path === doc.path && a.type === 'document')) prev.filter(
(a) => !(a.path === doc.path && a.type === 'document')
)
) )
toast.error(`Failed to ingest ${doc.name}`, { toast.error(`Failed to ingest ${doc.name}`, {
description: description:
@ -491,9 +530,7 @@ const ChatInput = ({
const newFiles: Attachment[] = [] const newFiles: Attachment[] = []
const duplicates: string[] = [] const duplicates: string[] = []
const existingImageNames = new Set( const existingImageNames = new Set(
attachments attachments.filter((a) => a.type === 'image').map((a) => a.name)
.filter((a) => a.type === 'image')
.map((a) => a.name)
) )
Array.from(files).forEach((file) => { Array.from(files).forEach((file) => {
@ -503,7 +540,6 @@ const ChatInput = ({
return return
} }
// Check file size // Check file size
if (file.size > maxSize) { if (file.size > maxSize) {
setMessage(`File is too large. Maximum size is 10MB.`) setMessage(`File is too large. Maximum size is 10MB.`)
@ -577,10 +613,9 @@ const ChatInput = ({
) )
) )
const result = await serviceHub.uploads().ingestImage( const result = await serviceHub
currentThreadId, .uploads()
img .ingestImage(currentThreadId, img)
)
if (result?.id) { if (result?.id) {
// Mark as processed with ID // Mark as processed with ID
@ -609,7 +644,9 @@ const ChatInput = ({
) )
toast.error(`Failed to ingest ${img.name}`, { toast.error(`Failed to ingest ${img.name}`, {
description: description:
error instanceof Error ? error.message : String(error), error instanceof Error
? error.message
: String(error),
}) })
} }
} }
@ -844,7 +881,10 @@ const ChatInput = ({
const isImage = att.type === 'image' const isImage = att.type === 'image'
const ext = att.fileType || att.mimeType?.split('/')[1] const ext = att.fileType || att.mimeType?.split('/')[1]
return ( return (
<div key={`${att.type}-${idx}-${att.name}`} className="relative"> <div
key={`${att.type}-${idx}-${att.name}`}
className="relative"
>
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
@ -886,7 +926,10 @@ const ChatInput = ({
{att.processed && !att.processing && ( {att.processed && !att.processing && (
<div className="absolute inset-0 flex items-center justify-center bg-black/5"> <div className="absolute inset-0 flex items-center justify-center bg-black/5">
<div className="bg-green-600/90 rounded-full p-1"> <div className="bg-green-600/90 rounded-full p-1">
<IconCheck size={14} className="text-white" /> <IconCheck
size={14}
className="text-white"
/>
</div> </div>
</div> </div>
)} )}
@ -894,14 +937,21 @@ const ChatInput = ({
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<div className="text-xs"> <div className="text-xs">
<div className="font-medium truncate max-w-52" title={att.name}> <div
className="font-medium truncate max-w-52"
title={att.name}
>
{att.name} {att.name}
</div> </div>
<div className="opacity-70"> <div className="opacity-70">
{isImage {isImage
? (att.mimeType || 'image') ? att.mimeType || 'image'
: (ext ? `.${ext}` : 'document')} : ext
{att.size ? ` · ${formatBytes(att.size)}` : ''} ? `.${ext}`
: 'document'}
{att.size
? ` · ${formatBytes(att.size)}`
: ''}
</div> </div>
</div> </div>
</TooltipContent> </TooltipContent>