@lobehub/chat 1.82.8 → 1.82.9
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 +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/models.json +18 -3
- package/locales/ar/oauth.json +16 -0
- package/locales/bg-BG/models.json +18 -3
- package/locales/bg-BG/oauth.json +16 -0
- package/locales/de-DE/models.json +18 -3
- package/locales/de-DE/oauth.json +16 -0
- package/locales/en-US/models.json +18 -3
- package/locales/en-US/oauth.json +16 -0
- package/locales/es-ES/models.json +18 -3
- package/locales/es-ES/oauth.json +16 -0
- package/locales/fa-IR/models.json +18 -3
- package/locales/fa-IR/oauth.json +16 -0
- package/locales/fr-FR/models.json +18 -3
- package/locales/fr-FR/oauth.json +16 -0
- package/locales/it-IT/models.json +18 -3
- package/locales/it-IT/oauth.json +16 -0
- package/locales/ja-JP/models.json +18 -3
- package/locales/ja-JP/oauth.json +16 -0
- package/locales/ko-KR/models.json +18 -3
- package/locales/ko-KR/oauth.json +16 -0
- package/locales/nl-NL/models.json +18 -3
- package/locales/nl-NL/oauth.json +16 -0
- package/locales/pl-PL/models.json +18 -3
- package/locales/pl-PL/oauth.json +16 -0
- package/locales/pt-BR/models.json +18 -3
- package/locales/pt-BR/oauth.json +16 -0
- package/locales/ru-RU/models.json +18 -3
- package/locales/ru-RU/oauth.json +16 -0
- package/locales/tr-TR/models.json +18 -3
- package/locales/tr-TR/oauth.json +16 -0
- package/locales/vi-VN/models.json +18 -3
- package/locales/vi-VN/oauth.json +16 -0
- package/locales/zh-CN/models.json +19 -4
- package/locales/zh-CN/oauth.json +16 -0
- package/locales/zh-TW/models.json +18 -3
- package/locales/zh-TW/oauth.json +16 -0
- package/package.json +1 -1
- package/src/app/(backend)/oidc/consent/route.ts +12 -0
- package/src/app/[variants]/oauth/consent/[uid]/{Client.tsx → Consent.tsx} +6 -40
- package/src/app/[variants]/oauth/consent/[uid]/Login.tsx +130 -0
- package/src/app/[variants]/oauth/consent/[uid]/components/OAuthApplicationLogo.tsx +82 -0
- package/src/app/[variants]/oauth/consent/[uid]/page.tsx +12 -7
- package/src/app/[variants]/oauth/handoff/Client.tsx +98 -0
- package/src/app/[variants]/oauth/handoff/page.tsx +13 -0
- package/src/locales/default/oauth.ts +16 -0
- package/src/middleware.ts +1 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.82.
|
3
|
+
"version": "1.82.9",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -113,6 +113,18 @@ export async function POST(request: NextRequest) {
|
|
113
113
|
const internalRedirectUrlString = await oidcService.getInteractionResult(uid, result);
|
114
114
|
log('OIDC Provider internal redirect URL string: %s', internalRedirectUrlString);
|
115
115
|
|
116
|
+
// // Construct the handoff URL
|
117
|
+
// const handoffUrl = new URL('/oauth/handoff', request.nextUrl.origin);
|
118
|
+
// // Set the original redirect URL as the 'target' query parameter (URL encoded)
|
119
|
+
// handoffUrl.searchParams.set('target', internalRedirectUrlString);
|
120
|
+
//
|
121
|
+
// log('Redirecting to handoff page: %s', handoffUrl.toString());
|
122
|
+
// // Redirect to the handoff page
|
123
|
+
// return NextResponse.redirect(handoffUrl.toString(), {
|
124
|
+
// headers: request.headers, // Keep original headers if necessary
|
125
|
+
// status: 303,
|
126
|
+
// });
|
127
|
+
|
116
128
|
return NextResponse.redirect(internalRedirectUrlString, {
|
117
129
|
headers: request.headers,
|
118
130
|
status: 303,
|
@@ -1,15 +1,12 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
-
import { Icon } from '@lobehub/ui';
|
4
3
|
import { Button, Card, Divider, Typography } from 'antd';
|
5
4
|
import { createStyles } from 'antd-style';
|
6
|
-
import { Link2Icon, ServerIcon } from 'lucide-react';
|
7
|
-
import Image from 'next/image';
|
8
5
|
import React, { memo } from 'react';
|
9
6
|
import { useTranslation } from 'react-i18next';
|
10
7
|
import { Center, Flexbox } from 'react-layout-kit';
|
11
8
|
|
12
|
-
import
|
9
|
+
import OAuthApplicationLogo from './components/OAuthApplicationLogo';
|
13
10
|
|
14
11
|
interface ClientProps {
|
15
12
|
clientId: string;
|
@@ -129,42 +126,11 @@ const ConsentClient = memo<ClientProps>(
|
|
129
126
|
return (
|
130
127
|
<Center className={styles.container} gap={16}>
|
131
128
|
<Flexbox gap={40}>
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
src={clientMetadata.logo!}
|
138
|
-
unoptimized
|
139
|
-
width={64}
|
140
|
-
/>
|
141
|
-
</Flexbox>
|
142
|
-
) : (
|
143
|
-
<Flexbox align={'center'} gap={12} horizontal justify={'center'}>
|
144
|
-
<div className={styles.icon}>
|
145
|
-
{clientMetadata?.logo ? (
|
146
|
-
<Image
|
147
|
-
alt={clientDisplayName}
|
148
|
-
height={56}
|
149
|
-
src={clientMetadata?.logo}
|
150
|
-
unoptimized
|
151
|
-
width={56}
|
152
|
-
/>
|
153
|
-
) : (
|
154
|
-
<Icon icon={ServerIcon} />
|
155
|
-
)}
|
156
|
-
</div>
|
157
|
-
<div className={styles.connectorLine} />
|
158
|
-
<Center className={styles.connector}>
|
159
|
-
<Icon icon={Link2Icon} style={{ color: theme.colorTextSecondary, fontSize: 20 }} />
|
160
|
-
</Center>
|
161
|
-
<div className={styles.connectorLine} />
|
162
|
-
<div className={styles.lobeIcon}>
|
163
|
-
<ProductLogo height={48} style={{ objectFit: 'cover' }} width={48} />
|
164
|
-
</div>
|
165
|
-
</Flexbox>
|
166
|
-
)}
|
167
|
-
|
129
|
+
<OAuthApplicationLogo
|
130
|
+
clientDisplayName={clientDisplayName}
|
131
|
+
isFirstParty={clientMetadata.isFirstParty}
|
132
|
+
logoUrl={clientMetadata.logo}
|
133
|
+
/>
|
168
134
|
<Title className={styles.title} level={3}>
|
169
135
|
{t('consent.title', { clientName: clientDisplayName })}
|
170
136
|
</Title>
|
@@ -0,0 +1,130 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { Avatar } from '@lobehub/ui';
|
4
|
+
import { Button, Card, Skeleton, Typography } from 'antd';
|
5
|
+
import { createStyles } from 'antd-style';
|
6
|
+
import React, { memo } from 'react';
|
7
|
+
import { useTranslation } from 'react-i18next';
|
8
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
9
|
+
|
10
|
+
import { useUserStore } from '@/store/user';
|
11
|
+
import { userProfileSelectors } from '@/store/user/selectors';
|
12
|
+
|
13
|
+
import OAuthApplicationLogo from './components/OAuthApplicationLogo';
|
14
|
+
|
15
|
+
interface LoginConfirmProps {
|
16
|
+
clientMetadata: {
|
17
|
+
clientName?: string;
|
18
|
+
isFirstParty?: boolean;
|
19
|
+
logo?: string;
|
20
|
+
};
|
21
|
+
uid: string;
|
22
|
+
}
|
23
|
+
|
24
|
+
const { Title } = Typography;
|
25
|
+
|
26
|
+
const useStyles = createStyles(({ css, token }) => ({
|
27
|
+
authButton: css`
|
28
|
+
width: 100%;
|
29
|
+
height: 40px;
|
30
|
+
border-radius: ${token.borderRadius}px;
|
31
|
+
font-weight: 500;
|
32
|
+
`,
|
33
|
+
card: css`
|
34
|
+
width: 100%;
|
35
|
+
max-width: 500px;
|
36
|
+
border-color: ${token.colorBorderSecondary};
|
37
|
+
border-radius: 12px;
|
38
|
+
|
39
|
+
background: ${token.colorBgContainer};
|
40
|
+
`,
|
41
|
+
container: css`
|
42
|
+
width: 100%;
|
43
|
+
min-height: 100vh;
|
44
|
+
color: ${token.colorTextBase};
|
45
|
+
background-color: ${token.colorBgLayout};
|
46
|
+
`,
|
47
|
+
title: css`
|
48
|
+
margin-block-end: ${token.marginLG}px;
|
49
|
+
color: ${token.colorTextBase};
|
50
|
+
text-align: center;
|
51
|
+
`,
|
52
|
+
}));
|
53
|
+
|
54
|
+
const LoginConfirmClient = memo<LoginConfirmProps>(({ uid, clientMetadata }) => {
|
55
|
+
const { styles } = useStyles();
|
56
|
+
const { t } = useTranslation('oauth'); // Assuming translations are in 'oauth'
|
57
|
+
|
58
|
+
const clientDisplayName = clientMetadata?.clientName || 'the application';
|
59
|
+
|
60
|
+
const isUserStateInit = useUserStore((s) => s.isUserStateInit);
|
61
|
+
const avatar = useUserStore(userProfileSelectors.userAvatar);
|
62
|
+
const nickName = useUserStore(userProfileSelectors.nickName);
|
63
|
+
|
64
|
+
const titleText = t('login.title', { clientName: clientDisplayName });
|
65
|
+
const descriptionText = t('login.description', { clientName: clientDisplayName });
|
66
|
+
const buttonText = t('login.button'); // Or "Continue"
|
67
|
+
|
68
|
+
return (
|
69
|
+
<Center className={styles.container} gap={16}>
|
70
|
+
<Flexbox align={'center'} gap={40}>
|
71
|
+
{/* Branding section - similar to Consent */}
|
72
|
+
<OAuthApplicationLogo
|
73
|
+
clientDisplayName={clientDisplayName}
|
74
|
+
isFirstParty={clientMetadata.isFirstParty}
|
75
|
+
logoUrl={clientMetadata.logo}
|
76
|
+
/>
|
77
|
+
</Flexbox>
|
78
|
+
<Title className={styles.title} level={3}>
|
79
|
+
{titleText}
|
80
|
+
</Title>
|
81
|
+
|
82
|
+
<Card className={styles.card}>
|
83
|
+
<Flexbox gap={64}>
|
84
|
+
{/* Increased gap for better spacing */}
|
85
|
+
<Flexbox gap={24}>
|
86
|
+
<Center horizontal justify={'center'}>
|
87
|
+
{isUserStateInit ? (
|
88
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
89
|
+
<Avatar alt={nickName || ''} avatar={avatar} size={40} />
|
90
|
+
<div style={{ fontSize: 20 }}>{nickName}</div>
|
91
|
+
</Flexbox>
|
92
|
+
) : (
|
93
|
+
<Flexbox gap={8} horizontal>
|
94
|
+
<Skeleton.Avatar active />
|
95
|
+
<Skeleton.Button active />
|
96
|
+
</Flexbox>
|
97
|
+
)}
|
98
|
+
</Center>
|
99
|
+
<div style={{ textAlign: 'center' }}>{descriptionText}</div>
|
100
|
+
</Flexbox>
|
101
|
+
|
102
|
+
<Flexbox gap={16}>
|
103
|
+
{/* Form points to the endpoint handling login confirmation */}
|
104
|
+
<form action="/oidc/consent" method="post" style={{ width: '100%' }}>
|
105
|
+
{/* Adjust action URL */}
|
106
|
+
<input name="uid" type="hidden" value={uid} />
|
107
|
+
<input name="choice" type="hidden" value={'accept'} />
|
108
|
+
{/* Single confirmation button */}
|
109
|
+
<Button
|
110
|
+
className={styles.authButton}
|
111
|
+
disabled={!isUserStateInit}
|
112
|
+
htmlType="submit"
|
113
|
+
name="consent"
|
114
|
+
size="large"
|
115
|
+
type="primary"
|
116
|
+
value="accept"
|
117
|
+
>
|
118
|
+
{buttonText}
|
119
|
+
</Button>
|
120
|
+
</form>
|
121
|
+
</Flexbox>
|
122
|
+
</Flexbox>
|
123
|
+
</Card>
|
124
|
+
</Center>
|
125
|
+
);
|
126
|
+
});
|
127
|
+
|
128
|
+
LoginConfirmClient.displayName = 'LoginConfirmClient';
|
129
|
+
|
130
|
+
export default LoginConfirmClient;
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
2
|
+
import { createStyles } from 'antd-style';
|
3
|
+
import { Link2Icon, ServerIcon } from 'lucide-react';
|
4
|
+
import Image from 'next/image';
|
5
|
+
import React, { memo } from 'react';
|
6
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
7
|
+
|
8
|
+
import { ProductLogo } from '@/components/Branding';
|
9
|
+
|
10
|
+
const useStyles = createStyles(({ css, token }) => ({
|
11
|
+
connector: css`
|
12
|
+
width: 40px;
|
13
|
+
height: 40px;
|
14
|
+
`,
|
15
|
+
connectorLine: css`
|
16
|
+
width: 32px;
|
17
|
+
height: 1px;
|
18
|
+
background-color: ${token.colorBorderSecondary};
|
19
|
+
`,
|
20
|
+
icon: css`
|
21
|
+
overflow: hidden;
|
22
|
+
display: flex;
|
23
|
+
align-items: center;
|
24
|
+
justify-content: center;
|
25
|
+
|
26
|
+
width: 64px;
|
27
|
+
height: 64px;
|
28
|
+
border: 1px solid ${token.colorBorderSecondary};
|
29
|
+
border-radius: 16px;
|
30
|
+
|
31
|
+
background-color: ${token.colorBgElevated};
|
32
|
+
`,
|
33
|
+
lobeIcon: css`
|
34
|
+
overflow: hidden;
|
35
|
+
display: flex;
|
36
|
+
align-items: center;
|
37
|
+
justify-content: center;
|
38
|
+
|
39
|
+
width: 64px;
|
40
|
+
height: 64px;
|
41
|
+
border-radius: 50%;
|
42
|
+
|
43
|
+
background-color: ${token.colorBgElevated};
|
44
|
+
`,
|
45
|
+
}));
|
46
|
+
|
47
|
+
interface OAuthApplicationLogoProps {
|
48
|
+
clientDisplayName: string;
|
49
|
+
isFirstParty?: boolean;
|
50
|
+
logoUrl?: string;
|
51
|
+
}
|
52
|
+
|
53
|
+
const OAuthApplicationLogo = memo<OAuthApplicationLogoProps>(
|
54
|
+
({ isFirstParty, clientDisplayName, logoUrl }) => {
|
55
|
+
const { styles, theme } = useStyles();
|
56
|
+
return isFirstParty ? (
|
57
|
+
<Flexbox align={'center'} gap={12} horizontal justify={'center'}>
|
58
|
+
<Image alt={clientDisplayName} height={64} src={logoUrl!} unoptimized width={64} />
|
59
|
+
</Flexbox>
|
60
|
+
) : (
|
61
|
+
<Flexbox align={'center'} gap={12} horizontal justify={'center'}>
|
62
|
+
<div className={styles.icon}>
|
63
|
+
{logoUrl ? (
|
64
|
+
<Image alt={clientDisplayName} height={56} src={logoUrl} unoptimized width={56} />
|
65
|
+
) : (
|
66
|
+
<Icon icon={ServerIcon} />
|
67
|
+
)}
|
68
|
+
</div>
|
69
|
+
<div className={styles.connectorLine} />
|
70
|
+
<Center className={styles.connector}>
|
71
|
+
<Icon icon={Link2Icon} style={{ color: theme.colorTextSecondary, fontSize: 20 }} />
|
72
|
+
</Center>
|
73
|
+
<div className={styles.connectorLine} />
|
74
|
+
<div className={styles.lobeIcon}>
|
75
|
+
<ProductLogo height={48} style={{ objectFit: 'cover' }} width={48} />
|
76
|
+
</div>
|
77
|
+
</Flexbox>
|
78
|
+
);
|
79
|
+
},
|
80
|
+
);
|
81
|
+
|
82
|
+
export default OAuthApplicationLogo;
|
@@ -4,8 +4,9 @@ import { oidcEnv } from '@/envs/oidc';
|
|
4
4
|
import { defaultClients } from '@/libs/oidc-provider/config';
|
5
5
|
import { OIDCService } from '@/server/services/oidc';
|
6
6
|
|
7
|
-
import ConsentClient from './Client';
|
8
7
|
import ConsentClientError from './ClientError';
|
8
|
+
import Consent from './Consent';
|
9
|
+
import Login from './Login';
|
9
10
|
|
10
11
|
const InteractionPage = async (props: { params: Promise<{ uid: string }> }) => {
|
11
12
|
if (!oidcEnv.ENABLE_OIDC) return notFound();
|
@@ -37,15 +38,19 @@ const InteractionPage = async (props: { params: Promise<{ uid: string }> }) => {
|
|
37
38
|
|
38
39
|
const clientDetail = await oidcService.getClientMetadata(clientId);
|
39
40
|
|
41
|
+
const clientMetadata = {
|
42
|
+
clientName: clientDetail?.client_name,
|
43
|
+
isFirstParty: defaultClients.map((c) => c.client_id).includes(clientId),
|
44
|
+
logo: clientDetail?.logo_uri,
|
45
|
+
};
|
40
46
|
// 渲染客户端组件,无论是 login 还是 consent 类型
|
47
|
+
if (details.prompt.name === 'login')
|
48
|
+
return <Login clientMetadata={clientMetadata} uid={params.uid} />;
|
49
|
+
|
41
50
|
return (
|
42
|
-
<
|
51
|
+
<Consent
|
43
52
|
clientId={clientId}
|
44
|
-
clientMetadata={
|
45
|
-
clientName: clientDetail?.client_name,
|
46
|
-
isFirstParty: defaultClients.map((c) => c.client_id).includes(clientId),
|
47
|
-
logo: clientDetail?.logo_uri,
|
48
|
-
}}
|
53
|
+
clientMetadata={clientMetadata}
|
49
54
|
redirectUri={details.params.redirect_uri as string}
|
50
55
|
scopes={scopes}
|
51
56
|
uid={params.uid}
|
@@ -0,0 +1,98 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { Spin, Typography } from 'antd';
|
4
|
+
import { createStyles } from 'antd-style';
|
5
|
+
import { useSearchParams } from 'next/navigation';
|
6
|
+
import React, { useEffect, useState } from 'react';
|
7
|
+
import { useTranslation } from 'react-i18next';
|
8
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
9
|
+
|
10
|
+
const { Title, Paragraph } = Typography;
|
11
|
+
|
12
|
+
const useStyles = createStyles(({ css, token }) => ({
|
13
|
+
container: css`
|
14
|
+
width: 100%;
|
15
|
+
min-height: 100vh;
|
16
|
+
padding-block: 40px;
|
17
|
+
padding-inline: 24px;
|
18
|
+
|
19
|
+
color: ${token.colorTextBase};
|
20
|
+
|
21
|
+
background-color: ${token.colorBgLayout};
|
22
|
+
`,
|
23
|
+
content: css`
|
24
|
+
max-width: 600px;
|
25
|
+
text-align: center;
|
26
|
+
`,
|
27
|
+
message: css`
|
28
|
+
margin-block-end: ${token.marginXL}px;
|
29
|
+
color: ${token.colorTextSecondary};
|
30
|
+
`,
|
31
|
+
title: css`
|
32
|
+
margin-block-end: ${token.marginLG}px;
|
33
|
+
`,
|
34
|
+
}));
|
35
|
+
|
36
|
+
interface Status {
|
37
|
+
desc: string;
|
38
|
+
status: 'processing' | 'success';
|
39
|
+
title: string;
|
40
|
+
}
|
41
|
+
const AuthHandoffPage = () => {
|
42
|
+
const { styles } = useStyles();
|
43
|
+
const { t } = useTranslation('oauth'); // Assuming 'oauth' namespace exists
|
44
|
+
const searchParams = useSearchParams();
|
45
|
+
|
46
|
+
const [status, setStatus] = useState<Status>({
|
47
|
+
desc: t('handoff.desc.processing'),
|
48
|
+
status: 'processing',
|
49
|
+
title: t('handoff.title.processing'),
|
50
|
+
});
|
51
|
+
|
52
|
+
const [isError, setIsError] = useState<boolean>(false);
|
53
|
+
|
54
|
+
useEffect(() => {
|
55
|
+
const targetUrl = searchParams.get('target');
|
56
|
+
|
57
|
+
if (targetUrl) {
|
58
|
+
try {
|
59
|
+
const decodedTargetUrl = decodeURIComponent(targetUrl);
|
60
|
+
console.log(`Attempting redirect to: ${decodedTargetUrl}`);
|
61
|
+
|
62
|
+
window.location.href = decodedTargetUrl;
|
63
|
+
|
64
|
+
const url = new URL(decodedTargetUrl);
|
65
|
+
if (!url.pathname.startsWith('/oidc/auth')) {
|
66
|
+
setStatus({
|
67
|
+
desc: t('handoff.desc.success'),
|
68
|
+
status: 'success',
|
69
|
+
title: t('handoff.title.success'),
|
70
|
+
});
|
71
|
+
}
|
72
|
+
} catch (error) {
|
73
|
+
console.error('Error decoding or redirecting:', error);
|
74
|
+
// setMessage(
|
75
|
+
// t('handoff.error', '无法自动打开桌面应用。请检查链接是否有效或尝试手动打开应用。'),
|
76
|
+
// );
|
77
|
+
setIsError(true);
|
78
|
+
}
|
79
|
+
} else {
|
80
|
+
console.error('Missing target URL for handoff.');
|
81
|
+
setIsError(true);
|
82
|
+
}
|
83
|
+
}, [searchParams]);
|
84
|
+
|
85
|
+
return (
|
86
|
+
<Center className={styles.container} gap={12}>
|
87
|
+
{!isError && <Spin size="large" />}
|
88
|
+
<Flexbox align="center" className={styles.content} gap={16}>
|
89
|
+
<Title className={styles.title} level={3}>
|
90
|
+
{status.title}
|
91
|
+
</Title>
|
92
|
+
<Paragraph className={styles.message}>{status.desc}</Paragraph>
|
93
|
+
</Flexbox>
|
94
|
+
</Center>
|
95
|
+
);
|
96
|
+
};
|
97
|
+
|
98
|
+
export default AuthHandoffPage;
|
@@ -34,6 +34,22 @@ const oauth = {
|
|
34
34
|
subTitle: '您已拒绝授权应用访问您的 LobeChat 账户',
|
35
35
|
title: '授权被拒绝',
|
36
36
|
},
|
37
|
+
handoff: {
|
38
|
+
desc: {
|
39
|
+
processing: '应用正在处理授权,即将跳转下一个页面...',
|
40
|
+
success: '已尝试打开桌面应用。如果应用未自动打开,请手动切换。您可以稍后关闭此浏览器窗口。',
|
41
|
+
},
|
42
|
+
title: {
|
43
|
+
processing: '授权处理中...',
|
44
|
+
success: '授权已完成',
|
45
|
+
},
|
46
|
+
},
|
47
|
+
login: {
|
48
|
+
button: '确认登录',
|
49
|
+
description: '应用 {{clientName}} 申请使用您的账户进行登录',
|
50
|
+
title: '登录 {{clientName}}',
|
51
|
+
userWelcome: '欢迎回来,',
|
52
|
+
},
|
37
53
|
success: {
|
38
54
|
subTitle: '您已成功授权应用访问您的 LobeChat 账户,可以关闭该页面了',
|
39
55
|
title: '授权成功',
|