@lobehub/chat 1.122.6 → 1.123.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/package.json +2 -2
- package/packages/model-bank/package.json +1 -0
- package/packages/model-bank/src/aiModels/index.ts +3 -1
- package/packages/model-bank/src/aiModels/newapi.ts +11 -0
- package/packages/model-runtime/src/RouterRuntime/createRuntime.test.ts +60 -0
- package/packages/model-runtime/src/RouterRuntime/createRuntime.ts +6 -3
- package/packages/model-runtime/src/index.ts +1 -0
- package/packages/model-runtime/src/newapi/index.test.ts +618 -0
- package/packages/model-runtime/src/newapi/index.ts +245 -0
- package/packages/model-runtime/src/runtimeMap.ts +2 -0
- package/packages/model-runtime/src/types/type.ts +1 -0
- package/packages/types/src/user/settings/keyVaults.ts +1 -0
- package/packages/utils/src/server/__tests__/auth.test.ts +1 -1
- package/packages/utils/src/server/auth.ts +2 -2
- package/src/app/(backend)/api/auth/adapter/route.ts +137 -0
- package/src/app/(backend)/api/webhooks/logto/route.ts +9 -0
- package/src/app/[variants]/(main)/settings/provider/(detail)/newapi/page.tsx +27 -0
- package/src/config/auth.ts +4 -0
- package/src/config/modelProviders/index.ts +3 -0
- package/src/config/modelProviders/newapi.ts +17 -0
- package/src/libs/next-auth/adapter/index.ts +103 -201
- package/src/libs/next-auth/auth.config.ts +22 -5
- package/src/libs/next-auth/index.ts +11 -24
- package/src/libs/trpc/edge/context.ts +2 -2
- package/src/libs/trpc/lambda/context.ts +2 -2
- package/src/locales/default/modelProvider.ts +26 -0
- package/src/middleware.ts +2 -2
- package/src/server/routers/lambda/user.test.ts +4 -17
- package/src/server/routers/lambda/user.ts +6 -15
- package/src/server/services/nextAuthUser/index.ts +282 -6
- package/packages/database/src/server/models/__tests__/nextauth.test.ts +0 -556
- package/src/libs/next-auth/edge.ts +0 -26
- package/src/server/services/nextAuthUser/index.test.ts +0 -108
- /package/{.env.development → .env.example.development} +0 -0
- /package/src/{libs/next-auth/adapter → server/services/nextAuthUser}/utils.ts +0 -0
@@ -0,0 +1,245 @@
|
|
1
|
+
import urlJoin from 'url-join';
|
2
|
+
|
3
|
+
import { createRouterRuntime } from '../RouterRuntime';
|
4
|
+
import { responsesAPIModels } from '../const/models';
|
5
|
+
import { ModelProvider } from '../types';
|
6
|
+
import { ChatStreamPayload } from '../types/chat';
|
7
|
+
import { detectModelProvider, processMultiProviderModelList } from '../utils/modelParse';
|
8
|
+
|
9
|
+
export interface NewAPIModelCard {
|
10
|
+
created: number;
|
11
|
+
id: string;
|
12
|
+
object: string;
|
13
|
+
owned_by: string;
|
14
|
+
supported_endpoint_types?: string[];
|
15
|
+
}
|
16
|
+
|
17
|
+
export interface NewAPIPricing {
|
18
|
+
completion_ratio?: number;
|
19
|
+
enable_groups: string[];
|
20
|
+
model_name: string;
|
21
|
+
model_price?: number;
|
22
|
+
model_ratio?: number;
|
23
|
+
quota_type: number; // 0: 按量计费, 1: 按次计费
|
24
|
+
supported_endpoint_types?: string[];
|
25
|
+
}
|
26
|
+
|
27
|
+
const handlePayload = (payload: ChatStreamPayload) => {
|
28
|
+
// 处理 OpenAI responses API 模式
|
29
|
+
if (
|
30
|
+
responsesAPIModels.has(payload.model) ||
|
31
|
+
payload.model.includes('gpt-') ||
|
32
|
+
/^o\d/.test(payload.model)
|
33
|
+
) {
|
34
|
+
return { ...payload, apiMode: 'responses' } as any;
|
35
|
+
}
|
36
|
+
return payload as any;
|
37
|
+
};
|
38
|
+
|
39
|
+
// 根据 owned_by 字段判断提供商
|
40
|
+
const getProviderFromOwnedBy = (ownedBy: string): string => {
|
41
|
+
const normalizedOwnedBy = ownedBy.toLowerCase();
|
42
|
+
|
43
|
+
if (normalizedOwnedBy.includes('anthropic') || normalizedOwnedBy.includes('claude')) {
|
44
|
+
return 'anthropic';
|
45
|
+
}
|
46
|
+
if (normalizedOwnedBy.includes('google') || normalizedOwnedBy.includes('gemini')) {
|
47
|
+
return 'google';
|
48
|
+
}
|
49
|
+
if (normalizedOwnedBy.includes('xai') || normalizedOwnedBy.includes('grok')) {
|
50
|
+
return 'xai';
|
51
|
+
}
|
52
|
+
|
53
|
+
// 默认为 openai
|
54
|
+
return 'openai';
|
55
|
+
};
|
56
|
+
|
57
|
+
// 全局的模型路由映射,在 models 函数执行后被填充
|
58
|
+
let globalModelRouteMap: Map<string, string> = new Map();
|
59
|
+
|
60
|
+
export const LobeNewAPIAI = createRouterRuntime({
|
61
|
+
debug: {
|
62
|
+
chatCompletion: () => process.env.DEBUG_NEWAPI_CHAT_COMPLETION === '1',
|
63
|
+
},
|
64
|
+
defaultHeaders: {
|
65
|
+
'X-Client': 'LobeHub',
|
66
|
+
},
|
67
|
+
id: ModelProvider.NewAPI,
|
68
|
+
models: async ({ client: openAIClient }) => {
|
69
|
+
// 每次调用 models 时清空并重建路由映射
|
70
|
+
globalModelRouteMap.clear();
|
71
|
+
|
72
|
+
// 获取基础 URL(移除末尾的 /v1)
|
73
|
+
const baseURL = openAIClient.baseURL.replace(/\/v1\/?$/, '');
|
74
|
+
|
75
|
+
const modelsPage = (await openAIClient.models.list()) as any;
|
76
|
+
const modelList: NewAPIModelCard[] = modelsPage.data || [];
|
77
|
+
|
78
|
+
// 尝试获取 pricing 信息以补充模型详细信息
|
79
|
+
let pricingMap: Map<string, NewAPIPricing> = new Map();
|
80
|
+
try {
|
81
|
+
// 使用保存的 baseURL
|
82
|
+
const pricingResponse = await fetch(`${baseURL}/api/pricing`, {
|
83
|
+
headers: {
|
84
|
+
Authorization: `Bearer ${openAIClient.apiKey}`,
|
85
|
+
},
|
86
|
+
});
|
87
|
+
|
88
|
+
if (pricingResponse.ok) {
|
89
|
+
const pricingData = await pricingResponse.json();
|
90
|
+
if (pricingData.success && pricingData.data) {
|
91
|
+
(pricingData.data as NewAPIPricing[]).forEach((pricing) => {
|
92
|
+
pricingMap.set(pricing.model_name, pricing);
|
93
|
+
});
|
94
|
+
}
|
95
|
+
}
|
96
|
+
} catch (error) {
|
97
|
+
// If fetching pricing information fails, continue using the basic model information
|
98
|
+
console.debug('Failed to fetch NewAPI pricing info:', error);
|
99
|
+
}
|
100
|
+
|
101
|
+
// Process the model list: determine the provider for each model based on priority rules
|
102
|
+
const enrichedModelList = modelList.map((model) => {
|
103
|
+
let enhancedModel: any = { ...model };
|
104
|
+
|
105
|
+
// 1. 添加 pricing 信息
|
106
|
+
const pricing = pricingMap.get(model.id);
|
107
|
+
if (pricing) {
|
108
|
+
// NewAPI 的价格计算逻辑:
|
109
|
+
// - quota_type: 0 表示按量计费(按 token),1 表示按次计费
|
110
|
+
// - model_ratio: 相对于基础价格的倍率(基础价格 = $0.002/1K tokens)
|
111
|
+
// - model_price: 直接指定的价格(优先使用)
|
112
|
+
// - completion_ratio: 输出价格相对于输入价格的倍率
|
113
|
+
//
|
114
|
+
// LobeChat 需要的格式:美元/百万 token
|
115
|
+
|
116
|
+
let inputPrice: number | undefined;
|
117
|
+
let outputPrice: number | undefined;
|
118
|
+
|
119
|
+
if (pricing.quota_type === 0) {
|
120
|
+
// 按量计费
|
121
|
+
if (pricing.model_price && pricing.model_price > 0) {
|
122
|
+
// model_price is a direct price value; need to confirm its unit.
|
123
|
+
// Assumption: model_price is the price per 1,000 tokens (i.e., $/1K tokens).
|
124
|
+
// To convert to price per 1,000,000 tokens ($/1M tokens), multiply by 1,000,000 / 1,000 = 1,000.
|
125
|
+
// Since the base price is $0.002/1K tokens, multiplying by 2 gives $2/1M tokens.
|
126
|
+
// Therefore, inputPrice = model_price * 2 converts the price to $/1M tokens for LobeChat.
|
127
|
+
inputPrice = pricing.model_price * 2;
|
128
|
+
} else if (pricing.model_ratio) {
|
129
|
+
// model_ratio × $0.002/1K = model_ratio × $2/1M
|
130
|
+
inputPrice = pricing.model_ratio * 2; // 转换为 $/1M tokens
|
131
|
+
}
|
132
|
+
|
133
|
+
if (inputPrice !== undefined) {
|
134
|
+
// 计算输出价格
|
135
|
+
outputPrice = inputPrice * (pricing.completion_ratio || 1);
|
136
|
+
|
137
|
+
enhancedModel.pricing = {
|
138
|
+
input: inputPrice,
|
139
|
+
output: outputPrice,
|
140
|
+
};
|
141
|
+
}
|
142
|
+
}
|
143
|
+
// quota_type === 1 按次计费暂不支持
|
144
|
+
}
|
145
|
+
|
146
|
+
// 2. 根据优先级处理 provider 信息并缓存路由
|
147
|
+
let detectedProvider = 'openai'; // 默认
|
148
|
+
|
149
|
+
// 优先级1:使用 supported_endpoint_types
|
150
|
+
if (model.supported_endpoint_types && model.supported_endpoint_types.length > 0) {
|
151
|
+
if (model.supported_endpoint_types.includes('anthropic')) {
|
152
|
+
detectedProvider = 'anthropic';
|
153
|
+
} else if (model.supported_endpoint_types.includes('gemini')) {
|
154
|
+
detectedProvider = 'google';
|
155
|
+
} else if (model.supported_endpoint_types.includes('xai')) {
|
156
|
+
detectedProvider = 'xai';
|
157
|
+
}
|
158
|
+
}
|
159
|
+
// 优先级2:使用 owned_by 字段
|
160
|
+
else if (model.owned_by) {
|
161
|
+
detectedProvider = getProviderFromOwnedBy(model.owned_by);
|
162
|
+
}
|
163
|
+
// 优先级3:基于模型名称检测
|
164
|
+
else {
|
165
|
+
detectedProvider = detectModelProvider(model.id);
|
166
|
+
}
|
167
|
+
|
168
|
+
// 将检测到的 provider 信息附加到模型上,供路由使用
|
169
|
+
enhancedModel._detectedProvider = detectedProvider;
|
170
|
+
// 同时更新全局路由映射表
|
171
|
+
globalModelRouteMap.set(model.id, detectedProvider);
|
172
|
+
|
173
|
+
return enhancedModel;
|
174
|
+
});
|
175
|
+
|
176
|
+
// 使用 processMultiProviderModelList 处理模型能力
|
177
|
+
const processedModels = await processMultiProviderModelList(enrichedModelList, 'newapi');
|
178
|
+
|
179
|
+
// 如果我们检测到了 provider,确保它被正确应用
|
180
|
+
return processedModels.map((model: any) => {
|
181
|
+
if (model._detectedProvider) {
|
182
|
+
// Here you can adjust certain model properties as needed.
|
183
|
+
// FIXME: The current data structure does not support storing provider information, and the official NewAPI does not provide a corresponding field. Consider extending the model schema if provider tracking is required in the future.
|
184
|
+
delete model._detectedProvider; // Remove temporary field
|
185
|
+
}
|
186
|
+
return model;
|
187
|
+
});
|
188
|
+
},
|
189
|
+
// 使用动态 routers 配置,在构造时获取用户的 baseURL
|
190
|
+
routers: (options) => {
|
191
|
+
// 使用全局的模型路由映射
|
192
|
+
const userBaseURL = options.baseURL?.replace(/\/v1\/?$/, '') || '';
|
193
|
+
|
194
|
+
return [
|
195
|
+
{
|
196
|
+
apiType: 'anthropic',
|
197
|
+
models: () =>
|
198
|
+
Promise.resolve(
|
199
|
+
Array.from(globalModelRouteMap.entries())
|
200
|
+
.filter(([, provider]) => provider === 'anthropic')
|
201
|
+
.map(([modelId]) => modelId),
|
202
|
+
),
|
203
|
+
options: {
|
204
|
+
// Anthropic 在 NewAPI 中使用 /v1 路径,会自动转换为 /v1/messages
|
205
|
+
baseURL: urlJoin(userBaseURL, '/v1'),
|
206
|
+
},
|
207
|
+
},
|
208
|
+
{
|
209
|
+
apiType: 'google',
|
210
|
+
models: () =>
|
211
|
+
Promise.resolve(
|
212
|
+
Array.from(globalModelRouteMap.entries())
|
213
|
+
.filter(([, provider]) => provider === 'google')
|
214
|
+
.map(([modelId]) => modelId),
|
215
|
+
),
|
216
|
+
options: {
|
217
|
+
// Gemini 在 NewAPI 中使用 /v1beta 路径
|
218
|
+
baseURL: urlJoin(userBaseURL, '/v1beta'),
|
219
|
+
},
|
220
|
+
},
|
221
|
+
{
|
222
|
+
apiType: 'xai',
|
223
|
+
models: () =>
|
224
|
+
Promise.resolve(
|
225
|
+
Array.from(globalModelRouteMap.entries())
|
226
|
+
.filter(([, provider]) => provider === 'xai')
|
227
|
+
.map(([modelId]) => modelId),
|
228
|
+
),
|
229
|
+
options: {
|
230
|
+
// xAI 使用标准 OpenAI 格式,走 /v1 路径
|
231
|
+
baseURL: urlJoin(userBaseURL, '/v1'),
|
232
|
+
},
|
233
|
+
},
|
234
|
+
{
|
235
|
+
apiType: 'openai',
|
236
|
+
options: {
|
237
|
+
baseURL: urlJoin(userBaseURL, '/v1'),
|
238
|
+
chatCompletion: {
|
239
|
+
handlePayload,
|
240
|
+
},
|
241
|
+
},
|
242
|
+
},
|
243
|
+
];
|
244
|
+
},
|
245
|
+
});
|
@@ -30,6 +30,7 @@ import { LobeMistralAI } from './mistral';
|
|
30
30
|
import { LobeModelScopeAI } from './modelscope';
|
31
31
|
import { LobeMoonshotAI } from './moonshot';
|
32
32
|
import { LobeNebiusAI } from './nebius';
|
33
|
+
import { LobeNewAPIAI } from './newapi';
|
33
34
|
import { LobeNovitaAI } from './novita';
|
34
35
|
import { LobeNvidiaAI } from './nvidia';
|
35
36
|
import { LobeOllamaAI } from './ollama';
|
@@ -91,6 +92,7 @@ export const providerRuntimeMap = {
|
|
91
92
|
modelscope: LobeModelScopeAI,
|
92
93
|
moonshot: LobeMoonshotAI,
|
93
94
|
nebius: LobeNebiusAI,
|
95
|
+
newapi: LobeNewAPIAI,
|
94
96
|
novita: LobeNovitaAI,
|
95
97
|
nvidia: LobeNvidiaAI,
|
96
98
|
ollama: LobeOllamaAI,
|
@@ -69,6 +69,7 @@ export interface UserKeyVaults extends SearchEngineKeyVaults {
|
|
69
69
|
modelscope?: OpenAICompatibleKeyVault;
|
70
70
|
moonshot?: OpenAICompatibleKeyVault;
|
71
71
|
nebius?: OpenAICompatibleKeyVault;
|
72
|
+
newapi?: OpenAICompatibleKeyVault;
|
72
73
|
novita?: OpenAICompatibleKeyVault;
|
73
74
|
nvidia?: OpenAICompatibleKeyVault;
|
74
75
|
ollama?: OpenAICompatibleKeyVault;
|
@@ -12,9 +12,9 @@ export const getUserAuth = async () => {
|
|
12
12
|
}
|
13
13
|
|
14
14
|
if (enableNextAuth) {
|
15
|
-
const { default:
|
15
|
+
const { default: NextAuth } = await import('@/libs/next-auth');
|
16
16
|
|
17
|
-
const session = await
|
17
|
+
const session = await NextAuth.auth();
|
18
18
|
|
19
19
|
const userId = session?.user.id;
|
20
20
|
|
@@ -0,0 +1,137 @@
|
|
1
|
+
import debug from 'debug';
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
3
|
+
|
4
|
+
import { serverDBEnv } from '@/config/db';
|
5
|
+
import { serverDB } from '@/database/server';
|
6
|
+
import { dateKeys } from '@/libs/next-auth/adapter';
|
7
|
+
import { NextAuthUserService } from '@/server/services/nextAuthUser';
|
8
|
+
|
9
|
+
const log = debug('lobe-next-auth:api:auth:adapter');
|
10
|
+
|
11
|
+
/**
|
12
|
+
* @description Process the db query for the NextAuth adapter.
|
13
|
+
* Returns the db query result directly and let NextAuth handle the raw results.
|
14
|
+
* @returns {
|
15
|
+
* success: boolean; // Only return false if the database query fails or the action is invalid.
|
16
|
+
* data?: any;
|
17
|
+
* error?: string;
|
18
|
+
* }
|
19
|
+
*/
|
20
|
+
export async function POST(req: NextRequest) {
|
21
|
+
try {
|
22
|
+
// try validate the request
|
23
|
+
if (
|
24
|
+
!req.headers.get('Authorization') ||
|
25
|
+
req.headers.get('Authorization')?.trim() !== `Bearer ${serverDBEnv.KEY_VAULTS_SECRET}`
|
26
|
+
) {
|
27
|
+
log('Unauthorized request, missing or invalid Authorization header');
|
28
|
+
return NextResponse.json({ error: 'Unauthorized', success: false }, { status: 401 });
|
29
|
+
}
|
30
|
+
|
31
|
+
// Parse the request body
|
32
|
+
const data = await req.json();
|
33
|
+
log('Received request data:', data);
|
34
|
+
// Preprocess
|
35
|
+
if (data?.data) {
|
36
|
+
for (const key of dateKeys) {
|
37
|
+
if (data?.data && data.data[key]) {
|
38
|
+
data.data[key] = new Date(data.data[key]);
|
39
|
+
continue;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
const service = new NextAuthUserService(serverDB);
|
44
|
+
let result;
|
45
|
+
switch (data.action) {
|
46
|
+
case 'createAuthenticator': {
|
47
|
+
result = await service.createAuthenticator(data.data);
|
48
|
+
break;
|
49
|
+
}
|
50
|
+
case 'createSession': {
|
51
|
+
result = await service.createSession(data.data);
|
52
|
+
break;
|
53
|
+
}
|
54
|
+
case 'createUser': {
|
55
|
+
result = await service.createUser(data.data);
|
56
|
+
break;
|
57
|
+
}
|
58
|
+
case 'createVerificationToken': {
|
59
|
+
result = await service.createVerificationToken(data.data);
|
60
|
+
break;
|
61
|
+
}
|
62
|
+
case 'deleteSession': {
|
63
|
+
result = await service.deleteSession(data.data);
|
64
|
+
break;
|
65
|
+
}
|
66
|
+
case 'deleteUser': {
|
67
|
+
result = await service.deleteUser(data.data);
|
68
|
+
break;
|
69
|
+
}
|
70
|
+
case 'getAccount': {
|
71
|
+
result = await service.getAccount(data.data.providerAccountId, data.data.provider);
|
72
|
+
break;
|
73
|
+
}
|
74
|
+
case 'getAuthenticator': {
|
75
|
+
result = await service.getAuthenticator(data.data);
|
76
|
+
break;
|
77
|
+
}
|
78
|
+
case 'getSessionAndUser': {
|
79
|
+
result = await service.getSessionAndUser(data.data);
|
80
|
+
break;
|
81
|
+
}
|
82
|
+
case 'getUser': {
|
83
|
+
result = await service.getUser(data.data);
|
84
|
+
break;
|
85
|
+
}
|
86
|
+
case 'getUserByAccount': {
|
87
|
+
result = await service.getUserByAccount(data.data);
|
88
|
+
break;
|
89
|
+
}
|
90
|
+
case 'getUserByEmail': {
|
91
|
+
result = await service.getUserByEmail(data.data);
|
92
|
+
break;
|
93
|
+
}
|
94
|
+
case 'linkAccount': {
|
95
|
+
result = await service.linkAccount(data.data);
|
96
|
+
break;
|
97
|
+
}
|
98
|
+
case 'listAuthenticatorsByUserId': {
|
99
|
+
result = await service.listAuthenticatorsByUserId(data.data);
|
100
|
+
break;
|
101
|
+
}
|
102
|
+
case 'unlinkAccount': {
|
103
|
+
result = await service.unlinkAccount(data.data);
|
104
|
+
break;
|
105
|
+
}
|
106
|
+
case 'updateAuthenticatorCounter': {
|
107
|
+
result = await service.updateAuthenticatorCounter(
|
108
|
+
data.data.credentialID,
|
109
|
+
data.data.counter,
|
110
|
+
);
|
111
|
+
break;
|
112
|
+
}
|
113
|
+
case 'updateSession': {
|
114
|
+
result = await service.updateSession(data.data);
|
115
|
+
break;
|
116
|
+
}
|
117
|
+
case 'updateUser': {
|
118
|
+
result = await service.updateUser(data.data);
|
119
|
+
break;
|
120
|
+
}
|
121
|
+
case 'useVerificationToken': {
|
122
|
+
result = await service.useVerificationToken(data.data);
|
123
|
+
break;
|
124
|
+
}
|
125
|
+
default: {
|
126
|
+
return NextResponse.json({ error: 'Invalid action', success: false }, { status: 400 });
|
127
|
+
}
|
128
|
+
}
|
129
|
+
return NextResponse.json({ data: result, success: true });
|
130
|
+
} catch (error) {
|
131
|
+
log('Error processing request:');
|
132
|
+
log(error);
|
133
|
+
return NextResponse.json({ error, success: false }, { status: 400 });
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
export const runtime = 'nodejs';
|
@@ -36,6 +36,15 @@ export const POST = async (req: Request): Promise<NextResponse> => {
|
|
36
36
|
},
|
37
37
|
);
|
38
38
|
}
|
39
|
+
case 'User.SuspensionStatus.Updated': {
|
40
|
+
if (data.isSuspended) {
|
41
|
+
return nextAuthUserService.safeSignOutUser({
|
42
|
+
provider: 'logto',
|
43
|
+
providerAccountId: data.id,
|
44
|
+
});
|
45
|
+
}
|
46
|
+
return NextResponse.json({ message: 'user reactivated', success: true }, { status: 200 });
|
47
|
+
}
|
39
48
|
|
40
49
|
default: {
|
41
50
|
pino.warn(
|
@@ -0,0 +1,27 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useTranslation } from 'react-i18next';
|
4
|
+
|
5
|
+
import { NewAPIProviderCard } from '@/config/modelProviders';
|
6
|
+
|
7
|
+
import ProviderDetail from '../[id]';
|
8
|
+
|
9
|
+
const Page = () => {
|
10
|
+
const { t } = useTranslation('modelProvider');
|
11
|
+
|
12
|
+
return (
|
13
|
+
<ProviderDetail
|
14
|
+
{...NewAPIProviderCard}
|
15
|
+
settings={{
|
16
|
+
...NewAPIProviderCard.settings,
|
17
|
+
proxyUrl: {
|
18
|
+
desc: t('newapi.apiUrl.desc'),
|
19
|
+
placeholder: 'https://any-newapi-provider.com/v1',
|
20
|
+
title: t('newapi.apiUrl.title'),
|
21
|
+
},
|
22
|
+
}}
|
23
|
+
/>
|
24
|
+
);
|
25
|
+
};
|
26
|
+
|
27
|
+
export default Page;
|
package/src/config/auth.ts
CHANGED
@@ -18,6 +18,8 @@ declare global {
|
|
18
18
|
|
19
19
|
NEXT_AUTH_DEBUG?: string;
|
20
20
|
|
21
|
+
NEXT_AUTH_SSO_SESSION_STRATEGY?: string;
|
22
|
+
|
21
23
|
AUTH0_CLIENT_ID?: string;
|
22
24
|
AUTH0_CLIENT_SECRET?: string;
|
23
25
|
AUTH0_ISSUER?: string;
|
@@ -159,6 +161,7 @@ export const getAuthConfig = () => {
|
|
159
161
|
NEXT_AUTH_SECRET: z.string().optional(),
|
160
162
|
NEXT_AUTH_SSO_PROVIDERS: z.string().optional().default('auth0'),
|
161
163
|
NEXT_AUTH_DEBUG: z.boolean().optional().default(false),
|
164
|
+
NEXT_AUTH_SSO_SESSION_STRATEGY: z.enum(['jwt', 'database']).optional().default('jwt'),
|
162
165
|
|
163
166
|
// Auth0
|
164
167
|
AUTH0_CLIENT_ID: z.string().optional(),
|
@@ -221,6 +224,7 @@ export const getAuthConfig = () => {
|
|
221
224
|
NEXT_AUTH_SSO_PROVIDERS: process.env.NEXT_AUTH_SSO_PROVIDERS,
|
222
225
|
NEXT_AUTH_SECRET: process.env.NEXT_AUTH_SECRET,
|
223
226
|
NEXT_AUTH_DEBUG: !!process.env.NEXT_AUTH_DEBUG,
|
227
|
+
NEXT_AUTH_SSO_SESSION_STRATEGY: process.env.NEXT_AUTH_SSO_SESSION_STRATEGY || 'jwt',
|
224
228
|
|
225
229
|
// Auth0
|
226
230
|
AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID,
|
@@ -32,6 +32,7 @@ import MistralProvider from './mistral';
|
|
32
32
|
import ModelScopeProvider from './modelscope';
|
33
33
|
import MoonshotProvider from './moonshot';
|
34
34
|
import NebiusProvider from './nebius';
|
35
|
+
import NewAPIProvider from './newapi';
|
35
36
|
import NovitaProvider from './novita';
|
36
37
|
import NvidiaProvider from './nvidia';
|
37
38
|
import OllamaProvider from './ollama';
|
@@ -135,6 +136,7 @@ export const DEFAULT_MODEL_PROVIDER_LIST = [
|
|
135
136
|
HuggingFaceProvider,
|
136
137
|
CloudflareProvider,
|
137
138
|
GithubProvider,
|
139
|
+
NewAPIProvider,
|
138
140
|
BflProvider,
|
139
141
|
NovitaProvider,
|
140
142
|
PPIOProvider,
|
@@ -221,6 +223,7 @@ export { default as MistralProviderCard } from './mistral';
|
|
221
223
|
export { default as ModelScopeProviderCard } from './modelscope';
|
222
224
|
export { default as MoonshotProviderCard } from './moonshot';
|
223
225
|
export { default as NebiusProviderCard } from './nebius';
|
226
|
+
export { default as NewAPIProviderCard } from './newapi';
|
224
227
|
export { default as NovitaProviderCard } from './novita';
|
225
228
|
export { default as NvidiaProviderCard } from './nvidia';
|
226
229
|
export { default as OllamaProviderCard } from './ollama';
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { ModelProviderCard } from '@/types/llm';
|
2
|
+
|
3
|
+
const NewAPI: ModelProviderCard = {
|
4
|
+
chatModels: [],
|
5
|
+
checkModel: 'gpt-4o-mini',
|
6
|
+
description: '开源的多个 AI 服务聚合统一转发平台',
|
7
|
+
enabled: true,
|
8
|
+
id: 'newapi',
|
9
|
+
name: 'New API',
|
10
|
+
settings: {
|
11
|
+
sdkType: 'router',
|
12
|
+
showModelFetcher: true,
|
13
|
+
},
|
14
|
+
url: 'https://github.com/Calcium-Ion/new-api',
|
15
|
+
};
|
16
|
+
|
17
|
+
export default NewAPI;
|