feat: complete Day 21-24 knowledge base features
- Day 21-22: fix CORS and file upload issues - Day 23-24: implement @knowledge base search and conversation integration - Backend: integrate Dify RAG into conversation system - Frontend: load knowledge base list and @ reference feature
This commit is contained in:
70
backend/check-docs-api.ps1
Normal file
70
backend/check-docs-api.ps1
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# 通过API查询文档状态
|
||||||
|
Write-Host "`n📋 查询知识库和文档状态...`n" -ForegroundColor Green
|
||||||
|
|
||||||
|
# 获取知识库列表
|
||||||
|
try {
|
||||||
|
$response = Invoke-RestMethod -Uri "http://localhost:3001/api/v1/knowledge-bases" -Method GET
|
||||||
|
|
||||||
|
if ($response.data.Count -eq 0) {
|
||||||
|
Write-Host "❌ 没有找到任何知识库" -ForegroundColor Red
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "找到 $($response.data.Count) 个知识库:`n" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
foreach ($kb in $response.data) {
|
||||||
|
Write-Host "📚 知识库: $($kb.name)" -ForegroundColor Yellow
|
||||||
|
Write-Host " ID: $($kb.id)"
|
||||||
|
Write-Host " 描述: $($kb.description)"
|
||||||
|
Write-Host " 文件数: $($kb.fileCount)"
|
||||||
|
Write-Host " 创建时间: $($kb.createdAt)"
|
||||||
|
|
||||||
|
# 获取该知识库的文档列表
|
||||||
|
try {
|
||||||
|
$docsResponse = Invoke-RestMethod -Uri "http://localhost:3001/api/v1/knowledge-bases/$($kb.id)/documents" -Method GET
|
||||||
|
|
||||||
|
if ($docsResponse.data.Count -gt 0) {
|
||||||
|
Write-Host "`n 📄 文档列表:" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
foreach ($doc in $docsResponse.data) {
|
||||||
|
$statusEmoji = switch ($doc.status) {
|
||||||
|
"uploading" { "📤" }
|
||||||
|
"parsing" { "🔄" }
|
||||||
|
"indexing" { "⚙️" }
|
||||||
|
"completed" { "✅" }
|
||||||
|
"error" { "❌" }
|
||||||
|
default { "❓" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " $statusEmoji $($doc.filename)" -ForegroundColor White
|
||||||
|
Write-Host " 状态: $($doc.status)"
|
||||||
|
Write-Host " 大小: $([math]::Round($doc.fileSizeBytes / 1024, 2)) KB"
|
||||||
|
Write-Host " 上传时间: $($doc.uploadedAt)"
|
||||||
|
|
||||||
|
if ($doc.status -eq "completed") {
|
||||||
|
Write-Host " ✓ 段落数: $($doc.segmentsCount)" -ForegroundColor Green
|
||||||
|
Write-Host " ✓ Token数: $($doc.tokensCount)" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($doc.status -eq "error" -and $doc.errorMessage) {
|
||||||
|
Write-Host " ✗ 错误: $($doc.errorMessage)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " (该知识库暂无文档)`n"
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
Write-Host " ❌ 获取文档列表失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ("-" * 60)
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
Write-Host "❌ 查询失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
64
backend/check-documents.js
Normal file
64
backend/check-documents.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// 查询文档状态脚本
|
||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
async function checkDocuments() {
|
||||||
|
try {
|
||||||
|
console.log('\n📋 查询最近上传的文档...\n');
|
||||||
|
|
||||||
|
const docs = await prisma.document.findMany({
|
||||||
|
orderBy: { uploadedAt: 'desc' },
|
||||||
|
take: 10,
|
||||||
|
include: {
|
||||||
|
knowledgeBase: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (docs.length === 0) {
|
||||||
|
console.log('❌ 没有找到任何文档');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`找到 ${docs.length} 个文档:\n`);
|
||||||
|
|
||||||
|
docs.forEach((doc, index) => {
|
||||||
|
const statusEmoji = {
|
||||||
|
'uploading': '📤',
|
||||||
|
'parsing': '🔄',
|
||||||
|
'indexing': '⚙️',
|
||||||
|
'completed': '✅',
|
||||||
|
'error': '❌'
|
||||||
|
}[doc.status] || '❓';
|
||||||
|
|
||||||
|
console.log(`${index + 1}. ${statusEmoji} ${doc.filename}`);
|
||||||
|
console.log(` 知识库: ${doc.knowledgeBase.name}`);
|
||||||
|
console.log(` 状态: ${doc.status}`);
|
||||||
|
console.log(` 大小: ${(doc.fileSizeBytes / 1024).toFixed(2)} KB`);
|
||||||
|
console.log(` 上传时间: ${doc.uploadedAt}`);
|
||||||
|
|
||||||
|
if (doc.status === 'completed') {
|
||||||
|
console.log(` ✓ 段落数: ${doc.segmentsCount || 0}`);
|
||||||
|
console.log(` ✓ Token数: ${doc.tokensCount || 0}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc.status === 'error' && doc.errorMessage) {
|
||||||
|
console.log(` ✗ 错误: ${doc.errorMessage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 查询失败:', error.message);
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDocuments();
|
||||||
|
|
||||||
@@ -8,12 +8,6 @@ import { agentRoutes } from './routes/agents.js';
|
|||||||
import { conversationRoutes } from './routes/conversations.js';
|
import { conversationRoutes } from './routes/conversations.js';
|
||||||
import knowledgeBaseRoutes from './routes/knowledgeBases.js';
|
import knowledgeBaseRoutes from './routes/knowledgeBases.js';
|
||||||
|
|
||||||
console.log('\n' + '='.repeat(60));
|
|
||||||
console.log('🔧 正在加载修复后的服务器配置...');
|
|
||||||
console.log('📅 修复版本: 2025-10-11 - CORS完整配置');
|
|
||||||
console.log('='.repeat(60) + '\n');
|
|
||||||
|
|
||||||
console.log('🚨🚨🚨 测试日志: 文件已加载 🚨🚨🚨');
|
|
||||||
|
|
||||||
// 全局处理BigInt序列化
|
// 全局处理BigInt序列化
|
||||||
(BigInt.prototype as any).toJSON = function() {
|
(BigInt.prototype as any).toJSON = function() {
|
||||||
@@ -54,10 +48,6 @@ await fastify.register(multipart, {
|
|||||||
});
|
});
|
||||||
console.log('✅ 文件上传插件已配置: 最大文件大小 10MB');
|
console.log('✅ 文件上传插件已配置: 最大文件大小 10MB');
|
||||||
|
|
||||||
// 添加请求日志钩子(用于调试)
|
|
||||||
fastify.addHook('onRequest', async (request, _reply) => {
|
|
||||||
console.log(`📥 ${request.method} ${request.url} - Origin: ${request.headers.origin || 'none'}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 健康检查路由
|
// 健康检查路由
|
||||||
fastify.get('/health', async () => {
|
fastify.get('/health', async () => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { prisma } from '../config/database.js';
|
|||||||
import { LLMFactory } from '../adapters/LLMFactory.js';
|
import { LLMFactory } from '../adapters/LLMFactory.js';
|
||||||
import { Message, ModelType, StreamChunk } from '../adapters/types.js';
|
import { Message, ModelType, StreamChunk } from '../adapters/types.js';
|
||||||
import { agentService } from './agentService.js';
|
import { agentService } from './agentService.js';
|
||||||
|
import * as knowledgeBaseService from './knowledgeBaseService.js';
|
||||||
|
|
||||||
interface CreateConversationData {
|
interface CreateConversationData {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -217,8 +218,44 @@ export class ConversationService {
|
|||||||
// 获取知识库上下文(如果有@知识库)
|
// 获取知识库上下文(如果有@知识库)
|
||||||
let knowledgeBaseContext = '';
|
let knowledgeBaseContext = '';
|
||||||
if (knowledgeBaseIds && knowledgeBaseIds.length > 0) {
|
if (knowledgeBaseIds && knowledgeBaseIds.length > 0) {
|
||||||
// TODO: 调用Dify RAG获取知识库上下文
|
const knowledgeResults: string[] = [];
|
||||||
knowledgeBaseContext = '相关文献内容...';
|
|
||||||
|
// 对每个知识库进行检索
|
||||||
|
for (const kbId of knowledgeBaseIds) {
|
||||||
|
try {
|
||||||
|
const searchResult = await knowledgeBaseService.searchKnowledgeBase(
|
||||||
|
userId,
|
||||||
|
kbId,
|
||||||
|
content,
|
||||||
|
3 // 每个知识库返回3个最相关的段落
|
||||||
|
);
|
||||||
|
|
||||||
|
// 格式化检索结果
|
||||||
|
if (searchResult.records && searchResult.records.length > 0) {
|
||||||
|
const kbInfo = await prisma.knowledgeBase.findUnique({
|
||||||
|
where: { id: kbId },
|
||||||
|
select: { name: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
knowledgeResults.push(
|
||||||
|
`【知识库:${kbInfo?.name || '未命名'}】\n` +
|
||||||
|
searchResult.records
|
||||||
|
.map((record: any, index: number) => {
|
||||||
|
const score = (record.score * 100).toFixed(1);
|
||||||
|
return `${index + 1}. [相关度${score}%] ${record.segment.content}`;
|
||||||
|
})
|
||||||
|
.join('\n\n')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to search knowledge base ${kbId}:`, error);
|
||||||
|
// 检索失败不阻止对话,继续处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (knowledgeResults.length > 0) {
|
||||||
|
knowledgeBaseContext = knowledgeResults.join('\n\n---\n\n');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组装上下文
|
// 组装上下文
|
||||||
@@ -299,8 +336,44 @@ export class ConversationService {
|
|||||||
// 获取知识库上下文(如果有@知识库)
|
// 获取知识库上下文(如果有@知识库)
|
||||||
let knowledgeBaseContext = '';
|
let knowledgeBaseContext = '';
|
||||||
if (knowledgeBaseIds && knowledgeBaseIds.length > 0) {
|
if (knowledgeBaseIds && knowledgeBaseIds.length > 0) {
|
||||||
// TODO: 调用Dify RAG获取知识库上下文
|
const knowledgeResults: string[] = [];
|
||||||
knowledgeBaseContext = '相关文献内容...';
|
|
||||||
|
// 对每个知识库进行检索
|
||||||
|
for (const kbId of knowledgeBaseIds) {
|
||||||
|
try {
|
||||||
|
const searchResult = await knowledgeBaseService.searchKnowledgeBase(
|
||||||
|
userId,
|
||||||
|
kbId,
|
||||||
|
content,
|
||||||
|
3 // 每个知识库返回3个最相关的段落
|
||||||
|
);
|
||||||
|
|
||||||
|
// 格式化检索结果
|
||||||
|
if (searchResult.records && searchResult.records.length > 0) {
|
||||||
|
const kbInfo = await prisma.knowledgeBase.findUnique({
|
||||||
|
where: { id: kbId },
|
||||||
|
select: { name: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
knowledgeResults.push(
|
||||||
|
`【知识库:${kbInfo?.name || '未命名'}】\n` +
|
||||||
|
searchResult.records
|
||||||
|
.map((record: any, index: number) => {
|
||||||
|
const score = (record.score * 100).toFixed(1);
|
||||||
|
return `${index + 1}. [相关度${score}%] ${record.segment.content}`;
|
||||||
|
})
|
||||||
|
.join('\n\n')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to search knowledge base ${kbId}:`, error);
|
||||||
|
// 检索失败不阻止对话,继续处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (knowledgeResults.length > 0) {
|
||||||
|
knowledgeBaseContext = knowledgeResults.join('\n\n---\n\n');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组装上下文
|
// 组装上下文
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import MessageList from '../components/chat/MessageList'
|
|||||||
import MessageInput from '../components/chat/MessageInput'
|
import MessageInput from '../components/chat/MessageInput'
|
||||||
import ModelSelector, { type ModelType } from '../components/chat/ModelSelector'
|
import ModelSelector, { type ModelType } from '../components/chat/ModelSelector'
|
||||||
import { useProjectStore } from '../stores/useProjectStore'
|
import { useProjectStore } from '../stores/useProjectStore'
|
||||||
|
import { useKnowledgeBaseStore } from '../stores/useKnowledgeBaseStore'
|
||||||
|
|
||||||
const AgentChatPage = () => {
|
const AgentChatPage = () => {
|
||||||
const { agentId } = useParams()
|
const { agentId } = useParams()
|
||||||
const { currentProject } = useProjectStore()
|
const { currentProject } = useProjectStore()
|
||||||
|
const { knowledgeBases, fetchKnowledgeBases } = useKnowledgeBaseStore()
|
||||||
|
|
||||||
// 智能体相关状态
|
// 智能体相关状态
|
||||||
const [agent, setAgent] = useState<AgentConfig | null>(null)
|
const [agent, setAgent] = useState<AgentConfig | null>(null)
|
||||||
@@ -27,9 +29,6 @@ const AgentChatPage = () => {
|
|||||||
const [sending, setSending] = useState(false)
|
const [sending, setSending] = useState(false)
|
||||||
const [streamingContent, setStreamingContent] = useState('')
|
const [streamingContent, setStreamingContent] = useState('')
|
||||||
|
|
||||||
// 知识库(预留)
|
|
||||||
const [knowledgeBases] = useState([])
|
|
||||||
|
|
||||||
// 加载智能体配置
|
// 加载智能体配置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAgent = async () => {
|
const fetchAgent = async () => {
|
||||||
@@ -56,6 +55,11 @@ const AgentChatPage = () => {
|
|||||||
fetchAgent()
|
fetchAgent()
|
||||||
}, [agentId])
|
}, [agentId])
|
||||||
|
|
||||||
|
// 加载知识库列表
|
||||||
|
useEffect(() => {
|
||||||
|
fetchKnowledgeBases()
|
||||||
|
}, [])
|
||||||
|
|
||||||
// 创建或加载对话
|
// 创建或加载对话
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initConversation = async () => {
|
const initConversation = async () => {
|
||||||
@@ -267,4 +271,3 @@ const AgentChatPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default AgentChatPage
|
export default AgentChatPage
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user