feat: the model's short name up to 12 characters

This commit is contained in:
redtea committed 2026-10-17 15:33:19 +03:00
1 parent 6c1da79b4a
commit 37ef886503
5 files changed
+9 -9

No files matched your search

+1 -1
View File
@@ -46,7 +46,7 @@
показать или записать файл - всё с правами пользователя. Плашка «Агент» - делает шаги сам, показать или записать файл - всё с правами пользователя. Плашка «Агент» - делает шаги сам,
спрашивая перед каждой командой и записью; «Думать» - сначала рассуждает (свёрнуто над спрашивая перед каждой командой и записью; «Думать» - сначала рассуждает (свёрнуто над
ответом). Модели задаёт администратор в «Настройки › ИИ-ассистент»: их может быть несколько, у каждой своё ответом). Модели задаёт администратор в «Настройки › ИИ-ассистент»: их может быть несколько, у каждой своё
название в чате (и короткое, до 9 символов, - для кнопки выбора), тип - Gradio (по умолчанию - прокси к Qwen Omni), OpenAI-совместимый API или Anthropic, название в чате (и короткое, до 12 символов, - для кнопки выбора), тип - Gradio (по умолчанию - прокси к Qwen Omni), OpenAI-совместимый API или Anthropic,
адрес, модель и ключ (хранится на сервере, в браузер не уходит). Если моделей несколько, в чате рядом с адрес, модель и ключ (хранится на сервере, в браузер не уходит). Если моделей несколько, в чате рядом с
плашками появляется выбор, первая - по умолчанию; выбор запоминается за пользователем. В «Аккаунт › Вид» плашками появляется выбор, первая - по умолчанию; выбор запоминается за пользователем. В «Аккаунт › Вид»
можно показывать выбор всегда. можно показывать выбор всегда.
+3 -3
View File
@@ -44,7 +44,7 @@ const (
func aiUserKey(id int64) string { return "ai_user_" + strconv.FormatInt(id, 10) } func aiUserKey(id int64) string { return "ai_user_" + strconv.FormatInt(id, 10) }
// aiConfig is one model the assistant can ask: its id and the names the chat // aiConfig is one model the assistant can ask: its id and the names the chat
// shows (Short, up to 9 characters, on the button of the model in use), the // shows (Short, up to 12 characters, on the button of the model in use), the
// kind of API, its address, model and key. // kind of API, its address, model and key.
type aiConfig struct { type aiConfig struct {
ID string `json:"id"` ID string `json:"id"`
@@ -77,8 +77,8 @@ func (c aiConfig) tidy() aiConfig {
c.Type = "gradio" c.Type = "gradio"
} }
c.Name, c.Model, c.Key = strings.TrimSpace(c.Name), strings.TrimSpace(c.Model), strings.TrimSpace(c.Key) c.Name, c.Model, c.Key = strings.TrimSpace(c.Name), strings.TrimSpace(c.Model), strings.TrimSpace(c.Key)
if r := []rune(strings.TrimSpace(c.Short)); len(r) > 9 { if r := []rune(strings.TrimSpace(c.Short)); len(r) > 12 {
c.Short = strings.TrimSpace(string(r[:9])) c.Short = strings.TrimSpace(string(r[:12]))
} else { } else {
c.Short = string(r) c.Short = string(r)
} }
+2 -2
View File
@@ -60,11 +60,11 @@ func TestAIModelsSaveAndList(t *testing.T) {
{"", "", "", "", "", "", ""}, // an empty card is dropped {"", "", "", "", "", "", ""}, // an empty card is dropped
}) })
ms := s.aiModels() ms := s.aiModels()
if len(ms) != 2 || ms[0].Name != "Qwen" || ms[1].Name != "Claude" || ms[1].Key != "secret-key" || ms[0].ID == "" || ms[0].ID == ms[1].ID || ms[1].Short != "Сонет 4.5" { if len(ms) != 2 || ms[0].Name != "Qwen" || ms[1].Name != "Claude" || ms[1].Key != "secret-key" || ms[0].ID == "" || ms[0].ID == ms[1].ID || ms[1].Short != "Сонет 4.5 дл" {
t.Fatalf("models: %+v", ms) t.Fatalf("models: %+v", ms)
} }
body := get(t, s, c, "/api/panel/ai").Body.String() body := get(t, s, c, "/api/panel/ai").Body.String()
if strings.Contains(body, "secret-key") || !strings.Contains(body, `"name":"Claude"`) || !strings.Contains(body, `"short":"Сонет 4.5"`) || !strings.Contains(body, ms[1].ID) { if strings.Contains(body, "secret-key") || !strings.Contains(body, `"name":"Claude"`) || !strings.Contains(body, `"short":"Сонет 4.5 дл"`) || !strings.Contains(body, ms[1].ID) {
t.Fatalf("state: %s", body) t.Fatalf("state: %s", body)
} }
// Saved again with its id, a model keeps it. // Saved again with its id, a model keeps it.
+1 -1
View File
@@ -963,7 +963,7 @@ dialog.uidlg { width:fit-content; min-width:min(300px, 92vw); max-width:min(560p
pointed at; the list opens upwards, as a small card. */ pointed at; the list opens upwards, as a small card. */
.ai-mpick { position:relative; margin-left:auto; min-width:0; } .ai-mpick { position:relative; margin-left:auto; min-width:0; }
.ai-mpick[hidden] { display:none; } .ai-mpick[hidden] { display:none; }
/* A short name (9 characters at most) is shown whole. */ /* A short name (12 characters at most) is shown whole. */
.ai-mpick.short { flex-shrink:0; } .ai-mpick.short { flex-shrink:0; }
.ai-mbtn { display:inline-flex; align-items:center; gap:3px; max-width:100%; height:28px; padding:0 5px 0 7px; border:0; border-radius:8px; .ai-mbtn { display:inline-flex; align-items:center; gap:3px; max-width:100%; height:28px; padding:0 5px 0 7px; border:0; border-radius:8px;
background:transparent; color:var(--muted); font-size:12.5px; font-weight:500; transition:background-color .15s, color .15s; } background:transparent; color:var(--muted); font-size:12.5px; font-weight:500; transition:background-color .15s, color .15s; }
+2 -2
View File
@@ -157,7 +157,7 @@
{{range .AIModels}} <fieldset class="ai-model"> {{range .AIModels}} <fieldset class="ai-model">
<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="9" placeholder="до 9" title="Название на кнопке модели в чате, до 9 символов"></label> <label class="ai-model-short">Коротко<input type="text" name="m_short" value="{{.Short}}" maxlength="12" placeholder="до 12" title="Название на кнопке модели в чате, до 12 символов"></label>
<button type="button" class="btn ghost small close ai-model-del" title="Удалить модель" aria-label="Удалить модель">Удалить</button></div> <button type="button" class="btn ghost small close ai-model-del" title="Удалить модель" aria-label="Удалить модель">Удалить</button></div>
<label>Тип API<select name="m_type"> <label>Тип API<select name="m_type">
<option value="gradio"{{if eq .Type "gradio"}} selected{{end}}>Gradio (Qwen Omni через прокси)</option> <option value="gradio"{{if eq .Type "gradio"}} selected{{end}}>Gradio (Qwen Omni через прокси)</option>
@@ -174,7 +174,7 @@
<fieldset class="ai-model"> <fieldset class="ai-model">
<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="9" placeholder="до 9" title="Название на кнопке модели в чате, до 9 символов"></label> <label class="ai-model-short">Коротко<input type="text" name="m_short" value="" maxlength="12" placeholder="до 12" title="Название на кнопке модели в чате, до 12 символов"></label>
<button type="button" class="btn ghost small close ai-model-del" title="Удалить модель" aria-label="Удалить модель">Удалить</button></div> <button type="button" class="btn ghost small close ai-model-del" title="Удалить модель" aria-label="Удалить модель">Удалить</button></div>
<label>Тип API<select name="m_type"> <label>Тип API<select name="m_type">
<option value="gradio">Gradio (Qwen Omni через прокси)</option> <option value="gradio">Gradio (Qwen Omni через прокси)</option>