{
  "name": "Image Processing - DocuSearch",
  "nodes": [
    {
      "parameters": {},
      "id": "workflow-trigger",
      "name": "\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u958b\u59cb",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "filePath": "={{ $json.path }}",
        "options": {}
      },
      "id": "read-image-file",
      "name": "\u753b\u50cf\u30d5\u30a1\u30a4\u30eb\u8aad\u8fbc",
      "type": "n8n-nodes-base.readBinaryFile",
      "typeVersion": 1,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// EXIF\u60c5\u5831\u3092\u62bd\u51fa\u3059\u308b\u30b3\u30fc\u30c9\uff08JPEG/TIFF\u5bfe\u5fdc\uff09\nconst item = $input.first();\nconst binaryData = item.binary?.data;\nconst prevJson = $('\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u958b\u59cb').first().json;\n\nif (!binaryData) {\n  return [{\n    json: {\n      ...prevJson,\n      exif: { error: 'No binary data found', has_gps: false }\n    }\n  }];\n}\n\n// Base64\u30c7\u30fc\u30bf\u3092\u53d6\u5f97\nconst binaryBuffer = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst base64String = binaryBuffer.toString('base64');\nconst fileName = binaryData.fileName || prevJson.name || 'unknown.jpg';\n\n// EXIF\u89e3\u6790\u95a2\u6570\nfunction parseExif(buffer) {\n  const result = {\n    datetime: null,\n    latitude: null,\n    longitude: null,\n    has_gps: false,\n    camera_make: null,\n    camera_model: null\n  };\n\n  try {\n    // JPEG\u30de\u30fc\u30ab\u30fc\u3092\u78ba\u8a8d\n    if (buffer[0] !== 0xFF || buffer[1] !== 0xD8) {\n      return result; // JPEG\u3067\u306f\u306a\u3044\n    }\n\n    let offset = 2;\n    while (offset < buffer.length - 1) {\n      if (buffer[offset] !== 0xFF) {\n        offset++;\n        continue;\n      }\n\n      const marker = buffer[offset + 1];\n      \n      // APP1\u30de\u30fc\u30ab\u30fc\uff08EXIF\uff09\n      if (marker === 0xE1) {\n        const length = (buffer[offset + 2] << 8) | buffer[offset + 3];\n        const exifData = buffer.slice(offset + 4, offset + 2 + length);\n        \n        // 'Exif\\0\\0'\u3092\u78ba\u8a8d\n        if (exifData[0] === 0x45 && exifData[1] === 0x78 && \n            exifData[2] === 0x69 && exifData[3] === 0x66) {\n          parseExifData(exifData.slice(6), result);\n        }\n        break;\n      }\n      \n      // SOS\u30de\u30fc\u30ab\u30fc\uff08\u753b\u50cf\u30c7\u30fc\u30bf\u958b\u59cb\uff09\u306b\u9054\u3057\u305f\u3089\u7d42\u4e86\n      if (marker === 0xDA) break;\n      \n      const segmentLength = (buffer[offset + 2] << 8) | buffer[offset + 3];\n      offset += 2 + segmentLength;\n    }\n  } catch (e) {\n    result.error = e.message;\n  }\n\n  return result;\n}\n\nfunction parseExifData(data, result) {\n  // \u30a8\u30f3\u30c7\u30a3\u30a2\u30f3\u5224\u5b9a\n  const littleEndian = data[0] === 0x49; // 'II'\n  \n  function readUint16(pos) {\n    return littleEndian \n      ? data[pos] | (data[pos + 1] << 8)\n      : (data[pos] << 8) | data[pos + 1];\n  }\n  \n  function readUint32(pos) {\n    return littleEndian\n      ? data[pos] | (data[pos + 1] << 8) | (data[pos + 2] << 16) | (data[pos + 3] << 24)\n      : (data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3];\n  }\n  \n  function readRational(pos) {\n    const num = readUint32(pos);\n    const den = readUint32(pos + 4);\n    return den !== 0 ? num / den : 0;\n  }\n  \n  function readString(pos, len) {\n    let str = '';\n    for (let i = 0; i < len && data[pos + i] !== 0; i++) {\n      str += String.fromCharCode(data[pos + i]);\n    }\n    return str.trim();\n  }\n  \n  function convertGPS(degrees, minutes, seconds, ref) {\n    let decimal = degrees + minutes / 60 + seconds / 3600;\n    if (ref === 'S' || ref === 'W') decimal = -decimal;\n    return decimal;\n  }\n\n  // IFD0\u3092\u89e3\u6790\n  const ifd0Offset = readUint32(4);\n  let gpsIfdOffset = null;\n  let exifIfdOffset = null;\n  \n  function parseIFD(ifdOffset, isGPS = false) {\n    const numEntries = readUint16(ifdOffset);\n    let gpsLatRef = 'N', gpsLonRef = 'E';\n    let gpsLat = null, gpsLon = null;\n    \n    for (let i = 0; i < numEntries; i++) {\n      const entryOffset = ifdOffset + 2 + i * 12;\n      const tag = readUint16(entryOffset);\n      const type = readUint16(entryOffset + 2);\n      const count = readUint32(entryOffset + 4);\n      let valueOffset = entryOffset + 8;\n      \n      // \u5024\u304c4\u30d0\u30a4\u30c8\u3092\u8d85\u3048\u308b\u5834\u5408\u306f\u30aa\u30d5\u30bb\u30c3\u30c8\u3092\u8aad\u3080\n      const typeSize = [0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8][type] || 1;\n      if (count * typeSize > 4) {\n        valueOffset = readUint32(entryOffset + 8);\n      }\n      \n      if (isGPS) {\n        // GPS IFD\n        if (tag === 0x01) gpsLatRef = readString(valueOffset, 1); // GPSLatitudeRef\n        if (tag === 0x03) gpsLonRef = readString(valueOffset, 1); // GPSLongitudeRef\n        if (tag === 0x02) { // GPSLatitude\n          gpsLat = [readRational(valueOffset), readRational(valueOffset + 8), readRational(valueOffset + 16)];\n        }\n        if (tag === 0x04) { // GPSLongitude\n          gpsLon = [readRational(valueOffset), readRational(valueOffset + 8), readRational(valueOffset + 16)];\n        }\n      } else {\n        // IFD0 / ExifIFD\n        if (tag === 0x010F) result.camera_make = readString(valueOffset, count); // Make\n        if (tag === 0x0110) result.camera_model = readString(valueOffset, count); // Model\n        if (tag === 0x0132) result.datetime = readString(valueOffset, count); // DateTime\n        if (tag === 0x9003) result.datetime = readString(valueOffset, count); // DateTimeOriginal (\u512a\u5148)\n        if (tag === 0x8825) gpsIfdOffset = readUint32(valueOffset); // GPSInfoIFDPointer\n        if (tag === 0x8769) exifIfdOffset = readUint32(valueOffset); // ExifIFDPointer\n      }\n    }\n    \n    if (isGPS && gpsLat && gpsLon) {\n      result.latitude = convertGPS(gpsLat[0], gpsLat[1], gpsLat[2], gpsLatRef);\n      result.longitude = convertGPS(gpsLon[0], gpsLon[1], gpsLon[2], gpsLonRef);\n      result.has_gps = true;\n    }\n  }\n  \n  parseIFD(ifd0Offset);\n  if (exifIfdOffset) parseIFD(exifIfdOffset);\n  if (gpsIfdOffset) parseIFD(gpsIfdOffset, true);\n}\n\n// EXIF\u89e3\u6790\u5b9f\u884c\nconst exifResult = parseExif(binaryBuffer);\n\nconst result = {\n  json: {\n    originalPath: prevJson.path,\n    fileName: fileName,\n    base64Data: base64String,\n    mimeType: binaryData.mimeType || 'image/jpeg',\n    fileSize: binaryData.fileSize,\n    exif: exifResult\n  }\n};\n\nreturn [result];"
      },
      "id": "extract-exif",
      "name": "EXIF\u62bd\u51fa",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "has-gps",
              "leftValue": "={{ $json.exif.has_gps }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "check-gps",
      "name": "GPS\u6709\u7121\u30c1\u30a7\u30c3\u30af",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://nominatim.openstreetmap.org/reverse",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "lat",
              "value": "={{ $json.exif.latitude }}"
            },
            {
              "name": "lon",
              "value": "={{ $json.exif.longitude }}"
            },
            {
              "name": "format",
              "value": "json"
            },
            {
              "name": "accept-language",
              "value": "ja"
            },
            {
              "name": "addressdetails",
              "value": "1"
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "DocuSearch_AI/1.0"
            }
          ]
        },
        "options": {
          "timeout": 10000
        }
      },
      "id": "geocoding",
      "name": "\u30b8\u30aa\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        880,
        -100
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\nconst geoData = item.json;\nconst prevData = $('EXIF\u62bd\u51fa').first().json;\n\n// \u4f4f\u6240\u3092\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\nconst address = geoData.address || {};\nconst parts = [];\n\nif (address.country) parts.push(address.country);\nif (address.state || address.province) parts.push(address.state || address.province);\nif (address.city || address.town || address.village) {\n  parts.push(address.city || address.town || address.village);\n}\nif (address.suburb || address.neighbourhood) {\n  parts.push(address.suburb || address.neighbourhood);\n}\n\nconst formattedAddress = parts.join(', ') || geoData.display_name || '\u4f4f\u6240\u4e0d\u660e';\n\nreturn [{\n  json: {\n    ...prevData,\n    location: {\n      formatted: formattedAddress,\n      full: geoData.display_name,\n      raw: address\n    }\n  }\n}];"
      },
      "id": "format-address",
      "name": "\u4f4f\u6240\u30d5\u30a9\u30fc\u30de\u30c3\u30c8",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        -100
      ]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\n\nreturn [{\n  json: {\n    ...item.json,\n    location: {\n      formatted: '\u4f4d\u7f6e\u60c5\u5831\u306a\u3057',\n      full: null,\n      raw: null\n    }\n  }\n}];"
      },
      "id": "no-gps",
      "name": "\u4f4d\u7f6e\u60c5\u5831\u306a\u3057",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        100
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u753b\u50cf\u306eBase64\u30c7\u30fc\u30bf\u304b\u3089Gemini API\u30ea\u30af\u30a8\u30b9\u30c8\u30da\u30a4\u30ed\u30fc\u30c9\u3092\u69cb\u7bc9\n// \u524d\u306e\u30ce\u30fc\u30c9\u304b\u3089\u30c7\u30fc\u30bf\u3092\u53d6\u5f97\uff08\u4f4f\u6240\u30d5\u30a9\u30fc\u30de\u30c3\u30c8 or \u4f4d\u7f6e\u60c5\u5831\u306a\u3057\uff09\nlet prevData;\ntry {\n  prevData = $('\u4f4f\u6240\u30d5\u30a9\u30fc\u30de\u30c3\u30c8').first().json;\n} catch (e) {\n  try {\n    prevData = $('\u4f4d\u7f6e\u60c5\u5831\u306a\u3057').first().json;\n  } catch (e2) {\n    prevData = $('EXIF\u62bd\u51fa').first().json;\n  }\n}\n\nconst base64Data = prevData.base64Data;\nconst mimeType = prevData.mimeType || 'image/jpeg';\n\nif (!base64Data) {\n  throw new Error('No base64Data found for image');\n}\n\n// Gemini API\u30ea\u30af\u30a8\u30b9\u30c8\u30da\u30a4\u30ed\u30fc\u30c9\u3092\u69cb\u7bc9\nconst requestPayload = {\n  contents: [{\n    parts: [\n      {\n        text: `\u3053\u306e\u753b\u50cf\u3092\u8a73\u7d30\u306b\u5206\u6790\u3057\u3001\u691c\u7d22\u7528\u306e\u30e1\u30bf\u30c7\u30fc\u30bf\u3092\u4f5c\u6210\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u4ee5\u4e0b\u306e\u70b9\u3092\u542b\u3081\u3066\u8a18\u8ff0\u3057\u3066\u304f\u3060\u3055\u3044\uff1a\n\n1. \u5199\u3063\u3066\u3044\u308b\u7269\u4f53\uff08\u5177\u4f53\u7684\u306b\uff09\n   - \u5efa\u7269\u304c\u3042\u308b\u5834\u5408\u3001\u305d\u306e\u7a2e\u985e\uff08\u30de\u30f3\u30b7\u30e7\u30f3\u3001\u30d3\u30eb\u3001\u6238\u5efa\u3066\u3001\u30aa\u30d5\u30a3\u30b9\u3001\u5546\u696d\u65bd\u8a2d\u306a\u3069\uff09\n   - \u8eca\u4e21\u304c\u3042\u308b\u5834\u5408\u3001\u7a2e\u985e\uff08\u4e57\u7528\u8eca\u3001\u30c8\u30e9\u30c3\u30af\u3001\u30d0\u30a4\u30af\u306a\u3069\uff09\n   - \u4eba\u7269\u304c\u3044\u308b\u5834\u5408\u3001\u4eba\u6570\u3068\u72b6\u6cc1\uff08\u30d3\u30b8\u30cd\u30b9\u3001\u30ab\u30b8\u30e5\u30a2\u30eb\u3001\u30a4\u30d9\u30f3\u30c8\u306a\u3069\uff09\n\n2. \u30b7\u30c1\u30e5\u30a8\u30fc\u30b7\u30e7\u30f3\u30fb\u5834\u9762\n   - \u98df\u4e8b\u3001\u4f1a\u8b70\u3001\u5de5\u4e8b\u73fe\u5834\u3001\u65c5\u884c\u3001\u30a4\u30d9\u30f3\u30c8\u3001\u65e5\u5e38\u306a\u3069\n   - \u6599\u7406\u304c\u3042\u308b\u5834\u5408\u3001\u30b8\u30e3\u30f3\u30eb\uff08\u30a4\u30bf\u30ea\u30a2\u30f3\u3001\u548c\u98df\u3001\u4e2d\u83ef\u3001\u30ab\u30d5\u30a7\u306a\u3069\uff09\n\n3. \u96f0\u56f2\u6c17\u3084\u8272\u5473\n   - \u660e\u308b\u3044\u3001\u6697\u3044\u3001\u30e2\u30c0\u30f3\u3001\u30ec\u30c8\u30ed\u3001\u30dd\u30c3\u30d7\u3001\u843d\u3061\u7740\u3044\u305f\u96f0\u56f2\u6c17\u306a\u3069\n\n4. \u8aad\u307f\u53d6\u308c\u308b\u6587\u5b57\u60c5\u5831\n   - \u770b\u677f\u3001\u66f8\u7c4d\u3001\u63b2\u793a\u677f\u306a\u3069\u8aad\u3081\u308b\u6587\u5b57\u304c\u3042\u308c\u3070\u8a18\u8ff0\n\n5. \u5834\u6240\u306e\u63a8\u6e2c\n   - \u5c4b\u5185/\u5c4b\u5916\u3001\u90fd\u5e02/\u90ca\u5916/\u81ea\u7136\u306a\u3069\n\n\u51fa\u529b\u306f\u65e5\u672c\u8a9e\u306e\u81ea\u7136\u306a\u6587\u7ae0\u3067\u3001\u691c\u7d22\u30ad\u30fc\u30ef\u30fc\u30c9\u5316\u3057\u3084\u3059\u3044\u5f62\u5f0f\u3067\u304a\u9858\u3044\u3057\u307e\u3059\u3002`\n      },\n      {\n        inline_data: {\n          mime_type: mimeType,\n          data: base64Data\n        }\n      }\n    ]\n  }],\n  generationConfig: {\n    temperature: 0.7,\n    maxOutputTokens: 1024\n  }\n};\n\nreturn [{\n  json: {\n    ...prevData,\n    geminiPayload: requestPayload\n  }\n}];"
      },
      "id": "prepare-gemini-image",
      "name": "Gemini\u30ea\u30af\u30a8\u30b9\u30c8\u6e96\u5099",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "key",
              "value": "={{ $env.GEMINI_API_KEY }}"
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.geminiPayload) }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "gemini-vision",
      "name": "Gemini Vision API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        1540,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\nconst geminiResponse = item.json;\n\n// \u524d\u306e\u30ce\u30fc\u30c9\u304b\u3089\u30c7\u30fc\u30bf\u3092\u53d6\u5f97\uff08\u4f4f\u6240\u30d5\u30a9\u30fc\u30de\u30c3\u30c8 or \u4f4d\u7f6e\u60c5\u5831\u306a\u3057\uff09\nlet prevData;\ntry {\n  prevData = $('\u4f4f\u6240\u30d5\u30a9\u30fc\u30de\u30c3\u30c8').first().json;\n} catch (e) {\n  try {\n    prevData = $('\u4f4d\u7f6e\u60c5\u5831\u306a\u3057').first().json;\n  } catch (e2) {\n    prevData = $('EXIF\u62bd\u51fa').first().json;\n  }\n}\n\n// Gemini\u30ec\u30b9\u30dd\u30f3\u30b9\u304b\u3089\u30ad\u30e3\u30d7\u30b7\u30e7\u30f3\u3092\u62bd\u51fa\nlet caption = '';\ntry {\n  caption = geminiResponse.candidates[0].content.parts[0].text || '';\n} catch (e) {\n  caption = '\u30ad\u30e3\u30d7\u30b7\u30e7\u30f3\u751f\u6210\u30a8\u30e9\u30fc';\n}\n\n// Dify\u7528\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30c6\u30ad\u30b9\u30c8\u3092\u69cb\u7bc9\nconst parts = [];\nparts.push(`\u25a0\u30d5\u30a1\u30a4\u30eb\u540d: ${prevData.fileName}`);\n\nif (prevData.exif && prevData.exif.datetime) {\n  parts.push(`\u25a0\u64ae\u5f71\u65e5\u6642: ${prevData.exif.datetime}`);\n}\n\nif (prevData.location && prevData.location.formatted) {\n  parts.push(`\u25a0\u64ae\u5f71\u5834\u6240: ${prevData.location.formatted}`);\n}\n\nif (prevData.exif && prevData.exif.latitude && prevData.exif.longitude) {\n  parts.push(`\u25a0\u5ea7\u6a19: \u7def\u5ea6${prevData.exif.latitude}, \u7d4c\u5ea6${prevData.exif.longitude}`);\n}\n\nif (prevData.exif && prevData.exif.camera_model) {\n  const camera = prevData.exif.camera_make \n    ? `${prevData.exif.camera_make} ${prevData.exif.camera_model}`\n    : prevData.exif.camera_model;\n  parts.push(`\u25a0\u30ab\u30e1\u30e9: ${camera}`);\n}\n\nparts.push('\u25a0\u753b\u50cf\u5185\u5bb9\u306e\u8aac\u660e:');\nparts.push(caption);\n\nconst documentText = parts.join('\\n');\n\nreturn [{\n  json: {\n    ...prevData,\n    visionCaption: caption,\n    documentText: documentText\n  }\n}];"
      },
      "id": "build-document",
      "name": "\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u69cb\u7bc9",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1760,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://dify-api:5001/v1/datasets/b6e67602-dbd0-4777-bf44-de9ab18a8a11/document/create-by-text",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"name\": \"{{ $json.originalPath.replace('/watch/', '') }}\",\n  \"text\": {{ JSON.stringify($json.documentText) }},\n  \"indexing_technique\": \"high_quality\",\n  \"process_rule\": {\n    \"mode\": \"automatic\"\n  }\n}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "dify-index",
      "name": "Dify\u767b\u9332",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        1980,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000,
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\nconst prevData = $('\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u69cb\u7bc9').first().json;\nconst difyResponse = item.json;\n\nreturn [{\n  json: {\n    success: true,\n    fileName: prevData.fileName,\n    originalPath: prevData.originalPath,\n    documentId: difyResponse.document?.id || null,\n    processedAt: new Date().toISOString()\n  }\n}];"
      },
      "id": "result",
      "name": "\u7d50\u679c\u51fa\u529b",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2200,
        0
      ]
    }
  ],
  "connections": {
    "\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u958b\u59cb": {
      "main": [
        [
          {
            "node": "\u753b\u50cf\u30d5\u30a1\u30a4\u30eb\u8aad\u8fbc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u753b\u50cf\u30d5\u30a1\u30a4\u30eb\u8aad\u8fbc": {
      "main": [
        [
          {
            "node": "EXIF\u62bd\u51fa",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "EXIF\u62bd\u51fa": {
      "main": [
        [
          {
            "node": "GPS\u6709\u7121\u30c1\u30a7\u30c3\u30af",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPS\u6709\u7121\u30c1\u30a7\u30c3\u30af": {
      "main": [
        [
          {
            "node": "\u30b8\u30aa\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\u4f4d\u7f6e\u60c5\u5831\u306a\u3057",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u30b8\u30aa\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0": {
      "main": [
        [
          {
            "node": "\u4f4f\u6240\u30d5\u30a9\u30fc\u30de\u30c3\u30c8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u4f4f\u6240\u30d5\u30a9\u30fc\u30de\u30c3\u30c8": {
      "main": [
        [
          {
            "node": "Gemini\u30ea\u30af\u30a8\u30b9\u30c8\u6e96\u5099",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u4f4d\u7f6e\u60c5\u5831\u306a\u3057": {
      "main": [
        [
          {
            "node": "Gemini\u30ea\u30af\u30a8\u30b9\u30c8\u6e96\u5099",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini\u30ea\u30af\u30a8\u30b9\u30c8\u6e96\u5099": {
      "main": [
        [
          {
            "node": "Gemini Vision API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Vision API": {
      "main": [
        [
          {
            "node": "\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u69cb\u7bc9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u69cb\u7bc9": {
      "main": [
        [
          {
            "node": "Dify\u767b\u9332",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dify\u767b\u9332": {
      "main": [
        [
          {
            "node": "\u7d50\u679c\u51fa\u529b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [
    "docusearch",
    "image-processing"
  ]
}