dsh-agent-office 4.6.1 → 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/lib/client.js CHANGED
@@ -76,6 +76,8 @@ window.__ModuleLoader__.load({
76
76
  } catch { return false }
77
77
  })
78
78
  const [weatherBusy, setWeatherBusy] = useState(false)
79
+ const [city, setCity] = useState('')
80
+ const [cityBusy, setCityBusy] = useState(false)
79
81
 
80
82
  const choose = async event => {
81
83
  const file = event.target.files?.[0]
@@ -118,33 +120,74 @@ window.__ModuleLoader__.load({
118
120
 
119
121
  const askWeather = () => {
120
122
  if (!navigator.geolocation) {
121
- setMessage('当前环境不支持定位,无法获取本地天气')
123
+ setMessage('当前环境不支持系统定位,请在下面手动输入城市')
122
124
  return
123
125
  }
124
126
  setWeatherBusy(true)
125
- setMessage('请在系统弹窗里允许定位…')
126
- navigator.geolocation.getCurrentPosition(
127
- position => {
128
- try {
129
- localStorage.setItem(WEATHER_KEY, JSON.stringify({
130
- lat: position.coords.latitude,
131
- lon: position.coords.longitude,
132
- at: Date.now(),
133
- }))
134
- setWeatherOn(true)
135
- setMessage('已开启本地天气,面板会按所在城市显示天气效果')
136
- } catch {
137
- setMessage('位置保存失败')
138
- }
139
- setWeatherBusy(false)
140
- },
141
- error => {
142
- setWeatherBusy(false)
143
- setWeatherOn(false)
144
- setMessage('没有拿到定位权限:' + String(error?.message ?? '未授权'))
145
- },
146
- { enableHighAccuracy: false, timeout: 12000, maximumAge: 10 * 60 * 1000 },
147
- )
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
+ }
148
191
  }
149
192
 
150
193
  const closeWeather = () => {
@@ -210,7 +253,26 @@ window.__ModuleLoader__.load({
210
253
  children: weatherBusy ? '定位中…' : weatherOn ? '关闭天气' : '允许定位',
211
254
  }),
212
255
  ] }),
213
- jsx('p', { style: { ...textStyle, margin: '8px 0 0', fontSize: 12 }, children: weatherOn ? '已允许定位。面板会获取所在城市天气,显示晴天、云、雨、雪或风。位置只保存在本地。' : '允许定位后,面板会获取所在城市天气,显示晴天、云、雨、雪或风;位置只保存在本地,不上传服务器。' }),
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: '系统定位超时或没有弹窗时,直接输入城市即可。位置只保存在本地。' }),
214
276
  ],
215
277
  }),
216
278
  message ? jsx('p', { style: { ...textStyle, marginTop: 10, marginBottom: 0 }, children: message }) : null,
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, fetchWeather } 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,21 @@ 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
+
74
89
  ctx.effect(() => ctx.webServer.register({
75
90
  kind: 'exact',
76
91
  path: '/agent-office/api/weather',