返回 AiToEarn
configSections.ts
根目录 / project / aitoearn-web / src / app / layout / ConfigManagerDialog / utils / configSections.ts
1 import type { ConfigPath, ConfigSectionView } from '../types'
2 import { ConfigEditorServiceTarget } from '@/api/config-editor/config-editor.types'
3 import { countLeafFields, countModifiedLeafFields } from './configFieldMeta'
4 import { getValueAtPath, isRecord } from './configPath'
5
6 interface SectionDefinition {
7 id: string
8 paths: ConfigPath[]
9 }
10
11 const serverSectionDefinitions: SectionDefinition[] = [
12 { id: 'relay', paths: [['relay'], ['ai', 'relay']] },
13 {
14 id: 'basic',
15 paths: [
16 ['environment'],
17 ['appDomain'],
18 ['globalPrefix'],
19 ['port'],
20 ['enableConfigLogging'],
21 ['enableBadRequestDetails'],
22 ['openapi'],
23 ['logger'],
24 ],
25 },
26 { id: 'channels', paths: [['channel']] },
27 { id: 'database', paths: [['mongodb']] },
28 { id: 'redis', paths: [['redis'], ['redlock']] },
29 { id: 'auth', paths: [['auth'], ['apiKey']] },
30 { id: 'assets', paths: [['assets']] },
31 { id: 'ai', paths: [['aiClient'], ['serverClient'], ['ai']] },
32 { id: 'agent', paths: [['agent']] },
33 ]
34
35 const aiSectionDefinitions: SectionDefinition[] = [
36 { id: 'relay', paths: [['relay'], ['ai', 'relay']] },
37 { id: 'aiProviders', paths: [['ai', 'openai'], ['ai', 'volcengine'], ['ai', 'grok'], ['ai', 'dashscope'], ['ai', 'gemini'], ['ai', 'anthropic']] },
38 {
39 id: 'basic',
40 paths: [
41 ['environment'],
42 ['appDomain'],
43 ['globalPrefix'],
44 ['port'],
45 ['enableConfigLogging'],
46 ['enableBadRequestDetails'],
47 ['openapi'],
48 ['logger'],
49 ],
50 },
51 { id: 'database', paths: [['mongodb']] },
52 { id: 'redis', paths: [['redis'], ['redlock']] },
53 { id: 'auth', paths: [['auth'], ['apiKey']] },
54 { id: 'assets', paths: [['assets']] },
55 { id: 'aiModels', paths: [['ai', 'models']] },
56 { id: 'aiDraft', paths: [['ai', 'draftGeneration']] },
57 { id: 'agent', paths: [['agent']] },
58 { id: 'aiClient', paths: [['aiClient'], ['serverClient']] },
59 ]
60
61 function getSectionDefinitions(serviceTarget: ConfigEditorServiceTarget) {
62 return serviceTarget === ConfigEditorServiceTarget.Ai ? aiSectionDefinitions : serverSectionDefinitions
63 }
64
65 export function getConfigSectionValue(config: Record<string, unknown>, path: ConfigPath) {
66 const value = getValueAtPath(config, path)
67 if (path.length !== 1 || path[0] !== 'ai' || !isRecord(value))
68 return value
69
70 const { relay: _relay, ...aiConfig } = value
71 return aiConfig
72 }
73
74 function sectionLabelKey(id: string) {
75 return `sections.${id}.label`
76 }
77
78 function sectionDescriptionKey(id: string) {
79 return `sections.${id}.description`
80 }
81
82 function translateWithFallback(t: (key: string) => string, key: string, fallback: string) {
83 const translated = t(key)
84 return translated === key ? fallback : translated
85 }
86
87 function hasConfiguredRelay(value: unknown): boolean {
88 if (typeof value === 'string')
89 return value.trim().length > 0
90
91 if (typeof value === 'number' || typeof value === 'boolean')
92 return true
93
94 if (Array.isArray(value))
95 return value.some(hasConfiguredRelay)
96
97 if (isRecord(value))
98 return Object.values(value).some(hasConfiguredRelay)
99
100 return false
101 }
102
103 function getSectionDescription(
104 sectionId: string,
105 relayConfigured: boolean,
106 t: (key: string) => string,
107 ) {
108 if (sectionId === 'channels' && relayConfigured) {
109 return translateWithFallback(
110 t,
111 'sections.channels.relayConfiguredDescription',
112 'Relay is configured. Editing channel platform settings is usually not recommended.',
113 )
114 }
115
116 return translateWithFallback(t, sectionDescriptionKey(sectionId), '')
117 }
118
119 export function buildConfigSections(
120 config: Record<string, unknown>,
121 originalConfig: Record<string, unknown> | null,
122 serviceTarget: ConfigEditorServiceTarget,
123 t: (key: string) => string,
124 ): ConfigSectionView[] {
125 const sectionDefinitions = getSectionDefinitions(serviceTarget)
126 const relayConfigured = serviceTarget === ConfigEditorServiceTarget.Server
127 && hasConfiguredRelay(getValueAtPath(config, ['relay']))
128 const knownTopLevelKeys = new Set(sectionDefinitions.flatMap(section => section.paths.map(path => String(path[0]))))
129 const knownAiKeys = new Set(sectionDefinitions.flatMap(section => section.paths
130 .filter(path => path[0] === 'ai' && typeof path[1] === 'string')
131 .map(path => String(path[1]))))
132 const sections = sectionDefinitions
133 .map((section) => {
134 const paths = section.paths.filter(path => getValueAtPath(config, path) !== undefined)
135 const fieldCount = paths.reduce((count, path) => count + countLeafFields(getConfigSectionValue(config, path)), 0)
136 const modifiedFieldCount = originalConfig
137 ? paths.reduce((count, path) => {
138 const value = getConfigSectionValue(config, path)
139 const originalValue = getConfigSectionValue(originalConfig, path)
140 return count + countModifiedLeafFields(value, originalValue)
141 }, 0)
142 : 0
143
144 return {
145 id: section.id,
146 label: translateWithFallback(t, sectionLabelKey(section.id), section.id),
147 description: getSectionDescription(section.id, relayConfigured, t),
148 paths,
149 fieldCount,
150 modifiedFieldCount,
151 notRecommended: section.id === 'channels' && relayConfigured,
152 }
153 })
154 .filter(section => section.paths.length > 0)
155
156 const advancedPaths = Object.keys(config)
157 .filter(key => !knownTopLevelKeys.has(key))
158 .map<ConfigPath>(key => [key])
159
160 if (serviceTarget === ConfigEditorServiceTarget.Ai && isRecord(config.ai)) {
161 Object.keys(config.ai)
162 .filter(key => !knownAiKeys.has(key))
163 .forEach(key => advancedPaths.push(['ai', key]))
164 }
165
166 if (advancedPaths.length > 0) {
167 sections.push({
168 id: 'advanced',
169 label: translateWithFallback(t, sectionLabelKey('advanced'), 'Advanced'),
170 description: translateWithFallback(t, sectionDescriptionKey('advanced'), ''),
171 paths: advancedPaths,
172 fieldCount: advancedPaths.reduce((count, path) => count + countLeafFields(getValueAtPath(config, path)), 0),
173 modifiedFieldCount: originalConfig
174 ? advancedPaths.reduce((count, path) => {
175 const value = getValueAtPath(config, path)
176 const originalValue = getValueAtPath(originalConfig, path)
177 return count + countModifiedLeafFields(value, originalValue)
178 }, 0)
179 : 0,
180 notRecommended: false,
181 })
182 }
183
184 return sections
185 }
186
186 lines TYPESCRIPT