@y9931666/dsh-plugin-crossfire 0.1.0 → 0.1.3
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 +2 -0
- package/lib/client.js +637 -204
- package/lib/index.js +339 -165
- package/lib/{state-CXNvzj_T.js → state-B2anbn4l.js} +2 -2
- package/lib/{state-DzrgVdrB.js → state-DvF_Jqcg.js} +13 -1
- package/package.json +56 -20
- package/release-notes/v0.1.1.md +58 -0
- package/release-notes/v0.1.2.md +44 -0
- package/release-notes/v0.1.3.md +21 -0
- package/lib/types/budget.d.ts +0 -86
- package/lib/types/client/ReviewCard.d.ts +0 -41
- package/lib/types/client/SettingsView.d.ts +0 -34
- package/lib/types/client/brand.d.ts +0 -55
- package/lib/types/client/index.d.ts +0 -21
- package/lib/types/client/locales.d.ts +0 -19
- package/lib/types/client/merge.d.ts +0 -27
- package/lib/types/client/slot-boundary.d.ts +0 -46
- package/lib/types/client/wire.d.ts +0 -192
- package/lib/types/dimensions.d.ts +0 -47
- package/lib/types/engine.d.ts +0 -153
- package/lib/types/etag.d.ts +0 -40
- package/lib/types/expert-events.d.ts +0 -110
- package/lib/types/export-md.d.ts +0 -21
- package/lib/types/harness-compat.d.ts +0 -104
- package/lib/types/index.d.ts +0 -76
- package/lib/types/members.d.ts +0 -76
- package/lib/types/noise.d.ts +0 -29
- package/lib/types/preferences.d.ts +0 -134
- package/lib/types/prompt.d.ts +0 -16
- package/lib/types/redteam.d.ts +0 -115
- package/lib/types/review.d.ts +0 -144
- package/lib/types/rpc.d.ts +0 -176
- package/lib/types/split.d.ts +0 -142
- package/lib/types/state.d.ts +0 -74
- package/lib/types/tools.d.ts +0 -51
- package/lib/types/types.d.ts +0 -287
- package/lib/types/version.d.ts +0 -18
- package/lib/types/web-guard.d.ts +0 -55
package/lib/client.js
CHANGED
|
@@ -76,7 +76,7 @@ async function fetchReviews(session, signal) {
|
|
|
76
76
|
try {
|
|
77
77
|
const query = session === "" ? "" : `?session=${encodeURIComponent(session)}`;
|
|
78
78
|
const response = await fetch(`${STATE_ROUTE}${query}`, {
|
|
79
|
-
cache: "no-
|
|
79
|
+
cache: "no-cache",
|
|
80
80
|
...signal === void 0 ? {} : { signal }
|
|
81
81
|
});
|
|
82
82
|
const body = await response.json();
|
|
@@ -222,9 +222,243 @@ function routeText(provider, model) {
|
|
|
222
222
|
return [provider, model].filter((part) => part !== void 0 && part.trim() !== "").join(" / ");
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/client/defectTitle.ts
|
|
227
|
+
/**
|
|
228
|
+
* 观点标题:一条缺陷卡片的"抬头"。
|
|
229
|
+
*
|
|
230
|
+
* 红队的报告信封要求每条发言以 `观点 N(指向 X,维度Y):正文` 开头,而这个前缀
|
|
231
|
+
* 恰恰就是最有信息量的一行(编号 + 定位 + 维度)。用户反馈:"观点虽然说我可以理解,
|
|
232
|
+
* 把完整内容给写出来了,但我觉得还是要在最开始那里立一个标题,这样目的明确"。
|
|
233
|
+
*
|
|
234
|
+
* ## 为什么单独一个模块
|
|
235
|
+
*
|
|
236
|
+
* 标题按钮是**折叠态唯一可见的内容**,所以这里的两条边界(空括号信封、内嵌换行)
|
|
237
|
+
* 一旦退化,肉眼看到的是"整张卡片塌成一团",而不是一条错误文案。它是纯字符串函数,
|
|
238
|
+
* 因此抽出来直接单测 —— 不需要 jsdom / react 测试环境(本仓库只有 `node:test`)。
|
|
239
|
+
*
|
|
240
|
+
* ## 标题从哪里来(三层,逐层兜底)
|
|
241
|
+
*
|
|
242
|
+
* 1. **压缩信封**:`观点 N(指向 dsh-plugin-crossfire/src/client/ReviewCard.module.css:106-176,维度 交互)`
|
|
243
|
+
* → `观点 N · ReviewCard.module.css:106-176 · 交互`
|
|
244
|
+
* 红队写**完整路径**是对的(审计要能定位到文件),但它不该占满标题 —— 用户实测反馈:
|
|
245
|
+
* "标题过长,导致后面无法显示;且有很多无用信息,比如路径的位置,这个可以放在内容里"。
|
|
246
|
+
* 所以完整路径仍留在缺陷正文(可追溯),标题只留**文件名 + 行号 + 维度**。
|
|
247
|
+
* 2. **原始信封**(压缩不出来时,例如括号里没有 `指向`/`维度` 的旧格式)→ 原样返回前缀。
|
|
248
|
+
* 3. **首句兜底**:未按格式提交时取首句(按换行、再按中英文句末标点切,限长)。
|
|
249
|
+
*
|
|
250
|
+
* **绝不**返回空串 —— 标题位空了比不显示更糟。
|
|
251
|
+
*/
|
|
252
|
+
/** 标题硬上限(字符)。超过即截断并加省略号。 */
|
|
253
|
+
const TITLE_MAX = 80;
|
|
254
|
+
/** 压缩后的单个片段上限(文件名或维度名过长时截断,避免任一片段独吞整行)。 */
|
|
255
|
+
const PART_MAX = 40;
|
|
256
|
+
/**
|
|
257
|
+
* 信封:`观点 N(…)` / `观点N(…)`。
|
|
258
|
+
*
|
|
259
|
+
* 括号内必须**至少有一个既非空白、也非右括号的字符**:`观点 1()`、`观点 1( )`、
|
|
260
|
+
* `观点 1(\u3000)` 是"格式没填",不是"合法信封"。旧写法 `[^))]*` 会把它们当成命中
|
|
261
|
+
* 直接返回,于是标题退化成无信息串、还绕过了首句兜底。
|
|
262
|
+
*
|
|
263
|
+
* 首个字符的字符类是 `[^))\s]` 而不是 `\S`:`\S` 能匹配**右括号本身**,于是
|
|
264
|
+
* `观点 1()真正的首句。` 会靠回溯让 `\S` 吃掉 `)`、再由 `[^))]*[))]` 一路吃到
|
|
265
|
+
* 下一个 `)`(跨过右括号),把一条没按格式提交的发言重新判成合法信封。
|
|
266
|
+
* 两侧的 `\s*` 只作用于**括号内首尾的空白**,正文与冒号之间仍允许空格(`观点 1(指向 y):`)。
|
|
267
|
+
*/
|
|
268
|
+
const ENVELOPE = /^(观点\s*\d+\s*[((]\s*[^))\s]\s*[^))]*[))])\s*[::]?/;
|
|
269
|
+
/**
|
|
270
|
+
* 信封内部:`观点 N(指向 X,维度 Y)` 的三段。
|
|
271
|
+
*
|
|
272
|
+
* `指向` 后取到**逗号或右括号**为止(中英文逗号、顿号都认);`维度` 后取到右括号为止。
|
|
273
|
+
* 用 `[\s\S]` 而不是 `.`:定位串里可能出现换行(红队偶尔换行排版)。
|
|
274
|
+
*/
|
|
275
|
+
const ENVELOPE_PARTS = /^观点\s*(\d+)\s*[((]\s*指向\s*([\s\S]*?)\s*[,,、]\s*维度\s*([\s\S]*?)\s*[))]/;
|
|
276
|
+
/**
|
|
277
|
+
* 从一条缺陷正文里导出标题。见模块注释的三层兜底。
|
|
278
|
+
*
|
|
279
|
+
* @param content 缺陷原文(可以是任意多行文本)。
|
|
280
|
+
* @returns 非空标题,最长 {@link TITLE_MAX} 字符。
|
|
281
|
+
*/
|
|
282
|
+
function defectTitle(content) {
|
|
283
|
+
const text = content.trim();
|
|
284
|
+
const envelope = ENVELOPE.exec(text);
|
|
285
|
+
if (envelope !== null && envelope[1] !== void 0) {
|
|
286
|
+
const raw = envelope[1].trim();
|
|
287
|
+
const compact = compactEnvelope(raw);
|
|
288
|
+
if (compact !== void 0) return take(compact, TITLE_MAX);
|
|
289
|
+
const index = /观点\s*(\d+)/.exec(raw)?.[1];
|
|
290
|
+
const tail = text.slice(envelope[0].length).replace(/^[\s::]+/, "");
|
|
291
|
+
const firstLine$1 = (tail.split(/\r?\n/)[0] ?? tail).trim();
|
|
292
|
+
const firstSentence = (firstLine$1.split(/(?<=[。..;;!!??])/)[0] ?? firstLine$1).trim();
|
|
293
|
+
if (index !== void 0 && firstSentence !== "") return take(`观点 ${index} · ${firstSentence}`, TITLE_MAX);
|
|
294
|
+
if (index !== void 0) {
|
|
295
|
+
const dimension = /维度\s*([^))]*)/.exec(raw)?.[1]?.trim();
|
|
296
|
+
if (dimension !== void 0 && dimension !== "") return take(`观点 ${index} · ${dimension}`, TITLE_MAX);
|
|
297
|
+
return `观点 ${index}`;
|
|
298
|
+
}
|
|
299
|
+
return take(raw, TITLE_MAX);
|
|
300
|
+
}
|
|
301
|
+
const firstLine = text.split(/\r?\n/)[0] ?? text;
|
|
302
|
+
const title = (firstLine.split(/(?<=[。..;;!!??])/)[0] ?? firstLine).trim();
|
|
303
|
+
if (title === "") return take(text, TITLE_MAX);
|
|
304
|
+
return take(title, TITLE_MAX);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* 把信封压缩成 `观点 N · 文件名:行号 · 维度`。
|
|
308
|
+
*
|
|
309
|
+
* @returns 压缩结果;**形状不认识时返回 `undefined`**(调用方退回原始前缀),
|
|
310
|
+
* 而不是硬拼一个残缺标题。
|
|
311
|
+
*/
|
|
312
|
+
function compactEnvelope(envelope) {
|
|
313
|
+
const parts = ENVELOPE_PARTS.exec(envelope);
|
|
314
|
+
if (parts === null) return void 0;
|
|
315
|
+
const index = parts[1] ?? "";
|
|
316
|
+
const pointer = shortPointer(parts[2] ?? "");
|
|
317
|
+
const dimension = (parts[3] ?? "").trim().replace(/\s+/g, " ");
|
|
318
|
+
if (index === "" || pointer === "" || dimension === "") return void 0;
|
|
319
|
+
return `观点 ${index} · ${take(pointer, PART_MAX)} · ${take(dimension, PART_MAX)}`;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* 把"指向"里的定位串压成**文件名 + 行号**:
|
|
323
|
+
*
|
|
324
|
+
* `dsh-plugin-crossfire/src/client/ReviewCard.module.css:106-176` → `ReviewCard.module.css:106-176`
|
|
325
|
+
* `src/a.ts` → `a.ts`
|
|
326
|
+
* `README.md` → `README.md`
|
|
327
|
+
*
|
|
328
|
+
* 只认 `/` 与 `\` 两种分隔符(Windows 路径也要能压)。文件名与行号之间的冒号保留 ——
|
|
329
|
+
* 行号是"跳到哪一行"的关键信息,不能丢。
|
|
330
|
+
*/
|
|
331
|
+
function shortPointer(pointer) {
|
|
332
|
+
const compact = pointer.trim().replace(/\s+/g, " ");
|
|
333
|
+
if (compact === "") return "";
|
|
334
|
+
return (compact.split(/[/\\]/).pop() ?? compact).trim();
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* 截断到 `max` 个字符;被截断时补省略号。
|
|
338
|
+
*
|
|
339
|
+
* 省略号只在**真的截掉了内容**时加:`text` 恰好 `max` 字时不该显示一个假的"还有更多"。
|
|
340
|
+
*/
|
|
341
|
+
function take(text, max) {
|
|
342
|
+
return text.length > max ? `${text.slice(0, max)}…` : text;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/client/session-cache.ts
|
|
347
|
+
/**
|
|
348
|
+
* 会话身份的**浏览器侧缓存**(设置页保存失败的真实修复)。
|
|
349
|
+
*
|
|
350
|
+
* ## 它修的是什么
|
|
351
|
+
*
|
|
352
|
+
* `crossfire/prefs.set` 在宿主侧要求"必须声称一个身份"(`judgeActor(undefined, session)`)。
|
|
353
|
+
* 而宿主的 slot 契约决定了设置页**结构上拿不到** `sessionId` —— 运行时 slot 目录里:
|
|
354
|
+
*
|
|
355
|
+
* conversation.view → standardProps 含 `sessionId: SessionId`
|
|
356
|
+
* tool.call.toolview → 含
|
|
357
|
+
* settings.section → **不含**(只有 useResource / useWorkspaces / usePanelInfo /
|
|
358
|
+
* useSessions / useSessionStatus / useSessionRetainInfo)
|
|
359
|
+
*
|
|
360
|
+
* 于是卡片(conversation.view / toolcall)保存偏好成功,而设置页的每一次保存都被
|
|
361
|
+
* 拒成"保存失败"—— Bug 的真实形态:**角色预设 / 阵容预设 / 强度 / 预算全部存不进去**。
|
|
362
|
+
*
|
|
363
|
+
* ## 为什么用一个模块级缓存而不是给设置页造假身份
|
|
364
|
+
*
|
|
365
|
+
* 设置页确实是全局的(偏好也是全局的),但它不能在**不知道自己是谁**的情况下写入。
|
|
366
|
+
* 而会话标签页(`conversation.view`)与轨迹卡片(`tool.call.toolview`)都拿到了宿主
|
|
367
|
+
* 注入的真实 `sessionId` —— 用户打开过任意一个,就等于把身份告诉了插件。这里把它记下来,
|
|
368
|
+
* 供设置页在提交时声明。
|
|
369
|
+
*
|
|
370
|
+
* ## 诚实的边界
|
|
371
|
+
*
|
|
372
|
+
* 冷启动后**直接**打开设置页、且本次会话从未渲染过上述任一入口时,缓存是空的,
|
|
373
|
+
* 保存仍会被宿主拒绝(这是刻意的:插件不编造身份)。绕开方式很简单——先看一眼
|
|
374
|
+
* 「针锋」标签页或对话卡片,再回来保存。
|
|
375
|
+
*/
|
|
376
|
+
/** 最近一次从宿主注入里观察到的会话 id(空串 = 还不知道)。 */
|
|
377
|
+
let lastKnownSessionId = "";
|
|
378
|
+
/**
|
|
379
|
+
* 记下宿主注入的会话 id。
|
|
380
|
+
*
|
|
381
|
+
* 只接受非空字符串;重复值不产生副作用。返回是否**发生了变化**,便于调用方避免
|
|
382
|
+
* 无意义的重渲染。
|
|
383
|
+
*/
|
|
384
|
+
function rememberSessionId(value) {
|
|
385
|
+
if (typeof value !== "string") return false;
|
|
386
|
+
const trimmed = value.trim();
|
|
387
|
+
if (trimmed === "" || trimmed === lastKnownSessionId) return false;
|
|
388
|
+
lastKnownSessionId = trimmed;
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
/** 当前已知的会话 id(空串 = 尚未观察到,调用方不得据此编造一个)。 */
|
|
392
|
+
function knownSessionId() {
|
|
393
|
+
return lastKnownSessionId;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* 把一个 payload 补上会话身份(已有 `session` 时不覆盖)。
|
|
397
|
+
*
|
|
398
|
+
* 这是设置页真正用得上的那个函数:它只在**确实知道**身份时才附加字段,否则原样返回,
|
|
399
|
+
* 让宿主按它自己的规则拒绝 —— 而不是伪造一个 `session: ''` 去骗过存在性检查。
|
|
400
|
+
*/
|
|
401
|
+
function withSessionIdentity(payload) {
|
|
402
|
+
if ("session" in payload) return payload;
|
|
403
|
+
const known = knownSessionId();
|
|
404
|
+
if (known === "") return payload;
|
|
405
|
+
return {
|
|
406
|
+
...payload,
|
|
407
|
+
session: known
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region src/client/promptView.ts
|
|
413
|
+
/**
|
|
414
|
+
* 点击"查看原提示词":无条目 → 写入 `loading` 并要求发请求;有条目 → 删除该条目
|
|
415
|
+
* (收起),`start` 为 false。
|
|
416
|
+
*
|
|
417
|
+
* 三态一律同样对待:`ready` / `error` / 正在 `loading` 的条目,再点都是收起。
|
|
418
|
+
* 失败后保留 `error` 条目正是为此 —— 用户再点即收起,而不是无限重拉。
|
|
419
|
+
*/
|
|
420
|
+
function togglePromptView(table, key) {
|
|
421
|
+
if (table[key] !== void 0) {
|
|
422
|
+
const next = { ...table };
|
|
423
|
+
delete next[key];
|
|
424
|
+
return {
|
|
425
|
+
table: next,
|
|
426
|
+
start: false
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
table: {
|
|
431
|
+
...table,
|
|
432
|
+
[key]: { status: "loading" }
|
|
433
|
+
},
|
|
434
|
+
start: true
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* 把一次 RPC 结果写回展开表。
|
|
439
|
+
*
|
|
440
|
+
* **只有该键仍处于 `loading` 时才写回**。用户已收起(键不存在)、或该键已经处于
|
|
441
|
+
* 终态时,返回原表**引用不变** —— 这样 `setState` 不会触发一次无意义的重渲染。
|
|
442
|
+
*/
|
|
443
|
+
function applyPromptResult(table, key, result) {
|
|
444
|
+
const current = table[key];
|
|
445
|
+
if (current === void 0 || current.status !== "loading") return table;
|
|
446
|
+
const next = result.ok ? {
|
|
447
|
+
status: "ready",
|
|
448
|
+
persona: result.persona
|
|
449
|
+
} : {
|
|
450
|
+
status: "error",
|
|
451
|
+
message: result.message
|
|
452
|
+
};
|
|
453
|
+
return {
|
|
454
|
+
...table,
|
|
455
|
+
[key]: next
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
225
459
|
//#endregion
|
|
226
460
|
//#region \0dsh-css:F:\AI\DSH\1changyong\dsh-plugin-crossfire\src\client\ReviewCard.module.css.mjs
|
|
227
|
-
const css$1 = "/*\n * Crossfire Chat card styles.\n *\n * 硬约束:**每个颜色都来自主题令牌**(`Theme.dsefkao_listTokens` 的 `--dsw-alias-*` 一族),\n * 所以浅色与深色主题都无需自己写 override。圆桌的设置页 CSS 用了硬编码\n * `#ffffff` / `rgba(0,0,0,.8)`,在深色主题下是坏的 —— 这里不重复那个错误。\n */\n\n.dsefkao_root {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 13px;\n line-height: 1.6;\n}\n\n.dsefkao_card {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dsefkao_card + .dsefkao_card {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px dashed var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n}\n\n/* 折叠的评审:一行摘要,点一下切换到它。卡片会列出本会话的全部评审,全展开\n 就是一堵墙(验收期间真实攒到 5 场、12 条缺陷)。 */\n.dsefkao_collapsed {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n margin-top: 6px;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n font: inherit;\n font-size: 12px;\n text-align: left;\n cursor: pointer;\n}\n\n.dsefkao_collapsed:hover {\n border-color: var(--dsw-alias-brand-primary, #4a6cf7);\n}\n\n.dsefkao_header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.dsefkao_title {\n font-weight: 700;\n font-size: 13px;\n letter-spacing: 0.02em;\n}\n\n.dsefkao_subjectTitle {\n font-weight: 600;\n}\n\n.dsefkao_badge {\n font-size: 11px;\n padding: 1px 8px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_badge_draft {\n border-color: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.5));\n}\n\n.dsefkao_badge_attacking {\n border-color: var(--dsw-alias-state-warn-primary, rgba(200, 150, 0, 0.6));\n color: var(--dsw-alias-state-warn-primary, inherit);\n}\n\n.dsefkao_badge_judging {\n border-color: var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-brand-primary, inherit);\n}\n\n.dsefkao_badge_settled {\n border-color: var(--dsw-alias-state-success-primary, #2e7d32);\n color: var(--dsw-alias-state-success-primary, inherit);\n}\n\n/* ---- 状态区:当前处于哪一态 + 下一步该做什么(唯一主行动) ----------------\n *\n * 这是卡片上**唯一**的强对比区域,其余段落一律保持低对比(标题灰、正文常规)。\n * 主行动是这一区里唯一的主色实心按钮,所以\"现在该干什么\"不需要读完全卡去找。\n */\n.dsefkao_statusStrip {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n/* 左侧信息块:态名 + 轮次 + 一句话说明;吃掉剩余宽度,主行动因此被推到右侧。 */\n.dsefkao_statusStripInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n flex: 1 1 240px;\n min-width: 0;\n}\n\n.dsefkao_statusStripHead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n/* 态名用反色实心药丸:它与主行动按钮同色,一眼就能配对(\"现在在这里 → 下一步在那里\")。 */\n.dsefkao_statusState {\n font-size: 12px;\n font-weight: 600;\n padding: 2px 10px;\n border-radius: 999px;\n background: var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-bg-base, #ffffff);\n}\n\n.dsefkao_statusHint {\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* 右侧行动块:一个主按钮 + 一行\"下一步\"小标题。 */\n.dsefkao_statusStripAction {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 4px;\n margin-left: auto;\n}\n\n.dsefkao_statusNextLabel {\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.04em;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* 主行动:唯一的主色实心按钮。字号与内边距都比次要按钮大一档,视觉权重随之提高。 */\n.dsefkao_cta {\n font: inherit;\n font-size: 13px;\n font-weight: 600;\n padding: 7px 18px;\n border-radius: 8px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-brand-primary, #4a6cf7);\n background: var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-bg-base, #ffffff);\n}\n\n.dsefkao_cta:disabled {\n cursor: progress;\n opacity: 0.6;\n}\n\n.dsefkao_statusError {\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-state-error-primary, #d9534f);\n word-break: break-word;\n}\n\n.dsefkao_statusOk {\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-state-success-primary, #2e7d32);\n word-break: break-word;\n}\n\n.dsefkao_section {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n/* ---- 品牌块(provider 缩写 + 品牌色) ----\n *\n * 底色是**第三方商标色**(深色主题下也一样),文字固定白色 —— 这是本插件里唯一\n * 不使用主题令牌的颜色。面积被刻意压到 18px 见方:它是身份标记,不是信息主体,\n * 主题(浅色/深色)不影响其可读性。\n */\n.dsefkao_brandBlock {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 18px;\n height: 18px;\n border-radius: 5px;\n font-size: 9px;\n font-weight: 700;\n letter-spacing: -0.02em;\n color: #ffffff;\n line-height: 1;\n user-select: none;\n}\n\n.dsefkao_sectionTitle {\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.04em;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_subject,\n.dsefkao_stance {\n white-space: pre-wrap;\n word-break: break-word;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n.dsefkao_stance {\n border-left: 2px solid var(--dsw-alias-brand-primary, #4a6cf7);\n}\n\n/* 立场编辑框:左侧那根品牌色竖线是刻意的 —— 它是\"锋\"的视觉标记,提醒用户\n 这段文字会被红队逐条对攻。 */\n.dsefkao_stanceInput {\n font: inherit;\n font-size: 12px;\n line-height: 1.6;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n border-left: 2px solid var(--dsw-alias-brand-primary, #4a6cf7);\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n resize: vertical;\n}\n\n.dsefkao_stanceInput:disabled {\n opacity: 0.7;\n}\n\n.dsefkao_chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n}\n\n.dsefkao_chip {\n font-size: 11px;\n padding: 1px 8px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_chipWarn {\n font-size: 11px;\n padding: 1px 8px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-state-warn-primary, rgba(200, 150, 0, 0.6));\n color: var(--dsw-alias-state-warn-primary, inherit);\n}\n\n.dsefkao_chipStrike {\n font-size: 11px;\n padding: 1px 8px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-state-error-primary, rgba(217, 83, 79, 0.6));\n color: var(--dsw-alias-state-error-primary, inherit);\n}\n\n.dsefkao_expertRow {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n padding: 2px 0;\n}\n\n.dsefkao_expertName {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_impactBar {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n font-size: 12px;\n}\n\n.dsefkao_countEndorsed {\n color: var(--dsw-alias-state-success-primary, #2e7d32);\n font-weight: 600;\n}\n\n.dsefkao_countRejected {\n color: var(--dsw-alias-state-error-primary, #d9534f);\n font-weight: 600;\n}\n\n.dsefkao_countPending {\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_countStrike {\n color: var(--dsw-alias-state-error-primary, #d9534f);\n}\n\n.dsefkao_defect {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n.dsefkao_defectHead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n font-size: 11px;\n}\n\n.dsefkao_quote {\n font-size: 12px;\n padding-left: 8px;\n border-left: 2px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_defectContent {\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.dsefkao_evidence {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, inherit);\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.dsefkao_evidenceLabel {\n font-weight: 600;\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n/* 复审核对区:用虚线框与\"待裁决\"的实线框区分开 —— 它问的是另一个问题\n (修没修),而不是\"这条缺陷成不成立\"。 */\n.dsefkao_recheckBox {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px dashed var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.45));\n}\n\n.dsefkao_recheckHead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n font-size: 11px;\n}\n\n.dsefkao_rejectReason {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.dsefkao_btnPrimary,\n.dsefkao_btnPrimaryOn,\n.dsefkao_btnDanger,\n.dsefkao_btnDangerOn,\n.dsefkao_btnGhost {\n font: inherit;\n font-size: 12px;\n padding: 4px 12px;\n border-radius: 8px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n.dsefkao_btnPrimaryOn {\n border-color: var(--dsw-alias-state-success-primary, #2e7d32);\n color: var(--dsw-alias-state-success-primary, inherit);\n font-weight: 600;\n}\n\n.dsefkao_btnDangerOn {\n border-color: var(--dsw-alias-state-error-primary, #d9534f);\n color: var(--dsw-alias-state-error-primary, inherit);\n font-weight: 600;\n}\n\n.dsefkao_btnGhost {\n border-color: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_btnPrimary:disabled,\n.dsefkao_btnPrimaryOn:disabled,\n.dsefkao_btnDanger:disabled,\n.dsefkao_btnDangerOn:disabled,\n.dsefkao_btnGhost:disabled {\n cursor: progress;\n opacity: 0.6;\n}\n\n.dsefkao_rejectBox {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-state-error-primary, rgba(217, 83, 79, 0.4));\n}\n\n.dsefkao_rejectPrompt {\n font-size: 12px;\n font-weight: 600;\n}\n\n.dsefkao_rejectInput {\n font: inherit;\n font-size: 12px;\n padding: 6px 8px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-base, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n resize: vertical;\n}\n\n.dsefkao_link {\n align-self: flex-start;\n border: none;\n background: none;\n padding: 0;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-brand-primary, #4a6cf7);\n cursor: pointer;\n}\n\n.dsefkao_footer {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.25));\n}\n\n.dsefkao_footerNote {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_muted {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_warn {\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary, inherit);\n}\n\n.dsefkao_notice {\n font-size: 12px;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-state-error-primary, rgba(217, 83, 79, 0.5));\n color: var(--dsw-alias-state-error-primary, inherit);\n}\n\n.dsefkao_empty {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n/* ==========================================================================\n * 用户反馈驱动的视觉改造(分区 / 三色表态 / 专家身份 / 可关闭说明 / 折叠按钮)\n *\n * 核心目标:**次要信息让位,主要信息抓得住**。手段只有三种 —— 分隔线、颜色语义、\n * 按钮形状。刻意不引入背景色块堆叠:那只会让\"一堵墙\"变成\"一堵彩色的墙\"。\n * 颜色仍然全部来自主题令牌(品牌色块是唯一例外,见 brand.dsefkao_ts)。\n * ========================================================================== */\n\n/* ---- 分区:带竖线的小标题 + 硬分隔线 ---- */\n\n.dsefkao_sectionDivider {\n border-top: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.28));\n padding-top: 10px;\n margin-top: 2px;\n}\n\n.dsefkao_sectionHeadRow {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n/* 分区标题:左侧品牌色竖线 + 加粗,成为扫读时的视觉锚点。 */\n.dsefkao_sectionTitleStrong {\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.04em;\n padding-left: 8px;\n border-left: 3px solid var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n.dsefkao_sectionCount {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* ---- 展开 / 折叠:做成按钮,不要看起来像正文链接 ---- */\n\n.dsefkao_foldButton {\n font: inherit;\n font-size: 11px;\n padding: 2px 10px;\n border-radius: 999px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.45));\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_foldButton:hover {\n border-color: var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n/* ---- 可关闭的解释性说明 ---- */\n\n.dsefkao_explain {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_explainText {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.dsefkao_explainClose {\n flex: none;\n font: inherit;\n font-size: 11px;\n line-height: 1;\n padding: 3px 7px;\n border-radius: 6px;\n cursor: pointer;\n border: 1px solid transparent;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_explainClose:hover {\n border-color: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.45));\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n/* ---- 专家:头像 + 名称 + 维度 + 状态 ---- */\n\n.dsefkao_expertCard {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n.dsefkao_expertHead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n/* 专家名用正文色与加粗:它是\"这条观点是谁提的\"的答案,必须比元数据更显眼。 */\n.dsefkao_expertNameStrong {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n/* 缺陷子卡抬头里的作者标识(头像 + 名称)。 */\n.dsefkao_defectAuthor {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* ---- 三态表态:绿 / 红 / 灰 -----------------------------------------------\n *\n * 用户要求:三个都做成按钮形状并各对应颜色;点中之后整块填充该颜色、文字变白。\n * 未点中 = 该颜色的描边 + 同色文字(颜色语义仍在,但不抢主行动的视觉权重)。\n */\n.dsefkao_verdictButton {\n font: inherit;\n font-size: 12px;\n font-weight: 600;\n padding: 4px 14px;\n border-radius: 8px;\n cursor: pointer;\n background: transparent;\n}\n\n.dsefkao_verdictEndorse {\n border: 1px solid var(--dsw-alias-state-success-primary, #2e7d32);\n color: var(--dsw-alias-state-success-primary, #2e7d32);\n}\n\n.dsefkao_verdictEndorseOn {\n border: 1px solid var(--dsw-alias-state-success-primary, #2e7d32);\n background: var(--dsw-alias-state-success-primary, #2e7d32);\n color: var(--dsw-alias-bg-base, #ffffff);\n}\n\n.dsefkao_verdictReject {\n border: 1px solid var(--dsw-alias-state-error-primary, #d9534f);\n color: var(--dsw-alias-state-error-primary, #d9534f);\n}\n\n.dsefkao_verdictRejectOn {\n border: 1px solid var(--dsw-alias-state-error-primary, #d9534f);\n background: var(--dsw-alias-state-error-primary, #d9534f);\n color: var(--dsw-alias-bg-base, #ffffff);\n}\n\n.dsefkao_verdictPending {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.5));\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_verdictPendingOn {\n border: 1px solid var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.85));\n background: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.85));\n color: var(--dsw-alias-bg-base, #ffffff);\n}\n\n.dsefkao_verdictButton:disabled {\n cursor: progress;\n opacity: 0.6;\n}\n\n/* ---- 立场全貌(可滚动) ---- */\n\n.dsefkao_stanceFull {\n white-space: pre-wrap;\n word-break: break-word;\n max-height: 320px;\n overflow: auto;\n padding: 8px 10px;\n border-radius: 8px;\n border-left: 2px solid var(--dsw-alias-brand-primary, #4a6cf7);\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n/* ---- 角色定位编辑器 ---- */\n\n.dsefkao_roleEditor {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.dsefkao_roleInput {\n font: inherit;\n font-size: 12px;\n line-height: 1.6;\n padding: 6px 8px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-base, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n resize: vertical;\n}\n\n";
|
|
461
|
+
const css$1 = "/*\n * Crossfire Chat card styles.\n *\n * 硬约束:**每个颜色都来自主题令牌**(`Theme.dsefkao_listTokens` 的 `--dsw-alias-*` 一族),\n * 所以浅色与深色主题都无需自己写 override。圆桌的设置页 CSS 用了硬编码\n * `#ffffff` / `rgba(0,0,0,.8)`,在深色主题下是坏的 —— 这里不重复那个错误。\n */\n\n.dsefkao_root {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 13px;\n line-height: 1.6;\n}\n\n.dsefkao_card {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.dsefkao_card + .dsefkao_card {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px dashed var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n}\n\n/* 折叠的评审:一行摘要,点一下切换到它。卡片会列出本会话的全部评审,全展开\n 就是一堵墙(验收期间真实攒到 5 场、12 条缺陷)。 */\n.dsefkao_collapsed {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n margin-top: 6px;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n font: inherit;\n font-size: 12px;\n text-align: left;\n cursor: pointer;\n}\n\n.dsefkao_collapsed:hover {\n border-color: var(--dsw-alias-brand-primary, #4a6cf7);\n}\n\n.dsefkao_header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.dsefkao_title {\n font-weight: 700;\n font-size: 13px;\n letter-spacing: 0.02em;\n}\n\n.dsefkao_subjectTitle {\n font-weight: 600;\n}\n\n.dsefkao_badge {\n font-size: 11px;\n padding: 1px 8px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_badge_draft {\n border-color: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.5));\n}\n\n.dsefkao_badge_attacking {\n border-color: var(--dsw-alias-state-warn-primary, rgba(200, 150, 0, 0.6));\n color: var(--dsw-alias-state-warn-primary, inherit);\n}\n\n.dsefkao_badge_judging {\n border-color: var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-brand-primary, inherit);\n}\n\n.dsefkao_badge_settled {\n border-color: var(--dsw-alias-state-success-primary, #2e7d32);\n color: var(--dsw-alias-state-success-primary, inherit);\n}\n\n/* ---- 状态区:当前处于哪一态 + 下一步该做什么(唯一主行动) ----------------\n *\n * 这是卡片上**唯一**的强对比区域,其余段落一律保持低对比(标题灰、正文常规)。\n * 主行动是这一区里唯一的主色实心按钮,所以\"现在该干什么\"不需要读完全卡去找。\n */\n.dsefkao_statusStrip {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n flex-wrap: wrap;\n padding: 12px 14px;\n border-radius: 10px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n/* 左侧信息块:态名 + 轮次 + 一句话说明;吃掉剩余宽度,主行动因此被推到右侧。 */\n.dsefkao_statusStripInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n flex: 1 1 240px;\n min-width: 0;\n}\n\n.dsefkao_statusStripHead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n/* 态名用反色实心药丸:它与主行动按钮同色,一眼就能配对(\"现在在这里 → 下一步在那里\")。\n *\n * 底色**按状态取色**,与抬头 badge 同源(draft=idle 灰 / attacking=warn 黄 /\n * judging=brand 蓝 / settled=success 绿)—— 此前这里恒为 brand 蓝,于是同一句\n * 「攻击中」在抬头是黄、在状态区是蓝,药丸对四态零区分,\"与主行动同色即可配对\"\n * 自然不成立。\n *\n * 缺省值就是 judging(品牌色,与主行动按钮同色);其余三态由下面的修饰类覆盖背景,\n * 文字色四态共用一个反色。四个状态名都来自调用点同一张\n * `styles[`statusState_${review.dsefkao_status}`]` 查表,颜色一律走 `--dsw-alias-*` 令牌。 */\n.dsefkao_statusState {\n font-size: 12px;\n font-weight: 600;\n padding: 2px 10px;\n border-radius: 999px;\n background: var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-bg-base, #ffffff);\n}\n\n/* draft:与 .dsefkao_badge_draft 同源(idle 灰)。 */\n.dsefkao_statusState_draft {\n background: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.85));\n}\n\n/* attacking:与 .dsefkao_badge_attacking 同源(warn 黄)。 */\n.dsefkao_statusState_attacking {\n background: var(--dsw-alias-state-warn-primary, rgba(200, 150, 0, 0.9));\n}\n\n/* settled:与 .dsefkao_badge_settled 同源(success 绿)。 */\n.dsefkao_statusState_settled {\n background: var(--dsw-alias-state-success-primary, rgba(46, 125, 50, 0.9));\n}\n\n.dsefkao_statusHint {\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* 右侧行动块:一个主按钮 + 一行\"下一步\"小标题。 */\n.dsefkao_statusStripAction {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 4px;\n margin-left: auto;\n}\n\n.dsefkao_statusNextLabel {\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.04em;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* 主行动:唯一的主色实心按钮。字号与内边距都比次要按钮大一档,视觉权重随之提高。 */\n.dsefkao_cta {\n font: inherit;\n font-size: 13px;\n font-weight: 600;\n padding: 7px 18px;\n border-radius: 8px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-brand-primary, #4a6cf7);\n background: var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-bg-base, #ffffff);\n}\n\n.dsefkao_cta:disabled {\n cursor: progress;\n opacity: 0.6;\n}\n\n.dsefkao_statusError {\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-state-error-primary, #d9534f);\n word-break: break-word;\n}\n\n.dsefkao_statusOk {\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-state-success-primary, #2e7d32);\n word-break: break-word;\n}\n\n.dsefkao_section {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n/* ---- 品牌块(provider 缩写 + 品牌色) ----\n *\n * 底色是**第三方商标色**(深色主题下也一样),文字固定白色 —— 这是本插件里唯一\n * 不使用主题令牌的颜色。面积被刻意压到 18px 见方:它是身份标记,不是信息主体,\n * 主题(浅色/深色)不影响其可读性。\n */\n.dsefkao_brandBlock {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 18px;\n height: 18px;\n border-radius: 5px;\n font-size: 9px;\n font-weight: 700;\n letter-spacing: -0.02em;\n color: #ffffff;\n line-height: 1;\n user-select: none;\n}\n\n.dsefkao_sectionTitle {\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.04em;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_subject,\n.dsefkao_stance {\n white-space: pre-wrap;\n word-break: break-word;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n.dsefkao_stance {\n border-left: 2px solid var(--dsw-alias-brand-primary, #4a6cf7);\n}\n\n/* 立场编辑框:左侧那根品牌色竖线是刻意的 —— 它是\"锋\"的视觉标记,提醒用户\n 这段文字会被红队逐条对攻。 */\n.dsefkao_stanceInput {\n font: inherit;\n font-size: 12px;\n line-height: 1.6;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n border-left: 2px solid var(--dsw-alias-brand-primary, #4a6cf7);\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n resize: vertical;\n}\n\n.dsefkao_stanceInput:disabled {\n opacity: 0.7;\n}\n\n.dsefkao_chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n}\n\n.dsefkao_chip {\n font-size: 11px;\n padding: 1px 8px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_chipWarn {\n font-size: 11px;\n padding: 1px 8px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-state-warn-primary, rgba(200, 150, 0, 0.6));\n color: var(--dsw-alias-state-warn-primary, inherit);\n}\n\n.dsefkao_chipStrike {\n font-size: 11px;\n padding: 1px 8px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-state-error-primary, rgba(217, 83, 79, 0.6));\n color: var(--dsw-alias-state-error-primary, inherit);\n}\n\n.dsefkao_expertRow {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n padding: 2px 0;\n}\n\n.dsefkao_expertName {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_impactBar {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n font-size: 12px;\n}\n\n.dsefkao_countEndorsed {\n color: var(--dsw-alias-state-success-primary, #2e7d32);\n font-weight: 600;\n}\n\n.dsefkao_countRejected {\n color: var(--dsw-alias-state-error-primary, #d9534f);\n font-weight: 600;\n}\n\n.dsefkao_countPending {\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_countStrike {\n color: var(--dsw-alias-state-error-primary, #d9534f);\n}\n\n/* 一条待裁决缺陷。\n *\n * 左侧 3px 竖条按表态状态着色(认定=绿 / 驳回=红 / 待定=灰):一条评审里常有\n * 十几条缺陷,用户最需要先看清\"哪些还没表态\",而这个信息此前只体现在底部按钮\n * 描边上 —— 必须把每条都读一遍才能得到全局分布。竖条让分布在一眼内可见。 */\n.dsefkao_defect {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n border-left-width: 3px;\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n.dsefkao_defect_pending {\n border-left-color: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n}\n\n.dsefkao_defect_endorsed {\n border-left-color: var(--dsw-alias-state-success-primary, #2e7d32);\n}\n\n.dsefkao_defect_rejected {\n border-left-color: var(--dsw-alias-state-error-primary, #d9534f);\n}\n\n/* 缺陷之间留出可辨认的呼吸:此前它们只隔 4px(父级 .dsefkao_section 的 gap),\n * 一条条读下来像一整块连续文本。 */\n.dsefkao_defect + .dsefkao_defect {\n margin-top: 8px;\n}\n\n.dsefkao_defectHead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n font-size: 12px;\n}\n\n.dsefkao_quote {\n font-size: 12px;\n padding-left: 8px;\n border-left: 2px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_defectContent {\n white-space: pre-wrap;\n word-break: break-word;\n line-height: 1.65;\n}\n\n/* ---- 观点标题 + 折叠 ----\n *\n * 用户诉求:①\"每条观点都要在开头立一个标题,这样目的明确\";②\"每条观点都可以折叠,\n * 折叠后只显示标题和对应专家\"。\n *\n * 标题做成**按钮**而不是纯文字:整行可点即展开/收起,省掉一个额外的折叠控件;视觉上\n * 它是这一条的抬头(比正文重一档),折叠态下也就是唯一可见的内容。\n */\n.dsefkao_defectTitle {\n font: inherit;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n text-align: left;\n width: 100%;\n padding: 4px 0;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n /* 折叠态下标题是这一条**唯一可见**的内容,所以它必须永远是单行:标题由\n `defectTitle()` 生成、硬上限 80 字符,13px 下 80 个汉字约 1040px,远超卡片列宽,\n 不约束就会折成 2–4 行,折叠体随之长回大块。 */\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n /* 长英文 token(文件名 / URL)没有可断点:nowrap 下它只会被省略号吃掉,但补上\n `anywhere` 后即使将来放宽 nowrap 也不会横向溢出。 */\n overflow-wrap: anywhere;\n}\n\n.dsefkao_defectTitle:hover {\n color: var(--dsw-alias-brand-primary, #4a6cf7);\n}\n\n.dsefkao_evidence {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, inherit);\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.dsefkao_evidenceLabel {\n font-weight: 600;\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n/* 复审核对区:用虚线框与\"待裁决\"的实线框区分开 —— 它问的是另一个问题\n (修没修),而不是\"这条缺陷成不成立\"。 */\n.dsefkao_recheckBox {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px dashed var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.45));\n}\n\n.dsefkao_recheckHead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n font-size: 11px;\n}\n\n.dsefkao_rejectReason {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.dsefkao_btnPrimary,\n.dsefkao_btnPrimaryOn,\n.dsefkao_btnDanger,\n.dsefkao_btnDangerOn,\n.dsefkao_btnGhost {\n font: inherit;\n font-size: 12px;\n padding: 4px 12px;\n border-radius: 8px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n.dsefkao_btnPrimaryOn {\n border-color: var(--dsw-alias-state-success-primary, #2e7d32);\n color: var(--dsw-alias-state-success-primary, inherit);\n font-weight: 600;\n}\n\n.dsefkao_btnDangerOn {\n border-color: var(--dsw-alias-state-error-primary, #d9534f);\n color: var(--dsw-alias-state-error-primary, inherit);\n font-weight: 600;\n}\n\n.dsefkao_btnGhost {\n border-color: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_btnPrimary:disabled,\n.dsefkao_btnPrimaryOn:disabled,\n.dsefkao_btnDanger:disabled,\n.dsefkao_btnDangerOn:disabled,\n.dsefkao_btnGhost:disabled {\n cursor: progress;\n opacity: 0.6;\n}\n\n.dsefkao_rejectBox {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-state-error-primary, rgba(217, 83, 79, 0.4));\n}\n\n.dsefkao_rejectPrompt {\n font-size: 12px;\n font-weight: 600;\n}\n\n.dsefkao_rejectInput {\n font: inherit;\n font-size: 12px;\n padding: 6px 8px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-base, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n resize: vertical;\n}\n\n.dsefkao_link {\n align-self: flex-start;\n border: none;\n background: none;\n padding: 0;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-brand-primary, #4a6cf7);\n cursor: pointer;\n}\n\n.dsefkao_footer {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.25));\n}\n\n.dsefkao_footerNote {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_muted {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_warn {\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary, inherit);\n}\n\n.dsefkao_notice {\n font-size: 12px;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-state-error-primary, rgba(217, 83, 79, 0.5));\n color: var(--dsw-alias-state-error-primary, inherit);\n}\n\n/* 空态:没有评审时整卡只剩一行字,看起来像\"坏了\"。\n * 用一圈虚线 + 内边距把它读成\"这里还没有东西\",而不是\"渲染失败\"。 */\n.dsefkao_empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 28px 16px;\n border: 1px dashed var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2, transparent);\n text-align: center;\n}\n\n/* ==========================================================================\n * 用户反馈驱动的视觉改造(分区 / 三色表态 / 专家身份 / 可关闭说明 / 折叠按钮)\n *\n * 核心目标:**次要信息让位,主要信息抓得住**。手段只有三种 —— 分隔线、颜色语义、\n * 按钮形状。刻意不引入背景色块堆叠:那只会让\"一堵墙\"变成\"一堵彩色的墙\"。\n * 颜色仍然全部来自主题令牌(品牌色块是唯一例外,见 brand.dsefkao_ts)。\n * ========================================================================== */\n\n/* ---- 分区:带竖线的小标题 + 硬分隔线 ---- */\n\n.dsefkao_sectionDivider {\n border-top: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.28));\n padding-top: 10px;\n margin-top: 2px;\n}\n\n.dsefkao_sectionHeadRow {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n/* 分区标题:左侧品牌色竖线 + 加粗,成为扫读时的视觉锚点。 */\n.dsefkao_sectionTitleStrong {\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.04em;\n padding-left: 8px;\n border-left: 3px solid var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n.dsefkao_sectionCount {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* ---- 展开 / 折叠:做成按钮,不要看起来像正文链接 ---- */\n\n.dsefkao_foldButton {\n font: inherit;\n font-size: 11px;\n padding: 2px 10px;\n border-radius: 999px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.45));\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_foldButton:hover {\n border-color: var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n/* ---- 可关闭的解释性说明 ---- */\n\n.dsefkao_explain {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_explainText {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.dsefkao_explainClose {\n flex: none;\n font: inherit;\n font-size: 11px;\n line-height: 1;\n padding: 3px 7px;\n border-radius: 6px;\n cursor: pointer;\n border: 1px solid transparent;\n background: transparent;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_explainClose:hover {\n border-color: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.45));\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n/* ---- 专家:头像 + 名称 + 维度 + 状态 ---- */\n\n.dsefkao_expertCard {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n.dsefkao_expertHead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n/* 专家名用正文色与加粗:它是\"这条观点是谁提的\"的答案,必须比元数据更显眼。 */\n.dsefkao_expertNameStrong {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n/* 缺陷子卡抬头里的作者标识(头像 + 名称)。 */\n.dsefkao_defectAuthor {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* ---- 三态表态:绿 / 红 / 灰 -----------------------------------------------\n *\n * 用户要求:三个都做成按钮形状并各对应颜色;点中之后整块填充该颜色、文字变白。\n * 未点中 = 该颜色的描边 + 同色文字(颜色语义仍在,但不抢主行动的视觉权重)。\n */\n.dsefkao_verdictButton {\n box-sizing: border-box;\n min-height: 28px;\n font: inherit;\n font-size: 12px;\n font-weight: 600;\n padding: 4px 14px;\n border-radius: 8px;\n cursor: pointer;\n background: transparent;\n}\n\n.dsefkao_verdictEndorse {\n border: 1px solid var(--dsw-alias-state-success-primary, #2e7d32);\n color: var(--dsw-alias-state-success-primary, #2e7d32);\n}\n\n.dsefkao_verdictEndorseOn {\n border: 1px solid var(--dsw-alias-state-success-primary, #2e7d32);\n background: var(--dsw-alias-state-success-primary, #2e7d32);\n color: var(--dsw-alias-bg-base, #ffffff);\n}\n\n.dsefkao_verdictReject {\n border: 1px solid var(--dsw-alias-state-error-primary, #d9534f);\n color: var(--dsw-alias-state-error-primary, #d9534f);\n}\n\n.dsefkao_verdictRejectOn {\n border: 1px solid var(--dsw-alias-state-error-primary, #d9534f);\n background: var(--dsw-alias-state-error-primary, #d9534f);\n color: var(--dsw-alias-bg-base, #ffffff);\n}\n\n.dsefkao_verdictPending {\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.5));\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.dsefkao_verdictPendingOn {\n border: 1px solid var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.85));\n background: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.85));\n color: var(--dsw-alias-bg-base, #ffffff);\n}\n\n.dsefkao_verdictButton:disabled {\n cursor: progress;\n opacity: 0.6;\n}\n\n/* ---- 立场全貌(可滚动) ---- */\n\n.dsefkao_stanceFull {\n white-space: pre-wrap;\n word-break: break-word;\n max-height: 320px;\n overflow: auto;\n padding: 8px 10px;\n border-radius: 8px;\n border-left: 2px solid var(--dsw-alias-brand-primary, #4a6cf7);\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n/* ---- 角色定位编辑器 ---- */\n\n.dsefkao_roleEditor {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.dsefkao_roleInput {\n font: inherit;\n font-size: 12px;\n line-height: 1.6;\n padding: 6px 8px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-base, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n resize: vertical;\n}\n\n";
|
|
228
462
|
const classMap$1 = {
|
|
229
463
|
"listTokens": "dsefkao_listTokens",
|
|
230
464
|
"root": "dsefkao_root",
|
|
@@ -241,7 +475,11 @@ const classMap$1 = {
|
|
|
241
475
|
"statusStrip": "dsefkao_statusStrip",
|
|
242
476
|
"statusStripInfo": "dsefkao_statusStripInfo",
|
|
243
477
|
"statusStripHead": "dsefkao_statusStripHead",
|
|
478
|
+
"status": "dsefkao_status",
|
|
244
479
|
"statusState": "dsefkao_statusState",
|
|
480
|
+
"statusState_draft": "dsefkao_statusState_draft",
|
|
481
|
+
"statusState_attacking": "dsefkao_statusState_attacking",
|
|
482
|
+
"statusState_settled": "dsefkao_statusState_settled",
|
|
245
483
|
"statusHint": "dsefkao_statusHint",
|
|
246
484
|
"statusStripAction": "dsefkao_statusStripAction",
|
|
247
485
|
"statusNextLabel": "dsefkao_statusNextLabel",
|
|
@@ -266,9 +504,13 @@ const classMap$1 = {
|
|
|
266
504
|
"countPending": "dsefkao_countPending",
|
|
267
505
|
"countStrike": "dsefkao_countStrike",
|
|
268
506
|
"defect": "dsefkao_defect",
|
|
507
|
+
"defect_pending": "dsefkao_defect_pending",
|
|
508
|
+
"defect_endorsed": "dsefkao_defect_endorsed",
|
|
509
|
+
"defect_rejected": "dsefkao_defect_rejected",
|
|
269
510
|
"defectHead": "dsefkao_defectHead",
|
|
270
511
|
"quote": "dsefkao_quote",
|
|
271
512
|
"defectContent": "dsefkao_defectContent",
|
|
513
|
+
"defectTitle": "dsefkao_defectTitle",
|
|
272
514
|
"evidence": "dsefkao_evidence",
|
|
273
515
|
"evidenceLabel": "dsefkao_evidenceLabel",
|
|
274
516
|
"recheckBox": "dsefkao_recheckBox",
|
|
@@ -344,6 +586,7 @@ function CrossfireCard(props) {
|
|
|
344
586
|
* (见 rpc.ts 的 judgeActor)。
|
|
345
587
|
*/
|
|
346
588
|
const sessionId = typeof props.sessionId === "string" ? props.sessionId : "";
|
|
589
|
+
rememberSessionId(sessionId);
|
|
347
590
|
const [reviews, setReviews] = (0, react.useState)([]);
|
|
348
591
|
const [note, setNote] = (0, react.useState)(void 0);
|
|
349
592
|
const [notice, setNotice] = (0, react.useState)(void 0);
|
|
@@ -361,6 +604,8 @@ function CrossfireCard(props) {
|
|
|
361
604
|
const [stanceExpanded, setStanceExpanded] = (0, react.useState)({});
|
|
362
605
|
const [recheckApproval, setRecheckApproval] = (0, react.useState)(null);
|
|
363
606
|
const [dismissedHints, setDismissedHints] = (0, react.useState)({});
|
|
607
|
+
const [expandedDefects, setExpandedDefects] = (0, react.useState)({});
|
|
608
|
+
const [promptViews, setPromptViews] = (0, react.useState)({});
|
|
364
609
|
const [dismissBusy, setDismissBusy] = (0, react.useState)(false);
|
|
365
610
|
const alive = (0, react.useRef)(true);
|
|
366
611
|
/**
|
|
@@ -590,7 +835,7 @@ function CrossfireCard(props) {
|
|
|
590
835
|
})
|
|
591
836
|
}));
|
|
592
837
|
};
|
|
593
|
-
/** 导出为 Markdown
|
|
838
|
+
/** 导出为 Markdown 并触发下载(盘上同时落 `export.md` 与带 BOM 的 `export.txt`)。 */
|
|
594
839
|
const exportReview = async (review) => {
|
|
595
840
|
setBusy(true);
|
|
596
841
|
const result = await rpc("crossfire/review.export", {
|
|
@@ -604,13 +849,13 @@ function CrossfireCard(props) {
|
|
|
604
849
|
reportNotice(`${t("exportFailed")}:${result.error.message}`);
|
|
605
850
|
return;
|
|
606
851
|
}
|
|
607
|
-
reportOk(`${t("exportDone")}:${result.value.path}`);
|
|
852
|
+
reportOk(result.value.textPath === void 0 || result.value.textPath === "" ? `${t("exportDone")}:${result.value.path}` : `${t("exportDone")}:${result.value.path} + ${result.value.textPath}`);
|
|
608
853
|
try {
|
|
609
|
-
const blob = new Blob([result.value.markdown], { type: "text/
|
|
854
|
+
const blob = new Blob([result.value.markdown], { type: "text/plain;charset=utf-8" });
|
|
610
855
|
const url = URL.createObjectURL(blob);
|
|
611
856
|
const anchor = document.createElement("a");
|
|
612
857
|
anchor.href = url;
|
|
613
|
-
anchor.download = `${review.reviewId}.
|
|
858
|
+
anchor.download = `${review.reviewId}.txt`;
|
|
614
859
|
anchor.click();
|
|
615
860
|
URL.revokeObjectURL(url);
|
|
616
861
|
} catch {}
|
|
@@ -682,6 +927,29 @@ function CrossfireCard(props) {
|
|
|
682
927
|
if (expert === void 0 || expert.provider === void 0) return fallback;
|
|
683
928
|
return routeText(expert.provider, expert.model);
|
|
684
929
|
}
|
|
930
|
+
/**
|
|
931
|
+
* 缺陷排序:**打穿立场的优先**,其次按维度分组,组内保持原始顺序。
|
|
932
|
+
*
|
|
933
|
+
* 这是 §5.3 方向 4 的落地:"按维度分组、按是否打穿立场排序,把最需要裁决的排在最前"。
|
|
934
|
+
* 用 `map + index` 稳定排序,保证同一组内的相对次序与收集时一致(否则每轮表态后
|
|
935
|
+
* 卡片会重排,用户会找不到刚看过的那条)。
|
|
936
|
+
*/
|
|
937
|
+
function orderDefects(defects) {
|
|
938
|
+
return defects.map((defect, index) => ({
|
|
939
|
+
defect,
|
|
940
|
+
index
|
|
941
|
+
})).sort((left, right) => {
|
|
942
|
+
const strikeDelta = Number(right.defect.strikesStance === true) - Number(left.defect.strikesStance === true);
|
|
943
|
+
if (strikeDelta !== 0) return strikeDelta;
|
|
944
|
+
const dimensionDelta = left.defect.dimension.localeCompare(right.defect.dimension, "zh");
|
|
945
|
+
if (dimensionDelta !== 0) return dimensionDelta;
|
|
946
|
+
return left.index - right.index;
|
|
947
|
+
}).map((entry) => entry.defect);
|
|
948
|
+
}
|
|
949
|
+
/** 折叠状态的键:`${reviewId}::${defectId}`。 */
|
|
950
|
+
function defectFoldKey(reviewId, defectId) {
|
|
951
|
+
return `${reviewId}::${defectId}`;
|
|
952
|
+
}
|
|
685
953
|
if (reviews.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
686
954
|
className: ReviewCard_module_css_default.root,
|
|
687
955
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -777,12 +1045,12 @@ function CrossfireCard(props) {
|
|
|
777
1045
|
className: ReviewCard_module_css_default.statusStripHead,
|
|
778
1046
|
children: [
|
|
779
1047
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
780
|
-
className: ReviewCard_module_css_default.statusState
|
|
1048
|
+
className: `${ReviewCard_module_css_default.statusState} ${ReviewCard_module_css_default[`statusState_${review.status}`] ?? ""}`,
|
|
781
1049
|
children: t(STATUS_LABEL_KEY[review.status])
|
|
782
1050
|
}),
|
|
783
1051
|
review.status === "judging" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
784
1052
|
className: ReviewCard_module_css_default.muted,
|
|
785
|
-
children:
|
|
1053
|
+
children: t("countPendingOfTotal").replace("{pending}", String(review.counts.pending)).replace("{total}", String(review.counts.total))
|
|
786
1054
|
}) : null,
|
|
787
1055
|
review.status === "attacking" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
788
1056
|
className: ReviewCard_module_css_default.muted,
|
|
@@ -1079,6 +1347,37 @@ function CrossfireCard(props) {
|
|
|
1079
1347
|
setRoleEditing(roleKey);
|
|
1080
1348
|
},
|
|
1081
1349
|
children: t("expertRoleEdit")
|
|
1350
|
+
}),
|
|
1351
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1352
|
+
type: "button",
|
|
1353
|
+
className: ReviewCard_module_css_default.foldButton,
|
|
1354
|
+
onClick: () => {
|
|
1355
|
+
const viewKey = `${review.reviewId}::${expert.key}`;
|
|
1356
|
+
let start = false;
|
|
1357
|
+
setPromptViews((previous) => {
|
|
1358
|
+
const toggled = togglePromptView(previous, viewKey);
|
|
1359
|
+
start = toggled.start;
|
|
1360
|
+
return toggled.table;
|
|
1361
|
+
});
|
|
1362
|
+
if (!start) return;
|
|
1363
|
+
(async () => {
|
|
1364
|
+
const result = await rpc("crossfire/expert.prompt", {
|
|
1365
|
+
reviewId: review.reviewId,
|
|
1366
|
+
stateRoot: review.stateRoot,
|
|
1367
|
+
session: sessionId,
|
|
1368
|
+
expertKey: expert.key
|
|
1369
|
+
});
|
|
1370
|
+
if (!alive.current) return;
|
|
1371
|
+
setPromptViews((previous) => applyPromptResult(previous, viewKey, result.ok ? {
|
|
1372
|
+
ok: true,
|
|
1373
|
+
persona: result.value.persona
|
|
1374
|
+
} : {
|
|
1375
|
+
ok: false,
|
|
1376
|
+
message: result.error.message
|
|
1377
|
+
}));
|
|
1378
|
+
})();
|
|
1379
|
+
},
|
|
1380
|
+
children: t("expertPromptView")
|
|
1082
1381
|
})
|
|
1083
1382
|
]
|
|
1084
1383
|
}),
|
|
@@ -1086,6 +1385,28 @@ function CrossfireCard(props) {
|
|
|
1086
1385
|
className: ReviewCard_module_css_default.muted,
|
|
1087
1386
|
children: `${t("expertRoleLabel")}:${expert.role}`
|
|
1088
1387
|
}),
|
|
1388
|
+
(() => {
|
|
1389
|
+
const view = promptViews[`${review.reviewId}::${expert.key}`];
|
|
1390
|
+
if (view === void 0) return null;
|
|
1391
|
+
if (view.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1392
|
+
className: ReviewCard_module_css_default.statusError,
|
|
1393
|
+
children: view.message
|
|
1394
|
+
});
|
|
1395
|
+
if (view.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1396
|
+
className: ReviewCard_module_css_default.muted,
|
|
1397
|
+
children: t("loading")
|
|
1398
|
+
});
|
|
1399
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1400
|
+
className: ReviewCard_module_css_default.section,
|
|
1401
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1402
|
+
className: ReviewCard_module_css_default.sectionTitle,
|
|
1403
|
+
children: t("expertPromptTitle")
|
|
1404
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1405
|
+
className: ReviewCard_module_css_default.stanceFull,
|
|
1406
|
+
children: view.persona
|
|
1407
|
+
})]
|
|
1408
|
+
});
|
|
1409
|
+
})(),
|
|
1089
1410
|
roleEditing === roleKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1090
1411
|
className: ReviewCard_module_css_default.roleEditor,
|
|
1091
1412
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
@@ -1183,210 +1504,240 @@ function CrossfireCard(props) {
|
|
|
1183
1504
|
review.defects.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1184
1505
|
className: ReviewCard_module_css_default.muted,
|
|
1185
1506
|
children: t("defectEmpty")
|
|
1186
|
-
}) : review.defects.map((defect) =>
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1507
|
+
}) : orderDefects(review.defects).map((defect) => {
|
|
1508
|
+
const foldKey = defectFoldKey(review.reviewId, defect.id);
|
|
1509
|
+
const isOpen = expandedDefects[foldKey] === true;
|
|
1510
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1511
|
+
className: `${ReviewCard_module_css_default.defect} ${ReviewCard_module_css_default[`defect_${defect.status}`] ?? ""}`,
|
|
1512
|
+
children: [
|
|
1513
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1514
|
+
className: ReviewCard_module_css_default.defectHead,
|
|
1515
|
+
children: [
|
|
1516
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1517
|
+
className: ReviewCard_module_css_default.chip,
|
|
1518
|
+
children: defect.dimension
|
|
1519
|
+
}),
|
|
1520
|
+
defect.dimensionFallback === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1521
|
+
className: ReviewCard_module_css_default.chipWarn,
|
|
1522
|
+
children: t("dimensionFallback")
|
|
1523
|
+
}) : null,
|
|
1524
|
+
defect.strikesStance === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1525
|
+
className: ReviewCard_module_css_default.chipStrike,
|
|
1526
|
+
children: t("strikesStance")
|
|
1527
|
+
}) : null,
|
|
1528
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: ReviewCard_module_css_default.spacer }),
|
|
1529
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1530
|
+
className: ReviewCard_module_css_default.defectAuthor,
|
|
1531
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1532
|
+
className: ReviewCard_module_css_default.brandBlock,
|
|
1533
|
+
style: { background: providerBrand(authorOf(defect.expertKey, review.experts)?.provider).color },
|
|
1534
|
+
title: routeOf(authorOf(defect.expertKey, review.experts), t("modelInherit")),
|
|
1535
|
+
children: providerBrand(authorOf(defect.expertKey, review.experts)?.provider).abbr
|
|
1536
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1537
|
+
className: ReviewCard_module_css_default.expertNameStrong,
|
|
1538
|
+
children: defect.expertKey
|
|
1539
|
+
})]
|
|
1540
|
+
}),
|
|
1541
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1542
|
+
type: "button",
|
|
1543
|
+
className: ReviewCard_module_css_default.foldButton,
|
|
1544
|
+
"aria-expanded": isOpen,
|
|
1545
|
+
onClick: () => setExpandedDefects((previous) => {
|
|
1546
|
+
const next = { ...previous };
|
|
1547
|
+
if (next[foldKey] === true) delete next[foldKey];
|
|
1548
|
+
else next[foldKey] = true;
|
|
1549
|
+
return next;
|
|
1550
|
+
}),
|
|
1551
|
+
children: isOpen ? t("collapseSection") : t("expandSection")
|
|
1552
|
+
})
|
|
1553
|
+
]
|
|
1554
|
+
}),
|
|
1555
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1556
|
+
type: "button",
|
|
1557
|
+
className: ReviewCard_module_css_default.defectTitle,
|
|
1558
|
+
"aria-expanded": isOpen,
|
|
1559
|
+
onClick: () => setExpandedDefects((previous) => {
|
|
1560
|
+
const next = { ...previous };
|
|
1561
|
+
if (next[foldKey] === true) delete next[foldKey];
|
|
1562
|
+
else next[foldKey] = true;
|
|
1563
|
+
return next;
|
|
1195
1564
|
}),
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
className: ReviewCard_module_css_default.expertNameStrong,
|
|
1214
|
-
children: defect.expertKey
|
|
1215
|
-
})]
|
|
1216
|
-
})
|
|
1217
|
-
]
|
|
1218
|
-
}),
|
|
1219
|
-
defect.quote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1220
|
-
className: ReviewCard_module_css_default.quote,
|
|
1221
|
-
children: [
|
|
1222
|
-
"“",
|
|
1223
|
-
defect.quote,
|
|
1224
|
-
"”"
|
|
1225
|
-
]
|
|
1226
|
-
}),
|
|
1227
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1228
|
-
className: ReviewCard_module_css_default.defectContent,
|
|
1229
|
-
children: defect.content
|
|
1230
|
-
}),
|
|
1231
|
-
defect.evidence === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1232
|
-
className: ReviewCard_module_css_default.evidence,
|
|
1233
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1234
|
-
className: ReviewCard_module_css_default.evidenceLabel,
|
|
1235
|
-
children: defect.evidence.kind === "repro" ? `[${t("evidenceRepro")}]` : `[${t("evidenceArgument")}]`
|
|
1236
|
-
}), defect.evidence.text]
|
|
1237
|
-
}),
|
|
1238
|
-
defect.recheck === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1239
|
-
className: ReviewCard_module_css_default.recheckBox,
|
|
1240
|
-
children: [
|
|
1241
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1242
|
-
className: ReviewCard_module_css_default.recheckHead,
|
|
1565
|
+
children: defectTitle(defect.content)
|
|
1566
|
+
}),
|
|
1567
|
+
isOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1568
|
+
defect.quote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1569
|
+
className: ReviewCard_module_css_default.quote,
|
|
1570
|
+
children: [
|
|
1571
|
+
"“",
|
|
1572
|
+
defect.quote,
|
|
1573
|
+
"”"
|
|
1574
|
+
]
|
|
1575
|
+
}),
|
|
1576
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1577
|
+
className: ReviewCard_module_css_default.defectContent,
|
|
1578
|
+
children: defect.content
|
|
1579
|
+
}),
|
|
1580
|
+
defect.evidence === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1581
|
+
className: ReviewCard_module_css_default.evidence,
|
|
1243
1582
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1244
1583
|
className: ReviewCard_module_css_default.evidenceLabel,
|
|
1245
|
-
children: t("
|
|
1246
|
-
}),
|
|
1247
|
-
className: ReviewCard_module_css_default.muted,
|
|
1248
|
-
children: `#${String(defect.recheck.pass)}`
|
|
1249
|
-
})]
|
|
1584
|
+
children: defect.evidence.kind === "repro" ? `[${t("evidenceRepro")}]` : `[${t("evidenceArgument")}]`
|
|
1585
|
+
}), defect.evidence.text]
|
|
1250
1586
|
}),
|
|
1251
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1252
|
-
className: ReviewCard_module_css_default.
|
|
1253
|
-
children: [
|
|
1587
|
+
defect.recheck === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1588
|
+
className: ReviewCard_module_css_default.recheckBox,
|
|
1589
|
+
children: [
|
|
1590
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1591
|
+
className: ReviewCard_module_css_default.recheckHead,
|
|
1592
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1593
|
+
className: ReviewCard_module_css_default.evidenceLabel,
|
|
1594
|
+
children: t("recheckLabel")
|
|
1595
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1596
|
+
className: ReviewCard_module_css_default.muted,
|
|
1597
|
+
children: `#${String(defect.recheck.pass)}`
|
|
1598
|
+
})]
|
|
1599
|
+
}),
|
|
1600
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1601
|
+
className: ReviewCard_module_css_default.actions,
|
|
1602
|
+
children: [RECHECK_VERDICTS.map((verdict) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1603
|
+
type: "button",
|
|
1604
|
+
disabled: busy,
|
|
1605
|
+
className: defect.recheck?.verdict === verdict ? ReviewCard_module_css_default.btnPrimaryOn : ReviewCard_module_css_default.btnGhost,
|
|
1606
|
+
onClick: () => {
|
|
1607
|
+
setVerdict(review, defect, verdict, recheckNotes[defect.id] ?? "");
|
|
1608
|
+
},
|
|
1609
|
+
children: t(VERDICT_KEY[verdict] ?? "verdictUnfixed")
|
|
1610
|
+
}, verdict)), defect.recheck?.verdict === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1611
|
+
className: ReviewCard_module_css_default.muted,
|
|
1612
|
+
children: t("verdictUndecided")
|
|
1613
|
+
}) : null]
|
|
1614
|
+
}),
|
|
1615
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1616
|
+
className: ReviewCard_module_css_default.actions,
|
|
1617
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1618
|
+
className: ReviewCard_module_css_default.rejectInput,
|
|
1619
|
+
disabled: busy,
|
|
1620
|
+
value: recheckNotes[defect.id] ?? defect.recheck?.note ?? "",
|
|
1621
|
+
placeholder: t("recheckNote"),
|
|
1622
|
+
onChange: (event) => {
|
|
1623
|
+
const value = event.target.value;
|
|
1624
|
+
setRecheckNotes((previous) => ({
|
|
1625
|
+
...previous,
|
|
1626
|
+
[defect.id]: value
|
|
1627
|
+
}));
|
|
1628
|
+
}
|
|
1629
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1630
|
+
type: "button",
|
|
1631
|
+
disabled: busy || defect.recheck?.verdict === void 0 || (recheckNotes[defect.id] ?? defect.recheck?.note ?? "") === (defect.recheck?.note ?? ""),
|
|
1632
|
+
title: defect.recheck?.verdict === void 0 ? t("recheckNoteNeedsVerdict") : void 0,
|
|
1633
|
+
className: ReviewCard_module_css_default.btnGhost,
|
|
1634
|
+
onClick: () => {
|
|
1635
|
+
const verdict = defect.recheck?.verdict;
|
|
1636
|
+
if (verdict === void 0) return;
|
|
1637
|
+
setVerdict(review, defect, verdict, recheckNotes[defect.id] ?? defect.recheck?.note ?? "");
|
|
1638
|
+
},
|
|
1639
|
+
children: t("presetSave")
|
|
1640
|
+
})]
|
|
1641
|
+
}),
|
|
1642
|
+
defect.recheck.note === void 0 || defect.recheck.note === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1643
|
+
className: ReviewCard_module_css_default.muted,
|
|
1644
|
+
children: defect.recheck.note
|
|
1645
|
+
})
|
|
1646
|
+
]
|
|
1647
|
+
})
|
|
1648
|
+
] }) : null,
|
|
1649
|
+
defect.status === "rejected" && defect.rejectReason !== void 0 && defect.rejectReason !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1650
|
+
className: ReviewCard_module_css_default.rejectReason,
|
|
1651
|
+
children: [
|
|
1652
|
+
t("btnReject"),
|
|
1653
|
+
":",
|
|
1654
|
+
defect.rejectReason
|
|
1655
|
+
]
|
|
1656
|
+
}) : null,
|
|
1657
|
+
review.status === "settled" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1658
|
+
className: ReviewCard_module_css_default.actions,
|
|
1659
|
+
children: [
|
|
1660
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1254
1661
|
type: "button",
|
|
1255
1662
|
disabled: busy,
|
|
1256
|
-
className: defect.
|
|
1257
|
-
onClick: () => {
|
|
1258
|
-
setVerdict(review, defect, verdict, recheckNotes[defect.id] ?? "");
|
|
1259
|
-
},
|
|
1260
|
-
children: t(VERDICT_KEY[verdict] ?? "verdictUnfixed")
|
|
1261
|
-
}, verdict)), defect.recheck?.verdict === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1262
|
-
className: ReviewCard_module_css_default.muted,
|
|
1263
|
-
children: t("verdictUndecided")
|
|
1264
|
-
}) : null]
|
|
1265
|
-
}),
|
|
1266
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1267
|
-
className: ReviewCard_module_css_default.actions,
|
|
1268
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1269
|
-
className: ReviewCard_module_css_default.rejectInput,
|
|
1270
|
-
disabled: busy,
|
|
1271
|
-
value: recheckNotes[defect.id] ?? defect.recheck?.note ?? "",
|
|
1272
|
-
placeholder: t("recheckNote"),
|
|
1273
|
-
onChange: (event) => {
|
|
1274
|
-
const value = event.target.value;
|
|
1275
|
-
setRecheckNotes((previous) => ({
|
|
1276
|
-
...previous,
|
|
1277
|
-
[defect.id]: value
|
|
1278
|
-
}));
|
|
1279
|
-
}
|
|
1280
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1281
|
-
type: "button",
|
|
1282
|
-
disabled: busy || defect.recheck?.verdict === void 0 || (recheckNotes[defect.id] ?? defect.recheck?.note ?? "") === (defect.recheck?.note ?? ""),
|
|
1283
|
-
title: defect.recheck?.verdict === void 0 ? t("recheckNoteNeedsVerdict") : void 0,
|
|
1284
|
-
className: ReviewCard_module_css_default.btnGhost,
|
|
1663
|
+
className: defect.status === "endorsed" ? `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictEndorseOn}` : `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictEndorse}`,
|
|
1285
1664
|
onClick: () => {
|
|
1286
|
-
|
|
1287
|
-
if (verdict === void 0) return;
|
|
1288
|
-
setVerdict(review, defect, verdict, recheckNotes[defect.id] ?? defect.recheck?.note ?? "");
|
|
1665
|
+
setStatus(review, defect, defect.status === "endorsed" ? "pending" : "endorsed");
|
|
1289
1666
|
},
|
|
1290
|
-
children: t("
|
|
1291
|
-
})
|
|
1292
|
-
|
|
1293
|
-
defect.recheck.note === void 0 || defect.recheck.note === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1294
|
-
className: ReviewCard_module_css_default.muted,
|
|
1295
|
-
children: defect.recheck.note
|
|
1296
|
-
})
|
|
1297
|
-
]
|
|
1298
|
-
}),
|
|
1299
|
-
defect.status === "rejected" && defect.rejectReason !== void 0 && defect.rejectReason !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1300
|
-
className: ReviewCard_module_css_default.rejectReason,
|
|
1301
|
-
children: [
|
|
1302
|
-
t("btnReject"),
|
|
1303
|
-
":",
|
|
1304
|
-
defect.rejectReason
|
|
1305
|
-
]
|
|
1306
|
-
}) : null,
|
|
1307
|
-
review.status === "settled" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1308
|
-
className: ReviewCard_module_css_default.actions,
|
|
1309
|
-
children: [
|
|
1310
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1311
|
-
type: "button",
|
|
1312
|
-
disabled: busy,
|
|
1313
|
-
className: defect.status === "endorsed" ? `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictEndorseOn}` : `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictEndorse}`,
|
|
1314
|
-
onClick: () => {
|
|
1315
|
-
setStatus(review, defect, defect.status === "endorsed" ? "pending" : "endorsed");
|
|
1316
|
-
},
|
|
1317
|
-
children: t("btnEndorse")
|
|
1318
|
-
}),
|
|
1319
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1320
|
-
type: "button",
|
|
1321
|
-
disabled: busy,
|
|
1322
|
-
className: defect.status === "rejected" ? `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictRejectOn}` : `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictReject}`,
|
|
1323
|
-
onClick: () => {
|
|
1324
|
-
if (defect.status === "rejected") {
|
|
1325
|
-
setStatus(review, defect, "pending");
|
|
1326
|
-
return;
|
|
1327
|
-
}
|
|
1328
|
-
setRejectingId(defect.id);
|
|
1329
|
-
setRejectDraft("");
|
|
1330
|
-
},
|
|
1331
|
-
children: t("btnReject")
|
|
1332
|
-
}),
|
|
1333
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1334
|
-
type: "button",
|
|
1335
|
-
disabled: busy,
|
|
1336
|
-
className: defect.status === "pending" ? `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictPendingOn}` : `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictPending}`,
|
|
1337
|
-
onClick: () => {
|
|
1338
|
-
setStatus(review, defect, "pending");
|
|
1339
|
-
},
|
|
1340
|
-
children: t("btnPending")
|
|
1341
|
-
})
|
|
1342
|
-
]
|
|
1343
|
-
}),
|
|
1344
|
-
rejectingId === defect.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1345
|
-
className: ReviewCard_module_css_default.rejectBox,
|
|
1346
|
-
children: [
|
|
1347
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1348
|
-
className: ReviewCard_module_css_default.rejectPrompt,
|
|
1349
|
-
children: t("rejectPrompt")
|
|
1350
|
-
}),
|
|
1351
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1352
|
-
className: ReviewCard_module_css_default.rejectInput,
|
|
1353
|
-
value: rejectDraft,
|
|
1354
|
-
autoFocus: true,
|
|
1355
|
-
rows: 2,
|
|
1356
|
-
placeholder: t("rejectPlaceholder"),
|
|
1357
|
-
onChange: (event) => setRejectDraft(event.target.value)
|
|
1358
|
-
}),
|
|
1359
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1360
|
-
className: ReviewCard_module_css_default.actions,
|
|
1361
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1667
|
+
children: t("btnEndorse")
|
|
1668
|
+
}),
|
|
1669
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1362
1670
|
type: "button",
|
|
1363
1671
|
disabled: busy,
|
|
1364
|
-
className: ReviewCard_module_css_default.
|
|
1672
|
+
className: defect.status === "rejected" ? `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictRejectOn}` : `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictReject}`,
|
|
1365
1673
|
onClick: () => {
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
reportNotice(t("rejectPrompt"));
|
|
1674
|
+
if (defect.status === "rejected") {
|
|
1675
|
+
setStatus(review, defect, "pending");
|
|
1369
1676
|
return;
|
|
1370
1677
|
}
|
|
1371
|
-
|
|
1372
|
-
setRejectingId(null);
|
|
1678
|
+
setRejectingId(defect.id);
|
|
1373
1679
|
setRejectDraft("");
|
|
1374
1680
|
},
|
|
1375
|
-
children: t("
|
|
1376
|
-
}),
|
|
1681
|
+
children: t("btnReject")
|
|
1682
|
+
}),
|
|
1683
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1377
1684
|
type: "button",
|
|
1378
|
-
|
|
1685
|
+
disabled: busy,
|
|
1686
|
+
className: defect.status === "pending" ? `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictPendingOn}` : `${ReviewCard_module_css_default.verdictButton} ${ReviewCard_module_css_default.verdictPending}`,
|
|
1379
1687
|
onClick: () => {
|
|
1380
|
-
|
|
1381
|
-
setRejectDraft("");
|
|
1688
|
+
setStatus(review, defect, "pending");
|
|
1382
1689
|
},
|
|
1383
|
-
children: t("
|
|
1384
|
-
})
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1690
|
+
children: t("btnPending")
|
|
1691
|
+
})
|
|
1692
|
+
]
|
|
1693
|
+
}),
|
|
1694
|
+
rejectingId === defect.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1695
|
+
className: ReviewCard_module_css_default.rejectBox,
|
|
1696
|
+
children: [
|
|
1697
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1698
|
+
className: ReviewCard_module_css_default.rejectPrompt,
|
|
1699
|
+
children: t("rejectPrompt")
|
|
1700
|
+
}),
|
|
1701
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1702
|
+
className: ReviewCard_module_css_default.rejectInput,
|
|
1703
|
+
value: rejectDraft,
|
|
1704
|
+
autoFocus: true,
|
|
1705
|
+
rows: 2,
|
|
1706
|
+
placeholder: t("rejectPlaceholder"),
|
|
1707
|
+
onChange: (event) => setRejectDraft(event.target.value)
|
|
1708
|
+
}),
|
|
1709
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1710
|
+
className: ReviewCard_module_css_default.actions,
|
|
1711
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1712
|
+
type: "button",
|
|
1713
|
+
disabled: busy,
|
|
1714
|
+
className: ReviewCard_module_css_default.btnDangerOn,
|
|
1715
|
+
onClick: () => {
|
|
1716
|
+
const reason = rejectDraft.trim();
|
|
1717
|
+
if (reason === "") {
|
|
1718
|
+
reportNotice(t("rejectPrompt"));
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
setStatus(review, defect, "rejected", reason);
|
|
1722
|
+
setRejectingId(null);
|
|
1723
|
+
setRejectDraft("");
|
|
1724
|
+
},
|
|
1725
|
+
children: t("rejectConfirm")
|
|
1726
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1727
|
+
type: "button",
|
|
1728
|
+
className: ReviewCard_module_css_default.btnGhost,
|
|
1729
|
+
onClick: () => {
|
|
1730
|
+
setRejectingId(null);
|
|
1731
|
+
setRejectDraft("");
|
|
1732
|
+
},
|
|
1733
|
+
children: t("rejectCancel")
|
|
1734
|
+
})]
|
|
1735
|
+
})
|
|
1736
|
+
]
|
|
1737
|
+
}) : null
|
|
1738
|
+
]
|
|
1739
|
+
}, defect.id);
|
|
1740
|
+
}),
|
|
1390
1741
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1391
1742
|
className: ReviewCard_module_css_default.footer,
|
|
1392
1743
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -1423,9 +1774,82 @@ function mergeSquad(list, squad) {
|
|
|
1423
1774
|
return list.some((item) => item.id === squad.id) ? list.map((item) => item.id === squad.id ? squad : item) : [...list, squad];
|
|
1424
1775
|
}
|
|
1425
1776
|
|
|
1777
|
+
//#endregion
|
|
1778
|
+
//#region src/client/icons.tsx
|
|
1779
|
+
const SHAPES = {
|
|
1780
|
+
layers: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1781
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 2.2 2.4 5 8 7.8 13.6 5 8 2.2Z" }),
|
|
1782
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.4 8 8 10.8 13.6 8" }),
|
|
1783
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.4 11 8 13.8 13.6 11" })
|
|
1784
|
+
] }),
|
|
1785
|
+
gauge: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1786
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.2 11.5a5.8 5.8 0 1 1 11.6 0" }),
|
|
1787
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 11.5 10.8 6.5" }),
|
|
1788
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1789
|
+
cx: "8",
|
|
1790
|
+
cy: "11.5",
|
|
1791
|
+
r: "0.9"
|
|
1792
|
+
})
|
|
1793
|
+
] }),
|
|
1794
|
+
role: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1795
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1796
|
+
x: "2.2",
|
|
1797
|
+
y: "3.2",
|
|
1798
|
+
width: "11.6",
|
|
1799
|
+
height: "9.6",
|
|
1800
|
+
rx: "2"
|
|
1801
|
+
}),
|
|
1802
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1803
|
+
cx: "6",
|
|
1804
|
+
cy: "7.2",
|
|
1805
|
+
r: "1.4"
|
|
1806
|
+
}),
|
|
1807
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8.6 6.4h3.2M8.6 8.4h2.4" })
|
|
1808
|
+
] }),
|
|
1809
|
+
squad: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1810
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1811
|
+
cx: "6",
|
|
1812
|
+
cy: "6",
|
|
1813
|
+
r: "2"
|
|
1814
|
+
}),
|
|
1815
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.6 13c.3-2.1 1.7-3.2 3.4-3.2s3.1 1.1 3.4 3.2" }),
|
|
1816
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1817
|
+
cx: "11.4",
|
|
1818
|
+
cy: "6.6",
|
|
1819
|
+
r: "1.5"
|
|
1820
|
+
}),
|
|
1821
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M11.4 9.4c1.5 0 2.5 1 2.8 2.6" })
|
|
1822
|
+
] }),
|
|
1823
|
+
panel: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1824
|
+
x: "2.2",
|
|
1825
|
+
y: "3.2",
|
|
1826
|
+
width: "11.6",
|
|
1827
|
+
height: "9.6",
|
|
1828
|
+
rx: "2"
|
|
1829
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M6.2 3.2v9.6" })] }),
|
|
1830
|
+
skill: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M9.2 2 4.6 8.8h3L6.8 14l4.6-6.8h-3L9.2 2Z" }),
|
|
1831
|
+
message: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3 3.4h10a1.6 1.6 0 0 1 1.6 1.6v5.2A1.6 1.6 0 0 1 13 11.8H8.4L5 14v-2.2H3A1.6 1.6 0 0 1 1.4 10.2V5A1.6 1.6 0 0 1 3 3.4Z" })
|
|
1832
|
+
};
|
|
1833
|
+
/** 渲染一个 16×16 线性图标,颜色跟随 `currentColor`。 */
|
|
1834
|
+
function InlineIcon({ name, size = 14 }) {
|
|
1835
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1836
|
+
width: size,
|
|
1837
|
+
height: size,
|
|
1838
|
+
viewBox: "0 0 16 16",
|
|
1839
|
+
fill: "none",
|
|
1840
|
+
stroke: "currentColor",
|
|
1841
|
+
strokeWidth: "1.4",
|
|
1842
|
+
strokeLinecap: "round",
|
|
1843
|
+
strokeLinejoin: "round",
|
|
1844
|
+
"aria-hidden": "true",
|
|
1845
|
+
focusable: "false",
|
|
1846
|
+
children: SHAPES[name]
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1426
1850
|
//#endregion
|
|
1427
1851
|
//#region \0dsh-css:F:\AI\DSH\1changyong\dsh-plugin-crossfire\src\client\SettingsView.module.css.mjs
|
|
1428
|
-
const css = "/*\n * Crossfire settings page styles.\n *\n * 硬约束:**每一个颜色都必须来自主题令牌**(运行时 `Theme.ddxi2lc_listTokens` 的\n * `--dsw-alias-*` 一族),因此浅色与深色主题都无需自己写 override。\n *\n * 这里刻意不照抄圆桌会议的 `RoundTableSettings.ddxi2lc_module.ddxi2lc_css`:那份用的是硬编码\n * `#ffffff` / `rgba(0, 0, 0, 0.8)`,在深色主题下会呈现出白底黑字的割裂块。\n * 令牌已经由宿主按主题解析过,直接用才是正确的做法。\n */\n\n.ddxi2lc_root {\n display: flex;\n flex-direction: column;\n gap: 20px;\n max-width: 620px;\n padding: 8px 0;\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n.ddxi2lc_notice {\n font-size: 12px;\n line-height: 1.6;\n}\n\n.ddxi2lc_noticeOk {\n color: var(--dsw-alias-state-success-primary, #2e7d32);\n}\n\n.ddxi2lc_noticeErr {\n color: var(--dsw-alias-state-error-primary, #d9534f);\n}\n\n.ddxi2lc_section {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.ddxi2lc_sectionTitle {\n font-size: 14px;\n font-weight: 600;\n}\n\n/* 区块抬头行:标题在左,\"新建\"这类**区块主操作**在右。\n *\n * 之前\"+ 新建预设\"与\"编辑/删除\"这类次要操作同级、且淹没在列表下方,用户实测\n * 反馈\"不够显眼\"。提为区块主操作后,它在本区里是唯一的实心按钮。 */\n.ddxi2lc_sectionHead {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n}\n\n.ddxi2lc_sectionHead .ddxi2lc_sectionTitle {\n flex: 0 1 auto;\n}\n\n/* 把主操作推到右侧;窄屏换行时它自然塌成整行。 */\n.ddxi2lc_sectionHeadGap {\n flex: 1 1 auto;\n}\n\n/* ---- 区块主操作(品牌色实心) ---- */\n\n.ddxi2lc_primaryButton {\n font-size: 13px;\n font-weight: 600;\n padding: 6px 14px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-brand-primary, #4a6cf7);\n background: var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-bg-base, #ffffff);\n cursor: pointer;\n}\n\n.ddxi2lc_primaryButton:disabled {\n cursor: not-allowed;\n border-color: var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.7));\n}\n\n/* ---- 品牌块(provider 缩写 + 品牌色) ----\n *\n * 与 ReviewCard.ddxi2lc_module.ddxi2lc_css 的同名规则刻意各写一份:CSS Modules 的 classMap 是\n * 按模块生成的,跨模块复用类名只能靠把 classMap 传来传去,不值得。两处的大小\n * 差异也是真实的(这里是列表行,那里是密集的专家行)。\n */\n.ddxi2lc_brandBlock {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 22px;\n height: 22px;\n border-radius: 6px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: -0.02em;\n color: #ffffff;\n line-height: 1;\n user-select: none;\n}\n\n/* 行内的品牌块 + 路由文本:块给身份,文本给可读的全名(块的 title 在触屏上读不到)。 */\n.ddxi2lc_routeRow {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ddxi2lc_hint {\n font-size: 12px;\n line-height: 1.7;\n color: var(--dsw-alias-label-secondary, rgba(128, 128, 128, 0.9));\n}\n\n.ddxi2lc_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.ddxi2lc_label {\n font-size: 13px;\n font-weight: 600;\n}\n\n.ddxi2lc_input,\n.ddxi2lc_select {\n font-size: 13px;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n max-width: 260px;\n}\n\n.ddxi2lc_input:focus,\n.ddxi2lc_select:focus {\n outline: 2px solid var(--dsw-alias-brand-primary, #4a6cf7);\n outline-offset: 1px;\n}\n\n/* ---- 强度:三选一的分段控件 ---- */\n\n.ddxi2lc_segmented {\n display: inline-flex;\n gap: 0;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n border-radius: 8px;\n overflow: hidden;\n width: fit-content;\n}\n\n.ddxi2lc_segment {\n font-size: 13px;\n padding: 6px 14px;\n border: none;\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n}\n\n.ddxi2lc_segment + .ddxi2lc_segment {\n border-left: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n}\n\n.ddxi2lc_segmentActive {\n background: var(--dsw-alias-brand-primary, #4a6cf7);\n color: var(--dsw-alias-bg-base, #ffffff);\n font-weight: 600;\n}\n\n.ddxi2lc_segment:disabled {\n cursor: progress;\n color: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.7));\n}\n\n/* ---- 维度池 ---- */\n\n.ddxi2lc_chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.ddxi2lc_chip {\n font-size: 12px;\n padding: 3px 10px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* 带品牌块的成员 chip:左边品牌色块,右边成员 key。 */\n.ddxi2lc_chipWithBrand {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n padding: 3px 10px 3px 4px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* 成员 chip 里的品牌块比列表行的再小一点:chip 本身只有 24px 高。 */\n.ddxi2lc_brandBlockSmall {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 18px;\n height: 18px;\n border-radius: 5px;\n font-size: 9px;\n font-weight: 700;\n letter-spacing: -0.02em;\n color: #ffffff;\n line-height: 1;\n user-select: none;\n}\n\n.ddxi2lc_chipCustom {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n padding: 3px 6px 3px 10px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n.ddxi2lc_chipRemove {\n border: none;\n background: transparent;\n color: var(--dsw-alias-state-error-primary, #d9534f);\n cursor: pointer;\n font-size: 12px;\n padding: 0 2px;\n line-height: 1;\n}\n\n.ddxi2lc_addRow {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ddxi2lc_addButton {\n font-size: 13px;\n padding: 6px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n}\n\n.ddxi2lc_addButton:disabled {\n cursor: not-allowed;\n color: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.7));\n}\n\n/* ---- 边界说明 ---- */\n\n.ddxi2lc_boundary {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 14px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n font-size: 12px;\n line-height: 1.7;\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n.ddxi2lc_boundaryTitle {\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n.ddxi2lc_batchNote {\n font-size: 12px;\n line-height: 1.6;\n padding: 10px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-state-warn-primary, rgba(200, 150, 0, 0.5));\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* ------------------------------------------------------------------ *\n * 角色预设 / 阵容预设(后补的类,颜色同样只用主题令牌)\n * ------------------------------------------------------------------ */\n\n/* 比 .ddxi2lc_input / .ddxi2lc_select 的 260px 宽一些:角色说明与模型名更长。 */\n.ddxi2lc_inputWide {\n font-size: 13px;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n width: 100%;\n max-width: 420px;\n}\n\n.ddxi2lc_textarea {\n font-size: 13px;\n font-family: inherit;\n line-height: 1.5;\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n width: 100%;\n max-width: 420px;\n resize: vertical;\n}\n\n.ddxi2lc_inputWide:focus,\n.ddxi2lc_textarea:focus {\n outline: 2px solid var(--dsw-alias-brand-primary, #4a6cf7);\n outline-offset: 1px;\n}\n\n/* ---- 列表 ---- */\n\n.ddxi2lc_list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.ddxi2lc_listRow {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n.ddxi2lc_rowHead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ddxi2lc_rowName {\n font-size: 13px;\n font-weight: 600;\n}\n\n.ddxi2lc_rowMeta {\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary, inherit);\n word-break: break-word;\n}\n\n/* 把行内按钮推到右侧;列表换行时它自然塌成 0 宽。 */\n.ddxi2lc_spacer {\n flex: 1 1 auto;\n}\n\n.ddxi2lc_rowButton {\n font-size: 12px;\n padding: 3px 10px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n}\n\n.ddxi2lc_rowDanger {\n font-size: 12px;\n padding: 3px 10px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-state-error-primary, #d9534f);\n background: transparent;\n color: var(--dsw-alias-state-error-primary, #d9534f);\n cursor: pointer;\n}\n\n.ddxi2lc_rowButton:disabled,\n.ddxi2lc_rowDanger:disabled {\n cursor: not-allowed;\n color: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.7));\n border-color: var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n}\n\n/* ---- 内联编辑器 ---- */\n\n.ddxi2lc_editor {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-layer-1, transparent));\n}\n\n.ddxi2lc_gridTwo {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 10px;\n}\n\n/* ---- 阵容成员行 ---- */\n\n.ddxi2lc_member {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n flex-wrap: wrap;\n padding: 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n}\n\n.ddxi2lc_memberFields {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 8px;\n flex: 1 1 320px;\n}\n\n/* 上限提示:与 .ddxi2lc_notice 同字号,但用 warn 令牌(浅深色都可读)。 */\n.ddxi2lc_warn {\n color: var(--dsw-alias-state-warn-primary, rgba(200, 150, 0, 0.9));\n}\n\n";
|
|
1852
|
+
const css = "/*\n * Crossfire settings page styles.\n *\n * 硬约束:**每一个颜色都必须来自主题令牌**(运行时 `Theme.ddxi2lc_listTokens` 的\n * `--dsw-alias-*` 一族),因此浅色与深色主题都无需自己写 override。\n *\n * 这里刻意不照抄圆桌会议的 `RoundTableSettings.ddxi2lc_module.ddxi2lc_css`:那份用的是硬编码\n * `#ffffff` / `rgba(0, 0, 0, 0.8)`,在深色主题下会呈现出白底黑字的割裂块。\n * 令牌已经由宿主按主题解析过,直接用才是正确的做法。\n */\n\n/* ---- 本地设计令牌(这一批改动的全部依据) ----\n *\n * 字号四档、圆角三档、间距 4px 栅格。圆桌设置页有同值的一份:\n * CSS Module 的 classMap 按模块生成,跨包无法共享类名,只能共享**数值**。\n * 这两份文件是同一套规格的两个副本 —— 改一处时另一处必须同步,否则两个\n * 插件的设置页会重新分叉。\n */\n.ddxi2lc_root {\n --cf-fs-11: 11px;\n --cf-fs-12: 12px;\n --cf-fs-13: 13px;\n --cf-fs-14: 14px;\n --cf-radius-s: 6px;\n --cf-radius-m: 8px;\n --cf-radius-l: 12px;\n --cf-space-1: 4px;\n --cf-space-2: 8px;\n --cf-space-3: 12px;\n --cf-space-4: 16px;\n --cf-control-h: 32px;\n --cf-border: var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n --cf-border-strong: var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n --cf-bg-soft: var(--dsw-alias-bg-layer-2, transparent);\n --cf-bg-card: var(--dsw-alias-bg-layer-1, transparent);\n --cf-fg: var(--dsw-alias-label-primary, inherit);\n --cf-fg-2: var(--dsw-alias-label-secondary, inherit);\n --cf-brand: var(--dsw-alias-brand-primary, #4a6cf7);\n --cf-on-brand: var(--dsw-alias-bg-base, #ffffff);\n\n display: flex;\n flex-direction: column;\n gap: var(--cf-space-4);\n max-width: 720px;\n padding: 8px 0;\n color: var(--cf-fg);\n font-size: var(--cf-fs-13);\n line-height: 1.6;\n}\n\n.ddxi2lc_notice {\n font-size: 12px;\n line-height: 1.6;\n}\n\n.ddxi2lc_noticeOk {\n color: var(--dsw-alias-state-success-primary, #2e7d32);\n}\n\n.ddxi2lc_noticeErr {\n color: var(--dsw-alias-state-error-primary, #d9534f);\n}\n\n/* 每个区块是一张卡片:边框 + 圆角 + 浅底把\"一组设置\"圈起来。\n * 之前所有区块靠 20px 空隙分隔,长页面滚到一半就分不清哪些控件属于同一组。 */\n.ddxi2lc_section {\n display: flex;\n flex-direction: column;\n gap: var(--cf-space-3);\n padding: var(--cf-space-4);\n border: 1px solid var(--cf-border);\n border-radius: var(--cf-radius-l);\n background: var(--cf-bg-card);\n}\n\n/* 第一个区块是整页标题 + 导语,不做卡片:给它加边框会变成\"一个框里的标题\"。 */\n.ddxi2lc_root > .ddxi2lc_section:first-child {\n padding: 0 2px var(--cf-space-1);\n border: none;\n background: transparent;\n gap: var(--cf-space-1);\n}\n\n.ddxi2lc_sectionTitle {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: var(--cf-fs-14);\n font-weight: 650;\n letter-spacing: 0.01em;\n}\n\n/* 图标降一档饱和度:它是\"目录\"记号,不和文字抢。 */\n.ddxi2lc_sectionTitle svg {\n flex: none;\n color: var(--cf-fg-2);\n}\n\n/* 区块抬头行:标题在左,\"新建\"这类**区块主操作**在右。\n *\n * 之前\"+ 新建预设\"与\"编辑/删除\"这类次要操作同级、且淹没在列表下方,用户实测\n * 反馈\"不够显眼\"。提为区块主操作后,它在本区里是唯一的实心按钮。 */\n.ddxi2lc_sectionHead {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n}\n\n.ddxi2lc_sectionHead .ddxi2lc_sectionTitle {\n flex: 0 1 auto;\n}\n\n/* 把主操作推到右侧;窄屏换行时它自然塌成整行。 */\n.ddxi2lc_sectionHeadGap {\n flex: 1 1 auto;\n}\n\n/* ---- 区块主操作(品牌色实心) ---- */\n\n.ddxi2lc_primaryButton {\n box-sizing: border-box;\n min-height: var(--cf-control-h);\n font-size: var(--cf-fs-13);\n font-weight: 600;\n padding: 6px 14px;\n border-radius: var(--cf-radius-m);\n border: 1px solid var(--cf-brand);\n background: var(--cf-brand);\n color: var(--cf-on-brand);\n cursor: pointer;\n}\n\n.ddxi2lc_primaryButton:hover:not(:disabled) {\n filter: brightness(1.06);\n}\n\n.ddxi2lc_primaryButton:disabled {\n cursor: not-allowed;\n border-color: var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.7));\n}\n\n/* ---- 品牌块(provider 缩写 + 品牌色) ----\n *\n * 与 ReviewCard.ddxi2lc_module.ddxi2lc_css 的同名规则刻意各写一份:CSS Modules 的 classMap 是\n * 按模块生成的,跨模块复用类名只能靠把 classMap 传来传去,不值得。两处的大小\n * 差异也是真实的(这里是列表行,那里是密集的专家行)。\n */\n.ddxi2lc_brandBlock {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 22px;\n height: 22px;\n border-radius: 6px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: -0.02em;\n color: #ffffff;\n line-height: 1;\n user-select: none;\n}\n\n/* 行内的品牌块 + 路由文本:块给身份,文本给可读的全名(块的 title 在触屏上读不到)。 */\n.ddxi2lc_routeRow {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ddxi2lc_hint {\n font-size: 12px;\n line-height: 1.7;\n color: var(--dsw-alias-label-secondary, rgba(128, 128, 128, 0.9));\n}\n\n.ddxi2lc_field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.ddxi2lc_label {\n font-size: 13px;\n font-weight: 600;\n}\n\n.ddxi2lc_input,\n.ddxi2lc_select {\n box-sizing: border-box;\n min-height: var(--cf-control-h);\n width: 100%;\n max-width: 320px;\n font-size: var(--cf-fs-13);\n padding: 6px 10px;\n border-radius: var(--cf-radius-m);\n border: 1px solid var(--cf-border-strong);\n background: var(--cf-bg-soft);\n color: var(--cf-fg);\n}\n\n.ddxi2lc_input:focus,\n.ddxi2lc_select:focus {\n outline: 2px solid var(--dsw-alias-brand-primary, #4a6cf7);\n outline-offset: 1px;\n}\n\n/* ---- 强度:三选一的分段控件 ---- */\n\n.ddxi2lc_segmented {\n display: inline-flex;\n gap: 2px;\n padding: 2px;\n border: 1px solid var(--cf-border);\n border-radius: var(--cf-radius-l);\n background: var(--cf-bg-soft);\n width: fit-content;\n}\n\n.ddxi2lc_segment {\n box-sizing: border-box;\n min-height: 28px;\n font-size: var(--cf-fs-13);\n padding: 5px 14px;\n border: none;\n border-radius: var(--cf-radius-m);\n background: transparent;\n color: var(--cf-fg);\n cursor: pointer;\n}\n\n/* 组内不再画分隔线:间隙 + 圆角已经能把两段分开,再加竖线会显得像表格。 */\n.ddxi2lc_segment + .ddxi2lc_segment {\n border-left: none;\n}\n\n.ddxi2lc_segmentActive {\n background: var(--cf-brand);\n color: var(--cf-on-brand);\n font-weight: 600;\n}\n\n.ddxi2lc_segment:disabled {\n cursor: progress;\n color: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.7));\n}\n\n/* ---- 维度池 ---- */\n\n.ddxi2lc_chips {\n display: flex;\n flex-wrap: wrap;\n gap: var(--cf-space-2);\n}\n\n.ddxi2lc_chip {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n min-height: 24px;\n font-size: var(--cf-fs-12);\n padding: 2px 10px;\n border-radius: 999px;\n border: 1px solid var(--cf-border);\n background: var(--cf-bg-soft);\n color: var(--cf-fg-2);\n}\n\n/* 带品牌块的成员 chip:左边品牌色块,右边成员 key。 */\n.ddxi2lc_chipWithBrand {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n padding: 3px 10px 3px 4px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n background: var(--dsw-alias-bg-layer-2, transparent);\n color: var(--dsw-alias-label-secondary, inherit);\n}\n\n/* 成员 chip 里的品牌块比列表行的再小一点:chip 本身只有 24px 高。 */\n.ddxi2lc_brandBlockSmall {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 18px;\n height: 18px;\n border-radius: 5px;\n font-size: 9px;\n font-weight: 700;\n letter-spacing: -0.02em;\n color: #ffffff;\n line-height: 1;\n user-select: none;\n}\n\n.ddxi2lc_chipCustom {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n padding: 3px 6px 3px 10px;\n border-radius: 999px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-layer-1, transparent);\n color: var(--dsw-alias-label-primary, inherit);\n}\n\n.ddxi2lc_chipRemove {\n border: none;\n background: transparent;\n color: var(--dsw-alias-state-error-primary, #d9534f);\n cursor: pointer;\n font-size: 12px;\n padding: 0 2px;\n line-height: 1;\n}\n\n.ddxi2lc_addRow {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ddxi2lc_addButton {\n box-sizing: border-box;\n min-height: var(--cf-control-h);\n font-size: var(--cf-fs-13);\n padding: 6px 12px;\n border-radius: var(--cf-radius-m);\n border: 1px solid var(--cf-border-strong);\n background: transparent;\n color: var(--cf-fg);\n cursor: pointer;\n}\n\n.ddxi2lc_addButton:hover:not(:disabled) {\n background: var(--cf-bg-soft);\n}\n\n.ddxi2lc_addButton:disabled {\n cursor: not-allowed;\n color: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.7));\n}\n\n/* ---- 边界说明 ---- */\n\n.ddxi2lc_boundary {\n display: flex;\n flex-direction: column;\n gap: var(--cf-space-2);\n padding: 14px 16px;\n border-radius: var(--cf-radius-l);\n border: 1px solid var(--cf-border);\n background: var(--cf-bg-soft);\n font-size: var(--cf-fs-12);\n line-height: 1.7;\n color: var(--cf-fg-2);\n}\n\n.ddxi2lc_boundaryTitle {\n font-size: var(--cf-fs-13);\n font-weight: 600;\n color: var(--cf-fg);\n}\n\n.ddxi2lc_batchNote {\n font-size: var(--cf-fs-12);\n line-height: 1.6;\n padding: 10px 12px;\n border-radius: var(--cf-radius-m);\n border: 1px solid var(--dsw-alias-state-warn-primary, rgba(200, 150, 0, 0.5));\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #c89600) 8%, transparent);\n color: var(--cf-fg-2);\n}\n\n/* ------------------------------------------------------------------ *\n * 角色预设 / 阵容预设(后补的类,颜色同样只用主题令牌)\n * ------------------------------------------------------------------ */\n\n/* 比 .ddxi2lc_input / .ddxi2lc_select 的 320px 宽一些:角色说明与模型名更长。 */\n.ddxi2lc_inputWide {\n box-sizing: border-box;\n min-height: var(--cf-control-h);\n font-size: var(--cf-fs-13);\n padding: 6px 10px;\n border-radius: var(--cf-radius-m);\n border: 1px solid var(--cf-border-strong);\n background: var(--cf-bg-soft);\n color: var(--cf-fg);\n width: 100%;\n max-width: 480px;\n}\n\n.ddxi2lc_textarea {\n box-sizing: border-box;\n font-size: var(--cf-fs-13);\n font-family: inherit;\n line-height: 1.5;\n padding: 8px 10px;\n border-radius: var(--cf-radius-m);\n border: 1px solid var(--cf-border-strong);\n background: var(--cf-bg-soft);\n color: var(--cf-fg);\n width: 100%;\n max-width: 480px;\n resize: vertical;\n}\n\n.ddxi2lc_inputWide:focus,\n.ddxi2lc_textarea:focus {\n outline: 2px solid var(--dsw-alias-brand-primary, #4a6cf7);\n outline-offset: 1px;\n}\n\n/* ---- 列表 ---- */\n\n.ddxi2lc_list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.ddxi2lc_listRow {\n display: flex;\n flex-direction: column;\n gap: var(--cf-space-1);\n padding: 10px 12px;\n border-radius: var(--cf-radius-m);\n border: 1px solid var(--cf-border);\n background: var(--cf-bg-soft);\n}\n\n.ddxi2lc_rowHead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ddxi2lc_rowName {\n font-size: 13px;\n font-weight: 600;\n}\n\n.ddxi2lc_rowMeta {\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary, inherit);\n word-break: break-word;\n}\n\n/* 把行内按钮推到右侧;列表换行时它自然塌成 0 宽。 */\n.ddxi2lc_spacer {\n flex: 1 1 auto;\n}\n\n.ddxi2lc_rowButton {\n box-sizing: border-box;\n min-height: 28px;\n font-size: var(--cf-fs-12);\n padding: 3px 10px;\n border-radius: var(--cf-radius-s);\n border: 1px solid var(--cf-border-strong);\n background: transparent;\n color: var(--cf-fg);\n cursor: pointer;\n}\n\n.ddxi2lc_rowButton:hover:not(:disabled) {\n background: var(--cf-bg-soft);\n}\n\n.ddxi2lc_rowDanger {\n box-sizing: border-box;\n min-height: 28px;\n font-size: var(--cf-fs-12);\n padding: 3px 10px;\n border-radius: var(--cf-radius-s);\n border: 1px solid var(--dsw-alias-state-error-primary, #d9534f);\n background: transparent;\n color: var(--dsw-alias-state-error-primary, #d9534f);\n cursor: pointer;\n}\n\n.ddxi2lc_rowDanger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d9534f) 7%, transparent);\n}\n\n.ddxi2lc_rowButton:disabled,\n.ddxi2lc_rowDanger:disabled {\n cursor: not-allowed;\n color: var(--dsw-alias-state-idle-primary, rgba(128, 128, 128, 0.7));\n border-color: var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.3));\n}\n\n/* ---- 内联编辑器 ---- */\n\n.ddxi2lc_editor {\n display: flex;\n flex-direction: column;\n gap: var(--cf-space-3);\n padding: 14px;\n border-radius: var(--cf-radius-l);\n border: 1px solid var(--cf-border-strong);\n background: var(--cf-bg-soft);\n}\n\n.ddxi2lc_gridTwo {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 10px;\n}\n\n/* ---- 阵容成员行 ---- */\n\n.ddxi2lc_member {\n display: flex;\n align-items: flex-end;\n gap: var(--cf-space-2);\n flex-wrap: wrap;\n padding: 10px;\n border-radius: var(--cf-radius-l);\n border: 1px solid var(--cf-border);\n background: var(--cf-bg-soft);\n}\n\n.ddxi2lc_memberFields {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 8px;\n flex: 1 1 320px;\n}\n\n/* 上限提示:与 .ddxi2lc_notice 同字号,但用 warn 令牌(浅深色都可读)。 */\n.ddxi2lc_warn {\n color: var(--dsw-alias-state-warn-primary, rgba(200, 150, 0, 0.9));\n}\n\n";
|
|
1429
1853
|
const classMap = {
|
|
1430
1854
|
"listTokens": "ddxi2lc_listTokens",
|
|
1431
1855
|
"module": "ddxi2lc_module",
|
|
@@ -1947,9 +2371,9 @@ function CrossfireSettings(props) {
|
|
|
1947
2371
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1948
2372
|
className: SettingsView_module_css_default.section,
|
|
1949
2373
|
children: [
|
|
1950
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2374
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1951
2375
|
className: SettingsView_module_css_default.sectionTitle,
|
|
1952
|
-
children: t("dimensionsTitle")
|
|
2376
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineIcon, { name: "layers" }), t("dimensionsTitle")]
|
|
1953
2377
|
}),
|
|
1954
2378
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1955
2379
|
className: SettingsView_module_css_default.hint,
|
|
@@ -2024,9 +2448,9 @@ function CrossfireSettings(props) {
|
|
|
2024
2448
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2025
2449
|
className: SettingsView_module_css_default.section,
|
|
2026
2450
|
children: [
|
|
2027
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2451
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2028
2452
|
className: SettingsView_module_css_default.sectionTitle,
|
|
2029
|
-
children: t("budgetTitle")
|
|
2453
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineIcon, { name: "gauge" }), t("budgetTitle")]
|
|
2030
2454
|
}),
|
|
2031
2455
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2032
2456
|
className: SettingsView_module_css_default.hint,
|
|
@@ -2061,9 +2485,9 @@ function CrossfireSettings(props) {
|
|
|
2061
2485
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2062
2486
|
className: SettingsView_module_css_default.sectionHead,
|
|
2063
2487
|
children: [
|
|
2064
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2488
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2065
2489
|
className: SettingsView_module_css_default.sectionTitle,
|
|
2066
|
-
children: t("settingsPresetsTitle")
|
|
2490
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineIcon, { name: "role" }), t("settingsPresetsTitle")]
|
|
2067
2491
|
}),
|
|
2068
2492
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: SettingsView_module_css_default.sectionHeadGap }),
|
|
2069
2493
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -2278,9 +2702,9 @@ function CrossfireSettings(props) {
|
|
|
2278
2702
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2279
2703
|
className: SettingsView_module_css_default.sectionHead,
|
|
2280
2704
|
children: [
|
|
2281
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2705
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2282
2706
|
className: SettingsView_module_css_default.sectionTitle,
|
|
2283
|
-
children: t("settingsSquadsTitle")
|
|
2707
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineIcon, { name: "squad" }), t("settingsSquadsTitle")]
|
|
2284
2708
|
}),
|
|
2285
2709
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: SettingsView_module_css_default.sectionHeadGap }),
|
|
2286
2710
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -2696,6 +3120,7 @@ const zh = {
|
|
|
2696
3120
|
countEndorsed: "认定",
|
|
2697
3121
|
countRejected: "驳回",
|
|
2698
3122
|
countPending: "待定",
|
|
3123
|
+
countPendingOfTotal: "待定 {pending} / 共 {total} 条",
|
|
2699
3124
|
defectEmpty: "还没有可裁决的缺陷。",
|
|
2700
3125
|
defectsTitle: "待裁决缺陷",
|
|
2701
3126
|
evidenceRepro: "可复现步骤",
|
|
@@ -2738,6 +3163,8 @@ const zh = {
|
|
|
2738
3163
|
expertRoleEdit: "改角色定位",
|
|
2739
3164
|
expertRoleSaved: "角色定位已保存",
|
|
2740
3165
|
expertRoleFromRoster: "(来自阵容预设)",
|
|
3166
|
+
expertPromptView: "查看原提示词",
|
|
3167
|
+
expertPromptTitle: "这位专家的原提示词(只读)",
|
|
2741
3168
|
stanceSectionTitle: "我的立场与理由",
|
|
2742
3169
|
stanceExpand: "展开看全文",
|
|
2743
3170
|
stanceCollapse: "收起立场",
|
|
@@ -2871,6 +3298,7 @@ const en = {
|
|
|
2871
3298
|
countEndorsed: "Endorsed",
|
|
2872
3299
|
countRejected: "Rejected",
|
|
2873
3300
|
countPending: "Pending",
|
|
3301
|
+
countPendingOfTotal: "Pending {pending} / {total} total",
|
|
2874
3302
|
defectEmpty: "No defects to adjudicate yet.",
|
|
2875
3303
|
defectsTitle: "Defects to adjudicate",
|
|
2876
3304
|
evidenceRepro: "Repro steps",
|
|
@@ -2913,6 +3341,8 @@ const en = {
|
|
|
2913
3341
|
expertRoleEdit: "Edit role",
|
|
2914
3342
|
expertRoleSaved: "Role saved",
|
|
2915
3343
|
expertRoleFromRoster: "(from a squad preset)",
|
|
3344
|
+
expertPromptView: "View original prompt",
|
|
3345
|
+
expertPromptTitle: "This expert's original prompt (read-only)",
|
|
2916
3346
|
stanceSectionTitle: "Your position and reasons",
|
|
2917
3347
|
stanceExpand: "Expand full text",
|
|
2918
3348
|
stanceCollapse: "Collapse stance",
|
|
@@ -2994,8 +3424,11 @@ function apply(ctx) {
|
|
|
2994
3424
|
return direct;
|
|
2995
3425
|
}
|
|
2996
3426
|
};
|
|
3427
|
+
const settingsRpc = async (endpoint, payload) => {
|
|
3428
|
+
return await rpc(endpoint, payload !== null && typeof payload === "object" && !Array.isArray(payload) ? withSessionIdentity(payload) : payload);
|
|
3429
|
+
};
|
|
2997
3430
|
const settingsInjected = () => ({
|
|
2998
|
-
rpc,
|
|
3431
|
+
rpc: settingsRpc,
|
|
2999
3432
|
t: ctx.locale.bind(NS)
|
|
3000
3433
|
});
|
|
3001
3434
|
slots.inject("settings.section", () => slots.register({
|