mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
fix(frontend): the content of the FinishObservation event is not being rendered correctly. (#11846)
This commit is contained in:
parent
96f13b15e7
commit
6c821ab73e
@ -1,15 +1,9 @@
|
||||
import React from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import { code } from "../markdown/code";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { ul, ol } from "../markdown/list";
|
||||
import { CopyToClipboardButton } from "#/components/shared/buttons/copy-to-clipboard-button";
|
||||
import { anchor } from "../markdown/anchor";
|
||||
import { OpenHandsSourceType } from "#/types/core/base";
|
||||
import { paragraph } from "../markdown/paragraph";
|
||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
||||
import { MarkdownRenderer } from "../markdown/markdown-renderer";
|
||||
|
||||
interface ChatMessageProps {
|
||||
type: OpenHandsSourceType;
|
||||
@ -116,18 +110,7 @@ export function ChatMessage({
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
<Markdown
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
a: anchor,
|
||||
p: paragraph,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
>
|
||||
{message}
|
||||
</Markdown>
|
||||
<MarkdownRenderer includeStandard>{message}</MarkdownRenderer>
|
||||
</div>
|
||||
{children}
|
||||
</article>
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
import React from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { code } from "../markdown/code";
|
||||
import { ol, ul } from "../markdown/list";
|
||||
import ArrowDown from "#/icons/angle-down-solid.svg?react";
|
||||
import ArrowUp from "#/icons/angle-up-solid.svg?react";
|
||||
import i18n from "#/i18n";
|
||||
import { MarkdownRenderer } from "../markdown/markdown-renderer";
|
||||
|
||||
interface ErrorMessageProps {
|
||||
errorId?: string;
|
||||
@ -40,18 +36,7 @@ export function ErrorMessage({ errorId, defaultMessage }: ErrorMessageProps) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showDetails && (
|
||||
<Markdown
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
>
|
||||
{defaultMessage}
|
||||
</Markdown>
|
||||
)}
|
||||
{showDetails && <MarkdownRenderer>{defaultMessage}</MarkdownRenderer>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
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 remarkBreaks from "remark-breaks";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import ArrowDown from "#/icons/angle-down-solid.svg?react";
|
||||
@ -13,9 +10,7 @@ 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 { MarkdownRenderer } from "../markdown/markdown-renderer";
|
||||
import { MonoComponent } from "./mono-component";
|
||||
import { PathComponent } from "./path-component";
|
||||
|
||||
@ -192,17 +187,7 @@ export function ExpandableMessage({
|
||||
</div>
|
||||
{showDetails && (
|
||||
<div className="text-sm">
|
||||
<Markdown
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
p: paragraph,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
>
|
||||
{details}
|
||||
</Markdown>
|
||||
<MarkdownRenderer includeStandard>{details}</MarkdownRenderer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
import React from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import { code } from "../markdown/code";
|
||||
import { ol, ul } from "../markdown/list";
|
||||
import ArrowDown from "#/icons/angle-down-solid.svg?react";
|
||||
import ArrowUp from "#/icons/angle-up-solid.svg?react";
|
||||
import { SuccessIndicator } from "./success-indicator";
|
||||
import { ObservationResultStatus } from "./event-content-helpers/get-observation-result";
|
||||
import { MarkdownRenderer } from "../markdown/markdown-renderer";
|
||||
|
||||
interface GenericEventMessageProps {
|
||||
title: React.ReactNode;
|
||||
@ -49,16 +45,7 @@ export function GenericEventMessage({
|
||||
|
||||
{showDetails &&
|
||||
(typeof details === "string" ? (
|
||||
<Markdown
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
>
|
||||
{details}
|
||||
</Markdown>
|
||||
<MarkdownRenderer>{details}</MarkdownRenderer>
|
||||
) : (
|
||||
details
|
||||
))}
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
import Markdown, { Components } from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import { code } from "./code";
|
||||
import { ul, ol } from "./list";
|
||||
import { paragraph } from "./paragraph";
|
||||
import { anchor } from "./anchor";
|
||||
import { h1, h2, h3, h4, h5, h6 } from "./headings";
|
||||
|
||||
interface MarkdownRendererProps {
|
||||
/**
|
||||
* The markdown content to render. Can be passed as children (string) or content prop.
|
||||
*/
|
||||
children?: string;
|
||||
content?: string;
|
||||
/**
|
||||
* Additional or override components for markdown elements.
|
||||
* Default components (code, ul, ol) are always included unless overridden.
|
||||
*/
|
||||
components?: Partial<Components>;
|
||||
/**
|
||||
* Whether to include standard components (anchor, paragraph).
|
||||
* Defaults to false.
|
||||
*/
|
||||
includeStandard?: boolean;
|
||||
/**
|
||||
* Whether to include heading components (h1-h6).
|
||||
* Defaults to false.
|
||||
*/
|
||||
includeHeadings?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A reusable Markdown renderer component that provides consistent
|
||||
* markdown rendering across the application.
|
||||
*
|
||||
* By default, includes:
|
||||
* - code, ul, ol components
|
||||
* - remarkGfm and remarkBreaks plugins
|
||||
*
|
||||
* Can be extended with:
|
||||
* - includeStandard: adds anchor and paragraph components
|
||||
* - includeHeadings: adds h1-h6 heading components
|
||||
* - components prop: allows custom overrides or additional components
|
||||
*/
|
||||
export function MarkdownRenderer({
|
||||
children,
|
||||
content,
|
||||
components: customComponents,
|
||||
includeStandard = false,
|
||||
includeHeadings = false,
|
||||
}: MarkdownRendererProps) {
|
||||
// Build the components object with defaults and optional additions
|
||||
const components: Components = {
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
...(includeStandard && {
|
||||
a: anchor,
|
||||
p: paragraph,
|
||||
}),
|
||||
...(includeHeadings && {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
}),
|
||||
...customComponents, // Custom components override defaults
|
||||
};
|
||||
|
||||
const markdownContent = content ?? children ?? "";
|
||||
|
||||
return (
|
||||
<Markdown components={components} remarkPlugins={[remarkGfm, remarkBreaks]}>
|
||||
{markdownContent}
|
||||
</Markdown>
|
||||
);
|
||||
}
|
||||
@ -1,16 +1,10 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Spinner } from "@heroui/react";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import { code } from "../markdown/code";
|
||||
import { ul, ol } from "../markdown/list";
|
||||
import { paragraph } from "../markdown/paragraph";
|
||||
import { anchor } from "../markdown/anchor";
|
||||
import { useMicroagentManagementStore } from "#/state/microagent-management-store";
|
||||
import { useRepositoryMicroagentContent } from "#/hooks/query/use-repository-microagent-content";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { extractRepositoryInfo } from "#/utils/utils";
|
||||
import { MarkdownRenderer } from "../markdown/markdown-renderer";
|
||||
|
||||
export function MicroagentManagementViewMicroagentContent() {
|
||||
const { t } = useTranslation();
|
||||
@ -49,18 +43,9 @@ export function MicroagentManagementViewMicroagentContent() {
|
||||
</div>
|
||||
)}
|
||||
{microagentData && !isLoading && !error && (
|
||||
<Markdown
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
a: anchor,
|
||||
p: paragraph,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
>
|
||||
<MarkdownRenderer includeStandard>
|
||||
{microagentData.content}
|
||||
</Markdown>
|
||||
</MarkdownRenderer>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -184,7 +184,22 @@ const getFinishObservationContent = (
|
||||
event: ObservationEvent<FinishObservation>,
|
||||
): string => {
|
||||
const { observation } = event;
|
||||
return observation.message || "";
|
||||
|
||||
// Extract text content from the observation
|
||||
const textContent = observation.content
|
||||
.filter((c) => c.type === "text")
|
||||
.map((c) => c.text)
|
||||
.join("\n");
|
||||
|
||||
let content = "";
|
||||
|
||||
if (observation.is_error) {
|
||||
content += `**Error:**\n${textContent}`;
|
||||
} else {
|
||||
content += textContent;
|
||||
}
|
||||
|
||||
return content;
|
||||
};
|
||||
|
||||
export const getObservationContent = (event: ObservationEvent): string => {
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
} from "../event-content-helpers/create-skill-ready-event";
|
||||
import { V1ConfirmationButtons } from "#/components/shared/buttons/v1-confirmation-buttons";
|
||||
import { ObservationResultStatus } from "../../../features/chat/event-content-helpers/get-observation-result";
|
||||
import { MarkdownRenderer } from "#/components/features/markdown/markdown-renderer";
|
||||
|
||||
interface GenericEventMessageWrapperProps {
|
||||
event: OpenHandsEvent | SkillReadyEvent;
|
||||
@ -23,11 +24,17 @@ export function GenericEventMessageWrapper({
|
||||
|
||||
// SkillReadyEvent is not an observation event, so skip the observation checks
|
||||
if (!isSkillReadyEvent(event)) {
|
||||
if (
|
||||
isObservationEvent(event) &&
|
||||
event.observation.kind === "TaskTrackerObservation"
|
||||
) {
|
||||
return <div>{details}</div>;
|
||||
if (isObservationEvent(event)) {
|
||||
if (event.observation.kind === "TaskTrackerObservation") {
|
||||
return <div>{details}</div>;
|
||||
}
|
||||
if (event.observation.kind === "FinishObservation") {
|
||||
return (
|
||||
<MarkdownRenderer includeStandard includeHeadings>
|
||||
{details as string}
|
||||
</MarkdownRenderer>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,22 +1,8 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import LessonPlanIcon from "#/icons/lesson-plan.svg?react";
|
||||
import { useConversationStore } from "#/state/conversation-store";
|
||||
import { code } from "#/components/features/markdown/code";
|
||||
import { ul, ol } from "#/components/features/markdown/list";
|
||||
import { paragraph } from "#/components/features/markdown/paragraph";
|
||||
import { anchor } from "#/components/features/markdown/anchor";
|
||||
import {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
} from "#/components/features/markdown/headings";
|
||||
import { MarkdownRenderer } from "#/components/features/markdown/markdown-renderer";
|
||||
|
||||
function PlannerTab() {
|
||||
const { t } = useTranslation();
|
||||
@ -26,24 +12,9 @@ function PlannerTab() {
|
||||
if (planContent !== null && planContent !== undefined) {
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full p-4 overflow-auto">
|
||||
<Markdown
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
a: anchor,
|
||||
p: paragraph,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
>
|
||||
<MarkdownRenderer includeStandard includeHeadings>
|
||||
{planContent}
|
||||
</Markdown>
|
||||
</MarkdownRenderer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -25,9 +25,13 @@ export interface MCPToolObservation
|
||||
export interface FinishObservation
|
||||
extends ObservationBase<"FinishObservation"> {
|
||||
/**
|
||||
* Final message sent to the user
|
||||
* Content returned from the finish action as a list of TextContent/ImageContent objects.
|
||||
*/
|
||||
message: string;
|
||||
content: Array<TextContent | ImageContent>;
|
||||
/**
|
||||
* Whether the finish action resulted in an error
|
||||
*/
|
||||
is_error: boolean;
|
||||
}
|
||||
|
||||
export interface ThinkObservation extends ObservationBase<"ThinkObservation"> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user