Skip to content

Commit

Permalink
Feature - Conversation Task (#10)
Browse files Browse the repository at this point in the history
* Started setting up chat task, added icon and edited audioToText icon

* Started adding stories

* basic setup for conversation input

* First pass at FE of Conversation

* minor renames

* refactored how chat history is working

* visual updates, updating api interactions

* Limited list of demo tasks

* fixed timing issues and cleaned up some console logs and unnecessary code

* misc cleanup

* removing one more console log
  • Loading branch information
walkingtowork committed Apr 12, 2024
1 parent 1ec180e commit ffc5eb5
Show file tree
Hide file tree
Showing 23 changed files with 483 additions and 33 deletions.
16 changes: 16 additions & 0 deletions src/components/Experiment/QuickInput/QuickInput.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
textToText,
textToCode,
audioToText,
textConversation,
} from "../../../helpers/TaskIDs";
import {
SampleImageClassificationInputs,
Expand Down Expand Up @@ -113,3 +114,18 @@ AudioToText.args = {
},
},
};

export const TextConversation = Template.bind({});
TextConversation.args = {
sampleInputs: [
"Show me a recipe for pizza",
"What is the weather tomorrow?",
"What is the meaning of life?",
],
hideUpload: true,
model: {
output: {
type: textConversation,
},
},
};
1 change: 1 addition & 0 deletions src/components/Experiment/QuickInput/QuickTextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default function QuickTextInput(props) {
selectTab,
selectInput,
runModel,
hideUpload=false,
} = useQuickInputControl(props);
const {getBlock, getElement} = useBEMNaming("quick-text-input");
const task = Task.getStaticTask(props.model.output.type);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import "../../../../../App";

.text-output, .text-to-code-output, .audio-to-text-output {
.text-output, .text-to-code-output, .audio-to-text-output, .text-conversation-output {
display: flex;
flex-direction: row;
gap: 72px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ export default function useTextOutput(trial) {
return trial?.inputs[0] ?? "";
};

const getInferenceDuration = () => {
return trial?.results?.duration_for_inference ?? "0s";
};

const [input, setInput] = useState(getInputText());
const [inferenceDuration, setInferenceDuration] = useState(getInferenceDuration());

const getOutput = () => {
if (!trial?.results?.responses || !trial?.results?.responses[0].features)
Expand All @@ -27,14 +32,13 @@ export default function useTextOutput(trial) {
}
};

const getInferenceDuration = () => {
return trial?.results?.duration_for_inference ?? "0s";
};


return {
output: getOutput(),
input,
setInput,
inferenceDuration: getInferenceDuration(),
inferenceDuration,
setInferenceDuration
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React, { useEffect, useState, useRef } from "react";
import "../Text/TextOutput.scss";
import "./TextConversationOutputChatContainer.scss";
import useBEMNaming from "../../../../../common/useBEMNaming";
import useTextOutput from "../Text/useTextOutput";
import OutputDuration from "../_Common/components/OutputDuration";
import { textConversation } from "../../../../../helpers/TaskIDs";
import Task from "../../../../../helpers/Task";
import Rating from "../Classification/Rating";

const ROLE = {
USER: 'user',
ASSISTANT: 'assistant'
}

export default function TextConversationOutput(props) {
const { getBlock, getElement } = useBEMNaming("text-conversation-output");
const { inferenceDuration, output, input, setInput, setInferenceDuration } = useTextOutput(
props.trial
);

const task = Task.getStaticTask(textConversation);

const [conversation, setConversation] = useState([
{ role: ROLE.USER, content: input },
{ role: ROLE.ASSISTANT, content: output }
]);
const [message, setMessage] = useState(null);
const [isSending, setIsSending] = useState(false);
const [newInput, setNewInput] = useState('');

const inputField = useRef(null); // Not working
const chatBottomPosition = useRef(null);

useEffect(() => {
if (message) {
setConversation([...conversation, message]);
}
}, [message])

useEffect(() => {
if (conversation) {
chatBottomPosition.current.scrollIntoView({ behavior: "smooth" });

if (conversation[conversation.length - 1].role === ROLE.USER) {
// Send to API
sendToAPI();
}
}
}, [conversation]);

useEffect(() => {
if (!isSending) {
// NOTE: Currently not working
if (inputField.current) {
console.log('focus on input field')
inputField.current.focus();
}

}
}, [isSending]);

const sendToAPI = async () => {
// Submit to API
const trialResponse = await props.onSubmit(newInput, conversation);
const newOutput = trialResponse?.results?.responses[0]?.features[0]?.text ?? "Something went wrong.";

setMessage({ role: ROLE.ASSISTANT, content: newOutput });

const newInferenceDuration = trialResponse?.results?.duration_for_inference ?? "0s";
setInferenceDuration(newInferenceDuration);

setIsSending(false);
};

const sendMessage = () => {
setMessage({ role: ROLE.USER, content: newInput });
setIsSending(true);
setNewInput('');
}


return (
<div className={getBlock()}>
<div className={getElement("results")}>
<div className={getElement("title-row")}>
<h3 className={getElement("title-row-title")}>Output</h3>
<OutputDuration duration={inferenceDuration} />
</div>
<p className={getElement("subtitle")}>
{task.outputText}
</p>
<div className={getElement("output-container")}>
<div className={getElement("chat-container")}>
{
conversation.map((message, index) => {
return (
<div
key={index}
className={getElement(`chat-${message.role}-message`)}
>
{ message.role === "assistant" && (
<div className="assistant-icon-container">
<div className="assistant-icon">
ML
</div>
</div>

)}
<div className="speech-bubble">
{message.content}
</div>
</div>
)
})
}
<div ref={chatBottomPosition} />
</div>

</div>
<div className={getElement("chat-input-container")}>
<textarea
useref={inputField}
value={newInput}
onChange={(e) => setNewInput(e.target.value)}
className={getElement("input-container-text")}
autoFocus
></textarea>

<div className={getElement("input-submit-row")}>
<button
onClick={sendMessage}
className={getElement("input-submit-button")}
disabled={isSending}
>
{ (!isSending) ?
(
<span className={getElement("input-submit-button-content")}>
Send
</span>
) : (
<span className={getElement("input-submit-button-content")}>
<div className={getElement('spinner-container')}>
<div className={getElement('spinner')}></div>
</div>
</span>
)
}
</button>
</div>
</div>

<Rating />
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from "react";
import TextConversationOutput from "./TextConversationOutput";
import { TestTextConversationOutput, TestTextConversationOutput2 } from "./testData/testTextConversationOutput";

export default {
title: "Experiments/Quick Output/Text Conversation",
component: TextConversationOutput,
};

const template = (args) => <TextConversationOutput {...args} />;

const fakeOnSubmit = async (input, context) => {
// Note: context param should contain the conversation history,
// to be sent to the api

// This is basically what "should" happen, uncomment to confirm in Storybook
// see what values would be sent to the api
// const api = GetApiHelper();
// await api.runTrial(DefaultTextConversationModel, input, null, context);

// Dummy response instead of actually calling api.runTrial above
return TestTextConversationOutput2;
}

export const Default = template.bind({});
Default.args = { trial: TestTextConversationOutput, onSubmit: fakeOnSubmit};
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
@import "src/App";
.text-conversation-output {
&__output-container {
border: 1px solid $smokeDarker;
height: 202px;
overflow-y: scroll;
}
&__chat-container {
display: flex;
flex-direction: column;
padding: 16px 32px;
height: 200px;
.speech-bubble {
padding: 15px;
border-radius: 15px;
margin-bottom: 15px;
}
}
&__chat-user-message {
display: flex;
justify-content: flex-end;
padding-left: 10%;
.speech-bubble {
background-color: $azul;
color: white;
}
}
&__chat-assistant-message {
display: flex;
justify-content: flex-start;
padding-right: 5%;
.assistant-icon {
background-color: $grayDark;
color: white;
height: 38px;
width: 38px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
margin-right: 10px;
margin-top: 5px;
}
.speech-bubble {
background-color: $smokeDarker;
}
}

&__input-submit-button-content {
width: 80px;
}

&__input-container-text {
min-height: 80px;
}

&__input-submit-row {
display: flex;
justify-content: flex-end;
}

&__spinner-container {
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
}
&__spinner {
animation: spin 1s linear 0s infinite;
border-bottom: solid 3px $purple;
border-left: solid 3px transparent;
border-radius: 50%;
border-right: solid 3px $purple;
border-top: solid 3px $purple;
height: 25px;
width: 25px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export const TestTextConversationOutputGeneratedToken = {
id: "sampleidhere"
};

export const TestTextConversationOutput = {
id: "sampletesttextconversationoutputidhere",
inputs: [
'Show me a recipe for pizza',
],
completed_at: "2023-06-03T18:17:14.513854Z",
results: {
'duration': "9.216154124s",
'duration_for_inference': "9.193807904s",
'responses': [
{
'features': [
{
'role': 'assistant',
'text': 'Buy a frozen pizza and put it in the microwave for ten minutes',
'type': 'TEXT'
}
],
'id': "sampletestaudiototextoutputresponseidhere"
},
]
}
}

export const TestTextConversationOutput2 = {
id: "sampletesttextconversationoutputidhere",
inputs: [
'Can I have a different recipe?',
],
completed_at: "2023-06-03T18:17:14.513854Z",
results: {
'duration': "9.216154124s",
'duration_for_inference': "7.233107566s",
'responses': [
{
'features': [
{
'role': 'assistant',
'text': 'Just order from Domino\'s website',
'type': 'TEXT'
}
],
'id': "sampletestaudiototextoutputresponseidhere"
},
]
}
}
Loading

0 comments on commit ffc5eb5

Please sign in to comment.