mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 16:56:16 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0fbe55f5c3 | |||
| dea9718174 | |||
| dacb751ea8 | |||
| 37016cc499 | |||
| 98a3bc938f |
@@ -5,7 +5,6 @@ use crate::init_status::{InitErrorPayload, SkillsMigrationPayload};
|
|||||||
use crate::services::ProviderService;
|
use crate::services::ProviderService;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::path::Path;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri::State;
|
use tauri::State;
|
||||||
@@ -100,9 +99,7 @@ pub async fn get_tool_versions() -> Result<Vec<ToolVersion>, String> {
|
|||||||
|
|
||||||
for tool in tools {
|
for tool in tools {
|
||||||
// 1. 获取本地版本 - 先尝试直接执行,失败则扫描常见路径
|
// 1. 获取本地版本 - 先尝试直接执行,失败则扫描常见路径
|
||||||
let (local_version, local_error) = if let Some(distro) = wsl_distro_for_tool(tool) {
|
let (local_version, local_error) = {
|
||||||
try_get_version_wsl(tool, &distro)
|
|
||||||
} else {
|
|
||||||
// 先尝试直接执行
|
// 先尝试直接执行
|
||||||
let direct_result = try_get_version(tool);
|
let direct_result = try_get_version(tool);
|
||||||
|
|
||||||
@@ -190,7 +187,7 @@ fn try_get_version(tool: &str) -> (Option<String>, Option<String>) {
|
|||||||
if out.status.success() {
|
if out.status.success() {
|
||||||
let raw = if stdout.is_empty() { &stderr } else { &stdout };
|
let raw = if stdout.is_empty() { &stderr } else { &stdout };
|
||||||
if raw.is_empty() {
|
if raw.is_empty() {
|
||||||
(None, Some("not installed or not executable".to_string()))
|
(None, Some("未安装或无法执行".to_string()))
|
||||||
} else {
|
} else {
|
||||||
(Some(extract_version(raw)), None)
|
(Some(extract_version(raw)), None)
|
||||||
}
|
}
|
||||||
@@ -199,7 +196,7 @@ fn try_get_version(tool: &str) -> (Option<String>, Option<String>) {
|
|||||||
(
|
(
|
||||||
None,
|
None,
|
||||||
Some(if err.is_empty() {
|
Some(if err.is_empty() {
|
||||||
"not installed or not executable".to_string()
|
"未安装或无法执行".to_string()
|
||||||
} else {
|
} else {
|
||||||
err
|
err
|
||||||
}),
|
}),
|
||||||
@@ -210,88 +207,6 @@ fn try_get_version(tool: &str) -> (Option<String>, Option<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 校验 WSL 发行版名称是否合法
|
|
||||||
/// WSL 发行版名称只允许字母、数字、连字符和下划线
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn is_valid_wsl_distro_name(name: &str) -> bool {
|
|
||||||
!name.is_empty()
|
|
||||||
&& name.len() <= 64
|
|
||||||
&& name
|
|
||||||
.chars()
|
|
||||||
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn try_get_version_wsl(tool: &str, distro: &str) -> (Option<String>, Option<String>) {
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
// 防御性断言:tool 只能是预定义的值
|
|
||||||
debug_assert!(
|
|
||||||
["claude", "codex", "gemini"].contains(&tool),
|
|
||||||
"unexpected tool name: {tool}"
|
|
||||||
);
|
|
||||||
|
|
||||||
// 校验 distro 名称,防止命令注入
|
|
||||||
if !is_valid_wsl_distro_name(distro) {
|
|
||||||
return (None, Some(format!("[WSL:{distro}] invalid distro name")));
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = Command::new("wsl.exe")
|
|
||||||
.args([
|
|
||||||
"-d",
|
|
||||||
distro,
|
|
||||||
"--",
|
|
||||||
"sh",
|
|
||||||
"-lc",
|
|
||||||
&format!("{tool} --version"),
|
|
||||||
])
|
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
|
||||||
.output();
|
|
||||||
|
|
||||||
match output {
|
|
||||||
Ok(out) => {
|
|
||||||
let stdout = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
|
||||||
let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string();
|
|
||||||
if out.status.success() {
|
|
||||||
let raw = if stdout.is_empty() { &stderr } else { &stdout };
|
|
||||||
if raw.is_empty() {
|
|
||||||
(
|
|
||||||
None,
|
|
||||||
Some(format!("[WSL:{distro}] not installed or not executable")),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(Some(extract_version(raw)), None)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let err = if stderr.is_empty() { stdout } else { stderr };
|
|
||||||
(
|
|
||||||
None,
|
|
||||||
Some(format!(
|
|
||||||
"[WSL:{distro}] {}",
|
|
||||||
if err.is_empty() {
|
|
||||||
"not installed or not executable".to_string()
|
|
||||||
} else {
|
|
||||||
err
|
|
||||||
}
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => (None, Some(format!("[WSL:{distro}] exec failed: {e}"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 非 Windows 平台的 WSL 版本检测存根
|
|
||||||
/// 注意:此函数实际上不会被调用,因为 `wsl_distro_from_path` 在非 Windows 平台总是返回 None。
|
|
||||||
/// 保留此函数是为了保持 API 一致性,防止未来重构时遗漏。
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
fn try_get_version_wsl(_tool: &str, _distro: &str) -> (Option<String>, Option<String>) {
|
|
||||||
(
|
|
||||||
None,
|
|
||||||
Some("WSL check not supported on this platform".to_string()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 扫描常见路径查找 CLI
|
/// 扫描常见路径查找 CLI
|
||||||
fn scan_cli_version(tool: &str) -> (Option<String>, Option<String>) {
|
fn scan_cli_version(tool: &str) -> (Option<String>, Option<String>) {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -387,49 +302,7 @@ fn scan_cli_version(tool: &str) -> (Option<String>, Option<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(None, Some("not installed or not executable".to_string()))
|
(None, Some("未安装或无法执行".to_string()))
|
||||||
}
|
|
||||||
|
|
||||||
fn wsl_distro_for_tool(tool: &str) -> Option<String> {
|
|
||||||
let override_dir = match tool {
|
|
||||||
"claude" => crate::settings::get_claude_override_dir(),
|
|
||||||
"codex" => crate::settings::get_codex_override_dir(),
|
|
||||||
"gemini" => crate::settings::get_gemini_override_dir(),
|
|
||||||
_ => None,
|
|
||||||
}?;
|
|
||||||
|
|
||||||
wsl_distro_from_path(&override_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 从 UNC 路径中提取 WSL 发行版名称
|
|
||||||
/// 支持 `\\wsl$\Ubuntu\...` 和 `\\wsl.localhost\Ubuntu\...` 两种格式
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn wsl_distro_from_path(path: &Path) -> Option<String> {
|
|
||||||
use std::path::{Component, Prefix};
|
|
||||||
let Some(Component::Prefix(prefix)) = path.components().next() else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
match prefix.kind() {
|
|
||||||
Prefix::UNC(server, share) | Prefix::VerbatimUNC(server, share) => {
|
|
||||||
let server_name = server.to_string_lossy();
|
|
||||||
if server_name.eq_ignore_ascii_case("wsl$")
|
|
||||||
|| server_name.eq_ignore_ascii_case("wsl.localhost")
|
|
||||||
{
|
|
||||||
let distro = share.to_string_lossy().to_string();
|
|
||||||
if !distro.is_empty() {
|
|
||||||
return Some(distro);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 非 Windows 平台不支持 WSL 路径解析
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
fn wsl_distro_from_path(_path: &Path) -> Option<String> {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 打开指定提供商的终端
|
/// 打开指定提供商的终端
|
||||||
|
|||||||
@@ -147,9 +147,6 @@ pub struct ProviderMeta {
|
|||||||
/// 用量查询脚本配置
|
/// 用量查询脚本配置
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub usage_script: Option<UsageScript>,
|
pub usage_script: Option<UsageScript>,
|
||||||
/// 请求地址管理:测速后自动选择最佳端点
|
|
||||||
#[serde(rename = "endpointAutoSelect", skip_serializing_if = "Option::is_none")]
|
|
||||||
pub endpoint_auto_select: Option<bool>,
|
|
||||||
/// 合作伙伴标记(前端使用 isPartner,保持字段名一致)
|
/// 合作伙伴标记(前端使用 isPartner,保持字段名一致)
|
||||||
#[serde(rename = "isPartner", skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "isPartner", skip_serializing_if = "Option::is_none")]
|
||||||
pub is_partner: Option<bool>,
|
pub is_partner: Option<bool>,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState, useEffect, useRef } from "react";
|
import { useMemo, useState, useEffect } from "react";
|
||||||
import { GripVertical, ChevronDown, ChevronUp } from "lucide-react";
|
import { GripVertical, ChevronDown, ChevronUp } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import type {
|
import type {
|
||||||
@@ -149,10 +149,6 @@ export function ProviderCard({
|
|||||||
// 多套餐默认展开
|
// 多套餐默认展开
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
// 操作按钮容器 ref,用于动态计算宽度
|
|
||||||
const actionsRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [actionsWidth, setActionsWidth] = useState(0);
|
|
||||||
|
|
||||||
// 当检测到多套餐时自动展开
|
// 当检测到多套餐时自动展开
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasMultiplePlans) {
|
if (hasMultiplePlans) {
|
||||||
@@ -160,20 +156,6 @@ export function ProviderCard({
|
|||||||
}
|
}
|
||||||
}, [hasMultiplePlans]);
|
}, [hasMultiplePlans]);
|
||||||
|
|
||||||
// 动态获取操作按钮宽度
|
|
||||||
useEffect(() => {
|
|
||||||
if (actionsRef.current) {
|
|
||||||
const updateWidth = () => {
|
|
||||||
const width = actionsRef.current?.offsetWidth || 0;
|
|
||||||
setActionsWidth(width);
|
|
||||||
};
|
|
||||||
updateWidth();
|
|
||||||
// 监听窗口大小变化
|
|
||||||
window.addEventListener("resize", updateWidth);
|
|
||||||
return () => window.removeEventListener("resize", updateWidth);
|
|
||||||
}
|
|
||||||
}, [onTest, onOpenTerminal]); // 按钮数量可能变化时重新计算
|
|
||||||
|
|
||||||
const handleOpenWebsite = () => {
|
const handleOpenWebsite = () => {
|
||||||
if (!isClickableUrl) {
|
if (!isClickableUrl) {
|
||||||
return;
|
return;
|
||||||
@@ -299,13 +281,10 @@ export function ProviderCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className="relative flex items-center ml-auto min-w-0">
|
||||||
className="relative flex items-center ml-auto min-w-0 gap-3"
|
|
||||||
style={{ "--actions-width": `${actionsWidth || 320}px` } as React.CSSProperties}
|
|
||||||
>
|
|
||||||
{/* 用量信息区域 - hover 时向左移动,为操作按钮腾出空间 */}
|
{/* 用量信息区域 - hover 时向左移动,为操作按钮腾出空间 */}
|
||||||
<div className="ml-auto">
|
<div className="ml-auto transition-transform duration-200 group-hover:-translate-x-[14.5rem] group-focus-within:-translate-x-[14.5rem] sm:group-hover:-translate-x-[16rem] sm:group-focus-within:-translate-x-[16rem]">
|
||||||
<div className="flex items-center gap-1 transition-transform duration-200 group-hover:-translate-x-[var(--actions-width)] group-focus-within:-translate-x-[var(--actions-width)]">
|
<div className="flex items-center gap-1">
|
||||||
{/* 多套餐时显示套餐数量,单套餐时显示详细信息 */}
|
{/* 多套餐时显示套餐数量,单套餐时显示详细信息 */}
|
||||||
{hasMultiplePlans ? (
|
{hasMultiplePlans ? (
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
|
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
|
||||||
@@ -350,11 +329,8 @@ export function ProviderCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 操作按钮区域 - 绝对定位在右侧,hover 时滑入,与用量信息保持间距 */}
|
{/* 操作按钮区域 - 绝对定位在右侧,hover 时滑入 */}
|
||||||
<div
|
<div className="absolute right-0 top-1/2 -translate-y-1/2 flex items-center gap-1.5 opacity-0 pointer-events-none group-hover:opacity-100 group-focus-within:opacity-100 group-hover:pointer-events-auto group-focus-within:pointer-events-auto transition-all duration-200 translate-x-2 group-hover:translate-x-0 group-focus-within:translate-x-0">
|
||||||
ref={actionsRef}
|
|
||||||
className="absolute right-0 top-1/2 -translate-y-1/2 flex items-center gap-1.5 pl-3 opacity-0 pointer-events-none group-hover:opacity-100 group-focus-within:opacity-100 group-hover:pointer-events-auto group-focus-within:pointer-events-auto transition-all duration-200 translate-x-2 group-hover:translate-x-0 group-focus-within:translate-x-0"
|
|
||||||
>
|
|
||||||
<ProviderActions
|
<ProviderActions
|
||||||
isCurrent={isCurrent}
|
isCurrent={isCurrent}
|
||||||
isTesting={isTesting}
|
isTesting={isTesting}
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ interface ClaudeFormFieldsProps {
|
|||||||
isEndpointModalOpen: boolean;
|
isEndpointModalOpen: boolean;
|
||||||
onEndpointModalToggle: (open: boolean) => void;
|
onEndpointModalToggle: (open: boolean) => void;
|
||||||
onCustomEndpointsChange?: (endpoints: string[]) => void;
|
onCustomEndpointsChange?: (endpoints: string[]) => void;
|
||||||
autoSelect: boolean;
|
|
||||||
onAutoSelectChange: (checked: boolean) => void;
|
|
||||||
|
|
||||||
// Model Selector
|
// Model Selector
|
||||||
shouldShowModelSelector: boolean;
|
shouldShowModelSelector: boolean;
|
||||||
@@ -85,8 +83,6 @@ export function ClaudeFormFields({
|
|||||||
isEndpointModalOpen,
|
isEndpointModalOpen,
|
||||||
onEndpointModalToggle,
|
onEndpointModalToggle,
|
||||||
onCustomEndpointsChange,
|
onCustomEndpointsChange,
|
||||||
autoSelect,
|
|
||||||
onAutoSelectChange,
|
|
||||||
shouldShowModelSelector,
|
shouldShowModelSelector,
|
||||||
claudeModel,
|
claudeModel,
|
||||||
reasoningModel,
|
reasoningModel,
|
||||||
@@ -174,8 +170,6 @@ export function ClaudeFormFields({
|
|||||||
initialEndpoints={speedTestEndpoints}
|
initialEndpoints={speedTestEndpoints}
|
||||||
visible={isEndpointModalOpen}
|
visible={isEndpointModalOpen}
|
||||||
onClose={() => onEndpointModalToggle(false)}
|
onClose={() => onEndpointModalToggle(false)}
|
||||||
autoSelect={autoSelect}
|
|
||||||
onAutoSelectChange={onAutoSelectChange}
|
|
||||||
onCustomEndpointsChange={onCustomEndpointsChange}
|
onCustomEndpointsChange={onCustomEndpointsChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ interface CodexFormFieldsProps {
|
|||||||
isEndpointModalOpen: boolean;
|
isEndpointModalOpen: boolean;
|
||||||
onEndpointModalToggle: (open: boolean) => void;
|
onEndpointModalToggle: (open: boolean) => void;
|
||||||
onCustomEndpointsChange?: (endpoints: string[]) => void;
|
onCustomEndpointsChange?: (endpoints: string[]) => void;
|
||||||
autoSelect: boolean;
|
|
||||||
onAutoSelectChange: (checked: boolean) => void;
|
|
||||||
|
|
||||||
// Model Name
|
// Model Name
|
||||||
shouldShowModelField?: boolean;
|
shouldShowModelField?: boolean;
|
||||||
@@ -52,8 +50,6 @@ export function CodexFormFields({
|
|||||||
isEndpointModalOpen,
|
isEndpointModalOpen,
|
||||||
onEndpointModalToggle,
|
onEndpointModalToggle,
|
||||||
onCustomEndpointsChange,
|
onCustomEndpointsChange,
|
||||||
autoSelect,
|
|
||||||
onAutoSelectChange,
|
|
||||||
shouldShowModelField = true,
|
shouldShowModelField = true,
|
||||||
modelName = "",
|
modelName = "",
|
||||||
onModelNameChange,
|
onModelNameChange,
|
||||||
@@ -134,8 +130,6 @@ export function CodexFormFields({
|
|||||||
initialEndpoints={speedTestEndpoints}
|
initialEndpoints={speedTestEndpoints}
|
||||||
visible={isEndpointModalOpen}
|
visible={isEndpointModalOpen}
|
||||||
onClose={() => onEndpointModalToggle(false)}
|
onClose={() => onEndpointModalToggle(false)}
|
||||||
autoSelect={autoSelect}
|
|
||||||
onAutoSelectChange={onAutoSelectChange}
|
|
||||||
onCustomEndpointsChange={onCustomEndpointsChange}
|
onCustomEndpointsChange={onCustomEndpointsChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ interface EndpointSpeedTestProps {
|
|||||||
initialEndpoints: EndpointCandidate[];
|
initialEndpoints: EndpointCandidate[];
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
autoSelect: boolean;
|
|
||||||
onAutoSelectChange: (checked: boolean) => void;
|
|
||||||
// 新建模式:当自定义端点列表变化时回传(仅包含 isCustom 的条目)
|
// 新建模式:当自定义端点列表变化时回传(仅包含 isCustom 的条目)
|
||||||
// 编辑模式:不使用此回调,端点直接保存到后端
|
// 编辑模式:不使用此回调,端点直接保存到后端
|
||||||
onCustomEndpointsChange?: (urls: string[]) => void;
|
onCustomEndpointsChange?: (urls: string[]) => void;
|
||||||
@@ -87,8 +85,6 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
initialEndpoints,
|
initialEndpoints,
|
||||||
visible = true,
|
visible = true,
|
||||||
onClose,
|
onClose,
|
||||||
autoSelect,
|
|
||||||
onAutoSelectChange,
|
|
||||||
onCustomEndpointsChange,
|
onCustomEndpointsChange,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -97,6 +93,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
);
|
);
|
||||||
const [customUrl, setCustomUrl] = useState("");
|
const [customUrl, setCustomUrl] = useState("");
|
||||||
const [addError, setAddError] = useState<string | null>(null);
|
const [addError, setAddError] = useState<string | null>(null);
|
||||||
|
const [autoSelect, setAutoSelect] = useState(true);
|
||||||
const [isTesting, setIsTesting] = useState(false);
|
const [isTesting, setIsTesting] = useState(false);
|
||||||
const [lastError, setLastError] = useState<string | null>(null);
|
const [lastError, setLastError] = useState<string | null>(null);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
@@ -491,9 +488,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={autoSelect}
|
checked={autoSelect}
|
||||||
onChange={(event) => {
|
onChange={(event) => setAutoSelect(event.target.checked)}
|
||||||
onAutoSelectChange(event.target.checked);
|
|
||||||
}}
|
|
||||||
className="h-3.5 w-3.5 rounded border-border-default bg-background text-primary focus:ring-2 focus:ring-primary/20"
|
className="h-3.5 w-3.5 rounded border-border-default bg-background text-primary focus:ring-2 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
{t("endpointTest.autoSelect")}
|
{t("endpointTest.autoSelect")}
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ interface GeminiFormFieldsProps {
|
|||||||
isEndpointModalOpen: boolean;
|
isEndpointModalOpen: boolean;
|
||||||
onEndpointModalToggle: (open: boolean) => void;
|
onEndpointModalToggle: (open: boolean) => void;
|
||||||
onCustomEndpointsChange: (endpoints: string[]) => void;
|
onCustomEndpointsChange: (endpoints: string[]) => void;
|
||||||
autoSelect: boolean;
|
|
||||||
onAutoSelectChange: (checked: boolean) => void;
|
|
||||||
|
|
||||||
// Model
|
// Model
|
||||||
shouldShowModelField: boolean;
|
shouldShowModelField: boolean;
|
||||||
@@ -57,8 +55,6 @@ export function GeminiFormFields({
|
|||||||
isEndpointModalOpen,
|
isEndpointModalOpen,
|
||||||
onEndpointModalToggle,
|
onEndpointModalToggle,
|
||||||
onCustomEndpointsChange,
|
onCustomEndpointsChange,
|
||||||
autoSelect,
|
|
||||||
onAutoSelectChange,
|
|
||||||
shouldShowModelField,
|
shouldShowModelField,
|
||||||
model,
|
model,
|
||||||
onModelChange,
|
onModelChange,
|
||||||
@@ -146,8 +142,6 @@ export function GeminiFormFields({
|
|||||||
initialEndpoints={speedTestEndpoints}
|
initialEndpoints={speedTestEndpoints}
|
||||||
visible={isEndpointModalOpen}
|
visible={isEndpointModalOpen}
|
||||||
onClose={() => onEndpointModalToggle(false)}
|
onClose={() => onEndpointModalToggle(false)}
|
||||||
autoSelect={autoSelect}
|
|
||||||
onAutoSelectChange={onAutoSelectChange}
|
|
||||||
onCustomEndpointsChange={onCustomEndpointsChange}
|
onCustomEndpointsChange={onCustomEndpointsChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -124,9 +124,6 @@ export function ProviderForm({
|
|||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const [endpointAutoSelect, setEndpointAutoSelect] = useState<boolean>(
|
|
||||||
() => initialData?.meta?.endpointAutoSelect ?? true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 使用 category hook
|
// 使用 category hook
|
||||||
const { category } = useProviderCategory({
|
const { category } = useProviderCategory({
|
||||||
@@ -144,7 +141,6 @@ export function ProviderForm({
|
|||||||
if (!initialData) {
|
if (!initialData) {
|
||||||
setDraftCustomEndpoints([]);
|
setDraftCustomEndpoints([]);
|
||||||
}
|
}
|
||||||
setEndpointAutoSelect(initialData?.meta?.endpointAutoSelect ?? true);
|
|
||||||
}, [appId, initialData]);
|
}, [appId, initialData]);
|
||||||
|
|
||||||
const defaultValues: ProviderFormData = useMemo(
|
const defaultValues: ProviderFormData = useMemo(
|
||||||
@@ -651,13 +647,6 @@ export function ProviderForm({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseMeta: ProviderMeta | undefined =
|
|
||||||
payload.meta ?? (initialData?.meta ? { ...initialData.meta } : undefined);
|
|
||||||
payload.meta = {
|
|
||||||
...(baseMeta ?? {}),
|
|
||||||
endpointAutoSelect,
|
|
||||||
};
|
|
||||||
|
|
||||||
onSubmit(payload);
|
onSubmit(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -867,8 +856,6 @@ export function ProviderForm({
|
|||||||
onCustomEndpointsChange={
|
onCustomEndpointsChange={
|
||||||
isEditMode ? undefined : setDraftCustomEndpoints
|
isEditMode ? undefined : setDraftCustomEndpoints
|
||||||
}
|
}
|
||||||
autoSelect={endpointAutoSelect}
|
|
||||||
onAutoSelectChange={setEndpointAutoSelect}
|
|
||||||
shouldShowModelSelector={category !== "official"}
|
shouldShowModelSelector={category !== "official"}
|
||||||
claudeModel={claudeModel}
|
claudeModel={claudeModel}
|
||||||
reasoningModel={reasoningModel}
|
reasoningModel={reasoningModel}
|
||||||
@@ -902,8 +889,6 @@ export function ProviderForm({
|
|||||||
onCustomEndpointsChange={
|
onCustomEndpointsChange={
|
||||||
isEditMode ? undefined : setDraftCustomEndpoints
|
isEditMode ? undefined : setDraftCustomEndpoints
|
||||||
}
|
}
|
||||||
autoSelect={endpointAutoSelect}
|
|
||||||
onAutoSelectChange={setEndpointAutoSelect}
|
|
||||||
shouldShowModelField={category !== "official"}
|
shouldShowModelField={category !== "official"}
|
||||||
modelName={codexModelName}
|
modelName={codexModelName}
|
||||||
onModelNameChange={handleCodexModelNameChange}
|
onModelNameChange={handleCodexModelNameChange}
|
||||||
@@ -932,8 +917,6 @@ export function ProviderForm({
|
|||||||
isEndpointModalOpen={isEndpointModalOpen}
|
isEndpointModalOpen={isEndpointModalOpen}
|
||||||
onEndpointModalToggle={setIsEndpointModalOpen}
|
onEndpointModalToggle={setIsEndpointModalOpen}
|
||||||
onCustomEndpointsChange={setDraftCustomEndpoints}
|
onCustomEndpointsChange={setDraftCustomEndpoints}
|
||||||
autoSelect={endpointAutoSelect}
|
|
||||||
onAutoSelectChange={setEndpointAutoSelect}
|
|
||||||
shouldShowModelField={true}
|
shouldShowModelField={true}
|
||||||
model={geminiModel}
|
model={geminiModel}
|
||||||
onModelChange={handleGeminiModelChange}
|
onModelChange={handleGeminiModelChange}
|
||||||
|
|||||||
@@ -92,8 +92,6 @@ export interface ProviderMeta {
|
|||||||
custom_endpoints?: Record<string, CustomEndpoint>;
|
custom_endpoints?: Record<string, CustomEndpoint>;
|
||||||
// 用量查询脚本配置
|
// 用量查询脚本配置
|
||||||
usage_script?: UsageScript;
|
usage_script?: UsageScript;
|
||||||
// 请求地址管理:测速后自动选择最佳端点
|
|
||||||
endpointAutoSelect?: boolean;
|
|
||||||
// 是否为官方合作伙伴
|
// 是否为官方合作伙伴
|
||||||
isPartner?: boolean;
|
isPartner?: boolean;
|
||||||
// 合作伙伴促销 key(用于后端识别 PackyCode 等)
|
// 合作伙伴促销 key(用于后端识别 PackyCode 等)
|
||||||
|
|||||||
Reference in New Issue
Block a user