Add tags to all entity types with chip-based input and autocomplete
- Add `tags: List[str]` field to all 13 entity types (devices, output targets, CSS sources, picture sources, audio sources, value sources, sync clocks, automations, scene presets, capture/audio/PP/pattern templates) - Update all stores, schemas, and route handlers for tag CRUD - Add GET /api/v1/tags endpoint aggregating unique tags across all stores - Create TagInput component with chip display, autocomplete dropdown, keyboard navigation, and API-backed suggestions - Display tag chips on all entity cards (searchable via existing text filter) - Add tag input to all 14 editor modals with dirty check support - Add CSS styles and i18n keys (en/ru/zh) for tag UI - Also includes code review fixes: thread safety, perf, store dedup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,7 @@ def _device_to_response(device) -> DeviceResponse:
|
||||
rgbw=device.rgbw,
|
||||
zone_mode=device.zone_mode,
|
||||
capabilities=sorted(get_device_capabilities(device.device_type)),
|
||||
tags=getattr(device, 'tags', []),
|
||||
created_at=device.created_at,
|
||||
updated_at=device.updated_at,
|
||||
)
|
||||
@@ -126,6 +127,7 @@ async def create_device(
|
||||
send_latency_ms=device_data.send_latency_ms or 0,
|
||||
rgbw=device_data.rgbw or False,
|
||||
zone_mode=device_data.zone_mode or "combined",
|
||||
tags=device_data.tags,
|
||||
)
|
||||
|
||||
# WS devices: auto-set URL to ws://{device_id}
|
||||
@@ -308,6 +310,7 @@ async def update_device(
|
||||
send_latency_ms=update_data.send_latency_ms,
|
||||
rgbw=update_data.rgbw,
|
||||
zone_mode=update_data.zone_mode,
|
||||
tags=update_data.tags,
|
||||
)
|
||||
|
||||
# Sync connection info in processor manager
|
||||
@@ -322,11 +325,12 @@ async def update_device(
|
||||
pass
|
||||
|
||||
# Sync auto_shutdown and zone_mode in runtime state
|
||||
if device_id in manager._devices:
|
||||
ds = manager.find_device_state(device_id)
|
||||
if ds:
|
||||
if update_data.auto_shutdown is not None:
|
||||
manager._devices[device_id].auto_shutdown = update_data.auto_shutdown
|
||||
ds.auto_shutdown = update_data.auto_shutdown
|
||||
if update_data.zone_mode is not None:
|
||||
manager._devices[device_id].zone_mode = update_data.zone_mode
|
||||
ds.zone_mode = update_data.zone_mode
|
||||
|
||||
return _device_to_response(device)
|
||||
|
||||
@@ -420,7 +424,7 @@ async def get_device_brightness(
|
||||
raise HTTPException(status_code=400, detail=f"Brightness control is not supported for {device.device_type} devices")
|
||||
|
||||
# Return cached hardware brightness if available (updated by SET endpoint)
|
||||
ds = manager._devices.get(device_id)
|
||||
ds = manager.find_device_state(device_id)
|
||||
if ds and ds.hardware_brightness is not None:
|
||||
return {"brightness": ds.hardware_brightness}
|
||||
|
||||
@@ -465,13 +469,15 @@ async def set_device_brightness(
|
||||
except NotImplementedError:
|
||||
# Provider has no hardware brightness; use software brightness
|
||||
device.software_brightness = bri
|
||||
device.updated_at = __import__("datetime").datetime.utcnow()
|
||||
from datetime import datetime, timezone
|
||||
device.updated_at = datetime.now(timezone.utc)
|
||||
store.save()
|
||||
if device_id in manager._devices:
|
||||
manager._devices[device_id].software_brightness = bri
|
||||
ds = manager.find_device_state(device_id)
|
||||
if ds:
|
||||
ds.software_brightness = bri
|
||||
|
||||
# Update cached hardware brightness
|
||||
ds = manager._devices.get(device_id)
|
||||
ds = manager.find_device_state(device_id)
|
||||
if ds:
|
||||
ds.hardware_brightness = bri
|
||||
|
||||
@@ -499,7 +505,7 @@ async def get_device_power(
|
||||
|
||||
try:
|
||||
# Serial devices: use tracked state (no hardware query available)
|
||||
ds = manager._devices.get(device_id)
|
||||
ds = manager.find_device_state(device_id)
|
||||
if device.device_type in ("adalight", "ambiled") and ds:
|
||||
return {"on": ds.power_on}
|
||||
|
||||
@@ -532,10 +538,10 @@ async def set_device_power(
|
||||
|
||||
try:
|
||||
# For serial devices, use the cached idle client to avoid port conflicts
|
||||
ds = manager._devices.get(device_id)
|
||||
ds = manager.find_device_state(device_id)
|
||||
if device.device_type in ("adalight", "ambiled") and ds:
|
||||
if not on:
|
||||
await manager._send_clear_pixels(device_id)
|
||||
await manager.send_clear_pixels(device_id)
|
||||
ds.power_on = on
|
||||
else:
|
||||
provider = get_provider(device.device_type)
|
||||
|
||||
Reference in New Issue
Block a user