Images are stored on the backend file system at:
./uploads/{userId}/{folderPath}/{filename}
Example:
./uploads/
└── 019a07bf-79e5-7fbc-86c4-e9f265c07fd6/ ← User ID
├── / ← Root folder
│ └── avatar.png
└── /Manga1/ ← Subfolder
└── LionKing.jpg ← Your image!
📍 src/components/FileUpload.vue (line 71)
// Frontend sends base64 encoded image
fileData: previewUrl.value // "data:image/jpeg;base64,/9j/4AAQ..."📍 concepts/MediaManagement/MediaManagement.ts (lines 111-118)
// Decode base64 and save to disk
const base64Data = fileData.replace(/^data:image\/\w+;base64,/, '');
const fileBytes = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
const fullPath = `./uploads/${userId}${filePath}/${filename}`;
await Deno.writeFile(fullPath, fileBytes);
// ✅ Image now exists on disk!📍 src/components/MediaCard.vue (lines 35-59)
// For each image card, fetch the actual image
const loadImage = async () => {
const response = await fetch('/api/MediaManagement/_serveImage', {
method: 'POST',
body: JSON.stringify({ userId, mediaId })
});
// Get binary data
const blob = await response.blob();
// Create temporary URL for display
imageUrl.value = URL.createObjectURL(blob);
// → "blob:http://localhost:5173/abc-123-def"
}📍 concepts/MediaManagement/MediaManagement.ts (lines 346-365)
async _serveImage({ userId, mediaId }) {
// 1. Get file metadata from database
const mediaFile = await this.mediaFiles.findOne({
_id: mediaId,
owner: userId // ← Security check!
});
// 2. Read actual file from disk
const fullPath = `./uploads/${userId}${mediaFile.filePath}/${mediaFile.filename}`;
const fileData = await Deno.readFile(fullPath);
// 3. Return binary data
return {
data: fileData, // Uint8Array of image bytes
contentType: "image/jpg" // Tell browser it's an image
};
}📍 src/components/MediaCard.vue (template)
<img :src="imageUrl" alt="LionKing.jpg" />
<!-- src = "blob:http://localhost:5173/abc-123" -->
<!-- Browser automatically displays the image! ✅ -->| Where | What | Purpose |
|---|---|---|
| Disk | ./uploads/{userId}/{path}/{file} |
Persistent storage |
| Database | Metadata (filename, path, owner) | Lookup & security |
| Browser | Blob URL (blob:http://...) |
Temporary display |
Users see previews because:
- ✅ Images are saved to disk during upload
- ✅ Frontend requests them with userId + mediaId
- ✅ Backend verifies ownership and reads from disk
- ✅ Frontend creates blob URL and displays in
<img>tag
I created a test file (test-ai-extraction.ts) that you can run on your backend!
- Copy files to your backend:
# In your backend directory
cp /path/to/TEPKonjacFrontEnd/Spirited\ away\ movie\ poster.jpg ./
cp /path/to/TEPKonjacFrontEnd/src/gemini-llm.ts ./src/
cp /path/to/TEPKonjacFrontEnd/test-ai-extraction.ts ./- Make sure you have
.envfile:
cat > .env << 'EOF'
GEMINI_API_KEY=AIzaSyDWBm5_rBO_zcx_liCFcnwPScPX5OOu00o
GEMINI_MODEL=gemini-2.5-flash
EOF- Run the test:
deno run --allow-read --allow-env test-ai-extraction.ts- Start your backend:
cd /path/to/backend
deno run --allow-net --allow-read --allow-write --allow-env src/concept_server.ts- Start your frontend:
cd /path/to/TEPKonjacFrontEnd
npm run dev-
Upload the Spirited Away poster:
- Log in to the UI
- Upload
Spirited away movie poster.jpgto any folder - Wait for it to appear in gallery
-
Test AI extraction:
- Click on the image
- Click "Edit Image" button
- In the image editor, click "Auto Extract Text"
- Watch the backend terminal for logs
-
Expected backend logs:
🤖 Starting text extraction for media: xxx-xxx-xxx
📂 Constructed path: ./uploads/.../SpiritedAway.jpg
📷 Reading image from: ./uploads/.../SpiritedAway.jpg
✅ Image file read successfully: 150000 bytes
🤖 Calling Gemini AI for text extraction
✅ Gemini response received
📝 Parsed 12 text blocks
✅ Created 12 extraction results
- Expected UI:
- Extraction list updates automatically
- Shows text like:
千と千尋の神隠し Spirited Away Hayao Miyazaki Studio Ghibli ...
For the Spirited Away poster, expect to see:
- Japanese text: 千と千尋の神隠し (Sen to Chihiro no Kamikakushi)
- English text: Spirited Away
- Names: Hayao Miyazaki (宮崎駿)
- Studio: Studio Ghibli (スタジオジブリ)
- Awards: Academy Award text (if visible)
- Credits: Production companies, distributors
- Date: 2001 or release year
The exact output depends on which version of the poster you have!
If extraction doesn't work, check:
- Is image on disk?
cd backend
find uploads -name "*.jpg"- Is Gemini API key valid?
cat .env | grep GEMINI_API_KEY- Check backend logs:
❌ Look for: "Error reading image file"
❌ Look for: "Gemini API error"
✅ Look for: "Gemini response received"
- Is
result.textaccessed correctly?
// ✅ Correct (in gemini-llm.ts)
const text = result.text;
// ❌ Wrong
const text = await result.text();I've created comprehensive guides for you:
-
IMAGE_STORAGE_AND_PREVIEW_FLOW.md- Complete diagram of upload → storage → preview flow
- Code locations and explanations
- Security model
- File system structure
-
AI_EXTRACTION_TEST_GUIDE.md- Step-by-step testing instructions
- Multiple test scenarios
- Debugging checklist
- Expected outputs
-
IMAGE_UPLOAD_DEBUG_GUIDE.md- Troubleshooting guide
- Common issues and fixes
- Success indicators
-
test-ai-extraction.ts- Ready-to-run test script
- 3 different extraction tests
- Formatted output
A: ./uploads/{userId}/{path}/{filename} on backend disk
A:
- Frontend fetches from
_serveImageendpoint - Backend reads file from disk
- Frontend creates blob URL
- Browser displays in
<img>tag
A:
- Quick: Run
test-ai-extraction.tswith Deno - Full: Upload image → Click "Edit Image" → "Auto Extract Text"
A: Check if:
- ✅
gemini-llm.tsuses.textproperty (not.text()) - ✅
.envhasGEMINI_API_KEY - ✅ Image exists on disk
- ✅ Backend logs show "Gemini response received"
-
Copy updated files to backend:
src/gemini-llm.ts(fixed.textaccess)concepts/MediaManagement/MediaManagement.ts(added logging)concepts/TextExtraction/TextExtraction.ts(path normalization)
-
Restart backend
-
Test upload:
- Upload Spirited Away poster
- Check it appears in gallery (preview works!)
-
Test AI extraction:
- Click "Edit Image"
- Click "Auto Extract Text"
- See extracted text appear!
Everything is ready to work! Just copy the files and test! 🎉