fix(ai): stop a stray selection hijacking a new-design prompt
A section-heavy new-design prompt ('Design a … page. Include a search
section …') trips is_section_add_request, so requests_new_whole_screen
returns false; the selection-modify bias then routed the whole prompt
into run_modify_turn, where M3's flat-JSONL output was renest-dropped
to nothing ('Could not parse design nodes'). Add a section-add-blind
creation-signal veto to both routing gates so a new-design request
reaches the design pipeline regardless of an active selection.
This commit is contained in:
parent
0dd7277b78
commit
9de2249164
|
|
@ -240,6 +240,13 @@ fn should_launch_direct_modify(state: &EditorState, user_text: &str) -> bool {
|
|||
if op_host_services::chat_intent::requests_new_whole_screen(user_text) {
|
||||
return false;
|
||||
}
|
||||
// Same intent, but section-add-blind: a full new-page spec that mentions
|
||||
// "section" ("Include a search section…") still reads as new, so a stray
|
||||
// selection can't drag it into modify (measured: a travel-app design with
|
||||
// a node selected fell into run_modify_turn → M3 flat-JSONL → empty).
|
||||
if op_host_services::chat_intent::has_new_screen_creation_signal(user_text) {
|
||||
return false;
|
||||
}
|
||||
let keyword_intent = op_host_services::chat_intent::classify_by_keywords(user_text);
|
||||
let selected_target_instruction = !state.selection.set.is_empty()
|
||||
&& keyword_intent != op_host_services::chat_intent::DesignIntent::Chat;
|
||||
|
|
|
|||
|
|
@ -180,8 +180,13 @@ pub fn classify_intent_for_standard_route(
|
|||
if !state.selection.set.is_empty() && keyword_intent == DesignIntent::Chat {
|
||||
return DesignIntent::Chat;
|
||||
}
|
||||
let selected_target_instruction =
|
||||
!state.selection.set.is_empty() && keyword_intent != DesignIntent::Chat;
|
||||
// A selection biases toward modify — UNLESS the prompt is itself a
|
||||
// whole-screen design request (a stray selection must not hijack "Design
|
||||
// a … page" into an edit; `requests_new_whole_screen` misses these when
|
||||
// they mention "section", so check the veto-free creation signal too).
|
||||
let selected_target_instruction = !state.selection.set.is_empty()
|
||||
&& keyword_intent != DesignIntent::Chat
|
||||
&& !has_new_screen_creation_signal(text);
|
||||
if keyword_intent == DesignIntent::Modify || selected_target_instruction {
|
||||
return DesignIntent::Modify;
|
||||
}
|
||||
|
|
@ -468,6 +473,23 @@ pub fn requests_new_whole_screen(prompt: &str) -> bool {
|
|||
english_requests_new_screen(&prompt.to_lowercase())
|
||||
}
|
||||
|
||||
/// Core "this is a NEW screen/design" signal: a creation verb aimed at a
|
||||
/// page/screen noun. Unlike [`requests_new_whole_screen`] it does NOT veto on
|
||||
/// section-add phrasing — a full new-page spec that merely mentions the word
|
||||
/// "section" ("Include a search section…", "Deals of the Week section") still
|
||||
/// reads as new. Used to stop a stray canvas selection from hijacking a
|
||||
/// whole-screen design prompt into the modify path (measured: a travel-app
|
||||
/// design with a node selected routed to modify → M3 flat-JSONL → empty).
|
||||
pub fn has_new_screen_creation_signal(prompt: &str) -> bool {
|
||||
let cjk_page = ["页面", "页", "屏幕", "屏"]
|
||||
.iter()
|
||||
.any(|k| prompt.contains(k));
|
||||
if cjk_page && DRAW_VERB_CJK.iter().any(|v| prompt.contains(v)) {
|
||||
return true;
|
||||
}
|
||||
english_requests_new_screen(&prompt.to_lowercase())
|
||||
}
|
||||
|
||||
/// English new-screen detection by the DETERMINER nearest a page/screen noun:
|
||||
/// an indefinite article ("a / an / another / new") means a NEW page is being
|
||||
/// made ("make a login page", "draw a checkout page"); a definite/demonstrative
|
||||
|
|
|
|||
|
|
@ -82,3 +82,24 @@ fn selection_bias_does_not_hijack_whole_new_screen_or_chat() {
|
|||
DesignIntent::Chat
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selection_does_not_hijack_english_section_heavy_new_design() {
|
||||
// Regression: "Design a … page" whose spec mentions "section" three times
|
||||
// trips `is_section_add_request`, so `requests_new_whole_screen` is false;
|
||||
// a stray selection then dragged the whole new-design prompt into modify
|
||||
// (measured: M3 flat-JSONL → "Could not parse design nodes"). The
|
||||
// creation-signal veto keeps it New.
|
||||
let provider = Scripted;
|
||||
let state = state_with_selected_card();
|
||||
let prompt = "Design a travel booking mobile app explore page. Include a search section with \"Where to?\" input, date picker chips, and guest count. \"Deals of the Week\" section with 2 featured deal cards. Recently viewed section with 2 compact cards. Bottom tab bar. Warm, inviting design with orange accents.";
|
||||
assert!(
|
||||
crate::chat_intent::has_new_screen_creation_signal(prompt),
|
||||
"creation signal must fire on a design-a-page prompt"
|
||||
);
|
||||
assert_ne!(
|
||||
classify_intent_for_standard_route(&provider, &state, prompt, None),
|
||||
DesignIntent::Modify,
|
||||
"a section-heavy new-design prompt must not be hijacked to modify by a selection"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue