@@ -156,6 +156,13 @@ export class WorkflowsTable extends TableBase<Workflow> {
|
||||
throw new Error("Method not allowed.");
|
||||
}
|
||||
|
||||
public async updateTopFields(
|
||||
id: string,
|
||||
change: Pick<Partial<Workflow>, "topFieldsConfig">,
|
||||
): Promise<Workflow | null> {
|
||||
return this._update(id, change);
|
||||
}
|
||||
|
||||
public async updateMetaInfo(
|
||||
id: string,
|
||||
change: Omit<Partial<Workflow>, "id" | "name" | "parentFolderID" | "json">,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @ts-expect-error ComfyUI import
|
||||
import { app } from "/scripts/app.js";
|
||||
import {
|
||||
Checkbox,
|
||||
Flex,
|
||||
@@ -12,7 +14,7 @@ import {
|
||||
ModalOverlay,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { mediaTable, workflowsTable } from "../db-tables/WorkspaceDB";
|
||||
import { IconArrowLeft, IconX } from "@tabler/icons-react";
|
||||
import { WorkspaceContext } from "../WorkspaceContext";
|
||||
@@ -20,6 +22,8 @@ import { Media } from "../types/dbTypes";
|
||||
import { MetaDataInfo } from "./components/MetaDataInfo.tsx";
|
||||
import GalleryMediaItem from "./components/GalleryMediaItem.tsx";
|
||||
import SearchInput from "../components/SearchInput.tsx";
|
||||
import { nanoid } from "nanoid";
|
||||
import { MediaWithMetaData } from "./components/MetaInfoBox.tsx";
|
||||
|
||||
export default function GalleryModal({ onclose }: { onclose: () => void }) {
|
||||
const { curFlowID } = useContext(WorkspaceContext);
|
||||
@@ -28,7 +32,7 @@ export default function GalleryModal({ onclose }: { onclose: () => void }) {
|
||||
const [isSelecting, setIsSelecting] = useState(false);
|
||||
const [coverPath, setCoverPath] = useState("");
|
||||
const [images, setImages] = useState<Media[]>([]);
|
||||
const [metaData, setMetaData] = useState<Media>();
|
||||
const [metaData, setMetaData] = useState<MediaWithMetaData>();
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const onUpdateSearchValue = (val: string) => {
|
||||
setSearchValue(val);
|
||||
@@ -38,6 +42,25 @@ export default function GalleryModal({ onclose }: { onclose: () => void }) {
|
||||
if (curFlowID == null) return;
|
||||
const media = await mediaTable?.listByWorkflowID(curFlowID);
|
||||
setImages(media ?? []);
|
||||
if (media?.length === 0) {
|
||||
app.graphToPrompt().then((prompt) => {
|
||||
setMetaData({
|
||||
id: nanoid(),
|
||||
workflowJSON: "",
|
||||
localPath: "",
|
||||
createTime: 0,
|
||||
format: "",
|
||||
workflowID: "",
|
||||
metaData: {
|
||||
prompt: prompt.output,
|
||||
workflow: prompt.workflow,
|
||||
},
|
||||
});
|
||||
return app.graph._nodes;
|
||||
});
|
||||
} else if (Number(media?.length) <= 6 && media?.[0]) {
|
||||
setMetaData(media[0]);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -8,83 +8,149 @@ import {
|
||||
Flex,
|
||||
} from "@chakra-ui/react";
|
||||
import { FormItemComponent } from "../FormItem/FormItemComponent.tsx";
|
||||
import { MetaData } from "../../utils.ts";
|
||||
import { FormItem } from "../FormItem/types.ts";
|
||||
import { isInTopField, TopFieldType } from "../MetaBox/MetaBox.tsx";
|
||||
import { useEffect, useState } from "react";
|
||||
import { isInTopField } from "../MetaBox/MetaBox.tsx";
|
||||
import { Fragment, useContext, useEffect, useState } from "react";
|
||||
import { InputResultItem } from "../MetaBox/utils.ts";
|
||||
import { MetaBoxContext } from "../MetaBox/metaBoxContext.ts";
|
||||
|
||||
export default function AllPromptForm() {
|
||||
const {
|
||||
topFields,
|
||||
updateTopField,
|
||||
calcInputList,
|
||||
updateMetaData,
|
||||
metaData,
|
||||
showNodeName,
|
||||
} = useContext(MetaBoxContext);
|
||||
|
||||
export default function AllPromptForm({
|
||||
metaData,
|
||||
updateMetaData,
|
||||
topFields,
|
||||
updateTopField,
|
||||
}: {
|
||||
metaData: MetaData;
|
||||
topFields: TopFieldType[];
|
||||
updateMetaData: FormItem["updateMetaData"];
|
||||
updateTopField?: (field: TopFieldType) => void;
|
||||
}) {
|
||||
const prompt = metaData.prompt;
|
||||
const [defaultIndex, setDefaultIndex] = useState<number[]>([]);
|
||||
|
||||
const groupInputList =
|
||||
calcInputList?.reduce<
|
||||
{
|
||||
list: InputResultItem[];
|
||||
class_type: string;
|
||||
}[]
|
||||
>((previousValue, currentValue) => {
|
||||
if (
|
||||
isInTopField(topFields, {
|
||||
name: currentValue.name,
|
||||
promptKey: currentValue.linkId,
|
||||
classType: currentValue.class_type,
|
||||
})
|
||||
) {
|
||||
return previousValue;
|
||||
}
|
||||
const findItem = previousValue.find(
|
||||
(v) => v.class_type === currentValue.class_type,
|
||||
);
|
||||
if (findItem) {
|
||||
return previousValue.map((item) => {
|
||||
if (item.class_type === currentValue.class_type) {
|
||||
return {
|
||||
...item,
|
||||
list: [...(item.list ?? []), currentValue],
|
||||
};
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return [
|
||||
...previousValue,
|
||||
{
|
||||
class_type: currentValue.class_type,
|
||||
list: [currentValue],
|
||||
},
|
||||
];
|
||||
}
|
||||
}, []) ?? [];
|
||||
|
||||
useEffect(() => {
|
||||
setDefaultIndex(Object.keys(prompt).map((_, i) => i));
|
||||
}, [prompt]);
|
||||
if (showNodeName && groupInputList?.length) {
|
||||
setDefaultIndex(
|
||||
Array(groupInputList?.length)
|
||||
.fill("")
|
||||
.map((_, i) => i),
|
||||
);
|
||||
}
|
||||
}, [groupInputList?.length, showNodeName]);
|
||||
|
||||
if (!showNodeName) {
|
||||
return (
|
||||
<>
|
||||
{groupInputList.map((groupInput) => (
|
||||
<Fragment
|
||||
key={`group${groupInput.class_type}${groupInput?.list?.[0]?.linkId}`}
|
||||
>
|
||||
{groupInput.list.map((input) => {
|
||||
const value = metaData.prompt[input.linkId]?.inputs?.[input.name];
|
||||
return (
|
||||
<FormItemComponent
|
||||
key={`form${input.linkId}${input.name}`}
|
||||
promptKey={input.linkId}
|
||||
classType={input?.class_type}
|
||||
value={value}
|
||||
name={input.name}
|
||||
updateMetaData={updateMetaData}
|
||||
updateTopField={updateTopField}
|
||||
topFields={topFields}
|
||||
metaData={metaData}
|
||||
label={input.formLabel}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Accordion
|
||||
index={defaultIndex}
|
||||
onChange={(val) => setDefaultIndex(val as number[])}
|
||||
allowMultiple
|
||||
>
|
||||
{Object.keys(prompt).map((promptKey) => {
|
||||
const promptElement = prompt[promptKey];
|
||||
const promptInputs = promptElement.inputs;
|
||||
const inputsKeyList = Object.keys(promptInputs).filter(
|
||||
(v) =>
|
||||
!Array.isArray(promptInputs[v]) &&
|
||||
!isInTopField(topFields, {
|
||||
name: v,
|
||||
promptKey: promptKey,
|
||||
classType: promptElement.class_type,
|
||||
}),
|
||||
);
|
||||
if (inputsKeyList.length === 0) return null;
|
||||
{groupInputList.map((groupInput) => {
|
||||
return (
|
||||
<AccordionItem
|
||||
key={`AccordionItem${promptKey}`}
|
||||
key={`AccordionItem${groupInput.class_type}${groupInput?.list?.[0]?.linkId}`}
|
||||
borderWidth={1}
|
||||
borderRadius={8}
|
||||
my={2}
|
||||
>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
{promptElement.class_type}
|
||||
{groupInput.class_type}
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel>
|
||||
<Flex px={2} gap={1} direction={"column"}>
|
||||
{inputsKeyList?.map((inputsKey) => {
|
||||
const value = promptInputs[inputsKey];
|
||||
{groupInput.list?.map((input) => {
|
||||
if (
|
||||
isInTopField(topFields, {
|
||||
name: inputsKey,
|
||||
promptKey,
|
||||
classType: promptElement.class_type,
|
||||
name: input.name,
|
||||
promptKey: input.linkId,
|
||||
classType: input.class_type,
|
||||
})
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const value =
|
||||
metaData.prompt[input.linkId]?.inputs?.[input.name];
|
||||
return (
|
||||
<FormItemComponent
|
||||
key={`form${inputsKey}`}
|
||||
promptKey={promptKey}
|
||||
classType={promptElement?.class_type}
|
||||
key={`form${input.linkId}${input.name}`}
|
||||
promptKey={input.linkId}
|
||||
classType={input?.class_type}
|
||||
value={value}
|
||||
name={inputsKey}
|
||||
name={input.name}
|
||||
updateMetaData={updateMetaData}
|
||||
updateTopField={updateTopField}
|
||||
topFields={topFields}
|
||||
metaData={metaData}
|
||||
label={input.formLabel}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -95,4 +161,71 @@ export default function AllPromptForm({
|
||||
})}
|
||||
</Accordion>
|
||||
);
|
||||
|
||||
// return (
|
||||
// <Accordion
|
||||
// index={defaultIndex}
|
||||
// onChange={(val) => setDefaultIndex(val as number[])}
|
||||
// allowMultiple
|
||||
// >
|
||||
// {Object.keys(prompt).map((promptKey) => {
|
||||
// const promptElement = prompt[promptKey];
|
||||
// const promptInputs = promptElement.inputs;
|
||||
// const inputsKeyList = Object.keys(promptInputs).filter(
|
||||
// (v) =>
|
||||
// !Array.isArray(promptInputs[v]) &&
|
||||
// !isInTopField(topFields, {
|
||||
// name: v,
|
||||
// promptKey: promptKey,
|
||||
// classType: promptElement.class_type,
|
||||
// }),
|
||||
// );
|
||||
// if (inputsKeyList.length === 0) return null;
|
||||
// return (
|
||||
// <AccordionItem
|
||||
// key={`AccordionItem${promptKey}`}
|
||||
// borderWidth={1}
|
||||
// borderRadius={8}
|
||||
// my={2}
|
||||
// >
|
||||
// <AccordionButton>
|
||||
// <Box as="span" flex="1" textAlign="left">
|
||||
// {promptElement.class_type}
|
||||
// </Box>
|
||||
// <AccordionIcon />
|
||||
// </AccordionButton>
|
||||
// <AccordionPanel>
|
||||
// <Flex px={2} gap={1} direction={"column"}>
|
||||
// {inputsKeyList?.map((inputsKey) => {
|
||||
// const value = promptInputs[inputsKey];
|
||||
// if (
|
||||
// isInTopField(topFields, {
|
||||
// name: inputsKey,
|
||||
// promptKey,
|
||||
// classType: promptElement.class_type,
|
||||
// })
|
||||
// ) {
|
||||
// return null;
|
||||
// }
|
||||
// return (
|
||||
// <FormItemComponent
|
||||
// key={`form${inputsKey}`}
|
||||
// promptKey={promptKey}
|
||||
// classType={promptElement?.class_type}
|
||||
// value={value}
|
||||
// name={inputsKey}
|
||||
// updateMetaData={updateMetaData}
|
||||
// updateTopField={updateTopField}
|
||||
// topFields={topFields}
|
||||
// metaData={metaData}
|
||||
// />
|
||||
// );
|
||||
// })}
|
||||
// </Flex>
|
||||
// </AccordionPanel>
|
||||
// </AccordionItem>
|
||||
// );
|
||||
// })}
|
||||
// </Accordion>
|
||||
// );
|
||||
}
|
||||
|
||||
@@ -3,8 +3,12 @@ import { Flex } from "@chakra-ui/react";
|
||||
import { Media } from "../../../types/dbTypes.ts";
|
||||
import TopForm from "../TopForm/TopForm.tsx";
|
||||
import AllPromptForm from "../AllPromptForm/AllPromptForm.tsx";
|
||||
import { useState } from "react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { FormItem } from "../FormItem/types.ts";
|
||||
import { calcMeta, type InputResultItem } from "./utils.ts";
|
||||
import { MetaBoxContext } from "./metaBoxContext.ts";
|
||||
import { workflowsTable } from "../../../db-tables/WorkspaceDB.ts";
|
||||
import { WorkspaceContext } from "../../../WorkspaceContext.ts";
|
||||
|
||||
export type TopFieldType = {
|
||||
promptKey: string | number;
|
||||
@@ -12,49 +16,6 @@ export type TopFieldType = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
// comfyui default workflow
|
||||
export const DEFAULT_TOP_FIELDS: TopFieldType[] = [
|
||||
{
|
||||
promptKey: "4",
|
||||
name: "ckpt_name",
|
||||
class_type: "CheckpointLoaderSimple",
|
||||
},
|
||||
{
|
||||
promptKey: "6",
|
||||
name: "text",
|
||||
class_type: "CLIPTextEncode",
|
||||
},
|
||||
{
|
||||
promptKey: "7",
|
||||
name: "text",
|
||||
class_type: "CLIPTextEncode",
|
||||
},
|
||||
{
|
||||
promptKey: "5",
|
||||
name: "width",
|
||||
class_type: "EmptyLatentImage",
|
||||
},
|
||||
{
|
||||
promptKey: "5",
|
||||
name: "height",
|
||||
class_type: "EmptyLatentImage",
|
||||
},
|
||||
{
|
||||
promptKey: "3",
|
||||
name: "steps",
|
||||
class_type: "KSampler",
|
||||
},
|
||||
{
|
||||
promptKey: "3",
|
||||
name: "sampler_name",
|
||||
class_type: "KSampler",
|
||||
},
|
||||
{
|
||||
promptKey: "3",
|
||||
name: "cfg",
|
||||
class_type: "KSampler",
|
||||
},
|
||||
];
|
||||
export const isInTopField = (
|
||||
topFields: TopFieldType[],
|
||||
item: Pick<FormItem, "name" | "promptKey" | "classType">,
|
||||
@@ -69,12 +30,22 @@ export const isInTopField = (
|
||||
|
||||
export default function MetaBox({
|
||||
metaData: oriMetaData,
|
||||
showNodeName,
|
||||
}: {
|
||||
metaData: MetaData;
|
||||
media: Media;
|
||||
showNodeName: boolean;
|
||||
}) {
|
||||
const _metaData = JSON.parse(JSON.stringify(oriMetaData));
|
||||
const [topFields, setTopFields] = useState(DEFAULT_TOP_FIELDS);
|
||||
const [calcInputList, setCalcInputList] = useState<InputResultItem[]>([]);
|
||||
const { curFlowID } = useContext(WorkspaceContext);
|
||||
|
||||
useEffect(() => {
|
||||
const calcInput = calcMeta(_metaData);
|
||||
setCalcInputList(calcInput);
|
||||
}, []);
|
||||
|
||||
const [topFields, setTopFields] = useState<TopFieldType[]>([]);
|
||||
const [metaData, setMetaData] = useState<MetaData>(_metaData);
|
||||
const updateMetaData = ({
|
||||
promptKey,
|
||||
@@ -100,6 +71,17 @@ export default function MetaBox({
|
||||
}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (curFlowID) {
|
||||
const topFieldsConfig = (await workflowsTable?.get(curFlowID))
|
||||
?.topFieldsConfig;
|
||||
if (topFieldsConfig) {
|
||||
setTopFields(topFieldsConfig);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [curFlowID]);
|
||||
const updateTopField = (field: TopFieldType) => {
|
||||
if (
|
||||
isInTopField(topFields, {
|
||||
@@ -108,30 +90,41 @@ export default function MetaBox({
|
||||
classType: field?.class_type ?? "",
|
||||
})
|
||||
) {
|
||||
setTopFields((pre) =>
|
||||
pre.filter(
|
||||
(v) => v.name !== field.name || v.promptKey !== field.promptKey,
|
||||
),
|
||||
const topFieldsConfig = topFields.filter(
|
||||
(v) => v.name !== field.name || v.promptKey !== field.promptKey,
|
||||
);
|
||||
setTopFields(topFieldsConfig);
|
||||
if (curFlowID) {
|
||||
workflowsTable?.updateTopFields(curFlowID, {
|
||||
topFieldsConfig,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setTopFields((pre) => [...pre, field]);
|
||||
const topFieldsConfig = [...topFields, field];
|
||||
setTopFields(topFieldsConfig);
|
||||
if (curFlowID) {
|
||||
workflowsTable?.updateTopFields(curFlowID, {
|
||||
topFieldsConfig,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction={"column"} align={"stretch"}>
|
||||
<TopForm
|
||||
topFields={topFields}
|
||||
metaData={metaData}
|
||||
updateMetaData={updateMetaData}
|
||||
updateTopField={updateTopField}
|
||||
/>
|
||||
<AllPromptForm
|
||||
topFields={topFields}
|
||||
metaData={metaData}
|
||||
updateMetaData={updateMetaData}
|
||||
updateTopField={updateTopField}
|
||||
/>
|
||||
</Flex>
|
||||
<MetaBoxContext.Provider
|
||||
value={{
|
||||
topFields,
|
||||
metaData,
|
||||
updateMetaData,
|
||||
showNodeName,
|
||||
calcInputList,
|
||||
updateTopField,
|
||||
}}
|
||||
>
|
||||
<Flex direction={"column"} align={"stretch"}>
|
||||
<TopForm />
|
||||
<AllPromptForm />
|
||||
</Flex>
|
||||
</MetaBoxContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createContext } from "react";
|
||||
import { MetaData } from "../../utils.ts";
|
||||
import { TopFieldType } from "./MetaBox.tsx";
|
||||
import { FormItem } from "../FormItem/types.ts";
|
||||
import { InputResultItem } from "./utils.ts";
|
||||
|
||||
interface MetaBoxContextProps {
|
||||
metaData: MetaData;
|
||||
topFields: TopFieldType[];
|
||||
updateMetaData: FormItem["updateMetaData"];
|
||||
updateTopField?: (field: TopFieldType) => void;
|
||||
calcInputList: InputResultItem[];
|
||||
showNodeName: boolean;
|
||||
}
|
||||
|
||||
export const MetaBoxContext = createContext<MetaBoxContextProps>({
|
||||
topFields: [],
|
||||
calcInputList: [],
|
||||
metaData: {} as MetaData,
|
||||
showNodeName: false,
|
||||
updateMetaData: undefined,
|
||||
updateTopField(): void {},
|
||||
});
|
||||
@@ -0,0 +1,222 @@
|
||||
import { getNodesInfo } from "../../utils.ts";
|
||||
|
||||
type MetaValue = string | number | null | any[];
|
||||
interface Workflow {
|
||||
last_node_id: number;
|
||||
last_link_id: number;
|
||||
nodes: Node[];
|
||||
links: Link[];
|
||||
groups: any[];
|
||||
config: object;
|
||||
extra: object;
|
||||
version: number;
|
||||
widget_idx_map: WidgetIdxMap;
|
||||
}
|
||||
|
||||
interface Properties {
|
||||
"Node name for S&R"?: string;
|
||||
}
|
||||
|
||||
interface Node {
|
||||
id: number;
|
||||
type: string;
|
||||
pos: number[];
|
||||
size: Size;
|
||||
flags: object;
|
||||
order: number;
|
||||
mode: number;
|
||||
inputs?: Input[];
|
||||
outputs?: Output[];
|
||||
properties: Properties;
|
||||
widgets_values: MetaValue[];
|
||||
}
|
||||
|
||||
interface Link {
|
||||
id?: number;
|
||||
type?: string;
|
||||
pos?: number[];
|
||||
size?: Size;
|
||||
flags?: object;
|
||||
order?: number;
|
||||
mode?: number;
|
||||
inputs?: Input[];
|
||||
outputs?: Output[];
|
||||
properties?: Properties;
|
||||
widgets_values?: MetaValue[];
|
||||
}
|
||||
|
||||
interface Size {
|
||||
0: number;
|
||||
1: number;
|
||||
}
|
||||
|
||||
interface Input {
|
||||
name: string;
|
||||
type: string;
|
||||
link: number;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface Output {
|
||||
name: string;
|
||||
type: string;
|
||||
links: number[];
|
||||
slot_index: number;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface WidgetIdxMap {
|
||||
[key: string]: {
|
||||
[key: string]: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface Meta {
|
||||
prompt: {
|
||||
[key: string | number]: {
|
||||
class_type: string;
|
||||
inputs: {
|
||||
[key: string]: MetaValue | any;
|
||||
};
|
||||
};
|
||||
};
|
||||
workflow: Workflow;
|
||||
}
|
||||
|
||||
export type InputResultItem = {
|
||||
class_type: string;
|
||||
name: string;
|
||||
linkId: string;
|
||||
value: MetaValue;
|
||||
path: InputResultItem[];
|
||||
inputInfo: any;
|
||||
isTop?: boolean;
|
||||
formLabel?: string;
|
||||
};
|
||||
|
||||
const nodesInfo = getNodesInfo();
|
||||
|
||||
const getInputConfig = (props: { classType: string; name: string }) => {
|
||||
const nodeData = nodesInfo[props.classType]?.nodeData;
|
||||
return {
|
||||
...({
|
||||
...(nodeData?.input?.required ?? {}),
|
||||
...(nodeData?.input?.optional ?? {}),
|
||||
}?.[props.name] ?? {}),
|
||||
output: nodeData?.output ?? [],
|
||||
output_name: nodeData?.output_name ?? [],
|
||||
};
|
||||
};
|
||||
|
||||
function fineRoot(prompt: Meta["prompt"]) {
|
||||
const keyList = Object.keys(prompt);
|
||||
const allInputs = keyList.reduce<string[]>((previousValue, currentValue) => {
|
||||
const inputs = prompt[currentValue].inputs;
|
||||
const allArrValue = Object.keys(inputs).reduce<string[]>(
|
||||
(previousValue1, currentValue1) => {
|
||||
if (Array.isArray(inputs[currentValue1])) {
|
||||
return [...previousValue1, inputs[currentValue1]?.[0]];
|
||||
}
|
||||
return [...previousValue1];
|
||||
},
|
||||
[],
|
||||
);
|
||||
return [...previousValue, ...allArrValue];
|
||||
}, []);
|
||||
return keyList.filter((item) => {
|
||||
return !allInputs.includes(item);
|
||||
});
|
||||
}
|
||||
|
||||
function getInputListByLinkId({
|
||||
oriMeta,
|
||||
linkId,
|
||||
parentItem,
|
||||
}: {
|
||||
oriMeta: Meta;
|
||||
linkId: string;
|
||||
parentItem?: InputResultItem[];
|
||||
}): InputResultItem[] {
|
||||
const prompt = oriMeta.prompt;
|
||||
if (!prompt[linkId]) {
|
||||
return [];
|
||||
}
|
||||
const class_type = prompt[linkId].class_type;
|
||||
const currentInputKeyList = Object.keys(prompt[linkId].inputs);
|
||||
const inputList = currentInputKeyList.map((input) => {
|
||||
const inputInfo = getInputConfig({ classType: class_type, name: input });
|
||||
let formLabel;
|
||||
// 判断是否是 正向提示词
|
||||
if (
|
||||
parentItem?.some((v) => v.name === "positive") &&
|
||||
inputInfo?.["0"] === "STRING"
|
||||
) {
|
||||
formLabel = `positive-${input}`;
|
||||
}
|
||||
if (
|
||||
parentItem?.some((v) => v.name === "negative") &&
|
||||
inputInfo?.["0"] === "STRING"
|
||||
) {
|
||||
formLabel = `negative-${input}`;
|
||||
}
|
||||
return {
|
||||
name: input,
|
||||
class_type: class_type,
|
||||
linkId: linkId,
|
||||
value: prompt[linkId]?.inputs?.[input],
|
||||
path: parentItem ?? [],
|
||||
inputInfo: getInputConfig({ classType: class_type, name: input }),
|
||||
formLabel,
|
||||
} as InputResultItem;
|
||||
});
|
||||
|
||||
// 遍历节点所有input
|
||||
const childrenInputList = inputList.reduce<InputResultItem[]>(
|
||||
(previousValue, currentValue) => {
|
||||
// 如果节点为array,则数据为上一节点传入的数据,递归查询上一节点的信息,同时保存当前节点路径,便于后续查找节点的父子关系
|
||||
if (Array.isArray(currentValue.value)) {
|
||||
const curList = getInputListByLinkId({
|
||||
oriMeta,
|
||||
linkId: currentValue?.value?.[0] ?? "",
|
||||
parentItem: [...(parentItem ?? []), currentValue],
|
||||
});
|
||||
return [
|
||||
...previousValue,
|
||||
// 如果之前已经添加过该节点,则跳过,(有的节点数据会提供给多个node)
|
||||
...curList.filter(
|
||||
(cur) =>
|
||||
!previousValue.some(
|
||||
(value) =>
|
||||
value.linkId === cur.linkId && value.name === cur.name,
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
return previousValue;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// 最终输出的是当前节点的信息, 加上递归的节点数据
|
||||
return [
|
||||
...inputList.filter((v) => !Array.isArray(v.value)),
|
||||
...childrenInputList,
|
||||
];
|
||||
}
|
||||
|
||||
// 递归遍历所有子节点
|
||||
export function calcMeta(oriMeta: Meta): InputResultItem[] {
|
||||
// const last_link_id = oriMeta.workflow.last_node_id;
|
||||
const rootId = fineRoot(oriMeta.prompt);
|
||||
|
||||
if (rootId.length) {
|
||||
return getInputListByLinkId({
|
||||
oriMeta,
|
||||
linkId: `${rootId[0]}`,
|
||||
});
|
||||
}
|
||||
return getInputListByLinkId({
|
||||
oriMeta,
|
||||
linkId: `${rootId}`,
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Media } from "../../types/dbTypes.ts";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { Flex } from "@chakra-ui/react";
|
||||
import { Box, Flex, Grid, Image } from "@chakra-ui/react";
|
||||
import Carousel from "../../components/Carousel/Carousel.tsx";
|
||||
import { MetaInfoBox } from "./MetaInfoBox.tsx";
|
||||
import { isImageFormat } from "../../utils.tsx";
|
||||
|
||||
interface MetaDataInfoProps {
|
||||
media: Media;
|
||||
@@ -19,7 +20,11 @@ export const MetaDataInfo: FC<MetaDataInfoProps> = ({ mediaList, media }) => {
|
||||
|
||||
return (
|
||||
<Flex gap={3} h={"100%"}>
|
||||
<Flex flex={1}>
|
||||
<Grid
|
||||
gridTemplateRows={mediaList.length <= 6 ? "1fr 20%" : "1fr"}
|
||||
flex={1}
|
||||
gap={2}
|
||||
>
|
||||
<Carousel
|
||||
media={mediaList.map((v) => ({
|
||||
id: v.id,
|
||||
@@ -30,7 +35,47 @@ export const MetaDataInfo: FC<MetaDataInfoProps> = ({ mediaList, media }) => {
|
||||
setMediaAct(mediaList?.find((v) => v.id === newMedia.id))
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
{mediaList.length <= 6 && (
|
||||
<Flex>
|
||||
{mediaList?.map((media) => (
|
||||
<Box
|
||||
display={"inline-block"}
|
||||
p={2}
|
||||
borderRadius={"4px"}
|
||||
key={`image-bottom-${media.id}`}
|
||||
width={"16.6%"}
|
||||
cursor={"pointer"}
|
||||
border={mediaAct?.id === media.id ? "1px solid gray" : ""}
|
||||
onClick={() => setMediaAct(media)}
|
||||
>
|
||||
{isImageFormat(
|
||||
`/workspace/view_media?filename=${media.localPath}`,
|
||||
) ? (
|
||||
<Image
|
||||
src={`/workspace/view_media?filename=${media.localPath}`}
|
||||
alt={`image-${media.id}`}
|
||||
width={"100%"}
|
||||
height={"100%"}
|
||||
objectFit="contain"
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
style={{ objectFit: "contain" }}
|
||||
width={"100%"}
|
||||
height={"100%"}
|
||||
src={`/workspace/view_media?filename=${media.localPath}`}
|
||||
loop={true}
|
||||
autoPlay={true}
|
||||
muted={true}
|
||||
>
|
||||
<track kind="captions" />
|
||||
</video>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
)}
|
||||
</Grid>
|
||||
<MetaInfoBox media={mediaAct} />
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getMetadataFromUrl, MetaData } from "../utils.ts";
|
||||
import { Media } from "../../types/dbTypes.ts";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Checkbox,
|
||||
Flex,
|
||||
IconButton,
|
||||
Link,
|
||||
@@ -13,7 +14,11 @@ import { IconDownload } from "@tabler/icons-react";
|
||||
import { formatTimestamp } from "../../utils.tsx";
|
||||
import MetaBox from "./MetaBox/MetaBox.tsx";
|
||||
|
||||
export const MetaInfoBox = ({ media }: { media?: Media }) => {
|
||||
export type MediaWithMetaData = Media & {
|
||||
metaData?: MetaData;
|
||||
};
|
||||
export const MetaInfoBox = ({ media }: { media?: MediaWithMetaData }) => {
|
||||
const [showNodeName, setShowNodeName] = useState(true);
|
||||
const [mediaMetaData, setMediaMetaData] = useState<MetaData>();
|
||||
const getMetaData = async (curMedia: Media) => {
|
||||
try {
|
||||
@@ -27,35 +32,59 @@ export const MetaInfoBox = ({ media }: { media?: Media }) => {
|
||||
};
|
||||
useEffect(() => {
|
||||
if (media) {
|
||||
getMetaData(media);
|
||||
if (media?.metaData) {
|
||||
setMediaMetaData(media?.metaData);
|
||||
} else {
|
||||
getMetaData(media);
|
||||
}
|
||||
}
|
||||
}, [media]);
|
||||
|
||||
return (
|
||||
<Flex overflowY={"auto"} mb={4} direction={"column"} gap={2} flex={1}>
|
||||
<SimpleGrid alignItems={"center"} columns={2} spacing={2}>
|
||||
<SimpleGrid alignItems={"center"} columns={3} spacing={2}>
|
||||
<Flex alignItems={"center"} gap={1}>
|
||||
<Text>{media?.localPath}</Text>
|
||||
<Tooltip label="Donwload image from gallery">
|
||||
<Link
|
||||
href={`/workspace/view_media?filename=${media?.localPath}`}
|
||||
download={media?.localPath}
|
||||
>
|
||||
<IconButton
|
||||
size={"sm"}
|
||||
icon={<IconDownload size={19} />}
|
||||
aria-label="donwload image from gallery"
|
||||
/>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
{media?.localPath && (
|
||||
<>
|
||||
<Text>{media?.localPath}</Text>
|
||||
<Tooltip label="Donwload image from gallery">
|
||||
<Link
|
||||
href={`/workspace/view_media?filename=${media?.localPath}`}
|
||||
download={media?.localPath}
|
||||
>
|
||||
<IconButton
|
||||
size={"sm"}
|
||||
icon={<IconDownload size={19} />}
|
||||
aria-label="donwload image from gallery"
|
||||
/>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex gap={1} alignItems={"center"}>
|
||||
<Text>Create Time:</Text>
|
||||
<Text>{formatTimestamp(media?.createTime ?? 0, true)}</Text>
|
||||
{!!media?.createTime && (
|
||||
<>
|
||||
<Text>Create Time:</Text>
|
||||
<Text>{formatTimestamp(media?.createTime ?? 0, true)}</Text>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Checkbox
|
||||
isChecked={showNodeName}
|
||||
onChange={(e) => setShowNodeName(e.target.checked)}
|
||||
>
|
||||
show node name
|
||||
</Checkbox>
|
||||
</Flex>
|
||||
</SimpleGrid>
|
||||
{media && mediaMetaData && (
|
||||
<MetaBox metaData={mediaMetaData} media={media} />
|
||||
<MetaBox
|
||||
showNodeName={showNodeName}
|
||||
metaData={mediaMetaData}
|
||||
media={media}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
import { MetaData } from "../../utils.ts";
|
||||
import { TopFieldType } from "../MetaBox/MetaBox.tsx";
|
||||
import { Flex } from "@chakra-ui/react";
|
||||
import { FormItemComponent } from "../FormItem/FormItemComponent.tsx";
|
||||
import { FormItem } from "../FormItem/types.ts";
|
||||
import { useContext } from "react";
|
||||
import { MetaBoxContext } from "../MetaBox/metaBoxContext.ts";
|
||||
|
||||
export default function TopForm({
|
||||
metaData,
|
||||
updateMetaData,
|
||||
topFields,
|
||||
updateTopField,
|
||||
}: {
|
||||
metaData: MetaData;
|
||||
topFields: TopFieldType[];
|
||||
updateMetaData: FormItem["updateMetaData"];
|
||||
updateTopField?: (field: TopFieldType) => void;
|
||||
}) {
|
||||
export default function TopForm() {
|
||||
const { topFields, updateTopField, calcInputList, updateMetaData, metaData } =
|
||||
useContext(MetaBoxContext);
|
||||
if (topFields.length === 0) return null;
|
||||
const prompt = metaData.prompt;
|
||||
return (
|
||||
@@ -26,6 +17,12 @@ export default function TopForm({
|
||||
const nodeClassType = prompt?.[field.promptKey]?.class_type;
|
||||
if (nodeClassType !== field.class_type) return null;
|
||||
const inputValue = nodeInputs?.[field.name];
|
||||
const label =
|
||||
calcInputList?.find(
|
||||
(input) =>
|
||||
input?.linkId === field?.promptKey &&
|
||||
input?.name === field?.name,
|
||||
)?.formLabel ?? undefined;
|
||||
return (
|
||||
<FormItemComponent
|
||||
key={`formTop${field.name}${i}`}
|
||||
@@ -36,6 +33,7 @@ export default function TopForm({
|
||||
updateMetaData={updateMetaData}
|
||||
updateTopField={updateTopField}
|
||||
topFields={topFields}
|
||||
label={label}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { MODEL_TYPE } from "../model-manager/install-models/util/modelTypes";
|
||||
import { TopFieldType } from "../gallery/components/MetaBox/MetaBox.tsx";
|
||||
|
||||
export interface SortableItem {
|
||||
name: string;
|
||||
@@ -21,6 +22,7 @@ export interface Workflow extends SortableItem {
|
||||
cloudOrigin?: string;
|
||||
saveLock?: boolean;
|
||||
latestImage?: string;
|
||||
topFieldsConfig?: TopFieldType[];
|
||||
}
|
||||
|
||||
export interface TableBaseModel {
|
||||
|
||||
Reference in New Issue
Block a user