${escapeHtml(group.title)}
@@ -5370,6 +6258,7 @@
手动安装依赖
if (configSummary) {
configSummary.innerHTML = summaryItems.join('');
}
+ renderSettingsNav(groups);
if (state.data) {
renderHomeModules(state.data);
}
@@ -5415,6 +6304,7 @@
手动安装依赖
let nextValue = null;
if (field.type === 'bool') {
nextValue = Boolean(target.checked);
+ syncConfigSwitchState(target);
} else if (field.type === 'int' || field.type === 'float') {
nextValue = normalizeFieldValue(field, target.value);
if (nextValue === null && !field.nullable) {
@@ -5444,22 +6334,36 @@
手动安装依赖
function applyConfigSearch() {
const query = String(state.config.search || '').trim().toLowerCase();
+ const activeGroup = ensureValidConfigGroup();
const groups = document.querySelectorAll('.config-group');
+ const dependencyPanel = $('dependencyInstallPanel');
+ const dependencyStatus = $('dependencyStatus');
+ const dependencyVisible = Boolean(query) || activeGroup === 'all' || activeGroup === 'Dependency_Install_Guide';
+ const dependencyMatches = !query || 'Dependency_Install_Guide 依赖安装 手动安装依赖 dependencies'.toLowerCase().includes(query);
+
+ if (dependencyPanel) {
+ dependencyPanel.hidden = !(dependencyVisible && dependencyMatches);
+ }
+ if (dependencyStatus) {
+ dependencyStatus.hidden = !(dependencyVisible && dependencyMatches);
+ }
groups.forEach((groupEl) => {
+ const groupKey = groupEl.dataset.groupKey || '';
+ const activeMatch = Boolean(query) || activeGroup === 'all' || groupKey === activeGroup;
const groupMatch = !query || (groupEl.dataset.search || '').includes(query);
const fields = groupEl.querySelectorAll('.config-field');
let fieldMatch = false;
fields.forEach((fieldEl) => {
- const match = !query || groupMatch || (fieldEl.dataset.search || '').includes(query);
+ const match = activeMatch && (!query || groupMatch || (fieldEl.dataset.search || '').includes(query));
fieldEl.hidden = !match;
if (match) {
fieldMatch = true;
}
});
- const visible = !query || groupMatch || fieldMatch;
+ const visible = activeMatch && (!query || groupMatch || fieldMatch);
groupEl.hidden = !visible;
if (visible) {
groupEl.open = true;
@@ -5800,10 +6704,10 @@
${escapeHtml(item.title)}
navigateToPage('settings');
const searchInput = $('configSearch');
if (searchInput) {
- state.config.search = 'Integration_Settings';
+ state.config.search = '';
searchInput.value = state.config.search;
- applyConfigSearch();
}
+ setConfigGroup('Integration_Settings');
const groupEl = document.querySelector('[data-group-key="Integration_Settings"]');
if (groupEl) {
groupEl.open = true;
@@ -6719,7 +7623,7 @@
${escapeHtml(item.title)}
document.documentElement.setAttribute('data-theme', state.theme);
document.documentElement.style.colorScheme = state.theme;
document.body.setAttribute('data-theme', state.theme);
- localStorage.setItem('sl-dashboard-theme', state.theme);
+ safeLocalStorageSet('sl-dashboard-theme', state.theme);
updateThemeIcon();
if (state.data) {
renderAll(state.data);
@@ -6760,7 +7664,10 @@
${escapeHtml(item.title)}
}
async function loadDashboard() {
- $('summaryText').innerHTML = '
正在刷新监控数据。';
+ const summaryText = $('summaryText');
+ if (summaryText) {
+ summaryText.innerHTML = '
正在刷新监控数据。';
+ }
const [
metrics,
@@ -6856,8 +7763,6 @@
${escapeHtml(item.title)}
items.reverse();
} else if (state.jargon.sort === 'name') {
items.sort((a, b) => (a.term || a.word || '').localeCompare(b.term || b.word || ''));
- } else if (state.jargon.sort === 'occurrences') {
- items.sort((a, b) => (b.occurrences || 0) - (a.occurrences || 0));
}
state.jargon.items = items;
@@ -6910,7 +7815,7 @@
${escapeHtml(item.title)}
['候选', jargonStats.total_candidates],
['确认', jargonStats.confirmed_jargon],
['完成', jargonStats.completed_inference],
- ['出现', jargonStats.total_occurrences],
+ ['样本', jargonStats.total_occurrences],
].map(([label, value]) => `
${label}
@@ -6928,7 +7833,7 @@
${escapeHtml(item.title)}
const meta = [
item.group_id ? `群组 ${item.group_id}` : null,
item.is_confirmed !== undefined ? (item.is_confirmed ? '已确认' : '待确认') : null,
- item.occurrences !== undefined ? `出现 ${formatCount(item.occurrences)}` : null,
+ item.occurrences !== undefined ? `样本 ${formatCount(item.occurrences)}` : null,
item.created_at ? `创建 ${formatTime(item.created_at)}` : null,
item.updated_at ? `更新 ${formatTime(item.updated_at)}` : null,
].filter(Boolean).join(' · ');
@@ -6986,6 +7891,13 @@
${escapeHtml(item.title)}
$('jargonNextBtn').disabled = page >= totalPages;
$('jargonHint').textContent = `候选 ${formatCount((jargonStats.total_candidates ?? jargonStats.totalCandidates) || 0)} · 第 ${page}/${totalPages} 页`;
+ updateReviewNavCounts({
+ pendingJargon: Math.max(
+ 0,
+ safeNumber(jargonStats.total_candidates ?? jargonStats.totalCandidates)
+ - safeNumber(jargonStats.confirmed_jargon ?? jargonStats.confirmedJargon),
+ ),
+ });
}
function renderBatchPanel() {
@@ -7023,6 +7935,7 @@
${escapeHtml(item.title)}
$('batchPrevBtn').disabled = page <= 1;
$('batchNextBtn').disabled = page >= totalPages;
$('batchHint').textContent = `共 ${formatCount(total)} 个批次`;
+ updateReviewNavCounts({ batchTotal: total });
}
function bindEvents() {
@@ -7054,6 +7967,12 @@
${escapeHtml(item.title)}
state.config.search = event.target.value || '';
applyConfigSearch();
});
+ $('settingsNav').addEventListener('click', (event) => {
+ const target = event.target.closest('[data-config-group]');
+ if (target) {
+ setConfigGroup(target.dataset.configGroup || 'all', { scroll: true });
+ }
+ });
$('configGroups').addEventListener('input', (event) => {
const target = event.target;
if (target && target.dataset && target.dataset.configKey) {
@@ -7097,7 +8016,7 @@
${escapeHtml(item.title)}
pipMirrorSelect.value = state.dependencyInstall.mirror;
pipMirrorSelect.addEventListener('change', (event) => {
state.dependencyInstall.mirror = event.target.value || 'default';
- localStorage.setItem('sl-pip-mirror', state.dependencyInstall.mirror);
+ safeLocalStorageSet('sl-pip-mirror', state.dependencyInstall.mirror);
});
}
@@ -7165,6 +8084,12 @@
${escapeHtml(item.title)}
return;
}
+ const reviewTab = event.target.closest('[data-review-tab]');
+ if (reviewTab) {
+ setReviewTab(reviewTab.dataset.reviewTab);
+ return;
+ }
+
const graphTab = event.target.closest('[data-graph-type]');
if (graphTab) {
state.graph.type = graphTab.dataset.graphType === 'knowledge' ? 'knowledge' : 'memory';
diff --git a/webui/manager.py b/webui/manager.py
index c113d335..0eb527c3 100644
--- a/webui/manager.py
+++ b/webui/manager.py
@@ -50,9 +50,63 @@ def __init__(
self._database_degraded = False
self._database_start_error: Optional[str] = None
self._database_start_attempted = False
+ self._register_plugin_pages_api()
# 创建
+ def _public_webui_base_url(self) -> str:
+ host = str(getattr(self._config, "web_interface_host", "127.0.0.1") or "127.0.0.1")
+ port = int(getattr(self._config, "web_interface_port", 7833) or 7833)
+ if host in {"0.0.0.0", "::", "[::]"}:
+ try:
+ from quart import request
+
+ host_header = request.host.split(":", 1)[0]
+ except (ImportError, RuntimeError):
+ host_header = ""
+ host = host_header or "127.0.0.1"
+ if ":" in host and not host.startswith("["):
+ host = f"[{host}]"
+ return f"http://{host}:{port}"
+
+ def _register_plugin_pages_api(self) -> None:
+ register_web_api = getattr(self._context, "register_web_api", None)
+ if not callable(register_web_api):
+ logger.debug("[WebUI] AstrBot Plugin Pages API is not available")
+ return
+
+ plugin_name = "astrbot_plugin_self_learning"
+ plugin_instance_name = getattr(self._plugin_instance, "name", None)
+ if isinstance(plugin_instance_name, str) and plugin_instance_name.strip():
+ plugin_name = plugin_instance_name.strip()
+
+ async def dashboard_url():
+ try:
+ from quart import jsonify
+ except ImportError:
+ def jsonify(payload):
+ return payload
+
+ base_url = self._public_webui_base_url()
+ return jsonify({
+ "url": f"{base_url}/static/html/dashboard.html",
+ "base_url": base_url,
+ })
+
+ for route in {
+ f"{plugin_name}/dashboard_url",
+ "astrbot_plugin_self_learning/dashboard_url",
+ }:
+ try:
+ register_web_api(
+ route,
+ dashboard_url,
+ ["GET"],
+ "Self Learning dashboard URL for AstrBot Plugin Pages",
+ )
+ except Exception as exc:
+ logger.debug(f"[WebUI] register Plugin Pages API failed for {route}: {exc}")
+
def create_server(self) -> bool:
"""创建 Server 实例(不启动)。返回 True 表示需要立即启动。"""
global _server_instance