perf(chat): reduce input rerender overhead in conversation shell
Memoize ChatInput to minimize unnecessary updates in the chat input area during frequent parent renders. Compute localized Claude API retry banner text lazily and only when retry state is present.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import { memo } from "react"
|
||||||
import { useTranslations } from "next-intl"
|
import { useTranslations } from "next-intl"
|
||||||
import type {
|
import type {
|
||||||
AgentType,
|
AgentType,
|
||||||
@@ -48,7 +49,7 @@ interface ChatInputProps {
|
|||||||
onForkSend?: (draft: PromptDraft, modeId?: string | null) => void
|
onForkSend?: (draft: PromptDraft, modeId?: string | null) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatInput({
|
export const ChatInput = memo(function ChatInput({
|
||||||
status,
|
status,
|
||||||
promptCapabilities,
|
promptCapabilities,
|
||||||
defaultPath,
|
defaultPath,
|
||||||
@@ -138,4 +139,6 @@ export function ChatInput({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
|
ChatInput.displayName = "ChatInput"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ReactNode } from "react"
|
import { useMemo, type ReactNode } from "react"
|
||||||
import { useTranslations } from "next-intl"
|
import { useTranslations } from "next-intl"
|
||||||
import type {
|
import type {
|
||||||
AgentType,
|
AgentType,
|
||||||
@@ -104,50 +104,48 @@ export function ConversationShell({
|
|||||||
onForkSend,
|
onForkSend,
|
||||||
}: ConversationShellProps) {
|
}: ConversationShellProps) {
|
||||||
const tAcp = useTranslations("Folder.chat.acpConnections")
|
const tAcp = useTranslations("Folder.chat.acpConnections")
|
||||||
const retry = claudeApiRetry
|
const retryLineText = useMemo(() => {
|
||||||
const retryAttemptRaw = retry?.attempt
|
const retry = claudeApiRetry
|
||||||
const retryMaxRaw = retry?.maxRetries
|
if (!retry) return null
|
||||||
const retryDelayMsRaw = retry?.retryDelayMs
|
|
||||||
const retryErrorStatusRaw = retry?.errorStatus
|
|
||||||
|
|
||||||
const retryAttempt =
|
const retryAttempt =
|
||||||
retryAttemptRaw !== null && retryAttemptRaw !== undefined
|
retry.attempt !== null && retry.attempt !== undefined
|
||||||
? Math.trunc(retryAttemptRaw)
|
? Math.trunc(retry.attempt)
|
||||||
: null
|
: null
|
||||||
const retryMax =
|
const retryMax =
|
||||||
retryMaxRaw !== null && retryMaxRaw !== undefined
|
retry.maxRetries !== null && retry.maxRetries !== undefined
|
||||||
? Math.trunc(retryMaxRaw)
|
? Math.trunc(retry.maxRetries)
|
||||||
: null
|
: null
|
||||||
const retryDelaySeconds =
|
const retryDelaySeconds =
|
||||||
retryDelayMsRaw !== null && retryDelayMsRaw !== undefined
|
retry.retryDelayMs !== null && retry.retryDelayMs !== undefined
|
||||||
? (retryDelayMsRaw / 1000).toFixed(1)
|
? (retry.retryDelayMs / 1000).toFixed(1)
|
||||||
: null
|
: null
|
||||||
const errorLabel = retry?.error ?? tAcp("claudeApiRetry.fallbackError")
|
const errorLabel = retry.error ?? tAcp("claudeApiRetry.fallbackError")
|
||||||
const statusLabel =
|
const statusLabel =
|
||||||
retryErrorStatusRaw !== null && retryErrorStatusRaw !== undefined
|
retry.errorStatus !== null && retry.errorStatus !== undefined
|
||||||
? tAcp("claudeApiRetry.httpStatus", {
|
? tAcp("claudeApiRetry.httpStatus", {
|
||||||
status: Math.trunc(retryErrorStatusRaw),
|
status: Math.trunc(retry.errorStatus),
|
||||||
})
|
|
||||||
: ""
|
|
||||||
const retryLabel =
|
|
||||||
retryAttempt !== null && retryMax !== null
|
|
||||||
? tAcp("claudeApiRetry.retryingWithMax", {
|
|
||||||
attempt: retryAttempt,
|
|
||||||
max: retryMax,
|
|
||||||
})
|
|
||||||
: retryAttempt !== null
|
|
||||||
? tAcp("claudeApiRetry.retryingAttempt", {
|
|
||||||
attempt: retryAttempt,
|
|
||||||
})
|
})
|
||||||
: tAcp("claudeApiRetry.retrying")
|
: ""
|
||||||
const delayLabel =
|
const retryLabel =
|
||||||
retryDelaySeconds !== null
|
retryAttempt !== null && retryMax !== null
|
||||||
? tAcp("claudeApiRetry.nextRetryIn", {
|
? tAcp("claudeApiRetry.retryingWithMax", {
|
||||||
seconds: retryDelaySeconds,
|
attempt: retryAttempt,
|
||||||
})
|
max: retryMax,
|
||||||
: null
|
})
|
||||||
const retryLineText =
|
: retryAttempt !== null
|
||||||
delayLabel !== null
|
? tAcp("claudeApiRetry.retryingAttempt", {
|
||||||
|
attempt: retryAttempt,
|
||||||
|
})
|
||||||
|
: tAcp("claudeApiRetry.retrying")
|
||||||
|
const delayLabel =
|
||||||
|
retryDelaySeconds !== null
|
||||||
|
? tAcp("claudeApiRetry.nextRetryIn", {
|
||||||
|
seconds: retryDelaySeconds,
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
|
||||||
|
return delayLabel !== null
|
||||||
? tAcp("claudeApiRetry.lineWithDelay", {
|
? tAcp("claudeApiRetry.lineWithDelay", {
|
||||||
error: errorLabel,
|
error: errorLabel,
|
||||||
status: statusLabel,
|
status: statusLabel,
|
||||||
@@ -159,6 +157,7 @@ export function ConversationShell({
|
|||||||
status: statusLabel,
|
status: statusLabel,
|
||||||
retry: retryLabel,
|
retry: retryLabel,
|
||||||
})
|
})
|
||||||
|
}, [claudeApiRetry, tAcp])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full min-h-0 flex-col">
|
<div className="flex h-full min-h-0 flex-col">
|
||||||
@@ -207,7 +206,7 @@ export function ConversationShell({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{claudeApiRetry && (
|
{retryLineText && (
|
||||||
<div className="border-t border-destructive/20 bg-destructive/5 px-4 py-2 text-xs text-destructive">
|
<div className="border-t border-destructive/20 bg-destructive/5 px-4 py-2 text-xs text-destructive">
|
||||||
<div className="flex items-center gap-2 font-medium">
|
<div className="flex items-center gap-2 font-medium">
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
|
|||||||
Reference in New Issue
Block a user