Skip to content

Commit 664d6d4

Browse files
♻️ refactor(config): 重构配置管理系统架构
- 【类型】改进配置类型系统,使用条件类型优化类型推导 - 【结构】重构 ConfigSchema 文件结构,添加更多类型定义 - 【功能】将diffSimplification重命名为codeAnalysis并重组其结构 - 【特性】新增ZhipuAI、DashScope和Doubao提供商支持 - 【优化】改进配置验证逻辑,使用PROVIDER_REQUIRED_FIELDS映射表 - 【重构】修改commitOptions为commitFormat,优化相关配置项名称
1 parent fbae238 commit 664d6d4

File tree

4 files changed

+250
-256
lines changed

4 files changed

+250
-256
lines changed

src/config/ConfigSchema.ts

+93-32
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,22 @@ export const CONFIG_SCHEMA = {
5555
},
5656
provider: {
5757
type: "string",
58-
default: "OpenAI", // 其他都是用OpenAI的SDK适配
59-
enum: ["OpenAI", "Ollama", "VS Code Provided"],
58+
default: "OpenAI",
59+
enum: [
60+
"OpenAI",
61+
"Ollama",
62+
"VS Code Provided",
63+
"ZhipuAI",
64+
"DashScope",
65+
"Doubao",
66+
],
6067
description: "AI provider",
6168
},
6269
model: {
6370
type: "string",
6471
default: "gpt-3.5-turbo",
65-
description: "AI Model",
72+
description: "AI model",
73+
scope: "machine",
6674
},
6775
},
6876
providers: {
@@ -109,9 +117,9 @@ export const CONFIG_SCHEMA = {
109117
},
110118
},
111119
features: {
112-
// 功能配置
113-
diffSimplification: {
114-
enabled: {
120+
// 代码分析功能
121+
codeAnalysis: {
122+
simplifyDiff: {
115123
type: "boolean",
116124
default: false,
117125
description:
@@ -128,13 +136,14 @@ export const CONFIG_SCHEMA = {
128136
description: "保留的上下文行数",
129137
},
130138
},
131-
commitOptions: {
132-
allowMergeCommits: {
139+
// 提交相关功能
140+
commitFormat: {
141+
enableMergeCommit: {
133142
type: "boolean",
134143
default: false,
135144
description: "是否允许将多个文件的变更合并为一条提交信息",
136145
},
137-
useEmoji: {
146+
enableEmoji: {
138147
type: "boolean",
139148
default: true,
140149
description: "在提交信息中使用 emoji",
@@ -143,17 +152,45 @@ export const CONFIG_SCHEMA = {
143152
},
144153
} as const;
145154

146-
// 更新 ConfigValue 接口定义为更具体的联合类型
147-
type ConfigValueType = {
148-
type: "string" | "boolean" | "number";
149-
default: any;
155+
// 修改类型定义,添加 isSpecial 可选属性
156+
export type ConfigValueTypeBase = {
150157
description: string;
158+
isSpecial?: boolean;
159+
};
160+
161+
export type ConfigValueTypeString = ConfigValueTypeBase & {
162+
type: "string";
163+
default: string;
151164
enum?: readonly string[];
152165
enumDescriptions?: readonly string[];
153-
isSpecial?: boolean;
166+
scope?:
167+
| "machine"
168+
| "window"
169+
| "resource"
170+
| "application"
171+
| "language-overridable";
154172
};
155173

156-
export interface ConfigValue extends ConfigValueType {}
174+
export type ConfigValueTypeBoolean = ConfigValueTypeBase & {
175+
type: "boolean";
176+
default: boolean;
177+
};
178+
179+
export type ConfigValueTypeNumber = ConfigValueTypeBase & {
180+
type: "number";
181+
default: number;
182+
};
183+
184+
export type ConfigValueType =
185+
| ConfigValueTypeString
186+
| ConfigValueTypeBoolean
187+
| ConfigValueTypeNumber;
188+
189+
// 或者直接使用联合类型
190+
export type ConfigValue =
191+
| ConfigValueTypeString
192+
| ConfigValueTypeBoolean
193+
| ConfigValueTypeNumber;
157194

158195
// 添加配置值的接口定义
159196
export interface ConfigObject {
@@ -170,51 +207,75 @@ export type SchemaType = {
170207
// 生成类型
171208
export type ConfigPath = string; // 例如: "providers.openai.apiKey"
172209

173-
// 辅助函数:从模式生成配置键
210+
// 修改:辅助函数生成配置键的逻辑
174211
export function generateConfigKeys(
175212
schema: SchemaType,
176213
prefix: string = ""
177214
): Record<string, string> {
178215
const keys: Record<string, string> = {};
179216

180-
function traverse(obj: any, path: string = "") {
217+
function traverse(obj: ConfigObject, path: string = "") {
181218
for (const [key, value] of Object.entries(obj)) {
182219
const fullPath = path ? `${path}.${key}` : key;
183-
if ((value as any).type) {
184-
const configKey = `${prefix}${fullPath}`
185-
.replace(/\./g, "_")
186-
.toUpperCase();
220+
if (isConfigValue(value)) {
221+
// 对于配置值,生成完整的配置键
222+
const configKey = fullPath.replace(/\./g, "_").toUpperCase();
187223
keys[configKey] = `dish-ai-commit.${fullPath}`;
188224
} else {
189-
traverse(value, fullPath);
225+
// 对于嵌套对象,生成中间键和继续遍历
226+
const intermediateKey = fullPath.replace(/\./g, "_").toUpperCase();
227+
keys[intermediateKey] = `dish-ai-commit.${fullPath}`;
228+
traverse(value as ConfigObject, fullPath);
190229
}
191230
}
192231
}
193232

194-
traverse(schema);
233+
traverse(schema as unknown as ConfigObject);
195234
return keys;
196235
}
197236

198-
// 生成配置元数据
199-
export function generateConfigMetadata(schema: SchemaType) {
200-
const metadata: any[] = [];
237+
// 添加元数据类型定义
238+
export interface ConfigMetadataItem {
239+
key: string;
240+
defaultValue: any;
241+
nested: boolean;
242+
parent: string;
243+
description: string;
244+
type: string;
245+
enum?: readonly string[];
246+
enumDescriptions?: readonly string[];
247+
isSpecial?: boolean;
248+
}
249+
250+
// 修改生成配置元数据函数的类型定义
251+
export function generateConfigMetadata(
252+
schema: SchemaType
253+
): ConfigMetadataItem[] {
254+
const metadata: ConfigMetadataItem[] = [];
201255

202256
function traverse(obj: ConfigObject, path: string = "") {
203257
for (const [key, value] of Object.entries(obj)) {
204258
const fullPath = path ? `${path}.${key}` : key;
205-
if ("type" in value) {
206-
metadata.push({
259+
if (isConfigValue(value)) {
260+
const metadataItem: ConfigMetadataItem = {
207261
key: fullPath.replace(/\./g, "_").toUpperCase(),
208262
defaultValue: value.default,
209263
nested: fullPath.includes("."),
210264
parent: fullPath.split(".")[0],
211265
description: value.description,
212266
type: value.type,
213-
enum: value.enum,
214-
enumDescriptions: value.enumDescriptions,
215267
isSpecial: value.isSpecial,
216-
});
217-
} else {
268+
};
269+
270+
// 只有当值是 ConfigValueTypeString 类型时才添加 enum 和 enumDescriptions
271+
if (value.type === "string" && "enum" in value) {
272+
metadataItem.enum = value.enum;
273+
metadataItem.enumDescriptions = value.enumDescriptions;
274+
}
275+
276+
metadata.push(metadataItem);
277+
} else if (typeof value === "object") {
278+
// 处理嵌套对象
218279
traverse(value as ConfigObject, fullPath);
219280
}
220281
}

src/config/ConfigurationManager.ts

+75-48
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
ConfigKey,
55
ExtensionConfiguration,
66
type ConfigurationValueType,
7+
PROVIDER_REQUIRED_FIELDS,
78
} from "./types";
89
import { EXTENSION_NAME } from "../constants";
910
import { generateCommitMessageSystemPrompt } from "../prompt/prompt";
@@ -17,13 +18,15 @@ import {
1718
generateConfiguration,
1819
isConfigValue,
1920
} from "./ConfigSchema";
21+
import { getSystemPrompt } from "../ai/utils/generateHelper";
2022

2123
export class ConfigurationManager {
2224
private static instance: ConfigurationManager;
2325
private configuration: vscode.WorkspaceConfiguration;
2426
private configCache: Map<string, any> = new Map();
2527
private readonly disposables: vscode.Disposable[] = [];
2628
private context?: vscode.ExtensionContext;
29+
private configurationInProgress: boolean = false;
2730

2831
private getUpdatedValue<T>(key: string): T | undefined {
2932
// 直接从workspace configuration获取最新值
@@ -110,50 +113,75 @@ export class ConfigurationManager {
110113
public getConfig<K extends ConfigKey>(
111114
key: K,
112115
useCache: boolean = true
113-
): ConfigurationValueType[K] {
116+
): K extends keyof ConfigurationValueType
117+
? ConfigurationValueType[K]
118+
: string {
114119
console.log("获取配置项:", key, ConfigKeys);
115120
const configKey = ConfigKeys[key].replace("dish-ai-commit.", "");
116121

117122
if (!useCache) {
118-
// 直接从 configuration 获取最新值,确保返回正确的类型
119-
const value =
120-
this.configuration.get<ConfigurationValueType[K]>(configKey);
121-
return value as ConfigurationValueType[K];
123+
const value = this.configuration.get<string>(configKey);
124+
return value as K extends keyof ConfigurationValueType
125+
? ConfigurationValueType[K]
126+
: string;
122127
}
123128

124129
if (!this.configCache.has(configKey)) {
125-
const value =
126-
this.configuration.get<ConfigurationValueType[K]>(configKey);
130+
const value = this.configuration.get<string>(configKey);
127131
this.configCache.set(configKey, value);
128132
}
129-
return this.configCache.get(configKey) as ConfigurationValueType[K];
133+
return this.configCache.get(
134+
configKey
135+
) as K extends keyof ConfigurationValueType
136+
? ConfigurationValueType[K]
137+
: string;
130138
}
131139

132-
public getConfiguration(): ExtensionConfiguration {
133-
// 使用generateConfiguration自动生成配置
134-
const config = generateConfiguration(CONFIG_SCHEMA, (key: string) => {
135-
return this.configuration.get<any>(`${key}`);
136-
});
137-
138-
// 处理特殊情况:system prompt
139-
const currentScm = SCMFactory.getCurrentSCMType() || "git";
140-
if (!config.base.systemPrompt) {
141-
config.base.systemPrompt = generateCommitMessageSystemPrompt(
142-
config.base.language,
143-
config.features.commitOptions.allowMergeCommits,
144-
false,
145-
currentScm,
146-
config.features.commitOptions.useEmoji
147-
);
140+
public getConfiguration(
141+
skipSystemPrompt: boolean = false
142+
): ExtensionConfiguration {
143+
if (this.configurationInProgress) {
144+
// 如果已经在获取配置过程中,返回基本配置而不包含 systemPrompt
145+
const config = generateConfiguration(CONFIG_SCHEMA, (key: string) => {
146+
return this.configuration.get<any>(`${key}`);
147+
});
148+
return config as ExtensionConfiguration;
148149
}
149150

150-
return config as ExtensionConfiguration;
151+
try {
152+
this.configurationInProgress = true;
153+
154+
// 使用generateConfiguration自动生成配置
155+
const config = generateConfiguration(CONFIG_SCHEMA, (key: string) => {
156+
return this.configuration.get<any>(`${key}`);
157+
});
158+
159+
// 只在非跳过模式下且明确需要 systemPrompt 时才生成
160+
if (!skipSystemPrompt && !config.base.systemPrompt) {
161+
const currentScm = SCMFactory.getCurrentSCMType() || "git";
162+
const promptConfig = {
163+
...config.base,
164+
...config.features.commitFormat,
165+
...config.features.codeAnalysis,
166+
scm: currentScm,
167+
diff: "",
168+
additionalContext: "",
169+
model: {},
170+
};
171+
172+
config.base.systemPrompt = getSystemPrompt(promptConfig);
173+
}
174+
175+
return config as ExtensionConfiguration;
176+
} finally {
177+
this.configurationInProgress = false;
178+
}
151179
}
152180

153-
// 修改updateConfig方法签名
181+
// 修改updateConfig方法签名,使用条件类型处理值的类型
154182
public async updateConfig<K extends ConfigKey>(
155183
key: K,
156-
value: ConfigurationValueType[K]
184+
value: string
157185
): Promise<void> {
158186
await this.configuration.update(
159187
ConfigKeys[key].replace("dish-ai-commit.", ""),
@@ -243,24 +271,23 @@ export class ConfigurationManager {
243271
*/
244272
public async validateConfiguration(): Promise<boolean> {
245273
const config = this.getConfiguration();
246-
const provider = config.base.provider.toLowerCase();
247-
248-
switch (provider) {
249-
case "openai":
250-
return this.validateProviderConfig("openai", "apiKey");
251-
case "ollama":
252-
return this.validateProviderConfig("ollama", "baseUrl");
253-
case "zhipuai":
254-
return this.validateProviderConfig("zhipuai", "apiKey");
255-
case "dashscope":
256-
return this.validateProviderConfig("dashscope", "apiKey");
257-
case "doubao":
258-
return this.validateProviderConfig("doubao", "apiKey");
259-
case "vs code provided":
260-
return Promise.resolve(true);
261-
default:
262-
return Promise.resolve(false);
274+
const provider = (config.base.provider as string).toLowerCase();
275+
276+
// VS Code 提供的AI不需要验证
277+
if (provider === "vs code provided") {
278+
return true;
263279
}
280+
281+
// 检查是否是支持的提供商
282+
if (provider in PROVIDER_REQUIRED_FIELDS) {
283+
const requiredField = PROVIDER_REQUIRED_FIELDS[provider];
284+
return this.validateProviderConfig(
285+
provider as keyof ExtensionConfiguration["providers"],
286+
requiredField
287+
);
288+
}
289+
290+
return false;
264291
}
265292

266293
/**
@@ -302,12 +329,12 @@ export class ConfigurationManager {
302329
* 更新 AI 提供商和模型配置
303330
*/
304331
public async updateAIConfiguration(
305-
provider: string,
306-
model: string
332+
provider: ExtensionConfiguration["base"]["provider"],
333+
model: ExtensionConfiguration["base"]["model"]
307334
): Promise<void> {
308335
await Promise.all([
309-
this.updateConfig("BASE_PROVIDER", provider),
310-
this.updateConfig("BASE_MODEL", model),
336+
this.updateConfig("BASE_PROVIDER" as ConfigKey, provider),
337+
this.updateConfig("BASE_MODEL" as ConfigKey, model),
311338
]);
312339
}
313340
}

0 commit comments

Comments
 (0)