Giới thiệu
Văn bản thay thế (alt text) là yếu tố cực kỳ quan trọng đối với cả khả năng tiếp cận (accessibility) và SEO. Trình đọc màn hình dựa vào nó để mô tả hình ảnh cho người dùng khiếm thị, và các công cụ tìm kiếm sử dụng nó để hiểu rõ hơn nội dung của trang.
Trong dự án Optimizely CMS của chúng tôi, chúng tôi đã triển khai một tác vụ tự động “tạo alt text” quét trang web tìm hình ảnh, gửi chúng đến dịch vụ AI để mô tả, và ghi kết quả trở lại các thuộc tính nội dung.
Phiên bản đầu tiên của tác vụ này duyệt qua nội dung thủ công và cố gắng đoán hình ảnh được sử dụng ở đâu. Sau đó, chúng tôi đã cải tiến bằng cách sử dụng API tích hợp sẵn của Optimizely: IContentModelUsage để tìm ra tất cả các vị trí sử dụng của một hình ảnh nhất định một cách đáng tin cậy. Bài viết này sẽ trình bày phương pháp tiếp cận cuối cùng.
Tổng quan về Kịch bản
Mục tiêu của tác vụ là:
- Tìm tất cả các tài sản hình ảnh trong thư viện media đang thiếu alt text tốt.
- Đối với mỗi hình ảnh, tìm tất cả các mục nội dung (trang/khối) thực sự sử dụng nó.
- Tạo chuỗi alt text mô tả bằng dịch vụ AI.
- Cập nhật các trường alt text tương ứng trên các mô hình nội dung.
- Chạy tất cả như một công việc định kỳ (scheduled job) để biên tập viên có thể kích hoạt theo yêu cầu.
Cải tiến quan trọng là thay thế logic "tìm hình ảnh đã sử dụng" mong manh bằng IContentModelUsage, API này hiểu cách media được tham chiếu trên toàn trang ở cấp độ mô hình dữ liệu.
Bước 1: Khám phá Hình ảnh Thiếu Alt Text
Chúng ta bắt đầu bằng cách truy vấn các tài sản media và lọc những tài sản không có alt text có ý nghĩa. Điều này phụ thuộc vào mô hình media của bạn, nhưng một mẫu điển hình trông như sau:
public class ImageFile : ImageData
{
[Display(Name = "Alt text", Order = 100)]
public virtual string AltText { get; set; }
}
Trong công việc định kỳ (scheduled job):
var imagesWithoutAlt = _contentRepository
.GetDescendents(ContentReference.GlobalBlockFolder) // hoặc thư mục media root của bạn
.Select(x => _contentRepository.Get<ImageFile>(x))
.Where(img => string.IsNullOrWhiteSpace(img.AltText))
.ToList();
Điều này cung cấp cho chúng ta tập hợp các hình ảnh ứng cử viên cần làm giàu thông tin.
Bước 2: Lấy Hình ảnh đã Sử dụng bằng ContentModelUsage
Ban đầu, tác vụ cố gắng suy luận vị trí hình ảnh được sử dụng bằng cách quét các thuộc tính nội dung và khớp ID. Cách làm này dễ bị lỗi và dễ hỏng khi các loại nội dung hoặc thuộc tính mới được thêm vào.
Phiên bản cải tiến sử dụng IContentModelUsage:
private readonly IContentModelUsage _contentModelUsage;
public AltTextGenerationJob(
IContentRepository contentRepository,
IContentModelUsage contentModelUsage,
IAltTextService altTextService,
ILogger logger)
{
_contentRepository = contentRepository;
_contentModelUsage = contentModelUsage;
_altTextService = altTextService;
_logger = logger;
}
private IEnumerable<UsageInContentModel> GetImageUsages(ContentReference imageRef)
{
// Lệnh này yêu cầu Optimizely cho biết tài sản cụ thể này được tham chiếu ở đâu
return _contentModelUsage.ListContentOfContent(imageRef);
}
Mỗi đối tượng UsageInContentModel cho chúng ta biết mục nội dung nào sử dụng hình ảnh, và trong nhiều trường hợp, thuộc tính nào chứa tham chiếu. Điều này mạnh mẽ hơn nhiều so với việc tự viết code reflection, vì nó tôn trọng cách Optimizely lưu trữ các tham chiếu nội bộ.
Bước 3: Tạo Alt Text bằng AI
Đối với mỗi hình ảnh và các vị trí sử dụng của nó, chúng tôi gọi một dịch vụ AI có thể kiểm tra hình ảnh và trả về mô tả ngôn ngữ tự nhiên. Chi tiết triển khai phụ thuộc vào nhà cung cấp của bạn (Azure OpenAI, custom vision, v.v.), nhưng mẫu chung là tương tự:
public interface IAltTextService
{
Task<string> GenerateAltTextAsync(ImageFile image);
}
public async Task<string> GenerateAltTextAsync(ImageFile image)
{
var url = _urlResolver.GetUrl(image.ContentLink);
// Gọi API AI bên ngoài với URL hình ảnh
var description = await _aiClient.DescribeImageAsync(url);
return description.Truncate(200); // giữ alt text ngắn gọn hợp lý
}
Công việc định kỳ điều phối luồng:
foreach (var image in imagesWithoutAlt)
{
var usages = GetImageUsages(image.ContentLink).ToList();
if (!usages.Any())
{
_logger.Information("Image {ImageId} is not used anywhere, skipping.", image.ContentLink);
continue;
}
var altText = await _altTextService.GenerateAltTextAsync(image);
if (string.IsNullOrWhiteSpace(altText))
{
_logger.Warning("AI did not return alt text for image {ImageId}", image.ContentLink);
continue;
}
var writable = image.CreateWritableClone() as ImageFile;
writable.AltText = altText;
_contentRepository.Save(writable, SaveAction.Publish, AccessLevel.NoAccess);
_logger.Information(
"Generated alt text for image {ImageId} used in {UsageCount} items.",
image.ContentLink, usages.Count);
}
Ở đây, hàm GetImageUsages được cung cấp bởi IContentModelUsage, vì vậy chúng ta biết chính xác mức độ sử dụng của mỗi hình ảnh và có thể ưu tiên tương ứng (ví dụ: bỏ qua các tài sản không được sử dụng hoặc ghi nhật ký chúng riêng biệt).
Bước 4: Lợi ích của việc sử dụng ContentModelUsage
Chuyển bước "tìm hình ảnh đã sử dụng" sang IContentModelUsage mang lại một số lợi ích cụ thể:
- Độ chính xác: Chúng tôi không còn bỏ sót các vị trí sử dụng bị ẩn sau các cấu trúc trang/khối phức tạp; API sử dụng mô hình của Optimizely theo dõi các tham chiếu một cách đáng tin cậy.
- An toàn khi tái cấu trúc (Refactor safety): Khi các biên tập viên hoặc nhà phát triển giới thiệu các loại nội dung hoặc thuộc tính hình ảnh mới,
IContentModelUsagevẫn hoạt động mà không cần thay đổi code trong công việc định kỳ. - Báo cáo tốt hơn: Danh sách sử dụng giúp dễ dàng ghi lại tần suất sử dụng một hình ảnh, giúp biên tập viên ưu tiên hình ảnh nào nên giữ lại hoặc thay thế.
- Code sạch hơn: Code của tác vụ tập trung vào luồng công việc alt text thay vì reflection cấp thấp và quét thuộc tính.
Nhìn chung, sự kết hợp giữa ImageFile.AltText, IContentModelUsage, và dịch vụ mô tả AI đã mang lại cho chúng tôi một cách thức lặp lại để cải thiện khả năng tiếp cận trên hàng nghìn hình ảnh với nỗ lực thủ công tối thiểu.
Kết luận
Tự động hóa việc tạo alt text là một cách mạnh mẽ để nâng cao nền tảng khả năng tiếp cận của một trang web Optimizely CMS. Lựa chọn thiết kế then chốt trong dự án này là để chính nền tảng cho chúng ta biết hình ảnh được sử dụng ở đâu, thông qua IContentModelUsage, thay vì cố gắng đoán.