diff --git a/ushadow/backend/src/config/instances.yaml b/ushadow/backend/src/config/instances.yaml index b4ef9e46..db6b14b4 100644 --- a/ushadow/backend/src/config/instances.yaml +++ b/ushadow/backend/src/config/instances.yaml @@ -19,7 +19,16 @@ instances: openai-3: template_id: openai name: red - config: {} deployment_target: cloud status: n/a created_at: '2026-01-08T16:10:24.972709+00:00' + whisper-local-1: + template_id: whisper-local + name: whisper-local-1 + deployment_target: local + created_at: '2026-01-17T17:06:53.998791+00:00' + ollama-2: + template_id: ollama + name: ollama-2 + deployment_target: local + created_at: '2026-01-17T17:35:48.517963+00:00' diff --git a/ushadow/backend/src/config/wiring.yaml b/ushadow/backend/src/config/wiring.yaml index 5620522e..b0c43821 100644 --- a/ushadow/backend/src/config/wiring.yaml +++ b/ushadow/backend/src/config/wiring.yaml @@ -1,17 +1,17 @@ defaults: {} wiring: -- id: 9dc8a4aa - source_instance_id: deepgram - source_capability: transcription - target_instance_id: chronicle-compose:chronicle-backend - target_capability: transcription - id: 3cacc293 source_instance_id: openai-2 source_capability: llm target_instance_id: openmemory-compose:mem0 target_capability: llm -- id: 62a97a18 - source_instance_id: openai-3 +- id: 19679d70 + source_instance_id: whisper-local-1 + source_capability: transcription + target_instance_id: chronicle-compose:chronicle-backend + target_capability: transcription +- id: 0bd39f1d + source_instance_id: ollama-2 source_capability: llm target_instance_id: chronicle-compose:chronicle-backend target_capability: llm diff --git a/ushadow/frontend/src/components/wiring/WiringBoard.tsx b/ushadow/frontend/src/components/wiring/WiringBoard.tsx index 96638a3f..83b7b185 100644 --- a/ushadow/frontend/src/components/wiring/WiringBoard.tsx +++ b/ushadow/frontend/src/components/wiring/WiringBoard.tsx @@ -288,6 +288,7 @@ export default function WiringBoard({ onDelete={() => onDeleteInstance(instance.id)} onStart={onStartProvider ? () => onStartProvider(instance.id, false) : undefined} onStop={onStopProvider ? () => onStopProvider(instance.id, false) : undefined} + templateProvider={template} /> ) })} @@ -391,9 +392,10 @@ interface DraggableProviderProps { onDelete?: () => void // Only for instances onStart?: () => Promise onStop?: () => Promise + templateProvider?: ProviderInfo // Parent template for instances } -function DraggableProvider({ provider, connectionCount, onEdit, onCreateInstance, onDelete, onStart, onStop }: DraggableProviderProps) { +function DraggableProvider({ provider, connectionCount, onEdit, onCreateInstance, onDelete, onStart, onStop, templateProvider }: DraggableProviderProps) { const [isStarting, setIsStarting] = useState(false) const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id: provider.id, @@ -452,7 +454,7 @@ function DraggableProvider({ provider, connectionCount, onEdit, onCreateInstance > {/* Draggable header */}
@@ -463,16 +465,15 @@ function DraggableProvider({ provider, connectionCount, onEdit, onCreateInstance {provider.name}
+ {/* Capability tag */} + + {provider.capability} + {isConnected && ( {connectionCount} )} - {provider.isTemplate && ( - - default - - )}
@@ -570,18 +571,24 @@ function DraggableProvider({ provider, connectionCount, onEdit, onCreateInstance +{missingRequiredVars.length - 2} required )} - {/* Configured fields */} - {configuredVars.slice(0, 3 - Math.min(missingRequiredVars.length, 2)).map((v) => ( - - {v.required && *} - {v.label}:{' '} - {v.value} - - ))} + {/* Configured fields - color code overrides */} + {configuredVars.slice(0, 3 - Math.min(missingRequiredVars.length, 2)).map((v) => { + // Check if this value is overridden from template + const isOverridden = templateProvider && + templateProvider.configVars.find(tv => tv.key === v.key)?.value !== v.value + + return ( + + {v.required && *} + {v.label}:{' '} + {v.value} + + ) + })} {configuredVars.length > (3 - Math.min(missingRequiredVars.length, 2)) && ( +{configuredVars.length - (3 - Math.min(missingRequiredVars.length, 2))} more @@ -831,31 +838,29 @@ function CapabilitySlot({ const isOrphaned = connection && !connection.provider return ( -
- {/* Capability label */} -
- {capability} -
- {/* Drop zone */} -
- {hasProvider ? ( -
+
+ {hasProvider ? ( +
+
{connection.provider!.name} + + {capability} +
- ) : isOrphaned ? ( -
-
- - Provider missing + {/* Show provider config vars */} + {connection.provider!.configVars.filter(v => v.value).length > 0 && ( +
+ {connection.provider!.configVars + .filter(v => v.value) + .slice(0, 2) + .map((v) => ( + + {v.label}:{' '} + {v.value} + + ))} + {connection.provider!.configVars.filter(v => v.value).length > 2 && ( + + +{connection.provider!.configVars.filter(v => v.value).length - 2} more + + )}
- -
- ) : ( -
- - - {isDropTarget ? 'Drop provider here' : 'Drag a provider here'} + )} +
+ ) : isOrphaned ? ( +
+
+ + Provider missing + + {capability}
- )} -
+ +
+ ) : ( +
+ + + {isDropTarget ? 'Drop ' : 'Drag '} + + {capability} + + {isDropTarget ? ' here' : ' provider here'} + +
+ )}
) } diff --git a/ushadow/frontend/src/pages/InstancesPage.tsx b/ushadow/frontend/src/pages/InstancesPage.tsx index c7be78bc..c52763f9 100644 --- a/ushadow/frontend/src/pages/InstancesPage.tsx +++ b/ushadow/frontend/src/pages/InstancesPage.tsx @@ -564,7 +564,8 @@ export default function InstancesPage() { } else if (field.value) { displayValue = String(field.value) } else if (field.has_value) { - displayValue = '(configured)' + // Has a value but we can't display it - show brief indicator + displayValue = '(set)' } } return { @@ -587,9 +588,18 @@ export default function InstancesPage() { status = t.available ? 'running' : 'stopped' } + // For LLM providers, append model to name for clarity + let displayName = t.name + if (t.provides === 'llm') { + const modelVar = configVars.find(v => v.key === 'model') + if (modelVar && modelVar.value && modelVar.value !== '(set)') { + displayName = `${t.name}-${modelVar.value}` + } + } + return { id: t.id, - name: t.name, + name: displayName, capability: t.provides!, status, mode: t.mode, @@ -618,11 +628,14 @@ export default function InstancesPage() { const isSecret = field.type === 'secret' let displayValue = '' if (overrideValue) { + // Instance has an override value displayValue = isSecret ? '••••••' : String(overrideValue) - } else if (field.has_value) { - displayValue = isSecret ? '••••••' : '(default)' } else if (field.value) { + // Inherited from template - show the actual value displayValue = isSecret ? '••••••' : String(field.value) + } else if (field.has_value) { + // Template has a value but we can't display it + displayValue = isSecret ? '••••••' : '(set)' } configVars.push({ key: field.key, @@ -649,9 +662,18 @@ export default function InstancesPage() { instanceStatus = i.status === 'running' ? 'running' : i.status } + // For LLM providers, append model to name for clarity + let displayName = i.name + if (template.provides === 'llm') { + const modelVar = configVars.find(v => v.key === 'model') + if (modelVar && modelVar.value && modelVar.value !== '(set)') { + displayName = `${i.name}-${modelVar.value}` + } + } + return { id: i.id, - name: i.name, + name: displayName, capability: template.provides!, status: instanceStatus, mode: template.mode, diff --git a/ushadow/frontend/src/wizards/TailscaleWizard.tsx b/ushadow/frontend/src/wizards/TailscaleWizard.tsx index 9dc47566..d8c2edae 100644 --- a/ushadow/frontend/src/wizards/TailscaleWizard.tsx +++ b/ushadow/frontend/src/wizards/TailscaleWizard.tsx @@ -564,7 +564,6 @@ export default function TailscaleWizard() { setMessage({ type: 'info', text: 'Enabling HTTPS on Tailscale...' }) const finalConfig = { ...config, hostname } const serveResponse = await tailscaleApi.configureServe(finalConfig) - } // Step 2: Provision the certificate (HTTPS is now enabled) setMessage({