Skip to content

Commit

Permalink
♻️ refactor(config): 重构配置管理系统架构
Browse files Browse the repository at this point in the history
- 【类型】改进配置类型系统,使用条件类型优化类型推导
- 【结构】重构 ConfigSchema 文件结构,添加更多类型定义
- 【功能】将diffSimplification重命名为codeAnalysis并重组其结构
- 【特性】新增ZhipuAI、DashScope和Doubao提供商支持
- 【优化】改进配置验证逻辑,使用PROVIDER_REQUIRED_FIELDS映射表
- 【重构】修改commitOptions为commitFormat,优化相关配置项名称
  • Loading branch information
littleCareless committed Dec 12, 2024
1 parent fbae238 commit 664d6d4
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 256 deletions.
125 changes: 93 additions & 32 deletions src/config/ConfigSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,22 @@ export const CONFIG_SCHEMA = {
},
provider: {
type: "string",
default: "OpenAI", // 其他都是用OpenAI的SDK适配
enum: ["OpenAI", "Ollama", "VS Code Provided"],
default: "OpenAI",
enum: [
"OpenAI",
"Ollama",
"VS Code Provided",
"ZhipuAI",
"DashScope",
"Doubao",
],
description: "AI provider",
},
model: {
type: "string",
default: "gpt-3.5-turbo",
description: "AI Model",
description: "AI model",
scope: "machine",
},
},
providers: {
Expand Down Expand Up @@ -109,9 +117,9 @@ export const CONFIG_SCHEMA = {
},
},
features: {
// 功能配置
diffSimplification: {
enabled: {
// 代码分析功能
codeAnalysis: {
simplifyDiff: {
type: "boolean",
default: false,
description:
Expand All @@ -128,13 +136,14 @@ export const CONFIG_SCHEMA = {
description: "保留的上下文行数",
},
},
commitOptions: {
allowMergeCommits: {
// 提交相关功能
commitFormat: {
enableMergeCommit: {
type: "boolean",
default: false,
description: "是否允许将多个文件的变更合并为一条提交信息",
},
useEmoji: {
enableEmoji: {
type: "boolean",
default: true,
description: "在提交信息中使用 emoji",
Expand All @@ -143,17 +152,45 @@ export const CONFIG_SCHEMA = {
},
} as const;

// 更新 ConfigValue 接口定义为更具体的联合类型
type ConfigValueType = {
type: "string" | "boolean" | "number";
default: any;
// 修改类型定义,添加 isSpecial 可选属性
export type ConfigValueTypeBase = {
description: string;
isSpecial?: boolean;
};

export type ConfigValueTypeString = ConfigValueTypeBase & {
type: "string";
default: string;
enum?: readonly string[];
enumDescriptions?: readonly string[];
isSpecial?: boolean;
scope?:
| "machine"
| "window"
| "resource"
| "application"
| "language-overridable";
};

export interface ConfigValue extends ConfigValueType {}
export type ConfigValueTypeBoolean = ConfigValueTypeBase & {
type: "boolean";
default: boolean;
};

export type ConfigValueTypeNumber = ConfigValueTypeBase & {
type: "number";
default: number;
};

export type ConfigValueType =
| ConfigValueTypeString
| ConfigValueTypeBoolean
| ConfigValueTypeNumber;

// 或者直接使用联合类型
export type ConfigValue =
| ConfigValueTypeString
| ConfigValueTypeBoolean
| ConfigValueTypeNumber;

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

// 辅助函数:从模式生成配置键
// 修改:辅助函数生成配置键的逻辑
export function generateConfigKeys(
schema: SchemaType,
prefix: string = ""
): Record<string, string> {
const keys: Record<string, string> = {};

function traverse(obj: any, path: string = "") {
function traverse(obj: ConfigObject, path: string = "") {
for (const [key, value] of Object.entries(obj)) {
const fullPath = path ? `${path}.${key}` : key;
if ((value as any).type) {
const configKey = `${prefix}${fullPath}`
.replace(/\./g, "_")
.toUpperCase();
if (isConfigValue(value)) {
// 对于配置值,生成完整的配置键
const configKey = fullPath.replace(/\./g, "_").toUpperCase();
keys[configKey] = `dish-ai-commit.${fullPath}`;
} else {
traverse(value, fullPath);
// 对于嵌套对象,生成中间键和继续遍历
const intermediateKey = fullPath.replace(/\./g, "_").toUpperCase();
keys[intermediateKey] = `dish-ai-commit.${fullPath}`;
traverse(value as ConfigObject, fullPath);
}
}
}

traverse(schema);
traverse(schema as unknown as ConfigObject);
return keys;
}

// 生成配置元数据
export function generateConfigMetadata(schema: SchemaType) {
const metadata: any[] = [];
// 添加元数据类型定义
export interface ConfigMetadataItem {
key: string;
defaultValue: any;
nested: boolean;
parent: string;
description: string;
type: string;
enum?: readonly string[];
enumDescriptions?: readonly string[];
isSpecial?: boolean;
}

// 修改生成配置元数据函数的类型定义
export function generateConfigMetadata(
schema: SchemaType
): ConfigMetadataItem[] {
const metadata: ConfigMetadataItem[] = [];

function traverse(obj: ConfigObject, path: string = "") {
for (const [key, value] of Object.entries(obj)) {
const fullPath = path ? `${path}.${key}` : key;
if ("type" in value) {
metadata.push({
if (isConfigValue(value)) {
const metadataItem: ConfigMetadataItem = {
key: fullPath.replace(/\./g, "_").toUpperCase(),
defaultValue: value.default,
nested: fullPath.includes("."),
parent: fullPath.split(".")[0],
description: value.description,
type: value.type,
enum: value.enum,
enumDescriptions: value.enumDescriptions,
isSpecial: value.isSpecial,
});
} else {
};

// 只有当值是 ConfigValueTypeString 类型时才添加 enum 和 enumDescriptions
if (value.type === "string" && "enum" in value) {
metadataItem.enum = value.enum;
metadataItem.enumDescriptions = value.enumDescriptions;
}

metadata.push(metadataItem);
} else if (typeof value === "object") {
// 处理嵌套对象
traverse(value as ConfigObject, fullPath);
}
}
Expand Down
123 changes: 75 additions & 48 deletions src/config/ConfigurationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ConfigKey,
ExtensionConfiguration,
type ConfigurationValueType,
PROVIDER_REQUIRED_FIELDS,
} from "./types";
import { EXTENSION_NAME } from "../constants";
import { generateCommitMessageSystemPrompt } from "../prompt/prompt";
Expand All @@ -17,13 +18,15 @@ import {
generateConfiguration,
isConfigValue,
} from "./ConfigSchema";
import { getSystemPrompt } from "../ai/utils/generateHelper";

export class ConfigurationManager {
private static instance: ConfigurationManager;
private configuration: vscode.WorkspaceConfiguration;
private configCache: Map<string, any> = new Map();
private readonly disposables: vscode.Disposable[] = [];
private context?: vscode.ExtensionContext;
private configurationInProgress: boolean = false;

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

if (!useCache) {
// 直接从 configuration 获取最新值,确保返回正确的类型
const value =
this.configuration.get<ConfigurationValueType[K]>(configKey);
return value as ConfigurationValueType[K];
const value = this.configuration.get<string>(configKey);
return value as K extends keyof ConfigurationValueType
? ConfigurationValueType[K]
: string;
}

if (!this.configCache.has(configKey)) {
const value =
this.configuration.get<ConfigurationValueType[K]>(configKey);
const value = this.configuration.get<string>(configKey);
this.configCache.set(configKey, value);
}
return this.configCache.get(configKey) as ConfigurationValueType[K];
return this.configCache.get(
configKey
) as K extends keyof ConfigurationValueType
? ConfigurationValueType[K]
: string;
}

public getConfiguration(): ExtensionConfiguration {
// 使用generateConfiguration自动生成配置
const config = generateConfiguration(CONFIG_SCHEMA, (key: string) => {
return this.configuration.get<any>(`${key}`);
});

// 处理特殊情况:system prompt
const currentScm = SCMFactory.getCurrentSCMType() || "git";
if (!config.base.systemPrompt) {
config.base.systemPrompt = generateCommitMessageSystemPrompt(
config.base.language,
config.features.commitOptions.allowMergeCommits,
false,
currentScm,
config.features.commitOptions.useEmoji
);
public getConfiguration(
skipSystemPrompt: boolean = false
): ExtensionConfiguration {
if (this.configurationInProgress) {
// 如果已经在获取配置过程中,返回基本配置而不包含 systemPrompt
const config = generateConfiguration(CONFIG_SCHEMA, (key: string) => {
return this.configuration.get<any>(`${key}`);
});
return config as ExtensionConfiguration;
}

return config as ExtensionConfiguration;
try {
this.configurationInProgress = true;

// 使用generateConfiguration自动生成配置
const config = generateConfiguration(CONFIG_SCHEMA, (key: string) => {
return this.configuration.get<any>(`${key}`);
});

// 只在非跳过模式下且明确需要 systemPrompt 时才生成
if (!skipSystemPrompt && !config.base.systemPrompt) {
const currentScm = SCMFactory.getCurrentSCMType() || "git";
const promptConfig = {
...config.base,
...config.features.commitFormat,
...config.features.codeAnalysis,
scm: currentScm,
diff: "",
additionalContext: "",
model: {},
};

config.base.systemPrompt = getSystemPrompt(promptConfig);
}

return config as ExtensionConfiguration;
} finally {
this.configurationInProgress = false;
}
}

// 修改updateConfig方法签名
// 修改updateConfig方法签名,使用条件类型处理值的类型
public async updateConfig<K extends ConfigKey>(
key: K,
value: ConfigurationValueType[K]
value: string
): Promise<void> {
await this.configuration.update(
ConfigKeys[key].replace("dish-ai-commit.", ""),
Expand Down Expand Up @@ -243,24 +271,23 @@ export class ConfigurationManager {
*/
public async validateConfiguration(): Promise<boolean> {
const config = this.getConfiguration();
const provider = config.base.provider.toLowerCase();

switch (provider) {
case "openai":
return this.validateProviderConfig("openai", "apiKey");
case "ollama":
return this.validateProviderConfig("ollama", "baseUrl");
case "zhipuai":
return this.validateProviderConfig("zhipuai", "apiKey");
case "dashscope":
return this.validateProviderConfig("dashscope", "apiKey");
case "doubao":
return this.validateProviderConfig("doubao", "apiKey");
case "vs code provided":
return Promise.resolve(true);
default:
return Promise.resolve(false);
const provider = (config.base.provider as string).toLowerCase();

// VS Code 提供的AI不需要验证
if (provider === "vs code provided") {
return true;
}

// 检查是否是支持的提供商
if (provider in PROVIDER_REQUIRED_FIELDS) {
const requiredField = PROVIDER_REQUIRED_FIELDS[provider];
return this.validateProviderConfig(
provider as keyof ExtensionConfiguration["providers"],
requiredField
);
}

return false;
}

/**
Expand Down Expand Up @@ -302,12 +329,12 @@ export class ConfigurationManager {
* 更新 AI 提供商和模型配置
*/
public async updateAIConfiguration(
provider: string,
model: string
provider: ExtensionConfiguration["base"]["provider"],
model: ExtensionConfiguration["base"]["model"]
): Promise<void> {
await Promise.all([
this.updateConfig("BASE_PROVIDER", provider),
this.updateConfig("BASE_MODEL", model),
this.updateConfig("BASE_PROVIDER" as ConfigKey, provider),
this.updateConfig("BASE_MODEL" as ConfigKey, model),
]);
}
}
Loading

0 comments on commit 664d6d4

Please sign in to comment.