mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
feat(frontend):Display path of file ops and cmd in headline (#7530)
Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Carlos Freund <carlosfreund@gmail.com>
This commit is contained in:
@@ -23,7 +23,7 @@ vi.mock("react-i18next", async () => {
|
||||
describe("ExpandableMessage", () => {
|
||||
it("should render with neutral border for non-action messages", () => {
|
||||
renderWithProviders(<ExpandableMessage message="Hello" type="thought" />);
|
||||
const element = screen.getByText("Hello");
|
||||
const element = screen.getAllByText("Hello")[0];
|
||||
const container = element.closest(
|
||||
"div.flex.gap-2.items-center.justify-start",
|
||||
);
|
||||
@@ -35,7 +35,7 @@ describe("ExpandableMessage", () => {
|
||||
renderWithProviders(
|
||||
<ExpandableMessage message="Error occurred" type="error" />,
|
||||
);
|
||||
const element = screen.getByText("Error occurred");
|
||||
const element = screen.getAllByText("Error occurred")[0];
|
||||
const container = element.closest(
|
||||
"div.flex.gap-2.items-center.justify-start",
|
||||
);
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PayloadAction } from "@reduxjs/toolkit";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { Link } from "react-router";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { code } from "../markdown/code";
|
||||
import { ol, ul } from "../markdown/list";
|
||||
import ArrowUp from "#/icons/angle-up-solid.svg?react";
|
||||
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 { useConfig } from "#/hooks/query/use-config";
|
||||
import { code } from "../markdown/code";
|
||||
import { ol, ul } from "../markdown/list";
|
||||
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<OpenHandsObservation>;
|
||||
action?: PayloadAction<OpenHandsAction>;
|
||||
}
|
||||
|
||||
export function ExpandableMessage({
|
||||
@@ -25,20 +37,63 @@ export function ExpandableMessage({
|
||||
message,
|
||||
type,
|
||||
success,
|
||||
observation,
|
||||
action,
|
||||
}: ExpandableMessageProps) {
|
||||
const { data: config } = useConfig();
|
||||
const { t, i18n } = useTranslation();
|
||||
const [showDetails, setShowDetails] = useState(true);
|
||||
const [headline, setHeadline] = useState("");
|
||||
const [details, setDetails] = useState(message);
|
||||
const [translationId, setTranslationId] = useState<string | undefined>(id);
|
||||
const [translationParams, setTranslationParams] = useState<
|
||||
Record<string, unknown>
|
||||
>({
|
||||
observation,
|
||||
action,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (id && i18n.exists(id)) {
|
||||
setHeadline(t(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, i18n.language]);
|
||||
}, [id, message, observation, action, i18n.language]);
|
||||
|
||||
const statusIconClasses = "h-4 w-4 ml-2 inline";
|
||||
|
||||
@@ -78,36 +133,44 @@ export function ExpandableMessage({
|
||||
<div className="flex flex-row justify-between items-center w-full">
|
||||
<span
|
||||
className={cn(
|
||||
headline ? "font-bold" : "",
|
||||
"font-bold",
|
||||
type === "error" ? "text-danger" : "text-neutral-300",
|
||||
)}
|
||||
>
|
||||
{headline && (
|
||||
<>
|
||||
{headline}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
className="cursor-pointer text-left"
|
||||
>
|
||||
{showDetails ? (
|
||||
<ArrowUp
|
||||
className={cn(
|
||||
"h-4 w-4 ml-2 inline",
|
||||
type === "error" ? "fill-danger" : "fill-neutral-300",
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<ArrowDown
|
||||
className={cn(
|
||||
"h-4 w-4 ml-2 inline",
|
||||
type === "error" ? "fill-danger" : "fill-neutral-300",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
{translationId && i18n.exists(translationId) ? (
|
||||
<Trans
|
||||
i18nKey={translationId}
|
||||
values={translationParams}
|
||||
components={{
|
||||
bold: <strong />,
|
||||
path: <PathComponent />,
|
||||
cmd: <MonoComponent />,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
message
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
className="cursor-pointer text-left"
|
||||
>
|
||||
{showDetails ? (
|
||||
<ArrowUp
|
||||
className={cn(
|
||||
"h-4 w-4 ml-2 inline",
|
||||
type === "error" ? "fill-danger" : "fill-neutral-300",
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<ArrowDown
|
||||
className={cn(
|
||||
"h-4 w-4 ml-2 inline",
|
||||
type === "error" ? "fill-danger" : "fill-neutral-300",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</span>
|
||||
{type === "action" && success !== undefined && (
|
||||
<span className="flex-shrink-0">
|
||||
@@ -125,7 +188,7 @@ export function ExpandableMessage({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{(!headline || showDetails) && (
|
||||
{showDetails && (
|
||||
<div className="text-sm overflow-auto">
|
||||
<Markdown
|
||||
components={{
|
||||
|
||||
@@ -26,6 +26,8 @@ export const Messages: React.FC<MessagesProps> = React.memo(
|
||||
id={message.translationID}
|
||||
message={message.content}
|
||||
success={message.success}
|
||||
observation={message.observation}
|
||||
action={message.action}
|
||||
/>
|
||||
{shouldShowConfirmationButtons && <ConfirmationButtons />}
|
||||
</div>
|
||||
|
||||
37
frontend/src/components/features/chat/mono-component.tsx
Normal file
37
frontend/src/components/features/chat/mono-component.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ReactNode } from "react";
|
||||
import EventLogger from "#/utils/event-logger";
|
||||
|
||||
const decodeHtmlEntities = (text: string): string => {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.innerHTML = text;
|
||||
return textarea.value;
|
||||
};
|
||||
|
||||
function MonoComponent(props: { children?: ReactNode }) {
|
||||
const { children } = props;
|
||||
|
||||
const decodeString = (str: string): string => {
|
||||
try {
|
||||
return decodeHtmlEntities(str);
|
||||
} catch (e) {
|
||||
EventLogger.error(String(e));
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
if (Array.isArray(children)) {
|
||||
const processedChildren = children.map((child) =>
|
||||
typeof child === "string" ? decodeString(child) : child,
|
||||
);
|
||||
|
||||
return <strong className="font-mono">{processedChildren}</strong>;
|
||||
}
|
||||
|
||||
if (typeof children === "string") {
|
||||
return <strong className="font-mono">{decodeString(children)}</strong>;
|
||||
}
|
||||
|
||||
return <strong className="font-mono">{children}</strong>;
|
||||
}
|
||||
|
||||
export { MonoComponent };
|
||||
67
frontend/src/components/features/chat/path-component.tsx
Normal file
67
frontend/src/components/features/chat/path-component.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { ReactNode } from "react";
|
||||
import EventLogger from "#/utils/event-logger";
|
||||
|
||||
/**
|
||||
* Decodes HTML entities in a string
|
||||
* @param text The text to decode
|
||||
* @returns The decoded text
|
||||
*/
|
||||
const decodeHtmlEntities = (text: string): string => {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.innerHTML = text;
|
||||
return textarea.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the filename from a path
|
||||
* @param path The full path
|
||||
* @returns The filename (last part of the path)
|
||||
*/
|
||||
const extractFilename = (path: string): string => {
|
||||
if (!path) return "";
|
||||
// Handle both Unix and Windows paths
|
||||
const parts = path.split(/[/\\]/);
|
||||
return parts[parts.length - 1];
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that displays only the filename in the text but shows the full path on hover
|
||||
* Similar to MonoComponent but with path-specific functionality
|
||||
*/
|
||||
function PathComponent(props: { children?: ReactNode }) {
|
||||
const { children } = props;
|
||||
|
||||
const processPath = (path: string) => {
|
||||
try {
|
||||
// First decode any HTML entities in the path
|
||||
const decodedPath = decodeHtmlEntities(path);
|
||||
// Extract the filename from the decoded path
|
||||
const filename = extractFilename(decodedPath);
|
||||
return (
|
||||
<span className="font-mono" title={decodedPath}>
|
||||
{filename}
|
||||
</span>
|
||||
);
|
||||
} catch (e) {
|
||||
// Just log the error without any message to avoid localization issues
|
||||
EventLogger.error(String(e));
|
||||
return <span className="font-mono">{path}</span>;
|
||||
}
|
||||
};
|
||||
|
||||
if (Array.isArray(children)) {
|
||||
const processedChildren = children.map((child) =>
|
||||
typeof child === "string" ? processPath(child) : child,
|
||||
);
|
||||
|
||||
return <strong className="font-mono">{processedChildren}</strong>;
|
||||
}
|
||||
|
||||
if (typeof children === "string") {
|
||||
return <strong>{processPath(children)}</strong>;
|
||||
}
|
||||
|
||||
return <strong className="font-mono">{children}</strong>;
|
||||
}
|
||||
|
||||
export { PathComponent };
|
||||
@@ -4751,19 +4751,19 @@
|
||||
"tr": "Çalışma alanını kapat"
|
||||
},
|
||||
"ACTION_MESSAGE$RUN": {
|
||||
"en": "Running a bash command",
|
||||
"zh-CN": "运行",
|
||||
"zh-TW": "執行",
|
||||
"ko-KR": "실행",
|
||||
"ja": "実行",
|
||||
"no": "Kjører en bash-kommando",
|
||||
"ar": "تشغيل أمر باش",
|
||||
"de": "Führt einen Bash-Befehl aus",
|
||||
"fr": "Exécution d'une commande bash",
|
||||
"it": "Esecuzione di un comando bash",
|
||||
"pt": "Executando um comando bash",
|
||||
"es": "Ejecutando un comando bash",
|
||||
"tr": "Bash komutu çalıştırılıyor"
|
||||
"en": "Running <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"zh-CN": "运行 <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"zh-TW": "執行 <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"ko-KR": "실행 <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"ja": "実行 <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"no": "Kjører <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"ar": "تشغيل <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"de": "Führt <cmd>{{action.payload.args.command}}</cmd> aus",
|
||||
"fr": "Exécution de <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"it": "Esecuzione di <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"pt": "Executando <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"es": "Ejecutando <cmd>{{action.payload.args.command}}</cmd>",
|
||||
"tr": "<cmd>{{action.payload.args.command}}</cmd> çalıştırılıyor"
|
||||
},
|
||||
"ACTION_MESSAGE$RUN_IPYTHON": {
|
||||
"en": "Running a Python command",
|
||||
@@ -4781,49 +4781,49 @@
|
||||
"tr": "Python komutu çalıştırılıyor"
|
||||
},
|
||||
"ACTION_MESSAGE$READ": {
|
||||
"en": "Reading the contents of a file",
|
||||
"zh-CN": "读取",
|
||||
"zh-TW": "讀取",
|
||||
"ko-KR": "읽기",
|
||||
"ja": "読み取り",
|
||||
"no": "Leser innholdet i en fil",
|
||||
"ar": "قراءة محتويات ملف",
|
||||
"de": "Liest den Inhalt einer Datei",
|
||||
"fr": "Lecture du contenu d'un fichier",
|
||||
"it": "Lettura del contenuto di un file",
|
||||
"pt": "Lendo o conteúdo de um arquivo",
|
||||
"es": "Leyendo el contenido de un archivo",
|
||||
"tr": "Dosya içeriği okunuyor"
|
||||
"en": "Reading <path>{{action.payload.args.path}}</path>",
|
||||
"zh-CN": "读取 <path>{{action.payload.args.path}}</path>",
|
||||
"zh-TW": "讀取 <path>{{action.payload.args.path}}</path>",
|
||||
"ko-KR": "읽기 <path>{{action.payload.args.path}}</path>",
|
||||
"ja": "読み取り <path>{{action.payload.args.path}}</path>",
|
||||
"no": "Leser <path>{{action.payload.args.path}}</path>",
|
||||
"ar": "قراءة <path>{{action.payload.args.path}}</path>",
|
||||
"de": "Liest <path>{{action.payload.args.path}}</path>",
|
||||
"fr": "Lecture de <path>{{action.payload.args.path}}</path>",
|
||||
"it": "Lettura di <path>{{action.payload.args.path}}</path>",
|
||||
"pt": "Lendo <path>{{action.payload.args.path}}</path>",
|
||||
"es": "Leyendo <path>{{action.payload.args.path}}</path>",
|
||||
"tr": "<path>{{action.payload.args.path}}</path> okunuyor"
|
||||
},
|
||||
"ACTION_MESSAGE$EDIT": {
|
||||
"en": "Editing the contents of a file",
|
||||
"zh-CN": "编辑",
|
||||
"zh-TW": "編輯",
|
||||
"ko-KR": "편집",
|
||||
"ja": "編集",
|
||||
"no": "Redigerer innholdet i en fil",
|
||||
"ar": "تحرير محتويات ملف",
|
||||
"de": "Bearbeitet den Inhalt einer Datei",
|
||||
"fr": "Modification du contenu d'un fichier",
|
||||
"it": "Modifica del contenuto di un file",
|
||||
"pt": "Editando o conteúdo de um arquivo",
|
||||
"es": "Editando el contenido de un archivo",
|
||||
"tr": "Dosya içeriği düzenleniyor"
|
||||
"en": "Editing <path>{{action.payload.args.path}}</path>",
|
||||
"zh-CN": "编辑 <path>{{action.payload.args.path}}</path>",
|
||||
"zh-TW": "編輯 <path>{{action.payload.args.path}}</path>",
|
||||
"ko-KR": "편집 <path>{{action.payload.args.path}}</path>",
|
||||
"ja": "編集 <path>{{action.payload.args.path}}</path>",
|
||||
"no": "Redigerer <path>{{action.payload.args.path}}</path>",
|
||||
"ar": "تحرير <path>{{action.payload.args.path}}</path>",
|
||||
"de": "Bearbeitet <path>{{action.payload.args.path}}</path>",
|
||||
"fr": "Modification de <path>{{action.payload.args.path}}</path>",
|
||||
"it": "Modifica di <path>{{action.payload.args.path}}</path>",
|
||||
"pt": "Editando <path>{{action.payload.args.path}}</path>",
|
||||
"es": "Editando <path>{{action.payload.args.path}}</path>",
|
||||
"tr": "<path>{{action.payload.args.path}}</path> düzenleniyor"
|
||||
},
|
||||
"ACTION_MESSAGE$WRITE": {
|
||||
"en": "Writing to a file",
|
||||
"zh-CN": "写入",
|
||||
"zh-TW": "寫入",
|
||||
"ko-KR": "쓰기",
|
||||
"ja": "書き込み",
|
||||
"no": "Skriver til en fil",
|
||||
"ar": "الكتابة إلى ملف",
|
||||
"de": "Schreibt in eine Datei",
|
||||
"fr": "Écriture dans un fichier",
|
||||
"it": "Scrittura su file",
|
||||
"pt": "Escrevendo em um arquivo",
|
||||
"es": "Escribiendo en un archivo",
|
||||
"tr": "Dosyaya yazılıyor"
|
||||
"en": "Writing to <path>{{action.payload.args.path}}</path>",
|
||||
"zh-CN": "写入 <path>{{action.payload.args.path}}</path>",
|
||||
"zh-TW": "寫入 <path>{{action.payload.args.path}}</path>",
|
||||
"ko-KR": "쓰기 <path>{{action.payload.args.path}}</path>",
|
||||
"ja": "書き込み <path>{{action.payload.args.path}}</path>",
|
||||
"no": "Skriver til <path>{{action.payload.args.path}}</path>",
|
||||
"ar": "الكتابة إلى <path>{{action.payload.args.path}}</path>",
|
||||
"de": "Schreibt in <path>{{action.payload.args.path}}</path>",
|
||||
"fr": "Écriture dans <path>{{action.payload.args.path}}</path>",
|
||||
"it": "Scrittura su <path>{{action.payload.args.path}}</path>",
|
||||
"pt": "Escrevendo em <path>{{action.payload.args.path}}</path>",
|
||||
"es": "Escribiendo en <path>{{action.payload.args.path}}</path>",
|
||||
"tr": "<path>{{action.payload.args.path}}</path> dosyasına yazılıyor"
|
||||
},
|
||||
"ACTION_MESSAGE$BROWSE": {
|
||||
"en": "Browsing the web",
|
||||
@@ -4871,19 +4871,19 @@
|
||||
"tr": "Düşünüyor"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$RUN": {
|
||||
"en": "Ran a bash command",
|
||||
"zh-CN": "运行",
|
||||
"zh-TW": "執行",
|
||||
"ko-KR": "실행",
|
||||
"ja": "実行",
|
||||
"no": "Kjørte en bash-kommando",
|
||||
"ar": "تم تشغيل أمر باش",
|
||||
"de": "Führte einen Bash-Befehl aus",
|
||||
"fr": "A exécuté une commande bash",
|
||||
"it": "Ha eseguito un comando bash",
|
||||
"pt": "Executou um comando bash",
|
||||
"es": "Ejecutó un comando bash",
|
||||
"tr": "Bash komutu çalıştırıldı"
|
||||
"en": "Ran <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"zh-CN": "运行 <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"zh-TW": "執行 <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"ko-KR": "실행 <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"ja": "実行 <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"no": "Kjørte <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"ar": "تم تشغيل <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"de": "Führte <cmd>{{observation.payload.extras.command}}</cmd> aus",
|
||||
"fr": "A exécuté <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"it": "Ha eseguito <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"pt": "Executou <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"es": "Ejecutó <cmd>{{observation.payload.extras.command}}</cmd>",
|
||||
"tr": "<cmd>{{observation.payload.extras.command}}</cmd> çalıştırıldı"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$RUN_IPYTHON": {
|
||||
"en": "Ran a Python command",
|
||||
@@ -4901,49 +4901,49 @@
|
||||
"tr": "Python komutu çalıştırıldı"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$READ": {
|
||||
"en": "Read the contents of a file",
|
||||
"zh-CN": "读取",
|
||||
"zh-TW": "讀取",
|
||||
"ko-KR": "읽기",
|
||||
"ja": "読み取り",
|
||||
"no": "Leste innholdet i en fil",
|
||||
"ar": "تمت قراءة محتويات ملف",
|
||||
"de": "Las den Inhalt einer Datei",
|
||||
"fr": "A lu le contenu d'un fichier",
|
||||
"it": "Ha letto il contenuto di un file",
|
||||
"pt": "Leu o conteúdo de um arquivo",
|
||||
"es": "Leyó el contenido de un archivo",
|
||||
"tr": "Dosya içeriği okundu"
|
||||
"en": "Read <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-CN": "读取 <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-TW": "讀取 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ko-KR": "읽기 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ja": "読み取り <path>{{observation.payload.extras.path}}</path>",
|
||||
"no": "Leste <path>{{observation.payload.extras.path}}</path>",
|
||||
"ar": "تمت قراءة <path>{{observation.payload.extras.path}}</path>",
|
||||
"de": "Las <path>{{observation.payload.extras.path}}</path>",
|
||||
"fr": "A lu <path>{{observation.payload.extras.path}}</path>",
|
||||
"it": "Ha letto <path>{{observation.payload.extras.path}}</path>",
|
||||
"pt": "Leu <path>{{observation.payload.extras.path}}</path>",
|
||||
"es": "Leyó <path>{{observation.payload.extras.path}}</path>",
|
||||
"tr": "<path>{{observation.payload.extras.path}}</path> okundu"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$EDIT": {
|
||||
"en": "Edited the contents of a file",
|
||||
"zh-CN": "编辑",
|
||||
"zh-TW": "編輯",
|
||||
"ko-KR": "편집",
|
||||
"ja": "編集",
|
||||
"no": "Redigerte innholdet i en fil",
|
||||
"ar": "تم تحرير محتويات ملف",
|
||||
"de": "Hat den Inhalt einer Datei bearbeitet",
|
||||
"fr": "A modifié le contenu d'un fichier",
|
||||
"it": "Ha modificato il contenuto di un file",
|
||||
"pt": "Editou o conteúdo de um arquivo",
|
||||
"es": "Editó el contenido de un archivo",
|
||||
"tr": "Dosya içeriği düzenlendi"
|
||||
"en": "Edited <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-CN": "编辑 <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-TW": "編輯 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ko-KR": "편집 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ja": "編集 <path>{{observation.payload.extras.path}}</path>",
|
||||
"no": "Redigerte <path>{{observation.payload.extras.path}}</path>",
|
||||
"ar": "تم تحرير <path>{{observation.payload.extras.path}}</path>",
|
||||
"de": "Hat <path>{{observation.payload.extras.path}}</path> bearbeitet",
|
||||
"fr": "A modifié <path>{{observation.payload.extras.path}}</path>",
|
||||
"it": "Ha modificato <path>{{observation.payload.extras.path}}</path>",
|
||||
"pt": "Editou <path>{{observation.payload.extras.path}}</path>",
|
||||
"es": "Editó <path>{{observation.payload.extras.path}}</path>",
|
||||
"tr": "<path>{{observation.payload.extras.path}}</path> düzenlendi"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$WRITE": {
|
||||
"en": "Wrote to a file",
|
||||
"zh-CN": "写入",
|
||||
"zh-TW": "寫入",
|
||||
"ko-KR": "쓰기",
|
||||
"ja": "書き込み",
|
||||
"no": "Skrev til en fil",
|
||||
"ar": "تمت الكتابة إلى ملف",
|
||||
"de": "Hat in eine Datei geschrieben",
|
||||
"fr": "A écrit dans un fichier",
|
||||
"it": "Ha scritto su un file",
|
||||
"pt": "Escreveu em um arquivo",
|
||||
"es": "Escribió en un archivo",
|
||||
"tr": "Dosyaya yazıldı"
|
||||
"en": "Wrote to <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-CN": "写入 <path>{{observation.payload.extras.path}}</path>",
|
||||
"zh-TW": "寫入 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ko-KR": "쓰기 <path>{{observation.payload.extras.path}}</path>",
|
||||
"ja": "書き込み <path>{{observation.payload.extras.path}}</path>",
|
||||
"no": "Skrev til <path>{{observation.payload.extras.path}}</path>",
|
||||
"ar": "تمت الكتابة إلى <path>{{observation.payload.extras.path}}</path>",
|
||||
"de": "Hat in <path>{{observation.payload.extras.path}}</path> geschrieben",
|
||||
"fr": "A écrit dans <path>{{observation.payload.extras.path}}</path>",
|
||||
"it": "Ha scritto su <path>{{observation.payload.extras.path}}</path>",
|
||||
"pt": "Escreveu em <path>{{observation.payload.extras.path}}</path>",
|
||||
"es": "Escribió en <path>{{observation.payload.extras.path}}</path>",
|
||||
"tr": "<path>{{observation.payload.extras.path}}</path> dosyasına yazıldı"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$BROWSE": {
|
||||
"en": "Browsing completed",
|
||||
|
||||
6
frontend/src/message.d.ts
vendored
6
frontend/src/message.d.ts
vendored
@@ -1,3 +1,7 @@
|
||||
import { PayloadAction } from "@reduxjs/toolkit";
|
||||
import { OpenHandsObservation } from "./types/core/observations";
|
||||
import { OpenHandsAction } from "./types/core/actions";
|
||||
|
||||
export type Message = {
|
||||
sender: "user" | "assistant";
|
||||
content: string;
|
||||
@@ -8,4 +12,6 @@ export type Message = {
|
||||
pending?: boolean;
|
||||
translationID?: string;
|
||||
eventID?: number;
|
||||
observation?: PayloadAction<OpenHandsObservation>;
|
||||
action?: PayloadAction<OpenHandsAction>;
|
||||
};
|
||||
|
||||
@@ -2,14 +2,14 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import type { Message } from "#/message";
|
||||
|
||||
import { ActionSecurityRisk } from "#/state/security-analyzer-slice";
|
||||
import {
|
||||
OpenHandsObservation,
|
||||
CommandObservation,
|
||||
IPythonObservation,
|
||||
RecallObservation,
|
||||
} from "#/types/core/observations";
|
||||
import { OpenHandsAction } from "#/types/core/actions";
|
||||
import { OpenHandsEventType } from "#/types/core/base";
|
||||
import {
|
||||
CommandObservation,
|
||||
IPythonObservation,
|
||||
OpenHandsObservation,
|
||||
RecallObservation,
|
||||
} from "#/types/core/observations";
|
||||
|
||||
type SliceState = { messages: Message[] };
|
||||
|
||||
@@ -135,6 +135,7 @@ export const chatSlice = createSlice({
|
||||
content: text,
|
||||
imageUrls: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
action,
|
||||
};
|
||||
|
||||
state.messages.push(message);
|
||||
@@ -224,6 +225,7 @@ export const chatSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
causeMessage.translationID = translationID;
|
||||
causeMessage.observation = observation;
|
||||
// Set success property based on observation type
|
||||
if (observationID === "run") {
|
||||
const commandObs = observation.payload as CommandObservation;
|
||||
@@ -253,9 +255,7 @@ export const chatSlice = createSlice({
|
||||
if (content.length > MAX_CONTENT_LENGTH) {
|
||||
content = `${content.slice(0, MAX_CONTENT_LENGTH)}...`;
|
||||
}
|
||||
content = `${
|
||||
causeMessage.content
|
||||
}\n\nOutput:\n\`\`\`\n${content.trim() || "[Command finished execution with no output]"}\n\`\`\``;
|
||||
content = `${causeMessage.content}\n\nOutput:\n\`\`\`\n${content.trim() || "[Command finished execution with no output]"}\n\`\`\``;
|
||||
causeMessage.content = content; // Observation content includes the action
|
||||
} else if (observationID === "read") {
|
||||
causeMessage.content = `\`\`\`\n${observation.payload.content}\n\`\`\``; // Content is already truncated by the ACI
|
||||
|
||||
Reference in New Issue
Block a user