export function getTimelineItemsfromConversation()

in x-pack/platform/packages/shared/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.tsx [62:329]


export function getTimelineItemsfromConversation({
  conversationId,
  chatService,
  currentUser,
  hasConnector,
  messages,
  chatState,
  isConversationOwnedByCurrentUser,
  onActionClick,
  isArchived,
}: {
  conversationId?: string;
  chatService: ObservabilityAIAssistantChatService;
  currentUser?: Pick<AuthenticatedUser, 'username' | 'full_name'>;
  hasConnector: boolean;
  messages: Message[];
  chatState: ChatState;
  isConversationOwnedByCurrentUser: boolean;
  onActionClick: ({
    message,
    payload,
  }: {
    message: Message;
    payload: ChatActionClickPayload;
  }) => void;
  isArchived: boolean;
}): ChatTimelineItem[] {
  const items: ChatTimelineItem[] = [
    {
      id: v4(),
      actions: { canCopy: false, canEdit: false, canGiveFeedback: false, canRegenerate: false },
      display: { collapsed: false, hide: false },
      currentUser,
      loading: false,
      message: {
        '@timestamp': new Date().toISOString(),
        message: {
          role: !!conversationId && !currentUser ? MessageRole.System : MessageRole.User,
        },
      },
      title: i18n.translate('xpack.aiAssistant.conversationStartTitle', {
        defaultMessage: 'started a conversation',
      }),
      role: !!conversationId && !currentUser ? MessageRole.System : MessageRole.User,
    },
    ...messages.map((message, index) => {
      const id = v4();

      let title: React.ReactNode = '';
      let content: string | undefined;
      let element: React.ReactNode | undefined;

      const prevFunctionCall =
        message.message.name && messages[index - 1] && messages[index - 1].message.function_call
          ? messages[index - 1].message.function_call
          : undefined;

      let role = message.message.function_call?.trigger || message.message.role;

      const actions = {
        canCopy: false,
        canEdit: false,
        canGiveFeedback: false,
        canRegenerate: false,
      };

      const display = {
        collapsed: false,
        hide: false,
      };

      switch (role) {
        case MessageRole.User:
          actions.canGiveFeedback = false;
          actions.canRegenerate = false;

          display.hide = false;

          // User executed a function:
          if (message.message.name && prevFunctionCall) {
            let isError = false;
            try {
              const parsedContent = JSON.parse(message.message.content ?? 'null');
              isError =
                parsedContent && typeof parsedContent === 'object' && 'error' in parsedContent;
            } catch (error) {
              isError = true;
            }

            title = !isError ? (
              <FormattedMessage
                id="xpack.aiAssistant.userExecutedFunctionEvent"
                defaultMessage="executed the function {functionName}"
                values={{
                  functionName: <FunctionName name={message.message.name} />,
                }}
              />
            ) : (
              <FormattedMessage
                id="xpack.aiAssistant.executedFunctionFailureEvent"
                defaultMessage="failed to execute the function {functionName}"
                values={{
                  functionName: <FunctionName name={message.message.name} />,
                }}
              />
            );

            element =
              !isError && chatService.hasRenderFunction(message.message.name) ? (
                <RenderFunction
                  name={message.message.name}
                  arguments={prevFunctionCall?.arguments}
                  response={message.message}
                  onActionClick={(payload) => {
                    onActionClick({ message, payload });
                  }}
                />
              ) : undefined;

            content = !element ? convertMessageToMarkdownCodeBlock(message.message) : undefined;

            if (prevFunctionCall?.trigger === MessageRole.Assistant) {
              role = MessageRole.Assistant;
            }

            actions.canEdit = false;
            display.collapsed = !element;
          } else if (message.message.function_call) {
            // User suggested a function
            title = (
              <FormattedMessage
                id="xpack.aiAssistant.userSuggestedFunctionEvent"
                defaultMessage="requested the function {functionName}"
                values={{
                  functionName: <FunctionName name={message.message.function_call.name} />,
                }}
              />
            );

            content = convertMessageToMarkdownCodeBlock(message.message);

            actions.canEdit = hasConnector && isConversationOwnedByCurrentUser && !isArchived;
            display.collapsed = true;
          } else {
            // is a prompt by the user
            title = '';
            content = message.message.content;

            actions.canEdit = hasConnector && isConversationOwnedByCurrentUser && !isArchived;
            display.collapsed = false;
          }

          if (!content) {
            actions.canCopy = false;
          } else {
            actions.canCopy = true;
          }

          break;

        case MessageRole.Assistant:
          actions.canRegenerate = hasConnector && isConversationOwnedByCurrentUser && !isArchived;
          actions.canGiveFeedback = isConversationOwnedByCurrentUser && !isArchived;
          display.hide = false;

          // is a function suggestion by the assistant
          if (message.message.function_call?.name) {
            title = (
              <FormattedMessage
                id="xpack.aiAssistant.suggestedFunctionEvent"
                defaultMessage="requested the function {functionName}"
                values={{
                  functionName: <FunctionName name={message.message.function_call.name} />,
                }}
              />
            );
            if (message.message.content) {
              // TODO: we want to show the content always, and hide
              // the function request initially, but we don't have a
              // way to do that yet, so we hide the request here until
              // we have a fix.
              // element = message.message.content;
              content = message.message.content;
              display.collapsed = false;
            } else {
              content = convertMessageToMarkdownCodeBlock(message.message);
              display.collapsed = true;
            }

            actions.canEdit = isConversationOwnedByCurrentUser && !isArchived;
          } else {
            // is an assistant response
            title = '';
            content = message.message.content;
            display.collapsed = false;
            actions.canEdit = false;
          }

          if (!content) {
            actions.canCopy = false;
          } else {
            actions.canCopy = true;
          }

          break;
      }

      return {
        id,
        role,
        title,
        content,
        element,
        actions,
        display,
        currentUser,
        function_call: message.message.function_call,
        loading: false,
        message,
      };
    }),
  ];

  const isLoading = chatState === ChatState.Loading;

  let lastMessage = last(items);

  const isNaturalLanguageOnlyAnswerFromAssistant =
    lastMessage?.message.message.role === MessageRole.Assistant &&
    !lastMessage.message.message.function_call?.name;

  const addLoadingPlaceholder = isLoading && !isNaturalLanguageOnlyAnswerFromAssistant;

  if (addLoadingPlaceholder) {
    items.push({
      id: v4(),
      actions: {
        canCopy: false,
        canEdit: false,
        canGiveFeedback: false,
        canRegenerate: false,
      },
      display: {
        collapsed: false,
        hide: false,
      },
      content: '',
      currentUser,
      loading: chatState === ChatState.Loading,
      role: MessageRole.Assistant,
      title: '',
      message: {
        '@timestamp': new Date().toISOString(),
        message: {
          role: MessageRole.Assistant,
          content: '',
        },
      },
    });
    lastMessage = last(items);
  }

  if (isLoading && lastMessage) {
    lastMessage.loading = isLoading;
  }

  return items;
}