@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,201 @@
1
+ // SPDX-FileCopyrightText: 2026 TKMD and Reftrix Contributors
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ /**
5
+ * Style Embedding Service
6
+ *
7
+ * スタイル特徴量のテキスト表現から埋め込みベクトルを生成するサービス。
8
+ * 既存のEmbeddingServiceをラップし、スタイル特徴量に特化した機能を提供。
9
+ *
10
+ * @module embeddings/style-embedding.service
11
+ * @version 0.1.0
12
+ */
13
+
14
+ import { EmbeddingService } from "./service.js";
15
+ import type { CacheStats } from "./types.js";
16
+
17
+ // 開発環境でのログ出力
18
+ const isDevelopment = process.env.NODE_ENV === "development";
19
+ const log = (message: string, data?: unknown): void => {
20
+ if (isDevelopment) {
21
+ // eslint-disable-next-line no-console
22
+ console.log(`[StyleEmbedding] ${message}`, data ?? "");
23
+ }
24
+ };
25
+
26
+ /**
27
+ * スタイル埋め込みの設定
28
+ */
29
+ export interface StyleEmbeddingConfig {
30
+ /**
31
+ * 内部で使用するEmbeddingServiceのインスタンス
32
+ * デフォルトは新規インスタンスを作成
33
+ */
34
+ embeddingService?: EmbeddingService;
35
+
36
+ /**
37
+ * バッチ処理時の最大並列数
38
+ * @default 32
39
+ */
40
+ batchSize?: number;
41
+ }
42
+
43
+ /**
44
+ * スタイル特徴量埋め込み生成サービス
45
+ *
46
+ * スタイル特徴量テキスト表現から768次元の埋め込みベクトルを生成。
47
+ * multilingual-e5-baseモデルを使用し、HNSWベクトル検索と互換。
48
+ */
49
+ export class StyleEmbeddingService {
50
+ private embeddingService: EmbeddingService;
51
+ private batchSize: number;
52
+
53
+ constructor(config: StyleEmbeddingConfig = {}) {
54
+ this.embeddingService = config.embeddingService ?? new EmbeddingService();
55
+ this.batchSize = config.batchSize ?? 32;
56
+
57
+ log("StyleEmbeddingService created", { batchSize: this.batchSize });
58
+ }
59
+
60
+ /**
61
+ * スタイル特徴量テキストから埋め込みを生成
62
+ *
63
+ * @param styleText - StyleFeaturesToTextで生成されたテキスト表現
64
+ * @returns 768次元の正規化済み埋め込みベクトル
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const styleText = 'Design style: thin stroke (1px) consistent outlined simple complexity';
69
+ * const embedding = await service.generateEmbedding(styleText);
70
+ * // embedding.length === 768
71
+ * ```
72
+ */
73
+ async generateEmbedding(styleText: string): Promise<number[]> {
74
+ log("Generating embedding for style text", { length: styleText.length });
75
+
76
+ // "passage:" プレフィックスを使用(ドキュメント埋め込みとして扱う)
77
+ // スタイル特徴量は検索対象のドキュメント側なのでpassageを使用
78
+ const embedding = await this.embeddingService.generateEmbedding(styleText, "passage");
79
+
80
+ log("Embedding generated", { dimension: embedding.length });
81
+
82
+ return embedding;
83
+ }
84
+
85
+ /**
86
+ * クエリテキストから検索用埋め込みを生成
87
+ *
88
+ * 類似デザイン検索時に使用。ユーザーのクエリや参照デザインの
89
+ * スタイル特徴量テキストから検索用の埋め込みを生成。
90
+ *
91
+ * @param queryText - 検索クエリまたは参照スタイルテキスト
92
+ * @returns 768次元の正規化済み埋め込みベクトル
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const queryEmbedding = await service.generateQueryEmbedding('thin stroke outlined icon');
97
+ * // HNSW検索で使用
98
+ * ```
99
+ */
100
+ async generateQueryEmbedding(queryText: string): Promise<number[]> {
101
+ log("Generating query embedding", { length: queryText.length });
102
+
103
+ // "query:" プレフィックスを使用(検索クエリとして扱う)
104
+ const embedding = await this.embeddingService.generateEmbedding(queryText, "query");
105
+
106
+ log("Query embedding generated", { dimension: embedding.length });
107
+
108
+ return embedding;
109
+ }
110
+
111
+ /**
112
+ * 複数のスタイル特徴量テキストからバッチで埋め込みを生成
113
+ *
114
+ * @param styleTexts - スタイル特徴量テキストの配列
115
+ * @returns 768次元の埋め込みベクトルの配列
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * const embeddings = await service.generateBatchEmbeddings([
120
+ * 'Design style: thin stroke outlined',
121
+ * 'Design style: thick stroke filled',
122
+ * ]);
123
+ * // embeddings.length === 2
124
+ * ```
125
+ */
126
+ async generateBatchEmbeddings(styleTexts: string[]): Promise<number[][]> {
127
+ if (styleTexts.length === 0) {
128
+ return [];
129
+ }
130
+
131
+ log("Generating batch embeddings", { count: styleTexts.length });
132
+
133
+ const startTime = performance.now();
134
+
135
+ // "passage:" プレフィックスを使用(ドキュメント埋め込み)
136
+ const embeddings = await this.embeddingService.generateBatchEmbeddings(styleTexts, "passage");
137
+
138
+ const elapsed = performance.now() - startTime;
139
+ log("Batch embeddings generated", {
140
+ count: embeddings.length,
141
+ elapsedMs: elapsed,
142
+ avgMs: elapsed / embeddings.length,
143
+ });
144
+
145
+ return embeddings;
146
+ }
147
+
148
+ /**
149
+ * キャッシュ統計を取得
150
+ */
151
+ getCacheStats(): CacheStats {
152
+ return this.embeddingService.getCacheStats();
153
+ }
154
+
155
+ /**
156
+ * キャッシュをクリア
157
+ */
158
+ clearCache(): void {
159
+ this.embeddingService.clearCache();
160
+ log("Cache cleared");
161
+ }
162
+ }
163
+
164
+ // =============================================================================
165
+ // シングルトンとヘルパー関数
166
+ // =============================================================================
167
+
168
+ /**
169
+ * デフォルトのスタイル埋め込みサービスインスタンス
170
+ */
171
+ export const styleEmbeddingService = new StyleEmbeddingService();
172
+
173
+ /**
174
+ * スタイル特徴量テキストから埋め込みを生成するヘルパー関数
175
+ *
176
+ * @param styleText - スタイル特徴量テキスト
177
+ * @returns 768次元の埋め込みベクトル
178
+ */
179
+ export async function createStyleEmbedding(styleText: string): Promise<number[]> {
180
+ return styleEmbeddingService.generateEmbedding(styleText);
181
+ }
182
+
183
+ /**
184
+ * 複数のスタイル特徴量テキストからバッチで埋め込みを生成するヘルパー関数
185
+ *
186
+ * @param styleTexts - スタイル特徴量テキストの配列
187
+ * @returns 768次元の埋め込みベクトルの配列
188
+ */
189
+ export async function createBatchStyleEmbeddings(styleTexts: string[]): Promise<number[][]> {
190
+ return styleEmbeddingService.generateBatchEmbeddings(styleTexts);
191
+ }
192
+
193
+ /**
194
+ * 検索クエリ用の埋め込みを生成するヘルパー関数
195
+ *
196
+ * @param queryText - 検索クエリテキスト
197
+ * @returns 768次元の埋め込みベクトル
198
+ */
199
+ export async function createQueryEmbedding(queryText: string): Promise<number[]> {
200
+ return styleEmbeddingService.generateQueryEmbedding(queryText);
201
+ }
@@ -0,0 +1,95 @@
1
+ // SPDX-FileCopyrightText: 2026 TKMD and Reftrix Contributors
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ /**
5
+ * Embedding service type definitions
6
+ */
7
+
8
+ /**
9
+ * Type of text for embedding generation
10
+ * - 'query': For search queries (prefixed with "query: ")
11
+ * - 'passage': For document/content (prefixed with "passage: ")
12
+ */
13
+ export type EmbeddingTextType = "query" | "passage";
14
+
15
+ /**
16
+ * Configuration options for EmbeddingService
17
+ */
18
+ export interface EmbeddingServiceConfig {
19
+ /**
20
+ * Model ID from Hugging Face Hub
21
+ * @default "Xenova/multilingual-e5-base"
22
+ */
23
+ modelId?: string;
24
+
25
+ /**
26
+ * Cache directory for downloaded models
27
+ */
28
+ cacheDir?: string;
29
+
30
+ /**
31
+ * Device to run inference on
32
+ * @default "cpu"
33
+ */
34
+ device?: "cpu" | "cuda" | "webgpu";
35
+
36
+ /**
37
+ * Data type for model precision
38
+ * @default "fp32"
39
+ */
40
+ dtype?: "fp32" | "fp16" | "q8" | "q4";
41
+
42
+ /**
43
+ * Maximum number of entries in the embedding cache (LRU eviction)
44
+ * @default 5000
45
+ */
46
+ maxCacheSize?: number;
47
+
48
+ /**
49
+ * Number of inferences before the ONNX pipeline is disposed and recreated.
50
+ *
51
+ * onnxruntime-node uses a native C++ arena allocator that grows monotonically
52
+ * during inference. Pipeline recycling tears down the InferenceSession via
53
+ * session.release(), freeing the arena's native memory back to the OS.
54
+ *
55
+ * Lower values reduce peak memory but increase overhead from model re-initialization.
56
+ * Higher values reduce overhead but allow more native memory accumulation.
57
+ *
58
+ * Set to 0 to disable automatic recycling.
59
+ *
60
+ * @default 30
61
+ */
62
+ pipelineRecycleThreshold?: number;
63
+ }
64
+
65
+ /**
66
+ * Cache statistics interface
67
+ */
68
+ export interface CacheStats {
69
+ hits: number;
70
+ misses: number;
71
+ size: number;
72
+ evictions: number;
73
+ }
74
+
75
+ /**
76
+ * Embedding result with metadata
77
+ */
78
+ export interface EmbeddingResult {
79
+ embedding: number[];
80
+ text: string;
81
+ type: EmbeddingTextType;
82
+ cached: boolean;
83
+ processingTimeMs: number;
84
+ }
85
+
86
+ /**
87
+ * Batch embedding result
88
+ */
89
+ export interface BatchEmbeddingResult {
90
+ embeddings: number[][];
91
+ texts: string[];
92
+ type: EmbeddingTextType;
93
+ totalProcessingTimeMs: number;
94
+ cachedCount: number;
95
+ }
@@ -0,0 +1,273 @@
1
+ // SPDX-FileCopyrightText: 2026 TKMD and Reftrix Contributors
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ /**
5
+ * Vision Embedding Service
6
+ *
7
+ * Vision Embedding生成ロジック
8
+ *
9
+ * 視覚特徴量(VisionFeatures)からテキスト表現を生成し、
10
+ * multilingual-e5-baseモデルで768次元の埋め込みベクトルを生成するサービス
11
+ */
12
+
13
+ import type {
14
+ VisionFeatures,
15
+ VisionEmbeddingServiceConfig,
16
+ VisionCacheStats,
17
+ } from "./vision-embedding.types.js";
18
+ import { EmbeddingService } from "./service.js";
19
+
20
+ /**
21
+ * デフォルトのキャッシュサイズ
22
+ */
23
+ const DEFAULT_MAX_CACHE_SIZE = 5000;
24
+
25
+ /**
26
+ * VisionFeaturesをテキスト表現に変換する
27
+ *
28
+ * @param features - 視覚特徴量
29
+ * @returns テキスト表現
30
+ */
31
+ export function visionFeaturesToText(features: VisionFeatures): string {
32
+ const parts: string[] = [
33
+ `visual_rhythm: ${features.rhythm}`,
34
+ `whitespace_ratio: ${features.whitespaceRatio}`,
35
+ `content_density: ${features.density}`,
36
+ `visual_gravity: ${features.gravity}`,
37
+ `color_theme: ${features.theme}`,
38
+ ];
39
+
40
+ // オプショナルフィールドを追加(存在する場合のみ)
41
+ if (features.mood !== undefined && features.mood !== null) {
42
+ parts.push(`mood: ${features.mood}`);
43
+ }
44
+
45
+ if (features.brandTone !== undefined && features.brandTone !== null) {
46
+ parts.push(`brandTone: ${features.brandTone}`);
47
+ }
48
+
49
+ return parts.join(", ");
50
+ }
51
+
52
+ /**
53
+ * VisionFeaturesからキャッシュキーを生成
54
+ */
55
+ function getCacheKey(features: VisionFeatures): string {
56
+ return JSON.stringify({
57
+ r: features.rhythm,
58
+ w: features.whitespaceRatio,
59
+ d: features.density,
60
+ g: features.gravity,
61
+ t: features.theme,
62
+ m: features.mood,
63
+ b: features.brandTone,
64
+ });
65
+ }
66
+
67
+ /**
68
+ * VisionEmbeddingService
69
+ *
70
+ * 視覚特徴量から768次元の埋め込みベクトルを生成するサービス
71
+ */
72
+ export class VisionEmbeddingService {
73
+ private embeddingService: EmbeddingService;
74
+ private cache: Map<string, number[]> = new Map();
75
+ private cacheHits = 0;
76
+ private cacheMisses = 0;
77
+ private cacheEvictions = 0;
78
+ private maxCacheSize: number;
79
+
80
+ constructor(config: VisionEmbeddingServiceConfig = {}) {
81
+ this.maxCacheSize = config.maxCacheSize ?? DEFAULT_MAX_CACHE_SIZE;
82
+ this.embeddingService = new EmbeddingService();
83
+
84
+ if (process.env.NODE_ENV === "development") {
85
+ // eslint-disable-next-line no-console
86
+ console.log("[VisionEmbedding] Service created with maxCacheSize:", this.maxCacheSize);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * VisionFeaturesから768次元の埋め込みベクトルを生成
92
+ *
93
+ * @param features - 視覚特徴量
94
+ * @returns 768次元のL2正規化されたベクトル
95
+ */
96
+ async generateVisionEmbedding(features: VisionFeatures): Promise<number[]> {
97
+ const cacheKey = getCacheKey(features);
98
+
99
+ // キャッシュチェック(LRU更新付き)
100
+ const cached = this.getCacheEntry(cacheKey);
101
+ if (cached) {
102
+ this.cacheHits++;
103
+ if (process.env.NODE_ENV === "development") {
104
+ // eslint-disable-next-line no-console
105
+ console.log("[VisionEmbedding] Cache hit");
106
+ }
107
+ return cached;
108
+ }
109
+
110
+ this.cacheMisses++;
111
+
112
+ // テキスト表現に変換
113
+ const text = visionFeaturesToText(features);
114
+
115
+ if (process.env.NODE_ENV === "development") {
116
+ // eslint-disable-next-line no-console
117
+ console.log("[VisionEmbedding] Generating embedding for:", text.substring(0, 80));
118
+ }
119
+
120
+ // EmbeddingServiceを使用して埋め込み生成
121
+ // 'passage'プレフィックスを使用(ドキュメント埋め込み)
122
+ const embedding = await this.embeddingService.generateEmbedding(text, "passage");
123
+
124
+ // キャッシュに保存(LRU eviction付き)
125
+ this.setCacheEntry(cacheKey, embedding);
126
+
127
+ return embedding;
128
+ }
129
+
130
+ /**
131
+ * 複数のVisionFeaturesから埋め込みベクトルを一括生成
132
+ *
133
+ * @param featuresArray - 視覚特徴量の配列
134
+ * @returns 768次元ベクトルの配列
135
+ */
136
+ async generateBatchVisionEmbeddings(featuresArray: VisionFeatures[]): Promise<number[][]> {
137
+ if (featuresArray.length === 0) {
138
+ return [];
139
+ }
140
+
141
+ if (process.env.NODE_ENV === "development") {
142
+ // eslint-disable-next-line no-console
143
+ console.log("[VisionEmbedding] Batch processing", featuresArray.length, "items");
144
+ }
145
+
146
+ // キャッシュチェックと未キャッシュアイテムの特定
147
+ const results: (number[] | undefined)[] = new Array(featuresArray.length);
148
+ const uncachedIndices: number[] = [];
149
+ const uncachedTexts: string[] = [];
150
+
151
+ for (let i = 0; i < featuresArray.length; i++) {
152
+ const features = featuresArray[i];
153
+ if (!features) continue;
154
+
155
+ const cacheKey = getCacheKey(features);
156
+ const cached = this.getCacheEntry(cacheKey);
157
+
158
+ if (cached) {
159
+ this.cacheHits++;
160
+ results[i] = cached;
161
+ } else {
162
+ this.cacheMisses++;
163
+ uncachedIndices.push(i);
164
+ uncachedTexts.push(visionFeaturesToText(features));
165
+ }
166
+ }
167
+
168
+ // 未キャッシュアイテムの一括生成
169
+ if (uncachedTexts.length > 0) {
170
+ const embeddings = await this.embeddingService.generateBatchEmbeddings(
171
+ uncachedTexts,
172
+ "passage"
173
+ );
174
+
175
+ for (let j = 0; j < uncachedIndices.length; j++) {
176
+ const originalIndex = uncachedIndices[j];
177
+ const embedding = embeddings[j];
178
+ const features = featuresArray[originalIndex ?? 0];
179
+
180
+ if (originalIndex !== undefined && embedding && features) {
181
+ results[originalIndex] = embedding;
182
+
183
+ // キャッシュに保存
184
+ const cacheKey = getCacheKey(features);
185
+ this.setCacheEntry(cacheKey, embedding);
186
+ }
187
+ }
188
+ }
189
+
190
+ // undefinedを除去して返却
191
+ return results.filter((r): r is number[] => r !== undefined);
192
+ }
193
+
194
+ /**
195
+ * キャッシュ統計を取得
196
+ */
197
+ getCacheStats(): VisionCacheStats {
198
+ return {
199
+ hits: this.cacheHits,
200
+ misses: this.cacheMisses,
201
+ size: this.cache.size,
202
+ evictions: this.cacheEvictions,
203
+ };
204
+ }
205
+
206
+ /**
207
+ * キャッシュをクリア
208
+ */
209
+ clearCache(): void {
210
+ this.cache.clear();
211
+ this.cacheHits = 0;
212
+ this.cacheMisses = 0;
213
+ this.cacheEvictions = 0;
214
+
215
+ if (process.env.NODE_ENV === "development") {
216
+ // eslint-disable-next-line no-console
217
+ console.log("[VisionEmbedding] Cache cleared");
218
+ }
219
+ }
220
+
221
+ /**
222
+ * キャッシュエントリを取得(LRU更新)
223
+ */
224
+ private getCacheEntry(key: string): number[] | undefined {
225
+ const value = this.cache.get(key);
226
+ if (value !== undefined) {
227
+ // LRU: 最近使用したエントリを末尾に移動
228
+ this.cache.delete(key);
229
+ this.cache.set(key, value);
230
+ }
231
+ return value;
232
+ }
233
+
234
+ /**
235
+ * キャッシュエントリを設定(LRU eviction)
236
+ */
237
+ private setCacheEntry(key: string, value: number[]): void {
238
+ // キャッシュがいっぱいの場合、最も古いエントリを削除
239
+ while (this.cache.size >= this.maxCacheSize) {
240
+ const oldestKey = this.cache.keys().next().value;
241
+ if (oldestKey !== undefined) {
242
+ this.cache.delete(oldestKey);
243
+ this.cacheEvictions++;
244
+ if (process.env.NODE_ENV === "development") {
245
+ // eslint-disable-next-line no-console
246
+ console.log("[VisionEmbedding] Cache evicted oldest entry");
247
+ }
248
+ } else {
249
+ break;
250
+ }
251
+ }
252
+ this.cache.set(key, value);
253
+ }
254
+ }
255
+
256
+ // シングルトンインスタンス
257
+ export const visionEmbeddingService = new VisionEmbeddingService();
258
+
259
+ /**
260
+ * 単一のVisionFeaturesから埋め込みを生成するヘルパー関数
261
+ */
262
+ export async function createVisionEmbedding(features: VisionFeatures): Promise<number[]> {
263
+ return visionEmbeddingService.generateVisionEmbedding(features);
264
+ }
265
+
266
+ /**
267
+ * 複数のVisionFeaturesから埋め込みを一括生成するヘルパー関数
268
+ */
269
+ export async function createBatchVisionEmbeddings(
270
+ featuresArray: VisionFeatures[]
271
+ ): Promise<number[][]> {
272
+ return visionEmbeddingService.generateBatchVisionEmbeddings(featuresArray);
273
+ }
@@ -0,0 +1,117 @@
1
+ // SPDX-FileCopyrightText: 2026 TKMD and Reftrix Contributors
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ /**
5
+ * Vision Embedding Types
6
+ *
7
+ * Vision Embedding生成ロジック
8
+ *
9
+ * 視覚特徴量を表す型定義
10
+ */
11
+
12
+ /**
13
+ * 視覚的リズムの種類
14
+ * - regular: 規則的なリズム(均等配置)
15
+ * - varied: 変化のあるリズム(意図的な強弱)
16
+ * - asymmetric: 非対称なリズム(アシンメトリック)
17
+ */
18
+ export type VisionRhythm = "regular" | "varied" | "asymmetric";
19
+
20
+ /**
21
+ * コンテンツ密度の種類
22
+ * - sparse: 疎(ホワイトスペースが多い)
23
+ * - moderate: 中程度
24
+ * - dense: 密集(コンテンツが多い)
25
+ */
26
+ export type VisionDensity = "sparse" | "moderate" | "dense";
27
+
28
+ /**
29
+ * 視覚的重心の位置
30
+ * - top: 上部
31
+ * - center: 中央
32
+ * - bottom: 下部
33
+ * - left: 左側
34
+ * - right: 右側
35
+ */
36
+ export type VisionGravity = "top" | "center" | "bottom" | "left" | "right";
37
+
38
+ /**
39
+ * カラーテーマの種類
40
+ * - light: ライトテーマ
41
+ * - dark: ダークテーマ
42
+ * - mixed: 混合テーマ
43
+ */
44
+ export type VisionTheme = "light" | "dark" | "mixed";
45
+
46
+ /**
47
+ * 視覚特徴量インターフェース
48
+ *
49
+ * Webページのレイアウト/デザインから抽出された視覚的特徴を表す
50
+ */
51
+ export interface VisionFeatures {
52
+ /**
53
+ * 視覚的リズム
54
+ * 要素の配置パターン・繰り返しの規則性
55
+ */
56
+ rhythm: VisionRhythm;
57
+
58
+ /**
59
+ * ホワイトスペース比率 (0-1)
60
+ * 0: ホワイトスペースなし(完全にコンテンツで埋まっている)
61
+ * 1: 完全にホワイトスペース(コンテンツなし)
62
+ */
63
+ whitespaceRatio: number;
64
+
65
+ /**
66
+ * コンテンツ密度
67
+ * ページ上のコンテンツ要素の密集度
68
+ */
69
+ density: VisionDensity;
70
+
71
+ /**
72
+ * 視覚的重心
73
+ * コンテンツの視覚的な重心位置
74
+ */
75
+ gravity: VisionGravity;
76
+
77
+ /**
78
+ * カラーテーマ
79
+ * 全体的な色調
80
+ */
81
+ theme: VisionTheme;
82
+
83
+ /**
84
+ * ムード (オプション)
85
+ * Vision AIから取得したページの雰囲気/印象
86
+ * 例: 'professional', 'playful', 'elegant', 'minimal'
87
+ */
88
+ mood?: string;
89
+
90
+ /**
91
+ * ブランドトーン (オプション)
92
+ * Vision AIから取得したブランドの印象
93
+ * 例: 'corporate', 'startup', 'luxury', 'friendly'
94
+ */
95
+ brandTone?: string;
96
+ }
97
+
98
+ /**
99
+ * VisionEmbeddingServiceの設定オプション
100
+ */
101
+ export interface VisionEmbeddingServiceConfig {
102
+ /**
103
+ * キャッシュの最大サイズ
104
+ * @default 5000
105
+ */
106
+ maxCacheSize?: number;
107
+ }
108
+
109
+ /**
110
+ * キャッシュ統計情報
111
+ */
112
+ export interface VisionCacheStats {
113
+ hits: number;
114
+ misses: number;
115
+ size: number;
116
+ evictions: number;
117
+ }