Выбор мозгов: Ollama или веб-версия DeepSeek
Разбор свободных фраз, теги и подсказку по фотографии теперь может делать не своя модель, а веб-версия DeepSeek. Своего клиента DeepSeek здесь нет и не нужно: рядом живёт отдельный сервис deepseek-api, который держит вкладку браузера и отдаёт наружу протокол Ollama. Для hlamingo вся разница сводится к тому, по какому адресу спрашивать. HLAMINGO_ENGINE=ollama|deepseek HLAMINGO_DEEPSEEK_HOST=http://localhost:11500 Эмбеддинги в выбор не входят и всегда идут на OLLAMA_HOST: веб-чат векторов не отдаёт, а подделать их нечем. Поэтому адрес Ollama нужен и в режиме DeepSeek, иначе молча умирает поиск по смыслу. Предохранитель в ollama.py теперь считается по каждому адресу отдельно. Иначе лежачая Ollama гасила бы живой DeepSeek: неудача по одному адресу на полминуты объявляла мёртвым и второй. Проба адреса больше не подменяет config.OLLAMA_HOST на время проверки — адрес передаётся явным параметром. Запросы идут в несколько потоков, и подмена задевала чужие. В настройках (вкладка "шестерёнка") появился выбор "Кто думает", поле адреса DeepSeek и вторая кнопка проверки связи. Списка моделей теперь два: для разбора фраз — у выбранного движка, для поиска по смыслу — у Ollama. Замер на разборе фразы и генерации тегов: Ollama qwen3:8b 2-3 с, DeepSeek 8-12 с плюс 15 с на подъём браузера при первом запросе. Тесты: 115 успешно, 0 провалено. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1ae98b0b6f
commit
4d8175931b
+103
-31
@@ -239,27 +239,50 @@
|
||||
|
||||
<!-- ================= Настройки ================= -->
|
||||
<section class="tab" id="tab-setup">
|
||||
<p class="hint" id="setupIntro">Здесь настраивается связь с Ollama — это «мозги»
|
||||
помощника. Без неё всё остальное работает, но не будет разбора фраз,
|
||||
автоматических тегов и поиска по смыслу.</p>
|
||||
<p class="hint" id="setupIntro">Здесь настраиваются «мозги» помощника. Без них
|
||||
всё остальное работает, но не будет разбора свободных фраз, автоматических
|
||||
тегов и подсказок по фотографии.</p>
|
||||
|
||||
<label>Кто думает</label>
|
||||
<select id="cfgEngine">
|
||||
<option value="ollama">Ollama — своя модель на своём компьютере</option>
|
||||
<option value="deepseek">DeepSeek — веб-версия через браузер</option>
|
||||
</select>
|
||||
<p class="hint">Ollama работает без интернета и ни о чём никому не сообщает.
|
||||
DeepSeek умнее, но отвечает медленнее, требует открытого браузера
|
||||
с выполненным входом, и ваши фразы уходят на его серверы.</p>
|
||||
|
||||
<div id="deepseekRow" hidden>
|
||||
<label>Адрес сервиса DeepSeek</label>
|
||||
<input type="text" id="cfgDeepseekHost" placeholder="http://localhost:11500"
|
||||
autocomplete="off" inputmode="url">
|
||||
<p class="hint">Это отдельная программа <code>deepseek-api</code>: она держит
|
||||
вкладку с чатом DeepSeek и притворяется Ollama. Её нужно запустить
|
||||
(<code>start.cmd</code>) — сама она не поднимется.</p>
|
||||
<div class="row mt">
|
||||
<button class="act ghost" id="cfgProbeDeepseek">Проверить DeepSeek</button>
|
||||
</div>
|
||||
<div id="cfgProbeDeepseekResult"></div>
|
||||
</div>
|
||||
|
||||
<label>Адрес Ollama</label>
|
||||
<input type="text" id="cfgHost" placeholder="http://192.168.1.50:11434" autocomplete="off"
|
||||
inputmode="url">
|
||||
<p class="hint">Если Ollama на этом же компьютере — <code>http://localhost:11434</code>.
|
||||
Если на другой машине — её адрес в локальной сети.</p>
|
||||
<p class="hint" id="cfgHostHint">Если Ollama на этом же компьютере —
|
||||
<code>http://localhost:11434</code>. Если на другой машине — её адрес
|
||||
в локальной сети.</p>
|
||||
<div class="row mt">
|
||||
<button class="act ghost" id="cfgProbe">Проверить связь</button>
|
||||
</div>
|
||||
<div id="cfgProbeResult"></div>
|
||||
|
||||
<label>Модель для разбора фраз</label>
|
||||
<label id="cfgChatLabel">Модель для разбора фраз</label>
|
||||
<select id="cfgChat"></select>
|
||||
<label>Модель для поиска по смыслу</label>
|
||||
<select id="cfgEmbed"></select>
|
||||
<p class="hint">Список заполняется моделями, которые реально установлены.
|
||||
Нужных нет — на машине с Ollama: <code>ollama pull qwen2.5:7b</code>
|
||||
и <code>ollama pull nomic-embed-text</code>.</p>
|
||||
<p class="hint">Список заполняется моделями, которые реально есть.
|
||||
Нужных нет — на машине с Ollama: <code>ollama pull qwen3:8b</code>
|
||||
и <code>ollama pull bge-m3</code>.</p>
|
||||
|
||||
<div class="mt"><button class="act" id="cfgSave">Сохранить</button></div>
|
||||
<p class="hint" id="cfgWhere"></p>
|
||||
@@ -346,10 +369,12 @@ api('/api/health').then(h => {
|
||||
if (h.ollama) return;
|
||||
const b = $('#banner');
|
||||
b.style.display = 'block';
|
||||
const who = h.engine === 'deepseek' ? 'сервис DeepSeek' : 'Ollama';
|
||||
b.textContent = h.fresh
|
||||
? 'Похоже, помощник только установлен, а Ollama по адресу ' + h.host + ' не отвечает. '
|
||||
: 'ИИ недоступен (' + h.host + '). Кнопки работают полностью; не будет только '
|
||||
+ 'тегов и разбора свободных фраз. ';
|
||||
? 'Похоже, помощник только установлен, а ' + who + ' по адресу ' + h.host
|
||||
+ ' не отвечает. '
|
||||
: 'ИИ недоступен: ' + who + ' по адресу ' + h.host + ' молчит. Кнопки работают '
|
||||
+ 'полностью; не будет только тегов и разбора свободных фраз. ';
|
||||
const link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.textContent = 'Настроить →';
|
||||
@@ -362,13 +387,21 @@ api('/api/health').then(h => {
|
||||
// ---------- настройки ----------
|
||||
async function loadConfig() {
|
||||
const d = await api('/api/config');
|
||||
$('#cfgEngine').value = d.engine || 'ollama';
|
||||
$('#cfgHost').value = d.ollama_host || '';
|
||||
fillModels(d.models || [], d.chat_model, d.embed_model);
|
||||
$('#cfgDeepseekHost').value = d.deepseek_host || '';
|
||||
showEngine();
|
||||
fillModels(d.models || [], d.embed_models || [], d.chat_model, d.embed_model);
|
||||
$('#cfgWhere').textContent = 'Настройки хранятся в файле ' + d.env_file
|
||||
+ ' — он не попадает в репозиторий.';
|
||||
if (!d.available) {
|
||||
$('#cfgProbeResult').innerHTML =
|
||||
'<div class="card"><div class="addr taken">Ollama по этому адресу не отвечает.</div></div>';
|
||||
// Ollama нужна всегда: даже когда думает DeepSeek, векторы для поиска
|
||||
// по смыслу считает она — веб-чат их не отдаёт.
|
||||
$('#cfgProbeResult').innerHTML = d.embed_available ? ''
|
||||
: '<div class="card"><div class="addr taken">Ollama по этому адресу не отвечает.</div></div>';
|
||||
if (d.engine === 'deepseek') {
|
||||
$('#cfgProbeDeepseekResult').innerHTML = d.available ? ''
|
||||
: '<div class="card"><div class="addr taken">Сервис DeepSeek не отвечает. '
|
||||
+ 'Запущен ли deepseek-api и открыт ли браузер с чатом?</div></div>';
|
||||
}
|
||||
showBackupState(d.backup || {});
|
||||
}
|
||||
@@ -460,30 +493,67 @@ $('#archiveInput').onchange = async () => {
|
||||
|
||||
// Модель выбирается из установленных; текущее значение показываем даже если
|
||||
// его нет в списке — иначе выбор молча сбросился бы на чужую модель.
|
||||
function fillModels(models, chat, embed) {
|
||||
const opts = extra => ['<option value="">— не задана —</option>']
|
||||
.concat((models.length ? models : []).map(m =>
|
||||
'<option value="' + esc(m) + '">' + esc(m) + '</option>'))
|
||||
.concat(extra && !models.includes(extra)
|
||||
// Списков два: разбор фраз может считать DeepSeek, а векторы для поиска
|
||||
// по смыслу — всегда настоящая Ollama.
|
||||
function fillModels(chatModels, embedModels, chat, embed) {
|
||||
const opts = (list, extra) => ['<option value="">— не задана —</option>']
|
||||
.concat(list.map(m => '<option value="' + esc(m) + '">' + esc(m) + '</option>'))
|
||||
.concat(extra && !list.includes(extra)
|
||||
? ['<option value="' + esc(extra) + '">' + esc(extra) + ' (не установлена)</option>'] : [])
|
||||
.join('');
|
||||
$('#cfgChat').innerHTML = opts(chat);
|
||||
$('#cfgEmbed').innerHTML = opts(embed);
|
||||
$('#cfgChat').innerHTML = opts(chatModels, chat);
|
||||
$('#cfgEmbed').innerHTML = opts(embedModels, embed);
|
||||
$('#cfgChat').value = chat || '';
|
||||
$('#cfgEmbed').value = embed || '';
|
||||
}
|
||||
|
||||
$('#cfgProbe').onclick = async () => {
|
||||
const btn = $('#cfgProbe');
|
||||
btn.disabled = true; btn.textContent = 'Проверяю…';
|
||||
// Поле адреса DeepSeek показываем только когда он выбран, а подпись у Ollama
|
||||
// меняем: в режиме DeepSeek она нужна уже не для мыслей, а только для поиска.
|
||||
function showEngine() {
|
||||
const ds = $('#cfgEngine').value === 'deepseek';
|
||||
$('#deepseekRow').hidden = !ds;
|
||||
$('#cfgHostHint').innerHTML = ds
|
||||
? 'Нужна и в этом режиме: поиск по смыслу считает только Ollama — веб-чат '
|
||||
+ 'векторов не отдаёт. Если её нет, останется поиск по названию и тегам.'
|
||||
: 'Если Ollama на этом же компьютере — <code>http://localhost:11434</code>. '
|
||||
+ 'Если на другой машине — её адрес в локальной сети.';
|
||||
$('#cfgChatLabel').textContent = ds
|
||||
? 'Чат DeepSeek для разбора фраз' : 'Модель для разбора фраз';
|
||||
}
|
||||
$('#cfgEngine').onchange = showEngine;
|
||||
|
||||
// Обе кнопки проверки делают одно и то же, отличаются только адресом и тем,
|
||||
// куда класть ответ.
|
||||
async function probeHost(btn, input, box, label) {
|
||||
const b = $(btn), was = $(btn).textContent;
|
||||
b.disabled = true; b.textContent = 'Проверяю…';
|
||||
try {
|
||||
const d = await api('/api/config/probe', {method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ollama_host: $('#cfgHost').value.trim()})});
|
||||
$('#cfgProbeResult').innerHTML = '<div class="card"><div class="addr'
|
||||
body: JSON.stringify({host: $(input).value.trim()})});
|
||||
$(box).innerHTML = '<div class="card"><div class="addr'
|
||||
+ (d.ok ? '' : ' taken') + '">' + esc(d.reply || '') + '</div></div>';
|
||||
if (d.ok) fillModels(d.models || [], $('#cfgChat').value, $('#cfgEmbed').value);
|
||||
} finally { btn.disabled = false; btn.textContent = 'Проверить связь'; }
|
||||
return d;
|
||||
} finally { b.disabled = false; b.textContent = was; }
|
||||
}
|
||||
|
||||
$('#cfgProbe').onclick = async () => {
|
||||
const d = await probeHost('#cfgProbe', '#cfgHost', '#cfgProbeResult');
|
||||
if (!d.ok) return;
|
||||
// Модели Ollama — всегда для поиска по смыслу; для разбора фраз они годятся
|
||||
// только когда думает она же.
|
||||
const ds = $('#cfgEngine').value === 'deepseek';
|
||||
fillModels(ds ? [...$('#cfgChat').options].map(o => o.value).filter(Boolean)
|
||||
: (d.models || []),
|
||||
d.models || [], $('#cfgChat').value, $('#cfgEmbed').value);
|
||||
};
|
||||
|
||||
$('#cfgProbeDeepseek').onclick = async () => {
|
||||
const d = await probeHost('#cfgProbeDeepseek', '#cfgDeepseekHost',
|
||||
'#cfgProbeDeepseekResult');
|
||||
if (d.ok) fillModels(d.models || [],
|
||||
[...$('#cfgEmbed').options].map(o => o.value).filter(Boolean),
|
||||
$('#cfgChat').value, $('#cfgEmbed').value);
|
||||
};
|
||||
|
||||
$('#cfgSave').onclick = async () => {
|
||||
@@ -492,7 +562,9 @@ $('#cfgSave').onclick = async () => {
|
||||
try {
|
||||
const d = await api('/api/config', {method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ollama_host: $('#cfgHost').value.trim(),
|
||||
body: JSON.stringify({engine: $('#cfgEngine').value,
|
||||
ollama_host: $('#cfgHost').value.trim(),
|
||||
deepseek_host: $('#cfgDeepseekHost').value.trim(),
|
||||
chat_model: $('#cfgChat').value,
|
||||
embed_model: $('#cfgEmbed').value,
|
||||
backup_dir: $('#cfgBackupDir').value.trim()})});
|
||||
|
||||
Reference in New Issue
Block a user