@astro-minimax/ai 0.7.0 → 0.7.2

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 (78) hide show
  1. package/dist/data/index.d.ts +1 -0
  2. package/dist/data/index.d.ts.map +1 -1
  3. package/dist/data/metadata-loader.d.ts +2 -2
  4. package/dist/data/metadata-loader.d.ts.map +1 -1
  5. package/dist/data/metadata-loader.js +15 -3
  6. package/dist/data/types.d.ts +2 -0
  7. package/dist/data/types.d.ts.map +1 -1
  8. package/dist/fact-registry/fact-matcher.d.ts +12 -0
  9. package/dist/fact-registry/fact-matcher.d.ts.map +1 -0
  10. package/dist/fact-registry/fact-matcher.js +94 -0
  11. package/dist/fact-registry/index.d.ts +5 -0
  12. package/dist/fact-registry/index.d.ts.map +1 -0
  13. package/dist/fact-registry/index.js +3 -0
  14. package/dist/fact-registry/prompt-injector.d.ts +7 -0
  15. package/dist/fact-registry/prompt-injector.d.ts.map +1 -0
  16. package/dist/fact-registry/prompt-injector.js +57 -0
  17. package/dist/fact-registry/registry.d.ts +10 -0
  18. package/dist/fact-registry/registry.d.ts.map +1 -0
  19. package/dist/fact-registry/registry.js +38 -0
  20. package/dist/fact-registry/types.d.ts +46 -0
  21. package/dist/fact-registry/types.d.ts.map +1 -0
  22. package/dist/fact-registry/types.js +5 -0
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -0
  26. package/dist/intelligence/citation-appender.d.ts +19 -0
  27. package/dist/intelligence/citation-appender.d.ts.map +1 -0
  28. package/dist/intelligence/citation-appender.js +65 -0
  29. package/dist/intelligence/citation-guard.d.ts.map +1 -1
  30. package/dist/intelligence/citation-guard.js +12 -13
  31. package/dist/intelligence/index.d.ts +2 -0
  32. package/dist/intelligence/index.d.ts.map +1 -1
  33. package/dist/intelligence/index.js +1 -0
  34. package/dist/intelligence/response-templates.d.ts +16 -0
  35. package/dist/intelligence/response-templates.d.ts.map +1 -0
  36. package/dist/intelligence/response-templates.js +116 -0
  37. package/dist/prompt/dynamic-layer.d.ts.map +1 -1
  38. package/dist/prompt/dynamic-layer.js +6 -2
  39. package/dist/prompt/prompt-builder.d.ts +0 -8
  40. package/dist/prompt/prompt-builder.d.ts.map +1 -1
  41. package/dist/prompt/prompt-builder.js +2 -9
  42. package/dist/prompt/semi-static-layer.d.ts +0 -4
  43. package/dist/prompt/semi-static-layer.d.ts.map +1 -1
  44. package/dist/prompt/semi-static-layer.js +7 -10
  45. package/dist/prompt/static-layer.d.ts.map +1 -1
  46. package/dist/prompt/static-layer.js +4 -2
  47. package/dist/prompt/types.d.ts +3 -0
  48. package/dist/prompt/types.d.ts.map +1 -1
  49. package/dist/search/idf.d.ts +18 -0
  50. package/dist/search/idf.d.ts.map +1 -0
  51. package/dist/search/idf.js +31 -0
  52. package/dist/search/index.d.ts +5 -0
  53. package/dist/search/index.d.ts.map +1 -1
  54. package/dist/search/index.js +3 -0
  55. package/dist/search/search-api.d.ts.map +1 -1
  56. package/dist/search/search-api.js +12 -3
  57. package/dist/search/search-index.d.ts +7 -1
  58. package/dist/search/search-index.d.ts.map +1 -1
  59. package/dist/search/search-index.js +15 -2
  60. package/dist/search/search-utils.d.ts +7 -3
  61. package/dist/search/search-utils.d.ts.map +1 -1
  62. package/dist/search/search-utils.js +23 -15
  63. package/dist/search/types.d.ts +2 -0
  64. package/dist/search/types.d.ts.map +1 -1
  65. package/dist/search/vector-reranker.d.ts +38 -0
  66. package/dist/search/vector-reranker.d.ts.map +1 -0
  67. package/dist/search/vector-reranker.js +135 -0
  68. package/dist/server/chat-handler.d.ts.map +1 -1
  69. package/dist/server/chat-handler.js +19 -2
  70. package/dist/server/metadata-init.d.ts.map +1 -1
  71. package/dist/server/metadata-init.js +2 -0
  72. package/dist/server/types.d.ts +2 -0
  73. package/dist/server/types.d.ts.map +1 -1
  74. package/dist/utils/i18n.d.ts +1 -1
  75. package/dist/utils/i18n.d.ts.map +1 -1
  76. package/dist/utils/i18n.js +8 -0
  77. package/package.json +6 -2
  78. package/src/components/AIChatWidget.astro +1 -1
@@ -1,3 +1,4 @@
1
1
  export { preloadMetadata, clearMetadataCache, getMetadata, getArticleSummary, getAllSummaries, getAuthorContext, getVoiceProfile, } from './metadata-loader.js';
2
2
  export type { AISummariesFile, AuthorContextFile, VoiceProfile, LoadedMetadata, ArticleSummaryData, AuthorPost, } from './types.js';
3
+ export type { FactRegistryFile } from '../fact-registry/types.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,UAAU,GACX,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -5,11 +5,11 @@ import type { AuthorContextFile, VoiceProfile, LoadedMetadata, ArticleSummaryDat
5
5
  *
6
6
  * Example (in functions/lib/ai.ts):
7
7
  * import summaries from '../../datas/ai-summaries.json' with { type: 'json' };
8
- * preloadMetadata({ summaries, authorContext, voiceProfile });
8
+ * preloadMetadata({ summaries, authorContext, voiceProfile, factRegistry });
9
9
  */
10
10
  export declare function preloadMetadata(data: Partial<LoadedMetadata>): void;
11
11
  /**
12
- * Clears the metadata cache (useful for testing).
12
+ * Clears the metadata cache and all associated sub-caches (useful for testing).
13
13
  */
14
14
  export declare function clearMetadataCache(): void;
15
15
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"metadata-loader.d.ts","sourceRoot":"","sources":["../../src/data/metadata-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,iBAAiB,EAAE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAKvH;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,CAMnE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,cAAc,CAE5C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAE9E;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,kBAAkB,CAAC,CAG9E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,iBAAiB,GAAG,IAAI,CAE3D;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,GAAG,IAAI,CAErD"}
1
+ {"version":3,"file":"metadata-loader.d.ts","sourceRoot":"","sources":["../../src/data/metadata-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,iBAAiB,EAAE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAQvH;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,CAgBnE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAIzC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,cAAc,CAE5C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAE9E;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,kBAAkB,CAAC,CAG9E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,iBAAiB,GAAG,IAAI,CAE3D;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,GAAG,IAAI,CAErD"}
@@ -1,3 +1,5 @@
1
+ import { loadFactRegistry as loadFactRegistryCache } from '../fact-registry/registry.js';
2
+ import { loadVectorIndex as loadVectorIndexCache } from '../search/vector-reranker.js';
1
3
  // Lazy-loaded, memory-cached metadata
2
4
  let cachedMetadata = null;
3
5
  /**
@@ -6,26 +8,36 @@ let cachedMetadata = null;
6
8
  *
7
9
  * Example (in functions/lib/ai.ts):
8
10
  * import summaries from '../../datas/ai-summaries.json' with { type: 'json' };
9
- * preloadMetadata({ summaries, authorContext, voiceProfile });
11
+ * preloadMetadata({ summaries, authorContext, voiceProfile, factRegistry });
10
12
  */
11
13
  export function preloadMetadata(data) {
12
14
  cachedMetadata = {
13
15
  summaries: data.summaries ?? null,
14
16
  authorContext: data.authorContext ?? null,
15
17
  voiceProfile: data.voiceProfile ?? null,
18
+ factRegistry: data.factRegistry ?? null,
19
+ vectorIndex: data.vectorIndex ?? null,
16
20
  };
21
+ if (cachedMetadata.factRegistry) {
22
+ loadFactRegistryCache(cachedMetadata.factRegistry);
23
+ }
24
+ if (cachedMetadata.vectorIndex) {
25
+ loadVectorIndexCache(cachedMetadata.vectorIndex);
26
+ }
17
27
  }
18
28
  /**
19
- * Clears the metadata cache (useful for testing).
29
+ * Clears the metadata cache and all associated sub-caches (useful for testing).
20
30
  */
21
31
  export function clearMetadataCache() {
22
32
  cachedMetadata = null;
33
+ loadFactRegistryCache(null);
34
+ loadVectorIndexCache(null);
23
35
  }
24
36
  /**
25
37
  * Returns the cached metadata. Must call preloadMetadata() first.
26
38
  */
27
39
  export function getMetadata() {
28
- return cachedMetadata ?? { summaries: null, authorContext: null, voiceProfile: null };
40
+ return cachedMetadata ?? { summaries: null, authorContext: null, voiceProfile: null, factRegistry: null, vectorIndex: null };
29
41
  }
30
42
  /**
31
43
  * Returns the AI-generated summary for an article by its slug.
@@ -47,5 +47,7 @@ export interface LoadedMetadata {
47
47
  summaries: AISummariesFile | null;
48
48
  authorContext: AuthorContextFile | null;
49
49
  voiceProfile: VoiceProfile | null;
50
+ factRegistry: import('../fact-registry/types.js').FactRegistryFile | null;
51
+ vectorIndex: import('../search/vector-reranker.js').VectorIndex | null;
50
52
  }
51
53
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,kBAAkB,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE;QACJ,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,aAAa,CAAC;IACvB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACxC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;CACnC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,kBAAkB,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE;QACJ,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,aAAa,CAAC;IACvB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACxC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,YAAY,EAAE,OAAO,2BAA2B,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1E,WAAW,EAAE,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,CAAC;CACxE"}
@@ -0,0 +1,12 @@
1
+ import type { Fact } from './types.js';
2
+ /**
3
+ * Selects facts most relevant to the user's query.
4
+ *
5
+ * Strategy:
6
+ * 1. Always include very-high-confidence core facts (confidence >= 0.95)
7
+ * 2. Add category-matched facts based on query keywords
8
+ * 3. Add tag-matched facts for more specific queries
9
+ * 4. Deduplicate and cap total count
10
+ */
11
+ export declare function matchFactsToQuery(query: string, lang?: string, maxFacts?: number): Fact[];
12
+ //# sourceMappingURL=fact-matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fact-matcher.d.ts","sourceRoot":"","sources":["../../src/fact-registry/fact-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAgB,MAAM,YAAY,CAAC;AAwDrD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,MAAM,EACb,QAAQ,SAAK,GACZ,IAAI,EAAE,CA2CR"}
@@ -0,0 +1,94 @@
1
+ import { queryFacts } from './registry.js';
2
+ /**
3
+ * Category detection keywords — when any keyword appears in the user query,
4
+ * the corresponding fact category is considered relevant.
5
+ */
6
+ const CATEGORY_KEYWORDS = {
7
+ author: [
8
+ '作者', '博主', '谁', '关于我', '自我介绍', '个人',
9
+ 'author', 'who', 'about me', 'introduce',
10
+ ],
11
+ blog: [
12
+ '博客', '文章', '多少', '数量', '统计', '总共', '分类', '标签', '语言',
13
+ 'blog', 'post', 'how many', 'count', 'statistic', 'category', 'tag',
14
+ ],
15
+ content: [
16
+ '写过', '提到', '讨论', '观点', '主题', '话题', '涵盖', '领域',
17
+ 'wrote', 'mention', 'discuss', 'topic', 'cover', 'area', 'opinion',
18
+ ],
19
+ project: [
20
+ '项目', '开源', '仓库', '工具', '产品',
21
+ 'project', 'open source', 'repo', 'github', 'tool', 'product',
22
+ ],
23
+ tech: [
24
+ '技术', '技术栈', '框架', '库', '编程语言', '前端', '后端',
25
+ 'tech', 'stack', 'framework', 'library', 'language', 'frontend', 'backend',
26
+ ],
27
+ };
28
+ /**
29
+ * Detect which fact categories are relevant to the user query.
30
+ */
31
+ function detectRelevantCategories(query) {
32
+ const q = query.toLowerCase();
33
+ const matched = [];
34
+ for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
35
+ if (keywords.some(kw => q.includes(kw))) {
36
+ matched.push(category);
37
+ }
38
+ }
39
+ return matched;
40
+ }
41
+ /**
42
+ * Extract potential matching tags from the query by splitting into tokens.
43
+ */
44
+ function extractQueryTags(query) {
45
+ const tokens = query.match(/[A-Za-z][A-Za-z0-9.+#-]{1,}|[\u4e00-\u9fa5]{2,6}/g);
46
+ return tokens?.map(t => t.toLowerCase()) ?? [];
47
+ }
48
+ /**
49
+ * Selects facts most relevant to the user's query.
50
+ *
51
+ * Strategy:
52
+ * 1. Always include very-high-confidence core facts (confidence >= 0.95)
53
+ * 2. Add category-matched facts based on query keywords
54
+ * 3. Add tag-matched facts for more specific queries
55
+ * 4. Deduplicate and cap total count
56
+ */
57
+ export function matchFactsToQuery(query, lang, maxFacts = 15) {
58
+ const categories = detectRelevantCategories(query);
59
+ const queryTags = extractQueryTags(query);
60
+ // Layer 1: always-present core facts (highest confidence)
61
+ const coreFacts = queryFacts({
62
+ minConfidence: 0.95,
63
+ lang,
64
+ limit: 5,
65
+ });
66
+ // Layer 2: category-matched facts
67
+ const categoryFacts = categories.length > 0
68
+ ? queryFacts({
69
+ categories,
70
+ minConfidence: 0.7,
71
+ lang,
72
+ limit: 10,
73
+ })
74
+ : [];
75
+ // Layer 3: tag-matched facts (for specificity)
76
+ const tagFacts = queryTags.length > 0
77
+ ? queryFacts({
78
+ tags: queryTags,
79
+ minConfidence: 0.6,
80
+ lang,
81
+ limit: 5,
82
+ })
83
+ : [];
84
+ // Merge with deduplication, preserving priority order
85
+ const seen = new Set();
86
+ const result = [];
87
+ for (const fact of [...categoryFacts, ...tagFacts, ...coreFacts]) {
88
+ if (!seen.has(fact.id)) {
89
+ seen.add(fact.id);
90
+ result.push(fact);
91
+ }
92
+ }
93
+ return result.slice(0, maxFacts);
94
+ }
@@ -0,0 +1,5 @@
1
+ export { loadFactRegistry, clearFactRegistry, getFactRegistry, queryFacts, } from './registry.js';
2
+ export { matchFactsToQuery } from './fact-matcher.js';
3
+ export { buildFactSection } from './prompt-injector.js';
4
+ export type { Fact, FactCategory, FactSource, FactRegistryFile, FactRegistryStats, FactQueryOptions, } from './types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fact-registry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,UAAU,GACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EACV,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { loadFactRegistry, clearFactRegistry, getFactRegistry, queryFacts, } from './registry.js';
2
+ export { matchFactsToQuery } from './fact-matcher.js';
3
+ export { buildFactSection } from './prompt-injector.js';
@@ -0,0 +1,7 @@
1
+ import type { Fact } from './types.js';
2
+ /**
3
+ * Formats matched facts into a prompt section ready for injection.
4
+ * Groups facts by category with clear structure.
5
+ */
6
+ export declare function buildFactSection(facts: Fact[], lang?: string): string;
7
+ //# sourceMappingURL=prompt-injector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-injector.d.ts","sourceRoot":"","sources":["../../src/fact-registry/prompt-injector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAgB,MAAM,YAAY,CAAC;AAgCrD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,GAAE,MAAa,GAAG,MAAM,CA+B3E"}
@@ -0,0 +1,57 @@
1
+ const CATEGORY_LABELS = {
2
+ zh: {
3
+ author: '关于作者',
4
+ blog: '博客数据',
5
+ content: '内容事实',
6
+ project: '项目信息',
7
+ tech: '技术相关',
8
+ },
9
+ en: {
10
+ author: 'About the Author',
11
+ blog: 'Blog Statistics',
12
+ content: 'Content Facts',
13
+ project: 'Project Info',
14
+ tech: 'Tech Related',
15
+ },
16
+ };
17
+ const SECTION_TEXT = {
18
+ zh: {
19
+ title: '已验证事实(基于博客真实数据)',
20
+ instruction: '以上事实来自博客的真实数据。回答时优先使用这些已验证的事实,不要编造与之矛盾的信息。如果某个问题的答案不在已验证事实中,请如实说明。',
21
+ },
22
+ en: {
23
+ title: 'Verified Facts (based on real blog data)',
24
+ instruction: 'The above facts are derived from real blog data. Prioritize these verified facts when answering. Do not fabricate information that contradicts them. If the answer is not among verified facts, state that honestly.',
25
+ },
26
+ };
27
+ /**
28
+ * Formats matched facts into a prompt section ready for injection.
29
+ * Groups facts by category with clear structure.
30
+ */
31
+ export function buildFactSection(facts, lang = 'zh') {
32
+ if (!facts.length)
33
+ return '';
34
+ const l = lang === 'zh' ? 'zh' : 'en';
35
+ const labels = CATEGORY_LABELS[l];
36
+ const text = SECTION_TEXT[l];
37
+ // Group by category
38
+ const grouped = new Map();
39
+ for (const fact of facts) {
40
+ const group = grouped.get(fact.category) ?? [];
41
+ group.push(fact);
42
+ grouped.set(fact.category, group);
43
+ }
44
+ const lines = [];
45
+ lines.push(`## ${text.title}`);
46
+ for (const [category, categoryFacts] of grouped) {
47
+ const label = labels[category] ?? category;
48
+ lines.push('');
49
+ lines.push(`### ${label}`);
50
+ for (const fact of categoryFacts) {
51
+ lines.push(`- ${fact.statement}`);
52
+ }
53
+ }
54
+ lines.push('');
55
+ lines.push(`> ${text.instruction}`);
56
+ return lines.join('\n');
57
+ }
@@ -0,0 +1,10 @@
1
+ import type { Fact, FactRegistryFile, FactQueryOptions } from './types.js';
2
+ export declare function loadFactRegistry(data: FactRegistryFile | null): void;
3
+ export declare function clearFactRegistry(): void;
4
+ export declare function getFactRegistry(): FactRegistryFile | null;
5
+ /**
6
+ * Query facts with optional filters.
7
+ * Returns facts sorted by confidence (highest first).
8
+ */
9
+ export declare function queryFacts(options?: FactQueryOptions): Fact[];
10
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/fact-registry/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAI3E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAEpE;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,wBAAgB,eAAe,IAAI,gBAAgB,GAAG,IAAI,CAEzD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,gBAAqB,GAAG,IAAI,EAAE,CAgCjE"}
@@ -0,0 +1,38 @@
1
+ let cachedRegistry = null;
2
+ export function loadFactRegistry(data) {
3
+ cachedRegistry = data;
4
+ }
5
+ export function clearFactRegistry() {
6
+ cachedRegistry = null;
7
+ }
8
+ export function getFactRegistry() {
9
+ return cachedRegistry;
10
+ }
11
+ /**
12
+ * Query facts with optional filters.
13
+ * Returns facts sorted by confidence (highest first).
14
+ */
15
+ export function queryFacts(options = {}) {
16
+ if (!cachedRegistry?.facts.length)
17
+ return [];
18
+ let facts = cachedRegistry.facts;
19
+ if (options.categories?.length) {
20
+ const cats = new Set(options.categories);
21
+ facts = facts.filter(f => cats.has(f.category));
22
+ }
23
+ if (options.lang) {
24
+ facts = facts.filter(f => f.lang === options.lang || f.lang === 'all');
25
+ }
26
+ if (options.minConfidence !== undefined) {
27
+ facts = facts.filter(f => f.confidence >= options.minConfidence);
28
+ }
29
+ if (options.tags?.length) {
30
+ const tagSet = new Set(options.tags.map(t => t.toLowerCase()));
31
+ facts = facts.filter(f => f.tags.some(t => tagSet.has(t.toLowerCase())));
32
+ }
33
+ facts = [...facts].sort((a, b) => b.confidence - a.confidence);
34
+ if (options.limit && options.limit > 0) {
35
+ facts = facts.slice(0, options.limit);
36
+ }
37
+ return facts;
38
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Fact Registry — structured, verifiable facts extracted from blog data.
3
+ * Injected into prompts to ground AI responses in real data and reduce hallucination.
4
+ */
5
+ export type FactCategory = 'author' | 'blog' | 'content' | 'project' | 'tech';
6
+ /**
7
+ * How the fact was produced:
8
+ * - `explicit`: directly stated in blog content or configuration
9
+ * - `derived`: computed from blog data (counts, aggregations)
10
+ * - `aggregated`: synthesized from multiple posts/sources
11
+ */
12
+ export type FactSource = 'explicit' | 'derived' | 'aggregated';
13
+ export interface Fact {
14
+ id: string;
15
+ category: FactCategory;
16
+ /** Human-readable statement in the target language */
17
+ statement: string;
18
+ /** Where this fact comes from (file, config, computation) */
19
+ evidence: string;
20
+ source: FactSource;
21
+ /** 0–1 reliability score; 1 = absolute certainty */
22
+ confidence: number;
23
+ /** Keywords for query matching */
24
+ tags: string[];
25
+ lang: string;
26
+ }
27
+ export interface FactRegistryFile {
28
+ $schema: string;
29
+ generatedAt: string;
30
+ version: number;
31
+ facts: Fact[];
32
+ stats: FactRegistryStats;
33
+ }
34
+ export interface FactRegistryStats {
35
+ total: number;
36
+ byCategory: Record<FactCategory, number>;
37
+ avgConfidence: number;
38
+ }
39
+ export interface FactQueryOptions {
40
+ categories?: FactCategory[];
41
+ tags?: string[];
42
+ minConfidence?: number;
43
+ lang?: string;
44
+ limit?: number;
45
+ }
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/fact-registry/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAE9E;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,YAAY,CAAC;AAE/D,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,YAAY,CAAC;IACvB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,iBAAiB,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACzC,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Fact Registry — structured, verifiable facts extracted from blog data.
3
+ * Injected into prompts to ground AI responses in real data and reduce hallucination.
4
+ */
5
+ export {};
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export * from './search/index.js';
14
14
  export * from './intelligence/index.js';
15
15
  export * from './prompt/index.js';
16
16
  export * from './data/index.js';
17
+ export * from './fact-registry/index.js';
17
18
  export * from './stream/index.js';
18
19
  export * from './server/index.js';
19
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,cAAc,sBAAsB,CAAC;AAGrC,cAAc,6BAA6B,CAAC;AAG5C,cAAc,uBAAuB,CAAC;AAGtC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,yBAAyB,CAAC;AAGxC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,iBAAiB,CAAC;AAGhC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,cAAc,sBAAsB,CAAC;AAGrC,cAAc,6BAA6B,CAAC;AAG5C,cAAc,uBAAuB,CAAC;AAGtC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,yBAAyB,CAAC;AAGxC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,iBAAiB,CAAC;AAGhC,cAAc,0BAA0B,CAAC;AAGzC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -22,6 +22,8 @@ export * from './intelligence/index.js';
22
22
  export * from './prompt/index.js';
23
23
  // Build-time metadata loading
24
24
  export * from './data/index.js';
25
+ // Fact Registry: verified facts for hallucination reduction
26
+ export * from './fact-registry/index.js';
25
27
  // Stream utilities
26
28
  export * from './stream/index.js';
27
29
  // Server-side API handlers (chat handler, metadata init)
@@ -0,0 +1,19 @@
1
+ import type { ArticleContext, ProjectContext } from '../search/types.js';
2
+ export interface CitationAppenderConfig {
3
+ articles: ArticleContext[];
4
+ projects: ProjectContext[];
5
+ lang: string;
6
+ maxCitations: number;
7
+ minScore: number;
8
+ }
9
+ interface CitationCandidate {
10
+ title: string;
11
+ url: string;
12
+ score: number;
13
+ }
14
+ export declare function selectCitations(articles: ArticleContext[], projects: ProjectContext[], maxCitations: number, minScore: number): CitationCandidate[];
15
+ export declare function formatCitationBlock(citations: CitationCandidate[], lang: string): string;
16
+ export declare function createCitationAppenderTransform(config: CitationAppenderConfig): (stream: ReadableStream<string>) => ReadableStream<string>;
17
+ export declare function shouldAppendCitations(response: string, articles: ArticleContext[], projects: ProjectContext[]): boolean;
18
+ export {};
19
+ //# sourceMappingURL=citation-appender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"citation-appender.d.ts","sourceRoot":"","sources":["../../src/intelligence/citation-appender.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,iBAAiB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAQD,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,cAAc,EAAE,EAC1B,QAAQ,EAAE,cAAc,EAAE,EAC1B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,iBAAiB,EAAE,CAarB;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,iBAAiB,EAAE,EAC9B,IAAI,EAAE,MAAM,GACX,MAAM,CAYR;AAED,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,sBAAsB,GAC7B,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,cAAc,CAAC,MAAM,CAAC,CAkC5D;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,EAAE,EAC1B,QAAQ,EAAE,cAAc,EAAE,GACzB,OAAO,CAQT"}
@@ -0,0 +1,65 @@
1
+ function hasExistingCitations(text, validUrls) {
2
+ const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
3
+ const matches = [...text.matchAll(linkPattern)];
4
+ return matches.some(m => validUrls.has(m[2]));
5
+ }
6
+ export function selectCitations(articles, projects, maxCitations, minScore) {
7
+ const candidates = [
8
+ ...articles
9
+ .filter(a => (a.score ?? 0) >= minScore)
10
+ .map(a => ({ title: a.title, url: a.url, score: a.score ?? 0 })),
11
+ ...projects
12
+ .filter(p => (p.score ?? 0) >= minScore)
13
+ .map(p => ({ title: p.name, url: p.url, score: p.score ?? 0 })),
14
+ ];
15
+ return candidates
16
+ .sort((a, b) => b.score - a.score)
17
+ .slice(0, maxCitations);
18
+ }
19
+ export function formatCitationBlock(citations, lang) {
20
+ if (citations.length === 0)
21
+ return '';
22
+ const heading = lang === 'zh' ? '延伸阅读' : 'Further Reading';
23
+ const lines = [
24
+ '',
25
+ `**${heading}:**`,
26
+ ...citations.map(c => `- [${c.title}](${c.url})`),
27
+ ];
28
+ return lines.join('\n');
29
+ }
30
+ export function createCitationAppenderTransform(config) {
31
+ const { articles, projects, lang, maxCitations = 3, minScore = 5 } = config;
32
+ const validUrls = new Set([
33
+ ...articles.map(a => a.url),
34
+ ...projects.map(p => p.url),
35
+ ]);
36
+ return (stream) => {
37
+ let fullText = '';
38
+ const transform = new TransformStream({
39
+ transform(chunk, controller) {
40
+ fullText += chunk;
41
+ controller.enqueue(chunk);
42
+ },
43
+ flush(controller) {
44
+ if (hasExistingCitations(fullText, validUrls)) {
45
+ return;
46
+ }
47
+ const citations = selectCitations(articles, projects, maxCitations, minScore);
48
+ if (citations.length === 0) {
49
+ return;
50
+ }
51
+ const citationBlock = formatCitationBlock(citations, lang);
52
+ controller.enqueue(citationBlock);
53
+ },
54
+ });
55
+ return stream.pipeThrough(transform);
56
+ };
57
+ }
58
+ export function shouldAppendCitations(response, articles, projects) {
59
+ const validUrls = new Set([
60
+ ...articles.map(a => a.url),
61
+ ...projects.map(p => p.url),
62
+ ]);
63
+ return !hasExistingCitations(response, validUrls) &&
64
+ [...articles, ...projects].some(item => (item.score ?? 0) >= 5);
65
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"citation-guard.d.ts","sourceRoot":"","sources":["../../src/intelligence/citation-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE9E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,GAAG,SAAS,CAAC;AAiB1G;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAS3D;AAkBD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,sBAAsB,GAAG,IAAI,CA2BhC;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE;IACnD,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,mBAAmB,EAAE,CAAA;KAAE,KAAK,IAAI,CAAC;CAClE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,cAAc,CAAC,MAAM,CAAC,CAuD7D"}
1
+ {"version":3,"file":"citation-guard.d.ts","sourceRoot":"","sources":["../../src/intelligence/citation-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAS9E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,GAAG,SAAS,CAAC;AAgB1G;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAS3D;AAoBD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,sBAAsB,GAAG,IAAI,CAuBhC;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE;IACnD,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,mBAAmB,EAAE,CAAA;KAAE,KAAK,IAAI,CAAC;CAClE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,cAAc,CAAC,MAAM,CAAC,CAuD7D"}
@@ -1,10 +1,11 @@
1
+ import { PRIVACY_REFUSAL_TEMPLATES, NO_ARTICLE_TEMPLATES, ARTICLE_COUNT_TEMPLATES, pickTemplate, pickTemplateWithVars, } from './response-templates.js';
1
2
  const PRIVACY_PATTERNS = [
2
- { regex: /(住址|地址|住在哪|address|where.*live)/iu, zh: '具体住址是私人信息,未在博客中公开。', en: 'Address is private and not disclosed on the blog.' },
3
- { regex: /(收入|工资|薪资|salary|income|earn)/iu, zh: '收入信息未在博客中公开。', en: 'Income information is not disclosed on the blog.' },
4
- { regex: /(家人|妻子|丈夫|孩子|父母|family|wife|husband|children|parent)/iu, zh: '家人信息未在博客中公开。', en: 'Family information is not disclosed on the blog.' },
5
- { regex: /(电话|手机号|phone|mobile)/iu, zh: '联系电话未在博客中公开。', en: 'Phone number is not disclosed on the blog.' },
6
- { regex: /(身份证|id\s*card|passport)/iu, zh: '身份证件信息未在博客中公开。', en: 'ID information is not disclosed on the blog.' },
7
- { regex: /(年龄|多大了|几岁|how old|age)/iu, zh: '年龄信息未在博客中公开。', en: 'Age information is not disclosed on the blog.' },
3
+ { regex: /(住址|地址|住在哪|address|where.*live)/iu, key: 'address' },
4
+ { regex: /(收入|工资|薪资|salary|income|earn)/iu, key: 'income' },
5
+ { regex: /(家人|妻子|丈夫|孩子|父母|family|wife|husband|children|parent)/iu, key: 'family' },
6
+ { regex: /(电话|手机号|phone|mobile)/iu, key: 'phone' },
7
+ { regex: /(身份证|id\s*card|passport)/iu, key: 'id' },
8
+ { regex: /(年龄|多大了|几岁|how old|age)/iu, key: 'age' },
8
9
  ];
9
10
  /**
10
11
  * Resolves the expected answer mode from the user query.
@@ -33,8 +34,10 @@ export function resolveAnswerMode(query) {
33
34
  function checkPrivacyRefusal(query, lang) {
34
35
  for (const pattern of PRIVACY_PATTERNS) {
35
36
  if (pattern.regex.test(query)) {
37
+ const templates = PRIVACY_REFUSAL_TEMPLATES[pattern.key];
38
+ const text = templates ? pickTemplate(templates, lang) : '';
36
39
  return {
37
- text: lang === 'en' ? pattern.en : pattern.zh,
40
+ text,
38
41
  actions: ['preflight_reject'],
39
42
  };
40
43
  }
@@ -55,17 +58,13 @@ export function getCitationGuardPreflight(params) {
55
58
  if (/有几篇|有多少篇|文章数量|总共.*文章|how many.*article/u.test(q)) {
56
59
  const total = articles.length;
57
60
  if (total > 0) {
58
- const text = lang === 'en'
59
- ? `Based on my search, I found ${total} related articles.`
60
- : `根据我检索到的信息,当前共找到 ${total} 篇相关文章。`;
61
+ const text = pickTemplateWithVars(ARTICLE_COUNT_TEMPLATES, lang, { count: total });
61
62
  return { text, actions: ['preflight_reject'] };
62
63
  }
63
64
  }
64
65
  if (/有没有|是否有|有.*文章|写过.*吗|is there|any.*article/u.test(q)) {
65
66
  if (articles.length === 0 && projects.length === 0) {
66
- const text = lang === 'en'
67
- ? 'No articles directly related to this topic were found. Try different keywords or ask another question.'
68
- : '根据博客内容搜索,目前没有找到与这个主题直接相关的文章。你可以尝试用其他关键词搜索,或者问我其他问题。';
67
+ const text = pickTemplate(NO_ARTICLE_TEMPLATES, lang);
69
68
  return { text, actions: ['preflight_reject'] };
70
69
  }
71
70
  }
@@ -4,5 +4,7 @@ export { shouldRunKeywordExtraction, extractSearchKeywords, KEYWORD_EXTRACTION_T
4
4
  export { shouldSkipAnalysis, analyzeRetrievedEvidence, buildEvidenceSection, EVIDENCE_ANALYSIS_TIMEOUT_MS, EVIDENCE_ANALYSIS_MAX_TOKENS, } from './evidence-analysis.js';
5
5
  export { getCitationGuardPreflight, createCitationGuardTransform, resolveAnswerMode, } from './citation-guard.js';
6
6
  export type { AnswerMode } from './citation-guard.js';
7
+ export { createCitationAppenderTransform, shouldAppendCitations, selectCitations, formatCitationBlock, } from './citation-appender.js';
8
+ export type { CitationAppenderConfig } from './citation-appender.js';
7
9
  export type { QueryComplexity, KeywordExtractionResult, TokenUsageStats, EvidenceAnalysisResult, CitationGuardPreflight, CitationGuardAction, } from './types.js';
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/intelligence/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,EACd,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,yBAAyB,EACzB,4BAA4B,EAC5B,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,YAAY,EACV,eAAe,EACf,uBAAuB,EACvB,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/intelligence/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,eAAe,EACf,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,EACd,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,yBAAyB,EACzB,4BAA4B,EAC5B,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EACL,+BAA+B,EAC/B,qBAAqB,EACrB,eAAe,EACf,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAErE,YAAY,EACV,eAAe,EACf,uBAAuB,EACvB,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
@@ -2,3 +2,4 @@ export { isLikelyFollowUp, hasNewSignificantTokens, hasQueryOverlap, shouldReuse
2
2
  export { shouldRunKeywordExtraction, extractSearchKeywords, KEYWORD_EXTRACTION_TIMEOUT_MS, } from './keyword-extract.js';
3
3
  export { shouldSkipAnalysis, analyzeRetrievedEvidence, buildEvidenceSection, EVIDENCE_ANALYSIS_TIMEOUT_MS, EVIDENCE_ANALYSIS_MAX_TOKENS, } from './evidence-analysis.js';
4
4
  export { getCitationGuardPreflight, createCitationGuardTransform, resolveAnswerMode, } from './citation-guard.js';
5
+ export { createCitationAppenderTransform, shouldAppendCitations, selectCitations, formatCitationBlock, } from './citation-appender.js';
@@ -0,0 +1,16 @@
1
+ export interface ResponseTemplate {
2
+ zh: string[];
3
+ en: string[];
4
+ }
5
+ export declare const PRIVACY_REFUSAL_TEMPLATES: Record<string, ResponseTemplate>;
6
+ export declare const NO_ARTICLE_TEMPLATES: ResponseTemplate;
7
+ export declare const ARTICLE_COUNT_TEMPLATES: ResponseTemplate;
8
+ /**
9
+ * Randomly selects a template from the available options.
10
+ */
11
+ export declare function pickTemplate(templates: ResponseTemplate, lang: string): string;
12
+ /**
13
+ * Picks a template and interpolates variables.
14
+ */
15
+ export declare function pickTemplateWithVars(templates: ResponseTemplate, lang: string, vars: Record<string, string | number>): string;
16
+ //# sourceMappingURL=response-templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-templates.d.ts","sourceRoot":"","sources":["../../src/intelligence/response-templates.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,EAAE,EAAE,MAAM,EAAE,CAAC;CACd;AAED,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAyEtE,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,gBAWlC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,gBAWrC,CAAC;AAEF;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,gBAAgB,EAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GACpC,MAAM,CAMR"}