mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-01 10:14:59 +00:00
feat: Local Analytics Dashboard for Aider
feat: Initialize LocalAnalyticsCollector in main.py feat: Display session data in local analytics dashboard fix: Use cumulative data from last interaction for dashboard stats fix: Extract initial query from diffs in local analytics collector.
This commit is contained in:
parent
4e0964046a
commit
e8bee42d76
12 changed files with 1732 additions and 12 deletions
184
local_analytics/run_aider_ask_test.py
Normal file
184
local_analytics/run_aider_ask_test.py
Normal file
|
@ -0,0 +1,184 @@
|
|||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import queue
|
||||
|
||||
# Define the aider command
|
||||
# Use the full path to the .env file
|
||||
# Assumes the script is run from the project root or a location where
|
||||
# ~/Dev/aider/.env is the correct path.
|
||||
# Using sys.executable ensures the script runs aider with the same python env.
|
||||
aider_command = [
|
||||
sys.executable,
|
||||
"-m", "aider",
|
||||
"--env-file", os.path.expanduser("~/Dev/aider/.env")
|
||||
]
|
||||
|
||||
# Inputs to send to aider
|
||||
inputs = [
|
||||
"/ask",
|
||||
"what is the reflection error",
|
||||
"/exit"
|
||||
]
|
||||
|
||||
# Expected prompts (as bytes, since we read bytes)
|
||||
# Use strip() because rich might add spaces or other control characters
|
||||
MAIN_PROMPT = b"> "
|
||||
EDIT_PROMPT = b"Apply edits? (y/n/commit/diff/quit) "
|
||||
|
||||
def enqueue_output(out, queue):
|
||||
"""Helper function to read output from a stream and put it in a queue."""
|
||||
# Read line by line
|
||||
for line in iter(out.readline, b''):
|
||||
queue.put(line)
|
||||
out.close()
|
||||
|
||||
def run_aider_session():
|
||||
print(f"[SCRIPT] Starting aider with command: {' '.join(aider_command)}")
|
||||
|
||||
# Start the subprocess
|
||||
# Use bufsize=1 for line buffering
|
||||
# universal_newlines=False to read bytes and reliably detect byte prompts
|
||||
# stderr is also piped as rich often prints to stderr
|
||||
process = subprocess.Popen(
|
||||
aider_command,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
bufsize=1,
|
||||
universal_newlines=False
|
||||
)
|
||||
|
||||
# Queues for stdout and stderr
|
||||
q_stdout = queue.Queue()
|
||||
q_stderr = queue.Queue()
|
||||
|
||||
# Start threads to read stdout and stderr asynchronously
|
||||
t_stdout = threading.Thread(target=enqueue_output, args=(process.stdout, q_stdout))
|
||||
t_stderr = threading.Thread(target=enqueue_output, args=(process.stderr, q_stderr))
|
||||
t_stdout.daemon = True # Thread dies with the main program
|
||||
t_stderr.daemon = True
|
||||
t_stdout.start()
|
||||
t_stderr.start()
|
||||
|
||||
# Give aider a moment to start and print initial messages
|
||||
time.sleep(3) # Increased initial sleep slightly
|
||||
|
||||
current_input_index = 0
|
||||
# State machine: WAITING_FOR_MAIN_PROMPT, WAITING_FOR_RESPONSE
|
||||
state = "WAITING_FOR_MAIN_PROMPT"
|
||||
|
||||
print(f"[SCRIPT] Initial state: {state}")
|
||||
|
||||
try:
|
||||
# Continue as long as the process is running OR there is output in the queues
|
||||
while process.poll() is None or not q_stdout.empty() or not q_stderr.empty():
|
||||
try:
|
||||
# Get a line from stdout queue with a timeout
|
||||
# A small timeout allows the loop to check process.poll() and stderr queue
|
||||
line = q_stdout.get(timeout=0.05)
|
||||
sys.stdout.buffer.write(line)
|
||||
sys.stdout.buffer.flush()
|
||||
|
||||
# Check for prompts based on the state
|
||||
if state == "WAITING_FOR_MAIN_PROMPT":
|
||||
# Check if the line ends with the main prompt bytes (after stripping)
|
||||
if line.strip().endswith(MAIN_PROMPT.strip()):
|
||||
print("\n[SCRIPT] Detected main prompt.")
|
||||
if current_input_index < len(inputs):
|
||||
command = inputs[current_input_index]
|
||||
print(f"[SCRIPT] Sending: {command}")
|
||||
process.stdin.write((command + "\n").encode()) # Encode string to bytes
|
||||
process.stdin.flush()
|
||||
current_input_index += 1
|
||||
state = "WAITING_FOR_RESPONSE" # After sending input, wait for response/next prompt
|
||||
print(f"[SCRIPT] State transition: {state}")
|
||||
else:
|
||||
# Should not happen if /exit is the last input, but as a safeguard
|
||||
print("[SCRIPT] No more inputs defined, waiting for process exit.")
|
||||
state = "SESSION_COMPLETE"
|
||||
print(f"[SCRIPT] State transition: {state}")
|
||||
|
||||
elif state == "WAITING_FOR_RESPONSE":
|
||||
# While waiting for response, we might see an edit prompt or the main prompt
|
||||
if line.strip().endswith(EDIT_PROMPT.strip()):
|
||||
print("\n[SCRIPT] Detected edit prompt.")
|
||||
print("[SCRIPT] Sending: n")
|
||||
process.stdin.write(b"n\n") # Send 'n' to decline edits
|
||||
process.stdin.flush()
|
||||
# Stay in WAITING_FOR_RESPONSE state, as declining might lead to another prompt
|
||||
print(f"[SCRIPT] State remains: {state}")
|
||||
elif line.strip().endswith(MAIN_PROMPT.strip()):
|
||||
print("\n[SCRIPT] Detected main prompt (while waiting for response).")
|
||||
# Response finished, now ready for next main input
|
||||
state = "WAITING_FOR_MAIN_PROMPT"
|
||||
print(f"[SCRIPT] State transition: {state}")
|
||||
|
||||
except queue.Empty:
|
||||
# No output from stdout, check stderr queue
|
||||
try:
|
||||
err_line = q_stderr.get(timeout=0.01)
|
||||
sys.stderr.buffer.write(err_line)
|
||||
sys.stderr.buffer.flush()
|
||||
except queue.Empty:
|
||||
# No output from either queue, check if process is still running
|
||||
if process.poll() is not None:
|
||||
# Process exited and queues are empty, we are done
|
||||
print("[SCRIPT] Process exited and queues are empty.")
|
||||
break
|
||||
# If process is still running but no output, just continue loop and wait
|
||||
|
||||
# Add a small sleep to prevent tight loop if process is slow to produce output
|
||||
time.sleep(0.01)
|
||||
|
||||
# End of while loop: process exited and queues are drained
|
||||
|
||||
except Exception as e:
|
||||
print(f"[SCRIPT] An error occurred: {e}")
|
||||
# Attempt to read remaining output before terminating
|
||||
try:
|
||||
# Give threads a moment to finish putting data in queues
|
||||
t_stdout.join(timeout=1)
|
||||
t_stderr.join(timeout=1)
|
||||
# Drain queues
|
||||
while not q_stdout.empty():
|
||||
sys.stdout.buffer.write(q_stdout.get_nowait())
|
||||
sys.stdout.buffer.flush()
|
||||
while not q_stderr.empty():
|
||||
sys.stderr.buffer.write(q_stderr.get_nowait())
|
||||
sys.stdout.buffer.flush()
|
||||
except Exception as e_drain:
|
||||
print(f"[SCRIPT] Error draining queues: {e_drain}")
|
||||
|
||||
|
||||
if process.poll() is None:
|
||||
print("[SCRIPT] Terminating process...")
|
||||
process.terminate() # Ensure process is terminated on error
|
||||
try:
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
print("[SCRIPT] Process did not terminate, killing...")
|
||||
process.kill()
|
||||
process.wait()
|
||||
|
||||
|
||||
finally:
|
||||
# Ensure process is waited upon if not already
|
||||
if process.poll() is None:
|
||||
print("[SCRIPT] Waiting for process to finish...")
|
||||
process.wait()
|
||||
|
||||
# Final drain of queues just in case
|
||||
while not q_stdout.empty():
|
||||
sys.stdout.buffer.write(q_stdout.get_nowait())
|
||||
sys.stdout.buffer.flush()
|
||||
while not q_stderr.empty():
|
||||
sys.stderr.buffer.write(q_stderr.get_nowait())
|
||||
sys.stderr.buffer.flush()
|
||||
|
||||
print(f"[SCRIPT] Aider process finished with return code {process.returncode}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_aider_session()
|
Loading…
Add table
Add a link
Reference in a new issue