Skip to main content
Glama
image-processor.js6.27 kB
/** * 圖像處理工具 * 提供可選的圖像記憶功能 */ import crypto from 'crypto'; /** * 生成圖像內容的哈希值(用於去重) * @param {Buffer|string} imageData - 圖像數據或 base64 字符串 * @returns {string} SHA256 哈希值 */ export function generateImageHash(imageData) { let buffer; if (typeof imageData === 'string') { // Base64 字符串 const base64Data = imageData.replace(/^data:image\/\w+;base64,/, ''); buffer = Buffer.from(base64Data, 'base64'); } else if (Buffer.isBuffer(imageData)) { buffer = imageData; } else { throw new Error('Invalid image data: must be Buffer or base64 string'); } return crypto.createHash('sha256').update(buffer).digest('hex'); } /** * 創建圖像 modality 對象 * @param {Object} options - 圖像選項 * @param {string} options.uri - 圖像 URI(URL 或 data URI) * @param {Array<number>} [options.embedding] - 圖像特徵向量(可選) * @param {Array<string>} [options.tags] - 圖像標籤(可選) * @param {string} [options.description] - 圖像描述(可選) * @param {Object} [options.metadata] - 額外元數據(可選) * @returns {Object} 標準化的圖像 modality */ export function createImageModality(options) { const { uri, embedding, tags, description, metadata = {} } = options; if (!uri || typeof uri !== 'string') { throw new Error('Image URI is required'); } const modality = { type: 'image', uri: uri.trim() }; // 添加特徵(如果提供) if (embedding || tags || description) { modality.features = {}; if (Array.isArray(embedding) && embedding.length > 0) { if (!embedding.every(v => Number.isFinite(v))) { throw new Error('Embedding must contain only finite numbers'); } modality.features.embedding = embedding; } if (Array.isArray(tags) && tags.length > 0) { modality.features.tags = tags.filter(t => typeof t === 'string' && t.trim()).map(t => t.trim()); } if (typeof description === 'string' && description.trim()) { modality.features.description = description.trim(); } } // 添加元數據(如果提供) if (Object.keys(metadata).length > 0) { modality.metadata = { ...metadata, processedAt: new Date().toISOString() }; } // 如果是 base64 圖像,生成哈希用於去重 if (uri.startsWith('data:image/')) { try { const hash = generateImageHash(uri); if (!modality.metadata) { modality.metadata = {}; } modality.metadata.contentHash = hash; } catch (error) { console.warn('Failed to generate image hash:', error.message); } } return modality; } /** * 從圖像 URL 創建簡單的 modality * @param {string} imageUrl - 圖像 URL * @param {Array<string>} [tags] - 可選標籤 * @returns {Object} 圖像 modality */ export function createImageModalityFromUrl(imageUrl, tags = []) { return createImageModality({ uri: imageUrl, tags }); } /** * 驗證圖像 modality 的完整性 * @param {Object} modality - modality 對象 * @returns {{valid: boolean, errors: string[]}} */ export function validateImageModality(modality) { const errors = []; if (!modality || typeof modality !== 'object') { errors.push('Modality must be an object'); return { valid: false, errors }; } if (modality.type !== 'image') { errors.push('Modality type must be "image"'); } if (!modality.uri || typeof modality.uri !== 'string') { errors.push('Modality must have a valid URI'); } if (modality.features) { if (typeof modality.features !== 'object') { errors.push('Features must be an object'); } else { if (modality.features.embedding) { if (!Array.isArray(modality.features.embedding)) { errors.push('Embedding must be an array'); } else if (!modality.features.embedding.every(v => Number.isFinite(v))) { errors.push('Embedding must contain only finite numbers'); } } if (modality.features.tags) { if (!Array.isArray(modality.features.tags)) { errors.push('Tags must be an array'); } } } } return { valid: errors.length === 0, errors }; } /** * 比較兩個圖像是否可能重複(基於哈希) * @param {Object} modality1 - 第一個 modality * @param {Object} modality2 - 第二個 modality * @returns {boolean} 是否可能是重複圖像 */ export function areImagesDuplicate(modality1, modality2) { if (!modality1 || !modality2) { return false; } // 檢查 URI 是否完全相同 if (modality1.uri === modality2.uri) { return true; } // 檢查內容哈希 const hash1 = modality1.metadata?.contentHash; const hash2 = modality2.metadata?.contentHash; if (hash1 && hash2 && hash1 === hash2) { return true; } return false; } /** * 從 modalities 列表中移除重複的圖像 * @param {Array<Object>} modalities - modalities 列表 * @returns {Array<Object>} 去重後的列表 */ export function deduplicateImageModalities(modalities) { if (!Array.isArray(modalities)) { return []; } const imageModalities = modalities.filter(m => m.type === 'image'); const otherModalities = modalities.filter(m => m.type !== 'image'); const uniqueImages = []; const seenHashes = new Set(); const seenUris = new Set(); for (const modality of imageModalities) { const hash = modality.metadata?.contentHash; const uri = modality.uri; // 檢查哈希去重 if (hash && seenHashes.has(hash)) { continue; } // 檢查 URI 去重 if (seenUris.has(uri)) { continue; } uniqueImages.push(modality); if (hash) { seenHashes.add(hash); } seenUris.add(uri); } return [...uniqueImages, ...otherModalities]; } export default { generateImageHash, createImageModality, createImageModalityFromUrl, validateImageModality, areImagesDuplicate, deduplicateImageModalities };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/win10ogod/memory-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server