From 7ae502eebd0ac1f639457dcf8aa036f3b11d1586 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Sun, 29 Jun 2025 23:46:51 +0300 Subject: [PATCH 01/47] refactor(eslint): implement custom ESLint rules for React and TypeScript --- frontend/.eslintrc.js | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 frontend/.eslintrc.js diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js new file mode 100644 index 00000000..2d3c563a --- /dev/null +++ b/frontend/.eslintrc.js @@ -0,0 +1,50 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:jsx-a11y/recommended', + ], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 'latest', + sourceType: 'module', + }, + plugins: [ + 'react', + '@typescript-eslint', + 'react-hooks', + 'jsx-a11y', + ], + rules: { + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'warn', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + }, + settings: { + react: { + version: 'detect', + }, + }, + ignorePatterns: [ + 'node_modules/', + 'public/', + '.cache/', + 'gatsby-*.js', + '*.config.js', + ], +}; From 34b795a359322232d012c5dc6d0b7ff892f479a0 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Sun, 29 Jun 2025 23:46:56 +0300 Subject: [PATCH 02/47] chore(package): add lint scripts and update package manager --- frontend/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 431f5bc3..26df1e72 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,8 @@ "build": "gatsby clean && rm -rf ../src/magentic_ui/backend/web/ui && PREFIX_PATH_VALUE='' gatsby build --prefix-paths && rsync -a --delete public/ ../src/magentic_ui/backend/web/ui/", "serve": "gatsby serve", "clean": "gatsby clean", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 150", + "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -66,5 +68,6 @@ "prismjs": "1.30.0", "cookie": "0.7.0", "base-x": "3.0.11" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } From 911d4fe465bc4518dcb2f52a8212ab7a051a506a Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Sun, 29 Jun 2025 23:46:58 +0300 Subject: [PATCH 03/47] feat(ci): introduce frontend checks GitHub Action --- .github/workflows/frontend-checks.yaml | 66 ++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/frontend-checks.yaml diff --git a/.github/workflows/frontend-checks.yaml b/.github/workflows/frontend-checks.yaml new file mode 100644 index 00000000..983b7787 --- /dev/null +++ b/.github/workflows/frontend-checks.yaml @@ -0,0 +1,66 @@ +name: Frontend Checks + +on: + push: + branches: + - main + paths: + - "frontend/**" + pull_request: + branches: + - main + paths: + - "frontend/**" + +permissions: + contents: read + +jobs: + frontend-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "18" + cache: "yarn" + cache-dependency-path: "frontend/yarn.lock" + + - name: Install dependencies + working-directory: frontend + run: yarn install --frozen-lockfile + + - name: Run ESLint (must have 0 errors) + working-directory: frontend + run: | + echo "๐Ÿงน Running ESLint - checking for 0 errors..." + + # Run lint and capture output + LINT_OUTPUT=$(yarn lint 2>&1) + LINT_EXIT_CODE=$? + + echo "$LINT_OUTPUT" + + # Parse the output to check for errors (but not warnings) + if [ $LINT_EXIT_CODE -ne 0 ]; then + echo "โŒ ESLint failed with exit code $LINT_EXIT_CODE" + exit 1 + else + echo "โœ… ESLint passed with 0 errors!" + fi + + - name: Run TypeScript check + working-directory: frontend + run: | + echo "๐Ÿ” Running TypeScript check..." + yarn typecheck + + if [ $? -eq 0 ]; then + echo "โœ… TypeScript check passed!" + else + echo "โŒ TypeScript check failed" + exit 1 + fi From 2fe0a832600b89f43d9522890bcd954e8afdfe2b Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Sun, 29 Jun 2025 23:48:40 +0300 Subject: [PATCH 04/47] feattypescript: add IPlan type import --- .../components/views/chat/DetailViewer/fullscreen_overlay.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/views/chat/DetailViewer/fullscreen_overlay.tsx b/frontend/src/components/views/chat/DetailViewer/fullscreen_overlay.tsx index 8e0f016e..e4ca1665 100644 --- a/frontend/src/components/views/chat/DetailViewer/fullscreen_overlay.tsx +++ b/frontend/src/components/views/chat/DetailViewer/fullscreen_overlay.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import FeedbackForm from "./FeedbackForm"; +import { IPlan } from "../../../types/plan"; interface FullscreenOverlayProps { isVisible: boolean; From efb03826304a9f95ca61143916c8470424dbec5a Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Sun, 29 Jun 2025 23:48:42 +0300 Subject: [PATCH 05/47] fix/runtime: ensure session status type safety --- frontend/src/components/views/manager.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/views/manager.tsx b/frontend/src/components/views/manager.tsx index a6b3fe33..e45afe47 100644 --- a/frontend/src/components/views/manager.tsx +++ b/frontend/src/components/views/manager.tsx @@ -364,7 +364,7 @@ export const SessionManager: React.FC = () => { const chatViews = useMemo(() => { return sessions.map((s: Session) => { - const status = sessionRunStatuses[s.id] as RunStatus; + const status = sessionRunStatuses[s.id as keyof typeof sessionRunStatuses] as RunStatus; const isSessionPotentiallyActive = [ "active", "awaiting_input", From 20e4503694e74263b4ad0aaed35cbeba6696de0f Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Sun, 29 Jun 2025 23:48:44 +0300 Subject: [PATCH 06/47] feat(sidebar): improve session status type safety --- frontend/src/components/views/sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/views/sidebar.tsx b/frontend/src/components/views/sidebar.tsx index 0d366bfc..ffc25464 100644 --- a/frontend/src/components/views/sidebar.tsx +++ b/frontend/src/components/views/sidebar.tsx @@ -103,7 +103,7 @@ export const Sidebar: React.FC = ({ const renderSessionGroup = (sessions: Session[]) => ( <> {sessions.map((s) => { - const status = sessionRunStatuses[s.id]; + const status = sessionRunStatuses[s.id as keyof typeof sessionRunStatuses]; const isActive = [ "active", "awaiting_input", From b55ccd7a71459beb5a4cf4562f8bdbc946139630 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Sun, 29 Jun 2025 23:48:46 +0300 Subject: [PATCH 07/47] fix(signin): use submit type for sign in button --- frontend/src/components/signin.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/signin.tsx b/frontend/src/components/signin.tsx index ff296eb9..652f6c5a 100644 --- a/frontend/src/components/signin.tsx +++ b/frontend/src/components/signin.tsx @@ -52,7 +52,7 @@ const SignInModal = ({ isVisible, onClose }: SignInModalProps) => {
-
e.stopPropagation()}> +
e.stopPropagation()} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.stopPropagation(); + } + }} + role="button" + tabIndex={0} + > {alt} - {alt} setIsFullScreen(true)} - /> + className="block border-0 bg-transparent p-0 cursor-zoom-in" + aria-label="Click to view fullscreen" + > + {alt} + {isFullScreen && ( Date: Mon, 30 Jun 2025 00:21:31 +0300 Subject: [PATCH 28/47] refactor(types): simplify datamodel imports --- frontend/src/components/views/chat/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/views/chat/types.ts b/frontend/src/components/views/chat/types.ts index 595e1f30..fb9e792c 100644 --- a/frontend/src/components/views/chat/types.ts +++ b/frontend/src/components/views/chat/types.ts @@ -1,4 +1,4 @@ -import { AgentMessageConfig, Message, TaskResult } from "../../types/datamodel"; +import { AgentMessageConfig, Message } from "../../types/datamodel"; export interface ThreadState { messages: AgentMessageConfig[]; From 8f338179afad02e1dbe95e4028f8982aa4a1ef48 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:21:35 +0300 Subject: [PATCH 29/47] fix(chat): prevent crashes when processing messages --- frontend/src/components/views/chat/runview.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/views/chat/runview.tsx b/frontend/src/components/views/chat/runview.tsx index 4d2a37a1..58f2fb5d 100644 --- a/frontend/src/components/views/chat/runview.tsx +++ b/frontend/src/components/views/chat/runview.tsx @@ -164,7 +164,7 @@ const RunView: React.FC = ({ Array.isArray(msg.config.content) && msg.config.metadata?.type === "browser_screenshot" ) { - msg.config.content.forEach((item: any, itemIndex: number) => { + msg.config.content.forEach((item: any, _itemIndex: number) => { if (typeof item === "object" && ("url" in item || "data" in item)) { const imageUrl = ("url" in item && item.url) || @@ -414,12 +414,13 @@ const RunView: React.FC = ({ // delay for 100ms await new Promise((resolve) => setTimeout(resolve, 100)); } - } catch {} + } catch { + // Ignore errors when waiting for additional messages + } } } continue; } - const content = JSON.parse(msg.config.content); // If this is a step execution that's not repeated if ( @@ -439,11 +440,15 @@ const RunView: React.FC = ({ await new Promise((resolve) => setTimeout(resolve, 100)); } } - } catch {} + } catch { + // Ignore errors when processing step execution + } } } } - } catch {} + } catch { + // Ignore errors when processing messages + } } if ( From 81427d18f2acf89d1ca1d9e844a914dd5bea4d44 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:21:38 +0300 Subject: [PATCH 30/47] fix(chat): ignore JSON parsing errors and improve fallback --- .../components/views/chat/rendermessage.tsx | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/views/chat/rendermessage.tsx b/frontend/src/components/views/chat/rendermessage.tsx index a74ae4ad..824528a5 100644 --- a/frontend/src/components/views/chat/rendermessage.tsx +++ b/frontend/src/components/views/chat/rendermessage.tsx @@ -188,7 +188,9 @@ const parseorchestratorContent = ( if (messageUtils.isStepExecution(metadata)) { return { type: "step-execution" as const, content: parsedContent }; } - } catch {} + } catch { + // Ignore JSON parsing errors and fall back to default type + } return { type: "default" as const, content }; }; @@ -225,12 +227,13 @@ const RenderMultiModalBrowserStep: React.FC<{ )} {/* Text content */} -
onImageClick?.(index)} > -
+
); @@ -238,6 +241,8 @@ const RenderMultiModalBrowserStep: React.FC<{
)); +RenderMultiModalBrowserStep.displayName = 'RenderMultiModalBrowserStep'; + const RenderMultiModal: React.FC<{ content: (string | ImageContent)[]; }> = memo(({ content }) => ( @@ -258,6 +263,8 @@ const RenderMultiModal: React.FC<{ )); +RenderMultiModal.displayName = 'RenderMultiModal'; + const RenderToolCall: React.FC<{ content: FunctionCall[] }> = memo( ({ content }) => (
@@ -274,6 +281,8 @@ const RenderToolCall: React.FC<{ content: FunctionCall[] }> = memo( ) ); +RenderToolCall.displayName = 'RenderToolCall'; + const RenderToolResult: React.FC<{ content: FunctionExecutionResult[] }> = memo( ({ content }) => (
@@ -287,6 +296,8 @@ const RenderToolResult: React.FC<{ content: FunctionExecutionResult[] }> = memo( ) ); +RenderToolResult.displayName = 'RenderToolResult'; + const RenderPlan: React.FC = memo( ({ content, isEditable, onSavePlan, onRegeneratePlan, forceCollapsed }) => { // Make sure content.steps is an array before using it @@ -320,6 +331,8 @@ const RenderPlan: React.FC = memo( } ); +RenderPlan.displayName = 'RenderPlan'; + const RenderStepExecution: React.FC = memo( ({ content, @@ -385,6 +398,14 @@ const RenderStepExecution: React.FC = memo(
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleToggle(); + } + }} + role="button" + tabIndex={0} >
)} From 6e04c837130d35eb8ae979bf75936774dca2b70b Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:21:44 +0300 Subject: [PATCH 32/47] refactor(chat): simplify component logic and remove unused state --- frontend/src/components/views/chat/detail_viewer.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/src/components/views/chat/detail_viewer.tsx b/frontend/src/components/views/chat/detail_viewer.tsx index 1cf3871b..f6f51f9c 100644 --- a/frontend/src/components/views/chat/detail_viewer.tsx +++ b/frontend/src/components/views/chat/detail_viewer.tsx @@ -21,7 +21,7 @@ interface VncScreenProps { } // Lazy load the VNC component const VncScreen = lazy>(() => - // @ts-ignore + // @ts-expect-error - react-vnc module types are not available import("react-vnc").then((module) => ({ default: module.VncScreen })) ); @@ -64,17 +64,14 @@ const DetailViewer: React.FC = ({ }) => { const [internalActiveTab, setInternalActiveTab] = useState("live"); const activeTab = controlledActiveTab ?? internalActiveTab; - const [viewMode, setViewMode] = useState<"iframe" | "novnc">("iframe"); const vncRef = useRef(); const [isModalOpen, setIsModalOpen] = useState(false); // Add state for fullscreen control mode const [isControlMode, setIsControlMode] = useState(false); - const browserIframeId = "browser-iframe-container"; // State for tracking if control was handed back from modal - const [showControlHandoverForm, setShowControlHandoverForm] = useState(false); // Handle take control action const handleTakeControl = () => { From 3488d0c6e11c3f9eeda7586f1708fdc30e5e2068 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:21:47 +0300 Subject: [PATCH 33/47] refactor(chatinput): simplify component logic and remove unused imports --- .../src/components/views/chat/chatinput.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/views/chat/chatinput.tsx b/frontend/src/components/views/chat/chatinput.tsx index 5e1d470a..24b69fcb 100644 --- a/frontend/src/components/views/chat/chatinput.tsx +++ b/frontend/src/components/views/chat/chatinput.tsx @@ -1,9 +1,7 @@ import { PaperAirplaneIcon, ExclamationTriangleIcon, - CheckCircleIcon, PauseCircleIcon, - XCircleIcon, } from "@heroicons/react/24/outline"; import * as React from "react"; import { appContext } from "../../../hooks/provider"; @@ -20,7 +18,7 @@ import { } from "antd"; import type { UploadFile, UploadProps, RcFile } from "antd/es/upload/interface"; import { FileTextIcon, ImageIcon, XIcon, UploadIcon } from "lucide-react"; -import { InputRequest, ApprovalInputRequest } from "../../types/datamodel"; +import { InputRequest } from "../../types/datamodel"; import { debounce } from "lodash"; import { planAPI } from "../api"; import RelevantPlans from "./relevant_plans"; @@ -65,13 +63,13 @@ const ChatInput = React.forwardRef<{ focus: () => void }, ChatInputProps>( onSubmit, error, disabled = false, - onCancel, + _onCancel, runStatus, inputRequest, isPlanMessage = false, onPause, enable_upload = false, - onExecutePlan, + _onExecutePlan, }, ref ) => { @@ -90,7 +88,7 @@ const ChatInput = React.forwardRef<{ focus: () => void }, ChatInputProps>( const [relevantPlans, setRelevantPlans] = React.useState([]); const [allPlans, setAllPlans] = React.useState([]); const [attachedPlan, setAttachedPlan] = React.useState(null); - const [isLoading, setIsLoading] = React.useState(false); + const [, setIsLoading] = React.useState(false); const userId = user?.email || "default_user"; const [isRelevantPlansVisible, setIsRelevantPlansVisible] = React.useState(false); @@ -601,12 +599,13 @@ const ChatInput = React.forwardRef<{ focus: () => void }, ChatInputProps>( > {/* Attached Plan */} {attachedPlan && ( -
@@ -622,7 +621,7 @@ const ChatInput = React.forwardRef<{ focus: () => void }, ChatInputProps>( }} icon={} /> -
+ )} {/* Attached Files */} @@ -820,4 +819,6 @@ const ChatInput = React.forwardRef<{ focus: () => void }, ChatInputProps>( } ); +ChatInput.displayName = 'ChatInput'; + export default ChatInput; From 6240d9ec8cc08004c32898cc98b82c378c237654 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:21:51 +0300 Subject: [PATCH 34/47] refactor(chat): simplify component logic and improve error handling --- frontend/src/components/views/chat/chat.tsx | 29 ++++++++++++--------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/views/chat/chat.tsx b/frontend/src/components/views/chat/chat.tsx index 1622aac7..583e718c 100644 --- a/frontend/src/components/views/chat/chat.tsx +++ b/frontend/src/components/views/chat/chat.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { message } from "antd"; -import { convertFilesToBase64, getServerUrl } from "../../utils"; +import { convertFilesToBase64 } from "../../utils"; import { IStatus } from "../../types/app"; import { Run, @@ -76,7 +76,6 @@ export default function ChatView({ visible = true, onRunStatusChange, }: ChatViewProps) { - const serverUrl = getServerUrl(); const [error, setError] = React.useState({ status: true, message: "All good", @@ -103,7 +102,7 @@ export default function ChatView({ const [activeSocket, setActiveSocket] = React.useState( null ); - const [teamConfig, setTeamConfig] = React.useState( + const [teamConfig] = React.useState( defaultTeamConfig ); @@ -318,8 +317,9 @@ export default function ChatView({ activeSocketRef.current = null; } console.log("Error: ", message.error); + break; - case "message": + case "message": { if (!message.data) return current; // Create new Message object from websocket data @@ -333,24 +333,26 @@ export default function ChatView({ ...current, messages: [...current.messages, newMessage], }; + } - case "input_request": + case "input_request": { //console.log("InputRequest: " + JSON.stringify(message)) - var input_request: InputRequest; + let input_request: InputRequest; switch (message.input_type) { case "text_input": case null: default: input_request = { input_type: "text_input" }; break; - case "approval": - var input_request_message = message as InputRequestMessage; + case "approval": { + const input_request_message = message as InputRequestMessage; input_request = { input_type: "approval", prompt: input_request_message.prompt, } as InputRequest; break; + } } // reset Updated Plan @@ -368,6 +370,8 @@ export default function ChatView({ status: "awaiting_input", input_request: input_request, }; + } + case "system": // update run status return { @@ -376,7 +380,7 @@ export default function ChatView({ }; case "result": - case "completion": + case "completion": { const status: BaseRunStatus = message.status === "complete" ? "complete" @@ -406,6 +410,7 @@ export default function ChatView({ team_result: message.data && isTeamResult(message.data) ? message.data : null, }; + } default: return current; @@ -442,7 +447,7 @@ export default function ChatView({ try { // Check if the last message is a plan const lastMessage = currentRun.messages.slice(-1)[0]; - var planString = ""; + let planString = ""; if (plan) { planString = convertPlanStepsToJsonString(plan.steps); } else if ( @@ -493,7 +498,7 @@ export default function ChatView({ try { // Check if the last message is a plan const lastMessage = currentRun.messages.slice(-1)[0]; - var planString = ""; + let planString = ""; if ( lastMessage && messageUtils.isPlanMessage(lastMessage.config.metadata) @@ -643,7 +648,7 @@ export default function ChatView({ const processedFiles = await convertFilesToBase64(files); // Send start message - var planString = plan ? convertPlanStepsToJsonString(plan.steps) : ""; + const planString = plan ? convertPlanStepsToJsonString(plan.steps) : ""; const taskJson = { content: query, From c18603280d7aa1ba10e8c42c60099229309b3a7a Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:21:54 +0300 Subject: [PATCH 35/47] refactor(approval): simplify approval buttons logic --- frontend/src/components/views/chat/approval_buttons.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/views/chat/approval_buttons.tsx b/frontend/src/components/views/chat/approval_buttons.tsx index 4554655b..a9e1d20c 100644 --- a/frontend/src/components/views/chat/approval_buttons.tsx +++ b/frontend/src/components/views/chat/approval_buttons.tsx @@ -22,7 +22,7 @@ const ApprovalButtons: React.FC = ({ onAcceptPlan, onRegeneratePlan, }) => { - const [planAcceptText, setPlanAcceptText] = React.useState(""); + const [planAcceptText] = React.useState(""); if (status !== "awaiting_input") { return null; From 44fc927729acc150ad96b1e5e9221539f120f64f Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:21:56 +0300 Subject: [PATCH 36/47] feat(accessibility): improve keyboard navigation in chat detail viewer --- .../components/views/chat/DetailViewer/browser_iframe.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/src/components/views/chat/DetailViewer/browser_iframe.tsx b/frontend/src/components/views/chat/DetailViewer/browser_iframe.tsx index 1867d741..57ea7d4a 100644 --- a/frontend/src/components/views/chat/DetailViewer/browser_iframe.tsx +++ b/frontend/src/components/views/chat/DetailViewer/browser_iframe.tsx @@ -115,6 +115,14 @@ const BrowserIframe: React.FC = ({
{ + if (e.key === 'Enter' || e.key === ' ') { + handleOverlayClick(); + } + }} + role="button" + tabIndex={0} + aria-label="Take control of browser" >
Take Control
From 33f888d6856435ec9ad0f85e4542d88ad25c85e2 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:21:58 +0300 Subject: [PATCH 37/47] fix(feedback): escape apostrophe in feedback form text --- .../src/components/views/chat/DetailViewer/FeedbackForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/views/chat/DetailViewer/FeedbackForm.tsx b/frontend/src/components/views/chat/DetailViewer/FeedbackForm.tsx index 7c6a1e24..cb55c9d7 100644 --- a/frontend/src/components/views/chat/DetailViewer/FeedbackForm.tsx +++ b/frontend/src/components/views/chat/DetailViewer/FeedbackForm.tsx @@ -28,7 +28,7 @@ const FeedbackForm: React.FC = ({

- Magentic-UI can't see what you do when you take control. + Magentic-UI can't see what you do when you take control.

Please describe what you did when you are ready to hand back From db1a95ae9caae17b80deebf498a14d65b418f9a2 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:22:01 +0300 Subject: [PATCH 38/47] feat(modal): improve keyboard navigation and accessibility --- frontend/src/components/common/filerenderer.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/common/filerenderer.tsx b/frontend/src/components/common/filerenderer.tsx index 9e32849b..28fb58de 100644 --- a/frontend/src/components/common/filerenderer.tsx +++ b/frontend/src/components/common/filerenderer.tsx @@ -96,7 +96,7 @@ const FileModal: React.FC = ({ file, content, }) => { - const [, setIsFullScreen] = useState(false); + const [isFullScreen] = useState(false); const modalRef = React.useRef(null); const [downloadUrl, setDownloadUrl] = useState(null); const [isLoading, setIsLoading] = useState(false); @@ -252,15 +252,22 @@ const FileModal: React.FC = ({

e.key === 'Escape' && onClose()} - role="dialog" - aria-modal="true" + onKeyDown={(e) => { + if (e.key === 'Escape') { + handleBackdropClick(e); + } + }} + role="presentation" + aria-label="File modal" + tabIndex={-1} >
{/* Header */}
From 264b83dadec0c67398f31ff128474388883eb7a5 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:22:49 +0300 Subject: [PATCH 39/47] fix(api): handle missing or invalid plan data --- frontend/src/components/views/api.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/views/api.ts b/frontend/src/components/views/api.ts index 5f83eafe..4851c459 100644 --- a/frontend/src/components/views/api.ts +++ b/frontend/src/components/views/api.ts @@ -315,6 +315,7 @@ export class PlanAPI { console.error("Missing or invalid steps in planData:", planData); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { created_at, ...dataWithoutCreatedAt } = planData; const plan = { @@ -366,6 +367,7 @@ export class PlanAPI { throw new Error(data.message || "Failed to delete plan"); } } catch (error) { + console.error("Error deleting plan:", error); throw error; } } From 870092562ff3b56f3784323bf918e85f810fac88 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:30:54 +0300 Subject: [PATCH 40/47] fix(modal): improve keyboard navigation --- frontend/src/components/common/filerenderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/common/filerenderer.tsx b/frontend/src/components/common/filerenderer.tsx index 28fb58de..14b41e2d 100644 --- a/frontend/src/components/common/filerenderer.tsx +++ b/frontend/src/components/common/filerenderer.tsx @@ -254,7 +254,7 @@ const FileModal: React.FC = ({ onClick={handleBackdropClick} onKeyDown={(e) => { if (e.key === 'Escape') { - handleBackdropClick(e); + onClose(); } }} role="presentation" From e880e77821095b77be70373eca0b9947fd3b3c8c Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:30:56 +0300 Subject: [PATCH 41/47] fix: handle error cases in chat view --- frontend/src/components/views/chat/chat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/views/chat/chat.tsx b/frontend/src/components/views/chat/chat.tsx index 583e718c..db5046c3 100644 --- a/frontend/src/components/views/chat/chat.tsx +++ b/frontend/src/components/views/chat/chat.tsx @@ -317,7 +317,7 @@ export default function ChatView({ activeSocketRef.current = null; } console.log("Error: ", message.error); - break; + return current; case "message": { if (!message.data) return current; From bea76606c44a89630d6d0816412e86704e8d5e92 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:30:58 +0300 Subject: [PATCH 42/47] refactor(chatinput): simplify component props --- frontend/src/components/views/chat/chatinput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/views/chat/chatinput.tsx b/frontend/src/components/views/chat/chatinput.tsx index 24b69fcb..854e31e9 100644 --- a/frontend/src/components/views/chat/chatinput.tsx +++ b/frontend/src/components/views/chat/chatinput.tsx @@ -63,13 +63,13 @@ const ChatInput = React.forwardRef<{ focus: () => void }, ChatInputProps>( onSubmit, error, disabled = false, - _onCancel, + onCancel: _onCancel, runStatus, inputRequest, isPlanMessage = false, onPause, enable_upload = false, - _onExecutePlan, + onExecutePlan: _onExecutePlan, }, ref ) => { From 414ac81e15239fb4c979fd680874c90950e40cd2 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:30:59 +0300 Subject: [PATCH 43/47] feat(detail viewer): add view mode property and control handover form --- frontend/src/components/views/chat/detail_viewer.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/components/views/chat/detail_viewer.tsx b/frontend/src/components/views/chat/detail_viewer.tsx index f6f51f9c..674eb353 100644 --- a/frontend/src/components/views/chat/detail_viewer.tsx +++ b/frontend/src/components/views/chat/detail_viewer.tsx @@ -44,6 +44,7 @@ interface DetailViewerProps { accepted?: boolean, plan?: IPlan ) => void; + viewMode?: "iframe" | "vnc"; } type TabType = "screenshots" | "live"; @@ -61,6 +62,7 @@ const DetailViewer: React.FC = ({ onTabChange, detailViewerContainerId, onInputResponse, + viewMode = "iframe", }) => { const [internalActiveTab, setInternalActiveTab] = useState("live"); const activeTab = controlledActiveTab ?? internalActiveTab; @@ -72,6 +74,7 @@ const DetailViewer: React.FC = ({ const [isControlMode, setIsControlMode] = useState(false); // State for tracking if control was handed back from modal + const [showControlHandoverForm, setShowControlHandoverForm] = useState(false); // Handle take control action const handleTakeControl = () => { From 905db801feed3274d793df0ca5c6a7935bc1cb8f Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:31:03 +0300 Subject: [PATCH 44/47] fix(chat): remove onBlur event handler --- frontend/src/components/views/chat/plan.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/views/chat/plan.tsx b/frontend/src/components/views/chat/plan.tsx index 8036cb0b..e8413c4d 100644 --- a/frontend/src/components/views/chat/plan.tsx +++ b/frontend/src/components/views/chat/plan.tsx @@ -225,7 +225,7 @@ const PlanView: React.FC = ({ onChange={( e: React.ChangeEvent ) => updateDetails(index, e.target.value)} - onBlur={() => setFocusedIndex(null)} + onBlur={() => {}} className={`flex-1 p-2 min-w-[100px] max-w-full resize-y bg-[var(--color-bg-secondary)] text-[var(--color-text-primary)] rounded ${ !item.details.trim() ? "border border-orange-300" From 72d5d7935da2d8a83cef2dfc651fb56721d47bc6 Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 00:31:05 +0300 Subject: [PATCH 45/47] refactor(RenderMessage): rename props for clarity --- frontend/src/components/views/chat/rendermessage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/views/chat/rendermessage.tsx b/frontend/src/components/views/chat/rendermessage.tsx index 824528a5..47e97e6d 100644 --- a/frontend/src/components/views/chat/rendermessage.tsx +++ b/frontend/src/components/views/chat/rendermessage.tsx @@ -575,7 +575,7 @@ export const messageUtils = { const RenderUserMessage: React.FC<{ parsedContent: ParsedContent; isUserProxy: boolean; -}> = memo(({ parsedContent, _isUserProxy }) => { +}> = memo(({ parsedContent, isUserProxy }) => { // Parse attached files from metadata if present const attachedFiles: AttachedFile[] = React.useMemo(() => { if (parsedContent.metadata?.attached_files) { @@ -656,7 +656,7 @@ export const RenderMessage: React.FC = memo( sessionId, messageIdx, runStatus, - _isLast = false, + isLast = false, className = "", isEditable = false, hidden = false, From 427be1875c7ef2b50b651c15f844df455563ea6c Mon Sep 17 00:00:00 2001 From: Stefano Amorelli Date: Mon, 30 Jun 2025 22:28:37 +0300 Subject: [PATCH 46/47] fix(detail viewer): simplify state management and event handling --- .../components/views/chat/detail_viewer.tsx | 16 +++---- .../components/views/chat/rendermessage.tsx | 45 +++++++++---------- 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/frontend/src/components/views/chat/detail_viewer.tsx b/frontend/src/components/views/chat/detail_viewer.tsx index 7df2db3c..06a58bfd 100644 --- a/frontend/src/components/views/chat/detail_viewer.tsx +++ b/frontend/src/components/views/chat/detail_viewer.tsx @@ -75,7 +75,7 @@ const DetailViewer: React.FC = ({ const [isControlMode, setIsControlMode] = useState(false); // State for tracking if control was handed back from modal - const [showControlHandoverForm, setShowControlHandoverForm] = useState(false); + const [, setShowControlHandoverForm] = useState(false); const config = useSettingsStore((state) => state.config); @@ -214,8 +214,8 @@ const DetailViewer: React.FC = ({ ) : (
{}} // Moved overlay to BrowserIframe - onMouseLeave={() => {}} // Moved overlay to BrowserIframe + onMouseEnter={() => { }} // Moved overlay to BrowserIframe + onMouseLeave={() => { }} // Moved overlay to BrowserIframe > Loading VNC viewer...
}> = ({
@@ -682,7 +682,6 @@ export const RenderMessage: React.FC = memo( sessionId, messageIdx, runStatus, - isLast = false, className = "", isEditable = false, hidden = false, @@ -726,29 +725,25 @@ export const RenderMessage: React.FC = memo( return (