dsh-dock 0.11.5 → 0.11.6
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 +102 -102
- package/features/animation/host.js +5 -10
- package/features/animation/view.jsx +10 -10
- package/features/games/view.jsx +2 -2
- package/features/mobile-relay/host.js +13 -14
- package/features/modelconfig/host.js +4 -4
- package/features/notify/host.js +5 -10
- package/features/notify/view.jsx +1 -1
- package/features/tokenlog/host.js +11 -10
- package/features/tokenlog/view.jsx +16 -3
- package/features/visionproxy/host.js +2 -3
- package/index.js +60 -29
- package/package.json +8 -6
- package/src/client.jsx +16 -6
- package/src/host-core.js +148 -16
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
//
|
|
11
11
|
// 配置模型(schemastery schema 见 src/host-core.js DockConfig.animation):
|
|
12
12
|
// animationEnabled / effectMode / robotScale
|
|
13
|
-
import {
|
|
13
|
+
import { ANIMATION_MODES, sendJson, readBody, readDockRoot, mutateDockSection } from '../../src/host-core.js'
|
|
14
14
|
import { acquireTaskTracker } from '../../src/task-track.js'
|
|
15
15
|
|
|
16
|
-
// 默认配置(schema 默认值一致;settings
|
|
16
|
+
// 默认配置(schema 默认值一致;settings 未挂载时的兜底)
|
|
17
17
|
function defaultConfig() {
|
|
18
18
|
return {
|
|
19
19
|
animationEnabled: true,
|
|
@@ -22,12 +22,11 @@ function defaultConfig() {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// 读本地 Config 镜像里的 animation 段(resolved 值已含 schema 默认),异常时回退默认
|
|
26
26
|
function readConfig(ctx) {
|
|
27
27
|
const cfg = defaultConfig()
|
|
28
28
|
try {
|
|
29
|
-
const
|
|
30
|
-
const v = settings && typeof settings.get === 'function' ? settings.get(DOCK_NS) : null
|
|
29
|
+
const v = readDockRoot(ctx)
|
|
31
30
|
const a = v && typeof v === 'object' && v.animation && typeof v.animation === 'object' ? v.animation : null
|
|
32
31
|
if (a) {
|
|
33
32
|
for (const key of Object.keys(cfg)) {
|
|
@@ -86,12 +85,8 @@ export const feature = {
|
|
|
86
85
|
cfg.robotScale = Math.max(0.85, Math.min(2.2, Math.round(p.robotScale * 100) / 100))
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
const settings = ctx.get('settings')
|
|
90
|
-
if (!settings || typeof settings.mutate !== 'function') {
|
|
91
|
-
throw new Error('settings 服务不可用,配置无法持久化')
|
|
92
|
-
}
|
|
93
88
|
try {
|
|
94
|
-
await
|
|
89
|
+
await mutateDockSection(ctx, ['animation'], cfg)
|
|
95
90
|
} catch (e) {
|
|
96
91
|
const err = new Error('保存配置被拒绝:' + ((e && e.message) || String(e)))
|
|
97
92
|
err.statusCode = 400
|
|
@@ -1286,18 +1286,18 @@ function AnimationStat(props) {
|
|
|
1286
1286
|
// ---------- 样式(dkan- 前缀;全部走主题变量,暗/亮色自适应;动效时长 = 基准 / --dkan-speed) ----------
|
|
1287
1287
|
const css = [
|
|
1288
1288
|
// 顶部流光细线(3px,比首版更醒目)
|
|
1289
|
-
".dkan-line{position:fixed;top:0;left:0;right:0;height:3px;z-index:
|
|
1289
|
+
".dkan-line{position:fixed;top:0;left:0;right:0;height:3px;z-index:2;pointer-events:none;background:color-mix(in srgb,var(--dk-accent) 30%,transparent);}",
|
|
1290
1290
|
".dkan-line::after{content:\"\";position:absolute;top:0;bottom:0;left:-42%;width:42%;background:linear-gradient(90deg,transparent,var(--dk-accent) 60%,#fff);box-shadow:0 0 10px 1px color-mix(in srgb,var(--dk-accent) 70%,transparent);animation:dkan-flow calc(1.9s / var(--dkan-speed,1)) linear infinite;}",
|
|
1291
1291
|
"@keyframes dkan-flow{to{left:100%}}",
|
|
1292
1292
|
// 完成瞬间的一次性流光(成功绿 / 异常红),动画结束自动清场
|
|
1293
|
-
".dkan-done{position:fixed;top:0;left:0;right:0;height:3px;z-index:
|
|
1293
|
+
".dkan-done{position:fixed;top:0;left:0;right:0;height:3px;z-index:3;pointer-events:none;background:linear-gradient(90deg,transparent,var(--dsw-alias-state-success-primary,#34d399) 50%,#fff);background-size:50% 100%;background-repeat:no-repeat;animation:dkan-done 1.1s var(--ds-ease-in-out) forwards;}",
|
|
1294
1294
|
".dkan-done.err{background-image:linear-gradient(90deg,transparent,var(--dsw-alias-state-error-primary,#f87171) 50%,#fff);}",
|
|
1295
1295
|
"@keyframes dkan-done{0%{background-position:-60% 0;opacity:0}25%{opacity:1}100%{background-position:160% 0;opacity:0}}",
|
|
1296
1296
|
// 环屏巡航:光点沿屏幕边缘转一整圈
|
|
1297
|
-
".dkan-orbit{position:fixed;top:0;left:0;width:12px;height:12px;z-index:
|
|
1297
|
+
".dkan-orbit{position:fixed;top:0;left:0;width:12px;height:12px;z-index:2;pointer-events:none;border-radius:50%;background:var(--dk-accent);box-shadow:0 0 14px 4px color-mix(in srgb,var(--dk-accent) 60%,transparent),0 0 30px 8px color-mix(in srgb,var(--dk-accent) 25%,transparent);animation:dkan-orbit calc(14s / var(--dkan-speed,1)) linear infinite;}",
|
|
1298
1298
|
"@keyframes dkan-orbit{0%{top:0;left:0}25%{top:0;left:calc(100vw - 12px)}50%{top:calc(100vh - 12px);left:calc(100vw - 12px)}75%{top:calc(100vh - 12px);left:0}100%{top:0;left:0}}",
|
|
1299
1299
|
// ===== 氛围动效 =====
|
|
1300
|
-
".dkan-amb{position:fixed;inset:0;z-index:
|
|
1300
|
+
".dkan-amb{position:fixed;inset:0;z-index:1;pointer-events:none;overflow:hidden;}",
|
|
1301
1301
|
// pointer-events 不继承:全屏装饰层的子粒子(星点/流星/极光/火花/彩带)必须各自透明于点击
|
|
1302
1302
|
".dkan-amb *,.dkan-fxwrap *{pointer-events:none;}",
|
|
1303
1303
|
// 代码雨:字符列缓落(提亮加大,速度随吞吐)
|
|
@@ -1327,15 +1327,15 @@ const css = [
|
|
|
1327
1327
|
".dkan-space-runner{position:fixed;left:0;top:0;width:108px;height:56px;opacity:0;animation-duration:calc(10.8s / var(--dkan-speed,1));animation-timing-function:linear;animation-iteration-count:infinite;animation-delay:var(--dkan-run-delay,0s);will-change:transform,opacity;contain:layout paint style;backface-visibility:hidden;}.dkan-space-runner .dkan-freighter{left:0;top:0;transform:translateZ(0) scale(.52);transform-origin:top left;filter:none}.dkan-space-runner .dkan-freighter-engine i{animation:none;opacity:.76;box-shadow:-6px 0 8px #22d3ee}.dkan-space-runner .dkan-freighter-nose{filter:none}.dkan-space-runner.route-0{animation-name:dkan-space-route-a}.dkan-space-runner.route-1{animation-name:dkan-space-route-b}.dkan-space-runner.route-2{animation-name:dkan-space-route-c}",
|
|
1328
1328
|
"@keyframes dkan-space-route-a{0%{opacity:0;transform:translate3d(var(--dkan-alpha-x),var(--dkan-alpha-y),0) rotate(151deg)}7%,86%{opacity:.9}100%{opacity:0;transform:translate3d(var(--dkan-beta-x),var(--dkan-beta-y),0) rotate(151deg)}}@keyframes dkan-space-route-b{0%{opacity:0;transform:translate3d(var(--dkan-beta-x),var(--dkan-beta-y),0) rotate(-90deg)}7%,86%{opacity:.9}100%{opacity:0;transform:translate3d(var(--dkan-gamma-x),var(--dkan-gamma-y),0) rotate(-90deg)}}@keyframes dkan-space-route-c{0%{opacity:0;transform:translate3d(var(--dkan-gamma-x),var(--dkan-gamma-y),0) rotate(-18deg)}7%,86%{opacity:.9}100%{opacity:0;transform:translate3d(var(--dkan-alpha-x),var(--dkan-alpha-y),0) rotate(-18deg)}}",
|
|
1329
1329
|
// 完成归航:一艘货运旗舰从航线飞到真实会话输入框上方,货舱逐格表达实际输出量。
|
|
1330
|
-
".dkan-delivery{position:fixed;left:0;top:0;width:210px;height:88px;z-index:
|
|
1330
|
+
".dkan-delivery{position:fixed;left:0;top:0;width:210px;height:88px;z-index:6;pointer-events:none;will-change:transform,opacity;contain:layout paint style;isolation:isolate;animation:dkan-delivery-flight var(--dkan-delivery-duration,8600ms) cubic-bezier(.22,.61,.36,1) forwards;}",
|
|
1331
1331
|
"@keyframes dkan-delivery-flight{0%{opacity:0;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(.5) rotate(-12deg)}6%{opacity:1;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(.56) rotate(-10deg)}100%{opacity:1;transform:translate3d(var(--dkan-to-x),var(--dkan-to-y),0) scale(1) rotate(0)}}",
|
|
1332
1332
|
".dkan-delivery-trail{position:absolute;left:-132px;top:26px;width:154px;height:4px;border-radius:999px;background:linear-gradient(90deg,transparent,#34d399 48%,#d9f99d 78%,transparent);opacity:.9;animation:dkan-delivery-trail var(--dkan-delivery-duration,8600ms) linear forwards;}@keyframes dkan-delivery-trail{0%,82%{opacity:.9}100%{opacity:0}}.dkan-freighter{position:absolute;left:10px;top:10px;width:176px;height:45px;display:block;filter:drop-shadow(0 0 9px color-mix(in srgb,#7dd3fc 62%,transparent));}.dkan-delivery .dkan-freighter,.dkan-departure .dkan-freighter{filter:none}.dkan-delivery .dkan-freighter-engine i,.dkan-departure .dkan-freighter-engine i{animation:none;opacity:.76;box-shadow:-6px 0 8px #22d3ee}.dkan-delivery .dkan-freighter-nose,.dkan-departure .dkan-freighter-nose{filter:none}.dkan-freighter-hull{position:absolute;z-index:3;left:21px;top:7px;width:126px;height:31px;background:linear-gradient(150deg,#f8fbff 0%,#9cc8ff 30%,#385d91 72%,#1d3150);clip-path:polygon(0 30%,43% 0,83% 17%,100% 50%,83% 83%,43% 100%,0 70%,12% 50%);box-shadow:inset 0 1px 0 rgb(255 255 255 / .7),inset -7px -5px 9px rgb(8 24 49 / .38);}.dkan-freighter-nose{position:absolute;z-index:4;right:10px;top:16px;width:28px;height:14px;background:linear-gradient(90deg,#a9d1ff,#e8f5ff);clip-path:polygon(0 0,100% 50%,0 100%,22% 50%);filter:drop-shadow(5px 0 5px color-mix(in srgb,#93c5fd 62%,transparent));}.dkan-freighter-canopy{position:absolute;z-index:5;left:77px;top:10px;width:39px;height:11px;border-radius:70% 48% 42% 32%;background:linear-gradient(135deg,#ecfeff 0%,#67e8f9 32%,#2563eb 75%);transform:skewX(-18deg);box-shadow:inset 0 2px 2px rgb(255 255 255 / .7),0 0 8px color-mix(in srgb,#67e8f9 42%,transparent);}.dkan-freighter-fin{position:absolute;z-index:2;left:48px;top:3px;width:24px;height:17px;background:linear-gradient(135deg,#31557f,#a7caf7);clip-path:polygon(0 100%,58% 0,100% 100%);}.dkan-freighter-wing{position:absolute;z-index:1;left:48px;width:60px;height:18px;background:linear-gradient(135deg,#24436d,#8ebaf4);}.dkan-freighter-wing.left{top:2px;clip-path:polygon(0 100%,100% 0,68% 100%)}.dkan-freighter-wing.right{top:26px;clip-path:polygon(0 0,68% 0,100% 100%)}",
|
|
1333
1333
|
".dkan-freighter-cargo{position:absolute;z-index:6;left:37px;top:31px;width:74px;height:7px;display:flex;align-items:center;gap:2px;padding:1px 3px;border-radius:4px;background:#172842;border:1px solid color-mix(in srgb,#b8d8ff 48%,transparent);box-shadow:inset 0 1px 0 rgb(255 255 255 / .2);}.dkan-freighter-cargo i{display:block;flex:1;min-width:4px;height:5px;border-radius:1px;background:linear-gradient(135deg,#fef3c7,#f59e0b 58%,#b45309);box-shadow:0 0 4px color-mix(in srgb,#fbbf24 58%,transparent);}.dkan-freighter-cargo.empty i{background:linear-gradient(135deg,#dbeafe,#38bdf8 58%,#1d4ed8);box-shadow:0 0 4px color-mix(in srgb,#38bdf8 55%,transparent);}.dkan-freighter-engine{position:absolute;z-index:0;left:5px;top:19px;display:flex;gap:3px;align-items:center;height:11px;}.dkan-freighter-engine i{width:18px;height:5px;border-radius:60% 0 0 60%;background:linear-gradient(90deg,transparent,#67e8f9);box-shadow:-9px 0 12px #22d3ee;animation:dkan-engine .34s ease-in-out infinite alternate;}.dkan-freighter-engine i:nth-child(2){animation-delay:-.14s}",
|
|
1334
1334
|
"@keyframes dkan-engine{to{transform:scaleX(.55);opacity:.5}}",
|
|
1335
1335
|
".dkan-delivery-label{position:absolute;left:21px;top:58px;display:flex;align-items:baseline;gap:6px;padding:4px 8px;border:1px solid color-mix(in srgb,#93c5fd 30%,var(--dsw-alias-border-l1));border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 82%,transparent);backdrop-filter:blur(10px);color:var(--dsw-alias-label-secondary);font:10px/1.2 var(--ds-font-family,system-ui,sans-serif);white-space:nowrap;}.dkan-delivery-label b{color:var(--dsw-alias-label-primary);font-weight:650}.dkan-delivery-label span{color:var(--dk-accent);}",
|
|
1336
1336
|
"@media (max-width:1100px){.dkan-space-system,.dkan-space-runner{display:none}}",
|
|
1337
|
-
".dkan-ready-freighter{position:fixed;width:210px;height:88px;z-index:
|
|
1338
|
-
".dkan-space-stuck{position:fixed;right:20px;top:88px;width:244px;height:160px;z-index:
|
|
1337
|
+
".dkan-ready-freighter{position:fixed;width:210px;height:88px;z-index:6;pointer-events:none;filter:drop-shadow(0 10px 14px rgb(0 0 0 / .18));}.dkan-ready-freighter>span:last-child{position:absolute;left:21px;top:58px;padding:4px 8px;border:1px solid color-mix(in srgb,#93c5fd 28%,var(--dsw-alias-border-l1));border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 80%,transparent);backdrop-filter:blur(10px);color:var(--dsw-alias-label-secondary);font:10px/1.2 var(--ds-font-family,system-ui,sans-serif);white-space:nowrap;}.dkan-departure{position:fixed;left:0;top:0;width:210px;height:88px;z-index:6;pointer-events:none;will-change:transform,opacity;contain:layout paint style;isolation:isolate;animation:dkan-departure-flight var(--dkan-departure-duration,8600ms) cubic-bezier(.42,0,.85,.45) forwards;}@keyframes dkan-departure-flight{0%{opacity:0;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(1) rotate(var(--dkan-flight-angle,0deg))}6%{opacity:1;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(1) rotate(var(--dkan-flight-angle,0deg))}100%{opacity:0;transform:translate3d(var(--dkan-to-x),var(--dkan-to-y),0) scale(.24) rotate(var(--dkan-flight-angle,0deg))}}",
|
|
1338
|
+
".dkan-space-stuck{position:fixed;right:20px;top:88px;width:244px;height:160px;z-index:5;pointer-events:none;border:1px solid color-mix(in srgb,#f59e0b 48%,var(--dsw-alias-border-l1));border-radius:20px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 88%,transparent);backdrop-filter:blur(16px);box-shadow:0 18px 54px rgb(0 0 0 / .28),0 0 30px color-mix(in srgb,#f59e0b 15%,transparent);}.dkan-space-stuck .dkan-freighter{left:37px;top:11px;transform:scale(.9);transform-origin:top left;}.dkan-stuck-code{position:absolute;left:17px;right:17px;top:60px;height:36px;border-radius:7px;background:linear-gradient(90deg,color-mix(in srgb,#f59e0b 18%,transparent),transparent);color:var(--dk-warn);font:700 14px/36px var(--ds-font-family-code,monospace);letter-spacing:.12em;text-align:center;overflow:hidden;}.dkan-stuck-code::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,color-mix(in srgb,#fef3c7 70%,transparent),transparent);transform:translateX(-110%);animation:dkan-stuck-scan 1.25s linear infinite;}.dkan-stuck-code i{display:inline-block;width:7px;height:7px;margin-right:8px;border-radius:50%;background:#f59e0b;box-shadow:0 0 9px #f59e0b;animation:dkan-stuck-pulse .8s ease-in-out infinite alternate;}.dkan-space-stuck strong{position:absolute;left:0;right:0;top:108px;color:var(--dsw-alias-label-primary);font:650 13px/1.2 var(--ds-font-family,system-ui,sans-serif);text-align:center;}.dkan-space-stuck small{position:absolute;left:18px;right:18px;top:128px;color:var(--dsw-alias-label-secondary);font:11px/1.25 var(--ds-font-family,system-ui,sans-serif);text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}@keyframes dkan-stuck-scan{to{transform:translateX(110%)}}@keyframes dkan-stuck-pulse{to{transform:scale(.55);opacity:.45}}",
|
|
1339
1339
|
// 窄屏(手机/分屏)贴右会溢出:回落到水平居中、屏幕上方,避开正文重心
|
|
1340
1340
|
"@media (max-width:760px){.dkan-space-stuck{right:50%;transform:translateX(50%);top:64px;}}",
|
|
1341
1341
|
// 极光:顶部柔光带呼吸流动(加浓)
|
|
@@ -1375,7 +1375,7 @@ const css = [
|
|
|
1375
1375
|
".dkan-lantern{contain:strict}.dkan-lantern i{position:absolute;bottom:-30px;border-radius:45% 45% 34% 34%;background:linear-gradient(150deg,#fff7c2 0%,#fbbf24 46%,#f97316 100%);box-shadow:0 0 10px 3px color-mix(in srgb,#fbbf24 36%,transparent);opacity:0;animation-name:dkan-lantern-rise;animation-timing-function:cubic-bezier(.34,.02,.6,1);animation-iteration-count:infinite;will-change:transform,opacity}.dkan-lantern i::after{content:'';position:absolute;left:42%;top:100%;width:16%;height:13px;background:linear-gradient(#f59e0b,transparent)}",
|
|
1376
1376
|
"@keyframes dkan-lantern-rise{0%{opacity:0;transform:translate3d(0,0,0) rotate(-3deg) scale(.72)}12%{opacity:.72}78%{opacity:.48}100%{opacity:0;transform:translate3d(var(--drift),-104vh,0) rotate(4deg) scale(1.04)}}",
|
|
1377
1377
|
// ===== 交互反馈层 =====
|
|
1378
|
-
".dkan-fxwrap{position:fixed;inset:0;z-index:
|
|
1378
|
+
".dkan-fxwrap{position:fixed;inset:0;z-index:4;pointer-events:none;overflow:hidden;}",
|
|
1379
1379
|
".dkan-fx{position:absolute;}",
|
|
1380
1380
|
// 纸飞机:从点击处向右上飞出淡出(加大加亮)
|
|
1381
1381
|
".dkan-fx-plane{color:var(--dk-accent);font-size:24px;text-shadow:0 0 8px color-mix(in srgb,var(--dk-accent) 70%,transparent);animation:dkan-plane 1.2s var(--ds-ease-in-out) forwards;}",
|
|
@@ -1397,7 +1397,7 @@ const css = [
|
|
|
1397
1397
|
".dkan-fx-confetti i{position:absolute;top:-14px;width:8px;height:13px;border-radius:2px;box-shadow:0 0 4px currentColor;animation:dkan-confetti 2s var(--ds-ease-in-out) forwards;}",
|
|
1398
1398
|
"@keyframes dkan-confetti{0%{opacity:1;transform:translateY(0) rotate(0)}100%{opacity:0;transform:translateY(75vh) translateX(var(--dx)) rotate(var(--r))}}",
|
|
1399
1399
|
// 右下角状态徽标(玻璃拟态,可点击);抬到 dsh 输入卡上方避免重叠错位
|
|
1400
|
-
".dkan-badge{position:fixed;right:20px;bottom:calc(var(--dsh-composer-height,152px) + 20px);z-index:
|
|
1400
|
+
".dkan-badge{position:fixed;right:20px;bottom:calc(var(--dsh-composer-height,152px) + 20px);z-index:2;display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border-radius:999px;cursor:pointer;border:1px solid var(--dsw-alias-border-l1);background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 78%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 6px 24px rgb(0 0 0 / .16);color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:18px;pointer-events:auto;animation:dkan-rise .3s var(--ds-ease-in-out);transition:color .15s var(--ds-ease-in-out);}",
|
|
1401
1401
|
".dkan-badge:hover{color:var(--dsw-alias-label-primary);}",
|
|
1402
1402
|
".dkan-badge .n{color:var(--dsw-alias-label-primary);font-weight:600;}",
|
|
1403
1403
|
"@keyframes dkan-rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}",
|
|
@@ -1487,7 +1487,7 @@ const css = [
|
|
|
1487
1487
|
".dkan-prev-bot .dkan-bot-scene{--dkan-bot-scale:1;transform:scale(.56);transform-origin:center;}",
|
|
1488
1488
|
// ===== 桌面伙伴:具象人物 + 紧凑双工位(尺寸可调,整卡可拖拽) =====
|
|
1489
1489
|
// 卡片:玻璃拟态;默认停泊右下(dsh 输入卡上方),拖动后位置持久化、可放屏幕任意处
|
|
1490
|
-
".dkan-botcard{--dkan-bot-scale:1.35;position:fixed;right:20px;bottom:calc(var(--dsh-composer-height,152px) + 20px);z-index:
|
|
1490
|
+
".dkan-botcard{--dkan-bot-scale:1.35;position:fixed;right:20px;bottom:calc(var(--dsh-composer-height,152px) + 20px);z-index:2;display:flex;flex-direction:column;gap:6px;padding:10px 12px;border-radius:16px;cursor:grab;border:1px solid var(--dsw-alias-border-l1);background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 86%,transparent);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);box-shadow:0 12px 36px rgb(0 0 0 / .22);color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;pointer-events:auto;touch-action:none;user-select:none;animation:dkan-rise .3s var(--ds-ease-in-out);}",
|
|
1491
1491
|
".dkan-botcard:hover,.dkan-botcard:focus-visible{border-color:var(--dk-accent);outline:none;}",
|
|
1492
1492
|
".dkan-botcard.dkan-dragging{cursor:grabbing;animation:none;}",
|
|
1493
1493
|
".dkan-botcard .n{color:var(--dsw-alias-label-primary);font-weight:600;}",
|
package/features/games/view.jsx
CHANGED
|
@@ -588,8 +588,8 @@ const baseCss = `
|
|
|
588
588
|
.dgcov-grid{--dgame-accent:var(--dk-games-accent,#7c3aed);display:flex;gap:10px;flex-wrap:wrap}.dgcov{flex:1;min-width:230px;display:flex;flex-direction:column;gap:8px;padding:11px;border:1px solid var(--dsw-alias-border-l1);border-radius:13px;background:var(--dsw-alias-bg-layer-2);cursor:pointer;font-family:inherit;text-align:left;transition:border-color .16s ease,transform .16s ease}.dgcov:hover{border-color:var(--dgame-accent);transform:translateY(-2px)}.dgcov:focus-visible{outline:2px solid var(--dgame-accent);outline-offset:2px}.dgcov.on{border-color:var(--dgame-accent);box-shadow:0 0 0 1px color-mix(in srgb,var(--dgame-accent) 40%,transparent)}.dgcov-name{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}.dgcov-name i{width:8px;height:8px;border-radius:50%;flex:none}.dgcov-name em{margin-left:auto;font-style:normal;font-size:10px;color:var(--dgame-accent);border:1px solid color-mix(in srgb,var(--dgame-accent) 45%,transparent);border-radius:999px;padding:1px 8px;white-space:nowrap}.dgcov-desc{font-size:11.5px;color:var(--dsw-alias-label-secondary);line-height:1.55}.dgcov-prev{position:relative;display:block;height:96px;border-radius:9px;overflow:hidden;border:1px solid color-mix(in srgb,var(--dgame-accent) 25%,var(--dsw-alias-border-l1))}
|
|
589
589
|
.dgcov-prev-dash{background:radial-gradient(circle at 70% 20%,color-mix(in srgb,#1d4ed8 30%,transparent),transparent 55%),linear-gradient(180deg,#0d1330,#171233)}.dgcov-prev-dash i{position:absolute;top:-22%;font-style:normal;color:#fb7185;text-shadow:0 0 10px #f43f5e;font-size:15px;animation:dgcov-fall 1.5s linear infinite}.dgcov-prev-dash i::after{content:'◆'}.dgcov-prev-dash i:nth-child(1){left:16%}.dgcov-prev-dash i:nth-child(2){left:50%;animation-delay:-.5s}.dgcov-prev-dash i:nth-child(3){left:82%;animation-delay:-1s}.dgcov-prev-dash b{position:absolute;left:50%;bottom:8px;margin-left:-9px;font-size:17px;color:#a5f3fc;text-shadow:0 0 12px #38bdf8;animation:dgcov-dodge 2.4s ease-in-out infinite}.dgcov-prev-dash b::after{content:'▲'}@keyframes dgcov-fall{to{transform:translateY(140px)}}@keyframes dgcov-dodge{0%,100%{transform:translateX(-46px)}25%,75%{transform:translateX(0)}50%{transform:translateX(46px)}}
|
|
590
590
|
.dgcov-prev-reactor{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;padding:14px;background:linear-gradient(135deg,#272463,#12102c)}.dgcov-prev-reactor i{display:block;border-radius:9px;background:#191c38;border:1px solid color-mix(in srgb,#ffffff 11%,transparent);animation:dgcov-reactor 3s ease-in-out infinite}.dgcov-prev-reactor i:nth-child(1){animation-delay:0s}.dgcov-prev-reactor i:nth-child(2){animation-delay:-.33s}.dgcov-prev-reactor i:nth-child(3){animation-delay:-.66s}.dgcov-prev-reactor i:nth-child(4){animation-delay:-1s}.dgcov-prev-reactor i:nth-child(5){animation-delay:-1.33s}.dgcov-prev-reactor i:nth-child(6){animation-delay:-1.66s}.dgcov-prev-reactor i:nth-child(7){animation-delay:-2s}.dgcov-prev-reactor i:nth-child(8){animation-delay:-2.33s}.dgcov-prev-reactor i:nth-child(9){animation-delay:-2.66s}@keyframes dgcov-reactor{0%,20%,100%{background:#191c38;box-shadow:none;border-color:color-mix(in srgb,#ffffff 11%,transparent)}40%,64%{background:radial-gradient(circle at 35% 30%,#ecfeff,#67e8f9 40%,#2563eb 85%);box-shadow:0 0 14px color-mix(in srgb,#22d3ee 55%,transparent);border-color:#67e8f9}}
|
|
591
|
-
.dgfab{--dgame-accent:var(--dk-games-accent,#7c3aed);position:fixed;z-index:
|
|
592
|
-
.dgwin{--dgame-accent:#a78bfa;--dgame-accent-soft:color-mix(in srgb,#a78bfa 16%,transparent);--dsw-alias-label-primary:#e7ecff;--dsw-alias-label-secondary:#aab3e8;--dsw-alias-label-tertiary:#7d86c0;--dsw-alias-border-l1:rgb(255 255 255 / .12);--dsw-alias-border-l2:rgb(255 255 255 / .17);--dsw-alias-bg-layer-1:#0d1230;--dsw-alias-bg-layer-2:#131a3d;--dsw-alias-interactive-bg-hover:rgb(255 255 255 / .08);position:fixed;z-index:
|
|
591
|
+
.dgfab{--dgame-accent:var(--dk-games-accent,#7c3aed);position:fixed;z-index:1;display:flex;flex-direction:column;align-items:center;gap:6px;box-sizing:border-box;width:36px;padding:11px 0;border:1px solid color-mix(in srgb,var(--dgame-accent) 42%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 82%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:var(--dsw-alias-label-primary);font-family:inherit;cursor:grab;user-select:none;touch-action:none;box-shadow:0 6px 24px rgb(0 0 0 / .18);transition:left .22s ease,top .22s ease,border-color .15s ease,opacity .15s ease}.dgfab:hover{border-color:var(--dgame-accent)}.dgfab-side-left{border-radius:0 12px 12px 0;border-left:none}.dgfab-side-right{border-radius:12px 0 0 12px;border-right:none}.dgfab-drag{transition:none;cursor:grabbing;box-shadow:0 10px 34px rgb(0 0 0 / .3);z-index:2}.dgfab-hide{display:none}.dgfab-ico{display:inline-flex;color:var(--dgame-accent)}.dgfab-label{writing-mode:vertical-rl;letter-spacing:.22em;font-size:11px;line-height:1;color:var(--dsw-alias-label-secondary)}
|
|
592
|
+
.dgwin{--dgame-accent:#a78bfa;--dgame-accent-soft:color-mix(in srgb,#a78bfa 16%,transparent);--dsw-alias-label-primary:#e7ecff;--dsw-alias-label-secondary:#aab3e8;--dsw-alias-label-tertiary:#7d86c0;--dsw-alias-border-l1:rgb(255 255 255 / .12);--dsw-alias-border-l2:rgb(255 255 255 / .17);--dsw-alias-bg-layer-1:#0d1230;--dsw-alias-bg-layer-2:#131a3d;--dsw-alias-interactive-bg-hover:rgb(255 255 255 / .08);position:fixed;z-index:5;display:flex;flex-direction:column;border:1px solid rgb(255 255 255 / .14);border-radius:14px;background:radial-gradient(600px 300px at 80% -10%,color-mix(in srgb,#312e81 30%,transparent),transparent 60%),radial-gradient(500px 340px at -10% 110%,color-mix(in srgb,#0ea5e9 14%,transparent),transparent 55%),linear-gradient(180deg,#0b1024,#12102a 60%,#0a0f22);color:var(--dsw-alias-label-primary);box-shadow:0 24px 72px rgb(0 0 0 / .5);animation:dgwin-in .18s ease-out}.dgwin::before{content:'';position:absolute;inset:0;pointer-events:none;background-image:radial-gradient(1px 1px at 12% 22%,rgb(255 255 255 / .35),transparent),radial-gradient(1px 1px at 34% 68%,rgb(255 255 255 / .26),transparent),radial-gradient(1.5px 1.5px at 58% 14%,rgb(199 210 254 / .35),transparent),radial-gradient(1px 1px at 76% 44%,rgb(255 255 255 / .22),transparent),radial-gradient(1.5px 1.5px at 88% 78%,rgb(165 243 252 / .3),transparent),radial-gradient(1px 1px at 22% 88%,rgb(255 255 255 / .18),transparent),radial-gradient(1px 1px at 45% 38%,rgb(255 255 255 / .15),transparent),radial-gradient(1px 1px at 67% 86%,rgb(255 255 255 / .19),transparent)}@keyframes dgwin-in{from{opacity:0;transform:translateY(8px) scale(.985)}}
|
|
593
593
|
.dgwin-min{height:auto!important;border-radius:12px}.dgwin-min .dgwin-body{display:none}.dgwin-min .dgwin-resize{display:none}
|
|
594
594
|
.dgwin-max{border-radius:12px}.dgwin-max .dgwin-body .dgame-game{align-items:stretch}.dgwin-max .dgwin-body .dgame-dash-stage,.dgwin-max .dgwin-body .dgame-tetris,.dgwin-max .dgwin-body .dgame-tetris-board,.dgwin-max .dgwin-body .dgame-snake-stage,.dgwin-max .dgwin-body .dgame-sokoban-stage,.dgwin-max .dgwin-body .dgame-gomoku-board,.dgwin-max .dgwin-body .dgame-xiangqi-board,.dgwin-max .dgwin-body .dgame-tank-stage,.dgwin-max .dgwin-body .dgame-breakout,.dgwin-max .dgwin-body .dgame-racer,.dgwin-max .dgwin-body .dgame-reactor{width:100%;max-width:none}.dgwin-max .dgwin-body .dgame-controls{width:100%;max-width:none}
|
|
595
595
|
.dgwin-bar{flex:none;display:flex;align-items:center;gap:9px;padding:10px 12px;border-bottom:1px solid rgb(255 255 255 / .09);background:rgb(7 11 28 / .72);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);color:#cdd6ff;cursor:move;user-select:none;touch-action:none;position:relative;z-index:2}.dgwin-ico{display:inline-flex;color:#a78bfa}.dgwin-title{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1}.dgwin-title b{font-size:13px}.dgwin-title span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;color:#8f98ce}.dgwin-task{display:inline-flex;align-items:center;gap:7px;padding:5px 11px;border-radius:999px;border:1px solid rgb(255 255 255 / .13);background:rgb(255 255 255 / .05);font-size:11px;color:#aab3e8;white-space:nowrap}.dgwin-task i{width:7px;height:7px;border-radius:50%;background:#6b7499;flex:none}.dgwin-task.live{color:#c4f5e4;border-color:color-mix(in srgb,#34d399 33%,transparent);background:color-mix(in srgb,#34d399 8%,transparent)}.dgwin-task.live i{background:#34d399;box-shadow:0 0 0 3px color-mix(in srgb,#34d399 14%,transparent);animation:dgame-pulse 1.8s ease-in-out infinite}.dgwin-ctrls{display:flex;align-items:center;gap:6px;flex:none}.dgwin-btn{cursor:pointer;border:1px solid rgb(255 255 255 / .17);background:rgb(255 255 255 / .07);color:#e6ebff;border-radius:8px;padding:5px 10px;font:inherit;font-size:11px;transition:background .15s ease,border-color .15s ease}.dgwin-btn:hover{background:rgb(255 255 255 / .13);border-color:rgb(255 255 255 / .28)}.dgwin-sq{cursor:pointer;border:1px solid transparent;background:transparent;color:#a9b2e2;border-radius:8px;width:28px;height:26px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;transition:background .15s ease,color .15s ease}.dgwin-sq:hover{background:rgb(255 255 255 / .12);color:#fff}
|
|
@@ -20,7 +20,7 @@ import { stat } from 'node:fs/promises'
|
|
|
20
20
|
import { homedir } from 'node:os'
|
|
21
21
|
import { dirname, join } from 'node:path'
|
|
22
22
|
import yaml from 'js-yaml'
|
|
23
|
-
import { DOCK_NS, readBody, sendJson } from '../../src/host-core.js'
|
|
23
|
+
import { DOCK_NS, readBody, sendJson, readDockRoot, mutateDockSection } from '../../src/host-core.js'
|
|
24
24
|
import { lanAddresses, startProtectedLanGateway } from './gateway.js'
|
|
25
25
|
|
|
26
26
|
function dshHome() { return process.env.DSH_HOME || join(homedir(), '.dsh') }
|
|
@@ -324,7 +324,6 @@ export const feature = {
|
|
|
324
324
|
const disposers = []
|
|
325
325
|
let gateway = null
|
|
326
326
|
let gatewayStarting = null
|
|
327
|
-
let settingsCtx = null
|
|
328
327
|
|
|
329
328
|
// 自愈:早期版本把 browse 行平铺写进补丁层——DSH 会把未知 id 的普通补丁行整行
|
|
330
329
|
// 跳过(且 auto 已被停用),结果 directoryPicker 服务无人提供,/api 整面 404,
|
|
@@ -337,8 +336,6 @@ export const feature = {
|
|
|
337
336
|
}
|
|
338
337
|
} catch { /* 补丁层不可读时交给 lanStatus 的错误呈现,不在启动路径上放大 */ }
|
|
339
338
|
|
|
340
|
-
disposers.push(ctx.inject(['settings'], (sctx) => { settingsCtx = sctx }))
|
|
341
|
-
|
|
342
339
|
// ── 浏览模式目录选择器的 Windows 跨盘补全 ──
|
|
343
340
|
// 官方 browse 后端从主目录起步,只能列出当前盘内的子目录,面包屑在主目录之上
|
|
344
341
|
// 全部折叠——Windows 开着远程访问时,选工作区根本到不了其他磁盘。
|
|
@@ -401,23 +398,25 @@ export const feature = {
|
|
|
401
398
|
return ba.length > 0 && ba.length === bb.length && timingSafeEqual(ba, bb)
|
|
402
399
|
}
|
|
403
400
|
function getAuth() {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
401
|
+
try {
|
|
402
|
+
const value = readDockRoot(ctx)
|
|
403
|
+
const auth = value && value.remoteAuth
|
|
404
|
+
return auth && auth.username && auth.passwordHash && auth.salt ? auth : null
|
|
405
|
+
} catch {
|
|
406
|
+
return null
|
|
407
|
+
}
|
|
408
408
|
}
|
|
409
409
|
async function saveAuth(username, password) {
|
|
410
|
-
|
|
410
|
+
const settings = ctx.get('settings')
|
|
411
|
+
if (!settings || typeof settings.mutate !== 'function') {
|
|
411
412
|
const error = new Error('settings 服务未就绪,无法保存账号')
|
|
412
413
|
error.statusCode = 500
|
|
413
414
|
throw error
|
|
414
415
|
}
|
|
415
416
|
const salt = randomBytes(16).toString('hex')
|
|
416
|
-
await
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
value: { username, salt, passwordHash: hashPassword(salt, password) },
|
|
420
|
-
}])
|
|
417
|
+
await mutateDockSection(ctx, ['remoteAuth'], {
|
|
418
|
+
username, salt, passwordHash: hashPassword(salt, password),
|
|
419
|
+
})
|
|
421
420
|
// 账号密码变更:作废所有已登录设备,强制重新登录。
|
|
422
421
|
if (gateway) gateway.revokeAllSessions()
|
|
423
422
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// - 输入类型官方 schema 仅接受 text/image;「视频」等以 dockTags 标注随配置持久化(不参与请求路由)
|
|
10
10
|
// - schemastery 保留未知字段(实测),dockTags 对官方校验透明
|
|
11
11
|
// - POST 也收图片理解代理配置(visionProxy 分支,写 dsh-dock 自有命名空间)
|
|
12
|
-
import { DOCK_NS, sendJson, readBody, walkPath } from '../../src/host-core.js'
|
|
12
|
+
import { DOCK_NS, sendJson, readBody, walkPath, readDockRoot, mutateDockSection } from '../../src/host-core.js'
|
|
13
13
|
|
|
14
14
|
/** 官方 schema 接受的输入模态(写回配置的 input 只允许这两个)。 */
|
|
15
15
|
const HOST_MODALITIES = ['text', 'image']
|
|
@@ -294,7 +294,7 @@ async function writeModelConfig(ctx, body) {
|
|
|
294
294
|
}
|
|
295
295
|
const revision = body.revisions && typeof body.revisions[DOCK_NS] === 'number' ? body.revisions[DOCK_NS] : undefined
|
|
296
296
|
try {
|
|
297
|
-
await
|
|
297
|
+
await mutateDockSection(ctx, ['visionProxy'], value, revision)
|
|
298
298
|
} catch (e) {
|
|
299
299
|
const msg = e && e.message ? e.message : String(e)
|
|
300
300
|
const err = new Error(`保存图片理解代理配置被拒绝:${msg}`)
|
|
@@ -373,11 +373,11 @@ export const feature = {
|
|
|
373
373
|
await enrichRuntimeInput(ctx, payload)
|
|
374
374
|
// 附带图片理解代理配置与自有命名空间 revision(面板保存用)
|
|
375
375
|
try {
|
|
376
|
-
const
|
|
377
|
-
const v = settings && typeof settings.get === 'function' ? settings.get(DOCK_NS) : undefined
|
|
376
|
+
const v = readDockRoot(ctx)
|
|
378
377
|
payload.visionProxy = v && v.visionProxy
|
|
379
378
|
? { enabled: !!v.visionProxy.enabled, provider: String(v.visionProxy.provider || ''), model: String(v.visionProxy.model || '') }
|
|
380
379
|
: { enabled: false, provider: '', model: '' }
|
|
380
|
+
const settings = ctx.get('settings')
|
|
381
381
|
const desc = settings && typeof settings.describe === 'function'
|
|
382
382
|
? settings.describe({ redactSecrets: true }) : []
|
|
383
383
|
for (const d of desc || []) {
|
package/features/notify/host.js
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
// systemNotify / soundNotify / soundEffect / dingtalkEnabled / dingtalkWebhook /
|
|
15
15
|
// feishuEnabled / feishuWebhook
|
|
16
16
|
// 模块启停由功能坞的 features.notify 开关负责(独立菜单项),本模块不再有第二个总开关。
|
|
17
|
-
import {
|
|
17
|
+
import { SOUND_EFFECTS, sendJson, readBody, readDockRoot, mutateDockSection } from '../../src/host-core.js'
|
|
18
18
|
import { acquireTaskTracker } from '../../src/task-track.js'
|
|
19
19
|
|
|
20
|
-
// 默认配置(schema 默认值一致;settings
|
|
20
|
+
// 默认配置(schema 默认值一致;settings 未挂载时的兜底)
|
|
21
21
|
function defaultConfig() {
|
|
22
22
|
return {
|
|
23
23
|
notifyOnComplete: true,
|
|
@@ -37,12 +37,11 @@ function defaultConfig() {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
//
|
|
40
|
+
// 读本地 Config 镜像里的 notify 配置(resolved 值已含 schema 默认),异常时回退默认
|
|
41
41
|
function readConfig(ctx) {
|
|
42
42
|
const cfg = defaultConfig()
|
|
43
43
|
try {
|
|
44
|
-
const
|
|
45
|
-
const v = settings && typeof settings.get === 'function' ? settings.get(DOCK_NS) : null
|
|
44
|
+
const v = readDockRoot(ctx)
|
|
46
45
|
const n = v && typeof v === 'object' && v.notify && typeof v.notify === 'object' ? v.notify : null
|
|
47
46
|
if (n) {
|
|
48
47
|
for (const key of Object.keys(cfg)) {
|
|
@@ -291,12 +290,8 @@ export const feature = {
|
|
|
291
290
|
cfg.feishuWebhook = hook
|
|
292
291
|
}
|
|
293
292
|
|
|
294
|
-
const settings = ctx.get('settings')
|
|
295
|
-
if (!settings || typeof settings.mutate !== 'function') {
|
|
296
|
-
throw new Error('settings 服务不可用,配置无法持久化')
|
|
297
|
-
}
|
|
298
293
|
try {
|
|
299
|
-
await
|
|
294
|
+
await mutateDockSection(ctx, ['notify'], cfg)
|
|
300
295
|
} catch (e) {
|
|
301
296
|
const err = new Error('保存配置被拒绝:' + ((e && e.message) || String(e)))
|
|
302
297
|
err.statusCode = 400
|
package/features/notify/view.jsx
CHANGED
|
@@ -685,7 +685,7 @@ function NotifyStat(props) {
|
|
|
685
685
|
// ---------- 样式(dknt- 前缀;全部走主题变量,暗/亮色自适应) ----------
|
|
686
686
|
const css = [
|
|
687
687
|
// 通知卡片栈(右上角,不遮 dsh 自身 UI)
|
|
688
|
-
".dknt-toasts{position:fixed;top:16px;right:16px;z-index:
|
|
688
|
+
".dknt-toasts{position:fixed;top:16px;right:16px;z-index:7;display:flex;flex-direction:column;gap:8px;width:min(380px,calc(100vw - 32px));}",
|
|
689
689
|
".dknt-toast{border-radius:12px;padding:12px 14px;pointer-events:auto;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 88%,transparent);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--dsw-alias-border-l1);box-shadow:0 10px 36px rgb(0 0 0 / .22);animation:dknt-toast-in .32s var(--ds-ease-in-out);}",
|
|
690
690
|
".dknt-toast.out{animation:dknt-toast-out .24s var(--ds-ease-in-out) forwards;}",
|
|
691
691
|
"@keyframes dknt-toast-in{from{opacity:0;transform:translateX(14px)}to{opacity:1;transform:none}}",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
// 定价优先级:用户自定义单价 > 官网抓取刊例价 > 内置默认表 > 兜底单价。
|
|
26
26
|
// 分时段价支持多段(start>end 跨零点、start=end 全天、小数小时表示半点);旧单段 peak 自动归一。
|
|
27
27
|
// 费用在查询/导出时按当前单价即时重算,因此改价无需重扫历史。
|
|
28
|
-
import {
|
|
28
|
+
import { sendJson, readBody, readDockRoot, mutateDockSection } from '../../src/host-core.js'
|
|
29
29
|
|
|
30
30
|
export const feature = {
|
|
31
31
|
id: 'tokenlog',
|
|
@@ -119,8 +119,7 @@ export const feature = {
|
|
|
119
119
|
// 取值做防御性归一(schema 已给默认, 但 settings 未挂载/异常时仍需兜底)。
|
|
120
120
|
function loadPricingConfig() {
|
|
121
121
|
try {
|
|
122
|
-
const
|
|
123
|
-
const root = settings && typeof settings.get === 'function' ? settings.get(DOCK_NS) : null
|
|
122
|
+
const root = readDockRoot(ctx)
|
|
124
123
|
const t = root && typeof root === 'object' && root.tokenlog && typeof root.tokenlog === 'object' ? root.tokenlog : null
|
|
125
124
|
if (!t) return
|
|
126
125
|
if (typeof t.usdCnyRate === 'number' && t.usdCnyRate > 0) usdCnyRate = t.usdCnyRate
|
|
@@ -247,7 +246,7 @@ export const feature = {
|
|
|
247
246
|
// 的字段) → 落盘。保存后由调用方 loadPricingConfig() 刷新内存, 使改价立即影响费用估算。
|
|
248
247
|
async function savePricingConfig(input) {
|
|
249
248
|
const settings = ctx.get('settings')
|
|
250
|
-
if (!settings || typeof settings.
|
|
249
|
+
if (!settings || typeof settings.mutate !== 'function') {
|
|
251
250
|
throw new Error('settings 服务不可用,单价无法持久化')
|
|
252
251
|
}
|
|
253
252
|
const bad = (msg) => { const err = new Error(msg); err.statusCode = 400; return err }
|
|
@@ -256,7 +255,7 @@ export const feature = {
|
|
|
256
255
|
if (!Number.isFinite(n) || n < 0) throw bad(`「${label}」需为不小于 0 的数字`)
|
|
257
256
|
return n
|
|
258
257
|
}
|
|
259
|
-
const root =
|
|
258
|
+
const root = readDockRoot(ctx)
|
|
260
259
|
const current = root && typeof root === 'object' && root.tokenlog && typeof root.tokenlog === 'object' ? root.tokenlog : {}
|
|
261
260
|
const next = Object.assign({}, current)
|
|
262
261
|
if (input.usdCnyRate !== undefined) {
|
|
@@ -314,7 +313,7 @@ export const feature = {
|
|
|
314
313
|
}
|
|
315
314
|
}
|
|
316
315
|
try {
|
|
317
|
-
await
|
|
316
|
+
await mutateDockSection(ctx, ['tokenlog'], next)
|
|
318
317
|
} catch (e) {
|
|
319
318
|
const err = new Error('保存单价被拒绝:' + ((e && e.message) || String(e)))
|
|
320
319
|
err.statusCode = 400
|
|
@@ -481,17 +480,19 @@ export const feature = {
|
|
|
481
480
|
}
|
|
482
481
|
}
|
|
483
482
|
|
|
484
|
-
//
|
|
483
|
+
// 读官方 llm-pi-ai 配置的「现有提供商」路由名 -> [模型 id]。
|
|
485
484
|
// 用途: 前端提供商/模型下拉 = 现有配置 ∪ 历史记录, 去重合并 ——
|
|
486
485
|
// - 没有任何记录时仍可选现有提供商;
|
|
487
486
|
// - 提供商/模型已从配置删除时, 因有历史记录仍可选中(兼容两者)。
|
|
487
|
+
// 0.1.7-alpha.1 起 settings.get 已移除:改走 describe()(SettingsForms 读路径)。
|
|
488
488
|
function readConfiguredProviders() {
|
|
489
489
|
const out = {}
|
|
490
490
|
try {
|
|
491
491
|
const settings = ctx.get('settings')
|
|
492
|
-
if (!settings || typeof settings.
|
|
493
|
-
const
|
|
494
|
-
const
|
|
492
|
+
if (!settings || typeof settings.describe !== 'function') return out
|
|
493
|
+
const views = settings.describe({ redactSecrets: true }) || []
|
|
494
|
+
const hit = views.find((d) => d && d.ns === 'llm-pi-ai' && d.value && typeof d.value === 'object')
|
|
495
|
+
const providers = hit && hit.value.providers ? hit.value.providers : {}
|
|
495
496
|
for (const pid of Object.keys(providers || {})) {
|
|
496
497
|
const p = providers[pid]
|
|
497
498
|
if (!p || typeof p !== 'object') { out[pid] = []; continue }
|
|
@@ -396,7 +396,7 @@ const css = `
|
|
|
396
396
|
.dtok-code.pend{color:var(--dsw-alias-label-tertiary,#94a3b8);}
|
|
397
397
|
.dtok-detail-link{color:var(--dk-accent);cursor:pointer;font-size:11px;}
|
|
398
398
|
.dtok-detail-link:hover{text-decoration:underline;}
|
|
399
|
-
.dtok-detail{position:fixed;inset:0;z-index:
|
|
399
|
+
.dtok-detail{position:fixed;inset:0;z-index:201;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;}
|
|
400
400
|
.dtok-detail-card{background:var(--dsw-alias-bg-layer-2,#1c212b);border:1px solid var(--dsw-alias-border-l2,#3a4150);border-radius:12px;padding:20px 24px;max-width:640px;width:92%;max-height:80vh;overflow:auto;color:var(--dsw-alias-label-primary,#e8eaf0);}
|
|
401
401
|
.dtok-detail-card h3{margin:0 0 12px;font-size:15px;}
|
|
402
402
|
.dtok-detail-row{display:flex;gap:8px;padding:4px 0;font-size:12px;border-bottom:1px solid var(--dk-tdim);}
|
|
@@ -460,7 +460,7 @@ const css = `
|
|
|
460
460
|
.dtok-btn.primary:not([disabled]):active{transform:none;}
|
|
461
461
|
}
|
|
462
462
|
/* ---- 单价设置(子弹窗) ---- */
|
|
463
|
-
.dtok-pm-backdrop{position:fixed;inset:0;z-index:
|
|
463
|
+
.dtok-pm-backdrop{position:fixed;inset:0;z-index:201;background:rgba(15,17,21,.5);backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;}
|
|
464
464
|
.dtok-pm{box-sizing:border-box;width:min(980px,calc(100vw - 32px));height:min(760px,calc(100vh - 32px));display:flex;flex-direction:column;border-radius:14px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2,#1c212b);color:var(--dsw-alias-label-primary);box-shadow:0 20px 64px rgb(0 0 0 / .4);overflow:hidden;}
|
|
465
465
|
.dtok-pm.max{border-radius:10px;}
|
|
466
466
|
.dtok-pm-head{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);cursor:move;user-select:none;flex:none;background:var(--dsw-alias-bg-layer-1);}
|
|
@@ -641,6 +641,11 @@ function PriceModal({ onClose, onSaved }) {
|
|
|
641
641
|
return () => window.removeEventListener("keydown", onKey);
|
|
642
642
|
}, [onClose]);
|
|
643
643
|
|
|
644
|
+
// 遮罩关闭必须「按下也在遮罩上」。
|
|
645
|
+
// 从弹窗内按下、拖选/拖出后在遮罩松开时,click 会派发到共同祖先(遮罩),
|
|
646
|
+
// 若无此标记会把框选误判成点外部关闭(2026-09-23 用户反馈)。
|
|
647
|
+
const backdropDownRef = useRef(false);
|
|
648
|
+
|
|
644
649
|
const style = maxed
|
|
645
650
|
? { position: "fixed", left: 8, top: 8, right: 8, bottom: 8, width: "auto", height: "auto" }
|
|
646
651
|
: geom
|
|
@@ -651,7 +656,15 @@ function PriceModal({ onClose, onSaved }) {
|
|
|
651
656
|
// 无 document(测试沙箱)/无 createPortal 时退回就地渲染,行为不变。
|
|
652
657
|
const portalTarget = typeof document !== "undefined" && document.body ? document.body : null;
|
|
653
658
|
const overlay = (
|
|
654
|
-
<div
|
|
659
|
+
<div
|
|
660
|
+
className="dtok-pm-backdrop"
|
|
661
|
+
onPointerDown={(e) => { backdropDownRef.current = e.target === e.currentTarget; }}
|
|
662
|
+
onClick={(e) => {
|
|
663
|
+
if (!backdropDownRef.current) return;
|
|
664
|
+
if (e.target !== e.currentTarget) return;
|
|
665
|
+
onClose();
|
|
666
|
+
}}
|
|
667
|
+
>
|
|
655
668
|
<div ref={dlgRef} className={"dtok-pm" + (maxed ? " max" : "")} style={style} onClick={(e) => e.stopPropagation()}>
|
|
656
669
|
<div className="dtok-pm-head" onPointerDown={(e) => startDrag(e, "move")} onDoubleClick={() => setMaxed((v) => !v)}>
|
|
657
670
|
<b>单价设置</b>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// 配置存自有 settings 命名空间 dsh-dock(visionProxy: enabled/provider/model),
|
|
8
8
|
// 其编辑 UI 在模型设置页内(见 features/modelconfig/view.js)。
|
|
9
9
|
import { BlockAssembler } from '@deepseek-ai/dsh-llm'
|
|
10
|
-
import {
|
|
10
|
+
import { readDockRoot } from '../../src/host-core.js'
|
|
11
11
|
import { readModelDirectory } from '../modelconfig/host.js'
|
|
12
12
|
|
|
13
13
|
/** 递归判断内容块里是否有图片(含 tool-result 嵌套,与官方 contentHasImage 同构)。 */
|
|
@@ -223,8 +223,7 @@ function installVisionProxy(ctx, directory) {
|
|
|
223
223
|
const deps = {
|
|
224
224
|
config: () => {
|
|
225
225
|
try {
|
|
226
|
-
const
|
|
227
|
-
const v = settings && typeof settings.get === 'function' ? settings.get(DOCK_NS) : undefined
|
|
226
|
+
const v = readDockRoot(ctx)
|
|
228
227
|
return v && v.visionProxy ? v.visionProxy : { enabled: false, provider: '', model: '' }
|
|
229
228
|
} catch {
|
|
230
229
|
return { enabled: false, provider: '', model: '' }
|
package/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// - notify 任务通知:完成/异常/需确认通知 + 提示音/系统通知/钉钉飞书推送(从任务动画拆出)
|
|
17
17
|
// - runstate 运行状态:进行中任务与最近完成一览(从任务动画拆出;只读,无配置段)
|
|
18
18
|
// - mobile-relay 手机接力(未发布):扫码反向代理接力 + 局域网电脑直连(0.0.0.0)
|
|
19
|
-
import {
|
|
19
|
+
import { DockConfig, sendJson, readBody, migrateNotifyConfig, migrateModelsFeatureId, migrateImportedFeatures, bindDockConfig, readDockRoot, mutateDockSection } from './src/host-core.js'
|
|
20
20
|
import { feature as fModels } from './features/modelconfig/host.js'
|
|
21
21
|
import { feature as fVisionProxy } from './features/visionproxy/host.js'
|
|
22
22
|
import { feature as fBalance } from './features/balance/host.js'
|
|
@@ -28,7 +28,10 @@ import { feature as fMobileRelay } from './features/mobile-relay/host.js'
|
|
|
28
28
|
|
|
29
29
|
export const name = 'dsh-dock'
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
/** Config schema:dsh ≥ 0.1.7-alpha.1 SettingsForms 要求插件导出同名 Config,否则 mutate 报 No configurable plugin entry。 */
|
|
32
|
+
export const Config = DockConfig
|
|
33
|
+
|
|
34
|
+
// 硬依赖 webServer(路由注册)+ llm(图片理解代理包装)+ settings(SettingsForms 写回)。
|
|
32
35
|
export const inject = ['webServer', 'llm', 'settings']
|
|
33
36
|
|
|
34
37
|
/** 测试钩子:清空识别缓存(冒烟在用例间隔离;实现在 visionproxy 模块)。 */
|
|
@@ -50,7 +53,7 @@ if (typeof process !== 'undefined' && typeof process.on === 'function' && !proce
|
|
|
50
53
|
})
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
export function apply(ctx) {
|
|
56
|
+
export function apply(ctx, config) {
|
|
54
57
|
// ---- 功能注册表(Host 侧):每个条目来自对应功能模块的 feature 描述符 ----
|
|
55
58
|
// defaultEnabled:与 Client 半部保持一致。已接入的功能默认打开,
|
|
56
59
|
// 规划中的功能缺省 false,等实现后移除 roadmap 并打开。
|
|
@@ -65,17 +68,22 @@ export function apply(ctx) {
|
|
|
65
68
|
fMobileRelay,
|
|
66
69
|
]
|
|
67
70
|
|
|
71
|
+
// 绑定 Config 镜像(替代已移除的 settings.get);volatile 字段热更时重绑。
|
|
72
|
+
bindDockConfig(config)
|
|
73
|
+
ctx.on('loader/volatile-update', () => {
|
|
74
|
+
try { bindDockConfig(config) } catch { /* Config 形态异常时保持旧镜像 */ }
|
|
75
|
+
})
|
|
76
|
+
|
|
68
77
|
const state = new Map()
|
|
69
78
|
for (const f of FEATURES) state.set(f.id, { enabled: false, dispose: null, error: null })
|
|
70
79
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
// 读宿主侧功能开关表(本地 Config 镜像优先;settings 未挂载时回退 defaultEnabled)。
|
|
81
|
+
// 兼容别名:宿主侧【模型设置】功能 id 曾叫 models(与客户端 modelconfig 对不上,
|
|
82
|
+
// 开关永远推不到宿主);迁移落盘前(异步)在这里读时兜底换算,保证启动开关表
|
|
83
|
+
// 与 /dsh-dock/features 响应第一时间就是新口径。
|
|
75
84
|
function persistedFeatureMap() {
|
|
76
85
|
try {
|
|
77
|
-
const
|
|
78
|
-
const v = settings && typeof settings.get === 'function' ? settings.get(DOCK_NS) : null
|
|
86
|
+
const v = readDockRoot(ctx)
|
|
79
87
|
if (v && typeof v === 'object' && v.features && typeof v.features === 'object') {
|
|
80
88
|
const map = v.features
|
|
81
89
|
if (typeof map.models === 'boolean' && typeof map.modelconfig !== 'boolean') {
|
|
@@ -115,25 +123,47 @@ export function apply(ctx) {
|
|
|
115
123
|
}
|
|
116
124
|
}
|
|
117
125
|
|
|
118
|
-
// 自有
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
126
|
+
// 自有 Config 段(profile 条目 id dsh-dock):功能开关与各功能配置的持久化。
|
|
127
|
+
// 读走本地镜像(apply 的 config / loader/volatile-update),写经 settings.mutate。
|
|
128
|
+
// 关键顺序:先跑完一次性迁移(可能改写 features),再应用初始开关——
|
|
129
|
+
// 否则首次启动时 bind 的还是空默认值,migrate 尚未落盘就 setEnabled,
|
|
130
|
+
// 面板显示 persisted=true 却全部「未启用」、路由 404/405(2026-09-23 事故)。
|
|
122
131
|
let initialTogglesApplied = false
|
|
123
132
|
ctx.inject(['settings'], (sctx) => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
// 0.1.7-alpha.1 起 settings.register 已移除——不再调用;导出 Config 即注册。
|
|
134
|
+
Promise.resolve()
|
|
135
|
+
// 通知配置从 animation 段搬到 notify 段(【任务通知】独立成模块)。
|
|
136
|
+
// 必须在任何面板保存动作之前跑——animation 模块保存时整段写回,旧字段会被覆盖丢失。
|
|
137
|
+
.then(() => migrateNotifyConfig(sctx))
|
|
138
|
+
// 宿主侧【模型设置】功能 id models → modelconfig(开关表键名对齐客户端)。
|
|
139
|
+
.then(() => migrateModelsFeatureId(sctx))
|
|
140
|
+
// settings.yaml.imported 里停住的功能开关表(Config 导出前 import 失败的那段)。
|
|
141
|
+
.then(() => migrateImportedFeatures(sctx))
|
|
142
|
+
.then(() => {
|
|
143
|
+
// 迁移可能 mutate 过 Config:刷新镜像后再读开关表。
|
|
144
|
+
try { bindDockConfig(config) } catch { /* 保持旧镜像 */ }
|
|
145
|
+
if (initialTogglesApplied) return
|
|
146
|
+
initialTogglesApplied = true
|
|
147
|
+
const persisted = persistedFeatureMap()
|
|
148
|
+
for (const f of FEATURES) {
|
|
149
|
+
const saved = persisted ? persisted[f.id] : undefined
|
|
150
|
+
setEnabled(f.id, typeof saved === 'boolean' ? saved : !!f.defaultEnabled)
|
|
151
|
+
}
|
|
152
|
+
console.log('[dsh-dock] initial toggles applied:', FEATURES
|
|
153
|
+
.filter((f) => state.get(f.id).enabled)
|
|
154
|
+
.map((f) => f.id).join(', ') || '(none)')
|
|
155
|
+
})
|
|
156
|
+
.catch((e) => {
|
|
157
|
+
console.error('[dsh-dock] settings init/migration failed:', (e && e.message) || e)
|
|
158
|
+
// 迁移失败也要尝试按当前镜像启用,避免整坞空白。
|
|
159
|
+
if (initialTogglesApplied) return
|
|
160
|
+
initialTogglesApplied = true
|
|
161
|
+
const persisted = persistedFeatureMap()
|
|
162
|
+
for (const f of FEATURES) {
|
|
163
|
+
const saved = persisted ? persisted[f.id] : undefined
|
|
164
|
+
setEnabled(f.id, typeof saved === 'boolean' ? saved : !!f.defaultEnabled)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
137
167
|
})
|
|
138
168
|
|
|
139
169
|
// 功能开关管理路由(无条件注册):GET 查询各功能宿主侧状态;POST 同步开关——
|
|
@@ -152,10 +182,11 @@ export function apply(ctx) {
|
|
|
152
182
|
const enabled = !!(body && body.enabled)
|
|
153
183
|
if (!state.has(id)) return sendJson(res, 404, { ok: false, error: { message: `未知功能:${id}` } })
|
|
154
184
|
setEnabled(id, enabled)
|
|
155
|
-
|
|
156
|
-
if (settings && typeof settings.mutate === 'function') {
|
|
185
|
+
try {
|
|
157
186
|
const features = Object.assign({}, persistedFeatureMap() || {}, { [id]: enabled })
|
|
158
|
-
await
|
|
187
|
+
await mutateDockSection(ctx, ['features'], features)
|
|
188
|
+
} catch (e) {
|
|
189
|
+
console.error('[dsh-dock] feature toggle persist failed:', (e && e.message) || e)
|
|
159
190
|
}
|
|
160
191
|
}
|
|
161
192
|
const persisted = persistedFeatureMap() || {}
|