285 lines
7.8 KiB
TypeScript
285 lines
7.8 KiB
TypeScript
/**
|
|
* Dify 服务端 API 模块
|
|
*
|
|
* 提供 Node.js 服务端调用 FastAPI 后端的函数
|
|
* 用于 Remix loader/action 中调用 Dify API
|
|
*
|
|
* 调用链路:
|
|
* Remix Server → FastAPI /dify/* → Dify
|
|
*
|
|
* @module api/dify/client.server
|
|
*/
|
|
|
|
import { API_BASE_URL } from '~/config/api-config';
|
|
|
|
// ============================================================================
|
|
// 配置
|
|
// ============================================================================
|
|
|
|
/**
|
|
* 获取环境变量的服务端函数
|
|
*/
|
|
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`,
|
|
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)
|
|
});
|
|
|
|
// ============================================================================
|
|
// 基础请求函数
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Dify API 基础请求函数
|
|
*
|
|
* 使用 JWT 认证通过 FastAPI 代理访问 Dify
|
|
*
|
|
* @param endpoint - API 端点路径
|
|
* @param options - fetch 请求选项
|
|
* @param jwt - JWT 认证令牌
|
|
* @returns Response 对象
|
|
*/
|
|
async function difyFetch(
|
|
endpoint: string,
|
|
options: RequestInit = {},
|
|
jwt?: string
|
|
): Promise<Response> {
|
|
const url = `${DIFY_CONFIG.API_URL}/${endpoint.replace(/^\//, '')}`;
|
|
|
|
const headers: HeadersInit = {
|
|
'Content-Type': 'application/json',
|
|
...options.headers,
|
|
};
|
|
|
|
if (jwt) {
|
|
(headers as Record<string, string>)['Authorization'] = `Bearer ${jwt}`;
|
|
} else {
|
|
console.warn('⚠️ [Dify Server] 没有提供 JWT,请求可能失败');
|
|
}
|
|
|
|
const response = await fetch(url, {
|
|
...options,
|
|
headers,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
console.error('❌ [Dify Server] Dify API 错误:', {
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
error: errorText
|
|
});
|
|
|
|
if (response.status === 401) {
|
|
throw new Error('JWT认证失败,请重新登录');
|
|
}
|
|
|
|
throw new Error(`Dify API Error: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Dify API 客户端
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Dify 服务端 API 客户端
|
|
*
|
|
* 所有方法都需要传入 JWT 进行认证
|
|
* user 参数由后端自动从 JWT 中提取
|
|
*/
|
|
export const difyClient = {
|
|
/**
|
|
* 获取应用参数
|
|
*/
|
|
async getApplicationParameters(jwt?: string): Promise<any> {
|
|
const response = await difyFetch('parameters', {
|
|
method: 'GET',
|
|
}, jwt);
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* 获取会话列表
|
|
*/
|
|
async getConversations(jwt?: string): Promise<any> {
|
|
const params = new URLSearchParams({
|
|
limit: '100',
|
|
first_id: '',
|
|
});
|
|
|
|
const response = await difyFetch(`conversations?${params}`, {
|
|
method: 'GET',
|
|
}, jwt);
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* 获取会话消息
|
|
*/
|
|
async getConversationMessages(conversationId: string, jwt?: string): Promise<any> {
|
|
const params = new URLSearchParams({
|
|
conversation_id: conversationId,
|
|
limit: '20',
|
|
last_id: '',
|
|
});
|
|
|
|
const response = await difyFetch(`messages?${params}`, {
|
|
method: 'GET',
|
|
}, jwt);
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* 发送聊天消息
|
|
*
|
|
* @param inputs - 输入参数
|
|
* @param query - 用户问题
|
|
* @param responseMode - 响应模式 ('streaming' | 'blocking')
|
|
* @param conversationId - 会话 ID
|
|
* @param files - 附件文件
|
|
* @param jwt - JWT 认证令牌
|
|
* @returns 对于流式响应返回 Response 对象,否则返回 JSON
|
|
*/
|
|
async createChatMessage(
|
|
inputs: Record<string, any>,
|
|
query: string,
|
|
responseMode: string = 'streaming',
|
|
conversationId?: string,
|
|
files?: any[],
|
|
jwt?: string
|
|
): Promise<Response | any> {
|
|
const body = {
|
|
inputs,
|
|
query,
|
|
response_mode: responseMode,
|
|
conversation_id: conversationId,
|
|
files: files || [],
|
|
};
|
|
const response = await difyFetch('chat-messages', {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
}, jwt);
|
|
|
|
// 对于流式响应,直接返回 Response 对象
|
|
if (responseMode === 'streaming') {
|
|
return response;
|
|
}
|
|
|
|
console.log('[Dify Server] 解析 JSON 响应');
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* 重命名会话
|
|
*/
|
|
async renameConversation(
|
|
conversationId: string,
|
|
name: string,
|
|
autoGenerate: boolean = false,
|
|
jwt?: string
|
|
): Promise<any> {
|
|
const body = {
|
|
name,
|
|
auto_generate: autoGenerate,
|
|
};
|
|
|
|
const response = await difyFetch(`conversations/${conversationId}/name`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
}, jwt);
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* 删除会话
|
|
*/
|
|
async deleteConversation(conversationId: string, jwt?: string): Promise<any> {
|
|
console.log('remix后端接收到删除请求,调用fastapi:', conversationId);
|
|
|
|
try {
|
|
const response = await difyFetch(`conversations/${conversationId}`, {
|
|
method: 'DELETE',
|
|
body: JSON.stringify({}),
|
|
}, jwt);
|
|
|
|
|
|
// 对于 204 No Content 响应,直接返回成功
|
|
if (response.status === 204) {
|
|
console.log('删除会话' + conversationId + '成功');
|
|
return { result: 'success' };
|
|
}
|
|
|
|
const contentType = response.headers.get('Content-Type');
|
|
|
|
if (contentType && contentType.includes('application/json')) {
|
|
const data = await response.json();
|
|
console.log('🗑️ [Dify Server] 删除会话 JSON 响应:', data);
|
|
return data;
|
|
}
|
|
|
|
const text = await response.text();
|
|
console.log('🗑️ [Dify Server] 删除会话文本响应:', text);
|
|
|
|
return { result: 'success' };
|
|
} catch (error: any) {
|
|
console.warn('⚠️ [Dify Server] 删除会话请求失败,但可能已成功删除:', error.message);
|
|
return { result: 'success' };
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 更新消息反馈
|
|
*/
|
|
async updateMessageFeedback(
|
|
messageId: string,
|
|
rating: 'like' | 'dislike' | null,
|
|
jwt?: string
|
|
): Promise<any> {
|
|
const body = {
|
|
rating,
|
|
};
|
|
|
|
const response = await difyFetch(`messages/${messageId}/feedbacks`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
}, jwt);
|
|
return response.json();
|
|
},
|
|
};
|
|
|
|
// ============================================================================
|
|
// 工具函数
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Dify 工具函数
|
|
*/
|
|
export const difyUtils = {
|
|
/**
|
|
* 获取 Dify 配置
|
|
*/
|
|
getConfig: () => DIFY_CONFIG,
|
|
};
|