momolulu 1.1.1 → 1.2.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/client.js +92 -51
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -12,7 +12,7 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
];
|
|
13
13
|
const zh = {
|
|
14
14
|
title: 'momolulu', subtitle: '慢慢来,我陪你一起。', meadow: '草甸', hike: '徒步', lake: '湖畔',
|
|
15
|
-
radarWaiting: '等待处理', radarApproval: '请求权限', radarQuestion: '等待输入', radarSoftHint: '有其他会话可查看,无需确认或立即处理。', radar: '会话雷达', radarRunning: '运行中', radarUnread: '
|
|
15
|
+
radarWaiting: '等待处理', radarApproval: '请求权限', radarQuestion: '等待输入', radarSoftHint: '有其他会话可查看,无需确认或立即处理。', radar: '会话雷达', radarRunning: '运行中', radarUnread: '待查看', radarQuiet: '暂无变化', radarEmpty: '暂无会话变化', radarHint: '结束不代表成功;待查看状态仅在当前页面保留。', openFailed: '无法打开此会话,请从侧栏重试。', showAll: '展开全部', showLess: '收起列表', reminderPaused: '提醒随专注暂停',
|
|
16
16
|
testReminder: '测试提醒(8 秒)', sound: '提示音', soundOn: '提示音 · 开', soundOff: '提示音 · 关', focusCommand: '专注', focusCommandHint: '开始或暂停 momolulu 的专注计时', minimize: '缩成小头像', restore: '恢复照片', details: '详情',
|
|
17
17
|
close: '收起', focus: '专注倒计时', start: '开始专注', pause: '暂停', resume: '继续', reset: '重置',
|
|
18
18
|
ready: '准备好,开始一小段专注', running: '专注中', paused: '已暂停', done: '这一轮完成啦',
|
|
@@ -20,7 +20,7 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
remindOff: '提醒已关闭,专注计时不受影响', remindToday: '今天已暂停提醒', remindReady: '开始专注后计时',
|
|
21
21
|
break: '喝口水、活动肩颈,让眼睛看看远处吧。', finish: '专注完成!休息一下,再继续也不迟。',
|
|
22
22
|
got: '收到', snooze: '10 分钟后提醒', today: '今天暂停', background: '背景', intensity: '背景浓度',
|
|
23
|
-
|
|
23
|
+
drift: '缓慢巡游', activity: '会话动态', idle: '空闲', processing: '正在处理', newSession: '新会话',
|
|
24
24
|
connecting: '正在连接会话…', empty: '暂无会话,开始创作吧。', more: '更多会话', view: '展开专注面板',
|
|
25
25
|
weather: '天气', nanjing: '南京', local: '当前位置', locate: '使用当前位置', locating: '定位中…', locationError: '定位不可用,已切回南京', weatherLoading: '天气加载中', weatherError: '天气暂不可用', stale: '上次更新', refresh: '刷新', brightness: '背景亮度', backgroundAdjust: '背景调节', privacy: '点击定位后,坐标仅用于 Open-Meteo 天气查询,不保存到本地。', clear: '晴', cloudy: '多云', fog: '雾', rain: '雨', snow: '雪', storm: '雷雨', unknownWeather: '天气',
|
|
26
26
|
restAfterFocus: '本轮结束后休息', enterFullscreen: '进入全屏', appearance: '外观设置', reminderSettings: '提醒设置', reminderEvery: '每 20 分钟提醒喝水 / 站立', noRunning: '当前没有运行中的任务', runningTasks: '运行中的任务', workMode: '工作模式', scenicMode: '陪伴模式', readingMode: '阅读保护',
|
|
@@ -28,7 +28,7 @@ window.__ModuleLoader__.load({
|
|
|
28
28
|
};
|
|
29
29
|
const en = {
|
|
30
30
|
title: 'momolulu', subtitle: 'One step at a time, together.', meadow: 'Meadow', hike: 'Hike', lake: 'Lake',
|
|
31
|
-
radarWaiting: 'Needs input', radarApproval: 'Permission requested', radarQuestion: 'Waiting for input', radarSoftHint: 'Other sessions have updates. No acknowledgement or immediate action required.', radar: 'Session radar', radarRunning: 'Running', radarUnread: '
|
|
31
|
+
radarWaiting: 'Needs input', radarApproval: 'Permission requested', radarQuestion: 'Waiting for input', radarSoftHint: 'Other sessions have updates. No acknowledgement or immediate action required.', radar: 'Session radar', radarRunning: 'Running', radarUnread: 'Unread', radarQuiet: 'No updates', radarEmpty: 'No session changes', radarHint: 'Ended does not mean successful. Unread status is local to this page.', openFailed: 'Unable to open this session. Try the sidebar.', showAll: 'Show all', showLess: 'Show less', reminderPaused: 'Reminders paused with focus',
|
|
32
32
|
testReminder: 'Test reminder (8 sec)', sound: 'Chime', soundOn: 'Chime · On', soundOff: 'Chime · Off', focusCommand: 'Focus', focusCommandHint: 'Start or pause the momolulu focus timer', minimize: 'Minimize to avatar', restore: 'Restore photo', details: 'Details',
|
|
33
33
|
close: 'Close', focus: 'Focus countdown', start: 'Start focus', pause: 'Pause', resume: 'Resume', reset: 'Reset',
|
|
34
34
|
ready: 'Ready for a little focus time', running: 'Focusing', paused: 'Paused', done: 'Session complete',
|
|
@@ -36,7 +36,7 @@ window.__ModuleLoader__.load({
|
|
|
36
36
|
remindOff: 'Reminders off · focus timer continues', remindToday: 'Reminders paused today', remindReady: 'Starts with your focus timer',
|
|
37
37
|
break: 'Have some water, stretch your shoulders and rest your eyes.', finish: 'Well done! Take a break before your next session.',
|
|
38
38
|
got: 'Got it', snooze: 'Remind in 10 min', today: 'Pause today', background: 'Background', intensity: 'Background visibility',
|
|
39
|
-
|
|
39
|
+
drift: 'Slow drift', activity: 'Session activity', idle: 'Idle', processing: 'Processing', newSession: 'New session',
|
|
40
40
|
connecting: 'Connecting to sessions…', empty: 'No sessions yet. Start creating.', more: 'More sessions', view: 'Open focus panel',
|
|
41
41
|
weather: 'Weather', nanjing: 'Nanjing', local: 'Current location', locate: 'Use my location', locating: 'Locating…', locationError: 'Location unavailable; using Nanjing', weatherLoading: 'Loading weather', weatherError: 'Weather unavailable', stale: 'Last update', refresh: 'Refresh', brightness: 'Background brightness', backgroundAdjust: 'Background settings', privacy: 'Location is sent only to Open-Meteo for weather after you click; coordinates are not stored locally.', clear: 'Clear', cloudy: 'Cloudy', fog: 'Fog', rain: 'Rain', snow: 'Snow', storm: 'Thunderstorm', unknownWeather: 'Weather',
|
|
42
42
|
restAfterFocus: 'Rest after this session', enterFullscreen: 'Enter fullscreen', appearance: 'Appearance', reminderSettings: 'Reminder settings', reminderEvery: 'Water / stand reminder every 20 minutes', noRunning: 'No tasks running', runningTasks: 'Running tasks', workMode: 'Work mode', scenicMode: 'Scenic mode', readingMode: 'Reading protection',
|
|
@@ -95,13 +95,13 @@ window.__ModuleLoader__.load({
|
|
|
95
95
|
reminderRemaining: Number.isFinite(saved?.reminderRemaining) ? Math.max(0, saved.reminderRemaining) : 20 * 60000,
|
|
96
96
|
sound: saved?.sound === true,
|
|
97
97
|
reminders: saved?.reminders !== false, nextReminder: Number.isFinite(saved?.nextReminder) ? saved.nextReminder : 0,
|
|
98
|
-
pausedDate: typeof saved?.pausedDate === 'string' ? saved.pausedDate : '', notice: '', noticeAt: 0,
|
|
98
|
+
pausedDate: typeof saved?.pausedDate === 'string' ? saved.pausedDate : '', notice: '', noticeAt: 0, previewAt: 0, previewUntil: 0,
|
|
99
99
|
scene: [0, 1, 2].includes(saved?.scene) ? saved.scene : 0,
|
|
100
100
|
minimized: saved?.minimized === true,
|
|
101
101
|
readingProtection: saved?.readingProtection !== false, fullscreen: false,
|
|
102
102
|
brightness: Math.min(180, Math.max(70, Number(saved?.brightness) || 122)),
|
|
103
103
|
weather: null, weatherState: 'loading', locationKey: 'nanjing', locationError: false, locating: false,
|
|
104
|
-
bgOn: saved?.bgOn !== false, intensity: Math.min(75, Math.max(15, Number(saved?.intensity) || 45)),
|
|
104
|
+
bgOn: saved?.bgOn !== false, intensity: Math.min(75, Math.max(15, Number(saved?.intensity) || 45)), drift: saved?.drift !== false,
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
let state;
|
|
@@ -127,31 +127,33 @@ window.__ModuleLoader__.load({
|
|
|
127
127
|
}
|
|
128
128
|
function playChime() {
|
|
129
129
|
const audio = ensureAudio();
|
|
130
|
-
if (!audio
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
130
|
+
if (!audio) return;
|
|
131
|
+
const emit = () => {
|
|
132
|
+
const start = audio.currentTime;
|
|
133
|
+
[660, 880].forEach((frequency, index) => {
|
|
134
|
+
const oscillator = audio.createOscillator();
|
|
135
|
+
const gain = audio.createGain();
|
|
136
|
+
const at = start + index * 0.18;
|
|
137
|
+
oscillator.type = 'sine';
|
|
138
|
+
oscillator.frequency.value = frequency;
|
|
139
|
+
gain.gain.setValueAtTime(0, at);
|
|
140
|
+
gain.gain.linearRampToValueAtTime(0.12, at + 0.02);
|
|
141
|
+
gain.gain.exponentialRampToValueAtTime(0.0001, at + 0.35);
|
|
142
|
+
oscillator.connect(gain).connect(audio.destination);
|
|
143
|
+
oscillator.start(at);
|
|
144
|
+
oscillator.stop(at + 0.4);
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
// The first chime often runs while the context is still suspended; wait for it.
|
|
148
|
+
if (audio.state === 'running') emit();
|
|
149
|
+
else Promise.resolve(audio.resume()).then(emit).catch(() => {});
|
|
145
150
|
}
|
|
146
151
|
function tick(now = Date.now()) {
|
|
147
152
|
const patch = { now };
|
|
148
153
|
let persist = false;
|
|
149
154
|
if (state.status === 'running' && remainingMs(state, now) === 0) {
|
|
150
|
-
Object.assign(patch, { status: 'done', remaining: 0 });
|
|
151
|
-
if (state.
|
|
152
|
-
Object.assign(patch, { notice: 'finish', noticeAt: now });
|
|
153
|
-
if (state.sound) playChime();
|
|
154
|
-
}
|
|
155
|
+
Object.assign(patch, { status: 'done', remaining: 0, notice: 'finish', noticeAt: now });
|
|
156
|
+
if (state.sound) playChime();
|
|
155
157
|
persist = true;
|
|
156
158
|
} else if (state.status === 'running' && state.reminders && state.pausedDate !== new Date(now).toDateString() && state.nextReminder && now >= state.nextReminder) {
|
|
157
159
|
Object.assign(patch, { notice: 'break', noticeAt: now, nextReminder: now + 20 * 60000 });
|
|
@@ -166,7 +168,7 @@ window.__ModuleLoader__.load({
|
|
|
166
168
|
update({ now, status: 'running', remaining, deadline: now + remaining, nextReminder: now + (state.status === 'paused' ? (state.reminderRemaining ?? 20 * 60000) : 20 * 60000), notice: '' });
|
|
167
169
|
}
|
|
168
170
|
function pause() { const now = Date.now(); update({ now, remaining: remainingMs(state, now), reminderRemaining: Math.max(0, state.nextReminder - now), status: 'paused' }); }
|
|
169
|
-
function reset(minutes = state.minutes) { update({ minutes, status: 'ready', remaining: minutes * 60000, deadline: 0, nextReminder: 0, notice: '' }); }
|
|
171
|
+
function reset(minutes = state.minutes) { update({ minutes, status: 'ready', remaining: minutes * 60000, deadline: 0, nextReminder: 0, notice: '', previewAt: 0, previewUntil: 0 }); }
|
|
170
172
|
function setReminders(enabled) {
|
|
171
173
|
const now = Date.now();
|
|
172
174
|
update({ reminders: enabled, pausedDate: '', nextReminder: now + 20 * 60000, reminderRemaining: 20 * 60000, notice: '' });
|
|
@@ -283,8 +285,8 @@ window.__ModuleLoader__.load({
|
|
|
283
285
|
.llm-primary{flex:1;font-weight:700;background:color-mix(in srgb,var(--dsw-alias-brand-primary) 17%,var(--dsw-alias-bg-layer-2));border-color:var(--dsw-alias-brand-primary)}
|
|
284
286
|
.llm-photo{position:relative;display:block;width:196px;height:238px;overflow:hidden;border:2px solid var(--dsw-alias-border-l2);border-radius:26px;padding:0;cursor:pointer;pointer-events:auto;background:var(--dsw-alias-bg-layer-1);box-shadow:0 12px 40px color-mix(in srgb,var(--dsw-alias-label-primary) 20%,transparent)}
|
|
285
287
|
.llm-photo img{width:100%;height:100%;object-fit:cover;object-position:73% 80%;filter:brightness(1.12);transition:transform .6s}
|
|
286
|
-
.llm-photo:hover img{transform:scale(1.06)}.llm-
|
|
287
|
-
.llm-photo-caption{position:absolute;inset:12px 10px auto;padding:0;border:0;background:transparent;color:#fff;text-shadow:0 1px 4px #000,0 0 12px #000;text-align:left;pointer-events:none}.llm-photo-caption .llm-sub{color:inherit}.llm-status-line{display:flex;justify-content:center;align-items:center;gap:12px;flex-wrap:wrap}.llm-next-inline{font-size:11px;font-variant-numeric:tabular-nums}.llm-photo-caption .llm-status-line{display:grid;grid-template-columns:1fr;gap:2px;margin-top:4px;text-align:left}.llm-photo-caption .llm-sub,.llm-photo-caption .llm-next-inline,.llm-weather-mini{max-width:100%;text-align:left;font:500 11px/1.45 system-ui,-apple-system,sans-serif;font-variant-numeric:tabular-nums;letter-spacing:0}.llm-weather-mini{margin-top:4px}.llm-photo-name{font-size:12px}.llm-
|
|
288
|
+
.llm-photo:hover img{transform:scale(1.06)}.llm-stack{position:relative;display:flex;flex-direction:column;align-items:flex-end;gap:7px}.llm-drift{animation:llm-drift 58s linear infinite alternate;will-change:transform}.llm-drift:hover,.llm-drift:has(:focus-visible),.llm-drift:has(.llm-card){animation-play-state:paused}
|
|
289
|
+
.llm-photo-caption{position:absolute;inset:12px 10px auto;padding:0;border:0;background:transparent;color:#fff;text-shadow:0 1px 4px #000,0 0 12px #000;text-align:left;pointer-events:none}.llm-photo-caption .llm-sub{color:inherit}.llm-status-line{display:flex;justify-content:center;align-items:center;gap:12px;flex-wrap:wrap}.llm-next-inline{font-size:11px;font-variant-numeric:tabular-nums}.llm-photo-caption .llm-status-line{display:grid;grid-template-columns:1fr;gap:2px;margin-top:4px;text-align:left}.llm-photo-caption .llm-sub,.llm-photo-caption .llm-next-inline,.llm-weather-mini{max-width:100%;text-align:left;font:500 11px/1.45 system-ui,-apple-system,sans-serif;font-variant-numeric:tabular-nums;letter-spacing:0}.llm-weather-mini{margin-top:4px}.llm-photo-name{font-size:12px}.llm-sliders{margin-top:12px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l1)}.llm-sliders label{display:grid;grid-template-columns:96px 1fr 42px;align-items:center;gap:8px;margin-top:10px;font-size:12px;color:var(--dsw-alias-label-secondary)}.llm-sliders input{width:100%;accent-color:var(--dsw-alias-brand-primary)}.llm-sliders output{text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary)}.llm-weather-panel{margin:12px 0;padding:10px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px}.llm-weather-panel a{color:var(--dsw-alias-label-secondary)}
|
|
288
290
|
.llm-photo-name{display:flex;justify-content:space-between;align-items:center;font-weight:750}.llm-mini-clock{font-size:26px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1.2;margin-top:4px}
|
|
289
291
|
.llm-focus{padding:18px 14px;border:1px solid var(--dsw-alias-border-l1);border-radius:20px;background:var(--dsw-alias-bg-layer-2);text-align:center}
|
|
290
292
|
.llm-label{color:var(--dsw-alias-label-secondary);font-size:12px}.llm-clock{font-size:64px;font-weight:800;letter-spacing:-3px;line-height:1.18;font-variant-numeric:tabular-nums;margin:7px 0;color:var(--dsw-alias-brand-primary)}
|
|
@@ -294,12 +296,12 @@ window.__ModuleLoader__.load({
|
|
|
294
296
|
.llm-section{margin:18px 0 8px;font-weight:700}.llm-row{padding:8px 0;border-top:1px solid var(--dsw-alias-border-l1)}.llm-row-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:280px}.llm-dot{height:8px;width:8px;flex:none;border-radius:50%;background:var(--dsw-alias-state-idle-primary)}.llm-live{background:var(--dsw-alias-state-success-primary)}
|
|
295
297
|
.llm-notice{margin-top:12px;padding:12px;border-radius:14px;background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 18%,var(--dsw-alias-bg-layer-2))}.llm-controls{margin-top:10px;flex-wrap:wrap}
|
|
296
298
|
.llm-settings{flex-wrap:wrap;margin-top:12px}.llm-settings label{display:flex;align-items:center;gap:8px}.llm-settings input[type=range]{width:110px;accent-color:var(--dsw-alias-brand-primary)}
|
|
297
|
-
.llm-float-widget{width:184px;pointer-events:auto}.llm-photo-stage{position:relative}.llm-photo-clean{width:184px;height:166px;border-radius:22px;border-width:1px}.llm-photo-clean img{object-position:73% 90%}.llm-photo-brand{position:absolute;top:10px;left:12px;font-size:12px;font-weight:700;color:#fff;text-shadow:0 1px 5px #000}.llm-minimize{position:absolute;right:7px;top:7px;width:30px;height:30px;padding:0;border:1px solid var(--dsw-alias-border-l1);border-radius:50%;background:color-mix(in srgb,var(--dsw-alias-bg-overlay) 70%,transparent);color:var(--dsw-alias-label-primary);cursor:pointer;font-size:18px!important}.llm-float-info{padding:8px 2px 0;text-align:left;background:transparent;text-shadow:0 1px 3px var(--dsw-alias-bg-base),0 0 8px var(--dsw-alias-bg-base)}.llm-float-clock-row{display:flex;align-items:center;justify-content:space-between;gap:8px}.llm-float-clock{font-size:28px;font-weight:750;font-variant-numeric:tabular-nums;line-height:1.1;letter-spacing:-.7px}.llm-quick{width:32px;height:32px;border-radius:50%;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer}.llm-float-status,.llm-float-reminder,.llm-float-weather{font-size:11px;font-weight:500;line-height:1.5;font-variant-numeric:tabular-nums;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.llm-float-weather{color:var(--dsw-alias-label-secondary)}.llm-float-notice{display:block;margin-top:5px;padding:5px 0;background:transparent;border:0;color:var(--dsw-alias-state-warn-primary);font-size:11px;cursor:pointer;text-align:left}.llm-bubble{position:relative;width:68px;height:68px;border-radius:50%;border:0;padding:4px;background:conic-gradient(var(--dsw-alias-brand-primary) var(--llm-progress),var(--dsw-alias-border-l2) 0);cursor:pointer;pointer-events:auto}.llm-bubble img{width:60px;height:60px;object-fit:cover;object-position:73% 85%;border-radius:50%;display:block}.llm-bubble-time{position:absolute;bottom:-7px;left:50%;transform:translateX(-50%);border-radius:10px;padding:1px 6px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);font-size:11px;font-variant-numeric:tabular-nums;border:1px solid var(--dsw-alias-border-l1)}.llm-bubble-notice{position:absolute;right:0;top:0;border-radius:50%;padding:0 6px;background:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-bg-base)}.llm-root button:focus-visible{outline:3px solid var(--dsw-alias-brand-primary);outline-offset:3px}.llm-
|
|
298
|
-
.llm-
|
|
299
|
+
.llm-float-widget{width:184px;pointer-events:auto}.llm-photo-stage{position:relative}.llm-photo-clean{width:184px;height:166px;border-radius:22px;border-width:1px}.llm-photo-clean img{object-position:73% 90%}.llm-photo-brand{position:absolute;top:10px;left:12px;font-size:12px;font-weight:700;color:#fff;text-shadow:0 1px 5px #000}.llm-minimize{position:absolute;right:7px;top:7px;width:30px;height:30px;padding:0;border:1px solid var(--dsw-alias-border-l1);border-radius:50%;background:color-mix(in srgb,var(--dsw-alias-bg-overlay) 70%,transparent);color:var(--dsw-alias-label-primary);cursor:pointer;font-size:18px!important}.llm-float-info{padding:8px 2px 0;text-align:left;background:transparent;text-shadow:0 1px 3px var(--dsw-alias-bg-base),0 0 8px var(--dsw-alias-bg-base)}.llm-float-clock-row{display:flex;align-items:center;justify-content:space-between;gap:8px}.llm-float-clock{font-size:28px;font-weight:750;font-variant-numeric:tabular-nums;line-height:1.1;letter-spacing:-.7px}.llm-quick{width:32px;height:32px;border-radius:50%;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer}.llm-float-status,.llm-float-reminder,.llm-float-weather{font-size:11px;font-weight:500;line-height:1.5;font-variant-numeric:tabular-nums;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.llm-float-weather{color:var(--dsw-alias-label-secondary)}.llm-float-notice{display:block;margin-top:5px;padding:5px 0;background:transparent;border:0;color:var(--dsw-alias-state-warn-primary);font-size:11px;cursor:pointer;text-align:left}.llm-bubble{position:relative;width:68px;height:68px;border-radius:50%;border:0;padding:4px;background:conic-gradient(var(--dsw-alias-brand-primary) var(--llm-progress),var(--dsw-alias-border-l2) 0);cursor:pointer;pointer-events:auto}.llm-bubble img{width:60px;height:60px;object-fit:cover;object-position:73% 85%;border-radius:50%;display:block}.llm-bubble-time{position:absolute;bottom:-7px;left:50%;transform:translateX(-50%);border-radius:10px;padding:1px 6px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);font-size:11px;font-variant-numeric:tabular-nums;border:1px solid var(--dsw-alias-border-l1)}.llm-bubble-notice{position:absolute;right:0;top:0;border-radius:50%;padding:0 6px;background:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-bg-base)}.llm-root button:focus-visible{outline:3px solid var(--dsw-alias-brand-primary);outline-offset:3px}.llm-bubble:hover,.llm-bubble:focus-visible{animation-play-state:paused}
|
|
300
|
+
.llm-radar-trigger{pointer-events:auto;max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:5px 9px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);cursor:pointer;font-size:11px!important}.llm-radar-badge{position:absolute;right:-5px;top:-9px;z-index:2;min-width:27px;max-width:48px;min-height:27px;border-radius:999px;font-weight:700!important}.llm-radar-panel{position:absolute;right:0;z-index:30;max-height:min(64vh,520px);width:min(320px,calc(100vw - 32px));max-height:min(440px,calc(100dvh - 130px));overflow:auto;padding:14px;border:1px solid var(--dsw-alias-border-l2);border-radius:18px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);pointer-events:auto;box-shadow:0 12px 40px color-mix(in srgb,var(--dsw-alias-label-primary) 15%,transparent)}.llm-radar-group{margin:10px 0}.llm-radar-label{font-size:12px;font-weight:650;margin-bottom:5px}.llm-radar-row{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;padding:9px 7px;text-align:left;background:transparent;border:0;border-bottom:1px solid var(--dsw-alias-border-l1);border-radius:6px;color:var(--dsw-alias-label-primary);cursor:pointer}.llm-radar-row:hover{background:var(--dsw-alias-bg-layer-2)}.llm-radar-row-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
299
301
|
.llm-reminder-button{display:block;width:100%;padding:0;background:transparent;border:0;color:inherit;cursor:pointer;text-align:left}.llm-alert-highlight{color:var(--dsw-alias-label-primary)!important;background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 24%,var(--dsw-alias-bg-layer-1));border-left:3px solid var(--dsw-alias-state-warn-primary);border-radius:6px;padding:6px 8px!important;font-weight:700!important;white-space:normal!important;text-shadow:none}.llm-alert-actions{display:flex;gap:4px;margin-top:5px;flex-wrap:wrap}.llm-alert-actions .llm-btn{font-size:10px;padding:4px 6px}.llm-alert-shake{animation:llm-nudge .75s ease-in-out 2;transform-origin:center bottom}@keyframes llm-nudge{0%,100%{transform:translateX(0) rotate(0)}20%{transform:translateX(-4px) rotate(-2deg)}40%{transform:translateX(4px) rotate(2deg)}60%{transform:translateX(-3px) rotate(-1deg)}80%{transform:translateX(2px) rotate(1deg)}}@media(prefers-reduced-motion:reduce){.llm-alert-shake{animation:none}}
|
|
300
|
-
.llm-radar-attention{border-color:var(--dsw-alias-state-warn-primary)!important;background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 22%,var(--dsw-alias-bg-overlay))!important;box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-warn-primary) 18%,transparent)}.llm-radar-kind{display:block;color:var(--dsw-alias-state-warn-primary);font-size:11px;font-weight:600;margin-top:2px}
|
|
301
|
-
@keyframes llm-
|
|
302
|
-
@media(prefers-reduced-motion:reduce){.llm-
|
|
302
|
+
.llm-radar-panel[data-placement='above']{bottom:calc(100% + 8px)}.llm-radar-panel[data-placement='below']{top:calc(100% + 8px)}.llm-radar-attention{border-color:var(--dsw-alias-state-warn-primary)!important;background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 22%,var(--dsw-alias-bg-overlay))!important;box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-warn-primary) 18%,transparent)}.llm-radar-count{margin-left:6px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary)}.llm-radar-kind{display:block;color:var(--dsw-alias-state-warn-primary);font-size:11px;font-weight:600;margin-top:2px}
|
|
303
|
+
@keyframes llm-drift{from{transform:translateY(0)}to{transform:translateY(var(--llm-drift-px, 0px))}}
|
|
304
|
+
@media(prefers-reduced-motion:reduce){.llm-drift{animation:none}.llm-photo img{transition:none}}
|
|
303
305
|
@media(max-width:600px){.llm-floating{right:12px;bottom:12px}.llm-photo{width:168px;height:212px}.llm-float-widget{width:168px}.llm-photo-clean{width:168px;height:150px}.llm-card{width:min(370px,calc(100vw - 24px));max-height:calc(100dvh - 48px)}.llm-panel{padding:64px 12px 12px}.llm-clock{font-size:54px}}
|
|
304
306
|
`;
|
|
305
307
|
function Companion({ useSessions, useSessionStatus, panelMode = false }) {
|
|
@@ -309,7 +311,10 @@ window.__ModuleLoader__.load({
|
|
|
309
311
|
const [radarOpen, setRadarOpen] = React.useState(false);
|
|
310
312
|
const [showAll, setShowAll] = React.useState(false);
|
|
311
313
|
const [navigationError, setNavigationError] = React.useState('');
|
|
314
|
+
const [radarPlacement, setRadarPlacement] = React.useState(null);
|
|
312
315
|
const radarTrigger = React.useRef(null);
|
|
316
|
+
const stackRef = React.useRef(null);
|
|
317
|
+
const [driftPx, setDriftPx] = React.useState(0);
|
|
313
318
|
const radarPanel = React.useRef(null);
|
|
314
319
|
const radarId = React.useId();
|
|
315
320
|
React.useEffect(() => {
|
|
@@ -347,22 +352,51 @@ window.__ModuleLoader__.load({
|
|
|
347
352
|
const statusKey = model.status === 'running' ? 'running' : model.status === 'paused' ? 'paused' : model.status === 'done' ? 'done' : 'ready';
|
|
348
353
|
const button = (label, onClick, extra = {}) => h('button', { type: 'button', className: 'llm-btn', onClick, ...extra }, label);
|
|
349
354
|
const radarCount = groups.running.length + groups.unread.length + groups.waiting.length;
|
|
350
|
-
const radarSummary =
|
|
355
|
+
const radarSummary = [
|
|
356
|
+
groups.waiting.length && `${groups.waiting.length} ${t('radarWaiting')}`,
|
|
357
|
+
groups.running.length && `${groups.running.length} ${t('radarRunning')}`,
|
|
358
|
+
groups.unread.length && `${groups.unread.length} ${t('radarUnread')}`,
|
|
359
|
+
].filter(Boolean).join(' · ') || t('radarQuiet');
|
|
351
360
|
const closeRadar = () => { setRadarOpen(false); radarTrigger.current?.focus(); };
|
|
352
361
|
const navigate = (row) => {
|
|
353
362
|
try { workspace.openSession(row.id); setRadarOpen(false); setOpen(false); setNavigationError(''); }
|
|
354
363
|
catch { setNavigationError(t('openFailed')); }
|
|
355
364
|
};
|
|
356
|
-
const renderGroup = (label, rows) => h('div', { className: 'llm-radar-group' },
|
|
357
|
-
h('div', { className: 'llm-radar-label' },
|
|
365
|
+
const renderGroup = (label, rows) => rows.length === 0 ? null : h('div', { className: 'llm-radar-group' },
|
|
366
|
+
h('div', { className: 'llm-radar-label' }, label, h('span', { className: 'llm-radar-count' }, rows.length)),
|
|
358
367
|
rows.slice(0, showAll ? rows.length : 3).map((row) => button(
|
|
359
368
|
h(React.Fragment, null, h('span', { className: 'llm-radar-row-title' }, row.displayTitle || row.id, row.attentionKind && h('small', { className: 'llm-radar-kind' }, t(row.attentionKind))), h('span', { 'aria-hidden': true }, '↗')),
|
|
360
369
|
() => navigate(row), { key: row.id, className: 'llm-radar-row', title: `${row.displayTitle || row.id}${row.cwd ? '\n' + row.cwd : ''}` })));
|
|
370
|
+
const drifting = model.drift && !panelMode;
|
|
371
|
+
React.useEffect(() => {
|
|
372
|
+
if (!drifting) { setDriftPx(0); return undefined; }
|
|
373
|
+
const measure = () => {
|
|
374
|
+
const height = stackRef.current?.getBoundingClientRect().height ?? 0;
|
|
375
|
+
const travel = Math.round(height + 136 - window.innerHeight);
|
|
376
|
+
setDriftPx(Math.min(0, travel));
|
|
377
|
+
};
|
|
378
|
+
measure();
|
|
379
|
+
window.addEventListener('resize', measure);
|
|
380
|
+
return () => window.removeEventListener('resize', measure);
|
|
381
|
+
}, [drifting, expanded, model.minimized]);
|
|
361
382
|
return h('div', { className: `llm-root ${panelMode ? 'llm-panel' : 'llm-floating'}`, onPointerDownCapture: () => { if (model.sound) ensureAudio(); } },
|
|
362
383
|
h('style', null, css),
|
|
363
384
|
model.bgOn && h('div', { className: 'llm-bg' + (model.readingProtection && !panelMode ? ' llm-bg-protected' : ''), style: { backgroundImage: `url("${asset(currentScene.name)}")`, opacity: model.intensity / 100, filter: `brightness(${model.brightness / 100}) saturate(1.12)` } }),
|
|
364
|
-
|
|
365
|
-
|
|
385
|
+
|
|
386
|
+
h('div', { ref: stackRef, className: 'llm-stack' + (drifting ? ' llm-drift' : ''), style: drifting ? { '--llm-drift-px': `${driftPx}px` } : undefined },
|
|
387
|
+
!panelMode && h('button', { type: 'button', ref: radarTrigger, className: 'llm-radar-trigger' + (radarHighlight ? ' llm-radar-attention' : '') + (radarShake ? ' llm-alert-shake' : '') + (model.minimized && !expanded ? ' llm-radar-badge' : ''), onClick: () => {
|
|
388
|
+
const next = !radarOpen;
|
|
389
|
+
if (next && radarTrigger.current) {
|
|
390
|
+
const rect = radarTrigger.current.getBoundingClientRect();
|
|
391
|
+
const spaceAbove = rect.top - 24;
|
|
392
|
+
const spaceBelow = window.innerHeight - rect.bottom - 24;
|
|
393
|
+
if (spaceAbove >= 240 || spaceAbove >= spaceBelow) setRadarPlacement({ side: 'above', maxHeight: Math.max(180, Math.min(440, spaceAbove)) });
|
|
394
|
+
else setRadarPlacement({ side: 'below', maxHeight: Math.max(180, Math.min(440, spaceBelow)) });
|
|
395
|
+
}
|
|
396
|
+
setRadarOpen(next);
|
|
397
|
+
setRadarAttentionAt(0);
|
|
398
|
+
}, 'aria-expanded': radarOpen, 'aria-controls': radarId, 'aria-label': `${t('radar')} · ${radarSummary}`, title: radarSummary + (radarHighlight ? ' · ' + t('radarSoftHint') : '') }, model.minimized && !expanded ? (radarCount > 99 ? '99+' : radarCount || '☷') : `☷ ${radarSummary}`),
|
|
399
|
+
!panelMode && radarOpen && h('section', { ref: radarPanel, id: radarId, className: 'llm-radar-panel', 'data-placement': radarPlacement?.side || 'above', style: radarPlacement ? { maxHeight: radarPlacement.maxHeight } : undefined, 'aria-label': t('radar'), onKeyDown: (event) => { if (event.key === 'Escape') { event.stopPropagation(); closeRadar(); } }, onBlur: (event) => { if (!event.currentTarget.contains(event.relatedTarget) && event.relatedTarget !== radarTrigger.current) setRadarOpen(false); } },
|
|
366
400
|
h('header', { className: 'llm-head' }, h('strong', null, t('radar')), button(t('close'), closeRadar, { className: 'llm-btn llm-actions' })),
|
|
367
401
|
groups.waiting.length > 0 && renderGroup(t('radarWaiting'), groups.waiting),
|
|
368
402
|
groups.running.length > 0 && renderGroup(t('radarRunning'), groups.running),
|
|
@@ -370,6 +404,7 @@ window.__ModuleLoader__.load({
|
|
|
370
404
|
!radarCount && h('div', { className: 'llm-sub' }, t(catalog.phase === 'ready' ? 'radarEmpty' : 'connecting')),
|
|
371
405
|
(groups.running.length > 3 || groups.unread.length > 3 || groups.waiting.length > 3) && button(t(showAll ? 'showLess' : 'showAll'), () => setShowAll(!showAll)),
|
|
372
406
|
navigationError && h('div', { role: 'status', className: 'llm-sub' }, navigationError), h('div', { className: 'llm-summary' }, t('radarHint'))),
|
|
407
|
+
|
|
373
408
|
!expanded ? model.minimized ? h('button', {
|
|
374
409
|
type: 'button', className: 'llm-bubble' + (alertShake || radarShake ? ' llm-alert-shake' : ''), key: `bubble-${Math.max(attentionAt, radarAttentionAt)}`, onClick: () => update({ minimized: false }),
|
|
375
410
|
'aria-label': `${t('restore')} · ${formatTime(remaining)} · ${model.notice ? t(model.notice) : t(statusKey)}`,
|
|
@@ -378,7 +413,7 @@ window.__ModuleLoader__.load({
|
|
|
378
413
|
h('span', { className: 'llm-bubble-time' }, formatTime(remaining)),
|
|
379
414
|
alertActive && h('span', { className: 'llm-bubble-notice', 'aria-hidden': true }, '!'))
|
|
380
415
|
: h('div', { className: 'llm-float-widget' + (alertShake || radarShake ? ' llm-alert-shake' : ''), key: `widget-${Math.max(attentionAt, radarAttentionAt)}` },
|
|
381
|
-
h('div', { className: 'llm-photo-stage'
|
|
416
|
+
h('div', { className: 'llm-photo-stage' },
|
|
382
417
|
h('button', { type: 'button', className: 'llm-photo llm-photo-clean', onClick: () => setOpen(true), 'aria-label': t('view'), 'aria-expanded': false },
|
|
383
418
|
h('img', { src: asset(currentScene.name), alt: t('title') }),
|
|
384
419
|
h('span', { className: 'llm-photo-brand' }, t('title'))),
|
|
@@ -401,29 +436,35 @@ window.__ModuleLoader__.load({
|
|
|
401
436
|
h('div', { className: 'llm-sub', style: { marginTop: 10 } }, `${t('elapsed')} · ${formatTime(total - remaining)}`)),
|
|
402
437
|
h('details', { className: 'llm-disclosure' }, h('summary', null, `${t('reminderSettings')} · ${t(model.reminders && !todayPaused ? 'on' : 'off')}`),
|
|
403
438
|
h('div', { className: 'llm-settings' }, button(t(model.reminders && !todayPaused ? 'on' : 'off'), () => setReminders(!model.reminders || todayPaused), { role: 'switch', 'aria-checked': model.reminders && !todayPaused })),
|
|
404
|
-
h('div', { className: 'llm-settings' }, button(t(model.sound ? 'soundOn' : 'soundOff'), () => { update({ sound:
|
|
439
|
+
h('div', { className: 'llm-settings' }, button(t(model.sound ? 'soundOn' : 'soundOff'), () => { const next = !model.sound; update({ sound: next }); if (next) playChime(); }, { role: 'switch', 'aria-checked': model.sound })),
|
|
405
440
|
h('div', { className: 'llm-summary' }, t('reminderEvery')),
|
|
406
|
-
button(t('testReminder'), () => { const now = Date.now(); update({ now, previewAt: now, previewUntil: now + 8000 }, false); setOpen(false); if (panelMode) layout.selectPanel(null); }),
|
|
441
|
+
button(t('testReminder'), () => { const now = Date.now(); update({ now, previewAt: now, previewUntil: now + 8000 }, false); if (state.sound) playChime(); setOpen(false); if (panelMode) layout.selectPanel(null); }),
|
|
407
442
|
h('div', { className: 'llm-summary' }, reminderLabel(model))),
|
|
408
443
|
model.notice && h('div', { className: 'llm-notice', role: 'status' }, t(model.notice), h('div', { className: 'llm-controls' }, button(t('got'), () => update({ notice: '' })), model.status === 'running' && button(t('snooze'), () => update({ notice: '', nextReminder: Date.now() + 10 * 60000 })), button(t('today'), () => update({ notice: '', pausedDate: new Date().toDateString() })))),
|
|
409
444
|
h('details', { className: 'llm-disclosure' }, h('summary', null, `${t('radar')} · ${radarSummary}`),
|
|
410
|
-
|
|
445
|
+
groups.waiting.length > 0 || groups.running.length > 0 || groups.unread.length > 0
|
|
446
|
+
? h(React.Fragment, null, renderGroup(t('radarWaiting'), groups.waiting), renderGroup(t('radarRunning'), groups.running), renderGroup(t('radarUnread'), groups.unread))
|
|
447
|
+
: h('div', { className: 'llm-sub' }, t(catalog.phase === 'ready' ? 'radarEmpty' : 'connecting')),
|
|
411
448
|
!radarCount && h('div', { className: 'llm-sub' }, t('radarEmpty')),
|
|
412
449
|
(groups.running.length > 3 || groups.unread.length > 3 || groups.waiting.length > 3) && button(t(showAll ? 'showLess' : 'showAll'), () => setShowAll(!showAll)),
|
|
413
450
|
navigationError && h('div', { role: 'status' }, navigationError)),
|
|
414
|
-
h('details', { className: 'llm-disclosure' },
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
451
|
+
h('details', { className: 'llm-disclosure' },
|
|
452
|
+
h('summary', null, t('appearance')),
|
|
453
|
+
h('div', { className: 'llm-settings' }, scenes.map((scene, index) => button(t(scene.key), () => update({ scene: index }), { key: scene.key, 'aria-pressed': model.scene === index }))),
|
|
454
|
+
h('div', { className: 'llm-settings' }, button(`${t('background')} · ${t(model.bgOn ? 'on' : 'off')}`, () => update({ bgOn: !model.bgOn }), { role: 'switch', 'aria-checked': model.bgOn })),
|
|
455
|
+
model.bgOn && h('div', { className: 'llm-sliders' },
|
|
456
|
+
h('div', { className: 'llm-sub' }, t('backgroundAdjust')),
|
|
457
|
+
h('label', null, t('brightness'), h('input', { type: 'range', min: 70, max: 180, value: model.brightness, onChange: (event) => update({ brightness: Number(event.target.value) }) }), h('output', null, `${model.brightness}%`)),
|
|
458
|
+
h('label', null, t('intensity'), h('input', { type: 'range', min: 15, max: 75, value: model.intensity, onChange: (event) => update({ intensity: Number(event.target.value) }) }), h('output', null, `${model.intensity}%`))),
|
|
459
|
+
h('div', { className: 'llm-settings' }, button(t(model.readingProtection ? 'workMode' : 'scenicMode'), () => update({ readingProtection: !model.readingProtection }), { role: 'switch', 'aria-checked': model.readingProtection, 'aria-label': t('readingMode') })),
|
|
460
|
+
h('div', { className: 'llm-settings' }, button(`${t('drift')} · ${t(model.drift ? 'on' : 'off')}`, () => update({ drift: !model.drift }), { role: 'switch', 'aria-checked': model.drift })),
|
|
461
|
+
),
|
|
421
462
|
h('details', { className: 'llm-disclosure' }, h('summary', null, weatherText(model)),
|
|
422
463
|
h('div', { className: 'llm-summary' }, `${t('stale')}: ${model.weather?.time?.replace('T', ' ') || '—'}`),
|
|
423
464
|
h('div', { className: 'llm-controls' }, button(t('nanjing'), () => useNanjing()), button(t(model.locating ? 'locating' : 'locate'), locateWeather, { disabled: model.locating }), button(t('refresh'), () => void loadWeather())),
|
|
424
465
|
model.locationError && h('div', { className: 'llm-sub', role: 'status' }, t('locationError')),
|
|
425
466
|
h('div', { className: 'llm-sub' }, t('privacy')),
|
|
426
|
-
h('a', { href: 'https://open-meteo.com/', target: '_blank', rel: 'noopener noreferrer', className: 'llm-sub' }, 'Weather: Open-Meteo · CC BY 4.0'))));
|
|
467
|
+
h('a', { href: 'https://open-meteo.com/', target: '_blank', rel: 'noopener noreferrer', className: 'llm-sub' }, 'Weather: Open-Meteo · CC BY 4.0')))));
|
|
427
468
|
}
|
|
428
469
|
function Icon({ size = 24 }) { return h('img', { src: asset(scenes[0].name), alt: '', width: size, height: size, style: { display: 'block', borderRadius: '50%', objectFit: 'cover', objectPosition: '73% 80%' } }); }
|
|
429
470
|
return {
|
package/package.json
CHANGED