@@ -16,6 +16,7 @@ import json
|
||||
from .version_control import update_version_if_outdated
|
||||
from .service.model_manager.model_installer import download_url_with_wget
|
||||
from .service.model_manager.model_list import get_model_list
|
||||
|
||||
WEB_DIRECTORY = "entry"
|
||||
DEFAULT_USER = "guest"
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
|
||||
-1
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{r as o,j as e,B as p,P as f}from"./input.js";import{app as l}from"/scripts/app.js";import{G as x,I as j}from"./ModelManagerTopbar-Zgx14Jgk.js";import{T as h,H as v,a3 as m,B as _,a5 as w,a6 as M,al as y,F as E,ab as I}from"./App-jF2-L8lr.js";import{V as S}from"./chunk-NTCQBYKE-5Z1buvob.js";import{I as b,G as k}from"./chunk-JARCRF6W-71z2fzu3.js";import"/scripts/api.js";function C({model:s}){const[n,t]=o.useState([]),r=D(s.received_value),d=async()=>{const i=await(await fetch(`https://huggingface.co/api/models?limit=3&search=${r}&full=true`)).json(),a=[];i.slice(0,1).forEach(({modelId:c,siblings:g})=>{a.push({name:c,downloadUrl:`https://huggingface.co/${c}/resolve/main/`,url:`https://huggingface.co/${c}/tree/main`})}),t(a)};return o.useEffect(()=>{d()},[]),e.jsx(x,{p:3,shadow:"md",borderWidth:"1px",children:e.jsxs(S,{align:"start",children:[e.jsx(h,{children:s.class_type}),e.jsxs(v,{ml:3,children:[e.jsxs(h,{children:[s.input_name,": "]}),e.jsx(h,{color:"red.400",children:s.received_value})]}),n.map(({url:u,name:i})=>e.jsx(p,{size:"sm",onClick:a=>{a.stopPropagation(),window.open(u,"_blank")},leftIcon:e.jsx(b,{width:5,src:"https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.png"}),rightIcon:e.jsx(m,{size:20}),children:i})),e.jsx(p,{colorScheme:"blue",mt:5,iconSpacing:1,leftIcon:e.jsx(m,{size:20}),size:"sm",onClick:()=>{window.open(`https://civitai.com/search/models?sortBy=models_v5&query=${r}`,"_blank")},children:"Search in CivitAI"})]})})}function D(s){return s.replace(/^.*(\\|\/|\:)/,"").replace(/\.[^/.]+$/,"").replaceAll("_"," ").replace(/([A-Z])/g," $1").trim()}function z({onClose:s,missingModels:n}){o.useEffect(()=>(l.canvasEl.addEventListener("click",s),()=>{l.canvasEl.removeEventListener("click",s)}),[]);const t=440;return e.jsx(f,{children:e.jsx(_,{style:{width:t},children:e.jsxs(w,{width:t,height:"100vh",p:4,gap:2,position:"fixed",top:0,left:0,shadow:"xl",zIndex:M,overflowY:"auto",children:[e.jsx(y,{children:e.jsxs(E,{justifyContent:"space-between",alignContent:"center",children:[e.jsxs(I,{size:"md",mr:2,children:["Missing Models (",n.length,")"]}),e.jsx(j,{})]})}),e.jsx(k,{templateColumns:"1",gap:1,marginTop:2,children:n.map(r=>e.jsx(C,{model:r},r.received_value))})]})})})}function F({}){const[s,n]=o.useState(!1),[t,r]=o.useState([]);return o.useEffect(()=>{const d=l.queuePrompt;l.queuePrompt=async function(){try{await d.apply(l,[...arguments])}finally{const u=l.lastNodeErrors??{};r(Object.values(u).flatMap(i=>i.errors.filter(a=>a.type==="value_not_in_list").map(a=>{const{input_name:c,received_value:g}=a.extra_info;return{class_type:i.class_type,input_name:c,received_value:g}})))}}},[]),t.length===0?null:e.jsxs(e.Fragment,{children:[e.jsxs(p,{size:"sm","aria-label":"missing models",px:2,onClick:()=>n(!0),children:["Install Missing (",t.length,")"]}),s&&e.jsx(z,{onClose:()=>n(!1),missingModels:t})]})}export{F as default};
|
||||
-1
@@ -1 +0,0 @@
|
||||
import{G as U,I as C}from"./chunk-JARCRF6W-71z2fzu3.js";import{f as x,D as b,j as p,aa as S,r as h}from"./input.js";import{ac as y,h as v,a9 as E}from"./App-jF2-L8lr.js";var V=x(function(o,s){const{columns:l,spacingX:a,spacingY:n,spacing:r,minChildWidth:e,...c}=o,i=b(),f=e?A(e,i):M(l);return p.jsx(U,{ref:s,gap:r,columnGap:a,rowGap:n,templateColumns:f,...c})});V.displayName="SimpleGrid";function k(t){return typeof t=="number"?`${t}px`:t}function A(t,o){return y(t,s=>{const l=S("sizes",s,k(s))(o);return s===null?null:`repeat(auto-fit, minmax(${l}, 1fr))`})}function M(t){return y(t,o=>o===null?null:`repeat(${o}, minmax(0, 1fr))`)}var R=v("chevron-right","IconChevronRight",[["path",{d:"M9 6l6 6l-6 6",key:"svg-0"}]]);function O(t){return new Promise((o,s)=>{const l=new FileReader;l.onload=a=>{const n=new Uint8Array(a.target.result),r=new DataView(n.buffer);r.getUint32(0)!==2303741511&&(console.error("Not a valid PNG file"),s());let e=8;const c={};for(;e<n.length;){const i=r.getUint32(e),f=String.fromCharCode(...n.slice(e+4,e+8));if(f==="tEXt"||f=="comf"){let u=e+8;for(;n[u]!==0;)u++;const g=String.fromCharCode(...n.slice(e+8,u)),d=n.slice(u+1,e+8+i),m=Array.from(d).map(w=>String.fromCharCode(w)).join("");c[g]=m}e+=12+i}o(c)},l.readAsArrayBuffer(t)})}function D(t){const o=new Uint16Array(t.slice(0,2))[0]===18761;function s(r,e,c){const i=t.slice(r,r+c);if(c===2)return new DataView(i.buffer,i.byteOffset,i.byteLength).getUint16(0,e);if(c===4)return new DataView(i.buffer,i.byteOffset,i.byteLength).getUint32(0,e)}const l=s(4,o,4);function a(r){const e=s(r,o,2),c={};for(let i=0;i<e;i++){const f=r+2+i*12,u=s(f,o,2),g=s(f+2,o,2),d=s(f+4,o,4),m=s(f+8,o,4);let w;g===2&&(w=String.fromCharCode(...t.slice(m,m+d-1))),c[u]=w}return c}return a(l)}function G(t){return new Promise(o=>{const s=new FileReader;s.onload=l=>{var c;const a=new Uint8Array((c=l==null?void 0:l.target)==null?void 0:c.result),n=new DataView(a.buffer);if(n.getUint32(0)!==1380533830||n.getUint32(8)!==1464156752){console.error("Not a valid WEBP file"),o();return}let r=12;const e={};for(;r<a.length;){const i=n.getUint32(r+4,!0);if(String.fromCharCode(...a.slice(r,r+4))==="EXIF"){String.fromCharCode(...a.slice(r+8,r+8+6))=="Exif\0\0"&&(r+=6);const u=D(a.slice(r+8,r+8+i));for(const g in u){const d=u[g],m=d.indexOf(":");e[d.slice(0,m)]=d.slice(m+1)}}r+=8+i}o(e)},s.readAsArrayBuffer(t)})}function N(t){return!!(t!=null&&t.endsWith(".webm")||t!=null&&t.endsWith(".mp4"))}function F(t){return new Promise(o=>{const s=new FileReader;s.onload=l=>{const a=new Uint8Array(l.target.result),n=new DataView(a.buffer),r=new TextDecoder;if(n.getUint32(0)==440786851){let e=12;for(;e<a.length-16;){if(n.getUint16(e)==17543&&String.fromCharCode(...a.slice(e-7,e))==="COMMENT"){const i=n.getUint32(e+2),f=Math.clz32(i)+1;if(f<4){const u=i>>8*(4-f)&~(1<<7*f),g=r.decode(a.slice(e+2+f,e+2+f+u)),d=JSON.parse(g);o(d);return}}e+=1}}else if(n.getUint32(4)==1718909296&&n.getUint32(8)==1769172845){let e=a.length-4;for(;e>16;){if(n.getUint32(e)==1684108385&&n.getUint32(e-8)==2841865588){n.getUint32(e+4),n.getUint32(e+8);const c=n.getUint32(e-4)-4*4,i=r.decode(a.slice(e+12,e+12+c)),f=JSON.parse(i);o(f);return}e-=1}}else{console.error("Unknown magic: "+n.getUint32(0)),o();return}},s.readAsArrayBuffer(t)})}function $({mediaLocalPath:t,size:o,autoPlay:s,hideBrokenImage:l,onBrokenLink:a}){const[n,r]=h.useState(!0);return h.useEffect(()=>{l&&(async()=>{try{if(!(await fetch(`/workspace/view_media?filename=${t}`)).ok)throw new Error("Media not found");r(!0)}catch{r(!1),a==null||a()}})()},[]),n?E(t)?p.jsx(C,{borderRadius:3,boxSize:`${o}px`,objectFit:"cover",src:`/workspace/view_media?filename=${t}`,alt:"workflow image renamed or moved from output folder"}):p.jsx("video",{width:o,height:o,src:`/workspace/view_media?filename=${t}`,loop:!0,autoPlay:s??!1,muted:!0,children:p.jsx("track",{kind:"captions"})}):null}export{R as I,$ as M,V as S,F as a,G as b,O as g,N as i};
|
||||
File diff suppressed because one or more lines are too long
-1
@@ -1 +0,0 @@
|
||||
.dragPanelIcon{display:none!important}.workspaceManagerPanel:hover .dragPanelIcon{display:block!important}
|
||||
@@ -1 +0,0 @@
|
||||
.drag-model-manager-top-bar-icon{visibility:hidden!important}.model-manager-top-bar:hover .drag-model-manager-top-bar-icon{visibility:visible!important}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{j as t}from"./App-jF2-L8lr.js";import{f as o,j as s}from"./input.js";var e=o((a,r)=>s.jsx(t,{align:"center",...a,direction:"column",ref:r}));e.displayName="VStack";export{e as V};
|
||||
Vendored
-688
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useRef } from "react";
|
||||
import { Box, Button, Card, useOutsideClick } from "@chakra-ui/react";
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { Box, Button, Card } from "@chakra-ui/react";
|
||||
import { IconChevronDown } from "@tabler/icons-react";
|
||||
|
||||
export interface CustomSelectorOption {
|
||||
@@ -17,10 +17,18 @@ export default function CustomSelector({ options }: Props) {
|
||||
const [selectedOption, setSelectedOption] = useState<
|
||||
CustomSelectorOption | undefined
|
||||
>(options[0]);
|
||||
useOutsideClick({
|
||||
ref: ref,
|
||||
handler: () => setIsOpen(false),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: any) => {
|
||||
if (ref.current && !ref.current.contains(event.target)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
const toggleDropdown = () => setIsOpen(!isOpen);
|
||||
|
||||
@@ -42,7 +50,6 @@ export default function CustomSelector({ options }: Props) {
|
||||
borderWidth="1px"
|
||||
p="2"
|
||||
position="absolute"
|
||||
zIndex="dropdown"
|
||||
>
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
|
||||
@@ -198,23 +198,18 @@ export default function DropdownTitle() {
|
||||
>
|
||||
Versions History
|
||||
</MenuItem>
|
||||
{/* <MenuItem
|
||||
<MenuItem
|
||||
onClick={() => setIsShareOpen(true)}
|
||||
icon={<IconShare2 size={20} />}
|
||||
iconSpacing={1}
|
||||
>
|
||||
Share
|
||||
</MenuItem> */}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
}
|
||||
/>
|
||||
{isShareOpen && workflowsTable?.curWorkflow && (
|
||||
<ShareDialog
|
||||
workflow={workflowsTable?.curWorkflow}
|
||||
onClose={() => setIsShareOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{isShareOpen && <ShareDialog onClose={() => setIsShareOpen(false)} />}
|
||||
{isVersionHistoryOpen && (
|
||||
<VersionHistoryDrawer onClose={() => setIsVersionHistoryOpen(false)} />
|
||||
)}
|
||||
|
||||
@@ -48,6 +48,12 @@ export class TableBase<T extends TableBaseModel> {
|
||||
await this.saveDiskDB();
|
||||
return newItem;
|
||||
}
|
||||
public async update(id: string, changes: Partial<T>): Promise<T | null> {
|
||||
await indexdb[this.tableName].update(id, changes);
|
||||
await this.saveDiskDB();
|
||||
return (await this.get(id)) ?? null;
|
||||
}
|
||||
|
||||
public async listAll(): Promise<T[]> {
|
||||
const list = await indexdb[this.tableName].toArray();
|
||||
return list as any[];
|
||||
|
||||
@@ -37,6 +37,7 @@ export class UserSettingsTable extends TableBase<UserSettings> {
|
||||
autoSave: true,
|
||||
twoWaySync: false,
|
||||
foldersOnTop: false,
|
||||
cloudHost: "https://www.comfyspace.art",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+243
-34
@@ -1,31 +1,59 @@
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
HStack,
|
||||
Input,
|
||||
Link,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { Workflow } from "../types/dbTypes";
|
||||
import { useState } from "react";
|
||||
import { Workflow, WorkflowVersion } from "../types/dbTypes";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import CustomDropdown, {
|
||||
CustomSelectorOption,
|
||||
} from "../components/CustomSelector";
|
||||
import {
|
||||
IconCloud,
|
||||
IconCopy,
|
||||
IconExternalLink,
|
||||
IconLink,
|
||||
IconLock,
|
||||
IconWorld,
|
||||
} from "@tabler/icons-react";
|
||||
import { workflowVersionsTable } from "../db-tables/WorkspaceDB";
|
||||
import {
|
||||
userSettingsTable,
|
||||
workflowVersionsTable,
|
||||
workflowsTable,
|
||||
} from "../db-tables/WorkspaceDB";
|
||||
// @ts-ignore
|
||||
import { api } from "/scripts/api.js";
|
||||
// @ts-ignore
|
||||
import { app } from "/scripts/app.js";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
workflow: Workflow;
|
||||
}
|
||||
type CreatedCloudflowVersionEvent = {
|
||||
createdVer: WorkflowVersion;
|
||||
localWorkflowID: string;
|
||||
localVersionID: string;
|
||||
};
|
||||
|
||||
function generateRandomKey(length: number) {
|
||||
// Generate a random array of bytes
|
||||
const array = new Uint8Array(length);
|
||||
window.crypto.getRandomValues(array);
|
||||
|
||||
// Convert the bytes to a hex string
|
||||
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
const privacyOptions: CustomSelectorOption[] = [
|
||||
@@ -37,44 +65,225 @@ const privacyOptions: CustomSelectorOption[] = [
|
||||
},
|
||||
{ label: "Public", value: "PUBLIC", icon: <IconWorld /> },
|
||||
];
|
||||
export default function ShareDialog({ onClose, workflow }: Props) {
|
||||
export default function ShareDialog({ onClose }: Props) {
|
||||
const [versionName, setVersionName] = useState(
|
||||
"version " + getCurDateString(),
|
||||
);
|
||||
// const onShare = async () => {
|
||||
// const url = await workflowVersionsTable?.add({
|
||||
// workflowID: workflow.id,
|
||||
// name: versionName,
|
||||
// createTime: Date.now(),
|
||||
// privacy: "public",
|
||||
// json: workflow.json,
|
||||
// remoteUrl: "",
|
||||
// });
|
||||
// if (url) {
|
||||
// alert(url);
|
||||
// }
|
||||
// };
|
||||
const [localVersions, setLocalVersions] = useState<WorkflowVersion[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [cloudHost, setCloudHost] = useState("");
|
||||
const cloudHostRef = useRef("");
|
||||
const [workflow, setWorkflow] = useState<Workflow>();
|
||||
const toast = useToast();
|
||||
const handleShareWorkflowSuccess = async (event: MessageEvent) => {
|
||||
const detail = event.data;
|
||||
if (
|
||||
event.origin !== cloudHostRef.current ||
|
||||
detail.type !== "share_workflow_success"
|
||||
) {
|
||||
console.log("event.origin !== cloudHost", event.origin !== cloudHost);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("share_workflow_success", detail);
|
||||
const localID = detail.localWorkflowID;
|
||||
const localVerID = detail.localVersionID;
|
||||
const cloudVersionID = detail.version.id;
|
||||
const cloudID = detail.version.workflowID;
|
||||
|
||||
cloudID &&
|
||||
localID &&
|
||||
(await workflowsTable?.updateFlow(localID, {
|
||||
cloudID: cloudID,
|
||||
cloudURL: cloudHost + "/workflow/" + cloudID,
|
||||
}));
|
||||
localVerID &&
|
||||
cloudVersionID &&
|
||||
(await workflowVersionsTable?.update(localVerID, {
|
||||
cloudID: cloudVersionID,
|
||||
}));
|
||||
loadData();
|
||||
};
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
window.addEventListener("message", handleShareWorkflowSuccess);
|
||||
return () => {
|
||||
window.removeEventListener("message", handleShareWorkflowSuccess);
|
||||
};
|
||||
}, []);
|
||||
const getNodeDefs = () => {
|
||||
const allNodes = app.graph._nodes;
|
||||
const nodeDefs = {};
|
||||
for (let n of allNodes) {
|
||||
// @ts-ignore
|
||||
if (n.type in LiteGraph.registered_node_types) {
|
||||
// @ts-ignore
|
||||
nodeDefs[n.type] = LiteGraph.registered_node_types[n.type].nodeData;
|
||||
}
|
||||
}
|
||||
return nodeDefs;
|
||||
};
|
||||
const loadData = async () => {
|
||||
const host =
|
||||
import.meta.env.MODE === "production"
|
||||
? await userSettingsTable?.getSetting("cloudHost")
|
||||
: "http://localhost:3000";
|
||||
if (host) {
|
||||
setCloudHost(host);
|
||||
cloudHostRef.current = host;
|
||||
}
|
||||
const workflow = workflowsTable?.curWorkflow ?? undefined;
|
||||
setWorkflow(workflow);
|
||||
workflowVersionsTable?.listByWorkflowID(workflow!.id).then((vers) => {
|
||||
setLocalVersions(vers);
|
||||
});
|
||||
};
|
||||
const copyTextToClipboard = (text: string) => {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
toast({
|
||||
title: "Copied to clipboard",
|
||||
status: "success",
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to copy text: ", err);
|
||||
});
|
||||
};
|
||||
const onShare = async () => {
|
||||
setLoading(true);
|
||||
const secretKey = generateRandomKey(32);
|
||||
const host =
|
||||
import.meta.env.MODE === "production"
|
||||
? ((await userSettingsTable?.getSetting("cloudHost")) as string)
|
||||
: "http://localhost:3000";
|
||||
const nodeDefs = getNodeDefs();
|
||||
const newVer = await workflowVersionsTable?.add({
|
||||
workflowID: workflow!.id,
|
||||
name: versionName,
|
||||
createTime: Date.now(),
|
||||
json: workflow!.json,
|
||||
});
|
||||
if (!newVer) {
|
||||
alert("Failed to create new version, please try again.");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const protocol = window.location.protocol;
|
||||
const baseHost = window.location.host;
|
||||
const baseUrl = `${protocol}//${baseHost}`;
|
||||
const popupUrl =
|
||||
host + `/share_workflow_confirm/${secretKey}?redirectUrl=${baseUrl}`;
|
||||
const sharePopup = window.open(
|
||||
popupUrl,
|
||||
"Share Workflow",
|
||||
"width=800,height=800",
|
||||
);
|
||||
setLoading(false);
|
||||
const handleChildReady = (event: MessageEvent) => {
|
||||
if (event.origin === host && event.data === "child_ready") {
|
||||
const curWorkflow = workflowsTable?.curWorkflow;
|
||||
console.log("Child window is ready.inputdata", {
|
||||
workflow: curWorkflow,
|
||||
version: newVer,
|
||||
nodeDefs: nodeDefs,
|
||||
});
|
||||
// Send data to the new window after it loads
|
||||
sharePopup!.postMessage(
|
||||
{
|
||||
workflow: curWorkflow,
|
||||
version: newVer,
|
||||
nodeDefs: nodeDefs,
|
||||
},
|
||||
host,
|
||||
);
|
||||
window.removeEventListener("message", handleChildReady);
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", handleChildReady);
|
||||
};
|
||||
|
||||
const cloudWorkflowID = workflowsTable?.curWorkflow?.cloudID;
|
||||
return (
|
||||
<Modal isOpen={true} onClose={onClose} size={"lg"}>
|
||||
<ModalOverlay />
|
||||
<ModalContent width={["98%", "90%", "50%"]} maxWidth={"600px"}>
|
||||
<ModalHeader>Share "{workflow.name}"</ModalHeader>
|
||||
|
||||
<ModalHeader>Share "{workflow?.name}"</ModalHeader>
|
||||
<ModalBody pb={10}>
|
||||
<Text mb={3}>Version name</Text>
|
||||
<HStack>
|
||||
<Input
|
||||
value={versionName}
|
||||
maxWidth={440}
|
||||
onChange={(e) => {
|
||||
setVersionName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
<Text mt={4} mb={3}></Text>
|
||||
<CustomDropdown options={privacyOptions} />
|
||||
<Stack gap={6}>
|
||||
<CustomDropdown options={privacyOptions} />
|
||||
|
||||
{cloudWorkflowID && (
|
||||
<HStack spacing={2} color="teal.400">
|
||||
<Link
|
||||
href={cloudHost + "/workflow/" + cloudWorkflowID}
|
||||
isExternal
|
||||
>
|
||||
{cloudHost + "/workflow/" + cloudWorkflowID}{" "}
|
||||
<IconExternalLink
|
||||
size={20}
|
||||
style={{ display: "inline-block", verticalAlign: "middle" }}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
width={"180px"}
|
||||
size={"sm"}
|
||||
leftIcon={<IconCopy />}
|
||||
onClick={() => {
|
||||
copyTextToClipboard(
|
||||
cloudHost +
|
||||
"/workflow/" +
|
||||
workflowsTable?.curWorkflow?.cloudID,
|
||||
);
|
||||
}}
|
||||
>
|
||||
Copy link
|
||||
</Button>
|
||||
</HStack>
|
||||
)}
|
||||
<HStack>
|
||||
<Text>New Version</Text>
|
||||
<Input
|
||||
value={versionName}
|
||||
maxWidth={440}
|
||||
onChange={(e) => {
|
||||
setVersionName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
{localVersions.slice(0, 4).map((ver) => {
|
||||
return (
|
||||
<HStack key={ver.id} spacing={4} alignItems={"center"} mb={3}>
|
||||
<Text>{ver.name}</Text>
|
||||
{ver.cloudID && (
|
||||
<a
|
||||
href={cloudHost + "/workflow_ver/" + ver.cloudID}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ textDecoration: "none" }}
|
||||
>
|
||||
<Button
|
||||
size={"xs"}
|
||||
colorScheme="teal"
|
||||
leftIcon={<IconCloud />}
|
||||
rightIcon={<IconExternalLink size={18} />}
|
||||
>
|
||||
Shared
|
||||
</Button>
|
||||
</a>
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
<HStack justifyContent={"flex-end"} mt={16}>
|
||||
<Button colorScheme="teal">Share</Button>
|
||||
<Button colorScheme="teal" onClick={onShare} isDisabled={loading}>
|
||||
{loading ? "Sharing" : "Share"}
|
||||
</Button>
|
||||
</HStack>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { Button } from "@chakra-ui/react";
|
||||
import { IconUsersGroup } from "@tabler/icons-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconCloud } from "@tabler/icons-react";
|
||||
import { workflowsTable } from "../db-tables/WorkspaceDB";
|
||||
|
||||
export function SharedTopbarButton({}) {
|
||||
const isCloudflow = workflowsTable?.curWorkflow?.cloudID;
|
||||
if (!isCloudflow) return null;
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {}}
|
||||
onClick={() => {
|
||||
window.open(workflowsTable?.curWorkflow?.cloudURL);
|
||||
}}
|
||||
aria-label={"Shared"}
|
||||
size={"sm"}
|
||||
leftIcon={<IconUsersGroup />}
|
||||
leftIcon={<IconCloud />}
|
||||
>
|
||||
Public
|
||||
</Button>
|
||||
|
||||
@@ -14,6 +14,7 @@ import EditFlowName from "../components/EditFlowName";
|
||||
import { WorkspaceContext } from "../WorkspaceContext";
|
||||
import { PanelPosition } from "../types/dbTypes";
|
||||
import "./Topbar.css";
|
||||
import { SharedTopbarButton } from "../share/SharedTopbarButton";
|
||||
|
||||
interface Props {
|
||||
positionStyle: PanelPosition;
|
||||
@@ -103,6 +104,7 @@ export function Topbar({
|
||||
<DropdownTitle />
|
||||
</HStack>
|
||||
)}
|
||||
<SharedTopbarButton />
|
||||
{curFlowID && isDirty ? (
|
||||
<Tooltip label="Save workflow">
|
||||
<IconButton
|
||||
|
||||
@@ -14,6 +14,9 @@ export interface Workflow extends SortableItem {
|
||||
parentFolderID?: string;
|
||||
mediaIDs?: string[];
|
||||
coverMediaPath?: string;
|
||||
cloudID?: string;
|
||||
cloudURL?: string;
|
||||
privacy?: WorkflowPrivacy;
|
||||
}
|
||||
|
||||
export interface TableBaseModel {
|
||||
@@ -56,8 +59,8 @@ export type WorkflowVersion = {
|
||||
workflowID: string;
|
||||
json: string;
|
||||
createTime: number;
|
||||
privacy?: "public" | "private";
|
||||
remoteUrl?: string;
|
||||
cloudID?: string;
|
||||
nodeDefs?: string; //for cloud workflow version
|
||||
};
|
||||
|
||||
export type UserSettings = {
|
||||
@@ -72,6 +75,7 @@ export type UserSettings = {
|
||||
twoWaySync?: boolean;
|
||||
foldersOnTop?: boolean;
|
||||
showNsfwModelThumbnail?: boolean;
|
||||
cloudHost: string;
|
||||
};
|
||||
|
||||
export interface PanelPosition {
|
||||
@@ -117,4 +121,6 @@ export enum EFlowOperationType {
|
||||
DELETE = "delete",
|
||||
}
|
||||
|
||||
type WorkflowPrivacy = "PUBLIC" | "PRIVATE" | "UNLISTED";
|
||||
|
||||
export const WORKSPACE_INDEXDB_NAME = "comfyui_workspace_db";
|
||||
|
||||
Reference in New Issue
Block a user