dsh-knowledge-library 0.1.1 → 0.2.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +107 -12
  3. package/dist/client.js +1420 -303
  4. package/dist/core/extract.d.ts +1 -0
  5. package/dist/core/extract.js +62 -5
  6. package/dist/core/fetch.js +82 -11
  7. package/dist/core/grouping.d.ts +6 -0
  8. package/dist/core/grouping.js +71 -0
  9. package/dist/core/incremental.d.ts +14 -0
  10. package/dist/core/incremental.js +52 -0
  11. package/dist/core/jobs.d.ts +1 -0
  12. package/dist/core/jobs.js +4 -0
  13. package/dist/core/knowledge.d.ts +37 -0
  14. package/dist/core/knowledge.js +617 -0
  15. package/dist/core/library.d.ts +20 -2
  16. package/dist/core/library.js +271 -12
  17. package/dist/core/page-access.d.ts +6 -0
  18. package/dist/core/page-access.js +38 -0
  19. package/dist/core/purge.d.ts +20 -0
  20. package/dist/core/purge.js +106 -0
  21. package/dist/directory-settings.d.ts +18 -0
  22. package/dist/directory-settings.js +92 -0
  23. package/dist/http.d.ts +3 -1
  24. package/dist/http.js +121 -4
  25. package/dist/index.js +17 -5
  26. package/dist/knowledge-model.d.ts +18 -0
  27. package/dist/knowledge-model.js +116 -0
  28. package/dist/preview-settings.d.ts +6 -0
  29. package/dist/preview-settings.js +42 -0
  30. package/dist/public/app.css +126 -2
  31. package/dist/public/app.js +11 -7
  32. package/dist/standalone.js +8 -3
  33. package/dist/tools.d.ts +2 -1
  34. package/dist/tools.js +10 -1
  35. package/dist/types.d.ts +143 -1
  36. package/dist/ui/App.js +243 -47
  37. package/dist/ui/Dialog.d.ts +7 -0
  38. package/dist/ui/Dialog.js +32 -0
  39. package/dist/ui/Grouping.d.ts +12 -0
  40. package/dist/ui/Grouping.js +93 -0
  41. package/dist/ui/Knowledge.d.ts +26 -0
  42. package/dist/ui/Knowledge.js +106 -0
  43. package/dist/ui/api.d.ts +7 -0
  44. package/dist/ui/api.js +16 -0
  45. package/dist/ui/icons.js +2 -0
  46. package/package.json +4 -1
@@ -0,0 +1,93 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { post } from './api.js';
4
+ import { Icon } from './icons.js';
5
+ const splitTags = (value) => value.split(/[,,]/).map(t => t.trim()).filter(Boolean);
6
+ export function GroupingEditor({ root, job, model, onRefresh, onNotify, onOpenSource }) {
7
+ const [plan, setPlan] = useState(job.plan);
8
+ const [version, setVersion] = useState(job.planVersion);
9
+ const [tagText, setTagText] = useState(Object.fromEntries(plan.groups.map(g => [g.id, g.tags.join(',')])));
10
+ const [apply, setApply] = useState(job.classification?.apply ?? plan.applyClassification ?? true);
11
+ const [busy, setBusy] = useState(false);
12
+ const [error, setError] = useState('');
13
+ const locked = !!job.classification;
14
+ const groups = plan.groups.filter(g => g.sourceIds.length);
15
+ function groupValue(id, change) { setPlan(p => ({ ...p, groups: p.groups.map(g => g.id === id ? { ...g, ...change } : g) })); }
16
+ function newGroup(sourceId) {
17
+ const source = job.sources.find(s => s.id === sourceId);
18
+ const analysis = job.analyses?.find(a => a.sourceId === sourceId);
19
+ const id = `group_${crypto.randomUUID()}`;
20
+ const item = { id, title: analysis?.theme || source?.title || '新分组', reason: source ? '独立整理这份资料' : '请填写资料之间的具体关联', sourceIds: sourceId ? [sourceId] : [], category: analysis?.category || '收件箱', tags: analysis?.tags || [] };
21
+ setTagText(values => ({ ...values, [id]: item.tags.join(',') }));
22
+ setPlan(p => ({ ...p, groups: [...p.groups.map(g => ({ ...g, sourceIds: g.sourceIds.filter(s => s !== sourceId) })), item], unassigned: p.unassigned.filter(s => s.sourceId !== sourceId) }));
23
+ }
24
+ function assign(sourceId, target) {
25
+ if (target === '__new__') {
26
+ newGroup(sourceId);
27
+ return;
28
+ }
29
+ const analysis = job.analyses?.find(a => a.sourceId === sourceId);
30
+ setPlan(p => ({ ...p,
31
+ groups: p.groups.map(g => ({ ...g, sourceIds: g.id === target ? [...g.sourceIds.filter(id => id !== sourceId), sourceId] : g.sourceIds.filter(id => id !== sourceId) })),
32
+ unassigned: [...p.unassigned.filter(s => s.sourceId !== sourceId), ...(target ? [] : [{ sourceId, reason: analysis?.uncertainty || '暂留待整理,后续再判断关联' }])],
33
+ }));
34
+ }
35
+ function removeGroup(id) {
36
+ setPlan(p => ({ ...p, groups: p.groups.filter(g => g.id !== id), unassigned: [...p.unassigned, ...(p.groups.find(g => g.id === id)?.sourceIds || []).map(sourceId => ({ sourceId, reason: '已移出分组,保留待整理' }))] }));
37
+ }
38
+ async function save(confirm, openSource) {
39
+ if (busy)
40
+ return;
41
+ setBusy(true);
42
+ setError('');
43
+ try {
44
+ let currentVersion = version;
45
+ if (!locked) {
46
+ const value = { ...plan, applyClassification: apply, groups: groups.map(g => ({ ...g, tags: splitTags(tagText[g.id] || '') })) };
47
+ const edited = await post(`/knowledge/jobs/${job.id}/plan`, { expectedRoot: root, version, plan: value }, 'PATCH');
48
+ currentVersion = edited.planVersion;
49
+ setVersion(currentVersion);
50
+ }
51
+ if (confirm) {
52
+ await post(`/knowledge/jobs/${job.id}/confirm-plan`, { expectedRoot: root, version: currentVersion, applyClassification: apply, expectedModel: model.model, confirmed: true });
53
+ onNotify(`${groups.length} 组资料已提交生成草稿;${plan.unassigned.length} 份保留待整理`);
54
+ onRefresh();
55
+ }
56
+ else if (openSource)
57
+ onOpenSource(openSource);
58
+ else {
59
+ onNotify('分组方案已保存,来源分类尚未修改');
60
+ onRefresh();
61
+ }
62
+ }
63
+ catch (e) {
64
+ setError(e.message);
65
+ if (confirm)
66
+ onRefresh();
67
+ }
68
+ finally {
69
+ setBusy(false);
70
+ }
71
+ }
72
+ async function rollback() {
73
+ setBusy(true);
74
+ setError('');
75
+ try {
76
+ await post(`/knowledge/jobs/${job.id}/rollback-plan`, { expectedRoot: root, confirmed: true });
77
+ onNotify('未完成的分类修改已回退');
78
+ onRefresh();
79
+ }
80
+ catch (e) {
81
+ setError(e.message);
82
+ onRefresh();
83
+ }
84
+ finally {
85
+ setBusy(false);
86
+ }
87
+ }
88
+ return _jsxs("div", { className: "kl-grouping-editor", children: [_jsxs("div", { className: "kl-grouping-summary", children: [_jsxs("b", { children: [groups.length, " \u4E2A\u5206\u7EC4"] }), _jsxs("span", { children: [plan.unassigned.length, " \u4EFD\u5F85\u6574\u7406"] })] }), _jsx("p", { className: "kl-knowledge-model", children: "\u5148\u6838\u5BF9\u9010\u7BC7\u5206\u6790\u4E0E\u5206\u7EC4\u7406\u7531\u3002\u65E0\u5173\u8054\u7684\u8D44\u6599\u53EF\u72EC\u7ACB\u6210\u7EC4\uFF0C\u4FE1\u606F\u4E0D\u8DB3\u7684\u8D44\u6599\u53EF\u4EE5\u7559\u5F85\u6574\u7406\u3002\u4FEE\u6539\u540E\u8BF7\u4FDD\u5B58\u65B9\u6848\uFF0C\u518D\u5173\u95ED\u7A97\u53E3\u3002" }), job.error && _jsx("p", { role: "alert", className: "kl-form-error", children: job.error }), locked && _jsxs("div", { className: "kl-grouping-recovery", role: "status", children: [_jsx("strong", { children: "\u4E0A\u6B21\u5F52\u7C7B\u5C1A\u672A\u5B8C\u6210" }), _jsxs("p", { children: [job.classification.steps.filter(s => s.done).length, " / ", job.classification.steps.length, " \u4EFD\u5DF2\u5B8C\u6210", job.classification.direction === 'rollback' ? '回退' : '归类', "\u3002\u65B9\u6848\u5DF2\u9501\u5B9A\uFF0C\u53EF\u7EE7\u7EED\u5904\u7406\u6216\u56DE\u9000\u5DF2\u66F4\u6539\u7684\u5206\u7C7B\u3002"] }), _jsx("button", { className: "kl-secondary", disabled: busy, onClick: () => void rollback(), children: job.classification.direction === 'rollback' ? '继续回退分类' : '回退已更改的分类' })] }), _jsx("div", { className: "kl-grouping-groups", children: plan.groups.map((group, index) => _jsxs("section", { className: "kl-grouping-group", "aria-label": `分组 ${index + 1}`, children: [_jsxs("div", { className: "kl-grouping-group-title", children: [_jsxs("span", { children: ["\u5206\u7EC4 ", index + 1, " \u00B7 ", group.sourceIds.length, " \u4EFD\u8D44\u6599"] }), _jsx("button", { className: "kl-icon-button kl-danger", "aria-label": `删除分组 ${index + 1}`, disabled: busy || locked, onClick: () => removeGroup(group.id), children: _jsx(Icon, { name: "trash", size: 15 }) })] }), _jsxs("label", { children: ["\u5206\u7EC4\u540D\u79F0", _jsx("input", { "aria-label": `分组名称 ${index + 1}`, maxLength: 200, value: group.title, disabled: busy || locked, onChange: e => groupValue(group.id, { title: e.target.value }) })] }), _jsx("p", { className: "kl-grouping-reason", children: group.reason }), _jsxs("details", { className: "kl-grouping-settings", children: [_jsxs("summary", { children: ["\u5206\u7C7B\u4E0E\u5206\u7EC4\u8BBE\u7F6E ", _jsx("span", { children: group.category })] }), _jsxs("label", { children: ["\u5206\u7EC4\u7406\u7531", _jsx("textarea", { "aria-label": `分组理由 ${index + 1}`, rows: 2, maxLength: 1200, value: group.reason, disabled: busy || locked, onChange: e => groupValue(group.id, { reason: e.target.value }) })] }), _jsxs("div", { className: "kl-knowledge-fields", children: [_jsxs("label", { children: ["\u6765\u6E90\u5206\u7C7B", _jsx("input", { "aria-label": `来源分类 ${index + 1}`, value: group.category, maxLength: 180, disabled: busy || locked, onChange: e => groupValue(group.id, { category: e.target.value }) })] }), _jsxs("label", { children: ["\u5206\u7EC4\u6807\u7B7E", _jsx("input", { "aria-label": `分组标签 ${index + 1}`, value: tagText[group.id] || '', disabled: busy || locked, onChange: e => setTagText(v => ({ ...v, [group.id]: e.target.value })) })] })] })] }), _jsx("ul", { children: group.sourceIds.map(id => _jsx("li", { children: job.sources.find(s => s.id === id)?.title }, id)) }), !group.sourceIds.length && _jsx("p", { className: "kl-muted", children: "\u6682\u65E0\u8D44\u6599\uFF0C\u4FDD\u5B58\u65F6\u4F1A\u5FFD\u7565\u7A7A\u5206\u7EC4\u3002" })] }, group.id)) }), _jsxs("button", { className: "kl-secondary", disabled: busy || locked || plan.groups.length >= 10, onClick: () => newGroup(), children: [_jsx(Icon, { name: "plus", size: 14 }), "\u65B0\u589E\u5206\u7EC4"] }), _jsx("h3", { className: "kl-grouping-section-title", children: "\u9010\u7BC7\u5206\u6790\u4E0E\u6210\u5458\u8C03\u6574" }), job.sources.map(source => {
89
+ const analysis = job.analyses?.find(a => a.sourceId === source.id);
90
+ const unassigned = plan.unassigned.find(s => s.sourceId === source.id);
91
+ return _jsxs("article", { className: "kl-grouping-source", "aria-label": `${source.title} 的分析`, children: [_jsxs("div", { className: "kl-grouping-source-title", children: [_jsx("strong", { children: source.title }), _jsx("span", { className: "kl-knowledge-badge", children: analysis?.certainty === 'uncertain' ? '需核对' : '主题建议' })] }), _jsx("p", { children: analysis?.theme }), _jsxs("label", { children: ["\u6240\u5C5E\u5206\u7EC4", _jsxs("select", { "aria-label": `所属分组 ${source.title}`, value: plan.groups.find(g => g.sourceIds.includes(source.id))?.id || '', disabled: busy || locked, onChange: e => assign(source.id, e.target.value), children: [_jsx("option", { value: "", children: "\u4FDD\u7559\u5F85\u6574\u7406" }), plan.groups.map(g => _jsx("option", { value: g.id, children: g.title || '未命名分组' }, g.id)), _jsx("option", { value: "__new__", children: "\u5355\u72EC\u6210\u7EC4" })] })] }), unassigned && _jsxs("label", { children: ["\u5F85\u6574\u7406\u7406\u7531", _jsx("input", { "aria-label": `待整理理由 ${source.title}`, maxLength: 1200, value: unassigned.reason, disabled: busy || locked, onChange: e => setPlan(p => ({ ...p, unassigned: p.unassigned.map(s => s.sourceId === source.id ? { ...s, reason: e.target.value } : s) })) })] }), _jsxs("details", { children: [_jsx("summary", { children: "\u5206\u6790\u8BE6\u60C5\u4E0E\u6765\u6E90" }), _jsx("p", { children: analysis?.summary }), _jsxs("p", { children: ["\u7528\u9014\uFF1A", analysis?.usage] }), _jsxs("p", { children: ["\u6982\u5FF5\uFF1A", analysis?.concepts.join('、')] }), _jsxs("p", { children: ["\u5EFA\u8BAE\u5206\u7C7B\uFF1A", analysis?.category, " \u00B7 \u6807\u7B7E\uFF1A", analysis?.tags.join('、') || '无'] }), analysis?.uncertainty && _jsxs("p", { children: ["\u5F85\u6838\u5B9E\uFF1A", analysis.uncertainty] }), _jsxs("button", { className: "kl-secondary", disabled: busy || locked, onClick: () => void save(false, source.id), children: ["\u4FDD\u5B58\u65B9\u6848\u5E76\u67E5\u770B\u6765\u6E90", _jsx(Icon, { name: "arrow", size: 14 })] })] })] }, source.id);
92
+ }), _jsxs("label", { className: "kl-grouping-sync", children: [_jsx("input", { type: "checkbox", "aria-label": "\u540C\u6B65\u6765\u6E90\u5206\u7C7B\u548C\u6807\u7B7E", checked: apply, disabled: busy || locked, onChange: e => setApply(e.target.checked) }), _jsx("span", { children: "\u786E\u8BA4\u540E\u540C\u6B65\u6765\u6E90\u5206\u7C7B\u548C\u6807\u7B7E\u5230\u672C\u5730\u76EE\u5F55" })] }), _jsx("p", { className: "kl-knowledge-model", children: "\u540C\u6B65\u65F6\u4F1A\u79FB\u52A8\u5DF2\u5206\u7EC4\u8D44\u6599\u7684\u76EE\u5F55\u3001\u4FDD\u7559\u539F\u6807\u7B7E\u5E76\u6DFB\u52A0\u5206\u7EC4\u6807\u7B7E\uFF1B\u672A\u5206\u7EC4\u8D44\u6599\u4FDD\u6301\u5F85\u6574\u7406\u3002\u5173\u95ED\u540C\u6B65\u53EF\u53EA\u6309\u7EC4\u751F\u6210\u8349\u7A3F\u3002\u6BCF\u7EC4\u751F\u6210\u540E\u4ECD\u9700\u5355\u72EC\u786E\u8BA4\u4FDD\u5B58\u6B63\u5F0F\u7B14\u8BB0\u3002" }), _jsxs("p", { className: "kl-knowledge-model", children: ["\u751F\u6210\u8349\u7A3F\u4F7F\u7528\u6A21\u578B\uFF1A", model.model || '尚未配置', "\u3002", model.model !== job.model && `逐篇分析时使用的是 ${job.model || '未记录模型'}。`, "\u786E\u8BA4\u540E\u5C06\u7EE7\u7EED\u5411\u8BE5\u6A21\u578B\u53D1\u9001\u5404\u7EC4\u6B63\u6587\u53CA\u68C0\u7D22\u5230\u7684\u76F8\u5173\u77E5\u8BC6\u7B14\u8BB0\uFF0C\u7528\u4E8E\u751F\u6210\u8349\u7A3F\u548C\u5224\u65AD\u589E\u91CF\u8865\u5145\u3002\u672C\u6B21\u667A\u80FD\u6574\u7406\u53CA\u5176\u5B50\u4EFB\u52A1\u5408\u8BA1\u6700\u591A 80 \u6B21\u6A21\u578B\u8BF7\u6C42\uFF08\u5DF2\u4F7F\u7528 ", job.modelCalls || 0, " \u6B21\uFF09\uFF0C\u53EF\u80FD\u4EA7\u751F\u8C03\u7528\u8D39\u7528\u3002"] }), !!groups.length && !model.available && _jsx("p", { role: "alert", className: "kl-form-error", children: model.reason || '请先配置生成模型' }), error && _jsx("p", { role: "alert", className: "kl-form-error", children: error }), _jsxs("footer", { children: [!locked && _jsx("button", { className: "kl-secondary", disabled: busy, onClick: () => void save(false), children: "\u4FDD\u5B58\u5206\u7EC4\u65B9\u6848" }), _jsx("button", { className: "kl-primary", disabled: busy || (!!groups.length && !model.available) || job.classification?.direction === 'rollback', onClick: () => void save(true), children: busy ? '正在处理…' : locked ? '继续归类并生成草稿' : groups.length ? `确认${apply ? '归类' : '分组'}并生成 ${groups.length} 份草稿` : '确认保留全部待整理' })] })] });
93
+ }
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import type { KnowledgeJob, KnowledgeOverview, KnowledgeState } from '../types.js';
3
+ export declare function KnowledgeBadge({ state }: {
4
+ state?: KnowledgeState;
5
+ }): React.JSX.Element;
6
+ export declare function StartKnowledgeDialog({ root, sourceIds, model, initialMode, onClose, onCreated }: {
7
+ root: string;
8
+ sourceIds: string[];
9
+ model: KnowledgeOverview;
10
+ initialMode?: 'smart' | 'compose';
11
+ onClose: () => void;
12
+ onCreated: (job: KnowledgeJob) => void;
13
+ }): React.JSX.Element;
14
+ interface JobsProps {
15
+ root: string;
16
+ jobId?: string;
17
+ overview: KnowledgeOverview;
18
+ onClose: () => void;
19
+ onRefresh: () => void;
20
+ onRegenerate: (ids: string[], mode?: 'smart' | 'compose') => void;
21
+ onOpenSource: (id: string, segmentId?: string) => void;
22
+ onSaved: (id: string) => void;
23
+ onNotify: (message: string) => void;
24
+ }
25
+ export declare function KnowledgeJobsDialog(props: JobsProps): React.JSX.Element;
26
+ export {};
@@ -0,0 +1,106 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { api, post } from './api.js';
4
+ import { Dialog } from './Dialog.js';
5
+ import { Icon } from './icons.js';
6
+ import { GroupingEditor } from './Grouping.js';
7
+ const stateLabels = { pending: '待整理', running: '整理中', grouping: '待分组确认', draft: '待确认', completed: '已整理', failed: '整理失败' };
8
+ const jobLabels = { queued: '排队中', running: '整理中', grouping: '待分组确认', classifying: '归类中', grouped: '已分组', draft: '待确认', completed: '已保存', failed: '失败', cancelled: '已取消', discarded: '已舍弃' };
9
+ export function KnowledgeBadge({ state }) {
10
+ return _jsx("span", { className: `kl-knowledge-badge ${state?.status || 'pending'}`, children: stateLabels[state?.status || 'pending'] });
11
+ }
12
+ export function StartKnowledgeDialog({ root, sourceIds, model, initialMode, onClose, onCreated }) {
13
+ const [sources, setSources] = useState();
14
+ const [topic, setTopic] = useState('');
15
+ const [error, setError] = useState('');
16
+ const [busy, setBusy] = useState(false);
17
+ const [mode, setMode] = useState(initialMode || (sourceIds.length > 1 ? 'smart' : 'compose'));
18
+ const smart = mode === 'smart';
19
+ useEffect(() => {
20
+ const abort = new AbortController();
21
+ void Promise.all(sourceIds.map(id => api(`/materials/${id}`, { signal: abort.signal }))).then(setSources).catch(e => { if (!abort.signal.aborted)
22
+ setError(e.message); });
23
+ return () => abort.abort();
24
+ }, [sourceIds]);
25
+ const characters = sources?.reduce((sum, source) => sum + source.content.length, 0) || 0;
26
+ const invalidSource = sources?.some(s => s.deletedAt || s.kind === 'note' || s.status !== 'ready');
27
+ async function start(event) {
28
+ event.preventDefault();
29
+ setBusy(true);
30
+ setError('');
31
+ try {
32
+ onCreated(await post('/knowledge/jobs', { expectedRoot: root, sourceIds, topic, mode }));
33
+ }
34
+ catch (e) {
35
+ setError(e.message);
36
+ }
37
+ finally {
38
+ setBusy(false);
39
+ }
40
+ }
41
+ return _jsx(Dialog, { title: smart ? '智能整理' : '生成知识笔记', description: smart ? '先逐篇分析、建议分组,再确认分类与生成各组知识草稿。' : '先提炼资料,再由你确认。原始资料与来源引用会保留。', onClose: () => { if (!busy)
42
+ onClose(); }, children: _jsxs("form", { className: "kl-knowledge-form", onSubmit: event => void start(event), children: [_jsxs("div", { className: "kl-knowledge-summary", children: [_jsxs("b", { children: [sourceIds.length, " \u4EFD\u6765\u6E90\u8D44\u6599"] }), _jsxs("span", { children: [characters.toLocaleString(), " \u5B57"] })] }), _jsx("ul", { className: "kl-knowledge-source-list", children: sources?.map(s => _jsxs("li", { children: [_jsx(Icon, { name: "file", size: 14 }), _jsx("span", { children: s.title })] }, s.id)) }), _jsxs("fieldset", { className: "kl-knowledge-mode", children: [_jsx("legend", { children: "\u6574\u7406\u65B9\u5F0F" }), _jsxs("label", { children: [_jsx("input", { type: "radio", name: "knowledge-mode", value: "smart", checked: smart, disabled: !model.smartSupported || busy, onChange: () => setMode('smart') }), "\u667A\u80FD\u5206\u7EC4", _jsx("small", { children: "\u9002\u5408\u96F6\u6563\u8D44\u6599\uFF0C\u5148\u5224\u65AD\u5173\u8054" })] }), _jsxs("label", { children: [_jsx("input", { type: "radio", name: "knowledge-mode", value: "compose", checked: !smart, disabled: busy, onChange: () => setMode('compose') }), "\u540C\u4E3B\u9898\u76F4\u63A5\u7EFC\u5408", _jsx("small", { children: "\u5DF2\u786E\u8BA4\u56F4\u7ED5\u540C\u4E00\u95EE\u9898\u65F6\u4F7F\u7528" })] })] }), _jsxs("label", { children: [smart ? '整理方向(可选)' : '整理主题', _jsx("input", { "aria-label": "\u6574\u7406\u4E3B\u9898", value: topic, onChange: e => setTopic(e.target.value), maxLength: 300, placeholder: smart ? '留空可按资料的实际主题自动分组' : '例如:Harness 如何提高 Agent 的可靠性' }), _jsx("small", { children: smart ? '方向用于辅助判断,不会要求把无关联资料合并。' : '留空时围绕资料本身整理;多篇资料会比较共识、差异和矛盾。' })] }), _jsx("p", { className: "kl-muted", children: smart ? '逐篇提取主题、概念与用途,给出分组理由和分类标签建议。无关联资料可独立成组,信息不足的资料保留待整理。确认方案前不移动资料。' : '将提炼核心观点、关键概念、可复用方法、适用条件与局限、待验证问题,并标注来源段落。' }), smart && !model.smartSupported && _jsx("p", { role: "alert", className: "kl-form-error", children: "\u8BF7\u91CD\u542F\u8D44\u6599\u5E93\u670D\u52A1\u4EE5\u542F\u7528\u667A\u80FD\u5206\u7EC4\u3002" }), model.available ? _jsxs("p", { className: "kl-knowledge-model", children: ["\u4F7F\u7528\u6A21\u578B\uFF1A", model.model, "\u3002\u70B9\u51FB\u5F00\u59CB\u540E\u4F1A\u5411\u8BE5\u6A21\u578B\u53D1\u9001\u6240\u9009\u6B63\u6587\u53CA\u68C0\u7D22\u5230\u7684\u76F8\u5173\u77E5\u8BC6\u7B14\u8BB0\uFF0C\u53EF\u80FD\u4EA7\u751F\u8C03\u7528\u8D39\u7528\u3002", smart ? '逐篇分析和分组最多 40 次请求;确认后各组继续生成草稿,整次任务合计最多 80 次。' : '长文会分段提炼,含旧笔记对比最多 22 次模型请求。', "\u5355\u6B21\u6700\u591A 10 \u4EFD\u3001\u6B63\u6587\u603B\u91CF 10 \u4E07\u5B57\u3002"] }) : _jsxs("div", { children: [_jsx("p", { role: "alert", className: "kl-form-error", children: model.reason }), _jsxs("a", { className: "kl-knowledge-help", href: "https://github.com/Alan-dong-dong/dsh-knowledge-library#\u5C06\u8D44\u6599\u6574\u7406\u4E3A\u77E5\u8BC6\u7B14\u8BB0", target: "_blank", rel: "noreferrer", children: ["\u67E5\u770B\u6A21\u578B\u914D\u7F6E\u8BF4\u660E ", _jsx(Icon, { name: "arrow", size: 12 })] })] }), characters > 100_000 && _jsx("p", { role: "alert", className: "kl-form-error", children: "\u6B63\u6587\u8D85\u8FC7 10 \u4E07\u5B57\uFF0C\u8BF7\u51CF\u5C11\u9009\u62E9\u540E\u5206\u6279\u6574\u7406\u3002" }), invalidSource && _jsx("p", { role: "alert", className: "kl-form-error", children: "\u90E8\u5206\u8D44\u6599\u5DF2\u5220\u9664\u3001\u4EC5\u5F52\u6863\u6216\u5C5E\u4E8E\u77E5\u8BC6\u7B14\u8BB0\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u3002" }), error && _jsx("p", { role: "alert", className: "kl-form-error", children: error }), _jsxs("footer", { children: [_jsx("button", { type: "button", className: "kl-secondary", disabled: busy, onClick: onClose, children: "\u53D6\u6D88" }), _jsx("button", { className: "kl-primary", disabled: busy || !sources || !model.available || (smart && !model.smartSupported) || characters > 100_000 || invalidSource, children: busy ? '正在提交…' : smart ? '开始分析分组' : '开始整理' })] })] }) });
43
+ }
44
+ export function KnowledgeJobsDialog(props) {
45
+ const { root, overview, onClose, onRefresh, onRegenerate, onOpenSource, onSaved, onNotify } = props;
46
+ const [selected, setSelected] = useState(props.jobId);
47
+ const [error, setError] = useState('');
48
+ const [busy, setBusy] = useState(false);
49
+ const job = overview.jobs.find(j => j.id === selected);
50
+ async function action(name) {
51
+ if (!job)
52
+ return;
53
+ setBusy(true);
54
+ setError('');
55
+ try {
56
+ await post(`/knowledge/jobs/${job.id}/${name}`, { expectedRoot: root });
57
+ onRefresh();
58
+ }
59
+ catch (e) {
60
+ setError(e.message);
61
+ }
62
+ finally {
63
+ setBusy(false);
64
+ }
65
+ }
66
+ return _jsx(Dialog, { title: job?.status === 'draft' ? '确认知识笔记' : job?.status === 'grouping' ? '确认智能分组' : '知识整理', description: "\u5206\u6790\u3001\u5206\u7EC4\u65B9\u6848\u4E0E\u8349\u7A3F\u4FDD\u5B58\u5728\u672C\u5730\u3002\u6B63\u5F0F\u77E5\u8BC6\u7B14\u8BB0\u4ECD\u9700\u9010\u4EFD\u786E\u8BA4\u4FDD\u5B58\u3002", onClose: () => { if (!busy)
67
+ onClose(); }, children: _jsxs("div", { className: "kl-knowledge-jobs", children: [!!selected && _jsx("button", { className: "kl-knowledge-back", onClick: () => { setSelected(undefined); setError(''); }, children: "\u2190 \u5168\u90E8\u6574\u7406\u4EFB\u52A1" }), !overview.supported && _jsx("p", { role: "alert", className: "kl-form-error", children: overview.reason }), error && _jsx("p", { role: "alert", className: "kl-form-error", children: error }), !selected ? _jsx(_Fragment, { children: !overview.jobs.length ? _jsxs("div", { className: "kl-knowledge-empty", children: [_jsx(Icon, { name: "note", size: 26 }), _jsx("p", { children: "\u8FD8\u6CA1\u6709\u6574\u7406\u4EFB\u52A1" }), _jsx("small", { children: "\u6253\u5F00\u4E00\u4EFD\u8D44\u6599\u70B9\u51FB\u201C\u751F\u6210\u77E5\u8BC6\u7B14\u8BB0\u201D\uFF0C\u6216\u5728\u5217\u8868\u4E2D\u9009\u62E9\u201C\u667A\u80FD\u6574\u7406\u201D\u3002" })] }) : overview.jobs.map(item => _jsxs("button", { className: "kl-knowledge-job", onClick: () => { setSelected(item.id); setError(''); }, children: [_jsxs("div", { children: [_jsx("strong", { children: item.topic || item.sources.map(s => s.title).join('、') }), _jsx("span", { className: `kl-knowledge-badge ${item.status}`, children: jobLabels[item.status] })] }), _jsxs("p", { children: [item.mode === 'smart' ? '智能分组 · ' : item.parentId ? '分组子任务 · ' : '', item.sourceIds.length, " \u4EFD\u6765\u6E90 \u00B7 ", new Date(item.updatedAt).toLocaleString('zh-CN')] }), item.error && _jsx("small", { children: item.error })] }, item.id)) }) : !job ? _jsx("p", { className: "kl-muted", children: "\u6B63\u5728\u8BFB\u53D6\u6574\u7406\u4EFB\u52A1\u2026" }) : _jsxs(_Fragment, { children: [_jsxs("div", { className: "kl-knowledge-job-heading", children: [_jsx("strong", { children: job.topic || job.sources.map(s => s.title).join('、') }), _jsx("span", { className: `kl-knowledge-badge ${job.status}`, children: jobLabels[job.status] })] }), job.status === 'grouping' && job.plan ? _jsx(GroupingEditor, { root: root, job: job, model: overview, onRefresh: onRefresh, onOpenSource: onOpenSource, onNotify: onNotify }, `${job.id}:${job.planVersion}`) : job.status === 'draft' && job.draft ? _jsx(DraftEditor, { root: root, job: job, onRefresh: onRefresh, onOpenSource: onOpenSource, onSaved: onSaved, onNotify: onNotify }, `${job.id}:${job.draftVersion}`) : _jsxs(_Fragment, { children: [_jsxs("p", { className: "kl-knowledge-progress", role: "status", children: [['queued', 'running'].includes(job.status) && _jsx("span", { className: "kl-spinner" }), job.progress] }), job.error && _jsx("p", { role: "alert", className: "kl-form-error", children: job.error }), job.model && _jsxs("p", { className: "kl-muted", children: ["\u6A21\u578B\uFF1A", job.model] }), job.status === 'completed' && job.noteId && (job.sourceIds.some(id => overview.states[id]?.noteIds.includes(job.noteId)) ? _jsx("button", { className: "kl-primary", onClick: () => onOpenSource(job.noteId), children: "\u6253\u5F00\u77E5\u8BC6\u7B14\u8BB0" }) : _jsx("p", { className: "kl-muted", children: "\u5173\u8054\u7B14\u8BB0\u5DF2\u79FB\u5165\u56DE\u6536\u7AD9\u6216\u6C38\u4E45\u5220\u9664\uFF0C\u53EF\u6062\u590D\u7B14\u8BB0\u6216\u91CD\u65B0\u6574\u7406\u3002" })), job.status === 'grouped' && _jsxs("div", { className: "kl-grouping-children", children: [job.children?.map(child => _jsxs("button", { className: "kl-knowledge-job", onClick: () => setSelected(child.jobId), children: [_jsxs("div", { children: [_jsx("strong", { children: job.plan?.groups.find(g => g.id === child.groupId)?.title || '分组草稿' }), _jsx("span", { className: "kl-knowledge-badge", children: jobLabels[overview.jobs.find(j => j.id === child.jobId)?.status || 'queued'] })] }), _jsx("p", { children: "\u6253\u5F00\u672C\u7EC4\u4EFB\u52A1\uFF0C\u5355\u72EC\u6838\u5BF9\u5E76\u4FDD\u5B58\u77E5\u8BC6\u7B14\u8BB0" })] }, child.jobId)), !!job.plan?.unassigned.length && _jsxs("div", { className: "kl-grouping-pending", children: [_jsx("strong", { children: "\u4FDD\u7559\u5F85\u6574\u7406" }), job.plan.unassigned.map(item => _jsxs("p", { children: [job.sources.find(s => s.id === item.sourceId)?.title, "\uFF1A", item.reason] }, item.sourceId))] })] })] }), _jsxs("div", { className: "kl-knowledge-actions", children: [['queued', 'running'].includes(job.status) && _jsx("button", { className: "kl-secondary", disabled: busy, onClick: () => void action('cancel'), children: "\u53D6\u6D88\u6574\u7406" }), ['draft', 'failed', 'grouping'].includes(job.status) && _jsxs("button", { className: "kl-secondary kl-danger", disabled: busy || !!job.classification, onClick: () => void action('discard'), children: ["\u820D\u5F03", job.status === 'draft' ? '草稿' : job.status === 'grouping' ? '方案' : '任务'] }), ['failed', 'cancelled', 'discarded', 'completed'].includes(job.status) && _jsx("button", { className: "kl-secondary", onClick: () => onRegenerate(job.sourceIds, job.mode), children: "\u91CD\u65B0\u6574\u7406" })] })] })] }) });
68
+ }
69
+ function DraftEditor({ root, job, onRefresh, onOpenSource, onSaved, onNotify }) {
70
+ const [draft, setDraft] = useState(job.draft);
71
+ const [integration, setIntegration] = useState(job.integration);
72
+ const [version, setVersion] = useState(job.draftVersion);
73
+ const [tags, setTags] = useState(draft.tags.join(','));
74
+ const [category, setCategory] = useState(draft.category || '知识笔记');
75
+ const [busy, setBusy] = useState(false);
76
+ const [error, setError] = useState('');
77
+ async function save(confirm, source) {
78
+ if (busy)
79
+ return;
80
+ setBusy(true);
81
+ setError('');
82
+ try {
83
+ const edited = await post(`/knowledge/jobs/${job.id}`, { expectedRoot: root, version, draft: { ...draft, category, tags: tags.split(/[,,]/).map(t => t.trim()).filter(Boolean) }, ...(integration ? { integration: { choice: integration.choice, content: integration.content } } : {}) }, 'PATCH');
84
+ setVersion(edited.draftVersion);
85
+ if (confirm) {
86
+ const result = await post(`/knowledge/jobs/${job.id}/confirm`, { expectedRoot: root, version: edited.draftVersion, category, confirmed: true, integrationChoice: integration?.choice });
87
+ onSaved(result.material.id);
88
+ }
89
+ else if (source)
90
+ onOpenSource(source.id, source.segment);
91
+ else {
92
+ onNotify('草稿已保存,正式知识笔记尚未修改');
93
+ onRefresh();
94
+ }
95
+ }
96
+ catch (e) {
97
+ setError(e.message);
98
+ }
99
+ finally {
100
+ setBusy(false);
101
+ }
102
+ }
103
+ const updating = integration?.choice === 'append' || integration?.choice === 'duplicate';
104
+ const confirmText = integration?.choice === 'append' ? '确认补充已有笔记' : integration?.choice === 'duplicate' ? '确认仅关联来源' : '确认保存笔记';
105
+ return _jsxs("form", { className: "kl-knowledge-draft", onSubmit: e => { e.preventDefault(); void save(true); }, children: [_jsx("p", { className: "kl-knowledge-model", children: "\u8FD9\u662F\u6A21\u578B\u751F\u6210\u7684\u8349\u7A3F\uFF0C\u8BF7\u6838\u5BF9\u7406\u89E3\u4E0E\u8BC1\u636E\u3002\u4FEE\u6539\u540E\u8BF7\u4FDD\u5B58\u8349\u7A3F\uFF0C\u518D\u5173\u95ED\u7A97\u53E3\u3002" }), integration && _jsxs("section", { className: "kl-note-integration", "aria-label": "\u5DF2\u6709\u77E5\u8BC6\u5BF9\u6BD4", children: [_jsxs("strong", { children: ["\u5EFA\u8BAE", integration.recommendation === 'append' ? '补充已有笔记' : integration.recommendation === 'duplicate' ? '仅关联已有笔记' : '新建笔记'] }), _jsx("p", { children: integration.reason }), _jsxs("p", { className: "kl-knowledge-model", children: ["\u68C0\u7D22\u4E86 ", integration.search.scanned, " \u4EFD\u6709\u6548\u7B14\u8BB0\uFF0C\u627E\u5230 ", integration.search.matched, " \u4EFD\u8BCD\u9762\u76F8\u5173\u7B14\u8BB0\uFF0C\u672C\u6B21\u5B8C\u6574\u5BF9\u6BD4 ", integration.search.compared, " \u4EFD\uFF08\u6700\u591A 5 \u4EFD\uFF09\u3002", integration.search.oversized > 0 && `${integration.search.oversized} 份超过对比长度上限。`, "\u672A\u5339\u914D\u4E0D\u4EE3\u8868\u6574\u5E93\u6CA1\u6709\u76F8\u5173\u77E5\u8BC6\u3002"] }), [['overlaps', '重复内容'], ['additions', '新增知识'], ['conflicts', '差异与待核实']].map(([key, title]) => !!integration[key].length && _jsxs("div", { className: "kl-integration-findings", children: [_jsx("span", { children: title }), _jsx("ul", { children: integration[key].map((item, index) => _jsx("li", { children: item }, index)) })] }, key)), integration.target && _jsxs(_Fragment, { children: [_jsxs("p", { className: "kl-integration-target", children: [integration.target.title, " \u00B7 ", integration.target.category, " \u00B7 \u7248\u672C ", integration.target.revision] }), _jsxs("fieldset", { className: "kl-knowledge-mode", children: [_jsx("legend", { children: "\u672C\u6B21\u5982\u4F55\u4FDD\u5B58" }), _jsxs("label", { children: [_jsx("input", { type: "radio", name: "integration-choice", "aria-label": integration.recommendation === 'duplicate' ? '仅关联已有笔记' : '补充已有笔记', checked: updating, onChange: () => setIntegration({ ...integration, choice: integration.recommendation }) }), integration.recommendation === 'duplicate' ? '仅关联来源,不重复正文' : '追加补充到已有笔记'] }), _jsxs("label", { children: [_jsx("input", { type: "radio", name: "integration-choice", "aria-label": "\u53E6\u5B58\u4E3A\u65B0\u7B14\u8BB0", checked: !updating, onChange: () => setIntegration({ ...integration, choice: 'new' }) }), "\u53E6\u5B58\u4E3A\u65B0\u7B14\u8BB0"] })] }), _jsxs("details", { children: [_jsx("summary", { children: "\u67E5\u770B\u5DF2\u6709\u7B14\u8BB0\u539F\u6587" }), _jsx("pre", { className: "kl-integration-preview", children: integration.target.content }), _jsxs("button", { type: "button", className: "kl-secondary", disabled: busy, onClick: () => void save(false, { id: integration.target.id }), children: ["\u4FDD\u5B58\u8349\u7A3F\u5E76\u6253\u5F00\u5DF2\u6709\u7B14\u8BB0", _jsx(Icon, { name: "arrow", size: 14 })] })] })] })] }), updating ? _jsxs(_Fragment, { children: [_jsx("p", { className: "kl-knowledge-model", children: "\u786E\u8BA4\u540E\u4FDD\u7559\u5DF2\u6709\u6807\u9898\u3001\u5206\u7C7B\u3001\u6B63\u6587\u4E0E\u65E7\u6BB5\u843D\u5F15\u7528\uFF0C\u5408\u5E76\u65B0\u6765\u6E90\u548C\u6807\u7B7E\u3002\u66F4\u65B0\u524D\u540E\u5FEB\u7167\u4FDD\u5B58\u5728\u672C\u5730\uFF1B\u5BA1\u6838\u671F\u95F4\u7B14\u8BB0\u53D8\u5316\u4F1A\u8981\u6C42\u91CD\u65B0\u6838\u5BF9\u3002" }), integration?.choice === 'append' ? _jsxs(_Fragment, { children: [_jsxs("label", { children: ["\u672C\u6B21\u65B0\u589E\u6B63\u6587", _jsx("textarea", { "aria-label": "\u672C\u6B21\u65B0\u589E\u6B63\u6587", required: true, rows: 12, maxLength: 50_000, value: integration.content, onChange: e => setIntegration({ ...integration, content: e.target.value }) })] }), _jsxs("details", { children: [_jsx("summary", { children: "\u66F4\u65B0\u540E\u6B63\u6587\u9884\u89C8" }), _jsxs("pre", { className: "kl-integration-preview", children: [integration.target.content, '\n\n## 知识补充 · ' + new Date().toISOString().slice(0, 10) + '\n\n' + integration.content] })] })] }) : _jsx("p", { className: "kl-muted", children: "\u672C\u6B21\u6CA1\u6709\u9700\u8981\u65B0\u589E\u7684\u6B63\u6587\u3002\u786E\u8BA4\u540E\u53EA\u628A\u8FD9\u4E9B\u6765\u6E90\u5173\u8054\u5230\u5DF2\u6709\u7B14\u8BB0\uFF0C\u4ECD\u4FDD\u7559\u5BF9\u6BD4\u7406\u7531\u4F9B\u6838\u67E5\u3002" })] }) : _jsxs(_Fragment, { children: [_jsxs("label", { children: ["\u7B14\u8BB0\u6807\u9898", _jsx("input", { "aria-label": "\u7B14\u8BB0\u6807\u9898", required: true, maxLength: 300, value: draft.title, onChange: e => setDraft({ ...draft, title: e.target.value }) })] }), _jsxs("label", { children: ["\u7B14\u8BB0\u6B63\u6587", _jsx("textarea", { "aria-label": "\u7B14\u8BB0\u6B63\u6587", required: true, rows: 14, maxLength: 50_000, value: draft.content, onChange: e => setDraft({ ...draft, content: e.target.value }) })] }), _jsxs("label", { children: ["\u7B14\u8BB0\u5206\u7C7B", _jsx("input", { "aria-label": "\u7B14\u8BB0\u5206\u7C7B", value: category, onChange: e => setCategory(e.target.value), maxLength: 180 })] })] }), _jsxs("label", { children: [updating ? '新增标签' : '标签', _jsx("input", { "aria-label": "\u7B14\u8BB0\u6807\u7B7E", value: tags, onChange: e => setTags(e.target.value), placeholder: "\u9017\u53F7\u5206\u9694" })] }), _jsxs("details", { className: "kl-knowledge-citations", children: [_jsxs("summary", { children: [draft.citations.length, " \u6761\u6765\u6E90\u5F15\u7528"] }), _jsx("p", { children: "\u67E5\u770B\u6765\u6E90\u524D\u4F1A\u5148\u4FDD\u5B58\u5F53\u524D\u8349\u7A3F\u3002" }), draft.citations.map((citation, index) => _jsxs("button", { type: "button", disabled: busy, onClick: () => void save(false, { id: citation.materialId, segment: citation.segmentId }), children: [_jsx(Icon, { name: "file", size: 14 }), _jsxs("span", { children: [job.sources.find(s => s.id === citation.materialId)?.title, " / ", citation.segmentId] }), _jsx(Icon, { name: "arrow", size: 14 })] }, index))] }), error && _jsx("p", { role: "alert", className: "kl-form-error", children: error }), _jsxs("footer", { children: [_jsx("button", { type: "button", className: "kl-secondary", disabled: busy, onClick: () => void save(false), children: "\u4FDD\u5B58\u8349\u7A3F" }), _jsx("button", { className: "kl-primary", disabled: busy, children: busy ? '正在保存…' : confirmText })] })] });
106
+ }
@@ -0,0 +1,7 @@
1
+ export declare const API = "/knowledge-library/api";
2
+ export declare class ApiError extends Error {
3
+ readonly status: number;
4
+ constructor(message: string, status: number);
5
+ }
6
+ export declare function api<T = any>(path: string, options?: RequestInit): Promise<T>;
7
+ export declare const post: <T>(path: string, value: unknown, method?: string, signal?: AbortSignal) => Promise<T>;
package/dist/ui/api.js ADDED
@@ -0,0 +1,16 @@
1
+ export const API = '/knowledge-library/api';
2
+ export class ApiError extends Error {
3
+ status;
4
+ constructor(message, status) {
5
+ super(message);
6
+ this.status = status;
7
+ }
8
+ }
9
+ export async function api(path, options = {}) {
10
+ const response = await fetch(API + path, { ...options, headers: { 'Content-Type': 'application/json', 'X-Knowledge-Library': '1', ...options.headers } });
11
+ const data = await response.json();
12
+ if (!response.ok)
13
+ throw new ApiError(data.error || `请求失败:${response.status}`, response.status);
14
+ return data;
15
+ }
16
+ export const post = (path, value, method = 'POST', signal) => api(path, { method, body: JSON.stringify(value), signal });
package/dist/ui/icons.js CHANGED
@@ -19,6 +19,8 @@ export function Icon({ name, size = 20 }) {
19
19
  copy: _jsxs(_Fragment, { children: [_jsx("rect", { x: "8", y: "8", width: "12", height: "13", rx: "2" }), _jsx("path", { d: "M16 8V3H3v13h5" })] }),
20
20
  download: _jsx(_Fragment, { children: _jsx("path", { d: "M12 3v13m-5-5 5 5 5-5M3 17v4h18v-4" }) }),
21
21
  settings: _jsxs(_Fragment, { children: [_jsx("path", { d: "M4 7h16M4 17h16" }), _jsx("circle", { cx: "9", cy: "7", r: "3" }), _jsx("circle", { cx: "15", cy: "17", r: "3" })] }),
22
+ trash: _jsx(_Fragment, { children: _jsx("path", { d: "M3 6h18M9 6V3h6v3M5 6l1 15h12l1-15M10 10v7M14 10v7" }) }),
23
+ restore: _jsx(_Fragment, { children: _jsx("path", { d: "M3 10a9 9 0 1 1 2 9M3 4v6h6M12 7v5l3 2" }) }),
22
24
  };
23
25
  return _jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: paths[name] || paths.file });
24
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-knowledge-library",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "本地优先的 DeepSeek Harness 资料库:采集、分类、检索与可追溯知识笔记",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -64,6 +64,9 @@
64
64
  "dev": "tsx src/standalone.ts",
65
65
  "start": "node dist/standalone.js",
66
66
  "test:ui": "python tests/run_ui.py",
67
+ "test:dsh:directory": "python tests/run_dsh_directory.py",
68
+ "test:knowledge:ui": "python tests/run_knowledge_ui.py",
69
+ "test:dsh:knowledge": "python tests/run_dsh_knowledge.py",
67
70
  "pack:check": "node scripts/check-package.mjs",
68
71
  "verify": "npm run build && npm test && npm run pack:check",
69
72
  "prepack": "npm run build"