From 7b30a4908eb2c341995a4985d8790140607f8568 Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Mon, 12 Jan 2026 16:54:38 +0000 Subject: [PATCH] Script Fixer Dialog (Vibe Kanban) (#1968) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * All checks pass. Let me provide a summary of what was implemented: ## Summary I've implemented the Script Fixer Dialog feature. Here's what was created and modified: ### New File Created: 1. **`frontend/src/components/dialogs/scripts/ScriptFixerDialog.tsx`** - A new dialog component that: - Shows the current script content in an editable textarea - Displays the last execution logs using `VirtualizedProcessLogs` - Provides "Save" and "Save and Test" buttons - Supports both setup scripts and dev server scripts - Handles multi-repo scenarios with a repo selector dropdown ### Files Modified: **New UI (Setup Script):** 1. **`ChatScriptEntry.tsx`** - Added `onFix` prop and a "Fix Script" button that appears when a script fails 2. **`NewDisplayConversationEntry.tsx`** - Added `ScriptEntryWithFix` wrapper component that provides the fix handler to `ChatScriptEntry` **Legacy UI (Setup Script):** 3. **`DisplayConversationEntry.tsx`** - Added `ScriptFixButton` component that renders a fix button for failed scripts in the legacy conversation view **Preview/Dev Server:** 4. **`PreviewPanel.tsx`** - Added "Fix Script" button to the troubleshooting alert when dev server has issues 5. **`PreviewBrowser.tsx`** - Added optional `handleFixDevScript` prop and "Fix Script" button alongside "Start Server" 6. **`PreviewBrowserContainer.tsx`** - Wired up the dialog opening for dev server script fixes 7. **`PreviewControls.tsx`** - Added `onFixScript` prop and "Fix Script" button in the dev server controls 8. **`PreviewControlsContainer.tsx`** - Wired up the dialog opening for the controls **Translations:** 9. **`tasks.json` (English)** - Added `scriptFixer` translation keys * Cleanup script changes for workspace 5496d4c0-d1d3-4e6a-b9d0-6a421270eeac * Type check passes. I've updated the `ScriptFixerDialog` to also start the dev server when "Save and Test" is clicked for dev_server script type. * Done! I've updated the `ScriptFixerDialog` to: 1. **Keep the dialog open after "Save and Test"** - The dialog no longer closes after clicking "Save and Test", so the user can watch the logs update in real-time as the script executes 2. **Changed "Cancel" to "Close"** - The button now says "Close" (using `t('common:close')`) and is always enabled, making it clear the user can close the dialog at any time 3. **Logs update automatically** - The `useExecutionProcesses` and `useLogStream` hooks will automatically pick up the new execution process and stream its logs in real-time * Cleanup script changes for workspace 5496d4c0-d1d3-4e6a-b9d0-6a421270eeac * Done! I've fixed the horizontal overflow by adding `min-w-0` to all flex containers and `overflow-hidden` to the `DialogContent`. This prevents flex children from overflowing their container horizontally. * The type check passes. The fix has been applied - the width constraints (`max-w-4xl w-[90vw]`) are now on the `Dialog` component instead of `DialogContent`. This will override the default `max-w-xl` from the Dialog component's base className, allowing the dialog to be wider and preventing horizontal overflow. * Done! I've added the status indicator to the ScriptFixerDialog. Here's what was implemented: **Changes made:** 1. **`ScriptFixerDialog.tsx`:** - Added import for `RunningDots` component - Added status computation logic to determine if the process is running, succeeded, failed, or killed - Added status indicator UI next to the "Last Execution Logs" label showing: - Animated dots + "Running..." when the script is executing - Green dot + "Completed successfully" when script succeeded (exit code 0) - Red dot + "Failed with exit code X" when script failed - Gray dot + "Process was killed" when killed 2. **`tasks.json` (translations):** - Added `statusRunning`, `statusSuccess`, `statusFailed`, and `statusKilled` translation keys The status indicator will update in real-time as the script executes since it's computed from `latestProcess` which is updated via the `useExecutionProcesses` hook. * Fixed! The issue was that `exit_code` can be `null` when the process completes successfully, and comparing `null !== BigInt(0)` returns `true`, which incorrectly triggered the error state. The fix: - Treat `exit_code === null` as success (same as exit code 0) - Only show error when `exit_code` is explicitly a non-zero value * Fixed. The issue was that `exit_code` comes back as a regular number `0` from the API, but the TypeScript type says `bigint`. Using `Number(exitCode) === 0` handles both cases correctly and satisfies the type checker. * Fixed! The translation key was missing the `buttons.` namespace prefix. It now uses `t('common:buttons.close')` which maps to "Close". * Done. Added `bg-error` and `text-error` classes alongside the `destructive` classes for the error state. * styles * Done! I've fixed the issue by passing `sessionId` to the `ScriptFixerDialog` in all three files: 1. **`PreviewPanel.tsx`** - Gets `sessionId` from `devServerProcesses[0]?.session_id` 2. **`PreviewBrowserContainer.tsx`** - Added `devServerProcesses` to destructuring from `usePreviewDevServer` and passes `sessionId` 3. **`PreviewControlsContainer.tsx`** - Already had `devServerProcesses`, just added `sessionId` to the dialog call Now when you click "Save and Test" for a dev server script, the dialog will receive the session ID, connect to the WebSocket stream, and show the execution logs in real-time. * rollback trigger the ScriptFixer for old UI * Done! I've extended the ScriptFixerDialog to support cleanup scripts. Here's a summary of the changes: **1. `frontend/src/i18n/locales/en/tasks.json`** - Added `cleanupScriptTitle: "Fix Cleanup Script"` translation key **2. `frontend/src/components/dialogs/scripts/ScriptFixerDialog.tsx`** - Updated `ScriptType` from `'setup' | 'dev_server'` to `'setup' | 'cleanup' | 'dev_server'` - Updated `latestProcess` filter to use `'cleanupscript'` run_reason for cleanup scripts - Updated script fetching logic to read `cleanup_script` field - Updated both `handleSave` and `handleSaveAndTest` to save `cleanup_script` field - Updated execution logic to call `attemptsApi.runCleanupScript(workspaceId)` for cleanup scripts - Updated dialog title to show "Fix Cleanup Script" for cleanup type - Updated placeholder text to show `#!/bin/bash\nrm -rf node_modules` for cleanup scripts **3. `frontend/src/components/ui-new/NewDisplayConversationEntry.tsx`** - Fixed the scriptType mapping so "Cleanup Script" maps to `'cleanup'` instead of incorrectly mapping to `'setup'` * All tasks are complete. Here's a summary of the changes I made: ## Summary I've implemented the ScriptFixerDialog in the old UI for both `DisplayConversationEntry.tsx` and `PreviewPanel.tsx`. ### Changes Made: **1. `DisplayConversationEntry.tsx`** (`frontend/src/components/NormalizedConversation/DisplayConversationEntry.tsx`): - Added imports for `ScriptFixerDialog`, `ScriptType`, `useAttemptRepo`, `Button`, and `Wrench` icon - Added `SCRIPT_TOOL_NAMES` constant to identify script entries - Added `getScriptType()` helper function to map tool names to script types - Created `ScriptToolCallCard` component that wraps `ToolCallCard` and adds a "Fix Script" button for failed scripts - Modified `renderToolUse()` to detect script entries and render them with `ScriptToolCallCard` **2. `PreviewPanel.tsx`** (`frontend/src/components/panels/PreviewPanel.tsx`): - Added imports for `ScriptFixerDialog`, `useAttemptRepo`, and `Wrench` icon - Added `useAttemptRepo(attemptId)` hook to get repos - Added `handleFixDevScript()` function that opens the ScriptFixerDialog - Added `canFixDevScript` computed value - Added "Fix Dev Script" button in the troubleshooting alert next to the "Stop and Edit" button **3. Translation Keys**: - Added `conversation.fixScript: "Fix Script"` in `en/common.json` - Added `preview.troubleAlert.fixScript: "Fix Dev Script"` in `en/tasks.json` The implementation follows the same pattern as the new UI's `ScriptEntryWithFix` component in `NewDisplayConversationEntry.tsx`, using `useAttemptRepo` hook to fetch repos instead of `useWorkspaceContext` (which is only available in the new design scope). * Cleanup script changes for workspace 5496d4c0-d1d3-4e6a-b9d0-6a421270eeac * All tasks are complete. Here's a summary of the fixes made: ## Summary of Changes ### Problem The "Fix Dev Script" button was only showing in the troubleshooting alert, which only appears when a dev server is **running** but slow to respond. When the dev server script **fails to start** (exits with error), there are no running dev servers, so the user saw `NoServerContent` without any fix option. ### Solution **1. `PreviewPanel.tsx`** - Added failed dev server detection: ```typescript const failedDevServerProcess = devServerProcesses.find( (p) => p.status === 'failed' || (p.status === 'completed' && p.exit_code !== null && p.exit_code !== 0n) ); const hasFailedDevServer = Boolean(failedDevServerProcess); ``` And passed `hasFailedDevServer` and `onFixDevScript` to `NoServerContent`. **2. `NoServerContent.tsx`** - Added new props and "Fix Dev Script" button: - Added `hasFailedDevServer?: boolean` and `onFixDevScript?: () => void` props - Added fix button that shows when dev server has failed **3. Translation Keys** - Added `preview.noServer.fixScript: "Fix Dev Script"` to `en/tasks.json` Now when a user tries to start a dev server and it fails, they'll see the "Fix Dev Script" button in the `NoServerContent` view, allowing them to open the ScriptFixerDialog to fix the script. * Done! I've successfully added all the Script Fixer i18n translations to the 5 non-English locale files: **Summary of changes:** ### common.json files (5 files updated) Added `fixScript` key to the `conversation` section: - **es**: `"Corregir Script"` - **ja**: `"スクリプトを修正"` - **ko**: `"스크립트 수정"` - **zh-Hans**: `"修复脚本"` - **zh-Hant**: `"修復腳本"` ### tasks.json files (5 files updated) 1. Added `fixScript` to `preview.noServer` section 2. Added `fixScript` to `preview.troubleAlert` section 3. Added the entire `scriptFixer` section with 17 keys including: - `title`, `setupScriptTitle`, `cleanupScriptTitle`, `devServerTitle` - `scriptLabel`, `logsLabel` - `saveButton`, `saveAndTestButton`, `saving`, `testing` - `noLogs`, `selectRepo`, `fixScript` - `statusRunning`, `statusSuccess`, `statusFailed`, `statusKilled` All type checks (`pnpm run check`) passed successfully. * Both lint and type check pass now. Here's a summary of the lint fixes I made: **Fixed lint issues in:** 1. **`ScriptFixerDialog.tsx`** (lines 198 and 253): - Added missing `repos` dependency to both `handleSave` and `handleSaveAndTest` useCallback hooks - Removed unnecessary `modal` dependency from `handleSaveAndTest` 2. **`NewDisplayConversationEntry.tsx`** (line 658): - Used a `useRef` pattern to access `repos` inside the `handleFix` callback without including it in the dependency array, since the `repos` array is recreated on every render from context * All checks pass. The fix is complete. **Summary:** The "Fix Script" button in `PreviewControls` will now only appear when the latest dev server process has a status of `'failed'`. When the dev server is running, completed successfully, or was killed by the user, the button will be hidden. --- .../DisplayConversationEntry.tsx | 107 +++++ .../dialogs/scripts/ScriptFixerDialog.tsx | 420 ++++++++++++++++++ .../src/components/panels/PreviewPanel.tsx | 61 ++- .../TaskDetails/preview/NoServerContent.tsx | 17 + .../ui-new/NewDisplayConversationEntry.tsx | 76 +++- .../containers/PreviewBrowserContainer.tsx | 21 +- .../containers/PreviewControlsContainer.tsx | 25 ++ .../conversation/ChatScriptEntry.tsx | 22 +- .../ui-new/views/PreviewBrowser.tsx | 26 +- .../ui-new/views/PreviewControls.tsx | 11 + frontend/src/i18n/locales/en/common.json | 3 +- frontend/src/i18n/locales/en/tasks.json | 25 +- frontend/src/i18n/locales/es/common.json | 3 +- frontend/src/i18n/locales/es/tasks.json | 25 +- frontend/src/i18n/locales/ja/common.json | 3 +- frontend/src/i18n/locales/ja/tasks.json | 25 +- frontend/src/i18n/locales/ko/common.json | 3 +- frontend/src/i18n/locales/ko/tasks.json | 25 +- frontend/src/i18n/locales/zh-Hans/common.json | 3 +- frontend/src/i18n/locales/zh-Hans/tasks.json | 25 +- frontend/src/i18n/locales/zh-Hant/common.json | 3 +- frontend/src/i18n/locales/zh-Hant/tasks.json | 25 +- 22 files changed, 915 insertions(+), 39 deletions(-) create mode 100644 frontend/src/components/dialogs/scripts/ScriptFixerDialog.tsx diff --git a/frontend/src/components/NormalizedConversation/DisplayConversationEntry.tsx b/frontend/src/components/NormalizedConversation/DisplayConversationEntry.tsx index 4803d365..52b4c9db 100644 --- a/frontend/src/components/NormalizedConversation/DisplayConversationEntry.tsx +++ b/frontend/src/components/NormalizedConversation/DisplayConversationEntry.tsx @@ -1,3 +1,4 @@ +import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import WYSIWYGEditor from '@/components/ui/wysiwyg'; import { @@ -27,6 +28,7 @@ import { Settings, Terminal, User, + Wrench, } from 'lucide-react'; import RawLogText from '../common/RawLogText'; import UserMessage from './UserMessage'; @@ -34,6 +36,12 @@ import PendingApprovalEntry from './PendingApprovalEntry'; import { NextActionCard } from './NextActionCard'; import { cn } from '@/lib/utils'; import { useRetryUi } from '@/contexts/RetryUiContext'; +import { Button } from '@/components/ui/button'; +import { + ScriptFixerDialog, + type ScriptType, +} from '@/components/dialogs/scripts/ScriptFixerDialog'; +import { useAttemptRepo } from '@/hooks/useAttemptRepo'; type Props = { entry: NormalizedEntry | ProcessStartPayload; @@ -582,6 +590,80 @@ const ToolCallCard: React.FC<{ ); }; +// Script tool names that can be fixed +const SCRIPT_TOOL_NAMES = [ + 'Setup Script', + 'Cleanup Script', + 'Tool Install Script', +]; + +const getScriptType = (toolName: string): ScriptType => { + if (toolName === 'Setup Script') return 'setup'; + if (toolName === 'Cleanup Script') return 'cleanup'; + return 'dev_server'; // Tool Install Script +}; + +const ScriptToolCallCard: React.FC<{ + entry: NormalizedEntry | ProcessStartPayload; + expansionKey: string; + taskAttemptId?: string; + sessionId?: string; + isFailed: boolean; + toolName: string; + forceExpanded?: boolean; +}> = ({ + entry, + expansionKey, + taskAttemptId, + sessionId, + isFailed, + toolName, + forceExpanded = false, +}) => { + const { t } = useTranslation('common'); + const { repos } = useAttemptRepo(taskAttemptId); + + const handleFix = useCallback(() => { + if (!taskAttemptId || repos.length === 0) return; + + const scriptType = getScriptType(toolName); + + ScriptFixerDialog.show({ + scriptType, + repos, + workspaceId: taskAttemptId, + sessionId, + initialRepoId: repos.length === 1 ? repos[0].id : undefined, + }); + }, [toolName, taskAttemptId, sessionId, repos]); + + const canFix = taskAttemptId && repos.length > 0 && isFailed; + + return ( +
+
+ +
+ {canFix && ( + + )} +
+ ); +}; + const LoadingCard = () => { return (
@@ -734,6 +816,31 @@ function DisplayConversationEntry({ ); } + // Script entries (Setup Script, Cleanup Script, Tool Install Script) + if ( + toolEntry.action_type.action === 'command_run' && + SCRIPT_TOOL_NAMES.includes(toolEntry.tool_name) + ) { + const actionType = toolEntry.action_type; + const exitCode = + actionType.result?.exit_status?.type === 'exit_code' + ? actionType.result.exit_status.code + : null; + const isFailed = exitCode !== null && exitCode !== 0; + + return ( + + ); + } + return ( ; + +const ScriptFixerDialogImpl = NiceModal.create( + ({ scriptType, repos, workspaceId, sessionId, initialRepoId }) => { + const modal = useModal(); + const { t } = useTranslation(['tasks', 'common']); + const queryClient = useQueryClient(); + + // State + const [selectedRepoId, setSelectedRepoId] = useState( + initialRepoId || repos[0]?.id || '' + ); + const [script, setScript] = useState(''); + const [originalScript, setOriginalScript] = useState(''); + const [isLoadingRepo, setIsLoadingRepo] = useState(true); + const [isSaving, setIsSaving] = useState(false); + const [isTesting, setIsTesting] = useState(false); + const [error, setError] = useState(null); + + // Get execution processes for the session to find latest script process + const { executionProcesses } = useExecutionProcesses(sessionId); + + // Find the latest process for this script type + const latestProcess = useMemo(() => { + const runReason = + scriptType === 'setup' + ? 'setupscript' + : scriptType === 'cleanup' + ? 'cleanupscript' + : 'devserver'; + const filtered = executionProcesses.filter( + (p) => p.run_reason === runReason && !p.dropped + ); + // Sort by created_at descending and return the first one + return filtered.sort( + (a, b) => + new Date(b.created_at as unknown as string).getTime() - + new Date(a.created_at as unknown as string).getTime() + )[0]; + }, [executionProcesses, scriptType]); + + // Stream logs for the latest process + const { logs: rawLogs, error: logsError } = useLogStream( + latestProcess?.id ?? '' + ); + const logs: LogEntry[] = rawLogs.filter( + (l): l is LogEntry => l.type === 'STDOUT' || l.type === 'STDERR' + ); + + // Compute status for the latest process + const isProcessRunning = latestProcess?.status === 'running'; + const isProcessCompleted = latestProcess?.status === 'completed'; + const isProcessKilled = latestProcess?.status === 'killed'; + const isProcessFailed = latestProcess?.status === 'failed'; + // exit_code can be null, number, or BigInt - convert to Number for comparison + const exitCode = latestProcess?.exit_code; + const isExitCodeZero = exitCode == null || Number(exitCode) === 0; + const isProcessSuccessful = isProcessCompleted && isExitCodeZero; + const hasProcessError = + isProcessFailed || (isProcessCompleted && !isExitCodeZero); + + // Fetch the selected repo's script + useEffect(() => { + if (!selectedRepoId) return; + + let cancelled = false; + setIsLoadingRepo(true); + setError(null); + + (async () => { + try { + const repo = await repoApi.getById(selectedRepoId); + if (cancelled) return; + + const scriptContent = + scriptType === 'setup' + ? (repo.setup_script ?? '') + : scriptType === 'cleanup' + ? (repo.cleanup_script ?? '') + : (repo.dev_server_script ?? ''); + + setScript(scriptContent); + setOriginalScript(scriptContent); + } catch (err) { + if (cancelled) return; + setError( + err instanceof Error ? err.message : t('common:error.generic') + ); + } finally { + if (!cancelled) setIsLoadingRepo(false); + } + })(); + + return () => { + cancelled = true; + }; + }, [selectedRepoId, scriptType, t]); + + const hasChanges = script !== originalScript; + + const handleClose = useCallback(() => { + modal.resolve({ action: 'canceled' } as ScriptFixerDialogResult); + modal.hide(); + }, [modal]); + + const handleOpenChange = (open: boolean) => { + if (!open) { + handleClose(); + } + }; + + const handleSave = useCallback(async () => { + if (!selectedRepoId) return; + + setIsSaving(true); + setError(null); + + try { + // Build update data with all required fields + const selectedRepo = repos.find((r) => r.id === selectedRepoId); + const updateData: UpdateRepo = { + display_name: selectedRepo?.display_name ?? null, + setup_script: + scriptType === 'setup' + ? script.trim() || null + : (selectedRepo?.setup_script ?? null), + cleanup_script: + scriptType === 'cleanup' + ? script.trim() || null + : (selectedRepo?.cleanup_script ?? null), + copy_files: selectedRepo?.copy_files ?? null, + parallel_setup_script: selectedRepo?.parallel_setup_script ?? null, + dev_server_script: + scriptType === 'dev_server' + ? script.trim() || null + : (selectedRepo?.dev_server_script ?? null), + }; + + await repoApi.update(selectedRepoId, updateData); + + // Invalidate repos cache + queryClient.invalidateQueries({ queryKey: ['repos'] }); + + setOriginalScript(script); + modal.resolve({ action: 'saved' } as ScriptFixerDialogResult); + modal.hide(); + } catch (err) { + setError( + err instanceof Error ? err.message : t('common:error.generic') + ); + } finally { + setIsSaving(false); + } + }, [selectedRepoId, script, scriptType, queryClient, modal, t, repos]); + + const handleSaveAndTest = useCallback(async () => { + if (!selectedRepoId) return; + + setIsTesting(true); + setError(null); + + try { + // First save the script + const selectedRepo = repos.find((r) => r.id === selectedRepoId); + const updateData: UpdateRepo = { + display_name: selectedRepo?.display_name ?? null, + setup_script: + scriptType === 'setup' + ? script.trim() || null + : (selectedRepo?.setup_script ?? null), + cleanup_script: + scriptType === 'cleanup' + ? script.trim() || null + : (selectedRepo?.cleanup_script ?? null), + copy_files: selectedRepo?.copy_files ?? null, + parallel_setup_script: selectedRepo?.parallel_setup_script ?? null, + dev_server_script: + scriptType === 'dev_server' + ? script.trim() || null + : (selectedRepo?.dev_server_script ?? null), + }; + + await repoApi.update(selectedRepoId, updateData); + + // Invalidate repos cache + queryClient.invalidateQueries({ queryKey: ['repos'] }); + + setOriginalScript(script); + + // Then run the script + if (scriptType === 'setup') { + await attemptsApi.runSetupScript(workspaceId); + } else if (scriptType === 'cleanup') { + await attemptsApi.runCleanupScript(workspaceId); + } else { + // Start the dev server + await attemptsApi.startDevServer(workspaceId); + } + + // Keep dialog open so user can see the new execution logs + // The logs will update automatically via useLogStream/useExecutionProcesses + } catch (err) { + setError( + err instanceof Error ? err.message : t('common:error.generic') + ); + } finally { + setIsTesting(false); + } + }, [ + selectedRepoId, + script, + scriptType, + workspaceId, + queryClient, + t, + repos, + ]); + + const dialogTitle = + scriptType === 'setup' + ? t('scriptFixer.setupScriptTitle') + : scriptType === 'cleanup' + ? t('scriptFixer.cleanupScriptTitle') + : t('scriptFixer.devServerTitle'); + + return ( + + + + {dialogTitle} + + +
+ {/* Repo selector (only show if multiple repos) */} + {repos.length > 1 && ( +
+ + +
+ )} + + {/* Script editor */} +
+ +
+ {isLoadingRepo ? ( +
+ +
+ ) : ( + setScript(e.target.value)} + className="font-mono text-sm p-3 border-0 min-h-full bg-panel" + placeholder={ + scriptType === 'setup' + ? '#!/bin/bash\nnpm install' + : scriptType === 'cleanup' + ? '#!/bin/bash\nrm -rf node_modules' + : '#!/bin/bash\nnpm run dev' + } + disableInternalScroll + /> + )} +
+
+ + {/* Logs section */} +
+
+ + {/* Status indicator */} + {latestProcess && ( +
+ {isProcessRunning ? ( + <> + + + {t('scriptFixer.statusRunning')} + + + ) : isProcessSuccessful ? ( + <> + + + {t('scriptFixer.statusSuccess')} + + + ) : hasProcessError ? ( + <> + + + {t('scriptFixer.statusFailed', { + exitCode: Number(latestProcess.exit_code ?? 0), + })} + + + ) : isProcessKilled ? ( + <> + + + {t('scriptFixer.statusKilled')} + + + ) : null} +
+ )} +
+
+ {latestProcess ? ( + + ) : ( +
+ {t('scriptFixer.noLogs')} +
+ )} +
+
+ + {/* Error display */} + {error &&
{error}
} +
+ + + + + + +
+
+ ); + } +); + +export const ScriptFixerDialog = defineModal< + ScriptFixerDialogProps, + ScriptFixerDialogResult +>(ScriptFixerDialogImpl); diff --git a/frontend/src/components/panels/PreviewPanel.tsx b/frontend/src/components/panels/PreviewPanel.tsx index 571a93d2..7ce9fbdb 100644 --- a/frontend/src/components/panels/PreviewPanel.tsx +++ b/frontend/src/components/panels/PreviewPanel.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from 'react'; import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { Loader2, X } from 'lucide-react'; +import { Loader2, X, Wrench } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useDevserverPreview } from '@/hooks/useDevserverPreview'; import { useDevServer } from '@/hooks/useDevServer'; @@ -16,6 +16,8 @@ import { DevServerLogsView } from '@/components/tasks/TaskDetails/preview/DevSer import { PreviewToolbar } from '@/components/tasks/TaskDetails/preview/PreviewToolbar'; import { NoServerContent } from '@/components/tasks/TaskDetails/preview/NoServerContent'; import { ReadyContent } from '@/components/tasks/TaskDetails/preview/ReadyContent'; +import { ScriptFixerDialog } from '@/components/dialogs/scripts/ScriptFixerDialog'; +import { useAttemptRepo } from '@/hooks/useAttemptRepo'; export function PreviewPanel() { const [iframeError, setIframeError] = useState(false); @@ -35,6 +37,7 @@ export function PreviewPanel() { rawAttemptId && rawAttemptId !== 'latest' ? rawAttemptId : undefined; const { data: projectHasDevScript = false } = useHasDevServerScript(projectId); + const { repos } = useAttemptRepo(attemptId); const { start: startDevServer, @@ -112,6 +115,14 @@ export function PreviewPanel() { const hasRunningDevServer = runningDevServers.length > 0; + // Detect failed dev server process (failed status or completed with non-zero exit code) + const failedDevServerProcess = devServerProcesses.find( + (p) => + p.status === 'failed' || + (p.status === 'completed' && p.exit_code !== null && p.exit_code !== 0n) + ); + const hasFailedDevServer = Boolean(failedDevServerProcess); + useEffect(() => { if ( loadingTimeFinished && @@ -161,6 +172,22 @@ export function PreviewPanel() { }); }; + const handleFixDevScript = () => { + if (!attemptId || repos.length === 0) return; + + const sessionId = devServerProcesses[0]?.session_id; + + ScriptFixerDialog.show({ + scriptType: 'dev_server', + repos, + workspaceId: attemptId, + sessionId, + initialRepoId: repos.length === 1 ? repos[0].id : undefined, + }); + }; + + const canFixDevScript = attemptId && repos.length > 0; + if (!attemptId) { return (
@@ -202,6 +229,8 @@ export function PreviewPanel() { startDevServer={handleStartDevServer} stopDevServer={stopDevServer} project={project} + hasFailedDevServer={hasFailedDevServer} + onFixDevScript={canFixDevScript ? handleFixDevScript : undefined} /> )} @@ -229,16 +258,28 @@ export function PreviewPanel() { . - + {canFixDevScript && ( + )} - {t('preview.noServer.stopAndEditButton')} - +
)} + + {hasFailedDevServer && onFixDevScript && ( + + )}
diff --git a/frontend/src/components/ui-new/NewDisplayConversationEntry.tsx b/frontend/src/components/ui-new/NewDisplayConversationEntry.tsx index e7b84dc1..dcf63e38 100644 --- a/frontend/src/components/ui-new/NewDisplayConversationEntry.tsx +++ b/frontend/src/components/ui-new/NewDisplayConversationEntry.tsx @@ -7,6 +7,7 @@ import { ToolStatus, TodoItem, type TaskWithAttemptStatus, + type RepoWithTargetBranch, } from 'shared/types'; import type { WorkspaceWithSession } from '@/types/attempt'; import { DiffLineType, parseInstance } from '@git-diff-view/react'; @@ -18,7 +19,12 @@ import DisplayConversationEntry from '@/components/NormalizedConversation/Displa import { useMessageEditContext } from '@/contexts/MessageEditContext'; import { useFileNavigation } from '@/contexts/FileNavigationContext'; import { useLogNavigation } from '@/contexts/LogNavigationContext'; +import { useWorkspaceContext } from '@/contexts/WorkspaceContext'; import { cn } from '@/lib/utils'; +import { + ScriptFixerDialog, + type ScriptType, +} from '@/components/dialogs/scripts/ScriptFixerDialog'; import { ChatToolSummary, ChatTodoList, @@ -215,11 +221,13 @@ function renderToolUseEntry( : null; return ( - ); } @@ -628,6 +636,72 @@ function SystemMessageEntry({ ); } +/** + * Script entry with fix button for failed scripts + */ +function ScriptEntryWithFix({ + title, + processId, + exitCode, + status, + workspaceId, + sessionId, +}: { + title: string; + processId: string; + exitCode: number | null; + status: ToolStatus; + workspaceId?: string; + sessionId?: string; +}) { + // Try to get repos from workspace context - may not be available in all contexts + let repos: RepoWithTargetBranch[] = []; + try { + const workspaceContext = useWorkspaceContext(); + repos = workspaceContext.repos; + } catch { + // Context not available, fix button won't be shown + } + + // Use ref to access current repos without causing callback recreation + const reposRef = useRef(repos); + reposRef.current = repos; + + const handleFix = useCallback(() => { + const currentRepos = reposRef.current; + if (!workspaceId || currentRepos.length === 0) return; + + // Determine script type based on title + const scriptType: ScriptType = + title === 'Setup Script' + ? 'setup' + : title === 'Cleanup Script' + ? 'cleanup' + : 'dev_server'; + + ScriptFixerDialog.show({ + scriptType, + repos: currentRepos, + workspaceId, + sessionId, + initialRepoId: currentRepos.length === 1 ? currentRepos[0].id : undefined, + }); + }, [title, workspaceId, sessionId]); + + // Only show fix button if we have the necessary context + const canFix = workspaceId && repos.length > 0; + + return ( + + ); +} + /** * Error message entry with expandable content */ diff --git a/frontend/src/components/ui-new/containers/PreviewBrowserContainer.tsx b/frontend/src/components/ui-new/containers/PreviewBrowserContainer.tsx index da6831b4..55c0572a 100644 --- a/frontend/src/components/ui-new/containers/PreviewBrowserContainer.tsx +++ b/frontend/src/components/ui-new/containers/PreviewBrowserContainer.tsx @@ -6,6 +6,7 @@ import { useLogStream } from '@/hooks/useLogStream'; import { useLayoutStore } from '@/stores/useLayoutStore'; import { useWorkspaceContext } from '@/contexts/WorkspaceContext'; import { useNavigate } from 'react-router-dom'; +import { ScriptFixerDialog } from '@/components/dialogs/scripts/ScriptFixerDialog'; interface PreviewBrowserContainerProps { attemptId?: string; @@ -20,7 +21,7 @@ export function PreviewBrowserContainer({ const previewRefreshKey = useLayoutStore((s) => s.previewRefreshKey); const { repos } = useWorkspaceContext(); - const { start, isStarting, runningDevServers } = + const { start, isStarting, runningDevServers, devServerProcesses } = usePreviewDevServer(attemptId); const primaryDevServer = runningDevServers[0]; @@ -44,6 +45,21 @@ export function PreviewBrowserContainer({ } }; + const handleFixDevScript = useCallback(() => { + if (!attemptId || repos.length === 0) return; + + // Get session ID from the latest dev server process + const sessionId = devServerProcesses[0]?.session_id; + + ScriptFixerDialog.show({ + scriptType: 'dev_server', + repos, + workspaceId: attemptId, + sessionId, + initialRepoId: repos.length === 1 ? repos[0].id : undefined, + }); + }, [attemptId, repos, devServerProcesses]); + return ( 0} repos={repos} handleEditDevScript={handleEditDevScript} + handleFixDevScript={ + attemptId && repos.length > 0 ? handleFixDevScript : undefined + } className={className} /> ); diff --git a/frontend/src/components/ui-new/containers/PreviewControlsContainer.tsx b/frontend/src/components/ui-new/containers/PreviewControlsContainer.tsx index 8ceb191e..4259c3e3 100644 --- a/frontend/src/components/ui-new/containers/PreviewControlsContainer.tsx +++ b/frontend/src/components/ui-new/containers/PreviewControlsContainer.tsx @@ -5,6 +5,7 @@ import { usePreviewUrl } from '../hooks/usePreviewUrl'; import { useLogStream } from '@/hooks/useLogStream'; import { useLayoutStore } from '@/stores/useLayoutStore'; import { useWorkspaceContext } from '@/contexts/WorkspaceContext'; +import { ScriptFixerDialog } from '@/components/dialogs/scripts/ScriptFixerDialog'; interface PreviewControlsContainerProps { attemptId?: string; @@ -88,10 +89,29 @@ export function PreviewControlsContainer({ } }, [urlInfo?.url]); + const handleFixScript = useCallback(() => { + if (!attemptId || repos.length === 0) return; + + // Get session ID from the latest dev server process + const sessionId = devServerProcesses[0]?.session_id; + + ScriptFixerDialog.show({ + scriptType: 'dev_server', + repos, + workspaceId: attemptId, + sessionId, + initialRepoId: repos.length === 1 ? repos[0].id : undefined, + }); + }, [attemptId, repos, devServerProcesses]); + const hasDevScript = repos.some( (repo) => repo.dev_server_script && repo.dev_server_script.trim() !== '' ); + // Only show "Fix Script" button when the latest dev server process failed + const latestDevServerFailed = + devServerProcesses.length > 0 && devServerProcesses[0]?.status === 'failed'; + // Don't render if no repos have dev server scripts configured if (!hasDevScript) { return null; @@ -111,6 +131,11 @@ export function PreviewControlsContainer({ onRefresh={handleRefresh} onCopyUrl={handleCopyUrl} onOpenInNewTab={handleOpenInNewTab} + onFixScript={ + attemptId && repos.length > 0 && latestDevServerFailed + ? handleFixScript + : undefined + } isStarting={isStarting} isStopping={isStopping} isServerRunning={runningDevServers.length > 0} diff --git a/frontend/src/components/ui-new/primitives/conversation/ChatScriptEntry.tsx b/frontend/src/components/ui-new/primitives/conversation/ChatScriptEntry.tsx index 16a6d859..4bfb0488 100644 --- a/frontend/src/components/ui-new/primitives/conversation/ChatScriptEntry.tsx +++ b/frontend/src/components/ui-new/primitives/conversation/ChatScriptEntry.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next'; -import { TerminalIcon } from '@phosphor-icons/react'; +import { TerminalIcon, WrenchIcon } from '@phosphor-icons/react'; import { cn } from '@/lib/utils'; import { ToolStatus } from 'shared/types'; import { ToolStatusDot } from './ToolStatusDot'; @@ -11,6 +11,7 @@ interface ChatScriptEntryProps { exitCode?: number | null; className?: string; status: ToolStatus; + onFix?: () => void; } export function ChatScriptEntry({ @@ -19,6 +20,7 @@ export function ChatScriptEntry({ exitCode, className, status, + onFix, }: ChatScriptEntryProps) { const { t } = useTranslation('tasks'); const { viewProcessInPanel } = useLogNavigation(); @@ -26,6 +28,11 @@ export function ChatScriptEntry({ const isSuccess = status.status === 'success'; const isFailed = status.status === 'failed'; + const handleFixClick = (e: React.MouseEvent) => { + e.stopPropagation(); + onFix?.(); + }; + const handleClick = () => { viewProcessInPanel(processId); }; @@ -66,10 +73,21 @@ export function ChatScriptEntry({ className="absolute -bottom-0.5 -left-0.5" /> -
+
{title} {getSubtitle()}
+ {isFailed && onFix && ( + + )}
); } diff --git a/frontend/src/components/ui-new/views/PreviewBrowser.tsx b/frontend/src/components/ui-new/views/PreviewBrowser.tsx index c2101fdc..ce005f67 100644 --- a/frontend/src/components/ui-new/views/PreviewBrowser.tsx +++ b/frontend/src/components/ui-new/views/PreviewBrowser.tsx @@ -1,4 +1,4 @@ -import { PlayIcon, SpinnerIcon } from '@phosphor-icons/react'; +import { PlayIcon, SpinnerIcon, WrenchIcon } from '@phosphor-icons/react'; import { useTranslation } from 'react-i18next'; import { cn } from '@/lib/utils'; import { PrimaryButton } from '../primitives/PrimaryButton'; @@ -11,6 +11,7 @@ interface PreviewBrowserProps { isServerRunning: boolean; repos: Repo[]; handleEditDevScript: () => void; + handleFixDevScript?: () => void; className?: string; } @@ -21,6 +22,7 @@ export function PreviewBrowser({ isServerRunning, repos, handleEditDevScript, + handleFixDevScript, className, }: PreviewBrowserProps) { const { t } = useTranslation(['tasks', 'common']); @@ -62,12 +64,22 @@ export function PreviewBrowser({ ) : hasDevScript ? ( <>

{t('preview.noServer.title')}

- +
+ + {handleFixDevScript && ( + + )} +
) : (
diff --git a/frontend/src/components/ui-new/views/PreviewControls.tsx b/frontend/src/components/ui-new/views/PreviewControls.tsx index f28963b8..31520cd0 100644 --- a/frontend/src/components/ui-new/views/PreviewControls.tsx +++ b/frontend/src/components/ui-new/views/PreviewControls.tsx @@ -5,6 +5,7 @@ import { ArrowClockwiseIcon, SpinnerIcon, CopyIcon, + WrenchIcon, } from '@phosphor-icons/react'; import { useTranslation } from 'react-i18next'; import { cn } from '@/lib/utils'; @@ -30,6 +31,7 @@ interface PreviewControlsProps { onRefresh: () => void; onCopyUrl: () => void; onOpenInNewTab: () => void; + onFixScript?: () => void; isStarting: boolean; isStopping: boolean; isServerRunning: boolean; @@ -49,6 +51,7 @@ export function PreviewControls({ onRefresh, onCopyUrl, onOpenInNewTab, + onFixScript, isStarting, isStopping, isServerRunning, @@ -118,6 +121,14 @@ export function PreviewControls({ disabled={isStarting} /> )} + {onFixScript && ( + + )}
diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json index 518ab60e..6349d00e 100644 --- a/frontend/src/i18n/locales/en/common.json +++ b/frontend/src/i18n/locales/en/common.json @@ -49,7 +49,8 @@ "ranCommand": "Ran command", "createdTask": "Created task: {{description}}", "todoOperation": "{{operation}} todos" - } + }, + "fixScript": "Fix Script" }, "folderPicker": { "legend": "Click folder names to navigate • Use action buttons to select", diff --git a/frontend/src/i18n/locales/en/tasks.json b/frontend/src/i18n/locales/en/tasks.json index 73c2679f..82901c34 100644 --- a/frontend/src/i18n/locales/en/tasks.json +++ b/frontend/src/i18n/locales/en/tasks.json @@ -79,7 +79,8 @@ "item2": "Did your dev server print the URL and port to the terminal in the format", "item2Suffix": "? (this is how we know it's running)", "item3": "Have you installed the Web Companion (required for click-to-edit)? If not, please", - "item3Link": "follow the installation instructions here" + "item3Link": "follow the installation instructions here", + "fixScript": "Fix Dev Script" }, "noServer": { "title": "No dev server running", @@ -89,7 +90,8 @@ "companionLink": "View installation guide", "startButton": "Start Dev Server", "configureButton": "Configure", - "stopAndEditButton": "Stop Dev Server & Resolve Issues" + "stopAndEditButton": "Stop Dev Server & Resolve Issues", + "fixScript": "Fix Dev Script" }, "devScript": { "saveAndStart": "Save & Start", @@ -655,5 +657,24 @@ "buttons": { "retry": "Retry" } + }, + "scriptFixer": { + "title": "Fix Script", + "setupScriptTitle": "Fix Setup Script", + "cleanupScriptTitle": "Fix Cleanup Script", + "devServerTitle": "Fix Dev Server Script", + "scriptLabel": "Script (edit)", + "logsLabel": "Last Execution Logs", + "saveButton": "Save", + "saveAndTestButton": "Save and Test", + "saving": "Saving...", + "testing": "Testing...", + "noLogs": "No execution logs available", + "selectRepo": "Repository", + "fixScript": "Fix Script", + "statusRunning": "Running...", + "statusSuccess": "Completed successfully", + "statusFailed": "Failed with exit code {{exitCode}}", + "statusKilled": "Process was killed" } } diff --git a/frontend/src/i18n/locales/es/common.json b/frontend/src/i18n/locales/es/common.json index 4c447dcc..c559a9b9 100644 --- a/frontend/src/i18n/locales/es/common.json +++ b/frontend/src/i18n/locales/es/common.json @@ -60,7 +60,8 @@ "ranCommand": "Ejecutó comando", "createdTask": "Creó tarea: {{description}}", "todoOperation": "{{operation}} tareas pendientes" - } + }, + "fixScript": "Corregir Script" }, "language": { "browserDefault": "Predeterminado del navegador" diff --git a/frontend/src/i18n/locales/es/tasks.json b/frontend/src/i18n/locales/es/tasks.json index 2087a854..3036b037 100644 --- a/frontend/src/i18n/locales/es/tasks.json +++ b/frontend/src/i18n/locales/es/tasks.json @@ -360,7 +360,8 @@ "startButton": "Iniciar Servidor de Desarrollo", "startPrompt": "Por favor inicia un servidor de desarrollo para ver la vista previa", "stopAndEditButton": "Detener Servidor de Desarrollo y Resolver Problemas", - "title": "No hay servidor de desarrollo en ejecución" + "title": "No hay servidor de desarrollo en ejecución", + "fixScript": "Corregir Script de Desarrollo" }, "selectAttempt": "Select an attempt to see preview", "title": "Preview", @@ -378,7 +379,8 @@ "item2Suffix": "? (así es como sabemos que está funcionando)", "item3": "¿Has instalado el Web Companion (requerido para hacer clic y editar)? Si no, por favor", "item3Link": "sigue las instrucciones de instalación aquí", - "title": "Tenemos problemas al previsualizar tu aplicación:" + "title": "Tenemos problemas al previsualizar tu aplicación:", + "fixScript": "Corregir Script de Desarrollo" } }, "processes": { @@ -655,5 +657,24 @@ "deleted": "Eliminado", "renamed": "Renombrado" } + }, + "scriptFixer": { + "title": "Corregir Script", + "setupScriptTitle": "Corregir Script de Configuración", + "cleanupScriptTitle": "Corregir Script de Limpieza", + "devServerTitle": "Corregir Script del Servidor de Desarrollo", + "scriptLabel": "Script (editar)", + "logsLabel": "Últimos Registros de Ejecución", + "saveButton": "Guardar", + "saveAndTestButton": "Guardar y Probar", + "saving": "Guardando...", + "testing": "Probando...", + "noLogs": "No hay registros de ejecución disponibles", + "selectRepo": "Repositorio", + "fixScript": "Corregir Script", + "statusRunning": "Ejecutando...", + "statusSuccess": "Completado exitosamente", + "statusFailed": "Falló con código de salida {{exitCode}}", + "statusKilled": "El proceso fue terminado" } } diff --git a/frontend/src/i18n/locales/ja/common.json b/frontend/src/i18n/locales/ja/common.json index 86cad25b..3932d811 100644 --- a/frontend/src/i18n/locales/ja/common.json +++ b/frontend/src/i18n/locales/ja/common.json @@ -60,7 +60,8 @@ "ranCommand": "コマンドを実行", "createdTask": "タスクを作成: {{description}}", "todoOperation": "{{operation}} Todo" - } + }, + "fixScript": "スクリプトを修正" }, "language": { "browserDefault": "ブラウザ設定" diff --git a/frontend/src/i18n/locales/ja/tasks.json b/frontend/src/i18n/locales/ja/tasks.json index c0b0d133..4cd20ccc 100644 --- a/frontend/src/i18n/locales/ja/tasks.json +++ b/frontend/src/i18n/locales/ja/tasks.json @@ -360,7 +360,8 @@ "startButton": "開発サーバーを開始", "startPrompt": "プレビューを表示するには開発サーバーを起動してください", "stopAndEditButton": "開発サーバーを停止して問題を解決", - "title": "開発サーバーが実行されていません" + "title": "開発サーバーが実行されていません", + "fixScript": "開発スクリプトを修正" }, "selectAttempt": "Select an attempt to see preview", "title": "Preview", @@ -378,7 +379,8 @@ "item2Suffix": "?(これにより実行中であることを認識します)", "item3": "Web Companion(クリックして編集機能に必要)をインストールしましたか?インストールしていない場合は、", "item3Link": "こちらのインストール手順に従ってください", - "title": "アプリケーションのプレビューに問題があります:" + "title": "アプリケーションのプレビューに問題があります:", + "fixScript": "開発スクリプトを修正" } }, "processes": { @@ -655,5 +657,24 @@ "deleted": "削除済み", "renamed": "名前変更済み" } + }, + "scriptFixer": { + "title": "スクリプトを修正", + "setupScriptTitle": "セットアップスクリプトを修正", + "cleanupScriptTitle": "クリーンアップスクリプトを修正", + "devServerTitle": "開発サーバースクリプトを修正", + "scriptLabel": "スクリプト(編集)", + "logsLabel": "最後の実行ログ", + "saveButton": "保存", + "saveAndTestButton": "保存してテスト", + "saving": "保存中...", + "testing": "テスト中...", + "noLogs": "利用可能な実行ログがありません", + "selectRepo": "リポジトリ", + "fixScript": "スクリプトを修正", + "statusRunning": "実行中...", + "statusSuccess": "正常に完了しました", + "statusFailed": "終了コード {{exitCode}} で失敗しました", + "statusKilled": "プロセスが強制終了されました" } } diff --git a/frontend/src/i18n/locales/ko/common.json b/frontend/src/i18n/locales/ko/common.json index d881ef79..18218b40 100644 --- a/frontend/src/i18n/locales/ko/common.json +++ b/frontend/src/i18n/locales/ko/common.json @@ -60,7 +60,8 @@ "ranCommand": "명령 실행", "createdTask": "작업 생성: {{description}}", "todoOperation": "{{operation}} 할 일" - } + }, + "fixScript": "스크립트 수정" }, "language": { "browserDefault": "브라우저 기본값" diff --git a/frontend/src/i18n/locales/ko/tasks.json b/frontend/src/i18n/locales/ko/tasks.json index 4f7cd622..a4b192e7 100644 --- a/frontend/src/i18n/locales/ko/tasks.json +++ b/frontend/src/i18n/locales/ko/tasks.json @@ -352,7 +352,8 @@ "startButton": "개발 서버 시작", "startPrompt": "미리보기를 보려면 개발 서버를 시작하세요", "stopAndEditButton": "개발 서버 중지 및 문제 해결", - "title": "실행 중인 개발 서버 없음" + "title": "실행 중인 개발 서버 없음", + "fixScript": "개발 스크립트 수정" }, "selectAttempt": "Select an attempt to see preview", "title": "Preview", @@ -370,7 +371,8 @@ "item2Suffix": "? (이것이 실행 중임을 아는 방법입니다)", "item3": "Web Companion(클릭하여 편집에 필요)을 설치했나요? 설치하지 않았다면", "item3Link": "여기의 설치 지침을 따르세요", - "title": "애플리케이션 미리보기에 문제가 발생했습니다:" + "title": "애플리케이션 미리보기에 문제가 발생했습니다:", + "fixScript": "개발 스크립트 수정" }, "browser": { "title": "개발 서버 미리보기", @@ -655,5 +657,24 @@ "deleted": "삭제됨", "renamed": "이름 변경됨" } + }, + "scriptFixer": { + "title": "스크립트 수정", + "setupScriptTitle": "설정 스크립트 수정", + "cleanupScriptTitle": "정리 스크립트 수정", + "devServerTitle": "개발 서버 스크립트 수정", + "scriptLabel": "스크립트 (편집)", + "logsLabel": "마지막 실행 로그", + "saveButton": "저장", + "saveAndTestButton": "저장 및 테스트", + "saving": "저장 중...", + "testing": "테스트 중...", + "noLogs": "실행 로그가 없습니다", + "selectRepo": "저장소", + "fixScript": "스크립트 수정", + "statusRunning": "실행 중...", + "statusSuccess": "성공적으로 완료됨", + "statusFailed": "종료 코드 {{exitCode}}(으)로 실패함", + "statusKilled": "프로세스가 종료되었습니다" } } diff --git a/frontend/src/i18n/locales/zh-Hans/common.json b/frontend/src/i18n/locales/zh-Hans/common.json index 824c1979..f77a740a 100644 --- a/frontend/src/i18n/locales/zh-Hans/common.json +++ b/frontend/src/i18n/locales/zh-Hans/common.json @@ -49,7 +49,8 @@ "ranCommand": "执行命令", "createdTask": "创建任务:{{description}}", "todoOperation": "{{operation}} 待办事项" - } + }, + "fixScript": "修复脚本" }, "folderPicker": { "legend": "点击文件夹名称进行导航 • 使用操作按钮进行选择", diff --git a/frontend/src/i18n/locales/zh-Hans/tasks.json b/frontend/src/i18n/locales/zh-Hans/tasks.json index dd63d9fd..c6416487 100644 --- a/frontend/src/i18n/locales/zh-Hans/tasks.json +++ b/frontend/src/i18n/locales/zh-Hans/tasks.json @@ -92,7 +92,8 @@ "item2": "您的开发服务器是否以格式打印 URL 和端口到终端", "item2Suffix": "?(这就是我们如何知道它正在运行)", "item3": "您是否安装了 Web Companion(点击编辑所需)?如果没有,请", - "item3Link": "按照此处的安装说明操作" + "item3Link": "按照此处的安装说明操作", + "fixScript": "修复开发脚本" }, "noServer": { "title": "没有运行开发服务器", @@ -102,7 +103,8 @@ "companionLink": "查看安装指南", "startButton": "启动开发服务器", "configureButton": "配置", - "stopAndEditButton": "停止开发服务器并解决问题" + "stopAndEditButton": "停止开发服务器并解决问题", + "fixScript": "修复开发脚本" }, "devScript": { "saveAndStart": "保存并启动", @@ -655,5 +657,24 @@ "updatedTodos": "更新的待办事项", "viewInChangesPanel": "在更改面板中查看", "unableToRenderDiff": "无法显示差异。" + }, + "scriptFixer": { + "title": "修复脚本", + "setupScriptTitle": "修复设置脚本", + "cleanupScriptTitle": "修复清理脚本", + "devServerTitle": "修复开发服务器脚本", + "scriptLabel": "脚本(编辑)", + "logsLabel": "上次执行日志", + "saveButton": "保存", + "saveAndTestButton": "保存并测试", + "saving": "保存中...", + "testing": "测试中...", + "noLogs": "没有可用的执行日志", + "selectRepo": "仓库", + "fixScript": "修复脚本", + "statusRunning": "运行中...", + "statusSuccess": "成功完成", + "statusFailed": "失败,退出代码 {{exitCode}}", + "statusKilled": "进程已被终止" } } diff --git a/frontend/src/i18n/locales/zh-Hant/common.json b/frontend/src/i18n/locales/zh-Hant/common.json index ea1136c4..4a2eaadf 100644 --- a/frontend/src/i18n/locales/zh-Hant/common.json +++ b/frontend/src/i18n/locales/zh-Hant/common.json @@ -49,7 +49,8 @@ "ranCommand": "執行命令", "createdTask": "建立任務:{{description}}", "todoOperation": "{{operation}} 待辦事項" - } + }, + "fixScript": "修復腳本" }, "folderPicker": { "legend": "點擊資料夾名稱進行導覽 • 使用操作按鈕進行選擇", diff --git a/frontend/src/i18n/locales/zh-Hant/tasks.json b/frontend/src/i18n/locales/zh-Hant/tasks.json index 5f14612b..eeeace27 100644 --- a/frontend/src/i18n/locales/zh-Hant/tasks.json +++ b/frontend/src/i18n/locales/zh-Hant/tasks.json @@ -92,7 +92,8 @@ "item2": "您的開發伺服器是否以格式將 URL 與連接埠輸出到終端機", "item2Suffix": "?(這是我們判斷它是否在執行的方式)", "item3": "您是否安裝了 Web Companion(點擊編輯所需)?如果沒有,請", - "item3Link": "依照此處的安裝說明操作" + "item3Link": "依照此處的安裝說明操作", + "fixScript": "修復開發腳本" }, "noServer": { "title": "沒有執行中的開發伺服器", @@ -102,7 +103,8 @@ "companionLink": "查看安裝指南", "startButton": "啟動開發伺服器", "configureButton": "設定", - "stopAndEditButton": "停止開發伺服器並解決問題" + "stopAndEditButton": "停止開發伺服器並解決問題", + "fixScript": "修復開發腳本" }, "devScript": { "saveAndStart": "儲存並啟動", @@ -655,5 +657,24 @@ "deleted": "已刪除", "renamed": "已重新命名" } + }, + "scriptFixer": { + "title": "修復腳本", + "setupScriptTitle": "修復設定腳本", + "cleanupScriptTitle": "修復清理腳本", + "devServerTitle": "修復開發伺服器腳本", + "scriptLabel": "腳本(編輯)", + "logsLabel": "上次執行日誌", + "saveButton": "儲存", + "saveAndTestButton": "儲存並測試", + "saving": "儲存中...", + "testing": "測試中...", + "noLogs": "沒有可用的執行日誌", + "selectRepo": "儲存庫", + "fixScript": "修復腳本", + "statusRunning": "執行中...", + "statusSuccess": "成功完成", + "statusFailed": "失敗,結束代碼 {{exitCode}}", + "statusKilled": "程序已被終止" } }