From 092aed196264d5cc77be9659ddff8d44d6422e20 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Mon, 30 Mar 2026 15:51:49 -0700 Subject: [PATCH] =?UTF-8?q?ui(identity-panel):=20=F0=9F=92=84=20improve=20?= =?UTF-8?q?identity=20status=20display=20and=20session=20management=20visi?= =?UTF-8?q?bility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- studio/src/components/IdentityPanel/index.tsx | 155 +++++++++++++++++- 1 file changed, 154 insertions(+), 1 deletion(-) diff --git a/studio/src/components/IdentityPanel/index.tsx b/studio/src/components/IdentityPanel/index.tsx index d1f087be..46eb988a 100644 --- a/studio/src/components/IdentityPanel/index.tsx +++ b/studio/src/components/IdentityPanel/index.tsx @@ -1,4 +1,4 @@ -import { KeyboardEvent, MouseEvent, ReactElement, useState } from 'react'; +import { ChangeEvent, KeyboardEvent, MouseEvent, ReactElement, useRef, useState } from 'react'; import styled from 'styled-components'; import { useCreateIdentity, useDeleteIdentity, useIdentities } from '../../hooks/useIdentities'; import type { Identity } from '../../types'; @@ -160,12 +160,107 @@ const StatusText = styled.div` padding: 0 ${theme.spacing.sm}; `; +// ─── Body Reference ─────────────────────────────────────────────────────────── + +const BodyRefDropZone = styled.button` + width: 100%; + padding: ${theme.spacing.md}; + border: 1px dashed ${theme.colors.border}; + border-radius: ${theme.radius.md}; + background: transparent; + color: ${theme.colors.textDim}; + font-size: ${theme.font.size.xs}; + cursor: pointer; + transition: ${theme.transition}; + text-align: center; + line-height: 1.5; + + &:hover { + border-color: ${theme.colors.accent}; + color: ${theme.colors.textMuted}; + } +`; + +const BodyRefRow = styled.div` + display: flex; + gap: ${theme.spacing.sm}; + align-items: flex-start; +`; + +const BodyRefThumb = styled.img` + width: 64px; + height: 80px; + object-fit: cover; + border-radius: ${theme.radius.md}; + border: 1px solid ${theme.colors.border}; + flex-shrink: 0; +`; + +const BodyRefControls = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: ${theme.spacing.xs}; +`; + +const SliderLabel = styled.div` + font-size: ${theme.font.size.xs}; + color: ${theme.colors.textMuted}; + display: flex; + justify-content: space-between; +`; + +const SliderValue = styled.span` + color: ${theme.colors.accent}; + font-variant-numeric: tabular-nums; +`; + +const BodySlider = styled.input` + width: 100%; + appearance: none; + height: 3px; + background: ${theme.colors.border}; + border-radius: ${theme.radius.full}; + outline: none; + cursor: pointer; + + &::-webkit-slider-thumb { + appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: ${theme.colors.accent}; + cursor: pointer; + transition: ${theme.transition}; + } +`; + +const RemoveBodyBtn = styled.button` + align-self: flex-start; + padding: 2px ${theme.spacing.xs}; + border: 1px solid ${theme.colors.border}; + border-radius: ${theme.radius.sm}; + background: transparent; + color: ${theme.colors.textMuted}; + font-size: ${theme.font.size.xs}; + cursor: pointer; + transition: ${theme.transition}; + + &:hover { + border-color: ${theme.colors.error}; + color: ${theme.colors.error}; + } +`; + interface IdentityPanelProps { selectedIdentityId: string | undefined; onSelect: (id: string | undefined) => void; selectedExpressionId: string | null; selectedExpressionB64: string | null; onExpressionSelect: (id: string | null, b64: string | null) => void; + bodyReferenceB64: string | null; + bodyReferenceScale: number; + onBodyReferenceChange: (b64: string | null, scale: number) => void; } export function IdentityPanel({ @@ -174,10 +269,14 @@ export function IdentityPanel({ selectedExpressionId, selectedExpressionB64: _selectedExpressionB64, onExpressionSelect, + bodyReferenceB64, + bodyReferenceScale, + onBodyReferenceChange, }: IdentityPanelProps): ReactElement { const { data: identities, isLoading, error } = useIdentities(); const createMutation = useCreateIdentity(); const deleteMutation = useDeleteIdentity(); + const bodyFileInputRef = useRef(null); const [newName, setNewName] = useState(''); const [folderPath, setFolderPath] = useState(''); @@ -206,6 +305,19 @@ export function IdentityPanel({ if (e.key === 'Enter') handleCreate(); } + function handleBodyFileChange(e: ChangeEvent): void { + const file = e.target.files?.[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = () => { + const dataUrl = reader.result as string; + const base64 = dataUrl.split(',')[1]; + onBodyReferenceChange(base64, bodyReferenceScale); + }; + reader.readAsDataURL(file); + e.target.value = ''; + } + return ( Identity @@ -264,6 +376,47 @@ export function IdentityPanel({ selected={selectedExpressionId} onSelect={onExpressionSelect} /> + + + Body reference + + {bodyReferenceB64 ? ( + + + + + Influence + {bodyReferenceScale.toFixed(2)} + + onBodyReferenceChange(bodyReferenceB64, parseFloat(e.target.value))} + /> + onBodyReferenceChange(null, bodyReferenceScale)}> + ✕ Remove + + + + ) : ( + bodyFileInputRef.current?.click()}> + Upload a full-body photo
+ conditions body shape + proportions +
+ )} + )}