fix: stabilize review detail and collabora loading
This commit is contained in:
+90
-116
@@ -1,82 +1,97 @@
|
||||
/**
|
||||
* Dify Chat API 模块
|
||||
* 自有 RAG Chat API 模块
|
||||
*
|
||||
* 提供客户端调用 Dify API 的函数
|
||||
* 用于 Remix loader/action 中调用 Dify API
|
||||
* 保持前端 dify-chat 调用面不变,内部转发到新的 /api/v3/rag/* 接口。
|
||||
*
|
||||
* @module api/dify/chat
|
||||
* @module api/dify-chat/chat
|
||||
*/
|
||||
|
||||
import { difyFetch } from './client.server';
|
||||
|
||||
// ============================================================================
|
||||
// Dify Chat API 客户端
|
||||
// ============================================================================
|
||||
function unwrapResult<T>(payload: any): T {
|
||||
if (payload && typeof payload === 'object' && 'data' in payload) {
|
||||
return payload.data as T;
|
||||
}
|
||||
return payload as T;
|
||||
}
|
||||
|
||||
function toIntAppId(appId?: string): string | undefined {
|
||||
if (!appId) return undefined;
|
||||
const parsed = Number(appId);
|
||||
return Number.isFinite(parsed) ? String(parsed) : undefined;
|
||||
}
|
||||
|
||||
function normalizeConversationName(name: string): string {
|
||||
const compact = (name || '').replace(/\s+/g, ' ').trim();
|
||||
if (!compact) return '新对话';
|
||||
return compact.length > 20 ? `${compact.slice(0, 20)}...` : compact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dify Chat API 客户端
|
||||
*
|
||||
* @param jwt - JWT 认证令牌
|
||||
* user 参数由后端自动从 JWT 中提取
|
||||
*/
|
||||
export const difyClient = {
|
||||
/**
|
||||
* 获取应用参数
|
||||
*/
|
||||
async getApplicationParameters(jwt?: string): Promise<any> {
|
||||
const response = await difyFetch('parameters', {
|
||||
async getApplicationParameters(jwt?: string, appId?: string): Promise<any> {
|
||||
const response = await difyFetch('chat/parameters', {
|
||||
method: 'GET',
|
||||
appId: toIntAppId(appId),
|
||||
}, jwt);
|
||||
return response.json();
|
||||
const payload = unwrapResult<any>(await response.json());
|
||||
return {
|
||||
opening_statement: payload?.openingStatement || '',
|
||||
suggested_questions: payload?.suggestedQuestions || [],
|
||||
user_input_form: payload?.userInputForm || [],
|
||||
file_upload: payload?.fileUpload || { enabled: false },
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
*
|
||||
* @param jwt - JWT 认证令牌
|
||||
* @param appId - 对话应用 ID(可选,用于获取特定应用的会话列表)
|
||||
*/
|
||||
async getConversations(jwt?: string, appId?: string): Promise<any> {
|
||||
const params = new URLSearchParams({
|
||||
limit: '100',
|
||||
first_id: '',
|
||||
page: '1',
|
||||
pageSize: '100',
|
||||
});
|
||||
const normalizedAppId = toIntAppId(appId);
|
||||
if (normalizedAppId) {
|
||||
params.set('appId', normalizedAppId);
|
||||
}
|
||||
|
||||
const response = await difyFetch(`conversations?${params}`, {
|
||||
const response = await difyFetch(`chat/conversations?${params.toString()}`, {
|
||||
method: 'GET',
|
||||
appId, // 传递应用 ID,会在请求头中添加 X-Dify-App-Id
|
||||
}, jwt);
|
||||
return response.json();
|
||||
const payload = unwrapResult<any>(await response.json());
|
||||
return {
|
||||
data: (payload?.data || []).map((item: any) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
introduction: item.introduction || '',
|
||||
created_at: item.createdAt || 0,
|
||||
updated_at: item.updatedAt || 0,
|
||||
})),
|
||||
has_more: Boolean(payload?.hasMore),
|
||||
limit: payload?.limit || 100,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取会话消息
|
||||
*/
|
||||
async getConversationMessages(conversationId: string, jwt?: string): Promise<any> {
|
||||
const params = new URLSearchParams({
|
||||
conversation_id: conversationId,
|
||||
limit: '20',
|
||||
last_id: '',
|
||||
page: '1',
|
||||
pageSize: '100',
|
||||
});
|
||||
|
||||
const response = await difyFetch(`messages?${params}`, {
|
||||
const response = await difyFetch(`chat/conversations/${conversationId}/messages?${params.toString()}`, {
|
||||
method: 'GET',
|
||||
}, jwt);
|
||||
return response.json();
|
||||
const payload = unwrapResult<any>(await response.json());
|
||||
return {
|
||||
data: (payload?.data || []).map((item: any) => ({
|
||||
id: item.id,
|
||||
query: item.query,
|
||||
answer: item.answer,
|
||||
feedback: item.feedback || undefined,
|
||||
retriever_resources: item.retrieverResources || [],
|
||||
created_at: item.createdAt || 0,
|
||||
})),
|
||||
has_more: Boolean(payload?.hasMore),
|
||||
limit: payload?.limit || 100,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 发送聊天消息
|
||||
*
|
||||
* @param inputs - 输入参数
|
||||
* @param query - 用户问题
|
||||
* @param responseMode - 响应模式 ('streaming' | 'blocking')
|
||||
* @param conversationId - 会话 ID
|
||||
* @param files - 附件文件
|
||||
* @param jwt - JWT 认证令牌
|
||||
* @param appId - 对话应用 ID(可选,用于切换不同的 Dify 应用)
|
||||
* @returns 对于流式响应返回 Response 对象,否则返回 JSON
|
||||
*/
|
||||
async createChatMessage(
|
||||
inputs: Record<string, any>,
|
||||
query: string,
|
||||
@@ -92,99 +107,58 @@ export const difyClient = {
|
||||
response_mode: responseMode,
|
||||
conversation_id: conversationId,
|
||||
files: files || [],
|
||||
appId: toIntAppId(appId) ? Number(appId) : null,
|
||||
conversationId: conversationId || null,
|
||||
};
|
||||
const response = await difyFetch('chat-messages', {
|
||||
const response = await difyFetch('chat/messages', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
appId, // 传递应用 ID,会在请求头中添加 X-Dify-App-Id
|
||||
}, jwt);
|
||||
|
||||
// 对于流式响应,直接返回 Response 对象
|
||||
if (responseMode === 'streaming') {
|
||||
return response;
|
||||
}
|
||||
|
||||
console.log('[Dify Chat] 解析 JSON 响应');
|
||||
return response.json();
|
||||
},
|
||||
|
||||
/**
|
||||
* 重命名会话
|
||||
*/
|
||||
async renameConversation(
|
||||
conversationId: string,
|
||||
name: string,
|
||||
autoGenerate: boolean = false,
|
||||
jwt?: string
|
||||
): Promise<any> {
|
||||
const body = {
|
||||
name,
|
||||
auto_generate: autoGenerate,
|
||||
};
|
||||
let nextName = name?.trim() || '';
|
||||
|
||||
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('[Dify Chat] 删除会话:', conversationId);
|
||||
|
||||
try {
|
||||
const response = await difyFetch(`conversations/${conversationId}`, {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify({}),
|
||||
}, jwt);
|
||||
|
||||
// 对于 204 No Content 响应,直接返回成功
|
||||
if (response.status === 204) {
|
||||
console.log('[Dify Chat] 删除会话成功:', conversationId);
|
||||
return { result: 'success' };
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
console.log('[Dify Chat] 删除会话文本响应:', text);
|
||||
|
||||
return { result: 'success' };
|
||||
} catch (error: any) {
|
||||
// 权限不足等明确错误需要抛出,不能吞掉
|
||||
if (error.message?.includes('403') || error.message?.includes('401')) {
|
||||
throw error;
|
||||
}
|
||||
// 网络超时等不确定错误才降级为成功(Dify 可能已执行删除)
|
||||
console.warn('[Dify Chat] 删除会话请求失败,但可能已成功删除:', error.message);
|
||||
return { result: 'success' };
|
||||
if (autoGenerate || !nextName) {
|
||||
const messages = await this.getConversationMessages(conversationId, jwt);
|
||||
const firstQuestion = messages?.data?.find((item: any) => item?.query)?.query || '';
|
||||
nextName = normalizeConversationName(firstQuestion);
|
||||
}
|
||||
|
||||
const response = await difyFetch(`chat/conversations/${conversationId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ name: nextName }),
|
||||
}, jwt);
|
||||
return unwrapResult<any>(await response.json());
|
||||
},
|
||||
|
||||
async deleteConversation(conversationId: string, jwt?: string): Promise<any> {
|
||||
const response = await difyFetch(`chat/conversations/${conversationId}`, {
|
||||
method: 'DELETE',
|
||||
}, jwt);
|
||||
return unwrapResult<any>(await response.json());
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新消息反馈
|
||||
*/
|
||||
async updateMessageFeedback(
|
||||
messageId: string,
|
||||
rating: 'like' | 'dislike' | null,
|
||||
jwt?: string
|
||||
): Promise<any> {
|
||||
const body = {
|
||||
rating,
|
||||
};
|
||||
|
||||
const response = await difyFetch(`messages/${messageId}/feedbacks`, {
|
||||
const response = await difyFetch(`chat/messages/${messageId}/feedback`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
body: JSON.stringify({ rating }),
|
||||
}, jwt);
|
||||
return response.json();
|
||||
return unwrapResult<any>(await response.json());
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user