dsh-dock 0.5.0 → 0.6.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/README.md +11 -9
- package/client.js +632 -352
- package/features/animation/host.js +150 -24
- package/features/animation/view.jsx +311 -131
- package/features/modelconfig/view.js +55 -14
- package/features/theme/view.js +35 -10
- package/features/tokenlog/view.jsx +3 -1
- package/features/visionproxy/host.js +24 -10
- package/package.json +2 -2
- package/src/client.jsx +20 -14
- package/src/host-core.js +3 -0
|
@@ -36,10 +36,10 @@ function rpcCall(method, args) {
|
|
|
36
36
|
// ---------- 动画模式 ----------
|
|
37
37
|
const EFFECT_MODES = [
|
|
38
38
|
{ id: "flow", name: "流光细线", desc: "顶部细线流光往返,速度随任务吞吐加快" },
|
|
39
|
-
{ id: "breathe", name: "呼吸光点", desc: "
|
|
39
|
+
{ id: "breathe", name: "呼吸光点", desc: "醒目呼吸光点带扩散光晕:颜色随任务阶段(思考蓝/输出绿/代码橙/查资料青),呼吸随吞吐加快" },
|
|
40
40
|
{ id: "ring", name: "轨道光环", desc: "细环绕圆点旋转,转速随任务速度" },
|
|
41
41
|
{ id: "orbit", name: "环屏巡航", desc: "一颗光点沿屏幕边缘巡航整圈,醒目不遮挡" },
|
|
42
|
-
{ id: "robot", name: "桌面伙伴", desc: "
|
|
42
|
+
{ id: "robot", name: "桌面伙伴", desc: "真人化双工位:查资料看左屏、写入/改代码移到右屏;座椅和转头随实时任务节奏加速" },
|
|
43
43
|
{ id: "matrix", name: "代码雨", desc: "字符沿屏幕缓落如数据流,速度随任务吞吐,克制的低透明度" },
|
|
44
44
|
{ id: "stars", name: "星野", desc: "细碎星点缓慢飘移闪烁,安静耐看的背景氛围" },
|
|
45
45
|
{ id: "aurora", name: "极光", desc: "屏幕顶部柔光带缓慢呼吸流动,像极光拂过" },
|
|
@@ -49,6 +49,9 @@ const EFFECT_MODES = [
|
|
|
49
49
|
// reasoning-delta=think、text-delta=write、tool 名分类=search/code)
|
|
50
50
|
const PHASE_LABELS = { think: "思考中", write: "输出中", code: "编写代码", search: "查资料" };
|
|
51
51
|
function phaseLabel(p) { return PHASE_LABELS[p] || "工作中"; }
|
|
52
|
+
// 阶段配色(徽标呼吸点/光环随任务阶段变色,--dkan-phase 下发到 CSS)
|
|
53
|
+
const PHASE_COLORS = { think: "#60a5fa", write: "#34d399", code: "#fbbf24", search: "#2dd4bf" };
|
|
54
|
+
function phaseColor(p) { return PHASE_COLORS[p] || "#4d9fff"; }
|
|
52
55
|
|
|
53
56
|
// ---------- 结束原因 ----------
|
|
54
57
|
const END_LABELS = {
|
|
@@ -172,6 +175,7 @@ function Box3(props) {
|
|
|
172
175
|
|
|
173
176
|
function Monitor3(props) {
|
|
174
177
|
const w = props.w, h = props.h;
|
|
178
|
+
const searchScreen = props.mode === "search";
|
|
175
179
|
// 屏幕内容:10 行代码(5 行×2 循环)+ 光标,整体向上滚动(速度随 --dkan-speed)
|
|
176
180
|
const lines = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
|
|
177
181
|
return (
|
|
@@ -179,77 +183,104 @@ function Monitor3(props) {
|
|
|
179
183
|
style={{ left: props.x - w / 2, top: props.y - h / 2, width: w, height: h, transform: "translateZ(" + (props.z || -3) + "px) rotateY(" + (props.ry || 0) + "deg)" }}>
|
|
180
184
|
<Box3 w={w} h={h} d={5} cls="dk3-frame" x={w / 2} y={h / 2} />
|
|
181
185
|
<span className="dk3-screen" style={{ width: w - 4, height: h - 4, transform: "translate(-50%,-50%) translateZ(3.1px)" }}>
|
|
182
|
-
<span className="dk3-
|
|
186
|
+
{searchScreen ? <span className="dk3-search-ui">
|
|
187
|
+
<span className="dk3-search-box"><b /></span>
|
|
188
|
+
<span className="dk3-search-results">{lines.slice(0, 6).map((n, i) => <i key={i} />)}</span>
|
|
189
|
+
</span> : <span className="dk3-code">
|
|
183
190
|
{lines.map((n, i) => <i key={i} />)}
|
|
184
191
|
<span className="dk3-cur" />
|
|
185
|
-
</span>
|
|
192
|
+
</span>}
|
|
186
193
|
</span>
|
|
187
194
|
</span>
|
|
188
195
|
);
|
|
189
196
|
}
|
|
190
197
|
|
|
191
|
-
// 3D 动漫人物:更拟人的比例与细节(圆角头/ volumetric
|
|
198
|
+
// 3D 动漫人物:更拟人的比例与细节(圆角头/ volumetric 头发/五官/脖颈/弯肘/腿脚鞋)。
|
|
199
|
+
// 紧凑双工位:左侧专做资料检索,右侧专做写作/改文件;顶部小屏放任务状态。
|
|
200
|
+
// 人物坐滚轮椅在两侧工位间滑动,思考时停在两屏之间。
|
|
192
201
|
function RobotScene(props) {
|
|
193
202
|
const phase = props && props.phase ? props.phase : "code";
|
|
203
|
+
const tasks = Array.isArray(props && props.tasks) ? props.tasks : null;
|
|
204
|
+
const n = tasks ? Math.max(1, Math.min(3, tasks.length)) : 1;
|
|
205
|
+
// 左工位只做资料检索;正文输出和工具写入都在右工位,思考回到中位。
|
|
206
|
+
const station = phase === "search" ? "left" : (phase === "write" || phase === "code" ? "right" : "center");
|
|
194
207
|
return (
|
|
195
|
-
<div className="dkan-bot-scene" data-phase={phase} aria-hidden="true">
|
|
208
|
+
<div className="dkan-bot-scene" data-phase={phase} data-station={station} data-tasks={tasks ? String(n) : undefined} aria-hidden="true">
|
|
196
209
|
<div className="dk3-world">
|
|
197
|
-
{/*
|
|
198
|
-
<Box3 w={
|
|
199
|
-
<Box3 w={5} h={
|
|
200
|
-
<Box3 w={5} h={
|
|
201
|
-
{/*
|
|
202
|
-
<Monitor3 w={34} h={24} x={140} y={
|
|
203
|
-
<Monitor3 w={
|
|
204
|
-
<Monitor3 w={28} h={
|
|
205
|
-
|
|
206
|
-
{
|
|
207
|
-
<Box3 w={
|
|
208
|
-
<Box3 w={4} h={5} d={4} cls="dk3-mug" x={
|
|
209
|
-
{
|
|
210
|
+
{/* 紧凑单人书桌:只留左右两个真实工作位,消除左侧空桌面。 */}
|
|
211
|
+
<Box3 w={134} h={7} d={38} cls="dk3-desk" x={145} y={76} />
|
|
212
|
+
<Box3 w={5} h={24} d={30} cls="dk3-metal dk3-leg" x={84} y={89} />
|
|
213
|
+
<Box3 w={5} h={24} d={30} cls="dk3-metal dk3-leg" x={206} y={89} />
|
|
214
|
+
{/* 左检索、右编码,顶部小屏显示任务进度。 */}
|
|
215
|
+
<Monitor3 w={34} h={24} x={140} y={59} ry={20} cls="left" mode="search" />
|
|
216
|
+
<Monitor3 w={42} h={30} x={180} y={57} ry={-16} cls="right" />
|
|
217
|
+
<Monitor3 w={28} h={18} x={160} y={31} ry={0} cls="top" />
|
|
218
|
+
{/* 两套键鼠紧贴相应屏幕,中央仅保留咖啡杯。 */}
|
|
219
|
+
<Box3 w={16} h={2.5} d={9} cls="dk3-metal dk3-kb3" x={130} y={71} z={14} />
|
|
220
|
+
<Box3 w={4} h={2.5} d={6} cls="dk3-metal dk3-mouse3" x={141} y={71} z={14} />
|
|
221
|
+
<Box3 w={4} h={5} d={4} cls="dk3-mug" x={154} y={69} z={14} />
|
|
222
|
+
<Box3 w={16} h={2.5} d={9} cls="dk3-metal dk3-kb3" x={170} y={71} z={14} />
|
|
223
|
+
<Box3 w={4} h={2.5} d={6} cls="dk3-metal dk3-mouse3" x={181} y={71} z={14} />
|
|
224
|
+
{/* 人物组:按当前任务在左检索/中思考/右编码两个工位间平滑移动。 */}
|
|
210
225
|
<div className="dk3-person">
|
|
211
|
-
{/*
|
|
212
|
-
<Box3 w={4} h={
|
|
213
|
-
<Box3 w={
|
|
214
|
-
<Box3 w={3} h={
|
|
215
|
-
<Box3 w={
|
|
226
|
+
{/* 椅子:靠背只到肩胛、降低支柱和底盘,贴近真实坐姿。 */}
|
|
227
|
+
<Box3 w={4} h={20} d={20} cls="dk3-chairback" x={2} y={47} />
|
|
228
|
+
<Box3 w={18} h={4} d={20} cls="dk3-chairseat" x={12} y={59} />
|
|
229
|
+
<Box3 w={3} h={9} d={3} cls="dk3-metal" x={12} y={66} />
|
|
230
|
+
<Box3 w={13} h={2} d={13} cls="dk3-metal" x={12} y={71} />
|
|
231
|
+
{/* 椅子滚轮(左右各一,滑动时可见) */}
|
|
232
|
+
<Box3 w={3} h={3} d={3} cls="dk3-wheel" x={6} y={74} />
|
|
233
|
+
<Box3 w={3} h={3} d={3} cls="dk3-wheel" x={18} y={74} />
|
|
216
234
|
{/* 腿:大腿/小腿/鞋 */}
|
|
217
|
-
<Box3 w={
|
|
218
|
-
<Box3 w={4} h={
|
|
219
|
-
<Box3 w={6} h={3} d={5} cls="dk3-shoe" x={28} y={
|
|
220
|
-
{/*
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<
|
|
244
|
-
<Box3 w={3.5} h={3} d={3} cls="dk3-skin dk3-hand" x={12.5} y={2} />
|
|
235
|
+
<Box3 w={12} h={5} d={9} cls="dk3-pants" x={20} y={54} />
|
|
236
|
+
<Box3 w={4} h={10} d={4} cls="dk3-pants" x={26} y={62} />
|
|
237
|
+
<Box3 w={6} h={3} d={5} cls="dk3-shoe" x={28} y={70} />
|
|
238
|
+
{/* 上身整体始终面向桌面;阶段动作只改变前倾、肩肘腕和视线。 */}
|
|
239
|
+
<div className="dk3-upper3">
|
|
240
|
+
{/* 肩线、胸腔和腰部做成上宽下窄,摆脱直筒积木感。 */}
|
|
241
|
+
<Box3 w={18} h={5} d={12} cls="dk3-hood dk3-shoulders3" x={13} y={30} />
|
|
242
|
+
<Box3 w={15} h={14} d={11} cls="dk3-hood dk3-torso dk3-chest3" x={12} y={38} />
|
|
243
|
+
<Box3 w={12} h={7} d={10} cls="dk3-hood dk3-torso dk3-waist3" x={12} y={47} />
|
|
244
|
+
<Box3 w={5} h={8} d={9} cls="dk3-hood dk3-hoodbump" x={5} y={32} />
|
|
245
|
+
{/* 颈部加长并与下颌、衣领重叠,避免悬空。 */}
|
|
246
|
+
<Box3 w={5} h={7} d={5} cls="dk3-skin dk3-neck3" x={14} y={24} />
|
|
247
|
+
<Box3 w={8} h={3} d={8} cls="dk3-collar3" x={14} y={28} />
|
|
248
|
+
{/* 头组与颈部同轴:颅骨 + 收窄下颌 + 短侧渐层发型。 */}
|
|
249
|
+
<div className="dk3-head3">
|
|
250
|
+
<Box3 w={12} h={11} d={11} cls="dk3-skin dk3-cranium" x={8} y={8}>
|
|
251
|
+
<span className="dk3-brows" style={{ width: 9, height: 4, transform: "translate(-50%,-50%) rotateY(90deg) translateZ(5.8px)" }}><i /><i /></span>
|
|
252
|
+
<span className="dk3-eyes" style={{ width: 9, height: 6, transform: "translate(-50%,-50%) rotateY(90deg) translateZ(5.7px)" }}><i /><i /></span>
|
|
253
|
+
<span className="dk3-nose" style={{ transform: "translate(-50%,-50%) rotateY(90deg) translateZ(6.1px)" }} />
|
|
254
|
+
</Box3>
|
|
255
|
+
<Box3 w={9} h={7} d={9} cls="dk3-skin dk3-jaw3" x={8.5} y={14}>
|
|
256
|
+
<span className="dk3-mouth" style={{ transform: "translate(-50%,-50%) rotateY(90deg) translateZ(4.7px)" }} />
|
|
257
|
+
</Box3>
|
|
258
|
+
<Box3 w={12} h={5} d={12} cls="dk3-hair dk3-hair-crown" x={8} y={3.5} />
|
|
259
|
+
<Box3 w={4} h={8} d={11} cls="dk3-hair dk3-hair-back" x={3} y={7.5} />
|
|
260
|
+
<span className="dk3-fringe3"><i /><i /><i /></span>
|
|
261
|
+
<span className="dk3-ear" />
|
|
245
262
|
</div>
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
263
|
+
{/* 近侧手臂:肩、肘、腕、手指分段动作。 */}
|
|
264
|
+
<div className="dk3-arm3 dk3-near">
|
|
265
|
+
<Box3 w={4} h={11} d={4} cls="dk3-hood dk3-uarm" x={2} y={6} />
|
|
266
|
+
<div className="dk3-elbow">
|
|
267
|
+
<Box3 w={10} h={3.5} d={3.5} cls="dk3-hood dk3-farm" x={5.5} y={2} />
|
|
268
|
+
<div className="dk3-wrist3">
|
|
269
|
+
<Box3 w={3.5} h={3} d={3} cls="dk3-skin dk3-hand" x={2} y={2} />
|
|
270
|
+
<span className="dk3-fingers3"><i /><i /></span>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
{/* 远侧手臂:在桌面深处,和近侧手错开节奏。 */}
|
|
275
|
+
<div className="dk3-arm3 dk3-far">
|
|
276
|
+
<Box3 w={4} h={11} d={4} cls="dk3-hood dk3-uarm" x={2} y={6} />
|
|
277
|
+
<div className="dk3-elbow">
|
|
278
|
+
<Box3 w={10} h={3.5} d={3.5} cls="dk3-hood dk3-farm" x={5.5} y={2} />
|
|
279
|
+
<div className="dk3-wrist3">
|
|
280
|
+
<Box3 w={3.5} h={3} d={3} cls="dk3-skin dk3-hand" x={2} y={2} />
|
|
281
|
+
<span className="dk3-fingers3"><i /><i /></span>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
253
284
|
</div>
|
|
254
285
|
</div>
|
|
255
286
|
</div>
|
|
@@ -477,7 +508,7 @@ export function AnimationOverlay(props) {
|
|
|
477
508
|
taskCountRef.current = n;
|
|
478
509
|
}, [snap.status, pushBurst]);
|
|
479
510
|
|
|
480
|
-
//
|
|
511
|
+
// 轮询:任务中 850ms,实时捕捉 chunk/工具活动;空闲 6s / 出错 15s;页面切回立即刷新。
|
|
481
512
|
useEffect(() => {
|
|
482
513
|
let stopped = false;
|
|
483
514
|
let timer = null;
|
|
@@ -485,7 +516,7 @@ export function AnimationOverlay(props) {
|
|
|
485
516
|
await animationStore.refresh();
|
|
486
517
|
if (stopped) return;
|
|
487
518
|
const s = animationStore.snap;
|
|
488
|
-
timer = setTimeout(loop, s.error ? 15000 : (s.status && s.status.active && s.status.active.length > 0 ?
|
|
519
|
+
timer = setTimeout(loop, s.error ? 15000 : (s.status && s.status.active && s.status.active.length > 0 ? 850 : 6000));
|
|
489
520
|
};
|
|
490
521
|
loop();
|
|
491
522
|
const onVisible = () => {
|
|
@@ -578,6 +609,10 @@ export function AnimationOverlay(props) {
|
|
|
578
609
|
const active = st && st.active ? st.active : [];
|
|
579
610
|
const animOn = !!(cfg && cfg.animationEnabled && active.length > 0);
|
|
580
611
|
const mode = cfg && cfg.effectMode ? cfg.effectMode : "flow";
|
|
612
|
+
// 机器人阶段:取最近发生阶段变化的活跃任务。
|
|
613
|
+
const phase = active.length > 0
|
|
614
|
+
? active.reduce((best, x) => (!best || (x.phaseAt || 0) > (best.phaseAt || 0) ? x : best), null).phase
|
|
615
|
+
: "think";
|
|
581
616
|
useTicker(ctx, animOn);
|
|
582
617
|
// 徽标计时:以最早开始的任务为基准
|
|
583
618
|
const now = Date.now();
|
|
@@ -588,23 +623,22 @@ export function AnimationOverlay(props) {
|
|
|
588
623
|
setPanelOpen(panelNav.open);
|
|
589
624
|
return subscribePanel(() => setPanelOpen(panelNav.open));
|
|
590
625
|
}, []);
|
|
591
|
-
//
|
|
626
|
+
// 任务速度:优先使用 host 从 chunk/tool 实时累积的活动脉冲;
|
|
627
|
+
// token usage 只在完整消息后才到达,不能再作为唯一速度来源。
|
|
592
628
|
const [speed, setSpeed] = useState(1);
|
|
593
|
-
const speedRef = useRef({
|
|
629
|
+
const speedRef = useRef({ ticks: -1, at: 0 });
|
|
594
630
|
useEffect(() => {
|
|
595
|
-
if (!st || active.length === 0) { speedRef.current = {
|
|
596
|
-
const
|
|
631
|
+
if (!st || active.length === 0) { speedRef.current = { ticks: -1, at: 0 }; setSpeed(1); return; }
|
|
632
|
+
const ticks = active.reduce((a, x) => a + (x.motionTicks || 0), 0);
|
|
597
633
|
const t = Date.now();
|
|
598
634
|
const prev = speedRef.current;
|
|
599
|
-
if (prev.
|
|
600
|
-
const rate = Math.max(0, (
|
|
601
|
-
|
|
602
|
-
|
|
635
|
+
if (prev.ticks < 0 || t - prev.at < 300) { speedRef.current = { ticks, at: t }; return; }
|
|
636
|
+
const rate = Math.max(0, (ticks - prev.ticks) / ((t - prev.at) / 1000));
|
|
637
|
+
const phaseBoost = phase === "write" || phase === "code" ? .28 : (phase === "search" ? .18 : 0);
|
|
638
|
+
const next = Math.max(.9, Math.min(4, Number((.9 + rate / 14 + phaseBoost).toFixed(2))));
|
|
639
|
+
setSpeed((current) => Math.abs(current - next) < .03 ? current : next);
|
|
640
|
+
speedRef.current = { ticks, at: t };
|
|
603
641
|
}, [snap.status]);
|
|
604
|
-
// 机器人阶段:取最近发生阶段变化的活跃任务
|
|
605
|
-
const phase = active.length > 0
|
|
606
|
-
? active.reduce((best, x) => (!best || (x.phaseAt || 0) > (best.phaseAt || 0) ? x : best), null).phase
|
|
607
|
-
: "think";
|
|
608
642
|
const ambientOn = animOn && !panelOpen;
|
|
609
643
|
const robotScaleFromConfig = Math.max(.85, Math.min(2.2, Number(cfg && cfg.robotScale) || 1.35));
|
|
610
644
|
const speedStyle = { "--dkan-speed": Number(speed).toFixed(2) };
|
|
@@ -721,7 +755,7 @@ export function AnimationOverlay(props) {
|
|
|
721
755
|
{ambientOn && mode === "orbit" ? <div className="dkan-orbit" style={speedStyle} aria-hidden="true" /> : null}
|
|
722
756
|
{/* 交互反馈层:纸飞机/火花/流光/微光/光涌/彩带(与动画开关独立,只要功能启用即回应操作) */}
|
|
723
757
|
<BurstLayer bursts={bursts} />
|
|
724
|
-
{/*
|
|
758
|
+
{/* 桌面伙伴:背侧视角紧凑双工位人物,阶段与任务同步;整卡可拖到任意位置(robot 模式) */}
|
|
725
759
|
{ambientOn && mode === "robot" ? (
|
|
726
760
|
<div className="dkan-botcard" role="button" tabIndex={0} style={botCardStyle}
|
|
727
761
|
title={active.length + " 个任务 · " + phaseLabel(phase) + " · 点击查看任务动画页 · 可拖动到任意位置"}
|
|
@@ -731,7 +765,7 @@ export function AnimationOverlay(props) {
|
|
|
731
765
|
onPointerCancel={onBotPointerUp}
|
|
732
766
|
onClick={onBotClick}
|
|
733
767
|
onKeyDown={(e) => { if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); onBotClick(); } }}>
|
|
734
|
-
<RobotScene phase={phase} />
|
|
768
|
+
<RobotScene phase={phase} tasks={active} />
|
|
735
769
|
<span className="dkan-bot-cap">
|
|
736
770
|
<span className="n">{active.length}</span> 个任务{elapsed ? " · " + elapsed : ""} · {phaseLabel(phase)}
|
|
737
771
|
</span>
|
|
@@ -750,16 +784,18 @@ export function AnimationOverlay(props) {
|
|
|
750
784
|
}} />
|
|
751
785
|
</div>
|
|
752
786
|
) : null}
|
|
753
|
-
{/* 右下角状态徽标:dot 随模式变化(breathe/ring
|
|
787
|
+
{/* 右下角状态徽标:dot 随模式变化(breathe/ring 装饰),颜色随任务阶段、呼吸随吞吐;点击进功能坞动画页 */}
|
|
754
788
|
{ambientOn && mode !== "robot" ? (
|
|
755
|
-
<button type="button" className="dkan-badge"
|
|
756
|
-
|
|
789
|
+
<button type="button" className={"dkan-badge" + (mode === "breathe" ? " dkan-badge-breathe" : "")}
|
|
790
|
+
style={Object.assign({}, speedStyle, { "--dkan-phase": phaseColor(phase) })}
|
|
791
|
+
title={active.length + " 个任务进行中 · " + phaseLabel(phase) + " · 点击查看任务动画页"}
|
|
757
792
|
onClick={() => openPanel("animation")}>
|
|
758
793
|
<span className="dkan-dotwrap">
|
|
759
794
|
<span className="dkan-dot" />
|
|
795
|
+
{mode === "breathe" ? <span className="dkan-halo" /> : null}
|
|
760
796
|
{mode === "ring" ? <span className="dkan-ring" /> : null}
|
|
761
797
|
</span>
|
|
762
|
-
<span className="dkan-badge-txt"><span className="n">{active.length}</span> 个任务{elapsed ? " · " + elapsed : ""}</span>
|
|
798
|
+
<span className="dkan-badge-txt"><span className="n">{active.length}</span> 个任务{elapsed ? " · " + elapsed : ""} · {phaseLabel(phase)}</span>
|
|
763
799
|
</button>
|
|
764
800
|
) : null}
|
|
765
801
|
{/* 完成瞬间的一次性流光(成功绿 / 异常红),动画结束自动清场 */}
|
|
@@ -823,7 +859,7 @@ function ModePreview({ id }) {
|
|
|
823
859
|
}
|
|
824
860
|
return (
|
|
825
861
|
<span className="dkan-prev">
|
|
826
|
-
<span className="dkan-dotwrap dkan-breathe"><span className="dkan-dot" /></span>
|
|
862
|
+
<span className="dkan-dotwrap dkan-breathe"><span className="dkan-dot" /><span className="dkan-halo" /></span>
|
|
827
863
|
</span>
|
|
828
864
|
);
|
|
829
865
|
}
|
|
@@ -856,15 +892,18 @@ function AnimationView(props) {
|
|
|
856
892
|
const [cfg, setCfg] = useState(null); // null = 尚未加载
|
|
857
893
|
const [saveErr, setSaveErr] = useState("");
|
|
858
894
|
const [testing, setTesting] = useState(false);
|
|
859
|
-
const [testState, setTestState] = useState(null); // { ok, msg }
|
|
895
|
+
const [testState, setTestState] = useState(null); // { ok, msg } 机器人测试结果
|
|
896
|
+
const [feishuTestState, setFeishuTestState] = useState(null); // { ok, msg } 飞书测试结果
|
|
897
|
+
const [feishuTesting, setFeishuTesting] = useState(false);
|
|
860
898
|
const cfgRef = useRef(null);
|
|
861
899
|
const pendingSavesRef = useRef(0); // 进行中的保存(轮询回包不覆盖乐观值)
|
|
862
|
-
const editingWebhookRef = useRef(false); // Webhook 输入中(轮询不覆盖草稿)
|
|
900
|
+
const editingWebhookRef = useRef(false); // 钉钉 Webhook 输入中(轮询不覆盖草稿)
|
|
901
|
+
const editingFeishuRef = useRef(false); // 飞书 Webhook 输入中(轮询不覆盖草稿)
|
|
863
902
|
// 拉到新配置(含保存回包)后同步本地编辑态;保存中/输入中不覆盖
|
|
864
903
|
useEffect(() => {
|
|
865
904
|
const c = snap.status && snap.status.config;
|
|
866
905
|
if (!c) return;
|
|
867
|
-
if (pendingSavesRef.current > 0 || editingWebhookRef.current) return;
|
|
906
|
+
if (pendingSavesRef.current > 0 || editingWebhookRef.current || editingFeishuRef.current) return;
|
|
868
907
|
if (c !== cfgRef.current) {
|
|
869
908
|
cfgRef.current = c;
|
|
870
909
|
setCfg(Object.assign({}, c));
|
|
@@ -901,6 +940,18 @@ function AnimationView(props) {
|
|
|
901
940
|
setCfg(Object.assign({}, c));
|
|
902
941
|
}
|
|
903
942
|
};
|
|
943
|
+
// 飞书 Webhook 草稿保存
|
|
944
|
+
const saveFeishuWebhook = async () => {
|
|
945
|
+
if (!editingFeishuRef.current) return;
|
|
946
|
+
const hook = String(cfg.feishuWebhook || "").trim();
|
|
947
|
+
await patch({ feishuWebhook: hook });
|
|
948
|
+
editingFeishuRef.current = false;
|
|
949
|
+
const c = animationStore.snap.status && animationStore.snap.status.config;
|
|
950
|
+
if (c && c !== cfgRef.current) {
|
|
951
|
+
cfgRef.current = c;
|
|
952
|
+
setCfg(Object.assign({}, c));
|
|
953
|
+
}
|
|
954
|
+
};
|
|
904
955
|
// 钉钉测试:草稿未保存先保存,再发测试消息
|
|
905
956
|
const runDingtalkTest = async () => {
|
|
906
957
|
setTesting(true);
|
|
@@ -925,6 +976,30 @@ function AnimationView(props) {
|
|
|
925
976
|
setTesting(false);
|
|
926
977
|
}
|
|
927
978
|
};
|
|
979
|
+
// 飞书测试:草稿未保存先保存,再发测试消息
|
|
980
|
+
const runFeishuTest = async () => {
|
|
981
|
+
setFeishuTesting(true);
|
|
982
|
+
setFeishuTestState(null);
|
|
983
|
+
try {
|
|
984
|
+
if (editingFeishuRef.current) {
|
|
985
|
+
const hook = String(cfg.feishuWebhook || "").trim();
|
|
986
|
+
if (!hook) throw new Error("请先填写 Webhook 地址");
|
|
987
|
+
const d = await rpcCall("config", { feishuWebhook: hook });
|
|
988
|
+
animationStore.applyConfig(d && d.config);
|
|
989
|
+
editingFeishuRef.current = false;
|
|
990
|
+
cfgRef.current = (d && d.config) || cfgRef.current;
|
|
991
|
+
setCfg(Object.assign({}, cfg, { feishuWebhook: hook }));
|
|
992
|
+
}
|
|
993
|
+
const r = await rpcCall("test", { target: "feishu" });
|
|
994
|
+
setFeishuTestState(r && r.sent
|
|
995
|
+
? { ok: true, msg: "测试消息已发送,去群里看看" }
|
|
996
|
+
: { ok: false, msg: (r && r.error) || "发送失败" });
|
|
997
|
+
} catch (e) {
|
|
998
|
+
setFeishuTestState({ ok: false, msg: (e && e.message) || String(e) });
|
|
999
|
+
} finally {
|
|
1000
|
+
setFeishuTesting(false);
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
928
1003
|
const enableSystemNotify = async (next) => {
|
|
929
1004
|
if (next && typeof Notification !== "undefined" && Notification.permission !== "granted") {
|
|
930
1005
|
try {
|
|
@@ -1115,6 +1190,46 @@ function AnimationView(props) {
|
|
|
1115
1190
|
<div className="dkan-note">机器人创建:钉钉群 → 设置 → 智能群助手 → 添加机器人 → 自定义(Webhook),
|
|
1116
1191
|
安全设置选「自定义关键词」填「任务」或「dsh」(推送标题含「任务」即可命中)。</div>
|
|
1117
1192
|
</div> : <div className="dkan-note">未开启——任务结束不推送钉钉。</div>}
|
|
1193
|
+
</div>,
|
|
1194
|
+
<div key="feishu" className="dkan-sec">
|
|
1195
|
+
<div className="dkan-sec-head">
|
|
1196
|
+
<span className="dkan-sec-title">飞书推送</span>
|
|
1197
|
+
<span className="dkan-sec-sub">任务结束推送到飞书群机器人(宿主直发,浏览器关着也能推;事件跟随上方完成/异常开关)</span>
|
|
1198
|
+
<span className="dkan-sec-sw">
|
|
1199
|
+
<span className={"dkan-sec-swlabel" + (cfg.feishuEnabled ? " on" : "")}>{cfg.feishuEnabled ? "已开启" : "已关闭"}</span>
|
|
1200
|
+
<button type="button" className={"dock-sw" + (cfg.feishuEnabled ? " on" : "")}
|
|
1201
|
+
role="switch" aria-checked={cfg.feishuEnabled} aria-label="开关飞书推送"
|
|
1202
|
+
title={cfg.feishuEnabled ? "关闭飞书推送" : "开启飞书推送"}
|
|
1203
|
+
onClick={() => patch({ feishuEnabled: !cfg.feishuEnabled })} />
|
|
1204
|
+
</span>
|
|
1205
|
+
</div>
|
|
1206
|
+
{cfg.feishuEnabled ? <div className="dkan-rows-narrow">
|
|
1207
|
+
<div className="dkan-row dkan-row-webhook">
|
|
1208
|
+
<span className="dkan-row-label">Webhook</span>
|
|
1209
|
+
<input type="text" className="dkan-input" spellCheck={false}
|
|
1210
|
+
value={cfg.feishuWebhook || ""}
|
|
1211
|
+
placeholder="https://open.feishu.cn/open-apis/bot/v2/hook/…"
|
|
1212
|
+
onChange={(e) => {
|
|
1213
|
+
editingFeishuRef.current = true;
|
|
1214
|
+
setCfg(Object.assign({}, cfg, { feishuWebhook: e.target.value }));
|
|
1215
|
+
}} />
|
|
1216
|
+
<button type="button" className="dkan-btn" disabled={!editingFeishuRef.current}
|
|
1217
|
+
onClick={saveFeishuWebhook}>
|
|
1218
|
+
{editingFeishuRef.current ? "保存" : "已保存"}
|
|
1219
|
+
</button>
|
|
1220
|
+
</div>
|
|
1221
|
+
<div className="dkan-row">
|
|
1222
|
+
<span className="dkan-row-label">连通测试</span>
|
|
1223
|
+
<button type="button" className="dkan-btn" disabled={feishuTesting} onClick={runFeishuTest}>
|
|
1224
|
+
{feishuTesting ? "发送中…" : "发送测试消息"}
|
|
1225
|
+
</button>
|
|
1226
|
+
{feishuTestState ? <span className={"dkan-row-sub" + (feishuTestState.ok ? " dkan-ok" : " dkan-err")}>
|
|
1227
|
+
{feishuTestState.ok ? "✓ " : "✗ "}{feishuTestState.msg}
|
|
1228
|
+
</span> : <span className="dkan-row-sub">用当前保存的 Webhook 发一条测试消息</span>}
|
|
1229
|
+
</div>
|
|
1230
|
+
<div className="dkan-note">机器人创建:飞书群 → 设置 → 群机器人 → 添加机器人 → 自定义机器人(获取 Webhook 地址);
|
|
1231
|
+
安全设置如选「自定义关键词」填「任务」或「dsh」(推送标题含「任务」即可命中)。</div>
|
|
1232
|
+
</div> : <div className="dkan-note">未开启——任务结束不推送飞书。</div>}
|
|
1118
1233
|
</div>
|
|
1119
1234
|
);
|
|
1120
1235
|
}
|
|
@@ -1169,6 +1284,8 @@ const css = [
|
|
|
1169
1284
|
"@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}}",
|
|
1170
1285
|
// ===== 氛围动效 =====
|
|
1171
1286
|
".dkan-amb{position:fixed;inset:0;z-index:9989;pointer-events:none;overflow:hidden;}",
|
|
1287
|
+
// pointer-events 不继承:全屏装饰层的子粒子(星点/流星/极光/火花/彩带)必须各自透明于点击
|
|
1288
|
+
".dkan-amb *,.dkan-fxwrap *{pointer-events:none;}",
|
|
1172
1289
|
// 代码雨:字符列缓落(提亮加大,速度随吞吐)
|
|
1173
1290
|
".dkan-matrix span{position:absolute;top:-12%;writing-mode:vertical-rl;font-family:var(--ds-font-family-code,monospace);color:var(--dsw-alias-accent,#4d9fff);text-shadow:0 0 6px color-mix(in srgb,var(--dsw-alias-accent,#4d9fff) 60%,transparent);animation:dkan-fall linear infinite;will-change:transform;}",
|
|
1174
1291
|
"@keyframes dkan-fall{to{transform:translateY(125vh)}}",
|
|
@@ -1215,10 +1332,16 @@ const css = [
|
|
|
1215
1332
|
".dkan-badge .n{color:var(--dsw-alias-label-primary);font-weight:600;}",
|
|
1216
1333
|
"@keyframes dkan-rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}",
|
|
1217
1334
|
// 徽标圆点与两种动效(呼吸 / 轨道环)
|
|
1218
|
-
".dkan-dotwrap{position:relative;width:
|
|
1219
|
-
".dkan-dot{width:
|
|
1220
|
-
".dkan-breathe .dkan-dot{animation:dkan-breathe calc(2.
|
|
1221
|
-
"@keyframes dkan-breathe{0%,100%{transform:scale(1);opacity:.
|
|
1335
|
+
".dkan-dotwrap{position:relative;width:20px;height:20px;flex:none;display:inline-flex;align-items:center;justify-content:center;}",
|
|
1336
|
+
".dkan-dot{width:12px;height:12px;border-radius:50%;background:var(--dkan-phase,var(--dsw-alias-accent,#4d9fff));box-shadow:0 0 10px color-mix(in srgb,var(--dkan-phase,var(--dsw-alias-accent,#4d9fff)) 70%,transparent);transition:background .4s var(--ds-ease-in-out),box-shadow .4s var(--ds-ease-in-out);}",
|
|
1337
|
+
".dkan-breathe .dkan-dot{animation:dkan-breathe calc(2.2s / var(--dkan-speed,1)) ease-in-out infinite;}",
|
|
1338
|
+
"@keyframes dkan-breathe{0%,100%{transform:scale(1);opacity:.5}50%{transform:scale(1.7);opacity:1}}",
|
|
1339
|
+
// 呼吸光晕:向外扩散的波纹环,与圆点同频(颜色随阶段)
|
|
1340
|
+
".dkan-halo{position:absolute;inset:0;border-radius:50%;border:2px solid var(--dkan-phase,var(--dsw-alias-accent,#4d9fff));animation:dkan-halo calc(2.2s / var(--dkan-speed,1)) ease-out infinite;}",
|
|
1341
|
+
"@keyframes dkan-halo{0%{transform:scale(.5);opacity:.9}70%{transform:scale(1.5);opacity:.25}100%{transform:scale(1.7);opacity:0}}",
|
|
1342
|
+
// breathe 模式徽标更醒目:阶段色描边 + 呼吸辉光
|
|
1343
|
+
".dkan-badge-breathe{border-color:color-mix(in srgb,var(--dkan-phase,#4d9fff) 55%,transparent);animation:dkan-rise .3s var(--ds-ease-in-out),dkan-badge-breathe calc(2.2s / var(--dkan-speed,1)) ease-in-out infinite;}",
|
|
1344
|
+
"@keyframes dkan-badge-breathe{0%,100%{box-shadow:0 0 6px color-mix(in srgb,var(--dkan-phase,#4d9fff) 22%,transparent),0 6px 24px rgb(0 0 0 / .16)}50%{box-shadow:0 0 18px color-mix(in srgb,var(--dkan-phase,#4d9fff) 55%,transparent),0 6px 24px rgb(0 0 0 / .16)}}",
|
|
1222
1345
|
".dkan-ring{position:absolute;inset:0;border-radius:50%;background:conic-gradient(from 0deg,transparent 0 68%,var(--dsw-alias-accent,#4d9fff) 92%,#fff);-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 2px),#000 calc(100% - 2px));mask:radial-gradient(farthest-side,transparent calc(100% - 2px),#000 calc(100% - 2px));animation:dkan-spin calc(2.2s / var(--dkan-speed,1)) linear infinite;opacity:.9;}",
|
|
1223
1346
|
"@keyframes dkan-spin{to{transform:rotate(360deg)}}",
|
|
1224
1347
|
// 通知卡片栈(右上角)
|
|
@@ -1293,7 +1416,7 @@ const css = [
|
|
|
1293
1416
|
"@keyframes dkan-prev-aurora{0%{transform:translateX(-10%)}100%{transform:translateX(15%)}}",
|
|
1294
1417
|
".dkan-prev-bot{height:92px;justify-content:center;}",
|
|
1295
1418
|
".dkan-prev-bot .dkan-bot-scene{--dkan-bot-scale:1;transform:scale(.56);transform-origin:center;}",
|
|
1296
|
-
// ===== 桌面伙伴:具象人物 +
|
|
1419
|
+
// ===== 桌面伙伴:具象人物 + 紧凑双工位(尺寸可调,整卡可拖拽) =====
|
|
1297
1420
|
// 卡片:玻璃拟态;默认停泊右下(dsh 输入卡上方),拖动后位置持久化、可放屏幕任意处
|
|
1298
1421
|
".dkan-botcard{--dkan-bot-scale:1.35;position:fixed;right:20px;bottom:calc(var(--dsh-composer-height,152px) + 20px);z-index:9990;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);}",
|
|
1299
1422
|
".dkan-botcard:hover,.dkan-botcard:focus-visible{border-color:var(--dsw-alias-accent,#4d9fff);outline:none;}",
|
|
@@ -1328,9 +1451,24 @@ const css = [
|
|
|
1328
1451
|
".dk3-code i:nth-child(5n+4){width:72%;}",
|
|
1329
1452
|
".dk3-code i:nth-child(5n){width:54%;background:var(--dkan-code-a);}",
|
|
1330
1453
|
".dk3-cur{position:absolute;left:3px;bottom:2px;width:4px;height:2px;background:var(--dkan-code-b);animation:dkan-blink3 1s steps(1) infinite;}",
|
|
1454
|
+
// 左屏检索视图:搜索框、放大镜与结果条,和右侧代码编辑器明确区分
|
|
1455
|
+
".dk3-search-ui{position:absolute;inset:3px;display:block;overflow:hidden;}",
|
|
1456
|
+
".dk3-search-box{display:block;height:5px;margin:0 1px 3px;border:1px solid color-mix(in srgb,var(--dkan-code-a) 72%,#d7fff4);border-radius:2px;box-sizing:border-box;}",
|
|
1457
|
+
".dk3-search-box b{display:block;width:2px;height:2px;margin:1px 0 0 2px;border:1px solid var(--dkan-code-a);border-radius:50%;position:relative;}",
|
|
1458
|
+
".dk3-search-box b:after{content:'';position:absolute;width:2px;height:1px;background:var(--dkan-code-a);right:-2px;bottom:-1px;transform:rotate(45deg);transform-origin:left center;}",
|
|
1459
|
+
".dk3-search-results{display:block;animation:dk3-search-scroll calc(2.1s / var(--dkan-speed,1)) linear infinite;}",
|
|
1460
|
+
".dk3-search-results i{display:block;height:2px;margin:2px 1px 0;border-radius:1px;background:var(--dkan-code-a);opacity:.72;}",
|
|
1461
|
+
".dk3-search-results i:nth-child(2n){width:74%;background:var(--dkan-code-b);}",
|
|
1462
|
+
".dk3-search-results i:nth-child(3n){width:48%;background:var(--dkan-code-c);}",
|
|
1463
|
+
"@keyframes dk3-search-scroll{to{transform:translateY(-5px)}}",
|
|
1331
1464
|
"@keyframes dk3-scroll{to{transform:translateY(-50%)}}",
|
|
1332
|
-
//
|
|
1333
|
-
".dk3-person{position:absolute;left:
|
|
1465
|
+
// 人物与椅子始终正对桌面:工位切换只横移,不再旋转整个人和座椅。
|
|
1466
|
+
".dk3-person{--dk3-seat-x:22px;position:absolute;left:98px;top:14px;width:44px;height:61px;transform-style:preserve-3d;transform:translateZ(30px) translateX(var(--dk3-seat-x));transition:transform max(.18s,calc(.38s / var(--dkan-speed,1))) cubic-bezier(.23,1,.32,1);will-change:transform;}",
|
|
1467
|
+
".dkan-bot-scene[data-station=left] .dk3-person{--dk3-seat-x:0px;}",
|
|
1468
|
+
".dkan-bot-scene[data-station=center] .dk3-person{--dk3-seat-x:22px;}",
|
|
1469
|
+
".dkan-bot-scene[data-station=right] .dk3-person{--dk3-seat-x:44px;}",
|
|
1470
|
+
// 上身以髋部为旋转原点;只做前后倾,始终朝向桌面。
|
|
1471
|
+
".dk3-upper3{position:absolute;inset:0;transform-style:preserve-3d;transform-origin:12px 52px;transform:translateZ(0);transition:transform .24s cubic-bezier(.77,0,.175,1);will-change:transform;}",
|
|
1334
1472
|
// 动漫人物配色:皮肤/头发/卫衣/裤子
|
|
1335
1473
|
".dk3-skin .dk3-face{background:linear-gradient(180deg,#ffd9b8,#f3b98d);}",
|
|
1336
1474
|
".dk3-skin .dk3-face:nth-child(5){background:#ffe3c9;}",
|
|
@@ -1340,64 +1478,106 @@ const css = [
|
|
|
1340
1478
|
".dk3-hood .dk3-face:nth-child(5){background:#93a5ba;}",
|
|
1341
1479
|
".dk3-pants .dk3-face{background:linear-gradient(180deg,#3d4a5c,#2c3646);}",
|
|
1342
1480
|
".dk3-pants .dk3-face:nth-child(5){background:#4a5a70;}",
|
|
1343
|
-
//
|
|
1344
|
-
".dk3-
|
|
1345
|
-
".dk3-
|
|
1481
|
+
// 人物细节:收窄下颌、连续肩颈、短侧渐层发型
|
|
1482
|
+
".dk3-cranium .dk3-face{border-radius:42% 46% 38% 36%;}",
|
|
1483
|
+
".dk3-jaw3 .dk3-face{border-radius:30% 30% 48% 48%;}",
|
|
1484
|
+
".dk3-neck3 .dk3-face{border-radius:2px;}",
|
|
1485
|
+
".dk3-collar3 .dk3-face{background:linear-gradient(180deg,#516176,#374459);border-radius:3px;}",
|
|
1486
|
+
".dk3-shoulders3 .dk3-face{border-radius:4px;}",
|
|
1487
|
+
".dk3-chest3 .dk3-face{border-radius:4px;}",
|
|
1488
|
+
".dk3-waist3 .dk3-face{border-radius:3px;}",
|
|
1346
1489
|
".dk3-hair .dk3-face{border-radius:2px;}",
|
|
1490
|
+
".dk3-hair-crown .dk3-face{border-radius:48% 42% 28% 30%;}",
|
|
1491
|
+
".dk3-hair-back .dk3-face{background:linear-gradient(180deg,#493727,#2f241b);border-radius:46% 24% 38% 44%;}",
|
|
1347
1492
|
".dk3-shoe .dk3-face{background:#20262f;border-radius:2px;}",
|
|
1348
1493
|
".dk3-mug .dk3-face{background:#c2703d;border-radius:1px;}",
|
|
1349
1494
|
".dk3-mug .dk3-face:nth-child(5){background:#e08a52;}",
|
|
1350
|
-
".dk3-mouth{position:absolute;left:50%;top:
|
|
1495
|
+
".dk3-mouth{position:absolute;left:50%;top:64%;width:3.5px;height:1px;border-radius:2px;background:#a9635c;}",
|
|
1351
1496
|
// 椅子
|
|
1352
1497
|
".dk3-chairback .dk3-face{background:linear-gradient(180deg,#3f4c60,#2c3648);}",
|
|
1353
1498
|
".dk3-chairseat .dk3-face{background:#33415a;}",
|
|
1354
|
-
//
|
|
1355
|
-
".dk3-head3{position:absolute;left:
|
|
1356
|
-
".dk3-
|
|
1357
|
-
".dk3-
|
|
1358
|
-
".dk3-
|
|
1359
|
-
".dk3-
|
|
1360
|
-
".dk3-
|
|
1361
|
-
".dk3-
|
|
1499
|
+
// 头组与颈部中心均为 x=14;只让头部观察左右屏,身体和椅子不转向。
|
|
1500
|
+
".dk3-head3{--dk3-head-turn:rotateY(0deg);position:absolute;left:6px;top:3px;width:17px;height:21px;transform-style:preserve-3d;transform-origin:8px 18px;transform:var(--dk3-head-turn);transition:transform max(.18s,calc(.22s / var(--dkan-speed,1))) cubic-bezier(.23,1,.32,1);will-change:transform;}",
|
|
1501
|
+
".dkan-bot-scene[data-station=left] .dk3-head3{--dk3-head-turn:rotateY(-18deg);}",
|
|
1502
|
+
".dkan-bot-scene[data-station=center] .dk3-head3{--dk3-head-turn:rotateY(0deg);}",
|
|
1503
|
+
".dkan-bot-scene[data-station=right] .dk3-head3{--dk3-head-turn:rotateY(14deg);}",
|
|
1504
|
+
".dk3-brows{position:absolute;left:50%;top:29%;display:flex;gap:2.5px;align-items:center;justify-content:center;}",
|
|
1505
|
+
".dk3-brows i{width:3.5px;height:1px;border-radius:2px;background:#3a2a21;transform:rotate(-7deg);}",
|
|
1506
|
+
".dk3-brows i+ i{transform:rotate(7deg);}",
|
|
1507
|
+
".dk3-eyes{position:absolute;left:50%;top:51%;display:flex;gap:2.5px;align-items:center;justify-content:center;}",
|
|
1508
|
+
".dk3-eyes i{width:3.5px;height:2.5px;border-radius:50%;background:radial-gradient(circle at 62% 30%,#fff 0 .5px,#263141 .8px 1.7px,#111827 1.9px);animation:dkan-blink3 4.8s infinite;}",
|
|
1509
|
+
".dk3-nose{position:absolute;left:50%;top:66%;width:2px;height:3px;border-radius:60% 40% 55% 45%;background:linear-gradient(90deg,#e7a67e,#c77f67);}",
|
|
1510
|
+
".dk3-ear{position:absolute;left:1px;top:10px;width:3px;height:5px;border-radius:52% 44% 48% 52%;background:linear-gradient(90deg,#edaa82,#ffd2ad);transform:translateZ(2px);}",
|
|
1511
|
+
".dk3-fringe3{position:absolute;left:13px;top:4px;width:5px;height:7px;transform-style:preserve-3d;transform:rotateY(90deg) translateZ(5.8px);}",
|
|
1512
|
+
".dk3-fringe3 i{position:absolute;top:0;width:2px;height:6px;border-radius:70% 25% 60% 30%;background:linear-gradient(180deg,#6b5239,#392b20);transform-origin:top center;}",
|
|
1513
|
+
".dk3-fringe3 i:nth-child(1){left:0;transform:rotate(-18deg);height:5px;}",
|
|
1514
|
+
".dk3-fringe3 i:nth-child(2){left:1.8px;transform:rotate(-6deg);height:6px;}",
|
|
1515
|
+
".dk3-fringe3 i:nth-child(3){left:3.5px;transform:rotate(12deg);height:4px;}",
|
|
1362
1516
|
"@keyframes dkan-blink3{0%,91%,100%{transform:scaleY(1)}94%{transform:scaleY(.15)}}",
|
|
1363
|
-
//
|
|
1364
|
-
".dk3-arm3{position:absolute;left:20px;top:30px;width:16px;height:16px;transform-style:preserve-3d;}",
|
|
1365
|
-
".dk3-elbow{position:absolute;left:0;top:10px;width:15px;height:5px;transform-style:preserve-3d;transform-origin:
|
|
1366
|
-
".dk3-
|
|
1517
|
+
// 手臂按肩→肘→腕分段,旋转原点位于真实关节连接处。
|
|
1518
|
+
".dk3-arm3{position:absolute;left:20px;top:30px;width:16px;height:16px;transform-style:preserve-3d;transform-origin:2px 2px;will-change:transform;}",
|
|
1519
|
+
".dk3-elbow{position:absolute;left:0;top:10px;width:15px;height:5px;transform-style:preserve-3d;transform-origin:1px 2px;will-change:transform;}",
|
|
1520
|
+
".dk3-wrist3{position:absolute;left:9.5px;top:0;width:6px;height:5px;transform-style:preserve-3d;transform-origin:1px 2px;will-change:transform;}",
|
|
1521
|
+
".dk3-fingers3{position:absolute;left:3px;top:1px;width:4px;height:3px;transform:translateZ(2px);}",
|
|
1522
|
+
".dk3-fingers3 i{position:absolute;left:0;width:4px;height:.8px;border-radius:2px;background:#f2b58d;}",
|
|
1523
|
+
".dk3-fingers3 i+ i{top:1.4px;width:3.5px;}",
|
|
1524
|
+
".dk3-far{transform:translateZ(-7px);filter:brightness(.72);}",
|
|
1367
1525
|
// 思考泡泡(贴镜头 2D 层,think 阶段显示)
|
|
1368
1526
|
".dkan-bubble{position:absolute;left:80px;top:4px;display:none;gap:3px;padding:4px 6px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);box-shadow:0 2px 6px rgb(0 0 0 / .15);z-index:3;}",
|
|
1369
1527
|
".dkan-bubble i{width:4px;height:4px;border-radius:50%;background:var(--dsw-alias-label-tertiary);animation:dkan-bubdot 1.2s ease-in-out infinite;}",
|
|
1370
1528
|
".dkan-bubble i:nth-child(2){animation-delay:.2s;}",
|
|
1371
1529
|
".dkan-bubble i:nth-child(3){animation-delay:.4s;}",
|
|
1372
1530
|
"@keyframes dkan-bubdot{0%,100%{opacity:.25;transform:translateY(0)}50%{opacity:1;transform:translateY(-2px)}}",
|
|
1531
|
+
// ===== 多任务工位(data-tasks=活跃任务数,上限 3) =====
|
|
1532
|
+
// 椅子滚轮
|
|
1533
|
+
".dk3-wheel .dk3-face{background:#1c232e;border-radius:50%;}",
|
|
1534
|
+
// 有活跃任务时,未轮到的侧工位稍暗;多任务则两个工位均保持可见。
|
|
1535
|
+
".dkan-bot-scene[data-tasks] .dk3-mon3.left .dk3-screen,.dkan-bot-scene[data-tasks] .dk3-mon3.right .dk3-screen{opacity:.3;}",
|
|
1536
|
+
// 2 个及以上任务:左右工位同时点亮(各自带辉光、代码滚动加速)。
|
|
1537
|
+
".dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.left .dk3-screen,.dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.right .dk3-screen,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.left .dk3-screen,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.right .dk3-screen{opacity:1;box-shadow:0 0 8px color-mix(in srgb,var(--dkan-code-b,#60a5fa) 42%,transparent);}",
|
|
1538
|
+
".dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.left .dk3-code,.dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.right .dk3-code,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.left .dk3-code,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.right .dk3-code{animation-duration:calc(2.6s / var(--dkan-speed,1));}",
|
|
1539
|
+
// 座椅移动期间滚轮跟随任务节奏转动;思考中停下,避免无意义的常驻运动。
|
|
1540
|
+
".dkan-bot-scene[data-station=left] .dk3-wheel,.dkan-bot-scene[data-station=right] .dk3-wheel{animation:dkan-wheel calc(.46s / var(--dkan-speed,1)) linear infinite;transform-origin:center;}",
|
|
1541
|
+
"@keyframes dkan-wheel{to{transform:rotateZ(360deg)}}",
|
|
1373
1542
|
// ===== 阶段驱动(data-phase 四态,host 由 chunk 流实时同步) =====
|
|
1374
|
-
// think
|
|
1543
|
+
// think:上身轻靠、近侧手托下巴、远侧手留在桌面;动作有停顿而不是机械往返。
|
|
1375
1544
|
".dkan-bot-scene[data-phase=think] .dk3-screen{opacity:.32;}",
|
|
1376
1545
|
".dkan-bot-scene[data-phase=think] .dkan-bubble{display:inline-flex;}",
|
|
1377
|
-
".dkan-bot-scene[data-phase=think] .dk3-
|
|
1378
|
-
".dkan-bot-scene[data-phase=think] .dk3-
|
|
1379
|
-
".dkan-bot-scene[data-phase=think] .dk3-arm3.dk3-
|
|
1380
|
-
"
|
|
1381
|
-
"
|
|
1382
|
-
"
|
|
1383
|
-
|
|
1384
|
-
"
|
|
1385
|
-
"
|
|
1386
|
-
|
|
1387
|
-
".dkan-bot-scene[data-phase=write] .dk3-
|
|
1388
|
-
".dkan-bot-scene[data-phase=write] .dk3-
|
|
1389
|
-
".dkan-bot-scene[data-phase=write] .dk3-
|
|
1390
|
-
"
|
|
1391
|
-
"
|
|
1392
|
-
"
|
|
1393
|
-
".dkan-bot-scene[data-phase=write] .dk3-
|
|
1394
|
-
"
|
|
1395
|
-
|
|
1396
|
-
".dkan-bot-scene[data-phase=
|
|
1397
|
-
"
|
|
1398
|
-
"
|
|
1399
|
-
"@keyframes dkan-
|
|
1400
|
-
"@
|
|
1546
|
+
".dkan-bot-scene[data-phase=think] .dk3-upper3{animation:dkan-think-upper max(1.8s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1547
|
+
".dkan-bot-scene[data-phase=think] .dk3-head3{animation:dkan-think-head max(1.8s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1548
|
+
".dkan-bot-scene[data-phase=think] .dk3-arm3.dk3-near{animation:dkan-think-near max(1.8s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1549
|
+
".dkan-bot-scene[data-phase=think] .dk3-arm3.dk3-far{transform:translateZ(-7px) rotateZ(3deg);}",
|
|
1550
|
+
".dkan-bot-scene[data-phase=think] .dk3-near .dk3-elbow{transform:rotateZ(-28deg) translateY(-1px);}",
|
|
1551
|
+
".dkan-bot-scene[data-phase=think] .dk3-near .dk3-wrist3{transform:rotateZ(-18deg) translateY(-1px);}",
|
|
1552
|
+
"@keyframes dkan-think-upper{0%,44%{transform:rotateZ(-1deg) translateY(0)}62%,100%{transform:rotateZ(-1.8deg) translateY(-.5px)}}",
|
|
1553
|
+
"@keyframes dkan-think-head{0%,44%{transform:var(--dk3-head-turn) rotateZ(-3deg)}62%,100%{transform:var(--dk3-head-turn) rotateZ(-6deg) translateY(-.5px)}}",
|
|
1554
|
+
"@keyframes dkan-think-near{0%,44%{transform:rotateZ(-31deg) translate(-8px,-14.5px)}62%,100%{transform:rotateZ(-34deg) translate(-8.5px,-15px)}}",
|
|
1555
|
+
// write/code:身体仍面向桌面,仅轻微前倾;主要由手腕和手指敲击。
|
|
1556
|
+
".dkan-bot-scene[data-phase=write] .dk3-mon3.right .dk3-screen,.dkan-bot-scene[data-phase=code] .dk3-mon3.right .dk3-screen{opacity:1;box-shadow:0 0 11px color-mix(in srgb,var(--dkan-code-b,#60a5fa) 52%,transparent);}",
|
|
1557
|
+
".dkan-bot-scene[data-phase=write] .dk3-mon3.right .dk3-code,.dkan-bot-scene[data-phase=code] .dk3-mon3.right .dk3-code{animation-duration:calc(1.35s / var(--dkan-speed,1));}",
|
|
1558
|
+
".dkan-bot-scene[data-phase=write] .dk3-upper3,.dkan-bot-scene[data-phase=code] .dk3-upper3{animation:dkan-code-upper max(1.2s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1559
|
+
".dkan-bot-scene[data-phase=write] .dk3-arm3.dk3-near,.dkan-bot-scene[data-phase=code] .dk3-arm3.dk3-near{transform:rotateZ(3deg);}",
|
|
1560
|
+
".dkan-bot-scene[data-phase=write] .dk3-arm3.dk3-far,.dkan-bot-scene[data-phase=code] .dk3-arm3.dk3-far{transform:translateZ(-7px) rotateZ(1deg);}",
|
|
1561
|
+
".dkan-bot-scene[data-phase=write] .dk3-elbow,.dkan-bot-scene[data-phase=code] .dk3-elbow{animation:dkan-type-elbow max(.28s,calc(.58s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1562
|
+
".dkan-bot-scene[data-phase=write] .dk3-wrist3,.dkan-bot-scene[data-phase=code] .dk3-wrist3{animation:dkan-type-wrist max(.18s,calc(.29s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1563
|
+
".dkan-bot-scene[data-phase=write] .dk3-far .dk3-elbow,.dkan-bot-scene[data-phase=code] .dk3-far .dk3-elbow{animation-delay:-.09s;animation-duration:max(.3s,calc(.63s / var(--dkan-speed,1)));}",
|
|
1564
|
+
".dkan-bot-scene[data-phase=write] .dk3-far .dk3-wrist3,.dkan-bot-scene[data-phase=code] .dk3-far .dk3-wrist3{animation-delay:-.09s;animation-duration:max(.2s,calc(.33s / var(--dkan-speed,1)));}",
|
|
1565
|
+
".dkan-bot-scene[data-phase=write] .dk3-head3,.dkan-bot-scene[data-phase=code] .dk3-head3{animation:dkan-output-head max(1.2s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1566
|
+
"@keyframes dkan-code-upper{0%,38%{transform:rotateZ(2deg) translate(1px,0)}62%,100%{transform:rotateZ(3deg) translate(1px,1px)}}",
|
|
1567
|
+
"@keyframes dkan-type-elbow{0%,38%{transform:rotateZ(-4deg)}62%,100%{transform:rotateZ(5deg) translateY(1px)}}",
|
|
1568
|
+
"@keyframes dkan-type-wrist{0%,34%{transform:rotateZ(-2deg) translateY(0)}62%,100%{transform:rotateZ(3deg) translateY(1.5px)}}",
|
|
1569
|
+
"@keyframes dkan-output-head{0%,46%,100%{transform:var(--dk3-head-turn) rotateZ(3deg)}58%{transform:var(--dk3-head-turn) rotateZ(5deg) translateY(1px)}}",
|
|
1570
|
+
// search:身体正对桌面,头看左屏,近侧手腕小幅操作鼠标。
|
|
1571
|
+
".dkan-bot-scene[data-phase=search] .dk3-mon3.left .dk3-screen{opacity:1;box-shadow:0 0 10px color-mix(in srgb,var(--dkan-code-a,#4ade80) 52%,transparent);}",
|
|
1572
|
+
".dkan-bot-scene[data-phase=search] .dk3-mon3.left .dk3-search-results{animation-duration:calc(1.05s / var(--dkan-speed,1));}",
|
|
1573
|
+
".dkan-bot-scene[data-phase=search] .dk3-upper3{transform:rotateZ(2deg) translateY(1px);}",
|
|
1574
|
+
".dkan-bot-scene[data-phase=search] .dk3-head3{animation:dkan-search-head max(.72s,calc(1.15s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1575
|
+
".dkan-bot-scene[data-phase=search] .dk3-near .dk3-wrist3{animation:dkan-search-wrist max(.45s,calc(.72s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1576
|
+
".dkan-bot-scene[data-phase=search] .dk3-far{transform:translateZ(-7px) rotateZ(2deg);}",
|
|
1577
|
+
"@keyframes dkan-search-head{0%,28%{transform:var(--dk3-head-turn) rotateZ(1deg)}48%,68%{transform:var(--dk3-head-turn) rotateZ(6deg) translate(1px,1px)}88%,100%{transform:var(--dk3-head-turn) rotateZ(2deg)}}",
|
|
1578
|
+
"@keyframes dkan-search-wrist{0%,34%{transform:translateX(-1px)}62%,100%{transform:translateX(1.5px)}}",
|
|
1579
|
+
// 减少动态时停止空间位移,保留屏幕亮暗与思考状态的淡入反馈。
|
|
1580
|
+
"@media (prefers-reduced-motion:reduce){.dk3-person,.dk3-upper3,.dk3-head3,.dk3-arm3,.dk3-elbow,.dk3-wrist3,.dk3-wheel,.dk3-code,.dk3-search-results{animation:none!important;transition:none!important}.dk3-screen,.dkan-bubble{transition:opacity .2s cubic-bezier(.23,1,.32,1)!important}}",
|
|
1401
1581
|
// 通知子选项行
|
|
1402
1582
|
".dkan-rows-narrow{display:flex;flex-direction:column;gap:6px;}",
|
|
1403
1583
|
".dkan-row{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--dsw-alias-label-secondary);flex-wrap:wrap;}",
|