mirror of
https://github.com/Direct-Dev-Ru/go-lcg.git
synced 2025-11-15 17:20:00 +00:00
alpha v.2
This commit is contained in:
100
USAGE_GUIDE.md
100
USAGE_GUIDE.md
@@ -2,7 +2,7 @@
|
||||
|
||||
## Что это
|
||||
|
||||
Linux Command GPT (`lcg`) преобразует описание на естественном языке в готовую Linux‑команду. Инструмент поддерживает сменные провайдеры LLM (Ollama или Proxy), управление системными промптами, историю за сессию, сохранение результатов и интерактивные действия над сгенерированной командой.
|
||||
Linux Command GPT (`lcg`) преобразует описание на естественном языке в готовую Linux‑команду. Инструмент поддерживает сменные провайдеры LLM (Ollama или Proxy), управление системными промптами, историю запросов, сохранение результатов, HTTP сервер для просмотра результатов и интерактивные действия над сгенерированной командой.
|
||||
|
||||
## Требования
|
||||
|
||||
@@ -66,17 +66,21 @@ lcg --file /path/to/context.txt "хочу вывести список дирек
|
||||
| Переменная | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `LCG_HOST` | `http://192.168.87.108:11434/` | Базовый URL API провайдера (для Ollama поставьте, например, `http://localhost:11434/`). |
|
||||
| `LCG_PROXY_URL` | `/api/v1/protected/sberchat/chat` | Относительный путь эндпоинта для Proxy провайдера. |
|
||||
| `LCG_COMPLETIONS_PATH` | `api/chat` | Относительный путь эндпоинта для Ollama. |
|
||||
| `LCG_MODEL` | `codegeex4` | Имя модели у выбранного провайдера. |
|
||||
| `LCG_MODEL` | `hf.co/yandex/YandexGPT-5-Lite-8B-instruct-GGUF:Q4_K_M` | Имя модели у выбранного провайдера. |
|
||||
| `LCG_PROMPT` | См. значение в коде | Содержимое системного промпта по умолчанию. |
|
||||
| `LCG_API_KEY_FILE` | `.openai_api_key` | Файл с API‑ключом (для Ollama/Proxy не требуется). |
|
||||
| `LCG_RESULT_FOLDER` | `$(pwd)/gpt_results` | Папка для сохранения результатов. |
|
||||
| `LCG_RESULT_FOLDER` | `~/.config/lcg/gpt_results` | Папка для сохранения результатов. |
|
||||
| `LCG_PROVIDER` | `ollama` | Тип провайдера: `ollama` или `proxy`. |
|
||||
| `LCG_JWT_TOKEN` | пусто | JWT токен для `proxy` провайдера (альтернатива — файл `~/.proxy_jwt_token`). |
|
||||
| `LCG_PROMPT_ID` | `1` | ID системного промпта по умолчанию. |
|
||||
| `LCG_TIMEOUT` | `120` | Таймаут запроса в секундах. |
|
||||
| `LCG_TIMEOUT` | `300` | Таймаут запроса в секундах. |
|
||||
| `LCG_RESULT_HISTORY` | `$(LCG_RESULT_FOLDER)/lcg_history.json` | Путь к JSON‑истории запросов. |
|
||||
| `LCG_PROMPT_FOLDER` | `~/.config/lcg/gpt_sys_prompts` | Папка для хранения системных промптов. |
|
||||
| `LCG_NO_HISTORY` | пусто | Если `1`/`true` — полностью отключает запись/обновление истории. |
|
||||
| `LCG_SERVER_PORT` | `8080` | Порт для HTTP сервера просмотра результатов. |
|
||||
| `LCG_SERVER_HOST` | `localhost` | Хост для HTTP сервера просмотра результатов. |
|
||||
|
||||
Примеры настройки:
|
||||
|
||||
@@ -104,7 +108,9 @@ lcg [глобальные опции] <описание команды>
|
||||
- `--file, -f string` — прочитать часть запроса из файла и добавить к описанию.
|
||||
- `--sys, -s string` — системный промпт (содержимое или ID как строка). Если не задан, используется `--prompt-id` или `LCG_PROMPT`.
|
||||
- `--prompt-id, --pid int` — ID системного промпта (1–5 для стандартных, либо ваш кастомный ID).
|
||||
- `--timeout, -t int` — таймаут запроса в секундах (по умолчанию 120).
|
||||
- `--timeout, -t int` — таймаут запроса в секундах (по умолчанию 300).
|
||||
- `--no-history, --nh` — отключить запись/обновление истории для текущего запуска.
|
||||
- `--debug, -d` — показать отладочную информацию (параметры запроса и промпты).
|
||||
- `--version, -v` — вывести версию.
|
||||
- `--help, -h` — помощь.
|
||||
|
||||
@@ -122,10 +128,14 @@ lcg [глобальные опции] <описание команды>
|
||||
- `lcg history delete <id>` (`-d`): удалить запись истории по `index` (с перенумерацией).
|
||||
- Флаг `--no-history` (`-nh`) отключает запись истории для текущего запуска и имеет приоритет над `LCG_NO_HISTORY`.
|
||||
- `lcg prompts ...` (`-p`): управление системными промптами:
|
||||
- `lcg prompts list` (`-l`) — список всех промптов.
|
||||
- `lcg prompts list` (`-l`) — список всех промптов с содержимым в читаемом формате.
|
||||
- `lcg prompts list --full` (`-f`) — полный вывод содержимого без обрезки длинных строк.
|
||||
- `lcg prompts add` (`-a`) — добавить пользовательский промпт (по шагам в интерактиве).
|
||||
- `lcg prompts delete <id>` (`-d`) — удалить пользовательский промпт по ID (>5).
|
||||
- `lcg test-prompt <prompt-id> <описание>` (`-tp`): показать детали выбранного системного промпта и протестировать его на заданном описании.
|
||||
- `lcg serve-result` (`serve`): запустить HTTP сервер для просмотра сохраненных результатов:
|
||||
- `--port, -p` — порт сервера (по умолчанию из `LCG_SERVER_PORT`)
|
||||
- `--host, -H` — хост сервера (по умолчанию из `LCG_SERVER_HOST`)
|
||||
|
||||
### Подробные объяснения (v/vv/vvv)
|
||||
|
||||
@@ -165,7 +175,7 @@ lcg [глобальные опции] <описание команды>
|
||||
|
||||
### Таймауты
|
||||
|
||||
- Стартовые значения: локально с Ollama — **60–120 сек**, удалённый proxy — **120–300 сек**.
|
||||
- Стартовые значения: локально с Ollama — **120–300 сек**, удалённый proxy — **300–600 сек**.
|
||||
- Увеличьте таймаут для больших моделей/длинных запросов. Флаг `--timeout` перекрывает `LCG_TIMEOUT` на время запуска.
|
||||
- Если часто видите таймауты — проверьте здоровье API (`lcg health`) и сетевую доступность `LCG_HOST`.
|
||||
|
||||
@@ -178,7 +188,17 @@ lcg [глобальные опции] <описание команды>
|
||||
|
||||
## Системные промпты
|
||||
|
||||
Встроенные (ID 1–5):
|
||||
### Управление промптами
|
||||
|
||||
Системные промпты хранятся в папке, указанной в переменной `LCG_PROMPT_FOLDER` (по умолчанию: `~/.config/lcg/gpt_sys_prompts`).
|
||||
|
||||
**Логика загрузки:**
|
||||
|
||||
- Если файл `sys_prompts` **не существует** — создается файл с системными промптами (ID 1–5) и промптами подробности (ID 6–8)
|
||||
- Если файл `sys_prompts` **существует** — загружаются все промпты из файла
|
||||
- **Промпты подробности** (v/vv/vvv) сохраняются в том же файле с ID 6, 7, 8
|
||||
|
||||
### Встроенные промпты (ID 1–5)
|
||||
|
||||
| ID | Name | Описание |
|
||||
| --- | --- | --- |
|
||||
@@ -188,16 +208,61 @@ lcg [глобальные опции] <описание команды>
|
||||
| 4 | linux-command-verbose | Команда с подробными объяснениями флагов и альтернатив. |
|
||||
| 5 | linux-command-simple | Простые команды, избегать сложных опций. |
|
||||
|
||||
Пользовательские промпты сохраняются в `~/.lcg_prompts.json` и доступны между запусками.
|
||||
### Промпты подробности (ID 6–8)
|
||||
|
||||
| ID | Name | Описание |
|
||||
| --- | --- | --- |
|
||||
| 6 | verbose-v | Подробный режим (v) - детальное объяснение команды |
|
||||
| 7 | verbose-vv | Очень подробный режим (vv) - исчерпывающее объяснение с альтернативами |
|
||||
| 8 | verbose-vvv | Максимально подробный режим (vvv) - полное руководство с примерами |
|
||||
|
||||
### Веб-интерфейс управления
|
||||
|
||||
Через HTTP сервер (`lcg serve-result`) доступно полное управление промптами:
|
||||
|
||||
- **Просмотр всех промптов** (встроенных и пользовательских)
|
||||
- **Редактирование любых промптов** (включая встроенные)
|
||||
- **Добавление новых промптов**
|
||||
- **Удаление промптов**
|
||||
- **Автоматическое сохранение** в файл `sys_prompts`
|
||||
|
||||
## Сохранение результатов
|
||||
|
||||
При выборе действия `s` ответ сохраняется в `LCG_RESULT_FOLDER` (по умолчанию: `./gpt_results`) в файл вида:
|
||||
При выборе действия `s` ответ сохраняется в `LCG_RESULT_FOLDER` (по умолчанию: `~/.config/lcg/gpt_results`) в файл вида:
|
||||
|
||||
```text
|
||||
gpt_request_<MODEL>_YYYY-MM-DD_HH-MM-SS.md
|
||||
```
|
||||
|
||||
## HTTP сервер для просмотра результатов
|
||||
|
||||
Команда `lcg serve-result` запускает веб-сервер для удобного просмотра всех сохраненных результатов:
|
||||
|
||||
```bash
|
||||
# Запуск с настройками по умолчанию
|
||||
lcg serve-result
|
||||
|
||||
# Запуск на другом порту
|
||||
lcg serve-result --port 9090
|
||||
|
||||
# Запуск на другом хосте
|
||||
lcg serve-result --host 0.0.0.0 --port 8080
|
||||
|
||||
# Использование переменных окружения
|
||||
export LCG_SERVER_PORT=3000
|
||||
export LCG_SERVER_HOST=0.0.0.0
|
||||
lcg serve-result
|
||||
```
|
||||
|
||||
### Возможности веб-интерфейса
|
||||
|
||||
- **Главная страница** (`/`) — отображает все сохраненные файлы с превью
|
||||
- **Статистика** — количество файлов, файлы за последние 7 дней
|
||||
- **Просмотр файлов** (`/file/{filename}`) — отображение содержимого конкретного файла
|
||||
- **Современный дизайн** — адаптивный интерфейс с карточками файлов
|
||||
- **Сортировка** — файлы отсортированы по дате изменения (новые сверху)
|
||||
- **Превью содержимого** — первые 200 символов каждого файла
|
||||
|
||||
Структура файла (команда):
|
||||
|
||||
- `# <заголовок>` — H1, это ваш запрос, при длине >120 символов обрезается до 116 + `...`.
|
||||
@@ -264,6 +329,19 @@ lcg health
|
||||
lcg models
|
||||
```
|
||||
|
||||
1. HTTP сервер для просмотра результатов:
|
||||
|
||||
```bash
|
||||
# Запуск сервера
|
||||
lcg serve-result
|
||||
|
||||
# Запуск на другом порту
|
||||
lcg serve-result --port 9090
|
||||
|
||||
# Запуск на всех интерфейсах
|
||||
lcg serve-result --host 0.0.0.0 --port 8080
|
||||
```
|
||||
|
||||
## История
|
||||
|
||||
`lcg history` выводит историю текущего процесса (не сохраняется между запусками, максимум 100 записей):
|
||||
@@ -279,6 +357,8 @@ lcg history list
|
||||
- Копирование не работает: установите `xclip` или `xsel`.
|
||||
- Нет допуска к папке результатов: настройте `LCG_RESULT_FOLDER` или права доступа.
|
||||
- Для `ollama`/`proxy` API‑ключ не нужен; команды `update-key`/`delete-key` просто уведомят об этом.
|
||||
- HTTP сервер не запускается: проверьте, что порт свободен, используйте `--port` для смены порта.
|
||||
- Веб-интерфейс не отображает файлы: убедитесь, что в `LCG_RESULT_FOLDER` есть `.md` файлы.
|
||||
|
||||
## JSON‑история запросов
|
||||
|
||||
|
||||
100
cmd/explain.go
100
cmd/explain.go
@@ -25,17 +25,86 @@ type ExplainDeps struct {
|
||||
|
||||
// ShowDetailedExplanation делает дополнительный запрос с подробным описанием и альтернативами
|
||||
func ShowDetailedExplanation(command string, gpt3 gpt.Gpt3, system, originalCmd string, timeout int, level int, deps ExplainDeps) {
|
||||
var detailedSystem string
|
||||
switch level {
|
||||
case 1: // v — кратко
|
||||
detailedSystem = "Ты опытный Linux-инженер. Объясни КРАТКО, по делу: что делает команда и самые важные ключи. Без сравнений и альтернатив. Минимум текста. Пиши на русском."
|
||||
case 2: // vv — средне
|
||||
detailedSystem = "Ты опытный Linux-инженер. Дай сбалансированное объяснение: назначение команды, разбор основных ключей, 1-2 примера. Кратко упомяни 1-2 альтернативы без глубокого сравнения. Пиши на русском."
|
||||
default: // vvv — максимально подробно
|
||||
detailedSystem = "Ты опытный Linux-инженер. Дай подробное объяснение команды с полным разбором ключей, подкоманд, сценариев применения, примеров. Затем предложи альтернативные способы решения задачи другой командой/инструментами (со сравнениями и когда что лучше применять). Пиши на русском."
|
||||
// Получаем домашнюю директорию пользователя
|
||||
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
|
||||
}
|
||||
|
||||
ask := fmt.Sprintf("Объясни подробно команду и предложи альтернативы. Исходная команда: %s. Исходное задание пользователя: %s", command, originalCmd)
|
||||
// Создаем менеджер промптов
|
||||
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)
|
||||
@@ -105,3 +174,16 @@ func truncateTitle(s string) string {
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -137,6 +137,34 @@ func SaveToHistory(historyPath, resultFolder, cmdText, response, system string,
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveToHistoryFromHistory сохраняет запись из истории без запроса о перезаписи
|
||||
func SaveToHistoryFromHistory(historyPath, resultFolder, cmdText, response, system, explanation string) error {
|
||||
items, _ := read(historyPath)
|
||||
duplicateIndex := -1
|
||||
for i, h := range items {
|
||||
if strings.EqualFold(strings.TrimSpace(h.Command), strings.TrimSpace(cmdText)) {
|
||||
duplicateIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
entry := HistoryEntry{
|
||||
Index: len(items) + 1,
|
||||
Command: cmdText,
|
||||
Response: response,
|
||||
Explanation: explanation,
|
||||
System: system,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
if duplicateIndex == -1 {
|
||||
items = append(items, entry)
|
||||
return write(historyPath, items)
|
||||
}
|
||||
// Если дубликат найден, перезаписываем без запроса
|
||||
entry.Index = items[duplicateIndex].Index
|
||||
items[duplicateIndex] = entry
|
||||
return write(historyPath, items)
|
||||
}
|
||||
|
||||
func CheckAndSuggestFromHistory(historyPath, cmdText string) (bool, *HistoryEntry) {
|
||||
items, err := read(historyPath)
|
||||
if err != nil || len(items) == 0 {
|
||||
|
||||
1938
cmd/serve.go
Normal file
1938
cmd/serve.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ type Config struct {
|
||||
Prompt string
|
||||
ApiKeyFile string
|
||||
ResultFolder string
|
||||
PromptFolder string
|
||||
ProviderType string
|
||||
JwtToken string
|
||||
PromptID string
|
||||
@@ -22,6 +23,7 @@ type Config struct {
|
||||
ResultHistory string
|
||||
NoHistoryEnv string
|
||||
MainFlags MainFlags
|
||||
Server ServerConfig
|
||||
}
|
||||
|
||||
type MainFlags struct {
|
||||
@@ -30,6 +32,12 @@ type MainFlags struct {
|
||||
Sys string
|
||||
PromptID int
|
||||
Timeout int
|
||||
Debug bool
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port string
|
||||
Host string
|
||||
}
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
@@ -49,6 +57,9 @@ func Load() Config {
|
||||
os.MkdirAll(path.Join(homedir, ".config", "lcg", "gpt_results"), 0755)
|
||||
resultFolder := getEnv("LCG_RESULT_FOLDER", path.Join(homedir, ".config", "lcg", "gpt_results"))
|
||||
|
||||
os.MkdirAll(path.Join(homedir, ".config", "lcg", "gpt_sys_prompts"), 0755)
|
||||
promptFolder := getEnv("LCG_PROMPT_FOLDER", path.Join(homedir, ".config", "lcg", "gpt_sys_prompts"))
|
||||
|
||||
return Config{
|
||||
Cwd: cwd,
|
||||
Host: getEnv("LCG_HOST", "http://192.168.87.108:11434/"),
|
||||
@@ -58,12 +69,17 @@ func Load() Config {
|
||||
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."),
|
||||
ApiKeyFile: getEnv("LCG_API_KEY_FILE", ".openai_api_key"),
|
||||
ResultFolder: resultFolder,
|
||||
PromptFolder: promptFolder,
|
||||
ProviderType: getEnv("LCG_PROVIDER", "ollama"),
|
||||
JwtToken: getEnv("LCG_JWT_TOKEN", ""),
|
||||
PromptID: getEnv("LCG_PROMPT_ID", "1"),
|
||||
Timeout: getEnv("LCG_TIMEOUT", "300"),
|
||||
ResultHistory: getEnv("LCG_RESULT_HISTORY", path.Join(resultFolder, "lcg_history.json")),
|
||||
NoHistoryEnv: getEnv("LCG_NO_HISTORY", ""),
|
||||
Server: ServerConfig{
|
||||
Port: getEnv("LCG_SERVER_PORT", "8080"),
|
||||
Host: getEnv("LCG_SERVER_HOST", "localhost"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
6
go.mod
6
go.mod
@@ -4,9 +4,11 @@ go 1.18
|
||||
|
||||
require github.com/atotto/clipboard v0.1.4
|
||||
|
||||
require gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 //indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
github.com/urfave/cli/v2 v2.27.5
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
)
|
||||
|
||||
3
go.sum
3
go.sum
@@ -8,3 +8,6 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
||||
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
124
gpt/builtin_prompts.go
Normal file
124
gpt/builtin_prompts.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package gpt
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
//go:embed builtin_prompts.yaml
|
||||
var builtinPromptsYAML string
|
||||
|
||||
var builtinPrompts string
|
||||
|
||||
// BuiltinPromptsData структура для YAML файла
|
||||
type BuiltinPromptsData struct {
|
||||
Prompts []BuiltinPrompt `yaml:"prompts"`
|
||||
}
|
||||
|
||||
// BuiltinPrompt структура для встроенных промптов с поддержкой языков
|
||||
type BuiltinPrompt struct {
|
||||
ID int `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
Description map[string]string `yaml:"description"`
|
||||
Content map[string]string `yaml:"content"`
|
||||
}
|
||||
|
||||
// ToSystemPrompt конвертирует BuiltinPrompt в SystemPrompt для указанного языка
|
||||
func (bp *BuiltinPrompt) ToSystemPrompt(lang string) SystemPrompt {
|
||||
// Если язык не найден, используем английский по умолчанию
|
||||
if _, exists := bp.Description[lang]; !exists {
|
||||
lang = "en"
|
||||
}
|
||||
|
||||
return SystemPrompt{
|
||||
ID: bp.ID,
|
||||
Name: bp.Name,
|
||||
Description: bp.Description[lang],
|
||||
Content: bp.Content[lang],
|
||||
}
|
||||
}
|
||||
|
||||
// GetBuiltinPrompts возвращает встроенные промпты из YAML (по умолчанию английские)
|
||||
func GetBuiltinPrompts() []SystemPrompt {
|
||||
return GetBuiltinPromptsByLanguage("en")
|
||||
}
|
||||
|
||||
// GetBuiltinPromptsByLanguage возвращает встроенные промпты для указанного языка
|
||||
func GetBuiltinPromptsByLanguage(lang string) []SystemPrompt {
|
||||
var data BuiltinPromptsData
|
||||
if err := yaml.Unmarshal([]byte(builtinPrompts), &data); err != nil {
|
||||
// В случае ошибки возвращаем пустой массив
|
||||
return []SystemPrompt{}
|
||||
}
|
||||
|
||||
var result []SystemPrompt
|
||||
for _, prompt := range data.Prompts {
|
||||
result = append(result, prompt.ToSystemPrompt(lang))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// IsBuiltinPrompt проверяет, является ли промпт встроенным
|
||||
func IsBuiltinPrompt(prompt SystemPrompt) bool {
|
||||
// Проверяем английскую версию
|
||||
englishPrompts := GetBuiltinPromptsByLanguage("en")
|
||||
for _, builtin := range englishPrompts {
|
||||
if builtin.ID == prompt.ID {
|
||||
if builtin.Content == prompt.Content &&
|
||||
builtin.Name == prompt.Name &&
|
||||
builtin.Description == prompt.Description {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем русскую версию
|
||||
russianPrompts := GetBuiltinPromptsByLanguage("ru")
|
||||
for _, builtin := range russianPrompts {
|
||||
if builtin.ID == prompt.ID {
|
||||
if builtin.Content == prompt.Content &&
|
||||
builtin.Name == prompt.Name &&
|
||||
builtin.Description == prompt.Description {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetBuiltinPromptByID возвращает встроенный промпт по ID (английская версия)
|
||||
func GetBuiltinPromptByID(id int) *SystemPrompt {
|
||||
builtinPrompts := GetBuiltinPrompts()
|
||||
|
||||
for _, prompt := range builtinPrompts {
|
||||
if prompt.ID == id {
|
||||
return &prompt
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBuiltinPromptByIDAndLanguage возвращает встроенный промпт по ID и языку
|
||||
func GetBuiltinPromptByIDAndLanguage(id int, lang string) *SystemPrompt {
|
||||
builtinPrompts := GetBuiltinPromptsByLanguage(lang)
|
||||
|
||||
for _, prompt := range builtinPrompts {
|
||||
if prompt.ID == id {
|
||||
return &prompt
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitBuiltinPrompts(embeddedBuiltinPromptsYAML string) {
|
||||
// Используем встроенный YAML, если переданный параметр пустой
|
||||
if embeddedBuiltinPromptsYAML == "" {
|
||||
builtinPrompts = builtinPromptsYAML
|
||||
} else {
|
||||
builtinPrompts = embeddedBuiltinPromptsYAML
|
||||
}
|
||||
}
|
||||
262
gpt/builtin_prompts.yaml
Normal file
262
gpt/builtin_prompts.yaml
Normal file
@@ -0,0 +1,262 @@
|
||||
prompts:
|
||||
- id: 1
|
||||
name: "linux-command"
|
||||
description:
|
||||
en: "Main prompt for generating Linux commands"
|
||||
ru: "Основной промпт для генерации Linux команд"
|
||||
content:
|
||||
en: |
|
||||
You are a Linux command line expert.
|
||||
Analyze the user's task, given in natural language, and suggest
|
||||
a Linux command that will help accomplish this task, and provide a detailed explanation of what it does,
|
||||
its parameters and possible use cases.
|
||||
Focus on practical examples and best practices.
|
||||
In the response, you should only provide the commands or sequence of commands ready to copy and execute
|
||||
in the command line without any explanationformatting or code blocks, without ```bash``` or ```sh```, ` or ``` symbols.
|
||||
|
||||
ru: |
|
||||
Вы эксперт по Linux командам и командной строке.
|
||||
Проанализируйте задачу пользователя на естественном языке и предложите Linux команду или набор команд, которые помогут выполнить эту задачу, и предоставьте подробное объяснение того, что она делает, её параметры и возможные случаи использования.
|
||||
Сосредоточьтесь на практических примерах и лучших практиках.
|
||||
В ответе должна присутствовать только команда или последовательность команд,
|
||||
готовая к копированию и выполнению в командной строке
|
||||
без объяснений, выделений и форматирования наподобие ```bash``` или ```sh```, без символов ` или ```.
|
||||
|
||||
- id: 2
|
||||
name: "linux-command-with-explanation"
|
||||
description:
|
||||
en: "Prompt with detailed command explanation"
|
||||
ru: "Промпт с подробным объяснением команд"
|
||||
content:
|
||||
en: |
|
||||
You are a Linux system administrator with extensive experience.
|
||||
Generate Linux commands based on user task descriptions and provide comprehensive explanations.
|
||||
|
||||
Provide a detailed analysis including:
|
||||
1. **Generated Command**: The Linux command that accomplishes the task
|
||||
2. **Command Breakdown**: Explain each part of the command
|
||||
3. **Parameters**: Explain each flag and option used
|
||||
4. **Examples**: Show practical usage scenarios
|
||||
5. **Security**: Highlight any security considerations
|
||||
6. **Alternatives**: Suggest similar commands if applicable
|
||||
7. **Best Practices**: Recommend optimal usage
|
||||
|
||||
Use clear formatting with headers and bullet points for readability.
|
||||
ru: |
|
||||
Вы системный администратор Linux с обширным опытом.
|
||||
Генерируйте Linux команды на основе описаний задач пользователей и предоставляйте исчерпывающие объяснения.
|
||||
|
||||
Предоставьте подробный анализ, включая:
|
||||
1. **Сгенерированная команда**: Linux команда, которая выполняет задачу
|
||||
2. **Разбор команды**: Объясните каждую часть команды
|
||||
3. **Параметры**: Объясните каждый используемый флаг и опцию
|
||||
4. **Примеры**: Покажите практические сценарии использования
|
||||
5. **Безопасность**: Выделите любые соображения безопасности
|
||||
6. **Альтернативы**: Предложите похожие команды, если применимо
|
||||
7. **Лучшие практики**: Рекомендуйте оптимальное использование
|
||||
|
||||
Используйте четкое форматирование с заголовками и маркерами для читаемости.
|
||||
|
||||
- id: 3
|
||||
name: "linux-command-safe"
|
||||
description:
|
||||
en: "Safe command analysis with warnings"
|
||||
ru: "Безопасный анализ команд с предупреждениями"
|
||||
content:
|
||||
en: |
|
||||
You are a Linux security expert. Generate safe Linux commands based on user task descriptions with a focus on safety and security implications.
|
||||
|
||||
Provide a security-focused analysis:
|
||||
1. **Generated Safe Command**: The secure Linux command for the task
|
||||
2. **Safety Assessment**: Why this command is safe to run
|
||||
3. **Potential Risks**: What could go wrong and how to mitigate
|
||||
4. **Data Impact**: What files or data might be affected
|
||||
5. **Permissions**: What permissions are required
|
||||
6. **Recovery**: How to undo changes if needed
|
||||
7. **Best Practices**: Safe alternatives or precautions
|
||||
8. **Warnings**: Critical safety considerations
|
||||
|
||||
Always prioritize user safety and data protection.
|
||||
ru: |
|
||||
Вы эксперт по безопасности Linux. Генерируйте безопасные Linux команды на основе описаний задач пользователей с акцентом на безопасность и последствия для безопасности.
|
||||
|
||||
Предоставьте анализ, ориентированный на безопасность:
|
||||
1. **Сгенерированная безопасная команда**: Безопасная Linux команда для задачи
|
||||
2. **Оценка безопасности**: Почему эта команда безопасна для выполнения
|
||||
3. **Потенциальные риски**: Что может пойти не так и как это смягчить
|
||||
4. **Воздействие на данные**: Какие файлы или данные могут быть затронуты
|
||||
5. **Разрешения**: Какие разрешения требуются
|
||||
6. **Восстановление**: Как отменить изменения при необходимости
|
||||
7. **Лучшие практики**: Безопасные альтернативы или меры предосторожности
|
||||
8. **Предупреждения**: Критические соображения безопасности
|
||||
|
||||
Всегда приоритизируйте безопасность пользователя и защиту данных.
|
||||
|
||||
- id: 4
|
||||
name: "linux-command-verbose"
|
||||
description:
|
||||
en: "Detailed analysis with technical details"
|
||||
ru: "Подробный анализ с техническими деталями"
|
||||
content:
|
||||
en: |
|
||||
You are a Linux kernel and system expert. Generate Linux commands based on user task descriptions and provide an in-depth technical analysis.
|
||||
|
||||
Deliver a comprehensive technical breakdown:
|
||||
1. **Generated Command**: The Linux command that accomplishes the task
|
||||
2. **System Level**: How the command interacts with the kernel
|
||||
3. **Process Flow**: Step-by-step execution details
|
||||
4. **Resource Usage**: CPU, memory, I/O implications
|
||||
5. **File System**: Impact on files and directories
|
||||
6. **Network**: Network operations if applicable
|
||||
7. **Performance**: Optimization considerations
|
||||
8. **Debugging**: Troubleshooting approaches
|
||||
9. **Advanced Usage**: Expert-level techniques
|
||||
|
||||
Include technical details, system calls, and low-level operations.
|
||||
ru: |
|
||||
Вы эксперт по ядру Linux и системам. Генерируйте Linux команды на основе описаний задач пользователей и предоставляйте глубокий технический анализ.
|
||||
|
||||
Предоставьте исчерпывающий технический разбор:
|
||||
1. **Сгенерированная команда**: Linux команда, которая выполняет задачу
|
||||
2. **Системный уровень**: Как команда взаимодействует с ядром
|
||||
3. **Поток выполнения**: Детали пошагового выполнения
|
||||
4. **Использование ресурсов**: Последствия для CPU, памяти, I/O
|
||||
5. **Файловая система**: Воздействие на файлы и каталоги
|
||||
6. **Сеть**: Сетевые операции, если применимо
|
||||
7. **Производительность**: Соображения по оптимизации
|
||||
8. **Отладка**: Подходы к устранению неполадок
|
||||
9. **Продвинутое использование**: Техники экспертного уровня
|
||||
|
||||
Включите технические детали, системные вызовы и низкоуровневые операции.
|
||||
|
||||
- id: 5
|
||||
name: "linux-command-simple"
|
||||
description:
|
||||
en: "Simple and clear explanation"
|
||||
ru: "Простое и понятное объяснение"
|
||||
content:
|
||||
en: |
|
||||
You are a friendly Linux mentor. Explain the given command in simple, easy-to-understand terms.
|
||||
|
||||
Command: {{.command}}
|
||||
|
||||
Provide a beginner-friendly explanation:
|
||||
1. **What it does**: Simple, clear description
|
||||
2. **Why use it**: Common reasons to use this command
|
||||
3. **Basic example**: Simple usage example
|
||||
4. **What to expect**: Expected output or behavior
|
||||
5. **Tips**: Helpful hints for beginners
|
||||
|
||||
Use plain language, avoid jargon, and focus on practical understanding.
|
||||
ru: |
|
||||
Вы дружелюбный наставник по Linux. Объясните данную команду простыми, понятными терминами.
|
||||
|
||||
Команда: {{.command}}
|
||||
|
||||
Предоставьте объяснение, подходящее для начинающих:
|
||||
1. **Что она делает**: Простое, четкое описание
|
||||
2. **Зачем использовать**: Общие причины использования этой команды
|
||||
3. **Базовый пример**: Простой пример использования
|
||||
4. **Что ожидать**: Ожидаемый вывод или поведение
|
||||
5. **Советы**: Полезные подсказки для начинающих
|
||||
|
||||
Используйте простой язык, избегайте жаргона и сосредоточьтесь на практическом понимании.
|
||||
|
||||
- id: 6
|
||||
name: "verbose-v"
|
||||
description:
|
||||
en: "Prompt for v mode (basic explanation)"
|
||||
ru: "Промпт для режима v (базовое объяснение)"
|
||||
content:
|
||||
en: |
|
||||
You are a Linux command expert. You can provide a clear and concise explanation of the given Linux command.
|
||||
Your explanation should include:
|
||||
1. What this command does for the task
|
||||
2. Main parameters and their purpose
|
||||
3. Common use cases
|
||||
4. Any important warnings or considerations
|
||||
ru: |
|
||||
Вы эксперт по Linux командам. Вы можете предоставьте четкое и краткое объяснение заданной Linux команды.
|
||||
Ваши краткие объяснения должны включать:
|
||||
1. Что делает эта команда
|
||||
2. Основные параметры и их назначение
|
||||
3. Общие случаи использования
|
||||
4. Любые важные предупреждения или соображения
|
||||
|
||||
- id: 7
|
||||
name: "verbose-vv"
|
||||
description:
|
||||
en: "Prompt for vv mode (detailed explanation)"
|
||||
ru: "Промпт для режима vv (подробное объяснение)"
|
||||
content:
|
||||
en: |
|
||||
You are a Linux system expert. Provide a detailed technical explanation of the given command.
|
||||
|
||||
Provide a comprehensive analysis:
|
||||
1. **Command Purpose**: What it accomplishes
|
||||
2. **Syntax Breakdown**: Detailed parameter analysis
|
||||
3. **Technical Details**: How it works internally
|
||||
4. **Use Cases**: Practical scenarios and examples
|
||||
5. **Performance Impact**: Resource usage and optimization
|
||||
6. **Security Considerations**: Potential risks and mitigations
|
||||
7. **Advanced Usage**: Expert techniques and tips
|
||||
8. **Troubleshooting**: Common issues and solutions
|
||||
|
||||
Include technical depth while maintaining clarity.
|
||||
ru: |
|
||||
Вы эксперт по Linux системам. Предоставьте подробное техническое объяснение заданной команды.
|
||||
|
||||
Предоставьте исчерпывающий анализ:
|
||||
1. **Цель команды**: Что она достигает
|
||||
2. **Разбор синтаксиса**: Подробный анализ параметров
|
||||
3. **Технические детали**: Как она работает внутренне
|
||||
4. **Случаи использования**: Практические сценарии и примеры
|
||||
5. **Влияние на производительность**: Использование ресурсов и оптимизация
|
||||
6. **Соображения безопасности**: Потенциальные риски и меры по их снижению
|
||||
7. **Продвинутое использование**: Экспертные техники и советы
|
||||
8. **Устранение неполадок**: Общие проблемы и решения
|
||||
|
||||
Включите техническую глубину, сохраняя ясность.
|
||||
|
||||
- id: 8
|
||||
name: "verbose-vvv"
|
||||
description:
|
||||
en: "Prompt for vvv mode (maximum detailed explanation)"
|
||||
ru: "Промпт для режима vvv (максимально подробное объяснение)"
|
||||
content:
|
||||
en: |
|
||||
You are a Linux kernel and system architecture expert. Provide an exhaustive technical analysis of the given command.
|
||||
|
||||
Deliver a comprehensive technical deep-dive:
|
||||
1. **System Architecture**: How it fits into the Linux ecosystem
|
||||
2. **Kernel Interaction**: System calls and kernel operations
|
||||
3. **Process Management**: Process creation, scheduling, and lifecycle
|
||||
4. **Memory Management**: Memory allocation and management
|
||||
5. **File System Operations**: I/O operations and file system impact
|
||||
6. **Network Stack**: Network operations and protocols
|
||||
7. **Security Model**: Permissions, capabilities, and security implications
|
||||
8. **Performance Analysis**: CPU, memory, I/O, and network impact
|
||||
9. **Debugging and Profiling**: Advanced troubleshooting techniques
|
||||
10. **Source Code Analysis**: Key implementation details
|
||||
11. **Alternative Implementations**: Different approaches and trade-offs
|
||||
12. **Historical Context**: Evolution and development history
|
||||
|
||||
Provide maximum technical depth with system-level insights, code examples, and architectural understanding.
|
||||
ru: |
|
||||
Вы эксперт по ядру Linux и системной архитектуре. Предоставьте исчерпывающий технический анализ заданной команды.
|
||||
|
||||
Предоставьте исчерпывающий технический глубокий анализ:
|
||||
1. **Системная архитектура**: Как она вписывается в экосистему Linux
|
||||
2. **Взаимодействие с ядром**: Системные вызовы и операции ядра
|
||||
3. **Управление процессами**: Создание, планирование и жизненный цикл процессов
|
||||
4. **Управление памятью**: Выделение и управление памятью
|
||||
5. **Операции файловой системы**: I/O операции и воздействие на файловую систему
|
||||
6. **Сетевой стек**: Сетевые операции и протоколы
|
||||
7. **Модель безопасности**: Разрешения, возможности и последствия безопасности
|
||||
8. **Анализ производительности**: Воздействие на CPU, память, I/O и сеть
|
||||
9. **Отладка и профилирование**: Продвинутые техники устранения неполадок
|
||||
10. **Анализ исходного кода**: Ключевые детали реализации
|
||||
11. **Альтернативные реализации**: Разные подходы и компромиссы
|
||||
12. **Исторический контекст**: Эволюция и история разработки
|
||||
|
||||
Предоставьте максимальную техническую глубину с системными инсайтами, примерами кода и архитектурным пониманием.
|
||||
230
gpt/prompts.go
230
gpt/prompts.go
@@ -6,6 +6,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/direct-dev-ru/linux-command-gpt/config"
|
||||
)
|
||||
|
||||
// SystemPrompt представляет системный промпт
|
||||
@@ -21,28 +23,50 @@ type PromptManager struct {
|
||||
Prompts []SystemPrompt
|
||||
ConfigFile string
|
||||
HomeDir string
|
||||
Language string // Текущий язык для файла sys_prompts (en/ru)
|
||||
}
|
||||
|
||||
// NewPromptManager создает новый менеджер промптов
|
||||
func NewPromptManager(homeDir string) *PromptManager {
|
||||
configFile := filepath.Join(homeDir, ".lcg_prompts.json")
|
||||
// Используем конфигурацию из модуля config
|
||||
promptFolder := config.AppConfig.PromptFolder
|
||||
|
||||
// Путь к файлу sys_prompts
|
||||
sysPromptsFile := filepath.Join(promptFolder, "sys_prompts")
|
||||
|
||||
pm := &PromptManager{
|
||||
ConfigFile: configFile,
|
||||
ConfigFile: sysPromptsFile,
|
||||
HomeDir: homeDir,
|
||||
}
|
||||
|
||||
// Загружаем предустановленные промпты
|
||||
pm.loadDefaultPrompts()
|
||||
// Проверяем, существует ли файл sys_prompts
|
||||
if _, err := os.Stat(sysPromptsFile); os.IsNotExist(err) {
|
||||
// Если файла нет, создаем его с системными промптами и промптами подробности
|
||||
pm.createInitialPromptsFile()
|
||||
}
|
||||
|
||||
// Загружаем пользовательские промпты
|
||||
pm.loadCustomPrompts()
|
||||
// Загружаем все промпты из файла
|
||||
pm.loadAllPrompts()
|
||||
|
||||
return pm
|
||||
}
|
||||
|
||||
// createInitialPromptsFile создает начальный файл с системными промптами и промптами подробности
|
||||
func (pm *PromptManager) createInitialPromptsFile() {
|
||||
// Загружаем все встроенные промпты из YAML (английские по умолчанию)
|
||||
pm.Prompts = GetBuiltinPrompts()
|
||||
|
||||
// Фикс: при первичном сохранении явно выставляем язык файла
|
||||
if pm.Language == "" {
|
||||
pm.Language = "en"
|
||||
}
|
||||
|
||||
// Сохраняем все промпты в файл
|
||||
pm.saveAllPrompts()
|
||||
}
|
||||
|
||||
// loadDefaultPrompts загружает предустановленные промпты
|
||||
func (pm *PromptManager) loadDefaultPrompts() {
|
||||
func (pm *PromptManager) LoadDefaultPrompts() {
|
||||
defaultPrompts := []SystemPrompt{
|
||||
{
|
||||
ID: 1,
|
||||
@@ -79,8 +103,8 @@ func (pm *PromptManager) loadDefaultPrompts() {
|
||||
pm.Prompts = defaultPrompts
|
||||
}
|
||||
|
||||
// loadCustomPrompts загружает пользовательские промпты из файла
|
||||
func (pm *PromptManager) loadCustomPrompts() {
|
||||
// loadAllPrompts загружает все промпты из файла sys_prompts
|
||||
func (pm *PromptManager) loadAllPrompts() {
|
||||
if _, err := os.Stat(pm.ConfigFile); os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
@@ -90,18 +114,60 @@ func (pm *PromptManager) loadCustomPrompts() {
|
||||
return
|
||||
}
|
||||
|
||||
var customPrompts []SystemPrompt
|
||||
if err := json.Unmarshal(data, &customPrompts); err != nil {
|
||||
// Новый формат: объект с полями language и prompts
|
||||
var pf promptsFile
|
||||
if err := json.Unmarshal(data, &pf); err == nil && len(pf.Prompts) > 0 {
|
||||
pm.Language = pf.Language
|
||||
pm.Prompts = pf.Prompts
|
||||
return
|
||||
}
|
||||
|
||||
// Добавляем пользовательские промпты с новыми ID
|
||||
for i, prompt := range customPrompts {
|
||||
prompt.ID = len(pm.Prompts) + i + 1
|
||||
pm.Prompts = append(pm.Prompts, prompt)
|
||||
// Старый формат: просто массив промптов
|
||||
var prompts []SystemPrompt
|
||||
if err := json.Unmarshal(data, &prompts); err == nil {
|
||||
pm.Prompts = prompts
|
||||
pm.Language = "en"
|
||||
// Миграция в новый формат при следующем сохранении
|
||||
}
|
||||
}
|
||||
|
||||
// saveAllPrompts сохраняет все промпты в файл sys_prompts
|
||||
// внутренний формат хранения файла sys_prompts
|
||||
type promptsFile struct {
|
||||
Language string `json:"language,omitempty"`
|
||||
Prompts []SystemPrompt `json:"prompts"`
|
||||
}
|
||||
|
||||
func (pm *PromptManager) saveAllPrompts() error {
|
||||
pf := promptsFile{
|
||||
Language: pm.Language,
|
||||
Prompts: pm.Prompts,
|
||||
}
|
||||
data, err := json.MarshalIndent(pf, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(pm.ConfigFile, data, 0644)
|
||||
}
|
||||
|
||||
// SaveAllPrompts экспортированная версия saveAllPrompts
|
||||
func (pm *PromptManager) SaveAllPrompts() error {
|
||||
return pm.saveAllPrompts()
|
||||
}
|
||||
|
||||
// GetCurrentLanguage возвращает текущий язык из файла промптов
|
||||
func (pm *PromptManager) GetCurrentLanguage() string {
|
||||
if pm.Language == "" {
|
||||
return "en"
|
||||
}
|
||||
return pm.Language
|
||||
}
|
||||
|
||||
// SetLanguage устанавливает язык для всех промптов
|
||||
func (pm *PromptManager) SetLanguage(lang string) {
|
||||
pm.Language = lang
|
||||
}
|
||||
|
||||
// saveCustomPrompts сохраняет пользовательские промпты
|
||||
func (pm *PromptManager) saveCustomPrompts() error {
|
||||
// Находим пользовательские промпты (ID > 5)
|
||||
@@ -140,24 +206,136 @@ func (pm *PromptManager) GetPromptByName(name string) (*SystemPrompt, error) {
|
||||
return nil, fmt.Errorf("промпт с именем '%s' не найден", name)
|
||||
}
|
||||
|
||||
// AddPrompt добавляет новый промпт
|
||||
func (pm *PromptManager) AddPrompt(name, description, content string) error {
|
||||
// Находим максимальный ID
|
||||
maxID := 0
|
||||
for _, prompt := range pm.Prompts {
|
||||
if prompt.ID > maxID {
|
||||
maxID = prompt.ID
|
||||
}
|
||||
}
|
||||
|
||||
newPrompt := SystemPrompt{
|
||||
ID: maxID + 1,
|
||||
Name: name,
|
||||
Description: description,
|
||||
Content: content,
|
||||
}
|
||||
|
||||
pm.Prompts = append(pm.Prompts, newPrompt)
|
||||
return pm.saveAllPrompts()
|
||||
}
|
||||
|
||||
// UpdatePrompt обновляет существующий промпт
|
||||
func (pm *PromptManager) UpdatePrompt(id int, name, description, content string) error {
|
||||
for i, prompt := range pm.Prompts {
|
||||
if prompt.ID == id {
|
||||
pm.Prompts[i].Name = name
|
||||
pm.Prompts[i].Description = description
|
||||
pm.Prompts[i].Content = content
|
||||
return pm.saveAllPrompts()
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("промпт с ID %d не найден", id)
|
||||
}
|
||||
|
||||
// DeletePrompt удаляет промпт по ID
|
||||
func (pm *PromptManager) DeletePrompt(id int) error {
|
||||
for i, prompt := range pm.Prompts {
|
||||
if prompt.ID == id {
|
||||
pm.Prompts = append(pm.Prompts[:i], pm.Prompts[i+1:]...)
|
||||
return pm.saveAllPrompts()
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("промпт с ID %d не найден", id)
|
||||
}
|
||||
|
||||
// ListPrompts выводит список всех доступных промптов
|
||||
func (pm *PromptManager) ListPrompts() {
|
||||
fmt.Println("Available system prompts:")
|
||||
fmt.Println("ID | Name | Description")
|
||||
fmt.Println("---+---------------------------+--------------------------------")
|
||||
pm.ListPromptsWithFull(false)
|
||||
}
|
||||
|
||||
for _, prompt := range pm.Prompts {
|
||||
description := prompt.Description
|
||||
if len(description) > 80 {
|
||||
description = description[:77] + "..."
|
||||
// ListPromptsWithFull выводит список промптов с опцией полного вывода
|
||||
func (pm *PromptManager) ListPromptsWithFull(full bool) {
|
||||
fmt.Println("📝 Доступные системные промпты:")
|
||||
fmt.Println()
|
||||
|
||||
for i, prompt := range pm.Prompts {
|
||||
// Разделитель между промптами
|
||||
if i > 0 {
|
||||
fmt.Println("─" + strings.Repeat("─", 60))
|
||||
}
|
||||
fmt.Printf("%-2d | %-25s | %s\n",
|
||||
prompt.ID,
|
||||
truncateString(prompt.Name, 25),
|
||||
description)
|
||||
|
||||
// Проверяем, является ли промпт встроенным и неизмененным
|
||||
isDefault := pm.isDefaultPrompt(prompt)
|
||||
|
||||
// Заголовок промпта
|
||||
if isDefault {
|
||||
fmt.Printf("🔹 ID: %d | Название: %s | Встроенный\n", prompt.ID, prompt.Name)
|
||||
} else {
|
||||
fmt.Printf("🔹 ID: %d | Название: %s\n", prompt.ID, prompt.Name)
|
||||
}
|
||||
|
||||
// Описание
|
||||
if prompt.Description != "" {
|
||||
fmt.Printf("📋 Описание: %s\n", prompt.Description)
|
||||
}
|
||||
|
||||
// Содержимое промпта
|
||||
fmt.Println("📄 Содержимое:")
|
||||
fmt.Println("┌" + strings.Repeat("─", 58) + "┐")
|
||||
|
||||
// Разбиваем содержимое на строки и выводим с отступами
|
||||
lines := strings.Split(prompt.Content, "\n")
|
||||
for _, line := range lines {
|
||||
if full {
|
||||
// Полный вывод без обрезки - разбиваем длинные строки
|
||||
if len(line) > 56 {
|
||||
// Разбиваем длинную строку на части
|
||||
for i := 0; i < len(line); i += 56 {
|
||||
end := i + 56
|
||||
if end > len(line) {
|
||||
end = len(line)
|
||||
}
|
||||
fmt.Printf("│ %-56s │\n", line[i:end])
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("│ %-56s │\n", line)
|
||||
}
|
||||
} else {
|
||||
// Обычный вывод с обрезкой
|
||||
fmt.Printf("│ %-56s │\n", truncateString(line, 56))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("└" + strings.Repeat("─", 58) + "┘")
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// isDefaultPrompt проверяет, является ли промпт встроенным и неизмененным
|
||||
func (pm *PromptManager) isDefaultPrompt(prompt SystemPrompt) bool {
|
||||
// Используем новую функцию из builtin_prompts.go
|
||||
return IsBuiltinPrompt(prompt)
|
||||
}
|
||||
|
||||
// IsDefaultPromptByID проверяет, является ли промпт встроенным только по ID (игнорирует содержимое)
|
||||
func (pm *PromptManager) IsDefaultPromptByID(prompt SystemPrompt) bool {
|
||||
// Проверяем, что ID находится в диапазоне встроенных промптов (1-8)
|
||||
return prompt.ID >= 1 && prompt.ID <= 8
|
||||
}
|
||||
|
||||
// GetRussianDefaultPrompts возвращает русские версии встроенных промптов
|
||||
func GetRussianDefaultPrompts() []SystemPrompt {
|
||||
return GetBuiltinPromptsByLanguage("ru")
|
||||
}
|
||||
|
||||
// getDefaultPrompts возвращает оригинальные встроенные промпты
|
||||
func (pm *PromptManager) GetDefaultPrompts() []SystemPrompt {
|
||||
return GetBuiltinPrompts()
|
||||
}
|
||||
|
||||
// AddCustomPrompt добавляет новый пользовательский промпт
|
||||
func (pm *PromptManager) AddCustomPrompt(name, description, content string) error {
|
||||
// Проверяем, что имя уникально
|
||||
|
||||
113
main.go
113
main.go
@@ -27,6 +27,9 @@ var Version string
|
||||
// disableHistory управляет записью/обновлением истории на уровне процесса (флаг имеет приоритет над env)
|
||||
var disableHistory bool
|
||||
|
||||
// fromHistory указывает, что текущий ответ взят из истории
|
||||
var fromHistory bool
|
||||
|
||||
const (
|
||||
colorRed = "\033[31m"
|
||||
colorGreen = "\033[32m"
|
||||
@@ -41,6 +44,13 @@ const (
|
||||
func main() {
|
||||
_ = colorBlue
|
||||
|
||||
gpt.InitBuiltinPrompts("")
|
||||
|
||||
// Авто-инициализация sys_prompts при старте CLI (создаст файл при отсутствии)
|
||||
if currentUser, err := user.Current(); err == nil {
|
||||
_ = gpt.NewPromptManager(currentUser.HomeDir)
|
||||
}
|
||||
|
||||
app := &cli.App{
|
||||
Name: "lcg",
|
||||
Usage: "Linux Command GPT - Генерация Linux команд из описаний",
|
||||
@@ -97,6 +107,12 @@ Linux Command GPT - инструмент для генерации Linux ком
|
||||
DefaultText: "120",
|
||||
Value: 120,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Show debug information (request parameters and prompts)",
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
file := c.String("file")
|
||||
@@ -117,6 +133,7 @@ Linux Command GPT - инструмент для генерации Linux ком
|
||||
Sys: system,
|
||||
PromptID: promptID,
|
||||
Timeout: timeout,
|
||||
Debug: c.Bool("debug"),
|
||||
}
|
||||
disableHistory = config.AppConfig.MainFlags.NoHistory || config.AppConfig.IsNoHistoryEnabled()
|
||||
args := c.Args().Slice()
|
||||
@@ -384,10 +401,18 @@ func getCommands() []*cli.Command {
|
||||
Name: "list",
|
||||
Aliases: []string{"l"},
|
||||
Usage: "List all available prompts",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "full",
|
||||
Aliases: []string{"f"},
|
||||
Usage: "Show full content without truncation",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
currentUser, _ := user.Current()
|
||||
pm := gpt.NewPromptManager(currentUser.HomeDir)
|
||||
pm.ListPrompts()
|
||||
full := c.Bool("full")
|
||||
pm.ListPromptsWithFull(full)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
@@ -491,10 +516,43 @@ func getCommands() []*cli.Command {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "serve-result",
|
||||
Aliases: []string{"serve"},
|
||||
Usage: "Start HTTP server to browse saved results",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "port",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Server port",
|
||||
Value: config.AppConfig.Server.Port,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Aliases: []string{"H"},
|
||||
Usage: "Server host",
|
||||
Value: config.AppConfig.Server.Host,
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
port := c.String("port")
|
||||
host := c.String("host")
|
||||
|
||||
printColored(fmt.Sprintf("🌐 Запускаю HTTP сервер на %s:%s\n", host, port), colorCyan)
|
||||
printColored(fmt.Sprintf("📁 Папка результатов: %s\n", config.AppConfig.ResultFolder), colorYellow)
|
||||
printColored(fmt.Sprintf("🔗 Откройте в браузере: http://%s:%s\n", host, port), colorGreen)
|
||||
|
||||
return cmdPackage.StartResultServer(host, port)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func executeMain(file, system, commandInput string, timeout int) {
|
||||
// Выводим debug информацию если включен флаг
|
||||
if config.AppConfig.MainFlags.Debug {
|
||||
printDebugInfo(file, system, commandInput, timeout)
|
||||
}
|
||||
if file != "" {
|
||||
if err := reader.FileToPrompt(&commandInput, file); err != nil {
|
||||
printColored(fmt.Sprintf("❌ Ошибка чтения файла: %v\n", err), colorRed)
|
||||
@@ -518,6 +576,7 @@ func executeMain(file, system, commandInput string, timeout int) {
|
||||
// Проверка истории: если такой запрос уже встречался — предложить открыть из истории
|
||||
if !disableHistory {
|
||||
if found, hist := cmdPackage.CheckAndSuggestFromHistory(config.AppConfig.ResultHistory, commandInput); found && hist != nil {
|
||||
fromHistory = true // Устанавливаем флаг, что ответ из истории
|
||||
gpt3 := initGPT(system, timeout)
|
||||
printColored("\nВНИМАНИЕ: ОТВЕТ СФОРМИРОВАН ИИ. ТРЕБУЕТСЯ ПРОВЕРКА И КРИТИЧЕСКИЙ АНАЛИЗ. ВОЗМОЖНЫ ОШИБКИ И ГАЛЛЮЦИНАЦИИ.\n", colorRed)
|
||||
printColored("\n📋 Команда (из истории):\n", colorYellow)
|
||||
@@ -527,7 +586,7 @@ func executeMain(file, system, commandInput string, timeout int) {
|
||||
fmt.Println(hist.Explanation)
|
||||
}
|
||||
// Показали из истории — не выполняем запрос к API, сразу меню действий
|
||||
handlePostResponse(hist.Response, gpt3, system, commandInput, timeout)
|
||||
handlePostResponse(hist.Response, gpt3, system, commandInput, timeout, hist.Explanation)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -553,7 +612,8 @@ func executeMain(file, system, commandInput string, timeout int) {
|
||||
|
||||
// Сохраняем в историю (после завершения работы – т.е. позже, в зависимости от выбора действия)
|
||||
// Здесь не сохраняем, чтобы учесть правило: сохранять после действия, отличного от v/vv/vvv
|
||||
handlePostResponse(response, gpt3, system, commandInput, timeout)
|
||||
fromHistory = false // Сбрасываем флаг для новых запросов
|
||||
handlePostResponse(response, gpt3, system, commandInput, timeout, "")
|
||||
}
|
||||
|
||||
// checkAndSuggestFromHistory проверяет файл истории и при совпадении запроса предлагает показать сохраненный результат
|
||||
@@ -607,7 +667,7 @@ func getCommand(gpt3 gpt.Gpt3, cmd string) (string, float64) {
|
||||
return response, elapsed
|
||||
}
|
||||
|
||||
func handlePostResponse(response string, gpt3 gpt.Gpt3, system, cmd string, timeout int) {
|
||||
func handlePostResponse(response string, gpt3 gpt.Gpt3, system, cmd string, timeout int, explanation string) {
|
||||
fmt.Printf("Действия: (c)копировать, (s)сохранить, (r)перегенерировать, (e)выполнить, (v|vv|vvv)подробно, (n)ничего: ")
|
||||
var choice string
|
||||
fmt.Scanln(&choice)
|
||||
@@ -617,12 +677,24 @@ func handlePostResponse(response string, gpt3 gpt.Gpt3, system, cmd string, time
|
||||
clipboard.WriteAll(response)
|
||||
fmt.Println("✅ Команда скопирована в буфер обмена")
|
||||
if !disableHistory {
|
||||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||||
if fromHistory {
|
||||
cmdPackage.SaveToHistoryFromHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt, explanation)
|
||||
} else {
|
||||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||||
}
|
||||
}
|
||||
case "s":
|
||||
saveResponse(response, gpt3.Model, gpt3.Prompt, cmd)
|
||||
if fromHistory && strings.TrimSpace(explanation) != "" {
|
||||
saveResponse(response, gpt3.Model, gpt3.Prompt, cmd, explanation)
|
||||
} else {
|
||||
saveResponse(response, gpt3.Model, gpt3.Prompt, cmd)
|
||||
}
|
||||
if !disableHistory {
|
||||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||||
if fromHistory {
|
||||
cmdPackage.SaveToHistoryFromHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt, explanation)
|
||||
} else {
|
||||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||||
}
|
||||
}
|
||||
case "r":
|
||||
fmt.Println("🔄 Перегенерирую...")
|
||||
@@ -630,7 +702,11 @@ func handlePostResponse(response string, gpt3 gpt.Gpt3, system, cmd string, time
|
||||
case "e":
|
||||
executeCommand(response)
|
||||
if !disableHistory {
|
||||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||||
if fromHistory {
|
||||
cmdPackage.SaveToHistoryFromHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt, explanation)
|
||||
} else {
|
||||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||||
}
|
||||
}
|
||||
case "v", "vv", "vvv":
|
||||
level := len(choice) // 1, 2, 3
|
||||
@@ -647,7 +723,11 @@ func handlePostResponse(response string, gpt3 gpt.Gpt3, system, cmd string, time
|
||||
default:
|
||||
fmt.Println(" До свидания!")
|
||||
if !disableHistory {
|
||||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||||
if fromHistory {
|
||||
cmdPackage.SaveToHistoryFromHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt, explanation)
|
||||
} else {
|
||||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -702,4 +782,19 @@ func showTips() {
|
||||
fmt.Println(" • Команда 'history list' покажет историю запросов")
|
||||
fmt.Println(" • Команда 'config' покажет текущие настройки")
|
||||
fmt.Println(" • Команда 'health' проверит доступность API")
|
||||
fmt.Println(" • Команда 'serve-result' запустит HTTP сервер для просмотра результатов")
|
||||
}
|
||||
|
||||
// printDebugInfo выводит отладочную информацию о параметрах запроса
|
||||
func printDebugInfo(file, system, commandInput string, timeout int) {
|
||||
printColored("\n🔍 DEBUG ИНФОРМАЦИЯ:\n", colorCyan)
|
||||
fmt.Printf("📁 Файл: %s\n", file)
|
||||
fmt.Printf("🤖 Системный промпт: %s\n", system)
|
||||
fmt.Printf("💬 Запрос: %s\n", commandInput)
|
||||
fmt.Printf("⏱️ Таймаут: %d сек\n", timeout)
|
||||
fmt.Printf("🌐 Провайдер: %s\n", config.AppConfig.ProviderType)
|
||||
fmt.Printf("🏠 Хост: %s\n", config.AppConfig.Host)
|
||||
fmt.Printf("🧠 Модель: %s\n", config.AppConfig.Model)
|
||||
fmt.Printf("📝 История: %t\n", !config.AppConfig.MainFlags.NoHistory)
|
||||
printColored("────────────────────────────────────────\n", colorCyan)
|
||||
}
|
||||
|
||||
15
response.go
15
response.go
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/direct-dev-ru/linux-command-gpt/config"
|
||||
@@ -25,12 +26,22 @@ func writeFile(filePath, content string) {
|
||||
}
|
||||
}
|
||||
|
||||
func saveResponse(response string, gpt3Model string, prompt string, cmd string) {
|
||||
func saveResponse(response string, gpt3Model string, prompt string, cmd string, explanation ...string) {
|
||||
timestamp := nowTimestamp()
|
||||
filename := fmt.Sprintf("gpt_request_%s_%s.md", gpt3Model, timestamp)
|
||||
filePath := pathJoin(config.AppConfig.ResultFolder, filename)
|
||||
title := truncateTitle(cmd)
|
||||
content := fmt.Sprintf("# %s\n\n## Prompt\n\n%s\n\n## Response\n\n%s\n", title, cmd+". "+prompt, response)
|
||||
|
||||
var content string
|
||||
if len(explanation) > 0 && strings.TrimSpace(explanation[0]) != "" {
|
||||
// Если есть объяснение, сохраняем полную структуру
|
||||
content = fmt.Sprintf("# %s\n\n## Prompt\n\n%s\n\n## Response\n\n%s\n\n## Explanation\n\n%s\n",
|
||||
title, cmd+". "+prompt, response, explanation[0])
|
||||
} else {
|
||||
// Если объяснения нет, сохраняем базовую структуру
|
||||
content = fmt.Sprintf("# %s\n\n## Prompt\n\n%s\n\n## Response\n\n%s\n",
|
||||
title, cmd+". "+prompt, response)
|
||||
}
|
||||
writeFile(filePath, content)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user