File size: 5,220 Bytes
58613ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
 * ComputerStatusPill (Batch 4) — the header "Studio PC · Online" control.
 *
 * Binds to the Batch-2 spine (ComputerContext + HomePilotAccountProvider) and
 * shows live presence WITHOUT a manual Refresh — the account provider already
 * polls `/v1/account/mirror/nodes` on an interval (a cloud SSE presence stream
 * will make this <10s in a later cloud batch). Clicking opens the Journey-B
 * picker (Automatic / a specific computer); selection updates ComputerContext
 * only — it does NOT route execution yet (that is Batch 5).
 *
 * ADDITIVE: renders `null` unless the Account & Computers flag is on and there
 * is at least one computer, so dropping it into the header changes nothing by
 * default.
 */
import React, { useEffect, useRef, useState } from 'react'
import { ChevronDown, Monitor } from 'lucide-react'

import { useAccount } from './HomePilotAccountProvider'
import { useComputer } from './ComputerContext'
import type { PresenceState } from './types'

function dotClass(state: PresenceState): string {
  return state === 'online' ? 'bg-emerald-400'
    : state === 'attention' ? 'bg-amber-400'
      : 'bg-white/30'
}

export function ComputerStatusPill(): JSX.Element | null {
  const { enabled, computers, loading, refresh } = useAccount()
  const { selectedComputer, selectionMode, presenceOf, anyOnline, selectComputer, setSelectionMode } = useComputer()

  const [open, setOpen] = useState(false)
  const rootRef = useRef<HTMLDivElement | null>(null)

  useEffect(() => {
    if (!open) return
    const onDoc = (e: MouseEvent) => {
      if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false)
    }
    document.addEventListener('mousedown', onDoc)
    return () => document.removeEventListener('mousedown', onDoc)
  }, [open])

  // Additive: invisible unless enabled and there is something to show.
  if (!enabled || computers.length === 0) return null

  // Resolve the pill's label + presence.
  let label: string
  let state: PresenceState
  if (selectionMode === 'fixed' && selectedComputer) {
    label = selectedComputer.node_name || selectedComputer.node_id
    state = presenceOf(selectedComputer.node_id) // offline fixed pick → 'offline' (attention)
    if (state === 'offline') state = 'attention'
  } else if (computers.length === 1) {
    const c = computers[0]
    label = c.node_name || c.node_id
    state = presenceOf(c.node_id)
  } else {
    label = 'Automatic'
    state = anyOnline ? 'online' : 'offline'
  }

  return (
    <div className="relative" ref={rootRef}>
      <button
        type="button"
        onClick={() => { if (!open) refresh(); setOpen((v) => !v) }}
        className="h-9 pl-2.5 pr-2 flex items-center gap-1.5 rounded-full bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition-colors max-w-[220px]"
        title="Where AI tasks run"
        aria-haspopup="menu"
        aria-expanded={open}
      >
        <span className={`w-2 h-2 rounded-full ${dotClass(state)} shrink-0`} />
        <span className="text-[12px] truncate">{label}</span>
        <ChevronDown size={14} className="text-white/40 shrink-0" />
      </button>

      {open && (
        <div
          role="menu"
          className="absolute right-0 mt-2 w-64 rounded-2xl border border-white/10 bg-[#17181a] shadow-2xl p-1.5 z-[60]"
        >
          <button
            role="menuitem"
            onClick={() => { setSelectionMode('automatic'); setOpen(false) }}
            className={`w-full text-left px-3 py-2 rounded-xl flex items-center gap-2 text-[13px] ${
              selectionMode !== 'fixed' ? 'bg-violet-500/15 text-white' : 'text-white/70 hover:bg-white/5'
            }`}
          >
            <span className={`w-2 h-2 rounded-full ${anyOnline ? 'bg-emerald-400' : 'bg-white/30'}`} />
            <span className="flex-1">Automatic</span>
            <span className="text-[10px] text-white/40">recommended</span>
          </button>

          <div className="my-1 h-px bg-white/10" />

          {computers.map((c) => {
            const st = presenceOf(c.node_id)
            const active = selectionMode === 'fixed' && selectedComputer?.node_id === c.node_id
            return (
              <button
                key={c.node_id}
                role="menuitem"
                onClick={() => { selectComputer(c.node_id); setOpen(false) }}
                className={`w-full text-left px-3 py-2 rounded-xl flex items-center gap-2 text-[13px] ${
                  active ? 'bg-violet-500/15 text-white' : 'text-white/70 hover:bg-white/5'
                }`}
              >
                <span className={`w-2 h-2 rounded-full ${dotClass(st)} shrink-0`} />
                <Monitor size={13} className="text-white/40 shrink-0" />
                <span className="flex-1 truncate">{c.node_name || c.node_id}</span>
                <span className="text-[10px] text-white/40">{st === 'online' ? 'Online' : 'Offline'}</span>
              </button>
            )
          })}

          <div className="px-3 pt-1.5 pb-1 text-[10px] text-white/30">
            {loading ? 'Updating…' : 'Updates automatically'}
          </div>
        </div>
      )}
    </div>
  )
}