1.9.6 (#1)
New functionality and bugfixes. Reviewed-on: #1 Co-authored-by: Rune Olsen <rune@rune.pm> Co-committed-by: Rune Olsen <rune@rune.pm>
This commit was merged in pull request #1.
This commit is contained in:
@@ -23,7 +23,7 @@ oAI is a command-line chat application that provides an interactive interface to
|
||||
- Python 3.7 or higher
|
||||
- OpenRouter API key (get one at https://openrouter.ai)
|
||||
|
||||
## Screenshot
|
||||
## Screenshot (<span style="font-size:0.8em;">from version 1.0</span>)
|
||||
|
||||
[<img src="https://gitlab.pm/rune/oai/raw/branch/main/images/screenshot_01.png">](https://gitlab.pm/rune/oai/src/branch/main/README.md)
|
||||
|
||||
|
||||
486
oai.py
486
oai.py
@@ -30,7 +30,7 @@ from packaging import version as pkg_version
|
||||
import io # Added for custom handler
|
||||
|
||||
# App version. Changes by author with new releases.
|
||||
version = '1.9.5'
|
||||
version = '1.9.6'
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
@@ -60,6 +60,218 @@ VALID_COMMANDS = {
|
||||
'/info', '/model', '/maxtoken', '/system', '/config', '/credits', '/clear', '/cl', '/help'
|
||||
}
|
||||
|
||||
# Detailed command help database
|
||||
COMMAND_HELP = {
|
||||
'/clear': {
|
||||
'aliases': ['/cl'],
|
||||
'description': 'Clear the terminal screen for a clean interface.',
|
||||
'usage': '/clear\n/cl',
|
||||
'examples': [
|
||||
('Clear screen', '/clear'),
|
||||
('Using short alias', '/cl'),
|
||||
],
|
||||
'notes': 'You can also use the keyboard shortcut Ctrl+L to clear the screen.'
|
||||
},
|
||||
'/help': {
|
||||
'description': 'Display help information for commands.',
|
||||
'usage': '/help [command]',
|
||||
'examples': [
|
||||
('Show all commands', '/help'),
|
||||
('Get help for a specific command', '/help /model'),
|
||||
('Get help for config', '/help /config'),
|
||||
],
|
||||
'notes': 'Use /help without arguments to see the full command list, or /help <command> for detailed information about a specific command.'
|
||||
},
|
||||
'/memory': {
|
||||
'description': 'Toggle conversation memory. When ON, the AI remembers conversation history. When OFF, each request is independent (saves tokens and cost).',
|
||||
'usage': '/memory [on|off]',
|
||||
'examples': [
|
||||
('Check current memory status', '/memory'),
|
||||
('Enable conversation memory', '/memory on'),
|
||||
('Disable memory (save costs)', '/memory off'),
|
||||
],
|
||||
'notes': 'Memory is ON by default. Disabling memory reduces API costs but the AI won\'t remember previous messages. Messages are still saved locally for your reference.'
|
||||
},
|
||||
'/online': {
|
||||
'description': 'Enable or disable online mode (web search capabilities) for the current session.',
|
||||
'usage': '/online [on|off]',
|
||||
'examples': [
|
||||
('Check online mode status', '/online'),
|
||||
('Enable web search', '/online on'),
|
||||
('Disable web search', '/online off'),
|
||||
],
|
||||
'notes': 'Not all models support online mode. The model must have "tools" parameter support. This setting overrides the default online mode configured with /config online.'
|
||||
},
|
||||
'/paste': {
|
||||
'description': 'Paste plain text or code from clipboard and send to the AI. Optionally add a prompt.',
|
||||
'usage': '/paste [prompt]',
|
||||
'examples': [
|
||||
('Paste clipboard content', '/paste'),
|
||||
('Paste with a question', '/paste Explain this code'),
|
||||
('Paste and ask for review', '/paste Review this for bugs'),
|
||||
],
|
||||
'notes': 'Only plain text is supported. Binary clipboard data will be rejected. The clipboard content is shown as a preview before sending.'
|
||||
},
|
||||
'/retry': {
|
||||
'description': 'Resend the last prompt from conversation history.',
|
||||
'usage': '/retry',
|
||||
'examples': [
|
||||
('Retry last message', '/retry'),
|
||||
],
|
||||
'notes': 'Useful when you get an error or want a different response to the same prompt. Requires at least one message in history.'
|
||||
},
|
||||
'/next': {
|
||||
'description': 'View the next response in conversation history.',
|
||||
'usage': '/next',
|
||||
'examples': [
|
||||
('Navigate to next response', '/next'),
|
||||
],
|
||||
'notes': 'Navigate through conversation history. Use /prev to go backward.'
|
||||
},
|
||||
'/prev': {
|
||||
'description': 'View the previous response in conversation history.',
|
||||
'usage': '/prev',
|
||||
'examples': [
|
||||
('Navigate to previous response', '/prev'),
|
||||
],
|
||||
'notes': 'Navigate through conversation history. Use /next to go forward.'
|
||||
},
|
||||
'/reset': {
|
||||
'description': 'Clear conversation history and reset system prompt. This resets all session metrics.',
|
||||
'usage': '/reset',
|
||||
'examples': [
|
||||
('Reset conversation', '/reset'),
|
||||
],
|
||||
'notes': 'Requires confirmation. This clears all message history, resets the system prompt, and resets token/cost counters. Use when starting a completely new conversation topic.'
|
||||
},
|
||||
'/info': {
|
||||
'description': 'Display detailed information about a model including pricing, capabilities, context length, and online support.',
|
||||
'usage': '/info [model_id]',
|
||||
'examples': [
|
||||
('Show current model info', '/info'),
|
||||
('Show specific model info', '/info gpt-4o'),
|
||||
('Check model capabilities', '/info claude-3-opus'),
|
||||
],
|
||||
'notes': 'Without arguments, shows info for the currently selected model. Displays pricing per million tokens, supported modalities (text, image, etc.), and parameter support.'
|
||||
},
|
||||
'/model': {
|
||||
'description': 'Select or change the AI model for the current session. Shows image and online capabilities.',
|
||||
'usage': '/model [search_term]',
|
||||
'examples': [
|
||||
('List all models', '/model'),
|
||||
('Search for GPT models', '/model gpt'),
|
||||
('Search for Claude models', '/model claude'),
|
||||
],
|
||||
'notes': 'Models are numbered for easy selection. The table shows Image (✓ if model accepts images) and Online (✓ if model supports web search) columns.'
|
||||
},
|
||||
'/config': {
|
||||
'description': 'View or modify application configuration settings.',
|
||||
'usage': '/config [setting] [value]',
|
||||
'examples': [
|
||||
('View all settings', '/config'),
|
||||
('Set API key', '/config api'),
|
||||
('Set default model', '/config model'),
|
||||
('Enable streaming', '/config stream on'),
|
||||
('Set cost warning threshold', '/config costwarning 0.05'),
|
||||
('Set log level', '/config loglevel debug'),
|
||||
('Set default online mode', '/config online on'),
|
||||
],
|
||||
'notes': 'Available settings: api (API key), url (base URL), model (default model), stream (on/off), costwarning (threshold $), maxtoken (limit), online (default on/off), log (size MB), loglevel (debug/info/warning/error/critical).'
|
||||
},
|
||||
'/maxtoken': {
|
||||
'description': 'Set a temporary session token limit (cannot exceed stored max token limit).',
|
||||
'usage': '/maxtoken [value]',
|
||||
'examples': [
|
||||
('View current session limit', '/maxtoken'),
|
||||
('Set session limit to 2000', '/maxtoken 2000'),
|
||||
('Set to 50000', '/maxtoken 50000'),
|
||||
],
|
||||
'notes': 'This is a session-only setting and cannot exceed the stored max token limit (set with /config maxtoken). View without arguments to see current value.'
|
||||
},
|
||||
'/system': {
|
||||
'description': 'Set or clear the session-level system prompt to guide AI behavior.',
|
||||
'usage': '/system [prompt|clear]',
|
||||
'examples': [
|
||||
('View current system prompt', '/system'),
|
||||
('Set as Python expert', '/system You are a Python expert'),
|
||||
('Set as code reviewer', '/system You are a senior code reviewer. Focus on bugs and best practices.'),
|
||||
('Clear system prompt', '/system clear'),
|
||||
],
|
||||
'notes': 'System prompts influence how the AI responds throughout the session. Use "clear" to remove the current system prompt.'
|
||||
},
|
||||
'/save': {
|
||||
'description': 'Save the current conversation history to the database.',
|
||||
'usage': '/save <name>',
|
||||
'examples': [
|
||||
('Save conversation', '/save my_chat'),
|
||||
('Save with descriptive name', '/save python_debugging_2024'),
|
||||
],
|
||||
'notes': 'Saved conversations can be loaded later with /load. Use descriptive names to easily find them later.'
|
||||
},
|
||||
'/load': {
|
||||
'description': 'Load a saved conversation from the database by name or number.',
|
||||
'usage': '/load <name|number>',
|
||||
'examples': [
|
||||
('Load by name', '/load my_chat'),
|
||||
('Load by number from /list', '/load 3'),
|
||||
],
|
||||
'notes': 'Use /list to see numbered conversations. Loading a conversation replaces current history and resets session metrics.'
|
||||
},
|
||||
'/delete': {
|
||||
'description': 'Delete a saved conversation from the database. Requires confirmation.',
|
||||
'usage': '/delete <name|number>',
|
||||
'examples': [
|
||||
('Delete by name', '/delete my_chat'),
|
||||
('Delete by number from /list', '/delete 3'),
|
||||
],
|
||||
'notes': 'Use /list to see numbered conversations. This action cannot be undone!'
|
||||
},
|
||||
'/list': {
|
||||
'description': 'List all saved conversations with numbers, message counts, and timestamps.',
|
||||
'usage': '/list',
|
||||
'examples': [
|
||||
('Show saved conversations', '/list'),
|
||||
],
|
||||
'notes': 'Conversations are numbered for easy use with /load and /delete commands. Shows message count and last saved time.'
|
||||
},
|
||||
'/export': {
|
||||
'description': 'Export the current conversation to a file in various formats.',
|
||||
'usage': '/export <format> <filename>',
|
||||
'examples': [
|
||||
('Export as Markdown', '/export md notes.md'),
|
||||
('Export as JSON', '/export json conversation.json'),
|
||||
('Export as HTML', '/export html report.html'),
|
||||
],
|
||||
'notes': 'Available formats: md (Markdown), json (JSON), html (HTML). The export includes all messages and the system prompt if set.'
|
||||
},
|
||||
'/stats': {
|
||||
'description': 'Display session statistics including tokens, costs, and credits.',
|
||||
'usage': '/stats',
|
||||
'examples': [
|
||||
('View session statistics', '/stats'),
|
||||
],
|
||||
'notes': 'Shows total input/output tokens, total cost, average cost per message, and remaining credits. Also displays credit warnings if applicable.'
|
||||
},
|
||||
'/credits': {
|
||||
'description': 'Display your OpenRouter account credits and usage.',
|
||||
'usage': '/credits',
|
||||
'examples': [
|
||||
('Check credits', '/credits'),
|
||||
],
|
||||
'notes': 'Shows total credits, used credits, and credits left. Displays warnings if credits are low (< $1 or < 10% of total).'
|
||||
},
|
||||
'/middleout': {
|
||||
'description': 'Enable or disable middle-out transform to compress prompts exceeding context size.',
|
||||
'usage': '/middleout [on|off]',
|
||||
'examples': [
|
||||
('Check status', '/middleout'),
|
||||
('Enable compression', '/middleout on'),
|
||||
('Disable compression', '/middleout off'),
|
||||
],
|
||||
'notes': 'Middle-out transform intelligently compresses long prompts to fit within model context limits. Useful for very long conversations.'
|
||||
},
|
||||
}
|
||||
|
||||
# Supported code file extensions
|
||||
SUPPORTED_CODE_EXTENSIONS = {
|
||||
'.py', '.js', '.ts', '.cs', '.java', '.c', '.cpp', '.h', '.hpp',
|
||||
@@ -77,6 +289,15 @@ LOW_CREDIT_RATIO = 0.1 # Warn if credits left < 10% of total
|
||||
LOW_CREDIT_AMOUNT = 1.0 # Warn if credits left < $1 in absolute terms
|
||||
HIGH_COST_WARNING = "cost_warning_threshold" # Configurable key for cost threshold, default $0.01
|
||||
|
||||
# Valid log levels mapping
|
||||
VALID_LOG_LEVELS = {
|
||||
'debug': logging.DEBUG,
|
||||
'info': logging.INFO,
|
||||
'warning': logging.WARNING,
|
||||
'error': logging.ERROR,
|
||||
'critical': logging.CRITICAL
|
||||
}
|
||||
|
||||
# DB configuration
|
||||
database = config_dir / 'oai_config.db'
|
||||
DB_FILE = str(database)
|
||||
@@ -148,27 +369,131 @@ class RotatingRichHandler(RotatingFileHandler):
|
||||
except Exception:
|
||||
self.handleError(record)
|
||||
|
||||
# Get log configuration from DB
|
||||
# ============================================================================
|
||||
# LOGGING SETUP - MUST BE DONE AFTER CONFIG IS LOADED
|
||||
# ============================================================================
|
||||
|
||||
# Load log configuration from DB FIRST (before creating handler)
|
||||
LOG_MAX_SIZE_MB = int(get_config('log_max_size_mb') or "10")
|
||||
LOG_BACKUP_COUNT = int(get_config('log_backup_count') or "2")
|
||||
LOG_LEVEL_STR = get_config('log_level') or "info"
|
||||
LOG_LEVEL = VALID_LOG_LEVELS.get(LOG_LEVEL_STR.lower(), logging.INFO)
|
||||
|
||||
# Create the custom rotating handler
|
||||
app_handler = RotatingRichHandler(
|
||||
filename=str(log_file),
|
||||
maxBytes=LOG_MAX_SIZE_MB * 1024 * 1024, # Convert MB to bytes
|
||||
backupCount=LOG_BACKUP_COUNT,
|
||||
encoding='utf-8'
|
||||
)
|
||||
# Global reference to the handler for dynamic reloading
|
||||
app_handler = None
|
||||
app_logger = None
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.NOTSET,
|
||||
format="%(message)s", # Rich formats it
|
||||
datefmt="[%X]",
|
||||
handlers=[app_handler]
|
||||
)
|
||||
def setup_logging():
|
||||
"""Setup or reset logging configuration with current settings."""
|
||||
global app_handler, LOG_MAX_SIZE_MB, LOG_BACKUP_COUNT, LOG_LEVEL, app_logger
|
||||
|
||||
# Get the root logger
|
||||
root_logger = logging.getLogger()
|
||||
|
||||
# Remove existing handler if present
|
||||
if app_handler is not None:
|
||||
root_logger.removeHandler(app_handler)
|
||||
try:
|
||||
app_handler.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
# Check if log file needs immediate rotation
|
||||
if os.path.exists(log_file):
|
||||
current_size = os.path.getsize(log_file)
|
||||
max_bytes = LOG_MAX_SIZE_MB * 1024 * 1024
|
||||
|
||||
if current_size >= max_bytes:
|
||||
# Perform immediate rotation
|
||||
import shutil
|
||||
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
backup_file = f"{log_file}.{timestamp}"
|
||||
try:
|
||||
shutil.move(str(log_file), backup_file)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not rotate log file: {e}")
|
||||
|
||||
# Clean up old backups if exceeding limit
|
||||
log_dir = os.path.dirname(log_file)
|
||||
log_basename = os.path.basename(log_file)
|
||||
backup_pattern = f"{log_basename}.*"
|
||||
|
||||
import glob
|
||||
backups = sorted(glob.glob(os.path.join(log_dir, backup_pattern)))
|
||||
|
||||
# Keep only the most recent backups
|
||||
while len(backups) > LOG_BACKUP_COUNT:
|
||||
oldest = backups.pop(0)
|
||||
try:
|
||||
os.remove(oldest)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Create new handler with current settings
|
||||
app_handler = RotatingRichHandler(
|
||||
filename=str(log_file),
|
||||
maxBytes=LOG_MAX_SIZE_MB * 1024 * 1024,
|
||||
backupCount=LOG_BACKUP_COUNT,
|
||||
encoding='utf-8'
|
||||
)
|
||||
|
||||
# Set handler level to NOTSET so it processes all records
|
||||
app_handler.setLevel(logging.NOTSET)
|
||||
|
||||
# Configure root logger - set to WARNING to suppress third-party library noise
|
||||
root_logger.setLevel(logging.WARNING)
|
||||
root_logger.addHandler(app_handler)
|
||||
|
||||
# Suppress noisy third-party loggers
|
||||
# These libraries create DEBUG logs that pollute our log file
|
||||
logging.getLogger('asyncio').setLevel(logging.WARNING)
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
logging.getLogger('requests').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpx').setLevel(logging.WARNING)
|
||||
logging.getLogger('httpcore').setLevel(logging.WARNING)
|
||||
logging.getLogger('openai').setLevel(logging.WARNING)
|
||||
logging.getLogger('openrouter').setLevel(logging.WARNING)
|
||||
|
||||
# Get or create app logger and set its level (this filters what gets logged)
|
||||
app_logger = logging.getLogger("oai_app")
|
||||
app_logger.setLevel(LOG_LEVEL)
|
||||
# Don't propagate to avoid root logger filtering
|
||||
app_logger.propagate = True
|
||||
|
||||
return app_logger
|
||||
|
||||
app_logger = logging.getLogger("oai_app")
|
||||
app_logger.setLevel(logging.INFO)
|
||||
# Initial logging setup
|
||||
app_logger = setup_logging()
|
||||
|
||||
def set_log_level(level_str: str) -> bool:
|
||||
"""Set the application log level. Returns True if successful."""
|
||||
global LOG_LEVEL, LOG_LEVEL_STR, app_logger
|
||||
level_str_lower = level_str.lower()
|
||||
if level_str_lower not in VALID_LOG_LEVELS:
|
||||
return False
|
||||
LOG_LEVEL = VALID_LOG_LEVELS[level_str_lower]
|
||||
LOG_LEVEL_STR = level_str_lower
|
||||
|
||||
# Update the logger level immediately
|
||||
if app_logger:
|
||||
app_logger.setLevel(LOG_LEVEL)
|
||||
|
||||
return True
|
||||
|
||||
def reload_logging_config():
|
||||
"""Reload logging configuration from database and reinitialize handler."""
|
||||
global LOG_MAX_SIZE_MB, LOG_BACKUP_COUNT, LOG_LEVEL, LOG_LEVEL_STR, app_logger
|
||||
|
||||
# Reload from database
|
||||
LOG_MAX_SIZE_MB = int(get_config('log_max_size_mb') or "10")
|
||||
LOG_BACKUP_COUNT = int(get_config('log_backup_count') or "2")
|
||||
LOG_LEVEL_STR = get_config('log_level') or "info"
|
||||
LOG_LEVEL = VALID_LOG_LEVELS.get(LOG_LEVEL_STR.lower(), logging.INFO)
|
||||
|
||||
# Reinitialize logging
|
||||
app_logger = setup_logging()
|
||||
|
||||
return app_logger
|
||||
|
||||
# ============================================================================
|
||||
# END OF LOGGING SETUP
|
||||
@@ -408,7 +733,64 @@ def export_as_html(session_history: List[Dict[str, str]], session_system_prompt:
|
||||
|
||||
return "\n".join(html_parts)
|
||||
|
||||
# Load configs
|
||||
def show_command_help(command: str):
|
||||
"""Display detailed help for a specific command."""
|
||||
# Normalize command to ensure it starts with /
|
||||
if not command.startswith('/'):
|
||||
command = '/' + command
|
||||
|
||||
# Check if command exists
|
||||
if command not in COMMAND_HELP:
|
||||
console.print(f"[bold red]Unknown command: {command}[/]")
|
||||
console.print("[bold yellow]Type /help to see all available commands.[/]")
|
||||
app_logger.warning(f"Help requested for unknown command: {command}")
|
||||
return
|
||||
|
||||
help_data = COMMAND_HELP[command]
|
||||
|
||||
# Create detailed help panel
|
||||
help_content = []
|
||||
|
||||
# Aliases if available
|
||||
if 'aliases' in help_data:
|
||||
aliases_str = ", ".join(help_data['aliases'])
|
||||
help_content.append(f"[bold cyan]Aliases:[/] {aliases_str}")
|
||||
help_content.append("")
|
||||
|
||||
# Description
|
||||
help_content.append(f"[bold cyan]Description:[/]")
|
||||
help_content.append(help_data['description'])
|
||||
help_content.append("")
|
||||
|
||||
# Usage
|
||||
help_content.append(f"[bold cyan]Usage:[/]")
|
||||
help_content.append(f"[yellow]{help_data['usage']}[/]")
|
||||
help_content.append("")
|
||||
|
||||
# Examples
|
||||
if 'examples' in help_data and help_data['examples']:
|
||||
help_content.append(f"[bold cyan]Examples:[/]")
|
||||
for desc, example in help_data['examples']:
|
||||
help_content.append(f" [dim]{desc}:[/]")
|
||||
help_content.append(f" [green]{example}[/]")
|
||||
help_content.append("")
|
||||
|
||||
# Notes
|
||||
if 'notes' in help_data:
|
||||
help_content.append(f"[bold cyan]Notes:[/]")
|
||||
help_content.append(f"[dim]{help_data['notes']}[/]")
|
||||
|
||||
console.print(Panel(
|
||||
"\n".join(help_content),
|
||||
title=f"[bold green]Help: {command}[/]",
|
||||
title_align="left",
|
||||
border_style="green",
|
||||
width=console.width - 4
|
||||
))
|
||||
|
||||
app_logger.info(f"Displayed detailed help for command: {command}")
|
||||
|
||||
# Load configs (AFTER logging is set up)
|
||||
API_KEY = get_config('api_key')
|
||||
OPENROUTER_BASE_URL = get_config('base_url') or "https://openrouter.ai/api/v1"
|
||||
STREAM_ENABLED = get_config('stream_enabled') or "on"
|
||||
@@ -618,7 +1000,7 @@ def display_paginated_table(table: Table, title: str):
|
||||
|
||||
@app.command()
|
||||
def chat():
|
||||
global API_KEY, OPENROUTER_BASE_URL, STREAM_ENABLED, MAX_TOKEN, COST_WARNING_THRESHOLD, DEFAULT_ONLINE_MODE, LOG_MAX_SIZE_MB, LOG_BACKUP_COUNT
|
||||
global API_KEY, OPENROUTER_BASE_URL, STREAM_ENABLED, MAX_TOKEN, COST_WARNING_THRESHOLD, DEFAULT_ONLINE_MODE, LOG_MAX_SIZE_MB, LOG_BACKUP_COUNT, LOG_LEVEL, LOG_LEVEL_STR, app_logger
|
||||
session_max_token = 0
|
||||
session_system_prompt = ""
|
||||
session_history = []
|
||||
@@ -1028,9 +1410,13 @@ def chat():
|
||||
console.print("[bold yellow]Usage: /middleout on|off (or /middleout to view status)[/]")
|
||||
continue
|
||||
elif user_input.lower() == "/reset":
|
||||
confirm = typer.confirm("Reset conversation context? This clears history and prompt.", default=False)
|
||||
if not confirm:
|
||||
console.print("[bold yellow]Reset cancelled.[/]")
|
||||
try:
|
||||
confirm = typer.confirm("Reset conversation context? This clears history and prompt.", default=False)
|
||||
if not confirm:
|
||||
console.print("[bold yellow]Reset cancelled.[/]")
|
||||
continue
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
console.print("\n[bold yellow]Reset cancelled.[/]")
|
||||
continue
|
||||
session_history = []
|
||||
current_index = -1
|
||||
@@ -1213,11 +1599,28 @@ def chat():
|
||||
console.print(f"[bold green]Streaming {'enabled' if sub_args == 'on' else 'disabled'}.[/]")
|
||||
else:
|
||||
console.print("[bold yellow]Usage: /config stream on|off[/]")
|
||||
elif args.startswith("loglevel"):
|
||||
sub_args = args[8:].strip()
|
||||
if not sub_args:
|
||||
console.print(f"[bold blue]Current log level: {LOG_LEVEL_STR.upper()}[/]")
|
||||
console.print(f"[dim yellow]Valid levels: debug, info, warning, error, critical[/]")
|
||||
continue
|
||||
if sub_args.lower() in VALID_LOG_LEVELS:
|
||||
if set_log_level(sub_args):
|
||||
set_config('log_level', sub_args.lower())
|
||||
console.print(f"[bold green]Log level set to: {sub_args.upper()}[/]")
|
||||
app_logger.info(f"Log level changed to {sub_args.upper()}")
|
||||
else:
|
||||
console.print(f"[bold red]Failed to set log level.[/]")
|
||||
else:
|
||||
console.print(f"[bold red]Invalid log level: {sub_args}[/]")
|
||||
console.print(f"[bold yellow]Valid levels: debug, info, warning, error, critical[/]")
|
||||
elif args.startswith("log"):
|
||||
sub_args = args[4:].strip()
|
||||
if not sub_args:
|
||||
console.print(f"[bold blue]Current log file size limit: {LOG_MAX_SIZE_MB} MB[/]")
|
||||
console.print(f"[bold blue]Log backup count: {LOG_BACKUP_COUNT} files[/]")
|
||||
console.print(f"[bold blue]Log level: {LOG_LEVEL_STR.upper()}[/]")
|
||||
console.print(f"[dim yellow]Total max disk usage: ~{LOG_MAX_SIZE_MB * (LOG_BACKUP_COUNT + 1)} MB[/]")
|
||||
continue
|
||||
try:
|
||||
@@ -1230,9 +1633,13 @@ def chat():
|
||||
new_size_mb = 100
|
||||
set_config('log_max_size_mb', str(new_size_mb))
|
||||
LOG_MAX_SIZE_MB = new_size_mb
|
||||
console.print(f"[bold green]Log size limit set to {new_size_mb} MB.[/]")
|
||||
console.print("[bold yellow]⚠️ Restart the application for this change to take effect.[/]")
|
||||
app_logger.info(f"Log size limit updated to {new_size_mb} MB (requires restart)")
|
||||
|
||||
# Reload logging configuration immediately
|
||||
app_logger = reload_logging_config()
|
||||
|
||||
console.print(f"[bold green]Log size limit set to {new_size_mb} MB and applied immediately.[/]")
|
||||
console.print(f"[dim cyan]Log file rotated if it exceeded the new limit.[/]")
|
||||
app_logger.info(f"Log size limit updated to {new_size_mb} MB and reloaded")
|
||||
except ValueError:
|
||||
console.print("[bold red]Invalid size. Provide a number in MB.[/]")
|
||||
elif args.startswith("online"):
|
||||
@@ -1315,6 +1722,7 @@ def chat():
|
||||
table.add_row("Logfile", str(log_file) or "[Not set]")
|
||||
table.add_row("Log Size Limit", f"{LOG_MAX_SIZE_MB} MB")
|
||||
table.add_row("Log Backups", str(LOG_BACKUP_COUNT))
|
||||
table.add_row("Log Level", LOG_LEVEL_STR.upper())
|
||||
table.add_row("Streaming", "Enabled" if STREAM_ENABLED == "on" else "Disabled")
|
||||
table.add_row("Default Model", DEFAULT_MODEL_ID or "[Not set]")
|
||||
table.add_row("Current Model", "[Not set]" if selected_model is None else str(selected_model["name"]))
|
||||
@@ -1366,7 +1774,15 @@ def chat():
|
||||
console.print("[bold cyan]Online mode: Enabled (web search active)[/]")
|
||||
continue
|
||||
|
||||
if user_input.lower() == "/help":
|
||||
if user_input.lower().startswith("/help"):
|
||||
args = user_input[6:].strip()
|
||||
|
||||
# If a specific command is requested
|
||||
if args:
|
||||
show_command_help(args)
|
||||
continue
|
||||
|
||||
# Otherwise show the full help menu
|
||||
help_table = Table("Command", "Description", "Example", show_header=True, header_style="bold cyan", width=console.width - 10)
|
||||
|
||||
# SESSION COMMANDS
|
||||
@@ -1381,9 +1797,9 @@ def chat():
|
||||
"/clear\n/cl"
|
||||
)
|
||||
help_table.add_row(
|
||||
"/help",
|
||||
"Show this help menu with all available commands.",
|
||||
"/help"
|
||||
"/help [command]",
|
||||
"Show this help menu or get detailed help for a specific command.",
|
||||
"/help\n/help /model"
|
||||
)
|
||||
help_table.add_row(
|
||||
"/memory [on|off]",
|
||||
@@ -1461,9 +1877,14 @@ def chat():
|
||||
)
|
||||
help_table.add_row(
|
||||
"/config log [size_mb]",
|
||||
"Set log file size limit in MB. Older logs are rotated automatically. Requires restart.",
|
||||
"Set log file size limit in MB. Older logs are rotated automatically. Takes effect immediately.",
|
||||
"/config log 20"
|
||||
)
|
||||
help_table.add_row(
|
||||
"/config loglevel [level]",
|
||||
"Set log verbosity level. Valid levels: debug, info, warning, error, critical. Takes effect immediately.",
|
||||
"/config loglevel debug\n/config loglevel warning"
|
||||
)
|
||||
help_table.add_row(
|
||||
"/config maxtoken [value]",
|
||||
"Set stored max token limit (persisted in DB). View current if no value provided.",
|
||||
@@ -1599,7 +2020,7 @@ def chat():
|
||||
help_table,
|
||||
title="[bold cyan]oAI Chat Help (Version %s)[/]" % version,
|
||||
title_align="center",
|
||||
subtitle="💡 Tip: Commands are case-insensitive • Memory ON by default (toggle with /memory) • Use // to escape / at start of input • Visit: https://iurl.no/oai",
|
||||
subtitle="💡 Tip: Commands are case-insensitive • Use /help <command> for detailed help • Memory ON by default • Use // to escape / • Visit: https://iurl.no/oai",
|
||||
subtitle_align="center",
|
||||
border_style="cyan"
|
||||
))
|
||||
@@ -1608,8 +2029,7 @@ def chat():
|
||||
if not selected_model:
|
||||
console.print("[bold yellow]Select a model first with '/model'.[/]")
|
||||
continue
|
||||
|
||||
# Process file attachments with PDF support
|
||||
# Process file attachments with PDF support
|
||||
content_blocks = []
|
||||
text_part = user_input
|
||||
file_attachments = []
|
||||
|
||||
Reference in New Issue
Block a user