From 36a412138d1396db7222cd110398b89eb16d159a Mon Sep 17 00:00:00 2001 From: Rune Olsen Date: Sun, 21 Dec 2025 19:21:14 +0100 Subject: [PATCH] =?UTF-8?q?More=20info=20in=20models=20using=20.=20To=20us?= =?UTF-8?q?e=20=1B]7;file://localhost/=07=20at=20start=20of=20query=20use?= =?UTF-8?q?=20=1B]7;file://localhost/=07.=20Plus=20some=20other=20changes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 ++- oai.py | 110 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 75 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index 3ee94d2..c1f0f04 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,7 @@ diagnose.py *.log *.xml build* -*.spec \ No newline at end of file +*.spec +compiled/ +images/oai-iOS-Default-1024x1024@1x.png +images/oai.icon/ diff --git a/oai.py b/oai.py index 8a252de..0f75fd4 100644 --- a/oai.py +++ b/oai.py @@ -47,6 +47,13 @@ cache_dir.mkdir(parents=True, exist_ok=True) # Rich console for chat UI (separate from logging) console = Console() +# Valid commands list for validation +VALID_COMMANDS = { + '/retry', '/online', '/memory', '/paste', '/export', '/save', '/load', + '/delete', '/list', '/prev', '/next', '/stats', '/middleout', '/reset', + '/info', '/model', '/maxtoken', '/system', '/config', '/credits', '/clear', '/cl', '/help' +} + # Supported code file extensions SUPPORTED_CODE_EXTENSIONS = { '.py', '.js', '.ts', '.cs', '.java', '.c', '.cpp', '.h', '.hpp', @@ -84,7 +91,7 @@ app_logger.setLevel(logging.INFO) # DB configuration database = config_dir / 'oai_config.db' DB_FILE = str(database) -version = '1.8' +version = '1.9' def create_table_if_not_exists(): """Ensure the config and conversation_sessions tables exist.""" @@ -163,6 +170,29 @@ def estimate_cost(input_tokens: int, output_tokens: int) -> float: """Estimate cost in USD based on token counts.""" return (input_tokens * MODEL_PRICING['input'] / 1_000_000) + (output_tokens * MODEL_PRICING['output'] / 1_000_000) +def has_web_search_capability(model: Dict[str, Any]) -> bool: + """Check if model supports web search based on supported_parameters.""" + supported_params = model.get("supported_parameters", []) + # Web search is typically indicated by 'tools' parameter support + return "tools" in supported_params + +def has_image_capability(model: Dict[str, Any]) -> bool: + """Check if model supports image input based on input modalities.""" + architecture = model.get("architecture", {}) + input_modalities = architecture.get("input_modalities", []) + return "image" in input_modalities + +def supports_online_mode(model: Dict[str, Any]) -> bool: + """Check if model supports :online suffix for web search.""" + # Models that support tools parameter can use :online + return has_web_search_capability(model) + +def get_effective_model_id(base_model_id: str, online_enabled: bool) -> str: + """Get the effective model ID with :online suffix if enabled.""" + if online_enabled and not base_model_id.endswith(':online'): + return f"{base_model_id}:online" + return base_model_id + def export_as_markdown(session_history: List[Dict[str, str]], session_system_prompt: str = "") -> str: """Export conversation history as Markdown.""" lines = ["# Conversation Export", ""] @@ -349,29 +379,6 @@ def clear_screen(): except: print("\n" * 100) -def has_web_search_capability(model: Dict[str, Any]) -> bool: - """Check if model supports web search based on supported_parameters.""" - supported_params = model.get("supported_parameters", []) - # Web search is typically indicated by 'tools' parameter support - return "tools" in supported_params - -def has_image_capability(model: Dict[str, Any]) -> bool: - """Check if model supports image input based on input modalities.""" - architecture = model.get("architecture", {}) - input_modalities = architecture.get("input_modalities", []) - return "image" in input_modalities - -def supports_online_mode(model: Dict[str, Any]) -> bool: - """Check if model supports :online suffix for web search.""" - # Models that support tools parameter can use :online - return has_web_search_capability(model) - -def get_effective_model_id(base_model_id: str, online_enabled: bool) -> str: - """Get the effective model ID with :online suffix if enabled.""" - if online_enabled and not base_model_id.endswith(':online'): - return f"{base_model_id}:online" - return base_model_id - def display_paginated_table(table: Table, title: str): """Display a table with pagination support using Rich console for colored output, repeating header on each page.""" # Get terminal height (subtract some lines for prompt and margins) @@ -573,6 +580,24 @@ def chat(): while True: try: user_input = session.prompt("You> ", auto_suggest=AutoSuggestFromHistory()).strip() + + # Handle // escape sequence - convert to single / and treat as regular text + if user_input.startswith("//"): + user_input = user_input[1:] # Remove first slash, keep the rest + # Don't process as command, jump to message processing + + # Check for unknown commands (starts with / but not a valid command) + elif user_input.startswith("/") and user_input.lower() not in ["exit", "quit", "bye"]: + # Extract command (first word after /) + command_word = user_input.split()[0].lower() if user_input.split() else user_input.lower() + + # Check if it's a valid command or partial match + if not any(command_word.startswith(cmd) for cmd in VALID_COMMANDS): + console.print(f"[bold red]Unknown command: {command_word}[/]") + console.print("[bold yellow]Type /help to see all available commands.[/]") + app_logger.warning(f"Unknown command attempted: {command_word}") + continue + if user_input.lower() in ["exit", "quit", "bye"]: total_tokens = total_input_tokens + total_output_tokens app_logger.info(f"Session ended. Total messages: {message_count}, Total tokens: {total_tokens}, Total cost: ${total_cost:.4f}") # Log session summary @@ -962,6 +987,7 @@ def chat(): table.add_row("Name", model_to_show["name"]) table.add_row("Description", model_to_show.get("description", "N/A")) table.add_row("Context Length", str(model_to_show.get("context_length", "N/A"))) + table.add_row("Online Support", "Yes" if supports_online_mode(model_to_show) else "No") table.add_row("Pricing - Prompt ($/M tokens)", pricing.get("prompt", "N/A")) table.add_row("Pricing - Completion ($/M tokens)", pricing.get("completion", "N/A")) table.add_row("Pricing - Request ($)", pricing.get("request", "N/A")) @@ -969,7 +995,6 @@ def chat(): table.add_row("Input Modalities", ", ".join(architecture.get("input_modalities", [])) or "None") table.add_row("Output Modalities", ", ".join(architecture.get("output_modalities", [])) or "None") table.add_row("Supported Parameters", supported_params) - table.add_row("Online Mode Support", "Yes" if supports_online_mode(model_to_show) else "No") table.add_row("Top Provider Context Length", str(top_provider.get("context_length", "N/A"))) table.add_row("Max Completion Tokens", str(top_provider.get("max_completion_tokens", "N/A"))) table.add_row("Moderated", "Yes" if top_provider.get("is_moderated", False) else "No") @@ -977,7 +1002,7 @@ def chat(): console.print(Panel(table, title=f"[bold green]Model Info: {model_to_show['name']}[/]", title_align="left")) continue - # Model selection with colored checkmarks (removed Web column) + # Model selection with Image and Online columns elif user_input.startswith("/model"): app_logger.info("User initiated model selection") args = user_input[7:].strip() @@ -989,11 +1014,12 @@ def chat(): console.print(f"[bold red]No models match '{search_term}'. Try '/model'.[/]") continue - # Create table with colored checkmarks (removed Web column) - table = Table("No.", "Name", "ID", "Image", show_header=True, header_style="bold magenta") + # Create table with Image and Online columns + table = Table("No.", "Name", "ID", "Image", "Online", show_header=True, header_style="bold magenta") for i, model in enumerate(filtered_models, 1): image_support = "[green]✓[/green]" if has_image_capability(model) else "[red]✗[/red]" - table.add_row(str(i), model["name"], model["id"], image_support) + online_support = "[green]✓[/green]" if supports_online_mode(model) else "[red]✗[/red]" + table.add_row(str(i), model["name"], model["id"], image_support, online_support) # Use pagination for the table title = f"[bold green]Available Models ({'All' if not search_term else f'Search: {search_term}'})[/]" @@ -1012,7 +1038,7 @@ def chat(): console.print("[dim yellow]Note: Online mode auto-disabled when changing models.[/]") console.print(f"[bold cyan]Selected: {selected_model['name']} ({selected_model['id']})[/]") if supports_online_mode(selected_model): - console.print("[dim green]This model supports online mode. Use '/online on' to enable web search.[/]") + console.print("[dim green]✓ This model supports online mode. Use '/online on' to enable web search.[/]") app_logger.info(f"Model selected: {selected_model['name']} ({selected_model['id']})") break console.print("[bold red]Invalid choice. Try again.[/]") @@ -1137,11 +1163,12 @@ def chat(): console.print(f"[bold red]No models match '{search_term}'. Try without search.[/]") continue - # Create table with colored checkmarks (removed Web column) - table = Table("No.", "Name", "ID", "Image", show_header=True, header_style="bold magenta") + # Create table with Image and Online columns + table = Table("No.", "Name", "ID", "Image", "Online", show_header=True, header_style="bold magenta") for i, model in enumerate(filtered_models, 1): image_support = "[green]✓[/green]" if has_image_capability(model) else "[red]✗[/red]" - table.add_row(str(i), model["name"], model["id"], image_support) + online_support = "[green]✓[/green]" if supports_online_mode(model) else "[red]✗[/red]" + table.add_row(str(i), model["name"], model["id"], image_support, online_support) # Use pagination for the table title = f"[bold green]Available Models for Default ({'All' if not search_term else f'Search: {search_term}'})[/]" @@ -1210,7 +1237,7 @@ def chat(): console.print("[bold red]Unable to fetch credits. Check your API key or network.[/]") continue - if user_input.lower() == "/clear": + if user_input.lower() == "/clear" or user_input.lower() == "/cl": clear_screen() DEFAULT_MODEL_ID = get_config('default_model') token_value = session_max_token if session_max_token != 0 else " Not set" @@ -1230,9 +1257,9 @@ def chat(): "" ) help_table.add_row( - "/clear", + "/clear or /cl", "Clear the terminal screen for a clean interface. You can also use the keycombo [bold]ctrl+l[/]", - "/clear" + "/clear\n/cl" ) help_table.add_row( "/help", @@ -1288,7 +1315,7 @@ def chat(): ) help_table.add_row( "/model [search]", - "Select or change the current model for the session. Supports searching by name or ID. Shows image capabilities.", + "Select or change the current model for the session. Shows image and online capabilities. Supports searching by name or ID.", "/model\n/model gpt" ) @@ -1320,7 +1347,7 @@ def chat(): ) help_table.add_row( "/config model [search]", - "Set default model that loads on startup. Doesn't change current session model. Shows image capabilities.", + "Set default model that loads on startup. Shows image and online capabilities. Doesn't change current session model.", "/config model gpt" ) help_table.add_row( @@ -1421,6 +1448,11 @@ def chat(): "Use /paste to send clipboard content (plain text/code) to AI.", "/paste\n/paste Explain this" ) + help_table.add_row( + "// escape", + "Start message with // to send a literal / character (e.g., //command sends '/command' as text, not a command)", + "//help sends '/help' as text" + ) # ===== EXIT ===== help_table.add_row( @@ -1438,7 +1470,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) • Visit: https://iurl.no/oai", + 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_align="center", border_style="cyan" ))