feat: assistant reads, lists and writes files and runs commands on any of the user's machines; agent mode does the steps itself, asking before each command and write

This commit is contained in:
redtea committed 2026-10-15 22:21:54 +03:00
1 parent d9c7c2dd42
commit a7e43ab700
5 files changed
+207 -31

No files matched your search

+18 -6
View File
@@ -348,11 +348,18 @@ func gradioAnswer(raw json.RawMessage, cur string) string {
}
const aiSystem = `Ты - ассистент панели LPRA (удалённый доступ к машинам: терминалы, файлы, порты). Отвечай по-русски, коротко и по делу.
Ниже - что пользователь сейчас видит в панели: вкладки, активная панель, текст терминала или список файлов.
Если нужно выполнить команду, дай её отдельным блоком
` + "```run\nкоманда\n```" + `
Пользователь сам нажмёт «Выполнить» - команда уйдёт в активный терминал с его правами. Одна команда на блок, без пояснений внутри блока.
Не выдумывай вывод команд: после выполнения ты увидишь его в следующем сообщении. Опасные команды (удаление, перезапуск, изменение системы) - только с предупреждением.`
Ниже - что пользователь сейчас видит в панели: его машины, вкладки, активная панель, текст терминала или список файлов.
Действия - отдельными блоками, панель выполнит их с правами пользователя:
` + "```run машина\nкоманда\n```" + ` - команда в терминале этой машины (машину можно не указывать - тогда активная);
` + "```read машина:/путь\n```" + ` - прочитать файл;
` + "```ls машина:/путь\n```" + ` - список папки;
` + "```write машина:/путь\nполное новое содержимое файла\n```" + ` - записать файл целиком.
Имя машины - как в списке машин. Одна команда на блок, без пояснений внутри блока. Результат действия придёт следующим сообщением - не выдумывай его.
Опасные команды (удаление, перезапуск, изменение системы) - только с предупреждением. Перед правкой файла сначала прочитай его.`
const aiAgent = `
Режим агента: ты сам ведёшь задачу шаг за шагом. В каждом ответе - коротко, что делаешь, и ровно одно действие. Получив результат, решай следующий шаг.
Когда задача решена (или решить нельзя) - ответь итогом без блоков действий.`
// POST /api/panel/ai/chat - {messages, context}: the answer, streamed as
// plain text. A failure after the start ends the text with "[ошибка: …]".
@@ -365,6 +372,7 @@ func (s *Server) handleAIChat(w http.ResponseWriter, r *http.Request) {
var in struct {
Messages []aiMsg `json:"messages"`
Context string `json:"context"`
Agent bool `json:"agent"`
}
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&in); err != nil || len(in.Messages) == 0 {
http.Error(w, "плохой запрос", http.StatusBadRequest)
@@ -400,7 +408,11 @@ func (s *Server) handleAIChat(w http.ResponseWriter, r *http.Request) {
fl.Flush()
}
}
err := aiStream(r.Context(), s.aiConfig(), aiSystem+"\n\n--- Панель сейчас ---\n"+in.Context, in.Messages, emit)
system := aiSystem
if in.Agent {
system += aiAgent
}
err := aiStream(r.Context(), s.aiConfig(), system+"\n\n--- Панель сейчас ---\n"+in.Context, in.Messages, emit)
if err != nil && r.Context().Err() == nil {
if !wrote {
w.WriteHeader(http.StatusBadGateway)