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
+300
View File
@@ -0,0 +1,300 @@
/**
* Dify Dataset 客户端 API 模块
*
* 提供浏览器端调用 Dify 知识库 API 的函数
* 通过 Remix API Routes 代理请求
*
* @module api/dify-dataset/client
*/
import axios from 'axios';
import type {
DatasetsResponse,
DocumentsResponse,
SegmentsResponse,
Document,
OperationResult,
} from './types';
// ============================================================================
// 基础配置
// ============================================================================
/**
* API 基础 URL
* 指向 Remix API Routes/api/dataset/*
*/
const API_URL = '/api/dataset';
// ============================================================================
// 知识库 API
// ============================================================================
/**
* 获取知识库列表
*
* @param page - 页码,默认 1
* @param limit - 每页数量,默认 20
* @returns 知识库列表响应
*/
export async function fetchDatasets(
page: number = 1,
limit: number = 20
): Promise<DatasetsResponse> {
const params = new URLSearchParams({
page: page.toString(),
limit: limit.toString(),
});
const response = await axios.get<DatasetsResponse>(
`${API_URL}/datasets?${params}`,
{ withCredentials: true }
);
return response.data;
}
/**
* 获取单个知识库详情
*
* @param datasetId - 知识库 ID
* @returns 知识库详情
*/
export async function fetchDataset(datasetId: string): Promise<any> {
const response = await axios.get(
`${API_URL}/datasets/${datasetId}`,
{ withCredentials: true }
);
return response.data;
}
// ============================================================================
// 文档 API
// ============================================================================
/**
* 获取知识库文档列表
*
* @param datasetId - 知识库 ID
* @param page - 页码,默认 1
* @param limit - 每页数量,默认 20
* @param keyword - 搜索关键词
* @returns 文档列表响应
*/
export async function fetchDocuments(
datasetId: string,
page: number = 1,
limit: number = 20,
keyword?: string
): Promise<DocumentsResponse> {
const params = new URLSearchParams({
page: page.toString(),
limit: limit.toString(),
});
if (keyword) {
params.append('keyword', keyword);
}
console.log('[Dataset Client] 获取文档列表:', { datasetId, page, limit, keyword });
const response = await axios.get<DocumentsResponse>(
`${API_URL}/datasets/${datasetId}/documents?${params}`,
{ withCredentials: true }
);
return response.data;
}
/**
* 获取单个文档详情
*
* @param datasetId - 知识库 ID
* @param documentId - 文档 ID
* @returns 文档详情
*/
export async function fetchDocument(
datasetId: string,
documentId: string
): Promise<Document> {
const response = await axios.get<Document>(
`${API_URL}/datasets/${datasetId}/documents/${documentId}`,
{ withCredentials: true }
);
return response.data;
}
/**
* 删除文档
*
* @param datasetId - 知识库 ID
* @param documentId - 文档 ID
* @returns 操作结果
*/
export async function deleteDocument(
datasetId: string,
documentId: string
): Promise<OperationResult> {
console.log('[Dataset Client] 删除文档:', { datasetId, documentId });
const response = await axios.delete<OperationResult>(
`${API_URL}/datasets/${datasetId}/documents/${documentId}`,
{ withCredentials: true }
);
return response.data;
}
/**
* 启用/禁用文档
*
* @param datasetId - 知识库 ID
* @param documentId - 文档 ID
* @param enabled - 是否启用
* @returns 操作结果
*/
export async function toggleDocumentStatus(
datasetId: string,
documentId: string,
enabled: boolean
): Promise<OperationResult> {
console.log('[Dataset Client] 切换文档状态:', { datasetId, documentId, enabled });
const response = await axios.patch<OperationResult>(
`${API_URL}/datasets/${datasetId}/documents/${documentId}/status`,
{ enabled },
{
headers: { 'Content-Type': 'application/json' },
withCredentials: true,
}
);
return response.data;
}
// ============================================================================
// 文档分段 API
// ============================================================================
/**
* 获取文档分段列表
*
* @param datasetId - 知识库 ID
* @param documentId - 文档 ID
* @param page - 页码,默认 1
* @param limit - 每页数量,默认 20
* @param keyword - 搜索关键词
* @returns 分段列表响应
*/
export async function fetchSegments(
datasetId: string,
documentId: string,
page: number = 1,
limit: number = 20,
keyword?: string
): Promise<SegmentsResponse> {
const params = new URLSearchParams({
page: page.toString(),
limit: limit.toString(),
});
if (keyword) {
params.append('keyword', keyword);
}
console.log('[Dataset Client] 获取分段列表:', { datasetId, documentId, page, limit });
const response = await axios.get<SegmentsResponse>(
`${API_URL}/datasets/${datasetId}/documents/${documentId}/segments?${params}`,
{ withCredentials: true }
);
return response.data;
}
/**
* 删除分段
*
* @param datasetId - 知识库 ID
* @param documentId - 文档 ID
* @param segmentId - 分段 ID
* @returns 操作结果
*/
export async function deleteSegment(
datasetId: string,
documentId: string,
segmentId: string
): Promise<OperationResult> {
console.log('[Dataset Client] 删除分段:', { datasetId, documentId, segmentId });
const response = await axios.delete<OperationResult>(
`${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`,
{ withCredentials: true }
);
return response.data;
}
/**
* 启用/禁用分段
*
* @param datasetId - 知识库 ID
* @param documentId - 文档 ID
* @param segmentId - 分段 ID
* @param enabled - 是否启用
* @returns 操作结果
*/
export async function toggleSegmentStatus(
datasetId: string,
documentId: string,
segmentId: string,
enabled: boolean
): Promise<OperationResult> {
const response = await axios.patch<OperationResult>(
`${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/status`,
{ enabled },
{
headers: { 'Content-Type': 'application/json' },
withCredentials: true,
}
);
return response.data;
}
// ============================================================================
// 文件上传 API
// ============================================================================
/**
* 上传文件到知识库
*
* @param datasetId - 知识库 ID
* @param file - 文件对象
* @param onProgress - 上传进度回调
* @returns 创建的文档信息
*/
export async function uploadDocument(
datasetId: string,
file: File,
onProgress?: (percent: number) => void
): Promise<any> {
const formData = new FormData();
formData.append('file', file);
formData.append('data', JSON.stringify({
indexing_technique: 'high_quality',
process_rule: {
mode: 'automatic',
},
}));
console.log('[Dataset Client] 上传文档:', { datasetId, fileName: file.name });
const response = await axios.post(
`${API_URL}/datasets/${datasetId}/documents/create-by-file`,
formData,
{
withCredentials: true,
onUploadProgress: (progressEvent) => {
if (progressEvent.total && onProgress) {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onProgress(percent);
}
},
}
);
return response.data;
}
+36
View File
@@ -0,0 +1,36 @@
/**
* Dify Dataset API 模块统一导出
*
* @module api/dify-dataset
*/
// 类型导出
export type {
Dataset,
DatasetsResponse,
Document,
DocumentsResponse,
Segment,
SegmentsResponse,
IndexingStatus,
OperationResult,
CreateDocumentResponse,
UploadProgress,
} from './types';
// 客户端 API 导出
export {
// 知识库
fetchDatasets,
fetchDataset,
// 文档
fetchDocuments,
fetchDocument,
deleteDocument,
toggleDocumentStatus,
uploadDocument,
// 分段
fetchSegments,
deleteSegment,
toggleSegmentStatus,
} from './client';
+167
View File
@@ -0,0 +1,167 @@
/**
* Dify Dataset API 类型定义
*
* @module api/dify-dataset/types
*/
// ============================================================================
// 知识库类型
// ============================================================================
/**
* 知识库信息
*/
export interface Dataset {
id: string;
name: string;
description: string;
permission: 'only_me' | 'all_team_members';
data_source_type: 'upload_file' | 'notion_import' | 'website_crawl';
indexing_technique: 'high_quality' | 'economy';
app_count: number;
document_count: number;
word_count: number;
created_by: string;
created_at: number;
updated_by: string;
updated_at: number;
}
/**
* 知识库列表响应
*/
export interface DatasetsResponse {
data: Dataset[];
has_more: boolean;
limit: number;
total: number;
page: number;
}
// ============================================================================
// 文档类型
// ============================================================================
/**
* 文档索引状态
*/
export type IndexingStatus =
| 'waiting'
| 'parsing'
| 'cleaning'
| 'splitting'
| 'indexing'
| 'paused'
| 'error'
| 'completed';
/**
* 文档信息
*/
export interface Document {
id: string;
position: number;
data_source_type: 'upload_file' | 'notion_import' | 'website_crawl';
data_source_info: {
upload_file_id?: string;
notion_page_id?: string;
website_url?: string;
};
dataset_process_rule_id: string;
name: string;
created_from: string;
created_by: string;
created_at: number;
tokens: number;
indexing_status: IndexingStatus;
error?: string;
enabled: boolean;
disabled_at?: number;
disabled_by?: string;
archived: boolean;
display_status: string;
word_count: number;
hit_count: number;
doc_form: string;
}
/**
* 文档列表响应
*/
export interface DocumentsResponse {
data: Document[];
has_more: boolean;
limit: number;
total: number;
page: number;
}
// ============================================================================
// 文档分段类型
// ============================================================================
/**
* 文档分段
*/
export interface Segment {
id: string;
position: number;
document_id: string;
content: string;
answer?: string;
word_count: number;
tokens: number;
keywords: string[];
index_node_id: string;
index_node_hash: string;
hit_count: number;
enabled: boolean;
disabled_at?: number;
disabled_by?: string;
status: 'waiting' | 'completed' | 'error' | 'indexing';
created_by: string;
created_at: number;
indexing_at?: number;
completed_at?: number;
error?: string;
stopped_at?: number;
}
/**
* 分段列表响应
*/
export interface SegmentsResponse {
data: Segment[];
has_more: boolean;
limit: number;
total: number;
}
// ============================================================================
// 操作响应类型
// ============================================================================
/**
* 通用操作结果
*/
export interface OperationResult {
result: 'success' | 'error';
message?: string;
}
/**
* 创建文档响应
*/
export interface CreateDocumentResponse {
document: Document;
batch: string;
}
/**
* 文档上传进度
*/
export interface UploadProgress {
loaded: number;
total: number;
percent: number;
}