Skip to content

Commit

Permalink
[WIP] Feature/style transfer (#13)
Browse files Browse the repository at this point in the history
* style transfer setup and icon updates

* adding multi-input support

* adding a few code comments

* misc cleanup

* Minor tweaks, multi-input still not working

* finished styling for Style Transfer Output

* Updates to QuickOutput

* fixed (probably) issue with selecting multiple inputs

* Added multi-input via urls

* small change to id in Quick Multi Input Tab Content

* misc cleanup

* minor text updates

* misc cleanup
  • Loading branch information
walkingtowork authored May 8, 2024
1 parent 5de9d59 commit fc3ffdf
Show file tree
Hide file tree
Showing 34 changed files with 38,320 additions and 102 deletions.
37,487 changes: 37,453 additions & 34 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions src/components/Experiment/QuickInput/QuickInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ import QuickTextInput from "./QuickTextInput";
import QuickAudioInput from "./QuickAudioInput";
import Task from "../../../helpers/Task";
import { TaskInputTypes } from "../../../helpers/TaskInputTypes";
import { TaskControls } from "../../HomePage/TaskControls";
import QuickMultiInput from "./QuickMultiInput";

export default function QuickInput(props) {
const task = Task.getStaticTask(props.model.output.type)

if (task.inputs.length > 1) {
// TODO: At some point this should replace the switch statement below
return <QuickMultiInput {...props} />
}

switch (task.inputType) {
case TaskInputTypes.Text:
Expand Down
12 changes: 12 additions & 0 deletions src/components/Experiment/QuickInput/QuickInput.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
image_enhancement,
object_detection,
semantic_segmentation,
styleTransfer,
textToText,
textToCode,
audioToText,
Expand All @@ -16,6 +17,7 @@ import {
SampleImageEnhancementInputs,
SampleObjectDetectionInputs,
SampleSegmentationInputs,
SampleStyleTransferInputs,
} from "../../../helpers/sampleImages";

export default {
Expand Down Expand Up @@ -65,6 +67,16 @@ ImageEnhancement.args = {
},
};

export const StyleTransfer = Template.bind({});
StyleTransfer.args = {
sampleInputs: SampleStyleTransferInputs,
model: {
output: {
type: styleTransfer,
},
},
};

export const Text = Template.bind({});
Text.args = {
sampleInputs: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function QuickInputTabContent(props) {
let className = 'tab';
if (props.tabIsSelected(index)) className += " tab--selected";

// Note: 'multiple' here just refers to URL tab allowing multiple uploads
return (
<div key={index} className={props.getElement(className)} role="tabpanel" aria-labelledby={`${tab.id}`}
id={`${tab.id}-panel`}>
Expand Down
78 changes: 78 additions & 0 deletions src/components/Experiment/QuickInput/QuickMultiInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from "react";
import "./QuickImageInput.scss";
import Task from "../../../helpers/Task";
import useQuickInputControl from "./useQuickInputControl";
import useBEMNaming from "../../../common/useBEMNaming";
import { QuickMultiInputTabContent } from "./QuickMultiInputTabContent";
import { QuickInputTabTitle } from "./QuickInputTabTitle";
import {QuickInputType} from "./quickInputType";

export default function QuickMultiInput(props) {
const {
tabIsSelected,
selectedInputs,
addInput,
getTabs,
removeInput,
selectTab,
selectInput,
runModel,
} = useQuickInputControl(props);
const { getBlock, getElement } = useBEMNaming("quick-image-input");

const task = Task.getStaticTask(props.model.output.type);
// Note: This feels pretty hacky and TaskInputType/QuickInputType should probably be refactored?
const tabs = getTabs(task.inputType.toLowerCase());

return (
<div className={getBlock()}>
{!props.hideHeader && (
<>
<h2 className={getElement("title")}>Try this model</h2>
<div className={getElement("subtitle")}>{task.description}</div>
</>
)}
<div className={getElement("tabs")}>
<div className={getElement("tab-titles")} role="tablist">
{tabs.map((tab, index) => (
<QuickInputTabTitle
key={index}
tab={tab}
index={index}
tabIsSelected={tabIsSelected}
selectTab={selectTab}
getElement={getElement}
/>
))}
</div>
{tabs.map((tab, tabIndex) => (
<div key={tabIndex}>
{task.inputs.map((input, inputIndex) => (
<QuickMultiInputTabContent
key={inputIndex}
tab={tab}
tabIndex={tabIndex}
getElement={getElement}
{...props}
removeInput={removeInput}
addInput={addInput}
selectInput={selectInput}
tabIsSelected={tabIsSelected}
selectedInputs={selectedInputs}
input={input}
inputIndex={inputIndex}
/>
))}
</div>
))}
</div>
<button
className={getElement("run-model")}
disabled={selectedInputs.length < task.inputs.length || selectedInputs[0] === ""}
onClick={() => runModel()}
>
Run model and see results
</button>
</div>
);
}
31 changes: 31 additions & 0 deletions src/components/Experiment/QuickInput/QuickMultiInputTabContent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from "react";

export function QuickMultiInputTabContent(props) {
let {tabIndex, tab} = props;
let Component = tab.component || (() => {
return <div/>
});
let className = 'tab';
if (props.tabIsSelected(tabIndex)) className += " tab--selected";

return (
<div
key={tabIndex}
className={props.getElement(className)}
role="tabpanel"
aria-labelledby={`${tab.id}`}
id={`${tab.id}-panel-${props.inputIndex}`}>
{/* Note: 'multiple' below refers to URL tab allowing multiple urls for a single input */}
<Component
multiple={props.multiple ?? false}
addInput={props.addInput}
removeInput={props.removeInput}
inputSelected={props.selectInput}
task={props.model.output.type}
values={props.selectedInputs} {...tab.props}
inputIndex={props.inputIndex}
input={props.input}
/>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import useBEMNaming from "../../../../../common/useBEMNaming";
import {QuickInputType} from "../../quickInputType";

export default function SampleInputsTab(props) {
// Note: This is the content for the Sample Input Tab, below the header

const {getBlock, getElement} = useBEMNaming("sample-inputs");
const {isUnselected, isSelected, selectInput, type} = useSampleInputControl(props);

Expand All @@ -30,6 +32,7 @@ export default function SampleInputsTab(props) {
}
}

// TODO: Rename "url" to "input" or similar
function makeSampleImageInput(url, index) {
return (
<button onClick={() => selectInput(index)} key={index} className={getElement(getInputClassName(url))}>
Expand Down Expand Up @@ -62,10 +65,13 @@ export default function SampleInputsTab(props) {
}

const task = Task.getStaticTask(props.task);
const sampleInputs = props.sampleInputs ?? [];
// Currently using both new and old way of handling inputs but should refactor in the future
const sampleInputs = task.useMultiInput ? props.sampleInputs[props.inputIndex] : (props.sampleInputs ?? []);
const inputText = task.inputText || props.input.inputText;

return (
<div className={getBlock()}>
<div className={getElement('title')}><b>{makeTaskTitle(props)}</b> to {task.inputText.toLowerCase()}</div>
<div className={getElement('title')}><b>{makeTaskTitle(props)}</b> to {inputText.toLowerCase()}</div>
<div className={getElement('list')}>
{sampleInputs.map(makeSampleInput)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,47 @@
import {useState} from "react";
import {QuickInputType} from "../../quickInputType";
import Task from "../../../../../helpers/Task";

export default function useSampleInputControl(props) {
const task = Task.getStaticTask(props.task);

const [selectedIndex, setSelectedIndex] = useState([]);

const isSelected = (input) => props.type === QuickInputType.Image ? selectedIndex.indexOf(input.src) > -1 : selectedIndex.indexOf(input) > -1;
const isUnselected = (input) => selectedIndex.length >= 0 && props.type === QuickInputType.Image ? selectedIndex.indexOf(input.src) === -1 : selectedIndex.indexOf(input) === -1;

const selectMultiInput = (selectedValueIndex) => {
// Note: Currently using both new and old way of handling inputs but should refactor in the future
let input = props.type === QuickInputType.Image ?
props.sampleInputs[props.inputIndex][selectedValueIndex].src :
props.sampleInputs[props.inputIndex][selectedValueIndex];

if (props.multiple) {
// TODO: This block was directly copied from selectInput and needs to be
// updated for useMultiInput

// const selected = Array.from(selectedIndex);
// let storedIndex = selected.indexOf(input);
// if (storedIndex === -1) {
// selected.push(input);
// } else {
// selected.splice(storedIndex, 1);
// }
// setSelectedIndex(selected);
// if (typeof (props.inputSelected) === 'function')
// props.inputSelected(selected);
} else {
setSelectedIndex([input]);
if (typeof(props.inputSelected) === 'function')
// Note: props.inputSelected is useQuickInputControl.selectMultiInput
props.inputSelected(input, props.inputIndex);
}
}

const selectInput = (index) => {
const input = props.type === QuickInputType.Image ?
props.sampleInputs[index].src :
props.sampleInputs[index];
const input = props.type === QuickInputType.Image ?
props.sampleInputs[index].src :
props.sampleInputs[index];

if (props.multiple) {
const selected = Array.from(selectedIndex);
Expand All @@ -32,5 +63,10 @@ export default function useSampleInputControl(props) {

const {type} = props;

return {selectedIndex, selectInput, isSelected, isUnselected, type};
return {
selectedIndex,
selectInput: !task.useMultiInput ? selectInput : selectMultiInput,
isSelected,
isUnselected,
type};
}
73 changes: 52 additions & 21 deletions src/components/Experiment/QuickInput/Tabs/URLInput/URLInputsTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,63 @@ export default function URLInputsTab(props) {
const {getBlock, getElement} = useBEMNaming("url-inputs");
const {urlChanged, getUrlValidity, task, values} = useURLInputControl(props);
const taskName = task.inputType.toLowerCase();
// Note: Currently using both new and old way of handling inputs but should refactor in the future
const inputText = task.inputText || props.input.inputText;
const getInputClassName = (index) => getElement(getUrlValidity(index) ? "url url-error" : "url")

return (
<div className={getBlock()}>
<div className={getElement('title')}>
<b>Copy an {taskName} URL ({taskName} address) and paste</b>
{" "}to {task.inputText.toLowerCase()}
</div>
{(values).map((value, index) => (
<div key={`input-tab-${index}`}>
<input className={getInputClassName(index)}
placeholder={`Paste any ${taskName} URL`}
type="url"
value={value}
onChange={(e) => urlChanged(e, index)}
/>
{getUrlValidity(index) &&
<p className={getElement("error-text")}>
Not a valid URL. Right click on an {taskName} to copy the {taskName}
address.
</p>}
</div>
{
task.useMultiInput ? (
<>
<div className={getElement('title')}>
<b>Copy an {taskName} URL ({taskName} address) and paste</b>
{" "}to {inputText.toLowerCase()}
</div>

<div key={`input-tab-${props.inputIndex}`}>
<input className={getInputClassName(props.inputIndex)}
placeholder={`Paste any ${taskName} URL`}
type="url"
value={values[props.inputIndex] || ''}
onChange={(e) => urlChanged(e, props.inputIndex)}
/>
{getUrlValidity(props.inputIndex) &&
<p className={getElement("error-text")}>
Not a valid URL. Right click on an {taskName} to copy the {taskName}&nbsp;
address.
</p>}
</div>
</>
) : (
<>
<div className={getElement('title')}>
<b>Copy an {taskName} URL ({taskName} address) and paste</b>
{" "}to {inputText.toLowerCase()}
</div>
{(values).map((value, index) => (
<div key={`input-tab-${index}`}>
<input className={getInputClassName(index)}
placeholder={`Paste any ${taskName} URL`}
type="url"
value={value}
onChange={(e) => urlChanged(e, index)}
/>
{getUrlValidity(index) &&
<p className={getElement("error-text")}>
Not a valid URL. Right click on an {taskName} to copy the {taskName}&nbsp;
address.
</p>}
</div>
)
)}
{props.multiple && <button onClick={props.addInput} className={getElement("add-btn")}><PlusSign
className={getElement("add-btn-icon")}/> Add another URL</button>}
</>
)
)}
{props.multiple && <button onClick={props.addInput} className={getElement("add-btn")}><PlusSign
className={getElement("add-btn-icon")}/> Add another URL</button>}
}


</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,20 @@ export default function useURLInputControl(props) {
setIsInvalidUrl(currentInvalidUrl);

if (typeof (props.inputSelected) === 'function') {
// console.log('about to set inputSelected..')
// console.log('isInvalidUrl', isInvalidUrl)
// if (isInvalidUrl[index]) {
// console.log('this url is invalid....')
// console.log(isInvalidUrl[index])
// } else {
// console.log('this url is valid....')
// console.log(isInvalidUrl[index])
// props.inputSelected(url, index);
// }


// Todo: How to not add this to inputSelected if invalid url?
props.inputSelected(url, index);

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ export default function UploadInputsTab(props) {

const task = Task.getStaticTask(props.task);
const taskName = task.inputType.toLowerCase();
// Currently using both new and old way of handling inputs but should refactor in the future
const inputText = task.inputText || props.input.inputText;

return (
<div className={getBlock()}>
<p className={getElement("help-text")}><b>Upload an {taskName} file</b> to {task.inputText.toLowerCase()} </p>
<p className={getElement("help-text")}><b>Upload an {taskName} file</b> to {inputText.toLowerCase()} </p>
<Dashboard uppy={uppy} width={"100%"}/>
</div>
);
Expand Down
Loading

0 comments on commit fc3ffdf

Please sign in to comment.