Skip to content

Conversation

@Danipiza
Copy link
Contributor

@Danipiza Danipiza commented Nov 12, 2025

TUI Migration (Textual)

New Textualize terminal

Website

Added

New console store more than the last plan.

New commands

  • /edit_tools: Opens a ModalScreen with all the loaded tools and a checkbox for each. The user can activate or disable all the tools.
  • /clear_history: Clear the right panel history. Also clear the list of plans.

Update command

  • /rerun <int>: Added a new optional parameter to execute i-th stored plan.

Functionalities (Keys)

  • F2: Show this help message
  • F3: Copy selected area
  • Ctrl+Q: Exit the console
  • Ctrl+L: Clears the console screen
  • Ctrl+U: Clears the entire command line input
  • Ctrl+K: Clears from the cursor to then end of the line
  • Ctrl+W: Delete the word before the cursor
  • Ctrl+<left/right>: Move cursor backward/forward by one word
  • Ctrl+R: Reverse search through command history (try typing part of a previous command).

Terminal example

Screenshot from 2025-12-23 16-01-04

Danipiza and others added 3 commits November 12, 2025 14:43
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
@Danipiza Danipiza force-pushed the feature/textual-terminal branch from 14280db to 50c80fc Compare November 12, 2025 13:43
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
…vigation

Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
@Danipiza Danipiza force-pushed the feature/textual-terminal branch from f347fae to 0cc685c Compare November 24, 2025 09:48
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
@Danipiza Danipiza changed the title [#23892] Textual terminal [#23892] Textual terminal & default tools Nov 24, 2025
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
… terminalstyle and applied ROS2 inspection commands revision

Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Copy link
Contributor

@cferreiragonz cferreiragonz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can discuss some of the questions. Mainly:

  • I would use a centralized logger system, like the one defined as the VulcanAILogger. This class is responsible of all logs, it defines all colors used and automatically applies the categorization (errors or logs). The call to the logger is identical from every class, there is no need to manually specify in each log the tag that needs to be applied. Additionally it moves some of the logic to a separate file. I believe this is a cleaner approach, where colors code and logic are not dispersed around the code, which makes it harder to edit and modify.
  • I cannot copy from the Textualize terminal. I don't know if are overriding the copy key-binding or if this is a Textualize error / miss-configuration.

Comment on lines 45 to 46
self.real_stream.write(data)
self.real_stream.flush()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really writing in the real stream? I do not see any logs in my terminal when Textualize is closed. In any case, do we want to write in both streams? How does this work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is for python print(), but yes, it is not necessary to print both times, I did not delete it when I was testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed optional print in 5090176


# INITIALIZE CODE
if user_input == "":
self.init_manager()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this during initialization of the manager instead? Why is it done in the second thread?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is added in the second thread so the Textual terminal can print the information while executing the method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clearer, I would make @on_mount call a separate method instead of reusing bootstrap for both initialization and handling of user input calls. Or at least define two different methods with the logic of the @worker method and use the bootstrap as entrypoint for executing functions in a new thread. In the end, initialization is only done once, so I would be explicit about this.

@on_mount could call a method like this:

def init_textualize(self):
    loop = asyncio.get_running_loop()
    await loop.run_in_executor(None, lambda: init_console())

    if user_input == "":
        self.is_ready = True
        self._log("VulcanAI Interactive Console", log_color=2)
        self._log("Type [bold]'exit'[/bold] to quit.", log_color=2)
        self._log("")

    # Activate the terminal input
    self.set_input_enabled(True)

def init_console(self):
    # Logic inside `@worker` when user_input == ""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it will work, but we could also rely on @work decorator to specify that a method needs to run in background, instead of mixing @work decorators with await loop.run_in_executor()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separated in bootstrap() and queriestrap() functions in 5090176


# region Utilities

@work # runs in a worker. waiting won't freeze the UI
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this method to run in an independent worker? Can't we just call it from the main method, this is meant to be blocking until the user presses Submit or Cancel, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it is the same with the other modal screen, it is not necessary to have this functions async, but I could make it works synchronously.

raise Exception(f"Failed to run '{' '.join(args)}': {e.output}")


def suggest_string(console, tool_name, string_name, input_string, real_string_list):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something specific of the console or the tools?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah is specific for the Textual terminal, it displays the "topics", "actions", ... that are available, if the user prints incorrectly in the query.

Comment on lines 139 to 140
# Print in textual terminal:
# [MANAGER] Using OpenAI API with model: <model_name>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is a little bit redundant to write two comments about what we are going to print

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5090176

…cution information

Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
Signed-off-by: danipiza <dpizarrogallego@gmail.com>
@Danipiza
Copy link
Contributor Author

Currently we have 3 threads:

1st Thread. The main/UI thread for the Textual app.

2nd Thread. Thread for the concurrent blocking jobs

async def bootstrap(self) -> None:

async def queriestrap(self) -> None:

3rd Thread. Thread worker. @work(thread=True)

The work runs off the UI thread in a background thread, to avoid possible errors when a VulcanAI tool use call_from_thread.
e.g.:

  • move_turtle tool contains a 'call_from_thread'
  • ros2_topic tool does not contains a 'call_from_thread'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants