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" } }