feat: QwenPaw support config username/password (#13478)

This commit is contained in:
CityFun
2026-08-05 16:35:09 +08:00
committed by GitHub
parent 9dfa451fae
commit 83a3675a0c
35 changed files with 346 additions and 89 deletions

View File

@@ -969,9 +969,9 @@ export namespace AI {
export interface AgentOtherConfigUpdateReq {
agentId: number;
userTimezone: string;
userTimezone?: string;
browserEnabled: boolean;
npmRegistry: string;
npmRegistry?: string;
dashboardUsername?: string;
dashboardPassword?: string;
}

View File

@@ -922,6 +922,8 @@ const message = {
channelAutoRestartHelper: 'Saving will automatically restart the container so the changes take effect.',
dashboardAuthAutoRebuildHelper:
'Saving the username or password will automatically rebuild the container so the changes take effect.',
dashboardAuthImmediateHelper:
'Saving updates the QwenPaw login credentials immediately without restarting the container.',
channelDeleteConfirm: 'Delete the {0} channel configuration?',
apiTypeBaseURLHelper: 'This type sends requests to {0}. Recommended Base URL: {1}',
customImageURLHelper:

View File

@@ -925,6 +925,8 @@ const message = {
'Al guardar, el contenedor se reiniciará automáticamente para que la configuración surta efecto.',
dashboardAuthAutoRebuildHelper:
'Al guardar el usuario o la contraseña, el contenedor se reconstruirá automáticamente para que los cambios surtan efecto.',
dashboardAuthImmediateHelper:
'Al guardar, las credenciales de QwenPaw se actualizan de inmediato sin reiniciar el contenedor.',
channelDeleteConfirm: '¿Eliminar la configuración del canal {0}?',
apiTypeBaseURLHelper: 'Este tipo envía solicitudes a {0}. Base URL recomendada: {1}',
customImageURLHelper:

View File

@@ -910,6 +910,8 @@ const message = {
channelAutoRestartHelper: 'ذخیره به طور خودکار کانتینر را مجدداً راه‌اندازی می‌کند تا تغییرات اعمال شوند.',
dashboardAuthAutoRebuildHelper:
'ذخیره نام کاربری یا رمز عبور، کانتینر را به‌طور خودکار بازسازی می‌کند تا تغییرات اعمال شوند.',
dashboardAuthImmediateHelper:
'با ذخیره‌سازی، اطلاعات ورود QwenPaw بدون راه‌اندازی مجدد کانتینر فوراً به‌روزرسانی می‌شود.',
channelDeleteConfirm: 'پیکربندی کانال {0} حذف شود؟',
apiTypeBaseURLHelper: 'این نوع درخواست‌ها را به {0} ارسال می‌کند. Base URL پیشنهادی: {1}',
apiTypeBaseURLMismatch: 'Base URL فعلی شبیه مسیر {0} است، اما {1} انتخاب شده است. URL پیشنهادی: {2}',

View File

@@ -914,6 +914,8 @@ const message = {
channelAutoRestartHelper: '保存後、設定を反映するためにコンテナが自動で再起動されます。',
dashboardAuthAutoRebuildHelper:
'ユーザー名またはパスワードを保存すると、変更を反映するためにコンテナが自動で再作成されます。',
dashboardAuthImmediateHelper:
'保存すると QwenPaw のログイン認証情報がすぐに更新され、コンテナの再起動は不要です。',
channelDeleteConfirm: '{0} チャンネルの設定を削除しますか?',
apiTypeBaseURLHelper: 'このタイプは最終的に {0} にリクエストします。推奨 Base URL: {1}',
customImageURLHelper:

View File

@@ -904,6 +904,8 @@ const message = {
channelAutoRestartHelper: '저장하면 설정 적용을 위해 컨테이너가 자동으로 재시작됩니다.',
dashboardAuthAutoRebuildHelper:
'사용자 이름 또는 비밀번호를 저장하면 변경 사항 적용을 위해 컨테이너가 자동으로 재생성됩니다.',
dashboardAuthImmediateHelper:
'저장하면 컨테이너를 다시 시작하지 않고 QwenPaw 로그인 자격 증명이 즉시 업데이트됩니다.',
channelDeleteConfirm: '{0} 채널 구성을 삭제하시겠습니까?',
apiTypeBaseURLHelper: '이 유형은 최종적으로 {0}에 요청합니다. 권장 Base URL: {1}',
customImageURLHelper:

View File

@@ -904,6 +904,8 @@ const message = {
channelAutoRestartHelper: 'ການບັນທຶກຈະຣີສະຕາດຄອນເທນເນີອັດຕະໂນມັດເພື່ອໃຫ້ການປ່ຽນແປງມີຜົນ.',
dashboardAuthAutoRebuildHelper:
'ການບັນທຶກຊື່ຜູ້ໃຊ້ ຫຼື ລະຫັດຜ່ານ Dashboard ຈະສ້າງຄອນເທນເນີໃໝ່ອັດຕະໂນມັດເພື່ອໃຫ້ການປ່ຽນແປງມີຜົນ.',
dashboardAuthImmediateHelper:
'ເມື່ອບັນທຶກ ຂໍ້ມູນເຂົ້າລະບົບ QwenPaw ຈະຖືກອັບເດດທັນທີໂດຍບໍ່ຕ້ອງຣີສະຕາດຄອນເທນເນີ.',
channelDeleteConfirm: 'ລຶບການຕັ້ງຄ່າຊ່ອງທາງ {0}?',
apiTypeBaseURLHelper: 'ປະເພດນີ້ຈະສົ່ງການຮ້ອງຂໍໄປທີ່ {0}. Base URL ທີ່ແນະນຳ: {1}',
customImageURLHelper:

View File

@@ -933,6 +933,8 @@ const message = {
'Menyimpan akan memulakan semula bekas secara automatik supaya konfigurasi berkuat kuasa.',
dashboardAuthAutoRebuildHelper:
'Menyimpan nama pengguna atau kata laluan akan membina semula bekas secara automatik supaya perubahan berkuat kuasa.',
dashboardAuthImmediateHelper:
'Apabila disimpan, kelayakan log masuk QwenPaw dikemas kini serta-merta tanpa memulakan semula bekas.',
channelDeleteConfirm: 'Padam konfigurasi saluran {0}?',
apiTypeBaseURLHelper: 'Jenis ini menghantar permintaan ke {0}. Base URL yang disyorkan: {1}',
customImageURLHelper:

View File

@@ -929,6 +929,8 @@ const message = {
'Ao salvar, o contêiner será reiniciado automaticamente para que a configuração entre em vigor.',
dashboardAuthAutoRebuildHelper:
'Ao salvar o usuário ou a senha, o contêiner será reconstruído automaticamente para que as alterações entrem em vigor.',
dashboardAuthImmediateHelper:
'Ao salvar, as credenciais do QwenPaw são atualizadas imediatamente, sem reiniciar o contêiner.',
channelDeleteConfirm: 'Excluir a configuração do canal {0}?',
apiTypeBaseURLHelper: 'Este tipo envia requisições para {0}. Base URL recomendada: {1}',
customImageURLHelper:

View File

@@ -922,6 +922,8 @@ const message = {
'После сохранения контейнер будет автоматически перезапущен, чтобы настройки вступили в силу.',
dashboardAuthAutoRebuildHelper:
'После сохранения имени пользователя или пароля контейнер будет автоматически пересобран, чтобы изменения вступили в силу.',
dashboardAuthImmediateHelper:
'После сохранения учетные данные QwenPaw обновляются сразу, без перезапуска контейнера.',
channelDeleteConfirm: 'Удалить конфигурацию канала {0}?',
apiTypeBaseURLHelper: 'Этот тип отправляет запросы в {0}. Рекомендуемый Base URL: {1}',
customImageURLHelper:

View File

@@ -931,6 +931,8 @@ const message = {
'Kaydettiğinizde ayarların etkili olması için konteyner otomatik olarak yeniden başlatılır.',
dashboardAuthAutoRebuildHelper:
'Kullanıcı adı veya parola kaydedildiğinde değişikliklerin etkili olması için konteyner otomatik olarak yeniden oluşturulur.',
dashboardAuthImmediateHelper:
'Kaydedildiğinde QwenPaw oturum açma bilgileri konteyner yeniden başlatılmadan hemen güncellenir.',
channelDeleteConfirm: '{0} kanal yapılandırması silinsin mi?',
apiTypeBaseURLHelper: 'Bu tür istekleri {0} yoluna gönderir. Önerilen Base URL: {1}',
customImageURLHelper:

View File

@@ -877,6 +877,7 @@ const message = {
scanConnectHelper: '點擊後將在任務日誌中顯示 QR Code掃碼確認成功後將自動重新啟動容器。',
channelAutoRestartHelper: '保存後將自動重新啟動容器以使設定生效。',
dashboardAuthAutoRebuildHelper: '保存使用者名稱或密碼後將自動重建容器以使設定生效。',
dashboardAuthImmediateHelper: '保存後將立即更新 QwenPaw 登入憑據,無需重新啟動容器。',
channelDeleteConfirm: '確認刪除 {0} 頻道設定?',
apiTypeBaseURLHelper: '目前類型最終請求 {0}Base URL 建議填寫 {1}',
customImageURLHelper: 'Custom 圖片帳號請填寫完整的圖片產生介面 URL系統不會自動補全路徑例如{0}',

View File

@@ -890,6 +890,7 @@ const message = {
scanConnectHelper: '点击后将在任务日志中显示二维码,扫码确认成功后将自动重启容器。',
channelAutoRestartHelper: '保存后将自动重启容器以使配置生效。',
dashboardAuthAutoRebuildHelper: '保存用户名密码后将自动重建容器以使配置生效。',
dashboardAuthImmediateHelper: '保存后将立即更新 QwenPaw 登录凭据,无需重启容器。',
channelDeleteConfirm: '确认删除 {0} 频道配置?',
apiTypeBaseURLHelper: '当前类型最终请求 {0}Base URL 建议填写 {1}',
customImageURLHelper: 'Custom 图片账号请填写完整的图片生成接口 URL系统不会自动补全路径例如{0}',

View File

@@ -26,6 +26,10 @@ export const supportsAgentToken = (agentType: string): boolean => {
return agentType === 'openclaw';
};
export const supportsAgentDashboardAuth = (agentType: string): boolean => {
return agentType === 'hermes-agent' || agentType === 'copaw';
};
export const isOpenclawHTTPSWindowVersion = (version: string): boolean => {
return compareVersion(version, openclawHTTPSVersion) && !compareVersion(version, openclawHTTPVersion);
};

View File

@@ -23,7 +23,7 @@
<el-form-item :label="$t('aiTools.agents.webuiPort')" prop="webUIPort">
<el-input-number v-model="form.webUIPort" :min="1" :max="65535" />
</el-form-item>
<template v-if="form.agentType === 'hermes-agent'">
<template v-if="supportsAgentDashboardAuth(form.agentType)">
<el-form-item :label="$t('commons.login.username')" prop="dashboardUsername">
<el-input v-model="form.dashboardUsername" />
</el-form-item>
@@ -131,6 +131,7 @@ import {
buildDefaultAllowedOrigin,
getAgentProviderDisplayName,
parseAllowedOriginsInput,
supportsAgentDashboardAuth,
validateAllowedOriginsInput,
} from '@/utils/agent';
import { App } from '@/api/interface/app';
@@ -187,8 +188,8 @@ const generateDashboardPassword = () => {
form.dashboardPassword = getRandomStr(8);
};
const ensureHermesDashboardAuth = () => {
if (form.agentType !== 'hermes-agent') {
const ensureDashboardAuth = () => {
if (!supportsAgentDashboardAuth(form.agentType)) {
return;
}
if (!form.dashboardUsername) {
@@ -411,7 +412,7 @@ const handleAgentTypeChange = async () => {
form.provider = '';
form.accountId = undefined as unknown as number;
form.baseURL = '';
ensureHermesDashboardAuth();
ensureDashboardAuth();
if (form.agentType === 'openclaw') {
await loadSystemIP();
allowedOriginsAutoFilled.value = true;
@@ -487,8 +488,8 @@ const submit = async () => {
model: showModelConfig.value ? form.model : undefined,
accountId: showModelConfig.value ? form.accountId : undefined,
token: form.agentType === 'openclaw' ? form.token : undefined,
dashboardUsername: form.agentType === 'hermes-agent' ? form.dashboardUsername : undefined,
dashboardPassword: form.agentType === 'hermes-agent' ? form.dashboardPassword : undefined,
dashboardUsername: supportsAgentDashboardAuth(form.agentType) ? form.dashboardUsername : undefined,
dashboardPassword: supportsAgentDashboardAuth(form.agentType) ? form.dashboardPassword : undefined,
taskID: taskID,
advanced: form.advanced,
containerName: form.containerName,
@@ -536,7 +537,7 @@ const openDrawer = async (agentType?: AI.AgentType) => {
form.agentType = targetType;
setDefaultWebUIPort();
form.token = getRandomStr(32).toLowerCase();
ensureHermesDashboardAuth();
ensureDashboardAuth();
if (form.agentType === 'copaw') {
form.allowedOrigins = '';
lastAutoAllowedOrigins.value = '';

View File

@@ -9,7 +9,7 @@
>
<ChannelsTab ref="channelsRef" :app-version="appVersion" :agent-type="agentType" />
</el-tab-pane>
<el-tab-pane :label="t('aiTools.model.model')" name="model">
<el-tab-pane v-if="supportsAgentModelConfig(agentType)" :label="t('aiTools.model.model')" name="model">
<ModelTab ref="modelRef" @updated="handleModelUpdated" />
</el-tab-pane>
<el-tab-pane v-if="agentType === 'openclaw'" :label="t('aiTools.agents.agentRoleTab')" name="agent">
@@ -26,7 +26,7 @@
<PluginsTab ref="pluginsRef" />
</el-tab-pane>
<el-tab-pane
v-if="agentType === 'openclaw' || agentType === 'hermes-agent'"
v-if="agentType === 'openclaw' || agentType === 'hermes-agent' || agentType === 'copaw'"
:label="t('file.setting')"
name="settings"
>
@@ -48,6 +48,7 @@ import AgentTab from './tabs/agents/index.vue';
import SkillsTab from './tabs/skills.vue';
import PluginsTab from './tabs/plugins.vue';
import SettingsTab from './tabs/settings.vue';
import { supportsAgentModelConfig } from '@/utils/agent';
const { t } = useI18n();
const emit = defineEmits(['updated']);
@@ -165,13 +166,13 @@ const openDrawer = async (agent: AI.AgentItem) => {
configPath.value = agent.configPath;
agentType.value = agent.agentType;
header.value = `${agent.name} - ${t('menu.config')}`;
activeTab.value = agent.agentType === 'openclaw' || agent.agentType === 'hermes-agent' ? 'channels' : 'model';
activeTab.value = agent.agentType === 'copaw' ? 'settings' : 'channels';
open.value = true;
if (agent.agentType === 'openclaw' || agent.agentType === 'hermes-agent') {
await loadChannels();
return;
}
await loadModel();
await loadSettings();
};
defineExpose({

View File

@@ -9,10 +9,10 @@
</el-select>
<span class="input-help">{{ t('aiTools.agents.npmRegistryHelper') }}</span>
</el-form-item>
<el-form-item :label="t('aiTools.agents.timeZone')" prop="userTimezone">
<el-form-item v-if="agentType !== 'copaw'" :label="t('aiTools.agents.timeZone')" prop="userTimezone">
<el-input v-model="form.userTimezone" />
</el-form-item>
<template v-if="agentType === 'hermes-agent'">
<template v-if="supportsAgentDashboardAuth(agentType)">
<el-form-item :label="t('commons.login.username')" prop="dashboardUsername">
<el-input v-model="form.dashboardUsername" />
</el-form-item>
@@ -33,7 +33,9 @@
{{
agentType === 'hermes-agent'
? t('aiTools.agents.dashboardAuthAutoRebuildHelper')
: t('aiTools.agents.channelAutoRestartHelper')
: agentType === 'copaw'
? t('aiTools.agents.dashboardAuthImmediateHelper')
: t('aiTools.agents.channelAutoRestartHelper')
}}
</span>
</el-form>
@@ -47,6 +49,7 @@ import { Rules } from '@/global/form-rules';
import { AI } from '@/api/interface/ai';
import { getAgentOtherConfig, updateAgentOtherConfig } from '@/api/modules/ai';
import { MsgSuccess } from '@/utils/message';
import { supportsAgentDashboardAuth } from '@/utils/agent';
const { t } = useI18n();
const loading = ref(false);
@@ -71,6 +74,16 @@ const npmRegistryOptions = [
'https://repo.huaweicloud.com/repository/npm/',
];
const resetForm = () => {
Object.assign(form, {
userTimezone: '',
browserEnabled: true,
npmRegistry: defaultNPMRegistry,
dashboardUsername: 'admin',
dashboardPassword: '',
});
};
const validateNPMRegistry = (_rule: any, value: string, callback: (error?: Error) => void) => {
if (!value) {
callback(new Error(t('commons.rule.requiredInput')));
@@ -93,11 +106,12 @@ const rules = reactive({
const load = async (params: { agentId: number; agentType: AI.AgentType }) => {
agentId.value = params.agentId;
agentType.value = params.agentType;
resetForm();
loading.value = true;
try {
const res = await getAgentOtherConfig({ agentId: params.agentId });
Object.assign(form, res.data || {});
if (params.agentType === 'hermes-agent' && !form.dashboardUsername) {
if (supportsAgentDashboardAuth(params.agentType) && !form.dashboardUsername) {
form.dashboardUsername = 'admin';
}
} finally {
@@ -114,11 +128,11 @@ const saveConfig = async () => {
try {
await updateAgentOtherConfig({
agentId: agentId.value,
userTimezone: form.userTimezone,
userTimezone: agentType.value === 'copaw' ? undefined : form.userTimezone,
browserEnabled: form.browserEnabled,
npmRegistry: agentType.value === 'openclaw' ? form.npmRegistry : defaultNPMRegistry,
dashboardUsername: agentType.value === 'hermes-agent' ? form.dashboardUsername : undefined,
dashboardPassword: agentType.value === 'hermes-agent' ? form.dashboardPassword : undefined,
dashboardUsername: supportsAgentDashboardAuth(agentType.value) ? form.dashboardUsername : undefined,
dashboardPassword: supportsAgentDashboardAuth(agentType.value) ? form.dashboardPassword : undefined,
});
MsgSuccess(t('aiTools.agents.saveSuccess'));
} finally {

View File

@@ -173,7 +173,7 @@
</el-button>
</el-space>
<el-space
v-else-if="row.agentType === 'hermes-agent'"
v-else-if="supportsAgentDashboardAuth(row.agentType)"
direction="vertical"
alignment="start"
:size="2"
@@ -303,6 +303,7 @@ import {
getOpenclawAccessScheme,
supportsAgentModelConfig,
supportsAgentToken,
supportsAgentDashboardAuth,
} from '@/utils/agent';
import { routerToFileWithPath } from '@/utils/router';
import { listDomains } from '@/api/modules/website';
@@ -356,7 +357,8 @@ const buttons = [
{
label: i18n.global.t('menu.config'),
click: (row: AI.AgentItem) => openConfig(row),
show: (row: AI.AgentItem) => supportsAgentModelConfig(row.agentType),
show: (row: AI.AgentItem) =>
supportsAgentModelConfig(row.agentType) || supportsAgentDashboardAuth(row.agentType),
disabled: (row: AI.AgentItem) => row.status !== 'Running',
},
{