Skip to content

Commit

Permalink
91 export log (#171)
Browse files Browse the repository at this point in the history
* try with react-pdf

* Export chat history and sent emails to pdf

* realign messages and add phase to title

* Button for download link (sidebar for now)

* remove double model box
  • Loading branch information
heatherlogan-scottlogic committed Aug 22, 2023
1 parent 0d27ffe commit cf38f71
Show file tree
Hide file tree
Showing 12 changed files with 1,434 additions and 272 deletions.
1,314 changes: 1,042 additions & 272 deletions frontend/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@react-pdf/renderer": "^3.1.12",
"react": "^18.2.0",
"react-contenteditable": "^3.3.7",
"react-dom": "^18.2.0",
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import EmailBox from "./components/EmailBox/EmailBox";
import PhaseSelectionBox from "./components/PhaseSelectionBox/PhaseSelectionBox";
import Header from "./components/Header";
import ModelSelectionBox from "./components/ModelSelectionBox/ModelSelectionBox";
import ExportPDFLink from "./components/ExportChat/ExportPDFLink";
import { EmailInfo } from "./models/email";
import { CHAT_MESSAGE_TYPE, ChatMessage } from "./models/chat";
import { DefenceInfo } from "./models/defence";
Expand Down Expand Up @@ -151,6 +152,11 @@ function App() {
currentPhase === PHASE_NAMES.SANDBOX) && (
<AttackBox attacks={ATTACKS_ALL} />
)}
<ExportPDFLink
messages={messages}
emails={emails}
currentPhase={currentPhase}
/>
{/* hide model selection box on phases 0 and 1 */}
{currentPhase === PHASE_NAMES.SANDBOX && <ModelSelectionBox />}
</div>
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/components/ExportChat/ExportChatBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React, { Fragment } from "react";
import { Text, View, StyleSheet } from "@react-pdf/renderer";
import { ChatMessage } from "../../models/chat";
import ExportChatMessage from "./ExportChatMessage";

const styles = StyleSheet.create({
row: {
flexDirection: "row",
alignItems: "center",
},
});

const ExportChatBox = ({ items }: { items: ChatMessage[] }) => {
const rows = items.map((item, index) => (
<View style={styles.row} key={index}>
<ExportChatMessage message={item} />
</View>
));
return <Fragment>{rows}</Fragment>;
};

export default ExportChatBox;
88 changes: 88 additions & 0 deletions frontend/src/components/ExportChat/ExportChatMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { Fragment } from "react";
import { Text, View, StyleSheet } from "@react-pdf/renderer";
import { CHAT_MESSAGE_TYPE, ChatMessage } from "../../models/chat";

const styles = StyleSheet.create({
chatBoxMessage: {
borderColor: "#888",
borderRadius: 8,
borderStyle: "solid",
borderWidth: 1,
marginTop: 8,
padding: 8,
maxWidth: "85%",
hyphens: "auto",
whiteSpace: "pre-wrap",
wordWrap: "break-word",
float: "left",
textAlign: "left",
},
chatBoxMessageBot: {
borderColor: "#888",
borderRadius: 8,
borderStyle: "solid",
borderWidth: 1,
marginTop: 8,
padding: 8,
maxWidth: "85%",
hyphens: "auto",
whiteSpace: "pre-wrap",
wordWrap: "break-word",
float: "right",
marginLeft: "auto",
textAlign: "right",
},
chatBoxInfo: {
marginTop: 8,
marginBottom: 8,
},
text: {
fontSize: 10,
},
});

const getFullPrefix = (message: ChatMessage) => {
switch (message.type) {
case CHAT_MESSAGE_TYPE.INFO:
return "Info: " + message.message;
case CHAT_MESSAGE_TYPE.DEFENCE_TRIGGERED:
return "Info: " + message.message;
case CHAT_MESSAGE_TYPE.USER:
if (message.isOriginalMessage) {
return "You: " + message.message;
} else {
return "You (edited): " + message.message;
}
case CHAT_MESSAGE_TYPE.BOT:
return "Bot: " + message.message;
default:
return message.message;
}
};

const getMessageStyle = (type: CHAT_MESSAGE_TYPE) => {
switch (type) {
case CHAT_MESSAGE_TYPE.INFO:
return styles.chatBoxInfo;
case CHAT_MESSAGE_TYPE.DEFENCE_TRIGGERED:
return styles.chatBoxInfo;
case CHAT_MESSAGE_TYPE.USER:
return styles.chatBoxMessage;
case CHAT_MESSAGE_TYPE.BOT:
return styles.chatBoxMessageBot;
case CHAT_MESSAGE_TYPE.PHASE_INFO:
return styles.chatBoxMessageBot;
default:
return styles.chatBoxMessage;
}
};

const ExportChatMessage = ({ message }: { message: ChatMessage }) => {
return (
<View style={getMessageStyle(message.type)}>
<Text style={styles.text}>{getFullPrefix(message)}</Text>
</View>
);
};

export default ExportChatMessage;
84 changes: 84 additions & 0 deletions frontend/src/components/ExportChat/ExportContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from "react";
import { Document, Page, View, Text, StyleSheet } from "@react-pdf/renderer";
import { ChatMessage } from "../../models/chat";
import { EmailInfo } from "../../models/email";
import ExportChatBox from "./ExportChatBox";
import ExportEmailBox from "./ExportEmailBox";

const styles = StyleSheet.create({
page: {
backgroundColor: "white",
},
pageContent: {
flexDirection: "row",
},
headerSection: {
margin: 10,
padding: 10,
borderBottom: "1px solid black",
alignContent: "center",
},
subheaderSection: {
borderBottom: "1px solid black",
marginBottom: 5,
alignContent: "center",
},
header: {
fontSize: 20,
},
section: {
margin: 5,
padding: 10,
width: "50%",
flexDirection: "column",
},
chatMessage: {
marginBottom: 5,
fontSize: 10,
},
});

const ExportContent = ({
messages,
emails,
currentPhase,
}: {
messages: ChatMessage[];
emails: EmailInfo[];
currentPhase: number;
}) => {
const getTitle = (currentPhase: number) => {
const title = "Prompt injection demo chat";
if (currentPhase === 3) {
return title + " (sandbox mode)";
} else {
return title + " (phase " + currentPhase + ")";
}
};
console.log("Exporting chat history");
return (
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.headerSection}>
<Text style={styles.header}>{getTitle(currentPhase)}</Text>
</View>
<View style={styles.pageContent}>
<View style={styles.section}>
<View style={styles.subheaderSection}>
<Text>chat</Text>
</View>
<ExportChatBox items={messages} />
</View>
<View style={styles.section}>
<View style={styles.subheaderSection}>
<Text>sent emails</Text>
</View>
<ExportEmailBox emails={emails} />
</View>
</View>
</Page>
</Document>
);
};

export default ExportContent;
22 changes: 22 additions & 0 deletions frontend/src/components/ExportChat/ExportEmailBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React, { Fragment } from "react";
import { Text, View, StyleSheet } from "@react-pdf/renderer";
import { EmailInfo } from "../../models/email";
import ExportEmailMessage from "./ExportEmailMessage";

const styles = StyleSheet.create({
row: {
flexDirection: "row",
alignItems: "center",
},
});

const ExportEmailBox = ({ emails }: { emails: EmailInfo[] }) => {
const rows = emails.map((email, index) => (
<View style={styles.row} key={index}>
<ExportEmailMessage email={email} />
</View>
));
return <Fragment>{rows}</Fragment>;
};

export default ExportEmailBox;
36 changes: 36 additions & 0 deletions frontend/src/components/ExportChat/ExportEmailMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { Fragment } from "react";
import { Text, View, StyleSheet } from "@react-pdf/renderer";
import { EmailInfo } from "../../models/email";

const styles = StyleSheet.create({
sentEmail: {
borderColor: "#ccc",
borderRadius: 5,
borderStyle: "solid",
borderWidth: 1,
fontSize: 14,
marginTop: 10,
padding: 5,
whiteSpace: "pre-wrap",
},
sentEmailDivider: {
borderBottom: "1px solid #ccc",
margin: "5px 0",
width: "100%",
},
text: {
fontSize: 10,
},
});
const ExportEmailMessage = ({ email }: { email: EmailInfo }) => {
return (
<View style={styles.sentEmail}>
<Text style={styles.text}>to: {email.address}</Text>
<Text style={styles.text}>subject: {email.subject}</Text>
<Text style={styles.sentEmailDivider}></Text>
<Text style={styles.text}>{email.content}</Text>
</View>
);
};

export default ExportEmailMessage;
23 changes: 23 additions & 0 deletions frontend/src/components/ExportChat/ExportPDFLink.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#export-chat-box {
margin: 15px;
padding-top: 20px;
padding-bottom: 20px;
text-align: center;
}

a {
padding: 5 12px;
font-size: 16px;
color: #666;
text-decoration: none;
}

button {
height: 30px;
border: 1px solid #ccc;
border-radius: 5px;
padding: 0 5px;
font-size: 16px;
box-sizing: border-box;
color: #666;
}
45 changes: 45 additions & 0 deletions frontend/src/components/ExportChat/ExportPDFLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from "react";
import ExportContent from "./ExportContent";
import { ChatMessage } from "../../models/chat";
import { EmailInfo } from "../../models/email";
import { PDFDownloadLink } from "@react-pdf/renderer";

import "./ExportPDFLink.css";

const ExportPDFLink = ({
messages,
emails,
currentPhase,
}: {
messages: ChatMessage[];
emails: EmailInfo[];
currentPhase: number;
}) => {
const getFileName = () => {
if (currentPhase === 3) {
return "prompt-injection-chat-log-sandbox.pdf";
}
return "prompt-injection-chat-log-phase-" + currentPhase + ".pdf";
};
return (
<div id="export-chat-box">
<button>
<PDFDownloadLink
className="pdf-download-button"
document={
<ExportContent
messages={messages}
emails={emails}
currentPhase={currentPhase}
/>
}
fileName={getFileName()}
>
{"Export chat history"}
</PDFDownloadLink>{" "}
</button>
</div>
);
};

export default ExportPDFLink;
Loading

0 comments on commit cf38f71

Please sign in to comment.