Skip to content

Commit

Permalink
feat(attach file): attach file using @ command
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcMcIntosh committed Feb 24, 2024
1 parent 89f1a15 commit 45c7790
Show file tree
Hide file tree
Showing 9 changed files with 24 additions and 211 deletions.
18 changes: 1 addition & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,20 +238,12 @@ interface ActiveFileInfo extends ActionToChat {
payload: { id: string; name: string; can_paste: boolean };
}

/**
* This message is sent from the host to the chat if the active file should be attached, this is used when adding snippets to the user input.
*/
interface ToggleActiveFile extends ActionToChat {
type: EVENT_NAMES_TO_CHAT.TOGGLE_ACTIVE_FILE; // = "chat_toggle_active_file"
payload: { id: string; attach_file: boolean };
}

/**
* This message is sent from the host to the chat to set the selected snippet.
*/
interface ChatSetSelectedSnippet extends ActionToChat {
type: EVENT_NAMES_TO_CHAT.SET_SELECTED_SNIPPET; // = "chat_set_selected_command"
payload: { id: string; snippet: string; language: string };
payload: { id: string; snippet: { code: string; language: string } };
}

/**
Expand Down Expand Up @@ -285,14 +277,6 @@ interface PasteDiffFromChat extends ActionFromChat {
type: EVENT_NAMES_FROM_CHAT.PASTE_DIFF; // = "chat_paste_diff"
payload: { id: string; content: string };
}

/**
* This message is used to attach context files from the host to the chat.
*/
interface ReceiveContextFile extends ActionToChat {
type: EVENT_NAMES_TO_CHAT.RECEIVE_FILES; // = "receive_context_file"
payload: { id: string; files: ChatContextFile[] };
}
```

### Data types in the events
Expand Down
2 changes: 2 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@
[x] limit the size of undo / redo history
[x] fix re-attaching files on retry
[x] undo redo, holding ctrl keeps the box open until the user releases it
[x] attach file with @ command,
[x] bug: add text, add file go back and edit the text fixed by prepending the command to the value

### EVENTS TODO FOR IDEs

Expand Down
1 change: 0 additions & 1 deletion src/components/ChatForm/CharForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const App: React.FC<Partial<ChatFormProps>> = (props) => {
error: "",
clearError: noop,
canChangeModel: false,
handleContextFile: noop,
hasContextFile: false,
commands: {
available_commands: [],
Expand Down
1 change: 0 additions & 1 deletion src/components/ChatForm/ChatForm.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ const meta = {
clearError: noop,
canChangeModel: true,
hasContextFile: false,
handleContextFile: noop,
commands: {
available_commands: testCommands,
selected_command: "",
Expand Down
25 changes: 19 additions & 6 deletions src/components/ChatForm/ChatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ComboBox, type ComboBoxProps } from "../ComboBox";
import type { ChatState } from "../../hooks";
import { ChatContextFile } from "../../services/refact";
import { FilesPreview } from "./FilesPreview";
import { useConfig } from "../../contexts/config-context";

const CapsSelect: React.FC<{
value: string;
Expand Down Expand Up @@ -53,10 +54,9 @@ export type ChatFormProps = {
canChangeModel: boolean;
isStreaming: boolean;
onStopStreaming: () => void;
handleContextFile: () => void;
hasContextFile: boolean;
commands: ChatState["rag_commands"];
attachFile: ChatState["active_file"];
hasContextFile: boolean;
requestCommandsCompletion: ComboBoxProps["requestCommandsCompletion"];
setSelectedCommand: (command: string) => void;
filesInPreview: ChatContextFile[];
Expand All @@ -77,7 +77,6 @@ export const ChatForm: React.FC<ChatFormProps> = ({
canChangeModel,
isStreaming,
onStopStreaming,
handleContextFile,
hasContextFile,
commands,
attachFile,
Expand All @@ -90,7 +89,9 @@ export const ChatForm: React.FC<ChatFormProps> = ({
}) => {
const [value, setValue] = React.useState("");
const [snippetAdded, setSnippetAdded] = React.useState(false);
const config = useConfig();

// TODO: this won't update the value in the text area
useEffect(() => {
if (!snippetAdded && selectedSnippet.code) {
setValue(
Expand Down Expand Up @@ -128,14 +129,26 @@ export const ChatForm: React.FC<ChatFormProps> = ({
);
}

const checked = value.includes(`@file ${attachFile.name}`);

return (
<Box mt="1" position="relative">
{!isOnline && <Callout type="info">Offline</Callout>}
{canChangeModel && (
{config.host !== "web" && !hasContextFile && (
<FileUpload
fileName={attachFile.name}
onClick={handleContextFile}
checked={hasContextFile || attachFile.attach}
onClick={() =>
setValue((preValue) => {
const command = `@file ${attachFile.name}${
value.length > 0 ? "\n" : ""
}`;
if (checked) {
return preValue.replace(command, "");
}
return `${command}${preValue}`;
})
}
checked={checked}
/>
)}
<Flex>
Expand Down
53 changes: 0 additions & 53 deletions src/events/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
ChatResponse,
CapsResponse,
isCapsResponse,
ChatContextFile,
CommandCompletionResponse,
ChatContextFileMessage,
} from "../services/refact";
Expand All @@ -13,7 +12,6 @@ export enum EVENT_NAMES_FROM_CHAT {
ASK_QUESTION = "chat_question",
REQUEST_CAPS = "chat_request_caps",
STOP_STREAMING = "chat_stop_streaming",
REQUEST_FILES = "chat_request_for_file",
BACK_FROM_CHAT = "chat_back_from_chat",
OPEN_IN_CHAT_IN_TAB = "open_chat_in_new_tab",
SEND_TO_SIDE_BAR = "chat_send_to_sidebar",
Expand All @@ -35,10 +33,7 @@ export enum EVENT_NAMES_TO_CHAT {
RECEIVE_CAPS_ERROR = "receive_caps_error",
SET_CHAT_MODEL = "chat_set_chat_model",
SET_DISABLE_CHAT = "set_disable_chat",
RECEIVE_FILES = "receive_context_file",
REMOVE_FILES = "remove_context_file",
ACTIVE_FILE_INFO = "chat_active_file_info",
TOGGLE_ACTIVE_FILE = "chat_toggle_active_file",
RECEIVE_AT_COMMAND_COMPLETION = "chat_receive_at_command_completion",
RECEIVE_AT_COMMAND_PREVIEW = "chat_receive_at_command_preview",
SET_SELECTED_AT_COMMAND = "chat_set_selected_command",
Expand Down Expand Up @@ -126,18 +121,6 @@ export function isPasteDiffFromChat(
return action.type === EVENT_NAMES_FROM_CHAT.PASTE_DIFF;
}

export interface RequestForFileFromChat extends ActionFromChat {
type: EVENT_NAMES_FROM_CHAT.REQUEST_FILES;
payload: { id: string };
}

export function isRequestForFileFromChat(
action: unknown,
): action is RequestForFileFromChat {
if (!isActionFromChat(action)) return false;
return action.type === EVENT_NAMES_FROM_CHAT.REQUEST_FILES;
}

export interface QuestionFromChat extends ActionFromChat {
type: EVENT_NAMES_FROM_CHAT.ASK_QUESTION;
payload: ChatThread;
Expand Down Expand Up @@ -235,17 +218,6 @@ export function isSetSelectedAtCommand(
return action.type === EVENT_NAMES_TO_CHAT.SET_SELECTED_AT_COMMAND;
}

export interface ToggleActiveFile extends ActionToChat {
type: EVENT_NAMES_TO_CHAT.TOGGLE_ACTIVE_FILE;
payload: { id: string; attach_file: boolean };
}

export function isToggleActiveFile(
action: unknown,
): action is ToggleActiveFile {
if (!isActionToChat(action)) return false;
return action.type === EVENT_NAMES_TO_CHAT.TOGGLE_ACTIVE_FILE;
}
export interface ActiveFileInfo extends ActionToChat {
type: EVENT_NAMES_TO_CHAT.ACTIVE_FILE_INFO;
payload: { id: string; name: string; can_paste: boolean };
Expand All @@ -256,31 +228,6 @@ export function isActiveFileInfo(action: unknown): action is ActiveFileInfo {
return action.type === EVENT_NAMES_TO_CHAT.ACTIVE_FILE_INFO;
}

export interface ReceiveContextFile extends ActionToChat {
type: EVENT_NAMES_TO_CHAT.RECEIVE_FILES;
payload: {
id: string;
files: ChatContextFile[];
};
}

export function isReceiveContextFile(
action: unknown,
): action is ReceiveContextFile {
if (!isActionToChat(action)) return false;
return action.type === EVENT_NAMES_TO_CHAT.RECEIVE_FILES;
}

export interface RemoveContextFile extends ActionToChat {
type: EVENT_NAMES_TO_CHAT.REMOVE_FILES;
payload: { id: string };
}

export function isRemoveContext(action: unknown): action is RemoveContextFile {
if (!isActionToChat(action)) return false;
return action.type === EVENT_NAMES_TO_CHAT.REMOVE_FILES;
}

export interface SetChatDisable extends ActionToChat {
type: EVENT_NAMES_TO_CHAT.SET_DISABLE_CHAT;
payload: { id: string; disable: boolean };
Expand Down
6 changes: 2 additions & 4 deletions src/features/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ export const Chat: React.FC<{ style?: React.CSSProperties }> = (props) => {
clearError,
setChatModel,
stopStreaming,
handleContextFile,
hasContextFile,
backFromChat,
openChatInNewTab,
sendToSideBar,
sendReadyMessage,
handleNewFileClick,
handlePasteDiffClick,
hasContextFile,
requestCommandsCompletion,
setSelectedCommand,
removePreviewFileByName,
Expand Down Expand Up @@ -118,9 +117,8 @@ export const Chat: React.FC<{ style?: React.CSSProperties }> = (props) => {
onSetChatModel={setChatModel}
caps={state.caps}
onStopStreaming={stopStreaming}
handleContextFile={handleContextFile}
hasContextFile={hasContextFile}
commands={state.rag_commands}
hasContextFile={hasContextFile}
requestCommandsCompletion={requestCommandsCompletion}
setSelectedCommand={setSelectedCommand}
onClose={maybeSendToSideBar}
Expand Down
79 changes: 0 additions & 79 deletions src/hooks/useEventBusForChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ import {
isChatReceiveCapsError,
isSetChatModel,
isSetDisableChat,
isReceiveContextFile,
isRequestForFileFromChat,
isRemoveContext,
isActiveFileInfo,
isToggleActiveFile,
type ToggleActiveFile,
type NewFileFromChat,
type PasteDiffFromChat,
type ReadyMessage,
Expand All @@ -48,7 +43,6 @@ import {
setPreviousMessagesLength,
type Snippet,
} from "../events";
import { useConfig } from "../contexts/config-context";
import { usePostMessage } from "./usePostMessage";
import { useDebounceCallback } from "usehooks-ts";

Expand Down Expand Up @@ -245,41 +239,6 @@ function reducer(state: ChatState, action: ActionToChat): ChatState {
};
}

if (isThisChat && isRequestForFileFromChat(action)) {
return {
...state,
waiting_for_response: true,
};
}

if (isThisChat && isReceiveContextFile(action)) {
return {
...state,
waiting_for_response: false,
chat: {
...state.chat,
messages: [
["context_file", action.payload.files],
...state.chat.messages,
],
},
};
}

if (isThisChat && isRemoveContext(action)) {
const messages = state.chat.messages.filter(
(message) => !isChatContextFileMessage(message),
);

return {
...state,
chat: {
...state.chat,
messages,
},
};
}

if (isThisChat && isActiveFileInfo(action)) {
const { name, can_paste } = action.payload;
return {
Expand All @@ -292,16 +251,6 @@ function reducer(state: ChatState, action: ActionToChat): ChatState {
};
}

if (isThisChat && isToggleActiveFile(action)) {
return {
...state,
active_file: {
...state.active_file,
attach: action.payload.attach_file,
},
};
}

if (isThisChat && isReceiveAtCommandCompletion(action)) {
const selectedCommand = state.rag_commands.selected_command;
const availableCommands = selectedCommand
Expand Down Expand Up @@ -450,7 +399,6 @@ function createInitialState(): ChatState {
const initialState = createInitialState();
// Maybe use context to avoid prop drilling?
export const useEventBusForChat = () => {
const config = useConfig();
const [state, dispatch] = useReducer(reducer, initialState);
const postMessage = usePostMessage();

Expand Down Expand Up @@ -580,32 +528,6 @@ export const useEventBusForChat = () => {
isChatContextFileMessage(message),
);

function handleContextFileForWeb() {
if (hasContextFile) {
dispatch({
type: EVENT_NAMES_TO_CHAT.REMOVE_FILES,
payload: { id: state.chat.id },
});
} else {
postMessage({
type: EVENT_NAMES_FROM_CHAT.REQUEST_FILES,
payload: { id: state.chat.id },
});
}
}

function handleContextFile(toggle?: boolean) {
if (config.host === "web") {
handleContextFileForWeb();
} else {
const action: ToggleActiveFile = {
type: EVENT_NAMES_TO_CHAT.TOGGLE_ACTIVE_FILE,
payload: { id: state.chat.id, attach_file: !!toggle },
};
dispatch(action);
}
}

function backFromChat() {
postMessage({
type: EVENT_NAMES_FROM_CHAT.BACK_FROM_CHAT,
Expand Down Expand Up @@ -716,7 +638,6 @@ export const useEventBusForChat = () => {
clearError,
setChatModel,
stopStreaming,
handleContextFile,
hasContextFile,
backFromChat,
openChatInNewTab,
Expand Down
Loading

0 comments on commit 45c7790

Please sign in to comment.