Skip to content

Commit

Permalink
[Improvement] More flexible and user-friendly command + clarification…
Browse files Browse the repository at this point in the history
… visability
  • Loading branch information
MladenSU committed Jan 2, 2025
1 parent be0b695 commit b3bd283
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 49 deletions.
6 changes: 3 additions & 3 deletions console_gpt/custom_stdout.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

# Define the specific types for 'ptype'
PrintType = Literal["ok", "warn", "info", "error", "sigint", "exit", "changelog"]
HeaderColor = Literal["green", "yellow", "blue", "red", "white", "cyan"]


def markdown_print(data: str, header: Optional[str] = None, end: Optional[str] = ""):
def markdown_print(data: str, header: Optional[str] = None, end: Optional[str] = "", header_color: Optional[HeaderColor] = "blue") -> None:
console = Console()

# Print the header if it exists
if header:
header_text = Text(f"╰─❯ {header}:", style="blue underline bold")
header_text = Text(f"╰─❯ {header}:", style=f"{header_color} underline bold")
console.print(header_text, end=end)

# Create a Markdown object
Expand Down
3 changes: 2 additions & 1 deletion console_gpt/menus/command_handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from console_gpt.custom_stdout import custom_print
from console_gpt.custom_stdout import custom_print, markdown_print
from console_gpt.general_utils import help_message
from console_gpt.menus.settings_menu import settings_menu
from console_gpt.menus.tools_menu import tools_menu
Expand Down Expand Up @@ -58,6 +58,7 @@ def command_handler(model_title, model_name, user_input, conversation, cached) -
else:
user_input = multiline_data
else:
markdown_print(clarification, header="Prompt clarifications", header_color="yellow", end="\n")
if cached is True:
user_input = clarification, multiline_data
else:
Expand Down
233 changes: 188 additions & 45 deletions console_gpt/prompts/multiline_prompt.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,198 @@
import re
from typing import Dict, Optional

from textual.app import App, ComposeResult
from textual.containers import Vertical, Horizontal
from textual.reactive import reactive
from textual.widgets import Button, Static, TextArea
from typing import Optional
from console_gpt.catch_errors import eof_wrapper
from console_gpt.constants import style
from console_gpt.custom_stdin import custom_input
from console_gpt.custom_stdout import custom_print
from rich.console import Console

# Compile the regex once at the module level
stop_regex = re.compile(r"(\n|\s)+$")
console = Console()

class MultilinePromptApp(App):
"""Textual app for handling multiline prompts."""

def _validate_description(val: str) -> str | bool:
"""
Sub-function to _add_custom_role() which validates
the user input and does not allow empty values
:param val: The STDIN from the user
:return: Either error string or bool to confirm that
the user input is valid
CSS = """
Screen {
align: center middle;
}
#dialog {
padding: 1 2;
border: heavy $background 80%;
height: 100%;
width: 80%;
layout: vertical;
}
#additional_input_area {
width: 100%;
height: 3;
min-height: 1;
max-height: 9;
}
#multiline_input {
width: 100%;
height: 10;
max-height: 30;
}
#output_label {
background: $error 20%;
color: $error;
margin: 1;
padding: 1;
border: round $error;
text-align: center;
text-style: bold;
height: auto;
display: none;
}
#output_label.show {
display: block;
}
#info_label {
background: $success 20%;
color: $success;
margin: 1;
padding: 1;
border: round $success;
text-align: center;
text-style: bold;
height: auto;
display: none;
}
#info_label.show {
display: block;
}
TextArea {
width: 100%;
}
TextArea:focus {
border: tall $accent !important;
}
#additional_placeholder, #multiline_placeholder {
color: $text;
padding-left: 1;
text-style: bold;
}
.buttons {
width: 100%;
height: auto;
align: center bottom;
}
"""
if not val or stop_regex.match(val):
return "Empty input not allowed!"
return True

multiline_data: str = reactive("")
additional_data: Optional[str] = reactive(None)

def show_error(self, message: str) -> None:
"""Display an error message."""
output_label = self.query_one("#output_label")
output_label.update(f"{message}")
output_label.add_class("show")

def clear_error(self) -> None:
"""Clear the error message."""
output_label = self.query_one("#output_label")
output_label.update("")
output_label.remove_class("show")

def clear_info(self) -> None:
"""Clear the info message."""
info_label = self.query_one("#info_label")
info_label.remove_class("show")

def compose(self) -> ComposeResult:
"""Create child widgets for the app."""
yield Vertical(
Static("💡 Use <TAB> to navigate and CTRL+Q or 'Exit' to quit.", id="info_label"),
Static("", id="output_label"),
Static("Instructions or actions to perform:", id="additional_placeholder"),
TextArea(id="additional_input_area"),

Static("Enter multiline text here:", id="multiline_placeholder"),
TextArea(id="multiline_input"),

Horizontal(
Button("Submit", id="submit_button", variant="primary"),
Button("Exit", id="exit_button", variant="default"),
classes="buttons"
),

id="dialog"
)

def on_mount(self) -> None:
# Enable both text areas on mount
self.query_one("#additional_input_area").disabled = False
self.query_one("#multiline_input").disabled = False
self.query_one("#additional_input_area").focus()
self.query_one("#info_label").add_class("show")

def on_text_area_changed(self, event: TextArea.Changed) -> None:
"""Handle text changes in TextArea widgets."""
self.clear_error()
self.clear_info()

if event.text_area.id == "multiline_input":
self.multiline_data = event.text_area.text
elif event.text_area.id == "additional_input_area":
self.additional_data = event.text_area.text

def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button presses."""
if event.button.id == "submit_button":
self.submit_data()
elif event.button.id == "exit_button":
self.multiline_data = ""
self.additional_data = None
self.exit((self.additional_data, self.multiline_data))

def clean_up_input(self, input_text: str) -> str:
"""Clean up the input text by removing leading and trailing whitespaces."""
return input_text.strip()

def submit_data(self):
"""Submit the data after validation."""
multiline_input = self.query_one("#multiline_input")
additional_input = self.query_one("#additional_input_area")

cleaned_multiline_data = self.clean_up_input(multiline_input.text)
cleaned_additional_data = self.clean_up_input(additional_input.text) if additional_input.text else None

if not cleaned_multiline_data:
self.show_error("Main text field cannot be empty!")
multiline_input.focus()
return

if cleaned_additional_data == "":
self.show_error("Additional info cannot be just spaces or new lines!")
additional_input.focus()
return

self.exit((cleaned_additional_data, cleaned_multiline_data))



@eof_wrapper
def multiline_prompt() -> Optional[str]:
"""
Multiline prompt which allows writing on multiple lines without
def multiline_prompt() -> tuple[Optional[str], str]:
"""Multiline prompt which allows writing on multiple lines without
"Enter" (Return) interrupting your input.
:return: The content or None (If cancelled)
:return: Tuple containing additional data (Optional[str]) and multiline data (str)
"""
multiline_data = custom_input(
is_single_line=False,
auto_exit=False,
message="Multiline input",
style=style,
qmark="❯",
validate=_validate_description,
multiline=True,
)
if not multiline_data:
custom_print("info", "Cancelled. Continuing normally!")
return None, None

additional_data = custom_input(
auto_exit=False,
message="Additional clarifications? (Press 'ENTER' to cancel):",
style=style,
qmark="❯",
)
if additional_data:
return additional_data, f"This is a multiline input:\n{multiline_data}"
else:
return None, f"This is a multiline input:\n{multiline_data}"

app = MultilinePromptApp()
try:
additional_data, multiline_data = app.run()
except TypeError:
return None, ""

return additional_data, multiline_data

0 comments on commit b3bd283

Please sign in to comment.