feat(dashboard): collapse planning model + depth into advanced disclosure
Group the planning model selector and depth controls under a collapsible "Advanced planning settings" disclosure (defaultOpen) in the Planning Mode modal. Adds a defaultOpen prop to OnboardingDisclosure and tightens the node.capabilities readonly typing in NodeDetailModal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
5
.changeset/planning-advanced-disclosure.md
Normal file
5
.changeset/planning-advanced-disclosure.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Group planning model selector and depth controls under a collapsible "Advanced planning settings" disclosure in the Planning Mode modal.
|
||||||
@@ -127,7 +127,10 @@ export function NodeDetailModal({
|
|||||||
return getProjectsForNode(projects, node);
|
return getProjectsForNode(projects, node);
|
||||||
}, [node, projects]);
|
}, [node, projects]);
|
||||||
|
|
||||||
const isManagedDockerNode = useMemo(() => node?.capabilities?.includes("docker-managed") ?? false, [node]);
|
const isManagedDockerNode = useMemo(
|
||||||
|
() => (node?.capabilities as readonly string[] | undefined)?.includes("docker-managed") ?? false,
|
||||||
|
[node],
|
||||||
|
);
|
||||||
|
|
||||||
const handleHealthCheck = useCallback(async () => {
|
const handleHealthCheck = useCallback(async () => {
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ interface OnboardingDisclosureProps {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
onToggle?: (isOpen: boolean) => void;
|
onToggle?: (isOpen: boolean) => void;
|
||||||
|
defaultOpen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OnboardingDisclosure({
|
export function OnboardingDisclosure({
|
||||||
@@ -14,8 +15,9 @@ export function OnboardingDisclosure({
|
|||||||
children,
|
children,
|
||||||
className = "",
|
className = "",
|
||||||
onToggle,
|
onToggle,
|
||||||
|
defaultOpen = false,
|
||||||
}: OnboardingDisclosureProps) {
|
}: OnboardingDisclosureProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`onboarding-disclosure ${className}`.trim()}>
|
<div className={`onboarding-disclosure ${className}`.trim()}>
|
||||||
|
|||||||
@@ -405,6 +405,18 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.planning-advanced-disclosure {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 520px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.planning-advanced-blurb {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.planning-model-select-group {
|
.planning-model-select-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -1556,91 +1556,97 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="planning-model-select-group">
|
<OnboardingDisclosure summary="Advanced planning settings" className="planning-advanced-disclosure" defaultOpen>
|
||||||
<label htmlFor="planning-modal-model" className="form-label">
|
<p className="planning-advanced-blurb">
|
||||||
Planning Model
|
Choose the planning model and tune plan depth to control how detailed the AI interview should be.
|
||||||
{modelsLoading && (
|
</p>
|
||||||
<span className="text-muted text-muted-sm">
|
|
||||||
Loading models…
|
<div className="planning-model-select-group">
|
||||||
</span>
|
<label htmlFor="planning-modal-model" className="form-label">
|
||||||
|
Planning Model
|
||||||
|
{modelsLoading && (
|
||||||
|
<span className="text-muted text-muted-sm">
|
||||||
|
Loading models…
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<CustomModelDropdown
|
||||||
|
id="planning-modal-model"
|
||||||
|
label="Planning Model"
|
||||||
|
value={planningSelectionValue}
|
||||||
|
onChange={(value) => {
|
||||||
|
const { provider, modelId } = parseModelSelection(value);
|
||||||
|
setPlanningModelProvider(provider);
|
||||||
|
setPlanningModelId(modelId);
|
||||||
|
}}
|
||||||
|
models={loadedModels}
|
||||||
|
disabled={modelsLoading}
|
||||||
|
favoriteProviders={favoriteProviders}
|
||||||
|
onToggleFavorite={handleToggleFavoriteProvider}
|
||||||
|
favoriteModels={favoriteModels}
|
||||||
|
onToggleModelFavorite={handleToggleFavoriteModel}
|
||||||
|
/>
|
||||||
|
{modelsError && (
|
||||||
|
<div className="form-hint form-hint-error">
|
||||||
|
{modelsError}{" "}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-link-btn"
|
||||||
|
onClick={() => {
|
||||||
|
void loadModels();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</label>
|
<div className="model-selector-current model-selector-current--spaced">
|
||||||
<CustomModelDropdown
|
<span
|
||||||
id="planning-modal-model"
|
className={`model-badge ${
|
||||||
label="Planning Model"
|
planningModelProvider && planningModelId
|
||||||
value={planningSelectionValue}
|
? "model-badge-custom"
|
||||||
onChange={(value) => {
|
: "model-badge-default"
|
||||||
const { provider, modelId } = parseModelSelection(value);
|
}`}
|
||||||
setPlanningModelProvider(provider);
|
|
||||||
setPlanningModelId(modelId);
|
|
||||||
}}
|
|
||||||
models={loadedModels}
|
|
||||||
disabled={modelsLoading}
|
|
||||||
favoriteProviders={favoriteProviders}
|
|
||||||
onToggleFavorite={handleToggleFavoriteProvider}
|
|
||||||
favoriteModels={favoriteModels}
|
|
||||||
onToggleModelFavorite={handleToggleFavoriteModel}
|
|
||||||
/>
|
|
||||||
{modelsError && (
|
|
||||||
<div className="form-hint form-hint-error">
|
|
||||||
{modelsError}{" "}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="text-link-btn"
|
|
||||||
onClick={() => {
|
|
||||||
void loadModels();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Retry
|
{getModelBadgeLabel(planningModelProvider, planningModelId)}
|
||||||
</button>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
<div className="model-selector-current model-selector-current--spaced">
|
|
||||||
<span
|
|
||||||
className={`model-badge ${
|
|
||||||
planningModelProvider && planningModelId
|
|
||||||
? "model-badge-custom"
|
|
||||||
: "model-badge-default"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{getModelBadgeLabel(planningModelProvider, planningModelId)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="planning-depth-selector">
|
<div className="planning-depth-selector">
|
||||||
<div className="planning-depth-chip-group" role="group" aria-label="Planning depth">
|
<div className="planning-depth-chip-group" role="group" aria-label="Planning depth">
|
||||||
{([
|
{([
|
||||||
{ value: "small", label: "Small" },
|
{ value: "small", label: "Small" },
|
||||||
{ value: "medium", label: "Medium" },
|
{ value: "medium", label: "Medium" },
|
||||||
{ value: "large", label: "Large" },
|
{ value: "large", label: "Large" },
|
||||||
] as const).map((depthOption) => (
|
] as const).map((depthOption) => (
|
||||||
<button
|
<button
|
||||||
key={depthOption.value}
|
key={depthOption.value}
|
||||||
type="button"
|
type="button"
|
||||||
className={`planning-depth-chip btn ${planningDepth === depthOption.value ? "btn-primary planning-depth-chip-active" : ""}`}
|
className={`planning-depth-chip btn ${planningDepth === depthOption.value ? "btn-primary planning-depth-chip-active" : ""}`}
|
||||||
onClick={() => setPlanningDepth(depthOption.value)}
|
onClick={() => setPlanningDepth(depthOption.value)}
|
||||||
aria-pressed={planningDepth === depthOption.value}
|
aria-pressed={planningDepth === depthOption.value}
|
||||||
>
|
>
|
||||||
{depthOption.label}
|
{depthOption.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="planning-depth-question-count" htmlFor="planning-depth-questions">
|
<label className="planning-depth-question-count" htmlFor="planning-depth-questions">
|
||||||
<span>Questions</span>
|
<span>Questions</span>
|
||||||
<input
|
<input
|
||||||
id="planning-depth-questions"
|
id="planning-depth-questions"
|
||||||
className="input planning-depth-question-input"
|
className="input planning-depth-question-input"
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
max={20}
|
max={20}
|
||||||
value={customQuestionCount}
|
value={customQuestionCount}
|
||||||
onChange={(e) => setCustomQuestionCount(e.target.value)}
|
onChange={(e) => setCustomQuestionCount(e.target.value)}
|
||||||
placeholder="Auto"
|
placeholder="Auto"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
|
</OnboardingDisclosure>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="planning-view-footer">
|
<div className="planning-view-footer">
|
||||||
|
|||||||
@@ -410,6 +410,8 @@ describe("PlanningModeModal", () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("button", { name: "Advanced planning settings" })).toBeDefined();
|
||||||
|
expect(screen.getByText(/Choose the planning model and tune plan depth/)).toBeDefined();
|
||||||
expect(screen.getByRole("button", { name: "Small" })).toBeDefined();
|
expect(screen.getByRole("button", { name: "Small" })).toBeDefined();
|
||||||
expect(screen.getByRole("button", { name: "Medium" }).getAttribute("aria-pressed")).toBe("true");
|
expect(screen.getByRole("button", { name: "Medium" }).getAttribute("aria-pressed")).toBe("true");
|
||||||
expect(screen.getByRole("button", { name: "Large" })).toBeDefined();
|
expect(screen.getByRole("button", { name: "Large" })).toBeDefined();
|
||||||
|
|||||||
Reference in New Issue
Block a user