Skip to content

Commit 105b1bc

Browse files
feat(i18n): add blog translations for image upload AI recognition post
Add translated blog content for German, Spanish, French, Japanese, Korean, Portuguese Brazil, Russian, and Traditional Chinese locales covering the new image upload and AI recognition blog post. Co-Authored-By: Hagicode <noreply@hagicode.com> Signed-off-by: newbe36524 <newbe36524@qq.com>
1 parent 3f9a91b commit 105b1bc

8 files changed

Lines changed: 2275 additions & 0 deletions

src/content/docs/de-DE/blog/2026-05-07-chat-image-upload-ai-recognition.mdx

Lines changed: 284 additions & 0 deletions
Large diffs are not rendered by default.

src/content/docs/es-ES/blog/2026-05-07-chat-image-upload-ai-recognition.mdx

Lines changed: 284 additions & 0 deletions
Large diffs are not rendered by default.

src/content/docs/fr-FR/blog/2026-05-07-chat-image-upload-ai-recognition.mdx

Lines changed: 284 additions & 0 deletions
Large diffs are not rendered by default.

src/content/docs/ja-JP/blog/2026-05-07-chat-image-upload-ai-recognition.mdx

Lines changed: 284 additions & 0 deletions
Large diffs are not rendered by default.

src/content/docs/ko-KR/blog/2026-05-07-chat-image-upload-ai-recognition.mdx

Lines changed: 284 additions & 0 deletions
Large diffs are not rendered by default.

src/content/docs/pt-BR/blog/2026-05-07-chat-image-upload-ai-recognition.mdx

Lines changed: 287 additions & 0 deletions
Large diffs are not rendered by default.

src/content/docs/ru-RU/blog/2026-05-07-chat-image-upload-ai-recognition.mdx

Lines changed: 284 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
---
2+
title: 在聊天中實現圖片上傳與 AI 識別:從設計到落地的完整方案
3+
date: 2026-05-07
4+
tags: [AI, 圖片上傳, 多模態, 系統設計, HagiCode]
5+
---
6+
7+
## 在聊天中實現圖片上傳與 AI 識別:從設計到落地的完整方案
8+
9+
> 在 AI 交互系統中,如何讓用戶上傳圖片並讓 AI 直接識別?其實這個問題我也糾結了很久,好在 HagiCode 的實踐中摸索出了一些門道。今天就聊聊這套圖片上傳與識別方案,從自定義協議設計到文件系統存儲,再到前後端分離預覽,也算是個完整的技術筆記了。
10+
11+
## 背景
12+
13+
在這個 AI 聊天盛行的時代,視覺信息其實是用戶表達意圖的重要載體。只是呢,傳統聊天系統大多只支持純文本輸入,這就導致用戶沒法把視覺上下文直接傳遞給 AI 分析,多少有點遺憾。
14+
15+
HagiCode 在開發過程中也遇到了類似的困境:用戶無法在聊天或主意見創建時上傳圖片,AI 無法訪問用戶本地的視覺信息,缺少圖片從輸入、存儲、渲染到 AI 上下文傳遞的完整閉環。
16+
17+
其實這些問題也沒什麼大不了的,只是需要一點時間和耐心去解決罷了。我們設計並實現了一套完整的圖片上傳與識別流程,讓 Claude 等 AI 能夠直接識別和分析用戶上傳的截圖。接下來我會慢慢細說這個方案的實現細節。
18+
19+
## 關於 HagiCode
20+
21+
本文分享的方案來自我們在 [HagiCode](https://hagicode.com) 項目中的實踐經驗。HagiCode 是一個開源的 AI 代碼助手項目,採用基於 OpenSpec 的工作流設計,致力於提供更智能的代碼編寫體驗。
22+
23+
## 分析
24+
25+
### 技術挑戰
26+
27+
在開始實現之前,我們還是得先理清楚面臨的主要挑戰,畢竟磨刀不誤砍柴工嘛。
28+
29+
**跨模塊協作**:圖片上傳涉及前端 UI、上傳服務、後端 API、文件存儲、消息持久化和 AI 執行映射等多個模塊。每個模塊都有自己的職責和接口,需要設計一個協調一致的整體方案。
30+
31+
**存儲策略選擇**:圖片應該存儲在數據庫還是文件系統?如果選擇文件系統,目錄結構如何設計?如何與現有的 OpenSpec 工作流集成?這些都需要仔細權衡。
32+
33+
**引用協議設計**:需要一種標準的圖片引用方式,既能被前端渲染顯示,又能被 AI 執行鏈路正確解析。直接使用文件路徑?HTTP URL?還是設計專門的協議?
34+
35+
**AI 能力兼容**:不同 AI 執行器對多模態的支持程度不同。有些執行器原生支持圖片輸入,有些只能處理文本。如何設計統一的適配層,確保所有執行器都能正確處理圖片信息?
36+
37+
### 設計決策
38+
39+
經過充分的討論和權衡,我們做出了以下關鍵設計決策。
40+
41+
**決策 1:文件系統存儲**
42+
43+
我們選擇將圖片存儲在文件系統而非數據庫中。目錄結構設計如下:
44+
45+
```
46+
<系統根目錄>/images/<sessionId>/
47+
├── <timestamp>-<uuid>.jpg
48+
└── <timestamp>-<uuid>.png
49+
```
50+
51+
理由其實也挺明確的:簡化實現,避免數據庫膨脹,文件可以直接被 AI 讀取。而且圖片文件本質上就不適合放在數據庫裡,文件系統才是更自然的選擇。這就像把書放在書架上,而不是塞進筆記本裡,一個道理罷了。
52+
53+
**決策 2:自定義協議 `hagiimag://`**
54+
55+
為了避免與 HTTP URL 衝突,同時讓引用語義更清晰,我們設計了一個自定義圖片引用協議:
56+
57+
```
58+
hagiimag://session-abc123/20260301-143022-a1b2c3d4
59+
```
60+
61+
這個協議的格式是 `hagiimag://<sessionId>/<imageId>`,語義清晰,便於解析和路由。看到這個格式,開發者立刻就能明白這是一個圖片引用,而不是普通的 URL。這種設計上的小心思,有時候還是挺有用的。
62+
63+
**決策 3:前端預覽與 AI 訪問分離**
64+
65+
在實現過程中,我們發現前端和 AI 對圖片的訪問需求不同:前端需要通過 HTTP API 進行預覽,而 AI 需要直接讀取本地文件路徑。因此我們設計了分離的訪問方式:
66+
67+
- 前端使用 `/api/Images/{sessionId}/{imageId}/content` 進行預覽
68+
- AI 使用服務端解析的本地文件路徑
69+
70+
這樣既保證了安全性(不暴露服務器路徑),又兼顧了可用性(瀏覽器可直接訪問)。畢竟安全性和可用性這兩件事,總是需要平衡的。
71+
72+
**決策 4:立即上傳策略**
73+
74+
另一個關鍵決策是上傳時機。我們選擇用戶選擇或粘貼圖片時立即觸發上傳,發送消息時只引用已經上傳成功的圖片。
75+
76+
這樣做的好處是錯誤處理前置,避免消息發送 API 變複雜,保持 JSON 契約的簡潔性。用戶在發送前就能知道圖片是否上傳成功,體驗也更好。這種"未雨綢繆"的設計思路,或許在很多時候都適用。
77+
78+
## 解決
79+
80+
### 架構設計
81+
82+
基於上述決策,我們設計了如下整體架構:
83+
84+
```
85+
前端層
86+
├── ConversationInputArea ◄─────── useImageAttachmentManager
87+
│ │ │
88+
│ ├── 文件選擇 ├── 附件狀態管理
89+
│ ├── 剪貼板粘貼 ├── 上傳/重試/刪除
90+
│ └── 附件預覽 └── 圖片引用生成
91+
92+
服務層
93+
├── ImageUploadService
94+
│ ├── uploadImage() ◄─────── ImagesController
95+
│ ├── deleteImage() │
96+
│ ├── parseHagiImageUrl() ◄─────── 解析協議鏈接
97+
│ └── buildPreviewUrl() │
98+
99+
後端層
100+
├── ImagesController ◄─────── ImagesDomainService
101+
│ │ │
102+
│ ├── POST /upload ├── 文件驗證
103+
│ ├── GET /{sessionId}/{imageId} ├── 圖片保存
104+
│ ├── DELETE ├── 圖片壓縮
105+
│ └── GET /content └── 引用解析
106+
107+
AI 執行層
108+
├── ImageContentBlock ◄─────── StructuredMessageDomainService
109+
│ │ │
110+
│ ├── 多模態執行器 ├── 圖片塊解析
111+
│ └── 文本執行器降級 └── 路徑提示生成
112+
```
113+
114+
這個架構清晰地展示了從前端到 AI 的完整數據流。每一層都有明確的職責,通過標準接口進行交互。其實好的架構就是這樣,各司其職,互不干擾,溝通順暢。
115+
116+
### 關鍵流程
117+
118+
**圖片上傳流程**
119+
120+
1. 用戶通過文件選擇或剪貼板粘貼選擇圖片
121+
2. 前端驗證文件類型和大小(支持 JPEG/PNG/WEBP/GIF,單文件 10MB)
122+
3. 調用上傳 API,圖片保存到 `/images/{sessionId}/` 目錄
123+
4. API 返回 `hagiimag://` 引用和預覽 URL
124+
5. 前端在附件條中顯示預覽縮略圖,用戶可以在發送前預覽
125+
126+
**AI 識別流程**
127+
128+
1. 用戶發送包含圖片引用的消息
129+
2. 後端解析 `hagiimag://` 協議鏈接,提取 sessionId 和 imageId
130+
3. 將圖片引用映射為 `ImageContentBlock`
131+
4. 根據執行器能力選擇處理方式:
132+
- 多模態執行器:傳遞結構化圖片輸入
133+
- 文本執行器:降級為圖片路徑提示
134+
135+
這樣就完成了一個完整的閉環:用戶上傳圖片 → AI 識別圖片 → AI 返回分析結果。這種流程上的順暢,往往能給用戶帶來更好的體驗。
136+
137+
## 實踐
138+
139+
### 前端實現
140+
141+
在前端,我們提供了一個專門的 Hook 來管理圖片附件狀態:
142+
143+
```typescript
144+
import { useImageAttachmentManager } from '@/hooks/useImageAttachmentManager';
145+
146+
function ChatInput() {
147+
const {
148+
attachments,
149+
uploadedImages,
150+
hasBlockingAttachments,
151+
isUploading,
152+
selectFiles,
153+
removeAttachment,
154+
clearAttachments,
155+
} = useImageAttachmentManager({
156+
ownerId: sessionId,
157+
mapUploadedImage: (response) => response,
158+
uploadOptions: { compress: false },
159+
});
160+
161+
const handleFileSelect = (files: File[]) => {
162+
selectFiles(files);
163+
};
164+
165+
const handlePaste = (e: ClipboardEvent) => {
166+
const files = Array.from(e.clipboardData?.files || [])
167+
.filter(f => f.type.startsWith('image/'));
168+
if (files.length > 0) {
169+
handleFileSelect(files);
170+
}
171+
};
172+
173+
return (
174+
<div>
175+
{/* 附件條 */}
176+
{attachments.map(att => (
177+
<AttachmentItem
178+
key={att.localId}
179+
file={att.file}
180+
status={att.status}
181+
onRemove={() => removeAttachment(att.localId)}
182+
/>
183+
))}
184+
185+
{/* 輸入框 */}
186+
<textarea onPaste={handlePaste} />
187+
188+
{/* 上傳按鈕 */}
189+
<button onClick={() => fileInputRef.current?.click()}>
190+
上傳圖片
191+
</button>
192+
</div>
193+
);
194+
}
195+
```
196+
197+
這個 Hook 封裝了所有附件管理的邏輯,包括上傳狀態跟蹤、失敗重試、附件刪除等。使用起來非常簡單,只需要調用幾個方法就能完成整個流程。其實好的 API 設計就是這樣,簡單易用,又不失靈活性。
198+
199+
**解析自定義協議**
200+
201+
```typescript
202+
// 從自定義協議中提取 sessionId 和 imageId
203+
const parsed = parseHagiImageUrl("hagiimag://session-abc123/20260301-143022-uuid");
204+
// 返回: { sessionId: "session-abc123", imageId: "20260301-143022-uuid" }
205+
206+
// 構建預覽 URL
207+
const previewUrl = buildPreviewUrl(parsed.sessionId, parsed.imageId);
208+
// 返回: "/api/Images/session-abc123/20260301-143022-uuid/content"
209+
```
210+
211+
通過這兩個工具函數,前端可以輕鬆地在 `hagiimag://` 協議和 HTTP URL 之間進行轉換。這種轉換邏輯封裝好了,使用起來就方便多了。
212+
213+
### 後端實現
214+
215+
後端使用 ASP.NET Core 實現,核心是 `ImagesController``ImagesDomainService`
216+
217+
```csharp
218+
[HttpPost("upload")]
219+
[RequestSizeLimit(50 * 1024 * 1024)]
220+
public async Task<ActionResult<ImageUploadResponseDto>> Upload(
221+
[FromForm] UploadImageFormRequest input)
222+
{
223+
// 1. 驗證請求
224+
if (file == null || file.Length == 0)
225+
throw new UserFriendlyException("No file provided");
226+
227+
// 2. 驗證文件類型和大小
228+
var (isValid, errorMessage) = _imagesDomainService.ValidateImage(
229+
file.FileName, file.ContentType, file.Length);
230+
if (!isValid)
231+
throw new UserFriendlyException(errorMessage);
232+
233+
// 3. 保存到文件系統
234+
await using var stream = file.OpenReadStream();
235+
var result = await _imagesDomainService.UploadImageAsync(
236+
stream,
237+
sessionId,
238+
file.FileName,
239+
file.ContentType,
240+
CurrentUserId,
241+
compress: input.Compress);
242+
243+
// 4. 返回結果
244+
return Ok(result);
245+
}
246+
```
247+
248+
這個實現遵循了典型的 Web API 開發模式:驗證、處理、返回。需要注意的是,我們設置了 50MB 的請求大小限制,防止惡意的大文件上傳。畢竟在網絡世界裡,小心一點總是沒錯的。
249+
250+
### 注意事項
251+
252+
在實現過程中,有一些細節需要特別注意:
253+
254+
**權限校驗**:圖片訪問必須驗證用戶身份,確保只能訪問自己會話的圖片。這是一個基本的安全要求,不能省略。安全這東西,不怕一萬就怕萬一。
255+
256+
**路徑安全**:嚴格驗證 `sessionId``imageId`,防止路徑遍歷攻擊。比如要拒絕包含 `../` 的路徑,防止用戶訪問系統中的任意文件。這種邊界條件處理好了,系統才能更穩健。
257+
258+
**文件清理**:會話刪除時需同步清理關聯圖片,避免孤兒文件堆積。長期運行後,這些文件可能會佔用大量磁盤空間。及時清理,也是一種好的習慣。
259+
260+
**壓縮策略**:對於截圖類文件名(如 `screenshot.png`),自動啟用壓縮以節省空間。這個策略可以根據實際需求調整。存儲空間嘛,能省一點是一點。
261+
262+
**降級處理**:不支持多模態的執行器必須收到圖片路徑提示,不能靜默丟棄圖片信息。這一點很重要,否則用戶會以為 AI 忽略了他的圖片。用戶體驗這種事,細節決定成敗。
263+
264+
**狀態管理**:上傳中的附件會阻止消息發送,失敗附件允許重試或刪除。這個設計保證了用戶體驗的連貫性。狀態管理清晰了,用戶就不會感到困惑。
265+
266+
## 總結
267+
268+
通過這套完整的圖片上傳與識別方案,HagiCode 實現了從用戶輸入到 AI 識別的完整閉環。整個方案的核心亮點包括:
269+
270+
- 自定義 `hagiimag://` 協議實現了圖片引用的標準化
271+
- 文件系統存儲簡化了實現並提高了性能
272+
- 前端預覽與 AI 訪問分離兼顧了安全性和可用性
273+
- 立即上傳策略優化了用戶體驗
274+
- 多模態與文本降級的兼容設計確保了靈活性
275+
276+
這個方案在 HagiCode 中運行穩定,用戶反饋良好。如果你也在實現類似的功能,希望這些經驗對你有幫助。
277+
278+
其實技術方案這東西,沒有絕對的對錯,只有適合不適合罷了。找到適合自己項目的路,才是最重要的。
279+
280+
## 參考資料
281+
282+
- HagiCode GitHub: [github.com/HagiCode-org/site](https://github.com/HagiCode-org/site)
283+
- HagiCode 官網: [hagicode.com](https://hagicode.com)
284+
- OpenSpec 工作流文檔: [docs.hagicode.com](https://docs.hagicode.com)

0 commit comments

Comments
 (0)