короче помоги теперь надо загружать оригинальные фотки тоже 
    private IEnumerator HandleBatchCompletion(BatchInfo batch)
    {
        int batchNum = batch.BatchNum;

        bool statusSuccess = false;
        Dictionary<string, object> statusInfo = null;

        // Получаем информацию о статусе Batch Job
        yield return openAIClient.CheckBatchJobStatus(batch.JobId, info =>
        {
            statusInfo = info;
            statusSuccess = true;
        }, error => { Debug.LogError($"Ошибка получения информации о Batch Job {batchNum}: {error}"); });

        if (!statusSuccess)
            yield break;

        // Проверяем наличие 'output_file_id' или 'error_file_id'
        string resultFileId = null;
        bool isErrorFile = false;

        if (statusInfo.TryGetValue("output_file_id", out object outputFileIdObj) && outputFileIdObj != null)
        {
            resultFileId = outputFileIdObj.ToString();
            Debug.Log($"Batch Job {batchNum}: найден 'output_file_id' = {resultFileId}");
        }
        else if (statusInfo.TryGetValue("error_file_id", out object errorFileIdObj) && errorFileIdObj != null)
        {
            resultFileId = errorFileIdObj.ToString();
            isErrorFile = true;
            Debug.LogWarning($"Batch Job {batchNum}: найден 'error_file_id' = {resultFileId}");
        }
        else
        {
            Debug.LogError(
                $"Batch Job {batchNum}: ни 'output_file_id', ни 'error_file_id' не найдены в информации о статусе задачи.");
            yield break;
        }

        // Скачиваем файл результатов или ошибок
        byte[] fileContent = null;
        bool downloadSuccess = false;
        yield return openAIClient.DownloadFile(resultFileId, data =>
        {
            fileContent = data;
            downloadSuccess = true;
        }, error => { Debug.LogError($"Ошибка загрузки файла для Batch Job {batchNum}: {error}"); });

        if (!downloadSuccess)
            yield break;

        if (isErrorFile)
        {
            // Обрабатываем файл с ошибками
            string errorText = System.Text.Encoding.UTF8.GetString(fileContent);
            Debug.LogError($"Содержимое файла с ошибкой для Batch Job {batchNum}:\n{errorText}");

            // Сохраняем файл с ошибкой (опционально)
            string errorFilePath = Path.Combine(Application.persistentDataPath, $"batch_{batchNum}_error.log");
            File.WriteAllText(errorFilePath, errorText);
            Debug.Log($"Файл с ошибкой сохранён по пути: {errorFilePath}");
        }
        else
        {
            // Обработка результатов
            var results = resultParser.ParseResults(fileContent);

            // **Загрузка изображений на Selectel и получение ссылок**
            // Получаем экземпляр SelectelStorageManager
            SelectelStorageManager storageManager = FindObjectOfType<SelectelStorageManager>();
            if (storageManager == null)
            {
                Debug.LogError("SelectelStorageManager не найден.");
                yield break;
            }

            // Создаём словарь для хранения ссылок на изображения
            Dictionary<string, string> imageLinks = new Dictionary<string, string>();

            for (int i = 0; i < batch.FilePaths.Count; i++)
            {
                string filePath = batch.FilePaths[i];
                string fileName = Path.GetFileName(filePath);

                // Загружаем файл и получаем ссылку
                string fileUrl = null;
                yield return StartCoroutine(storageManager.UploadFileAndGetUrl(filePath, url =>
                {
                    fileUrl = url;
                    Debug.Log($"Файл '{fileName}' загружен на Selectel. URL: {fileUrl}");
                }, error => { Debug.LogError($"Ошибка загрузки файла '{fileName}' на Selectel: {error}"); }));

                if (!string.IsNullOrEmpty(fileUrl))
                {
                    imageLinks[fileName] = fileUrl;
                }
                else
                {
                    imageLinks[fileName] = "Ошибка загрузки";
                }
            }

            // Записываем результаты в CSV и Excel, передавая ссылки на изображения
            List<string> batchFilenames = new List<string>();
            foreach (string path in batch.FilePaths)
            {
                batchFilenames.Add(Path.GetFileName(path));
            }

            resultParser.WriteResultsToCsv(
                results,
                "results.csv",
                batchFilenames,
                batch.StartingIndex,
                stateManager.Totals,
                batch,
                imageLinks
            );

            resultParser.WriteResultsToExcel(
                results,
                "results.xlsx",
                batchFilenames,
                batch.StartingIndex,
                stateManager.Totals,
                batch,
                imageLinks
            );

            // Обновляем UI
            mainUI.UpdateBatchInfoUI(batch);

            Debug.Log("Данные успешно загружены и результаты обновлены.");
        }
    } 
    /// <summary>
    /// Загрузка файла и получение публичного URL
    /// </summary>
    /// <param name="filePath">Путь к файлу на устройстве</param>
    /// <param name="onSuccess">Действие при успешной загрузке (получение публичного URL)</param>
    /// <param name="onError">Действие при ошибке загрузки</param>
    public IEnumerator UploadFileAndGetUrl(string filePath, System.Action<string> onSuccess, System.Action<string> onError)
    {
        // Проверка аутентификации
        if (string.IsNullOrEmpty(keystoneToken) || string.IsNullOrEmpty(projectId))
        {
            onError?.Invoke("Необходима аутентификация. Вызовите Initialize() перед загрузкой файлов.");
            yield break;
        }

        // Проверка наличия файла
        if (!File.Exists(filePath))
        {
            onError?.Invoke("Файл не найден.");
            yield break;
        }

        // Чтение файла
        byte[] fileData = File.ReadAllBytes(filePath);
        string fileName = Path.GetFileName(filePath);

        // Формирование URL для загрузки в контейнер
        string storageUrl = $"https://swift.{pool}.storage.selcloud.ru/v1/{projectId}/{containerName}/{fileName}";

        // Создание запроса PUT для загрузки файла
        UnityWebRequest www = new UnityWebRequest(storageUrl, "PUT");
        www.uploadHandler = new UploadHandlerRaw(fileData);
        www.downloadHandler = new DownloadHandlerBuffer();
        www.SetRequestHeader("X-Auth-Token", keystoneToken);
        www.SetRequestHeader("Content-Type", "application/octet-stream");

        // Отправка запроса
        yield return www.SendWebRequest();

        // Проверка результата загрузки
        if (www.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError($"Ошибка загрузки файла: {www.error}");
            onError?.Invoke($"Ошибка загрузки файла: {www.error}");
        }
        else
        {
            // Формирование публичного URL без имени контейнера
            string publicUrl = $"{publicDomain}/{fileName}";

            onSuccess?.Invoke(publicUrl);
        }
    } вот батч какой {
  "BatchNum": 1,
  "FilePaths": [
    "K:/GitHub/ImageRecognition/Assets/StreamingAssets\\photos_prepared\\id-0.jpg",
    "K:/GitHub/ImageRecognition/Assets/StreamingAssets\\photos_prepared\\id-1.jpg",
    "K:/GitHub/ImageRecognition/Assets/StreamingAssets\\photos_prepared\\id-2.jpg",
    "K:/GitHub/ImageRecognition/Assets/StreamingAssets\\photos_prepared\\id-3.jpg",
    "K:/GitHub/ImageRecognition/Assets/StreamingAssets\\photos_prepared\\id-4.jpg",
    "K:/GitHub/ImageRecognition/Assets/StreamingAssets\\photos_prepared\\id-5.jpg",
    "K:/GitHub/ImageRecognition/Assets/StreamingAssets\\photos_prepared\\id-6.jpg",
    "K:/GitHub/ImageRecognition/Assets/StreamingAssets\\photos_prepared\\id-7.jpg"
  ],
  "TasksFilePath": "data\\batch_tasks_1.jsonl",
  "Status": "completed",
  "JobId": "batch_67399c0145608190971b8961ac1a3aa9",
  "ResultFileId": null,
  "StartingIndex": 0,
  "UploadedFileId": "file-xHXHe9wNN2dX83xvywO2R9Tq",
  "Region": "МО",
  "City": "Москва",
  "Cemetery": "Иваново",
  "CreationDate": "2024-11-17T14:32:04.9598132+07:00",
  "ImageMetadatas": [
    {
      "Filename": "id-0.jpg",
      "Latitude": 53.176768412864185,
      "Longitude": 158.38135728639233
    },
    {
      "Filename": "id-1.jpg",
      "Latitude": 53.176764892511819,
      "Longitude": 158.38100231274217
    },
    {
      "Filename": "id-2.jpg",
      "Latitude": 53.177028000420044,
      "Longitude": 158.38089737138256
    },
    {
      "Filename": "id-3.jpg",
      "Latitude": 53.177377525890961,
      "Longitude": 158.38062160660553
    },
    {
      "Filename": "id-4.jpg",
      "Latitude": 53.177524795787193,
      "Longitude": 158.37979632446581
    },
    {
      "Filename": "id-5.jpg",
      "Latitude": 53.177954703675105,
      "Longitude": 158.37884824734905
    },
    {
      "Filename": "id-6.jpg",
      "Latitude": 53.176376475078982,
      "Longitude": 158.38119040250027
    },
    {
      "Filename": "id-7.jpg",
      "Latitude": 53.176787439778067,
      "Longitude": 158.38133842716553
    }
  ]
} типо оригиналы находятся сам найдешь using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.Serialization;
using Zenject;

public class MainController : MonoBehaviour
{
    [Inject] private StateManager stateManager;
    [Inject] private ConfigurationManager configManager;
    [Inject] private OpenAIClient openAIClient;
    [Inject] private MainUI mainUI;
    [Inject] private ProgressUI progressUI;

    [Inject] private ImageProcessor imageProcessor;
    [Inject] private BatchTaskCreator batchTaskCreator;
    [Inject] private ResultParser resultParser;

    private string originalDir;
    private string preparedDir;
    private List<string> validImageFiles;
    private List<string> preparedImageFiles;
    private List<List<string>> batches;
    private List<ImageMetadata> imageMetadatas;

    private void Awake()
    {
        InitializeComponents();
    }

    private void Start()
    {
        DisplayExistingBatches();
    }

    private void InitializeComponents()
    {
        originalDir = Path.Combine(Application.streamingAssetsPath, "photos");
        preparedDir = Path.Combine(Application.streamingAssetsPath, "photos_prepared");
        Directory.CreateDirectory(preparedDir);
    }

    private void DisplayExistingBatches()
    {
        if (stateManager.Batches != null && stateManager.Batches.Count > 0)
        {
            foreach (var batch in stateManager.Batches)
            {
                mainUI.CreateBatchInfoUI(batch);
            }
        }
    }

    public void OnBatchCreationConfirmed(string region, string city, string cemetery)
    {
        StartCoroutine(MainProcess(region, city, cemetery));
    }

    private IEnumerator MainProcess(string region, string city, string cemetery)
    {
        progressUI.ShowUI(true);

        UpdateProgress("Подготовка изображений...");

        yield return StartCoroutine(PrepareImages());

        if (preparedImageFiles == null || preparedImageFiles.Count == 0)
        {
            progressUI.ShowUI(false);
            yield break;
        }

        UpdateProgress("Разделение на батчи...");
        CreateBatches(region, city, cemetery);

        foreach (var batch in newBatches)
        {
            mainUI.CreateBatchInfoUI(batch);
        }

        progressUI.ShowUI(false);
    }

    private IEnumerator PrepareImages()
    {
        if (!Directory.Exists(originalDir))
        {
            Debug.LogError($"Папка с изображениями не существует: {originalDir}");
            yield break;
        }

        var imageFiles = Directory.GetFiles(originalDir, "*.*", SearchOption.TopDirectoryOnly).ToList();
        validImageFiles = new List<string>();

        // Фильтруем файлы изображений
        imageFiles = imageFiles.Where(file =>
                file.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ||
                file.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) ||
                file.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase))
            .ToList();

        if (imageFiles.Count == 0)
        {
            Debug.Log("Нет файлов для обработки.");
            yield break;
        }

        preparedImageFiles = new List<string>();
        imageMetadatas = new List<ImageMetadata>();

        Debug.Log("Переименование и подготовка изображений...");

        int totalImages = imageFiles.Count;
        int startingIndex = stateManager.Batches.Count > 0
            ? stateManager.Batches[stateManager.Batches.Count - 1].StartingIndex +
              stateManager.Batches[stateManager.Batches.Count - 1].FilePaths.Count
            : 0;

        for (int i = 0; i < totalImages; i++)
        {
            string oldFilePath = imageFiles[i];
            string extension = Path.GetExtension(oldFilePath);
            int imageIndex = startingIndex + i;
            string imageId = $"id-{imageIndex}-origin";
            string newFilename = $"{imageId}{extension}";
            string newFilePath = Path.Combine(originalDir, newFilename);

            // Переименовываем файл в папке photos
            try
            {
                File.Move(oldFilePath, newFilePath);
            }
            catch (Exception e)
            {
                Debug.LogError($"Ошибка при переименовании файла {oldFilePath}: {e.Message}");
                continue;
            }

            string preparedImageId = $"id-{imageIndex}";
            string preparedFilename = $"{preparedImageId}{extension}";
            string preparedPath = Path.Combine(preparedDir, preparedFilename);
            
            // Извлечение метаданных
            var metadata = imageProcessor.ExtractMetadata(newFilePath);
            metadata.Filename = preparedFilename; // Сохраняем новое имя файла в метаданных
            imageMetadatas.Add(metadata);

            // Сжатие изображения и удаление метаданных
            imageProcessor.CompressAndRemoveMetadata(newFilePath, preparedPath);

            preparedImageFiles.Add(preparedPath);

            UpdateProgress($"Подготовка изображений: {i + 1}/{totalImages}");

            if (i % 10 == 0)
            {
                yield return null;
            }

            if (i % 50 == 0)
            {
                yield return null;
                GC.Collect();
                yield return Resources.UnloadUnusedAssets();
            }
        }

        GC.Collect();
        yield return Resources.UnloadUnusedAssets();
    }

    private void UpdateProgress(string message)
    {
        progressUI.UpdateProgress(message);
    }

    private List<BatchInfo> newBatches;

    private void CreateBatches(string region, string city, string cemetery)
    {
        try
        {
            var preparedFiles = new List<string>(preparedImageFiles);

            Debug.Log("Разделение файлов на батчи...");
            batches = GetBatches(preparedFiles, 100);

            newBatches = new List<BatchInfo>();
            int startingIndex = stateManager.Batches.Count > 0
                ? stateManager.Batches[stateManager.Batches.Count - 1].StartingIndex +
                  stateManager.Batches[stateManager.Batches.Count - 1].FilePaths.Count
                : 0;
            int imageMetadataIndex = 0;

            for (int i = 0; i < batches.Count; i++)
            {
                int batchNum = stateManager.GetNextBatchNumber();
                var batchFiles = batches[i];

                // Получаем соответствующие метаданные для текущего батча
                var batchImageMetadatas = imageMetadatas.GetRange(imageMetadataIndex, batchFiles.Count);
                imageMetadataIndex += batchFiles.Count;

                var batchInfo = new BatchInfo
                {
                    BatchNum = batchNum,
                    FilePaths = batchFiles,
                    TasksFilePath = Path.Combine("data", $"batch_tasks_{batchNum}.jsonl"),
                    Status = "pending",
                    JobId = null,
                    ResultFileId = null,
                    StartingIndex = startingIndex,
                    Region = region,
                    City = city,
                    Cemetery = cemetery,
                    CreationDate = DateTime.Now,
                    ImageMetadatas = batchImageMetadatas
                };

                stateManager.Batches.Add(batchInfo);
                stateManager.SaveBatch(batchInfo);

                newBatches.Add(batchInfo);
                startingIndex += batchFiles.Count;
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"Ошибка при создании батчей: {ex.Message}");
        }
    }

    private List<List<string>> GetBatches(List<string> filePaths, int maxSizeMb)
    {
        var batches = new List<List<string>>();
        var currentBatch = new List<string>();
        float currentSize = 0f;

        foreach (string file in filePaths)
        {
            float sizeMb = new FileInfo(file).Length / (1024f * 1024f);
            if (currentSize + sizeMb > maxSizeMb)
            {
                if (currentBatch.Count > 0)
                {
                    batches.Add(new List<string>(currentBatch));
                }

                currentBatch.Clear();
                currentBatch.Add(file);
                currentSize = sizeMb;
            }
            else
            {
                currentBatch.Add(file);
                currentSize += sizeMb;
            }
        }

        if (currentBatch.Count > 0)
        {
            batches.Add(currentBatch);
        }

        return batches;
    }

    public void StartBatch(int batchNum)
    {
        StartCoroutine(ProcessPendingBatch(batchNum));
    }

    private IEnumerator ProcessPendingBatch(int batchNum)
    {
        var batch = stateManager.GetBatchInfo(batchNum);
        if (batch == null)
        {
            Debug.LogError($"Батч {batchNum} не найден.");
            yield break;
        }

        // Отображаем окно прогресса
        progressUI.ShowUI(true);

        var batchFiles = batch.FilePaths;
        int startingIndex = batch.StartingIndex;

        Debug.Log($"Обработка батча {batchNum} с {batchFiles.Count} файлами...");

        // Обновляем прогресс
        UpdateProgress($"Создание задач для батча {batchNum}...");

        // Создаем задачи
        var batchFilenames = new List<string>();
        foreach (string path in batchFiles)
        {
            batchFilenames.Add(Path.GetFileName(path));
        }

        var tasks = batchTaskCreator.CreateBatchTasks(
            batchFilenames,
            preparedDir,
            configManager.PromptTemplate,
            configManager.Detail,
            startingIndex
        );

        // Обновляем прогресс
        UpdateProgress($"Запись задач в файл для батча {batchNum}...");

        // Записываем задачи в JSONL
        string tasksFilePath = Path.Combine("data", $"batch_tasks_{batchNum}.jsonl");
        batchTaskCreator.WriteTasksToJsonl(tasks, tasksFilePath);

        // Обновляем информацию о батче в StateManager
        stateManager.UpdateBatch(batchNum, batchInfo =>
        {
            batchInfo.TasksFilePath = tasksFilePath;
            batchInfo.Status = "pending";
        });

        // Обновляем прогресс
        UpdateProgress($"Загрузка файла задач на OpenAI для батча {batchNum}...");

        // Загружаем файл на OpenAI
        string fileId = null;
        bool uploadSuccess = false;
        yield return openAIClient.UploadFile(
            tasksFilePath,
            id =>
            {
                fileId = id;
                uploadSuccess = true;
                Debug.Log($"Файл задач батча {batchNum} загружен. ID файла: {fileId}");
                stateManager.UpdateBatch(batchNum, b => b.UploadedFileId = fileId);
            },
            error =>
            {
                Debug.LogError($"Ошибка загрузки файла задач батча {batchNum}: {error}");
                stateManager.UpdateBatch(batchNum, b => b.Status = "failed");
            }
        );

        if (!uploadSuccess)
        {
            // Скрываем окно прогресса
            progressUI.ShowUI(false);
            yield break;
        }

        // Обновляем прогресс
        UpdateProgress($"Создание Batch Job для батча {batchNum}...");

        // Создаем Batch Job
        string jobId = null;
        bool jobSuccess = false;
        yield return openAIClient.CreateBatchJob(
            fileId,
            id =>
            {
                jobId = id;
                jobSuccess = true;
                Debug.Log($"Batch Job для батча {batchNum} создан. ID задачи: {jobId}");
                stateManager.UpdateBatch(batchNum, b =>
                {
                    b.Status = "in_progress";
                    b.JobId = jobId;
                });
            },
            error =>
            {
                Debug.LogError($"Ошибка создания Batch Job для батча {batchNum}: {error}");
                stateManager.UpdateBatch(batchNum, b => b.Status = "failed");
            }
        );

        if (!jobSuccess)
        {
            // Скрываем окно прогресса
            progressUI.ShowUI(false);
            yield break;
        }

        // Обновляем прогресс
        UpdateProgress($"Batch Job для батча {batchNum} успешно создан.");

        // Обновляем UI
        mainUI.UpdateBatchInfoUI(stateManager.GetBatchInfo(batchNum));

        // Скрываем окно прогресса
        progressUI.ShowUI(false);
    }

    /// <summary>
    /// Проверяет статус батча.
    /// </summary>
    /// <param name="batchNum">Номер батча.</param>
    public void UpdateBatchStatus(int batchNum)
    {
        var batch = stateManager.GetBatchInfo(batchNum);
        if (batch != null && batch.Status == "in_progress")
        {
            StartCoroutine(CheckBatchStatus(batch));
        }
    }

    private IEnumerator CheckBatchStatus(BatchInfo batch)
    {
        int batchNum = batch.BatchNum;
        string jobId = batch.JobId;

        bool statusSuccess = false;
        Dictionary<string, object> statusInfo = null;
        yield return openAIClient.CheckBatchJobStatus(jobId, info =>
        {
            statusInfo = info;
            statusSuccess = true;
        }, error => { Debug.LogError($"Ошибка проверки статуса Batch Job {batchNum}: {error}"); });

        if (!statusSuccess)
            yield break;

        // Проверяем статус задачи
        string status = statusInfo.GetValueOrDefault("status")?.ToString();

        if (status == "completed")
        {
            Debug.Log($"Статус Batch Job {batchNum}: {status}");
            stateManager.UpdateBatch(batchNum, b => b.Status = "completed");
            // Вызываем обработку завершенного батча
            yield return HandleBatchCompletion(batch);
        }
        else
        {
            Debug.Log($"Batch Job {batchNum} ещё не завершён. Текущий статус: {status}");
        }

        // Обновляем UI
        mainUI.UpdateBatchInfoUI(batch);
    }

    public void DownloadBatchResults(int batchNum)
    {
        var batch = stateManager.GetBatchInfo(batchNum);
        if (batch != null && batch.Status == "completed")
        {
            StartCoroutine(HandleBatchCompletion(batch));
        }
    }

    private IEnumerator HandleBatchCompletion(BatchInfo batch)
    {
        int batchNum = batch.BatchNum;

        bool statusSuccess = false;
        Dictionary<string, object> statusInfo = null;

        // Получаем информацию о статусе Batch Job
        yield return openAIClient.CheckBatchJobStatus(batch.JobId, info =>
        {
            statusInfo = info;
            statusSuccess = true;
        }, error => { Debug.LogError($"Ошибка получения информации о Batch Job {batchNum}: {error}"); });

        if (!statusSuccess)
            yield break;

        // Проверяем наличие 'output_file_id' или 'error_file_id'
        string resultFileId = null;
        bool isErrorFile = false;

        if (statusInfo.TryGetValue("output_file_id", out object outputFileIdObj) && outputFileIdObj != null)
        {
            resultFileId = outputFileIdObj.ToString();
            Debug.Log($"Batch Job {batchNum}: найден 'output_file_id' = {resultFileId}");
        }
        else if (statusInfo.TryGetValue("error_file_id", out object errorFileIdObj) && errorFileIdObj != null)
        {
            resultFileId = errorFileIdObj.ToString();
            isErrorFile = true;
            Debug.LogWarning($"Batch Job {batchNum}: найден 'error_file_id' = {resultFileId}");
        }
        else
        {
            Debug.LogError(
                $"Batch Job {batchNum}: ни 'output_file_id', ни 'error_file_id' не найдены в информации о статусе задачи.");
            yield break;
        }

        // Скачиваем файл результатов или ошибок
        byte[] fileContent = null;
        bool downloadSuccess = false;
        yield return openAIClient.DownloadFile(resultFileId, data =>
        {
            fileContent = data;
            downloadSuccess = true;
        }, error => { Debug.LogError($"Ошибка загрузки файла для Batch Job {batchNum}: {error}"); });

        if (!downloadSuccess)
            yield break;

        if (isErrorFile)
        {
            // Обрабатываем файл с ошибками
            string errorText = System.Text.Encoding.UTF8.GetString(fileContent);
            Debug.LogError($"Содержимое файла с ошибкой для Batch Job {batchNum}:\n{errorText}");

            // Сохраняем файл с ошибкой (опционально)
            string errorFilePath = Path.Combine(Application.persistentDataPath, $"batch_{batchNum}_error.log");
            File.WriteAllText(errorFilePath, errorText);
            Debug.Log($"Файл с ошибкой сохранён по пути: {errorFilePath}");
        }
        else
        {
            // Обработка результатов
            var results = resultParser.ParseResults(fileContent);

            // **Загрузка изображений на Selectel и получение ссылок**
            // Получаем экземпляр SelectelStorageManager
            SelectelStorageManager storageManager = FindObjectOfType<SelectelStorageManager>();
            if (storageManager == null)
            {
                Debug.LogError("SelectelStorageManager не найден.");
                yield break;
            }

            // Создаём словарь для хранения ссылок на изображения
            Dictionary<string, string> imageLinks = new Dictionary<string, string>();

            for (int i = 0; i < batch.FilePaths.Count; i++)
            {
                string filePath = batch.FilePaths[i];
                string fileName = Path.GetFileName(filePath);

                // Загружаем файл и получаем ссылку
                string fileUrl = null;
                yield return StartCoroutine(storageManager.UploadFileAndGetUrl(filePath, url =>
                {
                    fileUrl = url;
                    Debug.Log($"Файл '{fileName}' загружен на Selectel. URL: {fileUrl}");
                }, error => { Debug.LogError($"Ошибка загрузки файла '{fileName}' на Selectel: {error}"); }));

                if (!string.IsNullOrEmpty(fileUrl))
                {
                    imageLinks[fileName] = fileUrl;
                }
                else
                {
                    imageLinks[fileName] = "Ошибка загрузки";
                }
            }

            // Записываем результаты в CSV и Excel, передавая ссылки на изображения
            List<string> batchFilenames = new List<string>();
            foreach (string path in batch.FilePaths)
            {
                batchFilenames.Add(Path.GetFileName(path));
            }

            resultParser.WriteResultsToCsv(
                results,
                "results.csv",
                batchFilenames,
                batch.StartingIndex,
                stateManager.Totals,
                batch,
                imageLinks
            );

            resultParser.WriteResultsToExcel(
                results,
                "results.xlsx",
                batchFilenames,
                batch.StartingIndex,
                stateManager.Totals,
                batch,
                imageLinks
            );

            // Обновляем UI
            mainUI.UpdateBatchInfoUI(batch);

            Debug.Log("Данные успешно загружены и результаты обновлены.");
        }
    }

    /// <summary>
    /// Запускает все незапущенные батчи асинхронно и отображает прогресс.
    /// </summary>
    public void StartAllPendingBatches()
    {
        StartCoroutine(StartAllPendingBatchesCoroutine());
    }

    private IEnumerator StartAllPendingBatchesCoroutine()
    {
        var pendingBatches = stateManager.GetBatchesByStatus(new List<string> { "pending" });
        int totalBatches = pendingBatches.Count;
        if (totalBatches == 0)
        {
            yield break;
        }

        progressUI.ShowUI(true);

        for (int i = 0; i < totalBatches; i++)
        {
            var batch = pendingBatches[i];
            UpdateProgress($"Запуск батча {batch.BatchNum} ({i + 1}/{totalBatches})...");
            yield return StartCoroutine(ProcessPendingBatch(batch.BatchNum));
            yield return null;
        }

        progressUI.ShowUI(false);
    }

    /// <summary>
    /// Обновляет статус всех батчей в процессе асинхронно и отображает прогресс.
    /// </summary>
    public void UpdateAllSubmittedBatches()
    {
        StartCoroutine(UpdateAllSubmittedBatchesCoroutine());
    }

    private IEnumerator UpdateAllSubmittedBatchesCoroutine()
    {
        var submittedBatches = stateManager.GetBatchesByStatus(new List<string> { "in_progress" });
        int totalBatches = submittedBatches.Count;
        if (totalBatches == 0)
        {
            yield break;
        }

        progressUI.ShowUI(true);

        for (int i = 0; i < totalBatches; i++)
        {
            var batch = submittedBatches[i];
            UpdateProgress($"Обновление статуса батча {batch.BatchNum} ({i + 1}/{totalBatches})...");
            yield return StartCoroutine(CheckBatchStatus(batch));
            yield return null;
        }

        progressUI.ShowUI(false);
    }

    /// <summary>
    /// Скачивает результаты для всех завершенных батчей асинхронно и отображает прогресс.
    /// </summary>
    public void DownloadAllCompletedBatches()
    {
        StartCoroutine(DownloadAllCompletedBatchesCoroutine());
    }

    private IEnumerator DownloadAllCompletedBatchesCoroutine()
    {
        var completedBatches = stateManager.GetBatchesByStatus(new List<string> { "completed" });
        int totalBatches = completedBatches.Count;
        if (totalBatches == 0)
        {
            yield break;
        }

        progressUI.ShowUI(true);

        for (int i = 0; i < totalBatches; i++)
        {
            var batch = completedBatches[i];
            UpdateProgress($"Скачивание результатов батча {batch.BatchNum} ({i + 1}/{totalBatches})...");
            yield return StartCoroutine(HandleBatchCompletion(batch));
            yield return null;
        }

        progressUI.ShowUI(false);
    }
}  и называются они типо вот так id-0-origin.jpg и надо чтобы ссылки на оригинальное фото тоже в таблице было (ссылка из селектела)