Skip to content

Commit

Permalink
migrate frontend to typescript (#60) (#77)
Browse files Browse the repository at this point in the history
* migrate frontend to typescript (#60)

* Removed DEFENCE_NAMES enum

* Typing emails in ChatBox component

* Removed references to OpenAI in the frontend

* Moved defences about

* DefenceInfo class

* Models directory

* Removed unused import

---------

Co-authored-by: George Sproston <gsproston@scottlogic.com>
  • Loading branch information
scottrowan and gsproston-scottlogic authored Aug 3, 2023
1 parent 437a79b commit 004f57f
Show file tree
Hide file tree
Showing 30 changed files with 365 additions and 283 deletions.
4 changes: 3 additions & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"typescript": "^4.1.6"
}
}
11 changes: 6 additions & 5 deletions frontend/src/App.js → frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import DefenceBox from "./components/DefenceBox/DefenceBox";
import EmailBox from "./components/EmailBox/EmailBox";
import Header from "./components/Header";
import { useState } from "react";
import { EmailInfo } from "./models/email";

function App() {
const [defenceBoxKey, setDefenceBoxKey] = useState(0);
const [emails, setEmails] = useState([]);
const [triggeredDefences, setTriggeredDefences] = useState([]);
const [defenceBoxKey, setDefenceBoxKey] = useState<number>(0);
const [emails, setEmails] = useState<EmailInfo[]>([]);
const [triggeredDefences, setTriggeredDefences] = useState<string[]>([]);

const updateTriggeredDefences = (defences) => {
const updateTriggeredDefences = (defenceDetails: string[]) => {
// set the new triggered defences
setTriggeredDefences(defences);
setTriggeredDefences(defenceDetails);
// update the key of the defence box to force a re-render
setDefenceBoxKey(defenceBoxKey + 1);
};
Expand Down
29 changes: 0 additions & 29 deletions frontend/src/Defences.js

This file was deleted.

31 changes: 31 additions & 0 deletions frontend/src/Defences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { DEFENCE_TYPES, DefenceInfo } from "./models/defence";

const DEFENCE_DETAILS: DefenceInfo[] = [
new DefenceInfo(
DEFENCE_TYPES.CHARACTER_LIMIT,
"Character Limit",
"Limit the number of characters in the user input. This is a form of prompt validation."
),
new DefenceInfo(
DEFENCE_TYPES.EMAIL_WHITELIST,
"Email Whitelist",
"Only allow emails to those on a whitelist. They can be full email addresses, or domains in the format '*@scottlogic.com'"
),
new DefenceInfo(
DEFENCE_TYPES.RANDOM_SEQUENCE_ENCLOSURE,
"Random Sequence Enclosure",
"Enclose the prompt between a random string and instruct bot to only follow enclosed instructions. This is a form of prompt validation."
),
new DefenceInfo(
DEFENCE_TYPES.SYSTEM_ROLE,
"System Role",
"Tell the chat bot to follow a specific role."
),
new DefenceInfo(
DEFENCE_TYPES.XML_TAGGING,
"XML Tagging",
"Enclose the users prompt between <user_input> tags and escapes xml characters in raw input. This is a form of prompt validation."
),
];

export { DEFENCE_DETAILS };
Original file line number Diff line number Diff line change
@@ -1,54 +1,63 @@
import React, { useEffect, useState } from "react";

import "./ChatBox.css";
import ChatBoxFeed from "./ChatBoxFeed";
import {
clearOpenAiChat,
openAiSendMessage,
} from "../../service/openaiService";
import { clearChat, sendMessage } from "../../service/chatService";
import { getSentEmails } from "../../service/emailService";
import { ChatMessage, ChatResponse } from "../../models/chat";
import { EmailInfo } from "../../models/email";

function ChatBox(props) {
const [isSendingMessage, setIsSendingMessage] = useState(false);
const [messages, setMessages] = useState([]);
function ChatBox(
this: any,
{
setEmails,
updateTriggeredDefences,
}: {
setEmails: (emails: EmailInfo[]) => void;
updateTriggeredDefences: (defences: string[]) => void;
}
) {
const [isSendingMessage, setIsSendingMessage] = useState<boolean>(false);
const [messages, setMessages] = useState<ChatMessage[]>([]);

// called on mount
useEffect(() => {
// clear remote messages
clearOpenAiChat();
clearChat();
// get sent emails
getSentEmails().then((sentEmails) => {
props.setEmails(sentEmails);
setEmails(sentEmails);
});
}, []);
}, [setEmails]);

const clearClicked = () => {
// clear local messages
setMessages([]);
// clear remote messages
clearOpenAiChat();
clearChat();
};

const sendChatMessage = async (event) => {
if (event.key === "Enter" && !isSendingMessage) {
const sendChatMessage = async (
event: React.KeyboardEvent<HTMLInputElement>
) => {
if (event.key === "Enter" && event.target !== null && !isSendingMessage) {
setIsSendingMessage(true);
// get the message
const message = event.target.value;
const message = event.currentTarget.value;

// if input has been edited, add both messages to the list of messages. otherwise add original message only
setMessages((messages) => [
setMessages((messages: ChatMessage[]) => [
...messages,
{ message: message, isUser: true, isOriginalMessage: true },
]);
// clear the input
event.target.value = "";
event.currentTarget.value = "";

const reply = await openAiSendMessage(message);
const transformedMessage = reply.transformedMessage;
const response: ChatResponse = await sendMessage(message);
const transformedMessage = response.transformedMessage;
const isTransformed = transformedMessage !== message;
// add the transformed message to the chat box if it is different from the original message
if (isTransformed) {
setMessages((messages) => [
setMessages((messages: ChatMessage[]) => [
...messages,
{
message: transformedMessage,
Expand All @@ -58,20 +67,25 @@ function ChatBox(props) {
]);
}
// add it to the list of messages
setMessages((messages) => [
setMessages((messages: ChatMessage[]) => [
...messages,
{ isUser: false, message: reply.reply, defenceInfo: reply.defenceInfo },
{
isUser: false,
message: response.reply,
defenceInfo: response.defenceInfo,
isOriginalMessage: true,
},
]);
// update triggered defences
props.updateTriggeredDefences(reply.defenceInfo.triggeredDefences);
updateTriggeredDefences(response.defenceInfo.triggeredDefences);

// we have the message reply
setIsSendingMessage(false);

// get sent emails
const sentEmails = await getSentEmails();
const sentEmails: EmailInfo[] = await getSentEmails();
// update emails
props.setEmails(sentEmails);
setEmails(sentEmails);
}
};

Expand Down
22 changes: 0 additions & 22 deletions frontend/src/components/ChatBox/ChatBoxFeed.jsx

This file was deleted.

15 changes: 15 additions & 0 deletions frontend/src/components/ChatBox/ChatBoxFeed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import "./ChatBoxFeed.css";
import { ChatMessage } from "../../models/chat";
import ChatBoxMessage from "./ChatBoxMessage";

function ChatBoxFeed({ messages }: { messages: ChatMessage[] }) {
return (
<div id="chat-box-feed">
{[...messages].reverse().map((message, index) => {
return <ChatBoxMessage key={index} message={message} />;
})}
</div>
);
}

export default ChatBoxFeed;
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { ChatMessage } from "../../models/chat";
import "./ChatBoxMessage.css";

function ChatBoxMessage(props) {
function ChatBoxMessage({ message }: { message: ChatMessage }) {
return (
<div
className={
props.isUser
? props.isOriginalMessage
message.isUser
? message.isOriginalMessage
? "chat-box-message chat-box-message-user"
: "chat-box-message chat-box-message-user-transformed"
: props.defenceInfo.blocked
: message.defenceInfo?.blocked
? "chat-box-message chat-box-message-ai chat-box-message-blocked"
: "chat-box-message chat-box-message-ai chat-box-message-ok"
}
>
{(props.isUser && props.isOriginalMessage && <b>Input: </b>) ||
(props.isUser && !props.isOriginalMessage && <b>Edited: </b>)}
{props.message}
{(message.isUser && message.isOriginalMessage && <b>Input: </b>) ||
(message.isUser && !message.isOriginalMessage && <b>Edited: </b>)}
{message.message}
</div>
);
}
Expand Down
96 changes: 0 additions & 96 deletions frontend/src/components/DefenceBox/DefenceBox.jsx

This file was deleted.

Loading

0 comments on commit 004f57f

Please sign in to comment.