You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

100 lines
4.4 KiB

{% extends "base.html" %}
{% block title %}Настройки — LLM{% endblock %}
{% block content %}
<section class="{% if ui_variant == 'legacy' %}surface-card{% else %}rounded-2xl bg-white shadow-sm border border-ink-300/60{% endif %} p-6 max-w-2xl">
<div class="flex items-center gap-2">
<span class="material-symbols-outlined text-brand-600">settings</span>
<h1 class="text-2xl font-semibold">Настройки</h1>
</div>
<h2 class="mt-5 font-semibold {% if ui_variant == 'legacy' %}font-headline{% endif %}">Подключение к LLM</h2>
<p class="mt-1 text-sm text-ink-500">
Ключ задаётся в <code class="px-1 py-0.5 rounded bg-ink-100">.env</code> сервера
(общий, не на пользователя). Поддерживаются DeepSeek и OpenAI-совместимые API.
После изменения <code class="px-1 py-0.5 rounded bg-ink-100">.env</code> нужен рестарт процесса.
</p>
<dl class="mt-4 grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-2 text-sm">
<dt class="text-ink-500">Статус ключа</dt>
<dd>
{% if configured %}
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-green-50 text-green-700 border border-green-200">
<span class="material-symbols-outlined text-base">check_circle</span> Задан
</span>
{% else %}
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-red-50 text-red-700 border border-red-200">
<span class="material-symbols-outlined text-base">error</span> Не задан
</span>
{% endif %}
</dd>
<dt class="text-ink-500">Провайдер</dt>
<dd>{{ provider or '—' }}</dd>
<dt class="text-ink-500">Модель</dt>
<dd>{{ model or '—' }}</dd>
<dt class="text-ink-500">Base URL</dt>
<dd class="break-all">{{ base_url or '—' }}</dd>
</dl>
{% if not configured %}
<div class="mt-5 rounded-lg bg-ink-100/60 border border-ink-300/60 p-4 text-sm">
<p class="font-medium">Как задать ключ</p>
<pre class="mt-2 text-xs whitespace-pre-wrap font-mono">DEEPSEEK_API_KEY=sk-...
# либо
OPENAI_API_KEY=sk-...
# опционально:
# LLM_BASE_URL=https://api.deepseek.com/v1
# LLM_MODEL=deepseek-chat</pre>
<p class="mt-2 text-ink-500">
Файл: <code>flask_app/.env</code>. После сохранения — рестарт процесса.
</p>
</div>
{% endif %}
<div class="mt-5 flex items-center gap-3">
<button id="btn-ping"
class="{% if ui_variant == 'legacy' %}btn btn-primary{% else %}inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-brand-600 hover:bg-brand-700 text-white text-sm{% endif %}">
<span class="material-symbols-outlined text-base">cable</span>
Проверить подключение
</button>
<span id="ping-status" class="text-sm text-ink-500"></span>
</div>
<div id="ping-result" class="mt-4 hidden text-sm rounded-lg p-3 border"></div>
</section>
{% endblock %}
{% block scripts %}
<script>
(() => {
const btn = document.getElementById('btn-ping');
const status = document.getElementById('ping-status');
const result = document.getElementById('ping-result');
btn.addEventListener('click', async () => {
status.textContent = 'Запрос…';
btn.disabled = true;
try {
const r = await fetch('/api/llm/ping', { method: 'POST' });
const d = await r.json();
result.classList.remove('hidden', 'bg-green-50', 'border-green-200', 'text-green-800',
'bg-red-50', 'border-red-200', 'text-red-800');
if (d.ok) {
result.classList.add('bg-green-50', 'border-green-200', 'text-green-800');
result.innerHTML = `<b>OK</b> · ${d.provider} / ${d.model} · ${d.latencyMs} мс`
+ (d.sample ? `<br><span class="text-xs opacity-80">Ответ: ${d.sample.replace(/</g,'&lt;')}</span>` : '');
} else {
result.classList.add('bg-red-50', 'border-red-200', 'text-red-800');
result.innerHTML = `<b>Ошибка</b> · ${d.code || ''}<br>${(d.error || '').replace(/</g,'&lt;')}`;
}
} catch (e) {
result.classList.remove('hidden');
result.classList.add('bg-red-50', 'border-red-200', 'text-red-800');
result.textContent = e.message || 'Сбой запроса.';
} finally {
btn.disabled = false;
status.textContent = '';
}
});
})();
</script>
{% endblock %}