Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions refact-agent/gui/src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
useGetCapsQuery,
useCapsForToolUse,
useAgentUsage,
useLastSentCompressionStop,
} from "../../hooks";
import { type Config } from "../../features/Config/configSlice";
import {
Expand Down Expand Up @@ -53,7 +54,7 @@ export const Chat: React.FC<ChatProps> = ({
const { submit, abort, retryFromIndex } = useSendChatRequest();

const chatToolUse = useAppSelector(getSelectedToolUse);

const compressionStop = useLastSentCompressionStop();
const threadNewChatSuggested = useAppSelector(selectThreadNewChatSuggested);
const messages = useAppSelector(selectMessages);
const capsForToolUse = useCapsForToolUse();
Expand Down Expand Up @@ -105,8 +106,9 @@ export const Chat: React.FC<ChatProps> = ({
<AgentUsage />
<SuggestNewChat
shouldBeVisible={
threadNewChatSuggested.wasSuggested &&
!threadNewChatSuggested.wasRejectedByUser
compressionStop.stopped ||
(threadNewChatSuggested.wasSuggested &&
!threadNewChatSuggested.wasRejectedByUser)
}
/>
{!isStreaming && preventSend && unCalledTools && (
Expand Down
25 changes: 3 additions & 22 deletions refact-agent/gui/src/components/ChatForm/ChatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
useSendChatRequest,
useCompressChat,
useAutoFocusOnce,
useTotalTokenUsage,
// useTotalTokenUsage,
} from "../../hooks";
import { ErrorCallout, Callout } from "../Callout";
import { ComboBox } from "../ComboBox";
Expand All @@ -35,7 +35,7 @@ import { useInputValue } from "./useInputValue";
import {
clearInformation,
getInformationMessage,
setInformation,
// setInformation,
} from "../../features/Errors/informationSlice";
import { InformationCallout } from "../Callout/Callout";
import { ToolConfirmation } from "./ToolConfirmation";
Expand Down Expand Up @@ -98,18 +98,6 @@ export const ChatForm: React.FC<ChatFormProps> = ({
const { compressChat, compressChatRequest } = useCompressChat();
const autoFocus = useAutoFocusOnce();

const { limitReached, tokens, limit } = useTotalTokenUsage();

useEffect(() => {
if (limitReached) {
dispatch(
setInformation(
`Token Limit reached, ${tokens} out of ${limit} used. To continue click the compress button or start a new chat.`,
),
);
}
}, [tokens, limit, limitReached, dispatch]);

const shouldAgentCapabilitiesBeShown = useMemo(() => {
return threadToolUse === "agent";
}, [threadToolUse]);
Expand All @@ -131,7 +119,6 @@ export const ChatForm: React.FC<ChatFormProps> = ({
const disableSend = useMemo(() => {
// TODO: if interrupting chat some errors can occur
if (allDisabled) return true;
if (limitReached) return true;
// if (
// currentThreadMaximumContextTokens &&
// currentThreadUsage?.prompt_tokens &&
Expand All @@ -143,7 +130,6 @@ export const ChatForm: React.FC<ChatFormProps> = ({
return isWaiting || isStreaming || !isOnline || preventSend;
}, [
allDisabled,
limitReached,
messages.length,
isWaiting,
isStreaming,
Expand Down Expand Up @@ -321,11 +307,7 @@ export const ChatForm: React.FC<ChatFormProps> = ({

if (information) {
return (
<InformationCallout
mt="2"
onClick={onClearInformation}
timeout={limitReached ? null : 2000}
>
<InformationCallout mt="2" onClick={onClearInformation} timeout={2000}>
{information}
</InformationCallout>
);
Expand Down Expand Up @@ -385,7 +367,6 @@ export const ChatForm: React.FC<ChatFormProps> = ({
data-testid="chat-form-textarea"
required={true}
// disabled={isStreaming}
disabled={limitReached}
{...props}
autoFocus={autoFocus}
style={{ boxShadow: "none", outline: "none" }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import {
useAppDispatch,
useAppSelector,
useCompressChat,
useLastSentCompressionStop,
} from "../../../hooks";
import { popBackTo, push } from "../../../features/Pages/pagesSlice";
import { telemetryApi } from "../../../services/refact";
import {
newChatAction,
selectChatId,
selectLastSentCompression,
setIsNewChatSuggestionRejected,
} from "../../../features/Chat";

Expand All @@ -40,8 +40,8 @@ export const SuggestNewChat = ({
const [isRendered, setIsRendered] = useState(shouldBeVisible);
const [isAnimating, setIsAnimating] = useState(false);
const { compressChat, compressChatRequest } = useCompressChat();
const lastSentCompression = useLastSentCompressionStop();

const lastSentCompression = useAppSelector(selectLastSentCompression);
useEffect(() => {
if (shouldBeVisible) {
setIsRendered(true);
Expand All @@ -64,6 +64,7 @@ export const SuggestNewChat = ({

const handleClose = () => {
dispatch(setIsNewChatSuggestionRejected({ chatId, value: true }));

void sendTelemetryEvent({
scope: `dismissedNewChatSuggestionWarning`,
success: true,
Expand Down Expand Up @@ -118,27 +119,28 @@ export const SuggestNewChat = ({
<Link size="1" onClick={onCreateNewChat} color="indigo">
Start a new chat
</Link>
{lastSentCompression && lastSentCompression !== "absent" && (
<Link
size="1"
onClick={() => {
if (compressChatRequest.isLoading) return;
void compressChat();
}}
color="indigo"
asChild
>
<Flex
align="center"
justify="start"
gap="1"
display="inline-flex"
{lastSentCompression.strength &&
lastSentCompression.strength !== "absent" && (
<Link
size="1"
onClick={() => {
if (compressChatRequest.isLoading) return;
void compressChat();
}}
color="indigo"
asChild
>
<ArchiveIcon style={{ alignSelf: "start" }} />
Compress and open in a new chat.
</Flex>
</Link>
)}
<Flex
align="center"
justify="start"
gap="1"
display="inline-flex"
>
<ArchiveIcon style={{ alignSelf: "start" }} />
Compress and open in a new chat.
</Flex>
</Link>
)}
</Flex>
<Box position="absolute" top="1" right="1">
<IconButton
Expand Down
24 changes: 13 additions & 11 deletions refact-agent/gui/src/components/UsageCounter/useUsageCounter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import {
selectIsStreaming,
selectIsWaiting,
selectMessages,
selectLastSentCompression,
// selectLastSentCompression,
} from "../../features/Chat";
import { useAppSelector } from "../../hooks";
import { useAppSelector, useLastSentCompressionStop } from "../../hooks";
import {
calculateUsageInputTokens,
mergeUsages,
Expand All @@ -15,7 +15,7 @@ import { isAssistantMessage } from "../../services/refact";
export function useUsageCounter() {
const isStreaming = useAppSelector(selectIsStreaming);
const isWaiting = useAppSelector(selectIsWaiting);
const lastSentCompression = useAppSelector(selectLastSentCompression);
const compressionStop = useLastSentCompressionStop();
const messages = useAppSelector(selectMessages);
const assistantMessages = messages.filter(isAssistantMessage);
const usages = assistantMessages.map((msg) => msg.usage);
Expand All @@ -33,21 +33,23 @@ export function useUsageCounter() {
}, [currentThreadUsage]);

const isOverflown = useMemo(() => {
if (lastSentCompression === "low") return true;
if (lastSentCompression === "medium") return true;
if (lastSentCompression === "high") return true;
if (!compressionStop.stopped) return false;
if (compressionStop.strength === "low") return true;
if (compressionStop.strength === "medium") return true;
if (compressionStop.strength === "high") return true;
return false;
}, [lastSentCompression]);
}, [compressionStop.stopped, compressionStop.strength]);

const isWarning = useMemo(() => {
if (lastSentCompression === "medium") return true;
if (lastSentCompression === "high") return true;
if (!compressionStop.stopped) return false;
if (compressionStop.strength === "medium") return true;
if (compressionStop.strength === "high") return true;
return false;
}, [lastSentCompression]);
}, [compressionStop.stopped, compressionStop.strength]);

const shouldShow = useMemo(() => {
return messages.length > 0 && !isStreaming && !isWaiting;
}, [messages, isStreaming, isWaiting]);
}, [messages.length, isStreaming, isWaiting]);

return {
shouldShow,
Expand Down
5 changes: 5 additions & 0 deletions refact-agent/gui/src/features/Chat/Thread/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const setLastUserMessageId = createAction<PayloadWithChatAndMessageId>(
"chatThread/setLastUserMessageId",
);

// TBD: only used when `/links` suggests a new chat.
export const setIsNewChatSuggested = createAction<PayloadWithChatAndBoolean>(
"chatThread/setIsNewChatSuggested",
);
Expand Down Expand Up @@ -162,6 +163,10 @@ export const setIncreaseMaxTokens = createAction<boolean>(
"chatThread/setIncreaseMaxTokens",
);

export const setThreadPaused = createAction<boolean>(
"chatThread/setThreadPaused",
);

// TODO: This is the circular dep when imported from hooks :/
const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: RootState;
Expand Down
9 changes: 9 additions & 0 deletions refact-agent/gui/src/features/Chat/Thread/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
setIsNewChatSuggestionRejected,
upsertToolCall,
setIncreaseMaxTokens,
setThreadPaused,
} from "./actions";
import { formatChatResponse } from "./utils";
import {
Expand Down Expand Up @@ -78,6 +79,7 @@ const createChatThread = (
boost_reasoning: false,
automatic_patch: false,
increase_max_tokens: false,
paused: false,
};
return chat;
};
Expand Down Expand Up @@ -241,13 +243,15 @@ export const chatReducer = createReducer(initialState, (builder) => {

builder.addCase(setIsNewChatSuggested, (state, action) => {
if (state.thread.id !== action.payload.chatId) return state;
state.thread.paused = true;
state.thread.new_chat_suggested = {
wasSuggested: action.payload.value,
};
});

builder.addCase(setIsNewChatSuggestionRejected, (state, action) => {
if (state.thread.id !== action.payload.chatId) return state;
state.thread.paused = false;
state.thread.new_chat_suggested = {
...state.thread.new_chat_suggested,
wasRejectedByUser: action.payload.value,
Expand Down Expand Up @@ -275,6 +279,7 @@ export const chatReducer = createReducer(initialState, (builder) => {
state.streaming = true;
state.thread.read = false;
state.prevent_send = false;
state.thread.paused = false;
});

builder.addCase(removeChatFromCache, (state, action) => {
Expand Down Expand Up @@ -422,6 +427,10 @@ export const chatReducer = createReducer(initialState, (builder) => {
state.thread.increase_max_tokens = action.payload;
});

builder.addCase(setThreadPaused, (state, action) => {
state.thread.paused = action.payload;
});

builder.addMatcher(
capsApi.endpoints.getCaps.matchFulfilled,
(state, action) => {
Expand Down
19 changes: 15 additions & 4 deletions refact-agent/gui/src/features/Chat/Thread/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { RootState } from "../../../app/store";
import { createSelector } from "@reduxjs/toolkit";
import {
CompressionStrength,
isToolMessage,
isUserMessage,
UserMessage,
} from "../../../services/refact/types";

export const selectThread = (state: RootState) => state.chat.thread;
Expand All @@ -23,6 +23,7 @@ export const selectCheckpointsEnabled = (state: RootState) =>
export const selectThreadBoostReasoning = (state: RootState) =>
state.chat.thread.boost_reasoning;

// TBD: only used when `/links` suggests a new chat.
export const selectThreadNewChatSuggested = (state: RootState) =>
state.chat.thread.new_chat_suggested;
export const selectThreadMaximumTokens = (state: RootState) =>
Expand Down Expand Up @@ -77,14 +78,24 @@ export const selectThreadMode = createSelector(
export const selectLastSentCompression = createSelector(
selectMessages,
(messages) => {
const lastUserMessage = messages.reduce<null | UserMessage>(
const lastCompression = messages.reduce<null | CompressionStrength>(
(acc, message) => {
if (isUserMessage(message)) return message;
if (isUserMessage(message) && message.compression_strength) {
return message.compression_strength;
}
if (isToolMessage(message) && message.content.compression_strength) {
return message.content.compression_strength;
}
return acc;
},
null,
);

return lastUserMessage?.compression_strength ?? null;
return lastCompression;
},
);

export const selectThreadPaused = createSelector(
selectThread,
(thread) => thread.paused ?? false,
);
1 change: 1 addition & 0 deletions refact-agent/gui/src/features/Chat/Thread/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type ChatThread = {
currentMaximumContextTokens?: number;
currentMessageContextTokens?: number;
increase_max_tokens?: boolean;
paused?: boolean;
};

export type SuggestedChat = {
Expand Down
5 changes: 4 additions & 1 deletion refact-agent/gui/src/features/Chat/Thread/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,22 @@ export function formatChatResponse(
}

if (isToolResponse(response)) {
const { tool_call_id, content, finish_reason } = response;
const { tool_call_id, content, finish_reason, compression_strength } =
response;
const filteredMessages = finishToolCallInMessages(messages, tool_call_id);
const toolResult: ToolResult =
typeof content === "string"
? {
tool_call_id,
content,
finish_reason,
compression_strength,
}
: {
tool_call_id,
content,
finish_reason,
compression_strength,
};

return [...filteredMessages, { role: response.role, content: toolResult }];
Expand Down
2 changes: 1 addition & 1 deletion refact-agent/gui/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ export * from "./useResizeObserver";
export * from "./useCompressChat";
export * from "./useAutoFocusOnce";
export * from "./useHideScroll";
export * from "./useTotalTokenUsage";
export * from "./useCompressionStop";
Loading