171 lines · 5.5 KB · TypeScript
RawDownload
import React, { useState } from 'react';import ReactMarkdown from 'react-markdown'; import {  CODE_BG,  CODE_BORDER,  COLOR_TEXT_MUTED,  COLOR_TEXT_SECONDARY,  TOOL_CALL_BG,  TOOL_CALL_BORDER,  TOOL_CALL_TEXT,} from '@renderer/constants/cssVariables';import { formatTokensCompact as formatTokens } from '@shared/utils/tokenFormatting';import { format } from 'date-fns';import { ChevronRight, Layers } from 'lucide-react';import remarkGfm from 'remark-gfm'; import { CopyButton } from '../common/CopyButton'; import { markdownComponents } from './markdownComponents'; import type { CompactGroup } from '@renderer/types/groups'; interface CompactBoundaryProps {  compactGroup: CompactGroup;} /** * CompactBoundary displays an interactive, collapsible marker indicating where * the conversation was compacted. * * Features: * - Minimalist design with subtle border and hover states * - Click to expand/collapse compacted content * - Scrollable content area with enforced max-height * - Linear/Notion-inspired aesthetics */export const CompactBoundary = ({  compactGroup,}: Readonly<CompactBoundaryProps>): React.JSX.Element => {  const { timestamp, message } = compactGroup;  const [isExpanded, setIsExpanded] = useState(false);   // Extract content from message  const getCompactContent = (): string => {    if (!message?.content) return '';     if (typeof message.content === 'string') {      return message.content;    }     // If it's an array of content blocks, extract text    if (Array.isArray(message.content)) {      return message.content        .filter((block: { type: string; text?: string }) => block.type === 'text')        .map((block: { type: string; text?: string }) => block.text ?? '')        .join('\n\n');    }     return '';  };   const compactContent = getCompactContent();   return (    <div className="my-6">      {/* Collapsible Header - Amber/orange accent for distinction */}      <button        onClick={() => setIsExpanded(!isExpanded)}        className="group flex w-full cursor-pointer items-center gap-3 overflow-hidden rounded-lg px-4 py-2.5 transition-all duration-200"        style={{          backgroundColor: TOOL_CALL_BG,          border: `1px solid ${TOOL_CALL_BORDER}`,        }}        aria-expanded={isExpanded}        aria-label="Toggle compacted content"      >        {/* Icon Stack */}        <div          className="flex shrink-0 items-center gap-2 transition-colors"          style={{ color: TOOL_CALL_TEXT }}        >          <ChevronRight            size={16}            className={`transition-transform duration-200 ${isExpanded ? 'rotate-90' : ''}`}          />          <Layers size={16} />        </div>         {/* Label */}        <span          className="shrink-0 whitespace-nowrap text-sm font-medium transition-colors"          style={{ color: TOOL_CALL_TEXT }}        >          Compacted        </span>         {/* Token delta info */}        {compactGroup.tokenDelta && (          <span            className="ml-2 min-w-0 truncate text-xs tabular-nums"            style={{ color: COLOR_TEXT_MUTED }}          >            {formatTokens(compactGroup.tokenDelta.preCompactionTokens)} →{' '}            {formatTokens(compactGroup.tokenDelta.postCompactionTokens)}            <span style={{ color: '#4ade80' }}>              {' '}              ({formatTokens(Math.abs(compactGroup.tokenDelta.delta))} freed)            </span>          </span>        )}         {/* Phase badge */}        {compactGroup.startingPhaseNumber && (          <span            className="shrink-0 whitespace-nowrap rounded px-1.5 py-0.5 text-[10px]"            style={{ backgroundColor: 'rgba(99, 102, 241, 0.15)', color: '#818cf8' }}          >            Phase {compactGroup.startingPhaseNumber}          </span>        )}         {/* Timestamp */}        <span          className="ml-auto shrink-0 whitespace-nowrap text-xs transition-colors"          style={{ color: COLOR_TEXT_MUTED }}        >          {format(timestamp, 'h:mm:ss a')}        </span>      </button>       {/* Expanded Content */}      {isExpanded && (        <div          className="group relative mt-2 overflow-hidden rounded-lg"          style={{            backgroundColor: CODE_BG,            border: `1px solid ${CODE_BORDER}`,          }}        >          {compactContent && <CopyButton text={compactContent} />}           {/* Content - scrollable with left accent bar */}          <div            className="max-h-96 overflow-y-auto border-l-2 px-4 py-3"            style={{ borderColor: 'var(--chat-ai-border)' }}          >            {compactContent ? (              <ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>                {compactContent}              </ReactMarkdown>            ) : (              <div className="flex items-start gap-2">                <Layers size={14} className="mt-0.5 shrink-0" style={{ color: COLOR_TEXT_MUTED }} />                <div className="text-xs leading-relaxed" style={{ color: COLOR_TEXT_MUTED }}>                  <p className="mb-1 font-medium" style={{ color: COLOR_TEXT_SECONDARY }}>                    Conversation Compacted                  </p>                  <p>                    Previous messages were summarized to save context. The full conversation history                    is preserved in the session file.                  </p>                </div>              </div>            )}          </div>        </div>      )}    </div>  );};