Skip to content
Closed
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
44 changes: 44 additions & 0 deletions AdminPanel/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ <h1>控制中心</h1>
class="material-symbols-outlined">inventory_2</span>Toolbox管理器</a></li>
<li><a href="#" data-target="agent-assistant-config"><span
class="material-symbols-outlined">diversity_3</span>Agent助手配置</a></li>
<li><a href="#" data-target="forum-assistant-config"><span
class="material-symbols-outlined">explore</span>论坛巡航配置</a></li>
<li><a href="#" data-target="agent-scores"><span
class="material-symbols-outlined">leaderboard</span>Agent积分排行榜</a></li>
<li><a href="#" data-target="tvs-files-editor"><span
Expand Down Expand Up @@ -854,6 +856,48 @@ <h3>已配置的 Agent 助手</h3>
</div>
</section>

<section id="forum-assistant-config-section" class="config-section">
<h2>论坛巡航配置(VCPForumAssistant)</h2>
<p class="description">
配置哪些 Agent 参与 VCP 论坛巡航任务。每小时 cron 触发时,系统会从启用的 Agent 中随机选取一个唤醒。
<br>你可以为每个 Agent 设定允许巡航的时间窗口,实现差异化的活跃时段。
</p>
<div class="aa-config-container">
<div class="aa-global-settings">
<h3>全局设置</h3>
<div class="fa-switch-row" style="margin-bottom: 8px;">
<label class="switch-container">
<span>启用论坛巡航</span>
<input type="checkbox" id="fa-global-enabled">
<span class="switch-slider"></span>
</label>
<p class="aa-hint">关闭后所有 Agent 的巡航任务都不会触发。</p>
</div>
</div>

<div class="aa-agents-header">
<h3>巡航 Agent 列表</h3>
<div class="aa-agents-actions">
<div class="aa-existing-helper">
<label for="fa-existing-agent-select">从已注册 Agent 添加:</label>
<select id="fa-existing-agent-select">
<option value="">选择一个已注册 Agent...</option>
</select>
<button id="fa-add-from-existing-button" type="button">添加</button>
</div>
</div>
</div>

<div id="fa-agent-cards-container" class="aa-agent-cards-container">
</div>

<div class="aa-footer-actions">
<button id="fa-save-config-button" type="button">保存论坛巡航配置</button>
<span id="fa-status" class="status-message"></span>
</div>
</div>
</section>

<section id="tool-approval-manager-section" class="config-section">
<h2>插件调用审核管理</h2>
<p class="description">在此管理工具调用的审核机制。开启后,特定的工具调用将需要通过管理面板进行人工确认。</p>
Expand Down
248 changes: 248 additions & 0 deletions AdminPanel/js/forum-assistant-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import { apiFetch, showMessage } from './utils.js';

const API_BASE = '/admin_api';
let listenersAttached = false;

export async function initializeForumAssistantConfig() {
const section = document.getElementById('forum-assistant-config-section');
if (!section) return;

if (!listenersAttached) {
attachEventListeners();
listenersAttached = true;
}

await loadForumAssistantConfig();
}

function attachEventListeners() {
const saveBtn = document.getElementById('fa-save-config-button');
const addBtn = document.getElementById('fa-add-from-existing-button');

if (saveBtn && !saveBtn.dataset.listenerAttached) {
saveBtn.addEventListener('click', saveConfig);
saveBtn.dataset.listenerAttached = 'true';
}

if (addBtn && !addBtn.dataset.listenerAttached) {
addBtn.addEventListener('click', handleAddFromExisting);
addBtn.dataset.listenerAttached = 'true';
}
}

async function loadForumAssistantConfig() {
const statusSpan = document.getElementById('fa-status');
const container = document.getElementById('fa-agent-cards-container');
const globalSwitch = document.getElementById('fa-global-enabled');

if (statusSpan) {
statusSpan.textContent = '正在加载论坛巡航配置...';
statusSpan.className = 'status-message info';
}
if (container) container.innerHTML = '';

try {
const [config, available] = await Promise.all([
apiFetch(`${API_BASE}/forum-assistant/config`),
apiFetch(`${API_BASE}/forum-assistant/available-agents`, {}, false)
]);

if (globalSwitch) globalSwitch.checked = config.globalEnabled;

populateAgentSelect(available.agents || []);

if (Array.isArray(config.agents) && config.agents.length > 0) {
config.agents.forEach(a => addAgentCard(a));
} else if (container) {
container.innerHTML = '<p class="fa-placeholder">还没有配置论坛巡航 Agent,请从上方已注册 Agent 中选择添加。</p>';
}

if (statusSpan) {
statusSpan.textContent = '配置已加载。';
statusSpan.className = 'status-message success';
}
} catch (error) {
if (statusSpan) {
statusSpan.textContent = `加载失败:${error.message}`;
statusSpan.className = 'status-message error';
}
}
}

function populateAgentSelect(agents) {
const select = document.getElementById('fa-existing-agent-select');
if (!select) return;

select.innerHTML = '<option value="">选择一个已注册 Agent...</option>';

if (agents.length === 0) {
const opt = document.createElement('option');
opt.value = '';
opt.textContent = '(AgentAssistant 中尚无已注册 Agent)';
select.appendChild(opt);
select.disabled = true;
return;
}

agents.forEach(name => {
const opt = document.createElement('option');
opt.value = name;
opt.textContent = name;
select.appendChild(opt);
});
select.disabled = false;
}

function addAgentCard(agent) {
const container = document.getElementById('fa-agent-cards-container');
if (!container) return;

const placeholder = container.querySelector('.fa-placeholder');
if (placeholder) placeholder.remove();

const card = document.createElement('div');
card.className = 'aa-agent-card fa-agent-card';
card.dataset.baseName = agent.baseName || '';

// Header
const header = document.createElement('div');
header.className = 'aa-agent-card-header fa-card-header';

const nameSpan = document.createElement('span');
nameSpan.className = 'fa-agent-name';
nameSpan.textContent = agent.chineseName || '未命名';

const deleteBtn = document.createElement('button');
deleteBtn.type = 'button';
deleteBtn.className = 'aa-agent-delete-btn';
deleteBtn.textContent = '移除';
deleteBtn.addEventListener('click', () => {
if (confirm(`确定移除 "${agent.chineseName}" 的论坛巡航配置吗?`)) {
card.remove();
if (!container.children.length) {
container.innerHTML = '<p class="fa-placeholder">还没有配置论坛巡航 Agent,请从上方已注册 Agent 中选择添加。</p>';
}
}
});

header.appendChild(nameSpan);
header.appendChild(deleteBtn);

// Body
const body = document.createElement('div');
body.className = 'aa-agent-card-body';

// 开关
const enableRow = document.createElement('div');
enableRow.className = 'fa-switch-row';
const enableLabel = document.createElement('label');
enableLabel.className = 'switch-container';
const enableText = document.createElement('span');
enableText.textContent = '启用巡航';
const enableInput = document.createElement('input');
enableInput.type = 'checkbox';
enableInput.className = 'fa-agent-enabled';
enableInput.checked = agent.enabled;
const slider = document.createElement('span');
slider.className = 'switch-slider';
enableLabel.appendChild(enableText);
enableLabel.appendChild(enableInput);
enableLabel.appendChild(slider);
enableRow.appendChild(enableLabel);

// 巡航时间窗口
const hoursGroup = document.createElement('div');
hoursGroup.className = 'aa-field-group aa-field-group-full';
const hoursLabel = document.createElement('label');
hoursLabel.textContent = '巡航时段(24小时制,逗号分隔)';
const hoursInput = document.createElement('input');
hoursInput.type = 'text';
hoursInput.className = 'fa-agent-hours';
hoursInput.placeholder = '例如:9,12,18,21(留空 = 每小时都可能触发)';
hoursInput.value = agent.hours || '';
const hoursHint = document.createElement('p');
hoursHint.className = 'aa-hint';
hoursHint.textContent = '只在这些整点时刻有机会被选中巡航。留空则不限制,每次 cron 触发都参与随机选取。';
hoursGroup.appendChild(hoursLabel);
hoursGroup.appendChild(hoursInput);
hoursGroup.appendChild(hoursHint);

body.appendChild(enableRow);
body.appendChild(hoursGroup);

card.appendChild(header);
card.appendChild(body);
container.appendChild(card);
}

function handleAddFromExisting() {
const select = document.getElementById('fa-existing-agent-select');
const container = document.getElementById('fa-agent-cards-container');
if (!select || !container) return;

const name = select.value;
if (!name) {
showMessage('请先在下拉框中选择一个已注册 Agent。', 'info');
return;
}

const existing = Array.from(container.querySelectorAll('.fa-agent-name')).map(el => el.textContent);
if (existing.includes(name)) {
showMessage(`"${name}" 已在巡航列表中,无需重复添加。`, 'info');
return;
}

const base = name.toUpperCase().replace(/[^A-Z0-9_]/g, '') || 'AGENT_' + Date.now().toString(36).toUpperCase();
addAgentCard({ baseName: base, chineseName: name, enabled: true, hours: '' });
showMessage(`已添加 "${name}" 到论坛巡航列表。`, 'success');
}

async function saveConfig() {
const statusSpan = document.getElementById('fa-status');
const container = document.getElementById('fa-agent-cards-container');
const globalSwitch = document.getElementById('fa-global-enabled');

if (!container) return;

const globalEnabled = globalSwitch ? globalSwitch.checked : false;
const cards = Array.from(container.querySelectorAll('.fa-agent-card'));
const agents = [];

for (const card of cards) {
const nameEl = card.querySelector('.fa-agent-name');
const enabledInput = card.querySelector('.fa-agent-enabled');
const hoursInput = card.querySelector('.fa-agent-hours');

const chineseName = nameEl ? nameEl.textContent : '';
if (!chineseName) continue;

agents.push({
baseName: card.dataset.baseName || '',
chineseName,
enabled: enabledInput ? enabledInput.checked : false,
hours: hoursInput ? hoursInput.value.trim() : ''
});
}

if (statusSpan) {
statusSpan.textContent = '正在保存...';
statusSpan.className = 'status-message info';
}

try {
await apiFetch(`${API_BASE}/forum-assistant/config`, {
method: 'POST',
body: JSON.stringify({ globalEnabled, agents })
});
showMessage('论坛巡航配置已保存。', 'success');
if (statusSpan) {
statusSpan.textContent = '保存成功。';
statusSpan.className = 'status-message success';
}
} catch (error) {
if (statusSpan) {
statusSpan.textContent = `保存失败:${error.message}`;
statusSpan.className = 'status-message error';
}
}
}
4 changes: 4 additions & 0 deletions AdminPanel/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { initializeDreamManager } from './js/dream-manager.js';
import { initializeAgentScores } from './js/agent-scores.js';
import { initializePlaceholderViewer } from './js/placeholder-viewer.js';
import { initializeToolApprovalManager } from './js/tool-approval.js';
import { initializeForumAssistantConfig } from './js/forum-assistant-config.js';

document.addEventListener('DOMContentLoaded', async () => {
// 1. 通过后端验证登录状态(替代前端 Cookie 检查,解决 HttpOnly 无法读取问题)
Expand Down Expand Up @@ -129,6 +130,9 @@ document.addEventListener('DOMContentLoaded', async () => {
case 'agent-assistant-config-section':
initializeAgentAssistantConfig();
break;
case 'forum-assistant-config-section':
initializeForumAssistantConfig();
break;
case 'agent-scores-section':
initializeAgentScores();
break;
Expand Down
28 changes: 28 additions & 0 deletions AdminPanel/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -4207,3 +4207,31 @@ input:checked + .switch-slider:before {
border-color: var(--highlight-text);
outline: none;
}

/* ===== VCPForumAssistant 巡航配置 ===== */

.fa-agent-card .fa-card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}

.fa-agent-name {
font-size: 1.05em;
font-weight: 600;
color: var(--primary-text);
}

.fa-switch-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
}

.fa-placeholder {
color: var(--secondary-text);
padding: 24px 0;
text-align: center;
}
Loading
Loading