@dshtrading/client-ui-knowledge 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -6,7 +6,8 @@ window.__ModuleLoader__.load({
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
8
  let react_jsx_runtime = require("react/jsx-runtime");
9
- Object.freeze({
9
+ //#region ../knowledge/lib/authors.js
10
+ const AUTHOR_ALIASES = Object.freeze({
10
11
  "鳄鱼派": "鳄鱼派(像鳄鱼一样思考)",
11
12
  "鳄鱼派(公众号:像鳄鱼一样思考)": "鳄鱼派(像鳄鱼一样思考)",
12
13
  "像鳄鱼一样思考(鳄鱼派)": "鳄鱼派(像鳄鱼一样思考)",
@@ -16,6 +17,137 @@ window.__ModuleLoader__.load({
16
17
  "小鹿投研日记(转播大摩闭门会 9.14,发言人Robin/Steven/钟申/Jenny;Laura股票部分视频截断未录)": "小鹿投研日记",
17
18
  "中金点睛(刘刚、杨萱庭)": "中金点睛"
18
19
  });
20
+ /** 把作者名归一到规范名;未登记的写法原样返回(仅去除首尾空白)。 */
21
+ function canonicalAuthor(author) {
22
+ const trimmed = author.trim();
23
+ return AUTHOR_ALIASES[trimmed] ?? trimmed;
24
+ }
25
+ //#endregion
26
+ //#region ../knowledge/lib/validate.js
27
+ const ALLOWED_CREDIBILITY = /* @__PURE__ */ new Set([
28
+ "high",
29
+ "medium",
30
+ "low"
31
+ ]);
32
+ const ALLOWED_SOURCE_TYPES = /* @__PURE__ */ new Set([
33
+ "bilibili",
34
+ "wechat",
35
+ "manual"
36
+ ]);
37
+ function isValidSourceUrl(url, type) {
38
+ if (!url || typeof url !== "string") return false;
39
+ const trimmed = url.trim();
40
+ if (type === "manual") return trimmed === "manual" || trimmed.startsWith("manual:") || trimmed.startsWith("http://") || trimmed.startsWith("https://");
41
+ if (type === "bilibili") return trimmed.includes("bilibili.com") || trimmed.includes("b23.tv") || trimmed.startsWith("BV");
42
+ if (type === "wechat") return trimmed.includes("mp.weixin.qq.com") || trimmed.includes("weixin.qq.com");
43
+ return true;
44
+ }
45
+ function generateCardId() {
46
+ return `kc_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
47
+ }
48
+ function validateKnowledgeCard(input, existingCards = []) {
49
+ if (!input || typeof input !== "object") return {
50
+ ok: false,
51
+ error: "输入卡片数据必须是非空对象"
52
+ };
53
+ if (!input.title || typeof input.title !== "string" || input.title.trim().length === 0) return {
54
+ ok: false,
55
+ error: "卡片标题 (title) 为必填字段且不能为空"
56
+ };
57
+ if (!input.summary || typeof input.summary !== "string" || input.summary.trim().length === 0) return {
58
+ ok: false,
59
+ error: "卡片概述 (summary) 为必填字段且不能为空"
60
+ };
61
+ if (!input.source || typeof input.source !== "object") return {
62
+ ok: false,
63
+ error: "卡片来源 (source) 为必填对象"
64
+ };
65
+ const { type, url, author } = input.source;
66
+ if (!type || !ALLOWED_SOURCE_TYPES.has(type)) return {
67
+ ok: false,
68
+ error: `来源类型 (source.type) 非法,允许值为: ${Array.from(ALLOWED_SOURCE_TYPES).join(", ")}`
69
+ };
70
+ if (!url || typeof url !== "string" || url.trim().length === 0) return {
71
+ ok: false,
72
+ error: "来源链接 (source.url) 为必填字段且不能为空"
73
+ };
74
+ if (!isValidSourceUrl(url, type)) return {
75
+ ok: false,
76
+ error: `来源链接 (source.url) 与来源类型 (${type}) 不匹配或不符合白名单规范`
77
+ };
78
+ if (!author || typeof author !== "string" || author.trim().length === 0) return {
79
+ ok: false,
80
+ error: "来源作者/UP主/公众号 (source.author) 为必填字段"
81
+ };
82
+ if (!input.credibility || !ALLOWED_CREDIBILITY.has(input.credibility)) return {
83
+ ok: false,
84
+ error: `内容可信度 (credibility) 非法,允许值为: ${Array.from(ALLOWED_CREDIBILITY).join(", ")}`
85
+ };
86
+ if (!Array.isArray(input.coreClaims) || input.coreClaims.length === 0) return {
87
+ ok: false,
88
+ error: "核心论点 (coreClaims) 必须是非空字符串数组"
89
+ };
90
+ if (!input.factCheck || typeof input.factCheck !== "object") return {
91
+ ok: false,
92
+ error: "事实核查三桶 (factCheck) 为必填对象,包含 verified, discrepancies, unverifiable"
93
+ };
94
+ const { verified = [], discrepancies = [], unverifiable = [] } = input.factCheck;
95
+ if (!Array.isArray(verified) || !Array.isArray(discrepancies) || !Array.isArray(unverifiable)) return {
96
+ ok: false,
97
+ error: "事实核查分类项 (verified / discrepancies / unverifiable) 必须为数组"
98
+ };
99
+ if (!Array.isArray(input.takeaways)) return {
100
+ ok: false,
101
+ error: "可复用经验 (takeaways) 必须为字符串数组"
102
+ };
103
+ if (!Array.isArray(input.boundaries)) return {
104
+ ok: false,
105
+ error: "适用边界与避坑 (boundaries) 必须为字符串数组"
106
+ };
107
+ if (!Array.isArray(input.tags) || input.tags.length === 0) return {
108
+ ok: false,
109
+ error: "主题关键词标签 (tags) 必须包含至少一个标签"
110
+ };
111
+ if (input.related && Array.isArray(input.related) && input.related.length > 0) {
112
+ const existingIds = new Set(existingCards.map((c) => c.id));
113
+ for (const relId of input.related) if (!existingIds.has(relId)) return {
114
+ ok: false,
115
+ error: `显式关联卡片 id [${relId}] 在知识库中不存在(拒绝悬空关联)`
116
+ };
117
+ }
118
+ const now = (/* @__PURE__ */ new Date()).toISOString();
119
+ const id = input.id && input.id.startsWith("kc_") ? input.id : generateCardId();
120
+ const createdAt = input.createdAt || now;
121
+ const updatedAt = now;
122
+ return {
123
+ ok: true,
124
+ card: {
125
+ id,
126
+ title: input.title.trim(),
127
+ summary: input.summary.trim(),
128
+ source: {
129
+ type: input.source.type,
130
+ url: input.source.url.trim(),
131
+ author: canonicalAuthor(input.source.author),
132
+ publishedAt: input.source.publishedAt
133
+ },
134
+ credibility: input.credibility,
135
+ coreClaims: input.coreClaims.map((c) => String(c).trim()).filter(Boolean),
136
+ factCheck: {
137
+ verified: verified.map((c) => String(c).trim()).filter(Boolean),
138
+ discrepancies: discrepancies.map((c) => String(c).trim()).filter(Boolean),
139
+ unverifiable: unverifiable.map((c) => String(c).trim()).filter(Boolean)
140
+ },
141
+ takeaways: input.takeaways.map((c) => String(c).trim()).filter(Boolean),
142
+ boundaries: input.boundaries.map((c) => String(c).trim()).filter(Boolean),
143
+ tags: input.tags.map((t) => String(t).trim()).filter(Boolean),
144
+ tickers: input.tickers ? input.tickers.map((t) => String(t).trim().toUpperCase()).filter(Boolean) : void 0,
145
+ related: input.related ? Array.from(new Set(input.related)) : void 0,
146
+ createdAt,
147
+ updatedAt
148
+ }
149
+ };
150
+ }
19
151
  //#endregion
20
152
  //#region ../knowledge/lib/graph.js
21
153
  function buildGraph(cards, options = {}) {
@@ -161,6 +293,298 @@ window.__ModuleLoader__.load({
161
293
  };
162
294
  }
163
295
  //#endregion
296
+ //#region ../knowledge/lib/bundle.js
297
+ /** 单次导入卡片数上限:防止一次导入把库撑到图形/检索不可用的量级。 */
298
+ const KNOWLEDGE_BUNDLE_MAX_CARDS = 2e3;
299
+ /** 单文件字节上限(8 MiB):整个文件要先读进内存再解析,读之前就要有界。 */
300
+ const KNOWLEDGE_BUNDLE_MAX_BYTES = 8388608;
301
+ const BUNDLE_GENERATOR = "dsh-trading";
302
+ const BUNDLE_NOTICE = "知识卡片是第三方观点的结构化转述,不构成投资建议;来源作者与原始链接随卡片保留以便查证。";
303
+ /**
304
+ * 由卡片列表构造 bundle。
305
+ *
306
+ * 不做深拷贝也不做字段裁剪:导出的是库里已有的卡片(store 写入面已归一作者),
307
+ * 这里改字段只会让「导出—导入—再导出」三份内容对不上。
308
+ */
309
+ function createKnowledgeBundle(cards, options = {}) {
310
+ return {
311
+ meta: {
312
+ schemaVersion: 1,
313
+ exportedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
314
+ cardCount: cards.length,
315
+ generator: BUNDLE_GENERATOR,
316
+ notice: BUNDLE_NOTICE
317
+ },
318
+ cards
319
+ };
320
+ }
321
+ /** 落盘文本:2 空格缩进(人可读、可 diff)+ 收尾换行(符合 POSIX 文本约定)。 */
322
+ function serializeKnowledgeBundle(bundle) {
323
+ return JSON.stringify(bundle, null, 2) + "\n";
324
+ }
325
+ function isRecord(value) {
326
+ return typeof value === "object" && value !== null && !Array.isArray(value);
327
+ }
328
+ /**
329
+ * 解析 bundle 文本,逐条判定并把失败原因写成能直接给用户看的一句话。
330
+ *
331
+ * 判定顺序即优先级:先看「有没有内容/是不是 JSON/顶层像不像 bundle」这类
332
+ * 一眼能定性的问题,再进 schemaVersion 与卡数上限,最后才逐卡看必填字段——
333
+ * 用户拿到的第一条错误就是最该先修的那条。
334
+ */
335
+ function parseKnowledgeBundle(text) {
336
+ if (typeof text !== "string" || text.trim().length === 0) return {
337
+ ok: false,
338
+ error: "bundle 内容为空"
339
+ };
340
+ const bytes = new TextEncoder().encode(text).length;
341
+ if (bytes > 8388608) return {
342
+ ok: false,
343
+ error: `bundle 体积 ${bytes} 字节超过上限 ${KNOWLEDGE_BUNDLE_MAX_BYTES} 字节(8 MiB),请拆分后再导入`
344
+ };
345
+ let parsed;
346
+ try {
347
+ parsed = JSON.parse(text);
348
+ } catch (error) {
349
+ return {
350
+ ok: false,
351
+ error: `bundle 不是合法 JSON: ${error instanceof Error ? error.message : String(error)}`
352
+ };
353
+ }
354
+ if (!isRecord(parsed)) return {
355
+ ok: false,
356
+ error: "bundle 顶层必须是 JSON 对象(含 meta 与 cards 两个字段)"
357
+ };
358
+ if (!isRecord(parsed.meta)) return {
359
+ ok: false,
360
+ error: "bundle 缺少 meta 字段(应为对象)"
361
+ };
362
+ if (parsed.cards === void 0) return {
363
+ ok: false,
364
+ error: "bundle 缺少 cards 字段(应为卡片数组)"
365
+ };
366
+ const meta = parsed.meta;
367
+ if (meta.schemaVersion !== 1) return {
368
+ ok: false,
369
+ error: `不支持的 schemaVersion: ${String(meta.schemaVersion)}(本机支持 1)`
370
+ };
371
+ if (!Array.isArray(parsed.cards)) return {
372
+ ok: false,
373
+ error: "bundle 的 cards 必须是数组"
374
+ };
375
+ const cards = parsed.cards;
376
+ if (cards.length > 2e3) return {
377
+ ok: false,
378
+ error: `bundle 卡片数 ${cards.length} 超过上限 ${KNOWLEDGE_BUNDLE_MAX_CARDS} 张,请拆分后再导入`
379
+ };
380
+ for (let index = 0; index < cards.length; index += 1) {
381
+ const card = cards[index];
382
+ const position = `第 ${index + 1} 张卡片(下标 ${index})`;
383
+ if (!isRecord(card)) return {
384
+ ok: false,
385
+ error: `${position}不是 JSON 对象`
386
+ };
387
+ const missing = [];
388
+ if (typeof card.id !== "string" || card.id.trim().length === 0) missing.push("id");
389
+ if (typeof card.title !== "string" || card.title.trim().length === 0) missing.push("title");
390
+ const source = card.source;
391
+ if (!isRecord(source) || typeof source.url !== "string" || source.url.trim().length === 0) missing.push("source.url");
392
+ if (missing.length > 0) return {
393
+ ok: false,
394
+ error: `${position}缺少或非法字段: ${missing.join(", ")}`
395
+ };
396
+ }
397
+ return {
398
+ ok: true,
399
+ bundle: parsed
400
+ };
401
+ }
402
+ /**
403
+ * 拆出 related 与其余字段。
404
+ *
405
+ * related 不能简单「置 undefined」:exactOptionalPropertyTypes 下「没有关联」是
406
+ * 不带这个键,而且基础内容若残留 incoming 里已被丢弃的旧引用,校验会替我们放行
407
+ * 一条本该消失的关联。先摘出来、过滤完再条件回填,两个问题一起消掉。
408
+ */
409
+ function splitRelated(input) {
410
+ const { related, ...base } = input;
411
+ return {
412
+ base,
413
+ related
414
+ };
415
+ }
416
+ /**
417
+ * related 存在性校验用的最小占位卡片。
418
+ *
419
+ * 只为凑出「本地 id ∪ 本批次待写 id」这个集合(validateKnowledgeCard 只读 id),
420
+ * 永远不会被写库——所以字段能给多小给多小。
421
+ */
422
+ function placeholderCard(id) {
423
+ return {
424
+ id,
425
+ title: "",
426
+ summary: "",
427
+ source: {
428
+ type: "manual",
429
+ url: "",
430
+ author: ""
431
+ },
432
+ credibility: "low",
433
+ coreClaims: [],
434
+ factCheck: {
435
+ verified: [],
436
+ discrepancies: [],
437
+ unverifiable: []
438
+ },
439
+ takeaways: [],
440
+ boundaries: [],
441
+ tags: [],
442
+ createdAt: "",
443
+ updatedAt: ""
444
+ };
445
+ }
446
+ /**
447
+ * 纯函数:算出「把 incoming 导入本机库」要做的事,不读文件、不调 store、不写库。
448
+ *
449
+ * 去重键是 source.url(与 knowledge-fs.getByUrl 同口径:trim 后精确相等)。
450
+ * 顺序是有意的:先给每张 incoming 定身份与最终 id,再改 related(此时映射表已完整),
451
+ * 最后统一过 validate——任何一张校验失败只进 invalid,不影响其它卡片。
452
+ */
453
+ function planKnowledgeImport(local, incoming, mode, options = {}) {
454
+ const localIds = new Set(local.map((card) => card.id));
455
+ const localByUrl = /* @__PURE__ */ new Map();
456
+ for (const card of local) {
457
+ const url = typeof card.source?.url === "string" ? card.source.url.trim() : "";
458
+ if (url.length > 0 && !localByUrl.has(url)) localByUrl.set(url, card);
459
+ }
460
+ const skipped = [];
461
+ const remappedIds = [];
462
+ const pending = [];
463
+ /** 本批次已占用的 id:先到的卡片赢,后到的冲突重生成。 */
464
+ const claimedIds = /* @__PURE__ */ new Set();
465
+ /**
466
+ * remappedIds 的 from 侧。这些 bundle id 已经指向别的卡片(本机同链接卡、或本批更早的卡),
467
+ * 同批次里就不能再拿它当新卡 id——否则同一个 id 在映射表里同时指向两张卡,related 解析变含糊。
468
+ */
469
+ const remappedFrom = /* @__PURE__ */ new Set();
470
+ /** 记录一条 id 重映射:数组给报告与映射表用,集合给「已占用」判定用,两处必须同步。 */
471
+ const recordRemap = (from, to) => {
472
+ remappedIds.push({
473
+ from,
474
+ to
475
+ });
476
+ remappedFrom.add(from);
477
+ };
478
+ for (let index = 0; index < incoming.length; index += 1) {
479
+ const card = incoming[index];
480
+ if (!isRecord(card)) continue;
481
+ const url = typeof card.source?.url === "string" ? card.source.url.trim() : "";
482
+ const existing = url.length > 0 ? localByUrl.get(url) : void 0;
483
+ if (existing) {
484
+ const targetId = existing.id;
485
+ const bundleId = typeof card.id === "string" ? card.id.trim() : "";
486
+ if (bundleId.length > 0 && bundleId !== targetId) recordRemap(bundleId, targetId);
487
+ if (mode === "skip") {
488
+ skipped.push({
489
+ id: targetId,
490
+ title: existing.title,
491
+ url: existing.source.url,
492
+ reason: "source.url 已存在"
493
+ });
494
+ continue;
495
+ }
496
+ const id = targetId;
497
+ claimedIds.add(id);
498
+ const merged = {
499
+ ...existing,
500
+ ...card,
501
+ id,
502
+ createdAt: existing.createdAt
503
+ };
504
+ pending.push({
505
+ kind: "update",
506
+ index,
507
+ id,
508
+ ...splitRelated(merged)
509
+ });
510
+ continue;
511
+ }
512
+ const bundleId = typeof card.id === "string" ? card.id.trim() : "";
513
+ let id = bundleId.length > 0 && bundleId.startsWith("kc_") ? bundleId : generateCardId();
514
+ if (localIds.has(id) || claimedIds.has(id) || remappedFrom.has(id)) {
515
+ const fresh = generateCardId();
516
+ recordRemap(id, fresh);
517
+ id = fresh;
518
+ }
519
+ claimedIds.add(id);
520
+ const createdAt = typeof card.createdAt === "string" ? card.createdAt : void 0;
521
+ const created = createdAt === void 0 ? {
522
+ ...card,
523
+ id
524
+ } : {
525
+ ...card,
526
+ id,
527
+ createdAt
528
+ };
529
+ pending.push({
530
+ kind: "create",
531
+ index,
532
+ id,
533
+ ...splitRelated(created)
534
+ });
535
+ }
536
+ const pendingIds = new Set(pending.map((item) => item.id));
537
+ const knownIds = /* @__PURE__ */ new Set([...localIds, ...pendingIds]);
538
+ const remap = new Map(remappedIds.map((entry) => [entry.from, entry.to]));
539
+ const existingForValidation = [...local, ...[...pendingIds].map(placeholderCard)];
540
+ const create = [];
541
+ const update = [];
542
+ const droppedRelated = [];
543
+ const invalid = [];
544
+ const nowIso = options.now?.toISOString();
545
+ for (const item of pending) {
546
+ let related;
547
+ if (Array.isArray(item.related) && item.related.length > 0) {
548
+ const mapped = item.related.map((value) => remap.get(value) ?? value);
549
+ const kept = mapped.filter((value) => knownIds.has(value));
550
+ const missing = Array.from(new Set(mapped.filter((value) => !knownIds.has(value))));
551
+ if (missing.length > 0) droppedRelated.push({
552
+ id: item.id,
553
+ missing
554
+ });
555
+ related = kept.length > 0 ? Array.from(new Set(kept)) : void 0;
556
+ }
557
+ const input = related === void 0 ? item.base : {
558
+ ...item.base,
559
+ related
560
+ };
561
+ const validation = validateKnowledgeCard(input, existingForValidation);
562
+ if (!validation.ok || !validation.card) {
563
+ const titleText = typeof input.title === "string" && input.title.trim().length > 0 ? input.title : void 0;
564
+ invalid.push({
565
+ index: item.index,
566
+ error: validation.error ?? "未知校验错误",
567
+ ...titleText === void 0 ? {} : { title: titleText }
568
+ });
569
+ continue;
570
+ }
571
+ const card = nowIso === void 0 ? validation.card : {
572
+ ...validation.card,
573
+ updatedAt: nowIso
574
+ };
575
+ if (item.kind === "create") create.push(card);
576
+ else update.push(card);
577
+ }
578
+ return {
579
+ create,
580
+ update,
581
+ skipped,
582
+ droppedRelated,
583
+ invalid,
584
+ remappedIds
585
+ };
586
+ }
587
+ //#endregion
164
588
  //#region src/client/shell-faces.ts
165
589
  /**
166
590
  * Shell 面(最小结构镜像):client-ui-knowledge 不 import
@@ -231,6 +655,51 @@ window.__ModuleLoader__.load({
231
655
  return stale;
232
656
  }
233
657
  //#endregion
658
+ //#region src/client/import-export.ts
659
+ /**
660
+ * 知识库导入 / 导出的纯函数面(可 node 单测,不 import react)。
661
+ *
662
+ * 视图与单测共用同一份实现:导出文件名、导出文本、计划摘要、文件文本解析包装。
663
+ * 落盘格式与去重语义全部来自 @dshtrading/knowledge 的 bundle 模块,本模块只负责
664
+ * 「UI 要的那几个数 / 那一段文本」,不复制任何判定逻辑——同一事实只有一个家。
665
+ */
666
+ /** 两位补零(月份从 0 起、时间各段都可能是一位数)。 */
667
+ function pad2$1(value) {
668
+ return String(value).padStart(2, "0");
669
+ }
670
+ /**
671
+ * 导出文件名:'knowledge-YYYYMMDD-HHmmss.json'(本地时间)。
672
+ *
673
+ * 用本地时间而不是 UTC:文件名是给人看、给人找的,用户按自己墙上的时间认文件;
674
+ * 文件内部 meta.exportedAt 才是 UTC 的绝对时刻。
675
+ */
676
+ function exportKnowledgeFileName(now) {
677
+ return `knowledge-${`${now.getFullYear()}${pad2$1(now.getMonth() + 1)}${pad2$1(now.getDate())}`}-${`${pad2$1(now.getHours())}${pad2$1(now.getMinutes())}${pad2$1(now.getSeconds())}`}.json`;
678
+ }
679
+ /** 导出文本:卡片列表 → bundle → 落盘文本(换行与缩进口径见 serializeKnowledgeBundle)。 */
680
+ function exportKnowledgeText(cards, now) {
681
+ return serializeKnowledgeBundle(createKnowledgeBundle(cards, { now }));
682
+ }
683
+ /** 导入预览要显示的五个计数(预览面板与结果提示共用同一份口径)。 */
684
+ function importPlanSummary(plan) {
685
+ return {
686
+ create: plan.create.length,
687
+ update: plan.update.length,
688
+ skip: plan.skipped.length,
689
+ dropped: plan.droppedRelated.length,
690
+ invalid: plan.invalid.length
691
+ };
692
+ }
693
+ /**
694
+ * 解析文件文本(薄包装)。
695
+ *
696
+ * 保持单一解析入口:视图只经这里碰 bundle 文本,将来换解析实现或加护栏时
697
+ * 视图与单测一起跟着走,不会出现「预览用一种读法、写库用另一种读法」。
698
+ */
699
+ function parseImportFileText(text) {
700
+ return parseKnowledgeBundle(text);
701
+ }
702
+ //#endregion
234
703
  //#region src/client/icons.tsx
235
704
  /** 放大镜搜索图标。 */
236
705
  function IconSearch({ size = 14 }) {
@@ -10157,7 +10626,7 @@ window.__ModuleLoader__.load({
10157
10626
  });
10158
10627
  //#endregion
10159
10628
  //#region \0dsh-css:/home/runner/work/dsh-trading/dsh-trading/packages/client-ui-knowledge/src/client/KnowledgeView.module.css.mjs
10160
- const css = ".bZAtrq_root{background-color:var(--dsw-futu-bg-surface);width:100%;height:100%;color:var(--dsw-futu-text-primary);user-select:none;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;display:flex;position:relative;overflow:hidden}.bZAtrq_toolbar{background-color:var(--dsw-futu-bg-base);border-bottom:1px solid var(--dsw-futu-border-subtle);z-index:10;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px;padding:8px 12px;display:flex}.bZAtrq_filtersGroup{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.bZAtrq_searchBox{background-color:var(--dsw-futu-bg-surface);border:1px solid var(--dsw-futu-border-subtle);min-width:220px;color:var(--dsw-futu-text-primary);border-radius:4px;align-items:center;gap:6px;padding:4px 8px;display:flex}.bZAtrq_searchBox:focus-within{border-color:var(--dsw-futu-border-active);box-shadow:0 0 0 1px var(--dsw-futu-border-active)}.bZAtrq_searchIcon{color:var(--dsw-futu-text-muted);align-items:center;display:flex}.bZAtrq_searchInput{color:var(--dsw-futu-text-primary);background:0 0;border:none;outline:none;width:100%;font-size:12px}.bZAtrq_searchInput::placeholder{color:var(--dsw-futu-text-muted)}.bZAtrq_filterSelect{border:1px solid var(--dsw-futu-border-subtle);background-color:var(--dsw-futu-bg-surface);color:var(--dsw-futu-text-primary);cursor:pointer;border-radius:4px;outline:none;padding:4px 8px;font-size:12px}.bZAtrq_filterSelect:focus{border-color:var(--dsw-futu-border-active)}.bZAtrq_resetBtn{border:1px solid var(--dsw-futu-border-subtle);color:var(--dsw-futu-text-secondary);cursor:pointer;background-color:#0000;border-radius:4px;padding:4px 8px;font-size:11px}.bZAtrq_resetBtn:hover{background-color:var(--dsw-futu-bg-surface-hover);color:var(--dsw-futu-text-primary)}.bZAtrq_statsText{color:var(--dsw-futu-text-muted);font-size:11px;font-family:var(--dsw-futu-font-mono);white-space:nowrap}.bZAtrq_mainArea{flex:1;width:100%;height:100%;position:relative;overflow:hidden}.bZAtrq_graphCanvas{cursor:grab;width:100%;height:100%}.bZAtrq_graphCanvas:active{cursor:grabbing}.bZAtrq_emptyState{height:100%;color:var(--dsw-futu-text-muted);text-align:center;flex-direction:column;justify-content:center;align-items:center;gap:12px;padding:32px;display:flex}.bZAtrq_emptyIcon{color:var(--dsw-futu-text-muted);opacity:.7}.bZAtrq_emptyHint{max-width:360px;font-size:13px;line-height:1.6}.bZAtrq_drawer{background-color:var(--dsw-futu-bg-base);border-left:1px solid var(--dsw-futu-border-default);z-index:50;user-select:text;flex-direction:column;width:380px;max-width:85%;animation:.2s ease-out bZAtrq_slideIn;display:flex;position:absolute;top:0;bottom:0;right:0;box-shadow:-4px 0 16px #00000014}@keyframes bZAtrq_slideIn{0%{transform:translate(100%)}to{transform:translate(0)}}.bZAtrq_drawerHeader{border-bottom:1px solid var(--dsw-futu-border-subtle);background-color:var(--dsw-futu-bg-surface);justify-content:space-between;align-items:flex-start;gap:12px;padding:14px 16px;display:flex}.bZAtrq_drawerTitleGroup{flex:1}.bZAtrq_drawerTitle{color:var(--dsw-futu-text-primary);margin-bottom:6px;font-size:14px;font-weight:600;line-height:1.4}.bZAtrq_metaRow{color:var(--dsw-futu-text-muted);flex-wrap:wrap;align-items:center;gap:8px;font-size:11px;display:flex}.bZAtrq_badge{text-transform:uppercase;border-radius:3px;align-items:center;padding:2px 6px;font-size:10px;font-weight:500;display:inline-flex}.bZAtrq_badgeHigh{color:var(--dsw-futu-down);background-color:#2ba4711f}.bZAtrq_badgeMedium{color:#b88200;background-color:#f6bd1626}.bZAtrq_badgeLow{color:var(--dsw-futu-up);background-color:#e645451f}.bZAtrq_badgePlatform{color:#2563eb;background-color:#3b82f61a}.bZAtrq_closeBtn{color:var(--dsw-futu-text-muted);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:2px 6px;font-size:16px}.bZAtrq_closeBtn:hover{background-color:var(--dsw-futu-bg-surface-hover);color:var(--dsw-futu-text-primary)}.bZAtrq_drawerBody{flex-direction:column;flex:1;gap:16px;padding:16px;display:flex;overflow-y:auto}.bZAtrq_section{flex-direction:column;gap:6px;display:flex}.bZAtrq_sectionLabel{color:var(--dsw-futu-text-secondary);font-size:12px;font-weight:600}.bZAtrq_claimsList{color:var(--dsw-futu-text-primary);margin:0;padding-left:18px;font-size:12px;line-height:1.5}.bZAtrq_claimsList li{margin-bottom:4px}.bZAtrq_factBucket{border-radius:4px;margin-bottom:6px;padding:8px 10px;font-size:11px;line-height:1.4}.bZAtrq_factVerified{border-left:3px solid var(--dsw-futu-down);background-color:#2ba4710f}.bZAtrq_factDiscrepancies{background-color:#f6bd1614;border-left:3px solid #f6bd16}.bZAtrq_factUnverifiable{border-left:3px solid var(--dsw-futu-text-muted);background-color:#8e95a314}.bZAtrq_bucketTitle{margin-bottom:4px;font-weight:600}.bZAtrq_bucketList{margin:0;padding-left:14px}.bZAtrq_tagPills{flex-wrap:wrap;gap:6px;display:flex}.bZAtrq_tagPill{background-color:var(--dsw-futu-bg-surface);border:1px solid var(--dsw-futu-border-subtle);color:var(--dsw-futu-text-secondary);border-radius:12px;padding:2px 8px;font-size:11px}.bZAtrq_relatedItem{background-color:var(--dsw-futu-bg-surface);color:#2563eb;cursor:pointer;border:1px solid var(--dsw-futu-border-subtle);border-radius:4px;margin-bottom:4px;padding:4px 8px;font-size:11px}.bZAtrq_relatedItem:hover{background-color:var(--dsw-futu-bg-surface-hover);text-decoration:underline}.bZAtrq_drawerFooter{border-top:1px solid var(--dsw-futu-border-subtle);background-color:var(--dsw-futu-bg-surface);padding:12px 16px}.bZAtrq_openSourceBtn{text-align:center;background-color:var(--dsw-futu-border-active);color:#fff;cursor:pointer;border-radius:4px;width:100%;padding:6px 12px;font-size:12px;font-weight:500;text-decoration:none;display:block}.bZAtrq_openSourceBtn:hover{opacity:.9}";
10629
+ const css = ".bZAtrq_root{background-color:var(--dsw-futu-bg-surface);width:100%;height:100%;color:var(--dsw-futu-text-primary);user-select:none;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;display:flex;position:relative;overflow:hidden}.bZAtrq_toolbar{background-color:var(--dsw-futu-bg-base);border-bottom:1px solid var(--dsw-futu-border-subtle);z-index:10;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px;padding:8px 12px;display:flex}.bZAtrq_filtersGroup{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.bZAtrq_searchBox{background-color:var(--dsw-futu-bg-surface);border:1px solid var(--dsw-futu-border-subtle);min-width:220px;color:var(--dsw-futu-text-primary);border-radius:4px;align-items:center;gap:6px;padding:4px 8px;display:flex}.bZAtrq_searchBox:focus-within{border-color:var(--dsw-futu-border-active);box-shadow:0 0 0 1px var(--dsw-futu-border-active)}.bZAtrq_searchIcon{color:var(--dsw-futu-text-muted);align-items:center;display:flex}.bZAtrq_searchInput{color:var(--dsw-futu-text-primary);background:0 0;border:none;outline:none;width:100%;font-size:12px}.bZAtrq_searchInput::placeholder{color:var(--dsw-futu-text-muted)}.bZAtrq_filterSelect{border:1px solid var(--dsw-futu-border-subtle);background-color:var(--dsw-futu-bg-surface);color:var(--dsw-futu-text-primary);cursor:pointer;border-radius:4px;outline:none;padding:4px 8px;font-size:12px}.bZAtrq_filterSelect:focus{border-color:var(--dsw-futu-border-active)}.bZAtrq_resetBtn{border:1px solid var(--dsw-futu-border-subtle);color:var(--dsw-futu-text-secondary);cursor:pointer;background-color:#0000;border-radius:4px;padding:4px 8px;font-size:11px}.bZAtrq_resetBtn:hover{background-color:var(--dsw-futu-bg-surface-hover);color:var(--dsw-futu-text-primary)}.bZAtrq_statsText{color:var(--dsw-futu-text-muted);font-size:11px;font-family:var(--dsw-futu-font-mono);white-space:nowrap}.bZAtrq_mainArea{flex:1;width:100%;height:100%;position:relative;overflow:hidden}.bZAtrq_graphCanvas{cursor:grab;width:100%;height:100%}.bZAtrq_graphCanvas:active{cursor:grabbing}.bZAtrq_emptyState{height:100%;color:var(--dsw-futu-text-muted);text-align:center;flex-direction:column;justify-content:center;align-items:center;gap:12px;padding:32px;display:flex}.bZAtrq_emptyIcon{color:var(--dsw-futu-text-muted);opacity:.7}.bZAtrq_emptyHint{max-width:360px;font-size:13px;line-height:1.6}.bZAtrq_drawer{background-color:var(--dsw-futu-bg-base);border-left:1px solid var(--dsw-futu-border-default);z-index:50;user-select:text;flex-direction:column;width:380px;max-width:85%;animation:.2s ease-out bZAtrq_slideIn;display:flex;position:absolute;top:0;bottom:0;right:0;box-shadow:-4px 0 16px #00000014}@keyframes bZAtrq_slideIn{0%{transform:translate(100%)}to{transform:translate(0)}}.bZAtrq_drawerHeader{border-bottom:1px solid var(--dsw-futu-border-subtle);background-color:var(--dsw-futu-bg-surface);justify-content:space-between;align-items:flex-start;gap:12px;padding:14px 16px;display:flex}.bZAtrq_drawerTitleGroup{flex:1}.bZAtrq_drawerTitle{color:var(--dsw-futu-text-primary);margin-bottom:6px;font-size:14px;font-weight:600;line-height:1.4}.bZAtrq_metaRow{color:var(--dsw-futu-text-muted);flex-wrap:wrap;align-items:center;gap:8px;font-size:11px;display:flex}.bZAtrq_badge{text-transform:uppercase;border-radius:3px;align-items:center;padding:2px 6px;font-size:10px;font-weight:500;display:inline-flex}.bZAtrq_badgeHigh{color:var(--dsw-futu-down);background-color:#2ba4711f}.bZAtrq_badgeMedium{color:#b88200;background-color:#f6bd1626}.bZAtrq_badgeLow{color:var(--dsw-futu-up);background-color:#e645451f}.bZAtrq_badgePlatform{color:#2563eb;background-color:#3b82f61a}.bZAtrq_closeBtn{color:var(--dsw-futu-text-muted);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:2px 6px;font-size:16px}.bZAtrq_closeBtn:hover{background-color:var(--dsw-futu-bg-surface-hover);color:var(--dsw-futu-text-primary)}.bZAtrq_drawerBody{flex-direction:column;flex:1;gap:16px;padding:16px;display:flex;overflow-y:auto}.bZAtrq_section{flex-direction:column;gap:6px;display:flex}.bZAtrq_sectionLabel{color:var(--dsw-futu-text-secondary);font-size:12px;font-weight:600}.bZAtrq_claimsList{color:var(--dsw-futu-text-primary);margin:0;padding-left:18px;font-size:12px;line-height:1.5}.bZAtrq_claimsList li{margin-bottom:4px}.bZAtrq_factBucket{border-radius:4px;margin-bottom:6px;padding:8px 10px;font-size:11px;line-height:1.4}.bZAtrq_factVerified{border-left:3px solid var(--dsw-futu-down);background-color:#2ba4710f}.bZAtrq_factDiscrepancies{background-color:#f6bd1614;border-left:3px solid #f6bd16}.bZAtrq_factUnverifiable{border-left:3px solid var(--dsw-futu-text-muted);background-color:#8e95a314}.bZAtrq_bucketTitle{margin-bottom:4px;font-weight:600}.bZAtrq_bucketList{margin:0;padding-left:14px}.bZAtrq_tagPills{flex-wrap:wrap;gap:6px;display:flex}.bZAtrq_tagPill{background-color:var(--dsw-futu-bg-surface);border:1px solid var(--dsw-futu-border-subtle);color:var(--dsw-futu-text-secondary);border-radius:12px;padding:2px 8px;font-size:11px}.bZAtrq_relatedItem{background-color:var(--dsw-futu-bg-surface);color:#2563eb;cursor:pointer;border:1px solid var(--dsw-futu-border-subtle);border-radius:4px;margin-bottom:4px;padding:4px 8px;font-size:11px}.bZAtrq_relatedItem:hover{background-color:var(--dsw-futu-bg-surface-hover);text-decoration:underline}.bZAtrq_drawerFooter{border-top:1px solid var(--dsw-futu-border-subtle);background-color:var(--dsw-futu-bg-surface);padding:12px 16px}.bZAtrq_openSourceBtn{text-align:center;background-color:var(--dsw-futu-border-active);color:#fff;cursor:pointer;border-radius:4px;width:100%;padding:6px 12px;font-size:12px;font-weight:500;text-decoration:none;display:block}.bZAtrq_openSourceBtn:hover{opacity:.9}.bZAtrq_ioGroup{align-items:center;gap:8px;display:flex}.bZAtrq_ioBtn{border:1px solid var(--dsw-futu-border-subtle);color:var(--dsw-futu-text-secondary);cursor:pointer;white-space:nowrap;background-color:#0000;border-radius:4px;padding:4px 8px;font-size:11px}.bZAtrq_ioBtn:hover:not(:disabled){background-color:var(--dsw-futu-bg-surface-hover);color:var(--dsw-futu-text-primary)}.bZAtrq_ioBtn:focus-visible{border-color:var(--dsw-futu-border-active);box-shadow:0 0 0 1px var(--dsw-futu-border-active);outline:none}.bZAtrq_ioBtn:disabled{opacity:.45;cursor:not-allowed}.bZAtrq_ioBtnPrimary{background-color:var(--dsw-futu-border-active);border-color:var(--dsw-futu-border-active);color:#fff}.bZAtrq_ioBtnPrimary:hover:not(:disabled){background-color:var(--dsw-futu-border-active);color:#fff;opacity:.9}.bZAtrq_ioFileInput{display:none}.bZAtrq_ioOverlay{z-index:60;pointer-events:none;align-items:flex-start;padding:12px;display:flex;position:absolute;inset:0}.bZAtrq_ioPanel{pointer-events:auto;background-color:var(--dsw-futu-bg-base);border:1px solid var(--dsw-futu-border-default);user-select:text;border-radius:4px;flex-direction:column;width:400px;max-width:100%;max-height:100%;animation:.16s ease-out bZAtrq_ioPanelIn;display:flex;overflow-y:auto;box-shadow:0 4px 16px #0000001f}@keyframes bZAtrq_ioPanelIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.bZAtrq_ioPanelHeader{border-bottom:1px solid var(--dsw-futu-border-subtle);background-color:var(--dsw-futu-bg-surface);justify-content:space-between;align-items:center;gap:12px;padding:10px 12px;display:flex}.bZAtrq_ioPanelTitle{color:var(--dsw-futu-text-primary);font-size:12px;font-weight:600}.bZAtrq_ioPanelBody{flex-direction:column;gap:10px;padding:12px;display:flex}.bZAtrq_ioFileHint{color:var(--dsw-futu-text-muted);word-break:break-all;font-size:11px;line-height:1.5}.bZAtrq_ioModes{flex-direction:column;gap:6px;display:flex}.bZAtrq_ioMode{color:var(--dsw-futu-text-secondary);cursor:pointer;align-items:flex-start;gap:6px;font-size:11px;line-height:1.5;display:flex}.bZAtrq_ioMode input{accent-color:var(--dsw-futu-border-active);flex-shrink:0;margin:2px 0 0}.bZAtrq_ioCounts{grid-template-columns:repeat(5,minmax(0,1fr));gap:6px;display:grid}.bZAtrq_ioCount{background-color:var(--dsw-futu-bg-surface);border:1px solid var(--dsw-futu-border-subtle);border-radius:4px;flex-direction:column;align-items:center;gap:2px;padding:6px 4px;display:flex}.bZAtrq_ioCountValue{color:var(--dsw-futu-text-primary);font-size:13px;font-weight:600;font-family:var(--dsw-futu-font-mono)}.bZAtrq_ioCountLabel{color:var(--dsw-futu-text-muted);white-space:nowrap;font-size:10px}.bZAtrq_ioInvalid{border-left:3px solid var(--dsw-futu-up);background-color:#e645450f;border-radius:4px;flex-direction:column;gap:4px;padding:8px 10px;display:flex}.bZAtrq_ioInvalidTitle{color:var(--dsw-futu-up);font-size:11px;font-weight:600}.bZAtrq_ioInvalidList{color:var(--dsw-futu-text-primary);word-break:break-word;margin:0;padding-left:14px;font-size:11px;line-height:1.5}.bZAtrq_ioNotice{color:var(--dsw-futu-text-primary);word-break:break-word;border-radius:4px;padding:8px 10px;font-size:11px;line-height:1.5}.bZAtrq_ioNoticeError{border-left:3px solid var(--dsw-futu-up);background-color:#e6454514}.bZAtrq_ioNoticeOk{border-left:3px solid var(--dsw-futu-down);background-color:#2ba47114}.bZAtrq_ioPanelFooter{border-top:1px solid var(--dsw-futu-border-subtle);background-color:var(--dsw-futu-bg-surface);justify-content:flex-end;align-items:center;gap:8px;padding:10px 12px;display:flex}.bZAtrq_ioBanner{margin:8px 12px 0}";
10161
10630
  const tagId = "@dshtrading/client-ui-knowledge/KnowledgeView.module.css";
10162
10631
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
10163
10632
  const tag = document.createElement("style");
@@ -10167,47 +10636,72 @@ window.__ModuleLoader__.load({
10167
10636
  document.head.appendChild(tag);
10168
10637
  }
10169
10638
  var KnowledgeView_module_css_default = {
10170
- "filterSelect": "bZAtrq_filterSelect",
10171
- "metaRow": "bZAtrq_metaRow",
10172
- "badgeMedium": "bZAtrq_badgeMedium",
10173
- "drawerHeader": "bZAtrq_drawerHeader",
10174
- "badgeLow": "bZAtrq_badgeLow",
10175
- "bucketTitle": "bZAtrq_bucketTitle",
10176
- "root": "bZAtrq_root",
10177
- "graphCanvas": "bZAtrq_graphCanvas",
10178
- "badge": "bZAtrq_badge",
10179
- "searchIcon": "bZAtrq_searchIcon",
10180
10639
  "emptyState": "bZAtrq_emptyState",
10181
- "resetBtn": "bZAtrq_resetBtn",
10182
- "factUnverifiable": "bZAtrq_factUnverifiable",
10183
- "badgePlatform": "bZAtrq_badgePlatform",
10184
- "emptyIcon": "bZAtrq_emptyIcon",
10185
- "statsText": "bZAtrq_statsText",
10186
- "factVerified": "bZAtrq_factVerified",
10187
- "slideIn": "bZAtrq_slideIn",
10188
- "closeBtn": "bZAtrq_closeBtn",
10189
- "section": "bZAtrq_section",
10190
- "openSourceBtn": "bZAtrq_openSourceBtn",
10191
- "factDiscrepancies": "bZAtrq_factDiscrepancies",
10192
- "drawerFooter": "bZAtrq_drawerFooter",
10193
10640
  "emptyHint": "bZAtrq_emptyHint",
10194
- "tagPills": "bZAtrq_tagPills",
10641
+ "claimsList": "bZAtrq_claimsList",
10195
10642
  "searchInput": "bZAtrq_searchInput",
10196
- "searchBox": "bZAtrq_searchBox",
10643
+ "metaRow": "bZAtrq_metaRow",
10644
+ "ioBtn": "bZAtrq_ioBtn",
10645
+ "ioCountLabel": "bZAtrq_ioCountLabel",
10646
+ "ioInvalid": "bZAtrq_ioInvalid",
10647
+ "ioFileInput": "bZAtrq_ioFileInput",
10648
+ "bucketTitle": "bZAtrq_bucketTitle",
10649
+ "bucketList": "bZAtrq_bucketList",
10650
+ "ioNotice": "bZAtrq_ioNotice",
10651
+ "ioNoticeError": "bZAtrq_ioNoticeError",
10652
+ "ioPanelHeader": "bZAtrq_ioPanelHeader",
10197
10653
  "badgeHigh": "bZAtrq_badgeHigh",
10198
- "drawerBody": "bZAtrq_drawerBody",
10199
- "filtersGroup": "bZAtrq_filtersGroup",
10200
10654
  "sectionLabel": "bZAtrq_sectionLabel",
10655
+ "ioPanelFooter": "bZAtrq_ioPanelFooter",
10656
+ "openSourceBtn": "bZAtrq_openSourceBtn",
10657
+ "factDiscrepancies": "bZAtrq_factDiscrepancies",
10658
+ "drawerBody": "bZAtrq_drawerBody",
10201
10659
  "tagPill": "bZAtrq_tagPill",
10202
- "relatedItem": "bZAtrq_relatedItem",
10660
+ "filtersGroup": "bZAtrq_filtersGroup",
10661
+ "ioPanel": "bZAtrq_ioPanel",
10662
+ "ioPanelTitle": "bZAtrq_ioPanelTitle",
10663
+ "ioPanelBody": "bZAtrq_ioPanelBody",
10664
+ "drawerFooter": "bZAtrq_drawerFooter",
10665
+ "badgeMedium": "bZAtrq_badgeMedium",
10666
+ "ioInvalidTitle": "bZAtrq_ioInvalidTitle",
10667
+ "closeBtn": "bZAtrq_closeBtn",
10668
+ "toolbar": "bZAtrq_toolbar",
10669
+ "resetBtn": "bZAtrq_resetBtn",
10670
+ "ioCount": "bZAtrq_ioCount",
10203
10671
  "drawer": "bZAtrq_drawer",
10204
- "mainArea": "bZAtrq_mainArea",
10672
+ "tagPills": "bZAtrq_tagPills",
10673
+ "searchBox": "bZAtrq_searchBox",
10674
+ "factVerified": "bZAtrq_factVerified",
10675
+ "ioInvalidList": "bZAtrq_ioInvalidList",
10676
+ "ioNoticeOk": "bZAtrq_ioNoticeOk",
10677
+ "ioOverlay": "bZAtrq_ioOverlay",
10678
+ "section": "bZAtrq_section",
10679
+ "graphCanvas": "bZAtrq_graphCanvas",
10680
+ "ioCounts": "bZAtrq_ioCounts",
10681
+ "ioCountValue": "bZAtrq_ioCountValue",
10682
+ "ioBtnPrimary": "bZAtrq_ioBtnPrimary",
10683
+ "statsText": "bZAtrq_statsText",
10205
10684
  "drawerTitle": "bZAtrq_drawerTitle",
10685
+ "factUnverifiable": "bZAtrq_factUnverifiable",
10686
+ "ioGroup": "bZAtrq_ioGroup",
10687
+ "relatedItem": "bZAtrq_relatedItem",
10688
+ "filterSelect": "bZAtrq_filterSelect",
10689
+ "badge": "bZAtrq_badge",
10690
+ "ioPanelIn": "bZAtrq_ioPanelIn",
10206
10691
  "drawerTitleGroup": "bZAtrq_drawerTitleGroup",
10207
- "toolbar": "bZAtrq_toolbar",
10208
- "claimsList": "bZAtrq_claimsList",
10692
+ "ioModes": "bZAtrq_ioModes",
10693
+ "badgeLow": "bZAtrq_badgeLow",
10694
+ "ioFileHint": "bZAtrq_ioFileHint",
10695
+ "ioMode": "bZAtrq_ioMode",
10696
+ "ioBanner": "bZAtrq_ioBanner",
10697
+ "slideIn": "bZAtrq_slideIn",
10698
+ "emptyIcon": "bZAtrq_emptyIcon",
10699
+ "root": "bZAtrq_root",
10700
+ "searchIcon": "bZAtrq_searchIcon",
10701
+ "mainArea": "bZAtrq_mainArea",
10702
+ "badgePlatform": "bZAtrq_badgePlatform",
10209
10703
  "factBucket": "bZAtrq_factBucket",
10210
- "bucketList": "bZAtrq_bucketList"
10704
+ "drawerHeader": "bZAtrq_drawerHeader"
10211
10705
  };
10212
10706
  //#endregion
10213
10707
  //#region src/client/KnowledgeView.tsx
@@ -10219,6 +10713,7 @@ window.__ModuleLoader__.load({
10219
10713
  * 2. 主画布区(Obsidian 式 force-graph 力导知识图谱网络)
10220
10714
  * 3. 右侧详情抽屉(卡片全文:核心论点 + 事实核查三桶 + 复用经验 + 边界避坑 + 来源链接)
10221
10715
  * 4. 空态引导与状态持久化 (dshtrading.knowledge.view.v1)
10716
+ * 5. 导出 / 导入动作与导入预览浮动层(非模态:抽屉与图谱照常可交互)
10222
10717
  */
10223
10718
  const STORE_KEY = "dshtrading.knowledge.view.v1";
10224
10719
  const DEFAULT_STORED = {
@@ -10240,6 +10735,13 @@ window.__ModuleLoader__.load({
10240
10735
  const [cards, setCards] = (0, react.useState)([]);
10241
10736
  const [selectedCard, setSelectedCard] = (0, react.useState)(null);
10242
10737
  const [loading, setLoading] = (0, react.useState)(true);
10738
+ const [draft, setDraft] = (0, react.useState)(null);
10739
+ const [importError, setImportError] = (0, react.useState)(null);
10740
+ const [applied, setApplied] = (0, react.useState)(null);
10741
+ const [importing, setImporting] = (0, react.useState)(false);
10742
+ /** 导出超出导入侧上限(2000 张 / 8 MiB)时的可见提示,下一次导出成功或被缩小筛选后消失。 */
10743
+ const [exportTooLarge, setExportTooLarge] = (0, react.useState)(false);
10744
+ const fileInputRef = (0, react.useRef)(null);
10243
10745
  const graphRef = (0, react.useRef)(null);
10244
10746
  const bridgeRef = (0, react.useRef)(bridge);
10245
10747
  if (bridgeRef.current === null) bridgeRef.current = bridge;
@@ -10344,6 +10846,11 @@ window.__ModuleLoader__.load({
10344
10846
  coAuthor: false
10345
10847
  });
10346
10848
  }, [filteredCards]);
10849
+ const importPlan = (0, react.useMemo)(() => {
10850
+ if (draft === null || draft.bundle === null) return null;
10851
+ return planKnowledgeImport(cards, draft.bundle.cards, draft.mode);
10852
+ }, [draft, cards]);
10853
+ const importCounts = (0, react.useMemo)(() => importPlan === null ? null : importPlanSummary(importPlan), [importPlan]);
10347
10854
  const handleResetFilters = () => {
10348
10855
  setQuery("");
10349
10856
  setSelectedTag("");
@@ -10352,6 +10859,117 @@ window.__ModuleLoader__.load({
10352
10859
  setSelectedSourceType("");
10353
10860
  };
10354
10861
  const hasActiveFilters = Boolean(query || selectedTag || selectedAuthor || selectedCredibility || selectedSourceType);
10862
+ /**
10863
+ * 导出:当前筛选结果(无筛选即全库)直接下载为 JSON 文件,文件名带本地时间戳。
10864
+ *
10865
+ * 导出侧与导入侧同口径(KNOWLEDGE_BUNDLE_MAX_CARDS / KNOWLEDGE_BUNDLE_MAX_BYTES):
10866
+ * 超过上限就一个字节都不下载——否则我们造出的文件自己的导入侧必然拒收,
10867
+ * 用户要到下一次导入才知道白导了一场。
10868
+ */
10869
+ const handleExport = () => {
10870
+ const now = /* @__PURE__ */ new Date();
10871
+ const text = exportKnowledgeText(filteredCards, now);
10872
+ const bytes = new TextEncoder().encode(text).length;
10873
+ if (filteredCards.length > 2e3 || bytes > 8388608) {
10874
+ setExportTooLarge(true);
10875
+ return;
10876
+ }
10877
+ setExportTooLarge(false);
10878
+ const blob = new Blob([text], { type: "application/json" });
10879
+ const url = URL.createObjectURL(blob);
10880
+ const anchor = document.createElement("a");
10881
+ anchor.href = url;
10882
+ anchor.download = exportKnowledgeFileName(now);
10883
+ anchor.click();
10884
+ URL.revokeObjectURL(url);
10885
+ };
10886
+ const closeImportPanel = (0, react.useCallback)(() => {
10887
+ setDraft(null);
10888
+ setImportError(null);
10889
+ setApplied(null);
10890
+ setImporting(false);
10891
+ const input = fileInputRef.current;
10892
+ if (input !== null) input.value = "";
10893
+ }, []);
10894
+ const handleFileChange = async (event) => {
10895
+ const input = event.currentTarget;
10896
+ const file = input.files?.[0] ?? null;
10897
+ input.value = "";
10898
+ if (file === null) return;
10899
+ setApplied(null);
10900
+ let text;
10901
+ try {
10902
+ text = await file.text();
10903
+ } catch (error) {
10904
+ setImportError(error instanceof Error ? error.message : String(error));
10905
+ setDraft({
10906
+ fileName: file.name,
10907
+ text: "",
10908
+ bundle: null,
10909
+ mode: "skip"
10910
+ });
10911
+ return;
10912
+ }
10913
+ const parsed = parseImportFileText(text);
10914
+ if (!parsed.ok) {
10915
+ setImportError(parsed.error);
10916
+ setDraft({
10917
+ fileName: file.name,
10918
+ text,
10919
+ bundle: null,
10920
+ mode: "skip"
10921
+ });
10922
+ return;
10923
+ }
10924
+ setImportError(null);
10925
+ setDraft({
10926
+ fileName: file.name,
10927
+ text,
10928
+ bundle: parsed.bundle,
10929
+ mode: "skip"
10930
+ });
10931
+ };
10932
+ const handleSelectImportMode = (mode) => {
10933
+ setApplied(null);
10934
+ setImportError(null);
10935
+ setDraft((prev) => prev === null ? prev : {
10936
+ ...prev,
10937
+ mode
10938
+ });
10939
+ };
10940
+ const handleConfirmImport = async () => {
10941
+ if (draft === null || draft.bundle === null || importing || applied !== null) return;
10942
+ setImporting(true);
10943
+ setImportError(null);
10944
+ try {
10945
+ const result = await stableBridge.importKnowledgeBundle({
10946
+ mode: draft.mode,
10947
+ bundle: draft.text
10948
+ });
10949
+ if (!result.ok) {
10950
+ setImportError(result.error ?? "");
10951
+ return;
10952
+ }
10953
+ setApplied({
10954
+ created: result.created,
10955
+ updated: result.updated,
10956
+ skipped: result.skipped
10957
+ });
10958
+ await loadCards();
10959
+ } catch (error) {
10960
+ setImportError(error instanceof Error ? error.message : String(error));
10961
+ } finally {
10962
+ setImporting(false);
10963
+ }
10964
+ };
10965
+ (0, react.useEffect)(() => {
10966
+ if (draft === null) return;
10967
+ const onKeyDown = (event) => {
10968
+ if (event.key === "Escape") closeImportPanel();
10969
+ };
10970
+ window.addEventListener("keydown", onKeyDown);
10971
+ return () => window.removeEventListener("keydown", onKeyDown);
10972
+ }, [draft, closeImportPanel]);
10355
10973
  const handleJumpToRelated = (relatedId) => {
10356
10974
  const target = cards.find((c) => c.id === relatedId);
10357
10975
  if (target) {
@@ -10370,294 +10988,513 @@ window.__ModuleLoader__.load({
10370
10988
  for (const c of filteredCards) clusters.add(c.tags[0] ?? "untagged");
10371
10989
  return clusters.size;
10372
10990
  }, [filteredCards]);
10991
+ const exportTitle = filteredCards.length === 0 ? t("kv.io.exportEmpty") : exportTooLarge || filteredCards.length > 2e3 ? t("kv.io.exportTooLarge") : t("kv.io.export");
10373
10992
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10374
10993
  className: KnowledgeView_module_css_default.root,
10375
10994
  "data-dshtrading-knowledge-view": "",
10376
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10377
- className: KnowledgeView_module_css_default.toolbar,
10378
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10379
- className: KnowledgeView_module_css_default.filtersGroup,
10380
- children: [
10381
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10382
- className: KnowledgeView_module_css_default.searchBox,
10383
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10384
- className: KnowledgeView_module_css_default.searchIcon,
10385
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSearch, { size: 13 })
10386
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
10387
- type: "text",
10388
- className: KnowledgeView_module_css_default.searchInput,
10389
- placeholder: t("kv.search.placeholder"),
10390
- value: query,
10391
- onChange: (e) => setQuery(e.target.value)
10392
- })]
10393
- }),
10394
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
10395
- className: KnowledgeView_module_css_default.filterSelect,
10396
- value: selectedTag,
10397
- onChange: (e) => setSelectedTag(e.target.value),
10398
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
10399
- value: "",
10400
- children: t("kv.filter.allTags")
10401
- }), allTags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
10402
- value: tag,
10403
- children: tag
10404
- }, tag))]
10405
- }),
10406
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
10407
- className: KnowledgeView_module_css_default.filterSelect,
10408
- value: selectedAuthor,
10409
- onChange: (e) => setSelectedAuthor(e.target.value),
10410
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
10411
- value: "",
10412
- children: t("kv.filter.allAuthors")
10413
- }), allAuthors.map((author) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
10414
- value: author,
10415
- children: author
10416
- }, author))]
10417
- }),
10418
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
10419
- className: KnowledgeView_module_css_default.filterSelect,
10420
- value: selectedCredibility,
10421
- onChange: (e) => setSelectedCredibility(e.target.value),
10422
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
10423
- value: "",
10424
- children: t("kv.filter.allCredibility")
10425
- }), CREDIBILITY_OPTIONS.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
10426
- value: option.value,
10427
- children: t(option.labelKey)
10428
- }, option.value))]
10429
- }),
10430
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
10431
- className: KnowledgeView_module_css_default.filterSelect,
10432
- value: selectedSourceType,
10433
- onChange: (e) => setSelectedSourceType(e.target.value),
10434
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
10435
- value: "",
10436
- children: t("kv.filter.allSourceTypes")
10437
- }), SOURCE_TYPE_OPTIONS.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
10438
- value: option.value,
10439
- children: t(option.labelKey)
10440
- }, option.value))]
10441
- }),
10442
- hasActiveFilters && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10443
- type: "button",
10444
- className: KnowledgeView_module_css_default.resetBtn,
10445
- onClick: handleResetFilters,
10446
- children: t("kv.filter.reset")
10447
- })
10448
- ]
10449
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10450
- className: KnowledgeView_module_css_default.statsText,
10451
- children: [
10452
- filteredCards.length,
10453
- " ",
10454
- t("kv.stats.cards"),
10455
- " · ",
10456
- clusterCount,
10457
- " ",
10458
- t("kv.stats.clusters")
10459
- ]
10460
- })]
10461
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10462
- className: KnowledgeView_module_css_default.mainArea,
10463
- children: [cards.length === 0 && !loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10464
- className: KnowledgeView_module_css_default.emptyState,
10465
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10466
- className: KnowledgeView_module_css_default.emptyIcon,
10467
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconKnowledge, { size: 40 })
10468
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10469
- className: KnowledgeView_module_css_default.emptyHint,
10470
- children: t("kv.empty.hint")
10471
- })]
10472
- }) : filteredCards.length === 0 && !loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10473
- className: KnowledgeView_module_css_default.emptyState,
10474
- children: [
10475
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10476
- className: KnowledgeView_module_css_default.emptyIcon,
10477
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconKnowledge, { size: 32 })
10478
- }),
10479
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10480
- className: KnowledgeView_module_css_default.emptyHint,
10481
- children: t("kv.empty.filtered")
10482
- }),
10483
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10484
- type: "button",
10485
- className: KnowledgeView_module_css_default.resetBtn,
10486
- onClick: handleResetFilters,
10487
- children: t("kv.filter.reset")
10488
- })
10489
- ]
10490
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10491
- className: KnowledgeView_module_css_default.graphCanvas,
10492
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KnowledgeGraph, {
10493
- ref: graphRef,
10494
- data: graphData,
10495
- selectedCardId: selectedCard?.id,
10496
- onSelectCard: handleSelectCard,
10497
- onTagClick: handleTagClick
10498
- })
10499
- }), selectedCard && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10500
- className: KnowledgeView_module_css_default.drawer,
10501
- role: "dialog",
10502
- "aria-modal": "false",
10503
- children: [
10504
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10505
- className: KnowledgeView_module_css_default.drawerHeader,
10506
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10507
- className: KnowledgeView_module_css_default.drawerTitleGroup,
10508
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10509
- className: KnowledgeView_module_css_default.drawerTitle,
10510
- children: selectedCard.title
10511
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10512
- className: KnowledgeView_module_css_default.metaRow,
10513
- children: [
10514
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10515
- className: `${KnowledgeView_module_css_default.badge} ${KnowledgeView_module_css_default.badgePlatform}`,
10516
- children: selectedCard.source.type
10517
- }),
10518
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10519
- className: `${KnowledgeView_module_css_default.badge} ${selectedCard.credibility === "high" ? KnowledgeView_module_css_default.badgeHigh : selectedCard.credibility === "medium" ? KnowledgeView_module_css_default.badgeMedium : KnowledgeView_module_css_default.badgeLow}`,
10520
- children: selectedCard.credibility
10521
- }),
10522
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: selectedCard.source.author }),
10523
- selectedCard.source.publishedAt && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: selectedCard.source.publishedAt })
10524
- ]
10995
+ children: [
10996
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10997
+ className: KnowledgeView_module_css_default.toolbar,
10998
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10999
+ className: KnowledgeView_module_css_default.filtersGroup,
11000
+ children: [
11001
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11002
+ className: KnowledgeView_module_css_default.searchBox,
11003
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11004
+ className: KnowledgeView_module_css_default.searchIcon,
11005
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSearch, { size: 13 })
11006
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
11007
+ type: "text",
11008
+ className: KnowledgeView_module_css_default.searchInput,
11009
+ placeholder: t("kv.search.placeholder"),
11010
+ value: query,
11011
+ onChange: (e) => setQuery(e.target.value)
10525
11012
  })]
10526
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11013
+ }),
11014
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
11015
+ className: KnowledgeView_module_css_default.filterSelect,
11016
+ value: selectedTag,
11017
+ onChange: (e) => setSelectedTag(e.target.value),
11018
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11019
+ value: "",
11020
+ children: t("kv.filter.allTags")
11021
+ }), allTags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11022
+ value: tag,
11023
+ children: tag
11024
+ }, tag))]
11025
+ }),
11026
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
11027
+ className: KnowledgeView_module_css_default.filterSelect,
11028
+ value: selectedAuthor,
11029
+ onChange: (e) => setSelectedAuthor(e.target.value),
11030
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11031
+ value: "",
11032
+ children: t("kv.filter.allAuthors")
11033
+ }), allAuthors.map((author) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11034
+ value: author,
11035
+ children: author
11036
+ }, author))]
11037
+ }),
11038
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
11039
+ className: KnowledgeView_module_css_default.filterSelect,
11040
+ value: selectedCredibility,
11041
+ onChange: (e) => setSelectedCredibility(e.target.value),
11042
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11043
+ value: "",
11044
+ children: t("kv.filter.allCredibility")
11045
+ }), CREDIBILITY_OPTIONS.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11046
+ value: option.value,
11047
+ children: t(option.labelKey)
11048
+ }, option.value))]
11049
+ }),
11050
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
11051
+ className: KnowledgeView_module_css_default.filterSelect,
11052
+ value: selectedSourceType,
11053
+ onChange: (e) => setSelectedSourceType(e.target.value),
11054
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11055
+ value: "",
11056
+ children: t("kv.filter.allSourceTypes")
11057
+ }), SOURCE_TYPE_OPTIONS.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
11058
+ value: option.value,
11059
+ children: t(option.labelKey)
11060
+ }, option.value))]
11061
+ }),
11062
+ hasActiveFilters && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11063
+ type: "button",
11064
+ className: KnowledgeView_module_css_default.resetBtn,
11065
+ onClick: handleResetFilters,
11066
+ children: t("kv.filter.reset")
11067
+ })
11068
+ ]
11069
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11070
+ className: KnowledgeView_module_css_default.ioGroup,
11071
+ children: [
11072
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11073
+ type: "button",
11074
+ className: KnowledgeView_module_css_default.ioBtn,
11075
+ onClick: handleExport,
11076
+ disabled: filteredCards.length === 0,
11077
+ title: exportTitle,
11078
+ children: t("kv.io.export")
11079
+ }),
11080
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10527
11081
  type: "button",
10528
- className: KnowledgeView_module_css_default.closeBtn,
10529
- onClick: () => setSelectedCard(null),
10530
- "aria-label": "Close",
10531
- children: "✕"
11082
+ className: KnowledgeView_module_css_default.ioBtn,
11083
+ onClick: () => fileInputRef.current?.click(),
11084
+ children: t("kv.io.import")
11085
+ }),
11086
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
11087
+ ref: fileInputRef,
11088
+ type: "file",
11089
+ accept: "application/json,.json",
11090
+ className: KnowledgeView_module_css_default.ioFileInput,
11091
+ "aria-label": t("kv.io.import"),
11092
+ onChange: (event) => {
11093
+ handleFileChange(event);
11094
+ }
11095
+ }),
11096
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11097
+ className: KnowledgeView_module_css_default.statsText,
11098
+ children: [
11099
+ filteredCards.length,
11100
+ " ",
11101
+ t("kv.stats.cards"),
11102
+ " · ",
11103
+ clusterCount,
11104
+ " ",
11105
+ t("kv.stats.clusters")
11106
+ ]
11107
+ })
11108
+ ]
11109
+ })]
11110
+ }),
11111
+ exportTooLarge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11112
+ className: `${KnowledgeView_module_css_default.ioNotice} ${KnowledgeView_module_css_default.ioNoticeError} ${KnowledgeView_module_css_default.ioBanner}`,
11113
+ role: "status",
11114
+ children: t("kv.io.exportTooLarge")
11115
+ }),
11116
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11117
+ className: KnowledgeView_module_css_default.mainArea,
11118
+ children: [
11119
+ cards.length === 0 && !loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11120
+ className: KnowledgeView_module_css_default.emptyState,
11121
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11122
+ className: KnowledgeView_module_css_default.emptyIcon,
11123
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconKnowledge, { size: 40 })
11124
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11125
+ className: KnowledgeView_module_css_default.emptyHint,
11126
+ children: t("kv.empty.hint")
10532
11127
  })]
11128
+ }) : filteredCards.length === 0 && !loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11129
+ className: KnowledgeView_module_css_default.emptyState,
11130
+ children: [
11131
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11132
+ className: KnowledgeView_module_css_default.emptyIcon,
11133
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconKnowledge, { size: 32 })
11134
+ }),
11135
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11136
+ className: KnowledgeView_module_css_default.emptyHint,
11137
+ children: t("kv.empty.filtered")
11138
+ }),
11139
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11140
+ type: "button",
11141
+ className: KnowledgeView_module_css_default.resetBtn,
11142
+ onClick: handleResetFilters,
11143
+ children: t("kv.filter.reset")
11144
+ })
11145
+ ]
11146
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11147
+ className: KnowledgeView_module_css_default.graphCanvas,
11148
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KnowledgeGraph, {
11149
+ ref: graphRef,
11150
+ data: graphData,
11151
+ selectedCardId: selectedCard?.id,
11152
+ onSelectCard: handleSelectCard,
11153
+ onTagClick: handleTagClick
11154
+ })
10533
11155
  }),
10534
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10535
- className: KnowledgeView_module_css_default.drawerBody,
11156
+ selectedCard && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11157
+ className: KnowledgeView_module_css_default.drawer,
11158
+ role: "dialog",
11159
+ "aria-modal": "false",
10536
11160
  children: [
10537
11161
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10538
- className: KnowledgeView_module_css_default.section,
10539
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10540
- className: KnowledgeView_module_css_default.sectionLabel,
10541
- children: t("kv.drawer.coreClaims")
10542
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10543
- className: KnowledgeView_module_css_default.claimsList,
10544
- children: selectedCard.coreClaims.map((claim, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: claim }, idx))
11162
+ className: KnowledgeView_module_css_default.drawerHeader,
11163
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11164
+ className: KnowledgeView_module_css_default.drawerTitleGroup,
11165
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11166
+ className: KnowledgeView_module_css_default.drawerTitle,
11167
+ children: selectedCard.title
11168
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11169
+ className: KnowledgeView_module_css_default.metaRow,
11170
+ children: [
11171
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11172
+ className: `${KnowledgeView_module_css_default.badge} ${KnowledgeView_module_css_default.badgePlatform}`,
11173
+ children: selectedCard.source.type
11174
+ }),
11175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11176
+ className: `${KnowledgeView_module_css_default.badge} ${selectedCard.credibility === "high" ? KnowledgeView_module_css_default.badgeHigh : selectedCard.credibility === "medium" ? KnowledgeView_module_css_default.badgeMedium : KnowledgeView_module_css_default.badgeLow}`,
11177
+ children: selectedCard.credibility
11178
+ }),
11179
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: selectedCard.source.author }),
11180
+ selectedCard.source.publishedAt && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: selectedCard.source.publishedAt })
11181
+ ]
11182
+ })]
11183
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11184
+ type: "button",
11185
+ className: KnowledgeView_module_css_default.closeBtn,
11186
+ onClick: () => setSelectedCard(null),
11187
+ "aria-label": "Close",
11188
+ children: "✕"
10545
11189
  })]
10546
11190
  }),
10547
11191
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10548
- className: KnowledgeView_module_css_default.section,
11192
+ className: KnowledgeView_module_css_default.drawerBody,
10549
11193
  children: [
10550
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10551
- className: KnowledgeView_module_css_default.sectionLabel,
10552
- children: t("kv.drawer.factCheck")
10553
- }),
10554
- selectedCard.factCheck.verified.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10555
- className: `${KnowledgeView_module_css_default.factBucket} ${KnowledgeView_module_css_default.factVerified}`,
10556
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10557
- className: KnowledgeView_module_css_default.bucketTitle,
10558
- children: ["✅ ", t("kv.drawer.verified")]
11194
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11195
+ className: KnowledgeView_module_css_default.section,
11196
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11197
+ className: KnowledgeView_module_css_default.sectionLabel,
11198
+ children: t("kv.drawer.coreClaims")
10559
11199
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10560
- className: KnowledgeView_module_css_default.bucketList,
10561
- children: selectedCard.factCheck.verified.map((v, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: v }, i))
11200
+ className: KnowledgeView_module_css_default.claimsList,
11201
+ children: selectedCard.coreClaims.map((claim, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: claim }, idx))
10562
11202
  })]
10563
11203
  }),
10564
- selectedCard.factCheck.discrepancies.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10565
- className: `${KnowledgeView_module_css_default.factBucket} ${KnowledgeView_module_css_default.factDiscrepancies}`,
10566
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10567
- className: KnowledgeView_module_css_default.bucketTitle,
10568
- children: ["⚠️ ", t("kv.drawer.discrepancies")]
11204
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11205
+ className: KnowledgeView_module_css_default.section,
11206
+ children: [
11207
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11208
+ className: KnowledgeView_module_css_default.sectionLabel,
11209
+ children: t("kv.drawer.factCheck")
11210
+ }),
11211
+ selectedCard.factCheck.verified.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11212
+ className: `${KnowledgeView_module_css_default.factBucket} ${KnowledgeView_module_css_default.factVerified}`,
11213
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11214
+ className: KnowledgeView_module_css_default.bucketTitle,
11215
+ children: ["✅ ", t("kv.drawer.verified")]
11216
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
11217
+ className: KnowledgeView_module_css_default.bucketList,
11218
+ children: selectedCard.factCheck.verified.map((v, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: v }, i))
11219
+ })]
11220
+ }),
11221
+ selectedCard.factCheck.discrepancies.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11222
+ className: `${KnowledgeView_module_css_default.factBucket} ${KnowledgeView_module_css_default.factDiscrepancies}`,
11223
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11224
+ className: KnowledgeView_module_css_default.bucketTitle,
11225
+ children: ["⚠️ ", t("kv.drawer.discrepancies")]
11226
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
11227
+ className: KnowledgeView_module_css_default.bucketList,
11228
+ children: selectedCard.factCheck.discrepancies.map((d, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: d }, i))
11229
+ })]
11230
+ }),
11231
+ selectedCard.factCheck.unverifiable.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11232
+ className: `${KnowledgeView_module_css_default.factBucket} ${KnowledgeView_module_css_default.factUnverifiable}`,
11233
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11234
+ className: KnowledgeView_module_css_default.bucketTitle,
11235
+ children: ["❓ ", t("kv.drawer.unverifiable")]
11236
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
11237
+ className: KnowledgeView_module_css_default.bucketList,
11238
+ children: selectedCard.factCheck.unverifiable.map((u, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: u }, i))
11239
+ })]
11240
+ })
11241
+ ]
11242
+ }),
11243
+ selectedCard.takeaways.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11244
+ className: KnowledgeView_module_css_default.section,
11245
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11246
+ className: KnowledgeView_module_css_default.sectionLabel,
11247
+ children: t("kv.drawer.takeaways")
10569
11248
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10570
- className: KnowledgeView_module_css_default.bucketList,
10571
- children: selectedCard.factCheck.discrepancies.map((d, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: d }, i))
11249
+ className: KnowledgeView_module_css_default.claimsList,
11250
+ children: selectedCard.takeaways.map((item, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: item }, idx))
10572
11251
  })]
10573
11252
  }),
10574
- selectedCard.factCheck.unverifiable.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10575
- className: `${KnowledgeView_module_css_default.factBucket} ${KnowledgeView_module_css_default.factUnverifiable}`,
10576
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10577
- className: KnowledgeView_module_css_default.bucketTitle,
10578
- children: ["❓ ", t("kv.drawer.unverifiable")]
11253
+ selectedCard.boundaries.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11254
+ className: KnowledgeView_module_css_default.section,
11255
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11256
+ className: KnowledgeView_module_css_default.sectionLabel,
11257
+ children: t("kv.drawer.boundaries")
10579
11258
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10580
- className: KnowledgeView_module_css_default.bucketList,
10581
- children: selectedCard.factCheck.unverifiable.map((u, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: u }, i))
11259
+ className: KnowledgeView_module_css_default.claimsList,
11260
+ children: selectedCard.boundaries.map((item, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: item }, idx))
11261
+ })]
11262
+ }),
11263
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11264
+ className: KnowledgeView_module_css_default.section,
11265
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11266
+ className: KnowledgeView_module_css_default.sectionLabel,
11267
+ children: t("kv.filter.tag")
11268
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11269
+ className: KnowledgeView_module_css_default.tagPills,
11270
+ children: selectedCard.tags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11271
+ className: KnowledgeView_module_css_default.tagPill,
11272
+ children: ["#", tag]
11273
+ }, tag))
11274
+ })]
11275
+ }),
11276
+ selectedCard.tickers && selectedCard.tickers.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11277
+ className: KnowledgeView_module_css_default.section,
11278
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11279
+ className: KnowledgeView_module_css_default.sectionLabel,
11280
+ children: t("kv.drawer.tickers")
11281
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11282
+ className: KnowledgeView_module_css_default.tagPills,
11283
+ children: selectedCard.tickers.map((sym) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11284
+ className: KnowledgeView_module_css_default.tagPill,
11285
+ children: ["$", sym]
11286
+ }, sym))
11287
+ })]
11288
+ }),
11289
+ selectedCard.related && selectedCard.related.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11290
+ className: KnowledgeView_module_css_default.section,
11291
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11292
+ className: KnowledgeView_module_css_default.sectionLabel,
11293
+ children: t("kv.drawer.related")
11294
+ }), selectedCard.related.map((relId) => {
11295
+ const relCard = cards.find((c) => c.id === relId);
11296
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11297
+ className: KnowledgeView_module_css_default.relatedItem,
11298
+ onClick: () => handleJumpToRelated(relId),
11299
+ children: ["🔗 ", relCard?.title ?? relId]
11300
+ }, relId);
10582
11301
  })]
10583
11302
  })
10584
11303
  ]
10585
11304
  }),
10586
- selectedCard.takeaways.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10587
- className: KnowledgeView_module_css_default.section,
10588
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10589
- className: KnowledgeView_module_css_default.sectionLabel,
10590
- children: t("kv.drawer.takeaways")
10591
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10592
- className: KnowledgeView_module_css_default.claimsList,
10593
- children: selectedCard.takeaways.map((item, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: item }, idx))
10594
- })]
10595
- }),
10596
- selectedCard.boundaries.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10597
- className: KnowledgeView_module_css_default.section,
10598
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10599
- className: KnowledgeView_module_css_default.sectionLabel,
10600
- children: t("kv.drawer.boundaries")
10601
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10602
- className: KnowledgeView_module_css_default.claimsList,
10603
- children: selectedCard.boundaries.map((item, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: item }, idx))
10604
- })]
10605
- }),
10606
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10607
- className: KnowledgeView_module_css_default.section,
10608
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10609
- className: KnowledgeView_module_css_default.sectionLabel,
10610
- children: t("kv.filter.tag")
10611
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10612
- className: KnowledgeView_module_css_default.tagPills,
10613
- children: selectedCard.tags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10614
- className: KnowledgeView_module_css_default.tagPill,
10615
- children: ["#", tag]
10616
- }, tag))
10617
- })]
10618
- }),
10619
- selectedCard.tickers && selectedCard.tickers.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10620
- className: KnowledgeView_module_css_default.section,
10621
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10622
- className: KnowledgeView_module_css_default.sectionLabel,
10623
- children: t("kv.drawer.tickers")
10624
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10625
- className: KnowledgeView_module_css_default.tagPills,
10626
- children: selectedCard.tickers.map((sym) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10627
- className: KnowledgeView_module_css_default.tagPill,
10628
- children: ["$", sym]
10629
- }, sym))
10630
- })]
10631
- }),
10632
- selectedCard.related && selectedCard.related.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10633
- className: KnowledgeView_module_css_default.section,
10634
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10635
- className: KnowledgeView_module_css_default.sectionLabel,
10636
- children: t("kv.drawer.related")
10637
- }), selectedCard.related.map((relId) => {
10638
- const relCard = cards.find((c) => c.id === relId);
10639
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10640
- className: KnowledgeView_module_css_default.relatedItem,
10641
- onClick: () => handleJumpToRelated(relId),
10642
- children: ["🔗 ", relCard?.title ?? relId]
10643
- }, relId);
10644
- })]
11305
+ selectedCard.source.url && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11306
+ className: KnowledgeView_module_css_default.drawerFooter,
11307
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
11308
+ href: selectedCard.source.url,
11309
+ target: "_blank",
11310
+ rel: "noopener noreferrer",
11311
+ className: KnowledgeView_module_css_default.openSourceBtn,
11312
+ children: [t("kv.drawer.openSource"), " ↗"]
11313
+ })
10645
11314
  })
10646
11315
  ]
10647
11316
  }),
10648
- selectedCard.source.url && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10649
- className: KnowledgeView_module_css_default.drawerFooter,
10650
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
10651
- href: selectedCard.source.url,
10652
- target: "_blank",
10653
- rel: "noopener noreferrer",
10654
- className: KnowledgeView_module_css_default.openSourceBtn,
10655
- children: [t("kv.drawer.openSource"), " ↗"]
11317
+ draft !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11318
+ className: KnowledgeView_module_css_default.ioOverlay,
11319
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11320
+ className: KnowledgeView_module_css_default.ioPanel,
11321
+ role: "dialog",
11322
+ "aria-modal": "false",
11323
+ "aria-label": t("kv.io.dialogTitle"),
11324
+ children: [
11325
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11326
+ className: KnowledgeView_module_css_default.ioPanelHeader,
11327
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11328
+ className: KnowledgeView_module_css_default.ioPanelTitle,
11329
+ children: t("kv.io.dialogTitle")
11330
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11331
+ type: "button",
11332
+ className: KnowledgeView_module_css_default.closeBtn,
11333
+ onClick: closeImportPanel,
11334
+ "aria-label": t("kv.io.cancel"),
11335
+ children: "✕"
11336
+ })]
11337
+ }),
11338
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11339
+ className: KnowledgeView_module_css_default.ioPanelBody,
11340
+ children: [
11341
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11342
+ className: KnowledgeView_module_css_default.ioFileHint,
11343
+ children: [
11344
+ draft.fileName,
11345
+ " — ",
11346
+ t("kv.io.fileHint")
11347
+ ]
11348
+ }),
11349
+ importError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11350
+ className: `${KnowledgeView_module_css_default.ioNotice} ${KnowledgeView_module_css_default.ioNoticeError}`,
11351
+ children: [t("kv.io.failed"), importError === "" ? "" : `: ${importError}`]
11352
+ }),
11353
+ applied !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11354
+ className: `${KnowledgeView_module_css_default.ioNotice} ${KnowledgeView_module_css_default.ioNoticeOk}`,
11355
+ children: [
11356
+ t("kv.io.applied"),
11357
+ ": ",
11358
+ t("kv.io.planCreate"),
11359
+ " ",
11360
+ applied.created,
11361
+ " ·",
11362
+ " ",
11363
+ t("kv.io.planUpdate"),
11364
+ " ",
11365
+ applied.updated,
11366
+ " · ",
11367
+ t("kv.io.planSkip"),
11368
+ " ",
11369
+ applied.skipped
11370
+ ]
11371
+ }),
11372
+ applied === null && draft.bundle !== null && importCounts !== null && importPlan !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
11373
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11374
+ className: KnowledgeView_module_css_default.ioModes,
11375
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
11376
+ className: KnowledgeView_module_css_default.ioMode,
11377
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
11378
+ type: "radio",
11379
+ name: "dshtrading-knowledge-import-mode",
11380
+ value: "skip",
11381
+ checked: draft.mode === "skip",
11382
+ onChange: () => handleSelectImportMode("skip"),
11383
+ disabled: importing
11384
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("kv.io.modeSkip") })]
11385
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
11386
+ className: KnowledgeView_module_css_default.ioMode,
11387
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
11388
+ type: "radio",
11389
+ name: "dshtrading-knowledge-import-mode",
11390
+ value: "overwrite",
11391
+ checked: draft.mode === "overwrite",
11392
+ onChange: () => handleSelectImportMode("overwrite"),
11393
+ disabled: importing
11394
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("kv.io.modeOverwrite") })]
11395
+ })]
11396
+ }),
11397
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11398
+ className: KnowledgeView_module_css_default.ioCounts,
11399
+ children: [
11400
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11401
+ className: KnowledgeView_module_css_default.ioCount,
11402
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11403
+ className: KnowledgeView_module_css_default.ioCountValue,
11404
+ children: importCounts.create
11405
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11406
+ className: KnowledgeView_module_css_default.ioCountLabel,
11407
+ children: t("kv.io.planCreate")
11408
+ })]
11409
+ }),
11410
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11411
+ className: KnowledgeView_module_css_default.ioCount,
11412
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11413
+ className: KnowledgeView_module_css_default.ioCountValue,
11414
+ children: importCounts.update
11415
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11416
+ className: KnowledgeView_module_css_default.ioCountLabel,
11417
+ children: t("kv.io.planUpdate")
11418
+ })]
11419
+ }),
11420
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11421
+ className: KnowledgeView_module_css_default.ioCount,
11422
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11423
+ className: KnowledgeView_module_css_default.ioCountValue,
11424
+ children: importCounts.skip
11425
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11426
+ className: KnowledgeView_module_css_default.ioCountLabel,
11427
+ children: t("kv.io.planSkip")
11428
+ })]
11429
+ }),
11430
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11431
+ className: KnowledgeView_module_css_default.ioCount,
11432
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11433
+ className: KnowledgeView_module_css_default.ioCountValue,
11434
+ children: importCounts.dropped
11435
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11436
+ className: KnowledgeView_module_css_default.ioCountLabel,
11437
+ children: t("kv.io.planDropped")
11438
+ })]
11439
+ }),
11440
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11441
+ className: KnowledgeView_module_css_default.ioCount,
11442
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11443
+ className: KnowledgeView_module_css_default.ioCountValue,
11444
+ children: importCounts.invalid
11445
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11446
+ className: KnowledgeView_module_css_default.ioCountLabel,
11447
+ children: t("kv.io.planInvalid")
11448
+ })]
11449
+ })
11450
+ ]
11451
+ }),
11452
+ importCounts.invalid > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11453
+ className: KnowledgeView_module_css_default.ioInvalid,
11454
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11455
+ className: KnowledgeView_module_css_default.ioInvalidTitle,
11456
+ children: [
11457
+ t("kv.io.planInvalid"),
11458
+ " ",
11459
+ importCounts.invalid
11460
+ ]
11461
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
11462
+ className: KnowledgeView_module_css_default.ioInvalidList,
11463
+ children: importPlan.invalid.slice(0, 5).map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
11464
+ "#",
11465
+ item.index,
11466
+ " ",
11467
+ item.error
11468
+ ] }, item.index))
11469
+ })]
11470
+ })
11471
+ ] })
11472
+ ]
11473
+ }),
11474
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11475
+ className: KnowledgeView_module_css_default.ioPanelFooter,
11476
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11477
+ type: "button",
11478
+ className: KnowledgeView_module_css_default.ioBtn,
11479
+ onClick: closeImportPanel,
11480
+ disabled: importing,
11481
+ children: t("kv.io.cancel")
11482
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11483
+ type: "button",
11484
+ className: `${KnowledgeView_module_css_default.ioBtn} ${KnowledgeView_module_css_default.ioBtnPrimary}`,
11485
+ onClick: () => {
11486
+ handleConfirmImport();
11487
+ },
11488
+ disabled: importing || applied !== null || draft.bundle === null,
11489
+ children: t("kv.io.confirm")
11490
+ })]
11491
+ })
11492
+ ]
10656
11493
  })
10657
11494
  })
10658
11495
  ]
10659
- })]
10660
- })]
11496
+ })
11497
+ ]
10661
11498
  });
10662
11499
  }
10663
11500
  //#endregion
@@ -10689,7 +11526,24 @@ window.__ModuleLoader__.load({
10689
11526
  "kv.drawer.boundaries": "适用边界与避坑",
10690
11527
  "kv.drawer.tickers": "关联标的",
10691
11528
  "kv.drawer.related": "关联卡片",
10692
- "kv.drawer.openSource": "打开原始链接"
11529
+ "kv.drawer.openSource": "打开原始链接",
11530
+ "kv.io.export": "导出",
11531
+ "kv.io.import": "导入",
11532
+ "kv.io.exportEmpty": "当前筛选结果为空,没有可导出的卡片",
11533
+ "kv.io.dialogTitle": "导入知识库文件",
11534
+ "kv.io.modeSkip": "跳过:库里已有同来源链接的卡片保持原样",
11535
+ "kv.io.modeOverwrite": "覆盖:用文件内容改写同来源链接的卡片,保留本机卡片 id",
11536
+ "kv.io.planCreate": "新增",
11537
+ "kv.io.planUpdate": "更新",
11538
+ "kv.io.planSkip": "跳过",
11539
+ "kv.io.planDropped": "丢弃关联",
11540
+ "kv.io.planInvalid": "非法",
11541
+ "kv.io.confirm": "确认导入",
11542
+ "kv.io.cancel": "取消",
11543
+ "kv.io.applied": "导入完成",
11544
+ "kv.io.failed": "导入失败",
11545
+ "kv.io.fileHint": "预览按本机现有卡片计算,确认后才会写入知识库",
11546
+ "kv.io.exportTooLarge": "导出内容超过上限(2000 张卡片或 8 MiB):请先缩小筛选范围再导出,否则文件导不回来"
10693
11547
  };
10694
11548
  const en = {
10695
11549
  "kv.search.placeholder": "Search title, summary, tags...",
@@ -10718,7 +11572,24 @@ window.__ModuleLoader__.load({
10718
11572
  "kv.drawer.boundaries": "Boundaries & Pitfalls",
10719
11573
  "kv.drawer.tickers": "Related Tickers",
10720
11574
  "kv.drawer.related": "Related Cards",
10721
- "kv.drawer.openSource": "Open Original URL"
11575
+ "kv.drawer.openSource": "Open Original URL",
11576
+ "kv.io.export": "Export",
11577
+ "kv.io.import": "Import",
11578
+ "kv.io.exportEmpty": "No cards in the current filter — nothing to export",
11579
+ "kv.io.dialogTitle": "Import knowledge bundle",
11580
+ "kv.io.modeSkip": "Skip: cards with the same source link already on this machine stay unchanged",
11581
+ "kv.io.modeOverwrite": "Overwrite: replace cards with the same source link, keeping their local card ids",
11582
+ "kv.io.planCreate": "Create",
11583
+ "kv.io.planUpdate": "Update",
11584
+ "kv.io.planSkip": "Skip",
11585
+ "kv.io.planDropped": "Dropped links",
11586
+ "kv.io.planInvalid": "Invalid",
11587
+ "kv.io.confirm": "Import now",
11588
+ "kv.io.cancel": "Cancel",
11589
+ "kv.io.applied": "Import complete",
11590
+ "kv.io.failed": "Import failed",
11591
+ "kv.io.fileHint": "Preview is computed against the cards already on this machine; nothing is written until you confirm",
11592
+ "kv.io.exportTooLarge": "Export exceeds the limit (2000 cards or 8 MiB): narrow the filter before exporting, otherwise the file cannot be imported back"
10722
11593
  };
10723
11594
  //#endregion
10724
11595
  //#region src/client/index.ts
@@ -10738,7 +11609,8 @@ window.__ModuleLoader__.load({
10738
11609
  const faces = scope;
10739
11610
  const bridge = {
10740
11611
  fetchKnowledgeCards: () => faces.tradingBridge.fetchKnowledgeCards(),
10741
- subscribeTradingEvents: (handlers) => faces.tradingBridge.subscribeTradingEvents(handlers)
11612
+ subscribeTradingEvents: (handlers) => faces.tradingBridge.subscribeTradingEvents(handlers),
11613
+ importKnowledgeBundle: (input) => faces.tradingBridge.importKnowledgeBundle(input)
10742
11614
  };
10743
11615
  faces.tradingStageViews.register({
10744
11616
  id: "knowledge",