Skip to content

feat(explorer): let the assistant run a query on the metric explorer - #2305

Closed
Fiona2016 wants to merge 3 commits into
feat/log-extraction-ai-actionsfrom
feat/metric-explorer-ai-actions
Closed

Fiona2016 wants to merge 3 commits into
feat/log-extraction-ai-actionsfrom
feat/metric-explorer-ai-actions

Conversation

@Fiona2016

Copy link
Copy Markdown
Collaborator

Why

The assistant can already write PromQL into a reply, but the user then has to select it, copy it and paste it into the box. On a page whose whole job is run this expression, that is the last mile left undone.

The purpose-built path for this is dead, which is worth stating so nobody looks for it: QueryContentBlock renders a query card with an execute button and waits for content_type: "query", but fc-model-server emits no such content type (markdown, hint, reasoning, tool, tool_group, input_request only). Reviving it would mean splitting the model's output inside the streaming callback and adding a content type; registering a page action does the same job on rails that already carry traffic.

What changes

Registers one action, set_metric_query({ promql, time_range? }). It writes the expression into the same input the user types into; the panel's own effects re-run the query from there, exactly as they do after a keystroke. The time window moves only when the model asks for one, and only when both ends are given — a half-specified window would silently reframe the chart around a range nobody asked for.

Nothing is saved on this page, so there is nothing to undo: the previous expression is one undo, or one click of 历史记录, away.

Multi-panel correctness

Several panels can be open at once and they are the same component, so registering unconditionally would have a second panel quietly take over the first one's action name — ai-kit lets the later registration win.

panelKey was already read by Metric.tsx to close the chat when its panel is removed, but nothing ever wrote it into page_from.param, so that comparison could never hold. Sending it makes which panel does this write to answerable rather than incidental, and fixes the close-on-remove behaviour as a side effect.

The cursor anchor is resolved from the panel's own subtree rather than by position. Panels each pick their own data source and only some render a PromQL box at all — MySQL and PgSQL render none — so a page-wide index lands on a different panel's input, or off the end. The write was always correct; ringing the wrong box is what this avoids.

Testing

9 jest cases on the hook: registration gating, the disposer, live values read through the ref, trimming, the time-range rule, the anchor, and refusing a blank expression. tsc --noEmit matches the base branch exactly (51 pre-existing errors, none in these files). jest src/components/AiChatNG src/pages/explorer — 82 passing.

The registry is faked in the test rather than imported: ai-kit ships ESM this project's jest transform does not cover, and its schema validation is the package's own contract.

Base branch

Targets feat/log-extraction-ai-actions, not pre — ai-kit's fc-action support lives there and is not in main yet. That one lands first.

Pairs with fc-model-server#210, which teaches the model to verify the query before offering it. This PR is what puts the result on the page.

The assistant could already write PromQL into a reply, but the user had to
select it, copy it and paste it into the box. Register a page action so the
expression lands in the same input the user types into; the panel's own
effects re-run the query from there, exactly as they do after a keystroke.

The action sets the query and, only when asked for one, the time window.
Nothing is saved on this page, so there is nothing to undo — the previous
expression is one undo or one click of the history list away.

Several panels can be open at once, and they are the same component, so
registering unconditionally would have a second panel quietly take over the
first one's action name. panelKey was already read by Metric.tsx to close
the chat when its panel is removed, but nothing ever put it on page_from, so
that comparison could never hold. Send it, and let the panel register only
while it is the one the conversation was opened from — which makes "which
panel does this write to" answerable rather than incidental, and fixes the
close-on-remove behaviour as a side effect.

The registry is faked in the test rather than imported: ai-kit ships ESM that
this project's jest transform does not cover, and its schema validation is
the package's own contract. What is pinned here is what this hook decides.
Panels on this page each pick their own data source, and only some of those
render a PromQL box — MySQL and PgSQL render none at all. Indexing a
page-wide query by the panel's position therefore lands on a different
panel's input, or on nothing: with a MySQL panel above a Prometheus one the
lookup runs off the end, and with an IoTDB panel above two Prometheus ones
each of them points at the other's box.

The write itself was always correct — the setters are bound per instance —
but ringing the wrong input tells the user the AI touched a panel it did not.
Resolve the anchor from the panel's own subtree instead, through a wrapper
that generates no layout box, and drop the index the hook no longer needs.
@coderabbitai

coderabbitai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 41848503-9c52-4a49-a3e6-e2db69f3f14a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

ai-kit's validator enforces nested required, so a time_range with only one
end never reaches run() — the guard against it was dead. What does still get
through is an explicit null, which the validator skips like an absent value,
so that is the case worth keeping and the one the test now covers.

Also corrects a comment carried over from the log-extraction hook: the
validator does reject an empty string, so what run() is guarding is a query
made of whitespace, not an empty one.
@Fiona2016

Copy link
Copy Markdown
Collaborator Author

Superseded by #2309.

The approach here — registering an ai-kit page action on the metric explorer — cannot work in the commercial build. srm-fe/integrate.js replaces App.tsx with srm-fe's, so n9e/fe's AiChatNG is never mounted there and the chat that renders is srm-fe's, reading its own uiActionRuntime. An action registered in n9e/fe's registry is invisible to it.

Both frontends already render a query card and already wire its click back into the page. What was missing was on the backend — no content_type: "query" was ever emitted. That is flashcatcloud/fc-model-server#210, and it needs no frontend change at all.

The two genuine bugs found along the way are kept in #2309: the ENT path nested the page's params one level too deep, and panelKey was never sent.

@Fiona2016 Fiona2016 closed this Sep 3, 2026
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.

1 participant