feat:前端新增初版知识库管理页面

This commit is contained in:
PingChuan
2025-11-30 19:27:01 +08:00
parent 9614899171
commit c94cc00138
40 changed files with 3034 additions and 1024 deletions
+1 -2
View File
@@ -1,6 +1,5 @@
import { type ActionFunctionArgs, type LoaderFunctionArgs } from '@remix-run/node';
import { difyClient } from '~/api/dify/client.server';
import { getSessionInfo } from '../utils/session.server';
import { difyClient } from '~/api/dify-chat/client.server';
/**
* GET /api/chat-messages - 获取会话消息列表
+2 -2
View File
@@ -1,6 +1,6 @@
import { json, type ActionFunctionArgs } from '@remix-run/node';
import { difyClient } from '~/api/dify/client.server';
import { getSessionInfo, commitSession } from '../utils/session.server';
import { difyClient } from '~/api/dify-chat/client.server';
import { commitSession, getSessionInfo } from '../utils/session.server';
export async function action({ request, params }: ActionFunctionArgs) {
try {
+2 -2
View File
@@ -1,6 +1,6 @@
import { json, type ActionFunctionArgs } from '@remix-run/node';
import { difyClient } from '~/api/dify/client.server';
import { getSessionInfo, commitSession } from '../utils/session.server';
import { difyClient } from '~/api/dify-chat/client.server';
import { commitSession, getSessionInfo } from '../utils/session.server';
export async function action({ request, params }: ActionFunctionArgs) {
try {
+2 -2
View File
@@ -1,6 +1,6 @@
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import { difyClient } from '~/api/dify/client.server';
import { getSessionInfo, commitSession } from '../utils/session.server';
import { difyClient } from '~/api/dify-chat/client.server';
import { commitSession, getSessionInfo } from '../utils/session.server';
export async function loader({ request }: LoaderFunctionArgs) {
try {
@@ -0,0 +1,56 @@
import { type ActionFunctionArgs } from '@remix-run/node';
import { API_BASE_URL } from '~/config/api-config';
/**
* PATCH /api/dataset/datasets/:datasetId/documents/:documentId/status - 切换文档状态
*/
export async function action({ request, params }: ActionFunctionArgs) {
try {
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
if (!frontendJWT) {
return new Response(
JSON.stringify({ error: 'JWT认证失败,请重新登录' }),
{ status: 401, headers: { 'Content-Type': 'application/json' } }
);
}
const { datasetId, documentId } = params;
if (!datasetId || !documentId) {
return new Response(
JSON.stringify({ error: '缺少必要参数' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
const body = await request.json();
const { enabled } = body;
console.log('[API] Toggle Document Status:', { datasetId, documentId, enabled });
const apiUrl = `${API_BASE_URL}/dify-dataset/datasets/${datasetId}/documents/${documentId}/status`;
const response = await fetch(apiUrl, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
body: JSON.stringify({ enabled }),
});
const data = await response.json();
return new Response(JSON.stringify(data), {
status: response.status,
headers: { 'Content-Type': 'application/json' },
});
} catch (error: any) {
console.error('[API] Toggle Document Status - Error:', error.message);
return new Response(
JSON.stringify({ error: error.message || 'Failed to toggle status' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
}
@@ -0,0 +1,118 @@
import { type LoaderFunctionArgs, type ActionFunctionArgs } from '@remix-run/node';
import { API_BASE_URL } from '~/config/api-config';
/**
* GET /api/dataset/datasets/:datasetId/documents/:documentId - 获取文档详情
*/
export async function loader({ request, params }: LoaderFunctionArgs) {
try {
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
if (!frontendJWT) {
return new Response(
JSON.stringify({ error: 'JWT认证失败,请重新登录' }),
{ status: 401, headers: { 'Content-Type': 'application/json' } }
);
}
const { datasetId, documentId } = params;
if (!datasetId || !documentId) {
return new Response(
JSON.stringify({ error: '缺少必要参数' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
console.log('[API] Document Detail:', { datasetId, documentId });
const apiUrl = `${API_BASE_URL}/dify-dataset/datasets/${datasetId}/documents/${documentId}`;
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
});
const data = await response.json();
return new Response(JSON.stringify(data), {
status: response.status,
headers: { 'Content-Type': 'application/json' },
});
} catch (error: any) {
console.error('[API] Document Detail - Error:', error.message);
return new Response(
JSON.stringify({ error: error.message || 'Failed to get document' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
}
/**
* DELETE /api/dataset/datasets/:datasetId/documents/:documentId - 删除文档
*/
export async function action({ request, params }: ActionFunctionArgs) {
try {
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
if (!frontendJWT) {
return new Response(
JSON.stringify({ error: 'JWT认证失败,请重新登录' }),
{ status: 401, headers: { 'Content-Type': 'application/json' } }
);
}
const { datasetId, documentId } = params;
if (!datasetId || !documentId) {
return new Response(
JSON.stringify({ error: '缺少必要参数' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
const method = request.method;
if (method === 'DELETE') {
console.log('[API] Delete Document:', { datasetId, documentId });
const apiUrl = `${API_BASE_URL}/dify-dataset/datasets/${datasetId}/documents/${documentId}`;
const response = await fetch(apiUrl, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
});
// 处理 204 No Content
if (response.status === 204) {
return new Response(
JSON.stringify({ result: 'success' }),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
);
}
const data = await response.json();
return new Response(JSON.stringify(data), {
status: response.status,
headers: { 'Content-Type': 'application/json' },
});
}
return new Response(
JSON.stringify({ error: 'Method not allowed' }),
{ status: 405, headers: { 'Content-Type': 'application/json' } }
);
} catch (error: any) {
console.error('[API] Document Action - Error:', error.message);
return new Response(
JSON.stringify({ error: error.message || 'Failed to process request' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
}
@@ -0,0 +1,119 @@
import { type LoaderFunctionArgs, type ActionFunctionArgs } from '@remix-run/node';
import { API_BASE_URL } from '~/config/api-config';
/**
* GET /api/dataset/datasets/:datasetId/documents - 获取文档列表
*/
export async function loader({ request, params }: LoaderFunctionArgs) {
try {
// 获取用户会话信息和 JWT
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
if (!frontendJWT) {
return new Response(
JSON.stringify({ error: 'JWT认证失败,请重新登录' }),
{ status: 401, headers: { 'Content-Type': 'application/json' } }
);
}
const { datasetId } = params;
if (!datasetId) {
return new Response(
JSON.stringify({ error: '缺少 datasetId 参数' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
// 获取查询参数
const url = new URL(request.url);
const page = url.searchParams.get('page') || '1';
const limit = url.searchParams.get('limit') || '20';
const keyword = url.searchParams.get('keyword') || '';
console.log('[API] Documents List:', { datasetId, page, limit, keyword });
// 构建查询参数
const queryParams = new URLSearchParams({ page, limit });
if (keyword) queryParams.append('keyword', keyword);
// 转发请求到 FastAPI
const apiUrl = `${API_BASE_URL}/dify-dataset/datasets/${datasetId}/documents?${queryParams}`;
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
});
const data = await response.json();
return new Response(JSON.stringify(data), {
status: response.status,
headers: { 'Content-Type': 'application/json' },
});
} catch (error: any) {
console.error('[API] Documents List - Error:', error.message);
return new Response(
JSON.stringify({ error: error.message || 'Failed to get documents' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
}
/**
* POST /api/dataset/datasets/:datasetId/documents - 创建文档(上传文件)
*/
export async function action({ request, params }: ActionFunctionArgs) {
try {
// 获取用户会话信息和 JWT
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
if (!frontendJWT) {
return new Response(
JSON.stringify({ error: 'JWT认证失败,请重新登录' }),
{ status: 401, headers: { 'Content-Type': 'application/json' } }
);
}
const { datasetId } = params;
if (!datasetId) {
return new Response(
JSON.stringify({ error: '缺少 datasetId 参数' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
// 获取表单数据
const formData = await request.formData();
console.log('[API] Upload Document:', { datasetId });
// 转发请求到 FastAPI
const apiUrl = `${API_BASE_URL}/dify-dataset/datasets/${datasetId}/documents/create-by-file`;
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${frontendJWT}`,
},
body: formData,
});
const data = await response.json();
return new Response(JSON.stringify(data), {
status: response.status,
headers: { 'Content-Type': 'application/json' },
});
} catch (error: any) {
console.error('[API] Upload Document - Error:', error.message);
return new Response(
JSON.stringify({ error: error.message || 'Failed to upload document' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
}
+60
View File
@@ -0,0 +1,60 @@
import { type LoaderFunctionArgs } from '@remix-run/node';
import { API_BASE_URL } from '~/config/api-config';
/**
* GET /api/dataset/datasets - 获取知识库列表
*/
export async function loader({ request }: LoaderFunctionArgs) {
try {
// 获取用户会话信息和 JWT
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
// 检查 JWT 是否存在
if (!frontendJWT) {
console.error('[API] Dataset List - JWT不存在');
return new Response(
JSON.stringify({ error: 'JWT认证失败,请重新登录' }),
{
status: 401,
headers: { 'Content-Type': 'application/json' },
}
);
}
// 获取查询参数
const url = new URL(request.url);
const page = url.searchParams.get('page') || '1';
const limit = url.searchParams.get('limit') || '20';
console.log('[API] Dataset List - 获取知识库列表:', { page, limit });
// 转发请求到 FastAPI
const apiUrl = `${API_BASE_URL}/dify-dataset/datasets?page=${page}&limit=${limit}`;
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${frontendJWT}`,
},
});
const data = await response.json();
console.log('[API] Dataset List - Success');
return new Response(JSON.stringify(data), {
status: response.status,
headers: { 'Content-Type': 'application/json' },
});
} catch (error: any) {
console.error('[API] Dataset List - Error:', error.message);
return new Response(
JSON.stringify({ error: error.message || 'Failed to get datasets' }),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
);
}
}
@@ -1,5 +1,5 @@
import { type ActionFunctionArgs } from '@remix-run/node';
import { difyClient } from '~/api/dify/client.server';
import { difyClient } from '~/api/dify-chat/client.server';
/**
* POST /api/messages/:messageId/feedbacks - 提交消息反馈
+2 -2
View File
@@ -1,6 +1,6 @@
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import { difyClient } from '~/api/dify/client.server';
import { getSessionInfo, commitSession } from '../utils/session.server';
import { difyClient } from '~/api/dify-chat/client.server';
import { commitSession, getSessionInfo } from '../utils/session.server';
export async function loader({ request }: LoaderFunctionArgs) {
try {
+1 -1
View File
@@ -1,6 +1,6 @@
import { json } from "@remix-run/node";
import { type MetaFunction } from "@remix-run/node";
import Chat from "~/components/chat";
import Chat from "~/components/dify-chat";
import chatIndexStyles from "~/styles/components/chat-with-llm/index.css?url";
import chatMessageStyles from "~/styles/components/chat-with-llm/chat-message.css?url";
import chatInputStyles from "~/styles/components/chat-with-llm/chat-input.css?url";
+26
View File
@@ -0,0 +1,26 @@
import DatasetManager from "~/components/dify-dataset-manager";
import datasetManagerStyles from "~/styles/components/dify-dataset-manager/index.css?url";
import sidebarStyles from "~/styles/components/dify-dataset-manager/sidebar.css?url";
import documentListStyles from "~/styles/components/dify-dataset-manager/document-list.css?url";
/**
* 注册样式
*/
export function links() {
return [
{ rel: "stylesheet", href: datasetManagerStyles },
{ rel: "stylesheet", href: sidebarStyles },
{ rel: "stylesheet", href: documentListStyles },
];
}
/**
* 知识库管理首页
*/
export default function DatasetManagerIndex() {
return (
<div className="dataset-manager-page" style={{ height: '93vh', padding: '16px' }}>
<DatasetManager />
</div>
);
}
+20
View File
@@ -0,0 +1,20 @@
import { Outlet } from "@remix-run/react";
import type { MetaFunction } from "@remix-run/node";
export const meta: MetaFunction = () => {
return [
{ title: "知识库管理 - 智能审核系统" },
{ name: "description", content: "Dify 知识库文档管理" },
];
};
export const handle = {
breadcrumb: "知识库管理",
};
/**
* 知识库管理布局路由
*/
export default function DatasetManagerLayout() {
return <Outlet />;
}