mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-29 08:44:59 +00:00
Merge branch 'main' into watch
This commit is contained in:
commit
c9a27ba6ac
36 changed files with 1496 additions and 993 deletions
2
.github/workflows/docker-build-test.yml
vendored
2
.github/workflows/docker-build-test.yml
vendored
|
@ -24,6 +24,8 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
2
.github/workflows/docker-release.yml
vendored
2
.github/workflows/docker-release.yml
vendored
|
@ -12,6 +12,8 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
4
.github/workflows/pages.yml
vendored
4
.github/workflows/pages.yml
vendored
|
@ -36,7 +36,9 @@ jobs:
|
||||||
working-directory: aider/website
|
working-directory: aider/website
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -12,6 +12,8 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
|
|
4
.github/workflows/ubuntu-tests.yml
vendored
4
.github/workflows/ubuntu-tests.yml
vendored
|
@ -25,6 +25,8 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
|
@ -38,5 +40,7 @@ jobs:
|
||||||
pip install .
|
pip install .
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
env:
|
||||||
|
AIDER_ANALYTICS: false
|
||||||
run: |
|
run: |
|
||||||
pytest
|
pytest
|
||||||
|
|
4
.github/workflows/windows-tests.yml
vendored
4
.github/workflows/windows-tests.yml
vendored
|
@ -25,6 +25,8 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
|
@ -38,6 +40,8 @@ jobs:
|
||||||
pip install .
|
pip install .
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
env:
|
||||||
|
AIDER_ANALYTICS: false
|
||||||
run: |
|
run: |
|
||||||
pytest
|
pytest
|
||||||
|
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -11,6 +11,7 @@ _site
|
||||||
.jekyll-cache/
|
.jekyll-cache/
|
||||||
.jekyll-metadata
|
.jekyll-metadata
|
||||||
aider/__version__.py
|
aider/__version__.py
|
||||||
|
aider/_version.py
|
||||||
.venv/
|
.venv/
|
||||||
.#*
|
.#*
|
||||||
.gitattributes
|
.gitattributes
|
||||||
|
|
|
@ -3,12 +3,17 @@
|
||||||
### main branch
|
### main branch
|
||||||
|
|
||||||
- PDF support for Sonnet and Gemini models.
|
- PDF support for Sonnet and Gemini models.
|
||||||
|
- Added `--voice-input-device` to select audio input device for voice recording, by @preynal.
|
||||||
|
- Added `--timeout` option to configure API call timeouts.
|
||||||
- Set cwd to repo root when running shell commands.
|
- Set cwd to repo root when running shell commands.
|
||||||
- Improved error handling for failed .gitignore file operations.
|
- Improved error handling for failed .gitignore file operations.
|
||||||
- Improved error handling for input history file permissions.
|
- Improved error handling for input history file permissions.
|
||||||
- Improved error handling for analytics file access.
|
- Improved error handling for analytics file access.
|
||||||
- Removed broken support for Dart.
|
- Removed broken support for Dart.
|
||||||
- Aider wrote 85% of the code in this release.
|
- Bugfix when scraping URLs found in chat messages.
|
||||||
|
- Better handling of __version__ import errors.
|
||||||
|
- Improved `/drop` command to support substring matching for non-glob patterns.
|
||||||
|
- Aider wrote 79% of the code in this release.
|
||||||
|
|
||||||
### Aider v0.65.1
|
### Aider v0.65.1
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,20 @@
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
__version__ = "0.65.2.dev"
|
||||||
|
safe_version = __version__
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from aider.__version__ import __version__
|
from aider._version import __version__
|
||||||
except Exception:
|
except Exception:
|
||||||
__version__ = "0.65.2.dev"
|
__version__ = safe_version + "+import"
|
||||||
|
|
||||||
|
if type(__version__) is not str:
|
||||||
|
__version__ = safe_version + "+type"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if version.parse(__version__) < version.parse(safe_version):
|
||||||
|
__version__ = safe_version + "+less"
|
||||||
|
except Exception:
|
||||||
|
__version__ = safe_version + "+parse"
|
||||||
|
|
||||||
__all__ = [__version__]
|
__all__ = [__version__]
|
||||||
|
|
|
@ -214,7 +214,3 @@ class Analytics:
|
||||||
with open(self.logfile, "a") as f:
|
with open(self.logfile, "a") as f:
|
||||||
json.dump(log_entry, f)
|
json.dump(log_entry, f)
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self.ph:
|
|
||||||
self.ph.shutdown()
|
|
||||||
|
|
|
@ -205,6 +205,12 @@ def get_parser(default_config_files, git_root):
|
||||||
default=True,
|
default=True,
|
||||||
help="Verify the SSL cert when connecting to models (default: True)",
|
help="Verify the SSL cert when connecting to models (default: True)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--timeout",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="Timeout in seconds for API calls (default: None)",
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--edit-format",
|
"--edit-format",
|
||||||
"--chat-mode",
|
"--chat-mode",
|
||||||
|
@ -559,7 +565,7 @@ def get_parser(default_config_files, git_root):
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--test",
|
"--test",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Run tests and fix problems found",
|
help="Run tests, fix problems found and then exit",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -770,6 +776,12 @@ def get_parser(default_config_files, git_root):
|
||||||
default="en",
|
default="en",
|
||||||
help="Specify the language for voice using ISO 639-1 code (default: auto)",
|
help="Specify the language for voice using ISO 639-1 code (default: auto)",
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--voice-input-device",
|
||||||
|
metavar="VOICE_INPUT_DEVICE",
|
||||||
|
default=None,
|
||||||
|
help="Specify the input device name for voice recording",
|
||||||
|
)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
|
@ -771,6 +771,7 @@ class Coder:
|
||||||
self.lint_outcome = None
|
self.lint_outcome = None
|
||||||
self.test_outcome = None
|
self.test_outcome = None
|
||||||
self.shell_commands = []
|
self.shell_commands = []
|
||||||
|
self.message_cost = 0
|
||||||
|
|
||||||
if self.repo:
|
if self.repo:
|
||||||
self.commit_before_message.append(self.repo.get_head_commit_sha())
|
self.commit_before_message.append(self.repo.get_head_commit_sha())
|
||||||
|
@ -884,6 +885,7 @@ class Coder:
|
||||||
thresh = 2 # seconds
|
thresh = 2 # seconds
|
||||||
if self.last_keyboard_interrupt and now - self.last_keyboard_interrupt < thresh:
|
if self.last_keyboard_interrupt and now - self.last_keyboard_interrupt < thresh:
|
||||||
self.io.tool_warning("\n\n^C KeyboardInterrupt")
|
self.io.tool_warning("\n\n^C KeyboardInterrupt")
|
||||||
|
self.event("exit", reason="Control-C")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
self.io.tool_warning("\n\n^C again to exit")
|
self.io.tool_warning("\n\n^C again to exit")
|
||||||
|
@ -1185,6 +1187,8 @@ class Coder:
|
||||||
return chunks
|
return chunks
|
||||||
|
|
||||||
def send_message(self, inp):
|
def send_message(self, inp):
|
||||||
|
self.event("message_send_starting")
|
||||||
|
|
||||||
self.cur_messages += [
|
self.cur_messages += [
|
||||||
dict(role="user", content=inp),
|
dict(role="user", content=inp),
|
||||||
]
|
]
|
||||||
|
@ -1264,6 +1268,7 @@ class Coder:
|
||||||
lines = traceback.format_exception(type(err), err, err.__traceback__)
|
lines = traceback.format_exception(type(err), err, err.__traceback__)
|
||||||
self.io.tool_warning("".join(lines))
|
self.io.tool_warning("".join(lines))
|
||||||
self.io.tool_error(str(err))
|
self.io.tool_error(str(err))
|
||||||
|
self.event("message_send_exception", exception=str(err))
|
||||||
return
|
return
|
||||||
finally:
|
finally:
|
||||||
if self.mdstream:
|
if self.mdstream:
|
||||||
|
|
|
@ -808,15 +808,33 @@ class Commands:
|
||||||
# Expand tilde in the path
|
# Expand tilde in the path
|
||||||
expanded_word = os.path.expanduser(word)
|
expanded_word = os.path.expanduser(word)
|
||||||
|
|
||||||
# Handle read-only files separately, without glob_filtered_to_repo
|
# Handle read-only files with substring matching and samefile check
|
||||||
read_only_matched = [f for f in self.coder.abs_read_only_fnames if expanded_word in f]
|
read_only_matched = []
|
||||||
|
for f in self.coder.abs_read_only_fnames:
|
||||||
|
if expanded_word in f:
|
||||||
|
read_only_matched.append(f)
|
||||||
|
continue
|
||||||
|
|
||||||
if read_only_matched:
|
# Try samefile comparison for relative paths
|
||||||
for matched_file in read_only_matched:
|
try:
|
||||||
self.coder.abs_read_only_fnames.remove(matched_file)
|
abs_word = os.path.abspath(expanded_word)
|
||||||
self.io.tool_output(f"Removed read-only file {matched_file} from the chat")
|
if os.path.samefile(abs_word, f):
|
||||||
|
read_only_matched.append(f)
|
||||||
|
except (FileNotFoundError, OSError):
|
||||||
|
continue
|
||||||
|
|
||||||
matched_files = self.glob_filtered_to_repo(expanded_word)
|
for matched_file in read_only_matched:
|
||||||
|
self.coder.abs_read_only_fnames.remove(matched_file)
|
||||||
|
self.io.tool_output(f"Removed read-only file {matched_file} from the chat")
|
||||||
|
|
||||||
|
# For editable files, use glob if word contains glob chars, otherwise use substring
|
||||||
|
if any(c in expanded_word for c in "*?[]"):
|
||||||
|
matched_files = self.glob_filtered_to_repo(expanded_word)
|
||||||
|
else:
|
||||||
|
# Use substring matching like we do for read-only files
|
||||||
|
matched_files = [
|
||||||
|
self.coder.get_rel_fname(f) for f in self.coder.abs_fnames if expanded_word in f
|
||||||
|
]
|
||||||
|
|
||||||
if not matched_files:
|
if not matched_files:
|
||||||
matched_files.append(expanded_word)
|
matched_files.append(expanded_word)
|
||||||
|
@ -904,11 +922,12 @@ class Commands:
|
||||||
|
|
||||||
def cmd_exit(self, args):
|
def cmd_exit(self, args):
|
||||||
"Exit the application"
|
"Exit the application"
|
||||||
|
self.coder.event("exit", reason="/exit")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def cmd_quit(self, args):
|
def cmd_quit(self, args):
|
||||||
"Exit the application"
|
"Exit the application"
|
||||||
sys.exit()
|
self.cmd_exit(args)
|
||||||
|
|
||||||
def cmd_ls(self, args):
|
def cmd_ls(self, args):
|
||||||
"List all known files and indicate which are included in the chat session"
|
"List all known files and indicate which are included in the chat session"
|
||||||
|
@ -1080,7 +1099,9 @@ class Commands:
|
||||||
self.io.tool_error("To use /voice you must provide an OpenAI API key.")
|
self.io.tool_error("To use /voice you must provide an OpenAI API key.")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self.voice = voice.Voice(audio_format=self.args.voice_format)
|
self.voice = voice.Voice(
|
||||||
|
audio_format=self.args.voice_format, device_name=self.args.voice_input_device
|
||||||
|
)
|
||||||
except voice.SoundDeviceError:
|
except voice.SoundDeviceError:
|
||||||
self.io.tool_error(
|
self.io.tool_error(
|
||||||
"Unable to import `sounddevice` and/or `soundfile`, is portaudio installed?"
|
"Unable to import `sounddevice` and/or `soundfile`, is portaudio installed?"
|
||||||
|
|
|
@ -468,6 +468,10 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
litellm._lazy_module.client_session = httpx.Client(verify=False)
|
litellm._lazy_module.client_session = httpx.Client(verify=False)
|
||||||
litellm._lazy_module.aclient_session = httpx.AsyncClient(verify=False)
|
litellm._lazy_module.aclient_session = httpx.AsyncClient(verify=False)
|
||||||
|
|
||||||
|
if args.timeout:
|
||||||
|
litellm._load_litellm()
|
||||||
|
litellm._lazy_module.request_timeout = args.timeout
|
||||||
|
|
||||||
if args.dark_mode:
|
if args.dark_mode:
|
||||||
args.user_input_color = "#32FF32"
|
args.user_input_color = "#32FF32"
|
||||||
args.tool_error_color = "#FF3333"
|
args.tool_error_color = "#FF3333"
|
||||||
|
@ -548,9 +552,11 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
|
|
||||||
if args.gui and not return_coder:
|
if args.gui and not return_coder:
|
||||||
if not check_streamlit_install(io):
|
if not check_streamlit_install(io):
|
||||||
|
analytics.event("exit", reason="Streamlit not installed")
|
||||||
return
|
return
|
||||||
analytics.event("gui session")
|
analytics.event("gui session")
|
||||||
launch_gui(argv)
|
launch_gui(argv)
|
||||||
|
analytics.event("exit", reason="GUI session ended")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
|
@ -577,6 +583,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
io.tool_output(
|
io.tool_output(
|
||||||
"Provide either a single directory of a git repo, or a list of one or more files."
|
"Provide either a single directory of a git repo, or a list of one or more files."
|
||||||
)
|
)
|
||||||
|
analytics.event("exit", reason="Invalid directory input")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
git_dname = None
|
git_dname = None
|
||||||
|
@ -587,6 +594,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
fnames = []
|
fnames = []
|
||||||
else:
|
else:
|
||||||
io.tool_error(f"{all_files[0]} is a directory, but --no-git selected.")
|
io.tool_error(f"{all_files[0]} is a directory, but --no-git selected.")
|
||||||
|
analytics.event("exit", reason="Directory with --no-git")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# We can't know the git repo for sure until after parsing the args.
|
# We can't know the git repo for sure until after parsing the args.
|
||||||
|
@ -595,18 +603,22 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
if args.git and not force_git_root:
|
if args.git and not force_git_root:
|
||||||
right_repo_root = guessed_wrong_repo(io, git_root, fnames, git_dname)
|
right_repo_root = guessed_wrong_repo(io, git_root, fnames, git_dname)
|
||||||
if right_repo_root:
|
if right_repo_root:
|
||||||
|
analytics.event("exit", reason="Recursing with correct repo")
|
||||||
return main(argv, input, output, right_repo_root, return_coder=return_coder)
|
return main(argv, input, output, right_repo_root, return_coder=return_coder)
|
||||||
|
|
||||||
if args.just_check_update:
|
if args.just_check_update:
|
||||||
update_available = check_version(io, just_check=True, verbose=args.verbose)
|
update_available = check_version(io, just_check=True, verbose=args.verbose)
|
||||||
|
analytics.event("exit", reason="Just checking update")
|
||||||
return 0 if not update_available else 1
|
return 0 if not update_available else 1
|
||||||
|
|
||||||
if args.install_main_branch:
|
if args.install_main_branch:
|
||||||
success = install_from_main_branch(io)
|
success = install_from_main_branch(io)
|
||||||
|
analytics.event("exit", reason="Installed main branch")
|
||||||
return 0 if success else 1
|
return 0 if success else 1
|
||||||
|
|
||||||
if args.upgrade:
|
if args.upgrade:
|
||||||
success = install_upgrade(io)
|
success = install_upgrade(io)
|
||||||
|
analytics.event("exit", reason="Upgrade completed")
|
||||||
return 0 if success else 1
|
return 0 if success else 1
|
||||||
|
|
||||||
if args.check_update:
|
if args.check_update:
|
||||||
|
@ -614,6 +626,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
|
|
||||||
if args.list_models:
|
if args.list_models:
|
||||||
models.print_matching_models(io, args.list_models)
|
models.print_matching_models(io, args.list_models)
|
||||||
|
analytics.event("exit", reason="Listed models")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if args.git:
|
if args.git:
|
||||||
|
@ -657,6 +670,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
if len(parts) != 2:
|
if len(parts) != 2:
|
||||||
io.tool_error(f"Invalid alias format: {alias_def}")
|
io.tool_error(f"Invalid alias format: {alias_def}")
|
||||||
io.tool_output("Format should be: alias:model-name")
|
io.tool_output("Format should be: alias:model-name")
|
||||||
|
analytics.event("exit", reason="Invalid alias format error")
|
||||||
return 1
|
return 1
|
||||||
alias, model = parts
|
alias, model = parts
|
||||||
models.MODEL_ALIASES[alias.strip()] = model.strip()
|
models.MODEL_ALIASES[alias.strip()] = model.strip()
|
||||||
|
@ -685,6 +699,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
|
|
||||||
lint_cmds = parse_lint_cmds(args.lint_cmd, io)
|
lint_cmds = parse_lint_cmds(args.lint_cmd, io)
|
||||||
if lint_cmds is None:
|
if lint_cmds is None:
|
||||||
|
analytics.event("exit", reason="Invalid lint command format")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if args.show_model_warnings:
|
if args.show_model_warnings:
|
||||||
|
@ -697,6 +712,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
io.offer_url(urls.model_warnings, "Open documentation url for more info?")
|
io.offer_url(urls.model_warnings, "Open documentation url for more info?")
|
||||||
io.tool_output()
|
io.tool_output()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
analytics.event("exit", reason="Keyboard interrupt during model warnings")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
repo = None
|
repo = None
|
||||||
|
@ -720,6 +736,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
|
|
||||||
if not args.skip_sanity_check_repo:
|
if not args.skip_sanity_check_repo:
|
||||||
if not sanity_check_repo(repo, io):
|
if not sanity_check_repo(repo, io):
|
||||||
|
analytics.event("exit", reason="Repository sanity check failed")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if repo:
|
if repo:
|
||||||
|
@ -787,12 +804,15 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
except UnknownEditFormat as err:
|
except UnknownEditFormat as err:
|
||||||
io.tool_error(str(err))
|
io.tool_error(str(err))
|
||||||
io.offer_url(urls.edit_formats, "Open documentation about edit formats?")
|
io.offer_url(urls.edit_formats, "Open documentation about edit formats?")
|
||||||
|
analytics.event("exit", reason="Unknown edit format")
|
||||||
return 1
|
return 1
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
io.tool_error(str(err))
|
io.tool_error(str(err))
|
||||||
|
analytics.event("exit", reason="ValueError during coder creation")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if return_coder:
|
if return_coder:
|
||||||
|
analytics.event("exit", reason="Returning coder object")
|
||||||
return coder
|
return coder
|
||||||
|
|
||||||
ignores = []
|
ignores = []
|
||||||
|
@ -810,6 +830,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
]
|
]
|
||||||
messages = coder.format_messages().all_messages()
|
messages = coder.format_messages().all_messages()
|
||||||
utils.show_messages(messages)
|
utils.show_messages(messages)
|
||||||
|
analytics.event("exit", reason="Showed prompts")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.lint:
|
if args.lint:
|
||||||
|
@ -818,6 +839,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
if args.test:
|
if args.test:
|
||||||
if not args.test_cmd:
|
if not args.test_cmd:
|
||||||
io.tool_error("No --test-cmd provided.")
|
io.tool_error("No --test-cmd provided.")
|
||||||
|
analytics.event("exit", reason="No test command provided")
|
||||||
return 1
|
return 1
|
||||||
test_errors = coder.commands.cmd_test(args.test_cmd)
|
test_errors = coder.commands.cmd_test(args.test_cmd)
|
||||||
if test_errors:
|
if test_errors:
|
||||||
|
@ -830,32 +852,30 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
coder.commands.cmd_commit()
|
coder.commands.cmd_commit()
|
||||||
|
|
||||||
if args.lint or args.test or args.commit:
|
if args.lint or args.test or args.commit:
|
||||||
|
analytics.event("exit", reason="Completed lint/test/commit")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.show_repo_map:
|
if args.show_repo_map:
|
||||||
repo_map = coder.get_repo_map()
|
repo_map = coder.get_repo_map()
|
||||||
if repo_map:
|
if repo_map:
|
||||||
io.tool_output(repo_map)
|
io.tool_output(repo_map)
|
||||||
|
analytics.event("exit", reason="Showed repo map")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.apply:
|
if args.apply:
|
||||||
content = io.read_text(args.apply)
|
content = io.read_text(args.apply)
|
||||||
if content is None:
|
if content is None:
|
||||||
|
analytics.event("exit", reason="Failed to read apply content")
|
||||||
return
|
return
|
||||||
coder.partial_response_content = content
|
coder.partial_response_content = content
|
||||||
coder.apply_updates()
|
coder.apply_updates()
|
||||||
|
analytics.event("exit", reason="Applied updates")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.apply_clipboard_edits:
|
if args.apply_clipboard_edits:
|
||||||
args.edit_format = main_model.editor_edit_format
|
args.edit_format = main_model.editor_edit_format
|
||||||
args.message = "/paste"
|
args.message = "/paste"
|
||||||
|
|
||||||
if "VSCODE_GIT_IPC_HANDLE" in os.environ:
|
|
||||||
args.pretty = False
|
|
||||||
io.tool_output("VSCode terminal detected, pretty output has been disabled.")
|
|
||||||
|
|
||||||
io.tool_output('Use /help <question> for help, run "aider --help" to see cmd line args')
|
|
||||||
|
|
||||||
if args.show_release_notes is True:
|
if args.show_release_notes is True:
|
||||||
io.tool_output(f"Opening release notes: {urls.release_notes}")
|
io.tool_output(f"Opening release notes: {urls.release_notes}")
|
||||||
io.tool_output()
|
io.tool_output()
|
||||||
|
@ -887,6 +907,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
coder.run(with_message=args.message)
|
coder.run(with_message=args.message)
|
||||||
except SwitchCoder:
|
except SwitchCoder:
|
||||||
pass
|
pass
|
||||||
|
analytics.event("exit", reason="Completed --message")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.message_file:
|
if args.message_file:
|
||||||
|
@ -896,13 +917,18 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
coder.run(with_message=message_from_file)
|
coder.run(with_message=message_from_file)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
io.tool_error(f"Message file not found: {args.message_file}")
|
io.tool_error(f"Message file not found: {args.message_file}")
|
||||||
|
analytics.event("exit", reason="Message file not found")
|
||||||
return 1
|
return 1
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
io.tool_error(f"Error reading message file: {e}")
|
io.tool_error(f"Error reading message file: {e}")
|
||||||
|
analytics.event("exit", reason="Message file IO error")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
analytics.event("exit", reason="Completed --message-file")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.exit:
|
if args.exit:
|
||||||
|
analytics.event("exit", reason="Exit flag set")
|
||||||
return
|
return
|
||||||
|
|
||||||
analytics.event("cli session", main_model=main_model, edit_format=main_model.edit_format)
|
analytics.event("cli session", main_model=main_model, edit_format=main_model.edit_format)
|
||||||
|
@ -910,6 +936,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
coder.run()
|
coder.run()
|
||||||
|
analytics.event("exit", reason="Completed main CLI coder.run")
|
||||||
return
|
return
|
||||||
except SwitchCoder as switch:
|
except SwitchCoder as switch:
|
||||||
kwargs = dict(io=io, from_coder=coder)
|
kwargs = dict(io=io, from_coder=coder)
|
||||||
|
@ -928,6 +955,10 @@ def is_first_run_of_new_version(io, verbose=False):
|
||||||
installs_file = Path.home() / ".aider" / "installs.json"
|
installs_file = Path.home() / ".aider" / "installs.json"
|
||||||
key = (__version__, sys.executable)
|
key = (__version__, sys.executable)
|
||||||
|
|
||||||
|
# Never show notes for .dev versions
|
||||||
|
if ".dev" in __version__:
|
||||||
|
return False
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
io.tool_output(
|
io.tool_output(
|
||||||
f"Checking imports for version {__version__} and executable {sys.executable}"
|
f"Checking imports for version {__version__} and executable {sys.executable}"
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Voice:
|
||||||
|
|
||||||
threshold = 0.15
|
threshold = 0.15
|
||||||
|
|
||||||
def __init__(self, audio_format="wav"):
|
def __init__(self, audio_format="wav", device_name=None):
|
||||||
if sf is None:
|
if sf is None:
|
||||||
raise SoundDeviceError
|
raise SoundDeviceError
|
||||||
try:
|
try:
|
||||||
|
@ -42,6 +42,27 @@ class Voice:
|
||||||
import sounddevice as sd
|
import sounddevice as sd
|
||||||
|
|
||||||
self.sd = sd
|
self.sd = sd
|
||||||
|
|
||||||
|
|
||||||
|
devices = sd.query_devices()
|
||||||
|
|
||||||
|
if device_name:
|
||||||
|
# Find the device with matching name
|
||||||
|
device_id = None
|
||||||
|
for i, device in enumerate(devices):
|
||||||
|
if device_name in device["name"]:
|
||||||
|
device_id = i
|
||||||
|
break
|
||||||
|
if device_id is None:
|
||||||
|
available_inputs = [d["name"] for d in devices if d["max_input_channels"] > 0]
|
||||||
|
raise ValueError(f"Device '{device_name}' not found. Available input devices: {available_inputs}")
|
||||||
|
|
||||||
|
print(f"Using input device: {device_name} (ID: {device_id})")
|
||||||
|
|
||||||
|
self.device_id = device_id
|
||||||
|
else:
|
||||||
|
self.device_id = None
|
||||||
|
|
||||||
except (OSError, ModuleNotFoundError):
|
except (OSError, ModuleNotFoundError):
|
||||||
raise SoundDeviceError
|
raise SoundDeviceError
|
||||||
if audio_format not in ["wav", "mp3", "webm"]:
|
if audio_format not in ["wav", "mp3", "webm"]:
|
||||||
|
@ -93,7 +114,7 @@ class Voice:
|
||||||
temp_wav = tempfile.mktemp(suffix=".wav")
|
temp_wav = tempfile.mktemp(suffix=".wav")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sample_rate = int(self.sd.query_devices(None, "input")["default_samplerate"])
|
sample_rate = int(self.sd.query_devices(self.device_id, "input")["default_samplerate"])
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
sample_rate = 16000 # fallback to 16kHz if unable to query device
|
sample_rate = 16000 # fallback to 16kHz if unable to query device
|
||||||
except self.sd.PortAudioError:
|
except self.sd.PortAudioError:
|
||||||
|
@ -104,7 +125,7 @@ class Voice:
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.sd.InputStream(samplerate=sample_rate, channels=1, callback=self.callback):
|
with self.sd.InputStream(samplerate=sample_rate, channels=1, callback=self.callback, device=self.device_id):
|
||||||
prompt(self.get_prompt, refresh_interval=0.1)
|
prompt(self.get_prompt, refresh_interval=0.1)
|
||||||
except self.sd.PortAudioError as err:
|
except self.sd.PortAudioError as err:
|
||||||
raise SoundDeviceError(f"Error accessing audio input device: {err}")
|
raise SoundDeviceError(f"Error accessing audio input device: {err}")
|
||||||
|
|
|
@ -27,12 +27,17 @@ cog.out(text)
|
||||||
### main branch
|
### main branch
|
||||||
|
|
||||||
- PDF support for Sonnet and Gemini models.
|
- PDF support for Sonnet and Gemini models.
|
||||||
|
- Added `--voice-input-device` to select audio input device for voice recording, by @preynal.
|
||||||
|
- Added `--timeout` option to configure API call timeouts.
|
||||||
- Set cwd to repo root when running shell commands.
|
- Set cwd to repo root when running shell commands.
|
||||||
- Improved error handling for failed .gitignore file operations.
|
- Improved error handling for failed .gitignore file operations.
|
||||||
- Improved error handling for input history file permissions.
|
- Improved error handling for input history file permissions.
|
||||||
- Improved error handling for analytics file access.
|
- Improved error handling for analytics file access.
|
||||||
- Removed broken support for Dart.
|
- Removed broken support for Dart.
|
||||||
- Aider wrote 85% of the code in this release.
|
- Bugfix when scraping URLs found in chat messages.
|
||||||
|
- Better handling of __version__ import errors.
|
||||||
|
- Improved `/drop` command to support substring matching for non-glob patterns.
|
||||||
|
- Aider wrote 79% of the code in this release.
|
||||||
|
|
||||||
### Aider v0.65.1
|
### Aider v0.65.1
|
||||||
|
|
||||||
|
|
|
@ -1945,4 +1945,78 @@
|
||||||
command: aider --model gemini/gemini-exp-1114
|
command: aider --model gemini/gemini-exp-1114
|
||||||
date: 2024-11-15
|
date: 2024-11-15
|
||||||
versions: 0.63.2.dev
|
versions: 0.63.2.dev
|
||||||
seconds_per_case: 38.6
|
seconds_per_case: 38.6
|
||||||
|
- dirname: 2024-11-27-07-41-51--qwen2.5-coder-14b-whole-1
|
||||||
|
test_cases: 133
|
||||||
|
model: ollama/qwen2.5-coder:14b
|
||||||
|
edit_format: whole
|
||||||
|
commit_hash: 200295e
|
||||||
|
pass_rate_1: 53.4
|
||||||
|
pass_rate_2: 61.7
|
||||||
|
percent_cases_well_formed: 98.5
|
||||||
|
error_outputs: 4
|
||||||
|
num_malformed_responses: 4
|
||||||
|
num_with_malformed_responses: 2
|
||||||
|
user_asks: 48
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 2
|
||||||
|
indentation_errors: 2
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
test_timeouts: 2
|
||||||
|
command: aider --model ollama/qwen2.5-coder:14b
|
||||||
|
date: 2024-11-27
|
||||||
|
versions: 0.65.2.dev
|
||||||
|
seconds_per_case: 58.0
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-11-28-07-42-56--qwen2.5-coder-32b-whole-4
|
||||||
|
test_cases: 133
|
||||||
|
model: ollama/qwen2.5-coder:32b
|
||||||
|
edit_format: whole
|
||||||
|
commit_hash: 200295e
|
||||||
|
pass_rate_1: 58.6
|
||||||
|
pass_rate_2: 72.9
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
lazy_comments: 0
|
||||||
|
syntax_errors: 0
|
||||||
|
indentation_errors: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
command: aider --model ollama/qwen2.5-coder:32b
|
||||||
|
date: 2024-11-28
|
||||||
|
versions: 0.65.2.dev
|
||||||
|
seconds_per_case: 147.5
|
||||||
|
total_cost: 0.0000
|
||||||
|
- dirname: 2024-11-28-13-14-00--tulu3-whole-2
|
||||||
|
test_cases: 133
|
||||||
|
model: ollama/tulu3
|
||||||
|
edit_format: whole
|
||||||
|
commit_hash: 200295e
|
||||||
|
pass_rate_1: 21.8
|
||||||
|
pass_rate_2: 26.3
|
||||||
|
percent_cases_well_formed: 100.0
|
||||||
|
error_outputs: 0
|
||||||
|
num_malformed_responses: 0
|
||||||
|
num_with_malformed_responses: 0
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
command: aider --model ollama/tulu3
|
||||||
|
date: 2024-11-28
|
||||||
|
versions: 0.65.2.dev
|
||||||
|
seconds_per_case: 35.8
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
||||||
|
- dirname: 2024-11-28-14-41-46--granite3-dense-8b-whole-1
|
||||||
|
test_cases: 133
|
||||||
|
model: ollama/granite3-dense:8b
|
||||||
|
edit_format: whole
|
||||||
|
commit_hash: 200295e
|
||||||
|
pass_rate_1: 17.3
|
||||||
|
pass_rate_2: 20.3
|
||||||
|
percent_cases_well_formed: 78.9
|
||||||
|
exhausted_context_windows: 0
|
||||||
|
command: aider --model ollama/granite3-dense:8b
|
||||||
|
date: 2024-11-28
|
||||||
|
versions: 0.65.2.dev
|
||||||
|
seconds_per_case: 38.1
|
||||||
|
total_cost: 0.0000
|
||||||
|
|
|
@ -81,6 +81,9 @@ if run with Ollama's default 2k context window.
|
||||||
|
|
||||||
## Benchmark results
|
## Benchmark results
|
||||||
|
|
||||||
|
{: .note :}
|
||||||
|
These are results from single benchmark runs, so expect normal variance of +/- 1-2%.
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<script>
|
<script>
|
||||||
{% include quant-chart.js %}
|
{% include quant-chart.js %}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -97,6 +97,9 @@
|
||||||
## Verify the SSL cert when connecting to models (default: True)
|
## Verify the SSL cert when connecting to models (default: True)
|
||||||
#verify-ssl: true
|
#verify-ssl: true
|
||||||
|
|
||||||
|
## Timeout in seconds for API calls (default: None)
|
||||||
|
#timeout: xxx
|
||||||
|
|
||||||
## Specify what edit format the LLM should use (default depends on model)
|
## Specify what edit format the LLM should use (default depends on model)
|
||||||
#edit-format: xxx
|
#edit-format: xxx
|
||||||
|
|
||||||
|
@ -273,7 +276,7 @@
|
||||||
## Enable/disable automatic testing after changes (default: False)
|
## Enable/disable automatic testing after changes (default: False)
|
||||||
#auto-test: false
|
#auto-test: false
|
||||||
|
|
||||||
## Run tests and fix problems found
|
## Run tests, fix problems found and then exit
|
||||||
#test: false
|
#test: false
|
||||||
|
|
||||||
############
|
############
|
||||||
|
@ -390,3 +393,6 @@
|
||||||
|
|
||||||
## Specify the language for voice using ISO 639-1 code (default: auto)
|
## Specify the language for voice using ISO 639-1 code (default: auto)
|
||||||
#voice-language: en
|
#voice-language: en
|
||||||
|
|
||||||
|
## Specify the input device name for voice recording
|
||||||
|
#voice-input-device: xxx
|
||||||
|
|
|
@ -96,6 +96,9 @@
|
||||||
## Verify the SSL cert when connecting to models (default: True)
|
## Verify the SSL cert when connecting to models (default: True)
|
||||||
#AIDER_VERIFY_SSL=true
|
#AIDER_VERIFY_SSL=true
|
||||||
|
|
||||||
|
## Timeout in seconds for API calls (default: None)
|
||||||
|
#AIDER_TIMEOUT=
|
||||||
|
|
||||||
## Specify what edit format the LLM should use (default depends on model)
|
## Specify what edit format the LLM should use (default depends on model)
|
||||||
#AIDER_EDIT_FORMAT=
|
#AIDER_EDIT_FORMAT=
|
||||||
|
|
||||||
|
@ -267,7 +270,7 @@
|
||||||
## Enable/disable automatic testing after changes (default: False)
|
## Enable/disable automatic testing after changes (default: False)
|
||||||
#AIDER_AUTO_TEST=false
|
#AIDER_AUTO_TEST=false
|
||||||
|
|
||||||
## Run tests and fix problems found
|
## Run tests, fix problems found and then exit
|
||||||
#AIDER_TEST=false
|
#AIDER_TEST=false
|
||||||
|
|
||||||
############
|
############
|
||||||
|
@ -368,3 +371,6 @@
|
||||||
|
|
||||||
## Specify the language for voice using ISO 639-1 code (default: auto)
|
## Specify the language for voice using ISO 639-1 code (default: auto)
|
||||||
#AIDER_VOICE_LANGUAGE=en
|
#AIDER_VOICE_LANGUAGE=en
|
||||||
|
|
||||||
|
## Specify the input device name for voice recording
|
||||||
|
#AIDER_VOICE_INPUT_DEVICE=
|
||||||
|
|
|
@ -153,6 +153,9 @@ cog.outl("```")
|
||||||
## Verify the SSL cert when connecting to models (default: True)
|
## Verify the SSL cert when connecting to models (default: True)
|
||||||
#verify-ssl: true
|
#verify-ssl: true
|
||||||
|
|
||||||
|
## Timeout in seconds for API calls (default: None)
|
||||||
|
#timeout: xxx
|
||||||
|
|
||||||
## Specify what edit format the LLM should use (default depends on model)
|
## Specify what edit format the LLM should use (default depends on model)
|
||||||
#edit-format: xxx
|
#edit-format: xxx
|
||||||
|
|
||||||
|
@ -329,7 +332,7 @@ cog.outl("```")
|
||||||
## Enable/disable automatic testing after changes (default: False)
|
## Enable/disable automatic testing after changes (default: False)
|
||||||
#auto-test: false
|
#auto-test: false
|
||||||
|
|
||||||
## Run tests and fix problems found
|
## Run tests, fix problems found and then exit
|
||||||
#test: false
|
#test: false
|
||||||
|
|
||||||
############
|
############
|
||||||
|
@ -446,5 +449,8 @@ cog.outl("```")
|
||||||
|
|
||||||
## Specify the language for voice using ISO 639-1 code (default: auto)
|
## Specify the language for voice using ISO 639-1 code (default: auto)
|
||||||
#voice-language: en
|
#voice-language: en
|
||||||
|
|
||||||
|
## Specify the input device name for voice recording
|
||||||
|
#voice-input-device: xxx
|
||||||
```
|
```
|
||||||
<!--[[[end]]]-->
|
<!--[[[end]]]-->
|
||||||
|
|
|
@ -138,6 +138,9 @@ cog.outl("```")
|
||||||
## Verify the SSL cert when connecting to models (default: True)
|
## Verify the SSL cert when connecting to models (default: True)
|
||||||
#AIDER_VERIFY_SSL=true
|
#AIDER_VERIFY_SSL=true
|
||||||
|
|
||||||
|
## Timeout in seconds for API calls (default: None)
|
||||||
|
#AIDER_TIMEOUT=
|
||||||
|
|
||||||
## Specify what edit format the LLM should use (default depends on model)
|
## Specify what edit format the LLM should use (default depends on model)
|
||||||
#AIDER_EDIT_FORMAT=
|
#AIDER_EDIT_FORMAT=
|
||||||
|
|
||||||
|
@ -309,7 +312,7 @@ cog.outl("```")
|
||||||
## Enable/disable automatic testing after changes (default: False)
|
## Enable/disable automatic testing after changes (default: False)
|
||||||
#AIDER_AUTO_TEST=false
|
#AIDER_AUTO_TEST=false
|
||||||
|
|
||||||
## Run tests and fix problems found
|
## Run tests, fix problems found and then exit
|
||||||
#AIDER_TEST=false
|
#AIDER_TEST=false
|
||||||
|
|
||||||
############
|
############
|
||||||
|
@ -410,7 +413,8 @@ cog.outl("```")
|
||||||
|
|
||||||
## Specify the language for voice using ISO 639-1 code (default: auto)
|
## Specify the language for voice using ISO 639-1 code (default: auto)
|
||||||
#AIDER_VOICE_LANGUAGE=en
|
#AIDER_VOICE_LANGUAGE=en
|
||||||
|
|
||||||
|
## Specify the input device name for voice recording
|
||||||
|
#AIDER_VOICE_INPUT_DEVICE=
|
||||||
```
|
```
|
||||||
<!--[[[end]]]-->
|
<!--[[[end]]]-->
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ usage: aider [-h] [--openai-api-key] [--anthropic-api-key] [--model]
|
||||||
[--openai-api-type] [--openai-api-version]
|
[--openai-api-type] [--openai-api-version]
|
||||||
[--openai-api-deployment-id] [--openai-organization-id]
|
[--openai-api-deployment-id] [--openai-organization-id]
|
||||||
[--model-settings-file] [--model-metadata-file]
|
[--model-settings-file] [--model-metadata-file]
|
||||||
[--alias] [--verify-ssl | --no-verify-ssl]
|
[--alias] [--verify-ssl | --no-verify-ssl] [--timeout]
|
||||||
[--edit-format] [--architect] [--weak-model]
|
[--edit-format] [--architect] [--weak-model]
|
||||||
[--editor-model] [--editor-edit-format]
|
[--editor-model] [--editor-edit-format]
|
||||||
[--show-model-warnings | --no-show-model-warnings]
|
[--show-model-warnings | --no-show-model-warnings]
|
||||||
|
@ -76,6 +76,7 @@ usage: aider [-h] [--openai-api-key] [--anthropic-api-key] [--model]
|
||||||
[--fancy-input | --no-fancy-input]
|
[--fancy-input | --no-fancy-input]
|
||||||
[--detect-urls | --no-detect-urls] [--editor]
|
[--detect-urls | --no-detect-urls] [--editor]
|
||||||
[--voice-format] [--voice-language]
|
[--voice-format] [--voice-language]
|
||||||
|
[--voice-input-device]
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -204,6 +205,10 @@ Aliases:
|
||||||
- `--verify-ssl`
|
- `--verify-ssl`
|
||||||
- `--no-verify-ssl`
|
- `--no-verify-ssl`
|
||||||
|
|
||||||
|
### `--timeout VALUE`
|
||||||
|
Timeout in seconds for API calls (default: None)
|
||||||
|
Environment variable: `AIDER_TIMEOUT`
|
||||||
|
|
||||||
### `--edit-format EDIT_FORMAT`
|
### `--edit-format EDIT_FORMAT`
|
||||||
Specify what edit format the LLM should use (default depends on model)
|
Specify what edit format the LLM should use (default depends on model)
|
||||||
Environment variable: `AIDER_EDIT_FORMAT`
|
Environment variable: `AIDER_EDIT_FORMAT`
|
||||||
|
@ -509,7 +514,7 @@ Aliases:
|
||||||
- `--no-auto-test`
|
- `--no-auto-test`
|
||||||
|
|
||||||
### `--test`
|
### `--test`
|
||||||
Run tests and fix problems found
|
Run tests, fix problems found and then exit
|
||||||
Default: False
|
Default: False
|
||||||
Environment variable: `AIDER_TEST`
|
Environment variable: `AIDER_TEST`
|
||||||
|
|
||||||
|
@ -701,4 +706,8 @@ Environment variable: `AIDER_VOICE_FORMAT`
|
||||||
Specify the language for voice using ISO 639-1 code (default: auto)
|
Specify the language for voice using ISO 639-1 code (default: auto)
|
||||||
Default: en
|
Default: en
|
||||||
Environment variable: `AIDER_VOICE_LANGUAGE`
|
Environment variable: `AIDER_VOICE_LANGUAGE`
|
||||||
|
|
||||||
|
### `--voice-input-device VOICE_INPUT_DEVICE`
|
||||||
|
Specify the input device name for voice recording
|
||||||
|
Environment variable: `AIDER_VOICE_INPUT_DEVICE`
|
||||||
<!--[[[end]]]-->
|
<!--[[[end]]]-->
|
||||||
|
|
|
@ -53,6 +53,7 @@ Installing PortAudio is completely optional, but can usually be accomplished lik
|
||||||
- For Windows, there is no need to install PortAudio.
|
- For Windows, there is no need to install PortAudio.
|
||||||
- For Mac, do `brew install portaudio`
|
- For Mac, do `brew install portaudio`
|
||||||
- For Linux, do `sudo apt-get install libportaudio2`
|
- For Linux, do `sudo apt-get install libportaudio2`
|
||||||
|
- Some linux environments may also need `sudo apt install libasound2-plugins`
|
||||||
|
|
||||||
## Add aider to your editor
|
## Add aider to your editor
|
||||||
|
|
||||||
|
|
|
@ -181,6 +181,6 @@ mod_dates = [get_last_modified_date(file) for file in files]
|
||||||
latest_mod_date = max(mod_dates)
|
latest_mod_date = max(mod_dates)
|
||||||
cog.out(f"{latest_mod_date.strftime('%B %d, %Y.')}")
|
cog.out(f"{latest_mod_date.strftime('%B %d, %Y.')}")
|
||||||
]]]-->
|
]]]-->
|
||||||
November 24, 2024.
|
November 28, 2024.
|
||||||
<!--[[[end]]]-->
|
<!--[[[end]]]-->
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -47,10 +47,44 @@ def find_latest_benchmark_dir():
|
||||||
print("Error: No benchmark directories found under tmp.benchmarks.")
|
print("Error: No benchmark directories found under tmp.benchmarks.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
latest_dir = max(
|
# Get current time and 24 hours ago
|
||||||
benchmark_dirs,
|
now = datetime.datetime.now()
|
||||||
key=lambda d: next((f.stat().st_mtime for f in d.rglob("*.md") if f.is_file()), 0),
|
day_ago = now - datetime.timedelta(days=1)
|
||||||
)
|
|
||||||
|
# Filter directories by name pattern YYYY-MM-DD-HH-MM-SS--
|
||||||
|
recent_dirs = []
|
||||||
|
for d in benchmark_dirs:
|
||||||
|
try:
|
||||||
|
# Extract datetime from directory name
|
||||||
|
date_str = d.name[:19] # Takes YYYY-MM-DD-HH-MM-SS
|
||||||
|
dir_date = datetime.datetime.strptime(date_str, "%Y-%m-%d-%H-%M-%S")
|
||||||
|
if dir_date >= day_ago:
|
||||||
|
recent_dirs.append(d)
|
||||||
|
except ValueError:
|
||||||
|
# Skip directories that don't match the expected format
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not recent_dirs:
|
||||||
|
print("Error: No benchmark directories found from the last 24 hours.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Find directory with most recently modified .md file
|
||||||
|
latest_dir = None
|
||||||
|
latest_time = 0
|
||||||
|
|
||||||
|
for d in recent_dirs:
|
||||||
|
# Look for .md files in subdirectories
|
||||||
|
for md_file in d.glob("*/.*.md"):
|
||||||
|
if md_file.is_file():
|
||||||
|
mtime = md_file.stat().st_mtime
|
||||||
|
if mtime > latest_time:
|
||||||
|
latest_time = mtime
|
||||||
|
latest_dir = d
|
||||||
|
|
||||||
|
if not latest_dir:
|
||||||
|
print("Error: No .md files found in recent benchmark directories.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
print(f"Using the most recently updated benchmark directory: {latest_dir.name}")
|
print(f"Using the most recently updated benchmark directory: {latest_dir.name}")
|
||||||
return latest_dir
|
return latest_dir
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ requires = ["setuptools>=68", "setuptools_scm[toml]>=8"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[tool.setuptools_scm]
|
[tool.setuptools_scm]
|
||||||
write_to = "aider/__version__.py"
|
write_to = "aider/_version.py"
|
||||||
|
|
||||||
[tool.codespell]
|
[tool.codespell]
|
||||||
skip = "*.svg,Gemfile.lock"
|
skip = "*.svg,Gemfile.lock"
|
||||||
|
|
|
@ -6,5 +6,7 @@ testpaths =
|
||||||
tests/help
|
tests/help
|
||||||
tests/browser
|
tests/browser
|
||||||
tests/scrape
|
tests/scrape
|
||||||
|
|
||||||
|
env =
|
||||||
|
AIDER_ANALYTICS=false
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# pip-compile --output-file=requirements-dev.txt requirements-dev.in --upgrade
|
# pip-compile --output-file=requirements-dev.txt requirements-dev.in --upgrade
|
||||||
#
|
#
|
||||||
pytest
|
pytest
|
||||||
|
pytest-env
|
||||||
pip-tools
|
pip-tools
|
||||||
lox
|
lox
|
||||||
matplotlib
|
matplotlib
|
||||||
|
|
|
@ -145,6 +145,10 @@ pyproject-hooks==1.2.0
|
||||||
# build
|
# build
|
||||||
# pip-tools
|
# pip-tools
|
||||||
pytest==8.3.3
|
pytest==8.3.3
|
||||||
|
# via
|
||||||
|
# -r requirements/requirements-dev.in
|
||||||
|
# pytest-env
|
||||||
|
pytest-env==1.1.5
|
||||||
# via -r requirements/requirements-dev.in
|
# via -r requirements/requirements-dev.in
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
# via
|
# via
|
||||||
|
|
|
@ -22,7 +22,9 @@ def has_been_reopened(issue_number):
|
||||||
# Load environment variables from .env file
|
# Load environment variables from .env file
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
BOT_SUFFIX = """Note: A [bot script](https://github.com/Aider-AI/aider/blob/main/scripts/issues.py) made these updates to the issue.
|
BOT_SUFFIX = """
|
||||||
|
|
||||||
|
Note: [A bot script](https://github.com/Aider-AI/aider/blob/main/scripts/issues.py) made these updates to the issue.
|
||||||
""" # noqa
|
""" # noqa
|
||||||
|
|
||||||
DUPLICATE_COMMENT = (
|
DUPLICATE_COMMENT = (
|
||||||
|
|
|
@ -37,10 +37,39 @@ def main():
|
||||||
# Get the git log output
|
# Get the git log output
|
||||||
diff_content = run_git_log()
|
diff_content = run_git_log()
|
||||||
|
|
||||||
# Save to temporary file
|
# Extract relevant portion of HISTORY.md
|
||||||
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".diff") as tmp:
|
base_ver = get_base_version()
|
||||||
tmp.write(diff_content)
|
with open("HISTORY.md", "r") as f:
|
||||||
tmp_path = tmp.name
|
history_content = f.read()
|
||||||
|
|
||||||
|
# Find the section for this version
|
||||||
|
version_header = f"### Aider v{base_ver}"
|
||||||
|
start_idx = history_content.find("# Release history")
|
||||||
|
if start_idx == -1:
|
||||||
|
raise ValueError("Could not find start of release history")
|
||||||
|
|
||||||
|
# Find where this version's section ends
|
||||||
|
version_idx = history_content.find(version_header, start_idx)
|
||||||
|
if version_idx == -1:
|
||||||
|
raise ValueError(f"Could not find version header: {version_header}")
|
||||||
|
|
||||||
|
# Find the next version header after this one
|
||||||
|
next_version_idx = history_content.find("\n### Aider v", version_idx + len(version_header))
|
||||||
|
if next_version_idx == -1:
|
||||||
|
# No next version found, use the rest of the file
|
||||||
|
relevant_history = history_content[start_idx:]
|
||||||
|
else:
|
||||||
|
# Extract just up to the next version
|
||||||
|
relevant_history = history_content[start_idx:next_version_idx]
|
||||||
|
|
||||||
|
# Save relevant portions to temporary files
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".diff") as tmp_diff:
|
||||||
|
tmp_diff.write(diff_content)
|
||||||
|
diff_path = tmp_diff.name
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".md") as tmp_hist:
|
||||||
|
tmp_hist.write(relevant_history)
|
||||||
|
hist_path = tmp_hist.name
|
||||||
|
|
||||||
# Run blame to get aider percentage
|
# Run blame to get aider percentage
|
||||||
blame_result = subprocess.run(["python3", "scripts/blame.py"], capture_output=True, text=True)
|
blame_result = subprocess.run(["python3", "scripts/blame.py"], capture_output=True, text=True)
|
||||||
|
@ -58,14 +87,38 @@ Also, add this as the last bullet under the "### main branch" section:
|
||||||
{aider_line}
|
{aider_line}
|
||||||
""" # noqa
|
""" # noqa
|
||||||
|
|
||||||
cmd = ["aider", "HISTORY.md", "--read", tmp_path, "--msg", message, "--no-auto-commit"]
|
cmd = ["aider", hist_path, "--read", diff_path, "--msg", message, "--no-auto-commit"]
|
||||||
subprocess.run(cmd)
|
subprocess.run(cmd)
|
||||||
|
|
||||||
|
# Read back the updated history
|
||||||
|
with open(hist_path, "r") as f:
|
||||||
|
updated_history = f.read()
|
||||||
|
|
||||||
|
# Find where the next version section would start
|
||||||
|
if next_version_idx == -1:
|
||||||
|
# No next version found, use the rest of the file
|
||||||
|
full_history = history_content[:start_idx] + updated_history
|
||||||
|
else:
|
||||||
|
# Splice the updated portion back in between the unchanged parts
|
||||||
|
full_history = (
|
||||||
|
history_content[:start_idx]
|
||||||
|
+ updated_history # Keep unchanged header
|
||||||
|
+ history_content[next_version_idx:] # Add updated portion # Keep older entries
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write back the full history
|
||||||
|
with open("HISTORY.md", "w") as f:
|
||||||
|
f.write(full_history)
|
||||||
|
|
||||||
# Run update-docs.sh after aider
|
# Run update-docs.sh after aider
|
||||||
subprocess.run(["scripts/update-docs.sh"])
|
subprocess.run(["scripts/update-docs.sh"])
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
os.unlink(tmp_path)
|
os.unlink(diff_path)
|
||||||
|
os.unlink(hist_path)
|
||||||
|
|
||||||
|
# Show git diff of HISTORY.md
|
||||||
|
subprocess.run(["git", "diff", "HISTORY.md"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -236,7 +236,7 @@ class TestCommands(TestCase):
|
||||||
self.assertIn(str(Path("test_dir/test_file2.txt").resolve()), coder.abs_fnames)
|
self.assertIn(str(Path("test_dir/test_file2.txt").resolve()), coder.abs_fnames)
|
||||||
self.assertIn(str(Path("test_dir/another_dir/test_file.txt").resolve()), coder.abs_fnames)
|
self.assertIn(str(Path("test_dir/another_dir/test_file.txt").resolve()), coder.abs_fnames)
|
||||||
|
|
||||||
commands.cmd_drop("test_dir/another_dir")
|
commands.cmd_drop(str(Path("test_dir/another_dir")))
|
||||||
self.assertIn(str(Path("test_dir/test_file1.txt").resolve()), coder.abs_fnames)
|
self.assertIn(str(Path("test_dir/test_file1.txt").resolve()), coder.abs_fnames)
|
||||||
self.assertIn(str(Path("test_dir/test_file2.txt").resolve()), coder.abs_fnames)
|
self.assertIn(str(Path("test_dir/test_file2.txt").resolve()), coder.abs_fnames)
|
||||||
self.assertNotIn(
|
self.assertNotIn(
|
||||||
|
@ -272,6 +272,7 @@ class TestCommands(TestCase):
|
||||||
coder = Coder.create(self.GPT35, None, io)
|
coder = Coder.create(self.GPT35, None, io)
|
||||||
commands = Commands(io, coder)
|
commands = Commands(io, coder)
|
||||||
|
|
||||||
|
# Create test files in root and subdirectory
|
||||||
subdir = Path("subdir")
|
subdir = Path("subdir")
|
||||||
subdir.mkdir()
|
subdir.mkdir()
|
||||||
(subdir / "subtest1.py").touch()
|
(subdir / "subtest1.py").touch()
|
||||||
|
@ -279,17 +280,50 @@ class TestCommands(TestCase):
|
||||||
|
|
||||||
Path("test1.py").touch()
|
Path("test1.py").touch()
|
||||||
Path("test2.py").touch()
|
Path("test2.py").touch()
|
||||||
|
Path("test3.txt").touch()
|
||||||
|
|
||||||
# Add some files to the chat session
|
# Add all Python files to the chat session
|
||||||
commands.cmd_add("*.py")
|
commands.cmd_add("*.py")
|
||||||
|
initial_count = len(coder.abs_fnames)
|
||||||
|
self.assertEqual(initial_count, 2) # Only root .py files should be added
|
||||||
|
|
||||||
self.assertEqual(len(coder.abs_fnames), 2)
|
# Test dropping with glob pattern
|
||||||
|
|
||||||
# Call the cmd_drop method with a glob pattern
|
|
||||||
commands.cmd_drop("*2.py")
|
commands.cmd_drop("*2.py")
|
||||||
|
|
||||||
self.assertIn(str(Path("test1.py").resolve()), coder.abs_fnames)
|
self.assertIn(str(Path("test1.py").resolve()), coder.abs_fnames)
|
||||||
self.assertNotIn(str(Path("test2.py").resolve()), coder.abs_fnames)
|
self.assertNotIn(str(Path("test2.py").resolve()), coder.abs_fnames)
|
||||||
|
self.assertEqual(len(coder.abs_fnames), initial_count - 1)
|
||||||
|
|
||||||
|
def test_cmd_drop_without_glob(self):
|
||||||
|
# Initialize the Commands and InputOutput objects
|
||||||
|
io = InputOutput(pretty=False, fancy_input=False, yes=True)
|
||||||
|
from aider.coders import Coder
|
||||||
|
|
||||||
|
coder = Coder.create(self.GPT35, None, io)
|
||||||
|
commands = Commands(io, coder)
|
||||||
|
|
||||||
|
# Create test files
|
||||||
|
test_files = ["file1.txt", "file2.txt", "file3.py"]
|
||||||
|
for fname in test_files:
|
||||||
|
Path(fname).touch()
|
||||||
|
|
||||||
|
# Add all files to the chat session
|
||||||
|
for fname in test_files:
|
||||||
|
commands.cmd_add(fname)
|
||||||
|
|
||||||
|
initial_count = len(coder.abs_fnames)
|
||||||
|
self.assertEqual(initial_count, 3)
|
||||||
|
|
||||||
|
# Test dropping individual files without glob
|
||||||
|
commands.cmd_drop("file1.txt")
|
||||||
|
self.assertNotIn(str(Path("file1.txt").resolve()), coder.abs_fnames)
|
||||||
|
self.assertIn(str(Path("file2.txt").resolve()), coder.abs_fnames)
|
||||||
|
self.assertEqual(len(coder.abs_fnames), initial_count - 1)
|
||||||
|
|
||||||
|
# Test dropping multiple files without glob
|
||||||
|
commands.cmd_drop("file2.txt file3.py")
|
||||||
|
self.assertNotIn(str(Path("file2.txt").resolve()), coder.abs_fnames)
|
||||||
|
self.assertNotIn(str(Path("file3.py").resolve()), coder.abs_fnames)
|
||||||
|
self.assertEqual(len(coder.abs_fnames), 0)
|
||||||
|
|
||||||
def test_cmd_add_bad_encoding(self):
|
def test_cmd_add_bad_encoding(self):
|
||||||
# Initialize the Commands and InputOutput objects
|
# Initialize the Commands and InputOutput objects
|
||||||
|
@ -1397,6 +1431,43 @@ class TestCommands(TestCase):
|
||||||
finally:
|
finally:
|
||||||
os.unlink(external_file_path)
|
os.unlink(external_file_path)
|
||||||
|
|
||||||
|
def test_cmd_drop_read_only_with_relative_path(self):
|
||||||
|
with ChdirTemporaryDirectory() as repo_dir:
|
||||||
|
test_file = Path("test_file.txt")
|
||||||
|
test_file.write_text("Test content")
|
||||||
|
|
||||||
|
# Create a test file in a subdirectory
|
||||||
|
subdir = Path(repo_dir) / "subdir"
|
||||||
|
subdir.mkdir()
|
||||||
|
os.chdir(subdir)
|
||||||
|
|
||||||
|
io = InputOutput(pretty=False, fancy_input=False, yes=False)
|
||||||
|
coder = Coder.create(self.GPT35, None, io)
|
||||||
|
commands = Commands(io, coder)
|
||||||
|
|
||||||
|
# Add the file as read-only using absolute path
|
||||||
|
rel_path = str(Path("..") / "test_file.txt")
|
||||||
|
commands.cmd_read_only(rel_path)
|
||||||
|
self.assertEqual(len(coder.abs_read_only_fnames), 1)
|
||||||
|
|
||||||
|
# Try to drop using relative path from different working directories
|
||||||
|
commands.cmd_drop("test_file.txt")
|
||||||
|
self.assertEqual(len(coder.abs_read_only_fnames), 0)
|
||||||
|
|
||||||
|
# Add it again
|
||||||
|
commands.cmd_read_only(rel_path)
|
||||||
|
self.assertEqual(len(coder.abs_read_only_fnames), 1)
|
||||||
|
|
||||||
|
commands.cmd_drop(rel_path)
|
||||||
|
self.assertEqual(len(coder.abs_read_only_fnames), 0)
|
||||||
|
|
||||||
|
# Add it one more time
|
||||||
|
commands.cmd_read_only(rel_path)
|
||||||
|
self.assertEqual(len(coder.abs_read_only_fnames), 1)
|
||||||
|
|
||||||
|
commands.cmd_drop("test_file.txt")
|
||||||
|
self.assertEqual(len(coder.abs_read_only_fnames), 0)
|
||||||
|
|
||||||
def test_cmd_read_only_with_multiple_files(self):
|
def test_cmd_read_only_with_multiple_files(self):
|
||||||
with GitTemporaryDirectory() as repo_dir:
|
with GitTemporaryDirectory() as repo_dir:
|
||||||
io = InputOutput(pretty=False, fancy_input=False, yes=False)
|
io = InputOutput(pretty=False, fancy_input=False, yes=False)
|
||||||
|
|
|
@ -667,6 +667,10 @@ class TestMain(TestCase):
|
||||||
)
|
)
|
||||||
self.assertTrue(coder.detect_urls)
|
self.assertTrue(coder.detect_urls)
|
||||||
|
|
||||||
|
def test_pytest_env_vars(self):
|
||||||
|
# Verify that environment variables from pytest.ini are properly set
|
||||||
|
self.assertEqual(os.environ.get("AIDER_ANALYTICS"), "false")
|
||||||
|
|
||||||
def test_invalid_edit_format(self):
|
def test_invalid_edit_format(self):
|
||||||
with GitTemporaryDirectory():
|
with GitTemporaryDirectory():
|
||||||
with patch("aider.io.InputOutput.offer_url") as mock_offer_url:
|
with patch("aider.io.InputOutput.offer_url") as mock_offer_url:
|
||||||
|
|
96
tests/basic/test_voice.py
Normal file
96
tests/basic/test_voice.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import os
|
||||||
|
import queue
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aider.voice import SoundDeviceError, Voice
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_sounddevice():
|
||||||
|
with patch("sounddevice.query_devices") as mock_query:
|
||||||
|
mock_query.return_value = [
|
||||||
|
{"name": "test_device", "max_input_channels": 2},
|
||||||
|
{"name": "another_device", "max_input_channels": 1},
|
||||||
|
]
|
||||||
|
yield mock_query
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_soundfile():
|
||||||
|
with patch("soundfile.SoundFile") as mock_sf:
|
||||||
|
yield mock_sf
|
||||||
|
|
||||||
|
|
||||||
|
def test_voice_init_default_device(mock_sounddevice):
|
||||||
|
voice = Voice()
|
||||||
|
assert voice.device_id is None
|
||||||
|
assert voice.audio_format == "wav"
|
||||||
|
|
||||||
|
|
||||||
|
def test_voice_init_specific_device(mock_sounddevice):
|
||||||
|
voice = Voice(device_name="test_device")
|
||||||
|
assert voice.device_id == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_voice_init_invalid_device(mock_sounddevice):
|
||||||
|
with pytest.raises(ValueError) as exc:
|
||||||
|
Voice(device_name="nonexistent_device")
|
||||||
|
assert "Device" in str(exc.value)
|
||||||
|
assert "not found" in str(exc.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_voice_init_invalid_format():
|
||||||
|
with pytest.raises(ValueError) as exc:
|
||||||
|
Voice(audio_format="invalid")
|
||||||
|
assert "Unsupported audio format" in str(exc.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_callback_processing():
|
||||||
|
voice = Voice()
|
||||||
|
voice.q = queue.Queue()
|
||||||
|
|
||||||
|
# Test with silence (low amplitude)
|
||||||
|
test_data = np.zeros((1000, 1))
|
||||||
|
voice.callback(test_data, None, None, None)
|
||||||
|
assert voice.pct == 0.5 # When range is too small (<=0.001), pct is set to 0.5
|
||||||
|
|
||||||
|
# Test with loud signal (high amplitude)
|
||||||
|
test_data = np.ones((1000, 1))
|
||||||
|
voice.callback(test_data, None, None, None)
|
||||||
|
assert voice.pct > 0.9
|
||||||
|
|
||||||
|
# Verify data is queued
|
||||||
|
assert not voice.q.empty()
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_prompt():
|
||||||
|
voice = Voice()
|
||||||
|
voice.start_time = voice.start_time = os.times().elapsed
|
||||||
|
voice.pct = 0.5 # 50% volume level
|
||||||
|
|
||||||
|
prompt = voice.get_prompt()
|
||||||
|
assert "Recording" in prompt
|
||||||
|
assert "sec" in prompt
|
||||||
|
assert "█" in prompt # Should contain some filled blocks
|
||||||
|
assert "░" in prompt # Should contain some empty blocks
|
||||||
|
|
||||||
|
|
||||||
|
@patch("sounddevice.InputStream")
|
||||||
|
def test_record_and_transcribe_keyboard_interrupt(mock_stream):
|
||||||
|
voice = Voice()
|
||||||
|
mock_stream.side_effect = KeyboardInterrupt()
|
||||||
|
|
||||||
|
result = voice.record_and_transcribe()
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
|
@patch("sounddevice.InputStream")
|
||||||
|
def test_record_and_transcribe_device_error(mock_stream):
|
||||||
|
voice = Voice()
|
||||||
|
mock_stream.side_effect = SoundDeviceError("Test error")
|
||||||
|
|
||||||
|
result = voice.record_and_transcribe()
|
||||||
|
assert result is None
|
Loading…
Add table
Add a link
Reference in a new issue