vibe-monitor 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/renderer.js ADDED
@@ -0,0 +1,222 @@
1
+ import {
2
+ states, CHARACTER_CONFIG, DEFAULT_CHARACTER,
3
+ CHAR_X_BASE, CHAR_Y_BASE, SLEEP_TIMEOUT,
4
+ FRAME_INTERVAL, LOADING_DOT_COUNT, THINKING_ANIMATION_SLOWDOWN,
5
+ BLINK_START_FRAME, BLINK_END_FRAME,
6
+ PROJECT_NAME_MAX_LENGTH, PROJECT_NAME_TRUNCATE_AT,
7
+ MODEL_NAME_MAX_LENGTH, MODEL_NAME_TRUNCATE_AT
8
+ } from '../shared/config.js';
9
+ import { getThinkingText, getWorkingText, updateMemoryBar } from '../shared/utils.js';
10
+ import { initRenderer, drawCharacter } from '../shared/character.js';
11
+ import { drawInfoIcons } from '../shared/icons.js';
12
+ import { getFloatOffsetX, getFloatOffsetY, needsAnimationRedraw } from '../shared/animation.js';
13
+
14
+ // Platform detection: macOS uses emoji, Windows/Linux uses pixel art
15
+ const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
16
+ const useEmoji = isMac;
17
+
18
+ // Animation timing
19
+ let lastFrameTime = 0;
20
+
21
+ // Current state
22
+ let currentState = 'idle';
23
+ let currentCharacter = 'clawd';
24
+ let currentProject = '-';
25
+ let currentTool = '-';
26
+ let currentModel = '-';
27
+ let currentMemory = '-';
28
+ let animFrame = 0;
29
+ let blinkFrame = 0;
30
+ let lastActivityTime = Date.now();
31
+
32
+ // Canvas
33
+ let canvas, ctx;
34
+
35
+ // Cached DOM elements (initialized once in init())
36
+ let domCache = null;
37
+
38
+ // Initialize DOM cache
39
+ function initDomCache() {
40
+ domCache = {
41
+ display: document.getElementById('display'),
42
+ statusText: document.getElementById('status-text'),
43
+ loadingDots: document.getElementById('loading-dots'),
44
+ toolLine: document.getElementById('tool-line'),
45
+ modelLine: document.getElementById('model-line'),
46
+ memoryLine: document.getElementById('memory-line'),
47
+ projectValue: document.getElementById('project-value'),
48
+ toolValue: document.getElementById('tool-value'),
49
+ modelValue: document.getElementById('model-value'),
50
+ memoryValue: document.getElementById('memory-value'),
51
+ infoTexts: document.querySelectorAll('.info-text'),
52
+ infoLabels: document.querySelectorAll('.info-label'),
53
+ infoValues: document.querySelectorAll('.info-value'),
54
+ dots: document.querySelectorAll('.dot')
55
+ };
56
+ }
57
+
58
+ // Update canvas position for floating effect
59
+ function updateFloatingPosition() {
60
+ const offsetX = getFloatOffsetX(animFrame);
61
+ const offsetY = getFloatOffsetY(animFrame);
62
+ canvas.style.left = (CHAR_X_BASE + offsetX) + 'px';
63
+ canvas.style.top = (CHAR_Y_BASE + offsetY) + 'px';
64
+ }
65
+
66
+ // Initialize
67
+ function init() {
68
+ canvas = document.getElementById('character-canvas');
69
+ ctx = canvas.getContext('2d');
70
+ initRenderer(ctx);
71
+ initDomCache();
72
+
73
+ updateDisplay();
74
+ startAnimation();
75
+
76
+ // Listen for state updates from main process
77
+ if (window.electronAPI) {
78
+ window.electronAPI.onStateUpdate((data) => {
79
+ if (data.state) currentState = data.state;
80
+ if (data.character) currentCharacter = CHARACTER_CONFIG[data.character] ? data.character : DEFAULT_CHARACTER;
81
+ if (data.project) currentProject = data.project;
82
+ if (data.tool) currentTool = data.tool;
83
+ if (data.model !== undefined) currentModel = data.model || '-';
84
+ if (data.memory !== undefined) currentMemory = data.memory || '-';
85
+ lastActivityTime = Date.now();
86
+ updateDisplay();
87
+ });
88
+ }
89
+ }
90
+
91
+ // Update display
92
+ function updateDisplay() {
93
+ const state = states[currentState] || states.idle;
94
+ const d = domCache;
95
+
96
+ // Update background
97
+ d.display.style.background = state.bgColor;
98
+
99
+ // Update text and color
100
+ if (currentState === 'thinking') {
101
+ d.statusText.textContent = getThinkingText();
102
+ } else if (currentState === 'working') {
103
+ d.statusText.textContent = getWorkingText(currentTool);
104
+ } else {
105
+ d.statusText.textContent = state.text;
106
+ }
107
+ d.statusText.style.color = state.textColor;
108
+
109
+ // Update loading dots visibility
110
+ d.loadingDots.style.display = state.showLoading ? 'flex' : 'none';
111
+
112
+ // Update tool line visibility
113
+ d.toolLine.style.display = currentState === 'working' ? 'block' : 'none';
114
+
115
+ // Update values (using constants for truncation)
116
+ const displayProject = currentProject.length > PROJECT_NAME_MAX_LENGTH
117
+ ? currentProject.substring(0, PROJECT_NAME_TRUNCATE_AT) + '...'
118
+ : currentProject;
119
+ d.projectValue.textContent = displayProject;
120
+ d.toolValue.textContent = currentTool;
121
+
122
+ // Update model value (truncate if too long)
123
+ const displayModel = currentModel.length > MODEL_NAME_MAX_LENGTH
124
+ ? currentModel.substring(0, MODEL_NAME_TRUNCATE_AT) + '...'
125
+ : currentModel;
126
+ d.modelValue.textContent = displayModel;
127
+ d.memoryValue.textContent = currentMemory;
128
+
129
+ // Update model/memory visibility (hide memory on start state)
130
+ d.modelLine.style.display = currentModel && currentModel !== '-' ? 'block' : 'none';
131
+ const showMemory = currentState !== 'start' && currentMemory && currentMemory !== '-';
132
+ d.memoryLine.style.display = showMemory ? 'block' : 'none';
133
+
134
+ // Update memory bar (hide on start state)
135
+ updateMemoryBar(showMemory ? currentMemory : null, state.bgColor);
136
+
137
+ // Update all text colors based on state (using cached elements)
138
+ d.infoTexts.forEach(el => el.style.color = state.textColor);
139
+ d.infoLabels.forEach(el => el.style.color = state.textColor);
140
+ d.infoValues.forEach(el => el.style.color = state.textColor);
141
+
142
+ // Draw info icons
143
+ drawInfoIcons(state.textColor, state.bgColor, useEmoji);
144
+
145
+ // Draw character
146
+ drawCharacter(state.eyeType, currentState, currentCharacter, animFrame);
147
+ }
148
+
149
+ // Update loading dots (slower for thinking state)
150
+ function updateLoadingDots(slow = false) {
151
+ const frame = slow ? Math.floor(animFrame / THINKING_ANIMATION_SLOWDOWN) : animFrame;
152
+ const activeIndex = frame % LOADING_DOT_COUNT;
153
+ domCache.dots.forEach((dot, i) => {
154
+ dot.classList.toggle('dim', i !== activeIndex);
155
+ });
156
+ }
157
+
158
+ // Check sleep timer
159
+ function checkSleepTimer() {
160
+ if (currentState === 'start' || currentState === 'idle' || currentState === 'done') {
161
+ const elapsed = Date.now() - lastActivityTime;
162
+ if (elapsed >= SLEEP_TIMEOUT) {
163
+ currentState = 'sleep';
164
+ updateDisplay();
165
+ }
166
+ }
167
+ }
168
+
169
+ // Animation loop using requestAnimationFrame for smoother rendering
170
+ function animationLoop(timestamp) {
171
+ // Throttle to ~100ms intervals
172
+ if (timestamp - lastFrameTime < FRAME_INTERVAL) {
173
+ requestAnimationFrame(animationLoop);
174
+ return;
175
+ }
176
+ lastFrameTime = timestamp;
177
+ animFrame++;
178
+
179
+ updateFloatingPosition();
180
+
181
+ // Only redraw when necessary
182
+ if (needsAnimationRedraw(currentState, animFrame, blinkFrame)) {
183
+ if (currentState === 'start') {
184
+ drawCharacter('sparkle', currentState, currentCharacter, animFrame);
185
+ }
186
+
187
+ if (currentState === 'thinking') {
188
+ updateLoadingDots(true); // Slow for thinking
189
+ drawCharacter('thinking', currentState, currentCharacter, animFrame);
190
+ }
191
+
192
+ if (currentState === 'working') {
193
+ updateLoadingDots(false); // Normal speed for working
194
+ drawCharacter('focused', currentState, currentCharacter, animFrame);
195
+ }
196
+
197
+ if (currentState === 'idle') {
198
+ blinkFrame++;
199
+ if (blinkFrame === BLINK_START_FRAME) {
200
+ drawCharacter('blink', currentState, currentCharacter, animFrame);
201
+ } else if (blinkFrame === BLINK_END_FRAME) {
202
+ drawCharacter('normal', currentState, currentCharacter, animFrame);
203
+ blinkFrame = 0;
204
+ }
205
+ }
206
+
207
+ if (currentState === 'sleep') {
208
+ drawCharacter('sleep', currentState, currentCharacter, animFrame);
209
+ }
210
+ }
211
+
212
+ checkSleepTimer();
213
+ requestAnimationFrame(animationLoop);
214
+ }
215
+
216
+ // Start animation loop
217
+ function startAnimation() {
218
+ requestAnimationFrame(animationLoop);
219
+ }
220
+
221
+ // Initialize on load
222
+ window.onload = init;
package/styles.css ADDED
@@ -0,0 +1,66 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ html, body {
8
+ background: transparent;
9
+ overflow: hidden;
10
+ }
11
+
12
+ /* Draggable title bar */
13
+ .title-bar {
14
+ -webkit-app-region: drag;
15
+ height: 28px;
16
+ background: rgba(45, 45, 45, 0.95);
17
+ border-radius: 12px 12px 0 0;
18
+ display: flex;
19
+ align-items: center;
20
+ padding: 0 10px;
21
+ justify-content: space-between;
22
+ }
23
+
24
+ .title-bar-buttons {
25
+ -webkit-app-region: no-drag;
26
+ display: flex;
27
+ gap: 8px;
28
+ }
29
+
30
+ .title-bar-btn {
31
+ width: 12px;
32
+ height: 12px;
33
+ border-radius: 50%;
34
+ border: none;
35
+ cursor: pointer;
36
+ }
37
+
38
+ .btn-close {
39
+ background: #ff5f57;
40
+ }
41
+
42
+ .btn-close:hover {
43
+ background: #ff3b30;
44
+ }
45
+
46
+ .btn-minimize {
47
+ background: #febc2e;
48
+ }
49
+
50
+ .btn-minimize:hover {
51
+ background: #ff9500;
52
+ }
53
+
54
+ .title-text {
55
+ color: #888;
56
+ font-size: 11px;
57
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
58
+ }
59
+
60
+ /* Device Frame */
61
+ .device-frame {
62
+ background: rgba(45, 45, 45, 0.95);
63
+ padding: 0;
64
+ border-radius: 0 0 12px 12px;
65
+ box-shadow: 0 10px 40px rgba(0,0,0,0.5);
66
+ }