@yfwu2020/dsh-selection-explain 0.12.0 → 0.13.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 +18 -5
- package/lib/chat-context.js +6 -4
- package/lib/chat-context.js.map +1 -1
- package/lib/client.js +1553 -60
- package/lib/client.js.map +4 -4
- package/lib/config-values.js +5 -1
- package/lib/config-values.js.map +1 -1
- package/lib/host/analyze.js +74 -23
- package/lib/host/analyze.js.map +1 -1
- package/lib/host/chat-context.js +8 -3
- package/lib/host/chat-context.js.map +1 -1
- package/lib/host/config.js +6 -0
- package/lib/host/config.js.map +1 -1
- package/lib/host/history-store.js +18 -6
- package/lib/host/history-store.js.map +1 -1
- package/lib/host/plugin.js +31 -0
- package/lib/host/plugin.js.map +1 -1
- package/lib/host/shot.js +968 -0
- package/lib/host/shot.js.map +1 -0
- package/lib/prompts.js +54 -1
- package/lib/prompts.js.map +1 -1
- package/lib/settings.js +13 -2
- package/lib/settings.js.map +1 -1
- package/lib/types/chat-context.d.ts +2 -2
- package/lib/types/config-values.d.ts +24 -0
- package/lib/types/host/config.d.ts +12 -0
- package/lib/types/host/shot.d.ts +73 -0
- package/lib/types/host/types.d.ts +13 -0
- package/lib/types/prompts.d.ts +21 -0
- package/package.json +4 -2
package/lib/client.js
CHANGED
|
@@ -119,6 +119,9 @@ var HISTORY = "/selection-explain/api/history";
|
|
|
119
119
|
var MODELS = "/selection-explain/api/models";
|
|
120
120
|
var ATTACH = "/selection-explain/api/attach";
|
|
121
121
|
var ATTACHMENT = "/selection-explain/api/attachment";
|
|
122
|
+
var SHOT_START = "/selection-explain/api/shot/start";
|
|
123
|
+
var SHOT_PENDING = "/selection-explain/api/shot/pending";
|
|
124
|
+
var SHOT_IMAGE = "/selection-explain/api/shot/image";
|
|
122
125
|
var ATTACH_MAX_ITEMS = 10;
|
|
123
126
|
var ATTACH_MAX_ITEM_BYTES = 20 * 1024 * 1024;
|
|
124
127
|
var SETTINGS = "/selection-explain/api/settings";
|
|
@@ -282,6 +285,9 @@ var CSS = [
|
|
|
282
285
|
// 可以自动换行、超过 84px 内部滚动,但**不显示滚动条**。
|
|
283
286
|
// ⚠️ flex:0 0 auto 不能省:滚动区是 flex 列,而 overflow 非 visible 的 flex 项目
|
|
284
287
|
// 自动最小高度是 0 → 内容一多就被压扁,文字直接看不全(实测踩过)。
|
|
288
|
+
// 顶部那行是"生成的标题"时:两行封顶,超出用省略号(而不是出现滚动条)
|
|
289
|
+
'.dsh-sel-quote[data-title="1"]{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;',
|
|
290
|
+
"overflow:hidden;white-space:normal;max-height:none}",
|
|
285
291
|
".dsh-sel-quote{margin:0;padding:7px 11px;border-radius:10px;flex:0 0 auto;border-left:3px solid var(--sel-a1,#0d9488);",
|
|
286
292
|
"background:var(--dsw-alias-interactive-bg-hover,rgba(140,140,140,.11));color:var(--dsw-alias-label-secondary,inherit);",
|
|
287
293
|
"font-size:12.5px;line-height:1.6;box-sizing:border-box;max-width:100%;max-height:84px;",
|
|
@@ -458,6 +464,10 @@ var CSS = [
|
|
|
458
464
|
"display:grid;place-items:center;color:var(--dsw-alias-label-secondary,inherit)}",
|
|
459
465
|
".dsh-sel-thumb img{width:100%;height:100%;object-fit:cover;display:block}",
|
|
460
466
|
".dsh-sel-thumb svg{width:15px;height:15px;opacity:.55}",
|
|
467
|
+
// 可预览的缩略图:放大镜光标 + 键盘焦点环(它是 role=button,能 Tab 到)
|
|
468
|
+
'.dsh-sel-thumb[data-preview="1"]{cursor:zoom-in}',
|
|
469
|
+
'.dsh-sel-thumb[data-preview="1"]:focus-visible{outline:2px solid var(--sel-a1,#0d9488);outline-offset:1px}',
|
|
470
|
+
'.dsh-sel-thumb[data-preview="1"]:hover img{filter:brightness(1.06)}',
|
|
461
471
|
'.dsh-sel-thumb[data-state="error"]{border-color:color-mix(in srgb,var(--sel-bad,#e5534b) 70%,transparent)}',
|
|
462
472
|
'.dsh-sel-thumb[data-state="error"] svg{opacity:.9;color:var(--sel-bad,#e5534b)}',
|
|
463
473
|
// 转圈 + 不定态进度条:fetch 拿不到上传进度,所以**不假装知道百分比**
|
|
@@ -535,6 +545,50 @@ var CSS = [
|
|
|
535
545
|
"border-radius:5px;line-height:15px;background:var(--sel-fill,#0f766e);color:var(--sel-fill-fg,#fff)}",
|
|
536
546
|
".dsh-sel-bqfilename{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:190px}",
|
|
537
547
|
".dsh-sel-bqfilemeta{flex:0 0 auto;font-size:10.5px;opacity:.6}",
|
|
548
|
+
// ── 图片预览(灯箱)──
|
|
549
|
+
// 挂在 layer 上、铺满整扇窗口:小窗只有 540 宽,把截图塞回小窗里预览等于没放大。
|
|
550
|
+
// 层级压过面板与语音卡;pointer-events 必须显式打开 —— layer 自己是穿透的,
|
|
551
|
+
// 不写这一行的话灯箱看得见却点不着。
|
|
552
|
+
".dsh-sel-lightbox{position:fixed;inset:0;z-index:" + String(Z_PANEL + 2) + ";display:none;flex-direction:column;",
|
|
553
|
+
"align-items:center;justify-content:center;padding:48px 32px 34px;box-sizing:border-box;",
|
|
554
|
+
"background:rgba(9,11,13,.8);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);",
|
|
555
|
+
"pointer-events:auto;-webkit-app-region:no-drag;color:#fff;font:400 12.5px/1.5 " + FONT + "}",
|
|
556
|
+
'.dsh-sel-lightbox[data-open="1"]{display:flex;animation:dsh-sel-lightfade .12s ease}',
|
|
557
|
+
"@keyframes dsh-sel-lightfade{from{opacity:0}to{opacity:1}}",
|
|
558
|
+
// 顶栏:文件名(左,过长省略)+ 关闭键(右)。用 absolute 让它压在图片上方,
|
|
559
|
+
// 不用再给图片让出一条高度 —— 纵向空间本来就紧张。
|
|
560
|
+
".dsh-sel-lightbox-bar{position:absolute;top:0;left:0;right:0;height:44px;display:flex;align-items:center;gap:12px;",
|
|
561
|
+
"padding:0 14px 0 20px;box-sizing:border-box;background:linear-gradient(rgba(0,0,0,.5),transparent)}",
|
|
562
|
+
".dsh-sel-lightbox-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",
|
|
563
|
+
"font-size:12.5px;opacity:.9}",
|
|
564
|
+
// 计数(3 / 7):只在多图时出现,等宽数字 —— 翻页时数字不跟着左右抖。
|
|
565
|
+
".dsh-sel-lightbox-count{flex:0 0 auto;font-size:11.5px;opacity:.62;font-variant-numeric:tabular-nums;letter-spacing:.3px}",
|
|
566
|
+
'.dsh-sel-lightbox[data-gallery="0"] .dsh-sel-lightbox-count{display:none}',
|
|
567
|
+
".dsh-sel-lightbox-x{flex:0 0 auto;width:28px;height:28px;padding:0;border:0;border-radius:14px;cursor:pointer;",
|
|
568
|
+
"display:grid;place-items:center;background:rgba(255,255,255,.12);color:#fff;font:inherit;opacity:.9}",
|
|
569
|
+
".dsh-sel-lightbox-x:hover{background:rgba(255,255,255,.22);opacity:1}",
|
|
570
|
+
".dsh-sel-lightbox-x svg{width:14px;height:14px;display:block}",
|
|
571
|
+
".dsh-sel-lightbox-x:focus-visible{outline:2px solid #fff;outline-offset:2px}",
|
|
572
|
+
// 左右翻页:贴两侧垂直居中,和 ✕ 同一套半透明白底。
|
|
573
|
+
// 「到头」用 disabled 表示(22% 不透明)—— 小窗/窗口里没有滑动手势,全靠这两个键 + 方向键。
|
|
574
|
+
".dsh-sel-lightbox-nav{position:absolute;top:50%;margin-top:-20px;width:40px;height:40px;padding:0;border:0;border-radius:20px;",
|
|
575
|
+
"display:grid;place-items:center;cursor:pointer;background:rgba(255,255,255,.12);color:#fff;opacity:.9;",
|
|
576
|
+
"box-shadow:0 4px 16px rgba(0,0,0,.3);transition:background .12s ease,opacity .12s ease}",
|
|
577
|
+
".dsh-sel-lightbox-nav:hover:not(:disabled){background:rgba(255,255,255,.24);opacity:1}",
|
|
578
|
+
".dsh-sel-lightbox-nav:focus-visible{outline:2px solid #fff;outline-offset:2px}",
|
|
579
|
+
".dsh-sel-lightbox-nav:disabled{opacity:.22;cursor:default}",
|
|
580
|
+
'.dsh-sel-lightbox-nav[data-dir="prev"]{left:12px}',
|
|
581
|
+
'.dsh-sel-lightbox-nav[data-dir="next"]{right:12px}',
|
|
582
|
+
'.dsh-sel-lightbox[data-gallery="0"] .dsh-sel-lightbox-nav{display:none}',
|
|
583
|
+
// 舞台:占满可用高度,图 contain 到最大;点击图片本身不算"点空白",不会误关。
|
|
584
|
+
".dsh-sel-lightbox-stage{flex:1 1 auto;width:100%;min-height:0;display:flex;align-items:center;justify-content:center}",
|
|
585
|
+
".dsh-sel-lightbox-img{display:block;max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;",
|
|
586
|
+
"border-radius:10px;background:rgba(255,255,255,.04);box-shadow:0 24px 64px rgba(0,0,0,.55);cursor:default}",
|
|
587
|
+
'.dsh-sel-lightbox[data-error="1"] .dsh-sel-lightbox-img{display:none}',
|
|
588
|
+
".dsh-sel-lightbox-fail{display:none;margin:0;font-size:12.5px;opacity:.72}",
|
|
589
|
+
'.dsh-sel-lightbox[data-error="1"] .dsh-sel-lightbox-fail{display:block}',
|
|
590
|
+
".dsh-sel-lightbox-hint{margin:0;font-size:11px;opacity:.5}",
|
|
591
|
+
'@media (prefers-reduced-motion:reduce){.dsh-sel-lightbox[data-open="1"]{animation:none}}',
|
|
538
592
|
// 「引用整条」:贴在助手气泡末尾,**平时不出现**(正文优先),鼠标移到这条消息上才浮出来。
|
|
539
593
|
// 键盘 Tab 也能到(:focus-visible 同样点亮),触屏上另有浮标那条路。
|
|
540
594
|
".dsh-sel-bubquote{margin-top:7px;display:inline-flex;align-items:center;gap:4px;padding:2px 9px;",
|
|
@@ -692,6 +746,7 @@ var CSS = [
|
|
|
692
746
|
"background:var(--sel-warn-bg,rgba(255,166,87,.14));color:var(--sel-warn-fg,#b45309);font-size:11.5px;line-height:1.4}",
|
|
693
747
|
".dsh-sel-undo button{margin-left:auto;border:0;background:transparent;color:inherit;font:inherit;font-weight:600;cursor:pointer;text-decoration:underline}",
|
|
694
748
|
".dsh-sel-historytext{font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
749
|
+
".dsh-sel-historysub{font-size:10.5px;line-height:1.4;opacity:.62;margin-top:1px;}",
|
|
695
750
|
".dsh-sel-historymeta{display:flex;align-items:center;gap:6px;font-size:11px;opacity:.5;font-variant-numeric:tabular-nums;min-width:0}",
|
|
696
751
|
".dsh-sel-historymeta>span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
697
752
|
".dsh-sel-historymeta .dsh-sel-historypin{opacity:.9}",
|
|
@@ -956,6 +1011,32 @@ var CSS = [
|
|
|
956
1011
|
".dsh-sel-uline{position:fixed;height:2px;border-radius:1px;pointer-events:none}",
|
|
957
1012
|
// 牵引线是 **SVG 贝塞尔**(原来是一根 2px 竖条 → 控制点和端点同 x,退化成直线,
|
|
958
1013
|
// 用户一眼看出"这不是贝塞尔")。现在两端各给一个反向的横向控制量,做出可见的 S 形。
|
|
1014
|
+
// ── 截图提问:选框(选区落在窗口内时)/ 贴边缩略图(落在窗口外时)──
|
|
1015
|
+
".dsh-sel-shotframe{position:fixed;z-index:2147482997;display:none;pointer-events:none;",
|
|
1016
|
+
"border:2px solid var(--sel-a1,#0d9488);border-radius:3px;",
|
|
1017
|
+
"box-shadow:0 0 0 1px rgba(255,255,255,.85),0 0 0 4px color-mix(in srgb,var(--sel-a1,#0d9488) 14%,transparent)}",
|
|
1018
|
+
'.dsh-sel-shotframe[data-show="1"]{display:block}',
|
|
1019
|
+
".dsh-sel-shotframe-tag{position:absolute;left:-2px;bottom:-19px;padding:1px 6px;border-radius:5px;",
|
|
1020
|
+
"background:var(--sel-fill,#0f766e);color:var(--sel-fill-fg,#fff);font-size:10px;line-height:1.6;white-space:nowrap}",
|
|
1021
|
+
".dsh-sel-shotchip{position:fixed;z-index:2147482997;display:none;align-items:center;gap:7px;box-sizing:border-box;",
|
|
1022
|
+
"padding:5px 9px 5px 5px;border-radius:11px;background:var(--dsw-alias-bg-base,#fff);",
|
|
1023
|
+
"border:.5px solid var(--dsw-alias-border-l3,rgba(140,140,140,.35));box-shadow:0 8px 22px rgba(0,0,0,.24)}",
|
|
1024
|
+
'.dsh-sel-shotchip[data-show="1"]{display:flex}',
|
|
1025
|
+
".dsh-sel-shotchip img{display:block;width:56px;height:32px;object-fit:cover;border-radius:6px;",
|
|
1026
|
+
"border:1.5px solid #e5484d;flex:0 0 auto;background:var(--dsw-alias-interactive-bg-hover,rgba(140,140,140,.14))}",
|
|
1027
|
+
".dsh-sel-shotchip-tx{display:flex;flex-direction:column;font-size:10.5px;line-height:1.35;white-space:nowrap;",
|
|
1028
|
+
"color:var(--dsw-alias-label-secondary,rgba(90,90,90,.9))}",
|
|
1029
|
+
".dsh-sel-shotchip-tx b{font-size:10.5px;font-weight:600;color:var(--dsw-alias-label-primary,inherit)}",
|
|
1030
|
+
// ── 截图失败的可见提示(Toast)──
|
|
1031
|
+
// 以前所有失败都只写进 /shot/status 诊断,用户按了快捷键没反应。这条浮层是唯一的用户可见面:
|
|
1032
|
+
// 位置放在底部中间(不挡卡片/面板),点击可立刻关掉,4.5 秒后自动消失。
|
|
1033
|
+
".dsh-sel-shot-toast{position:fixed;left:50%;bottom:76px;transform:translate(-50%,6px);z-index:2147483005;",
|
|
1034
|
+
"box-sizing:border-box;max-width:min(82vw,460px);padding:9px 14px;border-radius:10px;",
|
|
1035
|
+
"background:rgba(28,32,36,.95);color:#fff;font-size:12px;line-height:1.55;text-align:left;",
|
|
1036
|
+
"box-shadow:0 10px 28px rgba(0,0,0,.28);opacity:0;pointer-events:none;",
|
|
1037
|
+
"transition:opacity .16s ease,transform .16s ease}",
|
|
1038
|
+
'.dsh-sel-shot-toast[data-show="1"]{opacity:1;pointer-events:auto;transform:translate(-50%,0)}',
|
|
1039
|
+
'.dsh-sel-shot-toast[data-tone="error"]{background:rgba(153,27,27,.96)}',
|
|
959
1040
|
".dsh-sel-thread{position:fixed;left:0;top:0;width:100%;height:100%;pointer-events:none;",
|
|
960
1041
|
"z-index:2147482998;overflow:visible;display:none;opacity:0;transition:opacity .14s ease}",
|
|
961
1042
|
".dsh-sel-thread path{fill:none;stroke-width:1.6;stroke-dasharray:4 4;",
|
|
@@ -2106,7 +2187,17 @@ function createSettingsController(env) {
|
|
|
2106
2187
|
var settingsRailButtons = [];
|
|
2107
2188
|
var settingsPanes = [];
|
|
2108
2189
|
var settingsSegs = [];
|
|
2109
|
-
var SLOT_LABEL = {
|
|
2190
|
+
var SLOT_LABEL = {
|
|
2191
|
+
off: "\u5173",
|
|
2192
|
+
low: "\u4F4E",
|
|
2193
|
+
high: "\u9AD8",
|
|
2194
|
+
max: "\u6700\u9AD8",
|
|
2195
|
+
// 截图取景范围(与推理档位共用 seg 标签表;值互不重叠)
|
|
2196
|
+
screen: "\u5168\u5C4F",
|
|
2197
|
+
window: "\u6574\u7A97",
|
|
2198
|
+
margin: "\u9009\u533A+\u7559\u767D",
|
|
2199
|
+
selection: "\u4EC5\u9009\u533A"
|
|
2200
|
+
};
|
|
2110
2201
|
var SLOT_ORDER = ["off", "low", "high", "max"];
|
|
2111
2202
|
var settingsEffortHints = [];
|
|
2112
2203
|
var settingsMenus = [];
|
|
@@ -3159,7 +3250,8 @@ function createVoiceCardController(env) {
|
|
|
3159
3250
|
var vcardHandoff = null;
|
|
3160
3251
|
var panelGrowTimer = null;
|
|
3161
3252
|
var panelGrowHandler = null;
|
|
3162
|
-
var quoteVoiceHandled
|
|
3253
|
+
var quoteVoiceHandled;
|
|
3254
|
+
var voiceSeenSinceOpen = false;
|
|
3163
3255
|
var selMark = null;
|
|
3164
3256
|
var selThread = null;
|
|
3165
3257
|
var selThreadPath = null;
|
|
@@ -3368,11 +3460,13 @@ function createVoiceCardController(env) {
|
|
|
3368
3460
|
function openVCard() {
|
|
3369
3461
|
if (vcardState.open || env.panelOpen) return;
|
|
3370
3462
|
if (!env.state.selection) return;
|
|
3371
|
-
if (!env.state.selection.range && env.state.selection.source !== "iframe") return;
|
|
3372
|
-
if (!hasFocusEvidence(env.state.selection)) return;
|
|
3373
|
-
if (env.isTypingTarget(document.activeElement)) return;
|
|
3463
|
+
if (!env.state.selection.range && env.state.selection.source !== "iframe" && env.state.selection.source !== "shot") return;
|
|
3464
|
+
if (env.state.selection.source !== "shot" && !hasFocusEvidence(env.state.selection)) return;
|
|
3465
|
+
if (env.state.selection.source !== "shot" && env.isTypingTarget(document.activeElement)) return;
|
|
3466
|
+
if (env.ensureLayer) env.ensureLayer();
|
|
3374
3467
|
ensureVCard();
|
|
3375
3468
|
if (vcardState.closing || vcardState.crtTimer) finishCrtOff();
|
|
3469
|
+
voiceSeenSinceOpen = false;
|
|
3376
3470
|
vcardState.range = env.state.selection.range || null;
|
|
3377
3471
|
vcardState.anchorRect = env.state.selection.rect || null;
|
|
3378
3472
|
var geo = vcardGeometry();
|
|
@@ -3417,7 +3511,7 @@ function createVoiceCardController(env) {
|
|
|
3417
3511
|
vcard.setAttribute("data-voiced", on ? "1" : "0");
|
|
3418
3512
|
if (!vcardBox) return;
|
|
3419
3513
|
if (String(vcardBox.value || "").trim()) return;
|
|
3420
|
-
vcardBox.placeholder = on ? "\u6B63\u5728\u542C\u2026" : env.settingsLive.voiceCancelOnSilence !== false ? "\u6B63\u5728\u5173\u95ED\u2026" : "\u76F4\u63A5\u6253\u5B57\u4E5F\u884C\u2026";
|
|
3514
|
+
vcardBox.placeholder = on ? "\u6B63\u5728\u542C\u2026" : isShotSelection() ? "\u5C31\u8FD9\u5F20\u622A\u56FE\u63D0\u95EE\u2026" : env.settingsLive.voiceCancelOnSilence !== false ? "\u6B63\u5728\u5173\u95ED\u2026" : "\u76F4\u63A5\u6253\u5B57\u4E5F\u884C\u2026";
|
|
3421
3515
|
}
|
|
3422
3516
|
function focusVCard() {
|
|
3423
3517
|
if (!vcardBox) return;
|
|
@@ -3650,14 +3744,22 @@ function createVoiceCardController(env) {
|
|
|
3650
3744
|
vcardState.settleStableAt = 0;
|
|
3651
3745
|
vcardState.settleDeadline = 0;
|
|
3652
3746
|
}
|
|
3747
|
+
function isShotSelection() {
|
|
3748
|
+
var sel = env.state && env.state.selection;
|
|
3749
|
+
return !!(sel && sel.source === "shot");
|
|
3750
|
+
}
|
|
3653
3751
|
function finishVoiceSettle(text) {
|
|
3654
3752
|
if (!vcardState.open) return;
|
|
3655
3753
|
var spoken = String(text || "").trim();
|
|
3656
|
-
if (spoken && env.settingsLive.voiceAutoSend !== false) {
|
|
3754
|
+
if (spoken && voiceSeenSinceOpen && env.settingsLive.voiceAutoSend !== false) {
|
|
3657
3755
|
sendVCard();
|
|
3658
3756
|
return;
|
|
3659
3757
|
}
|
|
3660
3758
|
if (!spoken && env.settingsLive.voiceCancelOnSilence !== false) {
|
|
3759
|
+
if (isShotSelection()) {
|
|
3760
|
+
markVCardListening(false);
|
|
3761
|
+
return;
|
|
3762
|
+
}
|
|
3661
3763
|
closeVCard(true);
|
|
3662
3764
|
return;
|
|
3663
3765
|
}
|
|
@@ -3687,6 +3789,7 @@ function createVoiceCardController(env) {
|
|
|
3687
3789
|
}
|
|
3688
3790
|
function closeVCard(animate) {
|
|
3689
3791
|
if (!vcardState.open) return;
|
|
3792
|
+
if (isShotSelection() && env.clearShotMarkers) env.clearShotMarkers();
|
|
3690
3793
|
vcardState.open = false;
|
|
3691
3794
|
if (vcardBox && !String(vcardBox.value || "").trim()) vcardBox.placeholder = "\u6B63\u5728\u5173\u95ED\u2026";
|
|
3692
3795
|
vcardState.range = null;
|
|
@@ -3714,6 +3817,36 @@ function createVoiceCardController(env) {
|
|
|
3714
3817
|
closeVCard();
|
|
3715
3818
|
return;
|
|
3716
3819
|
}
|
|
3820
|
+
if (selection.source === "shot") {
|
|
3821
|
+
var shotRect = selection.rect;
|
|
3822
|
+
var openShotPanel = function(ref) {
|
|
3823
|
+
if (!env.state.selection || env.state.selection !== selection) return;
|
|
3824
|
+
var why = env.shotFailed ? env.shotFailed() : "";
|
|
3825
|
+
if (!ref && why) {
|
|
3826
|
+
if (env.shotNotice) {
|
|
3827
|
+
env.shotNotice(
|
|
3828
|
+
why === "upload-failed" ? "\u622A\u56FE\u4E0A\u4F20\u5931\u8D25\uFF0C\u53EF\u4EE5\u518D\u6309\u4E00\u6B21\u53D1\u9001\u91CD\u8BD5" : "\u622A\u56FE\u6CA1\u6709\u751F\u6210\u6210\u529F\uFF0C\u8FD9\u6761\u5148\u4E0D\u53D1\u9001\uFF1B\u8BF7\u6309 Esc \u540E\u91CD\u65B0\u622A\u56FE"
|
|
3829
|
+
);
|
|
3830
|
+
}
|
|
3831
|
+
return;
|
|
3832
|
+
}
|
|
3833
|
+
vcardHandoff = vcardRect();
|
|
3834
|
+
closeVCard();
|
|
3835
|
+
env.openPanelWith("", "", "\u622A\u56FE", shotRect, "", {
|
|
3836
|
+
askFirst: asked,
|
|
3837
|
+
shotRef: ref,
|
|
3838
|
+
// 取景范围要跟着图一起进小窗:提示词要按"图里有没有红框 / 图是不是只有选区"换口径
|
|
3839
|
+
shotScope: selection.shotScope || "",
|
|
3840
|
+
shotFrame: selection.shotFrame !== false
|
|
3841
|
+
});
|
|
3842
|
+
if (env.clearShotMarkers) env.clearShotMarkers();
|
|
3843
|
+
env.state.selection = null;
|
|
3844
|
+
env.state.bridgeActive = false;
|
|
3845
|
+
};
|
|
3846
|
+
if (env.shotRefAsync) env.shotRefAsync().then(openShotPanel);
|
|
3847
|
+
else openShotPanel(selection.ref || null);
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3717
3850
|
var contextInfo = selection.source === "iframe" || !selection.range ? {
|
|
3718
3851
|
context: String(selection.context || ""),
|
|
3719
3852
|
keyContext: String(selection.keyContext || ""),
|
|
@@ -3794,6 +3927,7 @@ function createVoiceCardController(env) {
|
|
|
3794
3927
|
return;
|
|
3795
3928
|
}
|
|
3796
3929
|
if (vcardState.suppress) return;
|
|
3930
|
+
voiceSeenSinceOpen = true;
|
|
3797
3931
|
stopVoiceSettle();
|
|
3798
3932
|
if (!vcardState.open) openVCard();
|
|
3799
3933
|
else markVCardListening(true);
|
|
@@ -3815,7 +3949,7 @@ function createVoiceCardController(env) {
|
|
|
3815
3949
|
var selection = env.state.quoteSelection;
|
|
3816
3950
|
if (!env.panelOpen || !selection) return;
|
|
3817
3951
|
if (!hasFocusEvidence(selection)) return;
|
|
3818
|
-
if (env.isTypingTarget(document.activeElement)) return;
|
|
3952
|
+
if (env.state.selection.source !== "shot" && env.isTypingTarget(document.activeElement)) return;
|
|
3819
3953
|
if (env.voice && env.voice.phase !== "idle") return;
|
|
3820
3954
|
env.addQuote(selection.text, selection.label, env.quoteContextFor(selection));
|
|
3821
3955
|
hideQuoteButton();
|
|
@@ -4168,6 +4302,20 @@ function createAttachController(env) {
|
|
|
4168
4302
|
env.attachNote.textContent = env.state.attachNote || "";
|
|
4169
4303
|
env.attachNote.setAttribute("data-show", env.state.attachNote ? "1" : "0");
|
|
4170
4304
|
}
|
|
4305
|
+
function ensureUploaded() {
|
|
4306
|
+
var pending = items().filter(function(item) {
|
|
4307
|
+
return !item.ref && item.file;
|
|
4308
|
+
});
|
|
4309
|
+
if (pending.length === 0) return Promise.resolve();
|
|
4310
|
+
var chain = Promise.resolve();
|
|
4311
|
+
pending.forEach(function(item) {
|
|
4312
|
+
chain = chain.then(function() {
|
|
4313
|
+
item.started = false;
|
|
4314
|
+
return upload(item);
|
|
4315
|
+
});
|
|
4316
|
+
});
|
|
4317
|
+
return chain;
|
|
4318
|
+
}
|
|
4171
4319
|
function busy() {
|
|
4172
4320
|
var list = items();
|
|
4173
4321
|
for (var i = 0; i < list.length; i += 1) if (list[i].status === "uploading") return true;
|
|
@@ -4244,15 +4392,6 @@ function createAttachController(env) {
|
|
|
4244
4392
|
note(rejected.join("\uFF1B"));
|
|
4245
4393
|
render();
|
|
4246
4394
|
env.refreshAskState();
|
|
4247
|
-
var pending = list.filter(function(item) {
|
|
4248
|
-
return item.status === "uploading" && !item.started;
|
|
4249
|
-
});
|
|
4250
|
-
var chain = Promise.resolve();
|
|
4251
|
-
pending.forEach(function(item) {
|
|
4252
|
-
chain = chain.then(function() {
|
|
4253
|
-
return upload(item);
|
|
4254
|
-
});
|
|
4255
|
-
});
|
|
4256
4395
|
}
|
|
4257
4396
|
function createItem(file) {
|
|
4258
4397
|
var isImage = /^image\//.test(String(file.type || ""));
|
|
@@ -4265,8 +4404,10 @@ function createAttachController(env) {
|
|
|
4265
4404
|
file,
|
|
4266
4405
|
// 缩略图先用本地 object URL 显示(不必等上传完),失败时回落到图标
|
|
4267
4406
|
url: isImage && typeof URL !== "undefined" && URL.createObjectURL ? URL.createObjectURL(file) : "",
|
|
4268
|
-
|
|
4269
|
-
|
|
4407
|
+
// 'local' = 只在内存里,还没落盘。用户要求:叉掉的不该在附件库留垃圾,
|
|
4408
|
+
// 所以**点发送时才真正上传**(见 ensureUploaded)。
|
|
4409
|
+
status: "local",
|
|
4410
|
+
started: true,
|
|
4270
4411
|
ref: null,
|
|
4271
4412
|
error: ""
|
|
4272
4413
|
};
|
|
@@ -4278,6 +4419,7 @@ function createAttachController(env) {
|
|
|
4278
4419
|
item.status = "uploading";
|
|
4279
4420
|
item.error = "";
|
|
4280
4421
|
render();
|
|
4422
|
+
env.refreshAskState();
|
|
4281
4423
|
return fileToBase64(item.file).then(function(data) {
|
|
4282
4424
|
return fetch(ATTACH, {
|
|
4283
4425
|
method: "POST",
|
|
@@ -4347,10 +4489,44 @@ function createAttachController(env) {
|
|
|
4347
4489
|
});
|
|
4348
4490
|
return button;
|
|
4349
4491
|
}
|
|
4492
|
+
function previewItem(item) {
|
|
4493
|
+
if (!item || item.status === "error") return;
|
|
4494
|
+
var list = items();
|
|
4495
|
+
var images = [];
|
|
4496
|
+
var start = 0;
|
|
4497
|
+
for (var i = 0; i < list.length; i += 1) {
|
|
4498
|
+
var other = list[i];
|
|
4499
|
+
if (!other || other.kind !== "image" || other.status === "error") continue;
|
|
4500
|
+
var src = other.url || (other.ref ? attachmentUrl("image", other.ref) : "");
|
|
4501
|
+
if (!src) continue;
|
|
4502
|
+
if (other === item) start = images.length;
|
|
4503
|
+
images.push({ src, name: other.name });
|
|
4504
|
+
}
|
|
4505
|
+
if (images.length === 0 || typeof env.previewImage !== "function") return;
|
|
4506
|
+
env.previewImage({ images, index: start });
|
|
4507
|
+
}
|
|
4350
4508
|
function thumbNode(item) {
|
|
4351
4509
|
var node = el("span", "dsh-sel-thumb");
|
|
4352
4510
|
node.setAttribute("data-state", item.status);
|
|
4353
|
-
|
|
4511
|
+
var previewable = item.status !== "error" && !!(item.url || item.ref);
|
|
4512
|
+
if (previewable) {
|
|
4513
|
+
node.setAttribute("data-preview", "1");
|
|
4514
|
+
node.setAttribute("role", "button");
|
|
4515
|
+
node.tabIndex = 0;
|
|
4516
|
+
node.title = item.name + "\uFF08\u70B9\u51FB\u9884\u89C8\uFF09";
|
|
4517
|
+
node.setAttribute("aria-label", "\u9884\u89C8\u56FE\u7247 " + item.name);
|
|
4518
|
+
listen(node, "click", function() {
|
|
4519
|
+
previewItem(item);
|
|
4520
|
+
});
|
|
4521
|
+
listen(node, "keydown", function(event) {
|
|
4522
|
+
if (event.target !== node) return;
|
|
4523
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
4524
|
+
event.preventDefault();
|
|
4525
|
+
previewItem(item);
|
|
4526
|
+
});
|
|
4527
|
+
} else {
|
|
4528
|
+
node.title = item.name;
|
|
4529
|
+
}
|
|
4354
4530
|
if (item.url) {
|
|
4355
4531
|
var img = document.createElement("img");
|
|
4356
4532
|
img.src = item.url;
|
|
@@ -4481,6 +4657,7 @@ function createAttachController(env) {
|
|
|
4481
4657
|
retry,
|
|
4482
4658
|
render,
|
|
4483
4659
|
busy,
|
|
4660
|
+
ensureUploaded,
|
|
4484
4661
|
payload,
|
|
4485
4662
|
describe,
|
|
4486
4663
|
note,
|
|
@@ -4490,6 +4667,891 @@ function createAttachController(env) {
|
|
|
4490
4667
|
};
|
|
4491
4668
|
}
|
|
4492
4669
|
|
|
4670
|
+
// src/client/lightbox.js
|
|
4671
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
4672
|
+
function createLightboxController(env) {
|
|
4673
|
+
var root = null;
|
|
4674
|
+
var stage = null;
|
|
4675
|
+
var img = null;
|
|
4676
|
+
var nameEl = null;
|
|
4677
|
+
var failEl = null;
|
|
4678
|
+
var closeBtn = null;
|
|
4679
|
+
var prevBtn = null;
|
|
4680
|
+
var nextBtn = null;
|
|
4681
|
+
var countEl = null;
|
|
4682
|
+
var hintEl = null;
|
|
4683
|
+
var built = false;
|
|
4684
|
+
var open = false;
|
|
4685
|
+
var gallery = [];
|
|
4686
|
+
var index = 0;
|
|
4687
|
+
var lastFocus = null;
|
|
4688
|
+
function within(node, element) {
|
|
4689
|
+
if (!node || !element) return false;
|
|
4690
|
+
if (node === element) return true;
|
|
4691
|
+
if (typeof element.contains !== "function") return false;
|
|
4692
|
+
try {
|
|
4693
|
+
return element.contains(
|
|
4694
|
+
/** @type {Node} */
|
|
4695
|
+
node
|
|
4696
|
+
);
|
|
4697
|
+
} catch (error) {
|
|
4698
|
+
return false;
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
function chevron(dir) {
|
|
4702
|
+
var svg = document.createElementNS(SVG_NS, "svg");
|
|
4703
|
+
svg.setAttribute("viewBox", "0 0 16 16");
|
|
4704
|
+
svg.setAttribute("width", "16");
|
|
4705
|
+
svg.setAttribute("height", "16");
|
|
4706
|
+
svg.setAttribute("fill", "none");
|
|
4707
|
+
svg.setAttribute("stroke", "currentColor");
|
|
4708
|
+
svg.setAttribute("stroke-width", "1.9");
|
|
4709
|
+
svg.setAttribute("stroke-linecap", "round");
|
|
4710
|
+
svg.setAttribute("stroke-linejoin", "round");
|
|
4711
|
+
svg.setAttribute("aria-hidden", "true");
|
|
4712
|
+
var path = document.createElementNS(SVG_NS, "path");
|
|
4713
|
+
path.setAttribute("d", dir === "prev" ? "M10 3.5L5.5 8l4.5 4.5" : "M6 3.5L10.5 8 6 12.5");
|
|
4714
|
+
svg.appendChild(path);
|
|
4715
|
+
return svg;
|
|
4716
|
+
}
|
|
4717
|
+
function navButton(dir) {
|
|
4718
|
+
var button = el("button", "dsh-sel-lightbox-nav");
|
|
4719
|
+
button.type = "button";
|
|
4720
|
+
button.setAttribute("data-dir", dir);
|
|
4721
|
+
var label = dir === "prev" ? "\u4E0A\u4E00\u5F20\u56FE\u7247\uFF08\u2190\uFF09" : "\u4E0B\u4E00\u5F20\u56FE\u7247\uFF08\u2192\uFF09";
|
|
4722
|
+
button.title = label;
|
|
4723
|
+
button.setAttribute("aria-label", label);
|
|
4724
|
+
button.appendChild(chevron(dir));
|
|
4725
|
+
listen(button, "click", function() {
|
|
4726
|
+
step(dir === "prev" ? -1 : 1);
|
|
4727
|
+
});
|
|
4728
|
+
return button;
|
|
4729
|
+
}
|
|
4730
|
+
function build() {
|
|
4731
|
+
if (built) return;
|
|
4732
|
+
built = true;
|
|
4733
|
+
root = el("div", "dsh-sel-lightbox");
|
|
4734
|
+
root.setAttribute("data-open", "0");
|
|
4735
|
+
root.setAttribute("data-error", "0");
|
|
4736
|
+
root.setAttribute("data-gallery", "0");
|
|
4737
|
+
root.setAttribute("role", "dialog");
|
|
4738
|
+
root.setAttribute("aria-label", "\u56FE\u7247\u9884\u89C8");
|
|
4739
|
+
var bar = el("div", "dsh-sel-lightbox-bar");
|
|
4740
|
+
nameEl = el("span", "dsh-sel-lightbox-name");
|
|
4741
|
+
countEl = el("span", "dsh-sel-lightbox-count");
|
|
4742
|
+
closeBtn = el("button", "dsh-sel-lightbox-x");
|
|
4743
|
+
closeBtn.type = "button";
|
|
4744
|
+
closeBtn.title = "\u5173\u95ED\u9884\u89C8\uFF08Esc\uFF09";
|
|
4745
|
+
closeBtn.setAttribute("aria-label", "\u5173\u95ED\u56FE\u7247\u9884\u89C8");
|
|
4746
|
+
closeBtn.appendChild(closeIcon());
|
|
4747
|
+
listen(closeBtn, "click", function() {
|
|
4748
|
+
close();
|
|
4749
|
+
});
|
|
4750
|
+
bar.appendChild(nameEl);
|
|
4751
|
+
bar.appendChild(countEl);
|
|
4752
|
+
bar.appendChild(closeBtn);
|
|
4753
|
+
stage = el("div", "dsh-sel-lightbox-stage");
|
|
4754
|
+
img = document.createElement("img");
|
|
4755
|
+
img.className = "dsh-sel-lightbox-img";
|
|
4756
|
+
img.alt = "";
|
|
4757
|
+
img.decoding = "async";
|
|
4758
|
+
img.draggable = false;
|
|
4759
|
+
img.addEventListener("error", function() {
|
|
4760
|
+
if (!open) return;
|
|
4761
|
+
root.setAttribute("data-error", "1");
|
|
4762
|
+
failEl.textContent = "\u8FD9\u5F20\u56FE\u6253\u4E0D\u5F00\u4E86\uFF08\u9644\u4EF6\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406\uFF09";
|
|
4763
|
+
});
|
|
4764
|
+
stage.appendChild(img);
|
|
4765
|
+
failEl = el("p", "dsh-sel-lightbox-fail");
|
|
4766
|
+
prevBtn = navButton("prev");
|
|
4767
|
+
nextBtn = navButton("next");
|
|
4768
|
+
hintEl = el("p", "dsh-sel-lightbox-hint");
|
|
4769
|
+
root.appendChild(bar);
|
|
4770
|
+
root.appendChild(stage);
|
|
4771
|
+
root.appendChild(failEl);
|
|
4772
|
+
root.appendChild(prevBtn);
|
|
4773
|
+
root.appendChild(nextBtn);
|
|
4774
|
+
root.appendChild(hintEl);
|
|
4775
|
+
listen(root, "click", function(event) {
|
|
4776
|
+
if (!open) return;
|
|
4777
|
+
var target = event.target;
|
|
4778
|
+
if (target === img) return;
|
|
4779
|
+
if (within(target, bar) || within(target, prevBtn) || within(target, nextBtn)) return;
|
|
4780
|
+
close();
|
|
4781
|
+
});
|
|
4782
|
+
listen(root, "keydown", function(event) {
|
|
4783
|
+
if (!open || event.key !== "Tab") return;
|
|
4784
|
+
var order = [prevBtn, nextBtn, closeBtn].filter(function(button) {
|
|
4785
|
+
return !button.disabled;
|
|
4786
|
+
});
|
|
4787
|
+
var current = order.indexOf(document.activeElement);
|
|
4788
|
+
var next = event.shiftKey ? current - 1 : current + 1;
|
|
4789
|
+
if (next < 0) next = order.length - 1;
|
|
4790
|
+
if (next >= order.length) next = 0;
|
|
4791
|
+
event.preventDefault();
|
|
4792
|
+
if (order[next] && typeof order[next].focus === "function") order[next].focus();
|
|
4793
|
+
});
|
|
4794
|
+
env.layer.appendChild(root);
|
|
4795
|
+
}
|
|
4796
|
+
function paintImage() {
|
|
4797
|
+
var item = gallery[index] || { src: "", name: "\u56FE\u7247" };
|
|
4798
|
+
var many = gallery.length > 1;
|
|
4799
|
+
root.setAttribute("data-error", "0");
|
|
4800
|
+
root.setAttribute("data-gallery", many ? "1" : "0");
|
|
4801
|
+
failEl.textContent = "";
|
|
4802
|
+
nameEl.textContent = item.name || "\u56FE\u7247";
|
|
4803
|
+
img.alt = nameEl.textContent;
|
|
4804
|
+
img.src = item.src;
|
|
4805
|
+
countEl.textContent = many ? index + 1 + " / " + gallery.length : "";
|
|
4806
|
+
prevBtn.disabled = !many || index === 0;
|
|
4807
|
+
nextBtn.disabled = !many || index === gallery.length - 1;
|
|
4808
|
+
hintEl.textContent = many ? "\u2190 / \u2192 \u5207\u6362 \xB7 Esc \u6216\u70B9\u51FB\u7A7A\u767D\u5904\u5173\u95ED" : "Esc \u6216\u70B9\u51FB\u7A7A\u767D\u5904\u5173\u95ED";
|
|
4809
|
+
root.setAttribute("aria-label", "\u56FE\u7247\u9884\u89C8" + (many ? "\uFF08" + (index + 1) + " / " + gallery.length + "\uFF09" : ""));
|
|
4810
|
+
}
|
|
4811
|
+
function step(delta) {
|
|
4812
|
+
if (!open || gallery.length < 2) return;
|
|
4813
|
+
var next = index + delta;
|
|
4814
|
+
if (next < 0 || next >= gallery.length) return;
|
|
4815
|
+
index = next;
|
|
4816
|
+
paintImage();
|
|
4817
|
+
}
|
|
4818
|
+
function normalize(items, fallback) {
|
|
4819
|
+
var list = [];
|
|
4820
|
+
if (items && items.length) {
|
|
4821
|
+
for (var i = 0; i < items.length; i += 1) {
|
|
4822
|
+
var item = items[i] || {};
|
|
4823
|
+
if (!item.src) continue;
|
|
4824
|
+
list.push({ src: String(item.src), name: item.name ? String(item.name) : "\u56FE\u7247" });
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
if (list.length === 0 && fallback && fallback.src) {
|
|
4828
|
+
list.push({ src: String(fallback.src), name: fallback.name ? String(fallback.name) : "\u56FE\u7247" });
|
|
4829
|
+
}
|
|
4830
|
+
return list;
|
|
4831
|
+
}
|
|
4832
|
+
function openImage(options) {
|
|
4833
|
+
build();
|
|
4834
|
+
var given = options && Array.isArray(options.images) ? options.images : null;
|
|
4835
|
+
var list = normalize(given, options);
|
|
4836
|
+
if (list.length === 0) return;
|
|
4837
|
+
var start = options && typeof options.index === "number" ? Math.floor(options.index) : 0;
|
|
4838
|
+
if (!(start >= 0 && start < list.length)) start = 0;
|
|
4839
|
+
gallery = list;
|
|
4840
|
+
index = start;
|
|
4841
|
+
lastFocus = document.activeElement;
|
|
4842
|
+
open = true;
|
|
4843
|
+
root.setAttribute("data-open", "1");
|
|
4844
|
+
root.setAttribute("aria-modal", "true");
|
|
4845
|
+
paintImage();
|
|
4846
|
+
if (typeof closeBtn.focus === "function") closeBtn.focus();
|
|
4847
|
+
}
|
|
4848
|
+
function close() {
|
|
4849
|
+
if (!open || !built) return;
|
|
4850
|
+
open = false;
|
|
4851
|
+
gallery = [];
|
|
4852
|
+
index = 0;
|
|
4853
|
+
root.setAttribute("data-open", "0");
|
|
4854
|
+
root.setAttribute("data-error", "0");
|
|
4855
|
+
root.setAttribute("data-gallery", "0");
|
|
4856
|
+
root.removeAttribute("aria-modal");
|
|
4857
|
+
img.removeAttribute("src");
|
|
4858
|
+
img.alt = "";
|
|
4859
|
+
countEl.textContent = "";
|
|
4860
|
+
prevBtn.disabled = true;
|
|
4861
|
+
nextBtn.disabled = true;
|
|
4862
|
+
var back = lastFocus;
|
|
4863
|
+
lastFocus = null;
|
|
4864
|
+
if (back && back.isConnected !== false && typeof back.focus === "function") {
|
|
4865
|
+
try {
|
|
4866
|
+
back.focus();
|
|
4867
|
+
} catch (error) {
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
function foreignModalOpen() {
|
|
4872
|
+
var modals = document.querySelectorAll('[role="dialog"][aria-modal="true"]');
|
|
4873
|
+
for (var i = 0; i < modals.length; i += 1) if (modals[i] !== root) return true;
|
|
4874
|
+
return false;
|
|
4875
|
+
}
|
|
4876
|
+
var offKey = listen(document, "keydown", function(event) {
|
|
4877
|
+
if (!open) return;
|
|
4878
|
+
if (event.defaultPrevented || event.isComposing || event.keyCode === 229) return;
|
|
4879
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
4880
|
+
if (gallery.length < 2 || foreignModalOpen()) return;
|
|
4881
|
+
event.preventDefault();
|
|
4882
|
+
event.stopPropagation();
|
|
4883
|
+
step(event.key === "ArrowLeft" ? -1 : 1);
|
|
4884
|
+
return;
|
|
4885
|
+
}
|
|
4886
|
+
if (event.key !== "Escape") return;
|
|
4887
|
+
if (foreignModalOpen()) return;
|
|
4888
|
+
event.preventDefault();
|
|
4889
|
+
event.stopPropagation();
|
|
4890
|
+
close();
|
|
4891
|
+
}, true);
|
|
4892
|
+
return {
|
|
4893
|
+
open: openImage,
|
|
4894
|
+
close,
|
|
4895
|
+
get isOpen() {
|
|
4896
|
+
return open;
|
|
4897
|
+
},
|
|
4898
|
+
/** 卸载时摘掉 document 上的监听(灯箱 DOM 随 layer 一起销毁)。 */
|
|
4899
|
+
dispose: function() {
|
|
4900
|
+
close();
|
|
4901
|
+
offKey();
|
|
4902
|
+
}
|
|
4903
|
+
};
|
|
4904
|
+
}
|
|
4905
|
+
|
|
4906
|
+
// src/client/shot.js
|
|
4907
|
+
var MAX_ANNOTATED = 2048;
|
|
4908
|
+
var FRAME_COLOR = "#e5484d";
|
|
4909
|
+
var FRAME_WIDTH_MIN_PX = 3;
|
|
4910
|
+
var SHOT_INSIDE_SLOP = 2;
|
|
4911
|
+
function pageRectOf(rect, origin) {
|
|
4912
|
+
if (!rect) return null;
|
|
4913
|
+
var sx = origin && typeof origin.screenX === "number" ? origin.screenX : 0;
|
|
4914
|
+
var sy = origin && typeof origin.screenY === "number" ? origin.screenY : 0;
|
|
4915
|
+
return asRect({
|
|
4916
|
+
left: rect.x - sx,
|
|
4917
|
+
top: rect.y - sy,
|
|
4918
|
+
width: rect.width,
|
|
4919
|
+
height: rect.height
|
|
4920
|
+
});
|
|
4921
|
+
}
|
|
4922
|
+
function asRect(box) {
|
|
4923
|
+
var left = typeof box.left === "number" ? box.left : 0;
|
|
4924
|
+
var top = typeof box.top === "number" ? box.top : 0;
|
|
4925
|
+
var width = typeof box.width === "number" ? box.width : 0;
|
|
4926
|
+
var height = typeof box.height === "number" ? box.height : 0;
|
|
4927
|
+
return { left, top, width, height, right: left + width, bottom: top + height, x: left, y: top };
|
|
4928
|
+
}
|
|
4929
|
+
function insideViewport(rect, viewport, slop) {
|
|
4930
|
+
if (!rect || !viewport) return false;
|
|
4931
|
+
var pad = typeof slop === "number" ? slop : SHOT_INSIDE_SLOP;
|
|
4932
|
+
return rect.left >= -pad && rect.top >= -pad && rect.left + rect.width <= viewport.width + pad && rect.top + rect.height <= viewport.height + pad;
|
|
4933
|
+
}
|
|
4934
|
+
function nearestEdge(rect, viewport) {
|
|
4935
|
+
var cx = rect.left + rect.width / 2;
|
|
4936
|
+
var cy = rect.top + rect.height / 2;
|
|
4937
|
+
var d = { left: Math.abs(cx), right: Math.abs(viewport.width - cx), top: Math.abs(cy), bottom: Math.abs(viewport.height - cy) };
|
|
4938
|
+
var best = "left";
|
|
4939
|
+
var bestValue = Infinity;
|
|
4940
|
+
for (var key in d) {
|
|
4941
|
+
if (!Object.prototype.hasOwnProperty.call(d, key)) continue;
|
|
4942
|
+
if (d[key] < bestValue) {
|
|
4943
|
+
bestValue = d[key];
|
|
4944
|
+
best = key;
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
return best;
|
|
4948
|
+
}
|
|
4949
|
+
function chipRectFor(edge, anchorRect, viewport, size) {
|
|
4950
|
+
var w = size && size.width || 158;
|
|
4951
|
+
var h = size && size.height || 48;
|
|
4952
|
+
var pad = 12;
|
|
4953
|
+
var cx = anchorRect.left + anchorRect.width / 2;
|
|
4954
|
+
var cy = anchorRect.top + anchorRect.height / 2;
|
|
4955
|
+
var left;
|
|
4956
|
+
var top;
|
|
4957
|
+
if (edge === "left" || edge === "right") {
|
|
4958
|
+
left = edge === "left" ? pad : viewport.width - w - pad;
|
|
4959
|
+
top = Math.max(pad, Math.min(cy - h / 2, viewport.height - h - pad));
|
|
4960
|
+
} else {
|
|
4961
|
+
top = edge === "top" ? pad : viewport.height - h - pad;
|
|
4962
|
+
left = Math.max(pad, Math.min(cx - w / 2, viewport.width - w - pad));
|
|
4963
|
+
}
|
|
4964
|
+
return { left, top, width: w, height: h };
|
|
4965
|
+
}
|
|
4966
|
+
function findContentBox(g, width, height) {
|
|
4967
|
+
var probe = Math.min(400, width, height);
|
|
4968
|
+
function hasOpaque(data, count, stride, offset) {
|
|
4969
|
+
for (var i = 0; i < count; i += 1) {
|
|
4970
|
+
if (data[i * stride + offset + 3] > 128) return true;
|
|
4971
|
+
}
|
|
4972
|
+
return false;
|
|
4973
|
+
}
|
|
4974
|
+
var box = { left: 0, top: 0, right: width, bottom: height };
|
|
4975
|
+
var strip = g.getImageData(0, 0, width, probe);
|
|
4976
|
+
for (var y = 0; y < probe; y += 1) {
|
|
4977
|
+
if (hasOpaque(strip.data.subarray(y * width * 4), width, 4, 0)) {
|
|
4978
|
+
box.top = y;
|
|
4979
|
+
break;
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
strip = g.getImageData(0, Math.max(0, height - probe), width, probe);
|
|
4983
|
+
for (var y2 = probe - 1; y2 >= 0; y2 -= 1) {
|
|
4984
|
+
if (hasOpaque(strip.data.subarray(y2 * width * 4), width, 4, 0)) {
|
|
4985
|
+
box.bottom = Math.max(0, height - probe) + y2 + 1;
|
|
4986
|
+
break;
|
|
4987
|
+
}
|
|
4988
|
+
}
|
|
4989
|
+
strip = g.getImageData(0, 0, probe, height);
|
|
4990
|
+
for (var x = 0; x < probe; x += 1) {
|
|
4991
|
+
if (hasOpaque(strip.data.subarray(x * 4), height, probe * 4, 0)) {
|
|
4992
|
+
box.left = x;
|
|
4993
|
+
break;
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
strip = g.getImageData(Math.max(0, width - probe), 0, probe, height);
|
|
4997
|
+
for (var x2 = probe - 1; x2 >= 0; x2 -= 1) {
|
|
4998
|
+
if (hasOpaque(strip.data.subarray(x2 * 4), height, probe * 4, 0)) {
|
|
4999
|
+
box.right = Math.max(0, width - probe) + x2 + 1;
|
|
5000
|
+
break;
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
if (box.right - box.left < 16 || box.bottom - box.top < 16) return null;
|
|
5004
|
+
return box;
|
|
5005
|
+
}
|
|
5006
|
+
function annotate(image, rect, scale, shot) {
|
|
5007
|
+
var iw = image.naturalWidth || image.width;
|
|
5008
|
+
var ih = image.naturalHeight || image.height;
|
|
5009
|
+
var k = iw > MAX_ANNOTATED ? MAX_ANNOTATED / iw : 1;
|
|
5010
|
+
var canvas = document.createElement("canvas");
|
|
5011
|
+
canvas.width = Math.max(1, Math.round(iw * k));
|
|
5012
|
+
canvas.height = Math.max(1, Math.round(ih * k));
|
|
5013
|
+
var g = canvas.getContext("2d");
|
|
5014
|
+
var needContentBox = !shot || !shot.scope || shot.scope === "window";
|
|
5015
|
+
var content = null;
|
|
5016
|
+
if (needContentBox) {
|
|
5017
|
+
g.imageSmoothingEnabled = false;
|
|
5018
|
+
g.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
5019
|
+
try {
|
|
5020
|
+
content = findContentBox(g, canvas.width, canvas.height);
|
|
5021
|
+
} catch (error) {
|
|
5022
|
+
content = null;
|
|
5023
|
+
}
|
|
5024
|
+
g.clearRect(0, 0, canvas.width, canvas.height);
|
|
5025
|
+
}
|
|
5026
|
+
g.imageSmoothingEnabled = true;
|
|
5027
|
+
g.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
5028
|
+
if (content && shot && shot.windowRect && shot.selectionRect) {
|
|
5029
|
+
var win = shot.windowRect;
|
|
5030
|
+
var sel = shot.selectionRect;
|
|
5031
|
+
var left = content.left / k;
|
|
5032
|
+
var top = content.top / k;
|
|
5033
|
+
var right = content.right / k;
|
|
5034
|
+
var realScale = (right - left) / win.width;
|
|
5035
|
+
if (realScale > 0.2 && realScale < 5) {
|
|
5036
|
+
rect = {
|
|
5037
|
+
x: left + (sel.x - win.x) * realScale,
|
|
5038
|
+
y: top + (sel.y - win.y) * realScale,
|
|
5039
|
+
width: sel.width * realScale,
|
|
5040
|
+
height: sel.height * realScale
|
|
5041
|
+
};
|
|
5042
|
+
scale = realScale;
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
rect = { x: rect.x * k, y: rect.y * k, width: rect.width * k, height: rect.height * k };
|
|
5046
|
+
var line = Math.max(FRAME_WIDTH_MIN_PX, Math.round(3 * (scale || 1) * k));
|
|
5047
|
+
g.strokeStyle = FRAME_COLOR;
|
|
5048
|
+
g.lineWidth = line;
|
|
5049
|
+
var inset = line / 2;
|
|
5050
|
+
g.strokeRect(rect.x + inset, rect.y + inset, Math.max(0, rect.width - line), Math.max(0, rect.height - line));
|
|
5051
|
+
return new Promise(function(resolve) {
|
|
5052
|
+
canvas.toBlob(function(blob) {
|
|
5053
|
+
resolve(blob);
|
|
5054
|
+
}, "image/png");
|
|
5055
|
+
});
|
|
5056
|
+
}
|
|
5057
|
+
function report(payload) {
|
|
5058
|
+
try {
|
|
5059
|
+
fetch("/selection-explain/api/shot/hello", { method: "POST", body: JSON.stringify(payload) }).catch(function() {
|
|
5060
|
+
});
|
|
5061
|
+
} catch (error) {
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
function createShotController(env) {
|
|
5065
|
+
var host = env.window || (typeof window !== "undefined" ? window : null) || {};
|
|
5066
|
+
var myEpoch = 0;
|
|
5067
|
+
try {
|
|
5068
|
+
host.__dshSelShotEpoch = (host.__dshSelShotEpoch || 0) + 1;
|
|
5069
|
+
myEpoch = host.__dshSelShotEpoch;
|
|
5070
|
+
} catch (error) {
|
|
5071
|
+
myEpoch = 1;
|
|
5072
|
+
}
|
|
5073
|
+
function superseded() {
|
|
5074
|
+
try {
|
|
5075
|
+
return host.__dshSelShotEpoch !== myEpoch;
|
|
5076
|
+
} catch (error) {
|
|
5077
|
+
return false;
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
5080
|
+
var timer = 0;
|
|
5081
|
+
var busy = false;
|
|
5082
|
+
var lastId = "";
|
|
5083
|
+
var current = null;
|
|
5084
|
+
var frameEl = null;
|
|
5085
|
+
var chipEl = null;
|
|
5086
|
+
var chipImg = null;
|
|
5087
|
+
var chipUrl = "";
|
|
5088
|
+
var toastEl = null;
|
|
5089
|
+
var toastTimer = 0;
|
|
5090
|
+
function errorText(info) {
|
|
5091
|
+
var code = info && (info.code || info.reason) ? String(info.code || info.reason) : "";
|
|
5092
|
+
if (info && typeof info.message === "string" && info.message.trim()) return info.message;
|
|
5093
|
+
if (code === "not-macos") return "\u622A\u56FE\u529F\u80FD\u76EE\u524D\u4EC5\u652F\u6301 macOS";
|
|
5094
|
+
if (code === "no-screen-permission") return "\u6CA1\u6709\u5C4F\u5E55\u5F55\u5236\u6743\u9650\uFF1A\u8BF7\u5728 \u7CFB\u7EDF\u8BBE\u7F6E \u2192 \u9690\u79C1\u4E0E\u5B89\u5168\u6027 \u2192 \u5C4F\u5E55\u5F55\u5236 \u4E2D\u5141\u8BB8 DSH\uFF0C\u7136\u540E\u91CD\u8BD5";
|
|
5095
|
+
if (code === "busy") return "\u6B63\u5728\u622A\u56FE\uFF0C\u8BF7\u7A0D\u5019";
|
|
5096
|
+
if (code === "probe-missing") return "\u622A\u56FE\u7EC4\u4EF6\u7F3A\u5931\uFF0C\u8BF7\u91CD\u65B0\u6784\u5EFA\u63D2\u4EF6\u540E\u91CD\u8BD5";
|
|
5097
|
+
if (code === "spawn-failed") return "\u7CFB\u7EDF\u622A\u56FE\u547D\u4EE4\u542F\u52A8\u5931\u8D25";
|
|
5098
|
+
if (code === "timeout") return "\u6846\u9009\u8D85\u65F6\uFF0C\u5DF2\u53D6\u6D88\u8FD9\u6B21\u622A\u56FE";
|
|
5099
|
+
if (code === "capture-failed") return "\u622A\u56FE\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u5C4F\u5E55\u5F55\u5236\u6743\u9650\u540E\u91CD\u8BD5";
|
|
5100
|
+
if (code === "image-missing" || code === "finish-failed") return "\u622A\u56FE\u5DF2\u62CD\u4E0B\uFF0C\u4F46\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5";
|
|
5101
|
+
if (code.indexOf("tmp-failed") === 0) return "\u622A\u56FE\u4E34\u65F6\u76EE\u5F55\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u8BD5";
|
|
5102
|
+
if (code.indexOf("start-failed") === 0) return "\u622A\u56FE\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5";
|
|
5103
|
+
return "\u622A\u56FE\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5";
|
|
5104
|
+
}
|
|
5105
|
+
function ensureToast() {
|
|
5106
|
+
if (toastEl) {
|
|
5107
|
+
if (env.layer && toastEl.parentNode !== env.layer) env.layer.appendChild(toastEl);
|
|
5108
|
+
return toastEl;
|
|
5109
|
+
}
|
|
5110
|
+
var d = doc();
|
|
5111
|
+
toastEl = d.createElement("div");
|
|
5112
|
+
toastEl.className = "dsh-sel-shot-toast";
|
|
5113
|
+
toastEl.setAttribute("data-show", "0");
|
|
5114
|
+
toastEl.setAttribute("role", "status");
|
|
5115
|
+
toastEl.setAttribute("aria-live", "polite");
|
|
5116
|
+
toastEl.addEventListener("click", function() {
|
|
5117
|
+
hideNotice();
|
|
5118
|
+
});
|
|
5119
|
+
env.layer.appendChild(toastEl);
|
|
5120
|
+
return toastEl;
|
|
5121
|
+
}
|
|
5122
|
+
function notice(text, tone) {
|
|
5123
|
+
if (!text) return;
|
|
5124
|
+
if (env.ensureLayer) env.ensureLayer();
|
|
5125
|
+
var el2 = ensureToast();
|
|
5126
|
+
el2.textContent = String(text);
|
|
5127
|
+
el2.setAttribute("data-tone", tone || "error");
|
|
5128
|
+
el2.setAttribute("data-show", "1");
|
|
5129
|
+
if (toastTimer) clearTimeout(toastTimer);
|
|
5130
|
+
toastTimer = setTimeout(function() {
|
|
5131
|
+
toastTimer = 0;
|
|
5132
|
+
hideNotice();
|
|
5133
|
+
}, 4500);
|
|
5134
|
+
}
|
|
5135
|
+
function hideNotice() {
|
|
5136
|
+
if (toastTimer) {
|
|
5137
|
+
clearTimeout(toastTimer);
|
|
5138
|
+
toastTimer = 0;
|
|
5139
|
+
}
|
|
5140
|
+
if (toastEl) toastEl.setAttribute("data-show", "0");
|
|
5141
|
+
}
|
|
5142
|
+
function doc() {
|
|
5143
|
+
return env.document;
|
|
5144
|
+
}
|
|
5145
|
+
function viewport() {
|
|
5146
|
+
var win = env.window || window;
|
|
5147
|
+
return { width: win && win.innerWidth || 1200, height: win && win.innerHeight || 800 };
|
|
5148
|
+
}
|
|
5149
|
+
function origin() {
|
|
5150
|
+
var win = env.window || window;
|
|
5151
|
+
return { screenX: win && win.screenX ? win.screenX : 0, screenY: win && win.screenY ? win.screenY : 0 };
|
|
5152
|
+
}
|
|
5153
|
+
function ensureFrame() {
|
|
5154
|
+
if (frameEl) return frameEl;
|
|
5155
|
+
var d = doc();
|
|
5156
|
+
frameEl = d.createElement("div");
|
|
5157
|
+
frameEl.className = "dsh-sel-shotframe";
|
|
5158
|
+
frameEl.setAttribute("data-show", "0");
|
|
5159
|
+
var tag = d.createElement("span");
|
|
5160
|
+
tag.className = "dsh-sel-shotframe-tag";
|
|
5161
|
+
tag.textContent = "\u5DF2\u9009\u4E2D\u7684\u533A\u57DF";
|
|
5162
|
+
frameEl.appendChild(tag);
|
|
5163
|
+
env.layer.appendChild(frameEl);
|
|
5164
|
+
return frameEl;
|
|
5165
|
+
}
|
|
5166
|
+
function ensureChip() {
|
|
5167
|
+
if (chipEl) return chipEl;
|
|
5168
|
+
var d = doc();
|
|
5169
|
+
chipEl = d.createElement("div");
|
|
5170
|
+
chipEl.className = "dsh-sel-shotchip";
|
|
5171
|
+
chipEl.setAttribute("data-show", "0");
|
|
5172
|
+
chipImg = d.createElement("img");
|
|
5173
|
+
chipImg.alt = "\u622A\u56FE";
|
|
5174
|
+
chipEl.appendChild(chipImg);
|
|
5175
|
+
var tx = d.createElement("span");
|
|
5176
|
+
tx.className = "dsh-sel-shotchip-tx";
|
|
5177
|
+
var b = d.createElement("b");
|
|
5178
|
+
b.textContent = "\u6574\u7A97 + \u7EA2\u6846";
|
|
5179
|
+
var meta = d.createElement("span");
|
|
5180
|
+
meta.className = "dsh-sel-shotchip-meta";
|
|
5181
|
+
tx.appendChild(b);
|
|
5182
|
+
tx.appendChild(meta);
|
|
5183
|
+
chipEl.appendChild(tx);
|
|
5184
|
+
env.layer.appendChild(chipEl);
|
|
5185
|
+
return chipEl;
|
|
5186
|
+
}
|
|
5187
|
+
function scopeText(shot) {
|
|
5188
|
+
var scope = shot && shot.scope;
|
|
5189
|
+
if (!shot || shot.frame === false || scope === "selection") return "\u4EC5\u9009\u533A";
|
|
5190
|
+
if (scope === "screen") return "\u5168\u5C4F + \u7EA2\u6846";
|
|
5191
|
+
if (scope === "margin") return "\u9009\u533A + \u7559\u767D";
|
|
5192
|
+
return "\u6574\u7A97 + \u7EA2\u6846";
|
|
5193
|
+
}
|
|
5194
|
+
function hideMarkers() {
|
|
5195
|
+
if (frameEl) frameEl.setAttribute("data-show", "0");
|
|
5196
|
+
if (chipEl) chipEl.setAttribute("data-show", "0");
|
|
5197
|
+
}
|
|
5198
|
+
function clearAll() {
|
|
5199
|
+
hideNotice();
|
|
5200
|
+
hideMarkers();
|
|
5201
|
+
if (chipUrl) {
|
|
5202
|
+
URL.revokeObjectURL(chipUrl);
|
|
5203
|
+
chipUrl = "";
|
|
5204
|
+
}
|
|
5205
|
+
if (chipImg) chipImg.removeAttribute("src");
|
|
5206
|
+
current = null;
|
|
5207
|
+
if (env.state.selection && env.state.selection.source === "shot") env.state.selection = null;
|
|
5208
|
+
if (env.vcardOpen && env.vcardOpen() && env.closeVCard) env.closeVCard(true);
|
|
5209
|
+
}
|
|
5210
|
+
var cancelBound = false;
|
|
5211
|
+
function bindCancel() {
|
|
5212
|
+
if (cancelBound) return;
|
|
5213
|
+
var d = doc();
|
|
5214
|
+
if (!d || !d.addEventListener) return;
|
|
5215
|
+
cancelBound = true;
|
|
5216
|
+
var showing = function() {
|
|
5217
|
+
return !!current || frameEl && frameEl.getAttribute("data-show") === "1" || chipEl && chipEl.getAttribute("data-show") === "1" || toastEl && toastEl.getAttribute("data-show") === "1";
|
|
5218
|
+
};
|
|
5219
|
+
d.addEventListener("keydown", function(event) {
|
|
5220
|
+
if (event.key === "Escape" && showing()) clearAll();
|
|
5221
|
+
}, true);
|
|
5222
|
+
d.addEventListener("pointerdown", function(event) {
|
|
5223
|
+
if (!showing()) return;
|
|
5224
|
+
var target = event.target;
|
|
5225
|
+
var card = env.vcardElement && env.vcardElement();
|
|
5226
|
+
if (card && target && card.contains && card.contains(target)) return;
|
|
5227
|
+
if (chipEl && target && chipEl.contains && chipEl.contains(target)) return;
|
|
5228
|
+
if (toastEl && target && toastEl.contains && toastEl.contains(target)) return;
|
|
5229
|
+
clearAll();
|
|
5230
|
+
}, true);
|
|
5231
|
+
}
|
|
5232
|
+
function startCapture() {
|
|
5233
|
+
clearAll();
|
|
5234
|
+
var mode = env.panelOpen ? "panel" : "card";
|
|
5235
|
+
fetch(SHOT_START + "?mode=" + mode, { method: "POST", body: "" }).then(function(response) {
|
|
5236
|
+
if (!response.ok) throw new Error("HTTP " + response.status);
|
|
5237
|
+
return response.json();
|
|
5238
|
+
}).then(function(info) {
|
|
5239
|
+
if (!info || info.ok !== true) notice(errorText(info));
|
|
5240
|
+
}).catch(function() {
|
|
5241
|
+
notice("\u622A\u56FE\u670D\u52A1\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
|
|
5242
|
+
});
|
|
5243
|
+
}
|
|
5244
|
+
async function handle(shot) {
|
|
5245
|
+
if (!shot || shot.id === lastId) return;
|
|
5246
|
+
lastId = shot.id;
|
|
5247
|
+
var t0 = Date.now();
|
|
5248
|
+
current = { shot, file: null, blob: null, ref: null, refReady: null, uploading: null, error: "", t0 };
|
|
5249
|
+
var mine = current;
|
|
5250
|
+
var step = "geometry";
|
|
5251
|
+
try {
|
|
5252
|
+
if (env.ensureLayer) env.ensureLayer();
|
|
5253
|
+
var page = pageRectOf(shot.selectionRect, origin());
|
|
5254
|
+
var view = viewport();
|
|
5255
|
+
var inside = !!(page && insideViewport(page, view));
|
|
5256
|
+
var anchor = null;
|
|
5257
|
+
var panelMode = !!env.panelOpen && (!shot.mode || shot.mode === "panel");
|
|
5258
|
+
if (panelMode) {
|
|
5259
|
+
if (frameEl) frameEl.setAttribute("data-show", "0");
|
|
5260
|
+
if (chipEl) chipEl.setAttribute("data-show", "0");
|
|
5261
|
+
} else if (inside) {
|
|
5262
|
+
var frame = ensureFrame();
|
|
5263
|
+
frame.style.left = Math.round(page.left) + "px";
|
|
5264
|
+
frame.style.top = Math.round(page.top) + "px";
|
|
5265
|
+
frame.style.width = Math.round(page.width) + "px";
|
|
5266
|
+
frame.style.height = Math.round(page.height) + "px";
|
|
5267
|
+
frame.setAttribute("data-show", "1");
|
|
5268
|
+
if (chipEl) chipEl.setAttribute("data-show", "0");
|
|
5269
|
+
anchor = page;
|
|
5270
|
+
} else {
|
|
5271
|
+
var chip = ensureChip();
|
|
5272
|
+
var edge = nearestEdge(page || { left: 0, top: 0, width: 0, height: 0 }, view);
|
|
5273
|
+
var box = chipRectFor(edge, page || { left: 0, top: 0, width: 0, height: 0 }, view);
|
|
5274
|
+
chip.style.left = Math.round(box.left) + "px";
|
|
5275
|
+
chip.style.top = Math.round(box.top) + "px";
|
|
5276
|
+
var chipTitle = chip.querySelector(".dsh-sel-shotchip-tx b");
|
|
5277
|
+
if (chipTitle) chipTitle.textContent = scopeText(shot);
|
|
5278
|
+
var meta = chip.querySelector(".dsh-sel-shotchip-meta");
|
|
5279
|
+
if (meta) meta.textContent = Math.round(shot.selectionRect.width) + " \xD7 " + Math.round(shot.selectionRect.height);
|
|
5280
|
+
chip.setAttribute("data-show", "1");
|
|
5281
|
+
if (frameEl) frameEl.setAttribute("data-show", "0");
|
|
5282
|
+
anchor = asRect(box);
|
|
5283
|
+
}
|
|
5284
|
+
var md0 = Date.now();
|
|
5285
|
+
step = "annotate";
|
|
5286
|
+
var imageUrl = SHOT_IMAGE + "?id=" + encodeURIComponent(shot.id);
|
|
5287
|
+
var applyBlob = function(blob) {
|
|
5288
|
+
if (!blob) return null;
|
|
5289
|
+
if (current !== mine) return null;
|
|
5290
|
+
mine.blob = blob;
|
|
5291
|
+
mine.file = new File([blob], "\u622A\u56FE.png", { type: blob.type || "image/png" });
|
|
5292
|
+
mine.error = "";
|
|
5293
|
+
if (chipImg) {
|
|
5294
|
+
if (chipUrl) URL.revokeObjectURL(chipUrl);
|
|
5295
|
+
chipUrl = URL.createObjectURL(blob);
|
|
5296
|
+
chipImg.src = chipUrl;
|
|
5297
|
+
}
|
|
5298
|
+
report({ stage: "bg-done", ms: Date.now() - md0, frame: shot.frame !== false, scope: shot.scope || "" });
|
|
5299
|
+
return null;
|
|
5300
|
+
};
|
|
5301
|
+
var markWorkFailed = function(message, error) {
|
|
5302
|
+
if (current === mine) {
|
|
5303
|
+
mine.error = "image-failed";
|
|
5304
|
+
notice(message);
|
|
5305
|
+
}
|
|
5306
|
+
report({ stage: "bg-fail", ms: Date.now() - md0, error: String(error && error.message || error) });
|
|
5307
|
+
return null;
|
|
5308
|
+
};
|
|
5309
|
+
var startWork = function() {
|
|
5310
|
+
if (shot.frame === false) {
|
|
5311
|
+
return fetch(imageUrl, { cache: "no-store" }).then(function(response) {
|
|
5312
|
+
if (!response.ok) throw new Error("HTTP " + response.status);
|
|
5313
|
+
return response.blob();
|
|
5314
|
+
}).then(applyBlob).catch(function(error) {
|
|
5315
|
+
return markWorkFailed("\u622A\u56FE\u5DF2\u62CD\u4E0B\uFF0C\u4F46\u8BFB\u53D6\u56FE\u7247\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5", error);
|
|
5316
|
+
});
|
|
5317
|
+
}
|
|
5318
|
+
return loadImage(imageUrl).then(function(img) {
|
|
5319
|
+
return annotate(img, shot.rect, shot.scale, shot);
|
|
5320
|
+
}).then(applyBlob).catch(function(error) {
|
|
5321
|
+
return markWorkFailed("\u622A\u56FE\u5DF2\u62CD\u4E0B\uFF0C\u4F46\u5904\u7406\u56FE\u7247\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5", error);
|
|
5322
|
+
});
|
|
5323
|
+
};
|
|
5324
|
+
current.refReady = new Promise(function(resolve) {
|
|
5325
|
+
var kick = function() {
|
|
5326
|
+
resolve(startWork());
|
|
5327
|
+
};
|
|
5328
|
+
if (panelMode) {
|
|
5329
|
+
kick();
|
|
5330
|
+
return;
|
|
5331
|
+
}
|
|
5332
|
+
if (shot.frame === false) {
|
|
5333
|
+
kick();
|
|
5334
|
+
return;
|
|
5335
|
+
}
|
|
5336
|
+
if (typeof window !== "undefined" && window.requestIdleCallback) {
|
|
5337
|
+
setTimeout(function() {
|
|
5338
|
+
window.requestIdleCallback(kick, { timeout: 1200 });
|
|
5339
|
+
}, 350);
|
|
5340
|
+
} else {
|
|
5341
|
+
setTimeout(kick, 350);
|
|
5342
|
+
}
|
|
5343
|
+
});
|
|
5344
|
+
if (panelMode) {
|
|
5345
|
+
var ready = current.refReady;
|
|
5346
|
+
if (ready) ready.then(function() {
|
|
5347
|
+
if (current !== mine) return;
|
|
5348
|
+
if (mine.file) env.addFiles([mine.file]);
|
|
5349
|
+
else if (!mine.error) notice("\u622A\u56FE\u6CA1\u6709\u751F\u6210\u6210\u529F\uFF0C\u8BF7\u91CD\u8BD5");
|
|
5350
|
+
if (env.focusAskBox) env.focusAskBox();
|
|
5351
|
+
});
|
|
5352
|
+
report({ stage: "done", branch: "panel", scope: shot.scope || "", ms: Date.now() - t0 });
|
|
5353
|
+
return;
|
|
5354
|
+
}
|
|
5355
|
+
env.state.selection = {
|
|
5356
|
+
source: "shot",
|
|
5357
|
+
text: "",
|
|
5358
|
+
label: "\u622A\u56FE",
|
|
5359
|
+
context: "",
|
|
5360
|
+
keyContext: "",
|
|
5361
|
+
range: null,
|
|
5362
|
+
rect: anchor,
|
|
5363
|
+
shot: current,
|
|
5364
|
+
ref: null,
|
|
5365
|
+
// 后续提示词要按取景范围换口径(仅选区没有红框,留白/全屏/整窗口径不同)
|
|
5366
|
+
shotScope: shot.scope || "",
|
|
5367
|
+
shotFrame: shot.frame !== false
|
|
5368
|
+
};
|
|
5369
|
+
step = "openVCard";
|
|
5370
|
+
env.openVCard();
|
|
5371
|
+
var opened = !env.vcardOpen || env.vcardOpen();
|
|
5372
|
+
report({ stage: "done", branch: inside ? "frame" : "chip", opened, ms: Date.now() - t0 });
|
|
5373
|
+
setTimeout(function() {
|
|
5374
|
+
try {
|
|
5375
|
+
var card = env.vcardElement && env.vcardElement();
|
|
5376
|
+
var cinfo = null;
|
|
5377
|
+
if (card) {
|
|
5378
|
+
var cr = card.getBoundingClientRect();
|
|
5379
|
+
var cs = getComputedStyle(card);
|
|
5380
|
+
cinfo = { rect: [Math.round(cr.left), Math.round(cr.top), Math.round(cr.width), Math.round(cr.height)], display: cs.display, opacity: cs.opacity, z: cs.zIndex };
|
|
5381
|
+
}
|
|
5382
|
+
var layerEl = env.layer;
|
|
5383
|
+
report({
|
|
5384
|
+
stage: "probe",
|
|
5385
|
+
open: env.vcardOpen ? !!env.vcardOpen() : null,
|
|
5386
|
+
card: cinfo,
|
|
5387
|
+
layer: layerEl ? { connected: !!layerEl.isConnected, parent: layerEl.parentNode ? layerEl.parentNode.className || layerEl.parentNode.nodeName : null } : null,
|
|
5388
|
+
frame: frameEl ? { show: frameEl.getAttribute("data-show"), connected: !!frameEl.isConnected } : null,
|
|
5389
|
+
vp: [window.innerWidth, window.innerHeight]
|
|
5390
|
+
});
|
|
5391
|
+
} catch (error) {
|
|
5392
|
+
report({ stage: "probe", error: String(error && error.message || error) });
|
|
5393
|
+
}
|
|
5394
|
+
}, 500);
|
|
5395
|
+
if (!opened) {
|
|
5396
|
+
hideMarkers();
|
|
5397
|
+
env.state.selection = null;
|
|
5398
|
+
current = null;
|
|
5399
|
+
}
|
|
5400
|
+
} catch (error) {
|
|
5401
|
+
report({ stage: step, error: String(error && error.stack || error && error.message || error) });
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
function uploadAnnotated(blob) {
|
|
5405
|
+
return new Promise(function(resolve) {
|
|
5406
|
+
var reader = new FileReader();
|
|
5407
|
+
reader.onload = function() {
|
|
5408
|
+
var data = String(reader.result || "");
|
|
5409
|
+
var base64 = data.indexOf(",") >= 0 ? data.slice(data.indexOf(",") + 1) : data;
|
|
5410
|
+
fetch(ATTACH, {
|
|
5411
|
+
method: "POST",
|
|
5412
|
+
headers: { "content-type": "application/json" },
|
|
5413
|
+
body: JSON.stringify({ items: [{ kind: "image", name: "\u622A\u56FE.png", mediaType: "image/png", data: base64 }] })
|
|
5414
|
+
}).then(function(r) {
|
|
5415
|
+
return r.ok ? r.json() : null;
|
|
5416
|
+
}).then(function(json) {
|
|
5417
|
+
var item = json && json.items && json.items[0];
|
|
5418
|
+
resolve(item && item.ref ? item.ref : null);
|
|
5419
|
+
}).catch(function() {
|
|
5420
|
+
resolve(null);
|
|
5421
|
+
});
|
|
5422
|
+
};
|
|
5423
|
+
reader.onerror = function() {
|
|
5424
|
+
resolve(null);
|
|
5425
|
+
};
|
|
5426
|
+
reader.readAsDataURL(blob);
|
|
5427
|
+
});
|
|
5428
|
+
}
|
|
5429
|
+
function loadImage(src, attempt) {
|
|
5430
|
+
var tries = attempt || 0;
|
|
5431
|
+
return new Promise(function(resolve, reject) {
|
|
5432
|
+
var img = new Image();
|
|
5433
|
+
img.onload = function() {
|
|
5434
|
+
resolve(img);
|
|
5435
|
+
};
|
|
5436
|
+
img.onerror = function(error) {
|
|
5437
|
+
if (tries >= 8) {
|
|
5438
|
+
reject(error);
|
|
5439
|
+
return;
|
|
5440
|
+
}
|
|
5441
|
+
setTimeout(function() {
|
|
5442
|
+
loadImage(src, tries + 1).then(resolve, reject);
|
|
5443
|
+
}, 120);
|
|
5444
|
+
};
|
|
5445
|
+
img.src = src + (src.indexOf("?") >= 0 ? "&" : "?") + "r=" + tries;
|
|
5446
|
+
});
|
|
5447
|
+
}
|
|
5448
|
+
async function poll() {
|
|
5449
|
+
if (busy) return;
|
|
5450
|
+
busy = true;
|
|
5451
|
+
try {
|
|
5452
|
+
var res = await fetch(SHOT_PENDING, { cache: "no-store" });
|
|
5453
|
+
if (res.ok) {
|
|
5454
|
+
var data = await res.json();
|
|
5455
|
+
if (data && data.error) notice(errorText(data.error));
|
|
5456
|
+
if (data && data.shot) await handle(data.shot);
|
|
5457
|
+
}
|
|
5458
|
+
} catch (error) {
|
|
5459
|
+
} finally {
|
|
5460
|
+
busy = false;
|
|
5461
|
+
}
|
|
5462
|
+
}
|
|
5463
|
+
function loop() {
|
|
5464
|
+
if (superseded()) return;
|
|
5465
|
+
timer = setTimeout(function() {
|
|
5466
|
+
timer = 0;
|
|
5467
|
+
if (superseded()) return;
|
|
5468
|
+
var d = doc();
|
|
5469
|
+
if (d && d.visibilityState === "hidden") return loop();
|
|
5470
|
+
poll().then(loop, loop);
|
|
5471
|
+
}, 30);
|
|
5472
|
+
}
|
|
5473
|
+
return {
|
|
5474
|
+
start: function() {
|
|
5475
|
+
bindCancel();
|
|
5476
|
+
if (timer) return;
|
|
5477
|
+
loop();
|
|
5478
|
+
},
|
|
5479
|
+
stop: function() {
|
|
5480
|
+
if (timer) clearTimeout(timer);
|
|
5481
|
+
timer = 0;
|
|
5482
|
+
hideNotice();
|
|
5483
|
+
},
|
|
5484
|
+
/** 快捷键按下时调用。 */
|
|
5485
|
+
capture: startCapture,
|
|
5486
|
+
clear: clearAll,
|
|
5487
|
+
clearMarkers: hideMarkers,
|
|
5488
|
+
/** 发送时用:当前这张已经画好红框的图(File 与持久引用)。 */
|
|
5489
|
+
takeFile: function() {
|
|
5490
|
+
return current ? current.file : null;
|
|
5491
|
+
},
|
|
5492
|
+
/**
|
|
5493
|
+
* 发送时用:等后台把红框图画好,**此刻才上传**并返回持久引用。
|
|
5494
|
+
*
|
|
5495
|
+
* 为什么不在截完就传:附件库(~/.dsh/attachments)是内容寻址、**只增不删**,
|
|
5496
|
+
* 没有 GC。用户按了 Esc / 点空白取消时,我们不该往里面塞一张永远不会被用到的图。
|
|
5497
|
+
*/
|
|
5498
|
+
takeRefAsync: function() {
|
|
5499
|
+
var here = current;
|
|
5500
|
+
if (!here) return Promise.resolve(null);
|
|
5501
|
+
if (here.error && here.error !== "upload-failed") return Promise.resolve(null);
|
|
5502
|
+
if (here.ref) return Promise.resolve(here.ref);
|
|
5503
|
+
if (here.uploading) return here.uploading;
|
|
5504
|
+
if (!here.refReady) {
|
|
5505
|
+
here.error = "image-failed";
|
|
5506
|
+
notice("\u622A\u56FE\u5DF2\u62CD\u4E0B\uFF0C\u4F46\u8FD8\u6CA1\u6709\u51C6\u5907\u597D\uFF0C\u8BF7\u91CD\u8BD5");
|
|
5507
|
+
return Promise.resolve(null);
|
|
5508
|
+
}
|
|
5509
|
+
here.uploading = here.refReady.then(function() {
|
|
5510
|
+
if (here.ref) return here.ref;
|
|
5511
|
+
if (!here.blob) {
|
|
5512
|
+
if (!here.error) {
|
|
5513
|
+
here.error = "image-failed";
|
|
5514
|
+
notice("\u622A\u56FE\u5DF2\u62CD\u4E0B\uFF0C\u4F46\u8BFB\u53D6\u56FE\u7247\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
|
|
5515
|
+
}
|
|
5516
|
+
return null;
|
|
5517
|
+
}
|
|
5518
|
+
return uploadAnnotated(here.blob).then(function(ref) {
|
|
5519
|
+
if (!ref) {
|
|
5520
|
+
here.error = "upload-failed";
|
|
5521
|
+
here.uploading = null;
|
|
5522
|
+
return null;
|
|
5523
|
+
}
|
|
5524
|
+
here.ref = ref;
|
|
5525
|
+
here.blob = null;
|
|
5526
|
+
here.error = "";
|
|
5527
|
+
report({ stage: "uploaded", ref: true });
|
|
5528
|
+
return ref;
|
|
5529
|
+
});
|
|
5530
|
+
});
|
|
5531
|
+
return here.uploading;
|
|
5532
|
+
},
|
|
5533
|
+
/** 当前这张图是否已经失败(供语音卡在发送时决定"先别开小窗")。 */
|
|
5534
|
+
failed: function() {
|
|
5535
|
+
return current && current.error ? String(current.error) : "";
|
|
5536
|
+
},
|
|
5537
|
+
/** 给语音卡/其它控制器用的提示入口。 */
|
|
5538
|
+
notice,
|
|
5539
|
+
takeRef: function() {
|
|
5540
|
+
return current ? current.ref : null;
|
|
5541
|
+
},
|
|
5542
|
+
state: function() {
|
|
5543
|
+
return {
|
|
5544
|
+
polling: !!timer,
|
|
5545
|
+
lastId,
|
|
5546
|
+
hasFile: !!(current && current.file),
|
|
5547
|
+
error: current && current.error ? String(current.error) : "",
|
|
5548
|
+
frame: frameEl ? frameEl.getAttribute("data-show") === "1" : false,
|
|
5549
|
+
chip: chipEl ? chipEl.getAttribute("data-show") === "1" : false
|
|
5550
|
+
};
|
|
5551
|
+
}
|
|
5552
|
+
};
|
|
5553
|
+
}
|
|
5554
|
+
|
|
4493
5555
|
// src/client/bridge.js
|
|
4494
5556
|
function bridgeBody() {
|
|
4495
5557
|
if (window.__dshSelBridge) return;
|
|
@@ -4635,7 +5697,7 @@ function bridgeBody() {
|
|
|
4635
5697
|
if (!now) return "";
|
|
4636
5698
|
return now.text + "\0" + now.rect.x + "," + now.rect.y + "," + now.rect.bottom + "," + now.rect.right;
|
|
4637
5699
|
}
|
|
4638
|
-
function
|
|
5700
|
+
function report2(allowClear) {
|
|
4639
5701
|
var now = read();
|
|
4640
5702
|
if (!now) {
|
|
4641
5703
|
if (last && allowClear !== false) {
|
|
@@ -4681,10 +5743,10 @@ function bridgeBody() {
|
|
|
4681
5743
|
}, true);
|
|
4682
5744
|
window.addEventListener("mouseup", function() {
|
|
4683
5745
|
pressed = false;
|
|
4684
|
-
setTimeout(
|
|
5746
|
+
setTimeout(report2, 0);
|
|
4685
5747
|
}, true);
|
|
4686
5748
|
window.addEventListener("keyup", function() {
|
|
4687
|
-
setTimeout(
|
|
5749
|
+
setTimeout(report2, 0);
|
|
4688
5750
|
}, true);
|
|
4689
5751
|
window.addEventListener("blur", function() {
|
|
4690
5752
|
pressed = false;
|
|
@@ -4692,29 +5754,29 @@ function bridgeBody() {
|
|
|
4692
5754
|
window.addEventListener("scroll", function() {
|
|
4693
5755
|
if (pressed) return;
|
|
4694
5756
|
setTimeout(function() {
|
|
4695
|
-
|
|
5757
|
+
report2(false);
|
|
4696
5758
|
}, 60);
|
|
4697
5759
|
}, true);
|
|
4698
5760
|
window.addEventListener("resize", function() {
|
|
4699
|
-
if (!pressed)
|
|
5761
|
+
if (!pressed) report2(false);
|
|
4700
5762
|
});
|
|
4701
5763
|
window.addEventListener("message", function(event) {
|
|
4702
5764
|
var data = event && event.data;
|
|
4703
5765
|
if (!data || data.__dshSel !== 1) return;
|
|
4704
5766
|
if (data.kind === "ping") {
|
|
4705
5767
|
send("hello", null);
|
|
4706
|
-
|
|
5768
|
+
report2();
|
|
4707
5769
|
}
|
|
4708
|
-
if (data.kind === "rescan")
|
|
5770
|
+
if (data.kind === "rescan") report2();
|
|
4709
5771
|
});
|
|
4710
5772
|
try {
|
|
4711
5773
|
document.addEventListener("selectionchange", function() {
|
|
4712
|
-
if (!pressed)
|
|
5774
|
+
if (!pressed) report2();
|
|
4713
5775
|
});
|
|
4714
5776
|
} catch (error) {
|
|
4715
5777
|
}
|
|
4716
5778
|
setInterval(tick, 250);
|
|
4717
|
-
setTimeout(
|
|
5779
|
+
setTimeout(report2, 60);
|
|
4718
5780
|
}
|
|
4719
5781
|
var BRIDGE_MARK = "data-dsh-sel-bridge";
|
|
4720
5782
|
function bridgeIntoHtml(html) {
|
|
@@ -5036,7 +6098,82 @@ function createPanelController(env) {
|
|
|
5036
6098
|
clearDocSelection();
|
|
5037
6099
|
env.state.selection = null;
|
|
5038
6100
|
}
|
|
6101
|
+
function shotScopeLabel(scope, frame) {
|
|
6102
|
+
if (frame === false || scope === "selection") return "\u4EC5\u9009\u533A";
|
|
6103
|
+
if (scope === "screen") return "\u5168\u5C4F + \u7EA2\u6846";
|
|
6104
|
+
if (scope === "margin") return "\u9009\u533A + \u7559\u767D";
|
|
6105
|
+
return "\u6574\u7A97 + \u7EA2\u6846";
|
|
6106
|
+
}
|
|
6107
|
+
function setQuoteLine(text) {
|
|
6108
|
+
if (!env.quote) return;
|
|
6109
|
+
var title = env.state.shotTitle;
|
|
6110
|
+
if (title) {
|
|
6111
|
+
env.quote.textContent = String(title);
|
|
6112
|
+
env.quote.setAttribute("data-title", "1");
|
|
6113
|
+
return;
|
|
6114
|
+
}
|
|
6115
|
+
env.quote.removeAttribute("data-title");
|
|
6116
|
+
env.quote.textContent = text;
|
|
6117
|
+
}
|
|
6118
|
+
function applyShotTitle(title) {
|
|
6119
|
+
if (!title) return;
|
|
6120
|
+
env.state.shotTitle = String(title);
|
|
6121
|
+
setQuoteLine(String(title));
|
|
6122
|
+
if (env.paintPill) env.paintPill();
|
|
6123
|
+
}
|
|
6124
|
+
function openPanelForShot(anchor, options) {
|
|
6125
|
+
env.panelSessionId = currentSessionId();
|
|
6126
|
+
env.state.contextState = null;
|
|
6127
|
+
env.state.skipInterpretation = true;
|
|
6128
|
+
env.state.payload = buildPayload(String(options.askFirst || ""), "", "\u622A\u56FE");
|
|
6129
|
+
env.state.payload.kind = "shot";
|
|
6130
|
+
env.state.shotScope = options.shotScope || "";
|
|
6131
|
+
env.state.shotFrame = options.shotFrame !== false;
|
|
6132
|
+
if (env.state.shotScope) {
|
|
6133
|
+
env.state.payload.shotScope = env.state.shotScope;
|
|
6134
|
+
env.state.payload.shotFrame = env.state.shotFrame;
|
|
6135
|
+
}
|
|
6136
|
+
env.state.kind = "shot";
|
|
6137
|
+
var shotIdentity = "";
|
|
6138
|
+
if (options && options.shotRef && typeof options.shotRef === "object") {
|
|
6139
|
+
var ref = options.shotRef;
|
|
6140
|
+
shotIdentity = String(ref.attachmentId || ref.id || ref.hash || "");
|
|
6141
|
+
}
|
|
6142
|
+
if (!shotIdentity) {
|
|
6143
|
+
shotIdentity = "shot-" + Date.now().toString(36);
|
|
6144
|
+
}
|
|
6145
|
+
env.state.cacheKey = cacheKeyOf(String(options.askFirst || ""), "shot:" + shotIdentity);
|
|
6146
|
+
env.hideButton();
|
|
6147
|
+
showPanel(anchor);
|
|
6148
|
+
env.state.shotTitle = "";
|
|
6149
|
+
setQuoteLine("\u622A\u56FE \xB7 " + shotScopeLabel(env.state.shotScope, env.state.shotFrame));
|
|
6150
|
+
if (env.paintPill) env.paintPill();
|
|
6151
|
+
env.clearQuotes();
|
|
6152
|
+
env.clearAttachments();
|
|
6153
|
+
env.state.turns = [];
|
|
6154
|
+
env.state.parts = { translation: "", detail: "" };
|
|
6155
|
+
env.state.stage = "";
|
|
6156
|
+
env.state.raw = "";
|
|
6157
|
+
env.state.phase = "done";
|
|
6158
|
+
env.state.chars = 0;
|
|
6159
|
+
env.state.error = "";
|
|
6160
|
+
env.state.fromCache = false;
|
|
6161
|
+
env.state.sharedCache = false;
|
|
6162
|
+
env.state.elapsed = 0;
|
|
6163
|
+
env.state.toolBusy = false;
|
|
6164
|
+
env.state.cacheState = "";
|
|
6165
|
+
env.state.toolDigest = "";
|
|
6166
|
+
env.renderTurns();
|
|
6167
|
+
paint();
|
|
6168
|
+
var refs = options.shotRef ? [{ kind: "image", ref: options.shotRef }] : [];
|
|
6169
|
+
var note = options.shotRef ? "\u3010\u672C\u6761\u6D88\u606F\u5E26\u4E86\u9644\u4EF6\u3011\u622A\u56FE\uFF08\u56FE\u7247\uFF09" : "";
|
|
6170
|
+
env.sendAsk(options.askFirst, null, refs, note);
|
|
6171
|
+
}
|
|
5039
6172
|
function openPanelWithInner(text, context, label, anchor, keyContext, options) {
|
|
6173
|
+
if (options && options.shotRef !== void 0) {
|
|
6174
|
+
openPanelForShot(anchor, options);
|
|
6175
|
+
return;
|
|
6176
|
+
}
|
|
5040
6177
|
env.state.skipInterpretation = !!(options && options.askFirst);
|
|
5041
6178
|
env.panelSessionId = currentSessionId();
|
|
5042
6179
|
env.state.contextState = null;
|
|
@@ -5044,8 +6181,10 @@ function createPanelController(env) {
|
|
|
5044
6181
|
env.state.kind = env.state.payload.kind;
|
|
5045
6182
|
env.hideButton();
|
|
5046
6183
|
showPanel(anchor);
|
|
5047
|
-
env.
|
|
6184
|
+
env.state.shotTitle = "";
|
|
6185
|
+
setQuoteLine(text.length > 240 ? text.slice(0, 240) + " \u2026" : text);
|
|
5048
6186
|
applySectionTitle(env.state.kind, text);
|
|
6187
|
+
if (env.paintPill) env.paintPill();
|
|
5049
6188
|
var cacheKey = cacheKeyOf(text, keyContext === void 0 ? context : keyContext);
|
|
5050
6189
|
var cached = env.cache.get(cacheKey);
|
|
5051
6190
|
env.state.cacheKey = cacheKey;
|
|
@@ -5329,6 +6468,7 @@ function createPanelController(env) {
|
|
|
5329
6468
|
}
|
|
5330
6469
|
function closePanel() {
|
|
5331
6470
|
env.closeModelMenu();
|
|
6471
|
+
if (typeof env.closeLightbox === "function") env.closeLightbox();
|
|
5332
6472
|
env.closeSettings();
|
|
5333
6473
|
env.panelOpen = false;
|
|
5334
6474
|
env.panel.style.display = "none";
|
|
@@ -5348,9 +6488,11 @@ function createPanelController(env) {
|
|
|
5348
6488
|
env.hideQuoteButton();
|
|
5349
6489
|
env.state.quoteSelection = null;
|
|
5350
6490
|
env.cancelVoice();
|
|
5351
|
-
if (env.state.request)
|
|
5352
|
-
|
|
5353
|
-
|
|
6491
|
+
if (env.state.request) {
|
|
6492
|
+
env.state.aborted = true;
|
|
6493
|
+
env.state.request.abort();
|
|
6494
|
+
env.state.request = null;
|
|
6495
|
+
}
|
|
5354
6496
|
if (env.state.askRequest) {
|
|
5355
6497
|
env.state.stopped = true;
|
|
5356
6498
|
env.state.askRequest.abort();
|
|
@@ -5596,6 +6738,7 @@ function createPanelController(env) {
|
|
|
5596
6738
|
env.panel.style.top = Math.round(top) + "px";
|
|
5597
6739
|
}
|
|
5598
6740
|
return {
|
|
6741
|
+
applyShotTitle,
|
|
5599
6742
|
releaseRenderedContent,
|
|
5600
6743
|
openForSelection,
|
|
5601
6744
|
currentSessionId,
|
|
@@ -5648,7 +6791,9 @@ function createPanelController(env) {
|
|
|
5648
6791
|
// src/client/pill.js
|
|
5649
6792
|
function createPillController(env) {
|
|
5650
6793
|
function pillSnippet() {
|
|
5651
|
-
var
|
|
6794
|
+
var title = env.state.shotTitle ? String(env.state.shotTitle) : "";
|
|
6795
|
+
title = title.replace(/\s+/g, " ").trim();
|
|
6796
|
+
var text = title || (env.state.payload && env.state.payload.text ? String(env.state.payload.text) : "");
|
|
5652
6797
|
text = text.replace(/\s+/g, " ").trim();
|
|
5653
6798
|
if (!text) return "";
|
|
5654
6799
|
return text.length > 12 ? text.slice(0, 12) + "\u2026" : text;
|
|
@@ -5684,7 +6829,7 @@ function createPillController(env) {
|
|
|
5684
6829
|
var caret = env.panelOpen ? "\u25BE" : "\u25B4";
|
|
5685
6830
|
if (env.pillCaret.textContent !== caret) env.pillCaret.textContent = caret;
|
|
5686
6831
|
env.pill.title = [
|
|
5687
|
-
snippet ? "\u6700\u8FD1\u4E00\u6B21\u5212\u8BCD\uFF1A" + snippet : "\u8FD8\u6CA1\u6709\u5212\u8FC7\u8BCD",
|
|
6832
|
+
snippet ? (env.state.shotTitle ? "\u6700\u8FD1\u4E00\u6B21\u622A\u56FE\uFF1A" : "\u6700\u8FD1\u4E00\u6B21\u5212\u8BCD\uFF1A") + snippet : "\u8FD8\u6CA1\u6709\u5212\u8FC7\u8BCD",
|
|
5688
6833
|
status.text,
|
|
5689
6834
|
env.panelOpen ? "\u70B9\u51FB\u6536\u8D77\u5C0F\u7A97" : "\u70B9\u51FB\u56DE\u5230\u8FD9\u4E2A\u5C0F\u7A97",
|
|
5690
6835
|
"\u53EF\u62D6\u52A8\uFF08\u62D6\u5230\u53F3\u4E0B\u89D2\u9644\u8FD1\u4F1A\u5438\u9644\u56DE\u53BB\uFF09"
|
|
@@ -6249,6 +7394,7 @@ function prepareChatHistory(turns, rawState) {
|
|
|
6249
7394
|
// src/client/history.js
|
|
6250
7395
|
function createHistoryController(env) {
|
|
6251
7396
|
var foldCount = 0;
|
|
7397
|
+
var shotTitlePendingKey = "";
|
|
6252
7398
|
var HTML_VOID_TAGS = {
|
|
6253
7399
|
area: 1,
|
|
6254
7400
|
base: 1,
|
|
@@ -6583,30 +7729,40 @@ function createHistoryController(env) {
|
|
|
6583
7729
|
} else return;
|
|
6584
7730
|
renderTurns();
|
|
6585
7731
|
}
|
|
7732
|
+
var savedBody = {
|
|
7733
|
+
key: env.state.cacheKey,
|
|
7734
|
+
text: payload.text,
|
|
7735
|
+
context: payload.context,
|
|
7736
|
+
// 截图有标题时写进 label(旧列表/旧客户端也能显示);标题本体另存 title
|
|
7737
|
+
label: payload.kind === "shot" && String(env.state.shotTitle || "").trim() ? "\u622A\u56FE \xB7 " + String(env.state.shotTitle).trim() : payload.label,
|
|
7738
|
+
...payload.kind === "shot" && String(env.state.shotTitle || "").trim() ? { title: String(env.state.shotTitle).trim() } : {},
|
|
7739
|
+
parts: env.state.parts,
|
|
7740
|
+
turns,
|
|
7741
|
+
toolDigest: env.state.toolDigest,
|
|
7742
|
+
contextState: env.state.contextState || null,
|
|
7743
|
+
pinned: !!(options && options.pinned),
|
|
7744
|
+
// 这段对话有没有解读,必须跟着条目落盘:回放时靠它决定画不画「翻译」一节。
|
|
7745
|
+
// 不落盘的话,语音卡那条"跳过翻译与详解、直接追问"的对话下次开出来,
|
|
7746
|
+
// 会顶着一张空翻译卡片("这次没有返回内容")——用户实测。
|
|
7747
|
+
skipped: env.state.skipInterpretation === true,
|
|
7748
|
+
// 截图取景范围:标题那次请求要带上同样的 scope,才能得到同一个 SHOT_PROMPT 口径
|
|
7749
|
+
...payload.kind === "shot" && env.state.shotScope ? { shotScope: env.state.shotScope, shotFrame: env.state.shotFrame !== false } : {}
|
|
7750
|
+
};
|
|
6586
7751
|
try {
|
|
6587
7752
|
fetch(HISTORY, {
|
|
6588
7753
|
method: "POST",
|
|
6589
7754
|
headers: { "content-type": "application/json" },
|
|
6590
|
-
body: JSON.stringify(
|
|
6591
|
-
key: env.state.cacheKey,
|
|
6592
|
-
text: payload.text,
|
|
6593
|
-
context: payload.context,
|
|
6594
|
-
label: payload.label,
|
|
6595
|
-
parts: env.state.parts,
|
|
6596
|
-
turns,
|
|
6597
|
-
toolDigest: env.state.toolDigest,
|
|
6598
|
-
contextState: env.state.contextState || null,
|
|
6599
|
-
pinned: !!(options && options.pinned),
|
|
6600
|
-
// 这段对话有没有解读,必须跟着条目落盘:回放时靠它决定画不画「翻译」一节。
|
|
6601
|
-
// 不落盘的话,语音卡那条"跳过翻译与详解、直接追问"的对话下次开出来,
|
|
6602
|
-
// 会顶着一张空翻译卡片("这次没有返回内容")——用户实测。
|
|
6603
|
-
skipped: env.state.skipInterpretation === true
|
|
6604
|
-
})
|
|
7755
|
+
body: JSON.stringify(savedBody)
|
|
6605
7756
|
}).then(function(response) {
|
|
6606
7757
|
if (!response.ok) throw new Error("HTTP " + response.status);
|
|
6607
7758
|
return response.json();
|
|
6608
7759
|
}).then(function(result) {
|
|
6609
|
-
|
|
7760
|
+
var failed = !result || result.ok !== true;
|
|
7761
|
+
noteSaved(failed);
|
|
7762
|
+
if (!failed && payload.kind === "shot" && !String(env.state.shotTitle || "").trim() && shotTitlePendingKey !== savedBody.key) {
|
|
7763
|
+
shotTitlePendingKey = savedBody.key;
|
|
7764
|
+
requestShotTitle(savedBody, payload.sessionId);
|
|
7765
|
+
}
|
|
6610
7766
|
}).catch(function() {
|
|
6611
7767
|
noteSaved(true);
|
|
6612
7768
|
});
|
|
@@ -6614,6 +7770,99 @@ function createHistoryController(env) {
|
|
|
6614
7770
|
noteSaved(true);
|
|
6615
7771
|
}
|
|
6616
7772
|
}
|
|
7773
|
+
function requestShotTitle(saved, sessionId, attempt) {
|
|
7774
|
+
var tries = attempt || 0;
|
|
7775
|
+
var userTurn = null;
|
|
7776
|
+
var list = saved.turns || [];
|
|
7777
|
+
for (var i = 0; i < list.length; i += 1) {
|
|
7778
|
+
if (list[i] && list[i].role === "user" && list[i].attachments && list[i].attachments.length > 0) {
|
|
7779
|
+
userTurn = list[i];
|
|
7780
|
+
break;
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
if (!userTurn) return;
|
|
7784
|
+
var savedKey = saved.key;
|
|
7785
|
+
fetch(API, {
|
|
7786
|
+
method: "POST",
|
|
7787
|
+
headers: { "content-type": "application/json" },
|
|
7788
|
+
body: JSON.stringify({
|
|
7789
|
+
text: saved.text,
|
|
7790
|
+
kind: "shot",
|
|
7791
|
+
titleOnly: true,
|
|
7792
|
+
label: "\u622A\u56FE",
|
|
7793
|
+
attachments: userTurn.attachments,
|
|
7794
|
+
// 与回答那次同一个取景口径:标题是最好的"红框/仅选区"措辞,换口径会问错东西
|
|
7795
|
+
...saved.shotScope ? { shotScope: saved.shotScope, shotFrame: saved.shotFrame !== false } : {},
|
|
7796
|
+
// ⚠️ 必须带 sessionId:不带会被上游 400 拒绝(MissingSessionID),
|
|
7797
|
+
// 而 `env.panelSessionId` 在历史模块里并不存在 —— 用的是保存时那份 payload 里的。
|
|
7798
|
+
...sessionId ? { sessionId } : {},
|
|
7799
|
+
// ⚠️ 必须带上面板当前用的模型:不带的话 host 会退回"会话默认模型",
|
|
7800
|
+
// 那个路由可能不支持图片 → 上游报错 → 正文为空 → 标题永远写不回去(实测踩到)。
|
|
7801
|
+
...env.state.modelChoice ? { provider: env.state.modelChoice.provider, model: env.state.modelChoice.model } : {}
|
|
7802
|
+
})
|
|
7803
|
+
}).then(function(response) {
|
|
7804
|
+
return response.ok ? response.text() : "";
|
|
7805
|
+
}).then(function(raw) {
|
|
7806
|
+
var title = cleanShotTitle(readSseText(raw));
|
|
7807
|
+
if (!title) throw new Error("empty-title");
|
|
7808
|
+
if (shotTitlePendingKey === savedKey) shotTitlePendingKey = "";
|
|
7809
|
+
if (env.state.cacheKey === savedKey && env.applyShotTitle) env.applyShotTitle(title);
|
|
7810
|
+
writeShotTitle(savedKey, title);
|
|
7811
|
+
}).catch(function() {
|
|
7812
|
+
if (tries >= 1) {
|
|
7813
|
+
if (shotTitlePendingKey === savedKey) shotTitlePendingKey = "";
|
|
7814
|
+
return;
|
|
7815
|
+
}
|
|
7816
|
+
setTimeout(function() {
|
|
7817
|
+
requestShotTitle(saved, sessionId, tries + 1);
|
|
7818
|
+
}, 2e3);
|
|
7819
|
+
});
|
|
7820
|
+
}
|
|
7821
|
+
function writeShotTitle(key, title) {
|
|
7822
|
+
fetch(HISTORY + "?key=" + encodeURIComponent(key)).then(function(response) {
|
|
7823
|
+
return response.ok ? response.json() : null;
|
|
7824
|
+
}).then(function(data) {
|
|
7825
|
+
var entry = data && data.entry;
|
|
7826
|
+
if (!entry) return;
|
|
7827
|
+
return fetch(HISTORY, {
|
|
7828
|
+
method: "POST",
|
|
7829
|
+
headers: { "content-type": "application/json" },
|
|
7830
|
+
// title 是权威字段;label 继续写成「截图 · 标题」兼容旧列表/旧版本
|
|
7831
|
+
body: JSON.stringify(Object.assign({}, entry, { title, label: "\u622A\u56FE \xB7 " + title }))
|
|
7832
|
+
});
|
|
7833
|
+
}).then(function(response) {
|
|
7834
|
+
if (!response || !response.ok) return;
|
|
7835
|
+
if (env.historyList && env.historyList.style.display === "flex") openHistoryList();
|
|
7836
|
+
}).catch(function() {
|
|
7837
|
+
});
|
|
7838
|
+
}
|
|
7839
|
+
function readSseText(raw) {
|
|
7840
|
+
var out = [];
|
|
7841
|
+
var lines = String(raw || "").split("\n");
|
|
7842
|
+
for (var i = 0; i < lines.length; i += 1) {
|
|
7843
|
+
var line = lines[i].trim();
|
|
7844
|
+
if (line.indexOf("data:") !== 0) continue;
|
|
7845
|
+
var chunk = line.slice(5).trim();
|
|
7846
|
+
if (!chunk || chunk === "[DONE]") continue;
|
|
7847
|
+
try {
|
|
7848
|
+
var obj = JSON.parse(chunk);
|
|
7849
|
+
if (obj.type === "thought") continue;
|
|
7850
|
+
if (typeof obj.delta === "string") out.push(obj.delta);
|
|
7851
|
+
else if (obj.type === "delta" && typeof obj.text === "string") out.push(obj.text);
|
|
7852
|
+
} catch (error) {
|
|
7853
|
+
}
|
|
7854
|
+
}
|
|
7855
|
+
return out.join("");
|
|
7856
|
+
}
|
|
7857
|
+
function cleanShotTitle(raw) {
|
|
7858
|
+
var title = String(raw || "").trim();
|
|
7859
|
+
title = title.replace(/^标题[::]\s*/, "");
|
|
7860
|
+
title = title.replace(/^["'「『【]+|["'」』】]+$/g, "");
|
|
7861
|
+
title = title.replace(/[。!?.!?,,、::;;…]+$/g, "");
|
|
7862
|
+
title = title.replace(/\s+/g, " ").trim();
|
|
7863
|
+
if (!title) return "";
|
|
7864
|
+
return title.length > 24 ? title.slice(0, 24) : title;
|
|
7865
|
+
}
|
|
6617
7866
|
function applyHistoryEntry(entry, source) {
|
|
6618
7867
|
var parts = entry.parts || {};
|
|
6619
7868
|
env.state.parts = { translation: parts.translation || "", detail: parts.detail || "" };
|
|
@@ -6647,10 +7896,26 @@ function createHistoryController(env) {
|
|
|
6647
7896
|
env.state.toolBusy = false;
|
|
6648
7897
|
env.state.cacheState = source === "local" ? "local" : "history";
|
|
6649
7898
|
env.state.chars = (env.state.parts.translation || "").length + (env.state.parts.detail || "").length;
|
|
6650
|
-
|
|
7899
|
+
var replayTitle = typeof entry.title === "string" ? entry.title.trim() : "";
|
|
7900
|
+
if (!replayTitle) {
|
|
7901
|
+
var titleMatch = /^截图 · (.+)$/.exec(String(entry.label || ""));
|
|
7902
|
+
if (titleMatch) replayTitle = titleMatch[1];
|
|
7903
|
+
}
|
|
7904
|
+
env.state.shotTitle = replayTitle;
|
|
7905
|
+
if (replayTitle) {
|
|
7906
|
+
if (env.applyShotTitle) env.applyShotTitle(replayTitle);
|
|
7907
|
+
else {
|
|
7908
|
+
env.quote.textContent = replayTitle;
|
|
7909
|
+
env.quote.setAttribute("data-title", "1");
|
|
7910
|
+
}
|
|
7911
|
+
} else {
|
|
7912
|
+
env.quote.textContent = entry.text.length > 240 ? entry.text.slice(0, 240) + " \u2026" : entry.text;
|
|
7913
|
+
env.quote.removeAttribute("data-title");
|
|
7914
|
+
}
|
|
6651
7915
|
env.applySectionTitle(env.state.kind, entry.text);
|
|
6652
7916
|
hideHistoryList();
|
|
6653
7917
|
env.paint();
|
|
7918
|
+
if (env.paintPill) env.paintPill();
|
|
6654
7919
|
renderTurns();
|
|
6655
7920
|
}
|
|
6656
7921
|
function restoreFromHistory(cacheKey, onMiss) {
|
|
@@ -6757,8 +8022,11 @@ function createHistoryController(env) {
|
|
|
6757
8022
|
}
|
|
6758
8023
|
function historyRow(item) {
|
|
6759
8024
|
var row = el("div", "dsh-sel-historyrow");
|
|
6760
|
-
var
|
|
6761
|
-
|
|
8025
|
+
var shotTitle = typeof item.title === "string" ? item.title.trim() : "";
|
|
8026
|
+
var shotMatch = /^截图 · (.+)$/.exec(String(item.label || ""));
|
|
8027
|
+
var isShot = !!shotTitle || !!shotMatch;
|
|
8028
|
+
var main = shotTitle || (shotMatch ? shotMatch[1] : String(item.text || "").trim());
|
|
8029
|
+
row.appendChild(el("div", "dsh-sel-historytext", main || "\uFF08\u8FD9\u6B21\u6CA1\u9009\u4E2D\u6587\u5B57\uFF09"));
|
|
6762
8030
|
var del = el("button", "dsh-sel-historydel", "\u2715");
|
|
6763
8031
|
del.type = "button";
|
|
6764
8032
|
del.title = "\u5220\u9664\u8FD9\u6761\u8BB0\u5F55";
|
|
@@ -6769,7 +8037,8 @@ function createHistoryController(env) {
|
|
|
6769
8037
|
deleteHistoryEntry(item, row);
|
|
6770
8038
|
});
|
|
6771
8039
|
row.appendChild(del);
|
|
6772
|
-
var
|
|
8040
|
+
var metaLabel = isShot ? "\u622A\u56FE" : shortLabel(item.label);
|
|
8041
|
+
var meta = (metaLabel ? metaLabel + " \xB7 " : "") + (item.turns > 0 ? item.turns + " \u8F6E \xB7 " : "") + formatWhen(item.at);
|
|
6773
8042
|
var metaNode = el("div", "dsh-sel-historymeta");
|
|
6774
8043
|
metaNode.appendChild(el("span", null, meta));
|
|
6775
8044
|
if (item.pinned) metaNode.appendChild(el("span", "dsh-sel-historypin", "\u5DF2\u5347\u683C"));
|
|
@@ -7139,13 +8408,35 @@ function createHistoryController(env) {
|
|
|
7139
8408
|
}
|
|
7140
8409
|
return strip;
|
|
7141
8410
|
}
|
|
8411
|
+
function bubbleGallery(anchor) {
|
|
8412
|
+
var log = env.chatLog;
|
|
8413
|
+
var links = log && typeof log.querySelectorAll === "function" ? log.querySelectorAll(".dsh-sel-bqimgwrap") : [];
|
|
8414
|
+
var images = [];
|
|
8415
|
+
var index = 0;
|
|
8416
|
+
for (var i = 0; i < links.length; i += 1) {
|
|
8417
|
+
var wrap = links[i];
|
|
8418
|
+
var thumb = wrap.querySelector ? wrap.querySelector("img") : null;
|
|
8419
|
+
var src = String(wrap.getAttribute("href") || thumb && thumb.getAttribute("src") || "");
|
|
8420
|
+
if (!src) continue;
|
|
8421
|
+
if (wrap === anchor) index = images.length;
|
|
8422
|
+
images.push({ src, name: thumb && thumb.alt || "\u56FE\u7247" });
|
|
8423
|
+
}
|
|
8424
|
+
if (images.length === 0 && anchor) images.push({ src: anchor.href, name: "\u56FE\u7247" });
|
|
8425
|
+
return { images, index };
|
|
8426
|
+
}
|
|
7142
8427
|
function attachmentImage(ref) {
|
|
7143
8428
|
var link = document.createElement("a");
|
|
7144
8429
|
link.className = "dsh-sel-bqimgwrap";
|
|
7145
8430
|
link.href = attachmentUrl("image", ref);
|
|
7146
8431
|
link.target = "_blank";
|
|
7147
8432
|
link.rel = "noreferrer noopener";
|
|
7148
|
-
link.title = "\u70B9\u51FB\
|
|
8433
|
+
link.title = "\u70B9\u51FB\u9884\u89C8" + (ref.name ? "\uFF1A" + ref.name : "");
|
|
8434
|
+
listen(link, "click", function(event) {
|
|
8435
|
+
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
8436
|
+
if (typeof env.previewImage !== "function") return;
|
|
8437
|
+
event.preventDefault();
|
|
8438
|
+
env.previewImage(bubbleGallery(link));
|
|
8439
|
+
});
|
|
7149
8440
|
var img = document.createElement("img");
|
|
7150
8441
|
img.className = "dsh-sel-bqimg";
|
|
7151
8442
|
img.src = link.href;
|
|
@@ -7327,7 +8618,7 @@ function createConversationController(env) {
|
|
|
7327
8618
|
}
|
|
7328
8619
|
return false;
|
|
7329
8620
|
}
|
|
7330
|
-
function ask(question) {
|
|
8621
|
+
async function ask(question) {
|
|
7331
8622
|
var generating = env.state.asking || env.state.phase === "loading" || env.state.phase === "streaming";
|
|
7332
8623
|
if (generating) {
|
|
7333
8624
|
env.setStatus("\u8FD8\u5728\u751F\u6210\uFF0C\u8BF7\u7A0D\u5019\u2026\uFF08\u751F\u6210\u5B8C\u5C31\u80FD\u8FFD\u95EE\uFF09");
|
|
@@ -7337,6 +8628,17 @@ function createConversationController(env) {
|
|
|
7337
8628
|
env.cancelVoice({ note: "\u5DF2\u505C\u6B62\u5F55\u97F3\uFF08\u8FD9\u6761\u5148\u53D1\u51FA\u53BB\uFF09" });
|
|
7338
8629
|
}
|
|
7339
8630
|
var asked = String(question || "").trim();
|
|
8631
|
+
if (env.attachEnsureUploaded && env.state.uploadingAttachments) return;
|
|
8632
|
+
if (env.attachEnsureUploaded) {
|
|
8633
|
+
env.state.uploadingAttachments = true;
|
|
8634
|
+
env.setStatus("\u6B63\u5728\u4E0A\u4F20\u9644\u4EF6\u2026");
|
|
8635
|
+
try {
|
|
8636
|
+
await env.attachEnsureUploaded();
|
|
8637
|
+
} finally {
|
|
8638
|
+
env.state.uploadingAttachments = false;
|
|
8639
|
+
}
|
|
8640
|
+
if (env.state.asking || env.state.phase === "loading" || env.state.phase === "streaming") return;
|
|
8641
|
+
}
|
|
7340
8642
|
var quotes = env.state.quotes.slice();
|
|
7341
8643
|
var attachments = env.attachmentsPayload();
|
|
7342
8644
|
if (!asked && quotes.length === 0 && attachments.length === 0 || !env.state.payload) return;
|
|
@@ -7375,6 +8677,7 @@ function createConversationController(env) {
|
|
|
7375
8677
|
function sendAsk(asked, quotes, attachments, attachmentsNote) {
|
|
7376
8678
|
var payload = env.state.payload;
|
|
7377
8679
|
if (!payload) return;
|
|
8680
|
+
var firstShot = payload.kind === "shot" && env.state.turns.length === 0;
|
|
7378
8681
|
var withAttachments = attachments || [];
|
|
7379
8682
|
var sent = env.composeQuestion(quotes, asked);
|
|
7380
8683
|
if (attachmentsNote) sent = attachmentsNote + "\n" + sent;
|
|
@@ -7438,7 +8741,7 @@ function createConversationController(env) {
|
|
|
7438
8741
|
answerTurn.asking = false;
|
|
7439
8742
|
answerTurn.streaming = false;
|
|
7440
8743
|
clearWaiting(env.chatLog);
|
|
7441
|
-
env.saveHistory();
|
|
8744
|
+
if (env.state.turns === requestTurns && env.state.cacheKey === requestKey) env.saveHistory();
|
|
7442
8745
|
env.syncPanelStage();
|
|
7443
8746
|
refreshAskState();
|
|
7444
8747
|
env.state.askRequest = null;
|
|
@@ -7459,6 +8762,12 @@ function createConversationController(env) {
|
|
|
7459
8762
|
text: payload.text,
|
|
7460
8763
|
context: payload.context,
|
|
7461
8764
|
label: payload.label,
|
|
8765
|
+
// 截图首轮:host 靠它走独立的 SHOT_PROMPT(不是"选中文字"模板),
|
|
8766
|
+
// 并据此描述图里到底是整窗、全屏、留白还是仅选区。
|
|
8767
|
+
...firstShot ? {
|
|
8768
|
+
kind: "shot",
|
|
8769
|
+
...env.state.shotScope ? { shotScope: env.state.shotScope, shotFrame: env.state.shotFrame !== false } : {}
|
|
8770
|
+
} : {},
|
|
7462
8771
|
sessionId: env.panelOrCurrentSessionId(),
|
|
7463
8772
|
question: sent,
|
|
7464
8773
|
history,
|
|
@@ -7695,6 +9004,8 @@ function createConversationController(env) {
|
|
|
7695
9004
|
}
|
|
7696
9005
|
}
|
|
7697
9006
|
function runRequest(payload, cacheKey, options) {
|
|
9007
|
+
var requestTurns = env.state.turns;
|
|
9008
|
+
var requestKey = cacheKey;
|
|
7698
9009
|
env.state.cacheState = "miss";
|
|
7699
9010
|
if (env.state.request) env.state.request.abort();
|
|
7700
9011
|
var controller = new AbortController();
|
|
@@ -7763,7 +9074,7 @@ function createConversationController(env) {
|
|
|
7763
9074
|
env.state.request = null;
|
|
7764
9075
|
clearWaiting();
|
|
7765
9076
|
if (cacheKey) env.cache.set(cacheKey, { parts: { ...env.state.parts }, turns: env.state.turns.slice() });
|
|
7766
|
-
env.saveHistory();
|
|
9077
|
+
if (env.state.turns === requestTurns && env.state.cacheKey === requestKey) env.saveHistory();
|
|
7767
9078
|
env.paint();
|
|
7768
9079
|
}
|
|
7769
9080
|
var dirty = false;
|
|
@@ -9925,6 +11236,18 @@ function apply(ctx) {
|
|
|
9925
11236
|
}
|
|
9926
11237
|
});
|
|
9927
11238
|
var voiceCardController = createVoiceCardController({
|
|
11239
|
+
get shotRefAsync() {
|
|
11240
|
+
return shotController.takeRefAsync;
|
|
11241
|
+
},
|
|
11242
|
+
get shotFailed() {
|
|
11243
|
+
return shotController.failed;
|
|
11244
|
+
},
|
|
11245
|
+
get shotNotice() {
|
|
11246
|
+
return shotController.notice;
|
|
11247
|
+
},
|
|
11248
|
+
get clearShotMarkers() {
|
|
11249
|
+
return shotController.clearMarkers;
|
|
11250
|
+
},
|
|
9928
11251
|
get layer() {
|
|
9929
11252
|
return layer;
|
|
9930
11253
|
},
|
|
@@ -10044,6 +11367,11 @@ function apply(ctx) {
|
|
|
10044
11367
|
return later;
|
|
10045
11368
|
}
|
|
10046
11369
|
});
|
|
11370
|
+
var lightboxController = createLightboxController({
|
|
11371
|
+
get layer() {
|
|
11372
|
+
return layer;
|
|
11373
|
+
}
|
|
11374
|
+
});
|
|
10047
11375
|
var panelController = createPanelController({
|
|
10048
11376
|
get timerScope() {
|
|
10049
11377
|
return timerScope;
|
|
@@ -10168,6 +11496,9 @@ function apply(ctx) {
|
|
|
10168
11496
|
get closeSettings() {
|
|
10169
11497
|
return settingsController.closeSettings;
|
|
10170
11498
|
},
|
|
11499
|
+
get closeLightbox() {
|
|
11500
|
+
return lightboxController.close;
|
|
11501
|
+
},
|
|
10171
11502
|
get hideHistoryList() {
|
|
10172
11503
|
return historyController.hideHistoryList;
|
|
10173
11504
|
},
|
|
@@ -10352,12 +11683,18 @@ function apply(ctx) {
|
|
|
10352
11683
|
set panelSessionId(value) {
|
|
10353
11684
|
panelSessionId = value;
|
|
10354
11685
|
},
|
|
11686
|
+
get applyShotTitle() {
|
|
11687
|
+
return panelController.applyShotTitle;
|
|
11688
|
+
},
|
|
10355
11689
|
get sentTextOf() {
|
|
10356
11690
|
return quotesController.sentTextOf;
|
|
10357
11691
|
},
|
|
10358
11692
|
get panel() {
|
|
10359
11693
|
return panel;
|
|
10360
11694
|
},
|
|
11695
|
+
get previewImage() {
|
|
11696
|
+
return lightboxController.open;
|
|
11697
|
+
},
|
|
10361
11698
|
get state() {
|
|
10362
11699
|
return state;
|
|
10363
11700
|
},
|
|
@@ -10382,6 +11719,9 @@ function apply(ctx) {
|
|
|
10382
11719
|
get paint() {
|
|
10383
11720
|
return panelController.paint;
|
|
10384
11721
|
},
|
|
11722
|
+
get paintPill() {
|
|
11723
|
+
return pillController.paintPill;
|
|
11724
|
+
},
|
|
10385
11725
|
get timerScope() {
|
|
10386
11726
|
return timerScope;
|
|
10387
11727
|
},
|
|
@@ -10483,6 +11823,9 @@ function apply(ctx) {
|
|
|
10483
11823
|
get composeQuestion() {
|
|
10484
11824
|
return quotesController.composeQuestion;
|
|
10485
11825
|
},
|
|
11826
|
+
get attachEnsureUploaded() {
|
|
11827
|
+
return attachController.ensureUploaded;
|
|
11828
|
+
},
|
|
10486
11829
|
get attachmentsPayload() {
|
|
10487
11830
|
return attachController.payload;
|
|
10488
11831
|
},
|
|
@@ -10617,6 +11960,9 @@ function apply(ctx) {
|
|
|
10617
11960
|
get attachInput() {
|
|
10618
11961
|
return attachInput;
|
|
10619
11962
|
},
|
|
11963
|
+
get previewImage() {
|
|
11964
|
+
return lightboxController.open;
|
|
11965
|
+
},
|
|
10620
11966
|
get setStatus() {
|
|
10621
11967
|
return panelController.setStatus;
|
|
10622
11968
|
},
|
|
@@ -10624,6 +11970,48 @@ function apply(ctx) {
|
|
|
10624
11970
|
return conversationController.refreshAskState;
|
|
10625
11971
|
}
|
|
10626
11972
|
});
|
|
11973
|
+
var shotController = createShotController({
|
|
11974
|
+
get document() {
|
|
11975
|
+
return document;
|
|
11976
|
+
},
|
|
11977
|
+
get window() {
|
|
11978
|
+
return typeof window !== "undefined" ? window : null;
|
|
11979
|
+
},
|
|
11980
|
+
get layer() {
|
|
11981
|
+
return layer;
|
|
11982
|
+
},
|
|
11983
|
+
get state() {
|
|
11984
|
+
return state;
|
|
11985
|
+
},
|
|
11986
|
+
get panelOpen() {
|
|
11987
|
+
return panelOpen;
|
|
11988
|
+
},
|
|
11989
|
+
get addFiles() {
|
|
11990
|
+
return attachController.addFiles;
|
|
11991
|
+
},
|
|
11992
|
+
get focusAskBox() {
|
|
11993
|
+
return panelController.focusAskBox;
|
|
11994
|
+
},
|
|
11995
|
+
get openVCard() {
|
|
11996
|
+
return voiceCardController.openVCard;
|
|
11997
|
+
},
|
|
11998
|
+
get vcardOpen() {
|
|
11999
|
+
return function() {
|
|
12000
|
+
return voiceCardController.vcardState.open;
|
|
12001
|
+
};
|
|
12002
|
+
},
|
|
12003
|
+
get vcardElement() {
|
|
12004
|
+
return function() {
|
|
12005
|
+
return voiceCardController.vcard;
|
|
12006
|
+
};
|
|
12007
|
+
},
|
|
12008
|
+
get closeVCard() {
|
|
12009
|
+
return voiceCardController.closeVCard;
|
|
12010
|
+
},
|
|
12011
|
+
get ensureLayer() {
|
|
12012
|
+
return ensureLayer;
|
|
12013
|
+
}
|
|
12014
|
+
});
|
|
10627
12015
|
var modelsController = createModelsController({
|
|
10628
12016
|
get state() {
|
|
10629
12017
|
return state;
|
|
@@ -10744,6 +12132,10 @@ function apply(ctx) {
|
|
|
10744
12132
|
else layer.removeAttribute("data-full-view");
|
|
10745
12133
|
}
|
|
10746
12134
|
}
|
|
12135
|
+
function ensureLayer() {
|
|
12136
|
+
if (typeof document === "undefined" || !document.body) return;
|
|
12137
|
+
if (layer.parentNode !== document.body) document.body.appendChild(layer);
|
|
12138
|
+
}
|
|
10747
12139
|
function mountFloatingLayer(node) {
|
|
10748
12140
|
if (node) {
|
|
10749
12141
|
if (layer.parentNode !== document.body) document.body.appendChild(layer);
|
|
@@ -11239,6 +12631,15 @@ function apply(ctx) {
|
|
|
11239
12631
|
panelController.savePanelSize();
|
|
11240
12632
|
});
|
|
11241
12633
|
window.__dshSelectionExplain = {
|
|
12634
|
+
/** 截图提问的状态(调试/自动化)。 */
|
|
12635
|
+
shot: function() {
|
|
12636
|
+
return shotController.state();
|
|
12637
|
+
},
|
|
12638
|
+
/** 手动起一次截图(等同按 ⌘⇧X)。 */
|
|
12639
|
+
shotStart: function() {
|
|
12640
|
+
shotController.capture();
|
|
12641
|
+
return true;
|
|
12642
|
+
},
|
|
11242
12643
|
/**
|
|
11243
12644
|
* 语音卡(选中文字 + 输入法正在语音输入 → 就地追问)的状态。
|
|
11244
12645
|
* 给自动化验证用:能不能弹、有没有焦点、几何对不对,全在这里读。
|
|
@@ -11752,6 +13153,95 @@ function apply(ctx) {
|
|
|
11752
13153
|
});
|
|
11753
13154
|
}
|
|
11754
13155
|
};
|
|
13156
|
+
ctx.effect(function() {
|
|
13157
|
+
if (typeof window === "undefined" || !window.addEventListener) return function() {
|
|
13158
|
+
};
|
|
13159
|
+
function send(payload) {
|
|
13160
|
+
try {
|
|
13161
|
+
fetch("/selection-explain/api/shot/hello", {
|
|
13162
|
+
method: "POST",
|
|
13163
|
+
body: JSON.stringify(Object.assign({ at: Date.now() }, payload))
|
|
13164
|
+
}).catch(function() {
|
|
13165
|
+
});
|
|
13166
|
+
} catch (error) {
|
|
13167
|
+
}
|
|
13168
|
+
}
|
|
13169
|
+
function onError(event) {
|
|
13170
|
+
var err = event && (event.error || event);
|
|
13171
|
+
send({
|
|
13172
|
+
stage: "window-error",
|
|
13173
|
+
message: String(err && err.message || event.message || ""),
|
|
13174
|
+
stack: String(err && err.stack || "").slice(0, 2e3),
|
|
13175
|
+
source: String(event && event.filename || ""),
|
|
13176
|
+
line: event && event.lineno
|
|
13177
|
+
});
|
|
13178
|
+
}
|
|
13179
|
+
function onRejection(event) {
|
|
13180
|
+
var reason = event && event.reason;
|
|
13181
|
+
send({
|
|
13182
|
+
stage: "unhandled-rejection",
|
|
13183
|
+
message: String(reason && reason.message || reason || ""),
|
|
13184
|
+
stack: String(reason && reason.stack || "").slice(0, 2e3)
|
|
13185
|
+
});
|
|
13186
|
+
}
|
|
13187
|
+
window.addEventListener("error", onError);
|
|
13188
|
+
window.addEventListener("unhandledrejection", onRejection);
|
|
13189
|
+
return function() {
|
|
13190
|
+
window.removeEventListener("error", onError);
|
|
13191
|
+
window.removeEventListener("unhandledrejection", onRejection);
|
|
13192
|
+
};
|
|
13193
|
+
}, "selection-explain: error reporter");
|
|
13194
|
+
var shortcutsService = ctx.shortcuts;
|
|
13195
|
+
var shortcutReport = { loaded: true, shortcuts: !!shortcutsService, registered: false, error: "" };
|
|
13196
|
+
function reportShortcut() {
|
|
13197
|
+
try {
|
|
13198
|
+
fetch("/selection-explain/api/shot/hello", { method: "POST", body: JSON.stringify(shortcutReport) }).catch(function() {
|
|
13199
|
+
});
|
|
13200
|
+
} catch (error) {
|
|
13201
|
+
}
|
|
13202
|
+
}
|
|
13203
|
+
reportShortcut();
|
|
13204
|
+
if (shortcutsService && typeof shortcutsService.register === "function") {
|
|
13205
|
+
ctx.effect(function() {
|
|
13206
|
+
try {
|
|
13207
|
+
var off = shortcutsService.register({
|
|
13208
|
+
id: "selection-explain.screenshot",
|
|
13209
|
+
label: function() {
|
|
13210
|
+
return "\u622A\u56FE\u63D0\u95EE";
|
|
13211
|
+
},
|
|
13212
|
+
// ⚠️ 必须给数组:设置页渲染列表时会 `[...row.aliases]`,
|
|
13213
|
+
// 留成 undefined 会直接抛 TypeError,整个快捷键弹窗打不开(实测踩到)。
|
|
13214
|
+
aliases: [],
|
|
13215
|
+
// 键位是**物理键 + 修饰键**的结构(不是 "Mod+Shift+X" 这种字符串):
|
|
13216
|
+
// code 必须是 KeyA-Z / Digit0-9 / F1-24 这类物理 code,modifiers 是 meta/control/alt/shift/primary
|
|
13217
|
+
// (primary 在 macOS 上是 ⌘、Windows 上是 Ctrl)。
|
|
13218
|
+
defaults: {
|
|
13219
|
+
"desktop:macos": { code: "KeyX", modifiers: ["meta", "shift"] },
|
|
13220
|
+
"web:macos": { code: "KeyX", modifiers: ["meta", "shift"] },
|
|
13221
|
+
"desktop:windows": { code: "KeyX", modifiers: ["primary", "shift"] },
|
|
13222
|
+
"web:windows": { code: "KeyX", modifiers: ["primary", "shift"] }
|
|
13223
|
+
},
|
|
13224
|
+
resolve: function() {
|
|
13225
|
+
return {
|
|
13226
|
+
status: "handled",
|
|
13227
|
+
run: function() {
|
|
13228
|
+
shotController.capture();
|
|
13229
|
+
}
|
|
13230
|
+
};
|
|
13231
|
+
}
|
|
13232
|
+
});
|
|
13233
|
+
shortcutReport.registered = true;
|
|
13234
|
+
reportShortcut();
|
|
13235
|
+
return off;
|
|
13236
|
+
} catch (error) {
|
|
13237
|
+
shortcutReport.error = String(error && error.message || error);
|
|
13238
|
+
reportShortcut();
|
|
13239
|
+
return function() {
|
|
13240
|
+
};
|
|
13241
|
+
}
|
|
13242
|
+
}, "selection-explain: screenshot shortcut");
|
|
13243
|
+
shotController.start();
|
|
13244
|
+
}
|
|
11755
13245
|
ctx.effect(function() {
|
|
11756
13246
|
if (typeof console !== "undefined" && console.log) {
|
|
11757
13247
|
console.log("[dsh-selection-explain] \u5212\u8BCD\u89E3\u8BFB\u5DF2\u5C31\u7EEA\uFF08\u9009\u4E2D\u6587\u5B57\u8BD5\u8BD5\uFF09");
|
|
@@ -11761,7 +13251,10 @@ function apply(ctx) {
|
|
|
11761
13251
|
timerScope.dispose();
|
|
11762
13252
|
cache.dispose();
|
|
11763
13253
|
voiceCardController.closeVCard(false);
|
|
13254
|
+
lightboxController.dispose();
|
|
11764
13255
|
attachController.clear();
|
|
13256
|
+
shotController.stop();
|
|
13257
|
+
shotController.clearMarkers();
|
|
11765
13258
|
panelController.releaseRenderedContent();
|
|
11766
13259
|
if (state.askRequest) state.askRequest.abort();
|
|
11767
13260
|
offPopEnd();
|
|
@@ -11876,5 +13369,5 @@ function apply(ctx) {
|
|
|
11876
13369
|
};
|
|
11877
13370
|
}, "@yfwu2020/dsh-selection-explain: \u5212\u8BCD\u89E3\u8BFB UI");
|
|
11878
13371
|
}
|
|
11879
|
-
var inject = ["slots"];
|
|
13372
|
+
var inject = ["slots", "shortcuts"];
|
|
11880
13373
|
return module.exports;}})
|