photo-tools.js 5.19 KB
/**
 * 多模态资产关联 MCP 工具集 — Phase 6
 *
 * 工具:
 *   link_photo_to_person    — 将照片关联到联系人(Asset:photo 节点 + EXIF 提取)
 *   search_photos_by_person — 查询联系人关联的所有照片
 */

const { getGraphStore } = require('../../../db/GraphStore');
const { PhotoService } = require('../../../services/photo/photo-service');

/** 解析 name_or_id */
function resolvePerson(nameOrId) {
    const g = getGraphStore();
    let node = g.getNode(nameOrId);
    if (!node || node._type !== 'Person') {
        const results = g.findByName(nameOrId, 'Person');
        node = results[0] || null;
    }
    return node;
}

function createPhotoTools() {
    return [
        // ── 6.1 关联照片到联系人 ─────────────────────────────────────
        {
            name: 'link_photo_to_person',
            description: '将本地照片文件关联到指定联系人(创建 Asset:photo 节点),自动提取 EXIF 时空信息。',
            parameters: {
                type: 'object',
                required: ['image_path', 'person_name_or_id'],
                properties: {
                    image_path: {
                        type: 'string',
                        description: '照片文件绝对路径',
                    },
                    person_name_or_id: {
                        type: 'string',
                        description: '联系人姓名或节点 id',
                    },
                },
            },

            async execute(params) {
                try {
                    const svc = new PhotoService(params.image_path);
                    const result = await svc.linkPhotoToPerson(
                        params.image_path,
                        params.person_name_or_id
                    );

                    const lines = [
                        `✅ 照片已关联:${params.image_path}`,
                        `联系人:${result.personName}(id: ${result.personId})`,
                        `资产节点 id:${result.assetId}`,
                    ];

                    if (result.exif) {
                        if (result.exif.lat !== undefined) {
                            lines.push(`GPS 坐标:${result.exif.lat.toFixed(6)}, ${result.exif.lng.toFixed(6)}`);
                        }
                        if (result.exif.datetime) {
                            lines.push(`拍摄时间:${result.exif.datetime}`);
                        }
                    } else {
                        lines.push('(未提取到 EXIF 信息)');
                    }

                    return lines.join('\n');
                } catch (err) {
                    return `❌ 照片关联失败:${err.message}`;
                }
            },
        },

        // ── 6.2 查询联系人照片 ───────────────────────────────────────
        {
            name: 'search_photos_by_person',
            description: '查询某联系人关联的所有照片资产。',
            parameters: {
                type: 'object',
                required: ['person_name_or_id'],
                properties: {
                    person_name_or_id: {
                        type: 'string',
                        description: '联系人姓名或节点 id',
                    },
                },
            },

            async execute(params) {
                try {
                    const g = getGraphStore();
                    const person = resolvePerson(params.person_name_or_id);
                    if (!person) {
                        return `❌ 未找到联系人:${params.person_name_or_id}`;
                    }

                    const { outgoing } = g.getEdges(person.id);
                    const photos = [];

                    for (const edge of outgoing) {
                        if (edge.relation !== 'CONTAINS' && edge.relation !== 'ASSOCIATED') continue;
                        const asset = g.getNode(edge.to_id);
                        if (asset && asset._type === 'Asset' && asset.type === 'photo') {
                            photos.push(asset);
                        }
                    }

                    if (photos.length === 0) {
                        return `📷 ${person.name} 暂无关联照片。`;
                    }

                    const lines = [`📷 ${person.name} 的照片(${photos.length} 张):\n`];
                    photos.forEach((p, i) => {
                        const loc = p.lat !== undefined
                            ? `  GPS: ${Number(p.lat).toFixed(4)}, ${Number(p.lng).toFixed(4)}`
                            : '';
                        const time = p.takenAt ? `  拍摄时间: ${p.takenAt}` : '';
                        lines.push(`${i + 1}. ${p.name || p.id}\n   路径: ${p.filePath || '(未知)'}${loc}${time}`);
                    });

                    return lines.join('\n');
                } catch (err) {
                    return `❌ 照片查询失败:${err.message}`;
                }
            },
        },
    ];
}

module.exports = { createPhotoTools };