Skip to content

feat(explorer): ask the assistant for a PromQL query in place - #2311

Closed
Fiona2016 wants to merge 13 commits into
fix/ai-chat-page-paramsfrom
feat/ai-query-panel
Closed

Fiona2016 wants to merge 13 commits into
fix/ai-chat-page-paramsfrom
feat/ai-query-panel

Conversation

@Fiona2016

Copy link
Copy Markdown
Collaborator

Why

Writing a PromQL query means knowing which metric exists on this data source and what its labels are called. The assistant already knows. Reaching it meant opening the chat drawer — which covers the chart the query is written for — and then copying the answer back by hand.

What

A panel that sits under the query box instead of over the chart.

  • Shows what the assistant did to check its answer, read off the tool_group segments the backend sends.
  • Writes the verified expression into the field on arrival. The field runs it; Undo puts back whatever the user had written themselves.
  • Keeps one chat, so a follow-up ("group it by pod") carries the context.
  • Says plainly when nothing was delivered, and leaves the field alone.

It reads message/detail rather than the stream: that endpoint is the durable source of truth the chat itself falls back on, and a panel showing a handful of steps does not need token-level updates.

The old icon entry point is unchanged — the icon now toggles this panel.

Notes for review

  • Stacked on fix(ai-chat): send the page's own params where the chat reads them #2309. This targets fix/ai-chat-page-params; retarget to main once that merges.
  • Backend counterpart: flashcatcloud/fc-model-server#210 mounts the present_metric_query tool for the metric explorer page type. Without it the assistant has no way to deliver a query segment.
  • Not yet verified in a browser. 14 component/hook tests cover the panel, and tsc is at the repo baseline (52 pre-existing errors, none in touched files), but the local integrated dev environment's session expired and its login page hangs, so the rendered panel has not been exercised end to end.

Tests

npx jest src/components/AiQueryPanel src/components/AiChatNG — 106 passed.

Fiona2016 and others added 3 commits September 4, 2026 00:58
Writing a PromQL query means knowing which metric exists on this data source
and what its labels are called. The assistant already knows; reaching it meant
opening the chat drawer, which covers the chart the query is written for, and
then copying the answer back by hand.

The panel sits under the query box instead. It shows what the assistant did to
check its answer, writes the verified expression into the field on arrival —
the field runs it, and Undo puts back whatever the user had — and keeps one
chat so a follow-up ("group it by pod") carries the context.

It reads the message rather than the stream: message/detail is the durable
source of truth, and a panel showing a handful of steps does not need
token-level updates.

Nothing is claimed that was not delivered. When the assistant looked and found
nothing usable, the panel says so and leaves the field alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NifGUSCA6tmXym3k8B12Lu
A reset the panel never calls, a placeholder override no caller passes, and an
`any` on a ref antd already types.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NifGUSCA6tmXym3k8B12Lu
message/detail never returns a bare `tool` segment. GetData runs every
response through AggregateToolCallResponses, which folds each run of
consecutive tool calls into one `tool_group` carrying the counts. The panel
was matching on `tool`, so its "what the assistant did" list would have been
empty on every real answer — and the tests said otherwise only because they
mocked a wire shape the server does not produce.

Read the counts instead, and say them as counts: eight rows all reading "ran a
command" tell the reader less than one row reading "ran 8 commands".

Also drop panelKey from the panel's page params. It reaches nothing: the
backend's PageInfoParam has no such field, and the comparison that would use
it reads a chat context this panel never touches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NifGUSCA6tmXym3k8B12Lu
@coderabbitai

coderabbitai Bot commented Sep 4, 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: 62882f61-8589-4b32-a3af-6c7071a0db7f

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.

Fiona2016 and others added 9 commits September 4, 2026 02:16
A turn can end on a question rather than an answer — the data source the page
names is ambiguous, say. It finishes clean: no error, no query. The panel read
that as "nothing usable was delivered", which is true and useless; what the
user needs is the question.

Show it, and say to answer below — which is all it takes, verified against the
running assistant: a plain follow-up on the same chat answers the request and
the turn goes on to deliver its query.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NifGUSCA6tmXym3k8B12Lu
The slot the panel sits in is a flex column, so the panel was a shrinkable
item: it got 71px for 98px of content, and the rounded clip cut whatever did
not fit. Focusing the input on open then scrolled that clipped box down by the
missing 19px, which moved the cut to the top — the header came up sliced in
half, and the panel read as broken before it had done anything.

Keep the panel at its own height, and focus without scrolling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NifGUSCA6tmXym3k8B12Lu
It was shouting. The raw Go failure — dial address, chat id, seq id — was set
as body text at the same weight as the sentence above it, inside a full red
box, under a red "failed" tag, stretched across the whole monitor. Three
signals for one fact, and the loudest one was a stack address.

Now: one status tag, an accent rule instead of a second red outline, and the
raw failure kept but demoted to small muted monospace under its own label —
still the first thing an SRE wants, no longer the first thing anyone reads.
Prose wraps at a readable measure instead of running the full width. The body
collapses entirely before the first answer, so an idle panel is a quiet strip
rather than a hollow frame.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NifGUSCA6tmXym3k8B12Lu
A design review of the panel found the stack order was the order the machine
produced things — process, artifact, result — not the order someone reading it
mid-incident needs. The line that says "your query box was just overwritten,
here is the way back" sat last, inside a 240px scroll box, where it was the
most likely thing to be clipped.

Now the result card comes first and the write-back is that card's own footer,
so what happened and what it happened to cannot drift apart. The tallies stop
being a checklist of green ticks and become one footnote; while a run is in
flight they are the progress, under the assistant's own sentence about what it
is doing right now.

A run could not be stopped: for up to five minutes the input, send and
regenerate were all disabled. It can now, and the backend is told too, so a
retry does not race a run that is still working.

Failure was one bucket. "Nothing usable came back" and "the request never
reached the assistant" take different fixes, and telling a user the assistant
looked and found nothing — when nothing ran — sends them off rewriting a good
question. Each case now has its own title and one actionable line, the raw
error is behind a toggle, and a single dropped poll no longer kills a run.

Also: the panel asks the field what it holds rather than assuming, so a hand
edit is noticed instead of being reverted by a stale undo; refilling after an
undo is a local write rather than another model run; the header keeps the name
of the task across follow-ups; Esc is scoped to the panel instead of the whole
document; and the lavender fill is opacity-derived so it survives dark mode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NifGUSCA6tmXym3k8B12Lu
The panel decided what to say about the field by asking whether it was empty,
which meant that after an undo it told the user they had edited something. It
also armed an undo when the assistant returned exactly the query already in the
box — pressing it would have wiped the user's own text and re-run nothing. And
having undone, asking again for the same answer filled nothing, because the
write-once guard was never cleared.

All three were the same missing fact: what the field held when the run started.
The panel now remembers that, so it can tell "we wrote this" from "you got your
text back" from "you edited it", and can stay quiet when it changed nothing.

A follow-up that ends on a question, a miss or an error no longer takes the
previous answer off the screen with it — the field still holds that query, so
it keeps its card and its undo.

Dropped the scratch-file tally from the verification line: the assistant
writing its own notes is not evidence about the query, and reading "wrote 2
files" on a page that touches production invites a question nobody wants to be
asking mid-incident. Dropped "another way" too — it re-asked for an equivalent
phrasing, which is a thing the follow-up box does better and nobody asked for.

Closing or stopping now cancels the run rather than leaving the model working
on an answer no one will see, and the panel says so up front when no data
source is selected instead of spending a minute to ask.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NifGUSCA6tmXym3k8B12Lu
…hows

Sitting in the page flow costs height, and on this page the chart pays for it —
the panel is not pushed above the chart, it takes its space. A settled panel was
spending its largest block on a copy of the query that is legible in the box
forty pixels above.

So it shows the query only while the field does not hold it — after an undo, or
a hand edit, which is exactly when it has to be read to decide. The explanation
clamps to two lines with the whole of it on hover. Together that is a third of
the chart handed back, with no collapse toggle and no new state: a rule instead
of a mechanism.

Three seams three rounds of patching had left:

The status chip and the card ranked outcomes differently, so a run that changed
nothing was labelled 已填入 beside a card reading 未做改动. The chip is gone —
every label it carried was repeated one row below it.

A follow-up that ended on a question or an error still led with the previous
turn's green tick, and offered two re-run buttons thirty pixels apart aimed at
different things. Whatever this turn produced now leads; a card carried over
from an earlier turn follows it, dimmed, and keeps quiet about regenerating.

Retry re-ran the ask without refreshing what the field held, so undoing after
retrying could delete text the user typed while waiting. Retry goes through the
same door as everything else now — which is also the door that stops Enter from
sending with no data source selected.

The evidence line claimed verification and reported tool-call tallies, counting
files the user cannot see. It now says the one thing that is evidence and names
what it was run against. Timeouts and dropped polls cancel the backend run
instead of leaving it racing the retry. Undo became another write of the value
the field started with, so the page keeps no second copy of that fact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NifGUSCA6tmXym3k8B12Lu
The panel asks the page what the field holds so it can tell its own writes from
the user's. On this page the answer was stale: PromGraphCpt keeps the live value
and reports edits through onChange, which the page forwarded without ever
updating its own copy. So someone who typed a query by hand, then asked the
assistant, had that query recorded as empty — and undo handed them back an empty
box. The page now keeps its copy in step, and the prop says it must.

Two more ways a wrong value could reach the field. A follow-up carried the
previous answer forward under the same name as this turn's, so a turn that only
asked a clarifying question looked like a delivery: the value the user had just
undone was written back and re-run, underneath a block inviting them to answer.
Carried values now have their own name and are never mistaken for an answer.

And the undo target was re-snapshotted every turn, so after two refinements it
pointed at the assistant's own earlier answer rather than at the user's text.
It is snapshotted once per task now — before the assistant touched anything,
which is what the prop always claimed it was.

Failures showed whatever the assistant happened to be narrating when the request
died, in place of the one line that says what to do about it. That line is the
body now. And a turn that delivered nothing puts the question back in the box,
so Send is the retry — editable first, which is what the timeout copy already
advises. Three re-run buttons became none.

Polling and cancellation no longer raise a global toast: the hook tolerates
dropped polls on purpose, and cancels fire as the panel unmounts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NifGUSCA6tmXym3k8B12Lu
A query someone else wrote deserves a beat before it hits the data source. The
panel wrote into the field and the chart fetched immediately, because the field
and the query were the same piece of state: every change to the box was a
change to what the panes fetched.

They are separate now. The box holds what is being written; the panes fetch what
has been submitted. Everything that means "run this" still submits — the query
button, committing an edit with blur or enter, a query arriving from the URL or
from an alert event — but a value filled in on the user's behalf only fills, and
waits for them to press 查询.

The banner slot can now be given a function instead of a node, receiving the
field's live content and a way to fill it. That is how the assistant panel both
knows what the box holds and writes to it without running, and it replaces the
page-level copy of the query that existed only to answer the first question.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NifGUSCA6tmXym3k8B12Lu
The follow-up box used a fixed "group by pod" example regardless of what
was just delivered. The backend now sends a follow_up phrase with each
query segment; the panel reads it into run.suggestion and shows it as the
placeholder, falling back to a plain "keep going" when a turn brought none.
@Fiona2016

Copy link
Copy Markdown
Collaborator Author

Added in 4b000f4: the follow-up box's placeholder now comes from the assistant (param.follow_up on the query segment, see flashcatcloud/fc-model-server#210), falling back to a plain "keep going" when a turn brought none. New i18n key panel.follow_up_suggested in all 10 locales; 25/25 panel tests, tsc clean. Verified in the browser against a local backend build.

…s it ran

The panel showed "tried N times against <source>" under the spinner and
under the card. The count included the agent's own ls and --help calls,
and read as unrelated to the query. The backend already names each step
for a reader ("检索指标名"), so the running line now shows the current
step, the finished card says the answer was checked against the source,
and the explanation keeps only what was said after the query was
delivered rather than the narration of the search.

The follow-up hint the assistant wrote can now be accepted with Tab: it
fills the box and leaves sending to the user.
@Fiona2016

Copy link
Copy Markdown
Collaborator Author

Added in the latest commit: the running line shows the backend's name for the current step ("查询指标序列…") instead of a command tally; the finished card says "checked against " with no count; the explanation keeps only what the assistant said after delivering the query (its mid-run narration was leaking into the card); and Tab in the follow-up box accepts the assistant's suggested refinement without sending it. run.tried became run.checked; locale key tried/tried_one/tried_other replaced by verified_on. 26/26 panel tests, tsc clean, verified in the browser against the local backend build.

@Fiona2016

Copy link
Copy Markdown
Collaborator Author

Superseded: the panel drew its own transcript and filled the box through a callback. The replacement mounts the ordinary ChatPanel in a slim variant under the query box and writes the query through the ai-kit page-action channel. See the PR opened from feat/ai-query-dock.

@Fiona2016 Fiona2016 closed this Sep 8, 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