Created
February 13, 2025 12:25
-
-
Save jenya239/e2817c5c392947aa3cb924bca1bd345f to your computer and use it in GitHub Desktop.
скрипт генерации краткого описания проекта
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'net/http' | |
require 'json' | |
require 'uri' | |
require 'digest' | |
require 'fileutils' | |
# Константы | |
OPENAI_API_KEY = ENV['OPENAI_API_KEY'] | |
OPENAI_API_URL = "https://api.openai.com/v1/chat/completions" | |
MODEL = "gpt-3.5-turbo" | |
MAX_FILE_SIZE = 20 * 1024 # 20 КБ | |
ALLOWED_EXTENSIONS = %w[cpp hpp txt rb js py java cs go rs] # расширения файлов | |
# Пути к служебной директории и файлам базы данных | |
SERVICE_DIR = ".service_data" | |
DB_FILE = File.join(SERVICE_DIR, "db.json") | |
DIR_SUMMARY_FILE = File.join(SERVICE_DIR, "dir_summaries.json") | |
FINAL_SUMMARY_FILE = "project_summary.txt" | |
FINAL_SUMMARY_DATA_FILE = File.join(SERVICE_DIR, "final_summary.json") | |
# Rate limiting: 3 запроса в минуту -> минимум 20 секунд между запросами | |
RATE_LIMIT_DELAY = 20.0 | |
$last_api_call_time ||= Time.now - RATE_LIMIT_DELAY | |
# Функции для работы с базой данных файлов | |
def load_db | |
if File.exist?(DB_FILE) | |
JSON.parse(File.read(DB_FILE)) | |
else | |
{} | |
end | |
end | |
def save_db(db) | |
File.write(DB_FILE, JSON.pretty_generate(db)) | |
end | |
# Функции для работы с описаниями директорий | |
def load_dir_summaries | |
if File.exist?(DIR_SUMMARY_FILE) | |
JSON.parse(File.read(DIR_SUMMARY_FILE)) | |
else | |
{} | |
end | |
end | |
def save_dir_summaries(summaries) | |
File.write(DIR_SUMMARY_FILE, JSON.pretty_generate(summaries)) | |
end | |
# Функции для работы с итоговым описанием проекта | |
def load_final_summary | |
if File.exist?(FINAL_SUMMARY_DATA_FILE) | |
JSON.parse(File.read(FINAL_SUMMARY_DATA_FILE)) | |
else | |
nil | |
end | |
end | |
def save_final_summary(data) | |
File.write(FINAL_SUMMARY_DATA_FILE, JSON.pretty_generate(data)) | |
end | |
# Вычисление SHA256-хэша файла | |
def compute_file_hash(file_path) | |
Digest::SHA256.file(file_path).hexdigest | |
end | |
# Отправка запроса к API OpenAI через Net::HTTP с учетом rate limiting | |
def send_openai_request(prompt) | |
# Rate limiting: не более 3 запросов в минуту | |
elapsed = Time.now - $last_api_call_time | |
if elapsed < RATE_LIMIT_DELAY | |
sleep_time = RATE_LIMIT_DELAY - elapsed | |
puts "Ожидание #{sleep_time.round(2)} секунд из-за ограничения запросов..." | |
sleep(sleep_time) | |
end | |
$last_api_call_time = Time.now | |
uri = URI(OPENAI_API_URL) | |
header = { | |
"Content-Type" => "application/json", | |
"Authorization" => "Bearer #{OPENAI_API_KEY}" | |
} | |
body = { | |
"model" => MODEL, | |
"messages" => [ | |
{"role" => "system", "content" => "Ты — ассистент, который генерирует краткие технические описания кода."}, | |
{"role" => "user", "content" => prompt} | |
], | |
"temperature" => 0.2 | |
} | |
http = Net::HTTP.new(uri.host, uri.port) | |
http.use_ssl = true | |
request = Net::HTTP::Post.new(uri.request_uri, header) | |
request.body = body.to_json | |
response = http.request(request) | |
if response.code.to_i == 200 | |
result = JSON.parse(response.body) | |
result["choices"][0]["message"]["content"] | |
else | |
puts "Ошибка запроса: #{response.code} #{response.body}" | |
nil | |
end | |
end | |
# Генерация описания для файла с помощью промпта | |
def generate_file_description(file_path, file_content) | |
prompt = <<~PROMPT | |
Дай краткое техническое описание файла с исходным кодом. Выдели основные имена, классы, функции и опиши базовую логику работы. Сделай описание существенно короче, чем исходный файл. | |
Файл: #{file_path} | |
-------------------------------------------------- | |
#{file_content} | |
PROMPT | |
send_openai_request(prompt) | |
end | |
# Генерация описания для директории на основе описаний файлов | |
def generate_directory_description(dir_path, file_descriptions) | |
combined_text = file_descriptions.join("\n") | |
prompt = <<~PROMPT | |
На основе следующих описаний файлов, дай краткое описание директории "#{dir_path}", выделив основные имена и ключевые элементы кода. Сделай описание кратким: | |
#{combined_text} | |
PROMPT | |
send_openai_request(prompt) | |
end | |
# Генерация итогового описания проекта на основе описаний директорий | |
def generate_project_summary(dir_summaries) | |
# Используем только поле "summary" из сохранённых описаний директорий | |
combined_text = dir_summaries.map { |_, data| data["summary"] }.join("\n") | |
prompt = <<~PROMPT | |
На основе следующих описаний директорий, дай краткое техническое описание проекта, выделив ключевые компоненты и базовую логику. Сделай итоговое описание кратким: | |
#{combined_text} | |
PROMPT | |
send_openai_request(prompt) | |
end | |
# --- Основная логика скрипта --- | |
# Создаём служебную директорию, если её нет | |
FileUtils.mkdir_p(SERVICE_DIR) | |
# Загружаем базу данных для файлов и директорий | |
db = load_db | |
dir_summaries = load_dir_summaries | |
# Хеш для группировки описаний файлов по директориям | |
directory_files = {} | |
# Обходим все файлы в директории src (рекурсивно) | |
Dir.glob("src/**/*") do |file| | |
next unless File.file?(file) | |
ext = File.extname(file).downcase.delete_prefix('.') | |
next unless ALLOWED_EXTENSIONS.include?(ext) | |
next if File.size(file) > MAX_FILE_SIZE | |
mod_time = File.mtime(file).to_s | |
file_hash = compute_file_hash(file) | |
db_entry = db[file] | |
needs_update = db_entry.nil? || (db_entry["modified_time"] != mod_time) || (db_entry["hash"] != file_hash) | |
if needs_update | |
file_content = File.read(file) | |
puts "Генерирую описание для файла: #{file}" | |
description = generate_file_description(file, file_content) | |
if description | |
db[file] = { | |
"modified_time" => mod_time, | |
"hash" => file_hash, | |
"description" => description | |
} | |
else | |
puts "Не удалось получить описание для файла #{file}" | |
next | |
end | |
else | |
description = db_entry["description"] | |
end | |
# Группируем описания по директориям | |
dir = File.dirname(file) | |
directory_files[dir] ||= [] | |
directory_files[dir] << description | |
end | |
# Сохраняем обновлённую базу данных файлов | |
save_db(db) | |
# Обрабатываем каждую директорию: генерируем описание на основе описаний файлов | |
directory_files.each do |dir, descriptions| | |
combined_text = descriptions.join("\n") | |
current_hash = Digest::SHA256.hexdigest(combined_text) | |
if dir_summaries.key?(dir) && dir_summaries[dir]["hash"] == current_hash | |
puts "Директория #{dir} не изменилась, используем ранее сохранённое описание." | |
else | |
puts "Генерирую описание для директории: #{dir}" | |
dir_summary_text = generate_directory_description(dir, descriptions) | |
if dir_summary_text | |
dir_summaries[dir] = {"hash" => current_hash, "summary" => dir_summary_text} | |
else | |
puts "Не удалось получить описание для директории #{dir}" | |
end | |
end | |
end | |
# Сохраняем описания директорий | |
save_dir_summaries(dir_summaries) | |
# Генерируем итоговое описание проекта на основе описаний директорий | |
puts "Генерирую итоговое описание проекта..." | |
# Для итогового описания используем объединённый текст описаний директорий в отсортированном порядке | |
final_input_text = dir_summaries.sort_by { |dir, _| dir }.map { |_, data| data["summary"] }.join("\n") | |
final_input_hash = Digest::SHA256.hexdigest(final_input_text) | |
final_summary_data = load_final_summary | |
if final_summary_data && final_summary_data["hash"] == final_input_hash | |
puts "Итоговое описание проекта не изменилось, используем ранее сохранённое." | |
project_summary = final_summary_data["summary"] | |
else | |
project_summary = generate_project_summary(dir_summaries) | |
if project_summary | |
final_summary_data = {"hash" => final_input_hash, "summary" => project_summary} | |
save_final_summary(final_summary_data) | |
else | |
puts "Не удалось сгенерировать итоговое описание проекта." | |
end | |
end | |
if project_summary | |
File.write(FINAL_SUMMARY_FILE, project_summary) | |
puts "Итоговое описание проекта сохранено в файл #{FINAL_SUMMARY_FILE}" | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment