Merge pull request #61 from devchat-ai/sync_server_config

Sync config from server
This commit is contained in:
boob.yang 2024-06-14 13:31:38 +08:00 committed by GitHub
commit f78ab29e18
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 251 additions and 83 deletions

View File

@ -54,10 +54,14 @@ class MessageUtil {
}
// Handle a received message
handleMessage(message: { command: string | number }) {
handleMessage(message: { command: string | number } & Record<string, any>) {
if (!('command' in message)) {
throw new Error('Missing required field: command');
}
const handlers = this.handlers[message.command];
if (handlers) {
handlers.forEach((handler: (arg0: { command: string | number }) => any) =>
handlers.forEach((handler: (arg0: { command: string | number } & Record<string, any>) => any) =>
handler(message)
);
}

View File

@ -302,6 +302,18 @@ const JStoIdea = {
};
window.JSJavaBridge.callJava(JSON.stringify(params));
},
readServerConfigBase: () => {
// 获取完整的用户设置
const params = {
action: "getServerSetting/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: {},
};
window.JSJavaBridge.callJava(JSON.stringify(params));
},
saveConfig: (data) => {
const params = {
action: "updateSetting/request",
@ -314,6 +326,19 @@ const JStoIdea = {
console.log("ready to call java: ", JSON.stringify(params));
window.JSJavaBridge.callJava(JSON.stringify(params));
},
writeServerConfigBase: (data) => {
const params = {
action: "updateServerSetting/request",
metadata: {
callback: "IdeaToJSMessage",
},
payload: data,
};
console.log("ready to call java: ", JSON.stringify(params));
window.JSJavaBridge.callJava(JSON.stringify(params));
},
};
class IdeaBridge {
@ -462,6 +487,15 @@ class IdeaBridge {
});
}
resviceServerSettings(res) {
// 获取用户设置的回调
const setting = res?.payload || {};
this.executeHandlers("readServerConfigBase", {
value: setting,
});
}
resviceCommandList(res) {
this.executeHandlers("regCommandList", {
result: res.payload.commands,
@ -601,10 +635,17 @@ class IdeaBridge {
case "readConfig":
JStoIdea.readConfig();
break;
case "readServerConfigBase":
JStoIdea.readServerConfigBase();
break;
case "writeConfig":
// 保存用户设置
JStoIdea.saveConfig(message.value);
break;
case "writeServerConfigBase":
// 保存用户设置
JStoIdea.writeServerConfigBase(message.value);
break;
default:
break;
}

View File

@ -26,14 +26,59 @@ export default function App() {
};
const getConfig = () => {
// 比较函数
const compare_func = (configs: any) => {
const [serverConfig, userConfig] = config.updateConfig(configs.server_config, configs.server_config_base, configs.user_config);
if (serverConfig) {
// save server config as base
MessageUtil.sendMessage({ command: "writeServerConfigBase", value: serverConfig });
}
// set user config
config.setConfig(userConfig);
};
// 结果记录
let configs = {};
let configCount = 0;
// 处理函数
const handleConfig = (key: string, data: { value: any }) => {
console.log("-----> receive:", key);
configs[key] = {...data.value};
configCount++;
checkConfigs();
};
// 检查是否所有配置都已准备好
const checkConfigs = () => {
if (configCount === 3) {
compare_func(configs);
setReady(true); // 假设setReady是一个函数用于设置应用状态为准备就绪
}
};
// 注册处理函数
MessageUtil.registerHandler("readConfig", (data: { value: any }) => {
console.log("readConfig registerHandler: ", data);
config.setConfig(data.value);
APIUtil.config(config.getAPIBase(), config.getUserKey())
config.refreshModelList();
setReady(true);
config.fetchServerConfig();
handleConfig("user_config", data);
});
MessageUtil.registerHandler("readServerConfigBase", (data: { value: any }) => handleConfig("server_config_base", data));
MessageUtil.registerHandler("readServerConfig", (data: { value: any }) => handleConfig("server_config", data));
MessageUtil.registerHandler(
"reloadConfig",
(data: { value: any }) => {
configs = {};
configCount = 0;
// 发送消息
MessageUtil.sendMessage({ command: "readConfig", key: "" });
MessageUtil.sendMessage({ command: "readServerConfigBase", key: "" });
});
MessageUtil.handleMessage({ command: "reloadConfig" });
};
useEffect(() => {

View File

@ -113,7 +113,7 @@ const Config = observer(() => {
useEffect(() => {
MessageUtil.registerHandler("updateSetting", (data) => {
// 保存后的回调
MessageUtil.sendMessage({ command: "readConfig" });
MessageUtil.handleMessage({ command: "reloadConfig" });
});
if (router.currentRoute !== "config") {return;}
const modelArray = config.modelsTemplate.map((item) => ({
@ -160,8 +160,7 @@ const Config = observer(() => {
key: "",
});
setTimeout(() => {
config.setTemplate([], "");
MessageUtil.sendMessage({ command: "readConfig", key: "" });
MessageUtil.handleMessage({ command: "reloadConfig" });
}, 1000);
};

View File

@ -9,20 +9,44 @@ const defaultAPIBase = [
"https://api.devchat-ai.cn/v1",
];
export const fetchLLMs = async ({modelsUrl,devchatApiKey}) => {
return new Promise<{data:any}>((resolve, reject) => {
// 添加 header: "Authorization: Bearer ${devchatApiKey}"
axios.get(`${modelsUrl}/models`, { headers: { 'Authorization': `Bearer ${devchatApiKey}` }}).then((res) => {
// 获取 models 模版列表
if (res?.data?.data && Array.isArray(res?.data?.data)) {
resolve(res.data);
function deepCopy(obj) {
let copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
copy[key] = deepCopy(obj[key]); // 递归调用
} else {
copy[key] = obj[key];
}
}).catch((e) => {
console.error("fetchLLMs error:", e);
reject(e);
}
}
return copy;
}
export const fetchServerConfigUtil = async ({ modelsUrl, devchatApiKey }) => {
try {
const response = await axios.get(`${modelsUrl}/models`, {
headers: { 'Authorization': `Bearer ${devchatApiKey}` }
});
if (response.status === 200) {
if (response.data.data && Array.isArray(response.data.data)) {
// change data to models key
response.data.models = response.data.data;
delete response.data.data;
}
return response.data;
} else {
console.error("fetchServerConfig error: Non-200 status code", response.status);
return undefined;
}
} catch (e) {
console.error("fetchServerConfig error:", e);
return undefined;
}
);
};
export const Model = types.model({
@ -53,11 +77,27 @@ export const ConfigStore = types
provider: "devchat",
})
.actions((self) => {
const setTemplate = (value: any, provider: string) => {
const setTemplate = (value: any,) => {
const provider = self.provider;
const list: any[] = [];
for (const name in value) {
if (value.hasOwnProperty(name)) {
const item: any = { name };
for (const key in value[name]) {
if (value[name].hasOwnProperty(key)) {
item[key] = value[name][key];
}
}
list.push(item);
}
}
value = list;
const models = value
.map((item) => {
return {
name: item.model ?? item.id,
name: item.name,
max_input_tokens: item.max_input_tokens ?? 6000,
provider: provider,
stream: true,
@ -112,48 +152,101 @@ export const ConfigStore = types
}
return "";
},
updateConfig(server_config: any, server_config_base: any, user_config: any) {
console.log("----->:::updateConfig");
// 如果server_config没有获取到那么直接返回
if (!server_config || !server_config.models) {
return [undefined, user_config];
}
if (server_config_base === undefined) {
server_config_base = {};
}
if (server_config_base.models === undefined) {
server_config_base.models = {};
}
// 将 server_config 转换为本地配置存储的格式
const localConfig: any = {"models": {}};
server_config.models.forEach((model: any) => {
const modelConfig: any = {};
for (const key in model) {
if (key !== 'model') {
modelConfig[key] = model[key];
}
}
localConfig["models"][model.model || model.id] = modelConfig;
});
// 合并 config 部分,不假设具体的配置项名称
for (const key in server_config.config) {
localConfig[key] = server_config.config[key];
}
// 使用子函数处理对比和更新
const userConfigNew = this.compareConfigs(localConfig, server_config_base, user_config);
return [localConfig, userConfigNew];
},
compareConfigs(localConfig: any, baseConfig: any, userConfigIn: any) {
let userConfig = { ...userConfigIn };
for (const key in localConfig) {
if (baseConfig.hasOwnProperty(key) && userConfig.hasOwnProperty(key)) {
// 递归比较对象的每个叶子结点
if (typeof localConfig[key] === 'object' && !Array.isArray(localConfig[key]) && localConfig[key] !== null) {
userConfig[key] = this.compareConfigs(localConfig[key], baseConfig[key], userConfig[key]);
} else {
if (localConfig[key] !== baseConfig[key]) {
// 检查用户配置中是否存在该条目
if (!userConfig[key] || JSON.stringify(userConfig[key]) === JSON.stringify(baseConfig[key])) {
userConfig[key] = localConfig[key];
}
}
}
} else {
// 新增的配置项
if (!userConfig[key]) {
if (!Object.isExtensible(userConfig)) {
// 如果 userConfig 不可扩展,创建一个新的可扩展对象
userConfig = { ...userConfig };
}
userConfig[key] = localConfig[key];
}
}
}
// 处理删除的配置项,仅针对 models 下的 model
if (localConfig.models ) {
const localModels = localConfig.models;
const userModels = userConfig.models;
for (const modelKey in userModels) {
if (!localModels.hasOwnProperty(modelKey)) {
// 删除的 model从 userConfig 中移除
delete userModels[modelKey];
}
}
}
return userConfig;
},
setConfig: function (data) {
this.setTemplate(data.models);
this.updateSettle(false);
let needUpdate = false;
const newConfig = { ...data };
const newConfig: any = deepCopy(data);
newConfig.models = newConfig.models || {};
newConfig.providers = newConfig.providers || {};
newConfig.providers.openai = newConfig.providers.openai || {
if (!newConfig.providers.openai) {
newConfig.providers.openai = {
api_key: "",
api_base: "",
};
newConfig.providers.devchat = newConfig.providers.devchat || {
}
if (!newConfig.providers.devchat) {
newConfig.providers.devchat = {
api_key: "",
api_base: "",
};
self.modelsTemplate.forEach((item) => {
const currentModel: any = {
...item,
};
delete currentModel.name;
if (!newConfig.models[item.name]) {
newConfig.models[item.name] = {
...currentModel,
};
needUpdate = true;
}
if (newConfig.models[item.name].provider !== currentModel.provider) {
needUpdate = true;
newConfig.models[item.name].provider = currentModel.provider;
}
// 只有之前配置过 openai 的provider 才可以是 openai
if (
newConfig.models[item.name].provider === "openai" &&
!newConfig.providers.openai.api_key
) {
needUpdate = true;
newConfig.models[item.name].provider = "devchat";
}
});
// 尝试获取 devchat 的 api_base
self.provider = "devchat";
self.modelsUrl = data?.providers?.devchat?.cumstom_api_base || data?.providers?.devchat?.api_base;
@ -173,39 +266,29 @@ export const ConfigStore = types
self.provider = "devchat";
}
const modelsChat = self.modelsTemplate.filter(model => model.category === "chat");
if (modelsChat.length > 0 && modelsChat.find((item) => item.name === newConfig.default_model) === undefined) {
const defaultModelName = 'qwen-72b-chat'
newConfig.default_model = modelsChat.some(x => x.name === defaultModelName) ? defaultModelName : modelsChat[0].name;
needUpdate = true;
}
if (!defaultAPIBase.includes(newConfig.providers.devchat.api_base)) {
newConfig.providers.devchat.cumstom_api_base =
newConfig.providers.devchat.api_base;
newConfig.providers.devchat.api_base = "custom";
}
if (this.checkAndSetCompletionDefaults(newConfig)) {
needUpdate = true;
}
self.config = newConfig;
self.defaultModel = newConfig.default_model;
if (needUpdate) {
this.writeConfig();
}
this.updateSettle(true);
},
refreshModelList: flow(function* (){
fetchServerConfig: flow(function* (){
try {
if (self.modelsTemplate.length === 0) {
const { data } = yield fetchLLMs({modelsUrl:self.modelsUrl,devchatApiKey:self.devchatApiKey});
setTemplate(data,self.provider);
MessageUtil.sendMessage({ command: "readConfig", key: "" });
const data = yield fetchServerConfigUtil({ modelsUrl: self.modelsUrl, devchatApiKey: self.devchatApiKey });
if (data !== undefined) {
MessageUtil.handleMessage({ command: "readServerConfig", value: data });
} else {
console.log("fetchLLMs error: Failed to fetch server config");
MessageUtil.handleMessage({ command: "readServerConfig", value: undefined });
}
} catch (e) {
console.log("fetchLLMs error:", e);
MessageUtil.handleMessage({ command: "readServerConfig", value: undefined });
}
}),
checkAndSetCompletionDefaults: (newConfig) => {
@ -244,10 +327,6 @@ export const ConfigStore = types
value: writeConfig,
key: "",
});
setTimeout(() => {
MessageUtil.sendMessage({ command: "readConfig", key: "" });
}, 1000);
},
setConfigValue: function (key: string, value: any) {
if (key === "default_model") {