fix(web): keep reasoning and internet options when regenerating an answer (#18226)

This commit is contained in:
chanx
2026-08-13 18:52:04 +08:00
committed by GitHub
parent 34d38b4f80
commit 9d1a779af9
3 changed files with 53 additions and 17 deletions

View File

@@ -57,10 +57,10 @@ import {
UseSendSingleMessageParameter,
} from '../../hooks/use-send-single-message';
import { useUploadFile } from '../../hooks/use-upload-file';
import { useMessageReferences } from '../../hooks/use-message-references';
import { EmptyReference } from '../../utils';
import { EmptyReference, resolveResendOptions } from '../../utils';
import { useAddChatBox } from '../use-add-box';
import { useShowInternet } from '../use-show-internet';
import { useMessageReferences } from '../../hooks/use-message-references';
type MultipleChatBoxProps = {
controller: AbortController;
@@ -140,6 +140,11 @@ const ChatCard = forwardRef(function ChatCard(
const llmId = useWatch({ control: form.control, name: 'llm_id' });
// Regenerate is triggered from the transcript, which has no access to the
// input box's thinking / internet toggles. Remember what the last send used so
// a retry keeps the same options instead of silently dropping them.
const lastSendOptionsRef = useRef<NextMessageInputOnPressEnterParameter>({});
// Regenerate within this card: reuse the card's own message state and
// resend with the card's model settings (llm_id, temperature, ...).
const sendCardMessage = useCallback(
@@ -147,6 +152,7 @@ const ChatCard = forwardRef(function ChatCard(
sendMessage({
message,
messages,
...resolveResendOptions(lastSendOptionsRef.current),
...form.getValues(),
storeHistoryMessages: false,
omitSessionId: true,
@@ -203,13 +209,15 @@ const ChatCard = forwardRef(function ChatCard(
useImperativeHandle(
ref,
(): HandlePressEnterType => (params) =>
handlePressEnter({
(): HandlePressEnterType => (params) => {
lastSendOptionsRef.current = params;
return handlePressEnter({
...params,
...form.getValues(),
storeHistoryMessages: false,
omitSessionId: true,
}),
});
},
);
useEffect(() => {

View File

@@ -20,6 +20,7 @@ import {
useChatStreamStore,
useIsChatStreaming,
} from '../chat-stream/store';
import { resolveResendOptions } from '../utils';
import { useCreateConversationBeforeSendMessage } from './use-chat-url';
import { useFindPrologueFromDialogList } from './use-select-conversation-list';
import { useUploadFile } from './use-upload-file';
@@ -88,6 +89,11 @@ export const useSendMessage = () => {
);
const stopStream = useChatStreamStore((state) => state.stopStream);
// The regenerate button lives in the transcript, which has no access to the
// input box's thinking / internet toggles. Remember what the last send used
// so a retry keeps the same options instead of silently dropping them.
const lastSendOptionsRef = useRef<NextMessageInputOnPressEnterParameter>({});
const sendMessage = useCallback(
async ({
message,
@@ -98,17 +104,21 @@ export const useSendMessage = () => {
}: {
message: IMessage;
currentConversationId?: string;
messages: IMessage[];
messages?: IMessage[];
} & NextMessageInputOnPressEnterParameter) => {
const sessionId = currentConversationId ?? conversationId;
lastSendOptionsRef.current = { enableInternet, enableThinking };
const { ok, aborted } = await runChatCompletionStream({
conversationId: sessionId,
chatId,
// The history is always supplied by the caller, captured *before* it
// appended the question and its placeholder to the store — reading it
// here would include both and send the question twice.
messages: [...explicitMessages, message],
// An explicitly provided list is authoritative, even when empty
// (e.g. regenerating the first question must truncate history).
messages: [
...(Array.isArray(explicitMessages) ? explicitMessages : messages),
message,
],
enableThinking,
enableInternet,
});
@@ -122,7 +132,7 @@ export const useSendMessage = () => {
notification.error({ message: t('message.requestError') });
}
},
[conversationId, chatId, failStream, t],
[conversationId, chatId, messages, failStream, t],
);
// Hand a failed question back to the input box, but only once the box is
@@ -154,13 +164,11 @@ export const useSendMessage = () => {
conversationId,
};
// Snapshot the history before appendQuestion writes the question and its
// assistant placeholder into the store.
const history =
useChatStreamStore.getState().sessions[conversationId]?.messages ?? [];
appendQuestion(conversationId, questionMessage);
sendMessage({ message: questionMessage, messages: history });
sendMessage({
message: questionMessage,
...resolveResendOptions(lastSendOptionsRef.current),
});
},
[conversationId, isStreaming, appendQuestion, sendMessage],
);

View File

@@ -1,5 +1,25 @@
import { NextMessageInputOnPressEnterParameter } from '@/components/message-input/next';
import { EmptyConversationId } from '@/constants/chat';
import { IConversation, IReference } from '@/interfaces/database/chat';
import storage from '@/utils/authorization-util';
/**
* Regenerate is triggered from the transcript, which has no access to the input
* box's thinking / internet toggles, so callers replay the options of their last
* send. A view that hasn't sent anything yet has no record: fall back to the
* input box's own defaults — it re-reads the persisted thinking level and starts
* with internet off, so both stay in sync after a remount.
*/
export function resolveResendOptions(
lastSendOptions: NextMessageInputOnPressEnterParameter,
): NextMessageInputOnPressEnterParameter {
const {
enableThinking = storage.getThinkingLevel(),
enableInternet = false,
} = lastSendOptions;
return { enableThinking, enableInternet };
}
export const isConversationIdExist = (conversationId: string) => {
return conversationId !== EmptyConversationId && conversationId !== '';