Skip to content

Commit 9259c1d

Browse files
c121914yuxqvvu
andauthored
feat: close wecom channel;fix: scope chat resume to current app (#6874)
* feat: close wecom channel * fix: scope chat resume to current app (#6860) * fix: scope chat resume to current app Abort stale resume streams when switching apps and only resume after the current app/chat init state is aligned. Made-with: Cursor * refactor: centralize resume stream cancellation Ensure only one resume stream is active per client tab and discard queued resume messages after abort. Made-with: Cursor * fix: make sure controller unique * chore: remove redundant effect * perf: remove invalid code * update package --------- Co-authored-by: Ryo <whoeverimf5@gmail.com>
1 parent 34b351b commit 9259c1d

8 files changed

Lines changed: 105 additions & 40 deletions

File tree

.vscode/settings.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
"editor.mouseWheelZoom": true,
44
"editor.defaultFormatter": "esbenp.prettier-vscode",
55
"prettier.prettierPath": "node_modules/prettier",
6-
"js/ts.preferences.includePackageJsonAutoImports": "on",
7-
"js/ts.tsdk.path": "node_modules/typescript/lib",
86
"i18n-ally.localesPaths": [
97
"packages/web/i18n",
108
],
@@ -26,6 +24,9 @@
2624
"[typescript]": {
2725
"editor.defaultFormatter": "esbenp.prettier-vscode"
2826
},
27+
"[typescriptreact]": {
28+
"editor.defaultFormatter": "esbenp.prettier-vscode"
29+
},
2930
"mdx.server.enable": true,
3031
"markdown.copyFiles.overwriteBehavior": "nameIncrementally",
3132
"markdown.copyFiles.destination": {

pro

Submodule pro updated from 4aba60a to 6c51ba8

projects/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fastgpt/app",
3-
"version": "4.14.17",
3+
"version": "4.14.18",
44
"private": false,
55
"scripts": {
66
"dev": "pnpm run build:workers && next dev",

projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ const ChatBox = ({
154154
const questionGuideController = useRef(new AbortController());
155155
const pluginController = useRef(new AbortController());
156156
const resumeController = useRef<AbortController>();
157-
const resumedChatIdRef = useRef<string>();
157+
const resumedChatTargetRef = useRef<string>();
158158

159159
const [isLoading, setIsLoading] = useState(false);
160160
const [feedbackId, setFeedbackId] = useState<string>();
@@ -180,6 +180,8 @@ const ChatBox = ({
180180

181181
const appId = useContextSelector(WorkflowRuntimeContext, (v) => v.appId);
182182
const chatId = useContextSelector(WorkflowRuntimeContext, (v) => v.chatId);
183+
const activeAppIdRef = useRef<string | undefined>(appId);
184+
activeAppIdRef.current = appId;
183185
const activeChatIdRef = useRef<string | undefined>(chatId);
184186
activeChatIdRef.current = chatId;
185187
const outLinkAuthData = useContextSelector(WorkflowRuntimeContext, (v) => v.outLinkAuthData);
@@ -198,18 +200,21 @@ const ChatBox = ({
198200
const syncSidebarChatGenerateStatus = useMemoizedFn(
199201
(
200202
status: ChatGenerateStatusEnum,
201-
options?: { hasBeenRead?: boolean; targetChatId?: string }
203+
options?: { hasBeenRead?: boolean; targetAppId?: string; targetChatId?: string }
202204
) => {
205+
const targetAppId = options?.targetAppId ?? appId;
206+
if (targetAppId !== appId) return;
207+
203208
const targetChatId = options?.targetChatId ?? chatId;
204209
if (!targetChatId) return;
205210
setHistories((prev) => {
206-
const idx = prev.findIndex((h) => h.chatId === targetChatId);
211+
const idx = prev.findIndex((h) => h.chatId === targetChatId && h.appId === targetAppId);
207212
if (idx === -1) {
208213
queueMicrotask(loadHistories);
209214
return [
210215
{
211216
chatId: targetChatId,
212-
appId,
217+
appId: targetAppId,
213218
title: chatBoxData.title || t('common:core.chat.New Chat'),
214219
customTitle: '',
215220
top: false,
@@ -221,7 +226,7 @@ const ChatBox = ({
221226
];
222227
}
223228
return prev.map((h) =>
224-
h.chatId === targetChatId
229+
h.chatId === targetChatId && h.appId === targetAppId
225230
? {
226231
...h,
227232
chatGenerateStatus: status,
@@ -662,16 +667,26 @@ const ChatBox = ({
662667
});
663668
});
664669

670+
const isActiveResumeTarget = useMemoizedFn(
671+
({ appId, chatId }: { appId: string; chatId: string }) =>
672+
activeAppIdRef.current === appId && activeChatIdRef.current === chatId
673+
);
674+
665675
const getResumeUnavailablePlaceholderText = useMemoizedFn(() =>
666676
t('chat:resume_placeholder_generating')
667677
);
668678

669679
const upsertResumeAiPlaceholder = useMemoizedFn(
670-
(responseChatId: string, text = '', status: `${ChatStatusEnum}` = ChatStatusEnum.loading) => {
680+
(
681+
responseChatId: string,
682+
text = '',
683+
status: `${ChatStatusEnum}` = ChatStatusEnum.loading,
684+
options?: { resetExistingValue?: boolean }
685+
) => {
671686
setChatRecords((state) => {
672687
const lastItem = state[state.length - 1];
673688
if (lastItem?.dataId === responseChatId && lastItem.obj === ChatRoleEnum.AI) {
674-
if (!text) {
689+
if (!text && !options?.resetExistingValue) {
675690
return state;
676691
}
677692

@@ -687,6 +702,7 @@ const ChatBox = ({
687702
}
688703
}
689704
],
705+
responseData: options?.resetExistingValue ? [] : item.responseData,
690706
status,
691707
...(status === ChatStatusEnum.finish ? { time: new Date() } : {})
692708
}
@@ -833,7 +849,7 @@ const ChatBox = ({
833849
}
834850
];
835851

836-
resumedChatIdRef.current = chatId;
852+
resumedChatTargetRef.current = `${appId}:${chatId}`;
837853

838854
setChatBoxData((state) =>
839855
state.chatId === chatId
@@ -1252,8 +1268,12 @@ const ChatBox = ({
12521268
useEffect(() => {
12531269
setQuestionGuide([]);
12541270
setValue('chatStarted', false);
1255-
resumedChatIdRef.current = undefined;
1256-
abortRequest('leave');
1271+
resumedChatTargetRef.current = undefined;
1272+
// abortRequest('leave');
1273+
1274+
return () => {
1275+
abortRequest('leave');
1276+
};
12571277
}, [chatId, appId, abortRequest, setValue]);
12581278

12591279
useEffect(() => {
@@ -1264,20 +1284,24 @@ const ChatBox = ({
12641284
!appId ||
12651285
!chatId ||
12661286
isChatting ||
1287+
chatBoxData.appId !== appId ||
1288+
chatBoxData.chatId !== chatId ||
12671289
chatBoxData.chatGenerateStatus !== ChatGenerateStatusEnum.generating ||
1268-
resumedChatIdRef.current === chatId
1290+
resumedChatTargetRef.current === `${appId}:${chatId}`
12691291
) {
12701292
return;
12711293
}
12721294

1273-
resumedChatIdRef.current = chatId;
1295+
resumedChatTargetRef.current = `${appId}:${chatId}`;
12741296

1297+
const resumeForAppId = appId;
12751298
const resumeForChatId = chatId;
12761299
const responseChatId = resumeTargetAiDataId ?? getNanoid(24);
12771300
const controller = new AbortController();
12781301
resumeController.current = controller;
12791302
scrollToBottom('auto');
12801303
let resumeFinalStatus = ChatGenerateStatusEnum.done;
1304+
let hasPreparedResumeAiRecord = false;
12811305

12821306
(async () => {
12831307
try {
@@ -1287,7 +1311,7 @@ const ChatBox = ({
12871311
outLinkAuthData,
12881312
controller,
12891313
onResumeUnavailable: () => {
1290-
if (resumeForChatId !== activeChatIdRef.current) return;
1314+
if (!isActiveResumeTarget({ appId: resumeForAppId, chatId: resumeForChatId })) return;
12911315
resumeFinalStatus = ChatGenerateStatusEnum.generating;
12921316
upsertResumeAiPlaceholder(
12931317
responseChatId,
@@ -1296,15 +1320,18 @@ const ChatBox = ({
12961320
);
12971321
},
12981322
onmessage: (message) => {
1299-
if (resumeForChatId !== activeChatIdRef.current) return;
1323+
if (!isActiveResumeTarget({ appId: resumeForAppId, chatId: resumeForChatId })) return;
13001324
if (shouldCreateResumeAiPlaceholder(message.event)) {
1301-
upsertResumeAiPlaceholder(responseChatId);
1325+
upsertResumeAiPlaceholder(responseChatId, '', ChatStatusEnum.loading, {
1326+
resetExistingValue: !hasPreparedResumeAiRecord
1327+
});
1328+
hasPreparedResumeAiRecord = true;
13021329
}
13031330
generatingMessage(message);
13041331
}
13051332
});
13061333

1307-
if (resumeForChatId !== activeChatIdRef.current) return;
1334+
if (!isActiveResumeTarget({ appId: resumeForAppId, chatId: resumeForChatId })) return;
13081335

13091336
if (completedChat) {
13101337
resumeFinalStatus = completedChat.chatGenerateStatus;
@@ -1359,7 +1386,7 @@ const ChatBox = ({
13591386
});
13601387
} catch (error) {
13611388
if (controller.signal.aborted) return;
1362-
if (resumeForChatId !== activeChatIdRef.current) return;
1389+
if (!isActiveResumeTarget({ appId: resumeForAppId, chatId: resumeForChatId })) return;
13631390

13641391
const isStreamError = (error as ResumeStreamErrorType | undefined)?.isStreamError === true;
13651392
resumeFinalStatus = isStreamError
@@ -1404,8 +1431,13 @@ const ChatBox = ({
14041431
});
14051432
}
14061433
} finally {
1407-
resumeController.current = undefined;
1408-
const finishedInActiveChat = activeChatIdRef.current === resumeForChatId;
1434+
if (resumeController.current === controller) {
1435+
resumeController.current = undefined;
1436+
}
1437+
const finishedInActiveChat = isActiveResumeTarget({
1438+
appId: resumeForAppId,
1439+
chatId: resumeForChatId
1440+
});
14091441
const leftWhileResuming =
14101442
controller.signal.aborted && isAbortByLeave(controller.signal.reason);
14111443

@@ -1414,7 +1446,7 @@ const ChatBox = ({
14141446
}
14151447

14161448
setChatBoxData((state) =>
1417-
state.chatId === resumeForChatId
1449+
state.appId === resumeForAppId && state.chatId === resumeForChatId
14181450
? {
14191451
...state,
14201452
chatGenerateStatus: resumeFinalStatus,
@@ -1425,19 +1457,21 @@ const ChatBox = ({
14251457

14261458
if (finishedInActiveChat) {
14271459
void postMarkChatRead({
1428-
appId,
1460+
appId: resumeForAppId,
14291461
chatId: resumeForChatId,
14301462
...outLinkAuthData
14311463
})
14321464
.catch(() => {})
14331465
.finally(() => {
14341466
syncSidebarChatGenerateStatus(resumeFinalStatus, {
1467+
targetAppId: resumeForAppId,
14351468
hasBeenRead: true,
14361469
targetChatId: resumeForChatId
14371470
});
14381471
});
14391472
} else {
14401473
syncSidebarChatGenerateStatus(resumeFinalStatus, {
1474+
targetAppId: resumeForAppId,
14411475
hasBeenRead: false,
14421476
targetChatId: resumeForChatId
14431477
});
@@ -1451,9 +1485,12 @@ const ChatBox = ({
14511485
appId,
14521486
chatId,
14531487
isChatting,
1488+
chatBoxData.appId,
1489+
chatBoxData.chatId,
14541490
chatBoxData.chatGenerateStatus,
14551491
generatingMessage,
14561492
hasMeaningfulAiOutput,
1493+
isActiveResumeTarget,
14571494
getResumeUnavailablePlaceholderText,
14581495
outLinkAuthData,
14591496
resumeTargetAiDataId,

projects/app/src/pageComponents/app/detail/Publish/index.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,17 @@ const OutLink = () => {
4646
value: PublishChannelEnum.apikey,
4747
isProFn: false
4848
},
49-
{
50-
icon: 'core/app/publish/wechat',
51-
title: t('publish:wechat.bot'),
52-
desc: t('publish:wechat.bot_desc'),
53-
value: PublishChannelEnum.wechat,
54-
isProFn: false
55-
},
49+
...(feConfigs?.show_publish_wechat !== false
50+
? [
51+
{
52+
icon: 'core/app/publish/wechat',
53+
title: t('publish:wechat.bot'),
54+
desc: t('publish:wechat.bot_desc'),
55+
value: PublishChannelEnum.wechat,
56+
isProFn: false
57+
}
58+
]
59+
: []),
5660
...(feConfigs?.show_publish_feishu !== false &&
5761
!userInfo?.tags?.includes(UserTagsEnum.enum.wecom)
5862
? [

projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ const AppChatWindow = () => {
5252
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
5353
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
5454

55+
const isCurrentChatReady = chatBoxData.appId === appId && chatBoxData.chatId === chatId;
56+
5557
const pane = useContextSelector(ChatPageContext, (v) => v.pane);
5658
const chatSettings = useContextSelector(ChatPageContext, (v) => v.chatSettings);
5759
const handlePaneChange = useContextSelector(ChatPageContext, (v) => v.handlePaneChange);
@@ -192,7 +194,7 @@ const AppChatWindow = () => {
192194
<ChatBox
193195
appId={appId}
194196
chatId={chatId}
195-
isReady={!loading && !!appId}
197+
isReady={!loading && !!appId && isCurrentChatReady}
196198
enableAutoResume
197199
feedbackType={'user'}
198200
chatType={ChatTypeEnum.chat}

projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ const HomeChatWindow = () => {
8989
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
9090
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
9191

92+
const isCurrentChatReady = chatBoxData.appId === appId && chatBoxData.chatId === chatId;
93+
9294
const isQuickApp = useMemo(
9395
() => chatSettings?.quickAppList.some((app) => app._id === appId),
9496
[chatSettings?.quickAppList, appId]
@@ -463,7 +465,7 @@ const HomeChatWindow = () => {
463465
<ChatBox
464466
appId={appId}
465467
chatId={chatId}
466-
isReady={!loading && !!appId}
468+
isReady={!loading && !!appId && isCurrentChatReady}
467469
enableAutoResume
468470
feedbackType={'user'}
469471
chatType={ChatTypeEnum.home}

0 commit comments

Comments
 (0)