@musnows/scriverse 0.6.8 → 0.6.9

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/dist/ai.js CHANGED
@@ -1477,38 +1477,47 @@ export class AiManager {
1477
1477
  const normalizedQuery = normalizeRelationshipSearchText(query).trim();
1478
1478
  if (!normalizedQuery)
1479
1479
  return [];
1480
- await this.ensureRelationshipSearchIndex(workId);
1481
1480
  const requestedTypes = options.type ? new Set([options.type]) : new Set(HYBRID_SEARCH_TYPES);
1481
+ if (options.includeAgentHistory === false)
1482
+ requestedTypes.delete("agent-history");
1483
+ if (requestedTypes.size === 0)
1484
+ return [];
1485
+ const hasIndexedSourceTypes = [...requestedTypes].some((type) => type !== "chapter" && type !== "agent-history");
1486
+ if (requestedTypes.has("chapter") || hasIndexedSourceTypes)
1487
+ await this.ensureRelationshipSearchIndex(workId);
1482
1488
  const resultLimit = Math.min(100, Math.max(1, Math.trunc(options.limit ?? 50)));
1483
1489
  const channelLimit = Math.min(200, Math.max(50, resultLimit * 4));
1484
1490
  const accepts = (type) => requestedTypes.has(type);
1485
1491
  const metadataDetails = new Map();
1486
- const metadataCandidates = this.store.search(workId, normalizedQuery).flatMap((item) => {
1487
- const type = String(item.type);
1488
- const itemId = String(item.id ?? "");
1489
- if (!itemId || !accepts(type))
1490
- return [];
1491
- const key = `${type}:${itemId}`;
1492
- const { type: _type, id: _id, title: _title, snippet: _snippet, ...details } = item;
1493
- metadataDetails.set(key, { ...(metadataDetails.get(key) ?? {}), ...details });
1494
- return [{
1495
- key,
1496
- type,
1497
- id: itemId,
1498
- title: String(item.title ?? "未命名资料"),
1499
- subtitle: typeof item.category === "string" ? item.category : undefined,
1500
- snippet: buildHybridSearchSnippet(String(item.snippet ?? ""), normalizedQuery),
1501
- sectionId: typeof item.sectionId === "string" ? item.sectionId : undefined,
1502
- matchKind: "metadata"
1503
- }];
1504
- }).slice(0, channelLimit);
1492
+ const metadataCandidates = [...requestedTypes].some((type) => type !== "agent-history")
1493
+ ? this.store.search(workId, normalizedQuery).flatMap((item) => {
1494
+ const type = String(item.type);
1495
+ const itemId = String(item.id ?? "");
1496
+ if (!itemId || !accepts(type))
1497
+ return [];
1498
+ const key = `${type}:${itemId}`;
1499
+ const { type: _type, id: _id, title: _title, snippet: _snippet, ...details } = item;
1500
+ metadataDetails.set(key, { ...(metadataDetails.get(key) ?? {}), ...details });
1501
+ return [{
1502
+ key,
1503
+ type,
1504
+ id: itemId,
1505
+ title: String(item.title ?? "未命名资料"),
1506
+ subtitle: typeof item.category === "string" ? item.category : undefined,
1507
+ snippet: buildHybridSearchSnippet(String(item.snippet ?? ""), normalizedQuery),
1508
+ sectionId: typeof item.sectionId === "string" ? item.sectionId : undefined,
1509
+ matchKind: "metadata"
1510
+ }];
1511
+ }).slice(0, channelLimit)
1512
+ : [];
1505
1513
  const exactCandidates = [
1506
1514
  ...(requestedTypes.has("chapter") ? this.hybridChapterMatches(workId, normalizedQuery, "exact", channelLimit) : []),
1507
- ...this.hybridIndexedSourceMatches(workId, normalizedQuery, "exact", requestedTypes, channelLimit)
1515
+ ...(hasIndexedSourceTypes ? this.hybridIndexedSourceMatches(workId, normalizedQuery, "exact", requestedTypes, channelLimit) : []),
1516
+ ...(requestedTypes.has("agent-history") ? this.hybridAgentHistoryMatches(workId, normalizedQuery, channelLimit) : [])
1508
1517
  ];
1509
1518
  const phoneticCandidates = [
1510
1519
  ...(requestedTypes.has("chapter") ? this.hybridChapterMatches(workId, normalizedQuery, "phonetic", channelLimit) : []),
1511
- ...this.hybridIndexedSourceMatches(workId, normalizedQuery, "phonetic", requestedTypes, channelLimit)
1520
+ ...(hasIndexedSourceTypes ? this.hybridIndexedSourceMatches(workId, normalizedQuery, "phonetic", requestedTypes, channelLimit) : [])
1512
1521
  ];
1513
1522
  return fuseHybridSearchChannels([
1514
1523
  { weight: 1.4, candidates: metadataCandidates },
@@ -1519,6 +1528,44 @@ export class AiManager {
1519
1528
  ...item
1520
1529
  }));
1521
1530
  }
1531
+ hybridAgentHistoryMatches(workId, query, limit) {
1532
+ const columns = `SELECT history.source_type, history.source_id, history.conversation_id, history.message_id,
1533
+ history.role, history.content, conversation.title AS conversation_title
1534
+ FROM ai_history_search history
1535
+ JOIN ai_conversations conversation ON conversation.id = history.conversation_id`;
1536
+ const rows = [...query].length < 3
1537
+ ? this.store.db.all(`${columns}
1538
+ JOIN ai_history_search_short_terms term ON term.search_id = history.id
1539
+ WHERE history.work_id = ? AND term.term = ?
1540
+ ORDER BY history.created_at DESC, history.id DESC
1541
+ LIMIT ?`, workId, query, limit)
1542
+ : this.store.db.all(`${columns}
1543
+ JOIN ai_history_search_fts fts ON fts.rowid = history.id
1544
+ WHERE history.work_id = ? AND ai_history_search_fts MATCH ?
1545
+ ORDER BY bm25(ai_history_search_fts), history.created_at DESC, history.id DESC
1546
+ LIMIT ?`, workId, `"${query.replaceAll('"', '""')}"`, limit);
1547
+ return rows.map((row) => {
1548
+ const sourceType = String(row.source_type ?? "");
1549
+ const sourceId = String(row.source_id ?? "");
1550
+ const conversationId = String(row.conversation_id ?? "");
1551
+ const messageId = sourceType === "message" ? String(row.message_id ?? "") : "";
1552
+ const title = String(row.conversation_title ?? "新对话");
1553
+ const isMessage = sourceType === "message";
1554
+ const role = String(row.role ?? "");
1555
+ const content = String(row.content ?? "");
1556
+ return {
1557
+ key: `agent-history:${sourceType}:${sourceId}`,
1558
+ type: "agent-history",
1559
+ id: sourceId,
1560
+ title,
1561
+ subtitle: isMessage ? (role === "assistant" ? "Agent 回复" : "作者指令") : "对话标题与摘要",
1562
+ snippet: buildHybridSearchSnippet(isMessage ? content : `对话标题:${title}${content ? ` · ${content}` : ""}`, query),
1563
+ conversationId,
1564
+ ...(messageId ? { messageId } : {}),
1565
+ matchKind: "exact"
1566
+ };
1567
+ }).filter((candidate) => candidate.id && candidate.conversationId);
1568
+ }
1522
1569
  hybridChapterMatches(workId, query, matchKind, limit) {
1523
1570
  let rows;
1524
1571
  if (matchKind === "phonetic") {
@@ -3650,7 +3697,7 @@ export class AiManager {
3650
3697
  const character = this.store.getCharacter(roleplayCharacterId);
3651
3698
  if (String(character.workId) !== workId)
3652
3699
  throw new Error("Roleplay character belongs to a different work");
3653
- const characterList = this.store.listCharacters(workId, false, true);
3700
+ const characterList = this.store.listCharacters(workId);
3654
3701
  const characters = new Map(characterList.map((item) => [String(item.id), item]));
3655
3702
  const characterSearchText = (item) => {
3656
3703
  if (!item)
@@ -6766,6 +6813,8 @@ export class AiManager {
6766
6813
  const item = this.store.getCharacter(sourceId);
6767
6814
  if (String(item.workId) !== workId)
6768
6815
  return null;
6816
+ if (item.mergedIntoCharacterId)
6817
+ return null;
6769
6818
  return source(`人物档案:${String(item.name)}`, {
6770
6819
  name: item.name, isDead: item.isDead, aliases: item.aliases, code: item.code, species: item.species, race: item.race,
6771
6820
  organizations: item.organizations, attributes: item.attributes, profile: item.profile,