mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 19:45:34 +08:00
refactor(claude-desktop): replace [1M] suffix with supports1m field
inferenceModels entries now emit {name, supports1m: true} objects when
1M is enabled (plain strings otherwise), instead of appending a " [1M]"
suffix to model IDs. Route IDs and upstream model IDs are stored
verbatim; the suffix is rejected on input rather than silently stripped,
and proxy request mapping now requires an exact route_id match.
This commit is contained in:
@@ -21,7 +21,6 @@ const CONFIG_LIBRARY_DIR: &str = "configLibrary";
|
|||||||
const GATEWAY_TOKEN_SETTING_KEY: &str = "claude_desktop_gateway_token";
|
const GATEWAY_TOKEN_SETTING_KEY: &str = "claude_desktop_gateway_token";
|
||||||
const CLAUDE_DESKTOP_PROXY_PREFIX: &str = "/claude-desktop";
|
const CLAUDE_DESKTOP_PROXY_PREFIX: &str = "/claude-desktop";
|
||||||
const DEFAULT_CREATED_AT: &str = "2024-01-01T00:00:00Z";
|
const DEFAULT_CREATED_AT: &str = "2024-01-01T00:00:00Z";
|
||||||
const ONE_M_CONTEXT_SUFFIX: &str = " [1M]";
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize)]
|
#[derive(Debug, Clone, Copy, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -95,6 +94,12 @@ pub struct ResolvedModelRoute {
|
|||||||
pub supports_1m: bool,
|
pub supports_1m: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct InferenceModelSpec {
|
||||||
|
name: String,
|
||||||
|
supports_1m: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_provider(db: &Database, provider: &Provider) -> Result<(), AppError> {
|
pub fn apply_provider(db: &Database, provider: &Provider) -> Result<(), AppError> {
|
||||||
let paths = current_platform_paths()?;
|
let paths = current_platform_paths()?;
|
||||||
apply_provider_to_paths(db, provider, &paths)
|
apply_provider_to_paths(db, provider, &paths)
|
||||||
@@ -204,39 +209,22 @@ pub fn provider_mode(provider: &Provider) -> ClaudeDesktopMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_claude_safe_model_id(model: &str) -> bool {
|
pub fn is_claude_safe_model_id(model: &str) -> bool {
|
||||||
let normalized = strip_one_m_context_suffix(model).to_ascii_lowercase();
|
let normalized = model.trim().to_ascii_lowercase();
|
||||||
normalized.starts_with("claude-") || normalized.starts_with("anthropic/claude-")
|
(normalized.starts_with("claude-") || normalized.starts_with("anthropic/claude-"))
|
||||||
|
&& !normalized.contains("[1m]")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strip_one_m_context_suffix(model: &str) -> String {
|
fn inference_model_json(spec: &InferenceModelSpec) -> Value {
|
||||||
let trimmed = model.trim();
|
if spec.supports_1m {
|
||||||
let lower = trimmed.to_ascii_lowercase();
|
json!({
|
||||||
if lower.ends_with("[1m]") {
|
"name": spec.name,
|
||||||
trimmed[..trimmed.len().saturating_sub("[1M]".len())]
|
"supports1m": true,
|
||||||
.trim_end()
|
})
|
||||||
.to_string()
|
|
||||||
} else {
|
} else {
|
||||||
trimmed.to_string()
|
Value::String(spec.name.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_one_m_context_suffix(model: &str) -> bool {
|
|
||||||
model.trim().to_ascii_lowercase().ends_with("[1m]")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desktop_model_id(model_id: &str, supports_1m: bool) -> String {
|
|
||||||
let normalized = strip_one_m_context_suffix(model_id);
|
|
||||||
if supports_1m {
|
|
||||||
format!("{normalized}{ONE_M_CONTEXT_SUFFIX}")
|
|
||||||
} else {
|
|
||||||
normalized
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn upstream_model_id(model_id: &str, supports_1m: bool) -> String {
|
|
||||||
desktop_model_id(model_id, supports_1m)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_or_create_gateway_token(db: &Database) -> Result<String, AppError> {
|
pub fn get_or_create_gateway_token(db: &Database) -> Result<String, AppError> {
|
||||||
if let Some(token) = db.get_setting(GATEWAY_TOKEN_SETTING_KEY)? {
|
if let Some(token) = db.get_setting(GATEWAY_TOKEN_SETTING_KEY)? {
|
||||||
let trimmed = token.trim();
|
let trimmed = token.trim();
|
||||||
@@ -351,7 +339,7 @@ pub fn validate_direct_provider(provider: &Provider) -> Result<(), AppError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
direct_inference_model_ids(provider)?;
|
direct_inference_model_specs(provider)?;
|
||||||
direct_gateway_credentials(provider)?;
|
direct_gateway_credentials(provider)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -452,7 +440,7 @@ pub fn validate_provider(provider: &Provider) -> Result<(), AppError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn direct_inference_model_ids(provider: &Provider) -> Result<Vec<String>, AppError> {
|
fn direct_inference_model_specs(provider: &Provider) -> Result<Vec<InferenceModelSpec>, AppError> {
|
||||||
let Some(routes) = provider
|
let Some(routes) = provider
|
||||||
.meta
|
.meta
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -463,23 +451,32 @@ pub fn direct_inference_model_ids(provider: &Provider) -> Result<Vec<String>, Ap
|
|||||||
|
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
for (route_id, route) in routes {
|
for (route_id, route) in routes {
|
||||||
let supports_1m = route.supports_1m.unwrap_or(false) || has_one_m_context_suffix(route_id);
|
let supports_1m = route.supports_1m.unwrap_or(false);
|
||||||
let route_id = strip_one_m_context_suffix(route_id);
|
let route_id = route_id.trim();
|
||||||
if route_id.is_empty() {
|
if route_id.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !is_claude_safe_model_id(&route_id) {
|
if !is_claude_safe_model_id(route_id) {
|
||||||
return Err(AppError::localized(
|
return Err(AppError::localized(
|
||||||
"claude_desktop.provider.route_invalid",
|
"claude_desktop.provider.route_invalid",
|
||||||
format!("Claude Desktop 直连模型必须使用 claude-* 或 anthropic/claude-* 名称: {route_id}"),
|
format!("Claude Desktop 直连模型必须使用 claude-* 或 anthropic/claude-* 名称: {route_id}"),
|
||||||
format!("Claude Desktop direct model must use a claude-* or anthropic/claude-* name: {route_id}"),
|
format!("Claude Desktop direct model must use a claude-* or anthropic/claude-* name: {route_id}"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
result.push(desktop_model_id(&route_id, supports_1m));
|
result.push(InferenceModelSpec {
|
||||||
|
name: route_id.to_string(),
|
||||||
|
supports_1m,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
result.sort();
|
// Sort supports_1m=true first within each name so the subsequent dedup_by
|
||||||
result.dedup();
|
// (which keeps the first occurrence) preserves the 1M-capable variant.
|
||||||
|
result.sort_by(|a, b| {
|
||||||
|
a.name
|
||||||
|
.cmp(&b.name)
|
||||||
|
.then_with(|| b.supports_1m.cmp(&a.supports_1m))
|
||||||
|
});
|
||||||
|
result.dedup_by(|a, b| a.name == b.name);
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,13 +495,13 @@ pub fn proxy_model_routes(provider: &Provider) -> Result<Vec<ResolvedModelRoute>
|
|||||||
|
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
for (route_id, route) in routes {
|
for (route_id, route) in routes {
|
||||||
let supports_1m = route.supports_1m.unwrap_or(false) || has_one_m_context_suffix(route_id);
|
let supports_1m = route.supports_1m.unwrap_or(false);
|
||||||
let route_id = strip_one_m_context_suffix(route_id);
|
let route_id = route_id.trim();
|
||||||
let upstream_model = route.model.trim();
|
let upstream_model = route.model.trim();
|
||||||
if route_id.is_empty() || upstream_model.is_empty() {
|
if route_id.is_empty() || upstream_model.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !is_claude_safe_model_id(&route_id) {
|
if !is_claude_safe_model_id(route_id) {
|
||||||
return Err(AppError::localized(
|
return Err(AppError::localized(
|
||||||
"claude_desktop.provider.route_invalid",
|
"claude_desktop.provider.route_invalid",
|
||||||
format!("Claude Desktop 模型路由必须使用 claude-* 或 anthropic/claude-* 名称: {route_id}"),
|
format!("Claude Desktop 模型路由必须使用 claude-* 或 anthropic/claude-* 名称: {route_id}"),
|
||||||
@@ -512,8 +509,8 @@ pub fn proxy_model_routes(provider: &Provider) -> Result<Vec<ResolvedModelRoute>
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
result.push(ResolvedModelRoute {
|
result.push(ResolvedModelRoute {
|
||||||
route_id: desktop_model_id(&route_id, supports_1m),
|
route_id: route_id.to_string(),
|
||||||
upstream_model: upstream_model_id(upstream_model, supports_1m),
|
upstream_model: upstream_model.to_string(),
|
||||||
supports_1m,
|
supports_1m,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -537,7 +534,7 @@ pub fn model_list_response(provider: &Provider) -> Result<Value, AppError> {
|
|||||||
let data: Vec<Value> = routes
|
let data: Vec<Value> = routes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|route| {
|
.map(|route| {
|
||||||
let model_id = desktop_model_id(&route.route_id, route.supports_1m);
|
let model_id = route.route_id.clone();
|
||||||
let mut item = json!({
|
let mut item = json!({
|
||||||
"type": "model",
|
"type": "model",
|
||||||
"id": model_id,
|
"id": model_id,
|
||||||
@@ -584,12 +581,7 @@ pub fn map_proxy_request_model(mut body: Value, provider: &Provider) -> Result<V
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let routes = proxy_model_routes(provider)?;
|
let routes = proxy_model_routes(provider)?;
|
||||||
let route = routes.iter().find(|r| r.route_id == requested).or_else(|| {
|
let route = routes.iter().find(|r| r.route_id == requested);
|
||||||
let base = strip_one_m_context_suffix(&requested);
|
|
||||||
routes
|
|
||||||
.iter()
|
|
||||||
.find(|r| strip_one_m_context_suffix(&r.route_id) == base)
|
|
||||||
});
|
|
||||||
let Some(route) = route else {
|
let Some(route) = route else {
|
||||||
return Err(AppError::localized(
|
return Err(AppError::localized(
|
||||||
"claude_desktop.provider.route_unknown",
|
"claude_desktop.provider.route_unknown",
|
||||||
@@ -659,22 +651,25 @@ fn apply_provider_to_paths_inner(
|
|||||||
let profile = match provider_mode(provider) {
|
let profile = match provider_mode(provider) {
|
||||||
ClaudeDesktopMode::Direct => {
|
ClaudeDesktopMode::Direct => {
|
||||||
let credentials = direct_gateway_credentials(provider)?;
|
let credentials = direct_gateway_credentials(provider)?;
|
||||||
let model_ids = direct_inference_model_ids(provider)?;
|
let model_specs = direct_inference_model_specs(provider)?;
|
||||||
build_gateway_profile(
|
build_gateway_profile(
|
||||||
&credentials.base_url,
|
&credentials.base_url,
|
||||||
&credentials.api_key,
|
&credentials.api_key,
|
||||||
(!model_ids.is_empty()).then_some(model_ids.as_slice()),
|
(!model_specs.is_empty()).then_some(model_specs.as_slice()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ClaudeDesktopMode::Proxy => {
|
ClaudeDesktopMode::Proxy => {
|
||||||
let base_url = proxy_gateway_base_url_from_db(db)?;
|
let base_url = proxy_gateway_base_url_from_db(db)?;
|
||||||
let api_key = get_or_create_gateway_token(db)?;
|
let api_key = get_or_create_gateway_token(db)?;
|
||||||
let routes = proxy_model_routes(provider)?;
|
let routes = proxy_model_routes(provider)?;
|
||||||
let model_ids = routes
|
let model_specs = routes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|route| desktop_model_id(&route.route_id, route.supports_1m))
|
.map(|route| InferenceModelSpec {
|
||||||
|
name: route.route_id.clone(),
|
||||||
|
supports_1m: route.supports_1m,
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
build_gateway_profile(&base_url, &api_key, Some(model_ids.as_slice()))
|
build_gateway_profile(&base_url, &api_key, Some(model_specs.as_slice()))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -699,7 +694,11 @@ fn restore_official_at_paths_inner(paths: &ClaudeDesktopPaths) -> Result<(), App
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_gateway_profile(base_url: &str, api_key: &str, model_ids: Option<&[String]>) -> Value {
|
fn build_gateway_profile(
|
||||||
|
base_url: &str,
|
||||||
|
api_key: &str,
|
||||||
|
model_specs: Option<&[InferenceModelSpec]>,
|
||||||
|
) -> Value {
|
||||||
let mut profile = json!({
|
let mut profile = json!({
|
||||||
"disableDeploymentModeChooser": true,
|
"disableDeploymentModeChooser": true,
|
||||||
"inferenceGatewayApiKey": api_key,
|
"inferenceGatewayApiKey": api_key,
|
||||||
@@ -708,13 +707,9 @@ fn build_gateway_profile(base_url: &str, api_key: &str, model_ids: Option<&[Stri
|
|||||||
"inferenceProvider": "gateway"
|
"inferenceProvider": "gateway"
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(model_ids) = model_ids {
|
if let Some(model_specs) = model_specs {
|
||||||
profile["inferenceModels"] = Value::Array(
|
profile["inferenceModels"] =
|
||||||
model_ids
|
Value::Array(model_specs.iter().map(inference_model_json).collect());
|
||||||
.iter()
|
|
||||||
.map(|model_id| Value::String(model_id.clone()))
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
profile
|
profile
|
||||||
@@ -1160,7 +1155,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
profile["inferenceModels"],
|
profile["inferenceModels"],
|
||||||
json!(["claude-deepseek-chat [1M]"])
|
json!([{ "name": "claude-deepseek-chat", "supports1m": true }])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1186,7 +1181,7 @@ mod tests {
|
|||||||
.starts_with("ccs-"));
|
.starts_with("ccs-"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
profile["inferenceModels"],
|
profile["inferenceModels"],
|
||||||
json!(["claude-sonnet-4-6 [1M]"])
|
json!([{ "name": "claude-sonnet-4-6", "supports1m": true }])
|
||||||
);
|
);
|
||||||
assert!(!profile.to_string().contains("kimi-k2"));
|
assert!(!profile.to_string().contains("kimi-k2"));
|
||||||
}
|
}
|
||||||
@@ -1219,14 +1214,15 @@ mod tests {
|
|||||||
let provider = proxy_provider("proxy");
|
let provider = proxy_provider("proxy");
|
||||||
|
|
||||||
let mapped = map_proxy_request_model(
|
let mapped = map_proxy_request_model(
|
||||||
json!({"model": "claude-sonnet-4-6 [1M]", "messages": []}),
|
json!({"model": "claude-sonnet-4-6", "messages": []}),
|
||||||
&provider,
|
&provider,
|
||||||
)
|
)
|
||||||
.expect("map route");
|
.expect("map route");
|
||||||
assert_eq!(mapped["model"], json!("kimi-k2 [1M]"));
|
assert_eq!(mapped["model"], json!("kimi-k2"));
|
||||||
|
|
||||||
let models = model_list_response(&provider).expect("model list");
|
let models = model_list_response(&provider).expect("model list");
|
||||||
assert_eq!(models["data"][0]["id"], json!("claude-sonnet-4-6 [1M]"));
|
assert_eq!(models["data"][0]["id"], json!("claude-sonnet-4-6"));
|
||||||
|
assert_eq!(models["data"][0]["supports1m"], json!(true));
|
||||||
|
|
||||||
let err = map_proxy_request_model(json!({"model": "claude-opus-4-7"}), &provider)
|
let err = map_proxy_request_model(json!({"model": "claude-opus-4-7"}), &provider)
|
||||||
.expect_err("unknown route should fail");
|
.expect_err("unknown route should fail");
|
||||||
@@ -1234,29 +1230,22 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn claude_desktop_proxy_maps_route_without_1m_suffix() {
|
fn claude_desktop_proxy_rejects_1m_suffix_route() {
|
||||||
let provider = proxy_provider("proxy");
|
let provider = proxy_provider("proxy");
|
||||||
|
|
||||||
let mapped = map_proxy_request_model(
|
let err = map_proxy_request_model(
|
||||||
json!({"model": "claude-sonnet-4-6", "messages": []}),
|
json!({"model": "claude-sonnet-4-6 [1M]", "messages": []}),
|
||||||
&provider,
|
&provider,
|
||||||
)
|
)
|
||||||
.expect("base name should fallback-match the [1M] route");
|
.expect_err("1M suffix route should not be accepted");
|
||||||
assert_eq!(mapped["model"], json!("kimi-k2 [1M]"));
|
assert!(err.to_string().contains("claude-sonnet-4-6 [1M]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn claude_desktop_one_m_suffix_normalization_is_case_and_space_tolerant() {
|
fn claude_desktop_rejects_1m_suffix_as_model_id() {
|
||||||
assert!(is_claude_safe_model_id("claude-sonnet-4-6 [1m]"));
|
assert!(!is_claude_safe_model_id("claude-sonnet-4-6 [1m]"));
|
||||||
assert!(is_claude_safe_model_id(" claude-sonnet-4-6 [1M] "));
|
assert!(!is_claude_safe_model_id(" claude-sonnet-4-6 [1M] "));
|
||||||
assert_eq!(
|
assert!(is_claude_safe_model_id(" claude-sonnet-4-6 "));
|
||||||
strip_one_m_context_suffix(" claude-sonnet-4-6 [1m] "),
|
|
||||||
"claude-sonnet-4-6"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
desktop_model_id(" claude-sonnet-4-6 [1m] ", true),
|
|
||||||
"claude-sonnet-4-6 [1M]"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ type RouteRowValues = Omit<RouteRow, "rowId">;
|
|||||||
|
|
||||||
const CLAUDE_ROUTE_PREFIX = "claude-";
|
const CLAUDE_ROUTE_PREFIX = "claude-";
|
||||||
const ANTHROPIC_CLAUDE_ROUTE_PREFIX = "anthropic/claude-";
|
const ANTHROPIC_CLAUDE_ROUTE_PREFIX = "anthropic/claude-";
|
||||||
|
const LEGACY_ONE_M_MARKER = "[1m]";
|
||||||
|
|
||||||
function envString(
|
function envString(
|
||||||
settingsConfig: Record<string, unknown> | undefined,
|
settingsConfig: Record<string, unknown> | undefined,
|
||||||
@@ -188,11 +189,10 @@ function initialRouteRows(
|
|||||||
|
|
||||||
function isClaudeSafeRoute(route: string) {
|
function isClaudeSafeRoute(route: string) {
|
||||||
const normalized = route.trim().toLowerCase();
|
const normalized = route.trim().toLowerCase();
|
||||||
return (
|
if (normalized.includes(LEGACY_ONE_M_MARKER)) return false;
|
||||||
(normalized.startsWith(CLAUDE_ROUTE_PREFIX) &&
|
return [CLAUDE_ROUTE_PREFIX, ANTHROPIC_CLAUDE_ROUTE_PREFIX].some(
|
||||||
normalized.length > CLAUDE_ROUTE_PREFIX.length) ||
|
(prefix) =>
|
||||||
(normalized.startsWith(ANTHROPIC_CLAUDE_ROUTE_PREFIX) &&
|
normalized.startsWith(prefix) && normalized.length > prefix.length,
|
||||||
normalized.length > ANTHROPIC_CLAUDE_ROUTE_PREFIX.length)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,12 +825,12 @@ export function ClaudeDesktopProviderForm({
|
|||||||
<p className="text-xs leading-relaxed text-muted-foreground">
|
<p className="text-xs leading-relaxed text-muted-foreground">
|
||||||
{t("claudeDesktop.routeMapHint", {
|
{t("claudeDesktop.routeMapHint", {
|
||||||
defaultValue:
|
defaultValue:
|
||||||
"左侧决定 Claude Desktop 模型菜单里的可见模型 ID,实际写入为 claude-*;右侧填写供应商实际请求的模型名。",
|
"左侧决定 Claude Desktop 模型菜单里的可见模型 ID;1M 只控制是否向 Claude Desktop 声明支持 1M 上下文,实际请求模型按右侧填写内容发送。",
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hidden grid-cols-[1fr_1fr_92px_36px] gap-2 px-1 text-xs font-medium text-muted-foreground md:grid">
|
<div className="hidden grid-cols-[1fr_1fr_116px_36px] gap-2 px-1 text-xs font-medium text-muted-foreground md:grid">
|
||||||
<span>
|
<span>
|
||||||
{t("claudeDesktop.routeModelLabel", {
|
{t("claudeDesktop.routeModelLabel", {
|
||||||
defaultValue: "Desktop 显示模型",
|
defaultValue: "Desktop 显示模型",
|
||||||
@@ -843,7 +843,7 @@ export function ClaudeDesktopProviderForm({
|
|||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
{t("claudeDesktop.supports1mLabel", {
|
{t("claudeDesktop.supports1mLabel", {
|
||||||
defaultValue: "1M",
|
defaultValue: "声明支持 1M",
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
<span />
|
<span />
|
||||||
@@ -851,7 +851,7 @@ export function ClaudeDesktopProviderForm({
|
|||||||
{routes.map((route, index) => (
|
{routes.map((route, index) => (
|
||||||
<div
|
<div
|
||||||
key={route.rowId}
|
key={route.rowId}
|
||||||
className="grid grid-cols-1 gap-2 md:grid-cols-[1fr_1fr_92px_36px]"
|
className="grid grid-cols-1 gap-2 md:grid-cols-[1fr_1fr_116px_36px]"
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="inline-flex h-9 items-center rounded-l-md border border-r-0 border-input bg-muted px-3 text-sm text-muted-foreground">
|
<span className="inline-flex h-9 items-center rounded-l-md border border-r-0 border-input bg-muted px-3 text-sm text-muted-foreground">
|
||||||
@@ -896,7 +896,9 @@ export function ClaudeDesktopProviderForm({
|
|||||||
updateRoute(index, { supports1m: checked === true })
|
updateRoute(index, { supports1m: checked === true })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
1M
|
{t("claudeDesktop.supports1mShort", {
|
||||||
|
defaultValue: "1M",
|
||||||
|
})}
|
||||||
</label>
|
</label>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -975,7 +977,7 @@ export function ClaudeDesktopProviderForm({
|
|||||||
{routes.map((route, index) => (
|
{routes.map((route, index) => (
|
||||||
<div
|
<div
|
||||||
key={route.rowId}
|
key={route.rowId}
|
||||||
className="grid grid-cols-1 gap-2 md:grid-cols-[1fr_92px_36px]"
|
className="grid grid-cols-1 gap-2 md:grid-cols-[1fr_116px_36px]"
|
||||||
>
|
>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<Input
|
<Input
|
||||||
@@ -1004,7 +1006,9 @@ export function ClaudeDesktopProviderForm({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
1M
|
{t("claudeDesktop.supports1mShort", {
|
||||||
|
defaultValue: "1M",
|
||||||
|
})}
|
||||||
</label>
|
</label>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -181,13 +181,14 @@
|
|||||||
"modelMappingOffHint": "Use this when the provider already exposes and accepts claude-* / anthropic/claude-* model IDs through Anthropic Messages. Claude Desktop connects to the provider directly.",
|
"modelMappingOffHint": "Use this when the provider already exposes and accepts claude-* / anthropic/claude-* model IDs through Anthropic Messages. Claude Desktop connects to the provider directly.",
|
||||||
"modelMappingOnHint": "Claude Desktop currently restricts model IDs. If your provider offers non-Claude models, enable this switch and keep local routing running while in use.",
|
"modelMappingOnHint": "Claude Desktop currently restricts model IDs. If your provider offers non-Claude models, enable this switch and keep local routing running while in use.",
|
||||||
"routeMapTitle": "Model mapping",
|
"routeMapTitle": "Model mapping",
|
||||||
"routeMapHint": "The left field controls the Claude Desktop-visible model ID and is written as claude-*. Enter the provider's actual requested model on the right.",
|
"routeMapHint": "The left field controls the Claude Desktop-visible model ID. 1M only declares 1M context support to Claude Desktop; requested model IDs are sent upstream exactly as entered on the right.",
|
||||||
"routeModelLabel": "Desktop display model",
|
"routeModelLabel": "Desktop display model",
|
||||||
"upstreamModelLabel": "Requested model",
|
"upstreamModelLabel": "Requested model",
|
||||||
"supports1mLabel": "1M",
|
"supports1mLabel": "Declare 1M",
|
||||||
|
"supports1mShort": "1M",
|
||||||
"directModelListTitle": "Manually specify Claude Desktop models (advanced, optional)",
|
"directModelListTitle": "Manually specify Claude Desktop models (advanced, optional)",
|
||||||
"directModelListCollapsedHint": "Native Claude model providers usually do not need this. Claude Desktop will fetch /v1/models automatically.",
|
"directModelListCollapsedHint": "Native Claude model providers usually do not need this. Claude Desktop will fetch /v1/models automatically.",
|
||||||
"directModelListHint": "Only fill this when the provider's /v1/models is unavailable or does not return Claude Desktop-safe claude-* model IDs. Checking 1M writes the [1M] marker after the model ID.",
|
"directModelListHint": "Only fill this when the provider's /v1/models is unavailable or does not return Claude Desktop-safe claude-* model IDs. Checking 1M declares 1M context support to Claude Desktop.",
|
||||||
"directModelInvalid": "Direct models must use claude-* / anthropic/claude-* model IDs",
|
"directModelInvalid": "Direct models must use claude-* / anthropic/claude-* model IDs",
|
||||||
"addModel": "Add model",
|
"addModel": "Add model",
|
||||||
"addRoute": "Add model",
|
"addRoute": "Add model",
|
||||||
|
|||||||
@@ -181,13 +181,14 @@
|
|||||||
"modelMappingOffHint": "プロバイダーが Anthropic Messages で claude-* / anthropic/claude-* のモデル ID を公開し、そのまま受け付ける場合に使います。Claude Desktop はプロバイダーへ直接接続します。",
|
"modelMappingOffHint": "プロバイダーが Anthropic Messages で claude-* / anthropic/claude-* のモデル ID を公開し、そのまま受け付ける場合に使います。Claude Desktop はプロバイダーへ直接接続します。",
|
||||||
"modelMappingOnHint": "Claude Desktop は現在モデル ID を制限しています。お使いのプロバイダーが Claude 系列以外のモデルを提供する場合、このスイッチを有効にし、使用中はローカルルーティングを起動したままにしてください。",
|
"modelMappingOnHint": "Claude Desktop は現在モデル ID を制限しています。お使いのプロバイダーが Claude 系列以外のモデルを提供する場合、このスイッチを有効にし、使用中はローカルルーティングを起動したままにしてください。",
|
||||||
"routeMapTitle": "モデルマッピング",
|
"routeMapTitle": "モデルマッピング",
|
||||||
"routeMapHint": "左側は Claude Desktop のモデルメニューに表示されるモデル ID を指定し、claude-* として書き込まれます。右側にはプロバイダーへ実際にリクエストするモデル名を入力してください。",
|
"routeMapHint": "左側は Claude Desktop のモデルメニューに表示されるモデル ID です。1M は Claude Desktop に 1M コンテキスト対応を宣言するだけで、上流へのリクエストモデル ID は右側の入力どおりに送信されます。",
|
||||||
"routeModelLabel": "Desktop 表示モデル",
|
"routeModelLabel": "Desktop 表示モデル",
|
||||||
"upstreamModelLabel": "リクエストモデル",
|
"upstreamModelLabel": "リクエストモデル",
|
||||||
"supports1mLabel": "1M",
|
"supports1mLabel": "1M 対応を宣言",
|
||||||
|
"supports1mShort": "1M",
|
||||||
"directModelListTitle": "Claude Desktop モデルを手動指定(高度・任意)",
|
"directModelListTitle": "Claude Desktop モデルを手動指定(高度・任意)",
|
||||||
"directModelListCollapsedHint": "ネイティブ Claude モデルのプロバイダーでは通常不要です。Claude Desktop が /v1/models を自動取得します。",
|
"directModelListCollapsedHint": "ネイティブ Claude モデルのプロバイダーでは通常不要です。Claude Desktop が /v1/models を自動取得します。",
|
||||||
"directModelListHint": "プロバイダーの /v1/models が使えない、または Claude Desktop が認識できる claude-* モデル ID を返さない場合だけ入力してください。1M をオンにするとモデル ID の後ろに [1M] マーカーを書き込みます。",
|
"directModelListHint": "プロバイダーの /v1/models が使えない、または Claude Desktop が認識できる claude-* モデル ID を返さない場合だけ入力してください。1M をオンにすると Claude Desktop に 1M コンテキスト対応を宣言します。",
|
||||||
"directModelInvalid": "直結モデルは claude-* / anthropic/claude-* のモデル ID を使う必要があります",
|
"directModelInvalid": "直結モデルは claude-* / anthropic/claude-* のモデル ID を使う必要があります",
|
||||||
"addModel": "モデルを追加",
|
"addModel": "モデルを追加",
|
||||||
"addRoute": "モデルを追加",
|
"addRoute": "モデルを追加",
|
||||||
|
|||||||
@@ -181,13 +181,14 @@
|
|||||||
"modelMappingOffHint": "适合供应商已经暴露并接受 claude-* / anthropic/claude-* 模型名的 Anthropic Messages API;请求会由 Claude Desktop 直连供应商。",
|
"modelMappingOffHint": "适合供应商已经暴露并接受 claude-* / anthropic/claude-* 模型名的 Anthropic Messages API;请求会由 Claude Desktop 直连供应商。",
|
||||||
"modelMappingOnHint": "Claude Desktop 目前对模型 ID 进行了限制,如果您的供应商提供的模型不是 Claude 系列模型,则需要打开本开关,并在使用过程中保持本地路由开启。",
|
"modelMappingOnHint": "Claude Desktop 目前对模型 ID 进行了限制,如果您的供应商提供的模型不是 Claude 系列模型,则需要打开本开关,并在使用过程中保持本地路由开启。",
|
||||||
"routeMapTitle": "模型映射",
|
"routeMapTitle": "模型映射",
|
||||||
"routeMapHint": "左侧决定 Claude Desktop 模型菜单里的可见模型 ID,实际写入为 claude-*;右侧填写供应商实际请求的模型名。",
|
"routeMapHint": "左侧决定 Claude Desktop 模型菜单里的可见模型 ID;1M 只控制是否向 Claude Desktop 声明支持 1M 上下文,实际请求模型按右侧填写内容发送。",
|
||||||
"routeModelLabel": "Desktop 显示模型",
|
"routeModelLabel": "Desktop 显示模型",
|
||||||
"upstreamModelLabel": "实际请求模型",
|
"upstreamModelLabel": "实际请求模型",
|
||||||
"supports1mLabel": "1M",
|
"supports1mLabel": "声明支持 1M",
|
||||||
|
"supports1mShort": "1M",
|
||||||
"directModelListTitle": "手动指定 Claude Desktop 模型列表(高级,可选)",
|
"directModelListTitle": "手动指定 Claude Desktop 模型列表(高级,可选)",
|
||||||
"directModelListCollapsedHint": "原生 Claude 模型供应商通常不用填写,Claude Desktop 会自动读取 /v1/models。",
|
"directModelListCollapsedHint": "原生 Claude 模型供应商通常不用填写,Claude Desktop 会自动读取 /v1/models。",
|
||||||
"directModelListHint": "仅当供应商的 /v1/models 不可用或没有返回 Claude Desktop 可识别的 claude-* 模型名时填写;勾选 1M 会在模型名后写入 [1M] 标记。",
|
"directModelListHint": "仅当供应商的 /v1/models 不可用或没有返回 Claude Desktop 可识别的 claude-* 模型名时填写;勾选 1M 会向 Claude Desktop 声明支持 1M 上下文。",
|
||||||
"directModelInvalid": "直连模型必须使用 claude-* / anthropic/claude-* 模型名",
|
"directModelInvalid": "直连模型必须使用 claude-* / anthropic/claude-* 模型名",
|
||||||
"addModel": "添加模型",
|
"addModel": "添加模型",
|
||||||
"addRoute": "添加模型",
|
"addRoute": "添加模型",
|
||||||
|
|||||||
Reference in New Issue
Block a user