Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Prototype AI Searcher #7146

Merged
merged 37 commits into from
Jul 7, 2023
Merged

New Prototype AI Searcher #7146

merged 37 commits into from
Jul 7, 2023

Conversation

MichaelMauderer
Copy link
Contributor

@MichaelMauderer MichaelMauderer commented Jun 28, 2023

Pull Request Description

Adds a new bare-bones AI searcher that can be triggered with cmd+tab. It will interpret the searcher input as a prompt to an AI model and replace the created node with the suggestion that was computed.

Peek.2023-06-28.15-51.mp4

Implements the first step of #7099.

Important Notes

Contains some refactoring that allows us to have multiple controllers side by side. So QA testing should make sure that the Component Browser Searcher is still working as before.

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
  • Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.
  • All code follows the
    Scala,
    Java,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.
  • All code has been tested:
    • Unit tests have been written where possible.
    • If GUI codebase was changed, the GUI was tested when built using ./run ide build.

@MichaelMauderer MichaelMauderer marked this pull request as ready for review June 28, 2023 14:52
@MichaelMauderer MichaelMauderer marked this pull request as draft June 28, 2023 15:03
@MichaelMauderer MichaelMauderer marked this pull request as ready for review June 28, 2023 22:59
@MichaelMauderer MichaelMauderer mentioned this pull request Jun 28, 2023
10 tasks
Copy link
Contributor

@farmaazon farmaazon left a comment

Choose a reason for hiding this comment

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

I think the FRP API of Project View is a bit confusing ATM.

Comment on lines 85 to 88
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Fail)]
#[fail(display = "AST node is missing ID.")]
pub struct UnsupportedPattern;
Copy link
Contributor

Choose a reason for hiding this comment

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

The error message is copy-pasted

Comment on lines 120 to 131
/// Description of the nodes variable name.
#[derive(Clone, Debug)]
pub enum VariableName {
/// The node has a variable name.
Some(String),
/// The node has no variable name.
None,
/// The node has a variable name, that is not currently supported. For example, it is a
/// pattern subpart. See notes on [Node::variable_name].
NotSupported,
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Is it needed? It looks like was meant to be used as variable_name return type, but then replaced with Result<Option<&str>, UnsupportedPattern>.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right, this is no longer used. Removed.

Comment on lines 1317 to 1231

use crate::controller::ide::plain::ProjectOperationsNotSupported;
use crate::executor::test_utils::TestWithLocalPoolExecutor;
use crate::test::mock::data::project_qualified_name;
use crate::test::mock::data::MAIN_FINISH;
use crate::test::mock::data::MODULE_NAME;
use std::assert_matches::assert_matches;

use crate::controller::graph::RequiredImport;
use engine_protocol::language_server::types::test::value_update_with_type;
use engine_protocol::language_server::SuggestionId;
use enso_suggestion_database::entry::Argument;
use enso_suggestion_database::mock_suggestion_database;
use enso_suggestion_database::SuggestionDatabase;
use json_rpc::expect_call;
use parser::Parser;
use std::assert_matches::assert_matches;

use crate::controller::graph::RequiredImport;
use crate::controller::ide::plain::ProjectOperationsNotSupported;
use crate::executor::test_utils::TestWithLocalPoolExecutor;
use crate::searcher::apply_this_argument;
use crate::test::mock::data::project_qualified_name;
use crate::test::mock::data::MAIN_FINISH;
use crate::test::mock::data::MODULE_NAME;

use super::*;

Copy link
Contributor

Choose a reason for hiding this comment

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

These imports look reversed.

self.ide_controller.clone_ref(),
self.controller.clone_ref(),
self.graph_controller.clone_ref(),
&self.graph,
self.view.clone_ref(),
self.view.clone(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Why we clone view instead of clone-refing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Spurious change. Reverted.

Comment on lines 350 to 353
// fn view(app: &Application) -> Option<Instance>
// where Self: Sized {
// Some(component_browser::View::new(app).display_object().clone_ref())
// }
Copy link
Contributor

Choose a reason for hiding this comment

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

Commented code.

@@ -3022,7 +3022,6 @@ fn init_remaining_graph_editor_frp(
);
out.node_added <+ new_node;
node_to_edit_after_adding <- new_node.filter_map(|&(id,_,do_edit)| do_edit.as_some(id));
eval node_to_edit_after_adding((id) model.with_node(*id, |node| node.show_preview()));
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the AI Searcher does not want to show a preview. So, this has been moved to the init_input_node where we enable this conditionally based on the searcher.

Comment on lines +153 to +155
// TODO[MM]: this should not contain the group entry id as that is component browser
// specific. It should be refactored to be an implementation detail of the component
// browser.
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, in the new design we plan so far to have just a single view component for both searching entries and asking AI, so there will be only one view.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This comment refers more to the fact that each new searcher (i.e., Component Browser Searcher and the planned combined AI searcher) might have its own separate view with different APIs. Each, which will need to be handled in a generic way on the project level, and interactions between the View and Presenter should be an internal implementation detail.

@@ -472,7 +504,7 @@ impl View {
eval position ((pos) model.searcher.set_xy(*pos));

// Showing searcher.
searcher.show <+ frp.searcher.is_some().on_true().constant(());
searcher.show <+ frp.searcher.unwrap().map(|params| matches!(params.searcher_type, SearcherType::ComponentBrowser)).on_true();
Copy link
Contributor

Choose a reason for hiding this comment

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

Over 100 characters.

Comment on lines 558 to 565


// === Handling Inputs to the AI Searcher and Committing Edit ===

ai_searcher_active <- frp.searcher_type.map(|t| *t == SearcherType::AiCompletion);
committed_in_ai_searcher <- frp.accept_searcher_input.gate(&ai_searcher_active);
committed_in_ai_searcher <- committed_in_ai_searcher.map2(&last_searcher, |_, &s| (s.input, None));
frp.source.editing_committed <+ committed_in_ai_searcher;
Copy link
Contributor

Choose a reason for hiding this comment

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

It's strange thataccept_searcher_input FRP input seemingly is used only to accept AI input. The more strange is, that we accept AI input with Tab.

Is the accept_searcher_input relevant?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, it does work without this code. The fact that it uses tab was an oversight, but otherwise this was meant to compensate for the fact that there is no view associated with the AI searcher. But it appears that the Component Browser searcher is receiving the enter event anyway and starting the chain of events that is committing the current open searchers content.

@@ -1,6 +1,6 @@
# Options intended to be common for all developers.

wasm-size-limit: 15.97 MiB
wasm-size-limit: 16.05 MiB
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit odd. Why such a big change from 600-lines of PR?

I don't know, however, what could we do with it. Certainly, it's not like we add a big dependency or make an extensive usage of macros.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure. My initial guess would be that some code optimizations are no longer being applied because the searcher is no longer a fixed struct within the project, but a trait. Do we have a good way to debug Wasm size changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm trying to have a look with twiggy.

Copy link
Contributor

Choose a reason for hiding this comment

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

The _searcher suffix in module's name is redundant. Similarly, in ai_searcher.

Comment on lines 136 to 137
/// Accept the current input of the searcher.
accept_searcher_input(),
Copy link
Contributor

Choose a reason for hiding this comment

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

We still have this input, just not assigned to any shortcut.

And my original comment was mostly about name, as the name did not say about accepting AI searcher only. But if the shortcut from the original searcher works, we can go with it (just remove this input then).

@@ -127,6 +132,22 @@ impl Node {
pub fn has_position(&self) -> bool {
self.metadata.as_ref().map_or(false, |m| m.position.is_some())
}

/// Get the nodes variable name, if it has one.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Get the nodes variable name, if it has one.
/// Get the node's variable name, if it has one.

let target_node = mode.as_ref().map(|mode| mode.node_id());
if let Ok(target_node) = target_node {
graph.allow_expression_auto_updates(target_node, false);
let mode = mode?;
Copy link
Contributor

Choose a reason for hiding this comment

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

This will always succeed. mode does not need to be a Result.

Comment on lines 55 to 57
if let Some(target_node_view) = graph_presenter.view_id_of_ast_node(target_node) {
// We only want to show the preview of the node if it is a component browser searcher.
if matches!(parameters.searcher_type, SearcherType::ComponentBrowser) {
Copy link
Contributor

Choose a reason for hiding this comment

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

i think this would be clearer with this if around the whole if let.

Comment on lines 135 to 136
/// Create a new AST that combines a `this` argument with the given AS. For example, to add a
/// method call `sort` a this argument `table`. That would result in an AST that represents
Copy link
Contributor

Choose a reason for hiding this comment

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

Typos in first two sentences here.

/// Create a new AST that combines a `this` argument with the given AS. For example, to add a
/// method call `sort` a this argument `table`. That would result in an AST that represents
/// `table.sort`.
pub fn apply_this_argument(this_var: &str, ast: &Ast) -> Ast {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see that this is searcher-specific; should it go in the ast module?

}
}

/// Initialise the expression in case there is a source node for the new node. This allows us to to
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Initialise the expression in case there is a source node for the new node. This allows us to to
/// Initialise the expression in case there is a source node for the new node. This allows us to

graph_controller,
graph_presenter: graph_presenter.clone_ref(),
this_arg,
input_expression: RefCell::new("".to_string()),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
input_expression: RefCell::new("".to_string()),
input_expression: default(),


/// The Searcher presenter, synchronizing state between searcher view and searcher controller.
///
/// The presenter should be created for one instantiated searcher controller (when node starts to
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// The presenter should be created for one instantiated searcher controller (when node starts to
/// The presenter should be created for one instantiated searcher controller (when node starts

Copy link
Member

@wdanilo wdanilo left a comment

Choose a reason for hiding this comment

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

What happens if the user did not set OpenAI key? How the error is reported?

@JanBeelte
Copy link

What happens if the user did not set OpenAI key? How the error is reported?

currently we get a pretty misleading method_not_found RPC error from the language server. Given we want to get rid of the endpoint completely we did not spend time on proper error handling.

@farmaazon
Copy link
Contributor

QA Report 1: 🔴

Playing a little without API key, found two issues:

  • Having AI prompt, when I press escape, the node remains, with the prompt I wrote so far (may be empty if typed nothing) and Nothing in the code.
  • Right after opening AI prompt and canceling it, dragging-out edge creates nodes without Component Browser and in a strange state.

Both are blocking if we don't want to use any feature flag for disabling cmd+tab shortcut.

@MichaelMauderer
Copy link
Contributor Author

MichaelMauderer commented Jul 5, 2023

Both of these points should now be addressed. @farmaazon

@farmaazon
Copy link
Contributor

QA Report 2: 🔴

I still have a similar issue to the second point from last report: but this time accepting or cancelling an AI prompt makes future dragged-out edges become AI prompts until CB is opened some other way.

Also, I cannot accept AI prompt in any other way than clicking on the background (tried enter, tab, ctrl+tab - none of those worked).

At the end, I'd like to share you a nice response for my "What question should I ask?"
image

@MichaelMauderer
Copy link
Contributor Author

Thanks for the thorough testing. @farmaazon

It appears that the accepting AI prompts with "enter" worked fine only as long as the CB was not opened. Then its view would interfere with the event processing. This is now fixed.

Newly created nodes from dragging will now also use the “default” searcher (CB searcher at the moment).

@farmaazon
Copy link
Contributor

farmaazon commented Jul 7, 2023

QA report 3: 🍏

No more issues found!

@MichaelMauderer MichaelMauderer added the CI: Ready to merge This PR is eligible for automatic merge label Jul 7, 2023
@mergify mergify bot merged commit 66201dc into develop Jul 7, 2023
@mergify mergify bot deleted the wip/MichaelMauderer/ai_searcher branch July 7, 2023 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: Ready to merge This PR is eligible for automatic merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants