mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Chat AI: keep last Thinking block only (default)
This commit is contained in:
@@ -55,7 +55,7 @@ export async function runPersonaOnConversationHead(
|
||||
const parallelViewCount = getUXLabsHighPerformance() ? 0 : getInstantAppChatPanesCount();
|
||||
|
||||
// ai follow-up operations (fire/forget)
|
||||
const { autoSpeak, autoSuggestDiagrams, autoSuggestHTMLUI, autoSuggestQuestions, autoTitleChat } = getChatAutoAI();
|
||||
const { autoSpeak, autoSuggestDiagrams, autoSuggestHTMLUI, autoSuggestQuestions, autoTitleChat, chatKeepLastThinkingOnly } = getChatAutoAI();
|
||||
|
||||
// AutoSpeak
|
||||
const autoSpeaker: PersonaProcessorInterface | null = autoSpeak !== 'off' ? new PersonaChatMessageSpeak(autoSpeak) : null;
|
||||
@@ -129,6 +129,9 @@ export async function runPersonaOnConversationHead(
|
||||
if (!hasBeenAborted && (autoSuggestDiagrams || autoSuggestHTMLUI || autoSuggestQuestions))
|
||||
void autoChatFollowUps(conversationId, assistantMessageId, autoSuggestDiagrams, autoSuggestHTMLUI, autoSuggestQuestions);
|
||||
|
||||
if (chatKeepLastThinkingOnly)
|
||||
cHandler.historyKeepLastThinkingOnly();
|
||||
|
||||
// return true if this succeeded
|
||||
return messageStatus.outcome === 'success';
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ interface AppChatStore {
|
||||
autoVndAntBreakpoints: boolean;
|
||||
setAutoVndAntBreakpoints: (autoVndAntBreakpoints: boolean) => void;
|
||||
|
||||
chatKeepLastThinkingOnly: boolean,
|
||||
setChatKeepLastThinkingOnly: (chatKeepLastThinkingOnly: boolean) => void;
|
||||
|
||||
// chat UI
|
||||
|
||||
clearFilters: () => void;
|
||||
@@ -98,6 +101,9 @@ const useAppChatStore = create<AppChatStore>()(persist(
|
||||
autoVndAntBreakpoints: true, // 2024-08-24: on as it saves user's money
|
||||
setAutoVndAntBreakpoints: (autoVndAntBreakpoints: boolean) => _set({ autoVndAntBreakpoints }),
|
||||
|
||||
chatKeepLastThinkingOnly: true,
|
||||
setChatKeepLastThinkingOnly: (chatKeepLastThinkingOnly: boolean) => _set({ chatKeepLastThinkingOnly }),
|
||||
|
||||
// Chat UI
|
||||
|
||||
clearFilters: () => _set({ filterHasDocFragments: false, filterHasImageAssets: false, filterHasStars: false }),
|
||||
@@ -168,6 +174,7 @@ export const useChatAutoAI = () => useAppChatStore(useShallow(state => ({
|
||||
autoSuggestQuestions: state.autoSuggestQuestions,
|
||||
autoTitleChat: state.autoTitleChat,
|
||||
autoVndAntBreakpoints: state.autoVndAntBreakpoints,
|
||||
chatKeepLastThinkingOnly: state.chatKeepLastThinkingOnly,
|
||||
setAutoSpeak: state.setAutoSpeak,
|
||||
setAutoSuggestAttachmentPrompts: state.setAutoSuggestAttachmentPrompts,
|
||||
setAutoSuggestDiagrams: state.setAutoSuggestDiagrams,
|
||||
@@ -175,6 +182,7 @@ export const useChatAutoAI = () => useAppChatStore(useShallow(state => ({
|
||||
setAutoSuggestQuestions: state.setAutoSuggestQuestions,
|
||||
setAutoTitleChat: state.setAutoTitleChat,
|
||||
setAutoVndAntBreakpoints: state.setAutoVndAntBreakpoints,
|
||||
setChatKeepLastThinkingOnly: state.setChatKeepLastThinkingOnly,
|
||||
})));
|
||||
|
||||
export const getChatAutoAI = (): {
|
||||
@@ -185,6 +193,7 @@ export const getChatAutoAI = (): {
|
||||
autoSuggestQuestions: boolean,
|
||||
autoTitleChat: boolean,
|
||||
autoVndAntBreakpoints: boolean,
|
||||
chatKeepLastThinkingOnly: boolean,
|
||||
} => useAppChatStore.getState();
|
||||
|
||||
export const useChatAutoSuggestHTMLUI = (): boolean =>
|
||||
|
||||
@@ -8,6 +8,7 @@ import WarningRoundedIcon from '@mui/icons-material/WarningRounded';
|
||||
|
||||
import type { DModelDomainId } from '~/common/stores/llms/model.domains.types';
|
||||
import { FormLabelStart } from '~/common/components/forms/FormLabelStart';
|
||||
import { FormSelectControl, FormSelectOption } from '~/common/components/forms/FormSelectControl';
|
||||
import { useLLMSelect } from '~/common/components/forms/useLLMSelect';
|
||||
import { useLabsDevMode } from '~/common/state/store-ux-labs';
|
||||
import { useModelDomain } from '~/common/stores/llms/hooks/useModelDomain';
|
||||
@@ -15,6 +16,20 @@ import { useModelDomain } from '~/common/stores/llms/hooks/useModelDomain';
|
||||
import { useChatAutoAI } from '../chat/store-app-chat';
|
||||
|
||||
|
||||
const _keepThinkingBlocksOptions: FormSelectOption<'all' | 'last-only'>[] = [
|
||||
{
|
||||
value: 'all',
|
||||
label: 'All Messages',
|
||||
description: 'Keep all blocks',
|
||||
},
|
||||
{
|
||||
value: 'last-only',
|
||||
label: 'Last Message Only',
|
||||
description: 'Only keep last',
|
||||
},
|
||||
] as const;
|
||||
|
||||
|
||||
function FormControlDomainModel(props: {
|
||||
domainId: DModelDomainId,
|
||||
title: React.ReactNode,
|
||||
@@ -47,6 +62,7 @@ export function AppChatSettingsAI() {
|
||||
autoSuggestHTMLUI, setAutoSuggestHTMLUI,
|
||||
// autoSuggestQuestions, setAutoSuggestQuestions,
|
||||
autoTitleChat, setAutoTitleChat,
|
||||
chatKeepLastThinkingOnly, setChatKeepLastThinkingOnly,
|
||||
} = useChatAutoAI();
|
||||
|
||||
const labsDevMode = useLabsDevMode();
|
||||
@@ -107,6 +123,14 @@ export function AppChatSettingsAI() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormSelectControl
|
||||
title='Reasoning blocks'
|
||||
tooltip='Controls how AI thinking/reasoning blocks are kept in your chat history. Keeping only in the last message (default) reduces clutter.'
|
||||
options={_keepThinkingBlocksOptions}
|
||||
value={chatKeepLastThinkingOnly ? 'last-only' : 'all'}
|
||||
onChange={(value) => setChatKeepLastThinkingOnly(value === 'last-only')}
|
||||
selectSx={{ minWidth: 140 }}
|
||||
/>
|
||||
|
||||
<ListDivider inset='gutter'>Automatic AI Functions</ListDivider>
|
||||
|
||||
|
||||
@@ -220,6 +220,10 @@ export class ConversationHandler {
|
||||
return _chatStoreActions.historyView(this.conversationId)?.find(m => m.id === messageId);
|
||||
}
|
||||
|
||||
historyKeepLastThinkingOnly(): void {
|
||||
return _chatStoreActions.historyKeepLastThinkingOnly(this.conversationId);
|
||||
}
|
||||
|
||||
title(): string | undefined {
|
||||
return _chatStoreActions.title(this.conversationId);
|
||||
}
|
||||
|
||||
@@ -195,6 +195,10 @@ export function isVoidFragment(fragment: DMessageFragment) {
|
||||
return fragment.ft === 'void';
|
||||
}
|
||||
|
||||
export function isVoidThinkingFragment(fragment: DMessageFragment): fragment is DMessageVoidFragment & { part: DVoidModelAuxPart } {
|
||||
return fragment.ft === 'void' && fragment.part.pt === 'ma' && fragment.part.aType === 'reasoning';
|
||||
}
|
||||
|
||||
|
||||
export function isDocPart(part: DMessageContentFragment['part'] | DMessageAttachmentFragment['part']) {
|
||||
return part.pt === 'doc';
|
||||
|
||||
@@ -14,7 +14,7 @@ import { workspaceActions } from '~/common/stores/workspace/store-client-workspa
|
||||
import { workspaceForConversationIdentity } from '~/common/stores/workspace/workspace.types';
|
||||
|
||||
import { DMessage, DMessageId, DMessageMetadata, MESSAGE_FLAG_AIX_SKIP, messageHasUserFlag } from './chat.message';
|
||||
import type { DMessageFragment, DMessageFragmentId } from './chat.fragments';
|
||||
import { DMessageFragment, DMessageFragmentId, isVoidThinkingFragment } from './chat.fragments';
|
||||
import { V3StoreDataToHead, V4ToHeadConverters } from './chats.converters';
|
||||
import { conversationTitle, createDConversation, DConversation, DConversationId, duplicateDConversation } from './chat.conversation';
|
||||
import { estimateTokensForFragments } from './chat.tokens';
|
||||
@@ -41,6 +41,7 @@ export interface ChatActions {
|
||||
abortConversationTemp: (cId: DConversationId) => void;
|
||||
historyReplace: (cId: DConversationId, messages: DMessage[]) => void;
|
||||
historyTruncateToIncluded: (cId: DConversationId, mId: DMessageId, offset: number) => void;
|
||||
historyKeepLastThinkingOnly: (cId: DConversationId) => void;
|
||||
historyView: (cId: DConversationId) => Readonly<DMessage[]> | undefined;
|
||||
appendMessage: (cId: DConversationId, message: DMessage) => void;
|
||||
deleteMessage: (cId: DConversationId, mId: DMessageId) => void;
|
||||
@@ -245,6 +246,47 @@ export const useChatStore = create<ConversationsStore>()(/*devtools(*/
|
||||
};
|
||||
}),
|
||||
|
||||
historyKeepLastThinkingOnly: (conversationId: DConversationId) =>
|
||||
_get()._editConversation(conversationId, ({ messages: _currentMessages }) => {
|
||||
let madeChanges = false;
|
||||
const updatedMessages = [..._currentMessages];
|
||||
let foundLastAssistant = false;
|
||||
|
||||
// reverse iterate
|
||||
for (let i = updatedMessages.length - 1; i >= 0; i--) {
|
||||
const message = updatedMessages[i];
|
||||
|
||||
// skip non-assistant messages
|
||||
if (message.role !== 'assistant') continue;
|
||||
|
||||
// skip the last assistant message
|
||||
if (!foundLastAssistant) {
|
||||
foundLastAssistant = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip if doesn't have thinking blocks
|
||||
const hasThinkingBlocks = message.fragments.some(isVoidThinkingFragment);
|
||||
if (!hasThinkingBlocks) continue;
|
||||
|
||||
// Filter out thinking blocks
|
||||
updatedMessages[i] = {
|
||||
...message,
|
||||
fragments: message.fragments.filter(fragment => !isVoidThinkingFragment(fragment)),
|
||||
};
|
||||
madeChanges = true;
|
||||
}
|
||||
|
||||
if (!madeChanges) return {};
|
||||
|
||||
return {
|
||||
messages: updatedMessages,
|
||||
// No need to update the following as void fragments don't contribute
|
||||
// tokenCount: updateMessagesTokenCounts(updatedMessages, true, 'historyKeepLastThinkingOnly'),
|
||||
// updated: Date.now(),
|
||||
};
|
||||
}),
|
||||
|
||||
historyView: (conversationId: DConversationId): Readonly<DMessage[]> | undefined =>
|
||||
_get().conversations.find(_c => _c.id === conversationId)?.messages ?? undefined,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user