From 46aa683744ca9ffe3431f49a326d7edbbbb62cdf Mon Sep 17 00:00:00 2001 From: Martin Krasser Date: Thu, 23 Jan 2025 05:00:30 +0100 Subject: [PATCH] Support multiline inputs (#31) - Newlines created with Alt+Enter or Option+Enter - Supports pasting of multiline input messages - closes #7 --- docs/cli.md | 11 +++++++++++ docs/img/multiline.png | Bin 0 -> 9371 bytes freeact/cli/utils.py | 29 +++++++++++++++++++++++++---- mkdocs.yml | 2 +- poetry.lock | 29 +++++++++++++++++++++++++++-- pyproject.toml | 1 + 6 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 docs/img/multiline.png diff --git a/docs/cli.md b/docs/cli.md index f38373b..bda7889 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -7,3 +7,14 @@ python -m freeact.cli --help ``` or check [quickstart](quickstart.md) and [tutorials](tutorials/index.md) for usage examples. + +## Multiline input + +The `freeact` CLI supports entering messages that span multiple lines in two ways: + +1. **Copy-paste**: You can directly copy and paste multiline content into the CLI +2. **Manual entry**: Press `Alt+Enter` (Linux/Windows) or `Option+Enter` (macOS) to add a new line while typing + +To submit a multiline message, simply press `Enter`. + +![Multiline input](img/multiline.png) diff --git a/docs/img/multiline.png b/docs/img/multiline.png new file mode 100644 index 0000000000000000000000000000000000000000..e98ce85416572936c55828ebe9421a1d2f35c4b0 GIT binary patch literal 9371 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 ") if not user_message.strip(): empty_input = True diff --git a/mkdocs.yml b/mkdocs.yml index bc822ad..1767a2f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -88,7 +88,7 @@ nav: - Installation: installation.md - Building blocks: blocks.md - Supported models: models.md - - Command line interface: cli.md + - CLI: cli.md - Tutorials: - Overview: tutorials/index.md - Basic usage: tutorials/basics.md diff --git a/poetry.lock b/poetry.lock index c3af553..6dcef50 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aioconsole" @@ -2063,6 +2063,20 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "prompt-toolkit" +version = "3.0.50" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, + {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, +] + +[package.dependencies] +wcwidth = "*" + [[package]] name = "propcache" version = "0.2.1" @@ -2933,6 +2947,17 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + [[package]] name = "websockets" version = "14.1" @@ -3242,4 +3267,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.11,<3.14" -content-hash = "c4b6e5d8aef1278c5d368b532ed676f75adbfe65dbea6cd1bc6b25b527932dac" +content-hash = "c79051e2648aa472eec2fb06afe528c1656abdd2e467fabc90a37edef7373394" diff --git a/pyproject.toml b/pyproject.toml index 663aba0..1a24573 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ anthropic = "^0.43.0" google-genai = "^0.3.0" ipybox = "^0.3.1" openai = "^1.59" +prompt_toolkit = "^3.0" python = "^3.11,<3.14" python-dotenv = "^1.0" rich = "^13.9"