Refactor: refine chat message to use question only (#20654)

This commit is contained in:
Wang Qi
2026-10-09 20:08:10 +08:00
committed by GitHub
parent c5c75b2c5d
commit abc31142c4
12 changed files with 42 additions and 49 deletions
+1 -7
View File
@@ -3845,13 +3845,11 @@ Starts a chat completion request. The same endpoint supports three modes:
- `'Authorization: Bearer <YOUR_API_KEY>'`
- Body:
- `"messages"`: `list[object]`
- `"question"`: `string`
- `"stream"`: `boolean`
- `"chat_id"`: `string` (optional)
- `"session_id"`: `string` (optional)
- `"llm_id"`: `string` (optional)
- `"pass_all_history_messages"`: `boolean` (optional)
- `"legacy"`: `boolean` (optional)
##### Request example
@@ -3893,10 +3891,8 @@ curl --request POST \
##### Request Parameters
- `"messages"`: (*Body Parameter*), `list[object]`
The latest user message, or the conversation messages sent to the model when `pass_all_history_messages` is `true`. Either `messages` or `question` is required.
- `"question"`: (*Body Parameter*), `string`
Latest user question. This is equivalent to passing `messages: [{"role": "user", "content": question}]`.
The user question.
- `"stream"`: (*Body Parameter*), `boolean`
Enables streaming output:
- `true`: Enable streaming (default).
@@ -3907,8 +3903,6 @@ curl --request POST \
Optional session ID. If `chat_id` is provided but `session_id` is omitted, a new session will be generated automatically.
- `"llm_id"`: (*Body Parameter*), `string`
Optional model override when a specific chat model should be used for this request.
- `"pass_all_history_messages"`: (*Body Parameter*), `boolean`
When `chat_id` and `session_id` are provided, defaults to `false`, so the server uses stored session history and only the latest user message from the request. Set to `true` to replace/use the submitted full `messages` history, and overrides the stored session history.
- `"legacy"`: (*Body Parameter*), `boolean`
Defaults to `false`. Enables backward compatibility with RAGFlow v0.23.0 for streaming responses. When set to `true`:
- Cumulative output: The `"answer"` field in each chunk returns the entire text generated so far, rather than just the new tokens (deltas).
@@ -163,12 +163,14 @@ interface UserGroupButtonProps extends Partial<IRemoveMessageById> {
content: string;
regenerateMessage?: () => void;
sendLoading: boolean;
deleteDisabled?: boolean;
}
export const UserGroupButton = ({
content,
messageId,
sendLoading,
deleteDisabled = false,
removeMessageById,
regenerateMessage,
}: UserGroupButtonProps) => {
@@ -211,7 +213,7 @@ export const UserGroupButton = ({
size="icon-xs"
className="border-0"
onClick={onRemoveMessage}
disabled={loading}
disabled={loading || deleteDisabled}
>
<LucideTrash2 />
</Button>
@@ -43,6 +43,7 @@ interface IProps extends Partial<IRemoveMessageById>, IRegenerateMessage {
reference: IReference;
loading?: boolean;
sendLoading?: boolean;
deleteDisabled?: boolean;
visibleAvatar?: boolean;
nickname?: string;
avatar?: string;
@@ -61,6 +62,7 @@ const MessageItem = ({
avatar,
avatarDialog,
sendLoading = false,
deleteDisabled = false,
clickDocumentButton,
index,
removeMessageById,
@@ -152,6 +154,7 @@ const MessageItem = ({
removeMessageById={removeMessageById}
regenerateMessage={regenerateMessage && handleRegenerateMessage}
sendLoading={sendLoading}
deleteDisabled={deleteDisabled}
></UserGroupButton>
)}
{/* Show message content if there's any text besides the download */}
-3
View File
@@ -850,9 +850,6 @@ export const useRegenerateMessage = ({
if (message.id) {
removeMessagesAfterCurrentMessage(message.id);
const index = messages.findIndex((x) => x.id === message.id);
// Always pass the truncated history explicitly, even when it is
// empty (regenerating the first question), so the backend can
// overwrite the session with it via pass_all_history_messages.
const nextMessages = index !== -1 ? messages.slice(0, index) : [];
sendMessage({
// Keep the original id so the question/answer pair id stays
+1
View File
@@ -221,6 +221,7 @@ export interface IExternalChatInfo {
export interface IMessage extends Message {
id: string;
awaitingServerId?: boolean;
reference?: IReference; // the latest news has reference
conversationId?: string; // To distinguish which conversation the message belongs to
}
@@ -17,7 +17,8 @@ import { useChatStreamStore } from './store';
export type RunChatCompletionStreamParams = {
conversationId: string;
chatId?: string;
messages: IMessage[];
question: string;
files?: IMessage['files'];
enableThinking?: string;
enableInternet?: boolean;
llmSetting?: Variable;
@@ -31,7 +32,8 @@ export type RunChatCompletionStreamResult = {
export async function runChatCompletionStream({
conversationId,
chatId,
messages,
question,
files,
enableThinking,
enableInternet,
llmSetting,
@@ -75,7 +77,8 @@ export async function runChatCompletionStream({
{
chatId,
sessionId: conversationId,
messages,
question,
files,
enableThinking,
enableInternet,
llmSetting,
+17 -4
View File
@@ -246,15 +246,28 @@ export const useChatStreamStore = create<ChatStreamState>()(
// placeholder) were just deleted: without it, the flush would slice
// off whatever message is now last.
if (!previous.isStreaming) return state;
const messages = previous.messages.slice(0, -1);
const question = messages.at(-1);
// The server assigns the turn ID for question-only requests.
// Keep the local question and streamed answer paired under that ID.
if (
answer.id &&
question?.role === MessageType.User &&
(question.id !== answer.id || question.awaitingServerId)
) {
messages[messages.length - 1] = {
...question,
id: answer.id,
awaitingServerId: false,
};
}
messages.push(buildAssistantMessageFromAnswer(answer));
return {
sessions: {
...state.sessions,
[conversationId]: {
...previous,
messages: [
...previous.messages.slice(0, -1),
buildAssistantMessageFromAnswer(answer),
],
messages,
lastActiveAt: Date.now(),
},
},
@@ -26,6 +26,7 @@ export function buildQuestionAndPlaceholder(message: IMessage): IMessage[] {
{
...message,
id: buildMessageUuid(message),
awaitingServerId: true,
},
{
role: MessageType.Assistant,
@@ -103,6 +103,7 @@ export function SingleChatBox({ conversation }: IProps) {
index={i}
isLast={i === messages.length - 1}
removeMessageById={removeMessageById}
deleteDisabled={message.awaitingServerId}
regenerateMessage={regenerateMessage}
sendLoading={sendLoading}
/>
@@ -99,13 +99,11 @@ export const useSendMessage = () => {
async ({
message,
currentConversationId,
messages: explicitMessages,
enableInternet,
enableThinking,
}: {
message: IMessage;
currentConversationId?: string;
messages?: IMessage[];
} & NextMessageInputOnPressEnterParameter) => {
const sessionId = currentConversationId ?? conversationId;
@@ -114,12 +112,8 @@ export const useSendMessage = () => {
const { ok, aborted } = await runChatCompletionStream({
conversationId: sessionId,
chatId,
// 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,
],
question: message.content,
files: message.files,
enableThinking,
enableInternet,
llmSetting: currentDialog?.llm_setting,
@@ -134,14 +128,7 @@ export const useSendMessage = () => {
notification.error({ message: t('message.requestError') });
}
},
[
conversationId,
chatId,
messages,
failStream,
t,
currentDialog?.llm_setting,
],
[conversationId, chatId, failStream, t, currentDialog?.llm_setting],
);
// Hand a failed question back to the input box, but only once the box is
@@ -237,21 +224,11 @@ export const useSendMessage = () => {
// Route the question to the conversation it was asked in, not whichever
// is currently displayed.
//
// Snapshot the history before appendQuestion writes the question and its
// assistant placeholder into the store.
const history =
useChatStreamStore.getState().sessions[targetConversationId]
?.messages ?? [];
appendQuestion(targetConversationId, questionMessage);
setValue('');
sendMessage({
currentConversationId: targetConversationId,
// For an existing conversation currentMessages is empty; fall back to
// the store's message list instead of sending an empty history.
messages: currentMessages.length > 0 ? currentMessages : history,
message: {
id,
content: value.trim(),
@@ -86,7 +86,6 @@ export function useSendSingleMessage({
...(storeHistoryMessages === undefined
? {}
: { store_history_messages: storeHistoryMessages }),
pass_all_history_messages: true,
},
controller,
);
+6 -4
View File
@@ -19,7 +19,8 @@ import { EventSourceParserStream } from 'eventsource-parser/stream';
export type ChatCompletionStreamParams = {
chatId?: string;
sessionId: string;
messages: IMessage[];
question: string;
files?: IMessage['files'];
enableThinking?: string;
enableInternet?: boolean;
llmSetting?: Variable;
@@ -42,7 +43,8 @@ export function requestChatCompletionStream(
{
chatId,
sessionId,
messages,
question,
files,
enableThinking,
enableInternet,
llmSetting,
@@ -68,8 +70,8 @@ export function requestChatCompletionStream(
body: JSON.stringify({
chat_id: chatId,
session_id: sessionId,
messages,
pass_all_history_messages: true,
question,
files,
reasoning: Number(enableThinking),
internet: enableInternet,
...(temperature === undefined ? {} : { temperature }),