diff --git a/frontend/src/components/features/chat/chat-message.tsx b/frontend/src/components/features/chat/chat-message.tsx index c3be33e76b33..0dbdfa7af529 100644 --- a/frontend/src/components/features/chat/chat-message.tsx +++ b/frontend/src/components/features/chat/chat-message.tsx @@ -6,19 +6,24 @@ 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 { JumpToFileButton } from "#/components/shared/buttons/jump-to-file-button"; +import { useFiles } from "#/context/files"; interface ChatMessageProps { type: "user" | "assistant"; message: string; + filePath?: string; } export function ChatMessage({ type, message, + filePath, children, }: React.PropsWithChildren) { const [isHovering, setIsHovering] = React.useState(false); const [isCopy, setIsCopy] = React.useState(false); + const { setSelectedPath } = useFiles(); const handleCopyToClipboard = async () => { await navigator.clipboard.writeText(message); @@ -57,6 +62,12 @@ export function ChatMessage({ onClick={handleCopyToClipboard} mode={isCopy ? "copied" : "copy"} /> + {filePath && ( + setSelectedPath(filePath)} + /> + )} = React.memo( key={index} type={message.sender} message={message.content} + filePath={message.filePath} > {message.imageUrls && message.imageUrls.length > 0 && ( diff --git a/frontend/src/components/shared/buttons/jump-to-file-button.tsx b/frontend/src/components/shared/buttons/jump-to-file-button.tsx new file mode 100644 index 000000000000..535a580d4091 --- /dev/null +++ b/frontend/src/components/shared/buttons/jump-to-file-button.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { VscGoToFile } from "react-icons/vsc"; +import { I18nKey } from "#/i18n/declaration"; +import { ActionTooltip } from "#/components/shared/action-tooltip"; +import { cn } from "#/utils/utils"; + +interface JumpToFileButtonProps { + filePath: string; + onClick: () => void; +} + +export function JumpToFileButton({ filePath, onClick }: JumpToFileButtonProps) { + const { t } = useTranslation(); + + return ( + + + + ); +} \ No newline at end of file diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json index f0d0a87062d8..579d1916b9cc 100644 --- a/frontend/src/i18n/translation.json +++ b/frontend/src/i18n/translation.json @@ -4492,7 +4492,21 @@ "tr": "İstemcinin hazır olması bekleniyor...", "ja": "クライアントの準備を待機中" }, - "SUGGESTIONS$WHAT_TO_BUILD": { + "CHAT$JUMP_TO_FILE_TOOLTIP": { + "en": "Jump to file: {{path}}", + "zh-CN": "跳转到文件:{{path}}", + "de": "Zur Datei springen: {{path}}", + "ko-KR": "파일로 이동: {{path}}", + "no": "Hopp til fil: {{path}}", + "zh-TW": "跳轉到文件:{{path}}", + "it": "Vai al file: {{path}}", + "pt": "Ir para o arquivo: {{path}}", + "es": "Ir al archivo: {{path}}", + "ar": "انتقل إلى الملف: {{path}}", + "fr": "Aller au fichier: {{path}}", + "tr": "Dosyaya git: {{path}}" + }, + "SUGGESTIONS$WHAT_TO_BUILD": { "en": "What do you want to build?", "ja": "何を開発しますか?", "zh-CN": "你想要构建什么?", diff --git a/frontend/src/message.d.ts b/frontend/src/message.d.ts index 65bd7e0cb193..3a08684f2341 100644 --- a/frontend/src/message.d.ts +++ b/frontend/src/message.d.ts @@ -8,4 +8,5 @@ type Message = { pending?: boolean; translationID?: string; eventID?: number; + filePath?: string; }; diff --git a/frontend/src/state/chat-slice.ts b/frontend/src/state/chat-slice.ts index 5bfffb62d4b3..45c3cc80ca5c 100644 --- a/frontend/src/state/chat-slice.ts +++ b/frontend/src/state/chat-slice.ts @@ -166,8 +166,9 @@ export const chatSlice = createSlice({ }\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" || observationID === "edit") { - const { content } = observation.payload; + const { content, extras } = observation.payload; causeMessage.content = `\`\`\`${observationID === "edit" ? "diff" : "python"}\n${content}\n\`\`\``; // Content is already truncated by the ACI + causeMessage.filePath = extras.path; } else if (observationID === "browse") { let content = `**URL:** ${observation.payload.extras.url}\n`; if (observation.payload.extras.error) {