show-me / src /components /Controls.svelte
PierreRouanet's picture
Remove detect toggle button, keep model selector
a23d1b7
Raw
History Blame Contribute Delete
6.31 kB
<script>
/** @type {{ store: ReturnType<import('../lib/stores/robot.svelte.js').createRobotStore>, vision: ReturnType<import('../lib/stores/vision.svelte.js').createVisionStore>, auth: ReturnType<import('../lib/stores/auth.svelte.js').createAuthStore> }} */
let { store, vision, auth } = $props();
let expanded = $state(false);
let tokenInput = $state("");
const handleDepth = () => vision.toggleDepth();
const handlePanoptic = () => vision.togglePanoptic();
const handleFaceParse = () => vision.toggleFaceParse();
const handleTrack = () => vision.toggleTrack();
const handleResetOrientation = () => {
store.commands.gotoTarget({
head: [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
],
});
};
</script>
<div class="controls">
<div class="controls-bar">
{#if store.connectionStatus === 'disconnected'}
{#if auth.isLoggedIn}
<div class="btn-group">
<button class="btn btn-hf" onclick={() => store.connectCentral(auth.token)}>
Connect as @{auth.username}
</button>
<button class="btn btn-small" onclick={() => auth.logout()}>
Sign out
</button>
</div>
{:else if auth.isHfSpace}
<button class="btn btn-hf" onclick={() => auth.login()}>
Sign in with HF
</button>
{:else}
<div class="btn-group">
<input
class="host-input"
type="password"
placeholder="hf_... token"
bind:value={tokenInput}
/>
<button class="btn btn-hf" disabled={!tokenInput.trim()} onclick={() => { auth.setToken(tokenInput); store.connectCentral(tokenInput.trim()); }}>
Connect
</button>
</div>
{/if}
{:else if store.connectionStatus === 'connecting'}
{#if store.producers.length > 1}
<div class="btn-group">
{#each store.producers as producer}
<button class="btn btn-primary" onclick={() => store.connectProducer(auth.token, producer.id)}>
{producer.meta?.name || producer.id}
</button>
{/each}
</div>
{:else}
<button class="btn" disabled>
Connecting...
</button>
{/if}
{:else}
<div class="btn-group">
<select
class="model-select"
value={vision.detectorModelId}
onchange={(e) => vision.setDetectorModel(e.target.value)}
>
{#each vision.detectorModels as m}
<option value={m.id}>{m.label}</option>
{/each}
</select>
<button class="btn btn-ai" class:active={vision.depthActive} onclick={handleDepth}>
{vision.depthActive ? 'Stop Depth' : 'Depth'}
</button>
<button class="btn btn-ai" class:active={vision.panopticActive} onclick={handlePanoptic}>
{vision.panopticActive ? 'Stop Segment' : 'Segment'}
</button>
<button class="btn btn-ai" class:active={vision.faceParseActive} onclick={handleFaceParse}>
{vision.faceParseActive ? 'Stop Face' : 'Face'}
</button>
<button class="btn btn-ai" class:active={vision.trackActive} onclick={handleTrack}>
{vision.trackActive ? 'Stop Track' : 'Track'}
</button>
<button class="btn btn-ai btn-small" onclick={handleResetOrientation}>
Reset
</button>
</div>
<div class="btn-group">
<button class="btn btn-disconnect" onclick={() => store.disconnect()}>
Disconnect
</button>
</div>
{/if}
{#if store.robotState}
<button
class="btn btn-small"
onclick={() => expanded = !expanded}
>
{expanded ? 'Hide' : 'State'}
</button>
{/if}
</div>
{#if expanded && store.robotState}
<pre class="state-view">{JSON.stringify(store.robotState, null, 2)}</pre>
{/if}
</div>
<style>
.controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
pointer-events: none;
}
.controls-bar {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
pointer-events: auto;
}
.btn-group {
display: flex;
gap: 8px;
justify-content: center;
flex-wrap: wrap;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 0.95rem;
font-weight: 500;
cursor: pointer;
transition: opacity 0.15s;
color: #fff;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(8px);
}
.btn:hover:not(:disabled) {
opacity: 0.85;
}
.btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.btn.active {
opacity: 1;
background: rgba(59, 130, 246, 0.6);
animation: pulse 1s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.btn-primary {
background: #3b82f6;
}
.btn-ai {
background: rgba(168, 85, 247, 0.6);
}
.btn-hf {
background: rgba(255, 159, 28, 0.7);
}
.btn-disconnect {
background: rgba(239, 68, 68, 0.7);
}
.host-input {
padding: 10px 14px;
border: none;
border-radius: 8px;
font-size: 0.95rem;
font-weight: 500;
color: #fff;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(8px);
outline: none;
width: 180px;
}
.host-input::placeholder {
color: rgba(255, 255, 255, 0.4);
}
.host-input:focus {
background: rgba(255, 255, 255, 0.25);
}
.btn-small {
padding: 6px 12px;
font-size: 0.8rem;
}
.model-select {
padding: 6px 10px;
border: none;
border-radius: 8px;
font-size: 0.8rem;
font-weight: 500;
color: #fff;
background: rgba(168, 85, 247, 0.4);
backdrop-filter: blur(8px);
cursor: pointer;
outline: none;
}
.model-select option {
background: #1a1a2e;
color: #fff;
}
.state-view {
margin-top: 12px;
padding: 12px;
background: rgba(0, 0, 0, 0.7);
border-radius: 8px;
color: #a5f3a5;
font-size: 0.75rem;
line-height: 1.4;
max-height: 200px;
overflow-y: auto;
pointer-events: auto;
backdrop-filter: blur(8px);
}
</style>