diff --git a/app/api/dify-dataset/api/datasetApi.ts b/app/api/dify-dataset/api/datasetApi.ts new file mode 100644 index 0000000..036a788 --- /dev/null +++ b/app/api/dify-dataset/api/datasetApi.ts @@ -0,0 +1,78 @@ +/** + * Dify Dataset 知识库 API 模块 + * + * 提供浏览器端调用 Dify 知识库管理 API 的函数 + * + * @module api/dify-dataset/api/datasetApi + */ + +import axios from 'axios'; +import type { Dataset, DatasetsResponse } from '../type'; + +/** + * API 基础 URL + */ +const API_URL = '/api/dataset'; + +/** + * 获取知识库列表 + * + * @param page - 页码,默认 1 + * @param limit - 每页数量,默认 20 + * @returns 知识库列表响应 + */ +export async function fetchDatasets( + page: number = 1, + limit: number = 20 +): Promise { + const params = new URLSearchParams({ + page: page.toString(), + limit: limit.toString(), + }); + + const response = await axios.get( + `${API_URL}/datasets?${params}`, + { withCredentials: true } + ); + return response.data; +} + +/** + * 获取单个知识库详情 + * + * @param datasetId - 知识库 ID + * @returns 知识库详情 + */ +export async function fetchDataset(datasetId: string): Promise { + const response = await axios.get( + `${API_URL}/datasets/${datasetId}`, + { withCredentials: true } + ); + return response.data; +} + +/** + * 更新知识库名称 + * + * 注意:仅允许修改知识库名称,其他字段不开放修改 + * + * @param datasetId - 知识库 ID + * @param name - 新的知识库名称 + * @returns 更新后的知识库详情 + */ +export async function updateDatasetName( + datasetId: string, + name: string +): Promise { + console.log('[Dataset Client] 更新知识库名称:', { datasetId, name }); + + const response = await axios.patch( + `${API_URL}/datasets/${datasetId}`, + { name }, + { + headers: { 'Content-Type': 'application/json' }, + withCredentials: true, + } + ); + return response.data; +} diff --git a/app/api/dify-dataset/client.ts b/app/api/dify-dataset/api/documentApi.ts similarity index 54% rename from app/api/dify-dataset/client.ts rename to app/api/dify-dataset/api/documentApi.ts index ce0d09e..d147edf 100644 --- a/app/api/dify-dataset/client.ts +++ b/app/api/dify-dataset/api/documentApi.ts @@ -1,104 +1,25 @@ /** - * Dify Dataset 客户端 API 模块 + * Dify Dataset 文档 API 模块 * - * 提供浏览器端调用 Dify 知识库 API 的函数 - * 通过 Remix API Routes 代理请求 + * 提供浏览器端调用 Dify 文档管理 API 的函数 * - * @module api/dify-dataset/client + * @module api/dify-dataset/api/documentApi */ import axios from 'axios'; import type { - Dataset, - DatasetsResponse, - DocumentsResponse, - SegmentsResponse, Document, - OperationResult, + DocumentsResponse, IndexingStatusResponse, UploadFileInfo, - UpdateDatasetRequest, -} from './types'; - -// ============================================================================ -// 基础配置 -// ============================================================================ + OperationResult, +} from '../type'; /** * 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 { - const params = new URLSearchParams({ - page: page.toString(), - limit: limit.toString(), - }); - - const response = await axios.get( - `${API_URL}/datasets?${params}`, - { withCredentials: true } - ); - return response.data; -} - -/** - * 获取单个知识库详情 - * - * @param datasetId - 知识库 ID - * @returns 知识库详情 - */ -export async function fetchDataset(datasetId: string): Promise { - const response = await axios.get( - `${API_URL}/datasets/${datasetId}`, - { withCredentials: true } - ); - return response.data; -} - -/** - * 更新知识库详情 - * - * @param datasetId - 知识库 ID - * @param data - 更新数据 - * @returns 更新后的知识库详情 - */ -export async function updateDataset( - datasetId: string, - data: UpdateDatasetRequest -): Promise { - console.log('[Dataset Client] 更新知识库:', { datasetId, data }); - - const response = await axios.patch( - `${API_URL}/datasets/${datasetId}`, - data, - { - headers: { 'Content-Type': 'application/json' }, - withCredentials: true, - } - ); - return response.data; -} - -// ============================================================================ -// 文档 API -// ============================================================================ - /** * 获取知识库文档列表 * @@ -199,101 +120,6 @@ export async function toggleDocumentStatus( 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 { - 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( - `${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 { - console.log('[Dataset Client] 删除分段:', { datasetId, documentId, segmentId }); - - const response = await axios.delete( - `${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, - { withCredentials: true } - ); - return response.data; -} - -/** - * 启用/禁用分段 - * Dify API: POST /datasets/{dataset_id}/documents/{document_id}/segments/{segment_id} - * 通过更新分段的方式来切换状态 - * - * @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 { - console.log('[Dataset Client] 切换分段状态:', { datasetId, documentId, segmentId, enabled }); - - const response = await axios.post( - `${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, - { segment: { enabled } }, - { - headers: { 'Content-Type': 'application/json' }, - withCredentials: true, - } - ); - return response.data; -} - -// ============================================================================ -// 文件上传 API -// ============================================================================ - /** * 上传文件到知识库 * @@ -373,3 +199,56 @@ export async function fetchUploadFileInfo( ); return response.data; } + +/** + * 文档处理规则配置 + */ +export interface ProcessRule { + mode: 'automatic' | 'custom'; + rules?: { + pre_processing_rules?: Array<{ + id: 'remove_extra_spaces' | 'remove_urls_emails'; + enabled: boolean; + }>; + segmentation?: { + separator: string; + max_tokens: number; + }; + }; +} + +/** + * 更新文档设置参数 + */ +export interface UpdateDocumentSettings { + indexing_technique?: 'high_quality' | 'economy'; + process_rule?: ProcessRule; +} + +/** + * 更新文档设置并重新处理 + * 注意:Dify API 不直接支持修改已有文档的分段设置 + * 此函数尝试通过更新接口应用新设置 + * + * @param datasetId - 知识库 ID + * @param documentId - 文档 ID + * @param settings - 更新设置 + * @returns 操作结果 + */ +export async function updateDocumentWithSettings( + datasetId: string, + documentId: string, + settings: UpdateDocumentSettings +): Promise { + console.log('[Dataset Client] 更新文档设置:', { datasetId, documentId, settings }); + + const response = await axios.post( + `${API_URL}/datasets/${datasetId}/documents/${documentId}/update-settings`, + settings, + { + headers: { 'Content-Type': 'application/json' }, + withCredentials: true, + } + ); + return response.data; +} diff --git a/app/api/dify-dataset/api/index.ts b/app/api/dify-dataset/api/index.ts new file mode 100644 index 0000000..3e69d9e --- /dev/null +++ b/app/api/dify-dataset/api/index.ts @@ -0,0 +1,38 @@ +/** + * Dify Dataset API 客户端统一导出 + * + * @module api/dify-dataset/api + */ + +// 知识库 API +export { + fetchDatasets, + fetchDataset, + updateDatasetName, +} from './datasetApi'; + +// 文档 API +export { + fetchDocuments, + fetchDocument, + deleteDocument, + toggleDocumentStatus, + uploadDocument, + fetchIndexingStatus, + fetchUploadFileInfo, +} from './documentApi'; + +// 分段、子分段、检索 API +export { + fetchSegments, + fetchSegment, + createSegments, + updateSegment, + deleteSegment, + toggleSegmentStatus, + fetchChildChunks, + createChildChunk, + updateChildChunk, + deleteChildChunk, + retrieveDataset, +} from './segmentApi'; diff --git a/app/api/dify-dataset/api/segmentApi.ts b/app/api/dify-dataset/api/segmentApi.ts new file mode 100644 index 0000000..0761e06 --- /dev/null +++ b/app/api/dify-dataset/api/segmentApi.ts @@ -0,0 +1,359 @@ +/** + * Dify Dataset 分段、子分段、检索 API 模块 + * + * 提供浏览器端调用 Dify 分段管理和检索 API 的函数 + * + * @module api/dify-dataset/api/segmentApi + */ + +import axios from 'axios'; +import type { + Segment, + SegmentsResponse, + CreateSegmentRequest, + UpdateSegmentRequest, + CreateSegmentsResponse, + ChildChunk, + ChildChunksResponse, + CreateChildChunkResponse, + RetrieveRequest, + RetrieveResponse, + OperationResult, +} from '../type'; + +/** + * API 基础 URL + */ +const API_URL = '/api/dataset'; + +// ============================================================================ +// 分段 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 { + 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( + `${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 fetchSegment( + datasetId: string, + documentId: string, + segmentId: string +): Promise { + console.log('[Dataset Client] 获取分段详情:', { datasetId, documentId, segmentId }); + + const response = await axios.get<{ data: Segment }>( + `${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, + { withCredentials: true } + ); + return response.data.data; +} + +/** + * 新增分段(批量) + * Dify API: POST /datasets/{dataset_id}/documents/{document_id}/segments + * + * @param datasetId - 知识库 ID + * @param documentId - 文档 ID + * @param segments - 分段列表 + * @returns 创建的分段列表 + */ +export async function createSegments( + datasetId: string, + documentId: string, + segments: CreateSegmentRequest[] +): Promise { + console.log('[Dataset Client] 新增分段:', { datasetId, documentId, count: segments.length }); + + const response = await axios.post( + `${API_URL}/datasets/${datasetId}/documents/${documentId}/segments`, + { segments }, + { + headers: { 'Content-Type': 'application/json' }, + withCredentials: true, + } + ); + return response.data; +} + +/** + * 更新分段内容 + * Dify API: POST /datasets/{dataset_id}/documents/{document_id}/segments/{segment_id} + * + * @param datasetId - 知识库 ID + * @param documentId - 文档 ID + * @param segmentId - 分段 ID + * @param segment - 更新内容 + * @returns 更新后的分段 + */ +export async function updateSegment( + datasetId: string, + documentId: string, + segmentId: string, + segment: UpdateSegmentRequest +): Promise<{ data: Segment; doc_form: string }> { + console.log('[Dataset Client] 更新分段:', { datasetId, documentId, segmentId, segment }); + + const response = await axios.post<{ data: Segment; doc_form: string }>( + `${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, + { segment }, + { + headers: { 'Content-Type': 'application/json' }, + 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 { + console.log('[Dataset Client] 删除分段:', { datasetId, documentId, segmentId }); + + const response = await axios.delete( + `${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, + { withCredentials: true } + ); + return response.data; +} + +/** + * 启用/禁用分段 + * Dify API: POST /datasets/{dataset_id}/documents/{document_id}/segments/{segment_id} + * 通过更新分段的方式来切换状态 + * + * @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 { + console.log('[Dataset Client] 切换分段状态:', { datasetId, documentId, segmentId, enabled }); + + const response = await axios.post( + `${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, + { segment: { enabled } }, + { + headers: { 'Content-Type': 'application/json' }, + withCredentials: true, + } + ); + return response.data; +} + +// ============================================================================ +// 子分段 API(父子模式) +// ============================================================================ + +/** + * 获取子分段列表 + * Dify API: GET /datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks + * + * @param datasetId - 知识库 ID + * @param documentId - 文档 ID + * @param segmentId - 分段 ID + * @param page - 页码 + * @param limit - 每页数量 + * @param keyword - 搜索关键词 + * @returns 子分段列表响应 + */ +export async function fetchChildChunks( + datasetId: string, + documentId: string, + segmentId: string, + page: number = 1, + limit: number = 20, + keyword?: string +): Promise { + const params = new URLSearchParams({ + page: page.toString(), + limit: limit.toString(), + }); + + if (keyword) { + params.append('keyword', keyword); + } + + console.log('[Dataset Client] 获取子分段列表:', { datasetId, documentId, segmentId, page, limit }); + + const response = await axios.get( + `${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks?${params}`, + { withCredentials: true } + ); + return response.data; +} + +/** + * 新增子分段 + * Dify API: POST /datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks + * + * @param datasetId - 知识库 ID + * @param documentId - 文档 ID + * @param segmentId - 分段 ID + * @param content - 子分段内容 + * @returns 创建的子分段 + */ +export async function createChildChunk( + datasetId: string, + documentId: string, + segmentId: string, + content: string +): Promise { + console.log('[Dataset Client] 新增子分段:', { datasetId, documentId, segmentId }); + + const response = await axios.post( + `${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks`, + { content }, + { + headers: { 'Content-Type': 'application/json' }, + withCredentials: true, + } + ); + return response.data; +} + +/** + * 更新子分段 + * Dify API: PATCH /datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id} + * + * @param datasetId - 知识库 ID + * @param documentId - 文档 ID + * @param segmentId - 分段 ID + * @param childChunkId - 子分段 ID + * @param content - 更新内容 + * @returns 更新后的子分段 + */ +export async function updateChildChunk( + datasetId: string, + documentId: string, + segmentId: string, + childChunkId: string, + content: string +): Promise { + console.log('[Dataset Client] 更新子分段:', { datasetId, documentId, segmentId, childChunkId }); + + const response = await axios.patch<{ data: ChildChunk }>( + `${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks/${childChunkId}`, + { content }, + { + headers: { 'Content-Type': 'application/json' }, + withCredentials: true, + } + ); + return response.data.data; +} + +/** + * 删除子分段 + * Dify API: DELETE /datasets/{dataset_id}/documents/{document_id}/segments/{segment_id}/child_chunks/{child_chunk_id} + * + * @param datasetId - 知识库 ID + * @param documentId - 文档 ID + * @param segmentId - 分段 ID + * @param childChunkId - 子分段 ID + * @returns 操作结果 + */ +export async function deleteChildChunk( + datasetId: string, + documentId: string, + segmentId: string, + childChunkId: string +): Promise { + console.log('[Dataset Client] 删除子分段:', { datasetId, documentId, segmentId, childChunkId }); + + const response = await axios.delete( + `${API_URL}/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks/${childChunkId}`, + { withCredentials: true } + ); + return response.data; +} + +// ============================================================================ +// 检索 API +// ============================================================================ + +/** + * 检索知识库 + * Dify API: POST /datasets/{dataset_id}/retrieve + * + * @param datasetId - 知识库 ID + * @param query - 检索关键词 + * @param retrievalModel - 检索模型配置 + * @returns 检索结果 + */ +export async function retrieveDataset( + datasetId: string, + query: string, + retrievalModel?: RetrieveRequest['retrieval_model'] +): Promise { + console.log('[Dataset Client] 检索知识库:', { datasetId, query }); + + const requestBody: RetrieveRequest = { + query, + retrieval_model: retrievalModel, + }; + + const response = await axios.post( + `${API_URL}/datasets/${datasetId}/retrieve`, + requestBody, + { + headers: { 'Content-Type': 'application/json' }, + withCredentials: true, + } + ); + return response.data; +} diff --git a/app/api/dify-dataset/index.ts b/app/api/dify-dataset/index.ts index 1d1842a..136fe4e 100644 --- a/app/api/dify-dataset/index.ts +++ b/app/api/dify-dataset/index.ts @@ -1,47 +1,16 @@ /** - * Dify Dataset API 模块统一导出 + * Dify Dataset API 模块 + * + * 推荐直接从子包导入: + * - 类型:import type { ... } from '~/api/dify-dataset/type' + * - API:import { ... } from '~/api/dify-dataset/api' + * - 服务端:import { ... } from '~/api/dify-dataset/client.server' * * @module api/dify-dataset */ -// 类型导出 -export type { - Dataset, - DatasetsResponse, - Document, - DocumentsResponse, - Segment, - SegmentsResponse, - IndexingStatus, - OperationResult, - CreateDocumentResponse, - UploadProgress, - DocumentIndexingStatus, - IndexingStatusResponse, - UploadFileInfo, - RetrievalModel, - UpdateDatasetRequest, -} from './types'; +// 类型子包重新导出 +export * from './type'; -// 客户端 API 导出(浏览器端使用 axios) -export { - // 知识库 - fetchDatasets, - fetchDataset, - updateDataset, - // 文档 - fetchDocuments, - fetchDocument, - deleteDocument, - toggleDocumentStatus, - uploadDocument, - fetchIndexingStatus, - fetchUploadFileInfo, - // 分段 - fetchSegments, - deleteSegment, - toggleSegmentStatus, -} from './client'; - -// 服务端 API 请直接从 client.server.ts 导入 -// import { difyDatasetFetch } from '~/api/dify-dataset/client.server'; +// API 子包重新导出 +export * from './api'; diff --git a/app/api/dify-dataset/type/commonTypes.ts b/app/api/dify-dataset/type/commonTypes.ts new file mode 100644 index 0000000..18dc2ee --- /dev/null +++ b/app/api/dify-dataset/type/commonTypes.ts @@ -0,0 +1,13 @@ +/** + * Dify Dataset API 通用类型定义 + * + * @module api/dify-dataset/type/commonTypes + */ + +/** + * 通用操作结果 + */ +export interface OperationResult { + result: 'success' | 'error'; + message?: string; +} diff --git a/app/api/dify-dataset/type/datasetTypes.ts b/app/api/dify-dataset/type/datasetTypes.ts new file mode 100644 index 0000000..65dd4ee --- /dev/null +++ b/app/api/dify-dataset/type/datasetTypes.ts @@ -0,0 +1,66 @@ +/** + * Dify Dataset API 知识库类型定义 + * + * @module api/dify-dataset/type/datasetTypes + */ + +/** + * 知识库信息 + */ +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 interface RetrievalModel { + search_method: 'keyword_search' | 'semantic_search' | 'full_text_search' | 'hybrid_search'; + reranking_enable?: boolean; + reranking_mode?: string | null; + reranking_model?: { + reranking_provider_name: string; + reranking_model_name: string; + }; + weights?: number | null; + top_k?: number; + score_threshold_enabled?: boolean; + score_threshold?: number | null; +} + +/** + * 更新知识库请求参数 + */ +export interface UpdateDatasetRequest { + name?: string; + description?: string; + indexing_technique?: 'high_quality' | 'economy'; + permission?: 'only_me' | 'all_team_members' | 'partial_members'; + embedding_model_provider?: string; + embedding_model?: string; + retrieval_model?: RetrievalModel; + partial_member_list?: string[]; +} diff --git a/app/api/dify-dataset/type/documentTypes.ts b/app/api/dify-dataset/type/documentTypes.ts new file mode 100644 index 0000000..39b5e60 --- /dev/null +++ b/app/api/dify-dataset/type/documentTypes.ts @@ -0,0 +1,116 @@ +/** + * Dify Dataset API 文档类型定义 + * + * @module api/dify-dataset/type/documentTypes + */ + +/** + * 文档索引状态 + */ +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 CreateDocumentResponse { + document: Document; + batch: string; +} + +/** + * 文档上传进度 + */ +export interface UploadProgress { + loaded: number; + total: number; + percent: number; +} + +/** + * 单个文档的索引状态 + */ +export interface DocumentIndexingStatus { + id: string; + indexing_status: IndexingStatus; + processing_started_at: number | null; + parsing_completed_at: number | null; + cleaning_completed_at: number | null; + splitting_completed_at: number | null; + completed_at: number | null; + paused_at: number | null; + error: string | null; + stopped_at: number | null; + completed_segments: number; + total_segments: number; +} + +/** + * 批量文档索引状态响应 + */ +export interface IndexingStatusResponse { + data: DocumentIndexingStatus[]; +} + +/** + * 上传文件信息 + */ +export interface UploadFileInfo { + id: string; + name: string; + size: number; + extension: string; + url: string; + download_url: string; + mime_type: string; + created_by: string; + created_at: number; +} diff --git a/app/api/dify-dataset/type/index.ts b/app/api/dify-dataset/type/index.ts new file mode 100644 index 0000000..78d558e --- /dev/null +++ b/app/api/dify-dataset/type/index.ts @@ -0,0 +1,47 @@ +/** + * Dify Dataset API 类型定义统一导出 + * + * @module api/dify-dataset/type + */ + +// 通用类型 +export type { OperationResult } from './commonTypes'; + +// 知识库类型 +export type { + Dataset, + DatasetsResponse, + RetrievalModel, + UpdateDatasetRequest, +} from './datasetTypes'; + +// 文档类型 +export type { + IndexingStatus, + Document, + DocumentsResponse, + CreateDocumentResponse, + UploadProgress, + DocumentIndexingStatus, + IndexingStatusResponse, + UploadFileInfo, +} from './documentTypes'; + +// 分段、子分段、检索类型 +export type { + Segment, + SegmentsResponse, + CreateSegmentRequest, + UpdateSegmentRequest, + CreateSegmentsResponse, + ChildChunk, + ChildChunksResponse, + CreateChildChunkRequest, + UpdateChildChunkRequest, + CreateChildChunkResponse, + MetadataFilterCondition, + MetadataFilteringConditions, + RetrieveRequest, + RetrieveRecord, + RetrieveResponse, +} from './segmentTypes'; diff --git a/app/api/dify-dataset/type/segmentTypes.ts b/app/api/dify-dataset/type/segmentTypes.ts new file mode 100644 index 0000000..a976acd --- /dev/null +++ b/app/api/dify-dataset/type/segmentTypes.ts @@ -0,0 +1,191 @@ +/** + * Dify Dataset API 分段、子分段、检索类型定义 + * + * @module api/dify-dataset/type/segmentTypes + */ + +// ============================================================================ +// 分段类型 +// ============================================================================ + +/** + * 文档分段 + */ +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 CreateSegmentRequest { + content: string; + answer?: string; + keywords?: string[]; +} + +/** + * 更新分段请求参数 + */ +export interface UpdateSegmentRequest { + content?: string; + answer?: string; + keywords?: string[]; + enabled?: boolean; +} + +/** + * 创建分段响应 + */ +export interface CreateSegmentsResponse { + data: Segment[]; + doc_form: string; +} + +// ============================================================================ +// 子分段类型(父子模式) +// ============================================================================ + +/** + * 子分段信息 + */ +export interface ChildChunk { + id: string; + segment_id: string; + content: string; + word_count: number; + tokens: number; + index_node_id: string; + index_node_hash: string; + hit_count: number; + created_by: string; + created_at: number; + updated_by?: string; + updated_at?: number; + status: 'waiting' | 'completed' | 'error' | 'indexing'; + error?: string; +} + +/** + * 子分段列表响应 + */ +export interface ChildChunksResponse { + data: ChildChunk[]; + has_more: boolean; + limit: number; + total: number; + page: number; +} + +/** + * 创建子分段请求参数 + */ +export interface CreateChildChunkRequest { + content: string; +} + +/** + * 更新子分段请求参数 + */ +export interface UpdateChildChunkRequest { + content: string; +} + +/** + * 创建子分段响应 + */ +export interface CreateChildChunkResponse { + data: ChildChunk; +} + +// ============================================================================ +// 检索功能类型 +// ============================================================================ + +/** + * 元数据过滤条件 + */ +export interface MetadataFilterCondition { + name: string; + comparison_operator: 'contains' | 'not contains' | 'start with' | 'end with' | 'is' | 'is not' | 'empty' | 'not empty' | 'before' | 'after' | '=' | '!=' | '>' | '<' | '>=' | '<='; + value?: string | number; +} + +/** + * 元数据过滤条件组 + */ +export interface MetadataFilteringConditions { + logical_operator: 'and' | 'or'; + conditions: MetadataFilterCondition[]; +} + +/** + * 检索请求参数 + */ +export interface RetrieveRequest { + query: string; + retrieval_model?: { + search_method: 'keyword_search' | 'semantic_search' | 'full_text_search' | 'hybrid_search'; + reranking_enable?: boolean; + reranking_model?: { + reranking_provider_name: string; + reranking_model_name: string; + }; + top_k?: number; + score_threshold_enabled?: boolean; + score_threshold?: number; + }; + metadata_filtering_conditions?: MetadataFilteringConditions; +} + +/** + * 检索结果记录 + */ +export interface RetrieveRecord { + segment: Segment; + score: number; + tsne_position?: { + x: number; + y: number; + }; +} + +/** + * 检索响应 + */ +export interface RetrieveResponse { + query: { + content: string; + }; + records: RetrieveRecord[]; +} diff --git a/app/api/dify-dataset/types.ts b/app/api/dify-dataset/types.ts deleted file mode 100644 index 18cbbf9..0000000 --- a/app/api/dify-dataset/types.ts +++ /dev/null @@ -1,250 +0,0 @@ -/** - * 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; -} - -// ============================================================================ -// 索引状态类型 -// ============================================================================ - -/** - * 单个文档的索引状态 - */ -export interface DocumentIndexingStatus { - id: string; - indexing_status: IndexingStatus; - processing_started_at: number | null; - parsing_completed_at: number | null; - cleaning_completed_at: number | null; - splitting_completed_at: number | null; - completed_at: number | null; - paused_at: number | null; - error: string | null; - stopped_at: number | null; - completed_segments: number; - total_segments: number; -} - -/** - * 批量文档索引状态响应 - */ -export interface IndexingStatusResponse { - data: DocumentIndexingStatus[]; -} - -// ============================================================================ -// 上传文件信息类型 -// ============================================================================ - -/** - * 上传文件信息 - */ -export interface UploadFileInfo { - id: string; - name: string; - size: number; - extension: string; - url: string; - download_url: string; - mime_type: string; - created_by: string; - created_at: number; -} - -// ============================================================================ -// 知识库更新类型 -// ============================================================================ - -/** - * 检索模型配置 - */ -export interface RetrievalModel { - search_method: 'keyword_search' | 'semantic_search' | 'full_text_search' | 'hybrid_search'; - reranking_enable?: boolean; - reranking_mode?: string | null; - reranking_model?: { - reranking_provider_name: string; - reranking_model_name: string; - }; - weights?: number | null; - top_k?: number; - score_threshold_enabled?: boolean; - score_threshold?: number | null; -} - -/** - * 更新知识库请求参数 - */ -export interface UpdateDatasetRequest { - name?: string; - description?: string; - indexing_technique?: 'high_quality' | 'economy'; - permission?: 'only_me' | 'all_team_members' | 'partial_members'; - embedding_model_provider?: string; - embedding_model?: string; - retrieval_model?: RetrievalModel; - partial_member_list?: string[]; -} diff --git a/app/components/dify-dataset-manager/dataset-settings.tsx b/app/components/dify-dataset-manager/dataset-settings.tsx new file mode 100644 index 0000000..333dfde --- /dev/null +++ b/app/components/dify-dataset-manager/dataset-settings.tsx @@ -0,0 +1,187 @@ +import { useState, useEffect } from 'react'; +import { Form, Input, Button, Card, message, Spin } from 'antd'; +import { SaveOutlined } from '@ant-design/icons'; +import type { Dataset } from '~/api/dify-dataset/type/datasetTypes'; +import { updateDatasetName } from '~/api/dify-dataset/api/datasetApi'; + +const { TextArea } = Input; + +interface DatasetSettingsProps { + dataset: Dataset | null; + onDatasetUpdated: (dataset: Dataset) => void; +} + +/** + * 知识库设置组件 + * 用于修改知识库名称和描述 + */ +export default function DatasetSettings({ + dataset, + onDatasetUpdated, +}: DatasetSettingsProps) { + const [form] = Form.useForm(); + const [saving, setSaving] = useState(false); + const [hasChanges, setHasChanges] = useState(false); + + // 初始化表单数据 + useEffect(() => { + if (dataset) { + form.setFieldsValue({ + name: dataset.name, + description: dataset.description || '', + }); + setHasChanges(false); + } + }, [dataset, form]); + + /** + * 处理表单值变化 + */ + const handleValuesChange = () => { + const values = form.getFieldsValue(); + const changed = + values.name !== dataset?.name || + values.description !== (dataset?.description || ''); + setHasChanges(changed); + }; + + /** + * 保存设置 + */ + const handleSave = async () => { + if (!dataset) { + message.error('知识库不存在'); + return; + } + + try { + const values = await form.validateFields(); + setSaving(true); + + // 目前只支持修改名称 + const updatedDataset = await updateDatasetName(dataset.id, values.name); + + message.success('保存成功'); + onDatasetUpdated(updatedDataset); + setHasChanges(false); + } catch (err: any) { + console.error('保存设置失败:', err); + message.error(err.message || '保存失败'); + } finally { + setSaving(false); + } + }; + + /** + * 重置表单 + */ + const handleReset = () => { + if (dataset) { + form.setFieldsValue({ + name: dataset.name, + description: dataset.description || '', + }); + setHasChanges(false); + } + }; + + if (!dataset) { + return ( +
+ +
+ ); + } + + return ( +
+ {/* 页面标题 */} +
+

设置

+

+ 管理知识库的基本信息 +

+
+ + {/* 设置表单 */} + +
+ + + + + +