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/README.md +281 -0
- package/assets/generate-icons.js +86 -0
- package/assets/icon-128.png +0 -0
- package/assets/icon-16.png +0 -0
- package/assets/icon-256.png +0 -0
- package/assets/icon-32.png +0 -0
- package/assets/icon-64.png +0 -0
- package/assets/icon-generator.html +221 -0
- package/assets/icon.icns +0 -0
- package/assets/icon.ico +0 -0
- package/assets/icon.png +0 -0
- package/bin/cli.js +11 -0
- package/index.html +56 -0
- package/main.js +504 -0
- package/package.json +81 -0
- package/preload.js +9 -0
- package/renderer.js +222 -0
- package/styles.css +66 -0
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
|
+
}
|