8c7affc933
Fixed import paths for 43 components moved into features/ directories. Key fixes: - api/, types/, hooks/, state/, utils/ imports need ../../../ from features/*/ - components/ imports need ../../ from features/*/ - Cross-feature imports use relative paths (e.g., ../tool/tools-bottom-sheet) - app-shell.tsx updated to import from features/ subdirectories Frontend typecheck now passes except for one pre-existing error: xterm-addon-webgl missing type declarations. Quality gates: ruff passed on backend, py_compile passed on all backend files.
115 lines
3.1 KiB
TypeScript
115 lines
3.1 KiB
TypeScript
/** Modal for starting a tool on a workspace. */
|
|
|
|
import { useState } from "react";
|
|
import { Icon } from "../../icon";
|
|
import { listToolTypes, type ToolType } from "../../../api/tool-types";
|
|
import { useAsyncData } from "../../../hooks/use-async-data";
|
|
import type { Workspace } from "../../../types/workspace";
|
|
|
|
export interface StartToolModalProps {
|
|
workspace: Workspace;
|
|
onClose: () => void;
|
|
onStart: (toolTypeId: string, configProfileId?: string) => Promise<void>;
|
|
}
|
|
|
|
export function StartToolModal({
|
|
workspace,
|
|
onClose,
|
|
onStart,
|
|
}: StartToolModalProps) {
|
|
const [toolTypeId, setToolTypeId] = useState("");
|
|
const [configProfileId, setConfigProfileId] = useState("");
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const {
|
|
data: toolTypes,
|
|
status,
|
|
error: loadError,
|
|
} = useAsyncData<ToolType[]>(listToolTypes, []);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!toolTypeId) {
|
|
setError("Please select a tool type");
|
|
return;
|
|
}
|
|
setSubmitting(true);
|
|
setError(null);
|
|
try {
|
|
await onStart(toolTypeId, configProfileId || undefined);
|
|
onClose();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Failed to start tool");
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="modal-overlay" onClick={onClose}>
|
|
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
|
|
<div className="modal-header">
|
|
<h3>
|
|
<Icon name="play" size="sm" /> Start Tool on {workspace.name}
|
|
</h3>
|
|
<button className="btn btn-icon" onClick={onClose}>
|
|
<Icon name="cancel" size="sm" />
|
|
</button>
|
|
</div>
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="form-group">
|
|
<label htmlFor="tool-type">Tool Type</label>
|
|
<select
|
|
id="tool-type"
|
|
value={toolTypeId}
|
|
onChange={(e) => setToolTypeId(e.target.value)}
|
|
disabled={submitting || status === "loading"}
|
|
>
|
|
<option value="">Select a tool...</option>
|
|
{toolTypes?.map((tt) => (
|
|
<option key={tt.id} value={tt.id}>
|
|
{tt.display_name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
{status === "loading" && (
|
|
<span className="muted">Loading tools...</span>
|
|
)}
|
|
{loadError && <span className="error-text">{loadError}</span>}
|
|
</div>
|
|
<div className="form-group">
|
|
<label htmlFor="config-profile">Config Profile (optional)</label>
|
|
<input
|
|
id="config-profile"
|
|
type="text"
|
|
value={configProfileId}
|
|
onChange={(e) => setConfigProfileId(e.target.value)}
|
|
placeholder="Profile ID"
|
|
disabled={submitting}
|
|
/>
|
|
</div>
|
|
{error && <p className="form-error">{error}</p>}
|
|
<div className="form-actions">
|
|
<button
|
|
type="button"
|
|
className="btn btn-secondary"
|
|
onClick={onClose}
|
|
disabled={submitting}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="btn btn-primary"
|
|
disabled={submitting || status !== "ready"}
|
|
>
|
|
{submitting ? "Starting..." : "Start Tool"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|