Files
FastGPT/projects/app/src/web/common/api/fetch.ts
Archer 439c819ff1 4.8 preview (#1288)
* Revert "lafAccount add pat & re request when token invalid (#76)" (#77)

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

* perf: workflow ux

* system config

* Newflow (#89)

* docs: Add doc for Xinference (#1266)

Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>

* Revert "lafAccount add pat & re request when token invalid (#76)" (#77)

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

* perf: workflow ux

* system config

* Revert "lafAccount add pat & re request when token invalid (#76)" (#77)

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

* Revert "lafAccount add pat & re request when token invalid (#76)" (#77)

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

* Revert "lafAccount add pat & re request when token invalid (#76)" (#77)

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

* rename code

* move code

* update flow

* input type selector

* perf: workflow runtime

* feat: node adapt newflow

* feat: adapt plugin

* feat: 360 connection

* check workflow

* perf: flow 性能

* change plugin input type (#81)

* change plugin input type

* plugin label mode

* perf: nodecard

* debug

* perf: debug ui

* connection ui

* change workflow ui (#82)

* feat: workflow debug

* adapt openAPI for new workflow (#83)

* adapt openAPI for new workflow

* i18n

* perf: plugin debug

* plugin input ui

* delete

* perf: global variable select

* fix rebase

* perf: workflow performance

* feat: input render type icon

* input icon

* adapt flow (#84)

* adapt newflow

* temp

* temp

* fix

* feat: app schedule trigger

* feat: app schedule trigger

* perf: schedule ui

* feat: ioslatevm run js code

* perf: workflow varialbe table ui

* feat: adapt simple mode

* feat: adapt input params

* output

* feat: adapt tamplate

* fix: ts

* add if-else module (#86)

* perf: worker

* if else node

* perf: tiktoken worker

* fix: ts

* perf: tiktoken

* fix if-else node (#87)

* fix if-else node

* type

* fix

* perf: audio render

* perf: Parallel worker

* log

* perf: if else node

* adapt plugin

* prompt

* perf: reference ui

* reference ui

* handle ux

* template ui and plugin tool

* adapt v1 workflow

* adapt v1 workflow completions

* perf: time variables

* feat: workflow keyboard shortcuts

* adapt v1 workflow

* update workflow example doc (#88)

* fix: simple mode select tool

---------

Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
Co-authored-by: Carson Yang <yangchuansheng33@gmail.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>

* doc

* perf: extract node

* extra node field

* update plugin version

* doc

* variable

* change doc & fix prompt editor (#90)

* fold workflow code

* value type label

---------

Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
Co-authored-by: Carson Yang <yangchuansheng33@gmail.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
2024-04-25 17:51:20 +08:00

233 lines
6.8 KiB
TypeScript

import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { getErrText } from '@fastgpt/global/common/error/utils';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import type { StartChatFnProps } from '@/components/ChatBox/type.d';
import { getToken } from '@/web/support/user/auth';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import dayjs from 'dayjs';
import {
// refer to https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web
EventStreamContentType,
fetchEventSource
} from '@fortaine/fetch-event-source';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { useSystemStore } from '../system/useSystemStore';
type StreamFetchProps = {
url?: string;
data: Record<string, any>;
onMessage: StartChatFnProps['generatingMessage'];
abortCtrl: AbortController;
};
type StreamResponseType = {
responseText: string;
[DispatchNodeResponseKeyEnum.nodeResponse]: ChatHistoryItemResType[];
};
class FatalError extends Error {}
export const streamFetch = ({
url = '/api/v1/chat/completions',
data,
onMessage,
abortCtrl
}: StreamFetchProps) =>
new Promise<StreamResponseType>(async (resolve, reject) => {
const timeoutId = setTimeout(() => {
abortCtrl.abort('Time out');
}, 60000);
// response data
let responseText = '';
let responseQueue: (
| { event: SseResponseEventEnum.fastAnswer | SseResponseEventEnum.answer; text: string }
| {
event:
| SseResponseEventEnum.toolCall
| SseResponseEventEnum.toolParams
| SseResponseEventEnum.toolResponse;
[key: string]: any;
}
)[] = [];
let errMsg: string | undefined;
let responseData: ChatHistoryItemResType[] = [];
let finished = false;
const finish = () => {
if (errMsg !== undefined) {
return failedFinish();
}
return resolve({
responseText,
responseData
});
};
const failedFinish = (err?: any) => {
finished = true;
reject({
message: getErrText(err, errMsg ?? '响应过程出现异常~'),
responseText
});
};
const isAnswerEvent = (event: `${SseResponseEventEnum}`) =>
event === SseResponseEventEnum.answer || event === SseResponseEventEnum.fastAnswer;
// animate response to make it looks smooth
function animateResponseText() {
// abort message
if (abortCtrl.signal.aborted) {
responseQueue.forEach((item) => {
onMessage(item);
if (isAnswerEvent(item.event)) {
responseText += item.text;
}
});
return finish();
}
if (responseQueue.length > 0) {
const fetchCount = Math.max(1, Math.round(responseQueue.length / 30));
for (let i = 0; i < fetchCount; i++) {
const item = responseQueue[i];
onMessage(item);
if (isAnswerEvent(item.event)) {
responseText += item.text;
}
}
responseQueue = responseQueue.slice(fetchCount);
}
if (finished && responseQueue.length === 0) {
return finish();
}
requestAnimationFrame(animateResponseText);
}
// start animation
animateResponseText();
try {
// auto complete variables
const variables = data?.variables || {};
variables.cTime = dayjs().format('YYYY-MM-DD HH:mm:ss dddd');
const requestData = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
token: getToken()
},
signal: abortCtrl.signal,
body: JSON.stringify({
...data,
variables,
detail: true,
stream: true
})
};
// send request
await fetchEventSource(url, {
...requestData,
async onopen(res) {
clearTimeout(timeoutId);
const contentType = res.headers.get('content-type');
// not stream
if (contentType?.startsWith('text/plain')) {
return failedFinish(await res.clone().text());
}
// failed stream
if (
!res.ok ||
!res.headers.get('content-type')?.startsWith(EventStreamContentType) ||
res.status !== 200
) {
try {
failedFinish(await res.clone().json());
} catch {
const errText = await res.clone().text();
if (!errText.startsWith('event: error')) {
failedFinish();
}
}
}
},
onmessage({ event, data }) {
if (data === '[DONE]') {
return;
}
// parse text to json
const parseJson = (() => {
try {
return JSON.parse(data);
} catch (error) {
return {};
}
})();
// console.log(parseJson, event);
if (event === SseResponseEventEnum.answer) {
const text = parseJson.choices?.[0]?.delta?.content || '';
for (const item of text) {
responseQueue.push({
event,
text: item
});
}
} else if (event === SseResponseEventEnum.fastAnswer) {
const text = parseJson.choices?.[0]?.delta?.content || '';
responseQueue.push({
event,
text
});
} else if (
event === SseResponseEventEnum.toolCall ||
event === SseResponseEventEnum.toolParams ||
event === SseResponseEventEnum.toolResponse
) {
responseQueue.push({
event,
...parseJson
});
} else if (event === SseResponseEventEnum.flowNodeStatus) {
onMessage({
event,
...parseJson
});
} else if (event === SseResponseEventEnum.flowResponses && Array.isArray(parseJson)) {
responseData = parseJson;
} else if (event === SseResponseEventEnum.error) {
if (parseJson.statusText === TeamErrEnum.aiPointsNotEnough) {
useSystemStore.getState().setIsNotSufficientModal(true);
}
errMsg = getErrText(parseJson, '流响应错误');
}
},
onclose() {
finished = true;
},
onerror(err) {
if (err instanceof FatalError) {
throw err;
}
clearTimeout(timeoutId);
failedFinish(getErrText(err));
},
openWhenHidden: true
});
} catch (err: any) {
clearTimeout(timeoutId);
if (abortCtrl.signal.aborted) {
finished = true;
return;
}
console.log(err, 'fetch error');
failedFinish(err);
}
});