Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
114f8b1e1b | ||
|
|
a1604206ee | ||
|
|
a107745355 | ||
|
|
dcbc602d88 |
No files matched your search
+1
-1
@@ -1 +1 @@
|
|||||||
0.2.2
|
0.2.3
|
||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -1126,8 +1126,15 @@ body.no-motion *, body.no-motion *::before, body.no-motion *::after, body.no-mot
|
|||||||
.ai-form .ai-sub { margin:10px 0 0; font-size:15px; }
|
.ai-form .ai-sub { margin:10px 0 0; font-size:15px; }
|
||||||
/* Настройки: the models, a card each. */
|
/* Настройки: the models, a card each. */
|
||||||
.ai-models { display:grid; gap:10px; }
|
.ai-models { display:grid; gap:10px; }
|
||||||
.ai-model { display:grid; gap:10px; min-width:0; margin:0; padding:12px; border:1px solid var(--border); border-radius:10px; }
|
.ai-model, .ai-search { min-width:0; margin:0; border:1px solid var(--border); border-radius:10px; }
|
||||||
.ai-model-head { display:flex; align-items:flex-end; gap:8px; margin:12px 0 4px; }
|
/* Folded to a line: its name, and its kind and model quieter. */
|
||||||
|
.ai-model > summary.ai-model-sum, .ai-search > summary.ai-model-sum { min-width:0; padding:11px 12px; border-radius:10px; }
|
||||||
|
.ai-model > summary.ai-model-sum:hover, .ai-search > summary.ai-model-sum:hover { background:#151a21; }
|
||||||
|
.ai-model-title { flex:none; color:var(--text); font-size:14px; font-weight:550; }
|
||||||
|
.ai-model-sub { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--muted); font-size:12.5px; }
|
||||||
|
.ai-model-sub:not(:empty)::before { content:'· '; }
|
||||||
|
.ai-model-body { display:grid; gap:10px; min-width:0; padding:0 12px 12px; }
|
||||||
|
.ai-model-head { display:flex; align-items:flex-end; gap:8px; margin:2px 0 4px; }
|
||||||
.ai-model-head .ai-model-name { flex:1; min-width:0; margin:0; }
|
.ai-model-head .ai-model-name { flex:1; min-width:0; margin:0; }
|
||||||
.ai-model-head .ai-model-short { flex:none; width:96px; margin:0; }
|
.ai-model-head .ai-model-short { flex:none; width:96px; margin:0; }
|
||||||
.ai-model-del { flex:none; height:38px; }
|
.ai-model-del { flex:none; height:38px; }
|
||||||
|
|||||||
@@ -36,6 +36,19 @@
|
|||||||
const del = ev.target.closest('.ai-model-del');
|
const del = ev.target.closest('.ai-model-del');
|
||||||
if (del && list.children.length > 1) del.closest('.ai-model').remove();
|
if (del && list.children.length > 1) del.closest('.ai-model').remove();
|
||||||
});
|
});
|
||||||
|
// A folded card's line follows its fields: the name, the kind and model.
|
||||||
|
list.addEventListener('input', ev => {
|
||||||
|
const card = ev.target.closest('.ai-model');
|
||||||
|
if (!card) return;
|
||||||
|
const v = n => card.querySelector('[name=' + n + ']').value.trim();
|
||||||
|
card.querySelector('.ai-model-title').textContent = v('m_name') || 'Модель';
|
||||||
|
card.querySelector('.ai-model-sub').textContent = v('m_type') + (v('m_model') ? ' · ' + v('m_model') : '');
|
||||||
|
});
|
||||||
|
// A field to fix in a folded card: the card opens to show it.
|
||||||
|
list.closest('form').addEventListener('invalid', ev => {
|
||||||
|
const d = ev.target.closest('details');
|
||||||
|
if (d) d.open = true;
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,22 +10,19 @@
|
|||||||
height:40px; box-sizing:border-box; font-size:14px; line-height:1; margin:0;
|
height:40px; box-sizing:border-box; font-size:14px; line-height:1; margin:0;
|
||||||
}
|
}
|
||||||
.frow select { min-width:0; width:100%; padding:0 30px 0 12px; background-position:right 10px center; }
|
.frow select { min-width:0; width:100%; padding:0 30px 0 12px; background-position:right 10px center; }
|
||||||
/* The ufw switch hangs under the button, centred on it, level with the note. */
|
/* Under the fields: the note, and the ufw switch at the right, under the
|
||||||
.frow .act { position:relative; }
|
button - on a line of its own when the room runs out, never over the note. */
|
||||||
.frow .act label.check {
|
.under { display:flex; align-items:flex-start; justify-content:space-between; gap:8px 16px; flex-wrap:wrap;
|
||||||
position:absolute; top:100%; left:50%; transform:translateX(-50%);
|
min-height:20px; margin:12px 0 0; font-size:13px; line-height:20px; }
|
||||||
display:inline-flex; align-items:center; gap:8px; height:20px; margin:12px 0 0;
|
.under #note { flex:1 1 240px; min-width:0; }
|
||||||
color:var(--text); font-size:14px; cursor:pointer;
|
.under label.check { flex:none; display:inline-flex; align-items:center; gap:8px; height:20px; margin:0 0 0 auto;
|
||||||
}
|
color:var(--text); font-size:14px; cursor:pointer; white-space:nowrap; }
|
||||||
.under { min-height:20px; margin:12px 110px 0 0; font-size:13px; line-height:20px; }
|
|
||||||
/* Phones: two fields a line, the label and the button each a line of their own. */
|
/* Phones: two fields a line, the label and the button each a line of their own. */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.frow .fld, .frow .fld.addr { width:calc(50% - 6px); }
|
.frow .fld, .frow .fld.addr { width:calc(50% - 6px); }
|
||||||
.frow .fld.grow { flex:1 1 100%; }
|
.frow .fld.grow { flex:1 1 100%; }
|
||||||
.frow .act { display:flex; align-items:center; gap:16px; width:100%; }
|
.frow .act { width:100%; }
|
||||||
.frow .act button { flex:1; }
|
.frow .act button { width:100%; }
|
||||||
.frow .act label.check { position:static; transform:none; margin:0; }
|
|
||||||
.under { margin-right:0; }
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
@@ -94,14 +91,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="act">
|
<div class="act">
|
||||||
<button type="submit">Создать</button>
|
<button type="submit">Создать</button>
|
||||||
{{if .UfwEnabled}}
|
|
||||||
<label class="check" title="Порт не будет открыт в ufw, и при удалении правила в ufw ничего не изменится">
|
|
||||||
<input type="checkbox" name="no_ufw" value="1"> Без ufw
|
|
||||||
</label>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="under"><span class="muted" id="note"></span></div>
|
<div class="under"><span class="muted" id="note"></span>
|
||||||
|
{{if .UfwEnabled}}
|
||||||
|
<label class="check" title="Порт не будет открыт в ufw, и при удалении правила в ufw ничего не изменится">
|
||||||
|
<input type="checkbox" name="no_ufw" value="1"> Без ufw
|
||||||
|
</label>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -154,7 +154,8 @@
|
|||||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||||
<p class="muted" style="margin:0">Моделей может быть несколько - в чате появится выбор; первая - по умолчанию.</p>
|
<p class="muted" style="margin:0">Моделей может быть несколько - в чате появится выбор; первая - по умолчанию.</p>
|
||||||
<div class="ai-models">
|
<div class="ai-models">
|
||||||
{{range .AIModels}} <fieldset class="ai-model">
|
{{range .AIModels}} <details class="ai-model"><summary class="ai-model-sum"><span class="ai-model-title">{{if .Name}}{{.Name}}{{else}}Модель{{end}}</span><span class="ai-model-sub">{{.Type}}{{if .Model}} · {{.Model}}{{end}}</span></summary>
|
||||||
|
<div class="ai-model-body">
|
||||||
<input type="hidden" name="m_id" value="{{.ID}}">
|
<input type="hidden" name="m_id" value="{{.ID}}">
|
||||||
<div class="ai-model-head"><label class="ai-model-name">Название в чате<input type="text" name="m_name" value="{{.Name}}" placeholder="например, Claude"></label>
|
<div class="ai-model-head"><label class="ai-model-name">Название в чате<input type="text" name="m_name" value="{{.Name}}" placeholder="например, Claude"></label>
|
||||||
<label class="ai-model-short">Коротко<input type="text" name="m_short" value="{{.Short}}" maxlength="12" placeholder="до 12" title="Название на кнопке модели в чате, до 12 символов"></label>
|
<label class="ai-model-short">Коротко<input type="text" name="m_short" value="{{.Short}}" maxlength="12" placeholder="до 12" title="Название на кнопке модели в чате, до 12 символов"></label>
|
||||||
@@ -168,10 +169,11 @@
|
|||||||
<label>Модель<input type="text" name="m_model" value="{{.Model}}" placeholder="для OpenAI и Anthropic"></label>
|
<label>Модель<input type="text" name="m_model" value="{{.Model}}" placeholder="для OpenAI и Anthropic"></label>
|
||||||
<label>Ключ<span class="keybox"><input type="password" name="m_key" value="{{.Key}}" placeholder="не задан" autocomplete="off" spellcheck="false">
|
<label>Ключ<span class="keybox"><input type="password" name="m_key" value="{{.Key}}" placeholder="не задан" autocomplete="off" spellcheck="false">
|
||||||
<button type="button" class="btn ghost small icon sq" title="Показать" aria-label="Показать ключ" onclick="var i=this.previousElementSibling;i.type=i.type==='password'?'text':'password'"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg></button></span></label>
|
<button type="button" class="btn ghost small icon sq" title="Показать" aria-label="Показать ключ" onclick="var i=this.previousElementSibling;i.type=i.type==='password'?'text':'password'"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg></button></span></label>
|
||||||
</fieldset>
|
</div></details>
|
||||||
{{end}} </div>
|
{{end}} </div>
|
||||||
<template class="ai-model-new">
|
<template class="ai-model-new">
|
||||||
<fieldset class="ai-model">
|
<details class="ai-model" open><summary class="ai-model-sum"><span class="ai-model-title">Новая модель</span><span class="ai-model-sub"></span></summary>
|
||||||
|
<div class="ai-model-body">
|
||||||
<input type="hidden" name="m_id" value="">
|
<input type="hidden" name="m_id" value="">
|
||||||
<div class="ai-model-head"><label class="ai-model-name">Название в чате<input type="text" name="m_name" value="" placeholder="например, Claude"></label>
|
<div class="ai-model-head"><label class="ai-model-name">Название в чате<input type="text" name="m_name" value="" placeholder="например, Claude"></label>
|
||||||
<label class="ai-model-short">Коротко<input type="text" name="m_short" value="" maxlength="12" placeholder="до 12" title="Название на кнопке модели в чате, до 12 символов"></label>
|
<label class="ai-model-short">Коротко<input type="text" name="m_short" value="" maxlength="12" placeholder="до 12" title="Название на кнопке модели в чате, до 12 символов"></label>
|
||||||
@@ -185,11 +187,11 @@
|
|||||||
<label>Модель<input type="text" name="m_model" value="" placeholder="для OpenAI и Anthropic"></label>
|
<label>Модель<input type="text" name="m_model" value="" placeholder="для OpenAI и Anthropic"></label>
|
||||||
<label>Ключ<span class="keybox"><input type="password" name="m_key" value="" placeholder="не задан" autocomplete="off" spellcheck="false">
|
<label>Ключ<span class="keybox"><input type="password" name="m_key" value="" placeholder="не задан" autocomplete="off" spellcheck="false">
|
||||||
<button type="button" class="btn ghost small icon sq" title="Показать" aria-label="Показать ключ" onclick="var i=this.previousElementSibling;i.type=i.type==='password'?'text':'password'"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg></button></span></label>
|
<button type="button" class="btn ghost small icon sq" title="Показать" aria-label="Показать ключ" onclick="var i=this.previousElementSibling;i.type=i.type==='password'?'text':'password'"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg></button></span></label>
|
||||||
</fieldset>
|
</div></details>
|
||||||
</template>
|
</template>
|
||||||
<div><button type="button" class="btn ghost small ai-model-add">+ Добавить модель</button></div>
|
<div><button type="button" class="btn ghost small ai-model-add">+ Добавить модель</button></div>
|
||||||
<h3 class="ai-sub">Поиск в интернете</h3>
|
<details class="ai-search"><summary class="ai-model-sum"><span class="ai-model-title">Поиск в интернете</span><span class="ai-model-sub">Запасной - DuckDuckGo</span></summary>
|
||||||
<p class="muted" style="margin:0">Запасной - DuckDuckGo.</p>
|
<div class="ai-model-body">
|
||||||
<label>Поисковик<select name="ai_search" onchange="this.form.dataset.search = this.value">
|
<label>Поисковик<select name="ai_search" onchange="this.form.dataset.search = this.value">
|
||||||
<option value=""{{if eq .Search.Engine ""}} selected{{end}}>DuckDuckGo - бесплатно, без настройки</option>
|
<option value=""{{if eq .Search.Engine ""}} selected{{end}}>DuckDuckGo - бесплатно, без настройки</option>
|
||||||
<option value="searxng"{{if eq .Search.Engine "searxng"}} selected{{end}}>SearXNG - свой сервер</option>
|
<option value="searxng"{{if eq .Search.Engine "searxng"}} selected{{end}}>SearXNG - свой сервер</option>
|
||||||
@@ -199,6 +201,7 @@
|
|||||||
<label class="s-url">Адрес SearXNG<input type="url" name="ai_search_url" value="{{.Search.URL}}" placeholder="http://127.0.0.1:8888 (в settings.yml: search.formats - json)"></label>
|
<label class="s-url">Адрес SearXNG<input type="url" name="ai_search_url" value="{{.Search.URL}}" placeholder="http://127.0.0.1:8888 (в settings.yml: search.formats - json)"></label>
|
||||||
<label class="s-key">Ключ поисковика<span class="keybox"><input type="password" name="ai_search_key" value="{{.Search.Key}}" placeholder="не задан" autocomplete="off" spellcheck="false">
|
<label class="s-key">Ключ поисковика<span class="keybox"><input type="password" name="ai_search_key" value="{{.Search.Key}}" placeholder="не задан" autocomplete="off" spellcheck="false">
|
||||||
<button type="button" class="btn ghost small icon sq" title="Показать" aria-label="Показать ключ" onclick="var i=this.previousElementSibling;i.type=i.type==='password'?'text':'password'"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg></button></span></label>
|
<button type="button" class="btn ghost small icon sq" title="Показать" aria-label="Показать ключ" onclick="var i=this.previousElementSibling;i.type=i.type==='password'?'text':'password'"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg></button></span></label>
|
||||||
|
</div></details>
|
||||||
<div><button type="submit">Сохранить</button></div>
|
<div><button type="submit">Сохранить</button></div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user