801 lines
33 KiB
Python
801 lines
33 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
System Test Script for VRM Avatar Chat System
|
||
Tests each component in sequence with user-friendly feedback and troubleshooting tips.
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import time
|
||
import json
|
||
import yaml
|
||
import uuid
|
||
import requests
|
||
from pathlib import Path
|
||
from dotenv import load_dotenv
|
||
import sounddevice as sd
|
||
import numpy as np
|
||
|
||
# Color codes for terminal output
|
||
class Colors:
|
||
HEADER = '\033[95m'
|
||
OKBLUE = '\033[94m'
|
||
OKCYAN = '\033[96m'
|
||
OKGREEN = '\033[92m'
|
||
WARNING = '\033[93m'
|
||
FAIL = '\033[91m'
|
||
ENDC = '\033[0m'
|
||
BOLD = '\033[1m'
|
||
UNDERLINE = '\033[4m'
|
||
|
||
def print_header(text):
|
||
print(f"\n{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}")
|
||
print(f"{Colors.HEADER}{Colors.BOLD}{text}{Colors.ENDC}")
|
||
print(f"{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}\n")
|
||
|
||
def print_success(text):
|
||
print(f"{Colors.OKGREEN}✓ {text}{Colors.ENDC}")
|
||
|
||
def print_error(text):
|
||
print(f"{Colors.FAIL}✗ {text}{Colors.ENDC}")
|
||
|
||
def print_warning(text):
|
||
print(f"{Colors.WARNING}⚠ {text}{Colors.ENDC}")
|
||
|
||
def print_info(text):
|
||
print(f"{Colors.OKCYAN}ℹ {text}{Colors.ENDC}")
|
||
|
||
def wait_for_user(prompt="Press ENTER to continue..."):
|
||
input(f"\n{Colors.BOLD}{prompt}{Colors.ENDC}")
|
||
|
||
# Base URL for VRM server
|
||
BASE_URL = "http://localhost:8001"
|
||
|
||
# ============================================================================
|
||
# TEST 0: Configuration and API Keys
|
||
# ============================================================================
|
||
|
||
def test_config_and_keys():
|
||
print_header("TEST 0: Configuration and API Keys")
|
||
|
||
issues = []
|
||
|
||
# Check .env file
|
||
print_info("Checking .env file...")
|
||
load_dotenv()
|
||
|
||
if not os.path.exists('.env'):
|
||
print_warning(".env file not found in current directory")
|
||
issues.append(".env file missing")
|
||
else:
|
||
print_success(".env file found")
|
||
|
||
# Check OpenAI API Key
|
||
print_info("Checking OpenAI API Key...")
|
||
openai_key = os.getenv('OPENAI_API_KEY')
|
||
if not openai_key:
|
||
print_error("OPENAI_API_KEY not set in environment")
|
||
issues.append("OPENAI_API_KEY not set")
|
||
elif openai_key.startswith('sk-'):
|
||
print_success(f"OpenAI API Key found (starts with: {openai_key[:10]}...)")
|
||
else:
|
||
print_warning("OpenAI API Key found but doesn't start with 'sk-' (might be invalid)")
|
||
issues.append("OPENAI_API_KEY format looks incorrect")
|
||
|
||
# Check Groq API Key
|
||
print_info("Checking Groq API Key...")
|
||
groq_key = os.getenv('GROQ_API_KEY')
|
||
if not groq_key:
|
||
print_error("GROQ_API_KEY not set in environment")
|
||
issues.append("GROQ_API_KEY not set")
|
||
else:
|
||
print_success(f"Groq API Key found (starts with: {groq_key[:10]}...)")
|
||
|
||
# Check character_config.yaml
|
||
print_info("Checking character_config.yaml...")
|
||
config_path = 'character_config.yaml'
|
||
if not os.path.exists(config_path):
|
||
print_error(f"{config_path} not found")
|
||
issues.append("character_config.yaml missing")
|
||
else:
|
||
print_success(f"{config_path} found")
|
||
try:
|
||
with open(config_path, 'r') as f:
|
||
config = yaml.safe_load(f)
|
||
|
||
# Check required fields
|
||
required_fields = ['history_file', 'presets']
|
||
for field in required_fields:
|
||
if field in config:
|
||
print_success(f" - {field}: present")
|
||
else:
|
||
print_error(f" - {field}: missing")
|
||
issues.append(f"character_config.yaml missing '{field}'")
|
||
|
||
# Check if model is set
|
||
model = config.get('model', 'not set')
|
||
print_info(f" - Model: {model}")
|
||
|
||
except Exception as e:
|
||
print_error(f"Error reading config: {e}")
|
||
issues.append(f"character_config.yaml parse error: {e}")
|
||
|
||
# Summary
|
||
print("\n" + "─"*60)
|
||
if issues:
|
||
print_error("Configuration Issues Found:")
|
||
for issue in issues:
|
||
print(f" • {issue}")
|
||
print("\n" + Colors.WARNING + "Troubleshooting Tips:" + Colors.ENDC)
|
||
if "OPENAI_API_KEY not set" in str(issues):
|
||
print(" 1. Create a .env file in your project root")
|
||
print(" 2. Add: OPENAI_API_KEY=sk-your-key-here")
|
||
print(" 3. Get your key from: https://platform.openai.com/api-keys")
|
||
if "GROQ_API_KEY not set" in str(issues):
|
||
print(" 1. Add to .env file: GROQ_API_KEY=your-groq-key-here")
|
||
print(" 2. Get your key from: https://console.groq.com/keys")
|
||
if "character_config.yaml" in str(issues):
|
||
print(" 1. Ensure character_config.yaml exists in project root")
|
||
print(" 2. Check the file has correct YAML syntax")
|
||
return False
|
||
else:
|
||
print_success("All configuration checks passed!")
|
||
return True
|
||
|
||
# ============================================================================
|
||
# TEST 1: LLM Connection
|
||
# ============================================================================
|
||
|
||
def test_llm():
|
||
print_header("TEST 1: LLM (OpenAI) Connection")
|
||
|
||
print_info("Testing connection to OpenAI API...")
|
||
|
||
try:
|
||
from openai import OpenAI
|
||
|
||
api_key = os.getenv('OPENAI_API_KEY')
|
||
if not api_key:
|
||
print_error("OPENAI_API_KEY not found in environment")
|
||
print_warning("Troubleshooting:")
|
||
print(" 1. Make sure .env file contains OPENAI_API_KEY=sk-...")
|
||
print(" 2. Restart your terminal/IDE after adding the key")
|
||
return False
|
||
|
||
client = OpenAI(api_key=api_key)
|
||
|
||
print_info("Sending test message to OpenAI...")
|
||
response = client.chat.completions.create(
|
||
model="gpt-4o-mini",
|
||
messages=[
|
||
{"role": "user", "content": "Say 'Hello! LLM test successful.' and nothing else."}
|
||
],
|
||
max_tokens=50
|
||
)
|
||
|
||
response_text = response.choices[0].message.content
|
||
print_success(f"LLM Response: {response_text}")
|
||
print_success("OpenAI API is working correctly!")
|
||
return True
|
||
|
||
except ImportError:
|
||
print_error("OpenAI library not installed")
|
||
print_warning("Install with: pip install openai")
|
||
return False
|
||
except Exception as e:
|
||
print_error(f"LLM test failed: {e}")
|
||
print_warning("\nTroubleshooting:")
|
||
print(" 1. Check your API key is valid at https://platform.openai.com/api-keys")
|
||
print(" 2. Ensure you have credits/billing set up on your OpenAI account")
|
||
print(" 3. Check your internet connection")
|
||
print(f" 4. Error details: {str(e)}")
|
||
return False
|
||
|
||
# ============================================================================
|
||
# TEST 2: Audio Recording
|
||
# ============================================================================
|
||
|
||
def test_audio_recording():
|
||
print_header("TEST 2: Audio Recording (Microphone)")
|
||
|
||
print_info("Checking available audio devices...")
|
||
|
||
try:
|
||
devices = sd.query_devices()
|
||
print_info("Available audio devices:")
|
||
for i, device in enumerate(devices):
|
||
if device['max_input_channels'] > 0:
|
||
default = " (DEFAULT)" if i == sd.default.device[0] else ""
|
||
print(f" [{i}] {device['name']}{default}")
|
||
|
||
default_input = sd.default.device[0]
|
||
print_success(f"\nDefault input device: {devices[default_input]['name']}")
|
||
|
||
except Exception as e:
|
||
print_error(f"Error querying audio devices: {e}")
|
||
print_warning("Troubleshooting:")
|
||
print(" 1. Make sure a microphone is plugged in")
|
||
print(" 2. Check system audio settings")
|
||
print(" 3. Install required library: pip install sounddevice")
|
||
return False
|
||
|
||
print_info("\nTesting microphone recording...")
|
||
print_warning("When prompted, speak into your microphone for 3 seconds")
|
||
wait_for_user("Press ENTER when ready to record...")
|
||
|
||
try:
|
||
from process.asr_func.asr_auto_record import record_on_speech
|
||
|
||
test_audio_path = Path("audio") / "test_recording.wav"
|
||
test_audio_path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
print_info("Recording... (speak now!)")
|
||
record_on_speech(
|
||
output_file=str(test_audio_path),
|
||
samplerate=44100,
|
||
channels=1,
|
||
silence_threshold=0.02,
|
||
silence_duration=2,
|
||
device=None
|
||
)
|
||
|
||
if test_audio_path.exists() and test_audio_path.stat().st_size > 1000:
|
||
print_success(f"Audio recorded successfully! File: {test_audio_path}")
|
||
print_info(f"File size: {test_audio_path.stat().st_size} bytes")
|
||
|
||
# Test transcription
|
||
print_info("\nTesting transcription with Groq...")
|
||
try:
|
||
from process.asr_func.asr_transcribe_groq import transcribe_audio_groq
|
||
|
||
transcription = transcribe_audio_groq(aud_path=str(test_audio_path))
|
||
print_success(f"Transcription: '{transcription}'")
|
||
|
||
if transcription and len(transcription.strip()) > 0:
|
||
print_success("Audio recording and transcription working!")
|
||
return True
|
||
else:
|
||
print_warning("Transcription returned empty text")
|
||
print_info("This might be normal if you didn't speak during recording")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print_error(f"Transcription failed: {e}")
|
||
print_warning("\nTroubleshooting:")
|
||
print(" 1. Check GROQ_API_KEY is set in .env file")
|
||
print(" 2. Verify Groq API key at https://console.groq.com/keys")
|
||
print(" 3. Check internet connection")
|
||
return False
|
||
else:
|
||
print_error("Recording file not created or is too small")
|
||
return False
|
||
|
||
except ImportError as e:
|
||
print_error(f"Missing required module: {e}")
|
||
print_warning("Install required libraries:")
|
||
print(" pip install sounddevice soundfile")
|
||
return False
|
||
except Exception as e:
|
||
print_error(f"Recording test failed: {e}")
|
||
print_warning("\nTroubleshooting:")
|
||
print(" 1. Check microphone is plugged in and working")
|
||
print(" 2. Grant microphone permissions to Python/terminal")
|
||
print(" 3. Try a different microphone or audio device")
|
||
print(" 4. Check audio device settings in your OS")
|
||
return False
|
||
|
||
# ============================================================================
|
||
# TEST 3: TTS (SoVITS) Audio Generation
|
||
# ============================================================================
|
||
|
||
def test_tts_generation():
|
||
print_header("TEST 3: TTS Audio Generation (GPT-SoVITS)")
|
||
|
||
print_info("Testing connection to GPT-SoVITS server...")
|
||
|
||
try:
|
||
from process.tts_func.sovits_ping import sovits_gen
|
||
|
||
test_text = "Hello! This is a test of the text to speech system."
|
||
output_path = Path("audio") / f"test_tts_{uuid.uuid4().hex}.wav"
|
||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
print_info(f"Generating speech for: '{test_text}'")
|
||
print_warning("This requires GPT-SoVITS server to be running...")
|
||
|
||
sovits_gen(test_text, output_wav_pth=str(output_path))
|
||
|
||
if output_path.exists() and output_path.stat().st_size > 1000:
|
||
print_success(f"TTS audio generated! File: {output_path}")
|
||
print_info(f"File size: {output_path.stat().st_size} bytes")
|
||
|
||
# Try to get duration
|
||
try:
|
||
from process.tts_func.sovits_ping import get_wav_duration
|
||
duration = get_wav_duration(str(output_path))
|
||
print_info(f"Audio duration: {duration:.2f} seconds")
|
||
except:
|
||
pass
|
||
|
||
print_success("\n✓ TTS generation is working!")
|
||
print_info("You can manually check the audio file to verify quality")
|
||
|
||
response = input(f"\n{Colors.BOLD}Can you hear the audio when you play it? (y/n): {Colors.ENDC}").lower()
|
||
if response == 'y':
|
||
print_success("Great! TTS system is fully functional")
|
||
return True
|
||
else:
|
||
print_warning("Audio file was generated but might have issues")
|
||
print_info("Check the audio file manually: " + str(output_path))
|
||
return True
|
||
else:
|
||
print_error("TTS file not created or is too small")
|
||
return False
|
||
|
||
except ImportError as e:
|
||
print_error(f"Missing required module: {e}")
|
||
return False
|
||
except ConnectionError as e:
|
||
print_error(f"Cannot connect to GPT-SoVITS server: {e}")
|
||
print_warning("\nTroubleshooting:")
|
||
print(" 1. Make sure GPT-SoVITS server is running")
|
||
print(" 2. Check the server is accessible (usually http://localhost:9880)")
|
||
print(" 3. Verify server configuration in your code")
|
||
print(" 4. Check server logs for errors")
|
||
return False
|
||
except Exception as e:
|
||
print_error(f"TTS test failed: {e}")
|
||
print_warning("\nTroubleshooting:")
|
||
print(" 1. Ensure GPT-SoVITS server is running and accessible")
|
||
print(" 2. Check server URL/port configuration")
|
||
print(" 3. Verify server has required voice models loaded")
|
||
print(" 4. Check server logs for detailed error messages")
|
||
return False
|
||
|
||
# ============================================================================
|
||
# TEST 4: VRM Server and Audio Playback
|
||
# ============================================================================
|
||
|
||
def test_vrm_server():
|
||
print_header("TEST 4: VRM Server Connection")
|
||
|
||
print_info(f"Checking VRM server at {BASE_URL}...")
|
||
|
||
try:
|
||
response = requests.get(f"{BASE_URL}/", timeout=5)
|
||
print_success("VRM server is running!")
|
||
return True
|
||
except requests.exceptions.ConnectionError:
|
||
print_error(f"Cannot connect to VRM server at {BASE_URL}")
|
||
print_warning("\nTroubleshooting:")
|
||
print(" 1. Make sure the VRM server (Node.js/Three.js app) is running")
|
||
print(" 2. Check it's running on port 8001")
|
||
print(" 3. Verify no firewall is blocking the connection")
|
||
print(" 4. Start the server with: npm start (or your start command)")
|
||
return False
|
||
except Exception as e:
|
||
print_error(f"Error connecting to VRM server: {e}")
|
||
return False
|
||
|
||
def test_vrm_audio_playback():
|
||
print_header("TEST 5: VRM Audio Playback")
|
||
|
||
print_info("Testing vrm_talk function with audio playback...")
|
||
|
||
# First, generate a test audio file
|
||
try:
|
||
from process.tts_func.sovits_ping import sovits_gen, get_wav_duration
|
||
|
||
test_text = "Testing audio playback through the VRM system."
|
||
client_audio = Path("client/audio") / f"test_playback_{uuid.uuid4().hex}.wav"
|
||
client_audio.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
public_audio = Path("audio") / client_audio.name
|
||
public_audio.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
print_info("Generating test audio...")
|
||
sovits_gen(test_text, output_wav_pth=str(client_audio))
|
||
|
||
# Copy to public directory
|
||
import shutil
|
||
shutil.copy2(client_audio, public_audio)
|
||
|
||
duration = get_wav_duration(str(public_audio))
|
||
print_success(f"Test audio generated: {public_audio}")
|
||
|
||
# Send to VRM
|
||
print_info("Sending audio to VRM server...")
|
||
url = f"{BASE_URL}/talk"
|
||
payload = {
|
||
"audio_path": str(public_audio),
|
||
"expression": "relaxed",
|
||
"audio_text": test_text,
|
||
"audio_duraction": int(duration)
|
||
}
|
||
|
||
response = requests.post(url, json=payload, timeout=10)
|
||
|
||
if response.status_code == 200:
|
||
print_success(f"VRM talk request successful! Status: {response.status_code}")
|
||
print_info("Response: " + str(response.json()))
|
||
|
||
print_warning("\n" + "─"*60)
|
||
print_warning("IMPORTANT: Check the VRM client in your browser!")
|
||
print_warning("─"*60)
|
||
|
||
print_info("\nYou should see/hear:")
|
||
print(" 1. The avatar's mouth moving")
|
||
print(" 2. Audio playing from your speakers")
|
||
|
||
while True:
|
||
response = input(f"\n{Colors.BOLD}Did you see the mouth move AND hear audio? (y/n): {Colors.ENDC}").lower()
|
||
|
||
if response == 'y':
|
||
print_success("Perfect! VRM audio playback is working correctly!")
|
||
return True
|
||
else:
|
||
print_warning("\nDiagnosing the issue...")
|
||
|
||
mouth = input(f"{Colors.BOLD}Did you see the mouth move? (y/n): {Colors.ENDC}").lower()
|
||
audio = input(f"{Colors.BOLD}Did you hear audio? (y/n): {Colors.ENDC}").lower()
|
||
|
||
if audio == 'y' and mouth == 'n':
|
||
print_warning("\n🔧 Issue: Audio plays but mouth doesn't move")
|
||
print_info("Solution:")
|
||
print(" 1. Open client/config.js")
|
||
print(" 2. Find 'mouth_threshold' variable")
|
||
print(" 3. Try lowering it (e.g., from 0.01 to 0.005)")
|
||
print(" 4. Refresh the browser and test again")
|
||
|
||
elif audio == 'n' and mouth == 'n':
|
||
print_warning("\n🔧 Issue: No audio and no animation")
|
||
print_info("Solution:")
|
||
print(" 1. Refresh the browser page")
|
||
print(" 2. Click inside the browser window to activate it")
|
||
print(" 3. Try again")
|
||
print(" 4. Check browser console (F12) for errors")
|
||
print(" 5. Ensure audio files are in the correct directory")
|
||
|
||
elif audio == 'n' and mouth == 'y':
|
||
print_warning("\n🔧 Issue: Mouth moves but no audio")
|
||
print_info("Solution:")
|
||
print(" 1. Check browser audio isn't muted")
|
||
print(" 2. Check system volume")
|
||
print(" 3. Verify audio file path is correct")
|
||
print(" 4. Check browser console (F12) for audio errors")
|
||
|
||
# Ask if they want to retry or continue
|
||
print("\n" + "─"*60)
|
||
retry = input(f"{Colors.BOLD}Would you like to:\n [r] Retry the test after applying changes\n [c] Continue to next test anyway\n [q] Mark as failed and continue\nChoice (r/c/q): {Colors.ENDC}").lower()
|
||
|
||
if retry == 'r':
|
||
print_info("\nRetrying test...\n")
|
||
# Re-send the audio to VRM
|
||
time.sleep(1)
|
||
requests.post(url, json=payload, timeout=10)
|
||
continue # Loop back to ask if it worked
|
||
elif retry == 'c':
|
||
print_warning("Continuing despite issues...")
|
||
return True # Mark as passed but with warning
|
||
else:
|
||
print_warning("Marking test as failed")
|
||
return False
|
||
else:
|
||
print_error(f"VRM talk request failed! Status: {response.status_code}")
|
||
print_info("Response: " + str(response.text))
|
||
return False
|
||
|
||
except Exception as e:
|
||
print_error(f"VRM audio playback test failed: {e}")
|
||
print_warning("\nTroubleshooting:")
|
||
print(" 1. Ensure VRM server is running")
|
||
print(" 2. Check browser is open with the VRM client")
|
||
print(" 3. Verify audio files are in correct directories")
|
||
print(" 4. Check server logs for errors")
|
||
return False
|
||
|
||
# ============================================================================
|
||
# TEST 6: VRM Animations
|
||
# ============================================================================
|
||
|
||
def test_vrm_animations():
|
||
print_header("TEST 6: VRM Animations")
|
||
|
||
animations = [
|
||
("idle", "animations/mixamo/Idle.fbx", "Idle (looking around)"),
|
||
("thinking", "animations/mixamo/Thinking.fbx", "Thinking (looking away)"),
|
||
("talking", "animations/mixamo/Talking.fbx", "Talking (nodding)"),
|
||
]
|
||
|
||
print_info("Testing VRM animations...")
|
||
print_warning("Watch the VRM client in your browser during these tests\n")
|
||
|
||
all_passed = True
|
||
|
||
for state_name, anim_path, description in animations:
|
||
while True:
|
||
try:
|
||
print_info(f"Testing: {description}")
|
||
|
||
# Set animation
|
||
url = f"{BASE_URL}/animate"
|
||
payload = {
|
||
"animate_type": "start_mixamo",
|
||
"animation_url": anim_path,
|
||
"play_once": False,
|
||
"crop_start": 0.0,
|
||
"crop_end": 0.0,
|
||
"lock_position": False,
|
||
"track_position": True,
|
||
}
|
||
response = requests.post(url, json=payload, timeout=5)
|
||
|
||
# Set state
|
||
state_url = f"{BASE_URL}/set_state"
|
||
state_payload = {"state": state_name}
|
||
state_response = requests.post(state_url, json=state_payload, timeout=5)
|
||
|
||
if response.status_code == 200 and state_response.status_code == 200:
|
||
print_success(f" Animation command sent successfully")
|
||
|
||
time.sleep(2) # Let user observe
|
||
|
||
saw_it = input(f" {Colors.BOLD}Did you see the {description} animation? (y/n): {Colors.ENDC}").lower()
|
||
if saw_it == 'y':
|
||
print_success(f" ✓ {state_name} animation working!\n")
|
||
break # Move to next animation
|
||
else:
|
||
print_warning(f" ! {state_name} animation may not be working")
|
||
print_info("\nTroubleshooting:")
|
||
print(f" 1. Check that {anim_path} exists")
|
||
print(" 2. Refresh the browser and ensure VRM client is loaded")
|
||
print(" 3. Check browser console (F12) for errors")
|
||
print(" 4. Verify VRM server logs")
|
||
|
||
retry = input(f"\n{Colors.BOLD}Would you like to:\n [r] Retry this animation\n [c] Continue to next animation\n [q] Mark as failed and continue\nChoice (r/c/q): {Colors.ENDC}").lower()
|
||
|
||
if retry == 'r':
|
||
print_info("Retrying animation...\n")
|
||
time.sleep(1)
|
||
continue # Retry this animation
|
||
elif retry == 'c':
|
||
print_warning(f"Continuing despite {state_name} animation issues...\n")
|
||
all_passed = False
|
||
break # Move to next animation
|
||
else:
|
||
print_warning(f"Marking {state_name} animation as failed\n")
|
||
all_passed = False
|
||
break # Move to next animation
|
||
else:
|
||
print_error(f" Animation request failed: {response.status_code}")
|
||
print_warning(" Check VRM server logs for details\n")
|
||
|
||
retry = input(f"{Colors.BOLD}Would you like to:\n [r] Retry this animation\n [c] Continue to next animation\nChoice (r/c): {Colors.ENDC}").lower()
|
||
|
||
if retry == 'r':
|
||
print_info("Retrying...\n")
|
||
continue
|
||
else:
|
||
all_passed = False
|
||
break
|
||
|
||
except Exception as e:
|
||
print_error(f" Animation test failed: {e}")
|
||
|
||
retry = input(f"{Colors.BOLD}Would you like to:\n [r] Retry this animation\n [c] Continue to next animation\nChoice (r/c): {Colors.ENDC}").lower()
|
||
|
||
if retry == 'r':
|
||
print_info("Retrying...\n")
|
||
continue
|
||
else:
|
||
all_passed = False
|
||
break
|
||
|
||
if all_passed:
|
||
print_success("\n✓ All animations are working!")
|
||
return True
|
||
else:
|
||
print_warning("\nSome animations may not be working correctly")
|
||
print_info("Troubleshooting:")
|
||
print(" 1. Check that animation files exist in animations/mixamo/")
|
||
print(" 2. Verify file paths are correct")
|
||
print(" 3. Check VRM server console for errors")
|
||
print(" 4. Ensure browser client has loaded properly")
|
||
return False
|
||
|
||
# ============================================================================
|
||
# TEST 7: Click Interaction
|
||
# ============================================================================
|
||
|
||
def test_click_interaction():
|
||
print_header("TEST 7: Click Interaction (/send_click_interaction)")
|
||
|
||
print_info("Sends sample touch payloads and checks the avatar reacts")
|
||
print_info("(feedback voice + reaction VRMA + return to idle).")
|
||
print_warning("Watch the VRM client during this test.\n")
|
||
|
||
samples = [
|
||
{"region": "chest", "bone": "J_Sec_Bust1_R", "label": "chest/bust"},
|
||
{"region": "head", "bone": "J_Bip_C_Head", "label": "head"},
|
||
]
|
||
|
||
url = f"{BASE_URL}/send_click_interaction"
|
||
all_passed = True
|
||
|
||
for i, sample in enumerate(samples, 1):
|
||
payload = {
|
||
"type": "click_interaction",
|
||
"bone": sample["bone"],
|
||
"region": sample["region"],
|
||
}
|
||
print_info(f"[{i}/{len(samples)}] Sending {sample['label']} touch ({sample['region']!r})...")
|
||
try:
|
||
resp = requests.post(url, json=payload, timeout=5)
|
||
except Exception as e:
|
||
print_error(f" Request failed: {e}")
|
||
all_passed = False
|
||
continue
|
||
|
||
if resp.status_code != 200:
|
||
print_error(f" HTTP {resp.status_code}: {resp.text}")
|
||
all_passed = False
|
||
continue
|
||
|
||
body = resp.json()
|
||
print_success(f" Server picked: {body.get('animation')}")
|
||
print_info(f" Idle scheduled in: {body.get('idle_in')}s")
|
||
print_info(f" Pending actions buffered: {body.get('pending_actions')}")
|
||
|
||
# Give the user time to observe the reaction + idle return.
|
||
observe_seconds = float(body.get("idle_in") or 2.0) + 1.5
|
||
time.sleep(observe_seconds)
|
||
|
||
saw_it = input(
|
||
f" {Colors.BOLD}Did you see/hear a reaction for {sample['label']} and a return to idle? (y/n): {Colors.ENDC}"
|
||
).lower().strip()
|
||
if saw_it == "y":
|
||
print_success(f" ✓ {sample['label']} click reaction working!\n")
|
||
else:
|
||
print_warning(f" ! {sample['label']} reaction may not be working")
|
||
print_info(" Troubleshooting:")
|
||
print(f" 1. Check public/sounds/oh.wav and hey.wav exist")
|
||
print(f" 2. Check {body.get('animation')} exists on disk")
|
||
print(f" 3. Check browser console (F12) for WebSocket messages")
|
||
all_passed = False
|
||
|
||
if all_passed:
|
||
print_success("All click interaction samples passed!")
|
||
return all_passed
|
||
|
||
|
||
# ============================================================================
|
||
# TEST 8: Walk To
|
||
# ============================================================================
|
||
|
||
def test_walk_to():
|
||
print_header("TEST 8: Walk To (/walk_to)")
|
||
|
||
print_info("Commands the avatar to walk to a target position, then back.")
|
||
print_warning("Watch the VRM client - avatar should walk smoothly.\n")
|
||
|
||
url = f"{BASE_URL}/walk_to"
|
||
targets = [
|
||
{"x": -1.0, "y": 0.0, "z": 1.0, "speed": 1.0, "label": "forward-left (-1, 0, 1)"},
|
||
{"x": 0.0, "y": 0.0, "z": 0.0, "speed": 1.0, "label": "back to origin (0, 0, 0)"},
|
||
]
|
||
|
||
all_passed = True
|
||
for i, target in enumerate(targets, 1):
|
||
payload = {k: v for k, v in target.items() if k != "label"}
|
||
print_info(f"[{i}/{len(targets)}] Walking to {target['label']}...")
|
||
try:
|
||
resp = requests.post(url, json=payload, timeout=5)
|
||
except Exception as e:
|
||
print_error(f" Request failed: {e}")
|
||
all_passed = False
|
||
continue
|
||
|
||
if resp.status_code != 200:
|
||
print_error(f" HTTP {resp.status_code}: {resp.text}")
|
||
all_passed = False
|
||
continue
|
||
|
||
print_success(f" Walk command accepted: {resp.json()}")
|
||
# Give the avatar time to actually walk before asking.
|
||
time.sleep(4)
|
||
|
||
saw_it = input(
|
||
f" {Colors.BOLD}Did the avatar walk to {target['label']}? (y/n): {Colors.ENDC}"
|
||
).lower().strip()
|
||
if saw_it == "y":
|
||
print_success(f" ✓ Walk to {target['label']} succeeded!\n")
|
||
else:
|
||
print_warning(f" ! Walk to {target['label']} may not have worked")
|
||
print_info(" Troubleshooting:")
|
||
print(" 1. Check movement animations are loaded (walk + idle)")
|
||
print(" 2. Check browser console for movementController errors")
|
||
print(" 3. Try POSTing /load_movement_animation to load a walk anim")
|
||
all_passed = False
|
||
|
||
if all_passed:
|
||
print_success("All walk_to targets passed!")
|
||
return all_passed
|
||
|
||
|
||
# ============================================================================
|
||
# Main Test Runner
|
||
# ============================================================================
|
||
|
||
def main():
|
||
print(f"\n{Colors.HEADER}{Colors.BOLD}")
|
||
print("╔═══════════════════════════════════════════════════════════╗")
|
||
print("║ VRM AVATAR CHAT SYSTEM - COMPREHENSIVE TEST SUITE ║")
|
||
print("╚═══════════════════════════════════════════════════════════╝")
|
||
print(f"{Colors.ENDC}")
|
||
|
||
print_info("This script will test all components of your VRM chat system")
|
||
print_info("Each test will provide feedback and troubleshooting tips if needed\n")
|
||
|
||
wait_for_user()
|
||
|
||
results = {}
|
||
|
||
# Run tests in sequence
|
||
results['config'] = test_config_and_keys()
|
||
|
||
if results['config']:
|
||
results['llm'] = test_llm()
|
||
else:
|
||
print_warning("\nSkipping LLM test due to configuration issues")
|
||
results['llm'] = False
|
||
|
||
results['recording'] = test_audio_recording()
|
||
results['tts'] = test_tts_generation()
|
||
results['vrm_server'] = test_vrm_server()
|
||
|
||
if results['vrm_server']:
|
||
results['vrm_playback'] = test_vrm_audio_playback()
|
||
results['vrm_animations'] = test_vrm_animations()
|
||
results['click'] = test_click_interaction()
|
||
results['walk'] = test_walk_to()
|
||
else:
|
||
print_warning("\nSkipping VRM tests due to server connection issues")
|
||
results['vrm_playback'] = False
|
||
results['vrm_animations'] = False
|
||
results['click'] = False
|
||
results['walk'] = False
|
||
|
||
# Final Summary
|
||
print_header("TEST SUMMARY")
|
||
|
||
passed = sum(1 for v in results.values() if v)
|
||
total = len(results)
|
||
|
||
for test_name, result in results.items():
|
||
status = f"{Colors.OKGREEN}PASS{Colors.ENDC}" if result else f"{Colors.FAIL}FAIL{Colors.ENDC}"
|
||
print(f" {test_name.upper():.<50} {status}")
|
||
|
||
print(f"\n{Colors.BOLD}Overall: {passed}/{total} tests passed{Colors.ENDC}")
|
||
|
||
if passed == total:
|
||
print(f"\n{Colors.OKGREEN}{Colors.BOLD}🎉 All systems operational! Your VRM chat system is ready to use!{Colors.ENDC}")
|
||
else:
|
||
print(f"\n{Colors.WARNING}⚠ Some tests failed. Please review the troubleshooting tips above.{Colors.ENDC}")
|
||
print_info("\nCommon next steps:")
|
||
print(" 1. Fix configuration issues (API keys, config files)")
|
||
print(" 2. Ensure all servers are running (GPT-SoVITS, VRM server)")
|
||
print(" 3. Check hardware (microphone, speakers)")
|
||
print(" 4. Verify network connectivity")
|
||
print(" 5. Review server logs for detailed errors")
|
||
|
||
if __name__ == '__main__':
|
||
try:
|
||
main()
|
||
except KeyboardInterrupt:
|
||
print(f"\n\n{Colors.WARNING}Test interrupted by user{Colors.ENDC}")
|
||
sys.exit(0)
|
||
except Exception as e:
|
||
print(f"\n{Colors.FAIL}Unexpected error: {e}{Colors.ENDC}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
sys.exit(1) |