added new proxy llm provider

This commit is contained in:
2025-08-08 17:42:26 +06:00
parent ec2486ce3d
commit 3e1c4594b1
9 changed files with 583 additions and 101 deletions

272
main.go
View File

@@ -5,6 +5,7 @@ import (
"fmt"
"math"
"os"
"os/exec"
"os/user"
"path"
"strings"
@@ -27,33 +28,44 @@ var (
PROMPT = getEnv("LCG_PROMPT", "Reply with linux command and nothing else. Output with plain response - no need formatting. No need explanation. No need code blocks. No need ` symbols.")
API_KEY_FILE = getEnv("LCG_API_KEY_FILE", ".openai_api_key")
RESULT_FOLDER = getEnv("LCG_RESULT_FOLDER", path.Join(cwd, "gpt_results"))
PROVIDER_TYPE = getEnv("LCG_PROVIDER", "ollama") // "ollama", "proxy"
JWT_TOKEN = getEnv("LCG_JWT_TOKEN", "")
)
const (
colorRed = "\033[31m"
colorGreen = "\033[32m"
colorYellow = "\033[33m"
colorBlue = "\033[34m"
colorPurple = "\033[35m"
colorCyan = "\033[36m"
colorReset = "\033[0m"
colorBold = "\033[1m"
)
func main() {
app := &cli.App{
Name: "lcg",
Usage: "Linux Command GPT - Generate Linux commands from descriptions",
Usage: "Linux Command GPT - Генерация Linux команд из описаний",
Version: Version,
Commands: getCommands(),
UsageText: `
lcg [global options] <command description>
lcg [опции] <описание команды>
Examples:
lcg "I want to extract linux-command-gpt.tar.gz file"
lcg --file /path/to/file.txt "I want to list all directories with ls"
Примеры:
lcg "хочу извлечь файл linux-command-gpt.tar.gz"
lcg --file /path/to/file.txt "хочу вывести все директории с помощью ls"
`,
Description: `
Linux Command GPT is a tool for generating Linux commands from natural language descriptions.
It supports reading parts of the prompt from files and allows saving, copying, or regenerating results.
Additional commands are available for managing API keys.
Linux Command GPT - инструмент для генерации Linux команд из описаний на естественном языке.
Поддерживает чтение частей промпта из файлов и позволяет сохранять, копировать или перегенерировать результаты.
Environment Variables:
LCG_HOST Endpoint for LLM API (default: http://192.168.87.108:11434/)
LCG_COMPLETIONS_PATH Relative API path (default: api/chat)
LCG_MODEL Model name (default: codegeex4)
LCG_PROMPT Default prompt text
LCG_API_KEY_FILE API key storage file (default: ~/.openai_api_key)
LCG_RESULT_FOLDER Results folder (default: ./gpt_results)
Переменные окружения:
LCG_HOST Endpoint для LLM API (по умолчанию: http://192.168.87.108:11434/)
LCG_MODEL Название модели (по умолчанию: codegeex4)
LCG_PROMPT Текст промпта по умолчанию
LCG_PROVIDER Тип провайдера: "ollama" или "proxy" (по умолчанию: ollama)
LCG_JWT_TOKEN JWT токен для proxy провайдера
`,
Flags: []cli.Flag{
&cli.StringFlag{
@@ -75,6 +87,7 @@ Environment Variables:
args := c.Args().Slice()
if len(args) == 0 {
cli.ShowAppHelp(c)
showTips()
return nil
}
executeMain(file, system, strings.Join(args, " "))
@@ -121,67 +134,174 @@ func getCommands() []*cli.Command {
return nil
},
},
{
Name: "update-jwt",
Aliases: []string{"j"},
Usage: "Update the JWT token for proxy API",
Action: func(c *cli.Context) error {
if PROVIDER_TYPE != "proxy" {
fmt.Println("JWT token is only needed for proxy provider")
return nil
}
var jwtToken string
fmt.Print("JWT Token: ")
fmt.Scanln(&jwtToken)
currentUser, _ := user.Current()
jwtFile := currentUser.HomeDir + "/.proxy_jwt_token"
if err := os.WriteFile(jwtFile, []byte(strings.TrimSpace(jwtToken)), 0600); err != nil {
fmt.Printf("Ошибка сохранения JWT токена: %v\n", err)
return err
}
fmt.Println("JWT token updated.")
return nil
},
},
{
Name: "delete-jwt",
Aliases: []string{"dj"},
Usage: "Delete the JWT token for proxy API",
Action: func(c *cli.Context) error {
if PROVIDER_TYPE != "proxy" {
fmt.Println("JWT token is only needed for proxy provider")
return nil
}
currentUser, _ := user.Current()
jwtFile := currentUser.HomeDir + "/.proxy_jwt_token"
if err := os.Remove(jwtFile); err != nil && !os.IsNotExist(err) {
fmt.Printf("Ошибка удаления JWT токена: %v\n", err)
return err
}
fmt.Println("JWT token deleted.")
return nil
},
},
{
Name: "health",
Aliases: []string{"he"}, // Изменено с "h" на "he"
Usage: "Check API health",
Action: func(c *cli.Context) error {
gpt3 := initGPT(PROMPT)
if err := gpt3.Health(); err != nil {
fmt.Printf("Health check failed: %v\n", err)
return err
}
fmt.Println("API is healthy.")
return nil
},
},
{
Name: "config",
Aliases: []string{"co"}, // Изменено с "c" на "co"
Usage: "Show current configuration",
Action: func(c *cli.Context) error {
fmt.Printf("Provider: %s\n", PROVIDER_TYPE)
fmt.Printf("Host: %s\n", HOST)
fmt.Printf("Model: %s\n", MODEL)
fmt.Printf("Prompt: %s\n", PROMPT)
if PROVIDER_TYPE == "proxy" {
fmt.Printf("JWT Token: %s\n", func() string {
if JWT_TOKEN != "" {
return "***set***"
}
currentUser, _ := user.Current()
jwtFile := currentUser.HomeDir + "/.proxy_jwt_token"
if _, err := os.Stat(jwtFile); err == nil {
return "***from file***"
}
return "***not set***"
}())
}
return nil
},
},
{
Name: "history",
Aliases: []string{"hist"},
Usage: "Show command history",
Action: func(c *cli.Context) error {
showHistory()
return nil
},
},
}
}
func executeMain(file, system, commandInput string) {
// fmt.Println(system, commandInput)
// os.Exit(0)
if file != "" {
if err := reader.FileToPrompt(&commandInput, file); err != nil {
fmt.Println("Error reading file:", err)
printColored(fmt.Sprintf("❌ Ошибка чтения файла: %v\n", err), colorRed)
return
}
}
if _, err := os.Stat(RESULT_FOLDER); os.IsNotExist(err) {
os.MkdirAll(RESULT_FOLDER, 0755)
if err := os.MkdirAll(RESULT_FOLDER, 0755); err != nil {
printColored(fmt.Sprintf("❌ Ошибка создания папки результатов: %v\n", err), colorRed)
return
}
}
gpt3 := initGPT(system)
// if system != PROMPT {
// commandInput += ". " + system
// }
fmt.Println(commandInput)
printColored("🤖 Запрос: ", colorCyan)
fmt.Printf("%s\n", commandInput)
response, elapsed := getCommand(gpt3, commandInput)
if response == "" {
fmt.Println("No response received.")
printColored("❌ Ответ не получен. Проверьте подключение к API.\n", colorRed)
return
}
fmt.Printf("Completed in %v seconds\n\n%s\n", elapsed, response)
printColored(fmt.Sprintf("✅ Выполнено за %.2f сек\n", elapsed), colorGreen)
printColored("\n📋 Команда:\n", colorYellow)
printColored(fmt.Sprintf(" %s\n\n", response), colorBold+colorGreen)
saveToHistory(commandInput, response)
handlePostResponse(response, gpt3, system, commandInput)
}
func initGPT(system string) gpt.Gpt3 {
currentUser, _ := user.Current()
return gpt.Gpt3{
CompletionUrl: HOST + COMPLETIONS,
Model: MODEL,
Prompt: system,
HomeDir: currentUser.HomeDir,
ApiKeyFile: API_KEY_FILE,
Temperature: 0.01,
// Загружаем JWT токен в зависимости от провайдера
var jwtToken string
if PROVIDER_TYPE == "proxy" {
jwtToken = JWT_TOKEN
if jwtToken == "" {
// Пытаемся загрузить из файла
jwtFile := currentUser.HomeDir + "/.proxy_jwt_token"
if data, err := os.ReadFile(jwtFile); err == nil {
jwtToken = strings.TrimSpace(string(data))
}
}
}
return *gpt.NewGpt3(PROVIDER_TYPE, HOST, jwtToken, MODEL, system, 0.01)
}
func getCommand(gpt3 gpt.Gpt3, cmd string) (string, float64) {
gpt3.InitKey()
start := time.Now()
done := make(chan bool)
go func() {
loadingChars := []rune{'-', '\\', '|', '/'}
loadingChars := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
i := 0
for {
select {
case <-done:
fmt.Printf("\r")
fmt.Printf("\r%s", strings.Repeat(" ", 50))
fmt.Print("\r")
return
default:
fmt.Printf("\rLoading %c", loadingChars[i])
fmt.Printf("\r%s Обрабатываю запрос...", loadingChars[i])
i = (i + 1) % len(loadingChars)
time.Sleep(30 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
}
}
}()
@@ -194,20 +314,23 @@ func getCommand(gpt3 gpt.Gpt3, cmd string) (string, float64) {
}
func handlePostResponse(response string, gpt3 gpt.Gpt3, system, cmd string) {
fmt.Print("\nOptions: (c)opy, (s)ave, (r)egenerate, (n)one: ")
fmt.Printf("Действия: (c)копировать, (s)сохранить, (r)перегенерировать, (e)выполнить, (n)ничего: ")
var choice string
fmt.Scanln(&choice)
switch strings.ToLower(choice) {
case "c":
clipboard.WriteAll(response)
fmt.Println("Response copied to clipboard.")
fmt.Println("✅ Команда скопирована в буфер обмена")
case "s":
saveResponse(response, gpt3, cmd)
case "r":
fmt.Println("🔄 Перегенерирую...")
executeMain("", system, cmd)
case "e":
executeCommand(response)
default:
fmt.Println("No action taken.")
fmt.Println(" До свидания!")
}
}
@@ -224,9 +347,80 @@ func saveResponse(response string, gpt3 gpt.Gpt3, cmd string) {
}
}
func executeCommand(command string) {
fmt.Printf("🚀 Выполняю: %s\n", command)
fmt.Print("Продолжить? (y/N): ")
var confirm string
fmt.Scanln(&confirm)
if strings.ToLower(confirm) == "y" || strings.ToLower(confirm) == "yes" {
cmd := exec.Command("bash", "-c", command)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf("❌ Ошибка выполнения: %v\n", err)
} else {
fmt.Println("✅ Команда выполнена успешно")
}
} else {
fmt.Println("❌ Выполнение отменено")
}
}
func getEnv(key, defaultValue string) string {
if value, exists := os.LookupEnv(key); exists {
return value
}
return defaultValue
}
type CommandHistory struct {
Command string
Response string
Timestamp time.Time
}
var commandHistory []CommandHistory
func saveToHistory(cmd, response string) {
commandHistory = append(commandHistory, CommandHistory{
Command: cmd,
Response: response,
Timestamp: time.Now(),
})
// Ограничиваем историю 100 командами
if len(commandHistory) > 100 {
commandHistory = commandHistory[1:]
}
}
func showHistory() {
if len(commandHistory) == 0 {
printColored("📝 История пуста\n", colorYellow)
return
}
printColored("📝 История команд:\n", colorYellow)
for i, hist := range commandHistory {
fmt.Printf("%d. %s → %s (%s)\n",
i+1,
hist.Command,
hist.Response,
hist.Timestamp.Format("15:04:05"))
}
}
func printColored(text, color string) {
fmt.Printf("%s%s%s", color, text, colorReset)
}
func showTips() {
printColored("💡 Подсказки:\n", colorCyan)
fmt.Println(" • Используйте --file для чтения из файла")
fmt.Println(" • Используйте --sys для изменения системного промпта")
fmt.Println(" • Команда 'history' покажет историю запросов")
fmt.Println(" • Команда 'config' покажет текущие настройки")
fmt.Println(" • Команда 'health' проверит доступность API")
}