You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
426 lines
11 KiB
426 lines
11 KiB
import { dbGlobal } from "drizzle-pkg/lib/db";
|
|
import { llmProviders, llmModels } from "drizzle-pkg/lib/schema/llm";
|
|
import { eq, desc, like, and, sql, isNull } from "drizzle-orm";
|
|
import type { LlmParseMode, LlmProviderStatus, LlmModelType } from "drizzle-pkg/lib/schema/llm";
|
|
|
|
export interface LlmProviderRow {
|
|
id: number;
|
|
userId: number;
|
|
name: string;
|
|
slug: string;
|
|
baseUrl: string | null;
|
|
parseMode: LlmParseMode;
|
|
apiKey: string | null;
|
|
status: LlmProviderStatus;
|
|
description: string | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
export interface LlmModelRow {
|
|
id: number;
|
|
providerId: number;
|
|
name: string;
|
|
modelId: string;
|
|
type: LlmModelType;
|
|
enabled: number;
|
|
description: string | null;
|
|
maxTokens: number | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
export interface LlmProviderWithModels extends Omit<LlmProviderRow, "apiKey"> {
|
|
apiKeySet: boolean;
|
|
models: LlmModelRow[];
|
|
}
|
|
|
|
export async function listProviders(
|
|
userId: number,
|
|
page = 1,
|
|
pageSize = 20,
|
|
search?: string,
|
|
status?: LlmProviderStatus,
|
|
) {
|
|
const conditions = [eq(llmProviders.userId, userId)];
|
|
if (search) {
|
|
conditions.push(like(llmProviders.name, `%${search}%`));
|
|
}
|
|
if (status) {
|
|
conditions.push(eq(llmProviders.status, status));
|
|
}
|
|
|
|
const where = and(...conditions);
|
|
|
|
const [countResult] = await dbGlobal
|
|
.select({ count: sql<number>`count(*)` })
|
|
.from(llmProviders)
|
|
.where(where);
|
|
|
|
const total = countResult?.count ?? 0;
|
|
const totalPages = Math.ceil(total / pageSize);
|
|
|
|
const rows = await dbGlobal
|
|
.select()
|
|
.from(llmProviders)
|
|
.where(where)
|
|
.orderBy(desc(llmProviders.createdAt))
|
|
.limit(pageSize)
|
|
.offset((page - 1) * pageSize);
|
|
|
|
const sanitized = rows.map(({ apiKey, ...rest }) => ({
|
|
...rest,
|
|
apiKeySet: !!apiKey,
|
|
}));
|
|
|
|
return { list: sanitized, total, page, pageSize, totalPages };
|
|
}
|
|
|
|
export async function getProviderById(id: number, userId: number) {
|
|
const [row] = await dbGlobal
|
|
.select()
|
|
.from(llmProviders)
|
|
.where(and(eq(llmProviders.id, id), eq(llmProviders.userId, userId)))
|
|
.limit(1);
|
|
return row ?? null;
|
|
}
|
|
|
|
export async function getProviderWithModels(id: number, userId: number): Promise<LlmProviderWithModels | null> {
|
|
const provider = await getProviderById(id, userId);
|
|
if (!provider) return null;
|
|
|
|
const models = await dbGlobal
|
|
.select()
|
|
.from(llmModels)
|
|
.where(eq(llmModels.providerId, id))
|
|
.orderBy(desc(llmModels.createdAt));
|
|
|
|
const { apiKey, ...rest } = provider;
|
|
return {
|
|
...rest,
|
|
apiKeySet: !!apiKey,
|
|
models,
|
|
};
|
|
}
|
|
|
|
export async function createProvider(userId: number, data: {
|
|
name: string;
|
|
slug: string;
|
|
baseUrl?: string;
|
|
parseMode: LlmParseMode;
|
|
apiKey?: string;
|
|
status?: LlmProviderStatus;
|
|
description?: string;
|
|
}) {
|
|
const [inserted] = await dbGlobal
|
|
.insert(llmProviders)
|
|
.values({
|
|
userId,
|
|
name: data.name,
|
|
slug: data.slug,
|
|
baseUrl: data.baseUrl || null,
|
|
parseMode: data.parseMode,
|
|
apiKey: data.apiKey || null,
|
|
status: data.status || "active",
|
|
description: data.description || null,
|
|
})
|
|
.returning({ id: llmProviders.id });
|
|
return inserted!;
|
|
}
|
|
|
|
export async function updateProvider(
|
|
id: number,
|
|
userId: number,
|
|
data: {
|
|
name?: string;
|
|
slug?: string;
|
|
baseUrl?: string;
|
|
parseMode?: LlmParseMode;
|
|
apiKey?: string;
|
|
status?: LlmProviderStatus;
|
|
description?: string;
|
|
},
|
|
) {
|
|
const updates: Record<string, any> = {};
|
|
if (data.name !== undefined) updates.name = data.name;
|
|
if (data.slug !== undefined) updates.slug = data.slug;
|
|
if (data.baseUrl !== undefined) updates.baseUrl = data.baseUrl;
|
|
if (data.parseMode !== undefined) updates.parseMode = data.parseMode;
|
|
if (data.status !== undefined) updates.status = data.status;
|
|
if (data.description !== undefined) updates.description = data.description;
|
|
if (data.apiKey !== undefined) {
|
|
if (data.apiKey === "") {
|
|
updates.apiKey = null;
|
|
} else if (data.apiKey !== "__unchanged__") {
|
|
updates.apiKey = data.apiKey;
|
|
}
|
|
}
|
|
|
|
if (Object.keys(updates).length === 0) return;
|
|
|
|
await dbGlobal
|
|
.update(llmProviders)
|
|
.set(updates)
|
|
.where(and(eq(llmProviders.id, id), eq(llmProviders.userId, userId)));
|
|
}
|
|
|
|
export async function deleteProvider(id: number, userId: number) {
|
|
await dbGlobal.delete(llmProviders).where(and(eq(llmProviders.id, id), eq(llmProviders.userId, userId)));
|
|
}
|
|
|
|
export async function listModelsByProvider(providerId: number, userId: number) {
|
|
const provider = await getProviderById(providerId, userId);
|
|
if (!provider) return [];
|
|
|
|
return dbGlobal
|
|
.select()
|
|
.from(llmModels)
|
|
.where(eq(llmModels.providerId, providerId))
|
|
.orderBy(desc(llmModels.createdAt));
|
|
}
|
|
|
|
export async function createModel(userId: number, data: {
|
|
providerId: number;
|
|
name: string;
|
|
modelId: string;
|
|
type: LlmModelType;
|
|
enabled?: number;
|
|
description?: string;
|
|
maxTokens?: number;
|
|
}) {
|
|
const provider = await getProviderById(data.providerId, userId);
|
|
if (!provider) return null;
|
|
|
|
const [inserted] = await dbGlobal
|
|
.insert(llmModels)
|
|
.values({
|
|
providerId: data.providerId,
|
|
name: data.name,
|
|
modelId: data.modelId,
|
|
type: data.type,
|
|
enabled: data.enabled ?? 1,
|
|
description: data.description || null,
|
|
maxTokens: data.maxTokens || null,
|
|
})
|
|
.returning({ id: llmModels.id });
|
|
return inserted!;
|
|
}
|
|
|
|
export async function getModelById(id: number, userId: number) {
|
|
const [row] = await dbGlobal
|
|
.select()
|
|
.from(llmModels)
|
|
.innerJoin(llmProviders, eq(llmModels.providerId, llmProviders.id))
|
|
.where(and(eq(llmModels.id, id), eq(llmProviders.userId, userId)))
|
|
.limit(1);
|
|
return row?.llm_models ?? null;
|
|
}
|
|
|
|
export async function updateModel(
|
|
id: number,
|
|
userId: number,
|
|
data: {
|
|
name?: string;
|
|
modelId?: string;
|
|
type?: LlmModelType;
|
|
enabled?: number;
|
|
description?: string;
|
|
maxTokens?: number;
|
|
},
|
|
) {
|
|
const model = await getModelById(id, userId);
|
|
if (!model) return;
|
|
|
|
const updates: Record<string, any> = {};
|
|
if (data.name !== undefined) updates.name = data.name;
|
|
if (data.modelId !== undefined) updates.modelId = data.modelId;
|
|
if (data.type !== undefined) updates.type = data.type;
|
|
if (data.enabled !== undefined) updates.enabled = data.enabled;
|
|
if (data.description !== undefined) updates.description = data.description;
|
|
if (data.maxTokens !== undefined) updates.maxTokens = data.maxTokens;
|
|
|
|
if (Object.keys(updates).length === 0) return;
|
|
|
|
await dbGlobal
|
|
.update(llmModels)
|
|
.set(updates)
|
|
.where(eq(llmModels.id, id));
|
|
}
|
|
|
|
export async function deleteModel(id: number, userId: number) {
|
|
const model = await getModelById(id, userId);
|
|
if (!model) return;
|
|
|
|
await dbGlobal.delete(llmModels).where(eq(llmModels.id, id));
|
|
}
|
|
|
|
// ============ 系统级 Provider/Model 查询(userId IS NULL)============
|
|
|
|
export async function listSystemProviders() {
|
|
return dbGlobal
|
|
.select()
|
|
.from(llmProviders)
|
|
.where(isNull(llmProviders.userId))
|
|
.orderBy(desc(llmProviders.createdAt));
|
|
}
|
|
|
|
export async function getSystemProviderById(id: number) {
|
|
const [row] = await dbGlobal
|
|
.select()
|
|
.from(llmProviders)
|
|
.where(and(eq(llmProviders.id, id), isNull(llmProviders.userId)))
|
|
.limit(1);
|
|
return row ?? null;
|
|
}
|
|
|
|
export async function listSystemModels(): Promise<LlmModelRow[]> {
|
|
return dbGlobal
|
|
.select({
|
|
id: llmModels.id,
|
|
providerId: llmModels.providerId,
|
|
name: llmModels.name,
|
|
modelId: llmModels.modelId,
|
|
type: llmModels.type,
|
|
enabled: llmModels.enabled,
|
|
description: llmModels.description,
|
|
maxTokens: llmModels.maxTokens,
|
|
createdAt: llmModels.createdAt,
|
|
updatedAt: llmModels.updatedAt,
|
|
})
|
|
.from(llmModels)
|
|
.innerJoin(llmProviders, eq(llmModels.providerId, llmProviders.id))
|
|
.where(and(isNull(llmProviders.userId), eq(llmModels.enabled, 1)))
|
|
.orderBy(desc(llmModels.createdAt));
|
|
}
|
|
|
|
// ============ 通用 Model 查询(agent 对话核心使用)============
|
|
|
|
export async function getModelWithProviderById(modelId: number, userId: number | null) {
|
|
if (userId) {
|
|
const [row] = await dbGlobal
|
|
.select({
|
|
model: llmModels,
|
|
provider: llmProviders,
|
|
})
|
|
.from(llmModels)
|
|
.innerJoin(llmProviders, eq(llmModels.providerId, llmProviders.id))
|
|
.where(and(eq(llmModels.id, modelId), eq(llmProviders.userId, userId)))
|
|
.limit(1);
|
|
return row ?? null;
|
|
}
|
|
const [row] = await dbGlobal
|
|
.select({
|
|
model: llmModels,
|
|
provider: llmProviders,
|
|
})
|
|
.from(llmModels)
|
|
.innerJoin(llmProviders, eq(llmModels.providerId, llmProviders.id))
|
|
.where(and(eq(llmModels.id, modelId), isNull(llmProviders.userId)))
|
|
.limit(1);
|
|
return row ?? null;
|
|
}
|
|
|
|
export async function getSystemModelWithProviderById(modelId: number) {
|
|
const [row] = await dbGlobal
|
|
.select({
|
|
model: llmModels,
|
|
provider: llmProviders,
|
|
})
|
|
.from(llmModels)
|
|
.innerJoin(llmProviders, eq(llmModels.providerId, llmProviders.id))
|
|
.where(and(eq(llmModels.id, modelId), isNull(llmProviders.userId)))
|
|
.limit(1);
|
|
return row ?? null;
|
|
}
|
|
|
|
export async function listAllEnabledModelsForUser(userId: number | null): Promise<
|
|
{
|
|
id: number;
|
|
name: string;
|
|
modelId: string;
|
|
type: LlmModelType;
|
|
providerName: string;
|
|
providerId: number;
|
|
}[]
|
|
> {
|
|
if (userId) {
|
|
return dbGlobal
|
|
.select({
|
|
id: llmModels.id,
|
|
name: llmModels.name,
|
|
modelId: llmModels.modelId,
|
|
type: llmModels.type,
|
|
providerName: llmProviders.name,
|
|
providerId: llmProviders.id,
|
|
})
|
|
.from(llmModels)
|
|
.innerJoin(llmProviders, eq(llmModels.providerId, llmProviders.id))
|
|
.where(
|
|
and(
|
|
eq(llmModels.enabled, 1),
|
|
sql`(${llmProviders.userId} = ${userId} OR ${llmProviders.userId} IS NULL)`,
|
|
),
|
|
)
|
|
.orderBy(desc(llmModels.createdAt));
|
|
}
|
|
return dbGlobal
|
|
.select({
|
|
id: llmModels.id,
|
|
name: llmModels.name,
|
|
modelId: llmModels.modelId,
|
|
type: llmModels.type,
|
|
providerName: llmProviders.name,
|
|
providerId: llmProviders.id,
|
|
})
|
|
.from(llmModels)
|
|
.innerJoin(llmProviders, eq(llmModels.providerId, llmProviders.id))
|
|
.where(and(eq(llmModels.enabled, 1), isNull(llmProviders.userId)))
|
|
.orderBy(desc(llmModels.createdAt));
|
|
}
|
|
|
|
export async function createSystemProvider(data: {
|
|
name: string;
|
|
slug: string;
|
|
baseUrl?: string;
|
|
parseMode: LlmParseMode;
|
|
apiKey?: string;
|
|
status?: LlmProviderStatus;
|
|
description?: string;
|
|
}) {
|
|
const [inserted] = await dbGlobal
|
|
.insert(llmProviders)
|
|
.values({
|
|
userId: null,
|
|
name: data.name,
|
|
slug: data.slug,
|
|
baseUrl: data.baseUrl || null,
|
|
parseMode: data.parseMode,
|
|
apiKey: data.apiKey || null,
|
|
status: data.status || "active",
|
|
description: data.description || null,
|
|
})
|
|
.returning({ id: llmProviders.id });
|
|
return inserted!;
|
|
}
|
|
|
|
export async function createSystemModel(data: {
|
|
providerId: number;
|
|
name: string;
|
|
modelId: string;
|
|
type?: LlmModelType;
|
|
maxTokens?: number;
|
|
enabled?: number;
|
|
}) {
|
|
const [inserted] = await dbGlobal
|
|
.insert(llmModels)
|
|
.values({
|
|
providerId: data.providerId,
|
|
name: data.name,
|
|
modelId: data.modelId,
|
|
type: data.type || "chat",
|
|
maxTokens: data.maxTokens || null,
|
|
enabled: data.enabled ?? 1,
|
|
})
|
|
.returning({ id: llmModels.id });
|
|
return inserted!;
|
|
}
|
|
|