@yfwu2020/dsh-selection-explain 0.10.6 → 0.11.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/lib/client.js CHANGED
@@ -117,6 +117,10 @@ var PILL_POS = "/selection-explain/api/pill";
117
117
  var PANEL_SIZE = "/selection-explain/api/panel";
118
118
  var HISTORY = "/selection-explain/api/history";
119
119
  var MODELS = "/selection-explain/api/models";
120
+ var ATTACH = "/selection-explain/api/attach";
121
+ var ATTACHMENT = "/selection-explain/api/attachment";
122
+ var ATTACH_MAX_ITEMS = 10;
123
+ var ATTACH_MAX_ITEM_BYTES = 20 * 1024 * 1024;
120
124
  var SETTINGS = "/selection-explain/api/settings";
121
125
  var ROUTE = "/selection-explain/api/route";
122
126
  var SPEECH_API = "/selection-explain/api/speech";
@@ -441,12 +445,97 @@ var CSS = [
441
445
  "background:transparent;color:var(--dsw-alias-label-secondary,inherit);opacity:.5;font:inherit;font-size:11px;",
442
446
  "line-height:1;display:inline-flex;align-items:center;justify-content:center}",
443
447
  ".dsh-sel-quotechip-x:hover{opacity:1;background:var(--dsw-alias-interactive-bg-hover,rgba(140,140,140,.16))}",
448
+ // ── 附件(composer 第一行,和引用同栏)──
449
+ // 引用是竖着排的卡片(有出处 + 一段文字),附件是横着一排的缩略图 —— 两种形状,
450
+ // 所以是**两条独立的条**、共用 ask 的第一行区域,而不是把附件塞进引用卡片里。
451
+ // 横向溢出就滚动(附件最多 10 件,小窗宽度装不下是常态),滚动条藏起来不占高度。
452
+ ".dsh-sel-attach{display:none;flex-direction:row;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;",
453
+ "scrollbar-width:none;padding:1px 0 0}",
454
+ '.dsh-sel-attach[data-show="1"]{display:flex}',
455
+ ".dsh-sel-attach::-webkit-scrollbar{width:0;height:0;display:none}",
456
+ // 40px 见方:比主会话的 64px 小一圈(小窗的 composer 整体是收着的:按钮 26、输入 13px)
457
+ ".dsh-sel-thumb{position:relative;flex:0 0 auto;width:40px;height:40px;border-radius:9px;overflow:hidden;",
458
+ "border:1px solid var(--dsw-alias-border-l3,rgba(140,140,140,.35));background:var(--dsw-alias-interactive-bg-hover,rgba(140,140,140,.14));",
459
+ "display:grid;place-items:center;color:var(--dsw-alias-label-secondary,inherit)}",
460
+ ".dsh-sel-thumb img{width:100%;height:100%;object-fit:cover;display:block}",
461
+ ".dsh-sel-thumb svg{width:15px;height:15px;opacity:.55}",
462
+ '.dsh-sel-thumb[data-state="error"]{border-color:color-mix(in srgb,var(--sel-bad,#e5534b) 70%,transparent)}',
463
+ '.dsh-sel-thumb[data-state="error"] svg{opacity:.9;color:var(--sel-bad,#e5534b)}',
464
+ // 转圈 + 不定态进度条:fetch 拿不到上传进度,所以**不假装知道百分比**
465
+ // (主会话在"首次报告字节之前"用的也是不定态)
466
+ ".dsh-sel-attachspin{position:absolute;inset:0;display:grid;place-items:center;background:rgba(0,0,0,.42)}",
467
+ ".dsh-sel-attachspin svg{width:16px;height:16px;opacity:1;color:var(--sel-a1,#0d9488);animation:dsh-sel-spin .9s linear infinite}",
468
+ ".dsh-sel-attachbar{position:absolute;left:0;right:0;bottom:0;height:3px;background:rgba(140,140,140,.35);overflow:hidden}",
469
+ ".dsh-sel-attachbar i{display:block;height:100%;width:38%;background:var(--sel-a1,#0d9488);animation:dsh-sel-indeterminate 1.1s ease-in-out infinite}",
470
+ "@keyframes dsh-sel-indeterminate{0%{transform:translateX(-100%)}100%{transform:translateX(280%)}}",
471
+ // 移除:缩略图/文件卡右上角。**常驻可见**(触摸设备上没有 hover,藏起来就删不掉了)
472
+ // 兜底色必须是**中性灰 + 白字**:主题变量缺席时(非 DSH 宿主 / 测试页)
473
+ // 原来的 `bg-layer-2 → #3a3f46` 配 `label-primary → CanvasText`(浅色方案下是黑)
474
+ // 会画成"深底黑叉",实拍就是一坨看不出是什么的黑点。
475
+ ".dsh-sel-attachx{position:absolute;top:-5px;right:-5px;width:15px;height:15px;padding:0;border-radius:50%;",
476
+ "border:1px solid var(--dsw-alias-bg-layer-1,Canvas);background:var(--dsw-alias-bg-layer-2,rgba(120,120,120,.92));",
477
+ "color:var(--dsw-alias-label-primary,#fff);font:inherit;font-size:9px;line-height:1;cursor:pointer;",
478
+ "display:grid;place-items:center;opacity:.85}",
479
+ ".dsh-sel-attachx:hover{opacity:1}",
480
+ // 文件卡:和缩略图同高(40px),名字一行、扩展名 + 字节数一行小字
481
+ ".dsh-sel-fcard{position:relative;display:inline-flex;align-items:center;gap:7px;flex:0 0 auto;height:40px;",
482
+ "padding:0 7px 0 6px;border-radius:9px;border:1px solid var(--dsw-alias-border-l3,rgba(140,140,140,.35));",
483
+ "background:var(--dsw-alias-interactive-bg-hover,rgba(140,140,140,.1));font-size:11.5px;line-height:1.35}",
484
+ '.dsh-sel-fcard[data-state="error"]{border-color:color-mix(in srgb,var(--sel-bad,#e5534b) 70%,transparent)}',
485
+ ".dsh-sel-fico{flex:0 0 auto;width:22px;height:22px;border-radius:6px;display:grid;place-items:center;",
486
+ "background:linear-gradient(160deg,#3f6ee0,#7aa2ff);color:#fff}",
487
+ ".dsh-sel-fico svg{width:13px;height:13px;opacity:1}",
488
+ ".dsh-sel-fname{max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
489
+ ".dsh-sel-fmeta{font-size:10.5px;opacity:.6;letter-spacing:.3px;white-space:nowrap}",
490
+ ".dsh-sel-fretry{flex:0 0 auto;height:16px;padding:0 6px;border-radius:8px;cursor:pointer;font:inherit;font-size:10.5px;",
491
+ "line-height:14px;color:var(--sel-bad,#e5534b);border:1px solid color-mix(in srgb,var(--sel-bad,#e5534b) 50%,transparent);",
492
+ "background:transparent}",
493
+ ".dsh-sel-fretry:hover{background:color-mix(in srgb,var(--sel-bad,#e5534b) 12%,transparent)}",
494
+ // 附件出错时的说明行(贴在附件条下面,一行小字)
495
+ ".dsh-sel-attachnote{display:none;margin:0;padding:0 2px;font-size:11px;line-height:1.6;color:var(--sel-warn-fg,#b45309)}",
496
+ '.dsh-sel-attachnote[data-show="1"]{display:block}',
497
+ // + 入口:圆形浅底(和主会话 composer 的 + 同款),放在工具行**最左**
498
+ ".dsh-sel-add{background:var(--dsw-alias-interactive-bg-hover,rgba(140,140,140,.14));color:var(--dsw-alias-label-primary,CanvasText)}",
499
+ ".dsh-sel-add:hover{background:var(--dsw-alias-interactive-bg-active,rgba(140,140,140,.22))}",
500
+ '.dsh-sel-add[data-busy="1"]{opacity:.6}',
501
+ // 拖放遮罩:铺满面板(面板本身 overflow:hidden,所以内缩 6px 的圆角块正好贴住内容区)
502
+ ".dsh-sel-drop{position:absolute;inset:6px;z-index:9;display:none;flex-direction:column;align-items:center;",
503
+ "justify-content:center;gap:9px;border:2px dashed var(--sel-a1,#0d9488);border-radius:13px;",
504
+ "background:color-mix(in srgb,var(--dsw-alias-bg-layer-1,Canvas) 88%,transparent);pointer-events:none}",
505
+ '.dsh-sel-panel[data-dropping="1"] .dsh-sel-drop{display:flex}',
506
+ ".dsh-sel-drop-art{width:52px;height:52px;border-radius:14px;display:grid;place-items:center;",
507
+ "background:var(--sel-a1-soft);color:var(--sel-a1,#0d9488)}",
508
+ ".dsh-sel-drop-art svg{width:25px;height:25px}",
509
+ ".dsh-sel-drop-title{font-size:14px;font-weight:600}",
510
+ ".dsh-sel-drop-hint{font-size:11.5px;opacity:.7}",
444
511
  // 气泡里的引用块(用户消息):引用在上、问题在下 —— 和发出去给模型的那份顺序一致
445
512
  ".dsh-sel-bq{display:flex;flex-direction:column;gap:3px;margin-bottom:6px}",
446
513
  ".dsh-sel-bqitem{padding:3px 8px;border-radius:8px;border-left:2px solid color-mix(in srgb,var(--sel-a1,#0d9488) 45%,transparent);",
447
514
  "background:var(--dsw-alias-interactive-bg-hover,rgba(140,140,140,.14));font-size:11.5px;line-height:1.55;",
448
515
  "opacity:.82;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
449
516
  ".dsh-sel-bqitem b{font-weight:600;opacity:.75;margin-right:5px}",
517
+ // 气泡里的附件:图片是缩略图(点开原图),文件是一张卡(点一下下载)。
518
+ // 和输入框上面那条 rail 是**同一个东西的两个阶段**(发送前 / 发送后),
519
+ // 所以尺寸、圆角、配色都对齐:缩略图 40px、文件卡同高。
520
+ ".dsh-sel-bqattach{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:6px}",
521
+ // ⚠️ flex:0 0 auto 会让这个盒子的宽度由**图片自己**撑出来,于是图片上的 max-width:100%
522
+ // 变成"相对于自己"(循环引用)→ 一张 810px 宽的图会照原样渲染并溢出气泡(实测量到 810x180)。
523
+ // 必须允许它收缩(flex:0 1 auto + min-width:0),百分比才有一个确定的参照。
524
+ ".dsh-sel-bqimgwrap{flex:0 1 auto;min-width:0;max-width:100%;display:block;border-radius:9px;overflow:hidden;line-height:0;",
525
+ "border:1px solid var(--dsw-alias-border-l3,rgba(140,140,140,.35))}",
526
+ // 上限 180px 高、最宽占满气泡:小窗本来就窄,缩略图不能把气泡撑爆
527
+ ".dsh-sel-bqimg{display:block;max-width:100%;max-height:180px;width:auto;height:auto;border-radius:8px;cursor:zoom-in}",
528
+ ".dsh-sel-bqimgfail{display:inline-block;padding:5px 9px;border-radius:8px;font-size:11.5px;opacity:.7;",
529
+ "background:var(--dsw-alias-interactive-bg-hover,rgba(140,140,140,.14))}",
530
+ ".dsh-sel-bqfile{display:inline-flex;align-items:center;gap:7px;max-width:100%;padding:5px 9px;border-radius:9px;",
531
+ "text-decoration:none;color:inherit;cursor:pointer;font-size:11.5px;line-height:1.35;",
532
+ "border:1px solid var(--dsw-alias-border-l3,rgba(140,140,140,.35));",
533
+ "background:var(--dsw-alias-interactive-bg-hover,rgba(140,140,140,.1))}",
534
+ ".dsh-sel-bqfile:hover{border-color:color-mix(in srgb,var(--sel-a1,#0d9488) 45%,transparent)}",
535
+ ".dsh-sel-bqfileext{flex:0 0 auto;font-size:9.5px;font-weight:700;letter-spacing:.4px;padding:0 5px;",
536
+ "border-radius:5px;line-height:15px;background:var(--sel-fill,#0f766e);color:var(--sel-fill-fg,#fff)}",
537
+ ".dsh-sel-bqfilename{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:190px}",
538
+ ".dsh-sel-bqfilemeta{flex:0 0 auto;font-size:10.5px;opacity:.6}",
450
539
  // 「引用整条」:贴在助手气泡末尾,**平时不出现**(正文优先),鼠标移到这条消息上才浮出来。
451
540
  // 键盘 Tab 也能到(:focus-visible 同样点亮),触屏上另有浮标那条路。
452
541
  ".dsh-sel-bubquote{margin-top:7px;display:inline-flex;align-items:center;gap:4px;padding:2px 9px;",
@@ -1128,6 +1217,21 @@ function micIcon() {
1128
1217
  svg.appendChild(stem);
1129
1218
  return svg;
1130
1219
  }
1220
+ function spinnerIcon() {
1221
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1222
+ svg.setAttribute("viewBox", "0 0 16 16");
1223
+ svg.setAttribute("width", "14");
1224
+ svg.setAttribute("height", "14");
1225
+ svg.setAttribute("fill", "none");
1226
+ svg.setAttribute("stroke", "currentColor");
1227
+ svg.setAttribute("stroke-width", "1.7");
1228
+ svg.setAttribute("stroke-linecap", "round");
1229
+ svg.setAttribute("aria-hidden", "true");
1230
+ var arc = document.createElementNS("http://www.w3.org/2000/svg", "path");
1231
+ arc.setAttribute("d", "M8 1.8a6.2 6.2 0 1 1-4.4 1.8");
1232
+ svg.appendChild(arc);
1233
+ return svg;
1234
+ }
1131
1235
  function sendIcon() {
1132
1236
  var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1133
1237
  svg.setAttribute("viewBox", "0 0 16 16");
@@ -1178,6 +1282,34 @@ function quoteIcon() {
1178
1282
  }
1179
1283
  return svg;
1180
1284
  }
1285
+ function plusIcon() {
1286
+ var svg = iconCanvas(14);
1287
+ iconPath(svg, "M8 3.4v9.2M3.4 8h9.2");
1288
+ return svg;
1289
+ }
1290
+ function docIcon() {
1291
+ var svg = iconCanvas(14);
1292
+ iconPath(svg, "M4.3 1.9h4.4l3 3v9.2H4.3z");
1293
+ iconPath(svg, "M8.5 1.9v3.2h3.2");
1294
+ return svg;
1295
+ }
1296
+ function imageIcon() {
1297
+ var svg = iconCanvas(14);
1298
+ iconPath(svg, "M2.6 3.2h10.8v9.6H2.6z");
1299
+ var dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
1300
+ dot.setAttribute("cx", "5.7");
1301
+ dot.setAttribute("cy", "6.3");
1302
+ dot.setAttribute("r", "0.9");
1303
+ svg.appendChild(dot);
1304
+ iconPath(svg, "M2.6 10.9l3.1-2.9 2.5 2.3 2.3-2.1 2.9 2.7");
1305
+ return svg;
1306
+ }
1307
+ function retryIcon() {
1308
+ var svg = iconCanvas(13);
1309
+ iconPath(svg, "M12.6 8a4.6 4.6 0 1 1-1.6-3.5");
1310
+ iconPath(svg, "M12.9 2.6v3.1h-3.1");
1311
+ return svg;
1312
+ }
1181
1313
 
1182
1314
  // src/client/index.js
1183
1315
  var import_react = __toESM(require("react"), 1);
@@ -3935,7 +4067,12 @@ function createQuotesController(env) {
3935
4067
  function exportTurns() {
3936
4068
  var out = [];
3937
4069
  for (var i = 0; i < env.state.turns.length; i += 1) {
3938
- out.push({ role: env.state.turns[i].role, text: sentTextOf(env.state.turns[i]) });
4070
+ var turn = env.state.turns[i];
4071
+ out.push({
4072
+ role: turn.role,
4073
+ text: sentTextOf(turn),
4074
+ ...turn.attachments && turn.attachments.length > 0 ? { attachments: turn.attachments } : {}
4075
+ });
3939
4076
  }
3940
4077
  return out;
3941
4078
  }
@@ -3954,6 +4091,383 @@ function createQuotesController(env) {
3954
4091
  };
3955
4092
  }
3956
4093
 
4094
+ // src/client/attach.js
4095
+ function encodeAttachmentRef(ref) {
4096
+ var json = JSON.stringify(ref || {});
4097
+ if (typeof TextEncoder === "function") {
4098
+ var bytes = new TextEncoder().encode(json);
4099
+ var binary = "";
4100
+ for (var i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
4101
+ json = binary;
4102
+ }
4103
+ return btoa(json).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
4104
+ }
4105
+ function attachmentUrl(kind, ref, download) {
4106
+ return ATTACHMENT + "?kind=" + (kind === "file" ? "file" : "image") + "&ref=" + encodeAttachmentRef(ref) + (download ? "&download=1" : "");
4107
+ }
4108
+ function formatBytes(bytes) {
4109
+ var value = typeof bytes === "number" && isFinite(bytes) ? bytes : 0;
4110
+ if (value < 1024) return value + " B";
4111
+ if (value < 1024 * 1024) return (value / 1024).toFixed(1) + " KB";
4112
+ return (value / 1024 / 1024).toFixed(1) + " MB";
4113
+ }
4114
+ function extensionOf(name) {
4115
+ var match = /\.([A-Za-z0-9]+)$/.exec(String(name || ""));
4116
+ return match ? match[1].toUpperCase() : "FILE";
4117
+ }
4118
+ function fileToBase64(file) {
4119
+ return new Promise(function(resolve, reject) {
4120
+ var reader = new FileReader();
4121
+ reader.onload = function() {
4122
+ var result = String(reader.result || "");
4123
+ var comma = result.indexOf(",");
4124
+ resolve(comma >= 0 ? result.slice(comma + 1) : "");
4125
+ };
4126
+ reader.onerror = function() {
4127
+ reject(reader.error || new Error("\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25"));
4128
+ };
4129
+ reader.readAsDataURL(file);
4130
+ });
4131
+ }
4132
+ function looksLikeDirectory(file) {
4133
+ return file && file.size === 0 && !file.type && !/\.[A-Za-z0-9]+$/.test(String(file.name || ""));
4134
+ }
4135
+ function createAttachController(env) {
4136
+ var seq = 0;
4137
+ function items() {
4138
+ return env.state.attachments;
4139
+ }
4140
+ function note(text) {
4141
+ env.state.attachNote = text || "";
4142
+ renderNote();
4143
+ }
4144
+ function renderNote() {
4145
+ if (!env.attachNote) return;
4146
+ env.attachNote.textContent = env.state.attachNote || "";
4147
+ env.attachNote.setAttribute("data-show", env.state.attachNote ? "1" : "0");
4148
+ }
4149
+ function busy() {
4150
+ var list = items();
4151
+ for (var i = 0; i < list.length; i += 1) if (list[i].status === "uploading") return true;
4152
+ return false;
4153
+ }
4154
+ function payload() {
4155
+ var out = [];
4156
+ var list = items();
4157
+ for (var i = 0; i < list.length; i += 1) {
4158
+ if (list[i].status === "done" && list[i].ref) out.push({ kind: list[i].kind, ref: list[i].ref });
4159
+ }
4160
+ return out;
4161
+ }
4162
+ function describe(explicit) {
4163
+ var list = explicit || items();
4164
+ var names = [];
4165
+ for (var i = 0; i < list.length; i += 1) {
4166
+ if (list[i].status !== "done" || !list[i].ref) continue;
4167
+ names.push(list[i].name + (list[i].kind === "image" ? "\uFF08\u56FE\u7247\uFF09" : "\uFF08\u6587\u4EF6\uFF09"));
4168
+ }
4169
+ if (names.length === 0) return "";
4170
+ return "\u3010\u672C\u6761\u6D88\u606F\u5E26\u4E86\u9644\u4EF6\u3011" + names.join("\u3001");
4171
+ }
4172
+ function clear() {
4173
+ var list = items();
4174
+ for (var i = 0; i < list.length; i += 1) revoke(list[i]);
4175
+ env.state.attachments = [];
4176
+ env.state.attachNote = "";
4177
+ render();
4178
+ }
4179
+ function revoke(item) {
4180
+ if (item && item.url && typeof URL !== "undefined" && URL.revokeObjectURL) {
4181
+ try {
4182
+ URL.revokeObjectURL(item.url);
4183
+ } catch (error) {
4184
+ }
4185
+ }
4186
+ }
4187
+ function remove(id) {
4188
+ var list = items();
4189
+ for (var i = 0; i < list.length; i += 1) {
4190
+ if (list[i].id === id) {
4191
+ revoke(list[i]);
4192
+ list.splice(i, 1);
4193
+ break;
4194
+ }
4195
+ }
4196
+ env.state.attachNote = "";
4197
+ render();
4198
+ env.refreshAskState();
4199
+ }
4200
+ function addFiles(files) {
4201
+ var list = items();
4202
+ var incoming = [];
4203
+ for (var i = 0; i < files.length; i += 1) incoming.push(files[i]);
4204
+ if (incoming.length === 0) return;
4205
+ var rejected = [];
4206
+ for (var j = 0; j < incoming.length; j += 1) {
4207
+ var file = incoming[j];
4208
+ if (looksLikeDirectory(file)) {
4209
+ rejected.push("\u300C" + (file.name || "\u672A\u547D\u540D") + "\u300D\u662F\u6587\u4EF6\u5939\uFF0C\u4E0D\u652F\u6301");
4210
+ continue;
4211
+ }
4212
+ if (list.length >= ATTACH_MAX_ITEMS) {
4213
+ rejected.push("\u4E00\u6B21\u6700\u591A\u5E26 " + ATTACH_MAX_ITEMS + " \u4EF6\u9644\u4EF6");
4214
+ break;
4215
+ }
4216
+ if (file.size > ATTACH_MAX_ITEM_BYTES) {
4217
+ rejected.push("\u300C" + file.name + "\u300D\u8D85\u8FC7\u5355\u4EF6\u4E0A\u9650 " + formatBytes(ATTACH_MAX_ITEM_BYTES));
4218
+ continue;
4219
+ }
4220
+ list.push(createItem(file));
4221
+ }
4222
+ note(rejected.join("\uFF1B"));
4223
+ render();
4224
+ env.refreshAskState();
4225
+ var pending = list.filter(function(item) {
4226
+ return item.status === "uploading" && !item.started;
4227
+ });
4228
+ var chain = Promise.resolve();
4229
+ pending.forEach(function(item) {
4230
+ chain = chain.then(function() {
4231
+ return upload(item);
4232
+ });
4233
+ });
4234
+ }
4235
+ function createItem(file) {
4236
+ var isImage = /^image\//.test(String(file.type || ""));
4237
+ var item = {
4238
+ id: ++seq,
4239
+ kind: isImage ? "image" : "file",
4240
+ name: file.name || (isImage ? "\u7C98\u8D34\u7684\u56FE\u7247" : "\u672A\u547D\u540D\u6587\u4EF6"),
4241
+ size: typeof file.size === "number" ? file.size : 0,
4242
+ mediaType: file.type || "application/octet-stream",
4243
+ file,
4244
+ // 缩略图先用本地 object URL 显示(不必等上传完),失败时回落到图标
4245
+ url: isImage && typeof URL !== "undefined" && URL.createObjectURL ? URL.createObjectURL(file) : "",
4246
+ status: "uploading",
4247
+ started: false,
4248
+ ref: null,
4249
+ error: ""
4250
+ };
4251
+ return item;
4252
+ }
4253
+ function upload(item) {
4254
+ if (item.started) return Promise.resolve();
4255
+ item.started = true;
4256
+ item.status = "uploading";
4257
+ item.error = "";
4258
+ render();
4259
+ return fileToBase64(item.file).then(function(data) {
4260
+ return fetch(ATTACH, {
4261
+ method: "POST",
4262
+ headers: { "content-type": "application/json" },
4263
+ body: JSON.stringify({
4264
+ items: [{ kind: item.kind, name: item.name, mediaType: item.mediaType, data }]
4265
+ })
4266
+ });
4267
+ }).then(function(response) {
4268
+ return response.json().catch(function() {
4269
+ return {};
4270
+ }).then(function(body) {
4271
+ if (!response.ok || !body || body.ok !== true) {
4272
+ throw new Error(body && body.error || "HTTP " + response.status);
4273
+ }
4274
+ var first = body.items && body.items[0];
4275
+ if (!first || !first.ref) throw new Error("\u4E0A\u4F20\u540E\u6CA1\u6709\u62FF\u5230\u9644\u4EF6\u5F15\u7528");
4276
+ item.ref = first.ref;
4277
+ item.status = "done";
4278
+ item.file = null;
4279
+ if (first.ref.name) item.name = String(first.ref.name);
4280
+ if (typeof first.ref.bytes === "number") item.size = first.ref.bytes;
4281
+ if (env.state.attachNote.indexOf(item.name) >= 0) note("");
4282
+ });
4283
+ }).catch(function(error) {
4284
+ item.status = "error";
4285
+ item.error = String(error && error.message || error);
4286
+ note("\u300C" + item.name + "\u300D" + item.error);
4287
+ }).then(function() {
4288
+ render();
4289
+ env.refreshAskState();
4290
+ });
4291
+ }
4292
+ function retry(id) {
4293
+ var list = items();
4294
+ for (var i = 0; i < list.length; i += 1) {
4295
+ if (list[i].id === id && list[i].status === "error") {
4296
+ if (!list[i].file) {
4297
+ note("\u8FD9\u4E00\u4EF6\u5DF2\u7ECF\u6CA1\u6CD5\u91CD\u8BD5\u4E86\uFF0C\u8BF7\u91CD\u65B0\u6DFB\u52A0");
4298
+ return;
4299
+ }
4300
+ list[i].started = false;
4301
+ upload(list[i]);
4302
+ return;
4303
+ }
4304
+ }
4305
+ }
4306
+ function render() {
4307
+ var box = env.attachBox;
4308
+ if (!box) return;
4309
+ var list = items();
4310
+ box.textContent = "";
4311
+ box.setAttribute("data-show", list.length > 0 ? "1" : "0");
4312
+ for (var i = 0; i < list.length; i += 1) {
4313
+ box.appendChild(list[i].kind === "image" ? thumbNode(list[i]) : fileNode(list[i]));
4314
+ }
4315
+ renderNote();
4316
+ }
4317
+ function removeButton(item) {
4318
+ var button = el("button", "dsh-sel-attachx", "\u2715");
4319
+ button.type = "button";
4320
+ button.title = "\u79FB\u9664\u300C" + item.name + "\u300D";
4321
+ button.setAttribute("aria-label", "\u79FB\u9664\u9644\u4EF6 " + item.name);
4322
+ listen(button, "click", function(event) {
4323
+ event.stopPropagation();
4324
+ remove(item.id);
4325
+ });
4326
+ return button;
4327
+ }
4328
+ function thumbNode(item) {
4329
+ var node = el("span", "dsh-sel-thumb");
4330
+ node.setAttribute("data-state", item.status);
4331
+ node.title = item.name;
4332
+ if (item.url) {
4333
+ var img = document.createElement("img");
4334
+ img.src = item.url;
4335
+ img.alt = item.name;
4336
+ node.appendChild(img);
4337
+ } else {
4338
+ node.appendChild(imageIcon());
4339
+ }
4340
+ if (item.status === "uploading") {
4341
+ var spin = el("span", "dsh-sel-attachspin");
4342
+ spin.appendChild(spinnerIcon());
4343
+ node.appendChild(spin);
4344
+ var bar = el("span", "dsh-sel-attachbar");
4345
+ bar.appendChild(el("i"));
4346
+ node.appendChild(bar);
4347
+ } else if (item.status === "error") {
4348
+ var retryBox = el("span", "dsh-sel-attachspin");
4349
+ retryBox.style.background = "transparent";
4350
+ retryBox.title = "\u4E0A\u4F20\u5931\u8D25\uFF0C\u70B9\u51FB\u91CD\u8BD5";
4351
+ var retryButton = el("button", "dsh-sel-attachx", "");
4352
+ retryButton.type = "button";
4353
+ retryButton.style.position = "static";
4354
+ retryButton.style.width = "20px";
4355
+ retryButton.style.height = "20px";
4356
+ retryButton.setAttribute("aria-label", "\u91CD\u8BD5\u4E0A\u4F20 " + item.name);
4357
+ retryButton.appendChild(retryIcon());
4358
+ listen(retryButton, "click", function(event) {
4359
+ event.stopPropagation();
4360
+ retry(item.id);
4361
+ });
4362
+ retryBox.appendChild(retryButton);
4363
+ node.appendChild(retryBox);
4364
+ }
4365
+ node.appendChild(removeButton(item));
4366
+ return node;
4367
+ }
4368
+ function fileNode(item) {
4369
+ var node = el("span", "dsh-sel-fcard");
4370
+ node.setAttribute("data-state", item.status);
4371
+ var icon = el("span", "dsh-sel-fico");
4372
+ icon.appendChild(docIcon());
4373
+ node.appendChild(icon);
4374
+ node.appendChild(el("span", "dsh-sel-fname", item.name));
4375
+ var meta = extensionOf(item.name) + " \xB7 " + formatBytes(item.size);
4376
+ if (item.status === "uploading") meta = "\u6B63\u5728\u4E0A\u4F20\u2026";
4377
+ node.appendChild(el("span", "dsh-sel-fmeta", meta));
4378
+ if (item.status === "error") {
4379
+ var retryButton = el("button", "dsh-sel-fretry", "\u91CD\u8BD5");
4380
+ retryButton.type = "button";
4381
+ listen(retryButton, "click", function(event) {
4382
+ event.stopPropagation();
4383
+ retry(item.id);
4384
+ });
4385
+ node.appendChild(retryButton);
4386
+ }
4387
+ node.appendChild(removeButton(item));
4388
+ return node;
4389
+ }
4390
+ function pick() {
4391
+ if (!env.attachInput) return;
4392
+ env.attachInput.value = "";
4393
+ env.attachInput.click();
4394
+ }
4395
+ function handlePaste(event) {
4396
+ var files = event.clipboardData && event.clipboardData.files;
4397
+ if (!files || files.length === 0) return;
4398
+ event.preventDefault();
4399
+ addFiles(files);
4400
+ env.setStatus("\u5DF2\u4ECE\u526A\u8D34\u677F\u6DFB\u52A0 " + files.length + " \u4EF6\u9644\u4EF6");
4401
+ }
4402
+ function isFileDrag(event) {
4403
+ var transfer = event.dataTransfer;
4404
+ if (!transfer) return false;
4405
+ var types = transfer.types;
4406
+ if (!types) return false;
4407
+ for (var i = 0; i < types.length; i += 1) {
4408
+ if (types[i] === "Files") return true;
4409
+ }
4410
+ return false;
4411
+ }
4412
+ function wireDrop() {
4413
+ var depth = 0;
4414
+ var panel = env.panel;
4415
+ if (!panel) return;
4416
+ function show() {
4417
+ panel.setAttribute("data-dropping", "1");
4418
+ }
4419
+ function hide() {
4420
+ depth = 0;
4421
+ panel.removeAttribute("data-dropping");
4422
+ }
4423
+ listen(panel, "dragenter", function(event) {
4424
+ if (!isFileDrag(event)) return;
4425
+ event.preventDefault();
4426
+ event.stopPropagation();
4427
+ depth += 1;
4428
+ show();
4429
+ });
4430
+ listen(panel, "dragover", function(event) {
4431
+ if (!isFileDrag(event)) return;
4432
+ event.preventDefault();
4433
+ event.stopPropagation();
4434
+ if (event.dataTransfer.dropEffect !== void 0) event.dataTransfer.dropEffect = "copy";
4435
+ });
4436
+ listen(panel, "dragleave", function(event) {
4437
+ if (!isFileDrag(event)) return;
4438
+ event.stopPropagation();
4439
+ depth -= 1;
4440
+ if (depth <= 0) hide();
4441
+ });
4442
+ listen(panel, "drop", function(event) {
4443
+ if (!isFileDrag(event)) return;
4444
+ event.preventDefault();
4445
+ event.stopPropagation();
4446
+ hide();
4447
+ var files = event.dataTransfer.files;
4448
+ if (files && files.length > 0) {
4449
+ addFiles(files);
4450
+ env.setStatus("\u5DF2\u6DFB\u52A0 " + files.length + " \u4EF6\u9644\u4EF6");
4451
+ }
4452
+ });
4453
+ }
4454
+ return {
4455
+ items,
4456
+ addFiles,
4457
+ remove,
4458
+ clear,
4459
+ retry,
4460
+ render,
4461
+ busy,
4462
+ payload,
4463
+ describe,
4464
+ note,
4465
+ pick,
4466
+ handlePaste,
4467
+ wireDrop
4468
+ };
4469
+ }
4470
+
3957
4471
  // src/client/bridge.js
3958
4472
  function bridgeBody() {
3959
4473
  if (window.__dshSelBridge) return;
@@ -4515,6 +5029,7 @@ function createPanelController(env) {
4515
5029
  env.state.turns = [];
4516
5030
  env.state.toolDigest = "";
4517
5031
  env.clearQuotes();
5032
+ env.clearAttachments();
4518
5033
  env.state.parts = cached && cached.parts || { translation: "", detail: "" };
4519
5034
  env.state.stage = "";
4520
5035
  env.state.raw = "";
@@ -6339,7 +6854,9 @@ function createHistoryController(env) {
6339
6854
  if (turn.role === "user") {
6340
6855
  var usig = (turn.text || "") + "|" + (turn.quotes || []).map(function(item) {
6341
6856
  return item.text;
6342
- }).join("\0");
6857
+ }).join("\0") + "|" + (turn.attachments || []).map(function(item) {
6858
+ return (item.kind || "") + ":" + String(item.ref && item.ref.attachmentId || "");
6859
+ }).join(",");
6343
6860
  if (node.__sig !== usig) {
6344
6861
  node.__sig = usig;
6345
6862
  fillUserBubble(node, turn);
@@ -6393,6 +6910,10 @@ function createHistoryController(env) {
6393
6910
  function fillUserBubble(node, turn) {
6394
6911
  node.textContent = "";
6395
6912
  var quotes = turn.quotes || [];
6913
+ var attachments = turn.attachments || [];
6914
+ if (attachments.length > 0) {
6915
+ node.appendChild(attachmentStrip(attachments));
6916
+ }
6396
6917
  if (quotes.length > 0) {
6397
6918
  var box = el("div", "dsh-sel-bq");
6398
6919
  for (var i = 0; i < quotes.length; i += 1) {
@@ -6404,7 +6925,57 @@ function createHistoryController(env) {
6404
6925
  }
6405
6926
  node.appendChild(box);
6406
6927
  }
6407
- if (turn.text) node.appendChild(el("div", null, turn.text));
6928
+ var display = displayTextOf(turn);
6929
+ if (display) node.appendChild(el("div", null, display));
6930
+ }
6931
+ function attachmentStrip(attachments) {
6932
+ var strip = el("div", "dsh-sel-bqattach");
6933
+ for (var i = 0; i < attachments.length; i += 1) {
6934
+ var item = attachments[i] || {};
6935
+ var ref = item.ref || {};
6936
+ if (item.kind === "image") {
6937
+ strip.appendChild(attachmentImage(ref));
6938
+ } else {
6939
+ strip.appendChild(attachmentFile(ref));
6940
+ }
6941
+ }
6942
+ return strip;
6943
+ }
6944
+ function attachmentImage(ref) {
6945
+ var link = document.createElement("a");
6946
+ link.className = "dsh-sel-bqimgwrap";
6947
+ link.href = attachmentUrl("image", ref);
6948
+ link.target = "_blank";
6949
+ link.rel = "noreferrer noopener";
6950
+ link.title = "\u70B9\u51FB\u67E5\u770B\u539F\u56FE" + (ref.name ? "\uFF1A" + ref.name : "");
6951
+ var img = document.createElement("img");
6952
+ img.className = "dsh-sel-bqimg";
6953
+ img.src = link.href;
6954
+ img.alt = ref.name || "\u56FE\u7247";
6955
+ img.loading = "lazy";
6956
+ img.addEventListener("error", function() {
6957
+ var box = el("span", "dsh-sel-bqimgfail", "\u56FE\u7247\u5DF2\u4E0D\u5728\u9644\u4EF6\u4ED3\u91CC");
6958
+ if (link.parentNode) link.parentNode.replaceChild(box, link);
6959
+ });
6960
+ link.appendChild(img);
6961
+ return link;
6962
+ }
6963
+ function attachmentFile(ref) {
6964
+ var link = document.createElement("a");
6965
+ link.className = "dsh-sel-bqfile";
6966
+ link.href = attachmentUrl("file", ref, true);
6967
+ link.rel = "noreferrer noopener";
6968
+ link.title = "\u4E0B\u8F7D\uFF1A" + (ref.name || "\u6587\u4EF6");
6969
+ link.appendChild(el("span", "dsh-sel-bqfileext", ref.name ? extensionOf(ref.name) : "FILE"));
6970
+ link.appendChild(el("span", "dsh-sel-bqfilename", ref.name || "\u6587\u4EF6"));
6971
+ if (typeof ref.bytes === "number") link.appendChild(el("span", "dsh-sel-bqfilemeta", formatBytes(ref.bytes)));
6972
+ return link;
6973
+ }
6974
+ function displayTextOf(turn) {
6975
+ var text = turn.text || "";
6976
+ if (!text) return "";
6977
+ if (!turn.attachments || turn.attachments.length === 0) return text;
6978
+ return text.replace(/^【本条消息带了附件】[^\n]*\n?/, "");
6408
6979
  }
6409
6980
  function bubbleFor(turn) {
6410
6981
  var bubble = el("div", "dsh-sel-bubble " + (turn.role === "user" ? "dsh-sel-bubble-user" : "dsh-sel-bubble-bot"));
@@ -6539,7 +7110,10 @@ function createConversationController(env) {
6539
7110
  env.askSend.setAttribute("aria-label", generating ? "\u505C\u6B62" : "\u53D1\u9001");
6540
7111
  env.askSend.title = generating ? "\u505C\u6B62\u751F\u6210\uFF08\u70B9\u4E00\u4E0B\u6253\u65AD\u8FD9\u6B21\u8F93\u51FA\uFF09" : "\u53D1\u9001\uFF08Enter\uFF09";
6541
7112
  }
6542
- env.askSend.disabled = generating ? false : !String(env.askBox.value || "").trim() && env.state.quotes.length === 0;
7113
+ var waitingAttachments = env.attachmentsBusy();
7114
+ env.askSend.disabled = generating ? false : waitingAttachments || !String(env.askBox.value || "").trim() && env.state.quotes.length === 0 && env.state.attachments.length === 0;
7115
+ var placeholder = env.state.attachments.length > 0 ? "\u5C31\u8FD9\u6BB5\u6587\u5B57\u7EE7\u7EED\u8FFD\u95EE\u2026\uFF08Enter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C\uFF09" : "\u5C31\u8FD9\u6BB5\u6587\u5B57\u7EE7\u7EED\u8FFD\u95EE\u2026\uFF08\u53EF\u62D6\u5165\u56FE\u7247 / \u6587\u4EF6\uFF0C\u6216 \u2318V \u7C98\u8D34\uFF09";
7116
+ if (env.askBox.placeholder !== placeholder) env.askBox.placeholder = placeholder;
6543
7117
  }
6544
7118
  function stopCurrent() {
6545
7119
  if (env.state.askRequest) {
@@ -6572,7 +7146,12 @@ function createConversationController(env) {
6572
7146
  }
6573
7147
  var asked = String(question || "").trim();
6574
7148
  var quotes = env.state.quotes.slice();
6575
- if (!asked && quotes.length === 0 || !env.state.payload) return;
7149
+ var attachments = env.attachmentsPayload();
7150
+ if (!asked && quotes.length === 0 && attachments.length === 0 || !env.state.payload) return;
7151
+ if (env.attachmentsBusy()) {
7152
+ env.setStatus("\u9644\u4EF6\u8FD8\u5728\u4E0A\u4F20\uFF0C\u7A0D\u7B49\u4E00\u4E0B\u2026");
7153
+ return;
7154
+ }
6576
7155
  if (env.state.resolvingQuotes) {
6577
7156
  env.setStatus("\u6B63\u5728\u53D6\u5F15\u7528\u7684\u4E0A\u4E0B\u6587\u2026");
6578
7157
  return;
@@ -6586,24 +7165,31 @@ function createConversationController(env) {
6586
7165
  env.renderQuotes();
6587
7166
  refreshAskState();
6588
7167
  }
7168
+ var attachmentsNote = env.attachmentsDescribe();
7169
+ if (attachments.length > 0) {
7170
+ env.clearAttachments();
7171
+ refreshAskState();
7172
+ }
6589
7173
  if (quotes.length === 0) {
6590
- sendAsk(asked, quotes);
7174
+ sendAsk(asked, quotes, attachments, attachmentsNote);
6591
7175
  return;
6592
7176
  }
6593
7177
  env.state.resolvingQuotes = true;
6594
7178
  env.resolveQuoteContexts(quotes).then(function(resolved) {
6595
7179
  env.state.resolvingQuotes = false;
6596
- sendAsk(asked, resolved);
7180
+ sendAsk(asked, resolved, attachments, attachmentsNote);
6597
7181
  });
6598
7182
  }
6599
- function sendAsk(asked, quotes) {
7183
+ function sendAsk(asked, quotes, attachments, attachmentsNote) {
6600
7184
  var payload = env.state.payload;
6601
7185
  if (!payload) return;
7186
+ var withAttachments = attachments || [];
6602
7187
  var sent = env.composeQuestion(quotes, asked);
7188
+ if (attachmentsNote) sent = attachmentsNote + "\n" + sent;
6603
7189
  if (env.state.turns.length === 0 && (env.state.raw.trim() || env.state.parts.translation || env.state.parts.detail)) {
6604
7190
  env.state.turns.push({ role: "assistant", text: env.seedHistoryFromExplanation(), seed: true, hidden: true });
6605
7191
  }
6606
- env.state.turns.push({ role: "user", text: asked || QUOTE_ONLY_QUESTION, quotes, sent });
7192
+ env.state.turns.push({ role: "user", text: asked || QUOTE_ONLY_QUESTION, quotes, sent, attachments: withAttachments });
6607
7193
  var answerTurn = {
6608
7194
  role: "assistant",
6609
7195
  text: "",
@@ -6636,7 +7222,11 @@ function createConversationController(env) {
6636
7222
  var historyTurns = env.state.turns.slice(0, env.state.turns.length - 2);
6637
7223
  var history = historyTurns.map(function(turn, index) {
6638
7224
  var keepRaw = env.FOLD_HISTORY_KEEP_LATEST && index === historyTurns.length - 1;
6639
- return { role: turn.role, text: env.historyTextOf(turn, keepRaw) };
7225
+ return {
7226
+ role: turn.role,
7227
+ text: env.historyTextOf(turn, keepRaw),
7228
+ ...turn.attachments && turn.attachments.length > 0 ? { attachments: turn.attachments } : {}
7229
+ };
6640
7230
  });
6641
7231
  env.state.stopped = false;
6642
7232
  var askController = new AbortController();
@@ -6683,6 +7273,8 @@ function createConversationController(env) {
6683
7273
  sessionId: env.panelOrCurrentSessionId(),
6684
7274
  question: sent,
6685
7275
  history,
7276
+ // 附件只带**引用**(图片/文件都已经在 host 的附件服务里),body 不会因为一张截图胖几 MB
7277
+ ...withAttachments.length > 0 ? { attachments: withAttachments } : {},
6686
7278
  // 网页输出要跟着当前深浅走:深色 Harness 里给一张白底页面会很刺眼
6687
7279
  ...env.state.webAnswer ? { webAnswer: true, theme: env.currentSurfaceTheme() } : {},
6688
7280
  ...env.state.modelChoice ? { provider: env.state.modelChoice.provider, model: env.state.modelChoice.model } : {},
@@ -8565,6 +9157,14 @@ function apply(ctx) {
8565
9157
  * 只属于**下一条**提问(发送即清空);关掉面板不清 —— 回来接着写还看得见。
8566
9158
  */
8567
9159
  quotes: [],
9160
+ /**
9161
+ * 待发送的附件:[{ id, kind:'image'|'file', name, size, mediaType, file, url, status, ref, error }]。
9162
+ * 和引用同一条规矩 —— 只属于**下一条**追问,发送即清空;
9163
+ * 区别是上传状态挂在每一项上(逐件上传、逐件重试,见 attach.js)。
9164
+ */
9165
+ attachments: [],
9166
+ /** 附件条下面那行提示("文件夹不支持""一次最多 10 件"之类),空串就不显示。 */
9167
+ attachNote: "",
8568
9168
  /** 最近一次"可以引用"的选区:[{ text, label, rect, source }](浮标据此显示)。 */
8569
9169
  quoteSelection: null,
8570
9170
  /** 正在取引用的会话上下文(这期间再按 Enter 不能空发一条)。 */
@@ -8685,14 +9285,25 @@ function apply(ctx) {
8685
9285
  var askRow = el("div", "dsh-sel-ask");
8686
9286
  var quotesBox = el("div", "dsh-sel-quotes");
8687
9287
  askRow.appendChild(quotesBox);
9288
+ var attachBox = el("div", "dsh-sel-attach");
9289
+ askRow.appendChild(attachBox);
9290
+ var attachNote = el("p", "dsh-sel-attachnote");
9291
+ askRow.appendChild(attachNote);
9292
+ var attachInput = el("input");
9293
+ attachInput.type = "file";
9294
+ attachInput.multiple = true;
9295
+ attachInput.style.display = "none";
9296
+ attachInput.setAttribute("aria-hidden", "true");
9297
+ attachInput.tabIndex = -1;
9298
+ askRow.appendChild(attachInput);
8688
9299
  var askBox = el("textarea", "dsh-sel-askbox");
8689
9300
  askBox.rows = 1;
8690
9301
  askBox.placeholder = "\u5C31\u8FD9\u6BB5\u6587\u5B57\u7EE7\u7EED\u8FFD\u95EE\u2026\uFF08Enter \u53D1\u9001\uFF0CShift+Enter \u6362\u884C\uFF09";
8691
9302
  var webMode = el("span", "dsh-sel-pref");
8692
- webMode.appendChild(el("span", "dsh-sel-preftext", "\u8F93\u51FA\u504F\u597D"));
9303
+ webMode.title = "\u8F93\u51FA\u504F\u597D\uFF1AMarkdown\uFF08\u9ED8\u8BA4\u6587\u5B57\u56DE\u7B54\uFF09/ \u7F51\u9875\uFF08\u590D\u6742\u95EE\u9898\u751F\u6210 HTML \u9875\u9762\uFF09";
8693
9304
  var prefSeg = el("span", "dsh-sel-seg");
8694
9305
  prefSeg.setAttribute("role", "radiogroup");
8695
- prefSeg.setAttribute("aria-label", "\u8F93\u51FA\u504F\u597D");
9306
+ prefSeg.setAttribute("aria-label", "\u8F93\u51FA\u504F\u597D\uFF1AMarkdown \u6216\u7F51\u9875");
8696
9307
  var prefPill = el("span", "dsh-sel-segpill");
8697
9308
  var cellMd = el("button", "dsh-sel-segcell");
8698
9309
  cellMd.type = "button";
@@ -8773,7 +9384,13 @@ function apply(ctx) {
8773
9384
  captureRow.appendChild(voiceActionSlot);
8774
9385
  captureRow.appendChild(voiceStop);
8775
9386
  var askTools = el("div", "dsh-sel-asktools");
9387
+ var addButton = el("button", "dsh-sel-iconbtn dsh-sel-add");
9388
+ addButton.type = "button";
9389
+ addButton.setAttribute("aria-label", "\u6DFB\u52A0\u56FE\u7247\u6216\u6587\u4EF6");
9390
+ addButton.title = "\u6DFB\u52A0\u56FE\u7247\u6216\u6587\u4EF6\uFF08\u4E5F\u53EF\u4EE5\u76F4\u63A5\u7C98\u8D34\u6216\u62D6\u8FDB\u6765\uFF09";
9391
+ addButton.appendChild(plusIcon());
8776
9392
  var askSpacer = el("span", "dsh-sel-askspace");
9393
+ askTools.appendChild(addButton);
8777
9394
  askTools.appendChild(webMode);
8778
9395
  askTools.appendChild(askSpacer);
8779
9396
  askTools.appendChild(modelPill);
@@ -8837,6 +9454,14 @@ function apply(ctx) {
8837
9454
  panel.appendChild(head);
8838
9455
  panel.appendChild(body);
8839
9456
  panel.appendChild(askRow);
9457
+ var dropMask = el("div", "dsh-sel-drop");
9458
+ dropMask.setAttribute("aria-hidden", "true");
9459
+ var dropArt = el("div", "dsh-sel-drop-art");
9460
+ dropArt.appendChild(plusIcon());
9461
+ dropMask.appendChild(dropArt);
9462
+ dropMask.appendChild(el("div", "dsh-sel-drop-title", "\u677E\u5F00\u5373\u6DFB\u52A0\u4E3A\u8FD9\u6761\u8FFD\u95EE\u7684\u9644\u4EF6"));
9463
+ dropMask.appendChild(el("div", "dsh-sel-drop-hint", "\u56FE\u7247\u76F4\u63A5\u7ED9\u6A21\u578B\u770B \xB7 \u6587\u4EF6\u5B58\u6210\u53EA\u8BFB\u8DEF\u5F84\u6309\u9700\u8BFB\u53D6"));
9464
+ panel.appendChild(dropMask);
8840
9465
  layer.appendChild(panel);
8841
9466
  var settingsController = createSettingsController({
8842
9467
  get state() {
@@ -9125,6 +9750,9 @@ function apply(ctx) {
9125
9750
  get clearQuotes() {
9126
9751
  return quotesController.clearQuotes;
9127
9752
  },
9753
+ get clearAttachments() {
9754
+ return attachController.clear;
9755
+ },
9128
9756
  get renderTurns() {
9129
9757
  return historyController.renderTurns;
9130
9758
  },
@@ -9518,6 +10146,18 @@ function apply(ctx) {
9518
10146
  get composeQuestion() {
9519
10147
  return quotesController.composeQuestion;
9520
10148
  },
10149
+ get attachmentsPayload() {
10150
+ return attachController.payload;
10151
+ },
10152
+ get attachmentsBusy() {
10153
+ return attachController.busy;
10154
+ },
10155
+ get attachmentsDescribe() {
10156
+ return attachController.describe;
10157
+ },
10158
+ get clearAttachments() {
10159
+ return attachController.clear;
10160
+ },
9521
10161
  get seedHistoryFromExplanation() {
9522
10162
  return historyController.seedHistoryFromExplanation;
9523
10163
  },
@@ -9630,6 +10270,29 @@ function apply(ctx) {
9630
10270
  return panelController.buildPayload;
9631
10271
  }
9632
10272
  });
10273
+ var attachController = createAttachController({
10274
+ get state() {
10275
+ return state;
10276
+ },
10277
+ get panel() {
10278
+ return panel;
10279
+ },
10280
+ get attachBox() {
10281
+ return attachBox;
10282
+ },
10283
+ get attachNote() {
10284
+ return attachNote;
10285
+ },
10286
+ get attachInput() {
10287
+ return attachInput;
10288
+ },
10289
+ get setStatus() {
10290
+ return panelController.setStatus;
10291
+ },
10292
+ get refreshAskState() {
10293
+ return conversationController.refreshAskState;
10294
+ }
10295
+ });
9633
10296
  var modelsController = createModelsController({
9634
10297
  get state() {
9635
10298
  return state;
@@ -10162,7 +10825,7 @@ function apply(ctx) {
10162
10825
  event.preventDefault();
10163
10826
  event.stopPropagation();
10164
10827
  var question = askBox.value;
10165
- if (!question.trim() && state.quotes.length === 0) return;
10828
+ if (!question.trim() && state.quotes.length === 0 && state.attachments.length === 0) return;
10166
10829
  conversationController.ask(question);
10167
10830
  });
10168
10831
  var offAskInput = listen(askBox, "input", function() {
@@ -10175,9 +10838,21 @@ function apply(ctx) {
10175
10838
  return;
10176
10839
  }
10177
10840
  var question = askBox.value;
10178
- if (!question.trim() && state.quotes.length === 0) return;
10841
+ if (!question.trim() && state.quotes.length === 0 && state.attachments.length === 0) return;
10179
10842
  conversationController.ask(question);
10180
10843
  });
10844
+ var offAttachPick = listen(attachInput, "change", function() {
10845
+ attachController.addFiles(attachInput.files || []);
10846
+ attachInput.value = "";
10847
+ });
10848
+ var offAttachButton = listen(addButton, "click", function(event) {
10849
+ event.stopPropagation();
10850
+ attachController.pick();
10851
+ });
10852
+ var offAttachPaste = listen(panel, "paste", function(event) {
10853
+ attachController.handlePaste(event);
10854
+ });
10855
+ attachController.wireDrop();
10181
10856
  var offMic = listen(micButton, "click", function(event) {
10182
10857
  event.stopPropagation();
10183
10858
  voiceController.startVoice();
@@ -10773,6 +11448,9 @@ function apply(ctx) {
10773
11448
  offAskKey();
10774
11449
  offAskInput();
10775
11450
  offAskSend();
11451
+ offAttachPick();
11452
+ offAttachButton();
11453
+ offAttachPaste();
10776
11454
  offMic();
10777
11455
  offVoiceCancel();
10778
11456
  offVoiceStop();