mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 21:37:11 +00:00
fix: config 重复保存
This commit is contained in:
parent
6b36552c08
commit
f4fb3dddd2
2 changed files with 157 additions and 107 deletions
232
config/config.js
232
config/config.js
|
|
@ -191,10 +191,10 @@ class ChatGPTConfig {
|
||||||
* @param {string} configDir Directory containing config files
|
* @param {string} configDir Directory containing config files
|
||||||
*/
|
*/
|
||||||
startSync (configDir) {
|
startSync (configDir) {
|
||||||
|
// 配置路径设置
|
||||||
const jsonPath = path.join(configDir, 'config.json')
|
const jsonPath = path.join(configDir, 'config.json')
|
||||||
const yamlPath = path.join(configDir, 'config.yaml')
|
const yamlPath = path.join(configDir, 'config.yaml')
|
||||||
|
|
||||||
// Determine which config file to use
|
|
||||||
if (fs.existsSync(jsonPath)) {
|
if (fs.existsSync(jsonPath)) {
|
||||||
this.configPath = jsonPath
|
this.configPath = jsonPath
|
||||||
} else if (fs.existsSync(yamlPath)) {
|
} else if (fs.existsSync(yamlPath)) {
|
||||||
|
|
@ -204,140 +204,178 @@ class ChatGPTConfig {
|
||||||
this.saveToFile()
|
this.saveToFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load initial config
|
// 加载初始配置
|
||||||
this.loadFromFile()
|
this.loadFromFile()
|
||||||
|
|
||||||
// Watch for file changes
|
// 文件变更标志和保存定时器
|
||||||
|
this._saveOrigin = null
|
||||||
|
this._saveTimer = null
|
||||||
|
|
||||||
|
// 监听文件变化
|
||||||
this.watcher = fs.watchFile(this.configPath, (curr, prev) => {
|
this.watcher = fs.watchFile(this.configPath, (curr, prev) => {
|
||||||
if (curr.mtime !== prev.mtime) {
|
if (curr.mtime !== prev.mtime && this._saveOrigin !== 'code') {
|
||||||
this.loadFromFile()
|
this.loadFromFile()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const createDeepProxy = (obj, handler, seen = new WeakMap()) => {
|
// 处理所有嵌套对象
|
||||||
// 基本类型或非对象直接返回
|
return this._createProxyRecursively(this)
|
||||||
if (obj === null || typeof obj !== 'object') return obj
|
}
|
||||||
|
|
||||||
// 检查循环引用
|
// 递归创建代理
|
||||||
if (seen.has(obj)) {
|
_createProxyRecursively (obj, path = []) {
|
||||||
return seen.get(obj)
|
if (obj === null || typeof obj !== 'object' || obj instanceof Date) {
|
||||||
}
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
// 创建代理对象
|
// 处理数组和对象
|
||||||
const proxy = new Proxy(obj, handler)
|
if (Array.isArray(obj)) {
|
||||||
|
// 创建一个新数组,递归地处理每个元素
|
||||||
|
const proxiedArray = [...obj].map((item, index) =>
|
||||||
|
this._createProxyRecursively(item, [...path, index])
|
||||||
|
)
|
||||||
|
|
||||||
// 记录已创建的代理,避免循环引用
|
// 代理数组,捕获数组方法调用
|
||||||
seen.set(obj, proxy)
|
return new Proxy(proxiedArray, {
|
||||||
|
set: (target, prop, value) => {
|
||||||
|
// 处理数字属性(数组索引)和数组长度
|
||||||
|
if (typeof prop !== 'symbol' &&
|
||||||
|
((!isNaN(prop) && prop !== 'length') ||
|
||||||
|
prop === 'length')) {
|
||||||
|
// 直接设置值
|
||||||
|
target[prop] = value
|
||||||
|
|
||||||
// 处理子对象
|
// 触发保存
|
||||||
for (let key of Object.keys(obj)) {
|
this._triggerSave('array')
|
||||||
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
} else {
|
||||||
obj[key] = createDeepProxy(obj[key], handler, seen)
|
target[prop] = value
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
// 拦截数组方法调用
|
||||||
|
get: (target, prop) => {
|
||||||
|
const val = target[prop]
|
||||||
|
|
||||||
|
// 处理数组修改方法
|
||||||
|
if (typeof val === 'function' &&
|
||||||
|
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].includes(prop)) {
|
||||||
|
return function (...args) {
|
||||||
|
const result = Array.prototype[prop].apply(target, args)
|
||||||
|
|
||||||
|
// 方法调用后触发保存
|
||||||
|
this._triggerSave('array-method')
|
||||||
|
return result
|
||||||
|
}.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 对普通对象的处理
|
||||||
|
const proxiedObj = {}
|
||||||
|
|
||||||
|
// 处理所有属性
|
||||||
|
for (const key in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
|
// 跳过内部属性
|
||||||
|
if (key === 'watcher' || key === 'configPath' ||
|
||||||
|
key.startsWith('_save') || key === '_isSaving') {
|
||||||
|
proxiedObj[key] = obj[key]
|
||||||
|
} else {
|
||||||
|
// 递归处理嵌套对象
|
||||||
|
proxiedObj[key] = this._createProxyRecursively(
|
||||||
|
obj[key], [...path, key]
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxy
|
// 创建对象的代理
|
||||||
}
|
return new Proxy(proxiedObj, {
|
||||||
|
set: (target, prop, value) => {
|
||||||
|
// 跳过内部属性的处理
|
||||||
|
if (prop === 'watcher' || prop === 'configPath' ||
|
||||||
|
prop.startsWith('_save') || prop === '_isSaving') {
|
||||||
|
target[prop] = value
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
const handler = {
|
// 设置新值,如果是对象则递归创建代理
|
||||||
set: (target, prop, value) => {
|
if (value !== null && typeof value === 'object') {
|
||||||
if (prop !== 'watcher' && prop !== 'configPath') {
|
target[prop] = this._createProxyRecursively(
|
||||||
// 避免递归创建代理
|
value, [...path, prop]
|
||||||
if (typeof value === 'object' && value !== null) {
|
)
|
||||||
// 检查 value 是否已经是代理
|
|
||||||
if (!value.__isProxy) {
|
|
||||||
const newProxy = createDeepProxy(value, handler)
|
|
||||||
// 标记为代理对象
|
|
||||||
Object.defineProperty(newProxy, '__isProxy', {
|
|
||||||
value: true,
|
|
||||||
enumerable: false,
|
|
||||||
configurable: false
|
|
||||||
})
|
|
||||||
target[prop] = newProxy
|
|
||||||
} else {
|
|
||||||
target[prop] = value
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
target[prop] = value
|
target[prop] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 避免在代理对象保存时再次触发
|
// 触发保存
|
||||||
if (!target.__isSaving) {
|
this._triggerSave('object')
|
||||||
target.__isSaving = true
|
return true
|
||||||
try {
|
|
||||||
this.saveToFile()
|
|
||||||
} finally {
|
|
||||||
target.__isSaving = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为所有嵌套对象创建Proxy
|
|
||||||
this.basic = createDeepProxy(this.basic, handler)
|
|
||||||
this.bym = createDeepProxy(this.bym, handler)
|
|
||||||
this.llm = createDeepProxy(this.llm, handler)
|
|
||||||
this.management = createDeepProxy(this.management, handler)
|
|
||||||
this.chaite = createDeepProxy(this.chaite, handler)
|
|
||||||
|
|
||||||
// 返回最外层的Proxy
|
|
||||||
return new Proxy(this, handler)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load config from file
|
|
||||||
*/
|
|
||||||
loadFromFile () {
|
loadFromFile () {
|
||||||
this.__isSaving = false;
|
|
||||||
try {
|
try {
|
||||||
|
if (!fs.existsSync(this.configPath)) {
|
||||||
|
// 如果文件不存在,直接返回
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const content = fs.readFileSync(this.configPath, 'utf8')
|
const content = fs.readFileSync(this.configPath, 'utf8')
|
||||||
const loadedConfig = this.configPath.endsWith('.json')
|
const loadedConfig = this.configPath.endsWith('.json')
|
||||||
? JSON.parse(content)
|
? JSON.parse(content)
|
||||||
: yaml.load(content)
|
: yaml.load(content)
|
||||||
|
|
||||||
// Deep merge function that preserves default values
|
// 只更新存在的配置项
|
||||||
const deepMerge = (target, source) => {
|
if (loadedConfig) {
|
||||||
// Skip non-object properties or special properties
|
Object.keys(loadedConfig).forEach(key => {
|
||||||
if (!source || typeof source !== 'object' ||
|
if (key === 'version' || key === 'basic' || key === 'bym' || key === 'llm' ||
|
||||||
!target || typeof target !== 'object') {
|
key === 'management' || key === 'chaite') {
|
||||||
return
|
if (typeof loadedConfig[key] === 'object' && loadedConfig[key] !== null) {
|
||||||
}
|
// 对象的合并
|
||||||
|
if (!this[key]) this[key] = {}
|
||||||
for (const key in source) {
|
Object.assign(this[key], loadedConfig[key])
|
||||||
// Skip internal properties
|
} else {
|
||||||
if (key === 'watcher' || key === 'configPath') continue
|
// 基本类型直接赋值
|
||||||
|
this[key] = loadedConfig[key]
|
||||||
if (typeof source[key] === 'object' && source[key] !== null &&
|
}
|
||||||
typeof target[key] === 'object' && target[key] !== null) {
|
|
||||||
// Recursively merge nested objects
|
|
||||||
deepMerge(target[key], source[key])
|
|
||||||
} else if (source[key] !== undefined) {
|
|
||||||
// Only update if the value exists in the loaded config
|
|
||||||
target[key] = source[key]
|
|
||||||
}
|
}
|
||||||
// If source[key] is undefined, keep the default value in target
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply loaded config to this object, preserving defaults
|
logger.debug('Config loaded successfully')
|
||||||
deepMerge(this, loadedConfig)
|
|
||||||
|
|
||||||
// Save the file to persist any new default values
|
|
||||||
const hasChanges = JSON.stringify(this.toJSON()) !== content
|
|
||||||
if (hasChanges) {
|
|
||||||
this.saveToFile()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load config:', error)
|
logger.error('Failed to load config:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 合并触发保存,防抖处理
|
||||||
* Save config to file
|
_triggerSave (origin) {
|
||||||
*/
|
// 清除之前的定时器
|
||||||
|
if (this._saveTimer) {
|
||||||
|
clearTimeout(this._saveTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录保存来源
|
||||||
|
this._saveOrigin = origin || 'code'
|
||||||
|
|
||||||
|
// 设置定时器延迟保存
|
||||||
|
this._saveTimer = setTimeout(() => {
|
||||||
|
this.saveToFile()
|
||||||
|
// 保存完成后延迟一下再清除来源标记
|
||||||
|
setTimeout(() => {
|
||||||
|
this._saveOrigin = null
|
||||||
|
}, 100)
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
saveToFile () {
|
saveToFile () {
|
||||||
|
logger.debug('Saving config to file...')
|
||||||
try {
|
try {
|
||||||
const config = {
|
const config = {
|
||||||
version: this.version,
|
version: this.version,
|
||||||
|
|
|
||||||
|
|
@ -203,16 +203,28 @@ export async function initChaite () {
|
||||||
// 监听通过chaite对插件配置修改
|
// 监听通过chaite对插件配置修改
|
||||||
chaite.setUpdateConfigCallback(config => {
|
chaite.setUpdateConfigCallback(config => {
|
||||||
logger.debug('chatgpt-plugin config updated')
|
logger.debug('chatgpt-plugin config updated')
|
||||||
Object.keys(config).forEach(key => {
|
|
||||||
if (typeof config[key] === 'object' && config[key] !== null && ChatGPTConfig[key]) {
|
// 设置保存来源标记,而不是使用 _isSaving
|
||||||
deepMerge(ChatGPTConfig[key], config[key])
|
ChatGPTConfig._saveOrigin = 'chaite'
|
||||||
} else {
|
|
||||||
ChatGPTConfig[key] = config[key]
|
try {
|
||||||
}
|
Object.keys(config).forEach(key => {
|
||||||
})
|
if (typeof config[key] === 'object' && config[key] !== null && ChatGPTConfig[key]) {
|
||||||
// 回传部分需要同步的配置,以防不一致
|
deepMerge(ChatGPTConfig[key], config[key])
|
||||||
chaite.getGlobalConfig().setDebug(ChatGPTConfig.basic.debug)
|
} else {
|
||||||
chaite.getGlobalConfig().setAuthKey(ChatGPTConfig.chaite.authKey)
|
ChatGPTConfig[key] = config[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 回传部分需要同步的配置
|
||||||
|
chaite.getGlobalConfig().setDebug(ChatGPTConfig.basic.debug)
|
||||||
|
chaite.getGlobalConfig().setAuthKey(ChatGPTConfig.chaite.authKey)
|
||||||
|
|
||||||
|
// 使用新的触发保存方法,而不是直接调用saveToFile
|
||||||
|
ChatGPTConfig._triggerSave('chaite')
|
||||||
|
} finally {
|
||||||
|
// 不需要在这里清除标记,_triggerSave已经处理了延迟清除
|
||||||
|
}
|
||||||
})
|
})
|
||||||
// 授予Chaite获取插件配置的能力以便通过api放出
|
// 授予Chaite获取插件配置的能力以便通过api放出
|
||||||
chaite.setGetConfig(async () => {
|
chaite.setGetConfig(async () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue