Подсказка названия по фотографии
Фото выбрано в форме — приложение показывает его модели и предлагает название
с прочитанными надписями. «Подставить» переносит в поля, «Не надо» прячет.
Само не подставляется, и это принципиально: модели домысливают марки
и артикулы, когда надписей не видно. В замере клипсу для кабеля модель назвала
«конструктором LEGO Technic, 8376, 401 шт.». Запрет на выдумывание в промпте
помог: та же вещь стала «красной щёткой для посуды».
Смотрит gemma3:4b. Выбрана замером на настоящих снимках из базы:
gemma3:4b 3.5 с, отвечает всегда, надписи читает верно
(«Лампа светодиодная C35, Нейтральный белый, 9 Вт LED, E14»);
qwen3-vl:8b 11-32 с и часто пустой ответ — это ризонер, ему нужен бюджет
в 2000+ токенов, и уменьшение картинки не спасает.
Проверялась и гипотеза, что дело в размере снимка: у gemma3 разницы между
1280 и 512 px почти нет, у qwen3-vl время падает с 62 до 41 с, но ответ
всё равно пуст. Картинка всё же уменьшается до 768 px — незачем гонять лишнее.
Генерация тегов чинилась по ходу: на «Стабилизатор 7805 (TO-220)» модель
отвечала массивом объектов с пояснениями, не укладывалась в 256 токенов,
и JSON обрывался на полуслове — теги выходили пустыми. Бюджет поднят до 500,
промпт требует плоский массив строк, а из битого ответа строки теперь
вытаскиваются регуляркой: лучше несколько живых тегов, чем ничего.
Pillow добавлен в зависимости — для уменьшения картинки перед распознаванием.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -209,6 +209,7 @@
|
||||
<button class="act ghost small" id="formPhotoClear" style="flex:0 0 26%" hidden>Убрать</button>
|
||||
</div>
|
||||
<div id="formPhotoPreview" class="photo" hidden></div>
|
||||
<div id="guessBox" class="card" hidden></div>
|
||||
</div>
|
||||
|
||||
<div class="mt"><button class="act" id="goBtn">Положить</button></div>
|
||||
@@ -510,11 +511,53 @@ async function takeFormPhoto(input) {
|
||||
img.src = URL.createObjectURL(pendingPhoto);
|
||||
preview.appendChild(img);
|
||||
$('#formPhotoClear').hidden = false;
|
||||
recognizePending(); // фото есть — спросим модель, что на нём
|
||||
} catch (e) {
|
||||
say('Не смог прочитать это изображение: ' + e);
|
||||
clearPendingPhoto();
|
||||
}
|
||||
}
|
||||
|
||||
// Подсказка по фото: показываем, что увидела модель, и даём подставить это
|
||||
// в поле названия. Само не подставляется — модель домысливает бренды,
|
||||
// когда надписей не видно.
|
||||
async function recognizePending() {
|
||||
if (!pendingPhoto) return;
|
||||
const box = $('#guessBox');
|
||||
box.hidden = false;
|
||||
box.innerHTML = '<div class="meta">Смотрю на фото…</div>';
|
||||
try {
|
||||
const fd = new FormData();
|
||||
fd.append('photo', pendingPhoto, 'photo.jpg');
|
||||
const d = await api('/api/recognize', {method: 'POST', body: fd});
|
||||
if (!d.ok) { box.hidden = true; return; }
|
||||
|
||||
const g = d.guess;
|
||||
box.innerHTML = '<div class="meta">На фото похоже на:</div>'
|
||||
+ '<h3>' + esc(g.name) + '</h3>'
|
||||
+ (g.text ? '<div class="meta">Надписи: ' + esc(g.text) + '</div>' : '')
|
||||
+ (g.sure ? '' : '<div class="meta">Модель не уверена — проверьте.</div>');
|
||||
|
||||
const bar = document.createElement('div');
|
||||
bar.className = 'acts';
|
||||
const use = document.createElement('button');
|
||||
use.textContent = '✓ Подставить';
|
||||
use.onclick = () => {
|
||||
$('#itemName').value = g.name;
|
||||
if (g.text && !$('#itemNote').value) $('#itemNote').value = g.text;
|
||||
box.hidden = true;
|
||||
$('#itemName').focus();
|
||||
};
|
||||
const skip = document.createElement('button');
|
||||
skip.textContent = 'Не надо';
|
||||
skip.onclick = () => { box.hidden = true; };
|
||||
bar.appendChild(use);
|
||||
bar.appendChild(skip);
|
||||
box.appendChild(bar);
|
||||
} catch (e) {
|
||||
box.hidden = true;
|
||||
}
|
||||
}
|
||||
$('#formCamInput').onchange = () => takeFormPhoto($('#formCamInput'));
|
||||
$('#formGalInput').onchange = () => takeFormPhoto($('#formGalInput'));
|
||||
|
||||
@@ -524,6 +567,7 @@ function clearPendingPhoto() {
|
||||
$('#formPhotoPreview').hidden = true;
|
||||
$('#formPhotoPreview').innerHTML = '';
|
||||
$('#formPhotoClear').hidden = true;
|
||||
$('#guessBox').hidden = true;
|
||||
}
|
||||
|
||||
// Отправляет уже сжатый blob к готовой записи. Возвращает имя файла или null.
|
||||
|
||||
Reference in New Issue
Block a user