Реализация загрузки изображений и AI-распознавания в чате: полное решение от проектирования до внедрения
Реализация загрузки изображений и AI-распознавания в чате: полное решение от проектирования до внедрения
В системах AI-взаимодействия, как позволить пользователям загружать изображения и напрямую распознавать их с помощью AI? На самом деле, эта проблема беспокоила меня довольно долго, но к счастью, в практике HagiCode удалось найти кое-какие подходы. Сегодня поговорим об этом решении для загрузки и распознавания изображений — от проектирования пользовательского протокола до хранения в файловой системе, а затем до разделённого предварительного просмотра между фронтендом и бэкендом. Это можно считать полноценной технической заметкой.
Контекст
В эту эпоху расцвета AI-чатов визуальная информация на самом деле является важным носителем намерений пользователей. Однако традиционные системы чата в основном поддерживают только ввод простого текста, что не позволяет пользователям напрямую передавать визуальный контекст AI для анализа, что весьма прискорбно.
HagiCode столкнулся с аналогичными трудностями в процессе разработки: пользователи не могли загружать изображения при создании чата или основного мнения, AI не мог обращаться к локальной визуальной информации пользователей, отсутствовал полный замкнутый цикл от ввода, хранения, рендеринга изображений до передачи контекста AI.
На самом деле эти проблемы не такие уж серьёзные, просто нужно немного времени и терпения для их решения. Мы спроектировали и реализовали полный процесс загрузки и распознавания изображений, позволяющий Claude и другим AI напрямую распознавать и анализировать загруженные пользователями скриншоты. Далее я подробно расскажу о деталях реализации этого решения.
О HagiCode
Решение, которым я делюсь в этой статье, основано на нашем практическом опыте в проекте HagiCode. HagiCode — это проект с открытым исходным кодом AI-помощника по коду, использующий дизайн рабочих процессов на основе OpenSpec и стремящийся предоставить более умный опыт написания кода.
Анализ
Технические вызовы
Перед началом реализации нам нужно сначала чётко понять основные вызовы, с которыми мы сталкиваемся — ведь, как говорится, заточи топор перед тем, как рубить дрова.
Межмодульное взаимодействие: загрузка изображений включает в себя фронтенд UI, сервис загрузки, бэкенд API, файловое хранилище, персистентность сообщений и маппинг AI-выполнения. Каждый модуль имеет свои обязанности и интерфейсы, необходимо спроектировать согласованное общее решение.
Выбор стратегии хранения: хранить изображения в базе данных или в файловой системе? Если выбрана файловая система, как спроектировать структуру каталогов? Как интегрировать с существующим рабочим процессом OpenSpec? Всё это нужно тщательно взвесить.
Проектирование протокола ссылок: необходим стандартный способ ссылок на изображения, который мог бы быть отображён фронтендом и правильно проанализирован цепочкой выполнения AI. Использовать напрямую пути к файлам? HTTP URL? Или спроектировать специальный протокол?
Совместимость с AI-возможностями: разные AI-исполнители имеют разную степень поддержки мультимодальности. Некоторые исполнители нативно поддерживают ввод изображений, некоторые могут обрабатывать только текст. Как спроектировать унифицированный адаптерный слой, чтобы все исполнители могли правильно обрабатывать информацию об изображениях?
Проектные решения
После полного обсуждения и взвешивания мы приняли следующие ключевые проектные решения.
Решение 1: хранение в файловой системе
Мы выбрали хранение изображений в файловой системе, а не в базе данных. Структура каталогов спроектирована следующим образом:
<системный корневой каталог>/images/<sessionId>/├── <timestamp>-<uuid>.jpg└── <timestamp>-<uuid>.pngПричины на самом деле довольно ясны: упрощение реализации, избежание раздувания базы данных, файлы могут быть напрямую прочитаны AI. Кроме того, файлы изображений по сути не подходят для хранения в базе данных, файловая система — более естественный выбор. Это как положить книгу на полку, а не заталкивать её в блокнот, одна и та же причина.
Решение 2: пользовательский протокол hagiimag://
Чтобы избежать конфликтов с HTTP URL и сделать семантику ссылок более ясной, мы спроектировали пользовательский протокол ссылок на изображения:
hagiimag://session-abc123/20260301-143022-a1b2c3d4Формат этого протокола — hagiimag://<sessionId>/<imageId>, семантика ясна, удобно для парсинга и маршрутизации. Увидев этот формат, разработчик сразу поймёт, что это ссылка на изображение, а не обычный URL. Иногда такие мелкие детали дизайна довольно полезны.
Решение 3: разделение фронтенд-предпросмотра и AI-доступа
В процессе реализации мы обнаружили, что фронтенд и AI имеют разные требования к доступу к изображениям: фронтенду нужно осуществлять предпросмотр через HTTP API, а AI нужно напрямую читать пути к локальным файлам. Поэтому мы спроектировали разделённые способы доступа:
- Фронтенд использует
/api/Images/{sessionId}/{imageId}/contentдля предпросмотра - AI использует путь к локальному файлу, проанализированный на сервере
Это обеспечивает безопасность (не раскрываются пути сервера) и兼顾ает удобство использования (браузер может напрямую обращаться). Ведь безопасность и удобство использования всегда нужно балансировать.
Решение 4: стратегия немедленной загрузки
Другое ключевое решение — время загрузки. Мы выбрали немедленный запуск загрузки, когда пользователь выбирает или вставляет изображение, а при отправке сообщения ссылаемся только на успешно загруженные изображения.
Преимущество этого подхода — вынесенная вперёд обработка ошибок, избежание усложнения API отправки сообщений, сохранение простоты JSON-контракта. Пользователь знает до отправки, успешно ли загружено изображение, опыт также лучше. Такой подход “заботиться заранее”, возможно, применим во многих ситуациях.
Решение
Архитектурный дизайн
На основе вышеуказанных решений мы спроектировали следующую общую архитектуру:
Фронтенд слой├── ConversationInputArea ◄─────── useImageAttachmentManager│ │ ││ ├── Выбор файла ├── Управление состоянием вложений│ ├── Вставка из буфера обмена ├── Загрузка/повтор/удаление│ └── Предпросмотр вложений └── Генерация ссылок на изображения│Сервисный слой├── ImageUploadService│ ├── uploadImage() ◄─────── ImagesController│ ├── deleteImage() ││ ├── parseHagiImageUrl() ◄─────── Разбор ссылок протокола│ └── buildPreviewUrl() ││Бэкенд слой├── ImagesController ◄─────── ImagesDomainService│ │ ││ ├── POST /upload ├── Валидация файлов│ ├── GET /{sessionId}/{imageId} ├── Сохранение изображений│ ├── DELETE ├── Сжатие изображений│ └── GET /content └── Разбор ссылок│AI слой выполнения├── ImageContentBlock ◄─────── StructuredMessageDomainService│ │ ││ ├── Мультимодальный исполнитель ├── Разбор блоков изображений│ └── Деградация текстового исполнителя └── Генерация подсказок путиЭта архитектура наглядно демонстрирует полный поток данных от фронтенда к AI. Каждый слой имеет чёткие обязанности и взаимодействует через стандартные интерфейсы. На самом деле хорошая архитектура именно такая: каждый выполняет свои обязанности, не мешает друг другу, общение гладкое.
Ключевые процессы
Процесс загрузки изображения:
- Пользователь выбирает изображение через выбор файла или вставку из буфера обмена
- Фронтенд проверяет тип и размер файла (поддерживаются JPEG/PNG/WEBP/GIF, один файл до 10 МБ)
- Вызывается API загрузки, изображение сохраняется в каталог
/images/{sessionId}/ - API возвращает ссылку
hagiimag://и URL предпросмотра - Фронтенд отображает превью-миниатюру в панели вложений, пользователь может предварительно просмотреть перед отправкой
Процесс AI-распознавания:
- Пользователь отправляет сообщение, содержащее ссылку на изображение
- Бэкенд разбирает ссылку протокола
hagiimag://, извлекает sessionId и imageId - Ссылка на изображение отображается в
ImageContentBlock - В зависимости от возможностей исполнителя выбирается способ обработки:
- Мультимодальный исполнитель: передача структурированного ввода изображения
- Текстовый исполнитель: деградация в подсказку пути к изображению
Таким образом завершается полный замкнутый цикл: пользователь загружает изображение → AI распознаёт изображение → AI возвращает результат анализа. Такая плавность процесса часто приносит пользователям лучший опыт.
Практика
Реализация на фронтенде
На фронтенде мы предоставляем специализированный Hook для управления состоянием вложений изображений:
import { useImageAttachmentManager } from '@/hooks/useImageAttachmentManager';
function ChatInput() { const { attachments, uploadedImages, hasBlockingAttachments, isUploading, selectFiles, removeAttachment, clearAttachments, } = useImageAttachmentManager({ ownerId: sessionId, mapUploadedImage: (response) => response, uploadOptions: { compress: false }, });
const handleFileSelect = (files: File[]) => { selectFiles(files); };
const handlePaste = (e: ClipboardEvent) => { const files = Array.from(e.clipboardData?.files || []) .filter(f => f.type.startsWith('image/')); if (files.length > 0) { handleFileSelect(files); } };
return ( <div> {/* Панель вложений */} {attachments.map(att => ( <AttachmentItem key={att.localId} file={att.file} status={att.status} onRemove={() => removeAttachment(att.localId)} /> ))}
{/* Поле ввода */} <textarea onPaste={handlePaste} />
{/* Кнопка загрузки */} <button onClick={() => fileInputRef.current?.click()}> Загрузить изображение </button> </div> );}Этот Hook инкапсулирует всю логику управления вложениями, включая отслеживание состояния загрузки, повтор при неудаче, удаление вложений и т.д. Использовать его очень просто, нужно только вызвать несколько методов для завершения всего процесса. На самом деле хороший дизайн API именно такой: простой в использовании, но без потери гибкости.
Разбор пользовательского протокола:
// Извлечение sessionId и imageId из пользовательского протоколаconst parsed = parseHagiImageUrl("hagiimag://session-abc123/20260301-143022-uuid");// Возвращает: { sessionId: "session-abc123", imageId: "20260301-143022-uuid" }
// Построение URL предпросмотраconst previewUrl = buildPreviewUrl(parsed.sessionId, parsed.imageId);// Возвращает: "/api/Images/session-abc123/20260301-143022-uuid/content"С помощью этих двух утилит функции фронтенд может легко конвертировать между протоколом hagiimag:// и HTTP URL. Такая логика конвертации инкапсулирована, использовать её гораздо удобнее.
Реализация на бэкенде
Бэкенд реализован на ASP.NET Core, ядро — ImagesController и ImagesDomainService:
[HttpPost("upload")][RequestSizeLimit(50 * 1024 * 1024)]public async Task<ActionResult<ImageUploadResponseDto>> Upload( [FromForm] UploadImageFormRequest input){ // 1. Проверка запроса if (file == null || file.Length == 0) throw new UserFriendlyException("No file provided");
// 2. Проверка типа и размера файла var (isValid, errorMessage) = _imagesDomainService.ValidateImage( file.FileName, file.ContentType, file.Length); if (!isValid) throw new UserFriendlyException(errorMessage);
// 3. Сохранение в файловой системе await using var stream = file.OpenReadStream(); var result = await _imagesDomainService.UploadImageAsync( stream, sessionId, file.FileName, file.ContentType, CurrentUserId, compress: input.Compress);
// 4. Возврат результата return Ok(result);}Эта реализация следует типичному паттерну разработки Web API: валидация, обработка, возврат. Стоит отметить, что мы установили лимит размера запроса 50 МБ для предотвращения вредоносной загрузки больших файлов. Ведь в сетевом мире осторожность никогда не помешает.
Меры предосторожности
В процессе реализации есть некоторые детали, которые требуют особого внимания:
Проверка разрешений: доступ к изображениям должен проверять идентичность пользователя, чтобы гарантировать доступ только к изображениям собственной сессии. Это базовое требование безопасности, нельзя опускать. Безопасность — это такая вещь: лучше перестраховаться, чем потом жалеть.
Безопасность путей: строгая валидация sessionId и imageId для предотвращения атак обхода пути. Например, нужно отклонять пути, содержащие ../, чтобы предотвратить доступ пользователей к произвольным файлам в системе. Такие граничные условия хорошо обработаны — система будет более устойчивой.
Очистка файлов: при удалении сессии нужно синхронно очищать связанные изображения, чтобы избежать накопления файлов-сирот. После длительной работы эти файлы могут занимать много дискового пространства. Своевременная очистка — тоже хорошая привычка.
Стратегия сжатия: для файлов скриншотов (например, screenshot.png) автоматически включается сжатие для экономии места. Эта стратегия может быть скорректирована в соответствии с фактическими потребностями. Место для хранения: чем сможешь сэкономить, тем лучше.
Обработка деградации: исполнители, не поддерживающие мультимодальность, должны получать подсказку пути к изображению, нельзя молча отбрасывать информацию об изображении. Это очень важно, иначе пользователь подумает, что AI проигнорировал его изображение. Пользовательский опыт — это такая вещь, где детали решают всё.
Управление состоянием: загружаемые вложения блокируют отправку сообщений, неудачные вложения позволяют повторить или удалить. Этот дизайн гарантирует непрерывность пользовательского опыта. Когда управление состоянием чёткое, пользователь не будет чувствовать себя сбитым с толку.
Заключение
С помощью этого полного решения загрузки и распознавания изображений HagiCode реализовал полный замкнутый цикл от пользовательского ввода до AI-распознавания. Основные преимущества всего решения включают:
- Пользовательский протокол
hagiimag://реализует стандартизацию ссылок на изображения - Хранение в файловой системе упрощает реализацию и повышает производительность
- Разделение фронтенд-предпросмотра и AI-доступа учитывает безопасность и удобство использования
- Стратегия немедленной загрузки оптимизирует пользовательский опыт
- Совместимый дизайн мультимодальности и текстовой деградации обеспечивает гибкость
Это решение стабильно работает в HagiCode, пользователи дают хорошие отзывы. Если вы также реализуете аналогичную функциональность, надеюсь, этот опыт будет вам полезен.
На самом деле в технических решениях нет абсолютного права или ошибки, есть только подходящее или неподходящее. Найти путь, подходящий для вашего проекта, — самое важное.
Справочные материалы
- HagiCode GitHub: github.com/HagiCode-org/site
- Официальный сайт HagiCode: hagicode.com
- Документация по рабочим процессам OpenSpec: docs.hagicode.com
开始使用 HagiCode
一次安装,几分钟上手
HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。