v1.2.0 Side Bar & Menu rework (#189)

* Basic side tool bar skeleton + Theme toggle (#164)

* Side bar skeleton

* Fix grid layout

* nit

* Add theme toggle logic

* Change primevue color theme to blue to match beta menu UI

* Add litegraph canvas splitter overlay (#177)

* Add vue wrapper

* Splitter overlay

* Move teleport to side bar comp

* Toolbar placeholder

* Move settings button from top menu to side bar (#178)

* Reverse relationship between splitter overlay and sidebar component (#180)

* Reverse relationship between splitter overlay and sidebar component

* nit

* Remove border on splitter

* Fix canvas shift (#186)

* Move queue/history display to side bar (#185)

* Side bar placeholder

* Pinia store for queue items

* Flatten task item

* Fix schema

* computed

* Switch running / pending order

* Use class-transformer

* nit

* Show display status

* Add tag severity style

* Add execution time

* nit

* Rename to execution success

* Add time display

* Sort queue desc order

* nit

* Add remove item feature

* Load workflow

* Add confirmation popup

* Add empty table placeholder

* Remove beta menu UI's queue button/list

* Add tests on litegraph widget text truncate (#191)

* Add tests on litegraph widget text truncate

* Updated screenshots

* Revert port change

* Remove screenshots

* Update test expectations [skip ci]

* Add back menu.settingsGroup for compatibility (#192)

* Close side bar on menu location set as disabled (#194)

* Remove placeholder side bar tabs (#196)

---------

Co-authored-by: bymyself <abolkonsky.rem@gmail.com>
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Chenlei Hu
2024-07-22 10:15:41 -04:00
committed by GitHub
parent 1521cd47c8
commit 65740a30c5
27 changed files with 938 additions and 349 deletions

206
src/stores/queueStore.ts Normal file
View File

@@ -0,0 +1,206 @@
import { api } from "@/scripts/api";
import { app } from "@/scripts/app";
import {
validateTaskItem,
TaskItem,
TaskType,
TaskPrompt,
TaskStatus,
TaskOutput,
} from "@/types/apiTypes";
import { plainToClass } from "class-transformer";
import _ from "lodash";
import { defineStore } from "pinia";
import { toRaw } from "vue";
// Task type used in the API.
export type APITaskType = "queue" | "history";
export enum TaskItemDisplayStatus {
Running = "Running",
Pending = "Pending",
Completed = "Completed",
Failed = "Failed",
Cancelled = "Cancelled",
}
export class TaskItemImpl {
taskType: TaskType;
prompt: TaskPrompt;
status?: TaskStatus;
outputs?: TaskOutput;
get apiTaskType(): APITaskType {
switch (this.taskType) {
case "Running":
case "Pending":
return "queue";
case "History":
return "history";
}
}
get queueIndex() {
return this.prompt[0];
}
get promptId() {
return this.prompt[1];
}
get promptInputs() {
return this.prompt[2];
}
get extraData() {
return this.prompt[3];
}
get outputsToExecute() {
return this.prompt[4];
}
get extraPngInfo() {
return this.extraData.extra_pnginfo;
}
get clientId() {
return this.extraData.client_id;
}
get workflow() {
return this.extraPngInfo.workflow;
}
get messages() {
return this.status?.messages || [];
}
get interrupted() {
return _.some(
this.messages,
(message) => message[0] === "execution_interrupted"
);
}
get isHistory() {
return this.taskType === "History";
}
get isRunning() {
return this.taskType === "Running";
}
get displayStatus(): TaskItemDisplayStatus {
switch (this.taskType) {
case "Running":
return TaskItemDisplayStatus.Running;
case "Pending":
return TaskItemDisplayStatus.Pending;
case "History":
switch (this.status!.status_str) {
case "success":
return TaskItemDisplayStatus.Completed;
case "error":
return this.interrupted
? TaskItemDisplayStatus.Cancelled
: TaskItemDisplayStatus.Failed;
}
}
}
get executionStartTimestamp() {
const message = this.messages.find(
(message) => message[0] === "execution_start"
);
return message ? message[1].timestamp : undefined;
}
get executionEndTimestamp() {
const messages = this.messages.filter((message) =>
[
"execution_success",
"execution_interrupted",
"execution_error",
].includes(message[0])
);
if (!messages.length) {
return undefined;
}
return _.max(messages.map((message) => message[1].timestamp));
}
get executionTime() {
if (!this.executionStartTimestamp || !this.executionEndTimestamp) {
return undefined;
}
return this.executionEndTimestamp - this.executionStartTimestamp;
}
get executionTimeInSeconds() {
return this.executionTime !== undefined
? this.executionTime / 1000
: undefined;
}
public async loadWorkflow() {
await app.loadGraphData(toRaw(this.workflow));
if (this.outputs) {
app.nodeOutputs = toRaw(this.outputs);
}
}
}
interface State {
runningTasks: TaskItemImpl[];
pendingTasks: TaskItemImpl[];
historyTasks: TaskItemImpl[];
}
export const useQueueStore = defineStore("queue", {
state: (): State => ({
runningTasks: [],
pendingTasks: [],
historyTasks: [],
}),
getters: {
tasks(state) {
return [
...state.pendingTasks,
...state.runningTasks,
...state.historyTasks,
];
},
},
actions: {
// Fetch the queue data from the API
async update() {
const [queue, history] = await Promise.all([
api.getQueue(),
api.getHistory(),
]);
const toClassAll = (tasks: TaskItem[]): TaskItemImpl[] =>
tasks
.map((task) => validateTaskItem(task))
.filter((result) => result.success)
.map((result) => plainToClass(TaskItemImpl, result.data))
// Desc order to show the latest tasks first
.sort((a, b) => b.queueIndex - a.queueIndex);
this.runningTasks = toClassAll(queue.Running);
this.pendingTasks = toClassAll(queue.Pending);
this.historyTasks = toClassAll(history.History);
},
async clear() {
await Promise.all(
["queue", "history"].map((type) => api.clearItems(type))
);
await this.update();
},
async delete(task: TaskItemImpl) {
await api.deleteItem(task.apiTaskType, task.promptId);
await this.update();
},
},
});