diff --git a/CHANGELOG.md b/CHANGELOG.md
index d8a85d94..fc875ba7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,24 @@
所有重要更改都将记录在此文件中。
+## [3.2.7] - 2026-06-10
+
+### MaiBot 迁移
+
+- 修复 MaiBot 学习数据导入结果展示混淆的问题,表达方式、黑话和 A_memorix 记忆现在明确进入各自的审查/学习目标。
+- MaiBot 导入预览和导入结果增加分类去向与待审拆分统计,便于确认表达不会被误认为人格学习。
+- 修复 Web 表单传入字符串布尔值时 `"false"` 被当作开启的问题,避免导入开关失效。
+
+### WebUI 审查
+
+- 为独立 WebUI 与 AstrBot 内嵌 Dashboard 增加人格更新、表达审查和黑话候选的批量通过/拒绝入口。
+- 新增风格学习审查与黑话候选的批量审查 API,并复用现有单条审查逻辑以保持应用副作用一致。
+- 审查队列中的人格列表不再混入风格学习记录,避免“记忆/主题/表达都跑到人格那边”的观感。
+
+### 版本
+
+- 将插件发布版本号提升至 `3.2.7`。
+
## [3.2.6] - 2026-06-10
### 人格学习
diff --git a/README.md b/README.md
index 630615c0..738429be 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
让 AstrBot 在群聊中持续采集、学习、审查并注入上下文,使 Bot 逐步具备表达风格、群组黑话、社交关系、长期记忆和人格演化能力。
-[](https://github.com/NickCharlie/astrbot_plugin_self_learning)
+[](https://github.com/NickCharlie/astrbot_plugin_self_learning)
[](LICENSE)
[](https://github.com/Soulter/AstrBot)
[](https://www.python.org/)
diff --git a/README_EN.md b/README_EN.md
index 6cd94711..c6f2b9c7 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -14,7 +14,7 @@
-[](https://github.com/NickCharlie/astrbot_plugin_self_learning) [](LICENSE) [](https://github.com/Soulter/AstrBot) [](https://www.python.org/)
+[](https://github.com/NickCharlie/astrbot_plugin_self_learning) [](LICENSE) [](https://github.com/Soulter/AstrBot) [](https://www.python.org/)
[Features](#what-we-can-do) · [Quick Start](#quick-start) · [Web UI](#visual-management-interface) · [Community](#community) · [Contributing](CONTRIBUTING.md)
diff --git a/__init__.py b/__init__.py
index c42a4289..a1d89a04 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,5 +1,5 @@
# AstrBot 自学习插件
-__version__ = "3.2.6"
+__version__ = "3.2.7"
# Ensure parent namespace packages ("data", "data.plugins") are
# durably registered in sys.modules. AstrBot loads plugins via
diff --git a/core/page_api.py b/core/page_api.py
index 486558ea..48754b6f 100644
--- a/core/page_api.py
+++ b/core/page_api.py
@@ -279,6 +279,40 @@ async def post_reviews_action(self) -> dict[str, Any]:
result.get("message") or result.get("error") or "批量删除完成",
result=result,
)
+ if action == "batch_review_style":
+ learning_service = imports.LearningService(container)
+ review_ids = [
+ self._as_int(item, 0)
+ for item in self._body_list(body, "ids", fallback_key="review_ids")
+ ]
+ review_ids = [review_id for review_id in review_ids if review_id]
+ result = await learning_service.batch_review_style_learning_reviews(
+ review_ids,
+ str(body.get("decision") or body.get("review_action") or "approve"),
+ str(body.get("comment") or ""),
+ )
+ return self._operation(
+ bool(result.get("success")),
+ result.get("message") or result.get("error") or "批量表达审查完成",
+ result=result,
+ )
+ if action == "batch_review_jargon":
+ jargon_service = imports.JargonService(container)
+ jargon_ids = [
+ self._as_int(item, 0)
+ for item in self._body_list(body, "ids", fallback_key="jargon_ids")
+ ]
+ jargon_ids = [jargon_id for jargon_id in jargon_ids if jargon_id]
+ result = await jargon_service.batch_review_jargon(
+ jargon_ids,
+ str(body.get("decision") or body.get("review_action") or "approve"),
+ meaning=body.get("meaning"),
+ )
+ return self._operation(
+ bool(result.get("success")),
+ result.get("message") or result.get("error") or "批量黑话审查完成",
+ result=result,
+ )
if action.startswith("style_"):
learning_service = imports.LearningService(container)
review_id = self._body_int(body, "id")
diff --git a/docs/README.md b/docs/README.md
index 4c3a513b..88517c1f 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -6,7 +6,7 @@ AstrBot 自主学习插件的实现文档和使用文档。
- 插件名: `astrbot_plugin_self_learning`
- 展示名: `self-learning`
-- 当前元数据版本: `3.2.6`
+- 当前元数据版本: `3.2.7`
- 最低 AstrBot 版本: `4.11.4`
- 主要入口: `main.py`
- 配置入口: `_conf_schema.json`, `config.py`
diff --git a/metadata.yaml b/metadata.yaml
index ed728992..c65117c7 100644
--- a/metadata.yaml
+++ b/metadata.yaml
@@ -2,7 +2,7 @@ name: "astrbot_plugin_self_learning"
author: "NickMo, EterUltimate"
display_name: "self-learning"
description: "SELF LEARNING 自主学习插件 — 让 AI 聊天机器人自主学习对话风格、理解群组黑话、管理社交关系与好感度、自适应人格演化,像真人一样自然对话。(使用前必须手动备份人格数据)"
-version: "3.2.6"
+version: "3.2.7"
repo: "https://github.com/NickCharlie/astrbot_plugin_self_learning"
tags:
- "自学习"
diff --git a/pages/dashboard/app.js b/pages/dashboard/app.js
index a64f83c3..67ea9ac7 100644
--- a/pages/dashboard/app.js
+++ b/pages/dashboard/app.js
@@ -561,7 +561,8 @@
}
function renderReviews(data) {
- const personaPending = ((data.persona_pending || {}).updates || []);
+ const personaPending = ((data.persona_pending || {}).updates || [])
+ .filter((item) => item && item.review_source !== "style_learning");
const personaReviewed = ((data.persona_reviewed || {}).updates || []);
const styleReviews = ((data.style_reviews || {}).reviews || []);
const pendingJargon = (((data.jargon_pending || {}).jargon_list) || []);
@@ -880,7 +881,62 @@
function renderMaiBotImportPreview(summary) {
const output = $("maibot-import-output");
if (!output || !summary) return;
- output.textContent = JSON.stringify(summary, null, 2);
+ const counts = summary.counts || {};
+ const breakdown = summary.review_breakdown || {};
+ const destinations = summary.destinations || {};
+ const lines = [];
+ if (Object.keys(counts).length) {
+ lines.push(`预览: 表达 ${fmt(counts.expressions, 0)} · 黑话 ${fmt(counts.jargons, 0)} · 记忆 ${fmt(counts.memories, 0)}`);
+ }
+ if (Object.keys(breakdown).length) {
+ lines.push(`导入: 表达审查 ${fmt(breakdown.style_learning_reviews, 0)} · 黑话候选 ${fmt(breakdown.jargon_candidates, 0)} · 记忆审查 ${fmt(breakdown.persona_memory_reviews, 0)}`);
+ }
+ if (Object.keys(destinations).length) {
+ lines.push(`分类去向: 表达 -> ${destinations.expressions}; 黑话 -> ${destinations.jargons}; 记忆 -> ${destinations.memories}`);
+ }
+ output.textContent = `${lines.join("\n")}${lines.length ? "\n\n" : ""}${JSON.stringify(summary, null, 2)}`;
+ }
+
+ function currentBatchReviewIds(kind) {
+ const reviews = state.pageData.reviews || {};
+ if (kind === "persona") {
+ return ((reviews.persona_pending || {}).updates || [])
+ .filter((item) => item && item.review_source !== "style_learning")
+ .map((item) => item.id)
+ .filter((id) => id !== undefined && id !== null && String(id) !== "");
+ }
+ if (kind === "style") {
+ return ((reviews.style_reviews || {}).reviews || [])
+ .map((item) => item.id)
+ .filter((id) => id !== undefined && id !== null && String(id) !== "");
+ }
+ if (kind === "jargon") {
+ return (((reviews.jargon_pending || {}).jargon_list) || [])
+ .map((item) => item.id)
+ .filter((id) => id !== undefined && id !== null && String(id) !== "");
+ }
+ return [];
+ }
+
+ async function handleBatchReviewAction(kind, action) {
+ const ids = currentBatchReviewIds(kind);
+ if (!ids.length) {
+ showToast("当前页没有可批量处理的审查项", "error");
+ return;
+ }
+ const typeText = { persona: "人格更新", style: "表达审查", jargon: "黑话候选" }[kind] || "审查项";
+ const actionText = action === "approve" ? "通过" : "拒绝";
+ if (!window.confirm(`确定批量${actionText}当前页 ${ids.length} 条${typeText}?`)) return;
+
+ const payload = {
+ action: kind === "persona" ? "batch_review" : kind === "style" ? "batch_review_style" : "batch_review_jargon",
+ ids,
+ decision: action,
+ };
+ const result = await apiPost("reviews/action", payload);
+ showToast(result.message || "批量审查完成", result.success ? "ok" : "error");
+ state.pageData.reviews = null;
+ await loadPageData(state.page, { force: true });
}
async function runMaiBotImportAction(action) {
@@ -1199,11 +1255,12 @@
$("maibot-import-button")?.addEventListener("click", () => runMaiBotImportAction("maibot_import"));
document.addEventListener("click", async (event) => {
- const target = event.target.closest("[data-route-card],[data-refresh-page],[data-review-action],[data-jargon-action],[data-style-action],[data-persona-action],[data-content-action],[data-settings-group]");
+ const target = event.target.closest("[data-route-card],[data-refresh-page],[data-review-action],[data-batch-review-kind],[data-jargon-action],[data-style-action],[data-persona-action],[data-content-action],[data-settings-group]");
if (!target) return;
if (target.dataset.routeCard) navigateToPage(target.dataset.routeCard);
if (target.dataset.refreshPage) loadPageData(target.dataset.refreshPage, { force: true });
if (target.dataset.reviewAction) await handleReviewAction(target.dataset.kind, target.dataset.id, target.dataset.reviewAction);
+ if (target.dataset.batchReviewKind) await handleBatchReviewAction(target.dataset.batchReviewKind, target.dataset.batchReviewAction || "approve");
if (target.dataset.jargonAction) await handleJargonAction(target.dataset.jargonAction, target.dataset.id);
if (target.dataset.styleAction) await handleStyleAction(target.dataset.styleAction, target.dataset.id);
if (target.dataset.personaAction) await handlePersonaAction(target);
diff --git a/pages/dashboard/index.html b/pages/dashboard/index.html
index d0302b8f..3152e2d2 100644
--- a/pages/dashboard/index.html
+++ b/pages/dashboard/index.html
@@ -154,21 +154,33 @@