@lobehub/chat 1.22.19 → 1.22.21
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/package.json +1 -1
- package/src/app/(backend)/api/webhooks/casdoor/validateRequest.ts +1 -1
- package/src/app/(backend)/api/webhooks/clerk/validateRequest.ts +1 -1
- package/src/app/(backend)/api/webhooks/logto/validateRequest.ts +1 -1
- package/src/app/(main)/discover/(detail)/assistant/[slug]/page.tsx +8 -5
- package/src/app/(main)/discover/(list)/(home)/page.tsx +5 -3
- package/src/app/(main)/discover/(list)/assistants/page.tsx +5 -3
- package/src/app/(main)/discover/(list)/models/page.tsx +8 -4
- package/src/app/(main)/discover/(list)/plugins/page.tsx +5 -3
- package/src/app/(main)/discover/(list)/providers/page.tsx +5 -3
- package/src/app/layout.tsx +1 -1
- package/src/config/modelProviders/anthropic.ts +17 -12
- package/src/config/modelProviders/stepfun.ts +53 -0
- package/src/layout/GlobalProvider/index.tsx +5 -4
- package/src/server/translation.ts +1 -1
- package/src/types/discover.ts +6 -0
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,56 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.22.21](https://github.com/lobehub/lobe-chat/compare/v1.22.20...v1.22.21)
|
6
|
+
|
7
|
+
<sup>Released on **2024-10-23**</sup>
|
8
|
+
|
9
|
+
#### ♻ Code Refactoring
|
10
|
+
|
11
|
+
- **misc**: Refactor cookie/headers to async mode.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### Code refactoring
|
19
|
+
|
20
|
+
- **misc**: Refactor cookie/headers to async mode, closes [#4459](https://github.com/lobehub/lobe-chat/issues/4459) ([98c5d21](https://github.com/lobehub/lobe-chat/commit/98c5d21))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
30
|
+
### [Version 1.22.20](https://github.com/lobehub/lobe-chat/compare/v1.22.19...v1.22.20)
|
31
|
+
|
32
|
+
<sup>Released on **2024-10-23**</sup>
|
33
|
+
|
34
|
+
#### 💄 Styles
|
35
|
+
|
36
|
+
- **misc**: Add new claude-3.5-sonnet model.
|
37
|
+
|
38
|
+
<br/>
|
39
|
+
|
40
|
+
<details>
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
42
|
+
|
43
|
+
#### Styles
|
44
|
+
|
45
|
+
- **misc**: Add new claude-3.5-sonnet model, closes [#4452](https://github.com/lobehub/lobe-chat/issues/4452) ([7102393](https://github.com/lobehub/lobe-chat/commit/7102393))
|
46
|
+
|
47
|
+
</details>
|
48
|
+
|
49
|
+
<div align="right">
|
50
|
+
|
51
|
+
[](#readme-top)
|
52
|
+
|
53
|
+
</div>
|
54
|
+
|
5
55
|
### [Version 1.22.19](https://github.com/lobehub/lobe-chat/compare/v1.22.18...v1.22.19)
|
6
56
|
|
7
57
|
<sup>Released on **2024-10-22**</sup>
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.22.
|
3
|
+
"version": "1.22.21",
|
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",
|
@@ -17,7 +17,7 @@ interface CasdoorWebhookPayload {
|
|
17
17
|
|
18
18
|
export const validateRequest = async (request: Request, secret?: string) => {
|
19
19
|
const payloadString = await request.text();
|
20
|
-
const headerPayload = headers();
|
20
|
+
const headerPayload = await headers();
|
21
21
|
const casdoorSecret = headerPayload.get('casdoor-secret')!;
|
22
22
|
try {
|
23
23
|
if (casdoorSecret === secret) {
|
@@ -4,7 +4,7 @@ import { Webhook } from 'svix';
|
|
4
4
|
|
5
5
|
export const validateRequest = async (request: Request, secret: string) => {
|
6
6
|
const payloadString = await request.text();
|
7
|
-
const headerPayload = headers();
|
7
|
+
const headerPayload = await headers();
|
8
8
|
|
9
9
|
const svixHeaders = {
|
10
10
|
'svix-id': headerPayload.get('svix-id')!,
|
@@ -26,7 +26,7 @@ interface LogtoWebhookPayload {
|
|
26
26
|
|
27
27
|
export const validateRequest = async (request: Request, signingKey: string) => {
|
28
28
|
const payloadString = await request.text();
|
29
|
-
const headerPayload = headers();
|
29
|
+
const headerPayload = await headers();
|
30
30
|
const logtoHeaderSignature = headerPayload.get('logto-signature-sha-256')!;
|
31
31
|
try {
|
32
32
|
const hmac = createHmac('sha256', signingKey);
|
@@ -2,12 +2,11 @@ import { notFound } from 'next/navigation';
|
|
2
2
|
import urlJoin from 'url-join';
|
3
3
|
|
4
4
|
import StructuredData from '@/components/StructuredData';
|
5
|
-
import { Locales } from '@/locales/resources';
|
6
5
|
import { ldModule } from '@/server/ld';
|
7
6
|
import { metadataModule } from '@/server/metadata';
|
8
7
|
import { DiscoverService } from '@/server/services/discover';
|
9
8
|
import { translation } from '@/server/translation';
|
10
|
-
import { DiscoverPlugintem } from '@/types/discover';
|
9
|
+
import { DiscoverPageProps, DiscoverPlugintem } from '@/types/discover';
|
11
10
|
import { isMobileDevice } from '@/utils/server/responsive';
|
12
11
|
|
13
12
|
import DetailLayout from '../../features/DetailLayout';
|
@@ -19,9 +18,10 @@ import Temp from './features/Temp';
|
|
19
18
|
// import ConversationExample from './features/ConversationExample';
|
20
19
|
// import SystemRole from './features/SystemRole';
|
21
20
|
|
22
|
-
|
21
|
+
export const generateMetadata = async (props: DiscoverPageProps) => {
|
22
|
+
const params = await props.params;
|
23
|
+
const searchParams = await props.searchParams;
|
23
24
|
|
24
|
-
export const generateMetadata = async ({ params, searchParams }: Props) => {
|
25
25
|
const { slug: identifier } = params;
|
26
26
|
const { t, locale } = await translation('metadata', searchParams?.hl);
|
27
27
|
|
@@ -56,7 +56,10 @@ export const generateMetadata = async ({ params, searchParams }: Props) => {
|
|
56
56
|
};
|
57
57
|
};
|
58
58
|
|
59
|
-
const Page = async (
|
59
|
+
const Page = async (props: DiscoverPageProps) => {
|
60
|
+
const params = await props.params;
|
61
|
+
const searchParams = await props.searchParams;
|
62
|
+
|
60
63
|
const { slug: identifier } = params;
|
61
64
|
const { t, locale } = await translation('metadata', searchParams?.hl);
|
62
65
|
const mobile = isMobileDevice();
|
@@ -7,9 +7,10 @@ import { translation } from '@/server/translation';
|
|
7
7
|
|
8
8
|
import Client from './Client';
|
9
9
|
|
10
|
-
type Props = { searchParams: { hl?: Locales } };
|
10
|
+
type Props = { searchParams: Promise<{ hl?: Locales }> };
|
11
11
|
|
12
|
-
export const generateMetadata = async (
|
12
|
+
export const generateMetadata = async (props: Props) => {
|
13
|
+
const searchParams = await props.searchParams;
|
13
14
|
const { t, locale } = await translation('metadata', searchParams?.hl);
|
14
15
|
return metadataModule.generate({
|
15
16
|
alternate: true,
|
@@ -20,7 +21,8 @@ export const generateMetadata = async ({ searchParams }: Props) => {
|
|
20
21
|
});
|
21
22
|
};
|
22
23
|
|
23
|
-
const Page = async (
|
24
|
+
const Page = async (props: Props) => {
|
25
|
+
const searchParams = await props.searchParams;
|
24
26
|
const { t, locale } = await translation('metadata', searchParams?.hl);
|
25
27
|
const ld = ldModule.generate({
|
26
28
|
description: t('discover.description'),
|
@@ -8,9 +8,10 @@ import { isMobileDevice } from '@/utils/server/responsive';
|
|
8
8
|
|
9
9
|
import List from './features/List';
|
10
10
|
|
11
|
-
type Props = { searchParams: { hl?: Locales } };
|
11
|
+
type Props = { searchParams: Promise<{ hl?: Locales }> };
|
12
12
|
|
13
|
-
export const generateMetadata = async (
|
13
|
+
export const generateMetadata = async (props: Props) => {
|
14
|
+
const searchParams = await props.searchParams;
|
14
15
|
const { t, locale } = await translation('metadata', searchParams?.hl);
|
15
16
|
return metadataModule.generate({
|
16
17
|
alternate: true,
|
@@ -21,7 +22,8 @@ export const generateMetadata = async ({ searchParams }: Props) => {
|
|
21
22
|
});
|
22
23
|
};
|
23
24
|
|
24
|
-
const Page = async (
|
25
|
+
const Page = async (props: Props) => {
|
26
|
+
const searchParams = await props.searchParams;
|
25
27
|
const { t, locale } = await translation('metadata', searchParams?.hl);
|
26
28
|
const mobile = isMobileDevice();
|
27
29
|
|
@@ -9,10 +9,12 @@ import { isMobileDevice } from '@/utils/server/responsive';
|
|
9
9
|
|
10
10
|
import List from './features/List';
|
11
11
|
|
12
|
-
type Props = { searchParams: { hl?: Locales } };
|
12
|
+
type Props = { searchParams: Promise<{ hl?: Locales }> };
|
13
13
|
|
14
|
-
export const generateMetadata = async (
|
15
|
-
const
|
14
|
+
export const generateMetadata = async (props: Props) => {
|
15
|
+
const searchParams = await props.searchParams;
|
16
|
+
|
17
|
+
const { t, locale } = await translation('metadata', searchParams.hl);
|
16
18
|
|
17
19
|
return metadataModule.generate({
|
18
20
|
alternate: true,
|
@@ -23,7 +25,9 @@ export const generateMetadata = async ({ searchParams }: Props) => {
|
|
23
25
|
});
|
24
26
|
};
|
25
27
|
|
26
|
-
const Page = async (
|
28
|
+
const Page = async (props: Props) => {
|
29
|
+
const searchParams = await props.searchParams;
|
30
|
+
|
27
31
|
const { t, locale } = await translation('metadata', searchParams?.hl);
|
28
32
|
const mobile = isMobileDevice();
|
29
33
|
|
@@ -8,9 +8,10 @@ import { isMobileDevice } from '@/utils/server/responsive';
|
|
8
8
|
|
9
9
|
import List from './features/List';
|
10
10
|
|
11
|
-
type Props = { searchParams: { hl?: Locales } };
|
11
|
+
type Props = { searchParams: Promise<{ hl?: Locales }> };
|
12
12
|
|
13
|
-
export const generateMetadata = async (
|
13
|
+
export const generateMetadata = async (props: Props) => {
|
14
|
+
const searchParams = await props.searchParams;
|
14
15
|
const { t, locale } = await translation('metadata', searchParams?.hl);
|
15
16
|
|
16
17
|
return metadataModule.generate({
|
@@ -22,7 +23,8 @@ export const generateMetadata = async ({ searchParams }: Props) => {
|
|
22
23
|
});
|
23
24
|
};
|
24
25
|
|
25
|
-
const Page = async (
|
26
|
+
const Page = async (props: Props) => {
|
27
|
+
const searchParams = await props.searchParams;
|
26
28
|
const { t, locale } = await translation('metadata', searchParams?.hl);
|
27
29
|
const mobile = isMobileDevice();
|
28
30
|
|
@@ -8,9 +8,10 @@ import { isMobileDevice } from '@/utils/server/responsive';
|
|
8
8
|
|
9
9
|
import List from './features/List';
|
10
10
|
|
11
|
-
type Props = { searchParams: { hl?: Locales } };
|
11
|
+
type Props = { searchParams: Promise<{ hl?: Locales }> };
|
12
12
|
|
13
|
-
export const generateMetadata = async (
|
13
|
+
export const generateMetadata = async (props: Props) => {
|
14
|
+
const searchParams = await props.searchParams;
|
14
15
|
const { t, locale } = await translation('metadata', searchParams?.hl);
|
15
16
|
|
16
17
|
return metadataModule.generate({
|
@@ -22,7 +23,8 @@ export const generateMetadata = async ({ searchParams }: Props) => {
|
|
22
23
|
});
|
23
24
|
};
|
24
25
|
|
25
|
-
const Page = async (
|
26
|
+
const Page = async (props: Props) => {
|
27
|
+
const searchParams = await props.searchParams;
|
26
28
|
const { t, locale } = await translation('metadata', searchParams?.hl);
|
27
29
|
const mobile = isMobileDevice();
|
28
30
|
|
package/src/app/layout.tsx
CHANGED
@@ -19,7 +19,7 @@ type RootLayoutProps = {
|
|
19
19
|
};
|
20
20
|
|
21
21
|
const RootLayout = async ({ children, modal }: RootLayoutProps) => {
|
22
|
-
const cookieStore = cookies();
|
22
|
+
const cookieStore = await cookies();
|
23
23
|
|
24
24
|
const lang = cookieStore.get(LOBE_LOCALE_COOKIE);
|
25
25
|
const direction = isRtlLang(lang?.value || DEFAULT_LANG) ? 'rtl' : 'ltr';
|
@@ -9,6 +9,23 @@ const Anthropic: ModelProviderCard = {
|
|
9
9
|
displayName: 'Claude 3.5 Sonnet',
|
10
10
|
enabled: true,
|
11
11
|
functionCall: true,
|
12
|
+
id: 'claude-3-5-sonnet-20241022',
|
13
|
+
maxOutput: 8192,
|
14
|
+
pricing: {
|
15
|
+
cachedInput: 0.3,
|
16
|
+
input: 3,
|
17
|
+
output: 15,
|
18
|
+
writeCacheInput: 3.75,
|
19
|
+
},
|
20
|
+
releasedAt: '2024-10-22',
|
21
|
+
tokens: 200_000,
|
22
|
+
vision: true,
|
23
|
+
},
|
24
|
+
{
|
25
|
+
description:
|
26
|
+
'Claude 3.5 Sonnet 提供了超越 Opus 的能力和比 Sonnet 更快的速度,同时保持与 Sonnet 相同的价格。Sonnet 特别擅长编程、数据科学、视觉处理、代理任务。',
|
27
|
+
displayName: 'Claude 3.5 Sonnet 0620',
|
28
|
+
functionCall: true,
|
12
29
|
id: 'claude-3-5-sonnet-20240620',
|
13
30
|
maxOutput: 8192,
|
14
31
|
pricing: {
|
@@ -95,18 +112,6 @@ const Anthropic: ModelProviderCard = {
|
|
95
112
|
releasedAt: '2023-07-11',
|
96
113
|
tokens: 100_000,
|
97
114
|
},
|
98
|
-
{
|
99
|
-
description: 'Anthropic 的模型用于低延迟、高吞吐量的文本生成,支持生成数百页的文本。',
|
100
|
-
displayName: 'Claude Instant 1.2',
|
101
|
-
id: 'claude-instant-1.2',
|
102
|
-
maxOutput: 4096,
|
103
|
-
pricing: {
|
104
|
-
input: 0.8,
|
105
|
-
output: 2.4,
|
106
|
-
},
|
107
|
-
releasedAt: '2023-08-09',
|
108
|
-
tokens: 100_000,
|
109
|
-
},
|
110
115
|
],
|
111
116
|
checkModel: 'claude-3-haiku-20240307',
|
112
117
|
description:
|
@@ -10,6 +10,11 @@ const Stepfun: ModelProviderCard = {
|
|
10
10
|
enabled: true,
|
11
11
|
functionCall: true,
|
12
12
|
id: 'step-1-flash',
|
13
|
+
pricing: {
|
14
|
+
currency: 'CNY',
|
15
|
+
input: 1,
|
16
|
+
output: 4,
|
17
|
+
},
|
13
18
|
tokens: 8000,
|
14
19
|
},
|
15
20
|
{
|
@@ -18,6 +23,11 @@ const Stepfun: ModelProviderCard = {
|
|
18
23
|
enabled: true,
|
19
24
|
functionCall: true,
|
20
25
|
id: 'step-1-8k',
|
26
|
+
pricing: {
|
27
|
+
currency: 'CNY',
|
28
|
+
input: 5,
|
29
|
+
output: 20,
|
30
|
+
},
|
21
31
|
tokens: 8000,
|
22
32
|
},
|
23
33
|
{
|
@@ -26,6 +36,11 @@ const Stepfun: ModelProviderCard = {
|
|
26
36
|
enabled: true,
|
27
37
|
functionCall: true,
|
28
38
|
id: 'step-1-32k',
|
39
|
+
pricing: {
|
40
|
+
currency: 'CNY',
|
41
|
+
input: 15,
|
42
|
+
output: 70,
|
43
|
+
},
|
29
44
|
tokens: 32_000,
|
30
45
|
},
|
31
46
|
{
|
@@ -34,6 +49,11 @@ const Stepfun: ModelProviderCard = {
|
|
34
49
|
enabled: true,
|
35
50
|
functionCall: true,
|
36
51
|
id: 'step-1-128k',
|
52
|
+
pricing: {
|
53
|
+
currency: 'CNY',
|
54
|
+
input: 40,
|
55
|
+
output: 200,
|
56
|
+
},
|
37
57
|
tokens: 128_000,
|
38
58
|
},
|
39
59
|
{
|
@@ -41,6 +61,11 @@ const Stepfun: ModelProviderCard = {
|
|
41
61
|
displayName: 'Step 1 256K',
|
42
62
|
functionCall: true,
|
43
63
|
id: 'step-1-256k',
|
64
|
+
pricing: {
|
65
|
+
currency: 'CNY',
|
66
|
+
input: 95,
|
67
|
+
output: 300,
|
68
|
+
},
|
44
69
|
tokens: 256_000,
|
45
70
|
},
|
46
71
|
{
|
@@ -49,6 +74,11 @@ const Stepfun: ModelProviderCard = {
|
|
49
74
|
enabled: true,
|
50
75
|
functionCall: true,
|
51
76
|
id: 'step-2-16k',
|
77
|
+
pricing: {
|
78
|
+
currency: 'CNY',
|
79
|
+
input: 38,
|
80
|
+
output: 120,
|
81
|
+
},
|
52
82
|
tokens: 16_000,
|
53
83
|
},
|
54
84
|
{
|
@@ -57,6 +87,11 @@ const Stepfun: ModelProviderCard = {
|
|
57
87
|
enabled: true,
|
58
88
|
functionCall: true,
|
59
89
|
id: 'step-1v-8k',
|
90
|
+
pricing: {
|
91
|
+
currency: 'CNY',
|
92
|
+
input: 5,
|
93
|
+
output: 20,
|
94
|
+
},
|
60
95
|
tokens: 8000,
|
61
96
|
vision: true,
|
62
97
|
},
|
@@ -66,6 +101,24 @@ const Stepfun: ModelProviderCard = {
|
|
66
101
|
enabled: true,
|
67
102
|
functionCall: true,
|
68
103
|
id: 'step-1v-32k',
|
104
|
+
pricing: {
|
105
|
+
currency: 'CNY',
|
106
|
+
input: 15,
|
107
|
+
output: 70,
|
108
|
+
},
|
109
|
+
tokens: 32_000,
|
110
|
+
vision: true,
|
111
|
+
},
|
112
|
+
{
|
113
|
+
description: '该模型拥有强大的视频理解能力。',
|
114
|
+
displayName: 'Step 1.5V Turbo',
|
115
|
+
enabled: true,
|
116
|
+
id: 'step-1.5v-turbo',
|
117
|
+
pricing: {
|
118
|
+
currency: 'CNY',
|
119
|
+
input: 8,
|
120
|
+
output: 35,
|
121
|
+
},
|
69
122
|
tokens: 32_000,
|
70
123
|
vision: true,
|
71
124
|
},
|
@@ -23,7 +23,8 @@ import QueryProvider from './Query';
|
|
23
23
|
import StoreInitialization from './StoreInitialization';
|
24
24
|
import StyleRegistry from './StyleRegistry';
|
25
25
|
|
26
|
-
const parserFallbackLang = () => {
|
26
|
+
const parserFallbackLang = async () => {
|
27
|
+
const header = await headers();
|
27
28
|
/**
|
28
29
|
* The arguments are as follows:
|
29
30
|
*
|
@@ -32,7 +33,7 @@ const parserFallbackLang = () => {
|
|
32
33
|
* 3) The default locale.
|
33
34
|
*/
|
34
35
|
let fallbackLang: string = resolveAcceptLanguage(
|
35
|
-
|
36
|
+
header.get('accept-language') || '',
|
36
37
|
// Invalid locale identifier 'ar'. A valid locale should follow the BCP 47 'language-country' format.
|
37
38
|
locales.map((locale) => (locale === 'ar' ? 'ar-EG' : locale)),
|
38
39
|
'en-US',
|
@@ -45,14 +46,14 @@ const parserFallbackLang = () => {
|
|
45
46
|
|
46
47
|
const GlobalLayout = async ({ children }: PropsWithChildren) => {
|
47
48
|
// get default theme config to use with ssr
|
48
|
-
const cookieStore = cookies();
|
49
|
+
const cookieStore = await cookies();
|
49
50
|
const appearance = cookieStore.get(LOBE_THEME_APPEARANCE);
|
50
51
|
const neutralColor = cookieStore.get(LOBE_THEME_NEUTRAL_COLOR);
|
51
52
|
const primaryColor = cookieStore.get(LOBE_THEME_PRIMARY_COLOR);
|
52
53
|
|
53
54
|
// get default locale config to use with ssr
|
54
55
|
const defaultLang = cookieStore.get(LOBE_LOCALE_COOKIE);
|
55
|
-
const fallbackLang = parserFallbackLang();
|
56
|
+
const fallbackLang = await parserFallbackLang();
|
56
57
|
|
57
58
|
// if it's a new user, there's no cookie
|
58
59
|
// So we need to use the fallback language parsed by accept-language
|
@@ -11,7 +11,7 @@ import { isDev } from '@/utils/env';
|
|
11
11
|
|
12
12
|
export const getLocale = async (hl?: string): Promise<Locales> => {
|
13
13
|
if (hl) return normalizeLocale(hl) as Locales;
|
14
|
-
const cookieStore = cookies();
|
14
|
+
const cookieStore = await cookies();
|
15
15
|
const defaultLang = cookieStore.get(LOBE_LOCALE_COOKIE);
|
16
16
|
return (defaultLang?.value || DEFAULT_LANG) as Locales;
|
17
17
|
};
|
package/src/types/discover.ts
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
2
2
|
import { LobeChatPluginMeta, Meta } from '@lobehub/chat-plugin-sdk/lib/types/market';
|
3
3
|
|
4
|
+
import { Locales } from '@/locales/resources';
|
4
5
|
import { ChatModelCard, ModelProviderCard } from '@/types/llm';
|
5
6
|
import { MetaData } from '@/types/meta';
|
6
7
|
import { LobeAgentSettings } from '@/types/session';
|
7
8
|
|
9
|
+
export interface DiscoverPageProps {
|
10
|
+
params: Promise<{ slug: string }>;
|
11
|
+
searchParams: Promise<{ hl?: Locales }>;
|
12
|
+
}
|
13
|
+
|
8
14
|
export enum AssistantCategory {
|
9
15
|
Academic = 'academic',
|
10
16
|
All = 'all',
|