@lemoncat7/dsh-knowledge 2.9.6 → 2.10.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/README.md +7 -1
- package/docs/document-groups.md +22 -0
- package/docs/releases/2.10.0.md +17 -0
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js +22 -1
- package/lib/api.js.map +1 -1
- package/lib/client.js +33 -10
- package/lib/client.js.map +3 -3
- package/lib/document-group-tools.d.ts +6 -0
- package/lib/document-group-tools.d.ts.map +1 -0
- package/lib/document-group-tools.js +96 -0
- package/lib/document-group-tools.js.map +1 -0
- package/lib/document-groups.d.ts +8 -0
- package/lib/document-groups.d.ts.map +1 -0
- package/lib/document-groups.js +18 -0
- package/lib/document-groups.js.map +1 -0
- package/lib/documents/markdown.d.ts +1 -0
- package/lib/documents/markdown.d.ts.map +1 -1
- package/lib/documents/markdown.js +2 -0
- package/lib/documents/markdown.js.map +1 -1
- package/lib/domain.d.ts +6 -0
- package/lib/domain.d.ts.map +1 -1
- package/lib/domain.js +2 -0
- package/lib/domain.js.map +1 -1
- package/lib/extraction.d.ts.map +1 -1
- package/lib/extraction.js +16 -4
- package/lib/extraction.js.map +1 -1
- package/lib/knowledge-activity-panel.d.ts.map +1 -1
- package/lib/knowledge-activity-panel.js +23 -1
- package/lib/knowledge-activity-panel.js.map +1 -1
- package/lib/local-provider.d.ts +6 -0
- package/lib/local-provider.d.ts.map +1 -1
- package/lib/local-provider.js +94 -19
- package/lib/local-provider.js.map +1 -1
- package/lib/note-excerpt.d.ts +1 -0
- package/lib/note-excerpt.d.ts.map +1 -1
- package/lib/note-excerpt.js.map +1 -1
- package/lib/provider.d.ts +3 -0
- package/lib/provider.d.ts.map +1 -1
- package/lib/remote-provider.d.ts +2 -0
- package/lib/remote-provider.d.ts.map +1 -1
- package/lib/remote-provider.js +6 -0
- package/lib/remote-provider.js.map +1 -1
- package/lib/retrieval.d.ts +1 -0
- package/lib/retrieval.d.ts.map +1 -1
- package/lib/retrieval.js +2 -1
- package/lib/retrieval.js.map +1 -1
- package/lib/storage/migrations.d.ts.map +1 -1
- package/lib/storage/migrations.js +18 -1
- package/lib/storage/migrations.js.map +1 -1
- package/lib/tools.d.ts.map +1 -1
- package/lib/tools.js +3 -0
- package/lib/tools.js.map +1 -1
- package/lib/tracking.d.ts.map +1 -1
- package/lib/tracking.js +5 -0
- package/lib/tracking.js.map +1 -1
- package/lib/web.js +1 -1
- package/lib/web.js.map +1 -1
- package/package.json +1 -1
- package/web/app.js +88 -12
- package/web/document-groups.js +99 -0
- package/web/note-excerpt.js +13 -3
- package/web/styles.css +24 -0
package/lib/local-provider.js
CHANGED
|
@@ -13,13 +13,14 @@ import { enqueueDocumentProjection } from './documents/projection-queue.js';
|
|
|
13
13
|
import { applyKnowledgeTextEdits, mergeKnowledgeBodies } from './knowledge-merge.js';
|
|
14
14
|
import { normalizeFinalizationChange } from './document-lifecycle.js';
|
|
15
15
|
import { NoteStore } from './notes/store.js';
|
|
16
|
+
import { normalizeDocumentGroup, matchDocumentGroup } from './document-groups.js';
|
|
16
17
|
const ENTRY_COLUMNS = `
|
|
17
18
|
id, knowledge_base_id, title, body, type, tags_json, scope_kind, scope_id, confidence,
|
|
18
|
-
status, document_state, finalized_at, finalization_note, version, source_json, created_at, updated_at
|
|
19
|
+
status, document_group, document_state, finalized_at, finalization_note, version, source_json, created_at, updated_at
|
|
19
20
|
`;
|
|
20
21
|
const JOINED_ENTRY_COLUMNS = `
|
|
21
22
|
e.id AS id, e.knowledge_base_id AS knowledge_base_id, e.title AS title, e.body AS body, e.type AS type,
|
|
22
|
-
e.tags_json AS tags_json, e.scope_kind AS scope_kind, e.scope_id AS scope_id,
|
|
23
|
+
e.tags_json AS tags_json, e.document_group AS document_group, e.scope_kind AS scope_kind, e.scope_id AS scope_id,
|
|
23
24
|
e.confidence AS confidence, e.status AS status, e.document_state AS document_state,
|
|
24
25
|
e.finalized_at AS finalized_at, e.finalization_note AS finalization_note, e.version AS version,
|
|
25
26
|
e.source_json AS source_json, e.created_at AS created_at, e.updated_at AS updated_at
|
|
@@ -297,7 +298,7 @@ export class LocalKnowledgeProvider {
|
|
|
297
298
|
const rows = this.db.prepare(`
|
|
298
299
|
WITH document_index AS (
|
|
299
300
|
SELECT
|
|
300
|
-
id, knowledge_base_id, rel_path, title, entry_count, content_hash,
|
|
301
|
+
id, knowledge_base_id, rel_path, title, entry_count, content_hash, document_group,
|
|
301
302
|
document_state, finalized_at, finalization_note, created_at, updated_at,
|
|
302
303
|
CASE WHEN rel_path='README.md' THEN 0 ELSE 1 END AS sort_rank
|
|
303
304
|
FROM knowledge_documents${sourceWhere}
|
|
@@ -325,6 +326,50 @@ export class LocalKnowledgeProvider {
|
|
|
325
326
|
const row = this.db.prepare('SELECT * FROM knowledge_documents WHERE id=?').get(id);
|
|
326
327
|
return row === undefined ? undefined : rowToDocument(row);
|
|
327
328
|
}
|
|
329
|
+
async listDocumentGroups(knowledgeBaseId) {
|
|
330
|
+
this.assertOpen();
|
|
331
|
+
if (!await this.getKnowledgeBase(knowledgeBaseId))
|
|
332
|
+
throw notFound('knowledge base', knowledgeBaseId);
|
|
333
|
+
return this.documentGroups(knowledgeBaseId);
|
|
334
|
+
}
|
|
335
|
+
documentGroups(knowledgeBaseId) {
|
|
336
|
+
return this.db.prepare("SELECT document_group,COUNT(*) AS count FROM knowledge_entries WHERE knowledge_base_id=? AND status='active' GROUP BY document_group ORDER BY document_group COLLATE NOCASE").all(knowledgeBaseId)
|
|
337
|
+
.map(row => ({ name: String(row.document_group), count: Number(row.count) }));
|
|
338
|
+
}
|
|
339
|
+
resolveDocumentGroup(baseId, name) {
|
|
340
|
+
return matchDocumentGroup(name, this.documentGroups(baseId).map(group => group.name));
|
|
341
|
+
}
|
|
342
|
+
async assignDocumentGroup(knowledgeBaseId, ids, group) {
|
|
343
|
+
this.assertOpen();
|
|
344
|
+
await this.documentsReady;
|
|
345
|
+
const name = normalizeDocumentGroup(group);
|
|
346
|
+
if (!Array.isArray(ids) || ids.length < 1 || ids.length > 500 || ids.some(id => typeof id !== 'string'))
|
|
347
|
+
throw inputError('请选择 1 到 500 篇文档');
|
|
348
|
+
const entries = this.transaction(() => {
|
|
349
|
+
if (!this.db.prepare("SELECT id FROM knowledge_bases WHERE id=? AND status='active'").get(knowledgeBaseId))
|
|
350
|
+
throw notFound('active knowledge base', knowledgeBaseId);
|
|
351
|
+
const canonical = this.resolveDocumentGroup(knowledgeBaseId, name);
|
|
352
|
+
const selected = [...new Set(ids)].map(id => {
|
|
353
|
+
const row = this.db.prepare(`SELECT ${ENTRY_COLUMNS} FROM knowledge_entries WHERE id=?`).get(id);
|
|
354
|
+
if (!row || row.knowledge_base_id !== knowledgeBaseId || row.status !== 'active')
|
|
355
|
+
throw conflict('文档已移动、归档或删除,请刷新分组后重试');
|
|
356
|
+
return rowToEntry(row);
|
|
357
|
+
});
|
|
358
|
+
return selected.map(current => this.setEntryGroup(current, canonical));
|
|
359
|
+
});
|
|
360
|
+
for (const entry of entries)
|
|
361
|
+
await this.syncKnowledgeEntryQueued(entry.id);
|
|
362
|
+
return entries;
|
|
363
|
+
}
|
|
364
|
+
setEntryGroup(current, group) {
|
|
365
|
+
const canonical = this.resolveDocumentGroup(current.knowledgeBaseId, normalizeDocumentGroup(group));
|
|
366
|
+
if ((current.group ?? '') === canonical)
|
|
367
|
+
return current;
|
|
368
|
+
const updated = { ...current, group: canonical, version: current.version + 1, updatedAt: nowIso() };
|
|
369
|
+
this.db.prepare('UPDATE knowledge_entries SET document_group=?,version=?,updated_at=? WHERE id=?').run(canonical, updated.version, updated.updatedAt, updated.id);
|
|
370
|
+
this.writeVersion(updated, 'update');
|
|
371
|
+
return updated;
|
|
372
|
+
}
|
|
328
373
|
async listMounts(targetKind, targetId) {
|
|
329
374
|
this.assertOpen();
|
|
330
375
|
const where = [];
|
|
@@ -654,7 +699,7 @@ export class LocalKnowledgeProvider {
|
|
|
654
699
|
result = this.updateEntry(current.id, { ...current, body: `${current.body.trimEnd()}\n\n${body}`.trim() }, 'update');
|
|
655
700
|
}
|
|
656
701
|
else {
|
|
657
|
-
result = this.insertEntry({ knowledgeBaseId: input.knowledgeBaseId, title: input.title?.trim() || note.name.replace(/\.md$/i, ''), body, type: 'fact', tags: [], scope: { kind: 'global' }, confidence: 0.8 });
|
|
702
|
+
result = this.insertEntry({ knowledgeBaseId: input.knowledgeBaseId, ...(input.group === undefined ? {} : { group: input.group }), title: input.title?.trim() || note.name.replace(/\.md$/i, ''), body, type: 'fact', tags: [], scope: { kind: 'global' }, confidence: 0.8 });
|
|
658
703
|
}
|
|
659
704
|
this.db.prepare('INSERT OR IGNORE INTO knowledge_note_references(knowledge_id,note_id,source,source_session_id,created_at) VALUES(?,?,?,NULL,?)').run(result.id, note.id, 'user', nowIso());
|
|
660
705
|
this.db.prepare('INSERT INTO note_excerpt_receipts(request_id,input_hash,knowledge_id) VALUES(?,?,?)').run(input.requestId, hash, result.id);
|
|
@@ -665,6 +710,7 @@ export class LocalKnowledgeProvider {
|
|
|
665
710
|
}
|
|
666
711
|
insertEntry(input) {
|
|
667
712
|
const draft = normalizeDraft(input);
|
|
713
|
+
draft.group = this.resolveDocumentGroup(draft.knowledgeBaseId, normalizeDocumentGroup(input.group, true));
|
|
668
714
|
if (this.db.prepare("SELECT id FROM knowledge_bases WHERE id=? AND status='active'").get(draft.knowledgeBaseId) === undefined) {
|
|
669
715
|
throw notFound('active knowledge base', draft.knowledgeBaseId);
|
|
670
716
|
}
|
|
@@ -677,9 +723,9 @@ export class LocalKnowledgeProvider {
|
|
|
677
723
|
this.db.prepare(`
|
|
678
724
|
INSERT INTO knowledge_entries (
|
|
679
725
|
id,knowledge_base_id,title,body,type,tags_json,scope_kind,scope_id,confidence,status,
|
|
680
|
-
document_state,finalized_at,finalization_note,version,content_hash,source_json,created_at,updated_at
|
|
681
|
-
) VALUES (?,?,?,?,?,?,?,?,?,?,'open',NULL,NULL
|
|
682
|
-
`).run(id, draft.knowledgeBaseId, draft.title, draft.body, draft.type, JSON.stringify(draft.tags), draft.scope.kind, draft.scope.kind === 'project' ? draft.scope.id : null, draft.confidence, 'active', 1, contentHash(draft), draft.source === undefined ? null : JSON.stringify(draft.source), timestamp, timestamp);
|
|
726
|
+
document_state,finalized_at,finalization_note,version,content_hash,source_json,created_at,updated_at,document_group
|
|
727
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?,'open',NULL,NULL,?,?,?,?,?,?)
|
|
728
|
+
`).run(id, draft.knowledgeBaseId, draft.title, draft.body, draft.type, JSON.stringify(draft.tags), draft.scope.kind, draft.scope.kind === 'project' ? draft.scope.id : null, draft.confidence, 'active', 1, contentHash(draft), draft.source === undefined ? null : JSON.stringify(draft.source), timestamp, timestamp, draft.group);
|
|
683
729
|
this.writeVersion(entry, 'create');
|
|
684
730
|
this.upsertFts(entry);
|
|
685
731
|
return entry;
|
|
@@ -813,6 +859,7 @@ export class LocalKnowledgeProvider {
|
|
|
813
859
|
if (current.documentState !== 'open')
|
|
814
860
|
throw finalizedConflict(current);
|
|
815
861
|
const draft = normalizeDraft(input);
|
|
862
|
+
draft.group = input.group === undefined ? current.group ?? '' : this.resolveDocumentGroup(draft.knowledgeBaseId, normalizeDocumentGroup(input.group));
|
|
816
863
|
if (this.db.prepare("SELECT id FROM knowledge_bases WHERE id=? AND status='active'").get(draft.knowledgeBaseId) === undefined) {
|
|
817
864
|
throw notFound('active knowledge base', draft.knowledgeBaseId);
|
|
818
865
|
}
|
|
@@ -829,9 +876,9 @@ export class LocalKnowledgeProvider {
|
|
|
829
876
|
this.db.prepare(`
|
|
830
877
|
UPDATE knowledge_entries SET
|
|
831
878
|
knowledge_base_id=?,title=?,body=?,type=?,tags_json=?,scope_kind=?,scope_id=?,confidence=?,status='active',
|
|
832
|
-
version=?,content_hash=?,source_json=?,updated_at=?
|
|
879
|
+
version=?,content_hash=?,source_json=?,updated_at=?,document_group=?
|
|
833
880
|
WHERE id=?
|
|
834
|
-
`).run(draft.knowledgeBaseId, draft.title, draft.body, draft.type, JSON.stringify(draft.tags), draft.scope.kind, draft.scope.kind === 'project' ? draft.scope.id : null, draft.confidence, entry.version, contentHash(draft), draft.source === undefined ? null : JSON.stringify(draft.source), timestamp, id);
|
|
881
|
+
`).run(draft.knowledgeBaseId, draft.title, draft.body, draft.type, JSON.stringify(draft.tags), draft.scope.kind, draft.scope.kind === 'project' ? draft.scope.id : null, draft.confidence, entry.version, contentHash(draft), draft.source === undefined ? null : JSON.stringify(draft.source), timestamp, draft.group, id);
|
|
835
882
|
this.writeVersion(entry, changeKind);
|
|
836
883
|
this.upsertFts(entry);
|
|
837
884
|
return entry;
|
|
@@ -1091,9 +1138,11 @@ export class LocalKnowledgeProvider {
|
|
|
1091
1138
|
}
|
|
1092
1139
|
const entry = resolution.proposal.action === 'create'
|
|
1093
1140
|
? this.insertEntry(resolution.proposal.draft)
|
|
1094
|
-
: resolution.proposal.change?.kind === '
|
|
1095
|
-
? this.
|
|
1096
|
-
:
|
|
1141
|
+
: resolution.proposal.change?.kind === 'group'
|
|
1142
|
+
? this.setEntryGroup(this.activeEntry(resolution.proposal.targetId), resolution.proposal.change.group)
|
|
1143
|
+
: resolution.proposal.change?.kind === 'finalize'
|
|
1144
|
+
? this.finalizeEntry(resolution.proposal.targetId, resolution.proposal.change.state, resolution.proposal.change.note)
|
|
1145
|
+
: this.updateEntry(resolution.proposal.targetId, resolution.proposal.draft, 'update');
|
|
1097
1146
|
const reviewedAt = nowIso();
|
|
1098
1147
|
this.db.prepare('UPDATE knowledge_candidates SET status=?, reviewed_at=?, review_note=? WHERE id=?')
|
|
1099
1148
|
.run('approved', reviewedAt, resolution.outcome === 'merged'
|
|
@@ -1145,7 +1194,7 @@ export class LocalKnowledgeProvider {
|
|
|
1145
1194
|
return { outcome: 'conflict', proposal };
|
|
1146
1195
|
if (proposal.action === 'update') {
|
|
1147
1196
|
const target = this.activeEntry(proposal.targetId);
|
|
1148
|
-
if (target.documentState !== 'open')
|
|
1197
|
+
if (target.documentState !== 'open' && proposal.change?.kind !== 'group')
|
|
1149
1198
|
return { outcome: 'finalized', entry: target };
|
|
1150
1199
|
if (target.knowledgeBaseId !== proposal.draft.knowledgeBaseId) {
|
|
1151
1200
|
throw conflict('direct-write update cannot move knowledge between knowledge bases');
|
|
@@ -1158,7 +1207,7 @@ export class LocalKnowledgeProvider {
|
|
|
1158
1207
|
return { outcome: 'conflict', proposal: { ...proposal, action: 'conflict' } };
|
|
1159
1208
|
}
|
|
1160
1209
|
const draft = applied.draft;
|
|
1161
|
-
if (proposal.change?.kind !== 'finalize' && contentHash(draft) === contentHash(target))
|
|
1210
|
+
if (proposal.change?.kind === 'group' ? (draft.group ?? '') === (target.group ?? '') : proposal.change?.kind !== 'finalize' && contentHash(draft) === contentHash(target))
|
|
1162
1211
|
return { outcome: 'duplicate', entry: target };
|
|
1163
1212
|
return { outcome: 'merged', proposal: { ...proposal, action: 'update', draft } };
|
|
1164
1213
|
}
|
|
@@ -1238,7 +1287,17 @@ export class LocalKnowledgeProvider {
|
|
|
1238
1287
|
throw conflict(`candidate ${id} was already ${candidate.status}`);
|
|
1239
1288
|
let draft = decision.draft === undefined ? candidate.draft : normalizeDraft(decision.draft);
|
|
1240
1289
|
if (decision.decision === 'approve') {
|
|
1241
|
-
if (candidate.change?.kind === '
|
|
1290
|
+
if (candidate.change?.kind === 'group') {
|
|
1291
|
+
if (decision.draft !== undefined)
|
|
1292
|
+
throw conflict('分组候选不能替换正文,请拒绝后重新提交');
|
|
1293
|
+
const target = this.activeEntry(candidate.targetId);
|
|
1294
|
+
assertExpectedReviewVersion(target, decision.expectedVersion);
|
|
1295
|
+
const applied = applyCandidateToTarget(target, candidate, false);
|
|
1296
|
+
if (!applied.ok)
|
|
1297
|
+
throw conflict(applied.reason);
|
|
1298
|
+
touchedEntryId = this.setEntryGroup(target, candidate.change.group).id;
|
|
1299
|
+
}
|
|
1300
|
+
else if (candidate.change?.kind === 'finalize') {
|
|
1242
1301
|
if (decision.draft !== undefined)
|
|
1243
1302
|
throw conflict('结束候选不能替换正文,请拒绝后重新提交');
|
|
1244
1303
|
const target = this.activeEntry(candidate.targetId);
|
|
@@ -1507,6 +1566,7 @@ export class LocalKnowledgeProvider {
|
|
|
1507
1566
|
body: entry.body,
|
|
1508
1567
|
type: entry.type,
|
|
1509
1568
|
tags: entry.tags,
|
|
1569
|
+
group: entry.group ?? '',
|
|
1510
1570
|
scope: entry.scope,
|
|
1511
1571
|
confidence: entry.confidence,
|
|
1512
1572
|
...entry.source === undefined ? {} : { source: entry.source },
|
|
@@ -1591,14 +1651,14 @@ export class LocalKnowledgeProvider {
|
|
|
1591
1651
|
this.db.prepare(`
|
|
1592
1652
|
INSERT INTO knowledge_documents(
|
|
1593
1653
|
id,knowledge_base_id,rel_path,title,content,entry_count,content_hash,
|
|
1594
|
-
document_state,finalized_at,finalization_note,created_at,updated_at
|
|
1595
|
-
) VALUES(
|
|
1654
|
+
document_state,finalized_at,finalization_note,created_at,updated_at,document_group
|
|
1655
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
1596
1656
|
ON CONFLICT(id) DO UPDATE SET
|
|
1597
1657
|
knowledge_base_id=excluded.knowledge_base_id,rel_path=excluded.rel_path,
|
|
1598
1658
|
title=excluded.title,content=excluded.content,entry_count=excluded.entry_count,
|
|
1599
1659
|
content_hash=excluded.content_hash,document_state=excluded.document_state,
|
|
1600
1660
|
finalized_at=excluded.finalized_at,finalization_note=excluded.finalization_note,
|
|
1601
|
-
updated_at=excluded.updated_at
|
|
1661
|
+
updated_at=excluded.updated_at,document_group=excluded.document_group
|
|
1602
1662
|
WHERE knowledge_documents.knowledge_base_id<>excluded.knowledge_base_id
|
|
1603
1663
|
OR knowledge_documents.content_hash<>excluded.content_hash
|
|
1604
1664
|
OR knowledge_documents.rel_path<>excluded.rel_path
|
|
@@ -1607,7 +1667,8 @@ export class LocalKnowledgeProvider {
|
|
|
1607
1667
|
OR knowledge_documents.document_state<>excluded.document_state
|
|
1608
1668
|
OR knowledge_documents.finalized_at IS NOT excluded.finalized_at
|
|
1609
1669
|
OR knowledge_documents.finalization_note IS NOT excluded.finalization_note
|
|
1610
|
-
|
|
1670
|
+
OR knowledge_documents.document_group<>excluded.document_group
|
|
1671
|
+
`).run(entry.id, entry.knowledgeBaseId, relPath, entry.title, renderEntryContent(entry), 1, projectedHash, entry.documentState, entry.finalizedAt ?? null, entry.finalizationNote ?? null, entry.createdAt, entry.updatedAt, entry.group ?? '');
|
|
1611
1672
|
}
|
|
1612
1673
|
async syncKnowledgeDocuments(knowledgeBaseId) {
|
|
1613
1674
|
const baseRow = this.db.prepare('SELECT * FROM knowledge_bases WHERE id=?').get(knowledgeBaseId);
|
|
@@ -1660,6 +1721,7 @@ function renderEntryMarkdown(entry) {
|
|
|
1660
1721
|
id: entry.id,
|
|
1661
1722
|
type: entry.type,
|
|
1662
1723
|
tags: entry.tags,
|
|
1724
|
+
group: entry.group ?? '',
|
|
1663
1725
|
scope: entry.scope,
|
|
1664
1726
|
confidence: entry.confidence,
|
|
1665
1727
|
status: entry.status,
|
|
@@ -1682,6 +1744,7 @@ function rowToEntry(row) {
|
|
|
1682
1744
|
return {
|
|
1683
1745
|
id: String(row.id),
|
|
1684
1746
|
knowledgeBaseId: row.knowledge_base_id == null ? DEFAULT_KNOWLEDGE_BASE_ID : String(row.knowledge_base_id),
|
|
1747
|
+
group: String(row.document_group ?? ''),
|
|
1685
1748
|
title: String(row.title),
|
|
1686
1749
|
body: String(row.body),
|
|
1687
1750
|
type: String(row.type),
|
|
@@ -1704,6 +1767,7 @@ function rowToDocument(row) {
|
|
|
1704
1767
|
return {
|
|
1705
1768
|
id: String(row.id),
|
|
1706
1769
|
knowledgeBaseId: String(row.knowledge_base_id),
|
|
1770
|
+
group: String(row.document_group ?? ''),
|
|
1707
1771
|
relPath: String(row.rel_path),
|
|
1708
1772
|
title: String(row.title),
|
|
1709
1773
|
content: String(row.content),
|
|
@@ -1720,6 +1784,7 @@ function rowToDocumentSummary(row) {
|
|
|
1720
1784
|
return {
|
|
1721
1785
|
id: String(row.id),
|
|
1722
1786
|
knowledgeBaseId: String(row.knowledge_base_id),
|
|
1787
|
+
group: String(row.document_group ?? ''),
|
|
1723
1788
|
relPath: String(row.rel_path),
|
|
1724
1789
|
title: String(row.title),
|
|
1725
1790
|
entryCount: Number(row.entry_count),
|
|
@@ -1791,6 +1856,11 @@ function normalizeCandidateChange(input) {
|
|
|
1791
1856
|
return undefined;
|
|
1792
1857
|
if (input.kind === 'append')
|
|
1793
1858
|
return { kind: 'append' };
|
|
1859
|
+
if (input.kind === 'group') {
|
|
1860
|
+
if (!Number.isSafeInteger(input.baseVersion) || input.baseVersion < 1)
|
|
1861
|
+
throw new Error('分组 baseVersion 必须为正整数');
|
|
1862
|
+
return { kind: 'group', baseVersion: input.baseVersion, group: normalizeDocumentGroup(input.group, true) };
|
|
1863
|
+
}
|
|
1794
1864
|
if (input.kind === 'finalize')
|
|
1795
1865
|
return normalizeFinalizationChange(input);
|
|
1796
1866
|
if (input.kind !== 'revise')
|
|
@@ -1833,6 +1903,11 @@ function mergeKnowledgeDraft(current, incoming, preferIncomingTitle) {
|
|
|
1833
1903
|
});
|
|
1834
1904
|
}
|
|
1835
1905
|
function applyCandidateToTarget(current, proposal, preferIncomingTitle) {
|
|
1906
|
+
if (proposal.change?.kind === 'group') {
|
|
1907
|
+
if (current.version !== proposal.change.baseVersion || current.knowledgeBaseId !== proposal.draft.knowledgeBaseId)
|
|
1908
|
+
return { ok: false, reason: '分组候选已过期,请重新读取文档后提交' };
|
|
1909
|
+
return { ok: true, draft: { ...current, group: proposal.change.group } };
|
|
1910
|
+
}
|
|
1836
1911
|
if (proposal.change?.kind === 'finalize') {
|
|
1837
1912
|
if (current.documentState !== 'open' || current.version !== proposal.change.baseVersion || contentHash(current) !== proposal.change.baseHash
|
|
1838
1913
|
|| current.knowledgeBaseId !== proposal.draft.knowledgeBaseId || contentHash(current) !== contentHash(proposal.draft)) {
|