refactor: rename thumbnail_rendering queue to asset_pipeline
The queue handles far more than thumbnails: OCC tessellation, USD master generation, GLB production, order line renders, and workflow renders. asset_pipeline better reflects its role as the render-worker's primary queue. Updated all references in: task decorators, celery_app.py, beat_tasks.py, docker-compose.yml worker command, worker.py MONITORED_QUEUES, admin.py, CLAUDE.md, LEARNINGS.md, Dockerfile, helpTexts.ts, test files, and all .claude/commands/*.md skill files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -537,6 +537,17 @@ export default function ThreeDViewer({
|
||||
const map = glbExtras.partKeyMap as Record<string, string> | undefined
|
||||
if (map && Object.keys(map).length > 0) {
|
||||
setPartKeyMap(map)
|
||||
// Task 2: Stamp userData.partKey on every mesh (fallback for meshes whose
|
||||
// GLB node extras were not populated — e.g. files generated before Task 1).
|
||||
// For new GLBs, Three.js already set userData.partKey from node extras;
|
||||
// the guard `if (obj.userData.partKey) return` avoids overwriting it.
|
||||
sceneRef.current.traverse((obj) => {
|
||||
if (!(obj instanceof THREE.Mesh)) return
|
||||
if (obj.userData.partKey) return // already set by GLB node extras
|
||||
const normalized = normalizeMeshName((obj.userData?.name as string) || obj.name)
|
||||
const pk = map[normalized] ?? normalized
|
||||
if (pk) obj.userData.partKey = pk
|
||||
})
|
||||
}
|
||||
|
||||
const names = new Set<string>()
|
||||
@@ -679,8 +690,11 @@ export default function ThreeDViewer({
|
||||
e.stopPropagation()
|
||||
const mesh = e.object as THREE.Mesh
|
||||
const raw = (mesh?.userData?.name as string) || mesh?.name || (mesh?.parent?.userData?.name as string) || mesh?.parent?.name || ''
|
||||
const name = normalizeMeshName(raw) || 'Part'
|
||||
setHoverInfo({ name, x: e.nativeEvent.clientX, y: e.nativeEvent.clientY })
|
||||
const normalized = normalizeMeshName(raw) || 'Part'
|
||||
// Task 3: prefer userData.partKey (set by GLB node extras or Task 2 stamp) over
|
||||
// raw normalized name so tooltip shows canonical slug (e.g. "ring_outer") not OCC name
|
||||
const displayName = (mesh?.userData?.partKey as string | undefined) ?? resolvePartKey(normalized)
|
||||
setHoverInfo({ name: displayName, x: e.nativeEvent.clientX, y: e.nativeEvent.clientY })
|
||||
|
||||
// Restore previous hovered mesh (array-safe)
|
||||
if (hoveredMeshRef.current && hoveredMeshRef.current !== mesh) {
|
||||
@@ -703,7 +717,7 @@ export default function ThreeDViewer({
|
||||
const mat = m as THREE.MeshStandardMaterial
|
||||
if (mat && 'emissive' in mat) { mat.emissive.set(0x333333); mat.emissiveIntensity = 0.5 }
|
||||
})
|
||||
}, [showUnassigned])
|
||||
}, [showUnassigned, resolvePartKey])
|
||||
|
||||
const handlePointerOut = useCallback(() => {
|
||||
setHoverInfo(null)
|
||||
|
||||
@@ -59,6 +59,41 @@ const ACTION_CONFIG: Record<string, { icon: typeof Bell; label: (d: Record<strin
|
||||
},
|
||||
}
|
||||
|
||||
type NotifGroup =
|
||||
| { kind: 'single'; item: Notification }
|
||||
| { kind: 'batch'; count: number; failed: number; entityId: string | null; latest: Notification; ids: string[] }
|
||||
|
||||
function groupNotifications(items: Notification[]): NotifGroup[] {
|
||||
const result: NotifGroup[] = []
|
||||
let i = 0
|
||||
while (i < items.length) {
|
||||
const n = items[i]
|
||||
const isRender = n.action === 'render.completed' || n.action === 'render.failed'
|
||||
if (isRender) {
|
||||
const t0 = new Date(n.timestamp).getTime()
|
||||
let j = i
|
||||
let done = 0; let failed = 0
|
||||
const batchIds: string[] = []
|
||||
while (j < items.length) {
|
||||
const m = items[j]
|
||||
const isRenderM = m.action === 'render.completed' || m.action === 'render.failed'
|
||||
const tM = new Date(m.timestamp).getTime()
|
||||
if (!isRenderM || m.entity_id !== n.entity_id || Math.abs(tM - t0) > 5 * 60 * 1000) break
|
||||
batchIds.push(m.id)
|
||||
if (m.action === 'render.failed') failed++; else done++
|
||||
j++
|
||||
}
|
||||
if (j - i >= 2) {
|
||||
result.push({ kind: 'batch', count: done, failed, entityId: n.entity_id ?? null, latest: n, ids: batchIds })
|
||||
i = j; continue
|
||||
}
|
||||
}
|
||||
result.push({ kind: 'single', item: n })
|
||||
i++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function relativeTime(ts: string): string {
|
||||
const diff = Date.now() - new Date(ts).getTime()
|
||||
const seconds = Math.floor(diff / 1000)
|
||||
@@ -182,7 +217,44 @@ export default function NotificationCenter() {
|
||||
{!data?.items.length && (
|
||||
<div className="py-8 text-center text-sm text-content-muted">No notifications</div>
|
||||
)}
|
||||
{data?.items.map((n) => {
|
||||
{data?.items && groupNotifications(data.items).map((group) => {
|
||||
if (group.kind === 'batch') {
|
||||
const { count, failed, entityId, latest, ids } = group
|
||||
const BatchIcon = failed > 0 ? AlertTriangle : CheckCircle
|
||||
const batchColor = failed > 0 ? 'text-red-500' : 'text-status-success-text'
|
||||
const batchLabel = `Render batch: ${count} done${failed > 0 ? `, ${failed} failed` : ''}`
|
||||
return (
|
||||
<button
|
||||
key={`batch-${latest.id}`}
|
||||
onClick={() => {
|
||||
if (!latest.read_at) {
|
||||
ids.forEach((id) => markOneMutation.mutate(id))
|
||||
}
|
||||
if (entityId) navigate(`/orders/${entityId}`)
|
||||
setOpen(false)
|
||||
}}
|
||||
className={clsx(
|
||||
'w-full flex items-start gap-3 px-4 py-3 text-left hover:bg-surface-hover transition-colors border-b border-border-light',
|
||||
!latest.read_at && 'bg-status-info-bg',
|
||||
)}
|
||||
>
|
||||
<BatchIcon size={16} className={clsx('mt-0.5 shrink-0', batchColor)} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className={clsx('text-sm', !latest.read_at ? 'font-medium text-content' : 'text-content-secondary')}>
|
||||
{batchLabel}
|
||||
</p>
|
||||
{entityId && (
|
||||
<p className="text-xs text-content-muted mt-0.5">Click to view order</p>
|
||||
)}
|
||||
<p className="text-xs text-content-muted mt-0.5">{relativeTime(latest.timestamp)}</p>
|
||||
</div>
|
||||
{!latest.read_at && (
|
||||
<span className="mt-1.5 w-2 h-2 rounded-full bg-blue-500 shrink-0" />
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
const n = group.item
|
||||
const cfg = ACTION_CONFIG[n.action] ?? {
|
||||
icon: Bell,
|
||||
label: () => n.action,
|
||||
|
||||
Reference in New Issue
Block a user