fix(store): localised docs, debounced typing, explicit-submit always AI
Some checks failed
Security / Security check (push) Failing after 1s
Some checks failed
Security / Security check (push) Failing after 1s
Three Stefan-found bugs in one pass: 1. Documentation rendered in English even when Studio was set to German. _loadDocs now passes Localizations.localeOf(...) to fetchModuleDocs(); the hub tries README.de.md before README.md when locale='de'. 2. 'Keine Treffer' flickered on every keystroke. _onAskTyping used to call _runSearch synchronously per character — a short 2-3-letter substring matched nothing, so the empty- state showed for a frame, then the next character revealed results. Add a 250ms typing debounce (Timer + cancel on dispose) so the search only re-runs once typing pauses. 3. Submit button did substring search even when System-AI was enabled — the _looksLikeQuestion heuristic only routed 4+-word / question-mark queries through the LLM. The user reasonably expected an explicit submit (with the AI-icon showing on the button) to actually use AI. New behaviour: when system-AI is enabled, the submit button always calls _runAiQuery; live-typing keeps substring filtering. The heuristic helper was removed (now unused). Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
ec7417fef5
commit
98a6414eaa
2 changed files with 35 additions and 17 deletions
|
|
@ -350,8 +350,8 @@ class HubService {
|
||||||
/// back as `errorKind` strings (not exceptions) so callers can
|
/// back as `errorKind` strings (not exceptions) so callers can
|
||||||
/// render fallback UI without try/catch ceremony.
|
/// render fallback UI without try/catch ceremony.
|
||||||
Future<({String errorKind, String text, String sourceUrl})>
|
Future<({String errorKind, String text, String sourceUrl})>
|
||||||
fetchModuleDocs(String name) async {
|
fetchModuleDocs(String name, {String locale = ''}) async {
|
||||||
final r = await _client.fetchModuleDocs(name);
|
final r = await _client.fetchModuleDocs(name, locale: locale);
|
||||||
return (
|
return (
|
||||||
errorKind: r.errorKind,
|
errorKind: r.errorKind,
|
||||||
text: r.text,
|
text: r.text,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
// renders the full bilingual description, requires-list, and
|
// renders the full bilingual description, requires-list, and
|
||||||
// repository link.
|
// repository link.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -74,6 +75,11 @@ class _StorePageState extends State<StorePage> {
|
||||||
List<_AiMatch>? _aiMatches;
|
List<_AiMatch>? _aiMatches;
|
||||||
Set<String>? _aiMatchedNames;
|
Set<String>? _aiMatchedNames;
|
||||||
String? _aiError;
|
String? _aiError;
|
||||||
|
/// Debounce-Timer für Such-Filterung beim Tippen. Ohne
|
||||||
|
/// Debounce flackert "Keine Treffer" zwischen Buchstaben
|
||||||
|
/// (für 2-3 Buchstaben matched z.B. nichts, das nächste
|
||||||
|
/// dann doch). 250ms ist comfortable für Tippen.
|
||||||
|
Timer? _typingDebounce;
|
||||||
/// Whether the operator has a System-AI configured. Drives
|
/// Whether the operator has a System-AI configured. Drives
|
||||||
/// the Ask-bar hint copy and disables the AI path when off.
|
/// the Ask-bar hint copy and disables the AI path when off.
|
||||||
bool _systemAiEnabled = false;
|
bool _systemAiEnabled = false;
|
||||||
|
|
@ -105,6 +111,7 @@ class _StorePageState extends State<StorePage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_typingDebounce?.cancel();
|
||||||
_queryCtrl.dispose();
|
_queryCtrl.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -442,7 +449,6 @@ class _StorePageState extends State<StorePage> {
|
||||||
/// operator submits — otherwise typing "ein Modul, das …"
|
/// operator submits — otherwise typing "ein Modul, das …"
|
||||||
/// wipes the visible result set with every space.
|
/// wipes the visible result set with every space.
|
||||||
void _onAskTyping(String value) {
|
void _onAskTyping(String value) {
|
||||||
final trimmed = value.trim();
|
|
||||||
if (_aiAnswer != null || _aiMatches != null) {
|
if (_aiAnswer != null || _aiMatches != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_aiAnswer = null;
|
_aiAnswer = null;
|
||||||
|
|
@ -451,17 +457,35 @@ class _StorePageState extends State<StorePage> {
|
||||||
_aiError = null;
|
_aiError = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (_systemAiEnabled && _looksLikeQuestion(trimmed)) return;
|
// Tippen läuft IMMER nur durch die lokale Substring-Suche.
|
||||||
_runSearch();
|
// Die KI-Suche kostet einen LLM-Roundtrip und gehört hinter
|
||||||
|
// den expliziten Submit-Knopf — sonst feuern wir pro
|
||||||
|
// Keystroke einen Prompt. Debounce verhindert "Keine
|
||||||
|
// Treffer" zwischen Buchstaben.
|
||||||
|
_typingDebounce?.cancel();
|
||||||
|
_typingDebounce = Timer(const Duration(milliseconds: 250), () {
|
||||||
|
if (!mounted) return;
|
||||||
|
_runSearch();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAskSubmit() async {
|
Future<void> _onAskSubmit() async {
|
||||||
|
// Pending typing-debounce abbrechen — wir submitten jetzt
|
||||||
|
// explizit und wollen nicht hinterher nochmal substring-
|
||||||
|
// filter laufen lassen.
|
||||||
|
_typingDebounce?.cancel();
|
||||||
final q = _queryCtrl.text.trim();
|
final q = _queryCtrl.text.trim();
|
||||||
if (q.isEmpty) {
|
if (q.isEmpty) {
|
||||||
_clearQuery();
|
_clearQuery();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_systemAiEnabled && _looksLikeQuestion(q)) {
|
// Wenn System-AI verfügbar ist, geht der explizite Submit
|
||||||
|
// IMMER über die KI-Suche — egal ob die Eingabe wie eine
|
||||||
|
// Frage aussieht. Das war der Bug: Nutzer tippt "tabellen"
|
||||||
|
// + klick Suchen, erwartet KI, bekommt Substring-Filter.
|
||||||
|
// Substring-Filter passiert weiter live beim Tippen
|
||||||
|
// (siehe _onAskTyping).
|
||||||
|
if (_systemAiEnabled) {
|
||||||
await _runAiQuery(q);
|
await _runAiQuery(q);
|
||||||
} else {
|
} else {
|
||||||
_runSearch();
|
_runSearch();
|
||||||
|
|
@ -479,16 +503,6 @@ class _StorePageState extends State<StorePage> {
|
||||||
_runSearch();
|
_runSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Heuristic question detector — kept generous so a sentence
|
|
||||||
/// "modul für tabellen lesen" triggers the LLM path even
|
|
||||||
/// without a question mark. Single-word and two-word inputs
|
|
||||||
/// stay on substring search where they belong.
|
|
||||||
bool _looksLikeQuestion(String q) {
|
|
||||||
if (q.endsWith('?')) return true;
|
|
||||||
final words = q.split(RegExp(r'\s+'));
|
|
||||||
return words.length >= 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _runAiQuery(String query) async {
|
Future<void> _runAiQuery(String query) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_aiThinking = true;
|
_aiThinking = true;
|
||||||
|
|
@ -1922,10 +1936,14 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
|
|
||||||
void _loadDocs() {
|
void _loadDocs() {
|
||||||
if (_docsLoaded) return;
|
if (_docsLoaded) return;
|
||||||
|
// Sprache vom aktuellen Studio-Locale ans Hub weitergeben,
|
||||||
|
// damit `README.de.md` zuerst probiert wird wenn Studio auf
|
||||||
|
// Deutsch steht.
|
||||||
|
final locale = Localizations.localeOf(context).languageCode;
|
||||||
setState(() {
|
setState(() {
|
||||||
_docsLoaded = true;
|
_docsLoaded = true;
|
||||||
_docsFuture =
|
_docsFuture =
|
||||||
HubService.instance.fetchModuleDocs(widget.item.name);
|
HubService.instance.fetchModuleDocs(widget.item.name, locale: locale);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue