Merge branch 'master' into master
This commit is contained in:
@@ -862,8 +862,34 @@ CONFIG_METADATA_2 = {
|
||||
"api_base": "https://openspeech.bytedance.com/api/v1/tts",
|
||||
"timeout": 20,
|
||||
},
|
||||
"OpenAI Embedding": {
|
||||
"id": "openai_embedding",
|
||||
"type": "openai_embedding",
|
||||
"provider_type": "embedding",
|
||||
"enable": True,
|
||||
"embedding_api_key": "",
|
||||
"embedding_api_base": "",
|
||||
"embedding_model": "",
|
||||
"embedding_dimensions": 1536,
|
||||
"timeout": 20,
|
||||
},
|
||||
},
|
||||
"items": {
|
||||
"embedding_dimensions": {
|
||||
"description": "嵌入维度",
|
||||
"type": "int",
|
||||
"hint": "嵌入向量的维度。根据模型不同,可能需要调整,请参考具体模型的文档。此配置项请务必填写正确,否则将导致向量数据库无法正常工作。",
|
||||
},
|
||||
"embedding_model": {
|
||||
"description": "嵌入模型",
|
||||
"type": "string",
|
||||
"hint": "嵌入模型名称。",
|
||||
},
|
||||
"embedding_api_key": {
|
||||
"description": "API Key",
|
||||
"type": "string",
|
||||
"hint": "API Key",
|
||||
},
|
||||
"volcengine_cluster": {
|
||||
"type": "string",
|
||||
"description": "火山引擎集群",
|
||||
|
||||
@@ -19,6 +19,7 @@ class ProviderType(enum.Enum):
|
||||
CHAT_COMPLETION = "chat_completion"
|
||||
SPEECH_TO_TEXT = "speech_to_text"
|
||||
TEXT_TO_SPEECH = "text_to_speech"
|
||||
EMBEDDING = "embedding"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -155,7 +156,9 @@ class ProviderRequest:
|
||||
if self.image_urls:
|
||||
user_content = {
|
||||
"role": "user",
|
||||
"content": [{"type": "text", "text": self.prompt if self.prompt else "[图片]"}],
|
||||
"content": [
|
||||
{"type": "text", "text": self.prompt if self.prompt else "[图片]"}
|
||||
],
|
||||
}
|
||||
for image_url in self.image_urls:
|
||||
if image_url.startswith("http"):
|
||||
|
||||
@@ -98,6 +98,8 @@ class ProviderManager:
|
||||
"""加载的 Speech To Text Provider 的实例"""
|
||||
self.tts_provider_insts: List[TTSProvider] = []
|
||||
"""加载的 Text To Speech Provider 的实例"""
|
||||
self.embedding_provider_insts: List[Provider] = []
|
||||
"""加载的 Embedding Provider 的实例"""
|
||||
self.inst_map = {}
|
||||
"""Provider 实例映射. key: provider_id, value: Provider 实例"""
|
||||
self.llm_tools = llm_tools
|
||||
@@ -211,6 +213,10 @@ class ProviderManager:
|
||||
from .sources.volcengine_tts import (
|
||||
ProviderVolcengineTTS as ProviderVolcengineTTS,
|
||||
)
|
||||
case "openai_embedding":
|
||||
from .sources.openai_embedding_source import (
|
||||
OpenAIEmbeddingProvider as OpenAIEmbeddingProvider,
|
||||
)
|
||||
except (ImportError, ModuleNotFoundError) as e:
|
||||
logger.critical(
|
||||
f"加载 {provider_config['type']}({provider_config['id']}) 提供商适配器失败:{e}。可能是因为有未安装的依赖。"
|
||||
@@ -290,6 +296,14 @@ class ProviderManager:
|
||||
if not self.curr_provider_inst:
|
||||
self.curr_provider_inst = inst
|
||||
|
||||
elif provider_metadata.provider_type == ProviderType.EMBEDDING:
|
||||
inst = provider_metadata.cls_type(
|
||||
provider_config, self.provider_settings
|
||||
)
|
||||
if getattr(inst, "initialize", None):
|
||||
await inst.initialize()
|
||||
self.embedding_provider_insts.append(inst)
|
||||
|
||||
self.inst_map[provider_config["id"]] = inst
|
||||
except Exception as e:
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@@ -192,6 +192,11 @@ class EmbeddingProvider(AbstractProvider):
|
||||
"""获取文本的向量"""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
async def get_embeddings(self, text: list[str]) -> list[list[float]]:
|
||||
"""批量获取文本的向量"""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_dim(self) -> int:
|
||||
"""获取向量的维度"""
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
from openai import AsyncOpenAI
|
||||
from ..provider import EmbeddingProvider
|
||||
from ..register import register_provider_adapter
|
||||
from ..entities import ProviderType
|
||||
|
||||
|
||||
@register_provider_adapter(
|
||||
"openai_embedding",
|
||||
"OpenAI API Embedding 提供商适配器",
|
||||
provider_type=ProviderType.EMBEDDING,
|
||||
)
|
||||
class OpenAIEmbeddingProvider(EmbeddingProvider):
|
||||
def __init__(self, provider_config: dict, provider_settings: dict) -> None:
|
||||
super().__init__(provider_config, provider_settings)
|
||||
self.provider_config = provider_config
|
||||
self.provider_settings = provider_settings
|
||||
self.client = AsyncOpenAI(
|
||||
api_key=provider_config.get("embedding_api_key"),
|
||||
base_url=provider_config.get(
|
||||
"embedding_api_base", "https://api.openai.com/v1"
|
||||
),
|
||||
)
|
||||
self.model = provider_config.get("embedding_model", "text-embedding-3-small")
|
||||
self.dimension = provider_config.get("embedding_dimensions", 1536)
|
||||
|
||||
async def get_embedding(self, text: str) -> list[float]:
|
||||
"""
|
||||
获取文本的嵌入
|
||||
"""
|
||||
embedding = await self.client.embeddings.create(input=text, model=self.model)
|
||||
return embedding.data[0].embedding
|
||||
|
||||
async def get_embeddings(self, texts: list[str]) -> list[list[float]]:
|
||||
"""
|
||||
批量获取文本的嵌入
|
||||
"""
|
||||
embeddings = await self.client.embeddings.create(input=texts, model=self.model)
|
||||
return [item.embedding for item in embeddings.data]
|
||||
|
||||
def get_dim(self) -> int:
|
||||
"""获取向量的维度"""
|
||||
return self.dimension
|
||||
@@ -125,11 +125,8 @@ class Context:
|
||||
self.provider_manager.provider_insts.append(provider)
|
||||
|
||||
def get_provider_by_id(self, provider_id: str) -> Provider:
|
||||
"""通过 ID 获取用于文本生成任务的 LLM Provider(Chat_Completion 类型)。"""
|
||||
for provider in self.provider_manager.provider_insts:
|
||||
if provider.meta().id == provider_id:
|
||||
return provider
|
||||
return None
|
||||
"""通过 ID 获取对应的 LLM Provider(Chat_Completion 类型)。"""
|
||||
return self.provider_manager.inst_map.get(provider_id)
|
||||
|
||||
def get_all_providers(self) -> List[Provider]:
|
||||
"""获取所有用于文本生成任务的 LLM Provider(Chat_Completion 类型)。"""
|
||||
|
||||
@@ -166,6 +166,7 @@ class ConfigRoute(Route):
|
||||
"/config/provider/delete": ("POST", self.post_delete_provider),
|
||||
"/config/llmtools": ("GET", self.get_llm_tools),
|
||||
"/config/provider/check_status": ("GET", self.check_all_providers_status),
|
||||
"/config/provider/list": ("GET", self.get_provider_config_list),
|
||||
}
|
||||
self.register_routes()
|
||||
|
||||
@@ -250,6 +251,17 @@ class ConfigRoute(Route):
|
||||
return Response().ok(await self._get_astrbot_config()).__dict__
|
||||
return Response().ok(await self._get_plugin_config(plugin_name)).__dict__
|
||||
|
||||
async def get_provider_config_list(self):
|
||||
provider_type = request.args.get("provider_type", None)
|
||||
if not provider_type:
|
||||
return Response().error("缺少参数 provider_type").__dict__
|
||||
provider_list = []
|
||||
astrbot_config = self.core_lifecycle.astrbot_config
|
||||
for provider in astrbot_config["provider"]:
|
||||
if provider.get("provider_type", None) == provider_type:
|
||||
provider_list.append(provider)
|
||||
return Response().ok(provider_list).__dict__
|
||||
|
||||
async def post_astrbot_configs(self):
|
||||
post_configs = await request.json
|
||||
try:
|
||||
|
||||
@@ -65,11 +65,11 @@ const sidebarItem: menu[] = [
|
||||
icon: 'mdi-console',
|
||||
to: '/console'
|
||||
},
|
||||
// {
|
||||
// title: 'Alkaid',
|
||||
// icon: 'mdi-test-tube',
|
||||
// to: '/alkaid'
|
||||
// },
|
||||
{
|
||||
title: 'Alkaid',
|
||||
icon: 'mdi-test-tube',
|
||||
to: '/alkaid'
|
||||
},
|
||||
{
|
||||
title: '关于',
|
||||
icon: 'mdi-information',
|
||||
|
||||
@@ -27,13 +27,39 @@
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<!-- 添加分类标签页 -->
|
||||
<v-card-text class="px-4 pt-3 pb-0">
|
||||
<v-tabs v-model="activeProviderTypeTab" bg-color="transparent">
|
||||
<v-tab value="all" class="font-weight-medium px-3">
|
||||
<v-icon start>mdi-filter-variant</v-icon>
|
||||
全部
|
||||
</v-tab>
|
||||
<v-tab value="chat_completion" class="font-weight-medium px-3">
|
||||
<v-icon start>mdi-message-text</v-icon>
|
||||
基本对话
|
||||
</v-tab>
|
||||
<v-tab value="speech_to_text" class="font-weight-medium px-3">
|
||||
<v-icon start>mdi-microphone-message</v-icon>
|
||||
语音转文字
|
||||
</v-tab>
|
||||
<v-tab value="text_to_speech" class="font-weight-medium px-3">
|
||||
<v-icon start>mdi-volume-high</v-icon>
|
||||
文字转语音
|
||||
</v-tab>
|
||||
<v-tab value="embedding" class="font-weight-medium px-3">
|
||||
<v-icon start>mdi-code-json</v-icon>
|
||||
Embedding
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text class="px-4 py-3">
|
||||
<item-card-grid
|
||||
:items="config_data.provider || []"
|
||||
:items="filteredProviders"
|
||||
title-field="id"
|
||||
enabled-field="enable"
|
||||
empty-icon="mdi-api-off"
|
||||
empty-text="暂无服务提供商,点击 新增服务提供商 添加"
|
||||
:empty-text="getEmptyText()"
|
||||
@toggle-enabled="providerStatusChange"
|
||||
@delete="deleteProvider"
|
||||
@edit="configExistingProvider"
|
||||
@@ -154,10 +180,14 @@
|
||||
<v-icon start>mdi-volume-high</v-icon>
|
||||
文字转语音
|
||||
</v-tab>
|
||||
<v-tab value="embedding" class="font-weight-medium px-3">
|
||||
<v-icon start>mdi-code-json</v-icon>
|
||||
Embedding
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<v-window v-model="activeProviderTab" class="mt-4">
|
||||
<v-window-item v-for="tabType in ['chat_completion', 'speech_to_text', 'text_to_speech']"
|
||||
<v-window-item v-for="tabType in ['chat_completion', 'speech_to_text', 'text_to_speech', 'embedding']"
|
||||
:key="tabType"
|
||||
:value="tabType">
|
||||
<v-row class="mt-1">
|
||||
@@ -274,6 +304,21 @@ export default {
|
||||
// 新增提供商对话框相关
|
||||
showAddProviderDialog: false,
|
||||
activeProviderTab: 'chat_completion',
|
||||
|
||||
// 添加提供商类型分类
|
||||
activeProviderTypeTab: 'all',
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
// 根据选择的标签过滤提供商列表
|
||||
filteredProviders() {
|
||||
if (!this.config_data.provider || this.activeProviderTypeTab === 'all') {
|
||||
return this.config_data.provider || [];
|
||||
}
|
||||
|
||||
return this.config_data.provider.filter(provider =>
|
||||
provider.provider_type === this.activeProviderTypeTab);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -292,6 +337,15 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
// 获取空列表文本
|
||||
getEmptyText() {
|
||||
if (this.activeProviderTypeTab === 'all') {
|
||||
return "暂无服务提供商,点击 新增服务提供商 添加";
|
||||
} else {
|
||||
return `暂无${this.getTabTypeName(this.activeProviderTypeTab)}类型的服务提供商,点击 新增服务提供商 添加`;
|
||||
}
|
||||
},
|
||||
|
||||
// 按提供商类型获取模板列表
|
||||
getTemplatesByType(type) {
|
||||
const templates = this.metadata['provider_group']?.metadata?.provider?.config_template || {};
|
||||
@@ -343,7 +397,8 @@ export default {
|
||||
const names = {
|
||||
'chat_completion': '基本对话',
|
||||
'speech_to_text': '语音转文本',
|
||||
'text_to_speech': '文本转语音'
|
||||
'text_to_speech': '文本转语音',
|
||||
'embedding': 'Embedding'
|
||||
};
|
||||
return names[tabType] || tabType;
|
||||
},
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<div v-if="!installed" class="d-flex align-center justify-center flex-column"
|
||||
style="flex-grow: 1; width: 100%; height: 100%;">
|
||||
<h2>还没有安装知识库插件</h2>
|
||||
<v-btn style="margin-top: 16px;" variant="tonal" color="primary"
|
||||
@click="installPlugin" :loading="installing">
|
||||
<v-btn style="margin-top: 16px;" variant="tonal" color="primary" @click="installPlugin"
|
||||
:loading="installing">
|
||||
立即安装
|
||||
</v-btn>
|
||||
</div>
|
||||
@@ -18,7 +18,9 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h2 class="mb-4">知识库列表</h2>
|
||||
<h2 class="mb-4">知识库列表
|
||||
<v-icon v-class="ml-2" size="x-small" color="grey" @click="openUrl('https://astrbot.app/use/knowledge-base.html')">mdi-information-outline</v-icon>
|
||||
</h2>
|
||||
<v-btn class="mb-4" prepend-icon="mdi-plus" variant="tonal" color="primary"
|
||||
@click="showCreateDialog = true">
|
||||
创建知识库
|
||||
@@ -49,9 +51,9 @@
|
||||
<div style="padding: 16px; text-align: center;">
|
||||
<small style="color: #a3a3a3">Tips: 在聊天页面通过 /kb 指令了解如何使用!</small>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 创建知识库对话框 -->
|
||||
@@ -72,6 +74,12 @@
|
||||
|
||||
<v-textarea v-model="newKB.description" label="描述" variant="outlined" placeholder="知识库的简短描述..."
|
||||
rows="3"></v-textarea>
|
||||
|
||||
<v-select v-model="newKB.embedding_provider_id" :items="embeddingProviderConfigs"
|
||||
:item-props="embeddingModelProps" label="Embedding(嵌入)模型" variant="outlined" class="mt-2">
|
||||
</v-select>
|
||||
|
||||
<small>Tips: 一旦选择了一个知识库的嵌入模型,请不要再修改该提供商的模型或者向量维度信息,否则将严重影响该知识库的召回率甚至报错。</small>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
@@ -118,6 +126,18 @@
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<div v-if="currentKB._embedding_provider_config" class="px-6 py-2">
|
||||
<v-chip class="mr-2" color="primary" variant="tonal" size="small" rounded="sm">
|
||||
<v-icon start size="small">mdi-database</v-icon>
|
||||
嵌入模型: {{ currentKB._embedding_provider_config.embedding_model }}
|
||||
</v-chip>
|
||||
<v-chip color="secondary" variant="tonal" size="small" rounded="sm">
|
||||
<v-icon start size="small">mdi-vector-point</v-icon>
|
||||
向量维度: {{ currentKB._embedding_provider_config.embedding_dimensions }}
|
||||
</v-chip>
|
||||
<small style="margin-left: 8px;">💡 使用方式: 在聊天页中输入 “/kb use {{ currentKB.collection_name }}”</small>
|
||||
</div>
|
||||
|
||||
<v-card-text>
|
||||
<v-tabs v-model="activeTab">
|
||||
<v-tab value="upload">上传文件</v-tab>
|
||||
@@ -140,6 +160,54 @@
|
||||
<p class="mt-2">拖放文件到这里或点击上传</p>
|
||||
</div>
|
||||
|
||||
<!-- 优化后的分片长度和重叠长度设置 -->
|
||||
<v-card class="mt-4 chunk-settings-card" variant="outlined" color="grey-lighten-4">
|
||||
<v-card-title class="pa-4 pb-0 d-flex align-center">
|
||||
<v-icon color="primary" class="mr-2">mdi-puzzle-outline</v-icon>
|
||||
<span class="text-subtitle-1 font-weight-bold">分片设置</span>
|
||||
<v-tooltip location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-icon v-bind="props" class="ml-2" size="small" color="grey">
|
||||
mdi-information-outline
|
||||
</v-icon>
|
||||
</template>
|
||||
<span>
|
||||
分片长度决定每块文本的大小,重叠长度决定相邻文本块之间的重叠程度。<br>
|
||||
较小的分片更精确但会增加数量,适当的重叠可提高检索准确性。
|
||||
</span>
|
||||
</v-tooltip>
|
||||
</v-card-title>
|
||||
<v-card-text class="pa-4 pt-2">
|
||||
<div class="d-flex flex-wrap" style="gap: 8px">
|
||||
<v-text-field
|
||||
v-model="chunkSize"
|
||||
label="分片长度"
|
||||
type="number"
|
||||
hint="控制每个文本块大小,留空使用默认值"
|
||||
persistent-hint
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="flex-grow-1 chunk-field"
|
||||
prepend-inner-icon="mdi-text-box-outline"
|
||||
min="50"
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="overlap"
|
||||
label="重叠长度"
|
||||
type="number"
|
||||
hint="控制相邻文本块重叠度,留空使用默认值"
|
||||
persistent-hint
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="flex-grow-1 chunk-field"
|
||||
prepend-inner-icon="mdi-vector-intersection"
|
||||
min="0"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<div class="selected-files mt-4" v-if="selectedFile">
|
||||
<div type="info" variant="tonal" class="d-flex align-center">
|
||||
<div>
|
||||
@@ -256,7 +324,8 @@ export default {
|
||||
newKB: {
|
||||
name: '',
|
||||
emoji: '🙂',
|
||||
description: ''
|
||||
description: '',
|
||||
embedding_provider_id: ''
|
||||
},
|
||||
snackbar: {
|
||||
show: false,
|
||||
@@ -296,6 +365,8 @@ export default {
|
||||
},
|
||||
activeTab: 'upload',
|
||||
selectedFile: null,
|
||||
chunkSize: null,
|
||||
overlap: null,
|
||||
uploading: false,
|
||||
searchQuery: '',
|
||||
searchResults: [],
|
||||
@@ -306,13 +377,21 @@ export default {
|
||||
deleteTarget: {
|
||||
collection_name: ''
|
||||
},
|
||||
deleting: false
|
||||
deleting: false,
|
||||
embeddingProviderConfigs: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.checkPlugin();
|
||||
this.getEmbeddingProviderList();
|
||||
},
|
||||
methods: {
|
||||
embeddingModelProps(providerConfig) {
|
||||
return {
|
||||
title: providerConfig.embedding_model,
|
||||
subtitle: `提供商 ID: ${providerConfig.id} | 嵌入模型维度: ${providerConfig.embedding_dimensions}`,
|
||||
}
|
||||
},
|
||||
checkPlugin() {
|
||||
axios.get('/api/plugin/get?name=astrbot_plugin_knowledge_base')
|
||||
.then(response => {
|
||||
@@ -335,7 +414,7 @@ export default {
|
||||
installPlugin() {
|
||||
this.installing = true;
|
||||
axios.post('/api/plugin/install', {
|
||||
url: "https://github.com/soulter/astrbot_plugin_knowledge_base",
|
||||
url: "https://github.com/lxfight/astrbot_plugin_knowledge_base",
|
||||
proxy: localStorage.getItem('selectedGitHubProxy') || ""
|
||||
})
|
||||
.then(response => {
|
||||
@@ -365,10 +444,15 @@ export default {
|
||||
},
|
||||
|
||||
createCollection(name, emoji, description) {
|
||||
// 如果 this.newKB.embedding_provider_id 是 Object
|
||||
if (typeof this.newKB.embedding_provider_id === 'object') {
|
||||
this.newKB.embedding_provider_id = this.newKB.embedding_provider_id.id || '';
|
||||
}
|
||||
axios.post('/api/plug/alkaid/kb/create_collection', {
|
||||
collection_name: name,
|
||||
emoji: emoji,
|
||||
description: description
|
||||
description: description,
|
||||
embedding_provider_id: this.newKB.embedding_provider_id || ''
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.status === 'ok') {
|
||||
@@ -394,7 +478,8 @@ export default {
|
||||
this.createCollection(
|
||||
this.newKB.name,
|
||||
this.newKB.emoji || '🙂',
|
||||
this.newKB.description
|
||||
this.newKB.description,
|
||||
this.newKB.embedding_provider_id || ''
|
||||
);
|
||||
},
|
||||
|
||||
@@ -402,7 +487,8 @@ export default {
|
||||
this.newKB = {
|
||||
name: '',
|
||||
emoji: '🙂',
|
||||
description: ''
|
||||
description: '',
|
||||
embedding_provider: ''
|
||||
};
|
||||
},
|
||||
|
||||
@@ -419,6 +505,9 @@ export default {
|
||||
this.searchQuery = '';
|
||||
this.searchResults = [];
|
||||
this.searchPerformed = false;
|
||||
// 重置分片长度和重叠长度参数
|
||||
this.chunkSize = null;
|
||||
this.overlap = null;
|
||||
},
|
||||
|
||||
triggerFileInput() {
|
||||
@@ -472,6 +561,15 @@ export default {
|
||||
const formData = new FormData();
|
||||
formData.append('file', this.selectedFile);
|
||||
formData.append('collection_name', this.currentKB.collection_name);
|
||||
|
||||
// 添加可选的分片长度和重叠长度参数
|
||||
if (this.chunkSize && this.chunkSize > 0) {
|
||||
formData.append('chunk_size', this.chunkSize);
|
||||
}
|
||||
|
||||
if (this.overlap && this.overlap >= 0) {
|
||||
formData.append('chunk_overlap', this.overlap);
|
||||
}
|
||||
|
||||
axios.post('/api/plug/alkaid/kb/collection/add_file', formData, {
|
||||
headers: {
|
||||
@@ -480,7 +578,7 @@ export default {
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.status === 'ok') {
|
||||
this.showSnackbar('文件上传成功');
|
||||
this.showSnackbar('操作成功: ' + response.data.message);
|
||||
this.selectedFile = null;
|
||||
|
||||
// 刷新知识库列表,获取更新的数量
|
||||
@@ -582,6 +680,31 @@ export default {
|
||||
this.deleting = false;
|
||||
});
|
||||
},
|
||||
|
||||
getEmbeddingProviderList() {
|
||||
axios.get('/api/config/provider/list', {
|
||||
params: {
|
||||
provider_type: 'embedding'
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data.status === 'ok') {
|
||||
this.embeddingProviderConfigs = response.data.data || [];
|
||||
} else {
|
||||
this.showSnackbar(response.data.message || '获取嵌入模型列表失败', 'error');
|
||||
return [];
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching embedding providers:', error);
|
||||
this.showSnackbar('获取嵌入模型列表失败', 'error');
|
||||
return [];
|
||||
});
|
||||
},
|
||||
|
||||
openUrl(url) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -751,4 +874,28 @@ export default {
|
||||
.kb-card:hover .kb-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.chunk-settings-card {
|
||||
border: 1px solid rgba(92, 107, 192, 0.2) !important;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.chunk-settings-card:hover {
|
||||
border-color: rgba(92, 107, 192, 0.4) !important;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07) !important;
|
||||
}
|
||||
|
||||
.chunk-field :deep(.v-field__input) {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.chunk-field :deep(.v-field__prepend-inner) {
|
||||
padding-right: 8px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.chunk-field:focus-within :deep(.v-field__prepend-inner) {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div id="long-term-memory" class="flex-grow-1" style="display: flex; flex-direction: row; ">
|
||||
<div id="graph-container" style="flex-grow: 1; width: 100%; border: 1px solid #eee; border-radius: 8px; max-height: calc(100% - 40px);">
|
||||
<!-- <div id="graph-container"
|
||||
style="flex-grow: 1; width: 100%; border: 1px solid #eee; border-radius: 8px; max-height: calc(100% - 40px);">
|
||||
</div> -->
|
||||
<div id="graph-container-nonono"
|
||||
style="display: flex; justify-content: center; align-items: center; width: 100%; font-weight: 1000; font-size: 24px;">
|
||||
加速开发中...
|
||||
</div>
|
||||
<div id="graph-control-panel"
|
||||
style="min-width: 450px; border: 1px solid #eee; border-radius: 8px; padding: 16px; padding-bottom: 0px; margin-left: 16px; max-height: calc(100% - 40px);">
|
||||
@@ -31,42 +36,27 @@
|
||||
<div class="mt-4">
|
||||
<h3>搜索记忆</h3>
|
||||
<v-card variant="outlined" class="mt-2 pa-3">
|
||||
<div >
|
||||
<v-text-field
|
||||
v-model="searchMemoryUserId"
|
||||
label="用户 ID"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="mb-2"
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
v-model="searchQuery"
|
||||
label="输入关键词"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
@keyup.enter="searchMemory"
|
||||
class="mb-2"
|
||||
></v-text-field>
|
||||
<div>
|
||||
<v-text-field v-model="searchMemoryUserId" label="用户 ID" variant="outlined" density="compact" hide-details
|
||||
class="mb-2"></v-text-field>
|
||||
<v-text-field v-model="searchQuery" label="输入关键词" variant="outlined" density="compact" hide-details
|
||||
@keyup.enter="searchMemory" class="mb-2"></v-text-field>
|
||||
<v-btn color="info" @click="searchMemory" :loading="isSearching" variant="tonal">
|
||||
<v-icon start>mdi-text-search</v-icon>
|
||||
搜索
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 新增搜索结果展示区域 -->
|
||||
<div v-if="searchResults.length > 0" class="mt-3">
|
||||
<v-divider class="mb-3"></v-divider>
|
||||
<div class="text-subtitle-1 mb-2">搜索结果 ({{ searchResults.length }})</div>
|
||||
<v-expansion-panels variant="accordion">
|
||||
<v-expansion-panel
|
||||
v-for="(result, index) in searchResults"
|
||||
:key="index"
|
||||
>
|
||||
<v-expansion-panel v-for="(result, index) in searchResults" :key="index">
|
||||
<v-expansion-panel-title>
|
||||
<div>
|
||||
<span class="text-truncate d-inline-block" style="max-width: 300px;">{{ result.text.substring(0, 30) }}...</span>
|
||||
<span class="text-truncate d-inline-block" style="max-width: 300px;">{{ result.text.substring(0, 30)
|
||||
}}...</span>
|
||||
<span class="ms-2 text-caption text-grey">(相关度: {{ (result.score * 100).toFixed(1) }}%)</span>
|
||||
</div>
|
||||
</v-expansion-panel-title>
|
||||
@@ -86,42 +76,21 @@
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 新增添加记忆数据的表单 -->
|
||||
<div class="mt-4">
|
||||
<h3>添加记忆数据</h3>
|
||||
<v-card variant="outlined" class="mt-2 pa-3">
|
||||
<v-form @submit.prevent="addMemoryData">
|
||||
<v-textarea
|
||||
v-model="newMemoryText"
|
||||
label="输入文本内容"
|
||||
variant="outlined"
|
||||
rows="4"
|
||||
hide-details
|
||||
class="mb-2"
|
||||
></v-textarea>
|
||||
|
||||
<v-text-field
|
||||
v-model="newMemoryUserId"
|
||||
label="用户 ID"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
></v-text-field>
|
||||
|
||||
<v-switch
|
||||
v-model="needSummarize"
|
||||
color="primary"
|
||||
label="需要摘要"
|
||||
hide-details
|
||||
></v-switch>
|
||||
|
||||
<v-btn
|
||||
color="success"
|
||||
type="submit"
|
||||
:loading="isSubmitting"
|
||||
:disabled="!newMemoryText || !newMemoryUserId"
|
||||
>
|
||||
<v-textarea v-model="newMemoryText" label="输入文本内容" variant="outlined" rows="4" hide-details
|
||||
class="mb-2"></v-textarea>
|
||||
|
||||
<v-text-field v-model="newMemoryUserId" label="用户 ID" variant="outlined" density="compact"
|
||||
hide-details></v-text-field>
|
||||
|
||||
<v-switch v-model="needSummarize" color="primary" label="需要摘要" hide-details></v-switch>
|
||||
|
||||
<v-btn color="success" type="submit" :loading="isSubmitting" :disabled="!newMemoryText || !newMemoryUserId">
|
||||
<v-icon start>mdi-plus</v-icon>
|
||||
添加数据
|
||||
</v-btn>
|
||||
@@ -249,26 +218,26 @@ export default {
|
||||
this.$toast.warning('请输入搜索关键词');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.isSearching = true;
|
||||
this.hasSearched = true;
|
||||
this.searchResults = [];
|
||||
|
||||
|
||||
// 构建查询参数
|
||||
const params = {
|
||||
query: this.searchQuery
|
||||
};
|
||||
|
||||
|
||||
// 如果有选择用户ID,也加入查询参数
|
||||
if (this.searchMemoryUserId) {
|
||||
params.user_id = this.searchMemoryUserId;
|
||||
}
|
||||
|
||||
|
||||
axios.get('/api/plug/alkaid/ltm/graph/search', { params })
|
||||
.then(response => {
|
||||
if (response.data.status === 'ok') {
|
||||
const data = response.data.data;
|
||||
|
||||
|
||||
// 处理返回的文档数组
|
||||
this.searchResults = Object.keys(data).map(doc_id => {
|
||||
return {
|
||||
@@ -277,7 +246,7 @@ export default {
|
||||
score: data[doc_id].score || 0
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
if (this.searchResults.length === 0) {
|
||||
this.$toast.info('未找到相关记忆内容');
|
||||
} else {
|
||||
@@ -295,7 +264,7 @@ export default {
|
||||
this.isSearching = false;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// 添加新方法,用于提交记忆数据
|
||||
addMemoryData() {
|
||||
if (!this.newMemoryText || !this.newMemoryUserId) {
|
||||
@@ -303,23 +272,23 @@ export default {
|
||||
}
|
||||
|
||||
this.isSubmitting = true;
|
||||
|
||||
|
||||
// 准备提交数据
|
||||
const payload = {
|
||||
text: this.newMemoryText,
|
||||
user_id: this.newMemoryUserId,
|
||||
need_summarize: this.needSummarize
|
||||
};
|
||||
|
||||
|
||||
axios.post('/api/plug/alkaid/ltm/graph/add', payload)
|
||||
.then(response => {
|
||||
// 成功添加后刷新图表
|
||||
this.refreshGraph();
|
||||
|
||||
|
||||
// 重置表单
|
||||
// this.newMemoryText = '';
|
||||
// this.needSummarize = false;
|
||||
|
||||
|
||||
// 显示成功消息
|
||||
this.$toast.success('记忆数据添加成功!');
|
||||
})
|
||||
@@ -331,7 +300,7 @@ export default {
|
||||
this.isSubmitting = false;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
ltmGetGraph(userId = null) {
|
||||
this.isLoading = true;
|
||||
const params = userId ? { user_id: userId } : {};
|
||||
@@ -571,6 +540,7 @@ export default {
|
||||
<style scoped>
|
||||
#long-term-memory {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -585,8 +555,8 @@ export default {
|
||||
}
|
||||
|
||||
#graph-control-panel {
|
||||
height: 100%;
|
||||
overflow-y: auto; /* 让控制面板可滚动而不是整个页面滚动 */
|
||||
overflow-y: auto;
|
||||
/* 让控制面板可滚动而不是整个页面滚动 */
|
||||
min-width: 450px;
|
||||
max-width: 450px;
|
||||
}
|
||||
@@ -607,4 +577,5 @@ export default {
|
||||
.d3-graph {
|
||||
background-color: #f2f6f9;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user