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
53 changes: 53 additions & 0 deletions core/page_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def register_routes(self) -> None:
("metrics", self.get_metrics, ["GET"], "Self Learning embedded metrics module"),
("monitoring", self.get_monitoring, ["GET"], "Self Learning embedded monitoring module"),
("integrations", self.get_integrations, ["GET"], "Self Learning embedded integrations module"),
("integrations/action", self.post_integrations_action, ["POST"], "Self Learning embedded integrations actions"),
("settings", self.get_settings, ["GET"], "Self Learning embedded settings module"),
("settings/action", self.post_settings_action, ["POST"], "Self Learning embedded settings actions"),
]
Expand Down Expand Up @@ -426,6 +427,46 @@ async def get_monitoring(self) -> dict[str, Any]:
async def get_integrations(self) -> dict[str, Any]:
return self._ok(await self._load_integrations())

async def post_integrations_action(self) -> dict[str, Any]:
body = await self._body()
action = str(body.get("action", "")).strip()
try:
imports = self._imports()
database_manager = getattr(self._container(), "database_manager", None)
importer = imports.MaiBotLearningImporter(database_manager)
source_args = {
"maibot_root": body.get("maibot_root") or None,
"db_path": body.get("db_path") or body.get("maibot_db_path") or None,
"memorix_db_path": body.get("memorix_db_path") or None,
"payload": body.get("payload") if isinstance(body.get("payload"), dict) else None,
}
if action == "maibot_preview":
preview = importer.preview(**source_args)
return self._operation(True, "MaiBot 学习数据预览完成", preview=preview)
if action == "maibot_export":
payload = importer.export_json(**source_args)
return self._operation(True, "MaiBot 学习数据已导出为标准包", payload=payload)
if action == "maibot_import":
result = await importer.import_from_source(
**source_args,
default_group_id=str(body.get("default_group_id") or "global"),
import_expressions=self._body_bool(body, "import_expressions", True),
import_jargons=self._body_bool(body, "import_jargons", True),
import_memories=self._body_bool(body, "import_memories", True),
approve_checked_expressions=self._body_bool(
body, "approve_checked_expressions", True
),
)
return self._operation(
bool(result.get("success")),
"MaiBot 学习数据导入完成" if result.get("success") else "MaiBot 学习数据导入存在错误",
result=result,
)
return self._operation(False, f"未知融合操作: {action or '(empty)'}")
except Exception as exc:
logger.error(f"[PluginPageAPI] integrations action failed: {exc}", exc_info=True)
return self._operation(False, str(exc))

async def get_settings(self) -> dict[str, Any]:
args = self._query()
include_schema = self._query_bool(args, "schema", True)
Expand Down Expand Up @@ -1479,6 +1520,7 @@ class Imports:
from ..webui.services.persona_backup_service import PersonaBackupService
from ..webui.services.persona_review_service import PersonaReviewService
from ..webui.services.persona_service import PersonaService
from ..services.integration.maibot_learning_importer import MaiBotLearningImporter
except ImportError:
from webui.blueprints.config import (
DEPENDENCY_TIERS,
Expand All @@ -1495,6 +1537,7 @@ class Imports:
from webui.services.persona_backup_service import PersonaBackupService
from webui.services.persona_review_service import PersonaReviewService
from webui.services.persona_service import PersonaService
from services.integration.maibot_learning_importer import MaiBotLearningImporter

imports.get_container = get_container
imports.ConfigService = ConfigService
Expand All @@ -1506,6 +1549,7 @@ class Imports:
imports.PersonaBackupService = PersonaBackupService
imports.PersonaReviewService = PersonaReviewService
imports.PersonaService = PersonaService
imports.MaiBotLearningImporter = MaiBotLearningImporter
imports.DEPENDENCY_TIERS = DEPENDENCY_TIERS
imports.MANUAL_DEPENDENCY_INSTALL_SOURCE = MANUAL_DEPENDENCY_INSTALL_SOURCE
imports.PIP_MIRROR_SOURCES = PIP_MIRROR_SOURCES
Expand Down Expand Up @@ -1572,6 +1616,15 @@ def _query_optional_bool(cls, args: Any, key: str) -> Optional[bool]:
def _body_int(cls, body: Mapping[str, Any], key: str, default: int = 0) -> int:
return cls._as_int(body.get(key), default)

@staticmethod
def _body_bool(body: Mapping[str, Any], key: str, default: bool) -> bool:
value = body.get(key, default)
if isinstance(value, bool):
return value
if value is None:
return default
return str(value).strip().lower() in {"1", "true", "yes", "on"}

@staticmethod
def _body_list(
body: Mapping[str, Any],
Expand Down
58 changes: 58 additions & 0 deletions pages/dashboard/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@
<strong>${escapeHtml(value === true ? "开启" : value === false ? "关闭" : value ?? "未设置")}</strong>
</div>
`).join("") || empty("暂无融合设置"));
renderMaiBotImportPreview(data.maibot_learning || null);
}

function integrationCardHtml(item) {
Expand All @@ -859,6 +860,61 @@
</article>`;
}

function collectMaiBotPayload() {
const payload = {
maibot_root: $("maibot-root-input")?.value?.trim() || "",
db_path: $("maibot-db-input")?.value?.trim() || "",
memorix_db_path: $("maibot-memorix-input")?.value?.trim() || "",
default_group_id: $("maibot-default-group-input")?.value?.trim() || "global",
import_expressions: Boolean($("maibot-import-expressions")?.checked),
import_jargons: Boolean($("maibot-import-jargons")?.checked),
import_memories: Boolean($("maibot-import-memories")?.checked),
approve_checked_expressions: Boolean($("maibot-approve-checked")?.checked),
};
if (!payload.maibot_root && !payload.db_path) {
throw new Error("请填写 MaiBot 项目目录或主数据库路径");
}
return payload;
}

function renderMaiBotImportPreview(summary) {
const output = $("maibot-import-output");
if (!output || !summary) return;
output.textContent = JSON.stringify(summary, null, 2);
}

async function runMaiBotImportAction(action) {
const buttonEl = action === "maibot_import" ? $("maibot-import-button") : $("maibot-preview-button");
const originalLabel = buttonEl?.textContent || "";
try {
const payload = collectMaiBotPayload();
if (buttonEl) {
buttonEl.disabled = true;
buttonEl.classList.add("is-busy");
buttonEl.textContent = action === "maibot_import" ? "导入中" : "预览中";
}
setText("maibot-import-output", "正在读取 MaiBot 学习数据...");
const result = await apiPost("integrations/action", { action, ...payload });
const detail = result.preview || result.result || result.payload || result;
renderMaiBotImportPreview(detail);
showToast(result.message || "MaiBot 学习数据操作完成", result.success !== false ? "ok" : "error");
if (action === "maibot_import") {
state.pageData = {};
await loadDashboard(true);
}
} catch (error) {
const message = error.message || String(error);
setText("maibot-import-output", message);
showToast(message, "error");
} finally {
if (buttonEl) {
buttonEl.disabled = false;
buttonEl.classList.remove("is-busy");
buttonEl.textContent = originalLabel;
}
}
}

function renderSettings(data) {
const schema = data.schema || {};
const groups = schema.groups || [];
Expand Down Expand Up @@ -1139,6 +1195,8 @@
}
}
});
$("maibot-preview-button")?.addEventListener("click", () => runMaiBotImportAction("maibot_preview"));
$("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]");
Expand Down
49 changes: 49 additions & 0 deletions pages/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,55 @@ <h3>功能融合</h3>
<button class="ghost-button" type="button" data-refresh-page="integrations">刷新</button>
</div>
<div class="integration-cards" id="integration-cards"></div>
<div class="panel maibot-import-panel">
<div class="panel-heading">
<div>
<h3>MaiBot 学习数据迁移</h3>
<p>读取 MaiBot 的表达方式、黑话与 A_memorix 记忆段落,导入到当前插件的独立学习模块。</p>
</div>
<div class="inline-actions">
<button class="ghost-button" type="button" id="maibot-preview-button">预览</button>
<button class="solid-button" type="button" id="maibot-import-button">导入</button>
</div>
</div>
<div class="maibot-import-grid">
<label class="config-field">
<span>
<strong>MaiBot 项目目录</strong>
<small>例如 C:\Users\zacza\Desktop\x\MaiBot;也可以只填写数据库路径。</small>
</span>
<input id="maibot-root-input" type="text" placeholder="C:\Users\zacza\Desktop\x\MaiBot">
</label>
<label class="config-field">
<span>
<strong>MaiBot 主数据库</strong>
<small>可选;填写后优先读取该 SQLite 文件。</small>
</span>
<input id="maibot-db-input" type="text" placeholder="...\data\maibot.db">
</label>
<label class="config-field">
<span>
<strong>A_memorix metadata.db</strong>
<small>可选;用于导入记忆段落。</small>
</span>
<input id="maibot-memorix-input" type="text" placeholder="...\metadata.db">
</label>
<label class="config-field">
<span>
<strong>默认群组</strong>
<small>无法映射 MaiBot session 时使用。</small>
</span>
<input id="maibot-default-group-input" type="text" value="global">
</label>
</div>
<div class="toggle-row">
<label><input id="maibot-import-expressions" type="checkbox" checked> 表达方式</label>
<label><input id="maibot-import-jargons" type="checkbox" checked> 黑话</label>
<label><input id="maibot-import-memories" type="checkbox" checked> 记忆段落</label>
<label><input id="maibot-approve-checked" type="checkbox" checked> MaiBot 已确认表达自动批准</label>
</div>
<pre class="code-preview" id="maibot-import-output">等待预览。</pre>
</div>
<div class="panel">
<h3>融合设置</h3>
<div class="compact-table" id="integration-settings"></div>
Expand Down
41 changes: 41 additions & 0 deletions pages/dashboard/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,46 @@ textarea {
min-height: 80px;
}

.maibot-import-panel {
margin-bottom: 14px;
}

.maibot-import-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin-top: 12px;
}

.maibot-import-panel .config-field {
align-items: center;
}

.toggle-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 12px 0;
}

.toggle-row label {
min-height: 34px;
display: inline-flex;
align-items: center;
gap: 7px;
padding: 0 10px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--surface-strong);
color: var(--muted);
font-size: 13px;
font-weight: 700;
}

.maibot-import-panel .code-preview {
min-height: 136px;
}

.empty-state {
min-height: 54px;
display: grid;
Expand Down Expand Up @@ -1092,6 +1132,7 @@ textarea {
.review-layout,
.integration-cards,
.settings-grid,
.maibot-import-grid,
.graph-grid {
grid-template-columns: 1fr;
}
Expand Down
61 changes: 38 additions & 23 deletions services/integration/__init__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
"""External integrations -- MaiBot, knowledge graphs, memory engines."""

from .maibot_integration_factory import MaiBotIntegrationFactory
from .maibot_adapters import MaiBotStyleAnalyzer, MaiBotLearningStrategy, MaiBotQualityMonitor
from .maibot_enhanced_learning_manager import MaiBotEnhancedLearningManager
from .exemplar_library import ExemplarLibrary
from .knowledge_graph_manager import KnowledgeGraphManager
from .lightrag_knowledge_manager import LightRAGKnowledgeManager
from .mem0_memory_manager import Mem0MemoryManager
from .training_data_exporter import TrainingDataExporter

__all__ = [
"MaiBotIntegrationFactory",
"MaiBotStyleAnalyzer",
"MaiBotLearningStrategy",
"MaiBotQualityMonitor",
"MaiBotEnhancedLearningManager",
"ExemplarLibrary",
"KnowledgeGraphManager",
"LightRAGKnowledgeManager",
"Mem0MemoryManager",
"TrainingDataExporter",
]
"""External integrations -- MaiBot, knowledge graphs, memory engines.

The integration package contains optional-heavy adapters. Keep package import
lightweight and load concrete classes on demand so tests and WebUI startup can
import a single integration module without initializing every companion engine.
"""

from __future__ import annotations

from importlib import import_module
from typing import Any


_EXPORTS = {
"MaiBotIntegrationFactory": ".maibot_integration_factory",
"MaiBotStyleAnalyzer": ".maibot_adapters",
"MaiBotLearningStrategy": ".maibot_adapters",
"MaiBotQualityMonitor": ".maibot_adapters",
"MaiBotEnhancedLearningManager": ".maibot_enhanced_learning_manager",
"MaiBotLearningImporter": ".maibot_learning_importer",
"ExemplarLibrary": ".exemplar_library",
"KnowledgeGraphManager": ".knowledge_graph_manager",
"LightRAGKnowledgeManager": ".lightrag_knowledge_manager",
"Mem0MemoryManager": ".mem0_memory_manager",
"TrainingDataExporter": ".training_data_exporter",
}

__all__ = list(_EXPORTS)


def __getattr__(name: str) -> Any:
module_name = _EXPORTS.get(name)
if not module_name:
raise AttributeError(name)
module = import_module(module_name, __name__)
value = getattr(module, name)
globals()[name] = value
return value
Loading
Loading