decent queue display

This commit is contained in:
caranicas 2022-09-29 12:54:13 -04:00
parent fd2961ecb9
commit 129839ce21
21 changed files with 426 additions and 173 deletions

View File

@ -1,41 +0,0 @@
import React from "react";
import { doStopImage } from "../../../api";
import { useImageQueue } from "../../../stores/imageQueueStore";
import { BrandedButton } from "../../../styles/shared.css";
import { useCreateUI } from "../../../components/organisms/creationPanel/creationPanelUIStore";
export default function ClearQueue() {
const hasQueue = useImageQueue((state) => state.hasQueuedImages());
const clearQueue = useImageQueue((state) => state.clearQueue);
const showQueue = useCreateUI((state) => state.showQueue);
const toggleQueue = useCreateUI((state) => state.toggleQueue);
const stopAll = async () => {
console.log("stopAll");
try {
clearQueue();
const res = await doStopImage();
} catch (e) {
console.log(e);
}
};
return (
<>
<button className={BrandedButton} disabled={!hasQueue} onClick={() => void stopAll()}>Clear Queue</button>
<label>
<input
type="checkbox"
checked={showQueue}
onChange={() => toggleQueue()}
>
</input>
Display
</label>
</>
);
}

View File

@ -0,0 +1,25 @@
import React from "react";
import { doStopImage } from "../../../api";
import { useRequestQueue } from "../../../stores/requestQueueStore";
import { BrandedButton } from "../../../styles/shared.css";
export default function ClearQueue() {
const hasQueue = useRequestQueue((state) => state.hasPendingQueue());
const clearQueue = useRequestQueue((state) => state.clearQueue);
const stopAll = async () => {
try {
clearQueue();
const res = await doStopImage();
} catch (e) {
console.log(e);
}
};
return (
<button className={BrandedButton} disabled={!hasQueue} onClick={() => void stopAll()}>
Stop ALL
</button>
);
}

View File

@ -2,7 +2,11 @@
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { useImageCreate } from "../../../stores/imageCreateStore"; import { useImageCreate } from "../../../stores/imageCreateStore";
import { useImageQueue } from "../../../stores/imageQueueStore"; import {
QueueStatus,
useRequestQueue
} from "../../../stores/requestQueueStore";
import { import {
FetchingStates, FetchingStates,
useImageFetching useImageFetching
@ -42,10 +46,10 @@ export default function MakeButton() {
const isSoundEnabled = useImageCreate((state) => state.isSoundEnabled()); const isSoundEnabled = useImageCreate((state) => state.isSoundEnabled());
const addNewImage = useImageQueue((state) => state.addNewImage); const addtoQueue = useRequestQueue((state) => state.addtoQueue);
const hasQueue = useImageQueue((state) => state.hasQueuedImages()); const hasQueue = useRequestQueue((state) => state.hasPendingQueue());
const removeFirstInQueue = useImageQueue((state) => state.removeFirstInQueue); const { id, options } = useRequestQueue((state) => state.firstInQueue());
const { id, options } = useImageQueue((state) => state.firstInQueue()); const updateQueueStatus = useRequestQueue((state) => state.updateStatus);
const status = useImageFetching((state) => state.status); const status = useImageFetching((state) => state.status);
const setStatus = useImageFetching((state) => state.setStatus); const setStatus = useImageFetching((state) => state.setStatus);
@ -64,7 +68,11 @@ export default function MakeButton() {
try { try {
const parsed = JSON.parse(jsonStr); const parsed = JSON.parse(jsonStr);
const { status, request, output: outputs } = parsed as ImageReturnType; const { status, request, output: outputs } = parsed as ImageReturnType;
if (status === 'succeeded') { if (status === 'succeeded') {
updateQueueStatus(id, QueueStatus.complete);
outputs.forEach((output: any, index: number) => { outputs.forEach((output: any, index: number) => {
const { data, seed } = output as ImageOutput; const { data, seed } = output as ImageOutput;
@ -79,10 +87,12 @@ export default function MakeButton() {
else { else {
console.warn(`Unexpected status: ${status}`); console.warn(`Unexpected status: ${status}`);
updateQueueStatus(id, QueueStatus.error);
} }
} }
catch (e) { catch (e) {
updateQueueStatus(id, QueueStatus.error);
console.log("Error HACKING JSON: ", e) console.log("Error HACKING JSON: ", e)
} }
} }
@ -143,7 +153,7 @@ export default function MakeButton() {
} }
} }
catch (e) { catch (e) {
console.log('EXPECTED PARSE ERRROR') // console.log('EXPECTED PARSE ERRROR')
finalJSON += jsonStr; finalJSON += jsonStr;
} }
@ -152,8 +162,8 @@ export default function MakeButton() {
const startStream = async (id: string, req: ImageRequest) => { const startStream = async (id: string, req: ImageRequest) => {
try { try {
updateQueueStatus(id, QueueStatus.processing);
resetForFetching(); resetForFetching();
const res = await doMakeImage(req); const res = await doMakeImage(req);
const reader = res.body?.getReader(); const reader = res.body?.getReader();
@ -164,6 +174,7 @@ export default function MakeButton() {
} catch (e) { } catch (e) {
console.log('TOP LINE STREAM ERROR') console.log('TOP LINE STREAM ERROR')
updateQueueStatus(id, QueueStatus.error);
console.log(e); console.log(e);
} }
@ -201,7 +212,7 @@ export default function MakeButton() {
seed = useRandomSeed(); seed = useRandomSeed();
} }
// add the request to the queue // add the request to the queue
addNewImage(uuidv4(), { addtoQueue(uuidv4(), {
...req, ...req,
// updated the number of images to make // updated the number of images to make
num_outputs: num, num_outputs: num,
@ -224,7 +235,7 @@ export default function MakeButton() {
useEffect(() => { useEffect(() => {
const makeImages = async (options: ImageRequest) => { const makeImages = async (options: ImageRequest) => {
removeFirstInQueue(); // removeFirstInQueue();
await startStream(id ?? "", options); await startStream(id ?? "", options);
} }
@ -238,6 +249,7 @@ export default function MakeButton() {
console.log('req is undefined'); console.log('req is undefined');
return; return;
} }
makeImages(options).catch((e) => { makeImages(options).catch((e) => {
console.log('HAS QUEUE ERROR'); console.log('HAS QUEUE ERROR');
console.log(e); console.log(e);

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { useImageCreate } from "../../../../../stores/imageCreateStore"; import { useImageCreate } from "../../../../../stores/imageCreateStore";
import ModifierTag from "../../../../atoms/modifierTag"; import ModifierTag from "../../../../molecules/modifierTag";
export default function ActiveTags() { export default function ActiveTags() {
const selectedtags = useImageCreate((state) => state.selectedTags()); const selectedtags = useImageCreate((state) => state.selectedTags());

View File

@ -1,8 +1,10 @@
import React from "react"; import React from "react";
import MakeButton from "../../../../atoms/makeButton"; import MakeButton from "../../../../molecules/makeButton";
import StopButton from "../../../../atoms/stopButton"; // import StopButton from "../../../../molecules/stopButton";
import ClearQueue from "../../../../atoms/clearQueue"; // import ClearQueue from "../../../../molecules/clearQueue";
import ShowQueue from "../showQueue";
import { import {
StopContainer StopContainer
@ -12,10 +14,11 @@ export default function CreationActions() {
return ( return (
<div> <div>
<MakeButton></MakeButton> <MakeButton></MakeButton>
<div className={StopContainer}> <ShowQueue></ShowQueue>
{/* <div className={StopContainer}>
<StopButton></StopButton> <StopButton></StopButton>
<ClearQueue></ClearQueue> <ClearQueue></ClearQueue>
</div> </div> */}
</div> </div>
); );
} }

View File

@ -37,12 +37,6 @@ export default function BasicCreation() {
<CreationActions></CreationActions> <CreationActions></CreationActions>
{/* <MakeButton></MakeButton>
<div>
<StopButton></StopButton>
<ClearQueue></ClearQueue>
</div> */}
<SeedImage></SeedImage> <SeedImage></SeedImage>
<ActiveTags></ActiveTags> <ActiveTags></ActiveTags>
</div> </div>

View File

@ -0,0 +1,21 @@
import React from "react";
import { useCreateUI } from "../../creationPanelUIStore";
export default function ShowQueue() {
const showQueue = useCreateUI((state) => state.showQueue);
const toggleQueue = useCreateUI((state) => state.toggleQueue);
return (
<label>
<input
type="checkbox"
checked={showQueue}
onChange={() => toggleQueue()}
>
</input>
Display
</label>
);
}

View File

@ -1,11 +1,12 @@
import { style } from "@vanilla-extract/css"; import { style } from "@vanilla-extract/css";
import { PanelBox } from "../../../styles/shared.css";
export const CreationPaneMain = style({ export const CreationPaneMain = style({
position: "relative", position: "relative",
width: "100%", width: "100%",
height: "100%", height: "100%",
padding: "0 10px", padding: "0 10px",
overflowY: "auto", overflowY: "auto",
overflowX: "hidden",
}); });
export const InpaintingSlider = style({ export const InpaintingSlider = style({
@ -16,10 +17,11 @@ export const InpaintingSlider = style({
backgroundColor: "rgba(0, 0, 0, 0.5)", backgroundColor: "rgba(0, 0, 0, 0.5)",
}); });
export const QueueSlider = style({ export const QueueSlider = style([PanelBox, {
position: "absolute", position: "absolute",
top: "10px", top: "10px",
left: "400px", left: "400px",
zIndex: 1, zIndex: 1,
backgroundColor: "rgba(0, 0, 0, 0.5)", maxHeight: "90%",
}); overflowY: "auto",
}]);

View File

@ -15,7 +15,7 @@ import {
import { ModifierObject, useImageCreate } from "../../../../stores/imageCreateStore"; import { ModifierObject, useImageCreate } from "../../../../stores/imageCreateStore";
import { useCreateUI } from "../creationPanelUIStore"; import { useCreateUI } from "../creationPanelUIStore";
import ModifierTag from "../../../atoms/modifierTag"; import ModifierTag from "../../../molecules/modifierTag";
interface ModifierListProps { interface ModifierListProps {
category: string; category: string;

View File

@ -11,7 +11,7 @@ import QueueDisplay from "../queueDisplay";
// import { useImageCreate } from "@stores/imageCreateStore.ts"; // import { useImageCreate } from "@stores/imageCreateStore.ts";
import { useImageCreate } from "../../../stores/imageCreateStore"; import { useImageCreate } from "../../../stores/imageCreateStore";
import { useImageQueue } from "../../../stores/imageQueueStore"; import { useRequestQueue } from "../../../stores/requestQueueStore";
import { useCreateUI } from "./creationPanelUIStore"; import { useCreateUI } from "./creationPanelUIStore";
@ -24,12 +24,11 @@ import {
} from "./creationPanel.css"; } from "./creationPanel.css";
export default function CreationPanel() { export default function CreationPanel() {
const isInPaintingMode = useImageCreate((state) => state.isInpainting); const isInPaintingMode = useImageCreate((state) => state.isInpainting);
const showQueue = useCreateUI((state) => state.showQueue); const showQueue = useCreateUI((state) => state.showQueue);
const hasQueue = useImageQueue((state) => state.hasQueuedImages()); const hasQueue = useRequestQueue((state) => state.hasAnyQueue());
// console.log('hasQueue', hasQueue); // console.log('hasQueue', hasQueue);
console.log('showQueue', showQueue); console.log('showQueue', showQueue);
@ -48,7 +47,7 @@ export default function CreationPanel() {
</div> </div>
)} )}
{(showQueue) && ( {(showQueue && hasQueue) && (
<div className={QueueSlider}> <div className={QueueSlider}>
<QueueDisplay></QueueDisplay> <QueueDisplay></QueueDisplay>
</div> </div>

View File

@ -1,24 +1,45 @@
import React from "react"; import React from "react";
import { ImageRequest } from "../../../api";
import { useImageQueue } from "../../../stores/imageQueueStore"; import { QueuedRequest, useRequestQueue } from "../../../stores/requestQueueStore";
import { import {
QueueDisplayMain QueueDisplayMain,
QueueListButtons,
CompleteButtton,
ErrorButton
} from "./queueDisplay.css"; } from "./queueDisplay.css";
import ClearQueue from "../../molecules/clearQueue";
import QueueItem from "./queueItem"; import QueueItem from "./queueItem";
export default function QueueDisplay() { export default function QueueDisplay() {
const images = useImageQueue((state) => state.images); const requests: QueuedRequest[] = useRequestQueue((state) => state.requests);
console.log('images', images); const removeCompleted = useRequestQueue((state) => state.removeCompleted);
const removeErrored = useRequestQueue((state) => state.removeErrored);
const clearCompleted = () => {
console.log('clear completed');
removeCompleted();
}
const clearErrored = () => {
console.log('clear errored');
removeErrored();
}
return ( return (
<div className={QueueDisplayMain}> <div className={QueueDisplayMain}>
{images.map((image) => { <ClearQueue />
return <QueueItem key={image.id} info={image}></QueueItem>; <div className={QueueListButtons}>
<button className={CompleteButtton} onClick={clearCompleted}>Clear Completed</button>
<button className={ErrorButton} onClick={clearErrored}>Clear Errored</button>
</div>
{requests.map((request) => {
return <QueueItem key={request.id} request={request}></QueueItem>;
})} })}
</div> </div>
); );

View File

@ -1,10 +1,39 @@
import { style } from "@vanilla-extract/css"; import { style, globalStyle } from "@vanilla-extract/css";
import { BrandedButton } from "../../../styles/shared.css";
import { vars } from "../../../styles/theme/index.css";
export const QueueDisplayMain = style({ export const QueueDisplayMain = style({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
width: '400px', width: '400px',
height: '100%', height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
}); });
export const QueueListButtons = style({
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: vars.spacing.medium,
marginTop: vars.spacing.medium,
});
globalStyle(`${QueueListButtons} button`, {
flexGrow: 1,
});
globalStyle(`${QueueListButtons} button:first-child`, {
marginRight: vars.spacing.medium,
});
// TODO these should be a button recipe?
export const CompleteButtton = style([BrandedButton, {
}]);
export const ErrorButton = style([BrandedButton, {
}]);

View File

@ -1,20 +1,37 @@
import React from "react"; import React from "react";
import {
QueueItemMain
} from "./queueItem.css";
import { import {
ImageRequest ImageRequest
} from '../../../../api' } from '../../../../api'
import { QueueStatus, QueuedRequest, useRequestQueue } from '../../../../stores/requestQueueStore';
import StopButton from '../../../molecules/stopButton';
import {
QueueItemMain,
QueueButtons,
CompleteButtton,
PauseButton,
ResumeButton,
CancelButton,
RetryButton,
SendToTopButton,
} from "./queueItem.css";
interface QueueItemProps { interface QueueItemProps {
info: ImageRequest request: QueuedRequest;
} }
export default function QueueItem({ info }: QueueItemProps) { export default function QueueItem({ request }: QueueItemProps) {
console.log('info', info); // console.log('info', info);
// console.log('status', status);
const removeItem = useRequestQueue((state) => state.removeItem);
const updateStatus = useRequestQueue((state) => state.updateStatus);
const sendPendingToTop = useRequestQueue((state) => state.sendPendingToTop);
const { const {
id, id,
@ -23,16 +40,64 @@ export default function QueueItem({ info }: QueueItemProps) {
seed, seed,
sampler, sampler,
}, },
// status, status,
} = info; } = request;
const removeFromQueue = () => {
console.log('remove from queue');
removeItem(id);
}
const pauseItem = () => {
console.log('pause item');
updateStatus(id, QueueStatus.paused);
}
const retryRequest = () => {
console.log('retry request');
updateStatus(id, QueueStatus.pending);
}
const sendToTop = () => {
console.log('send to top');
sendPendingToTop(id);
}
return ( return (
<div className={QueueItemMain}> <div className={[QueueItemMain, status].join(' ')}>
<div>{id}</div> {/* @ts-expect-error */}
<div>{status}</div>
<div>{prompt}</div> <div>{prompt}</div>
<div>{seed}</div> <div>{seed}</div>
<div>{sampler}</div> <div>{sampler}</div>
<div className={QueueButtons}>
{status === QueueStatus.processing && (
<StopButton></StopButton>
)}
{status === QueueStatus.complete && (
<button className={CompleteButtton} onClick={removeFromQueue}>Clear</button>
)}
{status === QueueStatus.pending && (
<>
<button className={CancelButton} onClick={removeFromQueue}>Remove</button>
<button className={PauseButton} onClick={pauseItem}>Pause</button>
<button className={SendToTopButton} onClick={sendToTop}>Send to top</button>
</>
)}
{status === QueueStatus.paused && (
<button className={ResumeButton} onClick={retryRequest}>Resume</button>
)}
{status === QueueStatus.error && (
<button className={RetryButton} onClick={retryRequest}>Retry</button>
)}
</div>
</div> </div>
); );
} }

View File

@ -1,9 +1,68 @@
import { style } from "@vanilla-extract/css"; import { style, globalStyle } from "@vanilla-extract/css";
import { vars } from "../../../../styles/theme/index.css";
import { BrandedButton } from "../../../../styles/shared.css";
import { QueueStatus } from "../../../../stores/requestQueueStore";
export const QueueItemMain = style({ export const QueueItemMain = style({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
width: "100%", width: "100%",
padding: "0.5rem", padding: "0.5rem",
}); });
globalStyle(`${QueueItemMain}.${QueueStatus.processing}`, {
backgroundColor: vars.colors.warning,
});
globalStyle(`${QueueItemMain}.${QueueStatus.pending}`, {
backgroundColor: vars.colors.backgroundDark,
});
globalStyle(`${QueueItemMain}.${QueueStatus.paused}`, {
backgroundColor: vars.colors.backgroundAlt,
});
globalStyle(`${QueueItemMain}.${QueueStatus.complete}`, {
backgroundColor: vars.colors.success,
});
globalStyle(`${QueueItemMain}.${QueueStatus.error}`, {
backgroundColor: vars.colors.error,
});
export const QueueButtons = style({
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
});
// TODO these should be a button recipe?
export const CompleteButtton = style([BrandedButton, {
}]);
export const PauseButton = style([BrandedButton, {
}]);
export const ResumeButton = style([BrandedButton, {
}]);
export const CancelButton = style([BrandedButton, {
}]);
export const RetryButton = style([BrandedButton, {
}]);
export const SendToTopButton = style([BrandedButton, {
}]);

View File

@ -27,7 +27,6 @@ interface ImageFetchingState {
setStartTime: () => void; setStartTime: () => void;
setNowTime: () => void; setNowTime: () => void;
resetForFetching: () => void; resetForFetching: () => void;
} }
export const useImageFetching = create<ImageFetchingState>((set) => ({ export const useImageFetching = create<ImageFetchingState>((set) => ({

View File

@ -1,78 +0,0 @@
import create from "zustand";
import produce from "immer";
import { useRandomSeed } from "../utils";
import { ImageRequest } from "../api";
interface QueueItem {
id?: string;
options?: ImageRequest;
status?: "pending" | "complete" | "error";
}
interface ImageQueueState {
images: QueueItem[];
completedImageIds: string[];
addNewImage: (id: string, imgRec: ImageRequest) => void;
hasQueuedImages: () => boolean;
firstInQueue: () => QueueItem;
removeFirstInQueue: () => void;
clearQueue: () => void;
clearCachedIds: () => void;
}
export const useImageQueue = create<ImageQueueState>((set, get) => ({
images: [],
completedImageIds: [],
// use produce to make sure we don't mutate state
addNewImage: (id: string, imgRec: ImageRequest) => {
set(
produce((state) => {
const item: QueueItem = { id, options: imgRec, status: "pending" };
state.images.push(item);
})
);
},
hasQueuedImages: () => {
return get().images.length > 0;
},
firstInQueue: () => {
const { images } = get();
if (images.length > 0) {
return images[0];
}
// // cast an empty object to QueueItem
const empty: QueueItem = {};
return empty;
},
removeFirstInQueue: () => {
set(
produce((state) => {
const image = state.images.shift();
if (void 0 !== image) {
state.completedImageIds.push(image.id);
}
})
);
},
clearQueue: () => {
set(
produce((state) => {
state.images = [];
})
);
},
clearCachedIds: () => {
set(
produce((state) => {
state.completedImageIds = [];
})
);
},
}));

View File

@ -0,0 +1,143 @@
import create from "zustand";
import produce from "immer";
import { ImageRequest } from "../api";
export enum QueueStatus {
pending = "pending",
processing = "processing",
complete = "complete",
paused = "paused",
error = "error",
}
export interface QueuedRequest {
id: string;
options: ImageRequest;
status: QueueStatus[keyof QueueStatus];
//"pending" | "processing" | "complete" | "error";
}
interface RequestQueueState {
requests: QueuedRequest[];
addtoQueue: (id: string, imgRec: ImageRequest) => void;
pendingRequests: () => QueuedRequest[];
hasPendingQueue: () => boolean;
hasAnyQueue: () => boolean;
firstInQueue: () => QueuedRequest;
updateStatus: (id: string, status: QueueStatus[keyof QueueStatus]) => void;
sendPendingToTop: (id: string) => void;
removeItem: (id: string) => void;
removeCompleted: () => void;
removeErrored: () => void;
clearQueue: () => void;
}
export const useRequestQueue = create<RequestQueueState>((set, get) => ({
requests: [],
// use produce to make sure we don't mutate state
addtoQueue: (id: string, imgRec: ImageRequest) => {
set(
produce((state) => {
const item: QueuedRequest = { id, options: imgRec, status: QueueStatus.pending };
state.requests.push(item);
})
);
},
pendingRequests: () => {
return get().requests.filter((item) => item.status === QueueStatus.pending);
},
hasPendingQueue: () => {
return get().pendingRequests().length > 0;
},
hasAnyQueue: () => {
return get().requests.length > 0;
},
firstInQueue: () => {
const pending = get().pendingRequests()[0];
if (pending === undefined) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const temp: QueuedRequest = { id: "", options: ({} as ImageRequest), status: QueueStatus.pending };
return temp;
}
return pending;
},
updateStatus: (id: string, status: QueueStatus[keyof QueueStatus]) => {
set(
produce((state) => {
const item = state.requests.find((item: QueuedRequest) => item.id === id);
if (void 0 !== item) {
item.status = status;
}
})
);
},
sendPendingToTop: (id: string) => {
set(
produce((state) => {
const item = state.requests.find((item: QueuedRequest) => item.id === id);
if (void 0 !== item) {
const index = state.requests.indexOf(item);
// insert infront of the pending requests
const pending = get().pendingRequests();
const pendingIndex = state.requests.indexOf(pending[0]);
console.log()
state.requests.splice(index, 1);
state.requests.splice(pendingIndex, 0, item);
}
})
);
},
removeItem: (id: string) => {
set(
produce((state) => {
const index = state.requests.findIndex((item: QueuedRequest) => item.id === id);
if (index > -1) {
state.requests.splice(index, 1);
}
})
);
},
removeCompleted: () => {
set(
produce((state) => {
const completed = state.requests.filter((item: QueuedRequest) => item.status === QueueStatus.complete);
completed.forEach((item: QueuedRequest) => {
const index = state.requests.indexOf(item);
state.requests.splice(index, 1);
});
})
);
},
removeErrored: () => {
set(
produce((state) => {
const errored = state.requests.filter((item: QueuedRequest) => item.status === QueueStatus.error);
errored.forEach((item: QueuedRequest) => {
const index = state.requests.indexOf(item);
state.requests.splice(index, 1);
});
})
);
},
clearQueue: () => {
set(
produce((state) => {
state.requests = [];
})
);
},
}));