import { PayloadAction } from "@reduxjs/toolkit"; import { useEffect, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import Markdown from "react-markdown"; import { Link } from "react-router"; import remarkGfm from "remark-gfm"; import { useConfig } from "#/hooks/query/use-config"; import { I18nKey } from "#/i18n/declaration"; import ArrowDown from "#/icons/angle-down-solid.svg?react"; import ArrowUp from "#/icons/angle-up-solid.svg?react"; import CheckCircle from "#/icons/check-circle-solid.svg?react"; import XCircle from "#/icons/x-circle-solid.svg?react"; import { OpenHandsAction } from "#/types/core/actions"; import { OpenHandsObservation } from "#/types/core/observations"; import { cn } from "#/utils/utils"; import { code } from "../markdown/code"; import { ol, ul } from "../markdown/list"; import { paragraph } from "../markdown/paragraph"; import { MonoComponent } from "./mono-component"; import { PathComponent } from "./path-component"; const trimText = (text: string, maxLength: number): string => { if (!text) return ""; return text.length > maxLength ? `${text.substring(0, maxLength)}...` : text; }; interface ExpandableMessageProps { id?: string; message: string; type: string; success?: boolean; observation?: PayloadAction; action?: PayloadAction; } export function ExpandableMessage({ id, message, type, success, observation, action, }: ExpandableMessageProps) { const { data: config } = useConfig(); const { t, i18n } = useTranslation(); const [showDetails, setShowDetails] = useState(true); const [details, setDetails] = useState(message); const [translationId, setTranslationId] = useState(id); const [translationParams, setTranslationParams] = useState< Record >({ observation, action, }); useEffect(() => { // If we have a translation ID, process it if (id && i18n.exists(id)) { let processedObservation = observation; let processedAction = action; if (action && action.payload.action === "run") { const trimmedCommand = trimText(action.payload.args.command, 80); processedAction = { ...action, payload: { ...action.payload, args: { ...action.payload.args, command: trimmedCommand, }, }, }; } if (observation && observation.payload.observation === "run") { const trimmedCommand = trimText(observation.payload.extras.command, 80); processedObservation = { ...observation, payload: { ...observation.payload, extras: { ...observation.payload.extras, command: trimmedCommand, }, }, }; } setTranslationId(id); setTranslationParams({ observation: processedObservation, action: processedAction, }); setDetails(message); setShowDetails(false); } }, [id, message, observation, action, i18n.language]); const statusIconClasses = "h-4 w-4 ml-2 inline"; if ( config?.FEATURE_FLAGS.ENABLE_BILLING && config?.APP_MODE === "saas" && id === I18nKey.STATUS$ERROR_LLM_OUT_OF_CREDITS ) { return (
{t(I18nKey.STATUS$ERROR_LLM_OUT_OF_CREDITS)}
{t(I18nKey.BILLING$CLICK_TO_TOP_UP)}
); } return (
{translationId && i18n.exists(translationId) ? ( , path: , cmd: , }} /> ) : ( message )} {type === "action" && success !== undefined && ( {success ? ( ) : ( )} )}
{showDetails && (
{details}
)}
); }