mirror of
https://github.com/foxhui/WebAI2API.git
synced 2026-06-16 21:03:59 +08:00
fix: 恢复图片下载重试UI,修复兼容性问题
- 恢复 workers.vue 中"图片下载重试"UI (imgDlRetry, imgDlRetryMaxRetries) - 恢复批量代理弹窗提示文字 - 修复实例标识:有 id 用 id,没有用 name - download.js 参数名 maxRetries -> retries,与 adapter 调用一致 - routes.js 重试下载读取配置并传递 retries
This commit is contained in:
@@ -21,22 +21,22 @@ function isRetryableError(message) {
|
||||
* @param {import('playwright-core').Page} page - Playwright 页面对象
|
||||
* @param {object} [options] - 可选配置
|
||||
* @param {number} [options.timeout=60000] - 超时时间(毫秒)
|
||||
* @param {number} [options.maxRetries=3] - 最大重试次数
|
||||
* @param {number} [options.retries=3] - 最大重试次数
|
||||
* @param {number} [options.retryDelay=1000] - 重试延迟基数(毫秒)
|
||||
* @returns {Promise<{ image?: string, imageUrl?: string, error?: string }>} 下载结果(包含原始 URL)
|
||||
*/
|
||||
export async function useContextDownload(url, page, options = {}) {
|
||||
const { timeout = 120000, maxRetries = 3, retryDelay = 1000 } = options;
|
||||
const { timeout = 120000, retries = 3, retryDelay = 1000 } = options;
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
try {
|
||||
const response = await page.request.get(url, { timeout });
|
||||
|
||||
if (!response.ok()) {
|
||||
const status = response.status();
|
||||
// 5xx 错误可重试
|
||||
if (status >= 500 && attempt < maxRetries) {
|
||||
logger.warn('下载', `HTTP ${status},重试 ${attempt}/${maxRetries}...`);
|
||||
if (status >= 500 && attempt < retries) {
|
||||
logger.warn('下载', `HTTP ${status},重试 ${attempt}/${retries}...`);
|
||||
await new Promise(r => setTimeout(r, retryDelay * attempt));
|
||||
continue;
|
||||
}
|
||||
@@ -50,8 +50,8 @@ export async function useContextDownload(url, page, options = {}) {
|
||||
|
||||
return { image: `data:${mimeType};base64,${base64}`, imageUrl: url };
|
||||
} catch (e) {
|
||||
if (isRetryableError(e.message) && attempt < maxRetries) {
|
||||
logger.warn('下载', `${e.message},重试 ${attempt}/${maxRetries}...`);
|
||||
if (isRetryableError(e.message) && attempt < retries) {
|
||||
logger.warn('下载', `${e.message},重试 ${attempt}/${retries}...`);
|
||||
await new Promise(r => setTimeout(r, retryDelay * attempt));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -572,7 +572,10 @@ export function createAdminRouter(context) {
|
||||
const poolContext = queueManager?.getPoolContext?.();
|
||||
const page = poolContext?.getFirstPage?.();
|
||||
if (page) {
|
||||
downloadFn = (url) => useContextDownload(url, page);
|
||||
const imgDlCfg = config?.backend?.pool?.failover || {};
|
||||
downloadFn = (url) => useContextDownload(url, page, {
|
||||
retries: imgDlCfg.imgDlRetry ? (imgDlCfg.imgDlRetryMaxRetries || 3) : 1
|
||||
});
|
||||
}
|
||||
} catch { /* Pool 未初始化,使用后备方案 */ }
|
||||
|
||||
|
||||
@@ -87,6 +87,9 @@ const instanceData = computed({
|
||||
set: (val) => { settingsStore.workerConfig = val; }
|
||||
});
|
||||
|
||||
// 获取实例唯一标识(优先 id,没有则用 name)
|
||||
const getInstanceKey = (inst) => inst.id || inst.name;
|
||||
|
||||
// 批量选择
|
||||
const selectedRowKeys = ref([]);
|
||||
const rowSelection = computed(() => ({
|
||||
@@ -121,7 +124,7 @@ const openBatchProxy = () => {
|
||||
|
||||
const handleBatchProxySave = async () => {
|
||||
const newList = (instanceData.value || []).map(inst => {
|
||||
if (!selectedRowKeys.value.includes(inst.name)) return inst;
|
||||
if (!selectedRowKeys.value.includes(getInstanceKey(inst))) return inst;
|
||||
return {
|
||||
...inst,
|
||||
proxy: batchProxyForm.value.proxy ? {
|
||||
@@ -152,7 +155,7 @@ const handleBatchDelete = () => {
|
||||
cancelText: '取消',
|
||||
async onOk() {
|
||||
const newList = (instanceData.value || []).filter(
|
||||
inst => !selectedRowKeys.value.includes(inst.name)
|
||||
inst => !selectedRowKeys.value.includes(getInstanceKey(inst))
|
||||
);
|
||||
const success = await settingsStore.saveWorkerConfig(newList);
|
||||
if (success) {
|
||||
@@ -225,7 +228,8 @@ const handleEdit = (record) => {
|
||||
|
||||
// 删除实例
|
||||
const handleDelete = async (record) => {
|
||||
const newList = instanceData.value.filter(item => item.name !== record.name);
|
||||
const key = getInstanceKey(record);
|
||||
const newList = instanceData.value.filter(item => getInstanceKey(item) !== key);
|
||||
await settingsStore.saveWorkerConfig(newList);
|
||||
};
|
||||
|
||||
@@ -253,8 +257,9 @@ const handleSaveEdit = async () => {
|
||||
// 创建
|
||||
newList.push(instanceToSave);
|
||||
} else {
|
||||
// 更新 - 用原始 name 查找
|
||||
const index = newList.findIndex(item => item.name === editingInstance.value.name);
|
||||
// 更新 - 用唯一标识查找
|
||||
const editingKey = getInstanceKey(editingInstance.value);
|
||||
const index = newList.findIndex(item => getInstanceKey(item) === editingKey);
|
||||
if (index > -1) {
|
||||
newList[index] = instanceToSave;
|
||||
}
|
||||
@@ -374,6 +379,32 @@ const handleRemoveWorker = (index) => {
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider style="margin: 12px 0;" />
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :xs="24" :md="12">
|
||||
<div style="margin-bottom: 8px;">
|
||||
<div style="font-weight: 600; margin-bottom: 8px;">图片下载重试</div>
|
||||
<div style="font-size: 12px; color: #8c8c8c; margin-bottom: 12px;">
|
||||
启用后,图片/视频下载失败时会自动重试下载(不重新生成)
|
||||
</div>
|
||||
<a-switch v-model:checked="poolConfig.failover.imgDlRetry" />
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :xs="24" :md="12">
|
||||
<div style="margin-bottom: 8px;">
|
||||
<div style="font-weight: 600; margin-bottom: 8px;">下载重试次数</div>
|
||||
<div style="font-size: 12px; color: #8c8c8c; margin-bottom: 12px;">
|
||||
图片下载失败时的最大重试次数,范围 1-10
|
||||
</div>
|
||||
<a-input-number v-model:value="poolConfig.failover.imgDlRetryMaxRetries" :min="1"
|
||||
:max="10" :disabled="!poolConfig.failover.imgDlRetry" style="width: 100%"
|
||||
placeholder="请输入下载重试次数" />
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</div>
|
||||
@@ -408,7 +439,7 @@ const handleRemoveWorker = (index) => {
|
||||
|
||||
<!-- 实例表格 -->
|
||||
<a-table :columns="columns" :data-source="instanceData" :pagination="false"
|
||||
:row-selection="rowSelection" row-key="name">
|
||||
:row-selection="rowSelection" :row-key="record => record.id || record.name">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<!-- 实例名称 -->
|
||||
<template v-if="column.key === 'name'">
|
||||
@@ -609,6 +640,9 @@ const handleRemoveWorker = (index) => {
|
||||
<!-- 批量代理设置模态框 -->
|
||||
<a-modal v-model:open="batchProxyVisible" title="批量设置代理" okText="确定" cancelText="取消"
|
||||
@ok="handleBatchProxySave">
|
||||
<div style="font-size: 12px; color: #8c8c8c; margin-bottom: 16px;">
|
||||
将对选中的 {{ selectedRowKeys.length }} 个实例统一设置代理
|
||||
</div>
|
||||
<div style="margin-bottom: 16px;">
|
||||
<a-switch v-model:checked="batchProxyForm.proxy" />
|
||||
<span style="margin-left: 8px;">
|
||||
|
||||
Reference in New Issue
Block a user