fix: stabilize local folder AI document workflow

- scope local-folder PageTree revision and document sidebar rendering to fileTreeScope

- preserve projected table/image attrs for local Markdown aggregate fallback

- avoid FileTree restore forced layouts on cold design open

- add API ChatOnly provider runtime and local OCR task handling regressions
This commit is contained in:
lix-2026
2026-06-02 17:17:49 +08:00
parent 610b23d3a5
commit 9f4b5c4d48
27 changed files with 3825 additions and 229 deletions
@@ -60,6 +60,7 @@ struct EffectivePagePreferences {
page_options: PageOptions,
page_width_preferences: BTreeMap<String, EffectivePageWidthPreference>,
ai_preferences: BTreeMap<String, Value>,
local_ocr_preferences: BTreeMap<String, Value>,
sources: BTreeMap<String, String>,
}
@@ -246,10 +247,12 @@ fn resolve_effective_page_preferences(
let mut sources = BTreeMap::new();
let mut page_width_preferences = default_page_width_preferences();
let mut ai_preferences = BTreeMap::new();
let mut local_ocr_preferences = default_local_ocr_preferences();
apply_preference_records(
&mut page_options,
&mut page_width_preferences,
&mut ai_preferences,
&mut local_ocr_preferences,
&mut sources,
&scope,
&preferences,
@@ -260,6 +263,7 @@ fn resolve_effective_page_preferences(
page_options,
page_width_preferences,
ai_preferences,
local_ocr_preferences,
sources,
})
}
@@ -268,6 +272,7 @@ fn apply_preference_records(
page_options: &mut PageOptions,
page_width_preferences: &mut BTreeMap<String, EffectivePageWidthPreference>,
ai_preferences: &mut BTreeMap<String, Value>,
local_ocr_preferences: &mut BTreeMap<String, Value>,
sources: &mut BTreeMap<String, String>,
scope: &PagePreferenceScope,
preferences: &[UserUiPreferenceRecord],
@@ -277,6 +282,7 @@ fn apply_preference_records(
"source_family".to_string(),
"workspace".to_string(),
"document".to_string(),
"localOcr".to_string(),
];
for preference in preferences {
if preference.scope_kind.starts_with("ai.") && !scope_kinds.contains(&preference.scope_kind)
@@ -295,6 +301,7 @@ fn apply_preference_records(
"workspace" => preference.scope_id.trim() == scope.workspace_id,
"document" => preference.scope_id.trim() == scope.document_id,
kind if kind.starts_with("ai.") => preference.scope_id.trim() == scope.workspace_id,
"localOcr" => preference.scope_id.trim() == scope.workspace_id,
_ => false,
};
if !scope_matches {
@@ -311,6 +318,11 @@ fn apply_preference_records(
sources.insert(preference.key.clone(), scope_kind.to_string());
continue;
}
if preference.key.starts_with("localOcr.") {
local_ocr_preferences.insert(preference.key.clone(), value);
sources.insert(preference.key.clone(), scope_kind.to_string());
continue;
}
if let Some(content_type) = page_width_content_type_for_key(&preference.key) {
if let Some(normalized) = normalize_page_width_preference(content_type, &value) {
if let Some(preference_value) = page_width_preferences.get_mut(content_type) {
@@ -368,6 +380,9 @@ fn preference_scope_for_key(key: &str, scope: &PagePreferenceScope) -> Option<(S
return Some((format!("ai.agent.{agent}"), scope.workspace_id.clone()));
}
}
if trimmed.starts_with("localOcr.") {
return Some(("localOcr".to_string(), scope.workspace_id.clone()));
}
if page_width_content_type_for_key(key).is_some() {
return Some(("global".to_string(), "default".to_string()));
}
@@ -401,7 +416,11 @@ fn preference_scope_for_key(key: &str, scope: &PagePreferenceScope) -> Option<(S
}
fn preference_workspace_for_scope(workspace_id: &str, scope_kind: &str) -> Option<String> {
if scope_kind == "workspace" || scope_kind == "document" || scope_kind.starts_with("ai.") {
if scope_kind == "workspace"
|| scope_kind == "document"
|| scope_kind.starts_with("ai.")
|| scope_kind == "localOcr"
{
Some(workspace_id.to_string())
} else {
None
@@ -409,7 +428,11 @@ fn preference_workspace_for_scope(workspace_id: &str, scope_kind: &str) -> Optio
}
fn preference_source_kind_for_scope(source_kind: &str, scope_kind: &str) -> Option<String> {
if scope_kind == "workspace" || scope_kind == "document" || scope_kind.starts_with("ai.") {
if scope_kind == "workspace"
|| scope_kind == "document"
|| scope_kind.starts_with("ai.")
|| scope_kind == "localOcr"
{
Some(source_kind.to_string())
} else {
None
@@ -422,6 +445,8 @@ fn effective_preferences_payload(effective: EffectivePagePreferences) -> Value {
serde_json::to_value(&effective.page_width_preferences).unwrap_or_else(|_| json!({}));
let ai_preferences =
serde_json::to_value(&effective.ai_preferences).unwrap_or_else(|_| json!({}));
let local_ocr_preferences =
serde_json::to_value(&effective.local_ocr_preferences).unwrap_or_else(|_| json!({}));
let sources = effective
.sources
.into_iter()
@@ -440,6 +465,7 @@ fn effective_preferences_payload(effective: EffectivePagePreferences) -> Value {
"pageOptions": page_options,
"pageWidthPreferences": page_width_preferences,
"aiPreferences": ai_preferences,
"localOcrPreferences": local_ocr_preferences,
"sources": Value::Object(sources),
}
})
@@ -594,6 +620,10 @@ fn default_page_width_preferences() -> BTreeMap<String, EffectivePageWidthPrefer
.collect()
}
fn default_local_ocr_preferences() -> BTreeMap<String, Value> {
BTreeMap::from([("localOcr.autoEnabled".to_string(), Value::Bool(false))])
}
fn normalize_page_width_preference(
content_type: &str,
value: &Value,