Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions apps/src-tauri/src/commands/apikey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,9 @@ pub async fn service_model_source_mapping_save(
#[tauri::command]
pub async fn service_model_source_mapping_delete(
addr: Option<String>,
id: String,
payload: serde_json::Value,
) -> Result<serde_json::Value, String> {
let params = serde_json::json!({ "id": id });
rpc_call_in_background("apikey/modelSourceMappingDelete", addr, Some(params)).await
rpc_call_in_background("apikey/modelSourceMappingDelete", addr, Some(payload)).await
}

/// 函数 `service_apikey_usage_stats`
Expand Down
9 changes: 8 additions & 1 deletion apps/src/app/models/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,14 @@ export default function ModelsPage() {
size="icon"
aria-label={t("删除映射")}
disabled={isRoutingSaving}
onClick={() => void deleteSourceMapping(mapping.id)}
onClick={() =>
void deleteSourceMapping(
mapping.id,
mapping.sourceKind,
mapping.sourceId,
mapping.upstreamModel,
)
}
>
<Unlink className="h-4 w-4" />
</Button>
Expand Down
22 changes: 18 additions & 4 deletions apps/src/hooks/useManagedModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,12 @@ export function useManagedModels() {
});

const sourceMappingDeleteMutation = useMutation({
mutationFn: (mappingId: string) =>
accountClient.deleteManagedModelSourceMapping(mappingId),
mutationFn: (params: {
id: string;
sourceKind: string;
sourceId: string;
upstreamModel: string;
}) => accountClient.deleteManagedModelSourceMapping(params),
onSuccess: async () => {
await reloadManagedRouting();
await invalidateAll();
Expand Down Expand Up @@ -433,9 +437,19 @@ export function useManagedModels() {
if (!ensureServiceReady("保存模型映射")) return null;
return sourceMappingMutation.mutateAsync(params);
},
deleteSourceMapping: async (mappingId: string) => {
deleteSourceMapping: async (
mappingId: string,
sourceKind: string,
sourceId: string,
upstreamModel: string,
) => {
if (!ensureServiceReady("删除模型映射")) return false;
await sourceMappingDeleteMutation.mutateAsync(mappingId);
await sourceMappingDeleteMutation.mutateAsync({
id: mappingId,
sourceKind,
sourceId,
upstreamModel,
});
return true;
},
isRefreshing: refreshMutation.isPending,
Expand Down
9 changes: 7 additions & 2 deletions apps/src/lib/api/account-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,8 +838,13 @@ export const accountClient = {
if (!item) throw new Error("模型映射保存结果为空");
return item;
},
deleteManagedModelSourceMapping: (id: string) =>
invoke("service_model_source_mapping_delete", withAddr({ id })),
deleteManagedModelSourceMapping: (params: {
id: string;
sourceKind: string;
sourceId: string;
upstreamModel: string;
}) =>
invoke("service_model_source_mapping_delete", withAddr({ payload: params })),
async saveManagedModel(params: ManagedModelPayload): Promise<ManagedModelInfo> {
const payload = {
previousSlug: params.previousSlug || null,
Expand Down
1 change: 1 addition & 0 deletions apps/src/lib/api/transport-web-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ export function createWebCommandMap(
},
service_model_source_mapping_delete: {
rpcMethod: "apikey/modelSourceMappingDelete",
mapParams: (params) => asRecord(asRecord(params)?.payload) ?? {},
},
service_apikey_read_secret: {
rpcMethod: "apikey/readSecret",
Expand Down
11 changes: 11 additions & 0 deletions crates/core/migrations/065_model_source_mapping_preferences.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS model_source_mapping_preferences (
source_kind TEXT NOT NULL,
source_id TEXT NOT NULL,
upstream_model TEXT NOT NULL,
preference TEXT NOT NULL CHECK (preference IN ('unlinked', 'disabled')),
updated_at INTEGER NOT NULL,
PRIMARY KEY (source_kind, source_id, upstream_model)
);

CREATE INDEX IF NOT EXISTS idx_model_source_mapping_preferences_source
ON model_source_mapping_preferences(source_kind, source_id);
5 changes: 5 additions & 0 deletions crates/core/src/storage/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,11 @@ impl Storage {
WHERE source_kind = 'openai_account' AND source_id = ?1",
[account_id],
)?;
tx.execute(
"DELETE FROM model_source_mapping_preferences
WHERE source_kind = 'openai_account' AND source_id = ?1",
[account_id],
)?;
tx.execute("DELETE FROM accounts WHERE id = ?1", [account_id])?;
tx.commit()?;
Ok(())
Expand Down
24 changes: 20 additions & 4 deletions crates/core/src/storage/aggregate_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,16 +351,32 @@ impl Storage {
/// # 返回
/// 返回函数执行结果
pub fn delete_aggregate_api(&self, api_id: &str) -> Result<()> {
self.conn.execute(
let tx = self.conn.unchecked_transaction()?;
tx.execute(
"DELETE FROM aggregate_api_balance_secrets WHERE aggregate_api_id = ?1",
[api_id],
)?;
self.conn.execute(
tx.execute(
"DELETE FROM aggregate_api_secrets WHERE aggregate_api_id = ?1",
[api_id],
)?;
self.conn
.execute("DELETE FROM aggregate_apis WHERE id = ?1", [api_id])?;
tx.execute(
"DELETE FROM model_source_mapping_preferences
WHERE source_kind = 'aggregate_api' AND source_id = ?1",
[api_id],
)?;
tx.execute(
"DELETE FROM model_source_mappings
WHERE source_kind = 'aggregate_api' AND source_id = ?1",
[api_id],
)?;
tx.execute(
"DELETE FROM model_source_models
WHERE source_kind = 'aggregate_api' AND source_id = ?1",
[api_id],
)?;
tx.execute("DELETE FROM aggregate_apis WHERE id = ?1", [api_id])?;
tx.commit()?;
Ok(())
}

Expand Down
14 changes: 14 additions & 0 deletions crates/core/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ pub struct ModelSourceMapping {
pub updated_at: i64,
}

#[derive(Debug, Clone)]
pub struct ModelSourceMappingPreference {
pub source_kind: String,
pub source_id: String,
pub upstream_model: String,
pub preference: String,
pub updated_at: i64,
}

#[derive(Debug, Clone)]
pub struct AccountQuotaCapacityTemplate {
pub plan_type: String,
Expand Down Expand Up @@ -1007,6 +1016,11 @@ impl Storage {
"064_drop_gateway_error_logs",
include_str!("../../migrations/064_drop_gateway_error_logs.sql"),
)?;
self.apply_sql_or_compat_migration(
"065_model_source_mapping_preferences",
include_str!("../../migrations/065_model_source_mapping_preferences.sql"),
|s| s.ensure_model_source_tables(),
)?;
self.ensure_api_key_rotation_columns()?;
self.ensure_aggregate_apis_table()?;
self.ensure_aggregate_api_supplier_model_tables()?;
Expand Down
149 changes: 147 additions & 2 deletions crates/core/src/storage/model_sources.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{now_ts, ModelSourceMapping, ModelSourceModel, Storage};
use super::{now_ts, ModelSourceMapping, ModelSourceMappingPreference, ModelSourceModel, Storage};
use rusqlite::{params, OptionalExtension, Result, Row};

fn map_source_model(row: &Row<'_>) -> Result<ModelSourceModel> {
Expand Down Expand Up @@ -74,7 +74,17 @@ impl Storage {
CREATE INDEX IF NOT EXISTS idx_model_source_mappings_platform
ON model_source_mappings(platform_model_slug, enabled, priority DESC);
CREATE INDEX IF NOT EXISTS idx_model_source_mappings_source
ON model_source_mappings(source_kind, source_id, enabled);",
ON model_source_mappings(source_kind, source_id, enabled);
CREATE TABLE IF NOT EXISTS model_source_mapping_preferences (
source_kind TEXT NOT NULL,
source_id TEXT NOT NULL,
upstream_model TEXT NOT NULL,
preference TEXT NOT NULL CHECK (preference IN ('unlinked', 'disabled')),
updated_at INTEGER NOT NULL,
PRIMARY KEY (source_kind, source_id, upstream_model)
);
CREATE INDEX IF NOT EXISTS idx_model_source_mapping_preferences_source
ON model_source_mapping_preferences(source_kind, source_id);",
)
}

Expand Down Expand Up @@ -229,6 +239,13 @@ impl Storage {
AND discovery_kind = ?4",
params![&source_kind, &source_id, &upstream_model, &discovery_kind],
)?;
self.conn.execute(
"DELETE FROM model_source_mapping_preferences
WHERE source_kind = ?1
AND source_id = ?2
AND upstream_model = ?3",
params![&source_kind, &source_id, &upstream_model],
)?;
}
Ok(out)
}
Expand Down Expand Up @@ -340,6 +357,38 @@ impl Storage {
Ok(())
}

pub fn delete_model_source_mapping_with_unlink_preference(
&self,
id: &str,
source_kind: &str,
source_id: &str,
upstream_model: &str,
) -> Result<()> {
let id = normalize_text(id);
let source_kind = normalize_text(source_kind);
let source_id = normalize_text(source_id);
let upstream_model = normalize_text(upstream_model);
if source_kind.is_empty() || source_id.is_empty() || upstream_model.is_empty() {
return Ok(());
}
let tx = self.conn.unchecked_transaction()?;
tx.execute(
"INSERT INTO model_source_mapping_preferences
(source_kind, source_id, upstream_model, preference, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5)
ON CONFLICT(source_kind, source_id, upstream_model) DO UPDATE SET
preference = excluded.preference,
updated_at = excluded.updated_at",
params![&source_kind, &source_id, &upstream_model, "unlinked", now_ts()],
)?;
tx.execute(
"DELETE FROM model_source_mappings WHERE id = ?1",
params![&id],
)?;
tx.commit()?;
Ok(())
}

pub fn delete_model_source_mapping(&self, id: &str) -> Result<()> {
self.conn.execute(
"DELETE FROM model_source_mappings WHERE id = ?1",
Expand Down Expand Up @@ -368,4 +417,100 @@ impl Storage {
)?;
Ok(())
}

pub fn upsert_model_source_mapping_preference(
&self,
source_kind: &str,
source_id: &str,
upstream_model: &str,
preference: &str,
) -> Result<()> {
let source_kind = normalize_text(source_kind);
let source_id = normalize_text(source_id);
let upstream_model = normalize_text(upstream_model);
if source_kind.is_empty() || source_id.is_empty() || upstream_model.is_empty() {
return Ok(());
}
self.conn.execute(
"INSERT INTO model_source_mapping_preferences
(source_kind, source_id, upstream_model, preference, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5)
ON CONFLICT(source_kind, source_id, upstream_model) DO UPDATE SET
preference = excluded.preference,
updated_at = excluded.updated_at",
params![
&source_kind,
&source_id,
&upstream_model,
normalize_text(preference),
now_ts(),
],
)?;
Ok(())
}

pub fn delete_model_source_mapping_preference(
&self,
source_kind: &str,
source_id: &str,
upstream_model: &str,
) -> Result<()> {
self.conn.execute(
"DELETE FROM model_source_mapping_preferences
WHERE source_kind = ?1 AND source_id = ?2 AND upstream_model = ?3",
params![
normalize_text(source_kind),
normalize_text(source_id),
normalize_text(upstream_model),
],
)?;
Ok(())
}

pub fn delete_model_source_mapping_preferences_for_source(
&self,
source_kind: &str,
source_id: &str,
) -> Result<()> {
let source_kind = normalize_text(source_kind);
let source_id = normalize_text(source_id);
if source_kind.is_empty() || source_id.is_empty() {
return Ok(());
}
self.conn.execute(
"DELETE FROM model_source_mapping_preferences
WHERE source_kind = ?1 AND source_id = ?2",
params![&source_kind, &source_id],
)?;
Ok(())
}

pub fn list_model_source_mapping_preferences(
&self,
source_kind: &str,
source_id: &str,
) -> Result<Vec<ModelSourceMappingPreference>> {
let source_kind = normalize_text(source_kind);
let source_id = normalize_text(source_id);
if source_kind.is_empty() || source_id.is_empty() {
return Ok(Vec::new());
}
let mut stmt = self.conn.prepare(
"SELECT source_kind, source_id, upstream_model, preference, updated_at
FROM model_source_mapping_preferences
WHERE source_kind = ?1 AND source_id = ?2",
)?;
let rows = stmt.query_map(params![&source_kind, &source_id], map_preference)?;
rows.collect()
}
}

fn map_preference(row: &Row<'_>) -> Result<ModelSourceMappingPreference> {
Ok(ModelSourceMappingPreference {
source_kind: row.get(0)?,
source_id: row.get(1)?,
upstream_model: row.get(2)?,
preference: row.get(3)?,
updated_at: row.get(4)?,
})
}
2 changes: 1 addition & 1 deletion crates/core/tests/storage/migration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1180,7 +1180,7 @@ fn observability_storage_compaction_migration_rolls_up_and_prunes_legacy_rows()
"DELETE FROM schema_migrations WHERE version = '062_observability_storage_compaction'",
[],
)
.expect("remove 057 marker");
.expect("remove 062 marker");

storage
.conn
Expand Down
Loading