@lemoncat7/dsh-knowledge 2.5.1 → 2.6.1

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 (47) hide show
  1. package/README.md +15 -3
  2. package/docs/architecture.md +14 -4
  3. package/docs/releases/2.6.0.md +20 -0
  4. package/lib/api.d.ts.map +1 -1
  5. package/lib/api.js +19 -3
  6. package/lib/api.js.map +1 -1
  7. package/lib/client.d.ts.map +1 -1
  8. package/lib/client.js +118 -57
  9. package/lib/client.js.map +4 -4
  10. package/lib/config.d.ts +1 -0
  11. package/lib/config.d.ts.map +1 -1
  12. package/lib/config.js +5 -0
  13. package/lib/config.js.map +1 -1
  14. package/lib/extraction.d.ts +25 -1
  15. package/lib/extraction.d.ts.map +1 -1
  16. package/lib/extraction.js +70 -47
  17. package/lib/extraction.js.map +1 -1
  18. package/lib/index.d.ts.map +1 -1
  19. package/lib/index.js +55 -64
  20. package/lib/index.js.map +1 -1
  21. package/lib/local-provider.d.ts +3 -0
  22. package/lib/local-provider.d.ts.map +1 -1
  23. package/lib/local-provider.js +27 -6
  24. package/lib/local-provider.js.map +1 -1
  25. package/lib/notes/share-import.d.ts +2 -0
  26. package/lib/notes/share-import.d.ts.map +1 -1
  27. package/lib/notes/share-import.js +40 -5
  28. package/lib/notes/share-import.js.map +1 -1
  29. package/lib/provider.d.ts +3 -0
  30. package/lib/provider.d.ts.map +1 -1
  31. package/lib/remote-provider.d.ts +3 -0
  32. package/lib/remote-provider.d.ts.map +1 -1
  33. package/lib/remote-provider.js +10 -0
  34. package/lib/remote-provider.js.map +1 -1
  35. package/lib/writeback/queue.d.ts +40 -0
  36. package/lib/writeback/queue.d.ts.map +1 -0
  37. package/lib/writeback/queue.js +193 -0
  38. package/lib/writeback/queue.js.map +1 -0
  39. package/lib/writeback/status-client.d.ts +22 -0
  40. package/lib/writeback/status-client.d.ts.map +1 -0
  41. package/lib/writeback/status-client.js +97 -0
  42. package/lib/writeback/status-client.js.map +1 -0
  43. package/package.json +1 -1
  44. package/web/api-client.js +2 -1
  45. package/web/app.js +30 -12
  46. package/web/styles.css +4 -8
  47. package/web/ui-primitives.js +1 -0
@@ -0,0 +1,97 @@
1
+ /** One cancellable request stream per visible turn; stale GETs cannot undo retries. */
2
+ export class WritebackStatusClient {
3
+ url;
4
+ change;
5
+ fetcher;
6
+ request;
7
+ timer;
8
+ disposed = false;
9
+ visible = true;
10
+ retrying = false;
11
+ failures = 0;
12
+ value;
13
+ readError;
14
+ // Browser fetch checks its Window receiver. Never store it as an unbound
15
+ // instance method: this.fetcher() would supply the client as its receiver.
16
+ constructor(url, change, fetcher = (...args) => globalThis.fetch(...args)) {
17
+ this.url = url;
18
+ this.change = change;
19
+ this.fetcher = fetcher;
20
+ }
21
+ setVisible(visible) {
22
+ this.visible = visible;
23
+ if (visible)
24
+ this.refresh();
25
+ else {
26
+ clearTimeout(this.timer);
27
+ if (!this.retrying)
28
+ this.request?.abort();
29
+ }
30
+ }
31
+ refresh() {
32
+ if (this.disposed || !this.visible || this.retrying || this.request && !this.request.signal.aborted)
33
+ return;
34
+ void this.load(false);
35
+ }
36
+ retry() {
37
+ if (this.disposed || this.retrying || !this.value?.retryable)
38
+ return;
39
+ this.retrying = true;
40
+ this.request?.abort();
41
+ this.change(this.value, true);
42
+ void this.load(true);
43
+ }
44
+ async load(retry) {
45
+ clearTimeout(this.timer);
46
+ const controller = new AbortController();
47
+ this.request = controller;
48
+ const timeout = setTimeout(() => controller.abort(new Error('状态请求超时')), 15_000);
49
+ let delay = 15_000;
50
+ try {
51
+ const response = await this.fetcher(this.url, {
52
+ method: retry ? 'POST' : 'GET', signal: controller.signal,
53
+ headers: { accept: 'application/json', 'x-dsh-knowledge-client': 'conversation-web' },
54
+ });
55
+ const body = await response.json();
56
+ if (this.request !== controller || this.disposed)
57
+ return;
58
+ if (!response.ok && (retry || response.status !== 404))
59
+ throw new Error(body.error ?? `状态请求失败(HTTP ${response.status})`);
60
+ this.readError = undefined;
61
+ if (response.ok && body.summary) {
62
+ this.value = body;
63
+ this.failures = 0;
64
+ delay = body.status === 'completed' ? 0 : body.status === 'failed' ? 15_000 : 1_500;
65
+ }
66
+ else {
67
+ delay = ++this.failures <= 3 ? 1_500 : 60_000;
68
+ }
69
+ }
70
+ catch (error) {
71
+ if (this.request !== controller || this.disposed)
72
+ return;
73
+ delay = Math.min(60_000, 2_000 * 2 ** Math.min(5, this.failures++));
74
+ if (retry && this.value)
75
+ this.value = { ...this.value, error: error instanceof Error ? error.message : String(error) };
76
+ else if (this.visible)
77
+ this.readError = '暂时无法读取回写状态,正在自动重试;这不代表回写失败。';
78
+ }
79
+ finally {
80
+ clearTimeout(timeout);
81
+ if (this.request === controller && !this.disposed) {
82
+ this.request = undefined;
83
+ if (retry)
84
+ this.retrying = false;
85
+ this.change(this.value, this.retrying, this.readError);
86
+ if (delay && this.visible)
87
+ this.timer = setTimeout(() => this.refresh(), delay);
88
+ }
89
+ }
90
+ }
91
+ dispose() {
92
+ this.disposed = true;
93
+ clearTimeout(this.timer);
94
+ this.request?.abort();
95
+ }
96
+ }
97
+ //# sourceMappingURL=status-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-client.js","sourceRoot":"","sources":["../../src/writeback/status-client.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lemoncat7/dsh-knowledge",
3
- "version": "2.5.1",
3
+ "version": "2.6.1",
4
4
  "description": "Local and remote knowledge bases for DeepSeek Harness, with scoped recall, controlled write-back, and a Web management console",
5
5
  "keywords": [
6
6
  "deepseek-harness",
package/web/api-client.js CHANGED
@@ -30,6 +30,8 @@ export function createApiClient({ apiBase, authMode, getToken }) {
30
30
  if (!response.ok) {
31
31
  const error = new Error(payload?.error || `请求失败(HTTP ${response.status})`)
32
32
  error.status = response.status
33
+ error.code = payload?.code
34
+ error.origin = payload?.origin
33
35
  throw error
34
36
  }
35
37
  return payload
@@ -91,4 +93,3 @@ export function createApiClient({ apiBase, authMode, getToken }) {
91
93
 
92
94
  return { api, binaryRequest, binaryUploadRequest }
93
95
  }
94
-
package/web/app.js CHANGED
@@ -2181,8 +2181,9 @@ function openImportNoteShare() {
2181
2181
  destination.input.disabled = true
2182
2182
  const inspectButton = actionButton('读取分享', () => { void inspect() }, 'small')
2183
2183
  const fieldError = element('p', { class: 'share-import-field-error', role: 'alert', hidden: true })
2184
+ const privateConfirmation = element('div', { class: 'share-import-note', hidden: true, role: 'status' })
2184
2185
  const preview = element('div', { class: 'share-import-preview', 'data-state': 'empty', 'aria-live': 'polite' },
2185
- element('span', { class: 'share-import-preview-mark', 'aria-hidden': 'true' }),
2186
+ element('span', { class: 'share-import-preview-mark', 'aria-hidden': 'true' }, interfaceIcon('share-import')),
2186
2187
  element('div', {}, element('strong', {}, '等待读取分享'), element('p', {}, '读取后会显示类型、文件数量与大小;导入前不会写入任何内容。')),
2187
2188
  )
2188
2189
  const destinationHint = element('span', { class: 'field-hint' }, '正在读取你的笔记目录…')
@@ -2191,11 +2192,13 @@ function openImportNoteShare() {
2191
2192
  urlField.wrapper.replaceChildren(element('label', {}, '分享链接'), inputRow, fieldError)
2192
2193
  const form = element('form', { class: 'share-import-form', onSubmit: event => { event.preventDefault(); void inspect() } },
2193
2194
  urlField.wrapper,
2195
+ privateConfirmation,
2194
2196
  preview,
2195
2197
  destination.wrapper,
2196
2198
  element('p', { class: 'share-import-note' }, '导入会创建一份独立副本;之后对方更新或停止分享,都不会改动你已经导入的内容。'),
2197
2199
  )
2198
2200
  let inspectedUrl = ''
2201
+ let confirmedPrivateUrl = ''
2199
2202
 
2200
2203
  const showFieldError = message => {
2201
2204
  fieldError.textContent = message
@@ -2215,33 +2218,54 @@ function openImportNoteShare() {
2215
2218
  )
2216
2219
  }
2217
2220
  const inspect = async () => {
2221
+ if (inspectButton.disabled) return false
2218
2222
  if (!urlField.input.reportValidity()) return false
2223
+ const requestedUrl = urlField.input.value.trim()
2224
+ urlField.input.disabled = true
2225
+ privateConfirmation.hidden = true
2219
2226
  inspectButton.disabled = true
2220
2227
  inspectButton.setAttribute('aria-busy', 'true')
2221
2228
  inspectButton.textContent = '正在读取…'
2222
2229
  showFieldError('')
2223
2230
  preview.dataset.state = 'loading'
2224
2231
  try {
2225
- const result = await api('notes/import-share/inspect', { method: 'POST', body: { url: urlField.input.value.trim() } })
2226
- inspectedUrl = urlField.input.value.trim()
2232
+ const result = await api('notes/import-share/inspect', { method: 'POST', body: { url: requestedUrl, confirmPrivateShare: confirmedPrivateUrl === requestedUrl } })
2233
+ inspectedUrl = requestedUrl
2227
2234
  renderPreview(result.manifest)
2228
2235
  return !result.manifest.truncated
2229
2236
  } catch (error) {
2230
2237
  inspectedUrl = ''
2231
2238
  preview.dataset.state = 'error'
2232
2239
  preview.replaceChildren(
2233
- element('span', { class: 'share-import-preview-mark', 'aria-hidden': 'true' }),
2240
+ element('span', { class: 'share-import-preview-mark', 'aria-hidden': 'true' }, interfaceIcon('share-import')),
2234
2241
  element('div', {}, element('strong', {}, '无法读取这个分享'), element('p', {}, '请检查链接是否完整、分享是否仍有效。')),
2235
2242
  )
2236
2243
  showFieldError(friendlyError(error))
2244
+ if (error.code === 'PRIVATE_SHARE_CONFIRMATION_REQUIRED' && error.origin === new URL(requestedUrl).origin) {
2245
+ confirmedPrivateUrl = ''
2246
+ showFieldError('')
2247
+ preview.dataset.state = 'empty'
2248
+ preview.replaceChildren(
2249
+ element('span', { class: 'share-import-preview-mark', 'aria-hidden': 'true' }, interfaceIcon('share-import')),
2250
+ element('div', {}, element('strong', {}, '等待内网访问确认'), element('p', {}, '确认后读取分享清单,不会立即导入。')),
2251
+ )
2252
+ privateConfirmation.replaceChildren(
2253
+ element('p', {}, `目标 ${error.origin} 解析到内网地址。确认后,DSH 服务端将访问此链接的分享接口;请仅允许你信任的来源。授权仅用于本次读取和导入,不会保存到白名单。`),
2254
+ actionButton('允许本次内网分享并读取', () => { confirmedPrivateUrl = requestedUrl; void inspect() }, 'small'),
2255
+ )
2256
+ privateConfirmation.hidden = false
2257
+ }
2237
2258
  return false
2238
2259
  } finally {
2239
2260
  inspectButton.disabled = false
2261
+ urlField.input.disabled = false
2240
2262
  inspectButton.removeAttribute('aria-busy')
2241
2263
  inspectButton.textContent = '读取分享'
2242
2264
  }
2243
2265
  }
2244
2266
  urlField.input.addEventListener('input', () => {
2267
+ confirmedPrivateUrl = ''
2268
+ privateConfirmation.hidden = true
2245
2269
  if (urlField.input.value.trim() !== inspectedUrl) {
2246
2270
  inspectedUrl = ''
2247
2271
  preview.dataset.state = 'empty'
@@ -2265,7 +2289,7 @@ function openImportNoteShare() {
2265
2289
  showFieldError('')
2266
2290
  try {
2267
2291
  const result = await api('notes/import-share', {
2268
- method: 'POST', body: { url: requestedUrl, parentId: destination.input.value || null },
2292
+ method: 'POST', body: { url: requestedUrl, parentId: destination.input.value || null, confirmPrivateShare: confirmedPrivateUrl === requestedUrl },
2269
2293
  })
2270
2294
  state.notes.children.clear()
2271
2295
  state.notes.loadedFolders.clear()
@@ -2747,8 +2771,7 @@ function noteInfoItem(label, value, title = '', key = '') {
2747
2771
  }
2748
2772
 
2749
2773
  function renderNoteIcon(node, large = false) {
2750
- const label = node.kind === 'folder' ? '' : noteExtension(node.name)
2751
- return element('span', { class: `note-node-icon is-${node.kind}${large ? ' is-large' : ''}`, 'data-media-kind': noteMediaKind(node), 'aria-hidden': 'true' }, label)
2774
+ return element('span', { class: `note-node-icon ${node.kind === 'folder' ? 'tree-folder-icon' : 'tree-document-icon'}${large ? ' is-large' : ''}`, 'data-media-kind': noteMediaKind(node), 'aria-hidden': 'true' })
2752
2775
  }
2753
2776
 
2754
2777
  function noteKindLabel(node) {
@@ -3559,11 +3582,6 @@ function noteMediaKind(node) {
3559
3582
  return 'file'
3560
3583
  }
3561
3584
 
3562
- function noteExtension(name) {
3563
- const extension = name.includes('.') ? name.split('.').pop() : 'FILE'
3564
- return extension.slice(0, 4).toLocaleUpperCase()
3565
- }
3566
-
3567
3585
  function shortNoteId(id) {
3568
3586
  return `${id.slice(0, 11)}…${id.slice(-5)}`
3569
3587
  }
package/web/styles.css CHANGED
@@ -955,12 +955,8 @@ html,
955
955
  .notes-search-row strong, .notes-search-row small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
956
956
  .notes-search-row strong { font-size: 11px; }
957
957
  .notes-search-row small { margin-top: 1px; color: var(--text-tertiary); font-size: 10px; }
958
- .note-node-icon { position: relative; width: 18px; height: 21px; display: grid; place-items: end center; flex: none; border: 1px solid var(--border-strong); border-radius: 3px; padding-bottom: 2px; background: var(--surface-raised); color: var(--text-tertiary); font-size: 5px; font-weight: 720; letter-spacing: .02em; }
959
- .note-node-icon.is-folder { width: 20px; height: 15px; margin-top: 2px; border-radius: 3px; background: color-mix(in srgb, var(--text-tertiary) 11%, var(--surface-raised)); }
960
- .note-node-icon.is-folder::before { content: ""; position: absolute; top: -4px; left: 1px; width: 9px; height: 4px; border: 1px solid var(--border-strong); border-bottom: 0; border-radius: 3px 3px 0 0; background: inherit; }
961
- .note-node-icon.is-document, .note-node-icon[data-media-kind="text"] { border-color: color-mix(in srgb, var(--accent) 30%, var(--border-strong)); color: var(--accent); }
962
- .note-node-icon.is-large { width: 38px; height: 46px; border-radius: 6px; padding-bottom: 5px; font-size: 7px; }
963
- .note-node-icon.is-folder.is-large { width: 44px; height: 32px; }
958
+ .note-node-icon.is-large { width: 38px; height: 42px; }
959
+ .note-node-icon.tree-folder-icon.is-large { width: 40px; height: 35px; }
964
960
  .notes-content { min-width: 0; min-height: 0; overflow: auto; background: transparent; }
965
961
  .notes-content-header { position: sticky; top: 0; z-index: 2; border-bottom: 1px solid var(--separator); padding: 11px 22px 13px; background: transparent; -webkit-backdrop-filter: none; backdrop-filter: none; }
966
962
  .notes-breadcrumb { min-width: 0; display: flex; align-items: center; gap: 5px; overflow: hidden; color: var(--text-tertiary); font-size: 10px; }
@@ -1028,8 +1024,8 @@ html,
1028
1024
  .share-import-preview strong, .share-import-preview p { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1029
1025
  .share-import-preview strong { color: var(--text); font-size: 12px; }
1030
1026
  .share-import-preview p { margin: 4px 0 0; color: var(--text-tertiary); font-size: 10px; }
1031
- .share-import-preview-mark { position: relative; width: 42px; height: 32px; border: 1.5px solid var(--border-strong); border-radius: 8px; background: var(--surface-soft); }
1032
- .share-import-preview-mark::after { position: absolute; inset: 8px 12px; border: solid var(--text-tertiary); border-width: 0 1.5px 1.5px 0; content: ""; transform: rotate(45deg); }
1027
+ .share-import-preview-mark { display: grid; place-items: center; width: 42px; height: 42px; border: 1px solid var(--glass-border); border-radius: 12px; background: var(--surface-soft); color: var(--text-secondary); }
1028
+ .share-import-preview-mark > .interface-icon { width: 24px; height: 24px; }
1033
1029
  .share-import-note { margin: -4px 0 0; border-left: 2px solid var(--border-strong); padding: 5px 0 5px 10px; color: var(--text-tertiary); font-size: 10px; line-height: 1.6; }
1034
1030
  .dialog.share-import-dialog { width: min(560px, calc(100vw - 32px)); }
1035
1031
  .notes-content.is-document, .notes-content.is-file { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden; }
@@ -54,6 +54,7 @@ export function interfaceIcon(name, className = 'interface-icon') {
54
54
  outline: 'M8 6h11M8 12h11M8 18h11M4.5 6h.01M4.5 12h.01M4.5 18h.01',
55
55
  history: 'M4 4v5h5M4.8 8.2A8 8 0 1 1 4 13M12 7.5V12l3 2',
56
56
  download: 'M12 3v11M8 10l4 4 4-4M5 20h14',
57
+ 'share-import': 'M13 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-2M13 3v5h5l-5-5M21 12H10m4-4-4 4 4 4',
57
58
  link: 'M9.5 14.5l5-5M8.5 17H6a5 5 0 0 1 0-10h3M15.5 7H18a5 5 0 0 1 0 10h-3',
58
59
  rename: 'M4 20l4.2-1 10.4-10.4a2.1 2.1 0 0 0-3-3L5.2 16zM14.5 6.5l3 3',
59
60
  move: 'M3 7h6l2 2h10v11H3zM12 4h8m-3-3 3 3-3 3',