dsh-compact-relay 0.3.3 → 0.3.5
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/CHANGELOG.md +27 -0
- package/README.en.md +6 -3
- package/README.md +6 -3
- package/ROADMAP.md +30 -3
- package/TUTORIAL.md +1 -1
- package/lib/actions/better-compact.js +24 -72
- package/lib/actions/compact-run.js +76 -0
- package/lib/actions/compress-hint.js +16 -7
- package/lib/actions/handoff-docs.js +9 -3
- package/lib/actions/handoff.js +5 -22
- package/lib/actions/settings.js +1 -4
- package/lib/actions/state.js +15 -4
- package/lib/actions/support.js +76 -0
- package/lib/actions/test-compact.js +12 -41
- package/lib/adapters/http.js +2 -1
- package/lib/adapters/prompt.js +72 -0
- package/lib/adapters/tool.js +12 -8
- package/lib/client.js +440 -100
- package/lib/config.js +1 -2
- package/lib/core/compress-cue.js +96 -0
- package/lib/core/compress-hint.js +1 -1
- package/lib/core/errors.js +0 -1
- package/lib/core/handoff-spec.js +1 -1
- package/lib/core/markdown-lite.js +178 -0
- package/lib/core/object.js +19 -0
- package/lib/core/ranges.js +1 -2
- package/lib/core/relay.js +2 -2
- package/lib/core/tokens.js +34 -0
- package/lib/dsh/compaction-log.js +3 -19
- package/lib/dsh/compaction-pair.js +48 -0
- package/lib/dsh/engine.js +2 -2
- package/lib/dsh/handoff-files.js +28 -0
- package/lib/dsh/handoff-writer.js +16 -21
- package/lib/dsh/model-task.js +112 -0
- package/lib/dsh/relay.js +1 -1
- package/lib/dsh/session-view.js +5 -17
- package/lib/dsh/settings-file.js +1 -4
- package/lib/dsh/state-hub.js +95 -19
- package/lib/dsh/summarizer.js +16 -25
- package/lib/index.js +28 -6
- package/package.json +2 -2
- package/lib/actions/stage.js +0 -36
- package/lib/core/stage.js +0 -96
package/lib/client.js
CHANGED
|
@@ -376,6 +376,355 @@ window.__ModuleLoader__.load({
|
|
|
376
376
|
return theme;
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
/**
|
|
380
|
+
* 跨片段共享的浮层 / 指示灯环小部件(30-indicator 与 50-settings 共用)。
|
|
381
|
+
*
|
|
382
|
+
* 为什么单独一个片段:这些几何/材质在「指示灯」和「设置页教学卡片」里各画过一遍,
|
|
383
|
+
* 以前是两份点坐标/弧线 path/原生线刻度/闪烁 keyframes。放在 15 号片段(按文件名
|
|
384
|
+
* 排序在 10-runtime 之后、20-format/30-indicator/50-settings 之前),函数声明会
|
|
385
|
+
* 提升,后面的片段可直接调用;常量在使用点(组件渲染时)早已初始化。
|
|
386
|
+
*/
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* 环上比例 → SVG 坐标(viewBox 0 0 20 20,圆心 10,10,半径用 10-runtime 的 RADIUS)。
|
|
390
|
+
* @param {number} value 0–1 的比例
|
|
391
|
+
* @returns {[number, number]}
|
|
392
|
+
*/
|
|
393
|
+
function ringPoint(value) {
|
|
394
|
+
const angle = value * Math.PI * 2 - Math.PI / 2;
|
|
395
|
+
return [10 + RADIUS * Math.cos(angle), 10 + RADIUS * Math.sin(angle)];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* 环上两比例之间的弧线 path(0.5 以上用大弧标志)。
|
|
400
|
+
* @param {number} from
|
|
401
|
+
* @param {number} to
|
|
402
|
+
* @returns {string}
|
|
403
|
+
*/
|
|
404
|
+
function ringArcPath(from, to) {
|
|
405
|
+
const [x1, y1] = ringPoint(from);
|
|
406
|
+
const [x2, y2] = ringPoint(to);
|
|
407
|
+
const large = to - from > 0.5 ? 1 : 0;
|
|
408
|
+
return `M ${x1.toFixed(3)} ${y1.toFixed(3)} A ${RADIUS} ${RADIUS} 0 ${large} 1 ${x2.toFixed(3)} ${y2.toFixed(3)}`;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* DSH 原生自动压缩线的「径向刻度」:穿过环带的一条短线(内 5.6 → 外 9.6)。
|
|
413
|
+
* 指示灯与设置页教学图用同一套几何,只是描边/属性不同。
|
|
414
|
+
* @param {number} value 合法的原生阈值比例(0.02–1 之间,调用方先校验)
|
|
415
|
+
* @returns {{x1:number, y1:number, x2:number, y2:number}}
|
|
416
|
+
*/
|
|
417
|
+
function nativeTickAt(value) {
|
|
418
|
+
const angle = value * Math.PI * 2 - Math.PI / 2;
|
|
419
|
+
const cos = Math.cos(angle);
|
|
420
|
+
const sin = Math.sin(angle);
|
|
421
|
+
return { x1: 10 + 5.6 * cos, y1: 10 + 5.6 * sin, x2: 10 + 9.6 * cos, y2: 10 + 9.6 * sin };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** 越过原生自动压缩线后「整环红色闪烁」的 keyframes(指示灯/教学图共用一份)。 */
|
|
425
|
+
const NATIVE_ALERT_CSS = '@keyframes cr-native-alert { 0%,100% { opacity: 1 } 50% { opacity: 0.15 } } @media (prefers-reduced-motion: reduce) { [data-cr-native-alert] { animation: none !important; opacity: 0.85 !important; } }';
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* 浮层外观(菜单 / 悬停卡 / 确认卡 / 交接单面板 / 历史面板 / toast 共用)。
|
|
429
|
+
* 主题色由 10-runtime 的 detectTheme() 给出(BUG-001:优先 DSH 主题 token)。
|
|
430
|
+
* @param {object} theme detectTheme() 的返回值
|
|
431
|
+
* @returns {{background:string, backdropFilter:string, WebkitBackdropFilter:string, border:string, boxShadow:string}}
|
|
432
|
+
*/
|
|
433
|
+
function panelChrome(theme) {
|
|
434
|
+
return {
|
|
435
|
+
background: theme.panelBg,
|
|
436
|
+
backdropFilter: theme.panelBackdrop,
|
|
437
|
+
WebkitBackdropFilter: theme.panelBackdrop,
|
|
438
|
+
border: `1px solid ${theme.panelBorder}`,
|
|
439
|
+
boxShadow: theme.panelShadow,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* 轻量 Markdown 解析(0.3.4 BUG-005)—— 纯函数,零依赖。
|
|
445
|
+
*
|
|
446
|
+
* 为什么自己写:交接单详情面板以前把 markdown 原样塞进 `<pre>`,用户在面板里看到的是
|
|
447
|
+
* `## 目标`/`- xxx`/`**粗体**` 源码。引第三方 markdown 渲染器会增加运行时依赖
|
|
448
|
+
* (插件只有 peerDependencies、零运行时依赖是硬约束),而 `dangerouslySetInnerHTML`
|
|
449
|
+
* 又引入 XSS 面。所以这里只解析交接单实际用到的子集,产出**结构化数据**,由客户端
|
|
450
|
+
* 用 React 文本节点渲染 —— 从不生成 HTML,任何 `<script>` 都只会显示成文字。
|
|
451
|
+
*
|
|
452
|
+
* 支持(够用即可,不做完整 CommonMark):
|
|
453
|
+
* - 标题 `#`–`######`(段落里的行首)
|
|
454
|
+
* - 段落(连续非空行;保留原始换行)
|
|
455
|
+
* - 无序列表 `-` / `*` / `+`
|
|
456
|
+
* - 有序列表 `1.` / `1)`(保留起始编号)
|
|
457
|
+
* - 围栏代码块 ```(保留语言标记;块内不做行内解析)
|
|
458
|
+
* - 分隔线 `---` / `***` / `___`
|
|
459
|
+
* - 行内:`**粗体**`、`` `行内代码` ``、反斜杠转义
|
|
460
|
+
*
|
|
461
|
+
* 返回值约定(客户端渲染器按此映射 React 节点):
|
|
462
|
+
* blocks: Array<
|
|
463
|
+
* | {type:'heading', level:number, segments:Segment[]}
|
|
464
|
+
* | {type:'paragraph', segments:Segment[]}
|
|
465
|
+
* | {type:'list', ordered:boolean, start:number, items:Segment[][]}
|
|
466
|
+
* | {type:'code', lang:string, text:string}
|
|
467
|
+
* | {type:'hr'}
|
|
468
|
+
* >
|
|
469
|
+
* Segment = {type:'text'|'strong'|'code', text:string}
|
|
470
|
+
*/
|
|
471
|
+
|
|
472
|
+
const FENCE_RE = /^\s*```\s*([^\s`]*)\s*$/;
|
|
473
|
+
const HEADING_RE = /^(#{1,6})\s+(.*)$/;
|
|
474
|
+
const HR_RE = /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
|
|
475
|
+
const UL_RE = /^\s*[-*+]\s+(.*)$/;
|
|
476
|
+
const OL_RE = /^\s*(\d+)[.)]\s+(.*)$/;
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* 解析行内标记:`**粗体**`、`` `代码` ``、反斜杠转义。
|
|
480
|
+
* 未闭合的标记按普通文本处理(不吞字符)。
|
|
481
|
+
* @param {string} text
|
|
482
|
+
* @returns {{type:'text'|'strong'|'code', text:string}[]}
|
|
483
|
+
*/
|
|
484
|
+
function parseInlineLite(text) {
|
|
485
|
+
const source = typeof text === 'string' ? text : String(text ?? '');
|
|
486
|
+
const segments = [];
|
|
487
|
+
let buffer = '';
|
|
488
|
+
const flush = () => {
|
|
489
|
+
if (buffer.length > 0) {
|
|
490
|
+
segments.push({ type: 'text', text: buffer });
|
|
491
|
+
buffer = '';
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
let index = 0;
|
|
495
|
+
while (index < source.length) {
|
|
496
|
+
const char = source[index];
|
|
497
|
+
// 反斜杠转义:下一个字符按字面量处理(\* 不开启粗体)。
|
|
498
|
+
if (char === '\\' && index + 1 < source.length) {
|
|
499
|
+
buffer += source[index + 1];
|
|
500
|
+
index += 2;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (char === '`') {
|
|
504
|
+
const end = source.indexOf('`', index + 1);
|
|
505
|
+
if (end === -1) {
|
|
506
|
+
buffer += char;
|
|
507
|
+
index += 1;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
flush();
|
|
511
|
+
segments.push({ type: 'code', text: source.slice(index + 1, end) });
|
|
512
|
+
index = end + 1;
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
if (char === '*' && source[index + 1] === '*') {
|
|
516
|
+
const end = source.indexOf('**', index + 2);
|
|
517
|
+
if (end === -1) {
|
|
518
|
+
buffer += '**';
|
|
519
|
+
index += 2;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
flush();
|
|
523
|
+
segments.push({ type: 'strong', text: source.slice(index + 2, end) });
|
|
524
|
+
index = end + 2;
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
buffer += char;
|
|
528
|
+
index += 1;
|
|
529
|
+
}
|
|
530
|
+
flush();
|
|
531
|
+
return segments;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* 把 markdown 文本解析成块数组;空文本返回 []。
|
|
536
|
+
* @param {string} text
|
|
537
|
+
* @returns {object[]}
|
|
538
|
+
*/
|
|
539
|
+
function parseMarkdownLite(text) {
|
|
540
|
+
const source = typeof text === 'string' ? text : String(text ?? '');
|
|
541
|
+
const lines = source.replace(/\r\n?/g, '\n').split('\n');
|
|
542
|
+
const blocks = [];
|
|
543
|
+
let index = 0;
|
|
544
|
+
|
|
545
|
+
// 连续非空行攒成段落;遇到块级语法先 flush。
|
|
546
|
+
let paragraph = [];
|
|
547
|
+
const flushParagraph = () => {
|
|
548
|
+
if (paragraph.length === 0) return;
|
|
549
|
+
blocks.push({ type: 'paragraph', segments: parseInlineLite(paragraph.join('\n')) });
|
|
550
|
+
paragraph = [];
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
while (index < lines.length) {
|
|
554
|
+
const line = lines[index];
|
|
555
|
+
|
|
556
|
+
// 围栏代码块
|
|
557
|
+
const fence = FENCE_RE.exec(line);
|
|
558
|
+
if (fence !== null) {
|
|
559
|
+
flushParagraph();
|
|
560
|
+
const codeLines = [];
|
|
561
|
+
index += 1;
|
|
562
|
+
while (index < lines.length && FENCE_RE.exec(lines[index]) === null) {
|
|
563
|
+
codeLines.push(lines[index]);
|
|
564
|
+
index += 1;
|
|
565
|
+
}
|
|
566
|
+
// 吃掉收尾围栏(未闭合时到文件尾)
|
|
567
|
+
if (index < lines.length) index += 1;
|
|
568
|
+
blocks.push({ type: 'code', lang: fence[1] ?? '', text: codeLines.join('\n') });
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// 空行:结束段落
|
|
573
|
+
if (line.trim().length === 0) {
|
|
574
|
+
flushParagraph();
|
|
575
|
+
index += 1;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// 标题
|
|
580
|
+
const heading = HEADING_RE.exec(line);
|
|
581
|
+
if (heading !== null) {
|
|
582
|
+
flushParagraph();
|
|
583
|
+
blocks.push({ type: 'heading', level: heading[1].length, segments: parseInlineLite(heading[2]) });
|
|
584
|
+
index += 1;
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// 分隔线(注意:`---` 也匹配无序列表语法,必须先判 hr)
|
|
589
|
+
if (HR_RE.test(line)) {
|
|
590
|
+
flushParagraph();
|
|
591
|
+
blocks.push({ type: 'hr' });
|
|
592
|
+
index += 1;
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const unordered = UL_RE.exec(line);
|
|
597
|
+
const ordered = unordered === null ? OL_RE.exec(line) : null;
|
|
598
|
+
if (unordered !== null || ordered !== null) {
|
|
599
|
+
flushParagraph();
|
|
600
|
+
const isOrdered = ordered !== null;
|
|
601
|
+
const start = isOrdered ? Number(ordered[1]) : 1;
|
|
602
|
+
const items = [];
|
|
603
|
+
const itemRe = isOrdered ? OL_RE : UL_RE;
|
|
604
|
+
while (index < lines.length) {
|
|
605
|
+
const matched = itemRe.exec(lines[index]);
|
|
606
|
+
if (matched === null) break;
|
|
607
|
+
items.push(parseInlineLite(isOrdered ? matched[2] : matched[1]));
|
|
608
|
+
index += 1;
|
|
609
|
+
}
|
|
610
|
+
blocks.push({ type: 'list', ordered: isOrdered, start, items });
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// 普通段落行(保留换行)
|
|
615
|
+
paragraph.push(line);
|
|
616
|
+
index += 1;
|
|
617
|
+
}
|
|
618
|
+
flushParagraph();
|
|
619
|
+
return blocks;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* 交接单详情面板的轻量 Markdown 渲染(0.3.4 BUG-005)。
|
|
624
|
+
*
|
|
625
|
+
* 解析在 core/markdown-lite.js(构建时内联,同一份源码有 node 单测),这里只把
|
|
626
|
+
* 结构化块映射成 React 节点。全部是 React 文本节点,**从不生成 HTML**,
|
|
627
|
+
* 因此交接单内容里的 `<script>` 只会显示成文字,没有 XSS 面。
|
|
628
|
+
*/
|
|
629
|
+
const MARKDOWN_MONO_STYLE = {
|
|
630
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
|
|
631
|
+
};
|
|
632
|
+
const MARKDOWN_INLINE_CODE_STYLE = {
|
|
633
|
+
...MARKDOWN_MONO_STYLE,
|
|
634
|
+
fontSize: 10.5,
|
|
635
|
+
background: 'rgba(127,127,127,0.16)',
|
|
636
|
+
borderRadius: 4,
|
|
637
|
+
padding: '0 3px',
|
|
638
|
+
};
|
|
639
|
+
const MARKDOWN_CODE_BLOCK_STYLE = {
|
|
640
|
+
...MARKDOWN_MONO_STYLE,
|
|
641
|
+
margin: '6px 0',
|
|
642
|
+
padding: '6px 8px',
|
|
643
|
+
borderRadius: 6,
|
|
644
|
+
background: 'rgba(127,127,127,0.16)',
|
|
645
|
+
overflowX: 'auto',
|
|
646
|
+
whiteSpace: 'pre-wrap',
|
|
647
|
+
wordBreak: 'break-word',
|
|
648
|
+
fontSize: 10.5,
|
|
649
|
+
lineHeight: '15px',
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
/** 行内片段 → React 子节点(文本/粗体/行内代码)。 */
|
|
653
|
+
function renderInlineLite(segments, keyPrefix) {
|
|
654
|
+
return segments.map((segment, index) => {
|
|
655
|
+
const key = `${keyPrefix}-${index}`;
|
|
656
|
+
if (segment.type === 'strong') return h('strong', { key }, segment.text);
|
|
657
|
+
if (segment.type === 'code') return h('code', { key, style: MARKDOWN_INLINE_CODE_STYLE }, segment.text);
|
|
658
|
+
return segment.text;
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* 把 markdown 文本渲染成 React 节点数组(标题/段落/列表/代码块/分隔线)。
|
|
664
|
+
* @param {string} text
|
|
665
|
+
* @returns {object[]}
|
|
666
|
+
*/
|
|
667
|
+
function renderMarkdownLite(text) {
|
|
668
|
+
const blocks = parseMarkdownLite(typeof text === 'string' ? text : '');
|
|
669
|
+
return blocks
|
|
670
|
+
.map((block, index) => {
|
|
671
|
+
const key = `md-${index}`;
|
|
672
|
+
if (block.type === 'heading') {
|
|
673
|
+
return h(
|
|
674
|
+
'div',
|
|
675
|
+
{
|
|
676
|
+
key,
|
|
677
|
+
style: {
|
|
678
|
+
fontWeight: 600,
|
|
679
|
+
fontSize: block.level <= 2 ? 13 : 12,
|
|
680
|
+
lineHeight: '17px',
|
|
681
|
+
marginTop: index === 0 ? 0 : 10,
|
|
682
|
+
marginBottom: 4,
|
|
683
|
+
},
|
|
684
|
+
},
|
|
685
|
+
renderInlineLite(block.segments, `${key}-h`),
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (block.type === 'paragraph') {
|
|
689
|
+
return h(
|
|
690
|
+
'div',
|
|
691
|
+
{ key, style: { margin: '4px 0', whiteSpace: 'pre-wrap', wordBreak: 'break-word' } },
|
|
692
|
+
renderInlineLite(block.segments, `${key}-p`),
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
if (block.type === 'list') {
|
|
696
|
+
return h(
|
|
697
|
+
'div',
|
|
698
|
+
{ key, style: { margin: '2px 0 6px' } },
|
|
699
|
+
block.items.map((item, itemIndex) =>
|
|
700
|
+
h(
|
|
701
|
+
'div',
|
|
702
|
+
{ key: `${key}-i${itemIndex}`, style: { display: 'flex', gap: 6, alignItems: 'flex-start' } },
|
|
703
|
+
h(
|
|
704
|
+
'span',
|
|
705
|
+
{ style: { flex: 'none', minWidth: block.ordered ? 18 : 8, opacity: 0.7 } },
|
|
706
|
+
block.ordered ? `${block.start + itemIndex}.` : '•',
|
|
707
|
+
),
|
|
708
|
+
h(
|
|
709
|
+
'span',
|
|
710
|
+
{ style: { flex: 1, minWidth: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' } },
|
|
711
|
+
renderInlineLite(item, `${key}-i${itemIndex}-s`),
|
|
712
|
+
),
|
|
713
|
+
),
|
|
714
|
+
),
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
if (block.type === 'code') {
|
|
718
|
+
return h('pre', { key, style: MARKDOWN_CODE_BLOCK_STYLE }, block.text);
|
|
719
|
+
}
|
|
720
|
+
if (block.type === 'hr') {
|
|
721
|
+
return h('div', { key, style: { borderTop: '1px solid rgba(127,127,127,0.35)', margin: '8px 0' } });
|
|
722
|
+
}
|
|
723
|
+
return null;
|
|
724
|
+
})
|
|
725
|
+
.filter((node) => node !== null);
|
|
726
|
+
}
|
|
727
|
+
|
|
379
728
|
const fmt = (n) => {
|
|
380
729
|
if (n == null) return '—';
|
|
381
730
|
if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M';
|
|
@@ -665,6 +1014,9 @@ window.__ModuleLoader__.load({
|
|
|
665
1014
|
startSeq: data.compactRange.startSeq ?? null,
|
|
666
1015
|
endSeq: data.compactRange.endSeq ?? null,
|
|
667
1016
|
},
|
|
1017
|
+
// 0.3.5:范围存在但会话没有活 agent —— 菜单据此显示「会话未激活…」而不是
|
|
1018
|
+
// 「暂无可压缩内容」(applyState 只保留白名单字段,新字段必须显式透传)。
|
|
1019
|
+
compactRangeReason: data.compactRangeReason === 'no_agent' ? 'no_agent' : null,
|
|
668
1020
|
});
|
|
669
1021
|
}, []);
|
|
670
1022
|
|
|
@@ -841,16 +1193,7 @@ window.__ModuleLoader__.load({
|
|
|
841
1193
|
const nativeCrossed = nativeValid && ratio > nativeAuto;
|
|
842
1194
|
// 断点缺口:0.9 SVG 单位(≈6.4°),折算成比例;段太短时缺口会吃掉整段,就跳过该段。
|
|
843
1195
|
const ringGapRatio = 0.9 / CIRC;
|
|
844
|
-
|
|
845
|
-
const angle = value * Math.PI * 2 - Math.PI / 2;
|
|
846
|
-
return [10 + RADIUS * Math.cos(angle), 10 + RADIUS * Math.sin(angle)];
|
|
847
|
-
};
|
|
848
|
-
const ringArcPath = (from, to) => {
|
|
849
|
-
const [x1, y1] = ringPoint(from);
|
|
850
|
-
const [x2, y2] = ringPoint(to);
|
|
851
|
-
const largeArc = to - from > 0.5 ? 1 : 0;
|
|
852
|
-
return `M ${x1.toFixed(3)} ${y1.toFixed(3)} A ${RADIUS} ${RADIUS} 0 ${largeArc} 1 ${x2.toFixed(3)} ${y2.toFixed(3)}`;
|
|
853
|
-
};
|
|
1196
|
+
// 点坐标/弧线 path 是共享几何(15-shared.js 的 ringPoint / ringArcPath)。
|
|
854
1197
|
const ringNodes = ring.segments
|
|
855
1198
|
.map((segment, index) => {
|
|
856
1199
|
const trimmed = trimSegment(segment, ringGapRatio, ring.ratio);
|
|
@@ -871,14 +1214,11 @@ window.__ModuleLoader__.load({
|
|
|
871
1214
|
// 刻度用一条「穿过环带」的短线(内 5.6 → 外 9.6)+ 一圈主题底色描边,避免和红色分段糊在一起。
|
|
872
1215
|
const nativeNodes = [];
|
|
873
1216
|
if (nativeValid) {
|
|
874
|
-
const
|
|
875
|
-
const nativeCos = Math.cos(nativeAngle);
|
|
876
|
-
const nativeSin = Math.sin(nativeAngle);
|
|
877
|
-
const tick = { x1: 10 + 5.6 * nativeCos, y1: 10 + 5.6 * nativeSin, x2: 10 + 9.6 * nativeCos, y2: 10 + 9.6 * nativeSin };
|
|
1217
|
+
const tick = nativeTickAt(nativeAuto);
|
|
878
1218
|
// 越过原生压缩线:整环红色闪烁(1s 呼吸);prefers-reduced-motion 下改为常亮红色(不闪)。
|
|
879
1219
|
if (nativeCrossed) {
|
|
880
1220
|
nativeNodes.push(
|
|
881
|
-
h('style', { key: 'native-alert-style' },
|
|
1221
|
+
h('style', { key: 'native-alert-style' }, NATIVE_ALERT_CSS),
|
|
882
1222
|
h('circle', {
|
|
883
1223
|
key: 'native-alert',
|
|
884
1224
|
cx: 10,
|
|
@@ -1125,7 +1465,9 @@ window.__ModuleLoader__.load({
|
|
|
1125
1465
|
|
|
1126
1466
|
// M6:交接单列表(菜单入口)——只读接口,点开可看全文/复制路径。
|
|
1127
1467
|
// P2-C3/C4:请求带序号 + AbortController;只接受「仍然是当前选中文件」的响应。
|
|
1128
|
-
|
|
1468
|
+
// 0.3.4(BUG-006):列表先取最近 20 份,「加载更多」再把 limit 提到 50;
|
|
1469
|
+
// 端点返回 total,界面据此显示「共 N 份(显示最近 M)」并在无更多时隐藏按钮。
|
|
1470
|
+
const loadDocs = useCallback(async (limit = 20) => {
|
|
1129
1471
|
setOpen(false);
|
|
1130
1472
|
setPreview(null);
|
|
1131
1473
|
closeDiff();
|
|
@@ -1141,7 +1483,7 @@ window.__ModuleLoader__.load({
|
|
|
1141
1483
|
const reqId = ++docsRequestRef.current;
|
|
1142
1484
|
setDocs({ loading: true });
|
|
1143
1485
|
try {
|
|
1144
|
-
const res = await fetch(
|
|
1486
|
+
const res = await fetch(`/api/compact-relay-handoffs?limit=${limit}`, {
|
|
1145
1487
|
cache: 'no-store',
|
|
1146
1488
|
...(controller === null ? {} : { signal: controller.signal }),
|
|
1147
1489
|
});
|
|
@@ -1151,7 +1493,9 @@ window.__ModuleLoader__.load({
|
|
|
1151
1493
|
setDocs({ error: data && data.message ? String(data.message) : '读取交接单失败' });
|
|
1152
1494
|
return;
|
|
1153
1495
|
}
|
|
1154
|
-
|
|
1496
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
1497
|
+
const total = typeof data.total === 'number' && Number.isFinite(data.total) ? data.total : items.length;
|
|
1498
|
+
setDocs({ items, total, limit, selected: null });
|
|
1155
1499
|
} catch (error) {
|
|
1156
1500
|
if (reqId !== docsRequestRef.current || (error && error.name === 'AbortError')) return;
|
|
1157
1501
|
setDocs({ error: '读取交接单失败:' + (error && error.message ? error.message : '未知错误') });
|
|
@@ -1399,11 +1743,7 @@ window.__ModuleLoader__.load({
|
|
|
1399
1743
|
wordBreak: 'break-word',
|
|
1400
1744
|
padding: '6px 9px',
|
|
1401
1745
|
borderRadius: 8,
|
|
1402
|
-
|
|
1403
|
-
backdropFilter: theme.panelBackdrop,
|
|
1404
|
-
WebkitBackdropFilter: theme.panelBackdrop,
|
|
1405
|
-
border: `1px solid ${theme.panelBorder}`,
|
|
1406
|
-
boxShadow: theme.panelShadow,
|
|
1746
|
+
...panelChrome(theme),
|
|
1407
1747
|
color: theme.panelFg,
|
|
1408
1748
|
fontSize: 11,
|
|
1409
1749
|
lineHeight: '16px',
|
|
@@ -1518,7 +1858,8 @@ window.__ModuleLoader__.load({
|
|
|
1518
1858
|
role: 'menuitem',
|
|
1519
1859
|
'data-cr-item': action,
|
|
1520
1860
|
disabled,
|
|
1521
|
-
|
|
1861
|
+
// 禁用态的两行条目也把说明挂到 title:鼠标悬停能读到完整原因(含数字)。
|
|
1862
|
+
title: meta.title ?? (oneLine || disabled ? (meta.note ?? sub) : undefined),
|
|
1522
1863
|
onClick: () => {
|
|
1523
1864
|
if (disabled) return;
|
|
1524
1865
|
// M6:压缩项先走预览(meta.onClick),其余动作直接执行。
|
|
@@ -1575,6 +1916,12 @@ window.__ModuleLoader__.load({
|
|
|
1575
1916
|
|
|
1576
1917
|
// undefined=宿主没上报(老版本/未知);null=明确没有可压范围;object=可压多少。
|
|
1577
1918
|
const rangeInfo = state.compactRange;
|
|
1919
|
+
// 0.3.5:有可压范围、但会话还没有活 agent(passivized live session)——preview 需要
|
|
1920
|
+
// 活 agent,所以按钮仍禁用,但文案要说清「发一条消息唤醒 agent」,不能再报「暂无可压缩内容」。
|
|
1921
|
+
const passiveRangeNote =
|
|
1922
|
+
rangeInfo !== null && rangeInfo !== undefined && state.compactRangeReason === 'no_agent'
|
|
1923
|
+
? `会话未激活:发一条消息唤醒 agent 后可压 ${rangeInfo.nodes} 条 · 约 ${fmt(rangeInfo.tokens)} tokens`
|
|
1924
|
+
: null;
|
|
1578
1925
|
const panel = open
|
|
1579
1926
|
? h(
|
|
1580
1927
|
'div',
|
|
@@ -1591,11 +1938,7 @@ window.__ModuleLoader__.load({
|
|
|
1591
1938
|
overflowY: 'auto',
|
|
1592
1939
|
padding: 6,
|
|
1593
1940
|
borderRadius: 12,
|
|
1594
|
-
|
|
1595
|
-
backdropFilter: theme.panelBackdrop,
|
|
1596
|
-
WebkitBackdropFilter: theme.panelBackdrop,
|
|
1597
|
-
border: `1px solid ${theme.panelBorder}`,
|
|
1598
|
-
boxShadow: theme.panelShadow,
|
|
1941
|
+
...panelChrome(theme),
|
|
1599
1942
|
zIndex: 40,
|
|
1600
1943
|
},
|
|
1601
1944
|
},
|
|
@@ -1620,9 +1963,11 @@ window.__ModuleLoader__.load({
|
|
|
1620
1963
|
? `暂无可压内容:有 ${pins.count} 条消息被锁定保护(先点「解锁消息」)`
|
|
1621
1964
|
: '暂无可压缩内容(会话太短,或近期会话需保留原文)',
|
|
1622
1965
|
}
|
|
1623
|
-
:
|
|
1624
|
-
? {
|
|
1625
|
-
:
|
|
1966
|
+
: passiveRangeNote !== null
|
|
1967
|
+
? { disabled: true, title: passiveRangeNote, note: passiveRangeNote }
|
|
1968
|
+
: rangeInfo !== undefined
|
|
1969
|
+
? { note: `可压 ${rangeInfo.nodes} 条 · 约 ${fmt(rangeInfo.tokens)} tokens · 点击先看压缩预估` }
|
|
1970
|
+
: { note: '点击先看压缩预估(范围/容量)' }),
|
|
1626
1971
|
onClick: () => {
|
|
1627
1972
|
void startPreview();
|
|
1628
1973
|
},
|
|
@@ -1702,11 +2047,7 @@ window.__ModuleLoader__.load({
|
|
|
1702
2047
|
maxWidth: 'calc(100vw - 16px)',
|
|
1703
2048
|
padding: '10px 12px',
|
|
1704
2049
|
borderRadius: 12,
|
|
1705
|
-
|
|
1706
|
-
backdropFilter: theme.panelBackdrop,
|
|
1707
|
-
WebkitBackdropFilter: theme.panelBackdrop,
|
|
1708
|
-
border: `1px solid ${theme.panelBorder}`,
|
|
1709
|
-
boxShadow: theme.panelShadow,
|
|
2050
|
+
...panelChrome(theme),
|
|
1710
2051
|
color: theme.panelFg,
|
|
1711
2052
|
zIndex: 42,
|
|
1712
2053
|
},
|
|
@@ -1821,12 +2162,8 @@ window.__ModuleLoader__.load({
|
|
|
1821
2162
|
gap: 8,
|
|
1822
2163
|
padding: '10px 12px',
|
|
1823
2164
|
borderRadius: 12,
|
|
1824
|
-
|
|
1825
|
-
backdropFilter: theme.panelBackdrop,
|
|
1826
|
-
WebkitBackdropFilter: theme.panelBackdrop,
|
|
2165
|
+
...panelChrome(theme),
|
|
1827
2166
|
color: theme.panelFg,
|
|
1828
|
-
border: `1px solid ${theme.panelBorder}`,
|
|
1829
|
-
boxShadow: theme.panelShadow,
|
|
1830
2167
|
zIndex: 44,
|
|
1831
2168
|
},
|
|
1832
2169
|
},
|
|
@@ -1917,6 +2254,12 @@ window.__ModuleLoader__.load({
|
|
|
1917
2254
|
),
|
|
1918
2255
|
);
|
|
1919
2256
|
|
|
2257
|
+
// 0.3.4(BUG-006):total 与当前窗口(20/50),用于列表底部与「加载更多」。
|
|
2258
|
+
const docsItems = docs !== null && Array.isArray(docs.items) ? docs.items : [];
|
|
2259
|
+
const docsTotal =
|
|
2260
|
+
docs !== null && typeof docs.total === 'number' && Number.isFinite(docs.total)
|
|
2261
|
+
? docs.total
|
|
2262
|
+
: docsItems.length;
|
|
1920
2263
|
const docsPanel =
|
|
1921
2264
|
docs === null
|
|
1922
2265
|
? null
|
|
@@ -1937,12 +2280,8 @@ window.__ModuleLoader__.load({
|
|
|
1937
2280
|
flexDirection: 'column',
|
|
1938
2281
|
overflow: 'hidden',
|
|
1939
2282
|
borderRadius: 12,
|
|
1940
|
-
|
|
1941
|
-
backdropFilter: theme.panelBackdrop,
|
|
1942
|
-
WebkitBackdropFilter: theme.panelBackdrop,
|
|
2283
|
+
...panelChrome(theme),
|
|
1943
2284
|
color: theme.panelFg,
|
|
1944
|
-
border: `1px solid ${theme.panelBorder}`,
|
|
1945
|
-
boxShadow: theme.panelShadow,
|
|
1946
2285
|
zIndex: 43,
|
|
1947
2286
|
},
|
|
1948
2287
|
},
|
|
@@ -1964,7 +2303,7 @@ window.__ModuleLoader__.load({
|
|
|
1964
2303
|
{ style: { flex: 1, minWidth: 0 } },
|
|
1965
2304
|
docs.selected !== undefined && docs.selected !== null
|
|
1966
2305
|
? '交接单详情'
|
|
1967
|
-
: `历史交接单(${
|
|
2306
|
+
: `历史交接单(${docsTotal})`,
|
|
1968
2307
|
),
|
|
1969
2308
|
h('button', { type: 'button', 'data-cr-docs-close': '1', onClick: closeDocs, style: smallBtnStyle }, '关闭'),
|
|
1970
2309
|
),
|
|
@@ -1981,7 +2320,7 @@ window.__ModuleLoader__.load({
|
|
|
1981
2320
|
'button',
|
|
1982
2321
|
{
|
|
1983
2322
|
type: 'button',
|
|
1984
|
-
onClick: () => setDocs(
|
|
2323
|
+
onClick: () => setDocs((prev) => (prev === null ? null : { ...prev, selected: null })),
|
|
1985
2324
|
style: smallBtnStyle,
|
|
1986
2325
|
},
|
|
1987
2326
|
'← 列表',
|
|
@@ -2004,21 +2343,28 @@ window.__ModuleLoader__.load({
|
|
|
2004
2343
|
),
|
|
2005
2344
|
),
|
|
2006
2345
|
h(
|
|
2007
|
-
'
|
|
2346
|
+
'div',
|
|
2008
2347
|
{
|
|
2348
|
+
// 0.3.4(BUG-005):交接单详情从 <pre> 纯文本改成轻量 markdown 渲染。
|
|
2349
|
+
// 解析器是 core/markdown-lite.js(构建时内联),渲染只产生 React
|
|
2350
|
+
// 文本节点,不生成 HTML、不引第三方依赖。
|
|
2009
2351
|
'data-cr-doc-content': '1',
|
|
2010
2352
|
style: {
|
|
2011
2353
|
margin: 0,
|
|
2012
2354
|
padding: '8px 12px',
|
|
2013
2355
|
overflowY: 'auto',
|
|
2014
|
-
|
|
2356
|
+
overflowX: 'hidden',
|
|
2015
2357
|
wordBreak: 'break-word',
|
|
2016
2358
|
fontSize: 11,
|
|
2017
2359
|
lineHeight: '16px',
|
|
2018
2360
|
maxHeight: 'min(50vh, 320px)',
|
|
2019
2361
|
},
|
|
2020
2362
|
},
|
|
2021
|
-
docs.contentLoading === true
|
|
2363
|
+
docs.contentLoading === true
|
|
2364
|
+
? '读取中…'
|
|
2365
|
+
: docs.contentError !== undefined && docs.contentError !== null
|
|
2366
|
+
? h('div', { style: { color: COLORS.error } }, String(docs.contentError))
|
|
2367
|
+
: renderMarkdownLite(docs.content ?? ''),
|
|
2022
2368
|
),
|
|
2023
2369
|
)
|
|
2024
2370
|
: h(
|
|
@@ -2026,8 +2372,8 @@ window.__ModuleLoader__.load({
|
|
|
2026
2372
|
{ style: { overflowY: 'auto', padding: 4 } },
|
|
2027
2373
|
docs.error !== undefined && docs.error !== null
|
|
2028
2374
|
? h('div', { style: { padding: '8px 10px', fontSize: 11, color: COLORS.error } }, String(docs.error))
|
|
2029
|
-
:
|
|
2030
|
-
?
|
|
2375
|
+
: docsItems.length > 0
|
|
2376
|
+
? [...docsItems.map((entry) =>
|
|
2031
2377
|
h(
|
|
2032
2378
|
'button',
|
|
2033
2379
|
{
|
|
@@ -2070,7 +2416,38 @@ window.__ModuleLoader__.load({
|
|
|
2070
2416
|
`${fmtTime(entry.mtime)} · ${Math.max(1, Math.round((Number(entry.size) || 0) / 1024))}KB · ${entry.name}`,
|
|
2071
2417
|
),
|
|
2072
2418
|
),
|
|
2073
|
-
)
|
|
2419
|
+
),
|
|
2420
|
+
h(
|
|
2421
|
+
'div',
|
|
2422
|
+
{
|
|
2423
|
+
key: 'docs-footer',
|
|
2424
|
+
style: {
|
|
2425
|
+
display: 'flex',
|
|
2426
|
+
alignItems: 'center',
|
|
2427
|
+
gap: 8,
|
|
2428
|
+
padding: '6px 10px',
|
|
2429
|
+
borderTop: `1px solid ${theme.panelBorder}`,
|
|
2430
|
+
fontSize: 10,
|
|
2431
|
+
color: theme.subText,
|
|
2432
|
+
},
|
|
2433
|
+
},
|
|
2434
|
+
h('span', { style: { flex: 1, minWidth: 0 } }, `共 ${docsTotal} 份(显示最近 ${docsItems.length})`),
|
|
2435
|
+
docsItems.length < docsTotal && docsItems.length < 50
|
|
2436
|
+
? h(
|
|
2437
|
+
'button',
|
|
2438
|
+
{
|
|
2439
|
+
type: 'button',
|
|
2440
|
+
'data-cr-docs-more': '1',
|
|
2441
|
+
onClick: () => {
|
|
2442
|
+
void loadDocs(50);
|
|
2443
|
+
},
|
|
2444
|
+
style: smallBtnStyle,
|
|
2445
|
+
},
|
|
2446
|
+
'加载更多',
|
|
2447
|
+
)
|
|
2448
|
+
: null,
|
|
2449
|
+
),
|
|
2450
|
+
]
|
|
2074
2451
|
: h(
|
|
2075
2452
|
'div',
|
|
2076
2453
|
{ style: { padding: '10px 12px', fontSize: 11, color: theme.subText } },
|
|
@@ -2106,12 +2483,8 @@ window.__ModuleLoader__.load({
|
|
|
2106
2483
|
flexDirection: 'column',
|
|
2107
2484
|
overflow: 'hidden',
|
|
2108
2485
|
borderRadius: 12,
|
|
2109
|
-
|
|
2110
|
-
backdropFilter: theme.panelBackdrop,
|
|
2111
|
-
WebkitBackdropFilter: theme.panelBackdrop,
|
|
2486
|
+
...panelChrome(theme),
|
|
2112
2487
|
color: theme.panelFg,
|
|
2113
|
-
border: `1px solid ${theme.panelBorder}`,
|
|
2114
|
-
boxShadow: theme.panelShadow,
|
|
2115
2488
|
zIndex: 43,
|
|
2116
2489
|
},
|
|
2117
2490
|
},
|
|
@@ -2208,16 +2581,12 @@ window.__ModuleLoader__.load({
|
|
|
2208
2581
|
textAlign: 'left',
|
|
2209
2582
|
padding: '8px 12px',
|
|
2210
2583
|
borderRadius: 10,
|
|
2211
|
-
|
|
2212
|
-
backdropFilter: theme.panelBackdrop,
|
|
2213
|
-
WebkitBackdropFilter: theme.panelBackdrop,
|
|
2584
|
+
...panelChrome(theme),
|
|
2214
2585
|
color: theme.panelFg,
|
|
2215
|
-
border: `1px solid ${theme.panelBorder}`,
|
|
2216
2586
|
borderLeft: `3px solid ${/失败|错误|无法|没有可|不满足|拒绝|未知/.test(toastText) ? COLORS.ringAdviseHandoff : '#2fb344'}`,
|
|
2217
2587
|
fontSize: 12,
|
|
2218
2588
|
lineHeight: '18px',
|
|
2219
2589
|
zIndex: 41,
|
|
2220
|
-
boxShadow: theme.panelShadow,
|
|
2221
2590
|
display: 'flex',
|
|
2222
2591
|
alignItems: 'center',
|
|
2223
2592
|
gap: 8,
|
|
@@ -2232,16 +2601,7 @@ window.__ModuleLoader__.load({
|
|
|
2232
2601
|
type: 'button',
|
|
2233
2602
|
'data-cr-toast-cancel': '1',
|
|
2234
2603
|
onClick: cancelRun,
|
|
2235
|
-
style: {
|
|
2236
|
-
flex: 'none',
|
|
2237
|
-
border: `1px solid ${theme.panelBorder}`,
|
|
2238
|
-
borderRadius: 6,
|
|
2239
|
-
padding: '2px 8px',
|
|
2240
|
-
fontSize: 11,
|
|
2241
|
-
cursor: 'pointer',
|
|
2242
|
-
background: 'transparent',
|
|
2243
|
-
color: theme.panelFg,
|
|
2244
|
-
},
|
|
2604
|
+
style: { ...smallBtnStyle, flex: 'none' },
|
|
2245
2605
|
},
|
|
2246
2606
|
'取消',
|
|
2247
2607
|
)
|
|
@@ -2958,16 +3318,7 @@ window.__ModuleLoader__.load({
|
|
|
2958
3318
|
// BUG-009(2026-10-20):每个示例环单独构造参数——比例按各自语义取、hint 用 null;
|
|
2959
3319
|
// 不再共用 minRatio + 0.12 的假断点(那会让 5 个环的断点全挤在同一处、颜色也对不上)。
|
|
2960
3320
|
const demoRatio = (ratio, hintRatio) => ringSegments(ratio, form.thresholds.compactRatio, form.thresholds.handoffRatio, hintRatio ?? null);
|
|
2961
|
-
|
|
2962
|
-
const angle = value * Math.PI * 2 - Math.PI / 2;
|
|
2963
|
-
return [10 + 8 * Math.cos(angle), 10 + 8 * Math.sin(angle)];
|
|
2964
|
-
};
|
|
2965
|
-
const arcPath = (from, to) => {
|
|
2966
|
-
const [x1, y1] = point(from);
|
|
2967
|
-
const [x2, y2] = point(to);
|
|
2968
|
-
const large = to - from > 0.5 ? 1 : 0;
|
|
2969
|
-
return `M ${x1.toFixed(3)} ${y1.toFixed(3)} A 8 8 0 ${large} 1 ${x2.toFixed(3)} ${y2.toFixed(3)}`;
|
|
2970
|
-
};
|
|
3321
|
+
// 点坐标/弧线 path 是共享几何(15-shared.js 的 ringPoint / ringArcPath)。
|
|
2971
3322
|
const colorOf = (key) =>
|
|
2972
3323
|
key === 'stage' ? previewLight.stage : key === 'compact' ? previewLight.compact : key === 'handoff' ? previewLight.handoff : previewLight.idle;
|
|
2973
3324
|
const demoRing = (ring, options = {}) => {
|
|
@@ -2977,18 +3328,7 @@ window.__ModuleLoader__.load({
|
|
|
2977
3328
|
// 原生自动压缩线:和指示灯一样用「径向紫色刻度 + 底色描边」;越过线后整环红色闪烁。
|
|
2978
3329
|
const nativeAt =
|
|
2979
3330
|
typeof options.nativeAt === 'number' && options.nativeAt > 0.02 && options.nativeAt < 1 ? options.nativeAt : null;
|
|
2980
|
-
const
|
|
2981
|
-
const angle = value * Math.PI * 2 - Math.PI / 2;
|
|
2982
|
-
return [10 + radius * Math.cos(angle), 10 + radius * Math.sin(angle)];
|
|
2983
|
-
};
|
|
2984
|
-
const nativeTick =
|
|
2985
|
-
nativeAt === null
|
|
2986
|
-
? null
|
|
2987
|
-
: (() => {
|
|
2988
|
-
const [x1, y1] = pointAt(nativeAt, 5.6);
|
|
2989
|
-
const [x2, y2] = pointAt(nativeAt, 9.6);
|
|
2990
|
-
return { x1, y1, x2, y2 };
|
|
2991
|
-
})();
|
|
3331
|
+
const nativeTick = nativeAt === null ? null : nativeTickAt(nativeAt);
|
|
2992
3332
|
const nativeCrossed = nativeAt !== null && ring.ratio > nativeAt + 1e-9;
|
|
2993
3333
|
return h(
|
|
2994
3334
|
'svg',
|
|
@@ -3001,17 +3341,17 @@ window.__ModuleLoader__.load({
|
|
|
3001
3341
|
const from = segment.from <= 1e-9 ? segment.from : segment.from + gap;
|
|
3002
3342
|
const to = Math.abs(segment.to - ring.ratio) < 1e-9 ? segment.to : segment.to - gap;
|
|
3003
3343
|
if (to - from <= 0.001) return null;
|
|
3004
|
-
return h('path', { key: index, d:
|
|
3344
|
+
return h('path', { key: index, d: ringArcPath(from, to), fill: 'none', stroke: colorOf(segment.color), strokeWidth: 2, strokeLinecap: 'round', 'data-cr-demo': segment.color });
|
|
3005
3345
|
})
|
|
3006
3346
|
.filter((node) => node !== null),
|
|
3007
3347
|
// 断点缺口用「背景色小段」强调一下(教学图里看得清)
|
|
3008
3348
|
...gaps.map((at, index) =>
|
|
3009
|
-
h('path', { key: `b${index}`, d:
|
|
3349
|
+
h('path', { key: `b${index}`, d: ringArcPath(at - 0.01, at + 0.01), fill: 'none', stroke: 'var(--dsw-alias-bg-base, #fff)', strokeWidth: 3, 'data-cr-demo-gap': String(at) }),
|
|
3010
3350
|
),
|
|
3011
3351
|
// 越过原生压缩线:整环红色闪烁(教学图里也演示出来)。
|
|
3012
3352
|
...(nativeCrossed
|
|
3013
3353
|
? [
|
|
3014
|
-
h('style', { key: 'native-alert-style' },
|
|
3354
|
+
h('style', { key: 'native-alert-style' }, NATIVE_ALERT_CSS),
|
|
3015
3355
|
h('circle', {
|
|
3016
3356
|
key: 'native-alert',
|
|
3017
3357
|
cx: 10,
|