oli-server 0.1.4

A simple, blazingly fast AI coding assistant server
Documentation
import React, { useEffect, useState, useMemo } from "react";
import { Box, Text } from "ink";
import { ToolExecution } from "../types/index.js";
import { ToolStatusIndicator } from "./ToolStatusIndicator.js";
import AnimatedSpinner from "./AnimatedSpinner.js";
import theme from "../styles/gruvbox.js";

interface StatusDisplayProps {
  toolExecutions: Map<string, ToolExecution>;
  isProcessing: boolean;
  onInterrupt: () => void;
}

const StatusDisplay: React.FC<StatusDisplayProps> = ({
  toolExecutions,
  isProcessing,
  /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
  onInterrupt,
}) => {
  // State to ensure smooth transitions
  const [isVisible, setIsVisible] = useState(false);
  const [elapsedTime, setElapsedTime] = useState(0);

  // Set up elapsed time counter for processing
  useEffect(() => {
    let timerId: NodeJS.Timeout | null = null;

    if (isProcessing) {
      setElapsedTime(0);
      // Update elapsed time every second
      timerId = setInterval(() => {
        setElapsedTime((prev) => prev + 1);
      }, 1000);
    } else {
      setElapsedTime(0);
    }

    return () => {
      if (timerId) clearInterval(timerId);
    };
  }, [isProcessing]);

  // Memoized active tool to prevent unnecessary rerenders
  const activeTool = useMemo(() => {
    // Only check when processing is happening
    if (!isProcessing) return null;

    // Find the most recently started running tool
    const runningTools = Array.from(toolExecutions.values())
      .filter((tool) => tool.status === "running")
      .sort((a, b) => b.startTime - a.startTime);

    return runningTools.length > 0 ? runningTools[0] : null;
  }, [toolExecutions, isProcessing]);

  // Format elapsed time nicely MM:SS
  const formattedTime = useMemo(() => {
    const mins = Math.floor(elapsedTime / 60);
    const secs = Math.floor(elapsedTime % 60);
    return `${mins}:${secs < 10 ? "0" : ""}${secs}`;
  }, [elapsedTime]);

  // Show/hide with animation effect
  useEffect(() => {
    if (isProcessing) {
      setIsVisible(true);
    } else {
      // Small delay before hiding to prevent flashing during quick operations
      const timer = setTimeout(() => {
        setIsVisible(false);
      }, 300);
      return () => clearTimeout(timer);
    }
  }, [isProcessing]);

  // Don't render anything when not visible
  if (!isVisible) return null;

  return (
    <Box flexDirection="row" marginY={1} marginX={1}>
      {/* Left side: Processing indicator with elapsed time and interrupt option */}
      <Box marginLeft={1} flexDirection="row" alignItems="center">
        <AnimatedSpinner color={theme.colors.dark.yellow} />
        <Box marginLeft={1} marginRight={1}>
          <Text color={theme.colors.dark.yellow}>
            {elapsedTime > 0 && `${formattedTime}`}
          </Text>
        </Box>
        <Text color={theme.colors.dark.red}>ESC to interrupt</Text>

        {/* Add a separator when there's also a tool showing */}
        {activeTool && (
          <Box marginX={2}>
            <Text color={theme.colors.dark.gray}>|</Text>
          </Box>
        )}
      </Box>

      {/* Tool status (if available) now positioned next to the timer */}
      {activeTool && (
        <ToolStatusIndicator
          status={activeTool.status}
          data={{
            name: activeTool.name,
            file_path: activeTool.metadata.file_path as string | undefined,
            lines: activeTool.metadata.lines as number | undefined,
            description:
              activeTool.message ||
              (activeTool.metadata.description as string | undefined),
          }}
          compact={true}
        />
      )}

      {/* Empty space to maintain layout */}
      <Box flexGrow={1} />
    </Box>
  );
};

export default React.memo(StatusDisplay);