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. 34
      src/components/nodes/ControlPanel.tsx
  2. 8
      src/components/nodes/FloatingNodeHeader.tsx

34
src/components/nodes/ControlPanel.tsx

@ -13,6 +13,7 @@ import { deduplicatedFetch } from "@/utils/deduplicatedFetch";
import { evaluateRule } from "@/store/utils/ruleEvaluation"; import { evaluateRule } from "@/store/utils/ruleEvaluation";
import { EASING_PRESETS, getPresetBezier, getEasingBezier } from "@/lib/easing-presets"; import { EASING_PRESETS, getPresetBezier, getEasingBezier } from "@/lib/easing-presets";
import { getAllEasingNames, getEasingFunction } from "@/lib/easing-functions"; import { getAllEasingNames, getEasingFunction } from "@/lib/easing-functions";
import { getModelPageUrl, getProviderDisplayName } from "@/utils/providerUrls";
// List of node types that have configurable parameters // List of node types that have configurable parameters
const CONFIGURABLE_NODE_TYPES: NodeType[] = [ 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="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"> <div className="p-4">
{/* Header */} {/* 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)} {getNodeTypeTitle(selectedNode.type as NodeType)}
</h3> </h3>
{/* Node-specific controls */} {/* Node-specific controls */}
<div className="space-y-4"> <div className="space-y-4 mt-4">
{selectedNode.type === "nanoBanana" && ( {selectedNode.type === "nanoBanana" && (
<GenerateImageControls node={selectedNode} /> <GenerateImageControls node={selectedNode} />
)} )}
@ -353,16 +354,35 @@ function GenerateImageControls({ node }: { node: Node }) {
return ( return (
<> <>
<div className="space-y-3"> <div className="space-y-3">
{/* Model display + Browse */} {/* Model name + provider with link — sits directly under title divider */}
<div> <div className="border-t border-neutral-700 pt-3">
<label className="block text-xs font-medium text-neutral-400 mb-1">Model</label> <div className="flex items-start gap-2">
<div className="flex items-center gap-2">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="text-sm text-neutral-100 truncate"> <div className="text-sm text-neutral-100 truncate">
{nodeData.selectedModel?.displayName || GEMINI_IMAGE_MODELS.find(m => m.value === nodeData.model)?.label || "Select model..."} {nodeData.selectedModel?.displayName || GEMINI_IMAGE_MODELS.find(m => m.value === nodeData.model)?.label || "Select model..."}
</div> </div>
<div className="text-[10px] text-neutral-500 truncate"> <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} {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>
</div> </div>
<button <button

8
src/components/nodes/FloatingNodeHeader.tsx

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

Loading…
Cancel
Save