Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0ee2ee9
feat(proxy): 支持多API端点配置与429自动切换
QuickLAW Mar 11, 2026
e2b8697
fix(proxy): 改进 429 响应处理逻辑并增强日志信息
QuickLAW Mar 11, 2026
a1ba2eb
feat(proxy): 实现429错误自动故障转移与硅基流动参数适配
QuickLAW Mar 12, 2026
cb9f2da
feat(proxy): 为API端点添加目标模型ID并支持429故障转移
QuickLAW Mar 12, 2026
ab440c9
feat(config_service): 为全局配置添加429故障转移开关
QuickLAW Mar 12, 2026
cc8438f
fix(proxy_orchestration): 适配全局配置加载函数的新返回值格式
QuickLAW Mar 12, 2026
62dfeb9
feat: 添加 429 失败转移开关到配置系统
QuickLAW Mar 12, 2026
a52533e
feat(配置): 添加 API 429 故障转移开关
QuickLAW Mar 12, 2026
6adfaab
feat(proxy): 为429故障转移增加可配置冷却时间
QuickLAW Mar 12, 2026
6bda834
fix(ui): 修正转发策略相关文案描述
QuickLAW Mar 12, 2026
49b1289
Merge branch 'BiFangKNT:tauri' into tauri
QuickLAW Mar 12, 2026
481309f
Merge branch 'dev' into tauri
QuickLAW Mar 12, 2026
ac88bbb
fix(proxy): 修复路由切换逻辑并增强错误处理
QuickLAW Mar 13, 2026
459a4fb
feat(proxy): 为代理端点添加中间路由支持并重构冷却时间解析
QuickLAW Mar 13, 2026
1f1c9d9
Merge branch 'dev' into tauri
QuickLAW Mar 13, 2026
f1b222c
Merge branch 'dev' into tauri
BiFangKNT Mar 14, 2026
c5071a1
Merge branch 'dev' into tauri
QuickLAW Mar 16, 2026
2120ffa
feat(proxy): 为配置组添加唯一ID并支持轮询组选择
QuickLAW Mar 16, 2026
745209c
Merge branch 'tauri' of https://github.com/QuickLAW/mtga into tauri
QuickLAW Mar 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"editor.defaultFormatter": "redhat.vscode-yaml",
"editor.formatOnSave": true
},
"python.languageServer": "Pylance",
"python.languageServer": "None",
"rust-analyzer.cargo.extraEnv": {
"PYO3_PYTHON": "${workspaceFolder}/src-tauri/pyembed/python/python.exe"
}
Expand Down
6 changes: 5 additions & 1 deletion app/components/panels/ConfigGroupPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ const handleSave = async () => {
configGroups.value.push(payload);
currentIndex.value = configGroups.value.length - 1;
} else if (hasSelection.value) {
const currentGroup = configGroups.value[selectedIndex.value];
if (currentGroup?.id) {
payload.id = currentGroup.id;
}
configGroups.value.splice(selectedIndex.value, 1, payload);
}

Expand Down Expand Up @@ -449,7 +453,7 @@ const moveDown = async () => {
<tbody v-if="configGroups.length">
<tr
v-for="(group, index) in configGroups"
:key="index"
:key="group.id || index"
class="cursor-pointer transition-colors hover:bg-amber-100/30 group"
:class="selectedIndex === index ? 'bg-amber-100/70' : ''"
:style="{ height: 'var(--row-h)' }"
Expand Down
141 changes: 141 additions & 0 deletions app/components/panels/SettingsPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {

const store = useMtgaStore();
const appInfo = store.appInfo;
const configGroups = store.configGroups;

const clearConfirmOpen = ref(false);
const clearConfirmTitle = "确认清除数据";
Expand Down Expand Up @@ -116,6 +117,94 @@ const openThemeDialog = () => {
themeDialogOpen.value = true;
};

const enable429Failover = computed({
get: () => store.enable429Failover.value,
set: (value) => {
store.enable429Failover.value = value;
},
});

const failover429CooldownSeconds = computed({
get: () => store.failover429CooldownSeconds.value,
set: (value) => {
store.failover429CooldownSeconds.value = value;
},
});

const routingGroupIds = computed({
get: () => store.routingGroupIds.value,
set: (value) => {
store.routingGroupIds.value = value;
},
});

type RoutingGroupOption = {
key: string;
label: string;
ids: string[];
};

const routingGroupOptions = computed<RoutingGroupOption[]>(() => {
const grouped = new Map<string, RoutingGroupOption>();
configGroups.value.forEach((group, index) => {
const id = (group.id || "").trim();
if (!id) {
return;
}
const name = (group.name || "").trim();
const key = name ? `name:${name}` : `id:${id}`;
const label = name || `配置组 ${index + 1}`;
const current = grouped.get(key);
if (current) {
current.ids.push(id);
return;
}
grouped.set(key, { key, label, ids: [id] });
});
return Array.from(grouped.values());
});

const selectedRoutingGroupKeys = computed({
get: () => {
const selectedIds = new Set(routingGroupIds.value);
return routingGroupOptions.value
.filter((option) => option.ids.some((id) => selectedIds.has(id)))
.map((option) => option.key);
},
set: (keys: string[]) => {
const selectedKeys = new Set(keys);
routingGroupIds.value = Array.from(
new Set(
routingGroupOptions.value
.filter((option) => selectedKeys.has(option.key))
.flatMap((option) => option.ids),
),
);
},
});

const handleFailoverChange = async () => {
const ok = await store.saveConfig();
if (ok) {
store.appendLog(`API 智能调度已${enable429Failover.value ? "启用" : "禁用"}`);
} else {
store.appendLog("保存配置失败");
}
};

const handleRoutingGroupsChange = async () => {
const ok = await store.saveConfig();
if (ok) {
if (selectedRoutingGroupKeys.value.length) {
store.appendLog(`已设置轮询配置组数量:${selectedRoutingGroupKeys.value.length}`);
} else {
store.appendLog("轮询配置组未指定,仅使用当前激活配置");
}
return;
}
store.appendLog("保存配置失败");
};

const handleThemeSave = (value: ThemeConfig) => {
const normalized = sanitizeThemeConfig(value);
copyThemeConfig(themeConfig, normalized);
Expand All @@ -139,6 +228,58 @@ const handleThemeSave = (value: ThemeConfig) => {
</div>

<div class="mt-4 space-y-4">
<div class="mtga-soft-panel space-y-3">
<div>
<div class="text-sm font-semibold text-slate-900">转发策略</div>
<div class="text-xs text-slate-500">负载均衡与自动容错</div>
</div>
<label class="label cursor-pointer justify-start gap-3 p-0">
<input
v-model="enable429Failover"
type="checkbox"
class="toggle toggle-primary toggle-sm"
@change="handleFailoverChange"
/>
<span class="label-text text-slate-700">启用多节点轮询与自动故障转移</span>
</label>
<div v-if="enable429Failover" class="flex items-center gap-3 pl-11">
<span class="text-xs text-slate-600">节点冷却周期 (秒)</span>
<input
v-model.number="failover429CooldownSeconds"
type="number"
class="mtga-input w-20 px-2 py-1 text-center"
min="1"
@change="handleFailoverChange"
/>
</div>
<div v-if="enable429Failover" class="pl-11 space-y-2">
<div class="text-xs text-slate-600">轮询配置组(可多选,未选则仅使用当前激活配置)</div>
<div v-if="routingGroupOptions.length" class="space-y-1">
<label
v-for="option in routingGroupOptions"
:key="option.key"
class="label cursor-pointer justify-start gap-2 p-0"
>
<input
v-model="selectedRoutingGroupKeys"
type="checkbox"
class="checkbox checkbox-primary checkbox-xs"
:value="option.key"
@change="handleRoutingGroupsChange"
/>
<span class="label-text text-xs text-slate-700">
{{ option.label }} · {{ option.ids.length }} 个 API
</span>
</label>
</div>
<div v-else class="text-xs text-slate-400">暂无可选配置组</div>
</div>
<div class="text-xs text-slate-500 pl-11 leading-relaxed">
开启后,请求将在选中配置组间轮询分发。若节点触发 429 (Too Many Requests)
频率限制,将自动静默切换至可用节点并对受限节点执行冷却隔离,确保服务连续性。
</div>
</div>

<div class="mtga-soft-panel space-y-3">
<div>
<div class="text-sm font-semibold text-slate-900">用户数据</div>
Expand Down
4 changes: 4 additions & 0 deletions app/composables/mtgaTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type ConfigGroup = {
id?: string;
name?: string;
api_url: string;
model_id: string;
Expand All @@ -13,6 +14,9 @@ export type ConfigPayload = {
current_config_index: number;
mapped_model_id: string;
mtga_auth_key: string;
routing_group_ids?: string[];
enable_429_failover?: boolean;
failover_429_cooldown_seconds?: number;
};

export type AppInfo = {
Expand Down
59 changes: 58 additions & 1 deletion app/composables/useMtgaStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,32 @@ const clampIndex = (value: number, max: number) => {
return Math.min(Math.max(value, 0), max - 1);
};

const createConfigGroupId = (index: number) => {
if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) {
return globalThis.crypto.randomUUID();
}
return `group-${Date.now()}-${index}-${Math.random().toString(16).slice(2, 10)}`;
};

const normalizeConfigGroups = (groups: ConfigGroup[]) =>
groups.map((group, index) => {
const id = coerceText(group.id).trim() || createConfigGroupId(index);
return { ...group, id };
});

export const useMtgaStore = () => {
const api = useMtgaApi();

const configGroups = useState<ConfigGroup[]>("mtga-config-groups", () => []);
const currentConfigIndex = useState<number>("mtga-current-config-index", () => 0);
const mappedModelId = useState<string>("mtga-mapped-model-id", () => "");
const mtgaAuthKey = useState<string>("mtga-auth-key", () => "");
const routingGroupIds = useState<string[]>("mtga-routing-group-ids", () => []);
const enable429Failover = useState<boolean>("mtga-enable-429-failover", () => false);
const failover429CooldownSeconds = useState<number>(
"mtga-failover-429-cooldown-seconds",
() => 60,
);
const runtimeOptions = useState<RuntimeOptions>("mtga-runtime-options", () => ({
...DEFAULT_RUNTIME_OPTIONS,
}));
Expand Down Expand Up @@ -449,24 +468,59 @@ export const useMtgaStore = () => {
if (!result) {
return false;
}
configGroups.value = result.config_groups || [];
const loadedGroups = Array.isArray(result.config_groups) ? result.config_groups : [];
configGroups.value = normalizeConfigGroups(loadedGroups);
currentConfigIndex.value = clampIndex(
result.current_config_index ?? 0,
configGroups.value.length,
);
const availableGroupIds = new Set(
configGroups.value.map((group) => coerceText(group.id).trim()),
);
routingGroupIds.value = Array.isArray(result.routing_group_ids)
? Array.from(
new Set(
result.routing_group_ids
.map((id) => coerceText(id).trim())
.filter((id) => id && availableGroupIds.has(id)),
),
)
: [];
mappedModelId.value = coerceText(result.mapped_model_id);
mtgaAuthKey.value = coerceText(result.mtga_auth_key);
enable429Failover.value = Boolean(result.enable_429_failover);
const cooldownRaw = Number(result.failover_429_cooldown_seconds);
failover429CooldownSeconds.value = Number.isFinite(cooldownRaw)
? Math.max(1, Math.round(cooldownRaw))
: 60;
return true;
};

const saveConfig = async () => {
configGroups.value = normalizeConfigGroups(configGroups.value);
const clampedIndex = clampIndex(currentConfigIndex.value, configGroups.value.length);
currentConfigIndex.value = clampedIndex;
const availableGroupIds = new Set(
configGroups.value.map((group) => coerceText(group.id).trim()),
);
routingGroupIds.value = Array.from(
new Set(
routingGroupIds.value
.map((id) => coerceText(id).trim())
.filter((id) => id && availableGroupIds.has(id)),
),
);
const payload: ConfigPayload = {
config_groups: configGroups.value,
current_config_index: clampedIndex,
mapped_model_id: coerceText(mappedModelId.value),
mtga_auth_key: coerceText(mtgaAuthKey.value),
routing_group_ids: routingGroupIds.value,
enable_429_failover: enable429Failover.value,
failover_429_cooldown_seconds: Math.max(
1,
Math.round(Number(failover429CooldownSeconds.value) || 60),
),
};
const ok = await api.saveConfig(payload);
return Boolean(ok);
Expand Down Expand Up @@ -807,6 +861,9 @@ export const useMtgaStore = () => {
currentConfigIndex,
mappedModelId,
mtgaAuthKey,
routingGroupIds,
enable429Failover,
failover429CooldownSeconds,
runtimeOptions,
logs,
systemPrompts,
Expand Down
3 changes: 3 additions & 0 deletions app/docs/ui-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ app/
- [x] `MainTabs` 支持切换并挂载各 Tab 内容(证书/hosts/代理/数据/关于)。
- [x] `ConfigGroupPanel` 改为可交互:列表数据、选中状态、增删改弹窗。
- [x] `GlobalConfigPanel` 与 `RuntimeOptionsPanel` 接入真实数据与保存逻辑。
- [x] 新增 `routing_group_ids` 配置读写,支持按组路由选择。
- [x] 设置页接入配置组多选,轮询仅针对选中组(空则回退全部)。
- [x] `LogPanel` 支持追加日志流(从后端或前端事件)。
- [x] `UpdateDialog`、确认弹窗完善交互与 HTML 内容渲染。
- [x] 用 `pyInvoke` 串起最小功能链路(例如 `greet` -> 日志输出)。
Expand Down Expand Up @@ -146,6 +148,7 @@ config_groups: ConfigGroup[]
current_config_index: number
mapped_model_id: string
mtga_auth_key: string
routing_group_ids: string[]
runtime_options: {
debugMode: boolean
disableSslStrict: boolean
Expand Down
Loading