qwen3:8b вместо qwen2.5, отмена правки и пересоздание тегов

Замер на наших задачах (разбор 8 фраз + теги для 6 предметов):
  qwen2.5:7b  разбор 8/8, но теги «электротool Makita», «вoltage meter»
              (первая буква кириллическая), «многофункциональный мера» —
              модель думает по-английски и переводит на ходу;
  qwen3:8b    разбор 8/8, теги на нормальном русском.
Плата — примерно на треть медленнее на тегах. Теги важнее: по ним вещь
и находится.

Поддержка моделей-ризонеров: ollama.chat получил think=False. Без него
qwen3 тратит весь бюджет ответа на внутренний монолог и возвращает пустой
content — на первом прогоне модель выглядела нерабочей.

Разбор ответа с тегами укреплён: qwen3 иногда присылает объект
{"name":..., "synonyms":[...]} вместо списка строк, и он попадал в теги
целиком как строка. Теперь достаётся вложенный список, отсеиваются дубликаты
в разном регистре, вложенные объекты, bool и слишком длинные строки.

По замечаниям Алекса в правке предмета:
- кнопка «Отмена» — раньше открытую форму можно было только сохранить или
  удалить вещь целиком;
- кнопка «🔄 Пересоздать теги» — после смены модели теги остаются старыми,
  и это способ обновить их, не перезаводя вещь. Новый список показывается,
  но не сохраняется молча: его ещё можно поправить руками.

В README добавлен раздел «Какую модель выбрать» с замерами и требованиями
к своей модели для тех, кто ставит приложение себе.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 18:51:13 +03:00
co-authored by Claude Opus 5
parent 39c807d4de
commit 01d7f17f89
8 changed files with 197 additions and 16 deletions
+39 -2
View File
@@ -712,11 +712,16 @@ function itemEditor(it, cardEl) {
+ '<label>Заметка</label><input class="eNote">'
+ '<label>Теги для поиска</label><div class="tags eTags"></div>'
+ '<input class="eNewTag mt" placeholder="добавить тег и нажать Enter">'
+ '<div class="acts">'
+ '<button class="eRetag">🔄 Пересоздать теги</button>'
+ '</div>'
+ '<p class="editHint">Тег убирается нажатием на ×. По тегам вещь и находится, '
+ 'так что лишние лучше убрать, а свои — добавить.</p>'
+ 'так что лишние лучше убрать, а свои — добавить. «Пересоздать» просит модель '
+ 'придумать их заново — полезно после смены модели.</p>'
+ '<div class="acts">'
+ '<button class="eSave">Сохранить</button>'
+ '<button class="eDel warn">🗑 Удалить запись</button>'
+ '<button class="eCancel">Отмена</button>'
+ '<button class="eDel warn">🗑 Удалить</button>'
+ '</div>';
const q = s => box.querySelector(s);
@@ -768,6 +773,38 @@ function itemEditor(it, cardEl) {
} finally { btn.disabled = false; }
};
// Отмена: закрыть правку, вернув поля к тому, что было. Без неё случайное
// изменение можно было только сохранить или удалить вещь целиком.
q('.eCancel').onclick = () => {
q('.eName').value = it.name || '';
q('.eQty').value = it.qty || '';
q('.eNote').value = it.note || '';
tags = (it.aliases || []).slice();
drawTags();
q('.eNewTag').value = '';
box.hidden = true;
cardEl.classList.remove('wide');
};
q('.eRetag').onclick = async () => {
const btn = q('.eRetag');
btn.disabled = true;
const was = btn.textContent;
btn.textContent = 'Думаю…';
try {
const d = await api('/api/item/' + encodeURIComponent(it.id) + '/retag',
{method:'POST'});
say(d.reply);
if (d.ok && d.item) {
// Показываем сразу, но не сохраняем молча: список ещё можно поправить
// руками, а «Сохранить» закрепит его.
tags = (d.item.aliases || []).slice();
it.aliases = tags.slice();
drawTags();
}
} finally { btn.disabled = false; btn.textContent = was; }
};
q('.eDel').onclick = async () => {
if (!confirm('Удалить «' + (it.name || '') + '» из памяти?')) return;
const d = await api('/api/item/' + encodeURIComponent(it.id), {method:'DELETE'});