@ilaomianyang/dsh-memory-graph 0.10.4 → 0.12.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/src/extract.js CHANGED
@@ -121,7 +121,13 @@ function extractJson(text) {
121
121
 
122
122
  export function parseCards(text) {
123
123
  const parsed = extractJson(text)
124
- if (parsed === null) return { cards: [], skipped: 'model output was not JSON', raw: String(text ?? '').slice(0, 600) }
124
+ if (parsed === null) {
125
+ return {
126
+ cards: [],
127
+ skipped: '模型输出不是 JSON · model output was not JSON',
128
+ raw: String(text ?? '').slice(0, 600),
129
+ }
130
+ }
125
131
  const list = Array.isArray(parsed) ? parsed : Array.isArray(parsed.cards) ? parsed.cards : []
126
132
  const cards = []
127
133
  for (const raw of list) {
@@ -253,14 +259,16 @@ export function resolveRoute(cfg, selection) {
253
259
  if (!useCurrentModel) {
254
260
  if (!provider || !model) {
255
261
  throw new Error(
256
- 'extractor.useCurrentModel is off, so extractor.provider and extractor.model are both required.',
262
+ '关掉了 extractor.useCurrentModel,所以 extractor.provider 和 extractor.model 都必须填 · extractor.useCurrentModel is off, so extractor.provider and extractor.model are both required.',
257
263
  )
258
264
  }
259
265
  return { provider, model, effort: reasoningEffort, source: 'config extractor.provider/model' }
260
266
  }
261
267
  if (!selection?.provider || !selection?.model) {
262
268
  throw new Error(
263
- 'No model is selected in Settings → Models, so the extractor has no route to borrow. ' +
269
+ '设置 → 模型里没有选中任何模型,所以抽取器没有可借用的路由。' +
270
+ '在那里选一个模型,或者填好 extractor.provider 和 extractor.model 再把 extractor.useCurrentModel 关掉 · ' +
271
+ 'No model is selected in Settings → Models, so the extractor has no route to borrow. ' +
264
272
  'Pick a model there, or set extractor.provider and extractor.model and turn extractor.useCurrentModel off.',
265
273
  )
266
274
  }
@@ -270,7 +278,7 @@ export function resolveRoute(cfg, selection) {
270
278
  // An explicit setting wins, so a big thinking budget can be reserved for
271
279
  // distillation without changing the model the conversation itself uses.
272
280
  effort: reasoningEffort || selectionEffort(selection),
273
- source: 'Settings → Models selection',
281
+ source: '设置 → 模型里选中的路由 · Settings → Models selection',
274
282
  }
275
283
  }
276
284
 
@@ -293,7 +301,7 @@ function splitMessages(messages) {
293
301
  async function callHarnessModel(cfg, messages, { signal, llm, selection } = {}) {
294
302
  if (!llm || typeof llm.prepareCall !== 'function') {
295
303
  throw new Error(
296
- 'This harness build exposes no llm service to plugins, so set extractor.transport = "http" to post to an endpoint directly.',
304
+ '这个 harness 版本没有把 llm 服务暴露给插件,所以请把 extractor.transport 设成 "http",直接向接口发请求 · This harness build exposes no llm service to plugins, so set extractor.transport = "http" to post to an endpoint directly.',
297
305
  )
298
306
  }
299
307
  const route = resolveRoute(cfg, selection)
@@ -339,26 +347,33 @@ async function callHarnessModel(cfg, messages, { signal, llm, selection } = {})
339
347
  if (controller.signal.aborted) {
340
348
  // Either the caller cancelled us or our own timer fired; say which,
341
349
  // because the fix is different (wait longer vs. run less work).
342
- const why = signal?.aborted
350
+ const whyZh = signal?.aborted ? '在路由应答之前就被取消了' : `超时了 ${cfg.extractor.timeoutMs} ms`
351
+ const whyEn = signal?.aborted
343
352
  ? 'was cancelled before the route answered'
344
353
  : `timed out after ${cfg.extractor.timeoutMs} ms`
345
354
  throw new Error(
346
- `Extractor call to ${route.provider}/${route.model} ${why}; raise extractor.timeoutMs or distill fewer episodes per run.`,
355
+ `抽取器调用 ${route.provider}/${route.model} 时${whyZh};可以调大 extractor.timeoutMs,或者每次少蒸馏几条情节 · ` +
356
+ `Extractor call to ${route.provider}/${route.model} ${whyEn}; raise extractor.timeoutMs or distill fewer episodes per run.`,
347
357
  )
348
358
  }
349
359
  throw new Error(
350
- `Extractor call failed on ${route.provider}/${route.model} (${failure.code ?? kind}): ${failure.message ?? 'the route ended the stream without a reason'}`,
360
+ `抽取器调用 ${route.provider}/${route.model} 失败(${failure.code ?? kind}):${failure.message ?? '路由结束了流但没给原因'} · ` +
361
+ `Extractor call failed on ${route.provider}/${route.model} (${failure.code ?? kind}): ${failure.message ?? 'the route ended the stream without a reason'}`,
351
362
  )
352
363
  }
353
364
  if (kind === 'tool-calls') {
354
- throw new Error('Extractor answered with a tool call instead of JSON. Try a different model for distillation.')
365
+ throw new Error(
366
+ '抽取器返回的是工具调用而不是 JSON,蒸馏时换一个模型试试 · Extractor answered with a tool call instead of JSON. Try a different model for distillation.',
367
+ )
355
368
  }
356
369
  if (kind === 'max-tokens') {
357
370
  throw truncated(
358
- `Extractor output reached maxOutputTokens (${cfg.extractor.maxOutputTokens}); raise it or distill fewer episodes per run.`,
371
+ `抽取器输出撞到了 maxOutputTokens 上限(${cfg.extractor.maxOutputTokens});把它调大,或者每次少蒸馏几条情节 · Extractor output reached maxOutputTokens (${cfg.extractor.maxOutputTokens}); raise it or distill fewer episodes per run.`,
359
372
  )
360
373
  }
361
- if (text.trim() === '') throw new Error('Extractor returned an empty completion.')
374
+ if (text.trim() === '') {
375
+ throw new Error('抽取器返回了空回复 · Extractor returned an empty completion.')
376
+ }
362
377
  return {
363
378
  content: text,
364
379
  usage,
@@ -377,7 +392,7 @@ export async function callHttpModel(cfg, messages, { signal } = {}) {
377
392
  if (key === '') {
378
393
  const where = looked.length > 0 ? looked.join(' or ') : 'config extractor.apiKey'
379
394
  throw new Error(
380
- `No API key for the extractor. Looked in ${where}. Set the env var, add the name to the harness credential store, or set config extractor.apiKey.`,
395
+ `抽取器没有可用的 API key,找过这些地方:${where}。请设置该环境变量、把名字加进 harness 的凭据库,或者直接填 config extractor.apiKey · No API key for the extractor. Looked in ${where}. Set the env var, add the name to the harness credential store, or set config extractor.apiKey.`,
381
396
  )
382
397
  }
383
398
  const url = `${cfg.extractor.baseUrl}${cfg.extractor.path}`
@@ -406,13 +421,17 @@ export async function callHttpModel(cfg, messages, { signal } = {}) {
406
421
  text = await response.text()
407
422
  }
408
423
  if (!response.ok) {
409
- throw new Error(`Extractor HTTP ${response.status} from ${url}: ${text.slice(0, 400)}`)
424
+ throw new Error(
425
+ `抽取器请求 ${url} 返回 HTTP ${response.status}:${text.slice(0, 400)} · Extractor HTTP ${response.status} from ${url}: ${text.slice(0, 400)}`,
426
+ )
410
427
  }
411
428
  let data
412
429
  try {
413
430
  data = JSON.parse(text)
414
431
  } catch {
415
- throw new Error(`Extractor returned non-JSON response: ${text.slice(0, 300)}`)
432
+ throw new Error(
433
+ `抽取器返回的不是 JSON:${text.slice(0, 300)} · Extractor returned non-JSON response: ${text.slice(0, 300)}`,
434
+ )
416
435
  }
417
436
  const content =
418
437
  data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.output_text ?? ''
@@ -420,11 +439,11 @@ export async function callHttpModel(cfg, messages, { signal } = {}) {
420
439
  // Same situation as the harness route's max-tokens finish: the answer is
421
440
  // cut off mid-JSON, so ask for less instead of blaming the parser.
422
441
  throw truncated(
423
- `Extractor output reached maxOutputTokens (${cfg.extractor.maxOutputTokens}); raise it or distill fewer episodes per run.`,
442
+ `抽取器输出撞到了 maxOutputTokens 上限(${cfg.extractor.maxOutputTokens});把它调大,或者每次少蒸馏几条情节 · Extractor output reached maxOutputTokens (${cfg.extractor.maxOutputTokens}); raise it or distill fewer episodes per run.`,
424
443
  )
425
444
  }
426
445
  if (typeof content !== 'string' || content.trim() === '') {
427
- throw new Error('Extractor returned an empty completion.')
446
+ throw new Error('抽取器返回了空回复 · Extractor returned an empty completion.')
428
447
  }
429
448
  return { content, usage: data?.usage ?? null, model: data?.model ?? cfg.extractor.model, keySource: source }
430
449
  } finally {
@@ -496,13 +515,18 @@ export async function distill({ cfg, vault, redactor, project, dryRun = false, s
496
515
  return {
497
516
  ok: false,
498
517
  reason:
499
- 'Extractor is disabled. Set config extractor.enabled = true; the default transport reuses the model selected in Settings → Models.',
518
+ '抽取器没有启用。请把 config extractor.enabled 设成 true;默认传输方式会复用「设置 → 模型」里选中的模型 · Extractor is disabled. Set config extractor.enabled = true; the default transport reuses the model selected in Settings → Models.',
500
519
  }
501
520
  }
502
521
 
503
522
  const entries = await vault.readUndistilled(cfg.extractor.maxEpisodesPerRun)
504
523
  if (entries.length === 0) {
505
- return { ok: true, episodes: 0, written: 0, reason: 'Every episode has already been distilled.' }
524
+ return {
525
+ ok: true,
526
+ episodes: 0,
527
+ written: 0,
528
+ reason: '所有情节都已经蒸馏过了 · Every episode has already been distilled.',
529
+ }
506
530
  }
507
531
 
508
532
  const known = await vault.listCards({ includeRetracted: true })
package/src/graph.js CHANGED
@@ -143,7 +143,7 @@ function buildModel({ cards, entities, projects }) {
143
143
  key: `c:${card.id}`,
144
144
  type: 'card',
145
145
  kind: String(card.kind ?? 'note'),
146
- label: String(card.title ?? '(untitled)'),
146
+ label: String(card.title ?? '(无标题 untitled)'),
147
147
  id: card.id,
148
148
  project: card.project ?? 'shared',
149
149
  status: card.status ?? 'active',
@@ -843,7 +843,7 @@ const BODY = `
843
843
  <button id="theme" title="主题:跟随系统 / 深色 / 浅色">◐</button>
844
844
  <button id="zoom-in" title="放大">+</button>
845
845
  <button id="zoom-out" title="缩小">−</button>
846
- <button id="zoom-fit" title="复位 (f)">fit</button>
846
+ <button id="zoom-fit" title="复位 (f)">复位</button>
847
847
  </div>
848
848
  </div>
849
849
  </header>
@@ -907,6 +907,11 @@ function clientScript(dataJson) {
907
907
  var selected = null;
908
908
  var view = { k: 1, x: 0, y: 0 };
909
909
  var GRADIENT_KINDS = ['decision', 'fact', 'open_question', 'preference', 'note', 'entity'];
910
+ var CONFIDENCE_LABELS = {
911
+ user_stated: '用户明说 user_stated',
912
+ verified: '已验证 verified',
913
+ inferred: '推断 inferred'
914
+ };
910
915
 
911
916
  /* ------------------------------------------------------------ theme */
912
917
 
@@ -1167,7 +1172,7 @@ function clientScript(dataJson) {
1167
1172
  if (node.type === 'entity') {
1168
1173
  var cards = Object.keys(neighbors[node.key]).filter(function (key) { return key.indexOf('c:') === 0; });
1169
1174
  tooltip.appendChild(h('div', 'tt-snip', cards.length + ' 张卡片 · ' + (node.projects.length ? node.projects.join(' / ') : '未归档')));
1170
- tooltip.appendChild(h('div', 'tt-meta', 'first ' + (node.firstSeen || '—') + ' · last ' + (node.lastSeen || '—')));
1175
+ tooltip.appendChild(h('div', 'tt-meta', '首次 ' + (node.firstSeen || '—') + ' · 最近 ' + (node.lastSeen || '—')));
1171
1176
  } else {
1172
1177
  if (node.snippet) tooltip.appendChild(h('div', 'tt-snip', node.snippet));
1173
1178
  tooltip.appendChild(h('div', 'tt-meta', node.id + ' · ' + node.project + ' · ' + node.created));
@@ -1273,7 +1278,7 @@ function clientScript(dataJson) {
1273
1278
  if (node.status !== 'active') {
1274
1279
  badges.appendChild(badge('warn', node.status === 'superseded' ? '已替换 superseded' : '已撤回 retracted', true));
1275
1280
  }
1276
- badges.appendChild(badge('plain', node.confidence, true));
1281
+ badges.appendChild(badge('plain', CONFIDENCE_LABELS[node.confidence] || node.confidence, true));
1277
1282
  panel.appendChild(badges);
1278
1283
 
1279
1284
  var meta = h('dl');
@@ -1560,7 +1565,7 @@ export function renderGraphHtml({ cards = [], entities = [], projects = [], gene
1560
1565
  generatedAt: generatedAt || stamp(),
1561
1566
  })
1562
1567
  const dataJson = escapeForScript(JSON.stringify(payload))
1563
- const title = `记忆图 · ${payload.counts.cards} cards / ${payload.counts.entities} entities`
1568
+ const title = `记忆图 · ${payload.counts.cards} 张卡片 / ${payload.counts.entities} 个实体`
1564
1569
 
1565
1570
  return `<!doctype html>
1566
1571
  <html lang="zh-CN">
package/src/graphdata.js CHANGED
@@ -462,7 +462,16 @@ export function createGraphHandler({ getVault, getConfig }) {
462
462
  return async function handle(request) {
463
463
  const url = new URL(request.url)
464
464
  const session = url.searchParams.get('session') ?? ''
465
- if (session === '') return json({ ok: false, reason: 'missing-session' }, 400)
465
+ if (session === '') {
466
+ return json(
467
+ {
468
+ ok: false,
469
+ reason: 'missing-session',
470
+ message: '这个会话没有 id,画不出它的记忆图 · this session has no id, so there is no graph to draw',
471
+ },
472
+ 400,
473
+ )
474
+ }
466
475
  try {
467
476
  const cfg = getConfig()
468
477
  const vault = getVault()