Skip to content

Commit a4e506f

Browse files
committed
feat(dashboard): provider init
1 parent 223e90d commit a4e506f

File tree

25 files changed

+2033
-291
lines changed

25 files changed

+2033
-291
lines changed

be/apps/core/src/modules/setting/setting.constant.ts

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@ import { z } from 'zod'
33
import type { SettingDefinition, SettingMetadata } from './setting.type'
44

55
export const DEFAULT_SETTING_DEFINITIONS = {
6-
'ai.openai.apiKey': {
7-
isSensitive: true,
8-
schema: z.string().min(1, 'OpenAI API key cannot be empty'),
9-
},
10-
'ai.openai.baseUrl': {
11-
isSensitive: false,
12-
schema: z.url('OpenAI Base URL cannot be empty'),
13-
},
14-
'ai.embedding.model': {
15-
isSensitive: false,
16-
schema: z.string().min(1, 'AI Model name cannot be empty'),
17-
},
6+
// 'ai.openai.apiKey': {
7+
// isSensitive: true,
8+
// schema: z.string().min(1, 'OpenAI API key cannot be empty'),
9+
// },
10+
// 'ai.openai.baseUrl': {
11+
// isSensitive: false,
12+
// schema: z.url('OpenAI Base URL cannot be empty'),
13+
// },
14+
// 'ai.embedding.model': {
15+
// isSensitive: false,
16+
// schema: z.string().min(1, 'AI Model name cannot be empty'),
17+
// },
1818
'auth.google.clientId': {
1919
isSensitive: false,
2020
schema: z.string().min(1, 'Google Client ID cannot be empty'),
@@ -31,17 +31,44 @@ export const DEFAULT_SETTING_DEFINITIONS = {
3131
isSensitive: true,
3232
schema: z.string().min(1, 'GitHub Client secret cannot be empty'),
3333
},
34+
'builder.storage.providers': {
35+
isSensitive: false,
36+
schema: z.string().transform((value, ctx) => {
37+
const normalized = value.trim()
38+
if (normalized.length === 0) {
39+
return '[]'
40+
}
41+
42+
try {
43+
const parsed = JSON.parse(normalized)
44+
if (!Array.isArray(parsed)) {
45+
ctx.addIssue({
46+
code: z.ZodIssueCode.custom,
47+
message: 'Builder storage providers must be a JSON array',
48+
})
49+
return z.NEVER
50+
}
51+
return JSON.stringify(parsed)
52+
} catch {
53+
ctx.addIssue({
54+
code: z.ZodIssueCode.custom,
55+
message: 'Builder storage providers must be valid JSON',
56+
})
57+
return z.NEVER
58+
}
59+
}),
60+
},
61+
'builder.storage.activeProvider': {
62+
isSensitive: false,
63+
schema: z.string().transform((value) => value.trim()),
64+
},
3465
'http.cors.allowedOrigins': {
3566
isSensitive: false,
3667
schema: z
3768
.string()
3869
.min(1, 'CORS allowed origins cannot be empty')
3970
.transform((value) => value.trim()),
4071
},
41-
'services.amap.apiKey': {
42-
isSensitive: true,
43-
schema: z.string().min(1, 'Gaode Map API key cannot be empty'),
44-
},
4572
} as const satisfies Record<string, SettingDefinition>
4673

4774
export const DEFAULT_SETTING_METADATA = Object.fromEntries(

be/apps/core/src/modules/setting/setting.type.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { settings } from '@memora/db'
1+
import type { settings } from '@afilmory/db'
22
import type { z } from 'zod'
33

44
import type {

be/apps/core/src/modules/setting/setting.ui-schema.ts

Lines changed: 7 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -6,97 +6,26 @@ function getIsSensitive(key: SettingKeyType): boolean {
66
return DEFAULT_SETTING_METADATA[key]?.isSensitive ?? false
77
}
88

9-
export const SETTING_UI_SCHEMA_VERSION = '1.2.0'
9+
export const SETTING_UI_SCHEMA_VERSION = '1.1.0'
1010

1111
export const SETTING_UI_SCHEMA: SettingUiSchema = {
1212
version: SETTING_UI_SCHEMA_VERSION,
1313
title: '系统设置',
14-
description: '管理 Memora 平台的全局行为与第三方服务接入。',
14+
description: '管理 AFilmory 系统的全局行为与第三方服务接入。',
1515
sections: [
16-
{
17-
type: 'section',
18-
id: 'ai',
19-
title: 'AI 与智能功能',
20-
description: '配置 OpenAI 以及嵌入式模型以启用智能特性。',
21-
icon: 'i-lucide-brain-circuit',
22-
children: [
23-
{
24-
type: 'group',
25-
id: 'ai-openai',
26-
title: 'OpenAI 接入',
27-
description: '为 API 请求配置服务端所需的 OpenAI 凭据。',
28-
icon: 'i-lucide-bot',
29-
children: [
30-
{
31-
type: 'field',
32-
id: 'ai.openai.apiKey',
33-
title: 'API Key',
34-
description: '用于调用 OpenAI 接口的密钥,通常以 “sk-” 开头。',
35-
helperText: '出于安全考虑仅在受信环境中填写,提交后会进行加密存储。',
36-
key: 'ai.openai.apiKey',
37-
isSensitive: getIsSensitive('ai.openai.apiKey'),
38-
component: {
39-
type: 'secret',
40-
placeholder: 'sk-********************************',
41-
autoComplete: 'off',
42-
revealable: true,
43-
},
44-
},
45-
{
46-
type: 'field',
47-
id: 'ai.openai.baseUrl',
48-
title: '自定义 Base URL',
49-
description: '可选,若你使用自建代理,填写代理的完整 URL。',
50-
key: 'ai.openai.baseUrl',
51-
helperText: '例如 https://api.openai.com/v1,末尾无需斜杠。',
52-
isSensitive: getIsSensitive('ai.openai.baseUrl'),
53-
component: {
54-
type: 'text',
55-
inputType: 'url',
56-
placeholder: 'https://api.openai.com/v1',
57-
autoComplete: 'off',
58-
},
59-
},
60-
],
61-
},
62-
{
63-
type: 'group',
64-
id: 'ai-embedding',
65-
title: '向量嵌入模型',
66-
description: '用于语义搜索或文本向量化的模型。',
67-
icon: 'i-lucide-fingerprint',
68-
children: [
69-
{
70-
type: 'field',
71-
id: 'ai.embedding.model',
72-
title: 'Embedding 模型标识',
73-
description: '例如 text-embedding-3-large、text-embedding-3-small 等。',
74-
key: 'ai.embedding.model',
75-
helperText: '填写完整的模型名称,留空将导致相关功能不可用。',
76-
isSensitive: getIsSensitive('ai.embedding.model'),
77-
component: {
78-
type: 'text',
79-
placeholder: 'text-embedding-3-large',
80-
autoComplete: 'off',
81-
},
82-
},
83-
],
84-
},
85-
],
86-
},
8716
{
8817
type: 'section',
8918
id: 'auth',
9019
title: '登录与认证',
9120
description: '配置第三方 OAuth 登录用于后台访问控制。',
92-
icon: 'i-lucide-shield-check',
21+
icon: 'shield-check',
9322
children: [
9423
{
9524
type: 'group',
9625
id: 'auth-google',
9726
title: 'Google OAuth',
9827
description: '在 Google Cloud Console 中创建 OAuth 应用后填写以下信息。',
99-
icon: 'i-lucide-badge-check',
28+
icon: 'badge-check',
10029
children: [
10130
{
10231
type: 'field',
@@ -133,7 +62,7 @@ export const SETTING_UI_SCHEMA: SettingUiSchema = {
13362
id: 'auth-github',
13463
title: 'GitHub OAuth',
13564
description: '在 GitHub OAuth Apps 中创建应用后填写。',
136-
icon: 'i-lucide-github',
65+
icon: 'github',
13766
children: [
13867
{
13968
type: 'field',
@@ -172,14 +101,14 @@ export const SETTING_UI_SCHEMA: SettingUiSchema = {
172101
id: 'http',
173102
title: 'HTTP 与安全',
174103
description: '控制跨域访问等 Web 层配置。',
175-
icon: 'i-lucide-globe-2',
104+
icon: 'globe-2',
176105
children: [
177106
{
178107
type: 'group',
179108
id: 'http-cors',
180109
title: '跨域策略 (CORS)',
181110
description: '配置允许访问后台接口的来源列表。',
182-
icon: 'i-lucide-shield-alert',
111+
icon: 'shield-alert',
183112
children: [
184113
{
185114
type: 'field',
@@ -200,39 +129,6 @@ export const SETTING_UI_SCHEMA: SettingUiSchema = {
200129
},
201130
],
202131
},
203-
{
204-
type: 'section',
205-
id: 'services',
206-
title: '地图与定位',
207-
description: '配置地图底图与地理编码等服务。',
208-
icon: 'i-lucide-map',
209-
children: [
210-
{
211-
type: 'group',
212-
id: 'services-amap',
213-
title: '高德地图接入',
214-
description: '填写高德地图 Web 服务 Key 以启用后台地图选点与地理搜索能力。',
215-
icon: 'i-lucide-map-pinned',
216-
children: [
217-
{
218-
type: 'field',
219-
id: 'services.amap.apiKey',
220-
title: '高德地图 Key',
221-
description: '前往高德开发者控制台创建 Web 服务 Key,并授权所需的 IP/域名后填入。',
222-
helperText: '提交后将加密存储,仅后台调用地图与地理编码接口。',
223-
key: 'services.amap.apiKey',
224-
isSensitive: getIsSensitive('services.amap.apiKey'),
225-
component: {
226-
type: 'secret',
227-
placeholder: '****************',
228-
autoComplete: 'off',
229-
revealable: true,
230-
},
231-
},
232-
],
233-
},
234-
],
235-
},
236132
],
237133
} satisfies SettingUiSchema
238134

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
export type UiFieldComponentType = 'text' | 'secret' | 'textarea' | 'select' | 'slot'
2+
3+
interface UiFieldComponentBase<Type extends UiFieldComponentType> {
4+
readonly type: Type
5+
}
6+
7+
export interface UiTextInputComponent extends UiFieldComponentBase<'text'> {
8+
readonly inputType?: 'text' | 'email' | 'url' | 'number'
9+
readonly placeholder?: string
10+
readonly autoComplete?: string
11+
}
12+
13+
export interface UiSecretInputComponent extends UiFieldComponentBase<'secret'> {
14+
readonly placeholder?: string
15+
readonly autoComplete?: string
16+
readonly revealable?: boolean
17+
}
18+
19+
export interface UiTextareaComponent extends UiFieldComponentBase<'textarea'> {
20+
readonly placeholder?: string
21+
readonly minRows?: number
22+
readonly maxRows?: number
23+
}
24+
25+
export interface UiSelectComponent extends UiFieldComponentBase<'select'> {
26+
readonly placeholder?: string
27+
readonly options?: readonly string[]
28+
readonly allowCustom?: boolean
29+
}
30+
31+
export interface UiSlotComponent<Key extends string = string> extends UiFieldComponentBase<'slot'> {
32+
readonly name: string
33+
readonly fields?: ReadonlyArray<{ key: Key; label?: string; required?: boolean }>
34+
readonly props?: Readonly<Record<string, unknown>>
35+
}
36+
37+
export type UiFieldComponentDefinition<Key extends string = string> =
38+
| UiTextInputComponent
39+
| UiSecretInputComponent
40+
| UiTextareaComponent
41+
| UiSelectComponent
42+
| UiSlotComponent<Key>
43+
44+
interface BaseUiNode {
45+
readonly id: string
46+
readonly title: string
47+
readonly description?: string | null
48+
}
49+
50+
export interface UiFieldNode<Key extends string = string> extends BaseUiNode {
51+
readonly type: 'field'
52+
readonly key: Key
53+
readonly component: UiFieldComponentDefinition<Key>
54+
readonly helperText?: string | null
55+
readonly docUrl?: string | null
56+
readonly isSensitive?: boolean
57+
readonly required?: boolean
58+
readonly icon?: string
59+
readonly hidden?: boolean
60+
}
61+
62+
export interface UiGroupNode<Key extends string = string> extends BaseUiNode {
63+
readonly type: 'group'
64+
readonly icon?: string
65+
readonly children: ReadonlyArray<UiNode<Key>>
66+
}
67+
68+
export interface UiSectionNode<Key extends string = string> extends BaseUiNode {
69+
readonly type: 'section'
70+
readonly icon?: string
71+
readonly layout?: {
72+
readonly columns?: number
73+
}
74+
readonly children: ReadonlyArray<UiNode<Key>>
75+
}
76+
77+
export type UiNode<Key extends string = string> = UiSectionNode<Key> | UiGroupNode<Key> | UiFieldNode<Key>
78+
79+
export interface UiSchema<Key extends string = string> {
80+
readonly version: string
81+
readonly title: string
82+
readonly description?: string | null
83+
readonly sections: ReadonlyArray<UiSectionNode<Key>>
84+
}
85+
86+
export interface UiSchemaResponse<Key extends string = string, Value = string | null> {
87+
readonly schema: UiSchema<Key>
88+
readonly values?: Partial<Record<Key, Value>>
89+
}

be/apps/dashboard/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
"@radix-ui/react-dialog": "1.1.15",
2828
"@radix-ui/react-label": "2.1.7",
2929
"@radix-ui/react-scroll-area": "1.2.10",
30-
"@radix-ui/react-select": "2.2.6",
3130
"@radix-ui/react-slider": "1.3.6",
3231
"@radix-ui/react-slot": "1.2.3",
3332
"@radix-ui/react-switch": "1.2.6",

be/apps/dashboard/src/modules/.gitkeep

Whitespace-only changes.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { coreApi } from '~/lib/api-client'
2+
3+
import type { SettingEntryInput, SettingUiSchemaResponse } from './types'
4+
5+
export const getSettingUiSchema = async () => {
6+
return await coreApi<SettingUiSchemaResponse>('/settings/ui-schema')
7+
}
8+
9+
export const getSettings = async (keys: ReadonlyArray<string>) => {
10+
return await coreApi<{
11+
keys: string[]
12+
values: Record<string, string | null>
13+
}>('/settings', {
14+
query: { keys },
15+
})
16+
}
17+
18+
export const updateSettings = async (entries: ReadonlyArray<SettingEntryInput>) => {
19+
return await coreApi<{ updated: ReadonlyArray<SettingEntryInput> }>('/settings', {
20+
method: 'POST',
21+
body: { entries },
22+
})
23+
}

0 commit comments

Comments
 (0)