fix shell

This commit is contained in:
2025-06-03 02:12:11 +09:00
parent 42c85fc820
commit 1755dc2bec
2 changed files with 207 additions and 76 deletions

View File

@ -81,7 +81,7 @@ Recent memories:
{"role": "user", "content": prompt}
]
)
return response['message']['content']
return self._clean_response(response['message']['content'])
except Exception as e:
self.logger.error(f"Ollama generation failed: {e}")
return self._fallback_response(persona_state)
@ -101,11 +101,20 @@ Recent memories:
},
stream=False # ストリーミング無効化で安定性向上
)
return response['message']['content']
return self._clean_response(response['message']['content'])
except Exception as e:
self.logger.error(f"Ollama chat failed (host: {self.host}): {e}")
return "I'm having trouble connecting to the AI model."
def _clean_response(self, response: str) -> str:
"""Clean response by removing think tags and other unwanted content"""
import re
# Remove <think></think> tags and their content
response = re.sub(r'<think>.*?</think>', '', response, flags=re.DOTALL)
# Remove any remaining whitespace at the beginning/end
response = response.strip()
return response
def _fallback_response(self, persona_state: PersonaState) -> str:
"""Fallback response based on mood"""
mood_responses = {

View File

@ -10,7 +10,7 @@ from datetime import datetime, timedelta
import subprocess
import shlex
from prompt_toolkit import prompt as ptk_prompt
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.completion import WordCompleter, Completer, Completion
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
@ -379,21 +379,32 @@ def schedule(
@app.command()
def shell(
data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory"),
model: Optional[str] = typer.Option("qwen2.5", "--model", "-m", help="AI model to use"),
provider: Optional[str] = typer.Option("ollama", "--provider", help="AI provider (ollama/openai)")
model: Optional[str] = typer.Option(None, "--model", "-m", help="AI model to use"),
provider: Optional[str] = typer.Option(None, "--provider", help="AI provider (ollama/openai)")
):
"""Interactive shell mode (ai.shell)"""
persona = get_persona(data_dir)
# Get defaults from config if not provided
config_instance = Config()
if not provider:
provider = config_instance.get("default_provider", "ollama")
if not model:
if provider == "ollama":
model = config_instance.get("providers.ollama.default_model", "qwen3:latest")
elif provider == "openai":
model = config_instance.get("providers.openai.default_model", "gpt-4o-mini")
else:
model = "qwen3:latest" # fallback
# Create AI provider
ai_provider = None
if provider and model:
try:
ai_provider = create_ai_provider(provider=provider, model=model)
console.print(f"[dim]Using {provider} with model {model}[/dim]\n")
except Exception as e:
console.print(f"[yellow]Warning: Could not create AI provider: {e}[/yellow]")
console.print("[yellow]Falling back to simple responses[/yellow]\n")
try:
ai_provider = create_ai_provider(provider=provider, model=model)
console.print(f"[dim]Using {provider} with model {model}[/dim]\n")
except Exception as e:
console.print(f"[yellow]Warning: Could not create AI provider: {e}[/yellow]")
console.print("[yellow]Falling back to simple responses[/yellow]\n")
# Welcome message
console.print(Panel(
@ -411,24 +422,71 @@ def shell(
border_style="green"
))
# Command completer with shell commands
builtin_commands = ['help', 'exit', 'quit', 'chat', 'status', 'clear', 'fortune', 'relationships', 'load']
# Custom completer for ai.shell
class ShellCompleter(Completer):
def __init__(self):
# Slash commands (built-in)
self.slash_commands = [
'/help', '/exit', '/quit', '/status', '/clear', '/load',
'/fortune', '/relationships'
]
# Add common shell commands
shell_commands = ['ls', 'cd', 'pwd', 'cat', 'echo', 'grep', 'find', 'mkdir', 'rm', 'cp', 'mv',
'git', 'python', 'pip', 'npm', 'node', 'cargo', 'rustc', 'docker', 'kubectl']
# AI commands
self.ai_commands = [
'/analyze', '/generate', '/explain', '/optimize',
'/refactor', '/test', '/document'
]
# AI-specific commands
ai_commands = ['analyze', 'generate', 'explain', 'optimize', 'refactor', 'test', 'document']
# Project commands
self.project_commands = [
'/project-status', '/suggest-next', '/continuous'
]
# Remote execution commands (ai.bot integration)
remote_commands = ['remote', 'isolated', 'aibot-status']
# Remote commands
self.remote_commands = [
'/remote', '/isolated', '/aibot-status'
]
# Project management commands (Claude Code-like)
project_commands = ['project-status', 'suggest-next', 'continuous']
# Shell commands (with ! prefix)
self.shell_commands = [
'!ls', '!cd', '!pwd', '!cat', '!echo', '!grep', '!find',
'!mkdir', '!rm', '!cp', '!mv', '!git', '!python', '!pip',
'!npm', '!node', '!cargo', '!rustc', '!docker', '!kubectl'
]
all_commands = builtin_commands + ['!' + cmd for cmd in shell_commands] + ai_commands + remote_commands + project_commands
completer = WordCompleter(all_commands, ignore_case=True)
# All commands combined
self.all_commands = (self.slash_commands + self.ai_commands +
self.project_commands + self.remote_commands +
self.shell_commands)
def get_completions(self, document, complete_event):
text = document.text_before_cursor
# For slash commands
if text.startswith('/'):
for cmd in self.all_commands:
if cmd.startswith('/') and cmd.startswith(text):
yield Completion(cmd, start_position=-len(text))
# For shell commands (!)
elif text.startswith('!'):
for cmd in self.shell_commands:
if cmd.startswith(text):
yield Completion(cmd, start_position=-len(text))
# For regular text (AI chat)
else:
# Common AI prompts
ai_prompts = [
'analyze this file', 'generate code for', 'explain how to',
'optimize this', 'refactor the', 'create tests for',
'document this code', 'help me with'
]
for prompt in ai_prompts:
if prompt.startswith(text.lower()):
yield Completion(prompt, start_position=-len(text))
completer = ShellCompleter()
# History file
actual_data_dir = data_dir if data_dir else DEFAULT_DATA_DIR
@ -452,43 +510,45 @@ def shell(
continue
# Exit commands
if user_input.lower() in ['exit', 'quit']:
if user_input.lower() in ['exit', 'quit', '/exit', '/quit']:
console.print("[cyan]Goodbye![/cyan]")
break
# Help command
elif user_input.lower() == 'help':
elif user_input.lower() in ['help', '/help', '/']:
console.print(Panel(
"[cyan]ai.shell Commands:[/cyan]\n\n"
" help - Show this help message\n"
" exit/quit - Exit the shell\n"
" !<command> - Execute a shell command\n"
" chat <message> - Explicitly chat with AI\n"
" status - Show AI status\n"
" fortune - Check AI fortune\n"
" relationships - List all relationships\n"
" clear - Clear the screen\n"
" load - Load aishell.md project file\n\n"
" /help, / - Show this help message\n"
" /exit, /quit - Exit the shell\n"
" !<command> - Execute a shell command (!ls, !git status)\n"
" /status - Show AI status\n"
" /fortune - Check AI fortune\n"
" /relationships - List all relationships\n"
" /clear - Clear the screen\n"
" /load - Load aishell.md project file\n\n"
"[cyan]AI Commands:[/cyan]\n"
" analyze <file> - Analyze a file with AI\n"
" generate <desc> - Generate code from description\n"
" explain <topic> - Get AI explanation\n\n"
" /analyze <file> - Analyze a file with AI\n"
" /generate <desc> - Generate code from description\n"
" /explain <topic> - Get AI explanation\n\n"
"[cyan]Remote Commands (ai.bot):[/cyan]\n"
" remote <command> - Execute command in isolated container\n"
" isolated <code> - Run Python code in isolated environment\n"
" aibot-status - Check ai.bot server status\n\n"
" /remote <command> - Execute command in isolated container\n"
" /isolated <code> - Run Python code in isolated environment\n"
" /aibot-status - Check ai.bot server status\n\n"
"[cyan]Project Commands (Claude Code-like):[/cyan]\n"
" project-status - Analyze current project structure\n"
" suggest-next - AI suggests next development steps\n"
" continuous - Enable continuous development mode\n\n"
"You can also type any message to chat with AI\n"
"Use Tab for command completion",
" /project-status - Analyze current project structure\n"
" /suggest-next - AI suggests next development steps\n"
" /continuous - Enable continuous development mode\n\n"
"[cyan]Tab Completion:[/cyan]\n"
" /[Tab] - Show all slash commands\n"
" ![Tab] - Show all shell commands\n"
" <text>[Tab] - AI prompt suggestions\n\n"
"Type any message to chat with AI",
title="Help",
border_style="yellow"
))
# Clear command
elif user_input.lower() == 'clear':
elif user_input.lower() in ['clear', '/clear']:
console.clear()
# Shell command execution
@ -517,7 +577,7 @@ def shell(
console.print(f"[red]Error executing command: {e}[/red]")
# Status command
elif user_input.lower() == 'status':
elif user_input.lower() in ['status', '/status']:
state = persona.get_current_state()
console.print(f"\nMood: {state.current_mood}")
console.print(f"Fortune: {state.fortune.fortune_value}/10")
@ -527,14 +587,14 @@ def shell(
console.print(f"Score: {rel.score:.2f} / {rel.threshold}")
# Fortune command
elif user_input.lower() == 'fortune':
elif user_input.lower() in ['fortune', '/fortune']:
fortune = persona.fortune_system.get_today_fortune()
fortune_bar = "🌟" * fortune.fortune_value + "" * (10 - fortune.fortune_value)
console.print(f"\n{fortune_bar}")
console.print(f"Today's Fortune: {fortune.fortune_value}/10")
# Relationships command
elif user_input.lower() == 'relationships':
elif user_input.lower() in ['relationships', '/relationships']:
if persona.relationships.relationships:
console.print("\n[cyan]Relationships:[/cyan]")
for user_id, rel in persona.relationships.relationships.items():
@ -543,7 +603,7 @@ def shell(
console.print("[yellow]No relationships yet[/yellow]")
# Load aishell.md command
elif user_input.lower() in ['load', 'load aishell.md', 'project']:
elif user_input.lower() in ['load', '/load', 'load aishell.md', 'project']:
# Try to find and load aishell.md
search_paths = [
Path.cwd() / "aishell.md",
@ -572,10 +632,10 @@ def shell(
console.print("Create aishell.md to define project goals and AI instructions.")
# AI-powered commands
elif user_input.lower().startswith('analyze '):
elif user_input.lower().startswith(('analyze ', '/analyze ')):
# Analyze file or code with project context
target = user_input[8:].strip()
if os.path.exists(target):
target = user_input.split(' ', 1)[1].strip() if ' ' in user_input else ''
if target and os.path.exists(target):
console.print(f"[cyan]Analyzing {target} with project context...[/cyan]")
try:
developer = ContinuousDeveloper(Path.cwd(), ai_provider)
@ -589,11 +649,11 @@ def shell(
response, _ = persona.process_interaction(current_user, analysis_prompt, ai_provider)
console.print(f"\n[cyan]Analysis:[/cyan]\n{response}")
else:
console.print(f"[red]File not found: {target}[/red]")
console.print(f"[red]Usage: /analyze <file_path>[/red]")
elif user_input.lower().startswith('generate '):
elif user_input.lower().startswith(('generate ', '/generate ')):
# Generate code with project context
gen_prompt = user_input[9:].strip()
gen_prompt = user_input.split(' ', 1)[1].strip() if ' ' in user_input else ''
if gen_prompt:
console.print("[cyan]Generating code with project context...[/cyan]")
try:
@ -605,8 +665,10 @@ def shell(
full_prompt = f"Generate code for: {gen_prompt}. Provide clean, well-commented code."
response, _ = persona.process_interaction(current_user, full_prompt, ai_provider)
console.print(f"\n[cyan]Generated Code:[/cyan]\n{response}")
else:
console.print(f"[red]Usage: /generate <description>[/red]")
elif user_input.lower().startswith('explain '):
elif user_input.lower().startswith(('explain ', '/explain ')):
# Explain code or concept
topic = user_input[8:].strip()
if topic:
@ -921,20 +983,31 @@ def import_chatgpt(
def conversation(
user_id: str = typer.Argument(..., help="User ID (atproto DID)"),
data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory"),
model: Optional[str] = typer.Option("qwen2.5", "--model", "-m", help="AI model to use"),
provider: Optional[str] = typer.Option("ollama", "--provider", help="AI provider (ollama/openai)")
model: Optional[str] = typer.Option(None, "--model", "-m", help="AI model to use"),
provider: Optional[str] = typer.Option(None, "--provider", help="AI provider (ollama/openai)")
):
"""Simple continuous conversation mode"""
persona = get_persona(data_dir)
# Get defaults from config if not provided
config_instance = Config()
if not provider:
provider = config_instance.get("default_provider", "ollama")
if not model:
if provider == "ollama":
model = config_instance.get("providers.ollama.default_model", "qwen3:latest")
elif provider == "openai":
model = config_instance.get("providers.openai.default_model", "gpt-4o-mini")
else:
model = "qwen3:latest" # fallback
# Create AI provider
ai_provider = None
if provider and model:
try:
ai_provider = create_ai_provider(provider=provider, model=model)
console.print(f"[dim]Using {provider} with model {model}[/dim]")
except Exception as e:
console.print(f"[yellow]Warning: Could not create AI provider: {e}[/yellow]")
try:
ai_provider = create_ai_provider(provider=provider, model=model)
console.print(f"[dim]Using {provider} with model {model}[/dim]")
except Exception as e:
console.print(f"[yellow]Warning: Could not create AI provider: {e}[/yellow]")
# Welcome message
console.print(f"[cyan]Conversation with AI started. Type 'exit' or 'quit' to end.[/cyan]\n")
@ -944,23 +1017,72 @@ def conversation(
history_file = actual_data_dir / "conversation_history.txt"
history = FileHistory(str(history_file))
# Custom completer for slash commands and phrases
class ConversationCompleter(Completer):
def __init__(self):
self.slash_commands = ['/status', '/help', '/clear', '/exit', '/quit']
self.phrases = ['こんにちは', '今日は', 'ありがとう', 'お疲れ様',
'どう思う?', 'どうですか?', '教えて', 'わかりました']
def get_completions(self, document, complete_event):
text = document.text_before_cursor
# If text starts with '/', complete slash commands
if text.startswith('/'):
for cmd in self.slash_commands:
if cmd.startswith(text):
yield Completion(cmd, start_position=-len(text))
# For other text, complete phrases
else:
for phrase in self.phrases:
if phrase.startswith(text):
yield Completion(phrase, start_position=-len(text))
completer = ConversationCompleter()
while True:
try:
# Simple prompt
# Simple prompt with completion
user_input = ptk_prompt(
f"{user_id}> ",
history=history,
auto_suggest=AutoSuggestFromHistory()
auto_suggest=AutoSuggestFromHistory(),
completer=completer
).strip()
if not user_input:
continue
# Exit commands
if user_input.lower() in ['exit', 'quit', 'bye']:
if user_input.lower() in ['exit', 'quit', 'bye', '/exit', '/quit']:
console.print("[cyan]Conversation ended.[/cyan]")
break
# Slash commands
elif user_input.lower() == '/status':
state = persona.get_current_state()
rel = persona.relationships.get_or_create_relationship(user_id)
console.print(f"\n[cyan]AI Status:[/cyan]")
console.print(f"Mood: {state.current_mood}")
console.print(f"Fortune: {state.fortune.fortune_value}/10")
console.print(f"Relationship: {rel.status.value} ({rel.score:.2f})")
console.print("")
continue
elif user_input.lower() in ['/help', '/']:
console.print(f"\n[cyan]Conversation Commands:[/cyan]")
console.print(" /status - Show AI status and relationship")
console.print(" /help - Show this help")
console.print(" /clear - Clear screen")
console.print(" /exit - End conversation")
console.print(" / - Show commands (same as /help)")
console.print(" <message> - Chat with AI\n")
continue
elif user_input.lower() == '/clear':
console.clear()
continue
# Process interaction
response, relationship_delta = persona.process_interaction(user_id, user_input, ai_provider)
@ -981,8 +1103,8 @@ def conversation(
def conv(
user_id: str = typer.Argument(..., help="User ID (atproto DID)"),
data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory"),
model: Optional[str] = typer.Option("qwen2.5", "--model", "-m", help="AI model to use"),
provider: Optional[str] = typer.Option("ollama", "--provider", help="AI provider (ollama/openai)")
model: Optional[str] = typer.Option(None, "--model", "-m", help="AI model to use"),
provider: Optional[str] = typer.Option(None, "--provider", help="AI provider (ollama/openai)")
):
"""Alias for conversation command"""
conversation(user_id, data_dir, model, provider)