Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d97e1fc
ci: trigger build
MarcMcIntosh Jul 15, 2025
5a931a3
ci: remove file added to trigger build
MarcMcIntosh Jul 15, 2025
3c8a614
add workflow_dispatch to gui workflow
reymondzzzz Jul 15, 2025
ff07d9c
if hasError it doesn't block chat history
JegernOUTT Nov 27, 2025
2e476cc
postProcessMessagesAfterStreaming
JegernOUTT Nov 27, 2025
ca17ace
deduplicateToolCalls
JegernOUTT Nov 27, 2025
3fced87
update_textdoc_by_lines
JegernOUTT Nov 27, 2025
3056051
model selector in the middle of a thread
JegernOUTT Nov 27, 2025
555f90f
fixed all npm errors
JegernOUTT Nov 27, 2025
33b80aa
Fix CI build: use npm install instead of npm ci
JegernOUTT Dec 3, 2025
032805c
Fix localStorage tests: add undefined check
JegernOUTT Dec 3, 2025
bbc3204
Add localStorage mock for tests
JegernOUTT Dec 3, 2025
38774b8
Fix localStorage function check in storage.ts
JegernOUTT Dec 3, 2025
e342956
Fix code formatting with Prettier
JegernOUTT Dec 3, 2025
55c70e9
Fix TypeScript error in localStorage mock
JegernOUTT Dec 3, 2025
7a1221f
Merge pull request #910 from smallcloudai/sergei-fixes-cvox
JegernOUTT Dec 3, 2025
922c2cb
better model selection
JegernOUTT Dec 8, 2025
b3830eb
include_project_info, context_tokens_cap
JegernOUTT Dec 8, 2025
b7757c9
include_project_info, context_tokens_cap
JegernOUTT Dec 8, 2025
c1ad8cf
button to resend the last messages
JegernOUTT Dec 8, 2025
675b4d0
state.thread.model = action.payload;
JegernOUTT Dec 8, 2025
a0d22a5
fix: remove 'Try Again' buttons from error UI and fix prettier format…
JegernOUTT Dec 8, 2025
70b4810
Merge pull request #911 from smallcloudai/grouped-models-with-prices
JegernOUTT Dec 8, 2025
863bbb2
feat(gui): fix double scrollbar, add coins display, improve token stats
JegernOUTT Dec 11, 2025
ca2bdeb
rm unnecessary things
JegernOUTT Dec 11, 2025
25ae12f
fix: apply prettier formatting to GUI files
JegernOUTT Dec 11, 2025
c293843
Merge pull request #912 from smallcloudai/grouped-models-with-prices
JegernOUTT Dec 11, 2025
fa791b3
fix: improve usage counter layout and prevent text overlap
JegernOUTT Dec 15, 2025
871a46d
Merge pull request #913 from smallcloudai/fix/usage-counter-layout
JegernOUTT Dec 15, 2025
7c28e1a
DEFAULT_MAX_NEW_TOKENS = null
JegernOUTT Dec 15, 2025
33fef46
fix: remove DEFAULT_MAX_NEW_TOKENS reset to preserve model context fr…
JegernOUTT Dec 15, 2025
967e347
style: fix formatting in useCapsForToolUse.ts
JegernOUTT Dec 15, 2025
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
5 changes: 3 additions & 2 deletions .github/workflows/agent_gui_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
paths:
- "refact-agent/gui/**"
- ".github/workflows/agent_gui_*"
workflow_dispatch:

defaults:
run:
Expand All @@ -33,13 +34,13 @@ jobs:
cache: "npm"
cache-dependency-path: refact-agent/gui/package-lock.json

# Disable Husky install during npm ci
# Disable Husky install during npm install --prefer-offline
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev librsvg2-dev
npm pkg delete scripts.prepare
npm ci
npm install --prefer-offline

- run: npm run test
- run: npm run format:check
Expand Down
7 changes: 6 additions & 1 deletion refact-agent/gui/src/app/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,17 @@ function pruneHistory(key: string, item: string) {
}

function removeOldEntry(key: string) {
if (localStorage.getItem(key)) {
if (
typeof localStorage !== "undefined" &&
typeof localStorage.getItem === "function" &&
localStorage.getItem(key)
) {
localStorage.removeItem(key);
}
}

function cleanOldEntries() {
if (typeof localStorage === "undefined") return;
removeOldEntry("tour");
removeOldEntry("tipOfTheDay");
removeOldEntry("chatHistory");
Expand Down
143 changes: 143 additions & 0 deletions refact-agent/gui/src/components/Buttons/ContextCapButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, { useCallback, useMemo } from "react";
import { useAppDispatch, useAppSelector, useGetCapsQuery } from "../../hooks";
import {
selectChatId,
selectContextTokensCap,
selectModel,
setContextTokensCap,
} from "../../features/Chat/Thread";

import { Select, type SelectProps } from "../Select";
import { Skeleton } from "@radix-ui/themes";

const formatContextSize = (tokens: number): string => {
if (tokens >= 1000000) {
const m = tokens / 1000000;
return Number.isInteger(m) ? `${m}M` : `${Math.round(m)}M`;
}
if (tokens >= 1000) {
const k = tokens / 1000;
return Number.isInteger(k) ? `${k}K` : `${Math.round(k)}K`;
}
return String(tokens);
};

const FIXED_OPTIONS = [
256 * 1024, // 256K
200 * 1024, // 200K
128 * 1024, // 128K
64 * 1024, // 64K
32 * 1024, // 32K
16 * 1024, // 16K (minimum)
];

const MIN_CONTEXT_CAP = 16 * 1024; // 16K minimum

export const ContextCapButton: React.FC = () => {
const dispatch = useAppDispatch();
const chatId = useAppSelector(selectChatId);
const contextCap = useAppSelector(selectContextTokensCap);
const threadModel = useAppSelector(selectModel);
const capsQuery = useGetCapsQuery();

// Derive maxTokens directly from caps data and current model
// This avoids timing issues with threadMaxTokens state updates
const maxTokens = useMemo(() => {
if (!capsQuery.data) return undefined;

// Use thread model if available in caps
const modelToUse =
threadModel && threadModel in capsQuery.data.chat_models
? threadModel
: capsQuery.data.chat_default_model;

if (modelToUse in capsQuery.data.chat_models) {
return capsQuery.data.chat_models[modelToUse].n_ctx;
}

return undefined;
}, [capsQuery.data, threadModel]);

const capOptions: SelectProps["options"] = useMemo(() => {
if (!maxTokens) return [];
const options: SelectProps["options"] = [];

const maxLabel = `${formatContextSize(maxTokens)} (max)`;
options.push({
value: String(maxTokens),
textValue: maxLabel,
children: maxLabel,
});

for (const fixedValue of FIXED_OPTIONS) {
if (fixedValue < maxTokens && fixedValue >= MIN_CONTEXT_CAP) {
const isMin = fixedValue === MIN_CONTEXT_CAP;
const label = isMin
? `${formatContextSize(fixedValue)} (min)`
: formatContextSize(fixedValue);
options.push({
value: String(fixedValue),
textValue: label,
children: label,
});
}
}

return options;
}, [maxTokens]);

const handleCapChange = useCallback(
(value: string) => {
dispatch(
setContextTokensCap({
chatId,
value: parseInt(value, 10),
}),
);
},
[dispatch, chatId],
);

// Compute a safe default value that's guaranteed to exist in options
const safeDefaultValue = useMemo(() => {
if (!maxTokens || capOptions.length === 0) return undefined;

// Get all valid option values as numbers
const optionValues = capOptions
.filter(
(opt): opt is SelectProps["options"][number] & { value: string } =>
typeof opt === "object" && "value" in opt,
)
.map((opt) => Number(opt.value));

const desiredValue = contextCap ?? maxTokens;

// If desired value exists in options, use it
if (optionValues.includes(desiredValue)) {
return String(desiredValue);
}

// Otherwise fall back to maxTokens (always the first option)
return String(maxTokens);
}, [capOptions, contextCap, maxTokens]);

// Show skeleton while loading caps
if (capsQuery.isLoading || capsQuery.isFetching) {
return <Skeleton width="80px" height="24px" />;
}

if (!maxTokens || capOptions.length === 0 || !safeDefaultValue) return null;

// Use model + maxTokens as key to force remount when either changes
const selectKey = `${threadModel}-${maxTokens}`;

return (
<Select
key={selectKey}
title="Context cap"
options={capOptions}
defaultValue={safeDefaultValue}
onChange={handleCapChange}
/>
);
};
1 change: 1 addition & 0 deletions refact-agent/gui/src/components/Buttons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export {
} from "./Buttons";

export { ThinkingButton } from "./ThinkingButton";
export { ContextCapButton } from "./ContextCapButton";
export { FadedButton } from "./FadedButton";
21 changes: 12 additions & 9 deletions refact-agent/gui/src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
useAppDispatch,
useSendChatRequest,
useAutoSend,
useCapsForToolUse,
} from "../../hooks";
import { type Config } from "../../features/Config/configSlice";
import {
Expand All @@ -25,6 +24,7 @@ import { DropzoneProvider } from "../Dropzone";
import { useCheckpoints } from "../../hooks/useCheckpoints";
import { Checkpoints } from "../../features/Checkpoints";
import { SuggestNewChat } from "../ChatForm/SuggestNewChat";
import { EnhancedModelSelector } from "./EnhancedModelSelector";

export type ChatProps = {
host: Config["host"];
Expand All @@ -51,7 +51,6 @@ export const Chat: React.FC<ChatProps> = ({
const chatToolUse = useAppSelector(getSelectedToolUse);
const threadNewChatSuggested = useAppSelector(selectThreadNewChatSuggested);
const messages = useAppSelector(selectMessages);
const capsForToolUse = useCapsForToolUse();

const { shouldCheckpointsPopupBeShown } = useCheckpoints();

Expand All @@ -61,7 +60,7 @@ export const Chat: React.FC<ChatProps> = ({
const preventSend = useAppSelector(selectPreventSend);
const onEnableSend = () => dispatch(enableSend({ id: chatId }));

const handleSummit = useCallback(
const handleSubmit = useCallback(
(value: string) => {
submit({ question: value });
if (isViewingRawJSON) {
Expand All @@ -80,11 +79,10 @@ export const Chat: React.FC<ChatProps> = ({
return (
<DropzoneProvider asChild>
<Flex
style={style}
style={{ ...style, minHeight: 0 }}
direction="column"
flexGrow="1"
width="100%"
overflowY="auto"
justify="between"
px="1"
>
Expand Down Expand Up @@ -115,7 +113,7 @@ export const Chat: React.FC<ChatProps> = ({

<ChatForm
key={chatId} // TODO: think of how can we not trigger re-render on chatId change (checkboxes)
onSubmit={handleSummit}
onSubmit={handleSubmit}
onClose={maybeSendToSidebar}
unCalledTools={unCalledTools}
/>
Expand All @@ -124,13 +122,18 @@ export const Chat: React.FC<ChatProps> = ({
{/* Two flexboxes are left for the future UI element on the right side */}
{messages.length > 0 && (
<Flex align="center" justify="between" width="100%">
<Flex align="center" gap="1">
<Text size="1">model: {capsForToolUse.currentModel} </Text> •{" "}
<Flex align="center" gap="2">
<EnhancedModelSelector disabled={isStreaming} />
<Text size="1" color="gray">
</Text>
<Text
size="1"
color="gray"
onClick={() => setIsDebugChatHistoryVisible((prev) => !prev)}
style={{ cursor: "pointer" }}
>
mode: {chatToolUse}{" "}
mode: {chatToolUse}
</Text>
</Flex>
{messages.length !== 0 &&
Expand Down
Loading