Skip to content

Commit

Permalink
🐛 fix: fix non-https crypto.subtile missing error (lobehub#1238)
Browse files Browse the repository at this point in the history
* 🐛 fix: fix gpt-4 missing with custom model

* 🐛 fix: fix switch locale holdup

* 🎨 chore: clean code

* 🐛 fix: fix non-https crypto error

* ✅ test: fix test
  • Loading branch information
arvinxx authored Feb 6, 2024
1 parent b8118a2 commit 1750d0b
Show file tree
Hide file tree
Showing 20 changed files with 193 additions and 84 deletions.
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ OPENAI_API_KEY=sk-xxxxxxxxx
#ZHIPU_API_KEY=xxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxx

########################################
############ Google AI Service ##########
########### Google AI Service ##########
########################################

#GOOGLE_API_KEY=xxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxx
#GOOGLE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

########################################
############ AWS Bedrock Service ##########
######### AWS Bedrock Service ##########
########################################

#AWS_REGION=us-east-1
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,6 @@ ENV AZURE_API_VERSION ""
ENV GOOGLE_API_KEY ""

# Zhipu
ENV Zhipu_API_KEY ""
ENV ZHIPU_API_KEY ""

CMD ["node", "server.js"]
3 changes: 1 addition & 2 deletions docs/Deployment/Environment-Variable.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ LobeChat provides additional configuration options during deployment, which can
- [`ACCESS_CODE`](#access_code)
- [Model Service Providers](#model-service-providers)
- [OpenAI](#openai)
- [`CUSTOM_MODELS`](#custom_models)
- [Azure OpenAI](#azure-openai)
- [Zhipu AI](#zhipu-ai)
- [Google AI](#google-ai)
Expand Down Expand Up @@ -62,7 +61,7 @@ Related discussions:
- [Reasons for errors when using third-party interfaces](https://github.com/lobehub/lobe-chat/discussions/734)
- [No response when filling in the proxy server address for chatting](https://github.com/lobehub/lobe-chat/discussions/1065)

### `CUSTOM_MODELS`
#### `CUSTOM_MODELS`

- Type: Optional
- Description: Used to control the model list. Use `+` to add a model, `-` to hide a model, and `model_name=display_name` to customize the display name of a model, separated by commas.
Expand Down
3 changes: 1 addition & 2 deletions docs/Deployment/Environment-Variable.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
- [`ACCESS_CODE`](#access_code)
- [模型服务商](#模型服务商)
- [OpenAI](#openai)
- [`CUSTOM_MODELS`](#custom_models)
- [Azure OpenAI](#azure-openai)
- [智谱 AI](#智谱-ai)
- [Google AI](#google-ai)
Expand Down Expand Up @@ -62,7 +61,7 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
- [使用第三方接口报错的原因](https://github.com/lobehub/lobe-chat/discussions/734)
- [代理服务器地址填了聊天没任何反应](https://github.com/lobehub/lobe-chat/discussions/1065)

### `CUSTOM_MODELS`
#### `CUSTOM_MODELS`

- 类型:可选
- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"@lobehub/lint": "latest",
"@next/bundle-analyzer": "^14",
"@next/eslint-plugin-next": "^14",
"@peculiar/webcrypto": "^1.4.5",
"@testing-library/jest-dom": "^6",
"@testing-library/react": "^14",
"@types/chroma-js": "^2",
Expand Down
12 changes: 7 additions & 5 deletions src/app/api/chat/[provider]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ export const POST = async (req: Request, { params }: { params: { provider: strin
} catch (e) {
// if catch the error, just return it
const err = e as AgentInitErrorPayload;

return createErrorResponse(err.errorType as ILobeAgentRuntimeErrorType, {
error: err.error,
provider: params.provider,
});
return createErrorResponse(
(err.errorType || ChatErrorType.InternalServerError) as ILobeAgentRuntimeErrorType,
{
error: err.error || e,
provider: params.provider,
},
);
}

// ============ 2. create chat completion ============ //
Expand Down
15 changes: 13 additions & 2 deletions src/app/api/chat/auth.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { importJWK, jwtVerify } from 'jose';

import { getServerConfig } from '@/config/server';
import { JWTPayload, JWT_SECRET_KEY } from '@/const/auth';
import { JWTPayload, JWT_SECRET_KEY, NON_HTTP_PREFIX } from '@/const/auth';
import { AgentRuntimeError } from '@/libs/agent-runtime';
import { ChatErrorType } from '@/types/fetch';

export const getJWTPayload = async (token: string) => {
export const getJWTPayload = async (token: string): Promise<JWTPayload> => {
//如果是 HTTP 协议发起的请求,直接解析 token
// 这是一个非常 hack 的解决方案,未来要找更好的解决方案来处理这个问题
// refs: https://github.com/lobehub/lobe-chat/pull/1238
if (token.startsWith(NON_HTTP_PREFIX)) {
const jwtParts = token.split('.');

const payload = jwtParts[1];

return JSON.parse(atob(payload));
}

const encoder = new TextEncoder();
const secretKey = await crypto.subtle.digest('SHA-256', encoder.encode(JWT_SECRET_KEY));

Expand Down
20 changes: 9 additions & 11 deletions src/app/api/errorResponse.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { consola } from 'consola';

import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '@/libs/agent-runtime';
import { ChatErrorType, ErrorResponse, ErrorType } from '@/types/fetch';
import { ErrorResponse, ErrorType } from '@/types/fetch';

const getStatus = (errorType: ILobeAgentRuntimeErrorType | ErrorType) => {
// InvalidAccessCode / InvalidAzureAPIKey / InvalidOpenAIAPIKey / InvalidZhipuAPIKey ....
if (errorType.toString().includes('Invalid')) return 401;

switch (errorType) {
case ChatErrorType.InvalidAccessCode:
case AgentRuntimeErrorType.NoOpenAIAPIKey:
case AgentRuntimeErrorType.InvalidAzureAPIKey:
case AgentRuntimeErrorType.InvalidBedrockCredentials:
case AgentRuntimeErrorType.InvalidZhipuAPIKey:
case AgentRuntimeErrorType.InvalidGoogleAPIKey: {
// TODO: Need to refactor to Invalid OpenAI API Key
case AgentRuntimeErrorType.NoOpenAIAPIKey: {
return 401;
}

Expand Down Expand Up @@ -38,7 +35,8 @@ const getStatus = (errorType: ILobeAgentRuntimeErrorType | ErrorType) => {
return 475;
}
}
return errorType;

return errorType as number;
};

export const createErrorResponse = (
Expand All @@ -50,7 +48,7 @@ export const createErrorResponse = (
const data: ErrorResponse = { body, errorType };

if (typeof statusCode !== 'number' || statusCode < 200 || statusCode > 599) {
consola.error(
console.error(
`current StatusCode: \`${statusCode}\` .`,
'Please go to `./src/app/api/errorResponse.ts` to defined the statusCode.',
);
Expand Down
5 changes: 4 additions & 1 deletion src/components/ModelProviderIcon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@ const ModelProviderIcon = memo<ModelProviderIconProps>(({ provider }) => {
);
}

default:
case ModelProvider.OpenAI: {
return <OpenAI size={20} />;
}

default: {
return null;
}
}
});

Expand Down
3 changes: 3 additions & 0 deletions src/config/modelProviders/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const OpenAI: ModelProviderCard = {
tokens: 4096,
},
{
displayName: 'GPT-3.5 Turbo 16K',
hidden: true,
id: 'gpt-3.5-turbo-16k',
tokens: 16_385,
Expand Down Expand Up @@ -65,12 +66,14 @@ const OpenAI: ModelProviderCard = {
vision: true,
},
{
displayName: 'GPT-4 Turbo Preview (1106)',
functionCall: true,
hidden: true,
id: 'gpt-4-1106-preview',
tokens: 128_000,
},
{
displayName: 'GPT-4',
functionCall: true,
hidden: true,
id: 'gpt-4',
Expand Down
1 change: 1 addition & 0 deletions src/const/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const LOBE_CHAT_AUTH_HEADER = 'X-lobe-chat-auth';

export const JWT_SECRET_KEY = 'LobeHub · LobeChat';
export const NON_HTTP_PREFIX = 'http_nosafe';

/* eslint-disable typescript-sort-keys/interface */
export interface JWTPayload {
Expand Down
9 changes: 2 additions & 7 deletions src/features/Conversation/Error/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const getErrorAlertConfig = (
errorType?: IPluginErrorType | ILobeAgentRuntimeErrorType | ErrorType,
): AlertProps | undefined => {
// OpenAIBizError / ZhipuBizError / GoogleBizError / ...
if (typeof errorType === 'string' && errorType.includes('Biz'))
if (typeof errorType === 'string' && (errorType.includes('Biz') || errorType.includes('Invalid')))
return {
extraDefaultExpand: true,
extraIsolate: true,
Expand All @@ -31,12 +31,7 @@ export const getErrorAlertConfig = (
};
}

case PluginErrorType.PluginSettingsInvalid:
case ChatErrorType.InvalidAccessCode:
case AgentRuntimeErrorType.NoOpenAIAPIKey:
case AgentRuntimeErrorType.InvalidBedrockCredentials:
case AgentRuntimeErrorType.InvalidGoogleAPIKey:
case AgentRuntimeErrorType.InvalidZhipuAPIKey: {
case ChatErrorType.InvalidAccessCode: {
return {
extraDefaultExpand: true,
extraIsolate: true,
Expand Down
1 change: 1 addition & 0 deletions src/layout/GlobalLayout/Locale.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const Locale = memo<LocaleLayoutProps>(({ children, defaultLang }) => {
const [lang, setLang] = useState(defaultLang);

const { data: locale } = useSWR(['antd-locale', lang], ([, key]) => getAntdLocale(key), {
dedupingInterval: 0,
revalidateOnFocus: false,
});

Expand Down
7 changes: 3 additions & 4 deletions src/libs/agent-runtime/error.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
/* eslint-disable sort-keys-fix/sort-keys-fix */

// ******* Runtime Biz Error ******* //
export const AgentRuntimeErrorType = {
AgentRuntimeError: 'AgentRuntimeError', // Agent Runtime 模块运行时错误
LocationNotSupportError: 'LocationNotSupportError',

OpenAIBizError: 'OpenAIBizError', // OpenAI 返回的业务错误
OpenAIBizError: 'OpenAIBizError',

NoOpenAIAPIKey: 'NoOpenAIAPIKey',

InvalidAzureAPIKey: 'InvalidAzureAPIKey',
AzureBizError: 'AzureBizError', // Azure 返回的业务错误
AzureBizError: 'AzureBizError',

InvalidZhipuAPIKey: 'InvalidZhipuAPIKey',
ZhipuBizError: 'ZhipuBizError',
Expand All @@ -19,7 +18,7 @@ export const AgentRuntimeErrorType = {
GoogleBizError: 'GoogleBizError',

InvalidBedrockCredentials: 'InvalidBedrockCredentials',
BedrockBizError: 'BedrockBizError', // Bedrock 返回的业务错误
BedrockBizError: 'BedrockBizError',
} as const;

export type ILobeAgentRuntimeErrorType =
Expand Down
32 changes: 3 additions & 29 deletions src/services/_auth.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,8 @@
import { SignJWT, importJWK } from 'jose';

import { JWTPayload, JWT_SECRET_KEY, LOBE_CHAT_AUTH_HEADER } from '@/const/auth';
import { JWTPayload, LOBE_CHAT_AUTH_HEADER } from '@/const/auth';
import { ModelProvider } from '@/libs/agent-runtime';
import { useGlobalStore } from '@/store/global';
import { modelProviderSelectors, settingsSelectors } from '@/store/global/selectors';

const createJWT = async (payload: JWTPayload) => {
// 将AccessCode转换成适合作为密钥的格式,例如使用SHA-256进行哈希
const encoder = new TextEncoder();
const secretKey = await crypto.subtle.digest('SHA-256', encoder.encode(JWT_SECRET_KEY));

// 将密钥导入为JWK格式
const jwkSecretKey = await importJWK(
{
k: Buffer.from(secretKey).toString('base64'),
kty: 'oct',
},
'HS256',
);

// 获取当前时间戳
const now = Math.floor(Date.now() / 1000);

// 创建JWT
return new SignJWT(payload as Record<string, any>)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt(now) // 设置JWT的iat(签发时间)声明
.setExpirationTime(now + 100) // 设置 JWT 的 exp(过期时间)为 100 s
.sign(jwkSecretKey);
};
import { createJWT } from '@/utils/jwt';

const getProviderAuthPayload = (provider: string) => {
switch (provider) {
Expand Down Expand Up @@ -81,7 +55,7 @@ const getProviderAuthPayload = (provider: string) => {
const createAuthTokenWithPayload = async (payload = {}) => {
const accessCode = settingsSelectors.password(useGlobalStore.getState());

return await createJWT({ accessCode, ...payload });
return await createJWT<JWTPayload>({ accessCode, ...payload });
};

interface AuthParams {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exports[`modelProviderSelectors > CUSTOM_MODELS > custom deletion, addition, and
{
"displayName": "gpt-4-32k",
"functionCall": true,
"hidden": undefined,
"id": "gpt-4-0125-preview",
"tokens": 128000,
},
Expand Down
57 changes: 57 additions & 0 deletions src/store/global/slices/settings/selectors/modelProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,62 @@ describe('modelProviderSelectors', () => {
expect(result).toMatchSnapshot();
});

it('should work correct with gpt-4', () => {
const s = merge(initialSettingsState, {
serverConfig: {
customModelName:
'-all,+gpt-3.5-turbo-1106,+gpt-3.5-turbo,+gpt-3.5-turbo-16k,+gpt-4,+gpt-4-32k,+gpt-4-1106-preview,+gpt-4-vision-preview',
},
}) as unknown as GlobalStore;

const result = modelProviderSelectors.modelSelectList(s).filter((r) => r.enabled);

expect(result[0].chatModels).toEqual([
{
displayName: 'GPT-3.5 Turbo (1106)',
functionCall: true,
id: 'gpt-3.5-turbo-1106',
tokens: 16385,
},
{
description: 'GPT 3.5 Turbo,适用于各种文本生成和理解任务',
displayName: 'GPT-3.5 Turbo',
functionCall: true,
id: 'gpt-3.5-turbo',
tokens: 4096,
},
{
displayName: 'GPT-3.5 Turbo 16K',
id: 'gpt-3.5-turbo-16k',
tokens: 16385,
},
{
displayName: 'GPT-4',
functionCall: true,
id: 'gpt-4',
tokens: 8192,
},
{
displayName: 'gpt-4-32k',
functionCall: true,
id: 'gpt-4-32k',
tokens: 32768,
},
{
displayName: 'GPT-4 Turbo Preview (1106)',
functionCall: true,
id: 'gpt-4-1106-preview',
tokens: 128000,
},
{
description: 'GPT-4 视觉预览版,支持视觉任务',
displayName: 'GPT-4 Turbo Vision (Preview)',
id: 'gpt-4-vision-preview',
tokens: 128000,
vision: true,
},
]);
});
it('duplicate naming model', () => {
const s = merge(initialSettingsState, {
serverConfig: {},
Expand Down Expand Up @@ -67,6 +123,7 @@ describe('modelProviderSelectors', () => {
const result = modelProviderSelectors.modelSelectList(s).filter((r) => r.enabled);

expect(result[0].chatModels.find((o) => o.id === 'gpt-4-1106-preview')).toEqual({
displayName: 'GPT-4 Turbo Preview (1106)',
functionCall: true,
id: 'gpt-4-1106-preview',
tokens: 128000,
Expand Down
Loading

0 comments on commit 1750d0b

Please sign in to comment.