Skip to content

Commit

Permalink
Wip smart links button (#206)
Browse files Browse the repository at this point in the history
* wip(configuration chat): send first messages.

* wip(configuration chat): tool use in configuration chat.
  • Loading branch information
MarcMcIntosh authored Nov 21, 2024
1 parent 2647fc9 commit 3bfaf7c
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 46 deletions.
3 changes: 3 additions & 0 deletions src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
useAppDispatch,
useSendChatRequest,
useGetPromptsQuery,
useAutoSend,
} from "../../hooks";
import type { Config } from "../../features/Config/configSlice";
import {
Expand Down Expand Up @@ -57,6 +58,8 @@ export const Chat: React.FC<ChatProps> = ({
const dispatch = useAppDispatch();
const messages = useAppSelector(selectMessages);

useAutoSend();

const promptsRequest = useGetPromptsQuery();
const selectedSystemPrompt = useAppSelector(getSelectedSystemPrompt);
const onSetSelectedSystemPrompt = (prompt: SystemPrompts) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { useCallback, useEffect } from "react";
import React, { useCallback, useEffect } from "react";
import classNames from "classnames";
import { useGetIntegrationDataByPathQuery } from "../../../hooks/useGetIntegrationDataByPathQuery";

import type { FC, FormEvent } from "react";
import type {
// ChatMessage,
ChatMessages,
Integration,
IntegrationField,
IntegrationPrimitive,
// UserMessage,
} from "../../../services/refact";

import styles from "./IntegrationForm.module.css";
Expand All @@ -18,6 +21,18 @@ import {
CustomLabel,
} from "../CustomFieldsAndWidgets";
import { toPascalCase } from "../../../utils/toPascalCase";
import { type SmartLink } from "../../../services/refact";
import {
useAppDispatch,
useAppSelector,
useSendChatRequest,
} from "../../../hooks";
import {
setIsConfigFlag,
setToolUse,
} from "../../../features/Chat/Thread/actions";
import { push } from "../../../features/Pages/pagesSlice";
import { selectChatId } from "../../../features/Chat";

type IntegrationFormProps = {
integrationPath: string;
Expand Down Expand Up @@ -136,6 +151,58 @@ export const IntegrationForm: FC<IntegrationFormProps> = ({
</Button>
</Flex>
</form>
{/** smart links */}
<div>
<h3>Smart Links</h3>
{integration.data.integr_schema.smartlinks.map((smartlink, index) => {
return <SmartLink key={`smartlink-${index}`} smartlink={smartlink} />;
})}
</div>
</div>
);
};

const SmartLink: React.FC<{ smartlink: SmartLink }> = ({ smartlink }) => {
// TODO: send chat on click and navigate away
const dispatch = useAppDispatch();
const chatId = useAppSelector(selectChatId);

const { sendMessages } = useSendChatRequest();
const handleClick = React.useCallback(() => {
const messages = (smartlink.sl_chat ?? []).reduce<ChatMessages>(
(acc, message) => {
if (message.role === "user" && typeof message.content === "string") {
return [...acc, { role: message.role, content: message.content }];
}

// TODO: Other types.
return acc;
},
[],
);

// dispatch(newChatAction()); id is out of date
dispatch(setToolUse("agent"));
dispatch(setIsConfigFlag({ id: chatId, isConfig: true }));
// TODO: make another version of send messages so there's no need to converting the messages
// eslint-disable-next-line no-console
void sendMessages(messages)
.then(() => {
dispatch(push({ name: "chat" }));
})
// eslint-disable-next-line no-console
.catch(console.error);
}, [chatId, dispatch, sendMessages, smartlink.sl_chat]);

const title = (smartlink.sl_chat ?? []).reduce<string[]>((acc, cur) => {
if (typeof cur.content === "string")
return [...acc, `${cur.role}: ${cur.content}`];
return acc;
}, []);

return (
<Button onClick={handleClick} title={title.join("\n")}>
{smartlink.sl_label}
</Button>
);
};
25 changes: 23 additions & 2 deletions src/features/Chat/Thread/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export const chatAskedQuestion = createAction<PayloadWithId>(
);

export const backUpMessages = createAction<
PayloadWithId & { messages: ChatThread["messages"] }
PayloadWithId & {
messages: ChatThread["messages"];
}
>("chatThread/backUpMessages");

// TODO: add history actions to this, maybe not used any more
Expand Down Expand Up @@ -75,6 +77,10 @@ export const setToolUse = createAction<ToolUse>("chatThread/setToolUse");
export const saveTitle = createAction<PayloadWithIdAndTitle>(
"chatThread/saveTitle",
);

export const setIsConfigFlag = createAction<
PayloadWithId & { isConfig: boolean }
>("chatThread/setConfig");
// TODO: This is the circular dep when imported from hooks :/
const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: RootState;
Expand Down Expand Up @@ -186,17 +192,26 @@ function checkForToolLoop(message: ChatMessages): boolean {

return hasDuplicates;
}

// TODO: add props for config chat
export const chatAskQuestionThunk = createAppAsyncThunk<
unknown,
{
messages: ChatMessages;
chatId: string;
tools: ToolCommand[] | null;
// TODO: make a separate function for this... and it'll need to be saved.
}
>("chatThread/sendChat", ({ messages, chatId, tools }, thunkAPI) => {
const state = thunkAPI.getState();

const thread =
chatId in state.chat.cache
? state.chat.cache[chatId]
: state.chat.thread.id === chatId
? state.chat.thread
: null;
const isConfig = thread?.isConfig ?? false;

const onlyDeterministicMessages = checkForToolLoop(messages);

const messagesForLsp = formatMessagesForLsp(messages);
Expand All @@ -211,6 +226,7 @@ export const chatAskQuestionThunk = createAppAsyncThunk<
apiKey: state.config.apiKey,
port: state.config.lspPort,
onlyDeterministicMessages,
isConfig,
})
.then((response) => {
if (!response.ok) {
Expand All @@ -236,3 +252,8 @@ export const chatAskQuestionThunk = createAppAsyncThunk<
thunkAPI.dispatch(doneStreaming({ id: chatId }));
});
});

// export const sendConfigurationThunk = createAppAsyncThunk(
// "chatThread/checkConfiguration",

// );
11 changes: 11 additions & 0 deletions src/features/Chat/Thread/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
restoreChat,
setPreventSend,
saveTitle,
setIsConfigFlag,
} from "./actions";
import { formatChatResponse } from "./utils";

Expand All @@ -26,6 +27,7 @@ const createChatThread = (tool_use: ToolUse): ChatThread => {
title: "",
model: "",
tool_use,
isConfig: false,
};
return chat;
};
Expand Down Expand Up @@ -182,4 +184,13 @@ export const chatReducer = createReducer(initialState, (builder) => {
state.thread.title = action.payload.title;
state.thread.isTitleGenerated = action.payload.isTitleGenerated;
});

builder.addCase(setIsConfigFlag, (state, action) => {
if (state.thread.id === action.payload.id) {
state.thread.isConfig = action.payload.isConfig;
}
if (action.payload.id in state.cache) {
state.cache[action.payload.id].isConfig = action.payload.isConfig;
}
});
});
2 changes: 2 additions & 0 deletions src/features/Chat/Thread/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const selectChatId = (state: RootState) => state.chat.thread.id;
export const selectModel = (state: RootState) => state.chat.thread.model;
export const selectMessages = (state: RootState) => state.chat.thread.messages;
export const selectToolUse = (state: RootState) => state.chat.tool_use;
export const selectThreadToolUse = (state: RootState) =>
state.chat.thread.tool_use;
export const selectIsWaiting = (state: RootState) =>
state.chat.waiting_for_response;
export const selectIsStreaming = (state: RootState) => state.chat.streaming;
Expand Down
1 change: 1 addition & 0 deletions src/features/Chat/Thread/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type ChatThread = {
tool_use?: ToolUse;
read?: boolean;
isTitleGenerated?: boolean;
isConfig?: boolean;
};

export type ToolUse = "quick" | "explore" | "agent";
Expand Down
120 changes: 80 additions & 40 deletions src/hooks/useSendChatRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
selectMessages,
selectPreventSend,
selectSendImmediately,
selectToolUse,
selectThreadToolUse,
} from "../features/Chat/Thread/selectors";
import {
useCheckForConfirmationMutation,
Expand All @@ -27,9 +27,8 @@ import {
backUpMessages,
chatAskQuestionThunk,
chatAskedQuestion,
setToolUse,
} from "../features/Chat/Thread/actions";
import { isToolUse } from "../features/Chat";

import { selectAllImages } from "../features/AttachedImages";
import { useAbortControllers } from "./useAbortControllers";
import {
Expand All @@ -42,24 +41,24 @@ let recallCounter = 0;

export const useSendChatRequest = () => {
const dispatch = useAppDispatch();
const hasError = useAppSelector(selectChatError);
// const hasError = useAppSelector(selectChatError);
const abortControllers = useAbortControllers();

const [triggerGetTools] = useGetToolsLazyQuery();
const [triggerCheckForConfirmation] = useCheckForConfirmationMutation();

const chatId = useAppSelector(selectChatId);
const streaming = useAppSelector(selectIsStreaming);
const chatError = useAppSelector(selectChatError);
// const streaming = useAppSelector(selectIsStreaming);
// const chatError = useAppSelector(selectChatError);

const errored: boolean = !!hasError || !!chatError;
const preventSend = useAppSelector(selectPreventSend);
// const errored: boolean = !!hasError || !!chatError;
// const preventSend = useAppSelector(selectPreventSend);
const isWaiting = useAppSelector(selectIsWaiting);

const currentMessages = useAppSelector(selectMessages);
const systemPrompt = useAppSelector(getSelectedSystemPrompt);
const sendImmediately = useAppSelector(selectSendImmediately);
const toolUse = useAppSelector(selectToolUse);
const toolUse = useAppSelector(selectThreadToolUse);
const attachedImages = useAppSelector(selectAllImages);

const areToolsConfirmed = useAppSelector(getToolsConfirmationStatus);
Expand All @@ -79,9 +78,10 @@ export const useSendChatRequest = () => {
const sendMessages = useCallback(
async (messages: ChatMessages) => {
let tools = await triggerGetTools(undefined).unwrap();
if (isToolUse(toolUse)) {
dispatch(setToolUse(toolUse));
}
// TODO: save tool use to state.chat
// if (toolUse && isToolUse(toolUse)) {
// dispatch(setToolUse(toolUse));
// }
if (toolUse === "quick") {
tools = [];
} else if (toolUse === "explore") {
Expand Down Expand Up @@ -180,34 +180,34 @@ export const useSendChatRequest = () => {

// TODO: Automatically calls tool calls. This means that this hook can only be used once :/
// TODO: Think of better way to manage useEffect calls, not with outer counter
useEffect(() => {
if (!streaming && currentMessages.length > 0 && !errored && !preventSend) {
const lastMessage = currentMessages.slice(-1)[0];
if (
isAssistantMessage(lastMessage) &&
lastMessage.tool_calls &&
lastMessage.tool_calls.length > 0
) {
if (!areToolsConfirmed) {
abort();
if (recallCounter < 1) {
recallCounter++;
return;
}
}
void sendMessages(currentMessages);
recallCounter = 0;
}
}
}, [
errored,
currentMessages,
preventSend,
sendMessages,
abort,
streaming,
areToolsConfirmed,
]);
// useEffect(() => {
// if (!streaming && currentMessages.length > 0 && !errored && !preventSend) {
// const lastMessage = currentMessages.slice(-1)[0];
// if (
// isAssistantMessage(lastMessage) &&
// lastMessage.tool_calls &&
// lastMessage.tool_calls.length > 0
// ) {
// if (!areToolsConfirmed) {
// abort();
// if (recallCounter < 1) {
// recallCounter++;
// return;
// }
// }
// void sendMessages(currentMessages);
// recallCounter = 0;
// }
// }
// }, [
// errored,
// currentMessages,
// preventSend,
// sendMessages,
// abort,
// streaming,
// areToolsConfirmed,
// ]);

const retry = useCallback(
(messages: ChatMessages) => {
Expand Down Expand Up @@ -240,5 +240,45 @@ export const useSendChatRequest = () => {
retry,
retryFromIndex,
confirmToolUsage,
sendMessages,
};
};

// NOTE: only use this once
export function useAutoSend() {
const streaming = useAppSelector(selectIsStreaming);
const currentMessages = useAppSelector(selectMessages);
const errored = useAppSelector(selectChatError);
const preventSend = useAppSelector(selectPreventSend);
const areToolsConfirmed = useAppSelector(getToolsConfirmationStatus);
const { sendMessages, abort } = useSendChatRequest();

useEffect(() => {
if (!streaming && currentMessages.length > 0 && !errored && !preventSend) {
const lastMessage = currentMessages.slice(-1)[0];
if (
isAssistantMessage(lastMessage) &&
lastMessage.tool_calls &&
lastMessage.tool_calls.length > 0
) {
if (!areToolsConfirmed) {
abort();
if (recallCounter < 1) {
recallCounter++;
return;
}
}
void sendMessages(currentMessages);
recallCounter = 0;
}
}
}, [
errored,
currentMessages,
preventSend,
sendMessages,
abort,
streaming,
areToolsConfirmed,
]);
}
Loading

0 comments on commit 3bfaf7c

Please sign in to comment.