Merge branch 'PingChuan' into shiy-login

This commit is contained in:
2025-12-01 12:40:22 +08:00
41 changed files with 4494 additions and 1075 deletions
+15 -49
View File
@@ -1,13 +1,13 @@
/**
* Dify 服务端基础 API 模块
* Dify Chat 服务端 API 模块
*
* 提供 Node.js 服务端调用 FastAPI 后端的基础功能
* 包括配置管理和基础请求函数
* Dify 的 API_KEY 和 APP_ID 由 FastAPI 后端管理,前端只负责转发请求
*
* 调用链路:
* Remix Server → FastAPI /dify/* → Dify
* Remix Server → FastAPI /dify_chat/* → Dify
*
* @module api/dify/client.server
* @module api/dify-chat/client.server
*/
import { API_BASE_URL } from '~/config/api-config';
@@ -17,45 +17,25 @@ import { API_BASE_URL } from '~/config/api-config';
// ============================================================================
/**
* 获取环境变量的服务端函数
* Dify Chat API 代理地址
* 通过 FastAPI 后端的 /dify_chat 路由代理访问 Dify
* Dify 的认证(API_KEY)由 FastAPI 后端处理
*/
const getServerEnvVar = (name: string, defaultValue: string = ''): string => {
return process.env[name] || defaultValue;
};
/**
* Dify API 客户端配置
* 通过 FastAPI 后端的 /dify 路由代理访问 Dify
*/
const DIFY_CONFIG = {
API_URL: `${API_BASE_URL}/dify_chat`,
API_KEY: getServerEnvVar('NEXT_PUBLIC_APP_KEY', ''),
APP_ID: (() => {
const rawAppId = getServerEnvVar('NEXT_PUBLIC_APP_ID', '');
const match = rawAppId.match(/\/app\/([a-f0-9-]{36})/);
return match ? match[1] : rawAppId;
})(),
};
console.log('[Dify Server] 配置:', {
apiUrl: DIFY_CONFIG.API_URL,
apiBaseUrl: API_BASE_URL,
appId: DIFY_CONFIG.APP_ID,
configComplete: !!(DIFY_CONFIG.API_URL && DIFY_CONFIG.APP_ID)
});
const DIFY_CHAT_API_URL = `${API_BASE_URL}/dify_chat`;
// ============================================================================
// 基础请求函数
// ============================================================================
/**
* Dify API 基础请求函数
* Dify Chat API 基础请求函数
*
* 使用 JWT 认证通过 FastAPI 代理访问 Dify
* 使用用户 JWT 认证通过 FastAPI 代理访问 Dify
* FastAPI 后端会验证 JWT 并添加 Dify API_KEY
*
* @param endpoint - API 端点路径
* @param options - fetch 请求选项
* @param jwt - JWT 认证令牌
* @param jwt - 用户 JWT 认证令牌
* @returns Response 对象
*/
export async function difyFetch(
@@ -63,7 +43,7 @@ export async function difyFetch(
options: RequestInit = {},
jwt?: string
): Promise<Response> {
const url = `${DIFY_CONFIG.API_URL}/${endpoint.replace(/^\//, '')}`;
const url = `${DIFY_CHAT_API_URL}/${endpoint.replace(/^\//, '')}`;
const headers: HeadersInit = {
'Content-Type': 'application/json',
@@ -73,7 +53,7 @@ export async function difyFetch(
if (jwt) {
(headers as Record<string, string>)['Authorization'] = `Bearer ${jwt}`;
} else {
console.warn('[Dify Server] 没有提供 JWT转发fastapi请求可能失败');
console.warn('[Dify Chat] 没有提供 JWTFastAPI 请求可能失败');
}
const response = await fetch(url, {
@@ -83,7 +63,7 @@ export async function difyFetch(
if (!response.ok) {
const errorText = await response.text();
console.error('[Dify Server] API 转发错误:', {
console.error('[Dify Chat] API 转发错误:', {
status: response.status,
statusText: response.statusText,
error: errorText
@@ -99,19 +79,5 @@ export async function difyFetch(
return response;
}
// ============================================================================
// 工具函数
// ============================================================================
/**
* Dify 工具函数
*/
export const difyUtils = {
/**
* 获取 Dify 配置
*/
getConfig: () => DIFY_CONFIG,
};
// 重新导出 chat 模块的 difyClient
export { difyClient } from './chat';
+78
View File
@@ -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<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<Dataset> {
const response = await axios.get<Dataset>(
`${API_URL}/datasets/${datasetId}`,
{ withCredentials: true }
);
return response.data;
}
/**
* 更新知识库名称
*
* 注意:仅允许修改知识库名称,其他字段不开放修改
*
* @param datasetId - 知识库 ID
* @param name - 新的知识库名称
* @returns 更新后的知识库详情
*/
export async function updateDatasetName(
datasetId: string,
name: string
): Promise<Dataset> {
console.log('[Dataset Client] 更新知识库名称:', { datasetId, name });
const response = await axios.patch<Dataset>(
`${API_URL}/datasets/${datasetId}`,
{ name },
{
headers: { 'Content-Type': 'application/json' },
withCredentials: true,
}
);
return response.data;
}
@@ -1,76 +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 {
DatasetsResponse,
DocumentsResponse,
SegmentsResponse,
Document,
DocumentsResponse,
IndexingStatusResponse,
UploadFileInfo,
OperationResult,
} from './types';
// ============================================================================
// 基础配置
// ============================================================================
} 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<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
// ============================================================================
/**
*
*
@@ -144,6 +93,8 @@ export async function deleteDocument(
/**
* /
* Dify API: PATCH /datasets/{dataset_id}/documents/status/{action}
* action: enable / disable / archive / un_archive
*
* @param datasetId - ID
* @param documentId - ID
@@ -155,11 +106,12 @@ export async function toggleDocumentStatus(
documentId: string,
enabled: boolean
): Promise<OperationResult> {
console.log('[Dataset Client] 切换文档状态:', { datasetId, documentId, enabled });
const action = enabled ? 'enable' : 'disable';
console.log('[Dataset Client] 切换文档状态:', { datasetId, documentId, action });
const response = await axios.patch<OperationResult>(
`${API_URL}/datasets/${datasetId}/documents/${documentId}/status`,
{ enabled },
`${API_URL}/datasets/${datasetId}/documents/status/${action}`,
{ document_ids: [documentId] },
{
headers: { 'Content-Type': 'application/json' },
withCredentials: true,
@@ -168,97 +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<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
// ============================================================================
/**
*
*
@@ -284,7 +145,7 @@ export async function uploadDocument(
console.log('[Dataset Client] 上传文档:', { datasetId, fileName: file.name });
const response = await axios.post(
`${API_URL}/datasets/${datasetId}/documents/create-by-file`,
`${API_URL}/datasets/${datasetId}/documents`,
formData,
{
withCredentials: true,
@@ -298,3 +159,96 @@ export async function uploadDocument(
);
return response.data;
}
/**
*
*
* @param datasetId - ID
* @param batch -
* @returns
*/
export async function fetchIndexingStatus(
datasetId: string,
batch: string
): Promise<IndexingStatusResponse> {
console.log('[Dataset Client] 获取索引状态:', { datasetId, batch });
const response = await axios.get<IndexingStatusResponse>(
`${API_URL}/datasets/${datasetId}/documents/${batch}/indexing-status`,
{ withCredentials: true }
);
return response.data;
}
/**
*
*
* @param datasetId - ID
* @param documentId - ID
* @returns
*/
export async function fetchUploadFileInfo(
datasetId: string,
documentId: string
): Promise<UploadFileInfo> {
console.log('[Dataset Client] 获取上传文件信息:', { datasetId, documentId });
const response = await axios.get<UploadFileInfo>(
`${API_URL}/datasets/${datasetId}/documents/${documentId}/upload-file`,
{ withCredentials: true }
);
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<OperationResult> {
console.log('[Dataset Client] 更新文档设置:', { datasetId, documentId, settings });
const response = await axios.post<OperationResult>(
`${API_URL}/datasets/${datasetId}/documents/${documentId}/update-settings`,
settings,
{
headers: { 'Content-Type': 'application/json' },
withCredentials: true,
}
);
return response.data;
}
+38
View File
@@ -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';
+359
View File
@@ -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<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 fetchSegment(
datasetId: string,
documentId: string,
segmentId: string
): Promise<Segment> {
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<CreateSegmentsResponse> {
console.log('[Dataset Client] 新增分段:', { datasetId, documentId, count: segments.length });
const response = await axios.post<CreateSegmentsResponse>(
`${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<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;
}
/**
* 启用/禁用分段
* 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<OperationResult> {
console.log('[Dataset Client] 切换分段状态:', { datasetId, documentId, segmentId, enabled });
const response = await axios.post<OperationResult>(
`${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<ChildChunksResponse> {
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<ChildChunksResponse>(
`${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<CreateChildChunkResponse> {
console.log('[Dataset Client] 新增子分段:', { datasetId, documentId, segmentId });
const response = await axios.post<CreateChildChunkResponse>(
`${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<ChildChunk> {
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<OperationResult> {
console.log('[Dataset Client] 删除子分段:', { datasetId, documentId, segmentId, childChunkId });
const response = await axios.delete<OperationResult>(
`${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<RetrieveResponse> {
console.log('[Dataset Client] 检索知识库:', { datasetId, query });
const requestBody: RetrieveRequest = {
query,
retrieval_model: retrievalModel,
};
const response = await axios.post<RetrieveResponse>(
`${API_URL}/datasets/${datasetId}/retrieve`,
requestBody,
{
headers: { 'Content-Type': 'application/json' },
withCredentials: true,
}
);
return response.data;
}
+84
View File
@@ -0,0 +1,84 @@
/**
* Dify Dataset 服务端 API 模块
*
* 提供 Node.js 服务端调用 FastAPI 后端的基础功能
* Dify 的 DATASET_API_KEY 由 FastAPI 后端管理,前端只负责转发请求
*
* 调用链路:
* Remix Server → FastAPI /dify_dataset/* → Dify Knowledge API
*
* @module api/dify-dataset/client.server
*/
import { API_BASE_URL } from '~/config/api-config';
// ============================================================================
// 配置
// ============================================================================
/**
* Dify Dataset API 代理地址
* 通过 FastAPI 后端的 /dify_dataset 路由代理访问 Dify Knowledge API
* Dify 的认证(DATASET_API_KEY)由 FastAPI 后端处理
*/
const DIFY_DATASET_API_URL = `${API_BASE_URL}/dify_dataset`;
// ============================================================================
// 基础请求函数
// ============================================================================
/**
* Dify Dataset API 基础请求函数
*
* 使用用户 JWT 认证通过 FastAPI 代理访问 Dify Knowledge API
* FastAPI 后端会验证 JWT 并添加 Dify DATASET_API_KEY
*
* @param endpoint - API 端点路径
* @param options - fetch 请求选项
* @param jwt - 用户 JWT 认证令牌
* @returns Response 对象
*/
export async function difyDatasetFetch(
endpoint: string,
options: RequestInit = {},
jwt?: string
): Promise<Response> {
const url = `${DIFY_DATASET_API_URL}/${endpoint.replace(/^\//, '')}`;
const headers: HeadersInit = {
...options.headers,
};
// 如果不是 FormData,设置 Content-Type 为 JSON
if (!(options.body instanceof FormData)) {
(headers as Record<string, string>)['Content-Type'] = 'application/json';
}
if (jwt) {
(headers as Record<string, string>)['Authorization'] = `Bearer ${jwt}`;
} else {
console.warn('[Dify Dataset] 没有提供 JWTFastAPI 请求可能失败');
}
const response = await fetch(url, {
...options,
headers,
});
if (!response.ok) {
const errorText = await response.text();
console.error('[Dify Dataset] API 转发错误:', {
status: response.status,
statusText: response.statusText,
error: errorText
});
if (response.status === 401) {
throw new Error('JWT认证失败,请重新登录');
}
throw new Error(`Dify Dataset API Error: ${response.status} ${response.statusText}`);
}
return response;
}
+10 -30
View File
@@ -1,36 +1,16 @@
/**
* Dify Dataset API 模块统一导出
* Dify Dataset API 模块
*
* 推荐直接从子包导入:
* - 类型:import type { ... } from '~/api/dify-dataset/type'
* - APIimport { ... } 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,
} from './types';
// 类型子包重新导出
export * from './type';
// 客户端 API 导出
export {
// 知识库
fetchDatasets,
fetchDataset,
// 文档
fetchDocuments,
fetchDocument,
deleteDocument,
toggleDocumentStatus,
uploadDocument,
// 分段
fetchSegments,
deleteSegment,
toggleSegmentStatus,
} from './client';
// API 子包重新导出
export * from './api';
+13
View File
@@ -0,0 +1,13 @@
/**
* Dify Dataset API 通用类型定义
*
* @module api/dify-dataset/type/commonTypes
*/
/**
* 通用操作结果
*/
export interface OperationResult {
result: 'success' | 'error';
message?: string;
}
+66
View File
@@ -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[];
}
+116
View File
@@ -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;
}
+47
View File
@@ -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';
+191
View File
@@ -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[];
}
-167
View File
@@ -1,167 +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;
}