File size: 6,180 Bytes
b8cc2bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { Component, For, Show, createEffect, createSignal, onCleanup } from 'solid-js';

interface SidebarProps {
  activeTab: string;
  onTabChange: (tab: string) => void;
  // Recording controls
  isRecording: boolean;
  onToggleRecording: () => void;
  // Model state
  isModelReady: boolean;
  onLoadModel: () => void;
  modelState: string;
  // Device selection
  availableDevices: MediaDeviceInfo[];
  selectedDeviceId: string;
  onDeviceSelect: (id: string) => void;
  // Audio feedback
  audioLevel: number;
}

export const Sidebar: Component<SidebarProps> = (props) => {
  const [showDevices, setShowDevices] = createSignal(false);
  let triggerContainerRef: HTMLDivElement | undefined;
  let popoverRef: HTMLDivElement | undefined;

  createEffect(() => {
    if (!showDevices()) return;
    const onMouseDown = (e: MouseEvent) => {
      const target = e.target as Node;
      if (triggerContainerRef?.contains(target) || popoverRef?.contains(target)) return;
      setShowDevices(false);
    };
    const onKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') setShowDevices(false);
    };
    document.addEventListener('mousedown', onMouseDown);
    document.addEventListener('keydown', onKeyDown);
    onCleanup(() => {
      document.removeEventListener('mousedown', onMouseDown);
      document.removeEventListener('keydown', onKeyDown);
    });
  });

  return (
    <aside class="w-20 min-w-[80px] bg-neu-bg flex flex-col items-center py-6 h-full border-r border-sidebar-border/30">
      {/* Power Button - Reflects System Readiness; disabled when model already loaded or loading */}
      <div class="mb-8 relative">
        <button
          onClick={() => !props.isModelReady && props.modelState !== 'loading' && props.onLoadModel()}
          disabled={props.isModelReady || props.modelState === 'loading'}
          class="neu-circle-btn text-slate-600 transition-all active:scale-95 disabled:opacity-70 disabled:cursor-not-allowed disabled:active:scale-100"
          title={props.modelState === 'loading' ? "Loading..." : props.isModelReady ? "Model Loaded" : "Load Model"}
        >
          <span class="material-symbols-outlined text-xl">power_settings_new</span>
          <span class={`status-led ${props.isModelReady ? 'bg-green-500 shadow-[0_0_8px_#22c55e]' : 'bg-slate-300'}`}></span>
        </button>
      </div>

      <nav class="flex flex-col gap-6 items-center w-full px-2">
        {/* Record Button - Always enabled, recording works even before model is loaded */}
        <button
          onClick={() => props.onToggleRecording()}
          class={`neu-circle-btn transition-all active:scale-95 ${props.isRecording ? 'text-red-500 active' : 'text-slate-500'}`}
          title={props.isRecording ? "Stop Recording" : "Start Recording"}
        >
          <span class="material-symbols-outlined text-xl">mic</span>
        </button>

        <div class="w-8 h-[1px] bg-slate-300/60 my-2"></div>

        {/* Model Selection Icon */}
        <button
          onClick={() => props.onLoadModel()}
          class={`neu-square-btn transition-all active:scale-95 ${props.activeTab === 'ai' ? 'active' : 'text-slate-500'}`}
          title="AI Model Selection"
        >
          <span class="material-symbols-outlined text-xl">psychology</span>
        </button>

        {/* Device Selection Popover Trigger */}
        <div class="relative" ref={(el) => { triggerContainerRef = el; }}>
          <button
            class={`neu-square-btn transition-all active:scale-95 ${showDevices() ? 'active' : 'text-slate-500'}`}
            onClick={() => setShowDevices(!showDevices())}
            title="Audio Input Selection"
          >
            <span class="material-symbols-outlined text-xl">settings_input_composite</span>
          </button>

          {/* Device Selection Popover */}
          <Show when={showDevices()}>
            <div ref={(el) => { popoverRef = el; }} class="absolute left-full bottom-0 ml-6 w-64 nm-flat rounded-[32px] p-4 z-50 animate-in fade-in slide-in-from-left-2 duration-200">
              <div class="text-[9px] font-black text-slate-400 p-2 uppercase tracking-widest mb-2 border-b border-slate-200">Mechanical_Input</div>
              <div class="flex flex-col gap-1 max-h-64 overflow-y-auto pr-1">
                <For each={props.availableDevices}>
                  {(device) => (
                    <button
                      class={`w-full text-left px-4 py-3 rounded-2xl text-xs transition-all flex items-center gap-3 ${props.selectedDeviceId === device.deviceId
                        ? 'nm-inset text-primary font-bold'
                        : 'text-slate-600 hover:nm-flat'
                        }`}
                      onClick={() => {
                        props.onDeviceSelect(device.deviceId);
                        setShowDevices(false);
                      }}
                    >
                      <span class="material-symbols-outlined text-lg opacity-40">mic</span>
                      <span class="truncate font-medium">{device.label || `Channel ${device.deviceId.slice(0, 4)}`}</span>
                    </button>
                  )}
                </For>
              </div>
            </div>
          </Show>
        </div>

        {/* Placeholder Items matching design */}
        <button class="neu-square-btn text-slate-300 cursor-not-allowed" title="Translation (Pro)">
          <span class="material-symbols-outlined text-xl">translate</span>
        </button>

        <button class="neu-square-btn text-slate-500" title="Export Transcript" onClick={() => (window as any).appStore?.copyTranscript()}>
          <span class="material-symbols-outlined text-xl">download</span>
        </button>
      </nav>

      <div class="mt-auto">
        <button
          class={`neu-square-btn transition-all active:scale-95 ${props.activeTab === 'settings' ? 'active' : 'text-slate-500'}`}
          onClick={() => props.onTabChange('settings')}
          title="Settings"
        >
          <span class="material-symbols-outlined text-xl">settings</span>
        </button>
      </div>
    </aside>
  );
};

export default Sidebar;