@reftrixmcp/ml 0.1.7

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 (97) hide show
  1. package/LICENSE +661 -0
  2. package/dist/dinov2/index.d.ts +7 -0
  3. package/dist/dinov2/index.d.ts.map +1 -0
  4. package/dist/dinov2/index.js +7 -0
  5. package/dist/dinov2/index.js.map +1 -0
  6. package/dist/dinov2/service.d.ts +106 -0
  7. package/dist/dinov2/service.d.ts.map +1 -0
  8. package/dist/dinov2/service.js +446 -0
  9. package/dist/dinov2/service.js.map +1 -0
  10. package/dist/dinov2/worker-thread-types.d.ts +77 -0
  11. package/dist/dinov2/worker-thread-types.d.ts.map +1 -0
  12. package/dist/dinov2/worker-thread-types.js +4 -0
  13. package/dist/dinov2/worker-thread-types.js.map +1 -0
  14. package/dist/dinov2/worker-thread.d.ts +2 -0
  15. package/dist/dinov2/worker-thread.d.ts.map +1 -0
  16. package/dist/dinov2/worker-thread.js +231 -0
  17. package/dist/dinov2/worker-thread.js.map +1 -0
  18. package/dist/embeddings/index.d.ts +12 -0
  19. package/dist/embeddings/index.d.ts.map +1 -0
  20. package/dist/embeddings/index.js +13 -0
  21. package/dist/embeddings/index.js.map +1 -0
  22. package/dist/embeddings/multimodal-embedding.service.d.ts +400 -0
  23. package/dist/embeddings/multimodal-embedding.service.d.ts.map +1 -0
  24. package/dist/embeddings/multimodal-embedding.service.js +650 -0
  25. package/dist/embeddings/multimodal-embedding.service.js.map +1 -0
  26. package/dist/embeddings/service.d.ts +261 -0
  27. package/dist/embeddings/service.d.ts.map +1 -0
  28. package/dist/embeddings/service.js +1093 -0
  29. package/dist/embeddings/service.js.map +1 -0
  30. package/dist/embeddings/style-embedding.service.d.ts +117 -0
  31. package/dist/embeddings/style-embedding.service.d.ts.map +1 -0
  32. package/dist/embeddings/style-embedding.service.js +157 -0
  33. package/dist/embeddings/style-embedding.service.js.map +1 -0
  34. package/dist/embeddings/types.d.ts +83 -0
  35. package/dist/embeddings/types.d.ts.map +1 -0
  36. package/dist/embeddings/types.js +4 -0
  37. package/dist/embeddings/types.js.map +1 -0
  38. package/dist/embeddings/vision-embedding.service.d.ts +70 -0
  39. package/dist/embeddings/vision-embedding.service.d.ts.map +1 -0
  40. package/dist/embeddings/vision-embedding.service.js +221 -0
  41. package/dist/embeddings/vision-embedding.service.js.map +1 -0
  42. package/dist/embeddings/vision-embedding.types.d.ts +102 -0
  43. package/dist/embeddings/vision-embedding.types.d.ts.map +1 -0
  44. package/dist/embeddings/vision-embedding.types.js +4 -0
  45. package/dist/embeddings/vision-embedding.types.js.map +1 -0
  46. package/dist/embeddings/worker-thread-types.d.ts +152 -0
  47. package/dist/embeddings/worker-thread-types.d.ts.map +1 -0
  48. package/dist/embeddings/worker-thread-types.js +4 -0
  49. package/dist/embeddings/worker-thread-types.js.map +1 -0
  50. package/dist/embeddings/worker-thread.d.ts +4 -0
  51. package/dist/embeddings/worker-thread.d.ts.map +1 -0
  52. package/dist/embeddings/worker-thread.js +441 -0
  53. package/dist/embeddings/worker-thread.js.map +1 -0
  54. package/dist/index.d.ts +18 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +19 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/search/fulltext-helpers.d.ts +30 -0
  59. package/dist/search/fulltext-helpers.d.ts.map +1 -0
  60. package/dist/search/fulltext-helpers.js +40 -0
  61. package/dist/search/fulltext-helpers.js.map +1 -0
  62. package/dist/search/hybrid.d.ts +40 -0
  63. package/dist/search/hybrid.d.ts.map +1 -0
  64. package/dist/search/hybrid.js +61 -0
  65. package/dist/search/hybrid.js.map +1 -0
  66. package/dist/search/index.d.ts +12 -0
  67. package/dist/search/index.d.ts.map +1 -0
  68. package/dist/search/index.js +11 -0
  69. package/dist/search/index.js.map +1 -0
  70. package/dist/search/rrf.d.ts +69 -0
  71. package/dist/search/rrf.d.ts.map +1 -0
  72. package/dist/search/rrf.js +108 -0
  73. package/dist/search/rrf.js.map +1 -0
  74. package/dist/search/types.d.ts +130 -0
  75. package/dist/search/types.d.ts.map +1 -0
  76. package/dist/search/types.js +4 -0
  77. package/dist/search/types.js.map +1 -0
  78. package/package.json +65 -0
  79. package/src/dinov2/index.ts +21 -0
  80. package/src/dinov2/service.ts +539 -0
  81. package/src/dinov2/worker-thread-types.ts +103 -0
  82. package/src/dinov2/worker-thread.ts +306 -0
  83. package/src/embeddings/index.ts +74 -0
  84. package/src/embeddings/multimodal-embedding.service.ts +992 -0
  85. package/src/embeddings/service.ts +1287 -0
  86. package/src/embeddings/style-embedding.service.ts +201 -0
  87. package/src/embeddings/types.ts +95 -0
  88. package/src/embeddings/vision-embedding.service.ts +273 -0
  89. package/src/embeddings/vision-embedding.types.ts +117 -0
  90. package/src/embeddings/worker-thread-types.ts +194 -0
  91. package/src/embeddings/worker-thread.ts +529 -0
  92. package/src/index.ts +70 -0
  93. package/src/search/fulltext-helpers.ts +45 -0
  94. package/src/search/hybrid.ts +107 -0
  95. package/src/search/index.ts +25 -0
  96. package/src/search/rrf.ts +154 -0
  97. package/src/search/types.ts +141 -0
@@ -0,0 +1,107 @@
1
+ // SPDX-FileCopyrightText: 2025-2026 Reftrix Contributors
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ /**
5
+ * Hybrid Search Service
6
+ *
7
+ * Combines vector search (pgvector cosine similarity) and full-text search
8
+ * (PostgreSQL tsvector/tsquery) using Reciprocal Rank Fusion (RRF).
9
+ *
10
+ * Default weights: 60% vector + 40% full-text
11
+ */
12
+
13
+ import { mergeWithRRF, normalizeRRFScore } from "./rrf.js";
14
+ import type { RankedItem, RRFScoredItem } from "./rrf.js";
15
+
16
+ /**
17
+ * Configuration for hybrid search execution
18
+ */
19
+ export interface HybridSearchConfig {
20
+ /** Weight for vector search results (default 0.6) */
21
+ vectorWeight?: number;
22
+ /** Weight for full-text search results (default 0.4) */
23
+ fulltextWeight?: number;
24
+ /** RRF constant k (default 60) */
25
+ k?: number;
26
+ }
27
+
28
+ /**
29
+ * Result from hybrid search with normalized similarity score
30
+ */
31
+ export interface HybridSearchResult {
32
+ id: string;
33
+ /** Normalized similarity score (0-1) */
34
+ similarity: number;
35
+ /** Source information for debugging */
36
+ source: {
37
+ vectorRank?: number;
38
+ fulltextRank?: number;
39
+ };
40
+ /** Additional data from the search result */
41
+ data: Record<string, unknown>;
42
+ }
43
+
44
+ const DEFAULT_CONFIG: Required<HybridSearchConfig> = {
45
+ vectorWeight: 0.6,
46
+ fulltextWeight: 0.4,
47
+ k: 60,
48
+ };
49
+
50
+ /**
51
+ * Execute hybrid search by combining vector and full-text search results with RRF
52
+ *
53
+ * Both search functions are executed in parallel via Promise.all for minimum latency.
54
+ * Results are merged using Reciprocal Rank Fusion (RRF) with configurable weights.
55
+ *
56
+ * @param vectorSearchFn - Function that returns vector search results (ranked by cosine similarity)
57
+ * @param fulltextSearchFn - Function that returns full-text search results (ranked by ts_rank_cd)
58
+ * @param config - Optional hybrid search configuration
59
+ * @returns Merged results sorted by normalized RRF similarity score
60
+ */
61
+ export async function executeHybridSearch(
62
+ vectorSearchFn: () => Promise<RankedItem[]>,
63
+ fulltextSearchFn: () => Promise<RankedItem[]>,
64
+ config?: HybridSearchConfig
65
+ ): Promise<HybridSearchResult[]> {
66
+ const { vectorWeight, fulltextWeight, k } = {
67
+ ...DEFAULT_CONFIG,
68
+ ...config,
69
+ };
70
+
71
+ // Execute both searches in parallel for minimum latency
72
+ const [vectorResults, fulltextResults] = await Promise.all([
73
+ vectorSearchFn(),
74
+ fulltextSearchFn(),
75
+ ]);
76
+
77
+ // If one source returns empty, fall back to the other source only
78
+ if (vectorResults.length === 0 && fulltextResults.length === 0) {
79
+ return [];
80
+ }
81
+
82
+ // Merge with RRF
83
+ const merged: RRFScoredItem[] = mergeWithRRF(
84
+ vectorResults,
85
+ fulltextResults,
86
+ vectorWeight,
87
+ fulltextWeight,
88
+ k
89
+ );
90
+
91
+ // Normalize scores and format output
92
+ return merged.map((item: RRFScoredItem): HybridSearchResult => {
93
+ const source: HybridSearchResult["source"] = {};
94
+ if (item.vectorRank !== undefined) {
95
+ source.vectorRank = item.vectorRank;
96
+ }
97
+ if (item.fulltextRank !== undefined) {
98
+ source.fulltextRank = item.fulltextRank;
99
+ }
100
+ return {
101
+ id: item.id,
102
+ similarity: normalizeRRFScore(item.rrfScore),
103
+ source,
104
+ data: item.data,
105
+ };
106
+ });
107
+ }
@@ -0,0 +1,25 @@
1
+ // SPDX-FileCopyrightText: 2026 TKMD and Reftrix Contributors
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ /**
5
+ * Search utilities exports
6
+ *
7
+ * RRF utilities and types for WebDesign search services.
8
+ */
9
+
10
+ export { calculateRRF, mergeWithRRF, normalizeRRFScore, toRankedItems } from "./rrf.js";
11
+ export type { RankedItem, RRFScoredItem } from "./rrf.js";
12
+ export { executeHybridSearch } from "./hybrid.js";
13
+ export { buildFulltextConditions, buildFulltextRankExpression } from "./fulltext-helpers.js";
14
+ export type { HybridSearchConfig, HybridSearchResult } from "./hybrid.js";
15
+ export type {
16
+ SearchFilters,
17
+ SearchOptions,
18
+ HybridSearchOptions,
19
+ SearchResult,
20
+ SummarySearchResult,
21
+ SearchResultItem,
22
+ SummarySearchResultItem,
23
+ VectorSearchRawResult,
24
+ FullTextSearchRawResult,
25
+ } from "./types.js";
@@ -0,0 +1,154 @@
1
+ // SPDX-FileCopyrightText: 2026 TKMD and Reftrix Contributors
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ /**
5
+ * Reciprocal Rank Fusion (RRF) implementation
6
+ *
7
+ * RRF is a method for combining rankings from multiple search sources.
8
+ * It's particularly useful for hybrid search combining vector and full-text results.
9
+ *
10
+ * Formula: score = 1 / (k + rank)
11
+ * where k is a constant (typically 60) that prevents high-ranked items from
12
+ * dominating the final ranking.
13
+ */
14
+
15
+ /**
16
+ * Item with ranking information from a single source
17
+ */
18
+ export interface RankedItem {
19
+ id: string;
20
+ rank: number;
21
+ [key: string]: unknown;
22
+ }
23
+
24
+ /**
25
+ * Item with combined RRF score
26
+ */
27
+ export interface RRFScoredItem {
28
+ id: string;
29
+ rrfScore: number;
30
+ vectorRank?: number;
31
+ fulltextRank?: number;
32
+ data: Record<string, unknown>;
33
+ }
34
+
35
+ /**
36
+ * Calculate RRF score for a single rank position
37
+ *
38
+ * @param rank - Rank position (1-based)
39
+ * @param k - Ranking constant (default 60)
40
+ * @returns RRF score
41
+ */
42
+ export function calculateRRF(rank: number, k: number = 60): number {
43
+ return 1 / (k + rank);
44
+ }
45
+
46
+ /**
47
+ * Merge results from vector and full-text search using RRF
48
+ *
49
+ * @param vectorResults - Results from vector similarity search
50
+ * @param fulltextResults - Results from full-text search
51
+ * @param vectorWeight - Weight for vector search results (default 0.6)
52
+ * @param fulltextWeight - Weight for full-text search results (default 0.4)
53
+ * @param k - RRF constant (default 60)
54
+ * @returns Merged and sorted results
55
+ */
56
+ export function mergeWithRRF(
57
+ vectorResults: RankedItem[],
58
+ fulltextResults: RankedItem[],
59
+ vectorWeight: number = 0.6,
60
+ fulltextWeight: number = 0.4,
61
+ k: number = 60
62
+ ): RRFScoredItem[] {
63
+ const scoreMap = new Map<string, RRFScoredItem>();
64
+
65
+ // Process vector search results
66
+ vectorResults.forEach((item, index) => {
67
+ const rank = index + 1; // 1-based rank
68
+ const rrfScore = calculateRRF(rank, k) * vectorWeight;
69
+
70
+ const existing = scoreMap.get(item.id);
71
+ if (existing) {
72
+ existing.rrfScore += rrfScore;
73
+ existing.vectorRank = rank;
74
+ } else {
75
+ const { id: _id, rank: _, ...data } = item;
76
+ scoreMap.set(item.id, {
77
+ id: item.id,
78
+ rrfScore,
79
+ vectorRank: rank,
80
+ data: data as Record<string, unknown>,
81
+ });
82
+ }
83
+ });
84
+
85
+ // Process full-text search results
86
+ fulltextResults.forEach((item, index) => {
87
+ const rank = index + 1; // 1-based rank
88
+ const rrfScore = calculateRRF(rank, k) * fulltextWeight;
89
+
90
+ const existing = scoreMap.get(item.id);
91
+ if (existing) {
92
+ existing.rrfScore += rrfScore;
93
+ existing.fulltextRank = rank;
94
+ // Merge additional data if not present
95
+ existing.data = { ...item, ...existing.data };
96
+ } else {
97
+ const { id: _id, rank: _, ...data } = item;
98
+ scoreMap.set(item.id, {
99
+ id: item.id,
100
+ rrfScore,
101
+ fulltextRank: rank,
102
+ data: data as Record<string, unknown>,
103
+ });
104
+ }
105
+ });
106
+
107
+ // Convert to array and sort by RRF score (descending)
108
+ const merged = Array.from(scoreMap.values());
109
+ merged.sort((a, b) => b.rrfScore - a.rrfScore);
110
+
111
+ if (process.env.NODE_ENV === "development") {
112
+ // eslint-disable-next-line no-console
113
+ console.log("[Search] RRF merge completed:", {
114
+ vectorCount: vectorResults.length,
115
+ fulltextCount: fulltextResults.length,
116
+ mergedCount: merged.length,
117
+ topScore: merged[0]?.rrfScore,
118
+ });
119
+ }
120
+
121
+ return merged;
122
+ }
123
+
124
+ /**
125
+ * Convert an array of rows (with an `id` field) into RankedItem[] with 1-based ranks.
126
+ *
127
+ * The array order is preserved and used for rank assignment.
128
+ * All fields from each row are spread into the resulting RankedItem.
129
+ *
130
+ * @param rows - Array of objects that each contain at least an `id: string` field
131
+ * @returns RankedItem[] with rank starting from 1
132
+ */
133
+ export function toRankedItems<T extends { id: string }>(rows: T[]): RankedItem[] {
134
+ return rows.map((row, index) => ({
135
+ ...row,
136
+ rank: index + 1,
137
+ }));
138
+ }
139
+
140
+ /**
141
+ * Calculate effective score from RRF for display purposes
142
+ * Normalizes the RRF score to a 0-1 range
143
+ *
144
+ * @param rrfScore - Raw RRF score
145
+ * @param maxPossibleScore - Maximum possible score (sum of max vector and fulltext scores)
146
+ * @returns Normalized similarity score (0-1)
147
+ */
148
+ export function normalizeRRFScore(rrfScore: number, maxPossibleScore?: number): number {
149
+ // Default max score: best possible rank (1) in both searches with default weights
150
+ const defaultMax = calculateRRF(1, 60) * 0.6 + calculateRRF(1, 60) * 0.4;
151
+ const maxScore = maxPossibleScore ?? defaultMax;
152
+
153
+ return Math.min(1, rrfScore / maxScore);
154
+ }
@@ -0,0 +1,141 @@
1
+ // SPDX-FileCopyrightText: 2026 TKMD and Reftrix Contributors
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ /**
5
+ * Search service type definitions
6
+ *
7
+ * WebDesign専用 - Layout (SectionPattern) and Motion (MotionPattern) search
8
+ */
9
+
10
+ /**
11
+ * Search filter options for WebDesign patterns
12
+ */
13
+ export interface SearchFilters {
14
+ /** Pattern type filter (hero, feature, cta, footer, etc.) */
15
+ type?: string | undefined;
16
+ /** Trigger filter for motion patterns (scroll, hover, click, load) */
17
+ trigger?: string | undefined;
18
+ /** Category filter for motion patterns (entrance, exit, emphasis, etc.) */
19
+ category?: string | undefined;
20
+ /** Tag filters */
21
+ tags?: string[] | undefined;
22
+ /** Page ID filter */
23
+ pageId?: string | undefined;
24
+ }
25
+
26
+ /**
27
+ * Search options for all search methods
28
+ */
29
+ export interface SearchOptions {
30
+ /** Maximum number of results to return */
31
+ limit: number;
32
+ /** Offset for pagination */
33
+ offset?: number | undefined;
34
+ /** Search filters */
35
+ filters?: SearchFilters | undefined;
36
+ /** Summary mode: returns only id, name, similarity (default: false) */
37
+ summary?: boolean | undefined;
38
+ }
39
+
40
+ /**
41
+ * Hybrid search options extending base options
42
+ */
43
+ export interface HybridSearchOptions extends SearchOptions {
44
+ /** Weight for vector search results (default 0.6) */
45
+ vectorWeight?: number | undefined;
46
+ /** Weight for full-text search results (default 0.4) */
47
+ fulltextWeight?: number | undefined;
48
+ /** Minimum similarity threshold (0-1) */
49
+ minSimilarity?: number | undefined;
50
+ }
51
+
52
+ /**
53
+ * Individual search result item for WebDesign patterns
54
+ */
55
+ export interface SearchResultItem {
56
+ id: string;
57
+ name: string;
58
+ type: string;
59
+ description: string | undefined;
60
+ similarity: number;
61
+ /** HTML snippet for layout patterns */
62
+ htmlSnippet?: string | undefined;
63
+ /** Raw CSS for motion patterns */
64
+ rawCss?: string | undefined;
65
+ /** Trigger type for motion patterns */
66
+ trigger?: string | undefined;
67
+ /** Category for motion patterns */
68
+ category?: string | undefined;
69
+ /** Associated page ID */
70
+ pageId?: string | undefined;
71
+ createdAt: Date;
72
+ }
73
+
74
+ /**
75
+ * Summary search result item (lightweight, only essential fields)
76
+ */
77
+ export interface SummarySearchResultItem {
78
+ id: string;
79
+ name: string;
80
+ similarity: number;
81
+ }
82
+
83
+ /**
84
+ * Search result with pagination info
85
+ */
86
+ export interface SearchResult {
87
+ items: SearchResultItem[];
88
+ total: number;
89
+ limit: number;
90
+ offset: number;
91
+ searchTimeMs?: number;
92
+ /** Summary mode flag (only present when summary=true) */
93
+ _summary_mode?: true;
94
+ }
95
+
96
+ /**
97
+ * Summary search result with pagination info (lightweight response)
98
+ */
99
+ export interface SummarySearchResult {
100
+ items: SummarySearchResultItem[];
101
+ total: number;
102
+ limit: number;
103
+ offset: number;
104
+ searchTimeMs?: number;
105
+ /** Summary mode flag (always true for summary results) */
106
+ _summary_mode: true;
107
+ }
108
+
109
+ /**
110
+ * Vector search raw result from database (section_patterns / motion_patterns)
111
+ */
112
+ export interface VectorSearchRawResult {
113
+ id: string;
114
+ name: string;
115
+ type: string;
116
+ description?: string;
117
+ similarity: number;
118
+ html_snippet?: string;
119
+ raw_css?: string;
120
+ trigger?: string;
121
+ category?: string;
122
+ page_id?: string;
123
+ created_at: Date;
124
+ }
125
+
126
+ /**
127
+ * Full-text search raw result from database
128
+ */
129
+ export interface FullTextSearchRawResult {
130
+ id: string;
131
+ name: string;
132
+ type: string;
133
+ description?: string;
134
+ rank: number;
135
+ html_snippet?: string;
136
+ raw_css?: string;
137
+ trigger?: string;
138
+ category?: string;
139
+ page_id?: string;
140
+ created_at: Date;
141
+ }