Skip to content
Open
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
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
14 changes: 9 additions & 5 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 { ModelSelector } from "./ModelSelector";

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 Down Expand Up @@ -124,13 +123,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">
<ModelSelector />
<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
68 changes: 68 additions & 0 deletions refact-agent/gui/src/components/Chat/ModelSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useMemo } from "react";
import { Select, Text, Flex } from "@radix-ui/themes";
import { useCapsForToolUse } from "../../hooks";

export type ModelSelectorProps = {
disabled?: boolean;
};

export const ModelSelector: React.FC<ModelSelectorProps> = ({ disabled }) => {
const capsForToolUse = useCapsForToolUse();

const modelOptions = useMemo(() => {
return capsForToolUse.usableModelsForPlan.map((model) => ({
value: model.value,
label: model.textValue,
disabled: model.disabled,
}));
}, [capsForToolUse.usableModelsForPlan]);

if (!capsForToolUse.data || modelOptions.length === 0) {
return (
<Text size="1" color="gray">
model: {capsForToolUse.currentModel}
</Text>
);
}

return (
<Flex align="center" gap="1" style={{ height: "20px" }}>
<Text size="1" color="gray" style={{ lineHeight: "20px" }}>
model:
</Text>
<Select.Root
value={capsForToolUse.currentModel}
onValueChange={capsForToolUse.setCapModel}
disabled={disabled}
size="1"
>
<Select.Trigger
variant="ghost"
title={
disabled
? "Cannot change model while streaming"
: "Click to change model"
}
style={{
cursor: disabled ? "not-allowed" : "pointer",
padding: "0 4px",
minHeight: "20px",
height: "20px",
opacity: disabled ? 0.5 : 1,
}}
/>
<Select.Content position="popper">
{modelOptions.map((option) => (
<Select.Item
key={option.value}
value={option.value}
disabled={option.disabled}
>
{option.label}
</Select.Item>
))}
</Select.Content>
</Select.Root>
</Flex>
);
};
2 changes: 2 additions & 0 deletions refact-agent/gui/src/components/Chat/index.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export { Chat } from "./Chat";
export type { ChatProps } from "./Chat";
export { ModelSelector } from "./ModelSelector";
export type { ModelSelectorProps } from "./ModelSelector";
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ export const GroupTree: React.FC = () => {
onWorkspaceSelection,
availableWorkspaces,
treeHeight,
hasError,
} = useGroupTree();

if (hasError) {
return null;
}

return (
<Flex direction="column" gap="4" mt="4" width="100%">
<Flex direction="column" gap="1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ export function useGroupTree() {
return [];
}, [teamsWorkspaces.data?.query_basic_stuff.workspaces]);

const hasError = teamsWorkspaces.error !== undefined;
const isLoading = teamsWorkspaces.fetching;

return {
// Refs
treeParentRef,
Expand All @@ -222,5 +225,8 @@ export function useGroupTree() {
setCurrentTeamsWorkspace,
setGroupTreeData,
setCurrentSelectedTeamsGroupNode,
// Status
hasError,
isLoading,
};
}
19 changes: 6 additions & 13 deletions refact-agent/gui/src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ import {
import { push } from "../../features/Pages/pagesSlice";
import { restoreChat } from "../../features/Chat/Thread";
import { FeatureMenu } from "../../features/Config/FeatureMenu";
import { GroupTree } from "./GroupTree/";

import { ErrorCallout } from "../Callout";
import { getErrorMessage, clearError } from "../../features/Errors/errorsSlice";
import classNames from "classnames";
import { selectHost } from "../../features/Config/configSlice";
import styles from "./Sidebar.module.css";
import { useActiveTeamsGroup } from "../../hooks/useActiveTeamsGroup";

export type SidebarProps = {
takingNotes: boolean;
Expand All @@ -40,8 +39,6 @@ export const Sidebar: React.FC<SidebarProps> = ({ takingNotes, style }) => {
devModeChecks: { stabilityCheck: "never" },
});

const { groupSelectionEnabled } = useActiveTeamsGroup();

const onDeleteHistoryItem = useCallback(
(id: string) => dispatch(deleteChatById(id)),
[dispatch],
Expand All @@ -64,15 +61,11 @@ export const Sidebar: React.FC<SidebarProps> = ({ takingNotes, style }) => {
</Box>
</Flex>

{!groupSelectionEnabled ? (
<ChatHistory
history={history}
onHistoryItemClick={onHistoryItemClick}
onDeleteHistoryItem={onDeleteHistoryItem}
/>
) : (
<GroupTree />
)}
<ChatHistory
history={history}
onHistoryItemClick={onHistoryItemClick}
onDeleteHistoryItem={onDeleteHistoryItem}
/>
{/* TODO: duplicated */}
{globalError && (
<ErrorCallout
Expand Down
2 changes: 1 addition & 1 deletion refact-agent/gui/src/components/Toolbar/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ export const Dropdown: React.FC<DropdownProps> = ({
{isKnowledgeFeatureAvailable && (
<DropdownMenu.Item
// TODO: get real URL from cloud inference
onSelect={() => openUrl("https://test-teams.smallcloud.ai/")}
onSelect={() => openUrl("https://flexus.team/")}
>
Manage Knowledge
</DropdownMenu.Item>
Expand Down
40 changes: 40 additions & 0 deletions refact-agent/gui/src/components/Tools/Textdoc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import {
TextDocToolCall,
UpdateRegexTextDocToolCall,
UpdateTextDocToolCall,
UpdateTextDocByLinesToolCall,
isCreateTextDocToolCall,
isReplaceTextDocToolCall,
isUpdateRegexTextDocToolCall,
isUpdateTextDocToolCall,
isUpdateTextDocByLinesToolCall,
parseRawTextDocToolCall,
} from "./types";
import { Box, Card, Flex, Button } from "@radix-ui/themes";
Expand Down Expand Up @@ -58,6 +60,10 @@ export const TextDocTool: React.FC<{
return <UpdateRegexTextDoc toolCall={maybeTextDocToolCall} />;
}

if (isUpdateTextDocByLinesToolCall(maybeTextDocToolCall)) {
return <UpdateTextDocByLines toolCall={maybeTextDocToolCall} />;
}

return false;
};

Expand Down Expand Up @@ -94,6 +100,8 @@ const TextDocHeader = forwardRef<HTMLDivElement, TextDocHeaderProps>(
return toolCall.function.arguments.content;
if (isUpdateTextDocToolCall(toolCall))
return toolCall.function.arguments.replacement;
if (isUpdateTextDocByLinesToolCall(toolCall))
return toolCall.function.arguments.content;
return null;
}, [toolCall]);

Expand Down Expand Up @@ -306,6 +314,38 @@ const UpdateTextDoc: React.FC<{
);
};

const UpdateTextDocByLines: React.FC<{
toolCall: UpdateTextDocByLinesToolCall;
}> = ({ toolCall }) => {
const copyToClipBoard = useCopyToClipboard();
const ref = useRef<HTMLDivElement>(null);
const handleClose = useHideScroll(ref);
const handleCopy = useCallback(() => {
copyToClipBoard(toolCall.function.arguments.content);
}, [copyToClipBoard, toolCall.function.arguments.content]);

const className = useMemo(() => {
const extension = getFileExtension(toolCall.function.arguments.path);
return `language-${extension}`;
}, [toolCall.function.arguments.path]);

const lineCount = useMemo(
() => toolCall.function.arguments.content.split("\n").length,
[toolCall.function.arguments.content],
);

return (
<Box className={styles.textdoc}>
<TextDocHeader toolCall={toolCall} ref={ref} />
<Reveal isRevealingCode defaultOpen={lineCount < 9} onClose={handleClose}>
<MarkdownCodeBlock onCopyClick={handleCopy} className={className}>
{toolCall.function.arguments.content}
</MarkdownCodeBlock>
</Reveal>
</Box>
);
};

function getFileExtension(filePath: string): string {
const fileName = filename(filePath);
if (fileName.toLocaleLowerCase().startsWith("dockerfile"))
Expand Down
31 changes: 29 additions & 2 deletions refact-agent/gui/src/components/Tools/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const TEXTDOC_TOOL_NAMES = [
"update_textdoc",
"replace_textdoc",
"update_textdoc_regex",
"update_textdoc_by_lines",
];

type TextDocToolNames = (typeof TEXTDOC_TOOL_NAMES)[number];
Expand Down Expand Up @@ -151,11 +152,36 @@ export const isReplaceTextDocToolCall = (
return true;
};

export interface UpdateTextDocByLinesToolCall extends ParsedRawTextDocToolCall {
function: {
name: string;
arguments: {
path: string;
content: string;
ranges: string;
};
};
}

export const isUpdateTextDocByLinesToolCall = (
toolCall: ParsedRawTextDocToolCall,
): toolCall is UpdateTextDocByLinesToolCall => {
if (toolCall.function.name !== "update_textdoc_by_lines") return false;
if (!("path" in toolCall.function.arguments)) return false;
if (typeof toolCall.function.arguments.path !== "string") return false;
if (!("content" in toolCall.function.arguments)) return false;
if (typeof toolCall.function.arguments.content !== "string") return false;
if (!("ranges" in toolCall.function.arguments)) return false;
if (typeof toolCall.function.arguments.ranges !== "string") return false;
return true;
};

export type TextDocToolCall =
| CreateTextDocToolCall
| UpdateTextDocToolCall
| ReplaceTextDocToolCall
| UpdateRegexTextDocToolCall;
| UpdateRegexTextDocToolCall
| UpdateTextDocByLinesToolCall;

function isTextDocToolCall(
toolCall: ParsedRawTextDocToolCall,
Expand All @@ -164,7 +190,8 @@ function isTextDocToolCall(
if (isUpdateTextDocToolCall(toolCall)) return true;
if (isReplaceTextDocToolCall(toolCall)) return true;
if (isUpdateRegexTextDocToolCall(toolCall)) return true;
return true;
if (isUpdateTextDocByLinesToolCall(toolCall)) return true;
return false;
}

export function parseRawTextDocToolCall(
Expand Down
Loading