取消统一转发Dify请求,改为由客户端直接发送
This commit is contained in:
+259
-185
@@ -6,9 +6,10 @@ import { unicodeToChar } from '../utils/chat-utils';
|
||||
const baseOptions = {
|
||||
method: 'GET',
|
||||
mode: 'cors' as RequestMode,
|
||||
credentials: 'include' as RequestCredentials,
|
||||
credentials: 'omit' as RequestCredentials,
|
||||
headers: new Headers({
|
||||
'Content-Type': ContentType.json,
|
||||
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
|
||||
}),
|
||||
redirect: 'follow' as RequestRedirect,
|
||||
};
|
||||
@@ -300,101 +301,71 @@ const handleStream = (
|
||||
read();
|
||||
};
|
||||
|
||||
// 基础Fetch函数
|
||||
// 基础请求函数
|
||||
const baseFetch = (url: string, fetchOptions: any, needAllResponseContent: boolean = false) => {
|
||||
const options = Object.assign({}, baseOptions, fetchOptions);
|
||||
|
||||
// 构建完整URL - 修复重复的/v1问题
|
||||
// CHAT_CONFIG.API_URL 已经包含了 /v1,所以不需要再添加 API_PREFIX
|
||||
let urlWithPrefix = `${CHAT_CONFIG.API_URL}/${url.replace(/^\//, '')}`;
|
||||
// 直接构建Dify API URL
|
||||
const urlWithPrefix = `${CHAT_CONFIG.API_URL}/${url.replace(/^\//, '')}`;
|
||||
|
||||
|
||||
const { method, params, body } = options;
|
||||
|
||||
// 处理GET请求的查询参数
|
||||
if (method === 'GET' && params) {
|
||||
const paramsArray: string[] = [];
|
||||
Object.keys(params).forEach(key =>
|
||||
paramsArray.push(`${key}=${encodeURIComponent(params[key])}`),
|
||||
);
|
||||
if (urlWithPrefix.search(/\?/) === -1)
|
||||
urlWithPrefix += `?${paramsArray.join('&')}`;
|
||||
else
|
||||
urlWithPrefix += `&${paramsArray.join('&')}`;
|
||||
|
||||
delete options.params;
|
||||
}
|
||||
|
||||
// 处理请求体
|
||||
if (body && typeof body === 'object')
|
||||
options.body = JSON.stringify(body);
|
||||
|
||||
// 添加认证头
|
||||
if (!options.headers)
|
||||
options.headers = {};
|
||||
|
||||
if (CHAT_CONFIG.API_KEY) {
|
||||
// 确保Authorization头存在
|
||||
if (CHAT_CONFIG.API_KEY && options.headers) {
|
||||
options.headers['Authorization'] = `Bearer ${CHAT_CONFIG.API_KEY}`;
|
||||
}
|
||||
|
||||
return Promise.race([
|
||||
new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('请求超时'));
|
||||
}, SSE_TIMEOUT);
|
||||
}),
|
||||
new Promise((resolve, reject) => {
|
||||
fetch(urlWithPrefix, options)
|
||||
.then((res: Response) => {
|
||||
const resClone = res.clone();
|
||||
const { body } = options;
|
||||
if (body && typeof body === 'object') {
|
||||
// 为所有请求添加user参数
|
||||
const bodyWithUser = {
|
||||
...body,
|
||||
user: CHAT_CONFIG.generateUserId(),
|
||||
};
|
||||
options.body = JSON.stringify(bodyWithUser);
|
||||
}
|
||||
|
||||
// console.log('📥 API Response:', {
|
||||
// status: res.status,
|
||||
// statusText: res.statusText,
|
||||
// url: urlWithPrefix
|
||||
// });
|
||||
|
||||
// 错误处理
|
||||
if (!/^(2|3)\d{2}$/.test(res.status.toString())) {
|
||||
try {
|
||||
const bodyJson = res.json();
|
||||
switch (res.status) {
|
||||
case 401:
|
||||
console.error('❌ Invalid token');
|
||||
break;
|
||||
default:
|
||||
bodyJson.then((data: any) => {
|
||||
console.error('❌ API Error:', data.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error('❌ Response Error:', e);
|
||||
}
|
||||
return Promise.reject(resClone);
|
||||
}
|
||||
|
||||
// 处理删除API(204状态码)
|
||||
if (res.status === 204) {
|
||||
resolve({ result: 'success' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 返回数据
|
||||
const contentType = res.headers.get('Content-Type') || '';
|
||||
const data = contentType.includes('application/octet-stream') ? res.blob() : res.json();
|
||||
|
||||
resolve(needAllResponseContent ? resClone : data);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('❌ Fetch Error:', err);
|
||||
reject(err);
|
||||
return fetch(urlWithPrefix, options)
|
||||
.then((res: Response) => {
|
||||
if (!res.ok) {
|
||||
console.error('❌ Request failed:', {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
url: urlWithPrefix
|
||||
});
|
||||
}),
|
||||
]);
|
||||
if (res.status === 422) {
|
||||
return res.text().then(text => {
|
||||
let errorMessage = text;
|
||||
try {
|
||||
const data = JSON.parse(text);
|
||||
errorMessage = data.message || data.error || text;
|
||||
} catch (e) {
|
||||
// 如果不是JSON,使用原始文本
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
});
|
||||
}
|
||||
throw new Error(`${res.status}: ${res.statusText}`);
|
||||
}
|
||||
|
||||
if (needAllResponseContent) {
|
||||
return res.text().then(text => {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (e) {
|
||||
return text;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const data = res.json();
|
||||
return data;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('❌ Request error:', err.message);
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
|
||||
// SSE POST 请求
|
||||
// SSE请求处理
|
||||
export const ssePost = (
|
||||
url: string,
|
||||
fetchOptions: any,
|
||||
@@ -430,10 +401,9 @@ export const ssePost = (
|
||||
method: 'POST',
|
||||
}, fetchOptions);
|
||||
|
||||
// 修复URL构建逻辑,与baseFetch保持一致
|
||||
// 直接构建Dify API URL
|
||||
const urlWithPrefix = `${CHAT_CONFIG.API_URL}/${url.replace(/^\//, '')}`;
|
||||
|
||||
|
||||
const controller = new AbortController();
|
||||
if (getAbortController)
|
||||
getAbortController(controller);
|
||||
@@ -442,25 +412,28 @@ export const ssePost = (
|
||||
...options.headers,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': ContentType.stream,
|
||||
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
|
||||
};
|
||||
|
||||
if (CHAT_CONFIG.API_KEY) {
|
||||
options.headers['Authorization'] = `Bearer ${CHAT_CONFIG.API_KEY}`;
|
||||
}
|
||||
|
||||
options.signal = controller.signal;
|
||||
|
||||
const { body } = options;
|
||||
if (body && typeof body === 'object')
|
||||
options.body = JSON.stringify(body);
|
||||
if (body && typeof body === 'object') {
|
||||
// 为SSE请求添加user参数
|
||||
const bodyWithUser = {
|
||||
...body,
|
||||
user: CHAT_CONFIG.generateUserId(),
|
||||
};
|
||||
options.body = JSON.stringify(bodyWithUser);
|
||||
}
|
||||
|
||||
return fetch(urlWithPrefix, options)
|
||||
.then((res: Response) => {
|
||||
// console.log('📡 SSE Response:', {
|
||||
// status: res.status,
|
||||
// statusText: res.statusText,
|
||||
// url: urlWithPrefix
|
||||
// });
|
||||
console.log('📡 SSE Response:', {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
url: urlWithPrefix
|
||||
});
|
||||
|
||||
if (!/^(2|3)\d{2}$/.test(res.status.toString())) {
|
||||
res.json().then((data: any) => {
|
||||
@@ -491,6 +464,194 @@ export const ssePost = (
|
||||
});
|
||||
};
|
||||
|
||||
// 获取会话列表
|
||||
export const fetchConversations = async () => {
|
||||
const user = CHAT_CONFIG.generateUserId();
|
||||
const params = new URLSearchParams({
|
||||
user,
|
||||
limit: '100',
|
||||
first_id: '',
|
||||
});
|
||||
|
||||
return fetch(`${CHAT_CONFIG.API_URL}/conversations?${params}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
|
||||
},
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch conversations: ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
};
|
||||
|
||||
// 获取聊天消息列表
|
||||
export const fetchChatList = async (conversationId: string) => {
|
||||
const user = CHAT_CONFIG.generateUserId();
|
||||
const params = new URLSearchParams({
|
||||
user,
|
||||
conversation_id: conversationId,
|
||||
limit: '20',
|
||||
last_id: '',
|
||||
});
|
||||
|
||||
return fetch(`${CHAT_CONFIG.API_URL}/messages?${params}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
|
||||
},
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch chat list: ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
};
|
||||
|
||||
// 获取应用参数
|
||||
export const fetchAppParams = async () => {
|
||||
return fetch(`${CHAT_CONFIG.API_URL}/parameters`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
|
||||
},
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch app params: ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
};
|
||||
|
||||
// 更新反馈
|
||||
export const updateFeedback = async ({ url, body }: { url: string; body: Feedbacktype }) => {
|
||||
const messageId = url.split('/').pop(); // 从URL中提取messageId
|
||||
|
||||
return fetch(`${CHAT_CONFIG.API_URL}/messages/${messageId}/feedbacks`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...body,
|
||||
user: CHAT_CONFIG.generateUserId(),
|
||||
}),
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to update feedback: ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
};
|
||||
|
||||
// 生成会话名称
|
||||
export const generateConversationName = async (id: string) => {
|
||||
return fetch(`${CHAT_CONFIG.API_URL}/conversations/${id}/name`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
auto_generate: true,
|
||||
user: CHAT_CONFIG.generateUserId(),
|
||||
}),
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to generate conversation name: ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
};
|
||||
|
||||
// 重命名会话
|
||||
export const renameConversation = async (id: string, name: string, autoGenerate: boolean = false) => {
|
||||
return fetch(`${CHAT_CONFIG.API_URL}/conversations/${id}/name`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: autoGenerate ? undefined : name,
|
||||
auto_generate: autoGenerate,
|
||||
user: CHAT_CONFIG.generateUserId(),
|
||||
}),
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to rename conversation: ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
};
|
||||
|
||||
// 删除会话
|
||||
export const deleteConversation = async (id: string) => {
|
||||
return fetch(`${CHAT_CONFIG.API_URL}/conversations/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user: CHAT_CONFIG.generateUserId(),
|
||||
}),
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to delete conversation: ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
};
|
||||
|
||||
// 文件上传
|
||||
export const upload = (fetchOptions: any): Promise<any> => {
|
||||
const urlWithPrefix = `${CHAT_CONFIG.API_URL}/files/upload`;
|
||||
|
||||
const defaultOptions = {
|
||||
method: 'POST',
|
||||
url: urlWithPrefix,
|
||||
data: {},
|
||||
};
|
||||
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
...fetchOptions,
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = options.xhr;
|
||||
xhr.open(options.method, options.url);
|
||||
|
||||
for (const key in options.headers)
|
||||
xhr.setRequestHeader(key, options.headers[key]);
|
||||
|
||||
if (CHAT_CONFIG.API_KEY) {
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${CHAT_CONFIG.API_KEY}`);
|
||||
}
|
||||
|
||||
// 添加user参数到formData
|
||||
if (options.data instanceof FormData) {
|
||||
options.data.append('user', CHAT_CONFIG.generateUserId());
|
||||
}
|
||||
|
||||
xhr.withCredentials = false; // 改为false,因为直接调用Dify API
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200)
|
||||
resolve({ id: xhr.response });
|
||||
else
|
||||
reject(new Error(xhr.responseText || 'Upload failed'));
|
||||
}
|
||||
};
|
||||
|
||||
xhr.upload.onprogress = options.onprogress;
|
||||
xhr.send(options.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 公共请求函数
|
||||
export const request = (url: string, options = {}, needAllResponseContent = false) => {
|
||||
return baseFetch(url, { ...baseOptions, ...options }, needAllResponseContent);
|
||||
@@ -566,91 +727,4 @@ export const sendChatMessage = async (
|
||||
onWorkflowFinished,
|
||||
onNodeFinished
|
||||
});
|
||||
};
|
||||
|
||||
// 获取会话列表
|
||||
export const fetchConversations = async () => {
|
||||
return get('conversations', {
|
||||
params: { limit: 100, first_id: '' },
|
||||
});
|
||||
};
|
||||
|
||||
// 获取聊天消息列表
|
||||
export const fetchChatList = async (conversationId: string) => {
|
||||
return get('messages', {
|
||||
params: { conversation_id: conversationId, limit: 20, last_id: '' },
|
||||
});
|
||||
};
|
||||
|
||||
// 获取应用参数
|
||||
export const fetchAppParams = async () => {
|
||||
return get('parameters');
|
||||
};
|
||||
|
||||
// 更新反馈
|
||||
export const updateFeedback = async ({ url, body }: { url: string; body: Feedbacktype }) => {
|
||||
return post(url, { body });
|
||||
};
|
||||
|
||||
// 生成会话名称
|
||||
export const generateConversationName = async (id: string) => {
|
||||
return post(`conversations/${id}/name`, {
|
||||
body: { auto_generate: true },
|
||||
});
|
||||
};
|
||||
|
||||
// 重命名会话
|
||||
export const renameConversation = async (id: string, name: string, autoGenerate: boolean = false) => {
|
||||
return post(`conversations/${id}/name`, {
|
||||
body: {
|
||||
name: autoGenerate ? undefined : name,
|
||||
auto_generate: autoGenerate
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 删除会话
|
||||
export const deleteConversation = async (id: string) => {
|
||||
return del(`conversations/${id}`);
|
||||
};
|
||||
|
||||
// 文件上传
|
||||
export const upload = (fetchOptions: any): Promise<any> => {
|
||||
const urlPrefix = CHAT_CONFIG.API_PREFIX;
|
||||
const urlWithPrefix = `${CHAT_CONFIG.API_URL}${urlPrefix}/file-upload`;
|
||||
|
||||
const defaultOptions = {
|
||||
method: 'POST',
|
||||
url: urlWithPrefix,
|
||||
data: {},
|
||||
};
|
||||
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
...fetchOptions,
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = options.xhr;
|
||||
xhr.open(options.method, options.url);
|
||||
|
||||
for (const key in options.headers)
|
||||
xhr.setRequestHeader(key, options.headers[key]);
|
||||
|
||||
if (CHAT_CONFIG.API_KEY) {
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${CHAT_CONFIG.API_KEY}`);
|
||||
}
|
||||
|
||||
xhr.withCredentials = true;
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200)
|
||||
resolve({ id: xhr.response });
|
||||
else
|
||||
reject(xhr);
|
||||
}
|
||||
};
|
||||
xhr.upload.onprogress = options.onprogress;
|
||||
xhr.send(options.data);
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user