Add multi-line chat and help menu (#152)

# PR Description
Given that `arcade chat` is the entry point for all users, this PR makes
the developer's first experience with Arcade a better experience.

1. Add multi-line & pasting support to `arcade chat`. To start a
multi-line chat message, start your message with `"""`. To end the
multi-line chat message, end your line with `"""`.

![image](https://github.com/user-attachments/assets/f190f487-d8d6-4c1d-ab73-29e811528d16)

2. Added chat commands
  - `/?` displays all available chat commands that are available
- `/show` shows all of the tools available. This uses `arcade show`.
Currently no option to specify the toolkit, or tool.
  - `/clear` clears the chat conversation history of the current session
  - `/exit` exits the chat. (ctrl+c still works of course!)
  

![image](https://github.com/user-attachments/assets/265b9977-5621-441a-9b80-5f33f5be5681)
This commit is contained in:
Eric Gustin 2024-11-13 10:32:20 -08:00 committed by GitHub
parent c02aee3f14
commit 026d518c6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 105 additions and 5 deletions

View file

@ -31,8 +31,10 @@ from arcade.cli.utils import (
delete_deprecated_config_file,
get_eval_files,
get_tools_from_engine,
get_user_input,
handle_chat_interaction,
handle_tool_authorization,
handle_user_command,
is_authorization_pending,
load_eval_suites,
log_engine_health,
@ -270,16 +272,19 @@ def chat(
log_engine_health(client)
while True:
console.print(f"\n[magenta][bold]User[/bold] ({user_email}):[/magenta] ")
console.print(
f"\n[magenta][bold]User[/bold] {user_email}: [/magenta]"
+ "([bold][default]/?[/default][/bold] for help)"
)
# Use input() instead of console.input() to leverage readline history
user_input = input()
while not user_input.strip():
user_input = input()
user_input = get_user_input()
# Add the input to history
readline.add_history(user_input)
if handle_user_command(user_input, history, host, port, force_tls, force_no_tls, show):
continue
history.append({"role": "user", "content": user_input})
try:

View file

@ -3,7 +3,9 @@ import ipaddress
import os
import webbrowser
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from textwrap import dedent
from typing import Any, Callable, Union, cast
from urllib.parse import urlencode, urlparse
@ -36,6 +38,13 @@ class OrderCommands(TyperGroup):
return list(self.commands) # get commands using self.commands
class ChatCommand(str, Enum):
HELP = "/?"
CLEAR = "/clear"
SHOW = "/show"
EXIT = "/exit"
def create_cli_catalog(
toolkit: str | None = None,
show_toolkits: bool = False,
@ -587,3 +596,89 @@ def delete_deprecated_config_file() -> None:
if os.path.exists(deprecated_config_file_path):
os.remove(deprecated_config_file_path)
def get_user_input() -> str:
"""
Get input from the user, handling multi-line input.
"""
MULTI_LINE_PROMPT = '"""'
user_input = input()
# Handle multi-line input
if user_input.startswith(MULTI_LINE_PROMPT):
user_input = user_input[len(MULTI_LINE_PROMPT) :]
while not user_input.endswith(MULTI_LINE_PROMPT):
line = input()
if not line:
print()
user_input += "\n" + line
user_input = user_input.rstrip(MULTI_LINE_PROMPT)
else:
# Handle single-line input
while not user_input.strip():
user_input = input()
return user_input.strip()
def display_chat_help() -> None:
"""Display the help message for arcade chat."""
help_message = dedent(f"""\
[default]
Available Commands:
{ChatCommand.SHOW:<10} Show all available tools
{ChatCommand.CLEAR:<10} Clear the chat history
{ChatCommand.HELP:<10} Help for a command
{ChatCommand.EXIT:<10} Exit the chat
Use \"\"\" to begin & end a multi-line message[/default]
""")
console.print(help_message)
def handle_user_command(
user_input: str,
history: list,
host: str,
port: int,
force_tls: bool,
force_no_tls: bool,
show: Callable,
) -> bool:
"""
Handle user commands during `arcade chat` and return True if a command was processed, otherwise False.
"""
if user_input == ChatCommand.HELP:
display_chat_help()
return True
elif user_input == ChatCommand.EXIT:
raise KeyboardInterrupt
elif user_input == ChatCommand.CLEAR:
console.print("Chat history cleared.", style="bold green")
history.clear()
return True
elif user_input == ChatCommand.SHOW:
show(
toolkit=None,
tool=None,
host=host,
port=port,
force_tls=force_tls,
force_no_tls=force_no_tls,
debug=False,
)
return True
return False
def parse_user_command(user_input: str) -> ChatCommand | None:
"""
Parse the user command and return the corresponding ChatCommand enum.
Returns None if the input is not a valid chat command.
"""
try:
return ChatCommand(user_input)
except ValueError:
return None