mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-08-09 02:54:37 +00:00
merge
This commit is contained in:
+257
-28
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
@@ -25,14 +26,31 @@ logger = get_logger(__name__)
|
||||
|
||||
admin_router = APIRouter(prefix="/admin", include_in_schema=False)
|
||||
|
||||
admin_sessions: dict[str, int] = {}
|
||||
ADMIN_SESSION_DURATION = 3600
|
||||
|
||||
|
||||
def require_admin_api(request: Request) -> None:
|
||||
auth_header = request.headers.get("Authorization")
|
||||
if auth_header and auth_header.startswith("Bearer "):
|
||||
token = auth_header.split(" ", 1)[1]
|
||||
expiry = admin_sessions.get(token)
|
||||
if expiry and expiry > int(datetime.now(timezone.utc).timestamp()):
|
||||
return
|
||||
|
||||
admin_cookie = request.cookies.get("admin_password")
|
||||
if not admin_cookie or admin_cookie != settings.admin_password:
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
|
||||
|
||||
def is_admin_authenticated(request: Request) -> bool:
|
||||
auth_header = request.headers.get("Authorization")
|
||||
if auth_header and auth_header.startswith("Bearer "):
|
||||
token = auth_header.split(" ", 1)[1]
|
||||
expiry = admin_sessions.get(token)
|
||||
if expiry and expiry > int(datetime.now(timezone.utc).timestamp()):
|
||||
return True
|
||||
|
||||
admin_cookie = request.cookies.get("admin_password")
|
||||
return bool(admin_cookie and admin_cookie == settings.admin_password)
|
||||
|
||||
@@ -187,6 +205,53 @@ async def initial_setup(request: Request, payload: SetupRequest) -> dict[str, ob
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
class AdminLoginRequest(BaseModel):
|
||||
password: str
|
||||
|
||||
|
||||
@admin_router.post("/api/login")
|
||||
async def admin_login(request: Request, payload: AdminLoginRequest) -> dict[str, object]:
|
||||
try:
|
||||
current = SettingsService.get()
|
||||
admin_pw = current.admin_password
|
||||
except Exception:
|
||||
admin_pw = os.getenv("ADMIN_PASSWORD", "")
|
||||
|
||||
if not admin_pw:
|
||||
raise HTTPException(status_code=500, detail="Admin password not configured")
|
||||
|
||||
if payload.password != admin_pw:
|
||||
raise HTTPException(status_code=401, detail="Invalid password")
|
||||
|
||||
token = secrets.token_urlsafe(32)
|
||||
expiry_timestamp = int(datetime.now(timezone.utc).timestamp()) + ADMIN_SESSION_DURATION
|
||||
admin_sessions[token] = expiry_timestamp
|
||||
|
||||
expired_tokens = [
|
||||
t for t, exp in admin_sessions.items()
|
||||
if exp <= int(datetime.now(timezone.utc).timestamp())
|
||||
]
|
||||
for t in expired_tokens:
|
||||
del admin_sessions[t]
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"token": token,
|
||||
"expires_in": ADMIN_SESSION_DURATION
|
||||
}
|
||||
|
||||
|
||||
@admin_router.post("/api/logout", dependencies=[Depends(require_admin_api)])
|
||||
async def admin_logout(request: Request) -> dict[str, object]:
|
||||
auth_header = request.headers.get("Authorization")
|
||||
if auth_header and auth_header.startswith("Bearer "):
|
||||
token = auth_header.split(" ", 1)[1]
|
||||
if token in admin_sessions:
|
||||
del admin_sessions[token]
|
||||
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
class WithdrawRequest(BaseModel):
|
||||
amount: int
|
||||
mint_url: str | None = None
|
||||
@@ -1378,6 +1443,198 @@ def models_page() -> str:
|
||||
)
|
||||
|
||||
|
||||
class ModelCreate(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
created: int
|
||||
context_length: int
|
||||
architecture: dict[str, object]
|
||||
pricing: dict[str, object]
|
||||
per_request_limits: dict[str, object] | None = None
|
||||
top_provider: dict[str, object] | None = None
|
||||
upstream_provider_id: int | None = None
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class ModelUpdate(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
created: int
|
||||
context_length: int
|
||||
architecture: dict[str, object]
|
||||
pricing: dict[str, object]
|
||||
per_request_limits: dict[str, object] | None = None
|
||||
top_provider: dict[str, object] | None = None
|
||||
upstream_provider_id: int | None = None
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
@admin_router.get("/api/models", dependencies=[Depends(require_admin_api)])
|
||||
async def get_all_models() -> list[dict[str, object]]:
|
||||
async with create_session() as session:
|
||||
models = await list_models(session=session, include_disabled=True)
|
||||
return [m.dict() for m in models]
|
||||
|
||||
|
||||
@admin_router.get("/api/models/{model_id:path}", dependencies=[Depends(require_admin_api)])
|
||||
async def get_model(model_id: str) -> dict[str, object]:
|
||||
async with create_session() as session:
|
||||
result = await session.exec(
|
||||
select(ModelRow).where(
|
||||
ModelRow.id == model_id,
|
||||
ModelRow.upstream_provider_id.is_(None)
|
||||
)
|
||||
)
|
||||
row = result.first()
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Model not found")
|
||||
return _row_to_model(row, apply_provider_fee=False).dict()
|
||||
|
||||
|
||||
@admin_router.post("/api/models", dependencies=[Depends(require_admin_api)])
|
||||
async def create_model(payload: ModelCreate) -> dict[str, object]:
|
||||
async with create_session() as session:
|
||||
exists = await session.get(ModelRow, (payload.id, None))
|
||||
if exists:
|
||||
raise HTTPException(
|
||||
status_code=409, detail="Model with this ID already exists"
|
||||
)
|
||||
|
||||
row = ModelRow(
|
||||
id=payload.id,
|
||||
name=payload.name,
|
||||
description=payload.description,
|
||||
created=int(payload.created),
|
||||
context_length=int(payload.context_length),
|
||||
architecture=json.dumps(payload.architecture),
|
||||
pricing=json.dumps(payload.pricing),
|
||||
sats_pricing=None,
|
||||
per_request_limits=(
|
||||
json.dumps(payload.per_request_limits)
|
||||
if payload.per_request_limits is not None
|
||||
else None
|
||||
),
|
||||
top_provider=(
|
||||
json.dumps(payload.top_provider) if payload.top_provider else None
|
||||
),
|
||||
upstream_provider_id=None,
|
||||
enabled=payload.enabled,
|
||||
)
|
||||
session.add(row)
|
||||
await session.commit()
|
||||
await session.refresh(row)
|
||||
|
||||
await refresh_model_maps()
|
||||
return _row_to_model(row, apply_provider_fee=False).dict()
|
||||
|
||||
|
||||
@admin_router.patch("/api/models/{model_id:path}", dependencies=[Depends(require_admin_api)])
|
||||
async def update_model(model_id: str, payload: ModelUpdate) -> dict[str, object]:
|
||||
if payload.id != model_id:
|
||||
raise HTTPException(status_code=400, detail="Path id does not match payload id")
|
||||
|
||||
async with create_session() as session:
|
||||
row = await session.get(ModelRow, (model_id, None))
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Model not found")
|
||||
|
||||
row.name = payload.name
|
||||
row.description = payload.description
|
||||
row.created = int(payload.created)
|
||||
row.context_length = int(payload.context_length)
|
||||
row.architecture = json.dumps(payload.architecture)
|
||||
row.pricing = json.dumps(payload.pricing)
|
||||
row.sats_pricing = None
|
||||
row.per_request_limits = (
|
||||
json.dumps(payload.per_request_limits)
|
||||
if payload.per_request_limits is not None
|
||||
else None
|
||||
)
|
||||
row.top_provider = (
|
||||
json.dumps(payload.top_provider) if payload.top_provider else None
|
||||
)
|
||||
row.enabled = payload.enabled
|
||||
|
||||
session.add(row)
|
||||
await session.commit()
|
||||
await session.refresh(row)
|
||||
|
||||
await refresh_model_maps()
|
||||
return _row_to_model(row, apply_provider_fee=False).dict()
|
||||
|
||||
|
||||
@admin_router.delete("/api/models/{model_id:path}", dependencies=[Depends(require_admin_api)])
|
||||
async def delete_model(model_id: str) -> dict[str, object]:
|
||||
async with create_session() as session:
|
||||
row = await session.get(ModelRow, (model_id, None))
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Model not found")
|
||||
await session.delete(row)
|
||||
await session.commit()
|
||||
await refresh_model_maps()
|
||||
return {"ok": True, "deleted_id": model_id}
|
||||
|
||||
|
||||
@admin_router.delete("/api/models", dependencies=[Depends(require_admin_api)])
|
||||
async def delete_all_models() -> dict[str, object]:
|
||||
async with create_session() as session:
|
||||
result = await session.exec(
|
||||
select(ModelRow).where(ModelRow.upstream_provider_id.is_(None))
|
||||
)
|
||||
rows = result.all()
|
||||
for row in rows:
|
||||
await session.delete(row)
|
||||
await session.commit()
|
||||
await refresh_model_maps()
|
||||
return {"ok": True, "deleted": len(rows)}
|
||||
|
||||
|
||||
class BatchModelsRequest(BaseModel):
|
||||
models: list[ModelCreate]
|
||||
|
||||
|
||||
@admin_router.post("/api/models/batch", dependencies=[Depends(require_admin_api)])
|
||||
async def batch_add_models(payload: BatchModelsRequest) -> dict[str, object]:
|
||||
async with create_session() as session:
|
||||
created_models = []
|
||||
for model_data in payload.models:
|
||||
exists = await session.get(ModelRow, (model_data.id, None))
|
||||
if exists:
|
||||
continue
|
||||
|
||||
row = ModelRow(
|
||||
id=model_data.id,
|
||||
name=model_data.name,
|
||||
description=model_data.description,
|
||||
created=int(model_data.created),
|
||||
context_length=int(model_data.context_length),
|
||||
architecture=json.dumps(model_data.architecture),
|
||||
pricing=json.dumps(model_data.pricing),
|
||||
sats_pricing=None,
|
||||
per_request_limits=(
|
||||
json.dumps(model_data.per_request_limits)
|
||||
if model_data.per_request_limits is not None
|
||||
else None
|
||||
),
|
||||
top_provider=(
|
||||
json.dumps(model_data.top_provider)
|
||||
if model_data.top_provider
|
||||
else None
|
||||
),
|
||||
upstream_provider_id=None,
|
||||
enabled=model_data.enabled,
|
||||
)
|
||||
session.add(row)
|
||||
created_models.append(model_data.id)
|
||||
|
||||
await session.commit()
|
||||
|
||||
await refresh_model_maps()
|
||||
return {"ok": True, "created": len(created_models), "model_ids": created_models}
|
||||
|
||||
|
||||
@admin_router.get("/models", response_class=HTMLResponse)
|
||||
async def admin_models(request: Request) -> str:
|
||||
if is_admin_authenticated(request):
|
||||
@@ -2280,20 +2537,6 @@ async def admin_upstream_providers(request: Request) -> str:
|
||||
return admin_auth()
|
||||
|
||||
|
||||
class ModelCreate(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
created: int
|
||||
context_length: int
|
||||
architecture: dict[str, object]
|
||||
pricing: dict[str, object]
|
||||
per_request_limits: dict[str, object] | None = None
|
||||
top_provider: dict[str, object] | None = None
|
||||
upstream_provider_id: int | None = None
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
@admin_router.post(
|
||||
"/api/upstream-providers/{provider_id}/models",
|
||||
dependencies=[Depends(require_admin_api)],
|
||||
@@ -2363,20 +2606,6 @@ async def get_provider_model(provider_id: int, model_id: str) -> dict[str, objec
|
||||
).dict() # type: ignore
|
||||
|
||||
|
||||
class ModelUpdate(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
created: int
|
||||
context_length: int
|
||||
architecture: dict[str, object]
|
||||
pricing: dict[str, object]
|
||||
per_request_limits: dict[str, object] | None = None
|
||||
top_provider: dict[str, object] | None = None
|
||||
upstream_provider_id: int | None = None
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
@admin_router.patch(
|
||||
"/api/upstream-providers/{provider_id}/models/{model_id:path}",
|
||||
dependencies=[Depends(require_admin_api)],
|
||||
|
||||
+132
-88
@@ -8,6 +8,7 @@ export const UpstreamProviderSchema = z.object({
|
||||
api_key: z.string().optional(),
|
||||
api_version: z.string().nullable().optional(),
|
||||
enabled: z.boolean(),
|
||||
provider_fee: z.number().optional(),
|
||||
});
|
||||
|
||||
export const CreateUpstreamProviderSchema = z.object({
|
||||
@@ -16,6 +17,7 @@ export const CreateUpstreamProviderSchema = z.object({
|
||||
api_key: z.string(),
|
||||
api_version: z.string().nullable().optional(),
|
||||
enabled: z.boolean().default(true),
|
||||
provider_fee: z.number().optional(),
|
||||
});
|
||||
|
||||
export const UpdateUpstreamProviderSchema = z.object({
|
||||
@@ -24,6 +26,7 @@ export const UpdateUpstreamProviderSchema = z.object({
|
||||
api_key: z.string().optional(),
|
||||
api_version: z.string().nullable().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
provider_fee: z.number().optional(),
|
||||
});
|
||||
|
||||
export const AdminModelPricingSchema = z.object({
|
||||
@@ -269,31 +272,16 @@ export class AdminService {
|
||||
};
|
||||
}
|
||||
|
||||
static async getAdminModels(): Promise<AdminModel[]> {
|
||||
const models = await apiClient.get<AdminModel[]>('/admin/api/models');
|
||||
return models.map((m) => ({
|
||||
...m,
|
||||
pricing: this.convertPricingToPerMillionTokens(m.pricing),
|
||||
}));
|
||||
}
|
||||
|
||||
static async getAdminModel(modelId: string): Promise<AdminModel> {
|
||||
const model = await apiClient.get<AdminModel>(
|
||||
`/admin/api/models/${encodeURIComponent(modelId)}`
|
||||
);
|
||||
return {
|
||||
...model,
|
||||
pricing: this.convertPricingToPerMillionTokens(model.pricing),
|
||||
};
|
||||
}
|
||||
|
||||
static async createAdminModel(data: AdminModel): Promise<AdminModel> {
|
||||
static async createProviderModel(
|
||||
providerId: number,
|
||||
data: AdminModel
|
||||
): Promise<AdminModel> {
|
||||
const payload = {
|
||||
...data,
|
||||
pricing: this.convertPricingToPerToken(data.pricing),
|
||||
};
|
||||
const model = await apiClient.post<AdminModel>(
|
||||
'/admin/api/models',
|
||||
`/admin/api/upstream-providers/${providerId}/models`,
|
||||
payload
|
||||
);
|
||||
return {
|
||||
@@ -302,7 +290,21 @@ export class AdminService {
|
||||
};
|
||||
}
|
||||
|
||||
static async updateAdminModel(
|
||||
static async getProviderModel(
|
||||
providerId: number,
|
||||
modelId: string
|
||||
): Promise<AdminModel> {
|
||||
const model = await apiClient.get<AdminModel>(
|
||||
`/admin/api/upstream-providers/${providerId}/models/${encodeURIComponent(modelId)}`
|
||||
);
|
||||
return {
|
||||
...model,
|
||||
pricing: this.convertPricingToPerMillionTokens(model.pricing),
|
||||
};
|
||||
}
|
||||
|
||||
static async updateProviderModel(
|
||||
providerId: number,
|
||||
modelId: string,
|
||||
data: AdminModel
|
||||
): Promise<AdminModel> {
|
||||
@@ -311,7 +313,7 @@ export class AdminService {
|
||||
pricing: this.convertPricingToPerToken(data.pricing),
|
||||
};
|
||||
const model = await apiClient.patch<AdminModel>(
|
||||
`/admin/api/models/${encodeURIComponent(modelId)}`,
|
||||
`/admin/api/upstream-providers/${providerId}/models/${encodeURIComponent(modelId)}`,
|
||||
payload
|
||||
);
|
||||
return {
|
||||
@@ -320,35 +322,20 @@ export class AdminService {
|
||||
};
|
||||
}
|
||||
|
||||
static async deleteAdminModel(
|
||||
static async deleteProviderModel(
|
||||
providerId: number,
|
||||
modelId: string
|
||||
): Promise<{ ok: boolean; deleted_id: string }> {
|
||||
return await apiClient.delete<{ ok: boolean; deleted_id: string }>(
|
||||
`/admin/api/models/${encodeURIComponent(modelId)}`
|
||||
`/admin/api/upstream-providers/${providerId}/models/${encodeURIComponent(modelId)}`
|
||||
);
|
||||
}
|
||||
|
||||
static async deleteAllAdminModels(): Promise<{
|
||||
ok: boolean;
|
||||
deleted: string;
|
||||
}> {
|
||||
return await apiClient.delete<{ ok: boolean; deleted: string }>(
|
||||
'/admin/api/models'
|
||||
);
|
||||
}
|
||||
|
||||
static async batchCreateModels(
|
||||
models: AdminModel[]
|
||||
): Promise<{ created: number; skipped: number }> {
|
||||
const payload = {
|
||||
models: models.map((m) => ({
|
||||
...m,
|
||||
pricing: this.convertPricingToPerToken(m.pricing),
|
||||
})),
|
||||
};
|
||||
return await apiClient.post<{ created: number; skipped: number }>(
|
||||
'/admin/api/models/batch',
|
||||
payload
|
||||
static async deleteAllProviderModels(
|
||||
providerId: number
|
||||
): Promise<{ ok: boolean; deleted: number }> {
|
||||
return await apiClient.delete<{ ok: boolean; deleted: number }>(
|
||||
`/admin/api/upstream-providers/${providerId}/models`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -450,6 +437,10 @@ export class AdminService {
|
||||
static async createModel(
|
||||
data: Record<string, unknown>
|
||||
): Promise<AdminModelAsModel> {
|
||||
if (!data.provider_id) {
|
||||
throw new Error('provider_id is required to create a model');
|
||||
}
|
||||
|
||||
const pricing = {
|
||||
prompt: (data.input_cost as number) / 1000000,
|
||||
completion: (data.output_cost as number) / 1000000,
|
||||
@@ -475,13 +466,12 @@ export class AdminService {
|
||||
pricing,
|
||||
per_request_limits: null,
|
||||
top_provider: null,
|
||||
upstream_provider_id: data.provider_id
|
||||
? parseInt(data.provider_id as string)
|
||||
: null,
|
||||
upstream_provider_id: parseInt(data.provider_id as string),
|
||||
enabled: data.isEnabled !== false,
|
||||
};
|
||||
|
||||
const created = await this.createAdminModel(adminModel);
|
||||
const providerId = parseInt(data.provider_id as string);
|
||||
const created = await this.createProviderModel(providerId, adminModel);
|
||||
return this.transformAdminModelToModel(created, data.provider as string);
|
||||
}
|
||||
|
||||
@@ -489,51 +479,75 @@ export class AdminService {
|
||||
modelId: string,
|
||||
data: Record<string, unknown>
|
||||
): Promise<AdminModelAsModel> {
|
||||
if (!data.provider_id) {
|
||||
throw new Error('provider_id is required to update a model');
|
||||
}
|
||||
|
||||
const providerId = parseInt(data.provider_id as string);
|
||||
const existingModel = await this.getProviderModel(providerId, modelId);
|
||||
|
||||
const pricing = {
|
||||
prompt: (data.input_cost as number) / 1000000,
|
||||
completion: (data.output_cost as number) / 1000000,
|
||||
request: (data.min_cost_per_request as number) || 0,
|
||||
image: 0,
|
||||
web_search: 0,
|
||||
internal_reasoning: 0,
|
||||
};
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
const payload: AdminModel = {
|
||||
...existingModel,
|
||||
id: modelId,
|
||||
pricing,
|
||||
};
|
||||
|
||||
if (data.name) payload.name = data.name;
|
||||
if (data.description) payload.description = data.description;
|
||||
if (data.name) payload.name = data.name as string;
|
||||
if (data.description) payload.description = data.description as string;
|
||||
if (data.contextLength !== undefined)
|
||||
payload.context_length = data.contextLength;
|
||||
if (data.provider_id)
|
||||
payload.upstream_provider_id = parseInt(data.provider_id as string);
|
||||
if (data.isEnabled !== undefined) payload.enabled = data.isEnabled;
|
||||
|
||||
const updated = await apiClient.post<AdminModel>(
|
||||
'/admin/api/models/update',
|
||||
payload
|
||||
);
|
||||
payload.context_length = data.contextLength as number;
|
||||
if (data.isEnabled !== undefined) payload.enabled = data.isEnabled as boolean;
|
||||
|
||||
const updated = await this.updateProviderModel(providerId, modelId, payload);
|
||||
return this.transformAdminModelToModel(updated, data.provider as string);
|
||||
}
|
||||
|
||||
static async deleteModel(modelId: string): Promise<{ message: string }> {
|
||||
await this.deleteAdminModel(modelId);
|
||||
static async deleteModel(
|
||||
modelId: string,
|
||||
providerId?: string
|
||||
): Promise<{ message: string }> {
|
||||
if (!providerId) {
|
||||
throw new Error('provider_id is required to delete a model');
|
||||
}
|
||||
await this.deleteProviderModel(parseInt(providerId), modelId);
|
||||
return { message: 'Model deleted successfully' };
|
||||
}
|
||||
|
||||
static async softDeleteModel(modelId: string): Promise<{ message: string }> {
|
||||
await apiClient.post('/admin/api/models/update', {
|
||||
id: modelId,
|
||||
static async softDeleteModel(
|
||||
modelId: string,
|
||||
providerId?: string
|
||||
): Promise<{ message: string }> {
|
||||
if (!providerId) {
|
||||
throw new Error('provider_id is required to soft delete a model');
|
||||
}
|
||||
const providerIdNum = parseInt(providerId);
|
||||
const model = await this.getProviderModel(providerIdNum, modelId);
|
||||
await this.updateProviderModel(providerIdNum, modelId, {
|
||||
...model,
|
||||
enabled: false,
|
||||
});
|
||||
return { message: 'Model soft deleted successfully' };
|
||||
}
|
||||
|
||||
static async deleteModels(
|
||||
modelIds: string[]
|
||||
modelIds: string[],
|
||||
providerId?: string
|
||||
): Promise<{ deleted_count: number; message: string }> {
|
||||
if (!providerId) {
|
||||
throw new Error('provider_id is required to delete models');
|
||||
}
|
||||
const providerIdNum = parseInt(providerId);
|
||||
for (const id of modelIds) {
|
||||
await this.deleteAdminModel(id);
|
||||
await this.deleteProviderModel(providerIdNum, id);
|
||||
}
|
||||
return {
|
||||
deleted_count: modelIds.length,
|
||||
@@ -542,11 +556,19 @@ export class AdminService {
|
||||
}
|
||||
|
||||
static async softDeleteModels(
|
||||
modelIds: string[]
|
||||
modelIds: string[],
|
||||
providerId?: string
|
||||
): Promise<{ deleted_count: number; message: string }> {
|
||||
if (!providerId) {
|
||||
throw new Error('provider_id is required to soft delete models');
|
||||
}
|
||||
const providerIdNum = parseInt(providerId);
|
||||
for (const id of modelIds) {
|
||||
const model = await this.getAdminModel(id);
|
||||
await this.updateAdminModel(id, { ...model, enabled: false });
|
||||
const model = await this.getProviderModel(providerIdNum, id);
|
||||
await this.updateProviderModel(providerIdNum, id, {
|
||||
...model,
|
||||
enabled: false,
|
||||
});
|
||||
}
|
||||
return {
|
||||
deleted_count: modelIds.length,
|
||||
@@ -556,22 +578,28 @@ export class AdminService {
|
||||
|
||||
static async bulkUpdateModels(
|
||||
modelIds: string[],
|
||||
updates: { api_key?: string; url?: string }
|
||||
updates: { api_key?: string; url?: string },
|
||||
providerId?: string
|
||||
): Promise<{
|
||||
updated_count: number;
|
||||
total_count: number;
|
||||
message: string;
|
||||
errors: string[];
|
||||
}> {
|
||||
if (!providerId) {
|
||||
throw new Error('provider_id is required for bulk updates');
|
||||
}
|
||||
|
||||
const errors: string[] = [];
|
||||
let updated_count = 0;
|
||||
const providerIdNum = parseInt(providerId);
|
||||
|
||||
console.log('Bulk update not implemented, ignoring updates:', updates);
|
||||
|
||||
for (const id of modelIds) {
|
||||
try {
|
||||
const model = await this.getAdminModel(id);
|
||||
await this.updateAdminModel(id, model);
|
||||
const model = await this.getProviderModel(providerIdNum, id);
|
||||
await this.updateProviderModel(providerIdNum, id, model);
|
||||
updated_count++;
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
@@ -592,10 +620,16 @@ export class AdminService {
|
||||
deleted_count: number;
|
||||
message: string;
|
||||
}> {
|
||||
const models = await this.getAdminModels();
|
||||
await this.deleteAllAdminModels();
|
||||
const providers = await this.getUpstreamProviders();
|
||||
let totalDeleted = 0;
|
||||
|
||||
for (const provider of providers) {
|
||||
const result = await this.deleteAllProviderModels(provider.id);
|
||||
totalDeleted += result.deleted;
|
||||
}
|
||||
|
||||
return {
|
||||
deleted_count: models.length,
|
||||
deleted_count: totalDeleted,
|
||||
message: 'All models deleted successfully',
|
||||
};
|
||||
}
|
||||
@@ -603,25 +637,25 @@ export class AdminService {
|
||||
static async deleteModelsByProvider(
|
||||
providerId: string
|
||||
): Promise<{ deleted_count: number; message: string }> {
|
||||
const models = await this.getAdminModels();
|
||||
const providerModels = models.filter(
|
||||
(m) => m.upstream_provider_id === parseInt(providerId)
|
||||
);
|
||||
for (const model of providerModels) {
|
||||
await this.deleteAdminModel(model.id);
|
||||
}
|
||||
const result = await this.deleteAllProviderModels(parseInt(providerId));
|
||||
return {
|
||||
deleted_count: providerModels.length,
|
||||
deleted_count: result.deleted,
|
||||
message: 'Provider models deleted successfully',
|
||||
};
|
||||
}
|
||||
|
||||
static async restoreModels(
|
||||
modelIds: string[]
|
||||
modelIds: string[],
|
||||
providerId?: string
|
||||
): Promise<{ restored_count: number; message: string }> {
|
||||
if (!providerId) {
|
||||
throw new Error('provider_id is required to restore models');
|
||||
}
|
||||
const providerIdNum = parseInt(providerId);
|
||||
for (const id of modelIds) {
|
||||
await apiClient.post('/admin/api/models/update', {
|
||||
id,
|
||||
const model = await this.getProviderModel(providerIdNum, id);
|
||||
await this.updateProviderModel(providerIdNum, id, {
|
||||
...model,
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
@@ -644,4 +678,14 @@ export class AdminService {
|
||||
);
|
||||
return { message: 'Refresh not implemented for admin API' };
|
||||
}
|
||||
|
||||
static async getOpenRouterPresets(): Promise<AdminModel[]> {
|
||||
const presets = await apiClient.get<AdminModel[]>(
|
||||
'/admin/api/openrouter-presets'
|
||||
);
|
||||
return presets.map((m) => ({
|
||||
...m,
|
||||
pricing: this.convertPricingToPerMillionTokens(m.pricing),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user