perf: memo chat items, tool viewers, CollapsibleTeamSection, TaskTooltip
Wraps BaseItem, StatusDot, MetricsPill, LinkedToolItem, TeammateMessageItem, SlashItem, all linkedTool viewers (Default/Edit/Read/Skill/Write/ToolError, CollapsibleOutputSection), CollapsibleTeamSection, and TaskTooltip in React.memo to prevent unnecessary re-renders when chat history updates.
This commit is contained in:
parent
193def1d59
commit
5d6667b23d
14 changed files with 749 additions and 719 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
import { TOOL_ITEM_MUTED } from '@renderer/constants/cssVariables';
|
import { TOOL_ITEM_MUTED } from '@renderer/constants/cssVariables';
|
||||||
import { getTriggerColorDef, type TriggerColor } from '@shared/constants/triggerColors';
|
import { getTriggerColorDef, type TriggerColor } from '@shared/constants/triggerColors';
|
||||||
|
|
@ -57,14 +57,14 @@ interface BaseItemProps {
|
||||||
/**
|
/**
|
||||||
* Small status dot indicator.
|
* Small status dot indicator.
|
||||||
*/
|
*/
|
||||||
export const StatusDot: React.FC<{ status: ItemStatus }> = ({ status }) => {
|
export const StatusDot = memo(function StatusDot({ status }: { status: ItemStatus }) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className="base-item-status-dot inline-block size-1.5 shrink-0 rounded-full"
|
className="base-item-status-dot inline-block size-1.5 shrink-0 rounded-full"
|
||||||
style={{ backgroundColor: getStatusDotColor(status) }}
|
style={{ backgroundColor: getStatusDotColor(status) }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Main Component
|
// Main Component
|
||||||
|
|
@ -79,7 +79,8 @@ export const StatusDot: React.FC<{ status: ItemStatus }> = ({ status }) => {
|
||||||
*
|
*
|
||||||
* Used by: ThinkingItem, TextItem, LinkedToolItem, SlashItem, SubagentItem
|
* Used by: ThinkingItem, TextItem, LinkedToolItem, SlashItem, SubagentItem
|
||||||
*/
|
*/
|
||||||
export const BaseItem: React.FC<BaseItemProps> = ({
|
export const BaseItem = memo(
|
||||||
|
({
|
||||||
icon,
|
icon,
|
||||||
label,
|
label,
|
||||||
summary,
|
summary,
|
||||||
|
|
@ -97,7 +98,7 @@ export const BaseItem: React.FC<BaseItemProps> = ({
|
||||||
highlightStyle,
|
highlightStyle,
|
||||||
notificationDotColor,
|
notificationDotColor,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}: BaseItemProps): React.JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`rounded transition-[background-color,box-shadow] duration-300 ${highlightClasses}`}
|
className={`rounded transition-[background-color,box-shadow] duration-300 ${highlightClasses}`}
|
||||||
|
|
@ -140,7 +141,10 @@ export const BaseItem: React.FC<BaseItemProps> = ({
|
||||||
<span className="text-sm" style={{ color: TOOL_ITEM_MUTED }}>
|
<span className="text-sm" style={{ color: TOOL_ITEM_MUTED }}>
|
||||||
-
|
-
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 truncate text-sm" style={{ color: 'var(--tool-item-summary)' }}>
|
<span
|
||||||
|
className="flex-1 truncate text-sm"
|
||||||
|
style={{ color: 'var(--tool-item-summary)' }}
|
||||||
|
>
|
||||||
{summary}
|
{summary}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
|
|
@ -210,4 +214,5 @@ export const BaseItem: React.FC<BaseItemProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
* for summary generation and token calculation.
|
* for summary generation and token calculation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useRef } from 'react';
|
import React, { memo, useRef } from 'react';
|
||||||
|
|
||||||
import { CARD_ICON_MUTED } from '@renderer/constants/cssVariables';
|
import { CARD_ICON_MUTED } from '@renderer/constants/cssVariables';
|
||||||
import { getTeamColorSet, getThemedBadge } from '@renderer/constants/teamColors';
|
import { getTeamColorSet, getThemedBadge } from '@renderer/constants/teamColors';
|
||||||
|
|
@ -64,7 +64,8 @@ interface LinkedToolItemProps {
|
||||||
titleText?: string;
|
titleText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LinkedToolItem: React.FC<LinkedToolItemProps> = ({
|
export const LinkedToolItem = memo(
|
||||||
|
({
|
||||||
linkedTool,
|
linkedTool,
|
||||||
onClick,
|
onClick,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
|
|
@ -76,7 +77,7 @@ export const LinkedToolItem: React.FC<LinkedToolItemProps> = ({
|
||||||
notificationDotColor,
|
notificationDotColor,
|
||||||
registerRef,
|
registerRef,
|
||||||
titleText,
|
titleText,
|
||||||
}) => {
|
}: LinkedToolItemProps): React.JSX.Element => {
|
||||||
const status = getToolStatus(linkedTool);
|
const status = getToolStatus(linkedTool);
|
||||||
const { isLight } = useTheme();
|
const { isLight } = useTheme();
|
||||||
const summary = getToolSummary(linkedTool.name, linkedTool.input);
|
const summary = getToolSummary(linkedTool.name, linkedTool.input);
|
||||||
|
|
@ -233,4 +234,5 @@ export const LinkedToolItem: React.FC<LinkedToolItemProps> = ({
|
||||||
</BaseItem>
|
</BaseItem>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { memo, useEffect, useRef, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -41,7 +41,8 @@ interface MetricsPillProps {
|
||||||
// Unified Metrics Pill - Compact monospace pill with tooltip
|
// Unified Metrics Pill - Compact monospace pill with tooltip
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
export const MetricsPill = ({
|
export const MetricsPill = memo(
|
||||||
|
({
|
||||||
mainSessionImpact,
|
mainSessionImpact,
|
||||||
lastUsage,
|
lastUsage,
|
||||||
isolatedLabel,
|
isolatedLabel,
|
||||||
|
|
@ -212,4 +213,5 @@ export const MetricsPill = ({
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
import { Slash } from 'lucide-react';
|
import { Slash } from 'lucide-react';
|
||||||
|
|
||||||
|
|
@ -34,7 +34,8 @@ interface SlashItemProps {
|
||||||
* - MCP commands
|
* - MCP commands
|
||||||
* - User-defined commands
|
* - User-defined commands
|
||||||
*/
|
*/
|
||||||
export const SlashItem: React.FC<SlashItemProps> = ({
|
export const SlashItem = memo(
|
||||||
|
({
|
||||||
slash,
|
slash,
|
||||||
onClick,
|
onClick,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
|
|
@ -44,7 +45,7 @@ export const SlashItem: React.FC<SlashItemProps> = ({
|
||||||
highlightStyle,
|
highlightStyle,
|
||||||
notificationDotColor,
|
notificationDotColor,
|
||||||
titleText,
|
titleText,
|
||||||
}) => {
|
}: SlashItemProps): React.JSX.Element => {
|
||||||
const hasInstructions = !!slash.instructions;
|
const hasInstructions = !!slash.instructions;
|
||||||
|
|
||||||
// Display args or message as the description
|
// Display args or message as the description
|
||||||
|
|
@ -78,4 +79,5 @@ export const SlashItem: React.FC<SlashItemProps> = ({
|
||||||
)}
|
)}
|
||||||
</BaseItem>
|
</BaseItem>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CARD_BG,
|
CARD_BG,
|
||||||
|
|
@ -75,14 +75,15 @@ function isResendMessage(message: TeammateMessage): boolean {
|
||||||
*
|
*
|
||||||
* Operational noise (idle/shutdown/terminated) renders as minimal inline text.
|
* Operational noise (idle/shutdown/terminated) renders as minimal inline text.
|
||||||
*/
|
*/
|
||||||
export const TeammateMessageItem: React.FC<TeammateMessageItemProps> = ({
|
export const TeammateMessageItem = memo(
|
||||||
|
({
|
||||||
teammateMessage,
|
teammateMessage,
|
||||||
onClick,
|
onClick,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
onReplyHover,
|
onReplyHover,
|
||||||
highlightClasses = '',
|
highlightClasses = '',
|
||||||
highlightStyle,
|
highlightStyle,
|
||||||
}) => {
|
}: TeammateMessageItemProps): React.JSX.Element => {
|
||||||
const colors = getTeamColorSet(teammateMessage.color);
|
const colors = getTeamColorSet(teammateMessage.color);
|
||||||
const { isLight } = useTheme();
|
const { isLight } = useTheme();
|
||||||
|
|
||||||
|
|
@ -132,7 +133,10 @@ export const TeammateMessageItem: React.FC<TeammateMessageItemProps> = ({
|
||||||
if (noiseLabel) {
|
if (noiseLabel) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 px-3 py-1" style={{ opacity: 0.45 }}>
|
<div className="flex items-center gap-2 px-3 py-1" style={{ opacity: 0.45 }}>
|
||||||
<span className="size-2 shrink-0 rounded-full" style={{ backgroundColor: colors.border }} />
|
<span
|
||||||
|
className="size-2 shrink-0 rounded-full"
|
||||||
|
style={{ backgroundColor: colors.border }}
|
||||||
|
/>
|
||||||
<span className="text-[11px]" style={{ color: CARD_ICON_MUTED }}>
|
<span className="text-[11px]" style={{ color: CARD_ICON_MUTED }}>
|
||||||
{teammateMessage.teammateId}
|
{teammateMessage.teammateId}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -258,4 +262,5 @@ export const TeammateMessageItem: React.FC<TeammateMessageItemProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Shows a clickable header with label, StatusDot, and chevron toggle.
|
* Shows a clickable header with label, StatusDot, and chevron toggle.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { memo, useState } from 'react';
|
||||||
|
|
||||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||||
|
|
||||||
|
|
@ -18,11 +18,8 @@ interface CollapsibleOutputSectionProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CollapsibleOutputSection: React.FC<CollapsibleOutputSectionProps> = ({
|
export const CollapsibleOutputSection = memo(
|
||||||
status,
|
({ status, children, label = 'Output' }: CollapsibleOutputSectionProps): React.JSX.Element => {
|
||||||
children,
|
|
||||||
label = 'Output',
|
|
||||||
}) => {
|
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -30,7 +27,13 @@ export const CollapsibleOutputSection: React.FC<CollapsibleOutputSectionProps> =
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="mb-1 flex items-center gap-2 text-xs"
|
className="mb-1 flex items-center gap-2 text-xs"
|
||||||
style={{ color: 'var(--tool-item-muted)', background: 'none', border: 'none', padding: 0, cursor: 'pointer' }}
|
style={{
|
||||||
|
color: 'var(--tool-item-muted)',
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
padding: 0,
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
onClick={() => setIsExpanded((prev) => !prev)}
|
onClick={() => setIsExpanded((prev) => !prev)}
|
||||||
>
|
>
|
||||||
{isExpanded ? <ChevronDown className="size-3" /> : <ChevronRight className="size-3" />}
|
{isExpanded ? <ChevronDown className="size-3" /> : <ChevronRight className="size-3" />}
|
||||||
|
|
@ -54,4 +57,5 @@ export const CollapsibleOutputSection: React.FC<CollapsibleOutputSectionProps> =
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* Default rendering for tools that don't have specialized viewers.
|
* Default rendering for tools that don't have specialized viewers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
import { type ItemStatus } from '../BaseItem';
|
import { type ItemStatus } from '../BaseItem';
|
||||||
|
|
||||||
|
|
@ -23,7 +23,10 @@ interface DefaultToolViewerProps {
|
||||||
status: ItemStatus;
|
status: ItemStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DefaultToolViewer: React.FC<DefaultToolViewerProps> = ({ linkedTool, status }) => {
|
export const DefaultToolViewer = memo(function DefaultToolViewer({
|
||||||
|
linkedTool,
|
||||||
|
status,
|
||||||
|
}: DefaultToolViewerProps) {
|
||||||
const displayOutputContent = linkedTool.result
|
const displayOutputContent = linkedTool.result
|
||||||
? formatToolOutputForDisplay(linkedTool.name, linkedTool.result.content)
|
? formatToolOutputForDisplay(linkedTool.name, linkedTool.result.content)
|
||||||
: null;
|
: null;
|
||||||
|
|
@ -64,4 +67,4 @@ export const DefaultToolViewer: React.FC<DefaultToolViewerProps> = ({ linkedTool
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* Renders the Edit tool with DiffViewer.
|
* Renders the Edit tool with DiffViewer.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
import { DiffViewer } from '@renderer/components/chat/viewers';
|
import { DiffViewer } from '@renderer/components/chat/viewers';
|
||||||
|
|
||||||
|
|
@ -20,7 +20,10 @@ interface EditToolViewerProps {
|
||||||
status: ItemStatus;
|
status: ItemStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditToolViewer: React.FC<EditToolViewerProps> = ({ linkedTool, status }) => {
|
export const EditToolViewer = memo(function EditToolViewer({
|
||||||
|
linkedTool,
|
||||||
|
status,
|
||||||
|
}: EditToolViewerProps) {
|
||||||
const toolUseResult = linkedTool.result?.toolUseResult as Record<string, unknown> | undefined;
|
const toolUseResult = linkedTool.result?.toolUseResult as Record<string, unknown> | undefined;
|
||||||
|
|
||||||
const filePath = (toolUseResult?.filePath as string) || (linkedTool.input.file_path as string);
|
const filePath = (toolUseResult?.filePath as string) || (linkedTool.input.file_path as string);
|
||||||
|
|
@ -71,4 +74,4 @@ export const EditToolViewer: React.FC<EditToolViewerProps> = ({ linkedTool, stat
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* Renders the Read tool result using CodeBlockViewer.
|
* Renders the Read tool result using CodeBlockViewer.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
import { CodeBlockViewer, MarkdownViewer } from '@renderer/components/chat/viewers';
|
import { CodeBlockViewer, MarkdownViewer } from '@renderer/components/chat/viewers';
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ interface ReadToolViewerProps {
|
||||||
linkedTool: LinkedToolItem;
|
linkedTool: LinkedToolItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReadToolViewer: React.FC<ReadToolViewerProps> = ({ linkedTool }) => {
|
export const ReadToolViewer = memo(function ReadToolViewer({ linkedTool }: ReadToolViewerProps) {
|
||||||
const filePath = linkedTool.input.file_path as string;
|
const filePath = linkedTool.input.file_path as string;
|
||||||
|
|
||||||
// Prefer enriched toolUseResult data
|
// Prefer enriched toolUseResult data
|
||||||
|
|
@ -55,7 +55,9 @@ export const ReadToolViewer: React.FC<ReadToolViewerProps> = ({ linkedTool }) =>
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const isMarkdownFile = /\.mdx?$/i.test(filePath);
|
const isMarkdownFile = /\.mdx?$/i.test(filePath);
|
||||||
const [viewMode, setViewMode] = React.useState<'code' | 'preview'>(isMarkdownFile ? 'preview' : 'code');
|
const [viewMode, setViewMode] = React.useState<'code' | 'preview'>(
|
||||||
|
isMarkdownFile ? 'preview' : 'code'
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
@ -99,4 +101,4 @@ export const ReadToolViewer: React.FC<ReadToolViewerProps> = ({ linkedTool }) =>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* Renders the Skill tool with its instructions in a code block viewer style.
|
* Renders the Skill tool with its instructions in a code block viewer style.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
import { CodeBlockViewer } from '@renderer/components/chat/viewers';
|
import { CodeBlockViewer } from '@renderer/components/chat/viewers';
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ interface SkillToolViewerProps {
|
||||||
linkedTool: LinkedToolItem;
|
linkedTool: LinkedToolItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SkillToolViewer: React.FC<SkillToolViewerProps> = ({ linkedTool }) => {
|
export const SkillToolViewer = memo(function SkillToolViewer({ linkedTool }: SkillToolViewerProps) {
|
||||||
const skillInstructions = linkedTool.skillInstructions;
|
const skillInstructions = linkedTool.skillInstructions;
|
||||||
const skillName = (linkedTool.input.skill as string) || 'Unknown Skill';
|
const skillName = (linkedTool.input.skill as string) || 'Unknown Skill';
|
||||||
|
|
||||||
|
|
@ -64,4 +64,4 @@ export const SkillToolViewer: React.FC<SkillToolViewerProps> = ({ linkedTool })
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* Displays error output for tool results.
|
* Displays error output for tool results.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
import { StatusDot } from '../BaseItem';
|
import { StatusDot } from '../BaseItem';
|
||||||
|
|
||||||
|
|
@ -16,7 +16,9 @@ interface ToolErrorDisplayProps {
|
||||||
linkedTool: LinkedToolItem;
|
linkedTool: LinkedToolItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToolErrorDisplay: React.FC<ToolErrorDisplayProps> = ({ linkedTool }) => {
|
export const ToolErrorDisplay = memo(function ToolErrorDisplay({
|
||||||
|
linkedTool,
|
||||||
|
}: ToolErrorDisplayProps) {
|
||||||
if (!linkedTool.result?.isError) return null;
|
if (!linkedTool.result?.isError) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -40,4 +42,4 @@ export const ToolErrorDisplay: React.FC<ToolErrorDisplayProps> = ({ linkedTool }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* Renders the Write tool result.
|
* Renders the Write tool result.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
import { CodeBlockViewer, MarkdownViewer } from '@renderer/components/chat/viewers';
|
import { CodeBlockViewer, MarkdownViewer } from '@renderer/components/chat/viewers';
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ interface WriteToolViewerProps {
|
||||||
linkedTool: LinkedToolItem;
|
linkedTool: LinkedToolItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WriteToolViewer: React.FC<WriteToolViewerProps> = ({ linkedTool }) => {
|
export const WriteToolViewer = memo(function WriteToolViewer({ linkedTool }: WriteToolViewerProps) {
|
||||||
const toolUseResult = linkedTool.result?.toolUseResult as Record<string, unknown> | undefined;
|
const toolUseResult = linkedTool.result?.toolUseResult as Record<string, unknown> | undefined;
|
||||||
|
|
||||||
const filePath =
|
const filePath =
|
||||||
|
|
@ -74,4 +74,4 @@ export const WriteToolViewer: React.FC<WriteToolViewerProps> = ({ linkedTool })
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { Badge } from '@renderer/components/ui/badge';
|
import { Badge } from '@renderer/components/ui/badge';
|
||||||
import { cn } from '@renderer/lib/utils';
|
import { cn } from '@renderer/lib/utils';
|
||||||
|
|
@ -44,7 +44,7 @@ interface CollapsibleTeamSectionProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CollapsibleTeamSection = ({
|
export const CollapsibleTeamSection = memo(function CollapsibleTeamSection({
|
||||||
title,
|
title,
|
||||||
icon,
|
icon,
|
||||||
badge,
|
badge,
|
||||||
|
|
@ -63,7 +63,7 @@ export const CollapsibleTeamSection = ({
|
||||||
headerSurfaceClassName,
|
headerSurfaceClassName,
|
||||||
keepMounted,
|
keepMounted,
|
||||||
children,
|
children,
|
||||||
}: CollapsibleTeamSectionProps): React.JSX.Element => {
|
}: CollapsibleTeamSectionProps): React.JSX.Element {
|
||||||
const [open, setOpen] = useState(defaultOpen);
|
const [open, setOpen] = useState(defaultOpen);
|
||||||
const isOpen = forceOpen ? true : open;
|
const isOpen = forceOpen ? true : open;
|
||||||
const sectionRef = useRef<HTMLElement>(null);
|
const sectionRef = useRef<HTMLElement>(null);
|
||||||
|
|
@ -174,4 +174,4 @@ export const CollapsibleTeamSection = ({
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
|
|
||||||
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
|
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
|
||||||
import { MemberBadge } from '@renderer/components/team/MemberBadge';
|
import { MemberBadge } from '@renderer/components/team/MemberBadge';
|
||||||
|
|
@ -65,12 +65,12 @@ interface TaskTooltipProps {
|
||||||
* Tooltip that shows task summary on hover over any #taskId link.
|
* Tooltip that shows task summary on hover over any #taskId link.
|
||||||
* Reads task data from the current team in the store.
|
* Reads task data from the current team in the store.
|
||||||
*/
|
*/
|
||||||
export const TaskTooltip = ({
|
export const TaskTooltip = memo(function TaskTooltip({
|
||||||
taskId,
|
taskId,
|
||||||
teamName,
|
teamName,
|
||||||
children,
|
children,
|
||||||
side = 'top',
|
side = 'top',
|
||||||
}: TaskTooltipProps): React.JSX.Element => {
|
}: TaskTooltipProps): React.JSX.Element {
|
||||||
const { selectedTeamName, selectedTeamData, selectedTeamMembers, globalTasks, teamByName } =
|
const { selectedTeamName, selectedTeamData, selectedTeamMembers, globalTasks, teamByName } =
|
||||||
useStore(
|
useStore(
|
||||||
useShallow((s) => ({
|
useShallow((s) => ({
|
||||||
|
|
@ -194,4 +194,4 @@ export const TaskTooltip = ({
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue