101 lines
4.4 KiB
HTML
101 lines
4.4 KiB
HTML
{% 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,'<')}</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,'<')}`;
|
|
}
|
|
} 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 %}
|