dsh-data-domain-knowledge 0.1.0-alpha.11 → 0.1.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -13
- package/eval/README.md +19 -8
- package/eval/evaluate-retrieval.mjs +17 -8
- package/eval/queries.example.jsonl +1 -0
- package/lib/client.js +2294 -2096
- package/lib/doc-page.js +1 -1
- package/lib/pages.js +2 -2
- package/lib/types/axis-registry.d.ts +9 -4
- package/lib/types/axis-registry.d.ts.map +1 -1
- package/lib/types/axis-registry.js +62 -23
- package/lib/types/axis-registry.js.map +1 -1
- package/lib/types/card-organize.d.ts +5 -0
- package/lib/types/card-organize.d.ts.map +1 -1
- package/lib/types/card-organize.js +15 -0
- package/lib/types/card-organize.js.map +1 -1
- package/lib/types/compiler/embedding-identity.d.ts +3 -0
- package/lib/types/compiler/embedding-identity.d.ts.map +1 -0
- package/lib/types/compiler/embedding-identity.js +24 -0
- package/lib/types/compiler/embedding-identity.js.map +1 -0
- package/lib/types/compiler/snapshot.d.ts +16 -0
- package/lib/types/compiler/snapshot.d.ts.map +1 -0
- package/lib/types/compiler/snapshot.js +2 -0
- package/lib/types/compiler/snapshot.js.map +1 -0
- package/lib/types/diagnosis/store.d.ts.map +1 -1
- package/lib/types/diagnosis/store.js +2 -0
- package/lib/types/diagnosis/store.js.map +1 -1
- package/lib/types/environment/install.d.ts +7 -0
- package/lib/types/environment/install.d.ts.map +1 -0
- package/lib/types/environment/install.js +172 -0
- package/lib/types/environment/install.js.map +1 -0
- package/lib/types/environment/manifest.d.ts +208 -0
- package/lib/types/environment/manifest.d.ts.map +1 -0
- package/lib/types/environment/manifest.js +26 -0
- package/lib/types/environment/manifest.js.map +1 -0
- package/lib/types/environment/model-catalog.d.ts +191 -0
- package/lib/types/environment/model-catalog.d.ts.map +1 -0
- package/lib/types/environment/model-catalog.js +91 -0
- package/lib/types/environment/model-catalog.js.map +1 -0
- package/lib/types/environment/process.d.ts +10 -0
- package/lib/types/environment/process.d.ts.map +1 -0
- package/lib/types/environment/process.js +54 -0
- package/lib/types/environment/process.js.map +1 -0
- package/lib/types/environment/selection.d.ts +9 -0
- package/lib/types/environment/selection.d.ts.map +1 -0
- package/lib/types/environment/selection.js +29 -0
- package/lib/types/environment/selection.js.map +1 -0
- package/lib/types/environment/service.d.ts +47 -0
- package/lib/types/environment/service.d.ts.map +1 -0
- package/lib/types/environment/service.js +253 -0
- package/lib/types/environment/service.js.map +1 -0
- package/lib/types/environment/types.d.ts +37 -0
- package/lib/types/environment/types.d.ts.map +1 -0
- package/lib/types/environment/types.js +2 -0
- package/lib/types/environment/types.js.map +1 -0
- package/lib/types/events.d.ts +10 -0
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/events.js +38 -1
- package/lib/types/events.js.map +1 -1
- package/lib/types/images.d.ts +26 -0
- package/lib/types/images.d.ts.map +1 -0
- package/lib/types/images.js +196 -0
- package/lib/types/images.js.map +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +59 -3
- package/lib/types/index.js.map +1 -1
- package/lib/types/knowledge-bases.d.ts +3 -0
- package/lib/types/knowledge-bases.d.ts.map +1 -1
- package/lib/types/knowledge-bases.js +9 -0
- package/lib/types/knowledge-bases.js.map +1 -1
- package/lib/types/knowledge.d.ts +14 -6
- package/lib/types/knowledge.d.ts.map +1 -1
- package/lib/types/knowledge.js +56 -39
- package/lib/types/knowledge.js.map +1 -1
- package/lib/types/layout.d.ts +2 -0
- package/lib/types/layout.d.ts.map +1 -1
- package/lib/types/layout.js +2 -0
- package/lib/types/layout.js.map +1 -1
- package/lib/types/retrieval/vector-index.d.ts +31 -0
- package/lib/types/retrieval/vector-index.d.ts.map +1 -0
- package/lib/types/retrieval/vector-index.js +15 -0
- package/lib/types/retrieval/vector-index.js.map +1 -0
- package/lib/types/retrieval/visibility.d.ts +6 -0
- package/lib/types/retrieval/visibility.d.ts.map +1 -0
- package/lib/types/retrieval/visibility.js +11 -0
- package/lib/types/retrieval/visibility.js.map +1 -0
- package/lib/types/runtime.d.ts +36 -6
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +520 -190
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/source-evidence.d.ts +6 -0
- package/lib/types/source-evidence.d.ts.map +1 -0
- package/lib/types/source-evidence.js +28 -0
- package/lib/types/source-evidence.js.map +1 -0
- package/lib/types/store/axis-references.d.ts +5 -0
- package/lib/types/store/axis-references.d.ts.map +1 -0
- package/lib/types/store/axis-references.js +106 -0
- package/lib/types/store/axis-references.js.map +1 -0
- package/lib/types/store/derived.d.ts +11 -0
- package/lib/types/store/derived.d.ts.map +1 -1
- package/lib/types/store/derived.js +31 -6
- package/lib/types/store/derived.js.map +1 -1
- package/lib/types/store/knowledge-db.d.ts +4 -1
- package/lib/types/store/knowledge-db.d.ts.map +1 -1
- package/lib/types/store/knowledge-db.js +15 -9
- package/lib/types/store/knowledge-db.js.map +1 -1
- package/lib/types/store/migrate.d.ts.map +1 -1
- package/lib/types/store/migrate.js +16 -5
- package/lib/types/store/migrate.js.map +1 -1
- package/lib/types/types.d.ts +7 -1
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js +3 -4
- package/lib/types/types.js.map +1 -1
- package/package.json +3 -1
- package/python/prepare-models.py +19 -0
- package/python/requirements.txt +8 -6
- package/scripts/doctor.mjs +14 -2
- package/scripts/environment.mjs +29 -0
- package/scripts/setup-embedding.sh +11 -2
- package/src/axis-registry.ts +59 -23
- package/src/card-organize.ts +15 -0
- package/src/client/EnvironmentSetup.tsx +145 -0
- package/src/client/KnowledgePanel.tsx +9 -4
- package/src/compiler/embedding-identity.ts +21 -0
- package/src/compiler/snapshot.ts +17 -0
- package/src/diagnosis/store.ts +1 -0
- package/src/environment/install.ts +129 -0
- package/src/environment/manifest.ts +28 -0
- package/src/environment/model-catalog.ts +105 -0
- package/src/environment/process.ts +54 -0
- package/src/environment/selection.ts +29 -0
- package/src/environment/service.ts +211 -0
- package/src/environment/types.ts +34 -0
- package/src/events.ts +46 -0
- package/src/images.ts +203 -0
- package/src/index.ts +54 -3
- package/src/knowledge-bases.ts +9 -0
- package/src/knowledge.ts +56 -40
- package/src/layout.ts +2 -0
- package/src/retrieval/vector-index.ts +44 -0
- package/src/retrieval/visibility.ts +14 -0
- package/src/runtime.ts +473 -233
- package/src/source-evidence.ts +30 -0
- package/src/store/axis-references.ts +96 -0
- package/src/store/derived.ts +31 -6
- package/src/store/knowledge-db.ts +16 -9
- package/src/store/migrate.ts +15 -5
- package/src/types.ts +10 -5
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { KnowledgeEnvironment } from '../lib/types/environment/service.js'
|
|
2
|
+
import { KnowledgeRuntime, defaultKnowledgeRoot } from '../lib/types/runtime.js'
|
|
3
|
+
import { defaultAgentRoot, resourceRoot } from '../lib/types/layout.js'
|
|
4
|
+
|
|
5
|
+
const runtime = new KnowledgeRuntime(defaultKnowledgeRoot(), {}, { resourceRoot: resourceRoot(defaultAgentRoot()) })
|
|
6
|
+
const environment = new KnowledgeEnvironment({ resourceRoot: resourceRoot(defaultAgentRoot()),
|
|
7
|
+
snapshot: () => ({ config: runtime.config, paths: runtime.resourcePaths() }),
|
|
8
|
+
probe: kind => runtime.environmentProbe(kind), reload: () => runtime.reloadResources(),
|
|
9
|
+
})
|
|
10
|
+
const stop = () => environment.cancel()
|
|
11
|
+
process.once('SIGINT', stop)
|
|
12
|
+
process.once('SIGTERM', stop)
|
|
13
|
+
const action = process.argv.includes('--install') ? 'install' : 'check'
|
|
14
|
+
environment.start(action)
|
|
15
|
+
let last = ''
|
|
16
|
+
const timer = setInterval(() => {
|
|
17
|
+
const view = environment.view()
|
|
18
|
+
const line = `${view.job?.stage ?? ''}: ${view.job?.logs.at(-1) ?? ''}`
|
|
19
|
+
if (line !== last) { process.stderr.write(`${line}\n`); last = line }
|
|
20
|
+
}, 1000)
|
|
21
|
+
try {
|
|
22
|
+
await environment.wait()
|
|
23
|
+
const view = environment.view()
|
|
24
|
+
console.log(JSON.stringify(view, null, 2))
|
|
25
|
+
if (view.job?.state !== 'succeeded') process.exitCode = 1
|
|
26
|
+
} finally {
|
|
27
|
+
clearInterval(timer); environment.close(); runtime.close()
|
|
28
|
+
process.removeListener('SIGINT', stop); process.removeListener('SIGTERM', stop)
|
|
29
|
+
}
|
|
@@ -9,11 +9,20 @@ DSH_HOME_ROOT="${DSH_HOME:-$HOME/.dsh}"
|
|
|
9
9
|
TITA_HOME="$DSH_HOME_ROOT/tita-agent"
|
|
10
10
|
VENV="${DSH_EMBEDDING_VENV:-$TITA_HOME/resource/python}"
|
|
11
11
|
MODEL_DIR="${DSH_EMBEDDING_MODEL_PATH:-$TITA_HOME/resource/models/WeMM-Embedding-2B-Quantized}"
|
|
12
|
+
BASE_PYTHON="${DSH_PYTHON:-python3}"
|
|
12
13
|
PYTHON_BIN="$VENV/bin/python"
|
|
13
14
|
|
|
14
15
|
mkdir -p "$TITA_HOME" "$MODEL_DIR"
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
BASE_VERSION="$("$BASE_PYTHON" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
|
|
17
|
+
if [[ ! "$BASE_VERSION" =~ ^3\.(10|11|12|13)$ ]]; then
|
|
18
|
+
echo "Unsupported base Python version: $BASE_VERSION (need 3.10–3.13)" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
if [ ! -x "$PYTHON_BIN" ] || [ "$("$PYTHON_BIN" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null || true)" != "$BASE_VERSION" ]; then
|
|
22
|
+
if [ -d "$VENV" ]; then
|
|
23
|
+
mv "$VENV" "$VENV-backup-$(date +%Y%m%d%H%M%S)-$$"
|
|
24
|
+
fi
|
|
25
|
+
uv venv --python "$BASE_PYTHON" --python-preference only-system "$VENV"
|
|
17
26
|
fi
|
|
18
27
|
uv pip install --python "$PYTHON_BIN" -r "$ROOT/python/requirements.txt"
|
|
19
28
|
|
package/src/axis-registry.ts
CHANGED
|
@@ -34,14 +34,13 @@
|
|
|
34
34
|
* ## 为什么存在记忆段
|
|
35
35
|
*
|
|
36
36
|
* 注册表**不可重建**:它是人确认过的命名决定(改 canonical name、并两个实体、加一个别名),
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* 重跑一次抽取不会得到同样的结果。所以它与 Knowledge 共享 `knowledge.db`,
|
|
38
|
+
* 经过同一个属主与 schema 版本入口校验。
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
import { createHash } from 'node:crypto'
|
|
42
|
-
import { mkdir } from 'node:fs/promises'
|
|
43
|
-
import { dirname } from 'node:path'
|
|
44
42
|
import { loadSqlite, type SqliteDatabase } from './fts.js'
|
|
43
|
+
import { KnowledgeDb } from './store/knowledge-db.js'
|
|
45
44
|
|
|
46
45
|
/** 三个轴。三者地位相同(§3.1)—— 没有哪个更特殊。 */
|
|
47
46
|
export type AxisKind = 'object' | 'action' | 'scene'
|
|
@@ -252,20 +251,27 @@ CREATE INDEX IF NOT EXISTS idx_axis_relation_to ON axis_relations(to_id, relatio
|
|
|
252
251
|
*/
|
|
253
252
|
export class AxisRegistry {
|
|
254
253
|
private db: SqliteDatabase | null = null
|
|
254
|
+
private owner: KnowledgeDb | null = null
|
|
255
255
|
|
|
256
|
-
constructor(private readonly file: string) {}
|
|
256
|
+
constructor(private readonly file: string, private readonly shared?: SqliteDatabase) {}
|
|
257
257
|
|
|
258
258
|
async open(): Promise<boolean> {
|
|
259
259
|
if (this.db !== null) return true
|
|
260
260
|
const sqlite = await loadSqlite()
|
|
261
261
|
if (sqlite === null) return false
|
|
262
262
|
try {
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
if (this.shared) this.db = this.shared
|
|
264
|
+
else {
|
|
265
|
+
const opened = await KnowledgeDb.open(this.file, sqlite.DatabaseSync)
|
|
266
|
+
if (!opened.ok) return false
|
|
267
|
+
this.owner = opened.db
|
|
268
|
+
this.db = opened.db.connection()
|
|
269
|
+
}
|
|
265
270
|
this.db.exec(SCHEMA)
|
|
271
|
+
this.db.exec('CREATE TABLE IF NOT EXISTS axis_input_revision (id INTEGER PRIMARY KEY CHECK(id = 1), revision INTEGER NOT NULL); INSERT OR IGNORE INTO axis_input_revision VALUES (1, 0)')
|
|
266
272
|
/*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
273
|
+
* 主库已通过 `user_version` 校验,再幂等补齐存量注册表的扩展列。缺就补。
|
|
274
|
+
* `CREATE TABLE IF NOT EXISTS` 对
|
|
269
275
|
* 已经存在的表一个字都不改 —— 少了这一段,0068 之前建的库会永远没有这两列,
|
|
270
276
|
* 而症状是「定义存进去了,下次打开又没了」。
|
|
271
277
|
*/
|
|
@@ -277,11 +283,19 @@ export class AxisRegistry {
|
|
|
277
283
|
}
|
|
278
284
|
return true
|
|
279
285
|
} catch {
|
|
286
|
+
this.owner?.close()
|
|
287
|
+
this.owner = null
|
|
280
288
|
this.db = null
|
|
281
289
|
return false
|
|
282
290
|
}
|
|
283
291
|
}
|
|
284
292
|
|
|
293
|
+
inputRevision(): number {
|
|
294
|
+
return Number((this.db?.prepare('SELECT revision FROM axis_input_revision WHERE id = 1').get() as { revision?: number } | undefined)?.revision ?? 0)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private changedInput(): void { this.db?.prepare('UPDATE axis_input_revision SET revision = revision + 1 WHERE id = 1').run() }
|
|
298
|
+
|
|
285
299
|
available(): boolean {
|
|
286
300
|
return this.db !== null
|
|
287
301
|
}
|
|
@@ -343,7 +357,7 @@ export class AxisRegistry {
|
|
|
343
357
|
|
|
344
358
|
// 2 · 作用域内 alias
|
|
345
359
|
if (parentId) {
|
|
346
|
-
const aliased = this.findAlias(normalized, parentId)
|
|
360
|
+
const aliased = this.findAlias(input.kind, normalized, parentId)
|
|
347
361
|
if (aliased) {
|
|
348
362
|
this.learn(aliased, mention, normalized, parentId, input.description)
|
|
349
363
|
return { mention, normalized, outcome: 'matched', entity: aliased, how: 'scoped-alias' }
|
|
@@ -557,6 +571,7 @@ export class AxisRegistry {
|
|
|
557
571
|
const now = new Date().toISOString()
|
|
558
572
|
this.db.prepare('UPDATE axis_entities SET name = ?, updated_at = ? WHERE id = ?').run(next, now, id)
|
|
559
573
|
this.insertAlias(id, current.name, normalizeAxisName(current.name), current.scopeId, now)
|
|
574
|
+
this.changedInput()
|
|
560
575
|
return this.get(id)
|
|
561
576
|
}
|
|
562
577
|
|
|
@@ -572,6 +587,7 @@ export class AxisRegistry {
|
|
|
572
587
|
const normalized = normalizeAxisName(alias)
|
|
573
588
|
if (!normalized) return false
|
|
574
589
|
this.insertAlias(id, alias.trim(), normalized, scopeId.trim(), new Date().toISOString())
|
|
590
|
+
this.changedInput()
|
|
575
591
|
return true
|
|
576
592
|
}
|
|
577
593
|
|
|
@@ -583,6 +599,7 @@ export class AxisRegistry {
|
|
|
583
599
|
async setStatus(id: string, status: AxisStatus): Promise<AxisEntity | null> {
|
|
584
600
|
if (this.db === null) throw new Error('AXIS_REGISTRY_UNAVAILABLE')
|
|
585
601
|
this.db.prepare('UPDATE axis_entities SET status = ?, updated_at = ? WHERE id = ?').run(status, new Date().toISOString(), id)
|
|
602
|
+
this.changedInput()
|
|
586
603
|
return this.get(id)
|
|
587
604
|
}
|
|
588
605
|
|
|
@@ -593,19 +610,34 @@ export class AxisRegistry {
|
|
|
593
610
|
* 它转成 `merged`,`scopeId` 改存目标 id —— 复用这一列而不是新加一列,因为
|
|
594
611
|
* 「它现在归谁」与「它在谁的下面」对解析来说问的是同一件事。
|
|
595
612
|
*/
|
|
596
|
-
async merge(fromId: string, intoId: string): Promise<boolean> {
|
|
613
|
+
async merge(fromId: string, intoId: string, redirect?: (kind: AxisKind) => void): Promise<boolean> {
|
|
597
614
|
if (this.db === null) throw new Error('AXIS_REGISTRY_UNAVAILABLE')
|
|
598
615
|
if (fromId === intoId) return false
|
|
599
616
|
const [from, into] = [await this.get(fromId), await this.get(intoId)]
|
|
600
|
-
if (from === null || into === null || from.kind !== into.kind) return false
|
|
617
|
+
if (from === null || into === null || from.kind !== into.kind || from.status === 'merged' || into.status === 'merged') return false
|
|
601
618
|
const now = new Date().toISOString()
|
|
602
|
-
this.db.
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
619
|
+
this.db.exec('SAVEPOINT axis_merge')
|
|
620
|
+
try {
|
|
621
|
+
this.db.prepare('UPDATE axis_entities SET status = ?, scope_id = ?, updated_at = ? WHERE id = ?').run('merged', intoId, now, fromId)
|
|
622
|
+
// 被并者的别名与名字一起搬过去:按旧名、旧别名都要搜得到。
|
|
623
|
+
this.insertAlias(intoId, from.name, normalizeAxisName(from.name), into.scopeId, now)
|
|
624
|
+
const aliases = this.db.prepare('SELECT alias, scope_id FROM axis_aliases WHERE entity_id = ?').all(fromId) as Array<Record<string, unknown>>
|
|
625
|
+
for (const row of aliases) this.insertAlias(intoId, String(row.alias), normalizeAxisName(String(row.alias)), String(row.scope_id ?? ''), now)
|
|
626
|
+
this.redirectRelations(fromId, intoId, now)
|
|
627
|
+
redirect?.(from.kind)
|
|
628
|
+
// Scope references on both canonical names and aliases move together.
|
|
629
|
+
this.db.prepare("UPDATE axis_entities SET scope_id = ? WHERE scope_id = ? AND status <> 'merged'").run(intoId, fromId)
|
|
630
|
+
this.db.prepare(`INSERT OR IGNORE INTO axis_aliases (entity_id, alias, normalized, scope_id, created_at)
|
|
631
|
+
SELECT entity_id, alias, normalized, ?, created_at FROM axis_aliases WHERE scope_id = ?`).run(intoId, fromId)
|
|
632
|
+
this.db.prepare('DELETE FROM axis_aliases WHERE scope_id = ?').run(fromId)
|
|
633
|
+
this.db.prepare('UPDATE entity_guidance SET entity_id = ? WHERE entity_id = ?').run(intoId, fromId)
|
|
634
|
+
this.changedInput()
|
|
635
|
+
this.db.exec('RELEASE axis_merge')
|
|
636
|
+
return true
|
|
637
|
+
} catch (cause) {
|
|
638
|
+
this.db.exec('ROLLBACK TO axis_merge; RELEASE axis_merge')
|
|
639
|
+
throw cause
|
|
640
|
+
}
|
|
609
641
|
}
|
|
610
642
|
|
|
611
643
|
/** 合并轴实体时转移相关关系,并折叠重复边与自环。 */
|
|
@@ -642,6 +674,7 @@ export class AxisRegistry {
|
|
|
642
674
|
const next = text.trim()
|
|
643
675
|
this.db.prepare('UPDATE axis_entities SET description = ?, description_source = ?, updated_at = ? WHERE id = ?')
|
|
644
676
|
.run(next, next ? 'human' : '', new Date().toISOString(), id)
|
|
677
|
+
this.changedInput()
|
|
645
678
|
return this.get(id)
|
|
646
679
|
}
|
|
647
680
|
|
|
@@ -732,6 +765,7 @@ export class AxisRegistry {
|
|
|
732
765
|
}
|
|
733
766
|
this.db.prepare('INSERT INTO entity_guidance (id, entity_id, kind, text, author, created_at) VALUES (?, ?, ?, ?, ?, ?)')
|
|
734
767
|
.run(guidance.id, guidance.entityId, guidance.kind, guidance.text, guidance.author, guidance.createdAt)
|
|
768
|
+
this.changedInput()
|
|
735
769
|
return guidance
|
|
736
770
|
}
|
|
737
771
|
|
|
@@ -741,6 +775,7 @@ export class AxisRegistry {
|
|
|
741
775
|
const before = this.db.prepare('SELECT id FROM entity_guidance WHERE id = ?').get(id) as { id?: unknown } | undefined
|
|
742
776
|
if (before === undefined) return false
|
|
743
777
|
this.db.prepare('DELETE FROM entity_guidance WHERE id = ?').run(id)
|
|
778
|
+
this.changedInput()
|
|
744
779
|
return true
|
|
745
780
|
}
|
|
746
781
|
|
|
@@ -829,7 +864,8 @@ export class AxisRegistry {
|
|
|
829
864
|
}
|
|
830
865
|
|
|
831
866
|
close(): void {
|
|
832
|
-
this.
|
|
867
|
+
this.owner?.close()
|
|
868
|
+
this.owner = null
|
|
833
869
|
this.db = null
|
|
834
870
|
}
|
|
835
871
|
|
|
@@ -843,10 +879,10 @@ export class AxisRegistry {
|
|
|
843
879
|
return hit === undefined ? undefined : rowToEntity(hit)
|
|
844
880
|
}
|
|
845
881
|
|
|
846
|
-
private findAlias(normalized: string, scopeId: string): AxisEntity | undefined {
|
|
882
|
+
private findAlias(kind: AxisKind, normalized: string, scopeId: string): AxisEntity | undefined {
|
|
847
883
|
const row = this.db!.prepare(
|
|
848
|
-
"SELECT e.* FROM axis_aliases a JOIN axis_entities e ON e.id = a.entity_id WHERE a.normalized = ? AND a.scope_id = ? AND e.status <> 'merged' LIMIT 1",
|
|
849
|
-
).get(normalized, scopeId) as Record<string, unknown> | undefined
|
|
884
|
+
"SELECT e.* FROM axis_aliases a JOIN axis_entities e ON e.id = a.entity_id WHERE a.normalized = ? AND a.scope_id = ? AND e.kind = ? AND e.status <> 'merged' LIMIT 1",
|
|
885
|
+
).get(normalized, scopeId, kind) as Record<string, unknown> | undefined
|
|
850
886
|
return row === undefined ? undefined : rowToEntity(row)
|
|
851
887
|
}
|
|
852
888
|
|
package/src/card-organize.ts
CHANGED
|
@@ -428,6 +428,8 @@ export function checkOrganize(
|
|
|
428
428
|
plan: OrganizePlan,
|
|
429
429
|
known: string[],
|
|
430
430
|
options: {
|
|
431
|
+
/** Check literal changes; this does not prove semantic entailment. */
|
|
432
|
+
knowledge?: Array<{ id: string; text: string }>
|
|
431
433
|
/**
|
|
432
434
|
* 正文里**可以**指向的其他卡片(`buildOrganizePrompt` 下发的那一份)。
|
|
433
435
|
*
|
|
@@ -442,11 +444,24 @@ export function checkOrganize(
|
|
|
442
444
|
const knownSet = new Set(known)
|
|
443
445
|
const cited: string[] = []
|
|
444
446
|
const seen = new Set<string>()
|
|
447
|
+
const byId = new Map(options.knowledge?.map(item => [item.id, item.text]) ?? [])
|
|
448
|
+
const literals = (text: string): string[] => [...text.replace(/\[\^[^\]]+\]/g, '').matchAll(/(?<![A-Za-z0-9_])(?:[<>≤≥]=?\s*)?[+-]?\d+(?:\.\d+)?(?:%|%)?/g)].map(match => match[0].replace(/\s/g, '').replace('%', '%'))
|
|
449
|
+
if (options.knowledge) {
|
|
450
|
+
const supported = new Set(literals(citationsIn(plan.summary).map(id => byId.get(id) ?? '').join('\n')))
|
|
451
|
+
const unsupported = literals(plan.summary).filter(value => !supported.has(value))
|
|
452
|
+
if (unsupported.length > 0) problems.push({ code: 'ORGANIZE_UNSUPPORTED_LITERAL', message: `概述的数值或比较条件没有引用依据:${[...new Set(unsupported)].join('、')}` })
|
|
453
|
+
}
|
|
445
454
|
|
|
446
455
|
// 1 + 2:正文里的引用必须都存在,且每条知识都要落地。
|
|
447
456
|
for (const section of plan.sections) {
|
|
448
457
|
for (const block of section.blocks) {
|
|
449
458
|
for (const text of blockTexts(block)) {
|
|
459
|
+
if (options.knowledge) {
|
|
460
|
+
const citedText = citationsIn(text).map(id => byId.get(id) ?? '').join('\n')
|
|
461
|
+
const supported = new Set(literals(citedText))
|
|
462
|
+
const unsupported = literals(text).filter(value => !supported.has(value))
|
|
463
|
+
if (unsupported.length > 0) problems.push({ code: 'ORGANIZE_UNSUPPORTED_LITERAL', message: `小节「${section.title}」的数值或比较条件没有引用依据:${[...new Set(unsupported)].join('、')}` })
|
|
464
|
+
}
|
|
450
465
|
for (const id of citationsIn(text)) {
|
|
451
466
|
if (!knownSet.has(id)) {
|
|
452
467
|
problems.push({ code: 'ORGANIZE_UNKNOWN_CITATION', message: `小节「${section.title}」引用了这批知识里没有的 id「${id}」` })
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState, type ReactNode } from 'react'
|
|
2
|
+
import { Button, cn, Select, SelectContent, SelectItem, SelectLabel, SelectGroup, SelectTrigger, SelectValue } from '@tita-agent/ui/components'
|
|
3
|
+
import { AlertCircle, Check, ChevronDown, Download, LoaderCircle, RefreshCw } from '@tita-agent/ui/icons'
|
|
4
|
+
import type { ModelSelection, ModelOptionView } from '../environment/model-catalog.js'
|
|
5
|
+
import type { EnvironmentView } from '../environment/types.js'
|
|
6
|
+
|
|
7
|
+
async function environmentRequest(init?: RequestInit): Promise<EnvironmentView> {
|
|
8
|
+
const response = await fetch(init ? '/knowledge/environment/prepare' : '/knowledge/environment', init)
|
|
9
|
+
const payload = await response.json() as { ok?: boolean; data?: EnvironmentView; error?: { message?: string } }
|
|
10
|
+
if (!response.ok || !payload.ok || !payload.data) throw new Error(payload.error?.message ?? `环境服务响应失败(${response.status})`)
|
|
11
|
+
return payload.data
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useKnowledgeEnvironment(visible: boolean, revision: number) {
|
|
15
|
+
const [view, setView] = useState<EnvironmentView>()
|
|
16
|
+
const [error, setError] = useState<string>()
|
|
17
|
+
const [submitting, setSubmitting] = useState(false)
|
|
18
|
+
const refresh = useCallback(async (): Promise<void> => {
|
|
19
|
+
try { setView(await environmentRequest()); setError(undefined) }
|
|
20
|
+
catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)) }
|
|
21
|
+
}, [])
|
|
22
|
+
const busy = submitting || view?.job?.state === 'running' || view?.report.state === 'checking'
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!visible) return
|
|
25
|
+
void refresh()
|
|
26
|
+
const onFocus = (): void => { void refresh() }
|
|
27
|
+
window.addEventListener('focus', onFocus)
|
|
28
|
+
const timer = busy ? setInterval(() => { void refresh() }, 1500) : undefined
|
|
29
|
+
return () => { if (timer) clearInterval(timer); window.removeEventListener('focus', onFocus) }
|
|
30
|
+
}, [visible, revision, busy, refresh])
|
|
31
|
+
const prepare = useCallback(async (action: 'install' | 'check' | 'cancel', selection?: ModelSelection): Promise<void> => {
|
|
32
|
+
setSubmitting(true)
|
|
33
|
+
try {
|
|
34
|
+
setView(await environmentRequest({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action, ...(selection ? { selection } : {}) }) }))
|
|
35
|
+
setError(undefined)
|
|
36
|
+
} catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)) }
|
|
37
|
+
finally { setSubmitting(false) }
|
|
38
|
+
}, [])
|
|
39
|
+
return { view, error, busy, prepare }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type EnvironmentController = ReturnType<typeof useKnowledgeEnvironment>
|
|
43
|
+
|
|
44
|
+
function ModelSelect({ id, label, value, options, disabled, onChange }: {
|
|
45
|
+
id: string
|
|
46
|
+
label: string
|
|
47
|
+
value: string
|
|
48
|
+
options: ModelOptionView[]
|
|
49
|
+
disabled: boolean
|
|
50
|
+
onChange: (value: string) => void
|
|
51
|
+
}): ReactNode {
|
|
52
|
+
const chosen = options.find(option => option.id === value)
|
|
53
|
+
const descriptionId = `${id}-description`
|
|
54
|
+
return <div className="grid min-w-0 gap-1.5">
|
|
55
|
+
<label htmlFor={id} className="text-xs font-medium text-tita-ink">{label}</label>
|
|
56
|
+
<Select value={value} onValueChange={onChange} disabled={disabled}>
|
|
57
|
+
<SelectTrigger id={id} aria-describedby={descriptionId} className="w-full"><SelectValue /></SelectTrigger>
|
|
58
|
+
<SelectContent position="popper" align="start" className="max-h-64">
|
|
59
|
+
<SelectGroup>
|
|
60
|
+
<SelectLabel>按资源占用从低到高</SelectLabel>
|
|
61
|
+
{options.map(option => <SelectItem key={option.id} value={option.id}>
|
|
62
|
+
{option.label} · {option.download}
|
|
63
|
+
</SelectItem>)}
|
|
64
|
+
</SelectGroup>
|
|
65
|
+
</SelectContent>
|
|
66
|
+
</Select>
|
|
67
|
+
<p id={descriptionId} className="m-0 text-xs leading-relaxed text-tita-ink-muted">
|
|
68
|
+
{chosen ? `${chosen.description} 下载约 ${chosen.download}${chosen.dimension ? `,输出 ${chosen.dimension} 维` : ''}。` : '请选择模型。'}
|
|
69
|
+
</p>
|
|
70
|
+
{chosen?.modelCardUrl && <a href={chosen.modelCardUrl} target="_blank" rel="noreferrer" className="w-fit text-xs text-tita-ink underline underline-offset-2">查看模型卡与许可</a>}
|
|
71
|
+
</div>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function EnvironmentNotice({ controller, onOpen }: { controller: EnvironmentController; onOpen: () => void }): ReactNode {
|
|
75
|
+
const { view, error, busy } = controller
|
|
76
|
+
if (!error && (!view || view.report.state === 'ready')) return null
|
|
77
|
+
return <div className="mx-3 mt-2 flex shrink-0 items-center gap-2 border-b border-tita-line pb-2 text-xs text-tita-ink" role="status">
|
|
78
|
+
{busy ? <LoaderCircle size={14} className="tita-spin shrink-0" /> : <AlertCircle size={14} className="shrink-0 text-tita-warning" />}
|
|
79
|
+
<span className="min-w-0 flex-1">{error ? '环境状态读取失败' : busy ? view?.job?.stage ?? '正在检查运行环境' : '首次使用前,请准备向量和重排模型'}</span>
|
|
80
|
+
<Button variant="outline" size="sm" onClick={onOpen}>环境准备</Button>
|
|
81
|
+
</div>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function EnvironmentSetup({ controller }: { controller: EnvironmentController }): ReactNode {
|
|
85
|
+
const { view, error, busy, prepare } = controller
|
|
86
|
+
const [details, setDetails] = useState(false)
|
|
87
|
+
const [selection, setSelection] = useState<ModelSelection>()
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (view) setSelection(view.selection)
|
|
90
|
+
}, [view?.selection.embedding, view?.selection.rerank])
|
|
91
|
+
const ready = view?.report.state === 'ready'
|
|
92
|
+
const job = view?.job
|
|
93
|
+
const running = job?.state === 'running'
|
|
94
|
+
const failure = job?.state === 'failed' ? job.error : undefined
|
|
95
|
+
const chosen = selection ?? view?.selection
|
|
96
|
+
const selectionChanged = Boolean(chosen && view && (chosen.embedding !== view.selection.embedding || chosen.rerank !== view.selection.rerank))
|
|
97
|
+
return <section id="tita-environment" tabIndex={-1} aria-labelledby="tita-environment-title" className="tita-settingsSection">
|
|
98
|
+
<div className="tita-settingsSectionHeader">
|
|
99
|
+
<h3 id="tita-environment-title">环境准备</h3>
|
|
100
|
+
<span className={cn('text-xs', ready ? 'text-tita-success' : 'text-tita-ink-muted')} role="status" aria-live="polite">
|
|
101
|
+
{ready ? '已就绪' : busy ? '进行中' : '必需'}
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
<p className="m-0 mb-3 text-xs leading-relaxed text-tita-ink-muted">首次准备需要联网下载所选模型和推理依赖。模型保存在 Tita 专属目录,并通过实际推理自检后启用。</p>
|
|
105
|
+
<ul className="m-0 grid list-none gap-3 p-0" aria-label="运行环境检查项">
|
|
106
|
+
{(view?.report.items ?? []).map(item => <li key={item.id} className="flex min-w-0 items-start gap-2">
|
|
107
|
+
{item.state === 'ready' ? <Check size={14} className="mt-0.5 shrink-0 text-tita-success" /> : item.state === 'checking'
|
|
108
|
+
? <LoaderCircle size={14} className="tita-spin mt-0.5 shrink-0" /> : <AlertCircle size={14} className="mt-0.5 shrink-0 text-tita-warning" />}
|
|
109
|
+
<div className="min-w-0 flex-1 text-xs leading-relaxed">
|
|
110
|
+
<span className="font-medium text-tita-ink">{item.label}</span>
|
|
111
|
+
<p className="m-0 break-words text-tita-ink-muted">{item.message}</p>
|
|
112
|
+
</div>
|
|
113
|
+
</li>)}
|
|
114
|
+
</ul>
|
|
115
|
+
{view && chosen && <div className="my-4 grid gap-3" aria-label="模型选择">
|
|
116
|
+
<ModelSelect id="tita-embedding-model" label="向量模型" value={chosen.embedding} options={view.models.embedding} disabled={busy}
|
|
117
|
+
onChange={value => setSelection(current => ({ ...(current ?? view.selection), embedding: value as ModelSelection['embedding'] }))} />
|
|
118
|
+
<ModelSelect id="tita-rerank-model" label="重排模型" value={chosen.rerank} options={view.models.rerank} disabled={busy}
|
|
119
|
+
onChange={value => setSelection(current => ({ ...(current ?? view.selection), rerank: value as ModelSelection['rerank'] }))} />
|
|
120
|
+
{selectionChanged && <p className="m-0 text-xs leading-relaxed text-tita-warning" role="status">
|
|
121
|
+
安装并通过实际推理自检后才会切换当前模型。更改向量模型后,已有知识需要重新编译。
|
|
122
|
+
</p>}
|
|
123
|
+
</div>}
|
|
124
|
+
{!view && !error && <p className="text-xs text-tita-ink-muted" role="status">正在读取环境状态…</p>}
|
|
125
|
+
{running && <p className="my-3 text-xs text-tita-ink" role="status" aria-live="polite">{job.stage}。你可以离开此面板,任务会继续。</p>}
|
|
126
|
+
{(error || failure) && <p className="my-3 break-words text-xs text-tita-danger" role="alert">{error ?? failure}</p>}
|
|
127
|
+
{job?.state === 'cancelled' && <p className="my-3 text-xs text-tita-ink-muted" role="status">{job.error}</p>}
|
|
128
|
+
<div className="mt-4 flex flex-wrap gap-2">
|
|
129
|
+
<Button size="sm" variant={ready ? 'outline' : 'default'} disabled={busy} onClick={() => { void prepare('install', chosen) }}>
|
|
130
|
+
<Download size={13} />{selectionChanged ? '安装所选模型' : ready ? '修复当前环境' : job?.state === 'failed' || job?.state === 'cancelled' ? '重试准备' : '一键安装 / 修复'}
|
|
131
|
+
</Button>
|
|
132
|
+
<Button variant="outline" size="sm" disabled={busy || selectionChanged} onClick={() => { void prepare('check') }}><RefreshCw size={13} />重新自检</Button>
|
|
133
|
+
{running && <Button variant="ghost" size="sm" disabled={controller.busy && !running} onClick={() => { void prepare('cancel') }}>停止</Button>}
|
|
134
|
+
</div>
|
|
135
|
+
{ready && <p className="mb-0 mt-3 text-xs text-tita-ink-muted">环境自检通过。已有知识请重新编译,以生成当前模型的向量索引。</p>}
|
|
136
|
+
<Button variant="ghost" size="sm" className="mt-2" aria-expanded={details} aria-controls="tita-environment-details" onClick={() => setDetails(value => !value)}>
|
|
137
|
+
<ChevronDown size={13} />{details ? '收起检查详情' : '检查详情与日志'}
|
|
138
|
+
</Button>
|
|
139
|
+
{details && <div id="tita-environment-details" className="mt-2 grid gap-2 text-xs leading-relaxed text-tita-ink-muted">
|
|
140
|
+
{view?.report.checkedAt && <p className="m-0">最近自检:{new Date(view.report.checkedAt).toLocaleString()}</p>}
|
|
141
|
+
{view?.report.items.map(item => item.path && <p key={item.id} className="m-0 break-all">{item.label}:{item.path}</p>)}
|
|
142
|
+
{job?.logs.length ? <pre className="m-0 max-h-40 overflow-auto whitespace-pre-wrap break-all rounded-tita-control bg-tita-surface-muted p-2 font-mono">{job.logs.join('\n')}</pre> : null}
|
|
143
|
+
</div>}
|
|
144
|
+
</section>
|
|
145
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { KNOWLEDGE_SETTINGS_ORDER, NodeSettingsPanel, settingsAnchorId, useScope, type SettingsNode, type SettingsScopeLike } from './NodeSettings.tsx'
|
|
12
12
|
import { useAgentLayout } from './DirectoryPicker.tsx'
|
|
13
13
|
import { AgentPresetCard, type AgentPresetRemote } from './AgentPresetCard.tsx'
|
|
14
|
+
import { EnvironmentNotice, EnvironmentSetup, useKnowledgeEnvironment } from './EnvironmentSetup.tsx'
|
|
14
15
|
import {
|
|
15
16
|
NODE_META, NODE_ORDER, NODE_STATUS_LABEL, noteCountOf, sourceCountOf,
|
|
16
17
|
type KnowledgeStats, type KnowledgeTab, type NodeId, type PanelTab, type Tone, type WorkspaceTabKind,
|
|
@@ -212,7 +213,7 @@ export function KnowledgePanel({ visible, currentQuestion, onOpenTab, settings,
|
|
|
212
213
|
/** 从架构图节点跳到知识库分栏:tab + 一个递增的 nonce(同栏再点也要生效)。 */
|
|
213
214
|
const [knowledgeFocus, setKnowledgeFocus] = useState<{ tab: KnowledgeTab; nonce: number } | undefined>(undefined)
|
|
214
215
|
/** 设置抽屉入口。`null` = 关闭;否则定位到对应分组。 */
|
|
215
|
-
const [settingsTarget, setSettingsTarget] = useState<SettingsNode | null>(null)
|
|
216
|
+
const [settingsTarget, setSettingsTarget] = useState<SettingsNode | 'environment' | null>(null)
|
|
216
217
|
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false)
|
|
217
218
|
/**
|
|
218
219
|
* 固定 Agent 目录 + 家当解析结果。它们是**服务端算出来的**(`layout.ts` 是唯一真源),
|
|
@@ -223,6 +224,7 @@ export function KnowledgePanel({ visible, currentQuestion, onOpenTab, settings,
|
|
|
223
224
|
*/
|
|
224
225
|
const knowledgeSnapshot = useScope(settings?.knowledge)
|
|
225
226
|
const { layout } = useAgentLayout(knowledgeSnapshot.revision ?? 0)
|
|
227
|
+
const environment = useKnowledgeEnvironment(visible, knowledgeSnapshot.revision ?? 0)
|
|
226
228
|
|
|
227
229
|
/**
|
|
228
230
|
* 只报**失败**的两条(2026-10-06)。
|
|
@@ -252,7 +254,7 @@ export function KnowledgePanel({ visible, currentQuestion, onOpenTab, settings,
|
|
|
252
254
|
useEffect(() => {
|
|
253
255
|
if (!settingsTarget) return
|
|
254
256
|
const frame = requestAnimationFrame(() => {
|
|
255
|
-
const section = document.getElementById(settingsAnchorId(settingsTarget))
|
|
257
|
+
const section = document.getElementById(settingsTarget === 'environment' ? 'tita-environment' : settingsAnchorId(settingsTarget))
|
|
256
258
|
section?.scrollIntoView({ block: 'start' })
|
|
257
259
|
section?.focus({ preventScroll: true })
|
|
258
260
|
})
|
|
@@ -260,7 +262,7 @@ export function KnowledgePanel({ visible, currentQuestion, onOpenTab, settings,
|
|
|
260
262
|
}, [settingsTarget])
|
|
261
263
|
|
|
262
264
|
/** 打开设置抽屉并落到某一块。传 `null` 就是关。 */
|
|
263
|
-
const openSettings = useCallback((target: SettingsNode | null): void => { setSettingsTarget(target) }, [])
|
|
265
|
+
const openSettings = useCallback((target: SettingsNode | 'environment' | null): void => { setSettingsTarget(target) }, [])
|
|
264
266
|
|
|
265
267
|
const refreshStats = useCallback(async (): Promise<void> => {
|
|
266
268
|
setStatsLoading(true); setStatsError(null)
|
|
@@ -330,10 +332,12 @@ export function KnowledgePanel({ visible, currentQuestion, onOpenTab, settings,
|
|
|
330
332
|
<Button
|
|
331
333
|
variant="ghost" size="icon" className={styles.tabBarSettings}
|
|
332
334
|
aria-label="设置" title="设置 · 本方案参数"
|
|
333
|
-
onClick={() => openSettings('
|
|
335
|
+
onClick={() => openSettings('environment')}
|
|
334
336
|
><Settings2 size={15} /></Button>
|
|
335
337
|
</div>
|
|
336
338
|
|
|
339
|
+
<EnvironmentNotice controller={environment} onOpen={() => openSettings('environment')} />
|
|
340
|
+
|
|
337
341
|
{/* 两栏走「舞台」版式(自己占满高度、不参与面板滚动):智能归因是图 + 运行面板,
|
|
338
342
|
概览是画布 + 画布内浮层。同一套语汇,同一个理由 —— 它们的内容都要自己撑满
|
|
339
343
|
可视区,让面板替它们滚会把画布推成一条可以滚出视口的纸。 */}
|
|
@@ -436,6 +440,7 @@ export function KnowledgePanel({ visible, currentQuestion, onOpenTab, settings,
|
|
|
436
440
|
<DrawerBody>
|
|
437
441
|
{/* 设置按数据位置、向量模型、检索与上下文分组。坐标定义按知识库保存,入口在工具栏。 */}
|
|
438
442
|
<div className={styles.settingsStack}>
|
|
443
|
+
<EnvironmentSetup controller={environment} />
|
|
439
444
|
{/* 故障排在最前面(同一条纪律:对不上账的事排在「有什么」之前)。 */}
|
|
440
445
|
{layoutFailures.map(text => <div key={text} className="tita-settingsError" role="alert">
|
|
441
446
|
<AlertCircle size={13} /><span>{text}</span>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { readdirSync, statSync } from 'node:fs'
|
|
3
|
+
import { isAbsolute, join, relative, resolve, sep } from 'node:path'
|
|
4
|
+
|
|
5
|
+
/** Cache compatibility includes the checkpoint location and its local revision. */
|
|
6
|
+
export function embeddingFingerprint(model: string, resourceRoot?: string, endpoint?: string): string {
|
|
7
|
+
let location = model
|
|
8
|
+
const files: Array<[string, number, number]> = []
|
|
9
|
+
if (isAbsolute(model)) {
|
|
10
|
+
const root = resourceRoot && resolve(resourceRoot)
|
|
11
|
+
location = root && model.startsWith(`${root}${sep}`) ? `resource:${relative(root, model)}` : resolve(model)
|
|
12
|
+
try {
|
|
13
|
+
for (const name of readdirSync(model).sort()) {
|
|
14
|
+
if (!/\.(?:json|safetensors|bin|model)$/.test(name)) continue
|
|
15
|
+
const stat = statSync(join(model, name))
|
|
16
|
+
if (stat.isFile()) files.push([name, stat.size, stat.mtimeMs])
|
|
17
|
+
}
|
|
18
|
+
} catch { /* The normal model loader reports missing or inaccessible checkpoints. */ }
|
|
19
|
+
}
|
|
20
|
+
return createHash('sha256').update(JSON.stringify({ location, files, endpoint })).digest('hex')
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Knowledge } from '../knowledge.js'
|
|
2
|
+
import type { CardKnowledgeRow } from '../store/knowledge-db.js'
|
|
3
|
+
import type { ContextGraph, KnowledgeCard, WikiNote } from '../types.js'
|
|
4
|
+
|
|
5
|
+
/** One published generation; retrieval captures this value before any model call. */
|
|
6
|
+
export interface CompileSnapshot {
|
|
7
|
+
version: number
|
|
8
|
+
compileRunId: string
|
|
9
|
+
cards: KnowledgeCard[]
|
|
10
|
+
notes: WikiNote[]
|
|
11
|
+
graph: ContextGraph
|
|
12
|
+
diagnosisGraph: ContextGraph
|
|
13
|
+
placements: Array<[string, CardKnowledgeRow[]]>
|
|
14
|
+
knowledge: Knowledge[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const COMPILE_SNAPSHOT = 'recall-snapshot.json'
|
package/src/diagnosis/store.ts
CHANGED
|
@@ -317,6 +317,7 @@ export class DiagnosisStore {
|
|
|
317
317
|
const records: DiagnosisRunRecord[] = []
|
|
318
318
|
const problems: string[] = []
|
|
319
319
|
for (const source of await this.host.loadSources()) {
|
|
320
|
+
if (!source.sourceId.startsWith('diagnosis:') && !source.content.includes('```json diagnosis-run')) continue
|
|
320
321
|
const record = parseRunSource(source.content)
|
|
321
322
|
if (record) records.push(record)
|
|
322
323
|
else problems.push(`诊断日志 ${source.sourceId} 里没有可解析的 \`json diagnosis-run\` 块,已跳过`)
|