All files / src/components/Chatbot CopilotSuggestions.tsx

95.38% Statements 62/65
94.11% Branches 16/17
100% Functions 2/2
95.38% Lines 62/65

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 911x 1x 1x     1x     1x     1x     1x   1x 6x   6x   6x 6x 6x   6x   6x   6x         5x 1x 5x   5x 6x   6x 1x 1x 1x 1x 1x 1x 1x   6x 6x 6x   6x   6x 6x 6x 10x   10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 5x 5x 10x   10x 5x 5x 5x 5x   10x 10x   10x 6x 6x 6x   6x  
import { useCopilotChat } from '@copilotkit/react-core';
import { Role, TextMessage } from '@copilotkit/runtime-client-gql';
import { useEffect, useState } from 'react';
 
// Constants
import { CHATBOT_SUGGESTIONS, SUGGESTION_LOADING_DELAY } from '@/constants';
 
// Utils
import { combineClasses } from '@shared/utils';
 
// Icons
import { LoadingIcon } from '../icons/reacts';
 
// Stores
import { useShouldLoadSuggestions, useSetSuggestionsLoaded } from '@/stores';
 
export const CopilotSuggestions = () => {
  const { appendMessage } = useCopilotChat();
 
  const shouldLoadSuggestions = useShouldLoadSuggestions();
 
  const [activeIndex, setActiveIndex] = useState(
    shouldLoadSuggestions ? CHATBOT_SUGGESTIONS.length : 0,
  );
 
  useEffect(() => {
    // Skip animation if suggestions already loaded
    if (shouldLoadSuggestions) return;
 
    if (activeIndex >= CHATBOT_SUGGESTIONS.length) {
      useSetSuggestionsLoaded(true);
      return;
    }
 
    const timer = setTimeout(() => {
      setActiveIndex((prev) => prev + 1);
    }, SUGGESTION_LOADING_DELAY);
 
    return () => clearTimeout(timer);
  }, [activeIndex, shouldLoadSuggestions]);
 
  const handleSuggestionClick = (prompt: string) => () => {
    appendMessage(
      new TextMessage({
        role: Role.User,
        content: prompt,
      }),
    );
  };
 
  return (
    <div className="px-5 py-2 w-[370px]">
      <p className="mb-2 text-3xs text-muted-foreground dark:text-background font-medium">
        Suggested:
      </p>
 
      <div className="flex flex-wrap gap-2">
        {CHATBOT_SUGGESTIONS.slice(0, activeIndex + 1).map(
          ({ id, label, prompt }, index) => {
            const isLoading = index === activeIndex;
 
            return (
              <button
                key={id}
                disabled={isLoading}
                onClick={handleSuggestionClick(prompt)}
                className={combineClasses(
                  'inline-flex items-center',
                  'h-8 px-3 rounded-md',
                  'bg-primary-700 text-3xs font-medium',
                  isLoading
                    ? 'text-white/60 cursor-default'
                    : 'text-white hover:opacity-90',
                )}
              >
                {isLoading && (
                  <LoadingIcon
                    className="h-3.5 w-3.5 mr-2"
                    data-testid="loading-icon"
                  />
                )}
                <span>{label}</span>
              </button>
            );
          },
        )}
      </div>
    </div>
  );
};