fix(configure_mykey): 备份命名标准化 + 保留既有 LLM 配置
This commit is contained in:
1 parent
cf129162d5
commit
964f51dacb
2 files changed
+98
-19
No files matched your search
+91
-19
@@ -7,6 +7,7 @@ GenericAgent — 交互式初始化向导 (configure.py)
|
|||||||
python configure.py
|
python configure.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import ast
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
@@ -1068,7 +1069,7 @@ def generate_mykey(llm_cfgs, platform_configs):
|
|||||||
|
|
||||||
def _write_config_fields(lines, cfg):
|
def _write_config_fields(lines, cfg):
|
||||||
"""写入配置字典的键值对(缩进的 'key': value, 格式)"""
|
"""写入配置字典的键值对(缩进的 'key': value, 格式)"""
|
||||||
for key in ['name', 'apikey', 'apibase', 'model', 'api_mode',
|
for key in ['name', 'type', 'apikey', 'apibase', 'model', 'api_mode',
|
||||||
'fake_cc_system_prompt', 'thinking_type', 'thinking_budget_tokens',
|
'fake_cc_system_prompt', 'thinking_type', 'thinking_budget_tokens',
|
||||||
'reasoning_effort', 'max_tokens', 'max_retries', 'connect_timeout',
|
'reasoning_effort', 'max_tokens', 'max_retries', 'connect_timeout',
|
||||||
'read_timeout', 'temperature', 'context_win',
|
'read_timeout', 'temperature', 'context_win',
|
||||||
@@ -1101,7 +1102,7 @@ def _write_platform_value(lines, key, val):
|
|||||||
def _parse_existing_mykey():
|
def _parse_existing_mykey():
|
||||||
"""解析已有 mykey.py,返回 (model_names, platform_infos)
|
"""解析已有 mykey.py,返回 (model_names, platform_infos)
|
||||||
|
|
||||||
llm_cfgs: [{'name': str, 'type': str, ...}] — 模型配置字典列表
|
model_names: [str] — 模型名列表
|
||||||
platform_infos: [{'id': str, 'vars': [{'key': str, 'val': ...}]}] — 平台信息
|
platform_infos: [{'id': str, 'vars': [{'key': str, 'val': ...}]}] — 平台信息
|
||||||
解析失败时返回 ([], [])
|
解析失败时返回 ([], [])
|
||||||
"""
|
"""
|
||||||
@@ -1117,21 +1118,81 @@ def _parse_existing_mykey():
|
|||||||
if m:
|
if m:
|
||||||
model_names = re.findall(r"'([^']+)'", m.group(1))
|
model_names = re.findall(r"'([^']+)'", m.group(1))
|
||||||
|
|
||||||
# 解析平台变量 → 平台 ID
|
# 先收集所有已知平台 env var key → 判断值类型
|
||||||
platform_id_map = {
|
all_env_var_keys = {}
|
||||||
'tg_bot_token': 'telegram', 'qq_app_id': 'qq',
|
platform_env_keys = {} # pid -> [var_key]
|
||||||
'fs_app_id': 'feishu', 'wecom_bot_id': 'wecom',
|
for p in PLATFORMS:
|
||||||
'dingtalk_client_id': 'dingtalk', 'dc_bot_token': 'discord',
|
pid = p['id']
|
||||||
}
|
platform_env_keys.setdefault(pid, [])
|
||||||
|
for var in p.get('env_vars', []):
|
||||||
|
vkey = var['key']
|
||||||
|
all_env_var_keys[vkey] = var
|
||||||
|
platform_env_keys[pid].append(vkey)
|
||||||
|
|
||||||
|
# 逐平台解析所有已知变量
|
||||||
platform_infos = []
|
platform_infos = []
|
||||||
for var_key, pid in platform_id_map.items():
|
for pid, env_keys in platform_env_keys.items():
|
||||||
m_var = re.search(rf"^{var_key}\s*=\s*'([^']*)'", content, re.MULTILINE)
|
vars_found = []
|
||||||
if m_var:
|
for vkey in env_keys:
|
||||||
platform_infos.append({'id': pid, 'vars': [{'key': var_key, 'val': m_var.group(1)}]})
|
var_def = all_env_var_keys[vkey]
|
||||||
|
val = None
|
||||||
|
if var_def.get('is_list'):
|
||||||
|
# 匹配 `xxx = [...]`
|
||||||
|
m_var = re.search(rf"^{vkey}\s*=\s*(\[[^\]]*\])", content, re.MULTILINE)
|
||||||
|
if m_var:
|
||||||
|
try:
|
||||||
|
val = ast.literal_eval(m_var.group(1))
|
||||||
|
except (ValueError, SyntaxError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# 匹配 `xxx = '...'`
|
||||||
|
m_var = re.search(rf"^{vkey}\s*=\s*'([^']*)'", content, re.MULTILINE)
|
||||||
|
if m_var:
|
||||||
|
val = m_var.group(1)
|
||||||
|
if val is not None:
|
||||||
|
vars_found.append({'key': vkey, 'val': val})
|
||||||
|
if vars_found:
|
||||||
|
platform_infos.append({'id': pid, 'vars': vars_found})
|
||||||
|
|
||||||
return model_names, platform_infos
|
return model_names, platform_infos
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_existing_llm_cfgs():
|
||||||
|
"""解析已有 mykey.py,返回完整 LLM 配置字典列表 [{name, apikey, ...}]
|
||||||
|
解析失败时返回 []
|
||||||
|
"""
|
||||||
|
if not os.path.exists(MYKPY_PATH):
|
||||||
|
return []
|
||||||
|
|
||||||
|
with open(MYKPY_PATH, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
cfgs = []
|
||||||
|
# 匹配所有 `xxx = { ... }` 顶层字典赋值
|
||||||
|
# 用简单状态机: 找 `\w+ = {` 然后匹配花括号
|
||||||
|
pattern = re.compile(r'^(\w+)\s*=\s*\{', re.MULTILINE)
|
||||||
|
for m in pattern.finditer(content):
|
||||||
|
brace_start = m.end() - 1 # '{' 的位置
|
||||||
|
depth = 1
|
||||||
|
i = brace_start + 1
|
||||||
|
while i < len(content) and depth > 0:
|
||||||
|
if content[i] == '{':
|
||||||
|
depth += 1
|
||||||
|
elif content[i] == '}':
|
||||||
|
depth -= 1
|
||||||
|
i += 1
|
||||||
|
if depth == 0:
|
||||||
|
dict_text = content[m.end():i - 1]
|
||||||
|
try:
|
||||||
|
d = ast.literal_eval('{' + dict_text + '}')
|
||||||
|
if isinstance(d, dict) and 'name' in d:
|
||||||
|
cfgs.append(d)
|
||||||
|
except (ValueError, SyntaxError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
return cfgs
|
||||||
|
|
||||||
|
|
||||||
def _backup_with_name(model_names, platform_ids):
|
def _backup_with_name(model_names, platform_ids):
|
||||||
"""按 mykey+模型名+机器人名 格式备份旧 mykey.py"""
|
"""按 mykey+模型名+机器人名 格式备份旧 mykey.py"""
|
||||||
parts = ['mykey']
|
parts = ['mykey']
|
||||||
@@ -1142,6 +1203,8 @@ def _backup_with_name(model_names, platform_ids):
|
|||||||
if pid_clean not in parts:
|
if pid_clean not in parts:
|
||||||
parts.append(pid_clean)
|
parts.append(pid_clean)
|
||||||
safe_name = '_'.join(parts)
|
safe_name = '_'.join(parts)
|
||||||
|
if safe_name == 'mykey':
|
||||||
|
safe_name = 'mykey_backup' # 避免和源文件同名
|
||||||
if len(safe_name) > 100:
|
if len(safe_name) > 100:
|
||||||
safe_name = safe_name[:100]
|
safe_name = safe_name[:100]
|
||||||
backup_path = os.path.join(PROJECT_ROOT, f'{safe_name}.py')
|
backup_path = os.path.join(PROJECT_ROOT, f'{safe_name}.py')
|
||||||
@@ -1189,7 +1252,7 @@ def main():
|
|||||||
|
|
||||||
if mode == 'new':
|
if mode == 'new':
|
||||||
backup_path = _backup_with_name(model_names, [p['id'] for p in platform_infos])
|
backup_path = _backup_with_name(model_names, [p['id'] for p in platform_infos])
|
||||||
print(f" {C['green']}✓ 旧配置已备份至:{C['reset']} {C['dim']}{backup}{C['reset']}")
|
print(f" {C['green']}✓ 旧配置已备份至:{C['reset']} {C['dim']}{backup_path}{C['reset']}")
|
||||||
is_new = True
|
is_new = True
|
||||||
else:
|
else:
|
||||||
is_modify = True
|
is_modify = True
|
||||||
@@ -1212,8 +1275,12 @@ def main():
|
|||||||
config_dict = {v['key']: v['val'] for v in pi['vars']}
|
config_dict = {v['key']: v['val'] for v in pi['vars']}
|
||||||
platform_configs.append({'platform': p, 'config': config_dict})
|
platform_configs.append({'platform': p, 'config': config_dict})
|
||||||
elif scope == 'platform' and model_names:
|
elif scope == 'platform' and model_names:
|
||||||
print(f"\n {C['yellow']}⚠ 只修改平台时若未提供 LLM 配置将无法使用。{C['reset']}")
|
old_cfgs = _parse_existing_llm_cfgs()
|
||||||
cprint(f" 建议两项都重新配置。", 'dim')
|
if old_cfgs:
|
||||||
|
llm_cfgs = old_cfgs
|
||||||
|
print(f"\n {C['green']}✓ 已保留现有 LLM 配置: {', '.join(c['name'] for c in old_cfgs)}{C['reset']}")
|
||||||
|
else:
|
||||||
|
print(f"\n {C['yellow']}⚠ 保留 LLM 配置失败,将生成空配置。建议两项都重新配置。{C['reset']}")
|
||||||
|
|
||||||
if not is_modify:
|
if not is_modify:
|
||||||
if is_new:
|
if is_new:
|
||||||
@@ -1245,6 +1312,12 @@ def main():
|
|||||||
platform_configs, platform_deps = configure_platforms()
|
platform_configs, platform_deps = configure_platforms()
|
||||||
if ask_yesno("是否继续配置 LLM 模型?", default=True):
|
if ask_yesno("是否继续配置 LLM 模型?", default=True):
|
||||||
llm_cfgs = _do_llm()
|
llm_cfgs = _do_llm()
|
||||||
|
elif os.path.exists(MYKPY_PATH):
|
||||||
|
# 新建+仅平台:从备份保留旧 LLM 配置
|
||||||
|
old_cfgs = _parse_existing_llm_cfgs()
|
||||||
|
if old_cfgs:
|
||||||
|
llm_cfgs = old_cfgs
|
||||||
|
print(f"\n {C['green']}✓ 已保留备份中的 LLM 配置: {', '.join(c['name'] for c in old_cfgs)}{C['reset']}")
|
||||||
|
|
||||||
# ── 生成 mykey.py ──
|
# ── 生成 mykey.py ──
|
||||||
if not llm_cfgs and not platform_configs:
|
if not llm_cfgs and not platform_configs:
|
||||||
@@ -1253,10 +1326,9 @@ def main():
|
|||||||
|
|
||||||
content = generate_mykey(llm_cfgs, platform_configs)
|
content = generate_mykey(llm_cfgs, platform_configs)
|
||||||
|
|
||||||
# 备份旧文件
|
# 备份旧文件(修改模式不备份,直接在原文件修改)
|
||||||
if os.path.exists(MYKPY_PATH):
|
if os.path.exists(MYKPY_PATH) and not is_modify and not is_new:
|
||||||
backup = os.path.join(PROJECT_ROOT, f'mykey.py.bak.{datetime.now().strftime("%Y%m%d_%H%M%S")}')
|
backup = _backup_with_name(model_names, [p['id'] for p in platform_infos])
|
||||||
shutil.copy2(MYKPY_PATH, backup)
|
|
||||||
print(f"\n {C['green']}✓ 旧配置已备份至:{C['reset']} {C['dim']}{backup}{C['reset']}")
|
print(f"\n {C['green']}✓ 旧配置已备份至:{C['reset']} {C['dim']}{backup}{C['reset']}")
|
||||||
|
|
||||||
# 写入
|
# 写入
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ COMMANDS = {
|
|||||||
"desc": "启动终端图形界面(Textual),适合纯终端环境或 SSH",
|
"desc": "启动终端图形界面(Textual),适合纯终端环境或 SSH",
|
||||||
"cmd": ["python", "{FRONTENDS}/tuiapp.py"],
|
"cmd": ["python", "{FRONTENDS}/tuiapp.py"],
|
||||||
},
|
},
|
||||||
|
"tui2": {
|
||||||
|
"help": "启动终端 TUI v2 (tuiapp_v2)",
|
||||||
|
"desc": "启动增强版终端图形界面(Textual v2),更多功能更好的体验",
|
||||||
|
"cmd": ["python", "{FRONTENDS}/tuiapp_v2.py"],
|
||||||
|
},
|
||||||
"cli": {
|
"cli": {
|
||||||
"help": "启动 CLI 对话 (agentmain)",
|
"help": "启动 CLI 对话 (agentmain)",
|
||||||
"desc": "启动命令行交互对话模式,最轻量的使用方式",
|
"desc": "启动命令行交互对话模式,最轻量的使用方式",
|
||||||
@@ -151,6 +156,8 @@ def main():
|
|||||||
ga gui 启动桌面 GUI
|
ga gui 启动桌面 GUI
|
||||||
ga web 启动 Web 增强版
|
ga web 启动 Web 增强版
|
||||||
ga web --native 启动 Web 基础版(桌面壳)
|
ga web --native 启动 Web 基础版(桌面壳)
|
||||||
|
ga tui 启动终端 TUI (v1)
|
||||||
|
ga tui2 启动终端 TUI (v2 增强版)
|
||||||
ga pet 启动桌面宠物 v2
|
ga pet 启动桌面宠物 v2
|
||||||
ga launch 启动 webview 桌面壳
|
ga launch 启动 webview 桌面壳
|
||||||
ga list 列出所有命令
|
ga list 列出所有命令
|
||||||
|
|||||||
Reference in new issue
Block a user