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/lib/state.js CHANGED
@@ -301,6 +301,76 @@ export async function fetchDeepSeekBalance(options = {}) {
301
301
  }
302
302
  }
303
303
 
304
+ /** 获取某个坐标的当前天气;只返回公开天气字段,不含用户位置标识。 */
305
+ const weatherCache = new Map()
306
+ export async function fetchWeather(options = {}) {
307
+ const lat = Number(options.lat)
308
+ const lon = Number(options.lon)
309
+ if (!Number.isFinite(lat) || !Number.isFinite(lon) || lat < -90 || lat > 90 || lon < -180 || lon > 180) {
310
+ return { ok: false, error: '无效的坐标' }
311
+ }
312
+ const key = `${lat.toFixed(2)},${lon.toFixed(2)}`
313
+ const cached = weatherCache.get(key)
314
+ if (cached && Date.now() - cached.at < 10 * 60 * 1000) return cached.payload
315
+ const url = `https://api.open-meteo.com/v1/forecast?latitude=${encodeURIComponent(lat)}&longitude=${encodeURIComponent(lon)}&current=temperature_2m,weather_code,wind_speed_10m,is_day&timezone=auto`
316
+ try {
317
+ const response = await fetch(url, { signal: AbortSignal.timeout(10_000) })
318
+ if (!response.ok) throw new Error('weather HTTP ' + response.status)
319
+ const body = await response.json()
320
+ const current = body?.current
321
+ if (!current) throw new Error('weather payload empty')
322
+ const payload = {
323
+ ok: true,
324
+ temperature: Number(current.temperature_2m),
325
+ code: Number(current.weather_code),
326
+ wind: Number(current.wind_speed_10m),
327
+ is_day: Number(current.is_day) === 1,
328
+ timezone: body.timezone ?? '',
329
+ updated_at: Date.now(),
330
+ }
331
+ weatherCache.set(key, { at: Date.now(), payload })
332
+ return payload
333
+ } catch (error) {
334
+ const payload = { ok: false, error: '天气请求失败:' + String(error?.message ?? error) }
335
+ weatherCache.set(key, { at: Date.now(), payload })
336
+ return payload
337
+ }
338
+ }
339
+
340
+ /** 城市名 → 坐标;用于定位不可用时的天气回退。 */
341
+ const geocodeCache = new Map()
342
+ export async function fetchGeocode(options = {}) {
343
+ const name = String(options.name ?? '').trim()
344
+ if (!name) return { ok: false, error: '请输入城市名' }
345
+ if (name.length > 80) return { ok: false, error: '城市名过长' }
346
+ const cached = geocodeCache.get(name)
347
+ if (cached && Date.now() - cached.at < 24 * 3600 * 1000) return cached.payload
348
+ const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(name)}&count=1&language=zh&format=json`
349
+ try {
350
+ const response = await fetch(url, { signal: AbortSignal.timeout(10_000) })
351
+ if (!response.ok) throw new Error('geocode HTTP ' + response.status)
352
+ const body = await response.json()
353
+ const hit = body?.results?.[0]
354
+ if (!hit) {
355
+ const payload = { ok: false, error: '没有找到这个城市,换个写法试试' }
356
+ geocodeCache.set(name, { at: Date.now(), payload })
357
+ return payload
358
+ }
359
+ const payload = {
360
+ ok: true,
361
+ name: hit.name,
362
+ admin1: hit.admin1 ?? '',
363
+ country: hit.country ?? '',
364
+ lat: Number(hit.latitude),
365
+ lon: Number(hit.longitude),
366
+ }
367
+ geocodeCache.set(name, { at: Date.now(), payload })
368
+ return payload
369
+ } catch (error) {
370
+ return { ok: false, error: '城市查询失败:' + String(error?.message ?? error) }
371
+ }
372
+ }
373
+
304
374
  export function createNightShiftStore(options = {}) {
305
375
  const sessionsDir = options.sessionsDir ?? join(process.env.DSH_HOME || join(homedir(), '.dsh'), 'sessions')
306
376
  const maxAgents = Number(options.maxAgents ?? 8)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agent-office",
3
- "version": "4.6.0",
3
+ "version": "4.6.2",
4
4
  "description": "实时 3D 办公室可视化 DSH 会话;Catora 发布后拓展笔记、思维导图、私人知识库与 RAG 检索。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
Binary file
@@ -29,6 +29,8 @@
29
29
  --scroll:rgba(255,255,255,.2);
30
30
  --scroll-hi:rgba(255,255,255,.32);
31
31
  --label:rgba(8,11,15,.82);
32
+ --tag-bg:rgba(8,16,24,.48);
33
+ --tag-line:rgba(255,255,255,.24);
32
34
  --hint:rgba(12,15,20,.66);
33
35
  --chip:rgba(12,18,22,.86);
34
36
  --focus-line:rgba(61,225,198,.3);
@@ -79,6 +81,8 @@
79
81
  --scroll:rgba(16,40,54,.2);
80
82
  --scroll-hi:rgba(16,40,54,.32);
81
83
  --label:rgba(250,252,253,.95);
84
+ --tag-bg:rgba(250,253,255,.44);
85
+ --tag-line:rgba(16,40,54,.2);
82
86
  --hint:rgba(250,252,253,.84);
83
87
  --chip:rgba(252,253,254,.92);
84
88
  --focus-line:rgba(8,117,101,.3);
@@ -249,7 +253,43 @@
249
253
  .lbl .badge{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:5px;vertical-align:1px}
250
254
  .floorlbl{position:absolute;transform:translate(-50%,-50%);font-family:var(--mono);font-size:11px;letter-spacing:2px;color:rgba(138,148,163,.9);padding:3px 9px;border:1px solid var(--line-2);border-radius:999px;background:var(--label);white-space:nowrap}
251
255
  .floorlbl b{color:var(--ink);font-weight:600;margin-left:6px;letter-spacing:0}
252
- .officeTag{font-family:var(--mono);font-size:9px;letter-spacing:1.4px;color:var(--dim);padding:2px 7px;border:1px solid var(--line-2);border-radius:999px;background:var(--label);white-space:nowrap;backdrop-filter:blur(6px)}
256
+ .mcpTag{
257
+ display:inline-flex;align-items:center;gap:7px;font-family:var(--mono);font-size:11.5px;font-weight:600;
258
+ letter-spacing:1px;color:var(--ink);padding:0;border:0;border-radius:0;background:none;white-space:nowrap;
259
+ pointer-events:none;text-shadow:0 1px 3px rgba(0,0,0,.55);
260
+ }
261
+ body.day .mcpTag{text-shadow:0 1px 2px rgba(255,255,255,.6)}
262
+ .mcpTag .x{color:#4ea8ff;font-size:12px;font-weight:700;line-height:1}
263
+
264
+ .officeTag{
265
+ font-family:var(--mono);font-size:9px;font-weight:600;letter-spacing:1.2px;color:var(--ink);padding:2px 8px;
266
+ border:1px solid rgba(255,255,255,.62);border-radius:999px;background:var(--tag-bg);white-space:nowrap;
267
+ backdrop-filter:blur(10px) saturate(1.2);-webkit-backdrop-filter:blur(10px) saturate(1.2);
268
+ box-shadow:inset 0 1px 0 rgba(255,255,255,.3),0 4px 14px rgba(10,22,34,.12);
269
+ transition:background-color .4s,border-color .4s,color .4s,box-shadow .4s;
270
+ }
271
+ /* ---------- 建造入场 ---------- */
272
+ body.intro #top,body.intro #left,body.intro #right,body.intro #feed,body.intro #credit,body.intro #hint,body.intro .lbl,body.intro .mcpTag{opacity:0;pointer-events:none}
273
+ .lbl,.mcpTag{transition:opacity .45s ease}
274
+ #top,#left,#right,#feed,#credit,#hint{transition:opacity .65s cubic-bezier(.2,0,0,1),transform .65s cubic-bezier(.2,0,0,1)}
275
+ body.intro #top{transform:translateY(-10px)}
276
+ body.intro #left{transform:translateX(-14px)}
277
+ body.intro #right{transform:translateX(14px)}
278
+ body.intro #feed{transform:translateY(14px)}
279
+ #intro{position:absolute;inset:0;z-index:30;display:grid;place-items:center;background:radial-gradient(ellipse at 50% 45%,rgba(5,11,18,.26),rgba(3,7,11,.84));transition:opacity .55s ease,visibility .55s ease}
280
+ #intro.done{opacity:0;visibility:hidden;pointer-events:none}
281
+ .intro__loader{display:grid;justify-items:center;gap:14px;color:var(--ink)}
282
+ .intro__spinner{position:relative;width:76px;height:76px;border-radius:50%;filter:drop-shadow(0 0 18px rgba(61,225,198,.2))}
283
+ .intro__spinner i{position:absolute;inset:0;border-radius:50%;border:2px solid transparent;border-top-color:var(--acc);border-right-color:color-mix(in srgb,var(--acc) 32%,transparent);animation:intro-spin 1.05s linear infinite}
284
+ .intro__spinner i:nth-child(2){inset:9px;border-top-color:#5b8cff;border-left-color:rgba(91,140,255,.34);animation-duration:.78s;animation-direction:reverse}
285
+ .intro__spinner i:nth-child(3){inset:18px;border-top-color:rgba(233,238,245,.78);border-bottom-color:rgba(233,238,245,.18);animation-duration:.54s}
286
+ .intro__spinner::after{content:"";position:absolute;inset:31px;border-radius:50%;background:radial-gradient(circle,var(--acc) 0,rgba(61,225,198,.35) 38%,transparent 70%);box-shadow:0 0 20px rgba(61,225,198,.55);animation:intro-pulse 1.6s ease-in-out infinite}
287
+ .intro__label{font-family:var(--mono);font-size:9.5px;letter-spacing:3.2px;color:var(--dim);margin-top:2px}
288
+ .intro__hint{font-family:var(--mono);font-size:9px;letter-spacing:1.2px;color:var(--dim-2);opacity:.7}
289
+ @keyframes intro-spin{to{transform:rotate(360deg)}}
290
+ @keyframes intro-pulse{0%,100%{transform:scale(.82);opacity:.55}50%{transform:scale(1.1);opacity:.95}}
291
+ @media (prefers-reduced-motion:reduce){.intro__spinner i,.intro__spinner::after{animation:none}}
292
+ @media (prefers-reduced-motion:reduce){#intro{transition:none}}
253
293
  @media (max-width:1180px){
254
294
  #left{width:280px}
255
295
  #right{width:300px}
@@ -298,7 +338,7 @@
298
338
  }
299
339
  </style>
300
340
  </head>
301
- <body>
341
+ <body class="intro">
302
342
  <div id="app">
303
343
  <div id="stage"></div>
304
344
  <div id="labels"></div>
@@ -360,6 +400,14 @@
360
400
 
361
401
  <div id="hint">左键点楼层聚焦 · 右键返回全局 · 拖动旋转 · 滚轮缩放</div>
362
402
  <div id="offline">正在连接 DSH 会话数据…</div>
403
+
404
+ <div id="intro" role="status" aria-live="polite">
405
+ <div class="intro__loader">
406
+ <div class="intro__spinner" aria-hidden="true"><i></i><i></i><i></i></div>
407
+ <div class="intro__label">BUILDING OFFICE</div>
408
+ <div class="intro__hint">点击跳过</div>
409
+ </div>
410
+ </div>
363
411
  </div>
364
412
  <script>/*__BUNDLE__*/</script>
365
413
  </body>