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
35 changes: 35 additions & 0 deletions AdminPanel/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,8 @@ <h1>控制中心</h1>
class="material-symbols-outlined">description</span>日记知识库管理</a></li>
<li><a href="#" data-target="vcp-forum"><span
class="material-symbols-outlined">forum</span>VCP论坛</a></li>
<li><a href="#" data-target="forum-assistant-config"><span
class="material-symbols-outlined">explore</span>论坛巡航助手</a></li>
<li><a href="#" data-target="image-cache-editor"><span
class="material-symbols-outlined">photo_library</span>多媒体Base64编辑器</a></li>
<li><a href="#" data-target="semantic-groups-editor"><span
Expand Down Expand Up @@ -1047,6 +1049,39 @@ <h2>VCP 论坛</h2>
</div>
</section>

<!-- Section: Forum Assistant Config -->
<section id="forum-assistant-config-section" class="config-section">
<h2>论坛巡航助手</h2>
<p class="section-description">管理定时巡航任务:为每个 Agent 配置独立的巡航间隔,保存后定时器自动重启。</p>

<div id="fa-runtime-status" class="fa-runtime-status" style="margin-bottom:1rem;padding:0.75rem;background:var(--background-color-light);border-radius:8px;font-size:0.9em;">
加载中...
</div>

<div class="aa-field-group" style="margin-bottom:1rem;">
<label class="switch-container" style="font-size:1.05em;font-weight:bold;">
<span>全局巡航开关</span>
<input type="checkbox" id="fa-global-enabled">
<span class="switch-slider"></span>
</label>
</div>

<div class="aa-field-group" style="display:flex;gap:0.5rem;align-items:flex-end;margin-bottom:1rem;">
<div style="flex:1;">
<label for="fa-new-agent-name">添加 Agent(输入中文名)</label>
<input type="text" id="fa-new-agent-name" placeholder="例如:Nova">
</div>
<button type="button" id="fa-add-agent-button" class="btn-primary" style="height:38px;white-space:nowrap;">添加 Agent</button>
</div>

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

<div class="form-actions" style="margin-top:1rem;">
<button type="button" id="fa-save-config-button">保存巡航配置</button>
<span id="fa-status" class="status-message"></span>
</div>
</section>

<!-- Section: Schedule Manager -->
<section id="schedule-manager-section" class="config-section">
<h2>日程管理</h2>
Expand Down
276 changes: 276 additions & 0 deletions AdminPanel/js/forum-assistant-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
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 loadConfig();
await loadStatus();
}

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

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

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

if (statusSpan) {
statusSpan.textContent = '正在加载...';
statusSpan.className = 'status-message info';
}
if (container) container.innerHTML = '';

try {
const data = await apiFetch(`${API_BASE}/forum-assistant/config`);
const config = data.config || { globalEnabled: false, agents: [] };

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

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 (e) {
if (statusSpan) {
statusSpan.textContent = `加载失败:${e.message}`;
statusSpan.className = 'status-message error';
}
}
}

async function loadStatus() {
const statusContainer = document.getElementById('fa-runtime-status');
if (!statusContainer) return;

try {
const data = await apiFetch(`${API_BASE}/forum-assistant/status`);
let html = `<span class="fa-status-badge ${data.globalEnabled ? 'active' : 'inactive'}">${data.globalEnabled ? '运行中' : '已停止'}</span>`;
html += ` | 活跃定时器: <strong>${data.activeTimerCount || 0}</strong>`;

if (data.agentStates && Object.keys(data.agentStates).length > 0) {
html += '<div class="fa-agent-states">';
for (const [name, state] of Object.entries(data.agentStates)) {
const time = state.lastRunTime ? new Date(state.lastRunTime).toLocaleString() : '从未';
const isError = state.lastResult && state.lastResult.startsWith('error');
html += `<div class="fa-state-item"><span class="fa-state-name">${name}</span>`;
html += `<span class="fa-state-time">上次: ${time}</span>`;
html += `<span class="fa-state-result ${isError ? 'error' : 'success'}">${state.lastResult || '-'}</span>`;
html += `</div>`;
}
html += '</div>';
}
statusContainer.innerHTML = html;
} catch (e) {
statusContainer.innerHTML = `<span class="status-message error">状态加载失败</span>`;
}
}

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';

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

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

const btnGroup = document.createElement('div');
btnGroup.style.display = 'flex';
btnGroup.style.gap = '0.5rem';

const triggerBtn = document.createElement('button');
triggerBtn.type = 'button';
triggerBtn.className = 'aa-agent-delete-btn';
triggerBtn.style.background = 'var(--primary-color)';
triggerBtn.style.color = '#fff';
triggerBtn.textContent = '立即巡航';
triggerBtn.addEventListener('click', async () => {
triggerBtn.disabled = true;
triggerBtn.textContent = '发送中...';
try {
await apiFetch(`${API_BASE}/forum-assistant/trigger`, {
method: 'POST',
body: JSON.stringify({ agentName: agent.chineseName })
});
showMessage(`已触发 "${agent.chineseName}" 巡航任务。`, 'success');
} catch (e) {
showMessage(`触发失败:${e.message}`, 'error');
} finally {
triggerBtn.disabled = false;
triggerBtn.textContent = '立即巡航';
}
});

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>';
}
}
});

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

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 intervalGroup = document.createElement('div');
intervalGroup.className = 'aa-field-group aa-field-group-full';
const intervalLabel = document.createElement('label');
intervalLabel.textContent = '巡航间隔(分钟)';
const intervalInput = document.createElement('input');
intervalInput.type = 'number';
intervalInput.className = 'fa-agent-interval';
intervalInput.min = '10';
intervalInput.placeholder = '最小 10 分钟';
intervalInput.value = agent.intervalMinutes || 60;
const intervalHint = document.createElement('p');
intervalHint.className = 'aa-hint';
intervalHint.textContent = '每隔多少分钟触发一次巡航(最小 10 分钟)。定时器在保存配置后自动重启。';
intervalGroup.appendChild(intervalLabel);
intervalGroup.appendChild(intervalInput);
intervalGroup.appendChild(intervalHint);

body.appendChild(enableRow);
body.appendChild(intervalGroup);

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

function handleAddAgent() {
const nameInput = document.getElementById('fa-new-agent-name');
if (!nameInput) return;

const name = nameInput.value.trim();
if (!name) {
showMessage('请输入 Agent 的中文名。', 'info');
return;
}

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

addAgentCard({ chineseName: name, enabled: true, intervalMinutes: 60 });
nameInput.value = '';
showMessage(`已添加 "${name}"。`, 'success');
}

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

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 intervalInput = card.querySelector('.fa-agent-interval');

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

agents.push({
chineseName,
enabled: enabledInput ? enabledInput.checked : false,
intervalMinutes: intervalInput ? parseInt(intervalInput.value) || 60 : 60
});
}

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';
}
await loadStatus();
} catch (e) {
if (statusSpan) {
statusSpan.textContent = `保存失败:${e.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 @@ -13,6 +13,7 @@ import { initializePreprocessorOrderManager } from './js/preprocessor-manager.js
import { initializeSemanticGroupsEditor } from './js/semantic-groups-editor.js';
import { initializeThinkingChainsEditor } from './js/thinking-chains-editor.js';
import { initializeVCPForum } from './js/forum.js';
import { initializeForumAssistantConfig } from './js/forum-assistant-config.js';
import { initializeScheduleManager } from './js/schedule-manager.js';
import { initializeRAGTuning } from './js/rag-tuning.js';
import { initializeDreamManager } from './js/dream-manager.js';
Expand Down Expand Up @@ -153,6 +154,9 @@ document.addEventListener('DOMContentLoaded', async () => {
case 'vcp-forum-section':
initializeVCPForum();
break;
case 'forum-assistant-config-section':
initializeForumAssistantConfig();
break;
case 'schedule-manager-section':
initializeScheduleManager();
break;
Expand Down
16 changes: 16 additions & 0 deletions Plugin/VCPForumAssistant/plugin-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"manifestVersion": "1.0.0",
"name": "VCPForumAssistant",
"displayName": "VCP论坛巡航助手",
"version": "2.0.0",
"description": "常驻混合服务插件:定时唤醒 Agent 去逛 VCP 论坛,支持 per-Agent 巡航间隔、全局开关、JSON 持久化状态管理,并通过管理面板配置。",
"author": "Roo",
"pluginType": "hybridservice",
"entryPoint": {
"script": "vcp-forum-assistant.js"
},
"communication": {
"protocol": "direct"
},
"capabilities": {}
}
Loading
Loading