dsh-agent-office 4.6.0 → 4.6.2
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 +3 -1
- package/lib/client.js +126 -2
- package/lib/index.js +34 -1
- package/lib/panel.html +239 -191
- package/lib/state.js +70 -0
- package/package.json +1 -1
- package/panel-src/assets/exp-uneven.png +0 -0
- package/panel-src/index.html +50 -2
- package/panel-src/main.js +576 -72
package/README.md
CHANGED
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
- 📡 **真实数据**:状态、今日执行次数、本周/今日 **Token 用量** 全部来自本机会话日志,数字不编造;
|
|
34
34
|
- 🖱️ **可交互**:拖动旋转、滚轮缩放、点击立方体或名字看详情、详情卡 ×/Esc 关闭;
|
|
35
35
|
- 🧩 **原生插件**:bundle 插件,装完即用(宿主路由 + 浏览器半侧悬浮按钮),无外部依赖;
|
|
36
|
-
- 🔒
|
|
36
|
+
- 🔒 **默认只读本机**:只读 `$DSH_HOME/sessions`;只有你在设置里主动开启「本地天气」后,才会把坐标发给 Open-Meteo 获取天气,位置本身只保存在本地。
|
|
37
|
+
- 🏗️ **搭建入场**:每次打开面板先播放 0→1 的建造过程,楼层、工位、猫猫和 MCP 链路逐步落位。
|
|
38
|
+
- 🌦️ **本地天气**:设置里允许定位后,按所在城市显示晴/阴云/雨/雪/风;不开定位则不启用天气效果。
|
|
37
39
|
|
|
38
40
|
## 安装
|
|
39
41
|
|
package/lib/client.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
const CUSTOM_FACE_KEY = 'agent-office-custom-face'
|
|
10
10
|
const GLASS_KEY = 'agent-office-ui-opacity'
|
|
11
|
+
const WEATHER_KEY = 'agent-office-weather-location'
|
|
11
12
|
const CAT_BUTTON = 'data:image/png;base64,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'
|
|
12
13
|
|
|
13
14
|
window.__ModuleLoader__.load({
|
|
@@ -68,6 +69,15 @@ window.__ModuleLoader__.load({
|
|
|
68
69
|
const [message, setMessage] = useState('')
|
|
69
70
|
const [busy, setBusy] = useState(false)
|
|
70
71
|
const [glass, setGlass] = useState(readGlass())
|
|
72
|
+
const [weatherOn, setWeatherOn] = useState(() => {
|
|
73
|
+
try {
|
|
74
|
+
const value = JSON.parse(localStorage.getItem(WEATHER_KEY) ?? 'null')
|
|
75
|
+
return Number.isFinite(value?.lat) && Number.isFinite(value?.lon)
|
|
76
|
+
} catch { return false }
|
|
77
|
+
})
|
|
78
|
+
const [weatherBusy, setWeatherBusy] = useState(false)
|
|
79
|
+
const [city, setCity] = useState('')
|
|
80
|
+
const [cityBusy, setCityBusy] = useState(false)
|
|
71
81
|
|
|
72
82
|
const choose = async event => {
|
|
73
83
|
const file = event.target.files?.[0]
|
|
@@ -108,6 +118,84 @@ window.__ModuleLoader__.load({
|
|
|
108
118
|
setGlass(80)
|
|
109
119
|
}
|
|
110
120
|
|
|
121
|
+
const askWeather = () => {
|
|
122
|
+
if (!navigator.geolocation) {
|
|
123
|
+
setMessage('当前环境不支持系统定位,请在下面手动输入城市')
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
setWeatherBusy(true)
|
|
127
|
+
setMessage('正在请求定位权限;如果没有弹窗,请在下面手动输入城市…')
|
|
128
|
+
let settled = false
|
|
129
|
+
const finish = (ok, text) => {
|
|
130
|
+
if (settled) return
|
|
131
|
+
settled = true
|
|
132
|
+
clearTimeout(timer)
|
|
133
|
+
setWeatherBusy(false)
|
|
134
|
+
setWeatherOn(ok)
|
|
135
|
+
setMessage(text)
|
|
136
|
+
}
|
|
137
|
+
const timer = setTimeout(() => {
|
|
138
|
+
finish(false, '系统定位没有响应。请在下面手动输入城市,例如:上海')
|
|
139
|
+
}, 12000)
|
|
140
|
+
try {
|
|
141
|
+
navigator.geolocation.getCurrentPosition(
|
|
142
|
+
position => {
|
|
143
|
+
try {
|
|
144
|
+
localStorage.setItem(WEATHER_KEY, JSON.stringify({
|
|
145
|
+
lat: position.coords.latitude,
|
|
146
|
+
lon: position.coords.longitude,
|
|
147
|
+
at: Date.now(),
|
|
148
|
+
}))
|
|
149
|
+
finish(true, '已开启本地天气,面板会按所在城市显示天气效果')
|
|
150
|
+
} catch {
|
|
151
|
+
finish(false, '位置保存失败')
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
error => finish(false, '没有拿到定位权限:' + String(error?.message ?? '未授权') + '。可以改用手动输入城市。'),
|
|
155
|
+
{ enableHighAccuracy: false, timeout: 10000, maximumAge: 10 * 60 * 1000 },
|
|
156
|
+
)
|
|
157
|
+
} catch (error) {
|
|
158
|
+
finish(false, '定位启动失败:' + String(error?.message ?? error) + '。请手动输入城市。')
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const useCity = async () => {
|
|
163
|
+
const name = city.trim()
|
|
164
|
+
if (!name) {
|
|
165
|
+
setMessage('先输入城市名,例如:上海')
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
setCityBusy(true)
|
|
169
|
+
setMessage('正在查找「' + name + '」…')
|
|
170
|
+
try {
|
|
171
|
+
const response = await fetch('/agent-office/api/geocode?name=' + encodeURIComponent(name), { cache: 'no-store' })
|
|
172
|
+
const data = await response.json()
|
|
173
|
+
if (!data?.ok) {
|
|
174
|
+
setMessage(data?.error ? String(data.error) : '没有找到这个城市')
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
localStorage.setItem(WEATHER_KEY, JSON.stringify({
|
|
178
|
+
lat: data.lat,
|
|
179
|
+
lon: data.lon,
|
|
180
|
+
name: [data.name, data.admin1].filter(Boolean).join(' · '),
|
|
181
|
+
at: Date.now(),
|
|
182
|
+
}))
|
|
183
|
+
setWeatherOn(true)
|
|
184
|
+
setMessage('已使用「' + [data.name, data.admin1].filter(Boolean).join(' · ') + '」的天气')
|
|
185
|
+
setCity('')
|
|
186
|
+
} catch (error) {
|
|
187
|
+
setMessage('城市查询失败:' + String(error?.message ?? error))
|
|
188
|
+
} finally {
|
|
189
|
+
setCityBusy(false)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const closeWeather = () => {
|
|
194
|
+
try { localStorage.removeItem(WEATHER_KEY) } catch {}
|
|
195
|
+
setWeatherOn(false)
|
|
196
|
+
setMessage('已关闭本地天气')
|
|
197
|
+
}
|
|
198
|
+
|
|
111
199
|
const titleStyle = { fontSize: 15, fontWeight: 600, margin: '0 0 6px' }
|
|
112
200
|
const textStyle = { fontSize: 13, lineHeight: 1.6, margin: '0 0 10px', opacity: 0.72 }
|
|
113
201
|
const buttonStyle = { height: 32, padding: '0 14px', borderRadius: 8, border: '1px solid rgba(127,127,127,.35)', background: 'transparent', color: 'inherit', cursor: 'pointer', fontSize: 13 }
|
|
@@ -151,6 +239,42 @@ window.__ModuleLoader__.load({
|
|
|
151
239
|
] }),
|
|
152
240
|
],
|
|
153
241
|
}),
|
|
242
|
+
jsx('div', {
|
|
243
|
+
style: { marginTop: 14, padding: '14px 16px', border: '1px solid rgba(127,127,127,.28)', borderRadius: 12, background: 'rgba(127,127,127,.06)' },
|
|
244
|
+
children: [
|
|
245
|
+
jsx('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }, children: [
|
|
246
|
+
jsx('span', { style: { fontSize: 13, fontWeight: 600 }, children: '本地天气' }),
|
|
247
|
+
jsx('button', {
|
|
248
|
+
type: 'button',
|
|
249
|
+
className: 'ns-setting-btn',
|
|
250
|
+
style: { ...buttonStyle, height: 30, padding: '0 12px', flex: 'none' },
|
|
251
|
+
onClick: weatherOn ? closeWeather : askWeather,
|
|
252
|
+
disabled: weatherBusy,
|
|
253
|
+
children: weatherBusy ? '定位中…' : weatherOn ? '关闭天气' : '允许定位',
|
|
254
|
+
}),
|
|
255
|
+
] }),
|
|
256
|
+
jsx('p', { style: { ...textStyle, margin: '8px 0 0', fontSize: 12 }, children: weatherOn ? '天气已开启。面板会按所在城市显示晴天、云、雨、雪或风;位置只保存在本地。' : '允许定位后,面板会获取所在城市天气;如果系统定位没有反应,可以在下面直接输入城市。' }),
|
|
257
|
+
jsx('div', { style: { display: 'flex', gap: 8, marginTop: 10 }, children: [
|
|
258
|
+
jsx('input', {
|
|
259
|
+
type: 'text',
|
|
260
|
+
value: city,
|
|
261
|
+
placeholder: '手动输入城市,例如:上海',
|
|
262
|
+
onChange: event => setCity(event.target.value),
|
|
263
|
+
onKeyDown: event => { if (event.key === 'Enter') useCity() },
|
|
264
|
+
style: { flex: '1 1 0', minWidth: 0, height: 30, padding: '0 10px', borderRadius: 8, border: '1px solid rgba(127,127,127,.35)', background: 'transparent', color: 'inherit', fontSize: 13, outline: 'none' },
|
|
265
|
+
}),
|
|
266
|
+
jsx('button', {
|
|
267
|
+
type: 'button',
|
|
268
|
+
className: 'ns-setting-btn',
|
|
269
|
+
style: { ...buttonStyle, height: 30, padding: '0 12px', flex: 'none' },
|
|
270
|
+
onClick: useCity,
|
|
271
|
+
disabled: cityBusy,
|
|
272
|
+
children: cityBusy ? '查找中…' : '使用城市',
|
|
273
|
+
}),
|
|
274
|
+
] }),
|
|
275
|
+
jsx('p', { style: { ...textStyle, margin: '8px 0 0', fontSize: 11 }, children: '系统定位超时或没有弹窗时,直接输入城市即可。位置只保存在本地。' }),
|
|
276
|
+
],
|
|
277
|
+
}),
|
|
154
278
|
message ? jsx('p', { style: { ...textStyle, marginTop: 10, marginBottom: 0 }, children: message }) : null,
|
|
155
279
|
],
|
|
156
280
|
})
|
|
@@ -194,7 +318,7 @@ window.__ModuleLoader__.load({
|
|
|
194
318
|
<section class="dsh-ns-overlay" hidden>
|
|
195
319
|
<button type="button" class="dsh-ns-open">在新标签打开</button>
|
|
196
320
|
<button type="button" class="dsh-ns-close" aria-label="关闭">×</button>
|
|
197
|
-
<iframe title="Office 3D 面板"></iframe>
|
|
321
|
+
<iframe title="Office 3D 面板" allow="geolocation"></iframe>
|
|
198
322
|
</section>
|
|
199
323
|
`
|
|
200
324
|
document.body.append(host)
|
|
@@ -223,7 +347,7 @@ window.__ModuleLoader__.load({
|
|
|
223
347
|
if (event.key === 'Escape') close()
|
|
224
348
|
}
|
|
225
349
|
const open = () => {
|
|
226
|
-
|
|
350
|
+
frame.setAttribute('src', '/agent-office/?open=' + Date.now())
|
|
227
351
|
overlay.hidden = false
|
|
228
352
|
document.addEventListener('keydown', closedByEscape, true)
|
|
229
353
|
}
|
package/lib/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 不依赖 ntfy、不依赖外部进程;面板与 DSH 同源,直接轮询本机 API。
|
|
9
9
|
*/
|
|
10
10
|
import { readFile } from 'node:fs/promises'
|
|
11
|
-
import { createNightShiftStore, fetchDeepSeekBalance } from './state.js'
|
|
11
|
+
import { createNightShiftStore, fetchDeepSeekBalance, fetchWeather, fetchGeocode } from './state.js'
|
|
12
12
|
|
|
13
13
|
export const name = 'agent-office'
|
|
14
14
|
export const inject = ['webServer']
|
|
@@ -71,6 +71,39 @@ export function apply(ctx, config) {
|
|
|
71
71
|
},
|
|
72
72
|
}), 'agent-office: api')
|
|
73
73
|
|
|
74
|
+
ctx.effect(() => ctx.webServer.register({
|
|
75
|
+
kind: 'exact',
|
|
76
|
+
path: '/agent-office/api/geocode',
|
|
77
|
+
handler: async (req, res) => {
|
|
78
|
+
if (!hostTrusted(req)) return json(res, { ok: false, error: '不被信任的 Host' }, 403)
|
|
79
|
+
try {
|
|
80
|
+
const url = new URL(req.url ?? '/', 'http://localhost')
|
|
81
|
+
return json(res, await fetchGeocode({ name: url.searchParams.get('name') }))
|
|
82
|
+
} catch (error) {
|
|
83
|
+
ctx.logger.error(error instanceof Error ? error : new Error(String(error)))
|
|
84
|
+
return json(res, { ok: false, error: '城市查询暂时不可用' }, 500)
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
}), 'agent-office: geocode')
|
|
88
|
+
|
|
89
|
+
ctx.effect(() => ctx.webServer.register({
|
|
90
|
+
kind: 'exact',
|
|
91
|
+
path: '/agent-office/api/weather',
|
|
92
|
+
handler: async (req, res) => {
|
|
93
|
+
if (!hostTrusted(req)) return json(res, { ok: false, error: '不被信任的 Host' }, 403)
|
|
94
|
+
try {
|
|
95
|
+
const url = new URL(req.url ?? '/', 'http://localhost')
|
|
96
|
+
return json(res, await fetchWeather({
|
|
97
|
+
lat: url.searchParams.get('lat'),
|
|
98
|
+
lon: url.searchParams.get('lon'),
|
|
99
|
+
}))
|
|
100
|
+
} catch (error) {
|
|
101
|
+
ctx.logger.error(error instanceof Error ? error : new Error(String(error)))
|
|
102
|
+
return json(res, { ok: false, error: '天气暂时不可用' }, 500)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
}), 'agent-office: weather')
|
|
106
|
+
|
|
74
107
|
ctx.effect(() => ctx.webServer.register({
|
|
75
108
|
kind: 'exact',
|
|
76
109
|
path: '/agent-office/api/balance',
|