Files
go-lcg/cmd/explain.go
2025-10-21 18:51:30 +06:00

190 lines
8.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cmd
import (
"fmt"
"os"
"path"
"strings"
"time"
"github.com/atotto/clipboard"
"github.com/direct-dev-ru/linux-command-gpt/config"
"github.com/direct-dev-ru/linux-command-gpt/gpt"
)
// ExplainDeps инъекция зависимостей для вывода и окружения
type ExplainDeps struct {
DisableHistory bool
PrintColored func(string, string)
ColorPurple string
ColorGreen string
ColorRed string
ColorYellow string
GetCommand func(gpt.Gpt3, string) (string, float64)
}
// ShowDetailedExplanation делает дополнительный запрос с подробным описанием и альтернативами
func ShowDetailedExplanation(command string, gpt3 gpt.Gpt3, system, originalCmd string, timeout int, level int, deps ExplainDeps) {
// Получаем домашнюю директорию пользователя
homeDir, err := os.UserHomeDir()
if err != nil {
// Fallback к встроенным промптам
detailedSystem := getBuiltinVerbosePrompt(level)
ask := getBuiltinAsk(originalCmd, command)
processExplanation(detailedSystem, ask, gpt3, timeout, deps, originalCmd, command, system, level)
return
}
// Создаем менеджер промптов
pm := gpt.NewPromptManager(homeDir)
// Получаем промпт подробности по уровню
verbosePrompt := getVerbosePromptByLevel(pm.Prompts, level)
// Формируем ask в зависимости от языка
ask := getAskByLanguage(pm.GetCurrentLanguage(), originalCmd, command)
processExplanation(verbosePrompt, ask, gpt3, timeout, deps, originalCmd, command, system, level)
}
// getVerbosePromptByLevel возвращает промпт подробности по уровню
func getVerbosePromptByLevel(prompts []gpt.SystemPrompt, level int) string {
// Ищем промпт подробности по ID
for _, prompt := range prompts {
if prompt.ID >= 6 && prompt.ID <= 8 {
switch level {
case 1: // v
if prompt.ID == 6 {
return prompt.Content
}
case 2: // vv
if prompt.ID == 7 {
return prompt.Content
}
default: // vvv
if prompt.ID == 8 {
return prompt.Content
}
}
}
}
// Fallback к встроенным промптам
return getBuiltinVerbosePrompt(level)
}
// getBuiltinVerbosePrompt возвращает встроенный промпт подробности
func getBuiltinVerbosePrompt(level int) string {
switch level {
case 1: // v — кратко
return "Ты опытный Linux-инженер. Объясни КРАТКО, по делу: что делает команда и самые важные ключи. Без сравнений и альтернатив. Минимум текста. Пиши на русском."
case 2: // vv — средне
return "Ты опытный Linux-инженер. Дай сбалансированное объяснение: назначение команды, разбор основных ключей, 1-2 примера. Кратко упомяни 1-2 альтернативы без глубокого сравнения. Пиши на русском."
default: // vvv — максимально подробно
return "Ты опытный Linux-инженер. Дай подробное объяснение команды с полным разбором ключей, подкоманд, сценариев применения, примеров. Затем предложи альтернативные способы решения задачи другой командой/инструментами (со сравнениями и когда что лучше применять). Пиши на русском."
}
}
// getAskByLanguage формирует ask в зависимости от языка
func getAskByLanguage(lang, originalCmd, command string) string {
if lang == "ru" {
return fmt.Sprintf("Объясни подробно команду и предложи альтернативы. Исходная команда: %s. Исходное задание пользователя: %s", command, originalCmd)
}
// Английский
return fmt.Sprintf("Explain the command in detail and suggest alternatives. Original command: %s. Original user request: %s", command, originalCmd)
}
// getBuiltinAsk возвращает встроенный ask
func getBuiltinAsk(originalCmd, command string) string {
return fmt.Sprintf("Объясни подробно команду и предложи альтернативы. Исходная команда: %s. Исходное задание пользователя: %s", command, originalCmd)
}
// processExplanation обрабатывает объяснение
func processExplanation(detailedSystem, ask string, gpt3 gpt.Gpt3, timeout int, deps ExplainDeps, originalCmd string, command string, system string, level int) {
// Выводим debug информацию если включен флаг
if config.AppConfig.MainFlags.Debug {
printVerboseDebugInfo(detailedSystem, ask, gpt3, timeout, level)
}
detailed := gpt.NewGpt3(gpt3.ProviderType, config.AppConfig.Host, gpt3.ApiKey, gpt3.Model, detailedSystem, 0.2, timeout)
deps.PrintColored("\n🧠 Получаю подробное объяснение...\n", deps.ColorPurple)
explanation, elapsed := deps.GetCommand(*detailed, ask)
if explanation == "" {
deps.PrintColored("❌ Не удалось получить подробное объяснение.\n", deps.ColorRed)
return
}
deps.PrintColored(fmt.Sprintf("✅ Готово за %.2f сек\n", elapsed), deps.ColorGreen)
deps.PrintColored("\nВНИМАНИЕ: ОТВЕТ СФОРМИРОВАН ИИ. ТРЕБУЕТСЯ ПРОВЕРКА И КРИТИЧЕСКИЙ АНАЛИЗ. ВОЗМОЖНЫ ОШИБКИ И ГАЛЛЮЦИНАЦИИ.\n", deps.ColorRed)
deps.PrintColored("\n📖 Подробное объяснение и альтернативы:\n\n", deps.ColorYellow)
fmt.Println(explanation)
fmt.Printf("\nДействия: (c)копировать, (s)сохранить, (r)перегенерировать, (n)ничего: ")
var choice string
fmt.Scanln(&choice)
switch strings.ToLower(choice) {
case "c":
clipboard.WriteAll(explanation)
fmt.Println("✅ Объяснение скопировано в буфер обмена")
case "s":
saveExplanation(explanation, gpt3.Model, originalCmd, command, config.AppConfig.ResultFolder)
case "r":
fmt.Println("🔄 Перегенерирую подробное объяснение...")
ShowDetailedExplanation(command, gpt3, system, originalCmd, timeout, level, deps)
default:
fmt.Println(" Возврат в основное меню.")
}
if !deps.DisableHistory && (strings.ToLower(choice) == "c" || strings.ToLower(choice) == "s" || strings.ToLower(choice) == "n") {
SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, originalCmd, command, system, explanation)
}
}
// saveExplanation сохраняет подробное объяснение и альтернативные способы
func saveExplanation(explanation string, model string, originalCmd string, commandResponse string, resultFolder string) {
timestamp := time.Now().Format("2006-01-02_15-04-05")
filename := fmt.Sprintf("gpt_explanation_%s_%s.md", model, timestamp)
filePath := path.Join(resultFolder, filename)
title := truncateTitle(originalCmd)
content := fmt.Sprintf(
"# %s\n\n## Prompt\n\n%s\n\n## Command\n\n%s\n\n## Explanation and Alternatives (model: %s)\n\n%s\n",
title,
originalCmd,
commandResponse,
model,
explanation,
)
if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {
fmt.Println("Failed to save explanation:", err)
} else {
fmt.Printf("Saved to %s\n", filePath)
}
}
// truncateTitle сокращает строку до 120 символов (по рунам), добавляя " ..." при усечении
func truncateTitle(s string) string {
const maxLen = 120
if runeCount := len([]rune(s)); runeCount <= maxLen {
return s
}
const head = 116
r := []rune(s)
if len(r) <= head {
return s
}
return string(r[:head]) + " ..."
}
// printVerboseDebugInfo выводит отладочную информацию для режимов v/vv/vvv
func printVerboseDebugInfo(detailedSystem, ask string, gpt3 gpt.Gpt3, timeout int, level int) {
fmt.Printf("\n🔍 DEBUG VERBOSE (v%d):\n", level)
fmt.Printf("📝 Системный промпт подробности:\n%s\n", detailedSystem)
fmt.Printf("💬 Запрос подробности:\n%s\n", ask)
fmt.Printf("⏱️ Таймаут: %d сек\n", timeout)
fmt.Printf("🌐 Провайдер: %s\n", gpt3.ProviderType)
fmt.Printf("🏠 Хост: %s\n", config.AppConfig.Host)
fmt.Printf("🧠 Модель: %s\n", gpt3.Model)
fmt.Printf("🎯 Уровень подробности: %d\n", level)
fmt.Printf("────────────────────────────────────────\n")
}