Browse Source

fix(46-04): right-align header controls, clean up ControlPanel model display

- FloatingNodeHeader: Controls (comment, expand, run) now sit flush-right
  against the node edge with tighter spacing.
- ControlPanel: Remove "Model" label. Model name sits directly under the
  settings title, separated by a horizontal divider.
- Add new-tab link icon next to provider name that opens model details page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
7f4afb6e32
  1. 36
      src/components/nodes/ControlPanel.tsx
  2. 8
      src/components/nodes/FloatingNodeHeader.tsx

36
src/components/nodes/ControlPanel.tsx

@ -13,6 +13,7 @@ import { deduplicatedFetch } from "@/utils/deduplicatedFetch";
import { evaluateRule } from "@/store/utils/ruleEvaluation";
import { EASING_PRESETS, getPresetBezier, getEasingBezier } from "@/lib/easing-presets";
import { getAllEasingNames, getEasingFunction } from "@/lib/easing-functions";
import { getModelPageUrl, getProviderDisplayName } from "@/utils/providerUrls";
// List of node types that have configurable parameters
const CONFIGURABLE_NODE_TYPES: NodeType[] = [
@ -119,12 +120,12 @@ export function ControlPanel() {
<div className="w-80 bg-neutral-800 border border-neutral-700 rounded-xl shadow-xl max-h-[80vh] overflow-y-auto pointer-events-auto transition-opacity duration-200 nowheel">
<div className="p-4">
{/* Header */}
<h3 className="text-sm font-medium text-neutral-200 mb-4">
<h3 className="text-sm font-medium text-neutral-200">
{getNodeTypeTitle(selectedNode.type as NodeType)}
</h3>
{/* Node-specific controls */}
<div className="space-y-4">
<div className="space-y-4 mt-4">
{selectedNode.type === "nanoBanana" && (
<GenerateImageControls node={selectedNode} />
)}
@ -353,16 +354,35 @@ function GenerateImageControls({ node }: { node: Node }) {
return (
<>
<div className="space-y-3">
{/* Model display + Browse */}
<div>
<label className="block text-xs font-medium text-neutral-400 mb-1">Model</label>
<div className="flex items-center gap-2">
{/* Model name + provider with link — sits directly under title divider */}
<div className="border-t border-neutral-700 pt-3">
<div className="flex items-start gap-2">
<div className="flex-1 min-w-0">
<div className="text-sm text-neutral-100 truncate">
{nodeData.selectedModel?.displayName || GEMINI_IMAGE_MODELS.find(m => m.value === nodeData.model)?.label || "Select model..."}
</div>
<div className="text-[10px] text-neutral-500 truncate">
{enabledProviders.find(p => p.id === currentProvider)?.name || currentProvider}
<div className="flex items-center gap-1 mt-0.5">
<span className="text-[10px] text-neutral-500 truncate">
{enabledProviders.find(p => p.id === currentProvider)?.name || currentProvider}
</span>
{nodeData.selectedModel?.modelId && (
<a
href={getModelPageUrl(currentProvider, nodeData.selectedModel.modelId) || "#"}
target="_blank"
rel="noopener noreferrer"
className="text-neutral-500 hover:text-neutral-300 transition-colors"
title={`View on ${getProviderDisplayName(currentProvider)}`}
onClick={(e) => {
if (!getModelPageUrl(currentProvider, nodeData.selectedModel?.modelId || "")) {
e.preventDefault();
}
}}
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
)}
</div>
</div>
<button

8
src/components/nodes/FloatingNodeHeader.tsx

@ -213,9 +213,9 @@ export function FloatingNodeHeader({
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<div className="px-3 py-2 flex items-center justify-between">
<div className="px-1 py-2 flex items-center gap-2 w-full">
{/* Title Section */}
<div className="flex-1 min-w-0 flex items-center gap-1.5">
<div className="flex-1 min-w-0 flex items-center gap-1.5 pl-2">
{titlePrefix}
{isEditingTitle ? (
<input
@ -240,8 +240,8 @@ export function FloatingNodeHeader({
{headerAction}
</div>
{/* Controls - fade in on hover/selected */}
<div className={`flex items-center gap-2 transition-opacity duration-200 ${showControls ? 'opacity-100' : 'opacity-0'}`}>
{/* Controls - right-aligned, fade in on hover/selected */}
<div className={`shrink-0 flex items-center gap-1 pr-1 transition-opacity duration-200 ${showControls ? 'opacity-100' : 'opacity-0'}`}>
{/* Lock Badge for nodes in locked groups */}
{isInLockedGroup && (
<div className="shrink-0 flex items-center" title="This node is in a locked group and will be skipped during execution">

Loading…
Cancel
Save