stage simple webui

This commit is contained in:
Soulter
2025-11-21 17:59:22 +08:00
parent fbbaf1cd08
commit b7f3010d72
10 changed files with 551 additions and 3 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ class Result:
id: str
doc_id: str
text: str
metadata: dict
metadata: str
created_at: int
updated_at: int
+3 -2
View File
@@ -1,3 +1,4 @@
import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
@@ -114,7 +115,7 @@ class MemoryManager:
# Get all candidate memories from database
candidate_memories: list[tuple[str, MemoryChunk]] = []
for candidate in merge_candidates:
mem_id = candidate.data["metadata"]["mem_id"]
mem_id = json.loads(candidate.data["metadata"])["mem_id"]
memory = await self.mem_db.get_memory_by_id(mem_id)
if memory:
candidate_memories.append((mem_id, memory))
@@ -184,7 +185,7 @@ class MemoryManager:
# Step 4: Apply Hebbian learning to similar memories
hebb_mem_ids = [
r.data["metadata"]["mem_id"]
json.loads(r.data["metadata"])["mem_id"]
for r in similar_results
if r.similarity >= HEBB_THRESHOLD
]
+2
View File
@@ -5,6 +5,7 @@ from .conversation import ConversationRoute
from .file import FileRoute
from .knowledge_base import KnowledgeBaseRoute
from .log import LogRoute
from .memory import MemoryRoute
from .persona import PersonaRoute
from .plugin import PluginRoute
from .session_management import SessionManagementRoute
@@ -21,6 +22,7 @@ __all__ = [
"FileRoute",
"KnowledgeBaseRoute",
"LogRoute",
"MemoryRoute",
"PersonaRoute",
"PluginRoute",
"SessionManagementRoute",
+174
View File
@@ -0,0 +1,174 @@
"""Memory management API routes"""
from quart import jsonify, request
from astrbot.core import logger
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
from astrbot.core.db import BaseDatabase
from .route import Response, Route, RouteContext
class MemoryRoute(Route):
"""Memory management routes"""
def __init__(
self,
context: RouteContext,
db: BaseDatabase,
core_lifecycle: AstrBotCoreLifecycle,
):
super().__init__(context)
self.db = db
self.core_lifecycle = core_lifecycle
self.memory_manager = core_lifecycle.memory_manager
self.provider_manager = core_lifecycle.provider_manager
self.routes = [
("/memory/status", ("GET", self.get_status)),
("/memory/initialize", ("POST", self.initialize)),
("/memory/update_merge_llm", ("POST", self.update_merge_llm)),
]
self.register_routes()
async def get_status(self):
"""Get memory system status"""
try:
is_initialized = self.memory_manager._initialized
status_data = {
"initialized": is_initialized,
"embedding_provider_id": None,
"merge_llm_provider_id": None,
}
if is_initialized:
# Get embedding provider info
if self.memory_manager.embedding_provider:
status_data["embedding_provider_id"] = (
self.memory_manager.embedding_provider.provider_config["id"]
)
# Get merge LLM provider info
if self.memory_manager.merge_llm_provider:
status_data["merge_llm_provider_id"] = (
self.memory_manager.merge_llm_provider.provider_config["id"]
)
return jsonify(Response().ok(status_data).__dict__)
except Exception as e:
logger.error(f"Failed to get memory status: {e}")
return jsonify(Response().error(str(e)).__dict__)
async def initialize(self):
"""Initialize memory system with embedding and merge LLM providers"""
try:
data = await request.get_json()
embedding_provider_id = data.get("embedding_provider_id")
merge_llm_provider_id = data.get("merge_llm_provider_id")
if not embedding_provider_id or not merge_llm_provider_id:
return jsonify(
Response()
.error(
"embedding_provider_id and merge_llm_provider_id are required"
)
.__dict__,
)
# Check if already initialized
if self.memory_manager._initialized:
return jsonify(
Response()
.error(
"Memory system already initialized. Embedding provider cannot be changed.",
)
.__dict__,
)
# Get providers
embedding_provider = await self.provider_manager.get_provider_by_id(
embedding_provider_id,
)
merge_llm_provider = await self.provider_manager.get_provider_by_id(
merge_llm_provider_id,
)
if not embedding_provider:
return jsonify(
Response()
.error(f"Embedding provider {embedding_provider_id} not found")
.__dict__,
)
if not merge_llm_provider:
return jsonify(
Response()
.error(f"Merge LLM provider {merge_llm_provider_id} not found")
.__dict__,
)
# Initialize memory manager
await self.memory_manager.initialize(
embedding_provider=embedding_provider,
merge_llm_provider=merge_llm_provider,
)
logger.info(
f"Memory system initialized with embedding: {embedding_provider_id}, "
f"merge LLM: {merge_llm_provider_id}",
)
return jsonify(
Response()
.ok({"message": "Memory system initialized successfully"})
.__dict__,
)
except Exception as e:
logger.error(f"Failed to initialize memory system: {e}")
return jsonify(Response().error(str(e)).__dict__)
async def update_merge_llm(self):
"""Update merge LLM provider (only allowed after initialization)"""
try:
data = await request.get_json()
merge_llm_provider_id = data.get("merge_llm_provider_id")
if not merge_llm_provider_id:
return jsonify(
Response().error("merge_llm_provider_id is required").__dict__,
)
# Check if initialized
if not self.memory_manager._initialized:
return jsonify(
Response()
.error("Memory system not initialized. Please initialize first.")
.__dict__,
)
# Get new merge LLM provider
merge_llm_provider = await self.provider_manager.get_provider_by_id(
merge_llm_provider_id,
)
if not merge_llm_provider:
return jsonify(
Response()
.error(f"Merge LLM provider {merge_llm_provider_id} not found")
.__dict__,
)
# Update merge LLM provider
self.memory_manager.merge_llm_provider = merge_llm_provider
logger.info(f"Updated merge LLM provider to: {merge_llm_provider_id}")
return jsonify(
Response()
.ok({"message": "Merge LLM provider updated successfully"})
.__dict__,
)
except Exception as e:
logger.error(f"Failed to update merge LLM provider: {e}")
return jsonify(Response().error(str(e)).__dict__)
+1
View File
@@ -79,6 +79,7 @@ class AstrBotDashboard:
self.persona_route = PersonaRoute(self.context, db, core_lifecycle)
self.t2i_route = T2iRoute(self.context, core_lifecycle)
self.kb_route = KnowledgeBaseRoute(self.context, core_lifecycle)
self.memory_route = MemoryRoute(self.context, db, core_lifecycle)
self.app.add_url_rule(
"/api/plug/<path:subpath>",
@@ -12,6 +12,7 @@
"console": "Console",
"alkaid": "Alkaid Lab",
"knowledgeBase": "Knowledge Base",
"memory": "Long-term Memory",
"about": "About",
"settings": "Settings",
"documentation": "Documentation",
@@ -12,6 +12,7 @@
"console": "控制台",
"alkaid": "Alkaid",
"knowledgeBase": "知识库",
"memory": "长期记忆",
"about": "关于",
"settings": "设置",
"documentation": "官方文档",
@@ -48,6 +48,11 @@ const sidebarItem: menu[] = [
icon: 'mdi-book-open-variant',
to: '/knowledge-base',
},
{
title: 'core.navigation.memory',
icon: 'mdi-brain',
to: '/memory',
},
{
title: 'core.navigation.chat',
icon: 'mdi-chat',
+5
View File
@@ -90,6 +90,11 @@ const MainRoutes = {
}
]
},
{
name: 'Memory',
path: '/memory',
component: () => import('@/views/MemoryPage.vue')
},
// 旧版本的知识库路由
{
+358
View File
@@ -0,0 +1,358 @@
<template>
<div class="memory-page">
<v-container fluid class="pa-0">
<!-- 页面标题 -->
<v-row class="d-flex justify-space-between align-center px-4 py-3 pb-8">
<div>
<h1 class="text-h1 font-weight-bold mb-2">
<v-icon color="black" class="me-2">mdi-brain</v-icon>{{ t('core.navigation.memory') }}
</h1>
<p class="text-subtitle-1 text-medium-emphasis mb-4">
管理长期记忆系统的配置
</p>
</div>
</v-row>
<!-- 加载状态 -->
<v-row v-if="loading">
<v-col cols="12">
<v-card>
<v-card-text class="text-center">
<v-progress-circular indeterminate color="primary"></v-progress-circular>
</v-card-text>
</v-card>
</v-col>
</v-row>
<!-- 主内容 -->
<v-row v-else>
<v-col cols="12" md="8" lg="6">
<v-card rounded="lg">
<v-card-title class="d-flex align-center">
<v-icon class="mr-2">mdi-cog</v-icon>
记忆系统配置
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<!-- 状态显示 -->
<v-alert
:type="memoryStatus.initialized ? 'success' : 'info'"
variant="tonal"
class="mb-4"
>
<div class="d-flex align-center">
<v-icon class="mr-2">
{{ memoryStatus.initialized ? 'mdi-check-circle' : 'mdi-information' }}
</v-icon>
<div>
<strong>状态</strong>
{{ memoryStatus.initialized ? '已初始化' : '未初始化' }}
</div>
</div>
</v-alert>
<!-- 未初始化时显示初始化表单 -->
<div v-if="!memoryStatus.initialized">
<v-form @submit.prevent="initializeMemory">
<v-select
v-model="selectedEmbeddingProvider"
:items="embeddingProviders"
item-title="text"
item-value="value"
label="Embedding 模型 *"
hint="用于生成向量表示,初始化后不可更改"
persistent-hint
class="mb-4"
required
:disabled="initializing"
></v-select>
<v-select
v-model="selectedMergeLLM"
:items="llmProviders"
item-title="text"
item-value="value"
label="合并 LLM *"
hint="用于合并相似记忆,可在初始化后更改"
persistent-hint
class="mb-4"
required
:disabled="initializing"
></v-select>
<v-btn
type="submit"
color="primary"
:loading="initializing"
:disabled="!selectedEmbeddingProvider || !selectedMergeLLM"
block
size="large"
>
初始化记忆系统
</v-btn>
</v-form>
</div>
<!-- 已初始化时显示配置信息 -->
<div v-else>
<v-list>
<v-list-item>
<template v-slot:prepend>
<v-icon>mdi-vector-triangle</v-icon>
</template>
<v-list-item-title>Embedding 模型</v-list-item-title>
<v-list-item-subtitle>
{{ getProviderName(memoryStatus.embedding_provider_id) }}
</v-list-item-subtitle>
</v-list-item>
<v-divider class="my-2"></v-divider>
<v-list-item>
<template v-slot:prepend>
<v-icon>mdi-robot</v-icon>
</template>
<v-list-item-title>合并 LLM</v-list-item-title>
<v-list-item-subtitle>
{{ getProviderName(memoryStatus.merge_llm_provider_id) }}
</v-list-item-subtitle>
</v-list-item>
</v-list>
<v-divider class="my-4"></v-divider>
<v-form @submit.prevent="updateMergeLLM">
<v-select
v-model="newMergeLLM"
:items="llmProviders"
item-title="text"
item-value="value"
label="更新合并 LLM"
hint="可以更换用于合并记忆的 LLM"
persistent-hint
class="mb-4"
:disabled="updating"
></v-select>
<v-btn
type="submit"
color="primary"
:loading="updating"
:disabled="!newMergeLLM || newMergeLLM === memoryStatus.merge_llm_provider_id"
block
variant="tonal"
>
更新合并 LLM
</v-btn>
</v-form>
</div>
</v-card-text>
</v-card>
</v-col>
<!-- 说明卡片 -->
<v-col cols="12" md="4" lg="6">
<v-card rounded="lg">
<v-card-title class="d-flex align-center">
<v-icon class="mr-2">mdi-information</v-icon>
说明
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<v-list density="compact">
<v-list-item>
<v-list-item-title class="text-wrap">
<strong>Embedding 模型</strong>用于将文本转换为向量支持语义相似度搜索
<v-chip size="x-small" color="warning" class="ml-2">不可更改</v-chip>
</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-title class="text-wrap">
<strong>合并 LLM</strong>当检测到相似记忆时使用此模型合并为一条记忆
<v-chip size="x-small" color="success" class="ml-2">可更改</v-chip>
</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-title class="text-wrap">
<strong>注意</strong>Embedding 模型一旦选择后无法更改请谨慎选择
</v-list-item-title>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
<!-- 提示框 -->
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
{{ snackbar.message }}
</v-snackbar>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import axios from 'axios';
import { useI18n } from '@/i18n/composables';
const { t } = useI18n();
interface MemoryStatus {
initialized: boolean;
embedding_provider_id: string | null;
merge_llm_provider_id: string | null;
}
interface Provider {
value: string;
text: string;
}
const loading = ref(true);
const initializing = ref(false);
const updating = ref(false);
const memoryStatus = ref<MemoryStatus>({
initialized: false,
embedding_provider_id: null,
merge_llm_provider_id: null,
});
const embeddingProviders = ref<Provider[]>([]);
const llmProviders = ref<Provider[]>([]);
const selectedEmbeddingProvider = ref<string>('');
const selectedMergeLLM = ref<string>('');
const newMergeLLM = ref<string>('');
const snackbar = ref({
show: false,
message: '',
color: 'success',
});
const showMessage = (message: string, color: string = 'success') => {
snackbar.value.message = message;
snackbar.value.color = color;
snackbar.value.show = true;
};
const getProviderName = (providerId: string | null): string => {
if (!providerId) return '未设置';
const embedding = embeddingProviders.value.find(p => p.value === providerId);
const llm = llmProviders.value.find(p => p.value === providerId);
return embedding?.text || llm?.text || providerId;
};
const loadProviders = async () => {
try {
// Load embedding providers
const embeddingResponse = await axios.get('/api/config/provider/list', {
params: { provider_type: 'embedding' }
});
if (embeddingResponse.data.status === 'ok') {
embeddingProviders.value = (embeddingResponse.data.data || []).map((p: any) => ({
value: p.id,
text: `${p.embedding_model} (${p.id})`,
}));
}
// Load LLM providers
const llmResponse = await axios.get('/api/config/provider/list', {
params: { provider_type: 'chat_completion' }
});
if (llmResponse.data.status === 'ok') {
llmProviders.value = (llmResponse.data.data || []).map((p: any) => ({
value: p.id,
text: `${p?.model_config?.model} (${p.id})`,
}));
}
} catch (error) {
console.error('Failed to load providers:', error);
showMessage('加载提供商列表失败', 'error');
}
};
const loadStatus = async () => {
try {
const response = await axios.get('/api/memory/status');
if (response.data.status === 'ok') {
memoryStatus.value = response.data.data;
if (memoryStatus.value.merge_llm_provider_id) {
newMergeLLM.value = memoryStatus.value.merge_llm_provider_id;
}
}
} catch (error) {
console.error('Failed to load memory status:', error);
showMessage('加载记忆系统状态失败', 'error');
}
};
const initializeMemory = async () => {
if (!selectedEmbeddingProvider.value || !selectedMergeLLM.value) {
showMessage('请选择 Embedding 模型和合并 LLM', 'warning');
return;
}
initializing.value = true;
try {
const response = await axios.post('/api/memory/initialize', {
embedding_provider_id: selectedEmbeddingProvider.value,
merge_llm_provider_id: selectedMergeLLM.value,
});
if (response.data.status === 'ok') {
showMessage('记忆系统初始化成功', 'success');
await loadStatus();
} else {
showMessage(response.data.message || '初始化失败', 'error');
}
} catch (error: any) {
console.error('Failed to initialize memory:', error);
showMessage(error.response?.data?.message || '初始化失败', 'error');
} finally {
initializing.value = false;
}
};
const updateMergeLLM = async () => {
if (!newMergeLLM.value) {
showMessage('请选择新的合并 LLM', 'warning');
return;
}
updating.value = true;
try {
const response = await axios.post('/api/memory/update_merge_llm', {
merge_llm_provider_id: newMergeLLM.value,
});
if (response.data.status === 'ok') {
showMessage('合并 LLM 更新成功', 'success');
await loadStatus();
} else {
showMessage(response.data.message || '更新失败', 'error');
}
} catch (error: any) {
console.error('Failed to update merge LLM:', error);
showMessage(error.response?.data?.message || '更新失败', 'error');
} finally {
updating.value = false;
}
};
onMounted(async () => {
loading.value = true;
await Promise.all([loadProviders(), loadStatus()]);
loading.value = false;
});
</script>
<style scoped>
.memory-page {
min-height: 100vh;
padding: 8px;
}
</style>