feat(studio): plain-language ask bar + collapsible filter + AI search (v0.32.0)

Three changes against the user feedback "der text ist zu
zerhackt, die filter-leiste nimmt zu viel sicht weg, llm-suche
wenn das geht":

1. Today-Hero copy rewritten in flowing prose. The fallback
   story drops the code-fence templates ("`mcp.<server>.<tool>`")
   and reads as a single arc per language. Same trust model,
   gentler register.

2. Filter row collapses behind a single `_FilterButton` with a
   "Filter · N active" count badge. Status / Source / Installed
   chips now live inside `_FilterDialog` — invisible until the
   operator wants them, recovers a full row of viewport. The
   category strip stays inline because category is the most
   common cut and benefits from being one click away.

3. New `_AskBar` replaces the old single-line `_SearchField`:

   - Multi-line input (1-4 lines auto-grow) so questions don't
     overflow horizontally.
   - Cmd+Enter / Ctrl+Enter submits; bare Enter inserts a
     newline so the operator can write multi-line questions
     naturally.
   - Heuristic question detector (ends in `?` or ≥4 words)
     routes the submit to the System-AI's askAi RPC instead of
     the substring search. Live keystrokes still keyword-search
     for short queries; question-shaped input holds the grid
     steady until submit so the visible result set doesn't
     wipe with every space.
   - The LLM is asked for a strict-JSON ranking of up to 5
     modules with a one-phrase reason each. Unknown module
     names are dropped silently — operators must not see
     hallucinated entries.
   - The new `_AiAnswerCard` renders the LLM's answer plus the
     ranked match list above the grid; the grid filters to
     just those names so the answer and the visible cards
     stay coherent. Clearing the question collapses everything
     back.
   - Falls back to plain keyword search when System AI is off
     (with an explanatory hint instead of the AI hint).

The AI path uses the System-AI the operator already configured
in Settings; same privacy mode, same audit-log trail. Nothing
new leaves the hub.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-08 13:21:40 +02:00
parent b0f09260e1
commit c7869870c4
8 changed files with 1010 additions and 145 deletions

View file

@ -620,6 +620,108 @@ abstract class AppLocalizations {
/// **'Search modules — name, tagline, tag, capability…'**
String get storeSearchHint;
/// No description provided for @storeAskHint.
///
/// In en, this message translates to:
/// **'What do you need? Try \"a module that reads tabular data\" or just type a keyword.'**
String get storeAskHint;
/// No description provided for @storeAskSubmit.
///
/// In en, this message translates to:
/// **'Search'**
String get storeAskSubmit;
/// No description provided for @storeAskAi.
///
/// In en, this message translates to:
/// **'Ask the AI'**
String get storeAskAi;
/// No description provided for @storeAskClear.
///
/// In en, this message translates to:
/// **'Clear question'**
String get storeAskClear;
/// No description provided for @storeAskAiThinking.
///
/// In en, this message translates to:
/// **'Looking through modules…'**
String get storeAskAiThinking;
/// No description provided for @storeAskAiUnavailable.
///
/// In en, this message translates to:
/// **'Configure a System AI in Settings to ask questions in plain language.'**
String get storeAskAiUnavailable;
/// No description provided for @storeAskNoMatches.
///
/// In en, this message translates to:
/// **'No matches yet — refine the question or browse the grid below.'**
String get storeAskNoMatches;
/// No description provided for @storeAskAnswerLabel.
///
/// In en, this message translates to:
/// **'AI suggestion'**
String get storeAskAnswerLabel;
/// No description provided for @storeAskAnswerWhy.
///
/// In en, this message translates to:
/// **'Why this matches'**
String get storeAskAnswerWhy;
/// No description provided for @storeAskMatchPill.
///
/// In en, this message translates to:
/// **'match'**
String get storeAskMatchPill;
/// No description provided for @storeFilterLabel.
///
/// In en, this message translates to:
/// **'Filter'**
String get storeFilterLabel;
/// No description provided for @storeFilterActiveCount.
///
/// In en, this message translates to:
/// **'{n} active'**
String storeFilterActiveCount(int n);
/// No description provided for @storeFilterReset.
///
/// In en, this message translates to:
/// **'Reset filters'**
String get storeFilterReset;
/// No description provided for @storeFilterApply.
///
/// In en, this message translates to:
/// **'Done'**
String get storeFilterApply;
/// No description provided for @storeFilterStatusGroup.
///
/// In en, this message translates to:
/// **'Status'**
String get storeFilterStatusGroup;
/// No description provided for @storeFilterSourceGroup.
///
/// In en, this message translates to:
/// **'Source'**
String get storeFilterSourceGroup;
/// No description provided for @storeFilterInstalledGroup.
///
/// In en, this message translates to:
/// **'Show only installed'**
String get storeFilterInstalledGroup;
/// No description provided for @storeFeatured.
///
/// In en, this message translates to: