- Added support for a new worker in the team file system to list teams, improving performance and reliability in team management. - Introduced event loop lag monitoring in various IPC handlers to track and log slow operations, enhancing observability. - Implemented caching for CLI installation status to reduce redundant calls and improve responsiveness. - Updated project and team data services to include total session counts, optimizing data handling and performance. - Enhanced error handling and logging across multiple services to provide clearer insights into performance issues and failures. Made-with: Cursor
144 lines
4.7 KiB
TypeScript
144 lines
4.7 KiB
TypeScript
import React from 'react';
|
|
|
|
import {
|
|
getHighlightProps,
|
|
HIGHLIGHT_CLASSES,
|
|
isPresetColorKey,
|
|
type TriggerColor,
|
|
} from '@shared/constants/triggerColors';
|
|
|
|
import { AIChatGroup } from './AIChatGroup';
|
|
import { CompactBoundary } from './CompactBoundary';
|
|
import { SystemChatGroup } from './SystemChatGroup';
|
|
import { UserChatGroup } from './UserChatGroup';
|
|
|
|
import type { ChatItem } from '@renderer/types/groups';
|
|
|
|
interface ChatHistoryItemProps {
|
|
readonly item: ChatItem;
|
|
readonly highlightedGroupId: string | null;
|
|
readonly highlightToolUseId?: string;
|
|
readonly isSearchHighlight: boolean;
|
|
readonly isNavigationHighlight: boolean;
|
|
readonly highlightColor?: TriggerColor;
|
|
/** Whether this item just appeared (triggers enter animation) */
|
|
readonly isNew?: boolean;
|
|
readonly registerChatItemRef: (groupId: string) => (el: HTMLElement | null) => void;
|
|
readonly registerAIGroupRef: (groupId: string) => (el: HTMLElement | null) => void;
|
|
/** Register ref for individual tool items (for precise scroll targeting) */
|
|
readonly registerToolRef: (toolId: string, el: HTMLElement | null) => void;
|
|
}
|
|
|
|
/**
|
|
* Get highlight class/style based on type: search (yellow), navigation (blue), error (custom color)
|
|
*/
|
|
function getHighlight(
|
|
isHighlighted: boolean,
|
|
isSearchHighlight: boolean,
|
|
isNavigationHighlight: boolean,
|
|
highlightColor?: TriggerColor
|
|
): { className: string; style?: React.CSSProperties } {
|
|
if (!isHighlighted) return { className: 'ring-0 bg-transparent' };
|
|
if (isSearchHighlight) return { className: 'ring-2 ring-yellow-500/30 bg-yellow-500/5' };
|
|
if (isNavigationHighlight) return { className: 'ring-2 ring-blue-500/30 bg-blue-500/5' };
|
|
const key = highlightColor ?? 'red';
|
|
if (isPresetColorKey(key)) return { className: HIGHLIGHT_CLASSES[key] };
|
|
return getHighlightProps(key);
|
|
}
|
|
|
|
/**
|
|
* Renders a single chat history item (user, system, ai, or compact).
|
|
*/
|
|
const ChatHistoryItemInner = ({
|
|
item,
|
|
highlightedGroupId,
|
|
highlightToolUseId,
|
|
isSearchHighlight,
|
|
isNavigationHighlight,
|
|
highlightColor,
|
|
isNew,
|
|
registerChatItemRef,
|
|
registerAIGroupRef,
|
|
registerToolRef,
|
|
}: ChatHistoryItemProps): JSX.Element | null => {
|
|
const enterClass = isNew ? 'chat-message-enter-animate' : '';
|
|
const transitionStyle: React.CSSProperties = { transitionDuration: '3000ms' };
|
|
|
|
switch (item.type) {
|
|
case 'user': {
|
|
const isHighlighted = highlightedGroupId === item.group.id;
|
|
const hl = getHighlight(
|
|
isHighlighted,
|
|
isSearchHighlight,
|
|
isNavigationHighlight,
|
|
highlightColor
|
|
);
|
|
return (
|
|
<div
|
|
ref={registerChatItemRef(item.group.id)}
|
|
className={`rounded-lg transition-all ease-out ${hl.className} ${enterClass}`}
|
|
style={{ ...transitionStyle, ...(hl.style ?? {}) }}
|
|
>
|
|
<UserChatGroup userGroup={item.group} />
|
|
</div>
|
|
);
|
|
}
|
|
case 'system': {
|
|
const isHighlighted = highlightedGroupId === item.group.id;
|
|
const hl = getHighlight(
|
|
isHighlighted,
|
|
isSearchHighlight,
|
|
isNavigationHighlight,
|
|
highlightColor
|
|
);
|
|
return (
|
|
<div
|
|
ref={registerChatItemRef(item.group.id)}
|
|
className={`rounded-lg transition-all ease-out ${hl.className} ${enterClass}`}
|
|
style={{ ...transitionStyle, ...(hl.style ?? {}) }}
|
|
>
|
|
<SystemChatGroup systemGroup={item.group} />
|
|
</div>
|
|
);
|
|
}
|
|
case 'ai': {
|
|
const isHighlighted = highlightedGroupId === item.group.id;
|
|
// Pass highlightToolUseId to ALL AI groups (when not search highlight)
|
|
// Each group will check if it contains the tool and expand accordingly
|
|
// Allowed during navigation highlights so context panel tool deep-linking works
|
|
const toolUseIdForGroup = !isSearchHighlight ? highlightToolUseId : undefined;
|
|
const hl = getHighlight(
|
|
isHighlighted,
|
|
isSearchHighlight,
|
|
isNavigationHighlight,
|
|
highlightColor
|
|
);
|
|
return (
|
|
<div
|
|
ref={registerAIGroupRef(item.group.id)}
|
|
className={`rounded-lg transition-all ease-out ${hl.className} ${enterClass}`}
|
|
style={{ ...transitionStyle, ...(hl.style ?? {}) }}
|
|
>
|
|
<AIChatGroup
|
|
aiGroup={item.group}
|
|
highlightToolUseId={toolUseIdForGroup}
|
|
highlightColor={highlightColor}
|
|
registerToolRef={registerToolRef}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
case 'compact':
|
|
return isNew ? (
|
|
<div className={enterClass}>
|
|
<CompactBoundary compactGroup={item.group} />
|
|
</div>
|
|
) : (
|
|
<CompactBoundary compactGroup={item.group} />
|
|
);
|
|
default:
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export const ChatHistoryItem = React.memo(ChatHistoryItemInner);
|