147 lines
4.9 KiB
Python
Raw Normal View History

from contextlib import contextmanager
import os
import sys
import json
from typing import Tuple, List, Optional, Any
from git import Repo, InvalidGitRepositoryError, GitCommandError
import rich_click as click
from devchat.config import ConfigManager, OpenAIModelConfig
from devchat.utils import find_root_dir, add_gitignore, setup_logger, get_logger
logger = get_logger(__name__)
@contextmanager
def handle_errors():
"""Handle errors in the CLI."""
try:
yield
except Exception as error:
# import traceback
# traceback.print_exc()
logger.exception(error)
click.echo(f"{type(error).__name__}: {error}", err=True)
sys.exit(1)
def init_dir() -> Tuple[str, str]:
"""
Initialize the chat directories.
Returns:
repo_chat_dir: The chat directory in the repository.
user_chat_dir: The chat directory in the user's home.
"""
repo_dir, user_dir = find_root_dir()
if not repo_dir and not user_dir:
click.echo(f"Error: Failed to find home for .chat: {repo_dir}, {user_dir}", err=True)
sys.exit(1)
if not repo_dir:
repo_dir = user_dir
elif not user_dir:
user_dir = repo_dir
try:
repo_chat_dir = os.path.join(repo_dir, ".chat")
if not os.path.exists(repo_chat_dir):
os.makedirs(repo_chat_dir)
except Exception:
pass
try:
user_chat_dir = os.path.join(user_dir, ".chat")
if not os.path.exists(user_chat_dir):
os.makedirs(user_chat_dir)
except Exception:
pass
if not os.path.isdir(repo_chat_dir):
repo_chat_dir = user_chat_dir
if not os.path.isdir(user_chat_dir):
user_chat_dir = repo_chat_dir
if not os.path.isdir(repo_chat_dir) or not os.path.isdir(user_chat_dir):
click.echo(f"Error: Failed to create {repo_chat_dir} and {user_chat_dir}", err=True)
sys.exit(1)
try:
setup_logger(os.path.join(repo_chat_dir, 'error.log'))
add_gitignore(repo_chat_dir, '*')
except Exception as exc:
logger.error("Failed to setup logger or add .gitignore: %s", exc)
return repo_chat_dir, user_chat_dir
def valid_git_repo(target_dir: str, valid_urls: List[str]) -> bool:
"""
Check if a directory is a valid Git repository and if its URL is in a list of valid URLs.
:param target_dir: The path of the directory to check.
:param valid_urls: A list of valid Git repository URLs.
:return: True if the directory is a valid Git repository with a valid URL, False otherwise.
"""
try:
repo = Repo(target_dir)
repo_url = next(repo.remote().urls)
return repo_url in valid_urls
except InvalidGitRepositoryError:
logger.exception("Not a valid Git repository: %s", target_dir)
return False
def clone_git_repo(target_dir: str, repo_urls: List[str]):
"""
Clone a Git repository from a list of possible URLs.
:param target_dir: The path where the repository should be cloned.
:param repo_urls: A list of possible Git repository URLs.
"""
for url in repo_urls:
try:
click.echo(f"Cloning repository {url} to {target_dir}")
Repo.clone_from(url, target_dir)
click.echo("Cloned successfully")
return
except GitCommandError:
logger.exception("Failed to clone repository %s to %s", url, target_dir)
continue
raise GitCommandError(f"Failed to clone repository to {target_dir}")
def parse_legacy_config(config_json_file) -> Tuple[str, OpenAIModelConfig]:
with open(config_json_file, 'r', encoding='utf-8') as file:
legacy_data = json.load(file)
if 'model' not in legacy_data:
return None, None
model = legacy_data['model']
config = OpenAIModelConfig()
if 'tokens-per-prompt' in legacy_data:
config.max_input_tokens = legacy_data['tokens-per-prompt']
if 'OpenAI' in legacy_data:
if 'temperature' in legacy_data['OpenAI']:
config.temperature = legacy_data['OpenAI']['temperature']
if 'stream' in legacy_data['OpenAI']:
config.stream = legacy_data['OpenAI']['stream']
return model, config
def get_model_config(repo_chat_dir: str, user_chat_dir: str,
model: Optional[str] = None) -> Tuple[str, Any]:
manager = ConfigManager(user_chat_dir)
legacy_path = os.path.join(repo_chat_dir, 'config.json')
if os.path.isfile(legacy_path):
if manager.file_is_new or os.path.getmtime(legacy_path) > manager.file_last_modified:
model, config = parse_legacy_config(legacy_path)
if model:
manager.config.default_model = model
if config:
manager.update_model_config(model, config)
manager.sync()
os.rename(legacy_path, legacy_path + '.old')
return manager.model_config(model)