Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
306 changes: 303 additions & 3 deletions src/components/SysCogUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,135 @@
</el-form>
</div>

<div v-if="activeChannel === 'discord'">
<!-- 负载均衡配置 -->
<el-form
:model="discordSettings"
label-position="top"
class="channel-form"
>
<el-form-item label="负载均衡">
<el-switch v-model="discordSettings.loadBalance.enabled"/>
</el-form-item>
</el-form>

<el-form
v-for="(channel, index) in discordSettings.channels"
:key="index"
:model="channel"
label-position="top"
:rules="discordRules"
ref="discordChannelForm"
class="channel-form"
>
<el-form-item label="渠道名" prop="name">
<el-input v-model="channel.name" :disabled="channel.fixed"/>
</el-form-item>
<el-form-item label="启用渠道" prop="enabled">
<el-switch v-model="channel.enabled"/>
</el-form-item>
<el-form-item label="Bot Token" prop="botToken">
<el-input v-model="channel.botToken" :disabled="channel.fixed" type="password" show-password autocomplete="new-password"/>
</el-form-item>
<el-form-item label="Channel ID" prop="channelId">
<el-input v-model="channel.channelId" :disabled="channel.fixed" type="password" show-password autocomplete="new-password"/>
</el-form-item>
<el-form-item>
<template #label>
代理域名
<el-tooltip content="可选,用于国内访问 Discord CDN,填写代理域名(不含 https://)" placement="top">
<font-awesome-icon icon="question-circle" style="margin-left: 5px; cursor: pointer;"/>
</el-tooltip>
</template>
<el-input v-model="channel.proxyUrl" placeholder="例如: your-proxy.example.com"/>
</el-form-item>
<el-form-item>
<template #label>
Nitro 会员
<el-tooltip content="开启后单文件限制提升至 25MB,关闭则为 10MB" placement="top">
<font-awesome-icon icon="question-circle" style="margin-left: 5px; cursor: pointer;"/>
</el-tooltip>
</template>
<el-switch v-model="channel.isNitro"/>
</el-form-item>
<el-form-item>
<div class="discord-limit-tip">
<font-awesome-icon icon="info-circle" style="margin-right: 5px;"/>
{{ channel.isNitro ? 'Nitro 会员单文件限制 25MB,超过将自动切换其他渠道' : 'Discord 免费用户单文件限制 10MB,超过将自动切换其他渠道' }}
</div>
</el-form-item>
<!-- 删除 -->
<el-form-item>
<el-button type="danger" @click="deleteChannel(index)" size="small" :disabled="channel.fixed">
<font-awesome-icon icon="trash-alt" />
</el-button>
</el-form-item>
</el-form>
</div>

<div v-if="activeChannel === 'huggingface'">
<!-- 负载均衡配置 -->
<el-form
:model="huggingfaceSettings"
label-position="top"
class="channel-form"
>
<el-form-item label="负载均衡">
<el-switch v-model="huggingfaceSettings.loadBalance.enabled"/>
</el-form-item>
</el-form>

<el-form
v-for="(channel, index) in huggingfaceSettings.channels"
:key="index"
:model="channel"
label-position="top"
:rules="huggingfaceRules"
ref="huggingfaceChannelForm"
class="channel-form"
>
<el-form-item label="渠道名" prop="name">
<el-input v-model="channel.name" :disabled="channel.fixed"/>
</el-form-item>
<el-form-item label="启用渠道" prop="enabled">
<el-switch v-model="channel.enabled"/>
</el-form-item>
<el-form-item prop="repo">
<template #label>
仓库名
<el-tooltip content="格式:用户名/仓库名,例如 username/my-images" placement="top">
<font-awesome-icon icon="question-circle" style="margin-left: 5px; cursor: pointer;"/>
</el-tooltip>
</template>
<el-input v-model="channel.repo" :disabled="channel.fixed" placeholder="username/repo-name"/>
</el-form-item>
<el-form-item label="Access Token" prop="token">
<el-input v-model="channel.token" :disabled="channel.fixed" type="password" show-password autocomplete="new-password"/>
</el-form-item>
<el-form-item>
<template #label>
私有仓库
<el-tooltip content="开启后仓库将设为私有,访问时需要通过服务器代理" placement="top">
<font-awesome-icon icon="question-circle" style="margin-left: 5px; cursor: pointer;"/>
</el-tooltip>
</template>
<el-switch v-model="channel.isPrivate"/>
</el-form-item>
<el-form-item>
<div class="huggingface-tip">
<font-awesome-icon icon="info-circle" style="margin-right: 5px;"/>
{{ channel.isPrivate ? '私有仓库限制 100GB,访问时服务器会代理请求' : '公开仓库无容量限制,文件可直接访问' }}
</div>
</el-form-item>
<!-- 删除 -->
<el-form-item>
<el-button type="danger" @click="deleteChannel(index)" size="small" :disabled="channel.fixed">
<font-awesome-icon icon="trash-alt" />
</el-button>
</el-form-item>
</el-form>
</div>

</div>

<!-- 操作按钮 -->
Expand All @@ -258,8 +387,10 @@ data() {
// 一级设置:上传渠道
channels: [
{ value: 'telegram', label: 'Telegram' },
{ value: 'cfr2', label: 'CloudFlareR2' },
{ value: 's3', label: 'S3' }
{ value: 'cfr2', label: 'CloudFlare R2' },
{ value: 's3', label: 'S3' },
{ value: 'discord', label: 'Discord' },
{ value: 'huggingface', label: 'HuggingFace' }
],
activeChannel: 'telegram', // 当前选中的上传渠道

Expand Down Expand Up @@ -308,6 +439,72 @@ data() {
channels: []
},

// 二级设置:Discord 配置
discordSettings: {
loadBalance: {},
channels: []
},

// 二级设置:HuggingFace 配置
huggingfaceSettings: {
loadBalance: {},
channels: []
},

huggingfaceRules: {
name: [
{ required: true, message: '请输入渠道名', trigger: 'blur' },
{ validator: (rule, value, callback) => {
const names = this.huggingfaceSettings.channels.map((item) => item.name);
if (names.filter((name) => name === value).length > 1) {
callback(new Error('渠道名不能重复'));
} else if (value === 'HuggingFace_env') {
const savePath = this.huggingfaceSettings.channels.find((item) => item.name === value).savePath;
if (savePath !== 'environment variable') {
callback(new Error('渠道名不能为保留值'));
} else {
callback();
}
} else {
callback();
}
}, trigger: 'blur' }
],
token: [
{ required: true, message: '请输入 Access Token', trigger: 'blur' }
],
repo: [
{ required: true, message: '请输入仓库名', trigger: 'blur' }
]
},

discordRules: {
name: [
{ required: true, message: '请输入渠道名', trigger: 'blur' },
{ validator: (rule, value, callback) => {
const names = this.discordSettings.channels.map((item) => item.name);
if (names.filter((name) => name === value).length > 1) {
callback(new Error('渠道名不能重复'));
} else if (value === 'Discord_env') {
const savePath = this.discordSettings.channels.find((item) => item.name === value).savePath;
if (savePath !== 'environment variable') {
callback(new Error('渠道名不能为保留值'));
} else {
callback();
}
} else {
callback();
}
}, trigger: 'blur' }
],
botToken: [
{ required: true, message: '请输入 Bot Token', trigger: 'blur' }
],
channelId: [
{ required: true, message: '请输入 Channel ID', trigger: 'blur' }
]
},

// 容量统计数据
quotaStats: {},
quotaLoading: false,
Expand Down Expand Up @@ -410,6 +607,33 @@ methods: {
}
});
break;
case 'discord':
this.discordSettings.channels.push({
id: this.discordSettings.channels.length + 1,
name: '',
type: 'discord',
savePath: 'database',
botToken: '',
channelId: '',
proxyUrl: '',
isNitro: false,
enabled: true,
fixed: false
});
break;
case 'huggingface':
this.huggingfaceSettings.channels.push({
id: this.huggingfaceSettings.channels.length + 1,
name: '',
type: 'huggingface',
savePath: 'database',
token: '',
repo: '',
isPrivate: false,
enabled: true,
fixed: false
});
break;
}
},
deleteChannel(index) {
Expand Down Expand Up @@ -441,6 +665,24 @@ methods: {
});
this.s3Settings.channels.splice(index, 1);
break;
case 'discord':
// 调整 id
this.discordSettings.channels.forEach((item, i) => {
if (i > index) {
item.id -= 1;
}
});
this.discordSettings.channels.splice(index, 1);
break;
case 'huggingface':
// 调整 id
this.huggingfaceSettings.channels.forEach((item, i) => {
if (i > index) {
item.id -= 1;
}
});
this.huggingfaceSettings.channels.splice(index, 1);
break;
}
},
saveSettings() {
Expand All @@ -465,6 +707,24 @@ methods: {
});
}

// Discord
if (this.$refs.discordChannelForm) {
this.$refs.discordChannelForm.forEach((form) => {
validationPromises.push(new Promise((resolve) => {
form.validate((valid) => resolve(valid));
}));
});
}

// HuggingFace
if (this.$refs.huggingfaceChannelForm) {
this.$refs.huggingfaceChannelForm.forEach((form) => {
validationPromises.push(new Promise((resolve) => {
form.validate((valid) => resolve(valid));
}));
});
}

// 等待所有验证完成
Promise.all(validationPromises).then((results) => {
const isValid = results.every(valid => valid);
Expand All @@ -477,7 +737,9 @@ methods: {
const settings = {
telegram: this.telegramSettings,
cfr2: this.cfr2Settings,
s3: this.s3Settings
s3: this.s3Settings,
discord: this.discordSettings,
huggingface: this.huggingfaceSettings
};
fetchWithAuth('/api/manage/sysConfig/upload', {
method: 'POST',
Expand Down Expand Up @@ -643,6 +905,22 @@ mounted() {
}));
}
this.s3Settings = data.s3;
// 确保 Discord 渠道有默认值
if (data.discord && data.discord.channels) {
data.discord.channels = data.discord.channels.map(channel => ({
...channel,
proxyUrl: channel.proxyUrl || ''
}));
}
this.discordSettings = data.discord || { loadBalance: {}, channels: [] };
// 确保 HuggingFace 渠道有默认值
if (data.huggingface && data.huggingface.channels) {
data.huggingface.channels = data.huggingface.channels.map(channel => ({
...channel,
isPrivate: channel.isPrivate || false
}));
}
this.huggingfaceSettings = data.huggingface || { loadBalance: {}, channels: [] };
// 加载容量统计(仅读取,不重建索引)
this.loadQuotaStats();
})
Expand Down Expand Up @@ -798,6 +1076,28 @@ mounted() {
font-weight: 500;
}

/* Discord 限制提示 */
.discord-limit-tip {
font-size: 13px;
color: var(--el-color-info);
padding: 10px 14px;
background: var(--el-color-info-light-9);
border-radius: 6px;
border-left: 3px solid var(--el-color-info);
white-space: nowrap;
}

/* HuggingFace 提示 */
.huggingface-tip {
font-size: 13px;
color: var(--el-color-info);
padding: 10px 14px;
background: var(--el-color-info-light-9);
border-radius: 6px;
border-left: 3px solid var(--el-color-info);
white-space: nowrap;
}

/* 移动端适配 */
@media (max-width: 768px) {
.upload-settings {
Expand Down
Loading