From be7284f0f1b6878acdaf98276264200873b2358a Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Wed, 16 Sep 2026 00:34:48 +0800 Subject: [PATCH] fix(aliases): stop treating display names as aliases #CLOSE #259 --- src-tauri/src/agents/commands.rs | 17 ++++-- src-tauri/src/core_config/aliases.rs | 19 ++++--- src-tauri/src/tests/model_aliases.rs | 33 ++++++++++++ src/components/ModelSelectionPanel.tsx | 6 +-- src/i18n/ja.ts | 1 + src/i18n/locales/en.ts | 1 + src/i18n/locales/zh-CN.ts | 1 + src/pages/ApiAccessPage.tsx | 30 ++++++++--- src/pages/ThinkingAliasesPage.tsx | 8 +++ src/services/modelService.ts | 51 ++++++++++++++---- tests/modelService.test.ts | 68 +++++++++++++++++++++++ tests/providerConfig.test.ts | 74 +++++++++++++++++++++++++- 12 files changed, 277 insertions(+), 32 deletions(-) diff --git a/src-tauri/src/agents/commands.rs b/src-tauri/src/agents/commands.rs index 1b70043..d07b126 100644 --- a/src-tauri/src/agents/commands.rs +++ b/src-tauri/src/agents/commands.rs @@ -498,7 +498,7 @@ pub(crate) async fn get_model_alias_edit_source( alias: String, ) -> Result { let config = gui_config_state.snapshot()?; - let alias = validate_thinking_alias_model_id(&alias, "别名模型")?; + let alias = existing_thinking_alias_model_id(&alias, "别名模型")?; let content = fetch_management_config_yaml(&config).await?; let definitions = fetch_oauth_model_definitions(&config).await; model_alias_edit_context(&content, &alias, &definitions) @@ -519,7 +519,16 @@ pub(crate) async fn create_thinking_alias( if source_id.is_empty() { return Err("请先选择原模型".to_string()); } - let alias = validate_thinking_alias_model_id(&alias, "别名模型")?; + let original_alias = original_alias + .as_deref() + .map(|original| existing_thinking_alias_model_id(original, "别名模型")) + .transpose()?; + let alias = match original_alias.as_deref() { + Some(original) if original.eq_ignore_ascii_case(alias.trim()) => { + existing_thinking_alias_model_id(&alias, "别名模型")? + } + _ => validate_thinking_alias_model_id(&alias, "别名模型")?, + }; let effort = if effort.trim().is_empty() { String::new() } else { @@ -594,7 +603,7 @@ pub(crate) async fn delete_thinking_alias( oauth_channel: Option, ) -> Result, String> { let config = gui_config_state.snapshot()?; - let alias = validate_thinking_alias_model_id(&alias, "别名模型")?; + let alias = existing_thinking_alias_model_id(&alias, "别名模型")?; let content = fetch_management_config_yaml(&config).await?; let updated = remove_thinking_alias_from_yaml_for_channel(&content, &alias, oauth_channel.as_deref())?; @@ -690,7 +699,7 @@ pub(crate) async fn delete_speed_alias( oauth_channel: Option, ) -> Result, String> { let config = gui_config_state.snapshot()?; - let alias = validate_thinking_alias_model_id(&alias, "别名模型")?; + let alias = existing_thinking_alias_model_id(&alias, "别名模型")?; let content = fetch_management_config_yaml(&config).await?; let updated = remove_speed_alias_from_yaml_for_channel(&content, &alias, oauth_channel.as_deref())?; diff --git a/src-tauri/src/core_config/aliases.rs b/src-tauri/src/core_config/aliases.rs index 5b35608..0c8d9f1 100644 --- a/src-tauri/src/core_config/aliases.rs +++ b/src-tauri/src/core_config/aliases.rs @@ -185,19 +185,26 @@ pub(crate) async fn fetch_active_oauth_alias_channels( .collect()) } -pub(crate) fn validate_thinking_alias_model_id(value: &str, label: &str) -> Result { +pub(crate) fn existing_thinking_alias_model_id(value: &str, label: &str) -> Result { let value = value.trim(); if value.is_empty() { return Err(format!("{label}不能为空")); } - if value.len() > 240 - || value - .chars() - .any(|character| character.is_whitespace() || character.is_control()) + if value.len() > 240 || value.chars().any(char::is_control) { + return Err(format!("{label}格式无效")); + } + Ok(value.to_string()) +} + +pub(crate) fn validate_thinking_alias_model_id(value: &str, label: &str) -> Result { + let value = existing_thinking_alias_model_id(value, label)?; + if value + .chars() + .any(|character| character.is_whitespace() || character.is_control()) { return Err(format!("{label}格式无效,不能包含空白字符")); } - Ok(value.to_string()) + Ok(value) } pub(crate) fn validate_thinking_alias_effort(value: &str) -> Result { diff --git a/src-tauri/src/tests/model_aliases.rs b/src-tauri/src/tests/model_aliases.rs index 988c8d2..563f58b 100644 --- a/src-tauri/src/tests/model_aliases.rs +++ b/src-tauri/src/tests/model_aliases.rs @@ -562,6 +562,39 @@ fn thinking_alias_effort_accepts_provider_defined_levels() { assert!(validate_thinking_alias_effort("32768").is_err()); } +#[test] +fn existing_aliases_with_spaces_can_be_loaded_and_deleted() { + assert_eq!( + existing_thinking_alias_model_id(" Codex Auto Review ", "别名模型").unwrap(), + "Codex Auto Review" + ); + assert!(validate_thinking_alias_model_id("Codex Auto Review", "别名模型").is_err()); + let content = "codex-api-key:\n - name: provider\n base-url: https://www.loomex.cc\n models:\n - name: codex-auto-review\n alias: Codex Auto Review\n - name: keep-me\n"; + let entries = thinking_aliases_from_yaml(content).unwrap(); + assert_eq!(entries.len(), 1); + assert_eq!(entries[0].alias, "Codex Auto Review"); + assert_eq!(entries[0].source_model, "codex-auto-review"); + let context = model_alias_edit_context(content, "Codex Auto Review", &[]).unwrap(); + assert_eq!(context.source.model, "codex-auto-review"); + let deleted = remove_thinking_alias_from_yaml(content, "Codex Auto Review").unwrap(); + assert!(thinking_aliases_from_yaml(&deleted).unwrap().is_empty()); + assert!(deleted.contains("name: keep-me")); + assert!(!deleted.contains("Codex Auto Review")); + let source = resolve_model_alias_edit_source(content, "Codex Auto Review", &[]).unwrap(); + let renamed = edit_model_alias_in_yaml( + content, + "Codex Auto Review", + &source, + "codex-auto-review-alias", + "", + false, + ) + .unwrap(); + let renamed_entries = thinking_aliases_from_yaml(&renamed).unwrap(); + assert_eq!(renamed_entries.len(), 1); + assert_eq!(renamed_entries[0].alias, "codex-auto-review-alias"); +} + #[test] fn thinking_alias_removal_keeps_other_models_in_grouped_rule() { let input = "oauth-model-alias:\n codex:\n - name: gpt-5.5\n alias: gpt-5.5-xhigh\n fork: true\n - name: gpt-5.4\n alias: gpt-5.4-xhigh\n fork: true\npayload:\n override:\n - models:\n - name: gpt-5.5-xhigh\n protocol: codex\n - name: gpt-5.4-xhigh\n protocol: codex\n params:\n reasoning.effort: xhigh\n"; diff --git a/src/components/ModelSelectionPanel.tsx b/src/components/ModelSelectionPanel.tsx index 90a4c16..d45fac7 100644 --- a/src/components/ModelSelectionPanel.tsx +++ b/src/components/ModelSelectionPanel.tsx @@ -1,7 +1,7 @@ import { useId, useMemo, useRef, useState } from 'react'; import { ArrowRight, Search, X } from 'lucide-react'; import { useI18n } from '../i18n'; -import type { ModelOption } from '../services/modelService'; +import { modelSearchText, type ModelOption } from '../services/modelService'; type ModelSelectionPanelProps = { models: ModelOption[]; @@ -17,7 +17,7 @@ export function ModelSelectionPanel({ models, selected, loading, onMove }: Model const [search, setSearch] = useState(''); const query = search.trim().toLowerCase(); const visibleModels = useMemo(() => models.filter((model) => - `${model.name} ${model.alias ?? ''}`.toLowerCase().includes(query), + modelSearchText(model).includes(query), ), [models, query]); const searchLabel = t(selected ? 'apiAccess.modelDialog.searchSelected' : 'apiAccess.modelDialog.searchUnselected'); @@ -63,7 +63,7 @@ export function ModelSelectionPanel({ models, selected, loading, onMove }: Model onMove([model], !selected); }} > - {model.name}{model.alias ? {model.alias} : null} + {model.name}{model.alias || model.displayName ? {model.alias || model.displayName} : null} {selected ?