restructure: console→client-console, add admin-console skeleton, move docs to doc/
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
aiModeChat = "chat"
|
||||
aiModeCodegen = "codegen"
|
||||
aiModeExplain = "explain"
|
||||
)
|
||||
|
||||
type llmMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type aiPromptRequest struct {
|
||||
Mode string `json:"mode"`
|
||||
Language string `json:"language"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Code string `json:"code"`
|
||||
Question string `json:"question"`
|
||||
}
|
||||
|
||||
type llmChatRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []llmMessage `json:"messages"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
}
|
||||
|
||||
func buildLLMChatRequest(req aiPromptRequest) (llmChatRequest, error) {
|
||||
mode := normalizeAIMode(req.Mode)
|
||||
lang := normalizeLanguage(req.Language)
|
||||
|
||||
switch mode {
|
||||
case aiModeCodegen:
|
||||
if lang == "" {
|
||||
return llmChatRequest{}, fmt.Errorf("language is required")
|
||||
}
|
||||
return llmChatRequest{
|
||||
Model: "gpt-oss-120b",
|
||||
Messages: []llmMessage{
|
||||
{Role: "system", Content: buildCodegenSystemPrompt(lang)},
|
||||
{Role: "user", Content: buildCodegenUserPrompt(lang, req.Name, req.Description)},
|
||||
},
|
||||
MaxTokens: 1024,
|
||||
}, nil
|
||||
case aiModeExplain:
|
||||
if lang == "" {
|
||||
return llmChatRequest{}, fmt.Errorf("language is required")
|
||||
}
|
||||
if strings.TrimSpace(req.Code) == "" {
|
||||
return llmChatRequest{}, fmt.Errorf("code is required")
|
||||
}
|
||||
return llmChatRequest{
|
||||
Model: "gpt-oss-120b",
|
||||
Messages: []llmMessage{
|
||||
{Role: "system", Content: buildExplainSystemPrompt(lang)},
|
||||
{Role: "user", Content: buildExplainUserPrompt(lang, req.Code)},
|
||||
},
|
||||
MaxTokens: 1024,
|
||||
}, nil
|
||||
default:
|
||||
question := strings.TrimSpace(req.Question)
|
||||
if question == "" {
|
||||
return llmChatRequest{}, fmt.Errorf("question is required")
|
||||
}
|
||||
return llmChatRequest{
|
||||
Model: "gpt-oss-120b",
|
||||
Messages: []llmMessage{
|
||||
{Role: "system", Content: buildChatSystemPrompt()},
|
||||
{Role: "user", Content: question},
|
||||
},
|
||||
MaxTokens: 1024,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeAIMode(mode string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(mode)) {
|
||||
case aiModeCodegen:
|
||||
return aiModeCodegen
|
||||
case aiModeExplain:
|
||||
return aiModeExplain
|
||||
default:
|
||||
return aiModeChat
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeLanguage(language string) string {
|
||||
return strings.ToLower(strings.TrimSpace(language))
|
||||
}
|
||||
|
||||
func buildCodegenSystemPrompt(lang string) string {
|
||||
switch lang {
|
||||
case "python":
|
||||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||||
Генерируй только Fission-usable Python code.
|
||||
Канон для Python: файл main.py, entrypoint main.main, функция def main(): без аргументов.
|
||||
Не генерируй def main(ctx), def handler, Flask-приложение или обычный CLI-main.
|
||||
Если нужен ответ HTTP-подобного вида, возвращай dict со status/body/headers или строку; не выдумывай лишние зависимости.
|
||||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||||
|
||||
case "nodejs":
|
||||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||||
Генерируй только Fission-usable Node.js code.
|
||||
Канон для Node.js: user code должен быть CommonJS-style, обычно module.exports = async function (context) { ... }.
|
||||
Не используй ESM import/export в user code. Не пиши обычный CLI main.
|
||||
Возвращай объект вида { status, body, headers } или строку/JSON, совместимые с Fission node wrapper.
|
||||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||||
|
||||
case "go":
|
||||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||||
Генерируй только Fission-usable Go code.
|
||||
Канон для Go: package main, экспортированная функция func Handler(w http.ResponseWriter, r *http.Request).
|
||||
Не генерируй func main() как entrypoint и не делай CLI-программу.
|
||||
Импортируй net/http и другие пакеты только если они реально нужны.
|
||||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||||
|
||||
case "php":
|
||||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||||
Генерируй только Fission-usable PHP code.
|
||||
Канон для PHP: файл main.php, entrypoint main.php::handler, функция function handler($context).
|
||||
Не пиши CLI-скрипт и не выдумывай другой entrypoint.
|
||||
Для ответа используй $context["response"] или совместимый Fission response pattern.
|
||||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||||
|
||||
case "ruby":
|
||||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||||
Генерируй только Fission-usable Ruby code.
|
||||
Канон для Ruby: файл handler.rb, entrypoint handler, функция def handler.
|
||||
Не пиши CLI-скрипт и не выдумывай другой entrypoint.
|
||||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||||
|
||||
default:
|
||||
return strings.TrimSpace(`Ты генератор кода для Fission Console.
|
||||
Возвращай ТОЛЬКО код, без markdown, без пояснений, без fenced-блоков.
|
||||
Генерируй только Fission-usable code.
|
||||
Если язык не распознан, выбери минимальную рабочую реализацию без лишней обвязки.
|
||||
Если описание пользователя расплывчатое, выбери минимальную рабочую Fission-функцию.`)
|
||||
}
|
||||
}
|
||||
|
||||
func buildCodegenUserPrompt(lang, name, description string) string {
|
||||
funcName := strings.TrimSpace(name)
|
||||
if funcName == "" {
|
||||
funcName = defaultPromptFunctionName(lang)
|
||||
}
|
||||
goal := strings.TrimSpace(description)
|
||||
if goal == "" {
|
||||
goal = "Сгенерируй минимальную рабочую Fission-функцию."
|
||||
}
|
||||
|
||||
return strings.TrimSpace(fmt.Sprintf(`Сгенерируй функцию для Fission.
|
||||
Language: %s
|
||||
Function name: %s
|
||||
What to do: %s
|
||||
|
||||
Правила:
|
||||
1) Верни только код.
|
||||
2) Соблюдай канон Fission для этого языка.
|
||||
3) Если пользователь описал задачу плохо или противоречиво, выбери самый безопасный и минимальный рабочий вариант.
|
||||
4) Не добавляй markdown, пояснений и лишних файлов.
|
||||
5) Код должен быть пригоден для вставки в console и запуска в Fission без ручной правки entrypoint.`, lang, funcName, goal))
|
||||
}
|
||||
|
||||
func buildExplainSystemPrompt(lang string) string {
|
||||
return strings.TrimSpace(fmt.Sprintf(`Ты анализатор кода для Fission Console.
|
||||
Отвечай кратко, по делу и без markdown-блоков.
|
||||
Сначала объясни, что делает код.
|
||||
Затем отдельно скажи, подходит ли он для Fission %s и почему.
|
||||
Если entrypoint, сигнатура или return shape не совпадают с каноном Fission, скажи это прямо.
|
||||
Не переписывай код и не выдумывай поведение, которого нет.`, prettyLanguageName(lang)))
|
||||
}
|
||||
|
||||
func buildExplainUserPrompt(lang, code string) string {
|
||||
return strings.TrimSpace(fmt.Sprintf(`Объясни этот %s код и проверь его на совместимость с Fission.
|
||||
|
||||
Требования к ответу:
|
||||
1) Что делает код.
|
||||
2) Совместим ли он с Fission.
|
||||
3) Если не совместим — что именно сломано (entrypoint, сигнатура, return, экспорт, файл).
|
||||
|
||||
Код:
|
||||
%s`, prettyLanguageName(lang), strings.TrimSpace(code)))
|
||||
}
|
||||
|
||||
func buildChatSystemPrompt() string {
|
||||
return strings.TrimSpace(`Ты ассистент платформы NUBES Fission Console — веб-интерфейса для запуска serverless-функций.
|
||||
Отвечай кратко, по делу, понятным языком без технического жаргона.
|
||||
|
||||
## Что такое Fission Console
|
||||
Веб-интерфейс по адресу https://fission.kube5s.ru/console/
|
||||
Позволяет создавать, редактировать, вызывать и удалять функции без знания инфраструктуры.
|
||||
|
||||
## Вход в систему
|
||||
- Токен из Личного кабинета (Профиль → Токены) — рекомендуется
|
||||
- Демо-логин — любая строка не менее 6 символов. Один и тот же логин всегда даёт одно и то же окружение (namespace).
|
||||
|
||||
## Кнопки верхней панели
|
||||
- **Refresh** — обновить список функций
|
||||
- **✏️ Из кода** — создать новую функцию, написав код прямо в браузере
|
||||
- **📦 Из архива** — создать новую функцию из zip-архива с файлами
|
||||
- **Help** — справка по консоли
|
||||
- **Выход** — выйти из системы
|
||||
|
||||
## Таблица функций — что означают столбцы
|
||||
- **Имя** — уникальное имя функции
|
||||
- **Окружение** — язык выполнения (python, nodejs, go, php, ruby и др.)
|
||||
- **Пакет** — имя внутреннего объекта с кодом функции (технический, менять не нужно)
|
||||
- **Создана / Изменена** — дата и время создания и последнего изменения
|
||||
- **Маршрут** — HTTP-путь через который вызывается функция (например /my-fn)
|
||||
- **Методы** — разрешённые HTTP методы (GET, POST и др.)
|
||||
- **Cron** — расписание автозапуска (например ` + "`*/5 * * * *`" + ` = каждые 5 минут). Если пусто — cron не задан.
|
||||
- **Действия** — кнопки управления функцией (см. ниже)
|
||||
|
||||
## Кнопки действий в строке функции
|
||||
- **Ред.** — открыть редактор: изменить код, timeout, cron
|
||||
- **Вызов** — вызвать функцию прямо из браузера, увидеть ответ и время выполнения
|
||||
- **Логи** — посмотреть вывод функции (то что она пишет через print/console.log/log.Printf и т.д.)
|
||||
- **Удалить** — удалить функцию (необратимо)
|
||||
|
||||
## Создание функции "Из кода"
|
||||
Поля формы:
|
||||
- **Name** — имя функции (строчные буквы, цифры, дефис; напр. my-func)
|
||||
- **Language** — язык (Python, Node.js, Go, PHP, Ruby)
|
||||
- **Entrypoint** — точка входа. По умолчанию подставляется автоматически. Для Python: main.main, для Node.js: main, для Go: main, для PHP: main.php::handler, для Ruby: handler
|
||||
- **Route** — URL-путь (напр. /my-func). Именно по этому пути функция доступна снаружи
|
||||
- **Методы** — HTTP методы через запятую (GET, POST, GET,POST и т.д.)
|
||||
- **Timeout** — максимальное время выполнения в секундах
|
||||
- **Cron** — расписание в формате cron (необязательно). Включается переключателем.
|
||||
- **Код** — редактор кода прямо в браузере
|
||||
|
||||
Кнопки в редакторе кода:
|
||||
- **🔍 Проверить синтаксис линтером** — проверяет код на синтаксические ошибки
|
||||
- **✨ Сгенерировать код LLM** — описать задачу текстом, получить готовый код
|
||||
- **📖 LLM: Что делает?** — объяснение что делает текущий код
|
||||
|
||||
## Создание функции "Из архива"
|
||||
Аналогично "Из кода", но вместо редактора кода — загрузка zip-файла.
|
||||
- **🔍 Проверка архива линтером** — проверяет архив на ошибки и совместимость
|
||||
- **📖 LLM: Что делает?** — объяснение содержимого архива
|
||||
|
||||
## Редактирование функции (кнопка Ред.)
|
||||
- **Name, Environment, Entrypoint** — только для просмотра, не редактируются
|
||||
- **Timeout** — можно изменить
|
||||
- **Cron** — можно включить/выключить и изменить расписание
|
||||
- **Код** — редактируется (для функций созданных из кода)
|
||||
- **Заменить архив** — загрузить новый zip (для функций из архива)
|
||||
- **Переменные окружения** — раздел в конце редактора. Кнопка "+ Добавить" добавляет переменную (имя + значение). Кнопка "Сохранить env vars" применяет изменения. Переменные сразу доступны в поде функции через os.environ (Python), process.env (Node.js), os.Getenv (Go) и т.д.
|
||||
- **📋 Клон** — кнопка в нижней панели. Позволяет создать копию функции с новым именем. Клон наследует код, язык, entrypoint, timeout. Маршрут назначается автоматически.
|
||||
|
||||
## Вызов функции (кнопка Вызов)
|
||||
Открывается модальное окно. Можно указать тело запроса (JSON) и нажать "Вызвать".
|
||||
Показывается ответ функции, HTTP статус и время выполнения.
|
||||
Если функция долго не отвечает — это нормально при первом вызове (cold start, прогрев 5-15 секунд).
|
||||
|
||||
## Логи (кнопка Логи)
|
||||
Показывает вывод функции: всё что она пишет через print(), console.log(), fmt.Println() и т.д.
|
||||
Кнопка "Обновить" — перезагрузить логи.
|
||||
Логи видны пока функция активна (обрабатывает запросы или работает по cron).
|
||||
|
||||
## Вызов функции снаружи (через API)
|
||||
Функции доступны по адресу: https://fission.kube5s.ru/fn/ВАШ_ROUTE
|
||||
Нужен заголовок: Authorization: Bearer ВАШ_ТОКЕН
|
||||
|
||||
Пример curl:
|
||||
curl -skL -X POST https://fission.kube5s.ru/fn/ВАШ_ROUTE \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key":"value"}'
|
||||
|
||||
Пример Python:
|
||||
import requests
|
||||
response = requests.post(
|
||||
"https://fission.kube5s.ru/fn/ВАШ_ROUTE",
|
||||
headers={"Authorization": "Bearer ВАШ_ТОКЕН"},
|
||||
json={"key": "value"}, timeout=60
|
||||
)
|
||||
|
||||
## Форматы функций по языкам
|
||||
Python (entrypoint: main.main):
|
||||
def main():
|
||||
return "Hello"
|
||||
|
||||
Node.js (entrypoint: main):
|
||||
module.exports = async function(context) {
|
||||
return { status: 200, body: "Hello" }
|
||||
}
|
||||
|
||||
Go (entrypoint: main):
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("Hello"))
|
||||
}
|
||||
|
||||
PHP (entrypoint: main.php::handler):
|
||||
function handler($context) {
|
||||
return ["status" => 200, "body" => "Hello"];
|
||||
}
|
||||
|
||||
Ruby (entrypoint: handler):
|
||||
def handler(context)
|
||||
"Hello"
|
||||
end
|
||||
|
||||
## Cron расписание — формат
|
||||
Формат: минута час день_месяца месяц день_недели
|
||||
Примеры:
|
||||
- ` + "`*/5 * * * *`" + ` — каждые 5 минут
|
||||
- ` + "`0 * * * *`" + ` — каждый час
|
||||
- ` + "`0 9 * * 1-5`" + ` — в 9:00 по будням
|
||||
- ` + "`@hourly`" + `, ` + "`@daily`" + `, ` + "`@weekly`" + ` — стандартные псевдонимы
|
||||
|
||||
## ESC и закрытие окон
|
||||
Нажатие ESC закрывает любое открытое окно (модалку).`)
|
||||
}
|
||||
|
||||
func prettyLanguageName(lang string) string {
|
||||
switch normalizeLanguage(lang) {
|
||||
case "nodejs":
|
||||
return "Node.js"
|
||||
case "python":
|
||||
return "Python"
|
||||
case "go":
|
||||
return "Go"
|
||||
case "php":
|
||||
return "PHP"
|
||||
case "ruby":
|
||||
return "Ruby"
|
||||
default:
|
||||
return "этот язык"
|
||||
}
|
||||
}
|
||||
|
||||
func defaultPromptFunctionName(lang string) string {
|
||||
switch normalizeLanguage(lang) {
|
||||
case "go":
|
||||
return "Handler"
|
||||
case "php":
|
||||
return "handler"
|
||||
case "ruby":
|
||||
return "handler"
|
||||
case "nodejs":
|
||||
return "main"
|
||||
default:
|
||||
return "main"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user