send2trash, fix indexdb opening error by try catch, restructure and simplify share dialog
Beta
This commit is contained in:
+13
-15
@@ -5,17 +5,18 @@ import folder_paths
|
||||
import shutil
|
||||
import os
|
||||
import sys
|
||||
import subprocess # don't remove this
|
||||
from urllib.parse import urlparse
|
||||
import subprocess
|
||||
import subprocess
|
||||
import os
|
||||
import json
|
||||
from .service.model_manager.model_installer import download_url_with_wget
|
||||
from .service.model_manager.model_list import get_model_list
|
||||
from .service.media_service import *
|
||||
from .service.file_sync_service import *
|
||||
from .service.db_service import get_my_workflows_dir
|
||||
from .service.node_service import *
|
||||
try:
|
||||
from send2trash import send2trash
|
||||
except ImportError:
|
||||
send2trash = None
|
||||
|
||||
WEB_DIRECTORY = "entry"
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
@@ -112,31 +113,28 @@ async def update_file(request):
|
||||
await asyncio.to_thread(write_json_to_file, json_str)
|
||||
return web.Response(text="File updated successfully")
|
||||
|
||||
|
||||
@server.PromptServer.instance.routes.post("/workspace/delete_file")
|
||||
async def delete_file(request):
|
||||
data = await request.json()
|
||||
file_path = data['file_path']
|
||||
delete_empty_folder = data['deleteEmptyFolder']
|
||||
|
||||
def sync_delete_file(file_path, delete_empty_folder):
|
||||
def sync_delete_file(file_path):
|
||||
my_workflows_dir = get_my_workflows_dir()
|
||||
full_path = os.path.join(my_workflows_dir, file_path)
|
||||
|
||||
if os.path.exists(full_path):
|
||||
os.remove(full_path)
|
||||
directory = os.path.dirname(full_path)
|
||||
if delete_empty_folder and not os.listdir(directory):
|
||||
# If the directory is empty, remove the directory
|
||||
os.rmdir(directory)
|
||||
return "File and empty directory deleted successfully"
|
||||
if send2trash:
|
||||
send2trash(full_path)
|
||||
else:
|
||||
return "File deleted successfully"
|
||||
os.remove(full_path)
|
||||
print("❌⛔️send2trash is not available. Deleting file permanently. Please `pip install send2trash`")
|
||||
|
||||
return "File deleted successfully"
|
||||
else:
|
||||
return "File not found"
|
||||
|
||||
# Run the synchronous file operation in a separate thread
|
||||
response_text = await asyncio.to_thread(sync_delete_file, file_path, delete_empty_folder)
|
||||
response_text = await asyncio.to_thread(sync_delete_file, file_path)
|
||||
|
||||
if response_text == "File not found":
|
||||
return web.Response(text=response_text, status=404)
|
||||
|
||||
Vendored
+27
File diff suppressed because one or more lines are too long
Vendored
-29
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{r as u,j as E}from"./input.js";import{Z as x,a5 as r,aj as C,x as a,i as W,T as j,E as d,ak as A,al as L,k as h,am as R}from"./App-eQybp91e.js";import{u as O}from"./useDebounceFn-ld478fd0.js";function M(){const{isDirty:p,setIsDirty:g,setRoute:v,saveCurWorkflow:T,setCurFlowIDAndName:b}=u.useContext(x),w=u.useRef(p),[S,D]=u.useState(!1),k=u.useRef(null);u.useEffect(()=>{w.current=p},[p]),u.useEffect(()=>{var n,i;const c=e=>{var s;if(document.visibilityState==="hidden")return;const t=L(e);if(t)switch(t===d.openSpotlightSearch&&e.preventDefault(),window.dispatchEvent(new CustomEvent(A,{detail:{shortcutType:t}})),t){case d.SAVE:T();break;case d.SAVE_AS:v("saveAsModal");break;case d.openSpotlightSearch:v("spotlightSearch");break}else(s=e.target)!=null&&s.matches("input, textarea")&&Object.keys(r.canvas.selected_nodes??{}).length&&m()},o=r.graph.onConfigure;return r.graph.onConfigure=function(){o==null||o.apply(this,arguments),setTimeout(()=>{var t,s,l,f;const e=(s=(t=r.graph.extra)==null?void 0:t[C])==null?void 0:s.id;(e==null||e!=((f=(l=a)==null?void 0:l.curWorkflow)==null?void 0:f.id))&&b(null)},500)},document.addEventListener("click",e=>{Object.keys(r.canvas.selected_nodes??{}).length&&(r.canvas.node_over!=null||r.canvas.node_capturing_input!=null||r.canvas.node_widget!=null)&&m()}),document.addEventListener("keydown",c),(i=(n=h)==null?void 0:n.settings)!=null&&i.autoSave&&h.settings.autoSaveDuration&&(k.current=setInterval(()=>{_()},h.settings.autoSaveDuration*1e3)),()=>{document.removeEventListener("keydown",c),k.current&&clearInterval(k.current)}},[]);const _=async()=>{var o,n,i,e,t,s;if((n=(o=a)==null?void 0:o.curWorkflow)!=null&&n.saveLock||!((e=(i=a)==null?void 0:i.curWorkflow)!=null&&e.id)||!w.current)return;const c=JSON.stringify(r.graph.serialize());await((t=a)==null?void 0:t.updateFlow(a.curWorkflow.id,{json:c})),(s=R)==null||s.create({workflowID:a.curWorkflow.id,isAutoSave:!0,json:c}),g(!1)},I=async()=>{var o,n,i,e,t,s,l,f,y;if((n=(o=a)==null?void 0:o.curWorkflow)!=null&&n.saveLock||w.current)return;const c=r.graph.serialize();JSON.stringify(c)!==((e=(i=a)==null?void 0:i.curWorkflow)==null?void 0:e.json)&&(g(!0),(s=(t=h)==null?void 0:t.settings)!=null&&s.autoSave&&((f=(l=a)==null?void 0:l.curWorkflow)!=null&&f.id)&&!S&&(await((y=a)==null?void 0:y.latestVersionCheck())||D(!0)))},[m,N]=O(I,900);return S?E.jsx(W,{label:"This workflow was changed by another tab, so you are not working on the latest version. Please refresh page to see the latest version of this workflow.",children:E.jsx(j,{colorScheme:"yellow",children:"⚠️Outdated version"})}):null}export{M as default};
|
||||
import{r as u,j as E}from"./input.js";import{X as x,a3 as r,af as C,x as a,i as W,T as A,E as d,ag as L,ah as R,k as h,ai as j}from"./App-Pi18TWng.js";import{u as O}from"./useDebounceFn-ld478fd0.js";function M(){const{isDirty:p,setIsDirty:k,setRoute:v,saveCurWorkflow:T,setCurFlowIDAndName:b}=u.useContext(x),w=u.useRef(p),[S,D]=u.useState(!1),g=u.useRef(null);u.useEffect(()=>{w.current=p},[p]),u.useEffect(()=>{var n,i;const c=e=>{var s;if(document.visibilityState==="hidden")return;const t=R(e);if(t)switch(t===d.openSpotlightSearch&&e.preventDefault(),window.dispatchEvent(new CustomEvent(L,{detail:{shortcutType:t}})),t){case d.SAVE:T();break;case d.SAVE_AS:v("saveAsModal");break;case d.openSpotlightSearch:v("spotlightSearch");break}else(s=e.target)!=null&&s.matches("input, textarea")&&Object.keys(r.canvas.selected_nodes??{}).length&&m()},o=r.graph.onConfigure;return r.graph.onConfigure=function(){o==null||o.apply(this,arguments),setTimeout(()=>{var t,s,l,f;const e=(s=(t=r.graph.extra)==null?void 0:t[C])==null?void 0:s.id;(e==null||e!=((f=(l=a)==null?void 0:l.curWorkflow)==null?void 0:f.id))&&b(null)},500)},document.addEventListener("click",e=>{Object.keys(r.canvas.selected_nodes??{}).length&&(r.canvas.node_over!=null||r.canvas.node_capturing_input!=null||r.canvas.node_widget!=null)&&m()}),document.addEventListener("keydown",c),(i=(n=h)==null?void 0:n.settings)!=null&&i.autoSave&&h.settings.autoSaveDuration&&(g.current=setInterval(()=>{_()},h.settings.autoSaveDuration*1e3)),()=>{document.removeEventListener("keydown",c),g.current&&clearInterval(g.current)}},[]);const _=async()=>{var o,n,i,e,t,s;if((n=(o=a)==null?void 0:o.curWorkflow)!=null&&n.saveLock||!((e=(i=a)==null?void 0:i.curWorkflow)!=null&&e.id)||!w.current)return;const c=JSON.stringify(r.graph.serialize());await((t=a)==null?void 0:t.updateFlow(a.curWorkflow.id,{json:c})),(s=j)==null||s.create({workflowID:a.curWorkflow.id,isAutoSave:!0,json:c}),k(!1)},I=async()=>{var o,n,i,e,t,s,l,f,y;if((n=(o=a)==null?void 0:o.curWorkflow)!=null&&n.saveLock||w.current)return;const c=r.graph.serialize();JSON.stringify(c)!==((e=(i=a)==null?void 0:i.curWorkflow)==null?void 0:e.json)&&(k(!0),(s=(t=h)==null?void 0:t.settings)!=null&&s.autoSave&&((f=(l=a)==null?void 0:l.curWorkflow)!=null&&f.id)&&!S&&(await((y=a)==null?void 0:y.latestVersionCheck())||D(!0)))},[m,N]=O(I,900);return S?E.jsx(W,{label:"This workflow was changed by another tab, so you are not working on the latest version. Please refresh page to see the latest version of this workflow.",children:E.jsx(A,{colorScheme:"yellow",children:"⚠️Outdated version"})}):null}export{M as default};
|
||||
+1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{d as a}from"./App-eQybp91e.js";var p=a("copy","IconCopy",[["path",{d:"M7 7m0 2.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667z",key:"svg-0"}],["path",{d:"M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1",key:"svg-1"}]]);export{p as I};
|
||||
import{d as a}from"./App-Pi18TWng.js";var p=a("copy","IconCopy",[["path",{d:"M7 7m0 2.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667z",key:"svg-0"}],["path",{d:"M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1",key:"svg-1"}]]);export{p as I};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{d as a}from"./App-eQybp91e.js";var r=a("search","IconSearch",[["path",{d:"M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0",key:"svg-0"}],["path",{d:"M21 21l-6 -6",key:"svg-1"}]]);export{r as I};
|
||||
import{d as a}from"./App-Pi18TWng.js";var r=a("search","IconSearch",[["path",{d:"M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0",key:"svg-0"}],["path",{d:"M21 21l-6 -6",key:"svg-1"}]]);export{r as I};
|
||||
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{f as x,j as l,g as i,h as b,l as C,o as E,n as I,r as f,a3 as P}from"./input.js";import{ac as z}from"./App-eQybp91e.js";var v=x(function(o,s){const{children:t,placeholder:n,className:r,...a}=o;return l.jsxs(i.select,{...a,ref:s,className:b("chakra-select",r),children:[n&&l.jsx("option",{value:"",children:n}),t]})});v.displayName="SelectField";function F(e,o){const s={},t={};for(const[n,r]of Object.entries(e))o.includes(n)?s[n]=r:t[n]=r;return[s,t]}var H=x((e,o)=>{var s;const t=C("Select",e),{rootProps:n,placeholder:r,icon:a,color:c,height:_,h:d,minH:h,minHeight:y,iconColor:p,iconSize:u,...j}=E(e),[g,N]=F(j,P),m=z(N),k={width:"100%",height:"fit-content",position:"relative",color:c},w={paddingEnd:"2rem",...t.field,_focus:{zIndex:"unset",...(s=t.field)==null?void 0:s._focus}};return l.jsxs(i.div,{className:"chakra-select__wrapper",__css:k,...g,...n,children:[l.jsx(v,{ref:o,height:d??_,minH:h??y,placeholder:r,...m,__css:w,children:e.children}),l.jsx(S,{"data-disabled":I(m.disabled),...(p||c)&&{color:p||c},__css:t.icon,...u&&{fontSize:u},children:a})]})});H.displayName="Select";var M=e=>l.jsx("svg",{viewBox:"0 0 24 24",...e,children:l.jsx("path",{fill:"currentColor",d:"M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"})}),R=i("div",{baseStyle:{position:"absolute",display:"inline-flex",alignItems:"center",justifyContent:"center",pointerEvents:"none",top:"50%",transform:"translateY(-50%)"}}),S=e=>{const{children:o=l.jsx(M,{}),...s}=e,t=f.cloneElement(o,{role:"presentation",className:"chakra-select__icon",focusable:!1,"aria-hidden":!0,style:{width:"1em",height:"1em",color:"currentColor"}});return l.jsx(R,{...s,className:"chakra-select__icon-wrapper",children:f.isValidElement(o)?t:null})};S.displayName="SelectIcon";export{H as S};
|
||||
import{f as x,j as l,g as i,e as b,k as C,o as E,l as I,r as f,a3 as P}from"./input.js";import{aa as z}from"./App-Pi18TWng.js";var v=x(function(o,s){const{children:t,placeholder:n,className:r,...a}=o;return l.jsxs(i.select,{...a,ref:s,className:b("chakra-select",r),children:[n&&l.jsx("option",{value:"",children:n}),t]})});v.displayName="SelectField";function F(e,o){const s={},t={};for(const[n,r]of Object.entries(e))o.includes(n)?s[n]=r:t[n]=r;return[s,t]}var H=x((e,o)=>{var s;const t=C("Select",e),{rootProps:n,placeholder:r,icon:a,color:c,height:_,h:d,minH:h,minHeight:y,iconColor:p,iconSize:u,...j}=E(e),[g,N]=F(j,P),m=z(N),k={width:"100%",height:"fit-content",position:"relative",color:c},w={paddingEnd:"2rem",...t.field,_focus:{zIndex:"unset",...(s=t.field)==null?void 0:s._focus}};return l.jsxs(i.div,{className:"chakra-select__wrapper",__css:k,...g,...n,children:[l.jsx(v,{ref:o,height:d??_,minH:h??y,placeholder:r,...m,__css:w,children:e.children}),l.jsx(S,{"data-disabled":I(m.disabled),...(p||c)&&{color:p||c},__css:t.icon,...u&&{fontSize:u},children:a})]})});H.displayName="Select";var M=e=>l.jsx("svg",{viewBox:"0 0 24 24",...e,children:l.jsx("path",{fill:"currentColor",d:"M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"})}),R=i("div",{baseStyle:{position:"absolute",display:"inline-flex",alignItems:"center",justifyContent:"center",pointerEvents:"none",top:"50%",transform:"translateY(-50%)"}}),S=e=>{const{children:o=l.jsx(M,{}),...s}=e,t=f.cloneElement(o,{role:"presentation",className:"chakra-select__icon",focusable:!1,"aria-hidden":!0,style:{width:"1em",height:"1em",color:"currentColor"}});return l.jsx(R,{...s,className:"chakra-select__icon-wrapper",children:f.isValidElement(o)?t:null})};S.displayName="SelectIcon";export{H as S};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{b as D,r as p,j as t,g as i,a0 as v,f as j,l as T,o as B,H as E,h as $}from"./input.js";import{u as z}from"./chunk-7D6N5TE5-lFR77IBa.js";var[Z,F]=D({name:"CheckboxGroupContext",strict:!1});function M(o){const[s,n]=p.useState(o),[e,r]=p.useState(!1);return o!==s&&(r(!0),n(o)),e}function W(o){return t.jsx(i.svg,{width:"1.2em",viewBox:"0 0 12 10",style:{fill:"none",strokeWidth:2,stroke:"currentColor",strokeDasharray:16},...o,children:t.jsx("polyline",{points:"1.5 6 4.5 9 10.5 1"})})}function X(o){return t.jsx(i.svg,{width:"1.2em",viewBox:"0 0 24 24",style:{stroke:"currentColor",strokeWidth:4},...o,children:t.jsx("line",{x1:"21",x2:"3",y1:"12",y2:"12"})})}function H(o){const{isIndeterminate:s,isChecked:n,...e}=o,r=s?X:W;return n||s?t.jsx(i.div,{style:{display:"flex",alignItems:"center",justifyContent:"center",height:"100%"},children:t.jsx(r,{...e})}):null}var L={display:"inline-flex",alignItems:"center",justifyContent:"center",verticalAlign:"top",userSelect:"none",flexShrink:0},O={cursor:"pointer",display:"inline-flex",alignItems:"center",verticalAlign:"top",position:"relative"},q=v({from:{opacity:0,strokeDashoffset:16,transform:"scale(0.95)"},to:{opacity:1,strokeDashoffset:0,transform:"scale(1)"}}),J=v({from:{opacity:0},to:{opacity:1}}),K=v({from:{transform:"scaleX(0.65)"},to:{transform:"scaleX(1)"}}),Q=j(function(s,n){const e=F(),r={...e,...s},a=T("Checkbox",r),c=B(s),{spacing:x="0.5rem",className:C,children:m,iconColor:u,iconSize:d,icon:f=t.jsx(H,{}),isChecked:g,isDisabled:k=e==null?void 0:e.isDisabled,onChange:h,inputProps:w,..._}=c;let y=g;e!=null&&e.value&&c.value&&(y=e.value.includes(c.value));let b=h;e!=null&&e.onChange&&c.value&&(b=E(e.onChange,h));const{state:l,getInputProps:A,getCheckboxProps:S,getLabelProps:G,getRootProps:P}=z({..._,isDisabled:k,isChecked:y,onChange:b}),I=M(l.isChecked),R=p.useMemo(()=>({animation:I?l.isIndeterminate?`${J} 20ms linear, ${K} 200ms linear`:`${q} 200ms linear`:void 0,fontSize:d,color:u,...a.icon}),[u,d,I,l.isIndeterminate,a.icon]),N=p.cloneElement(f,{__css:R,isIndeterminate:l.isIndeterminate,isChecked:l.isChecked});return t.jsxs(i.label,{__css:{...O,...a.container},className:$("chakra-checkbox",C),...P(),children:[t.jsx("input",{className:"chakra-checkbox__input",...A(w,n)}),t.jsx(i.span,{__css:{...L,...a.control},className:"chakra-checkbox__control",...S(),children:N}),m&&t.jsx(i.span,{className:"chakra-checkbox__label",...G(),__css:{marginStart:x,...a.label},children:m})]})});Q.displayName="Checkbox";var U=j(function(s,n){const{templateAreas:e,gap:r,rowGap:a,columnGap:c,column:x,row:C,autoFlow:m,autoRows:u,templateRows:d,autoColumns:f,templateColumns:g,...k}=s,h={display:"grid",gridTemplateAreas:e,gridGap:r,gridRowGap:a,gridColumnGap:c,gridAutoColumns:f,gridColumn:x,gridRow:C,gridAutoFlow:m,gridAutoRows:u,gridTemplateRows:d,gridTemplateColumns:g};return t.jsx(i.div,{ref:n,__css:h,...k})});U.displayName="Grid";export{Q as C,U as G};
|
||||
@@ -0,0 +1 @@
|
||||
import{b as D,r as h,j as t,g as i,$ as v,f as j,k as T,o as $,H as B,e as E}from"./input.js";import{u as z}from"./chunk-7D6N5TE5-ZfhYDM9i.js";var[Z,F]=D({name:"CheckboxGroupContext",strict:!1});function M(o){const[s,n]=h.useState(o),[e,r]=h.useState(!1);return o!==s&&(r(!0),n(o)),e}function W(o){return t.jsx(i.svg,{width:"1.2em",viewBox:"0 0 12 10",style:{fill:"none",strokeWidth:2,stroke:"currentColor",strokeDasharray:16},...o,children:t.jsx("polyline",{points:"1.5 6 4.5 9 10.5 1"})})}function X(o){return t.jsx(i.svg,{width:"1.2em",viewBox:"0 0 24 24",style:{stroke:"currentColor",strokeWidth:4},...o,children:t.jsx("line",{x1:"21",x2:"3",y1:"12",y2:"12"})})}function H(o){const{isIndeterminate:s,isChecked:n,...e}=o,r=s?X:W;return n||s?t.jsx(i.div,{style:{display:"flex",alignItems:"center",justifyContent:"center",height:"100%"},children:t.jsx(r,{...e})}):null}var L={display:"inline-flex",alignItems:"center",justifyContent:"center",verticalAlign:"top",userSelect:"none",flexShrink:0},O={cursor:"pointer",display:"inline-flex",alignItems:"center",verticalAlign:"top",position:"relative"},q=v({from:{opacity:0,strokeDashoffset:16,transform:"scale(0.95)"},to:{opacity:1,strokeDashoffset:0,transform:"scale(1)"}}),J=v({from:{opacity:0},to:{opacity:1}}),K=v({from:{transform:"scaleX(0.65)"},to:{transform:"scaleX(1)"}}),Q=j(function(s,n){const e=F(),r={...e,...s},a=T("Checkbox",r),c=$(s),{spacing:x="0.5rem",className:C,children:m,iconColor:u,iconSize:d,icon:k=t.jsx(H,{}),isChecked:f,isDisabled:g=e==null?void 0:e.isDisabled,onChange:p,inputProps:w,..._}=c;let y=f;e!=null&&e.value&&c.value&&(y=e.value.includes(c.value));let b=p;e!=null&&e.onChange&&c.value&&(b=B(e.onChange,p));const{state:l,getInputProps:A,getCheckboxProps:S,getLabelProps:G,getRootProps:P}=z({..._,isDisabled:g,isChecked:y,onChange:b}),I=M(l.isChecked),R=h.useMemo(()=>({animation:I?l.isIndeterminate?`${J} 20ms linear, ${K} 200ms linear`:`${q} 200ms linear`:void 0,fontSize:d,color:u,...a.icon}),[u,d,I,l.isIndeterminate,a.icon]),N=h.cloneElement(k,{__css:R,isIndeterminate:l.isIndeterminate,isChecked:l.isChecked});return t.jsxs(i.label,{__css:{...O,...a.container},className:E("chakra-checkbox",C),...P(),children:[t.jsx("input",{className:"chakra-checkbox__input",...A(w,n)}),t.jsx(i.span,{__css:{...L,...a.control},className:"chakra-checkbox__control",...S(),children:N}),m&&t.jsx(i.span,{className:"chakra-checkbox__label",...G(),__css:{marginStart:x,...a.label},children:m})]})});Q.displayName="Checkbox";var U=j(function(s,n){const{templateAreas:e,gap:r,rowGap:a,columnGap:c,column:x,row:C,autoFlow:m,autoRows:u,templateRows:d,autoColumns:k,templateColumns:f,...g}=s,p={display:"grid",gridTemplateAreas:e,gridGap:r,gridRowGap:a,gridColumnGap:c,gridAutoColumns:k,gridColumn:x,gridRow:C,gridAutoFlow:m,gridAutoRows:u,gridTemplateRows:d,gridTemplateColumns:f};return t.jsx(i.div,{ref:n,__css:p,...g})});U.displayName="Grid";export{Q as C,U as G};
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{g as t}from"./App-eQybp91e.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};
|
||||
import{g as t}from"./App-Pi18TWng.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
+1
-1
@@ -1 +1 @@
|
||||
import{f as l,p as f,o as h,j as e,g as n,h as m,l as y,r as o}from"./input.js";import{u as w}from"./chunk-7D6N5TE5-lFR77IBa.js";var N=l(function(a,i){const s=f("Link",a),{className:c,isExternal:t,...r}=h(a);return e.jsx(n.a,{target:t?"_blank":void 0,rel:t?"noopener":void 0,ref:i,className:m("chakra-link",c),...r,__css:s})});N.displayName="Link";var j=l(function(a,i){const s=y("Switch",a),{spacing:c="0.5rem",children:t,...r}=h(a),{getIndicatorProps:p,getInputProps:u,getCheckboxProps:x,getRootProps:_,getLabelProps:g}=w(r),b=o.useMemo(()=>({display:"inline-block",position:"relative",verticalAlign:"middle",lineHeight:0,...s.container}),[s.container]),S=o.useMemo(()=>({display:"inline-flex",flexShrink:0,justifyContent:"flex-start",boxSizing:"content-box",cursor:"pointer",...s.track}),[s.track]),d=o.useMemo(()=>({userSelect:"none",marginStart:c,...s.label}),[c,s.label]);return e.jsxs(n.label,{..._(),className:m("chakra-switch",a.className),__css:b,children:[e.jsx("input",{className:"chakra-switch__input",...u({},i)}),e.jsx(n.span,{...x(),className:"chakra-switch__track",__css:S,children:e.jsx(n.span,{__css:s.thumb,className:"chakra-switch__thumb",...p()})}),t&&e.jsx(n.span,{className:"chakra-switch__label",...g(),__css:d,children:t})]})});j.displayName="Switch";export{N as L,j as S};
|
||||
import{f as l,n as f,o as h,j as e,g as n,e as m,k as y,r as o}from"./input.js";import{u as w}from"./chunk-7D6N5TE5-ZfhYDM9i.js";var N=l(function(a,i){const s=f("Link",a),{className:c,isExternal:t,...r}=h(a);return e.jsx(n.a,{target:t?"_blank":void 0,rel:t?"noopener":void 0,ref:i,className:m("chakra-link",c),...r,__css:s})});N.displayName="Link";var j=l(function(a,i){const s=y("Switch",a),{spacing:c="0.5rem",children:t,...r}=h(a),{getIndicatorProps:u,getInputProps:p,getCheckboxProps:x,getRootProps:_,getLabelProps:g}=w(r),b=o.useMemo(()=>({display:"inline-block",position:"relative",verticalAlign:"middle",lineHeight:0,...s.container}),[s.container]),S=o.useMemo(()=>({display:"inline-flex",flexShrink:0,justifyContent:"flex-start",boxSizing:"content-box",cursor:"pointer",...s.track}),[s.track]),d=o.useMemo(()=>({userSelect:"none",marginStart:c,...s.label}),[c,s.label]);return e.jsxs(n.label,{..._(),className:m("chakra-switch",a.className),__css:b,children:[e.jsx("input",{className:"chakra-switch__input",...p({},i)}),e.jsx(n.span,{...x(),className:"chakra-switch__track",__css:S,children:e.jsx(n.span,{__css:s.thumb,className:"chakra-switch__thumb",...u()})}),t&&e.jsx(n.span,{className:"chakra-switch__label",...g(),__css:d,children:t})]})});j.displayName="Switch";export{N as L,j as S};
|
||||
+1
@@ -0,0 +1 @@
|
||||
import{k as m}from"./App-Pi18TWng.js";const r="WORKSPACE_CIVIT_API_KEY_STORAGE_KEY";function g(){return localStorage.getItem(r)}function h(e){localStorage.setItem(r,e)}function v(e){return`https://civitai.com/api/download/models/${e}`}async function w(e){var s,a,n;try{const l=`https://civitai.com/api/v1/model-versions/by-hash/${e}`,t=await(await fetch(l)).json();let i;if(await((s=m)==null?void 0:s.getSetting("showNsfwModelThumbnail"))===!0)i=(n=(a=t==null?void 0:t.images)==null?void 0:a[0])==null?void 0:n.url;else if(!t.model.nsfw){const o=t.images.find(c=>c.nsfwLevel==1);i=o==null?void 0:o.url}return{modelName:t.model.name,civitModelID:String(t.modelId),civitModelVersionID:String(t.id),imageUrl:i??void 0}}catch{return{}}}export{v as a,w as f,g,h as s};
|
||||
Vendored
+2
-2
@@ -679,10 +679,10 @@ Error generating stack: `+i.message+`
|
||||
}
|
||||
|
||||
${aB}
|
||||
`}),zw=document.createElement("div");document.body.append(zw);const lB=Ar.lazy(()=>Xk(()=>import("./App-eQybp91e.js").then(e=>e.aT),__vite__mapDeps([0,1])).then(({default:e})=>({default:e}))),uB={hide:-1,auto:"auto",base:0,docked:10,dropdown:1e7,sticky:11e6,banner:12e6,overlay:13e6,modal:14e6,popover:15e6,skipLink:16e6,toast:17e6,tooltip:18e6},cB=Pz({zIndices:uB,components:{Card:{baseStyle:e=>({container:{backgroundColor:e.colorMode==="dark"?"#171B21":"#F6F8FA"}})}}});gd.createRoot(zw).render(E.jsx(Ar.StrictMode,{children:E.jsxs(BI,{resetCSS:!1,disableGlobalStyle:!0,theme:cB,children:[E.jsx(sB,{scope:".workspace_manager"}),E.jsx(DP,{initialColorMode:"dark"}),E.jsx(iB,{children:E.jsx(S.Suspense,{children:E.jsx(lB,{})})})]})}));const Hf=document.body,dB={attributes:!0,attributeFilter:["class"]},fB=function(e,t){const r=document.documentElement;r.style.colorScheme==="dark"&&r.style.removeProperty("color-scheme");for(const n of e)if(n.type==="attributes"&&n.attributeName==="class")for(const o of Hf.classList)o.includes("chakra")&&Hf.classList.remove(o)},hB=new MutationObserver(fB);hB.observe(Hf,dB);export{Qh as $,PB as A,CB as B,TB as C,_B as D,TP as E,lr as F,oi as G,vB as H,Ya as I,Ap as J,yB as K,Ka as L,yw as M,Ew as N,_w as O,ja as P,Tw as Q,Aw as R,$w as S,Pw as T,Tp as U,xb as V,EB as W,os as X,Ar as Y,pB as Z,Xk as _,kB as a,vP as a0,rB as a1,nB as a2,gB as a3,mn as b,wB as c,i3 as d,Bl as e,pt as f,_e as g,Be as h,Mx as i,E as j,SB as k,Xb as l,If as m,_v as n,Iu as o,Bu as p,mB as q,S as r,Ei as s,Gf as t,IP as u,uw as v,bB as w,Kt as x,yg as y,uO as z};
|
||||
`}),zw=document.createElement("div");document.body.append(zw);const lB=Ar.lazy(()=>Xk(()=>import("./App-Pi18TWng.js").then(e=>e.aJ),__vite__mapDeps([0,1])).then(({default:e})=>({default:e}))),uB={hide:-1,auto:"auto",base:0,docked:10,dropdown:1e7,sticky:11e6,banner:12e6,overlay:13e6,modal:14e6,popover:15e6,skipLink:16e6,toast:17e6,tooltip:18e6},cB=Pz({config:{initialColorMode:"dark",useSystemColorMode:!1},zIndices:uB,components:{Card:{baseStyle:e=>({container:{backgroundColor:e.colorMode==="dark"?"#171B21":"#F6F8FA"}})}}});gd.createRoot(zw).render(E.jsx(Ar.StrictMode,{children:E.jsxs(BI,{resetCSS:!1,disableGlobalStyle:!0,theme:cB,children:[E.jsx(sB,{scope:".workspace_manager"}),E.jsx(DP,{initialColorMode:"dark"}),E.jsx(iB,{children:E.jsx(S.Suspense,{children:E.jsx(lB,{})})})]})}));const Hf=document.body,dB={attributes:!0,attributeFilter:["class"]},fB=function(e,t){const r=document.documentElement;r.style.colorScheme==="dark"&&r.style.removeProperty("color-scheme");for(const n of e)if(n.type==="attributes"&&n.attributeName==="class")for(const o of Hf.classList)o.includes("chakra")&&Hf.classList.remove(o)},hB=new MutationObserver(fB);hB.observe(Hf,dB);export{vP as $,PB as A,CB as B,TB as C,_B as D,TP as E,lr as F,oi as G,vB as H,Ya as I,Ap as J,yB as K,Ka as L,yw as M,Ew as N,_w as O,ja as P,Tw as Q,Aw as R,$w as S,Pw as T,xb as U,EB as V,os as W,Ar as X,pB as Y,Qh as Z,Xk as _,kB as a,Tp as a0,rB as a1,nB as a2,gB as a3,mn as b,wB as c,i3 as d,Be as e,pt as f,_e as g,Mx as h,SB as i,E as j,Xb as k,_v as l,If as m,Bu as n,Iu as o,mB as p,Ei as q,S as r,Gf as s,uw as t,IP as u,bB as v,Kt as w,yg as x,Bl as y,uO as z};
|
||||
function __vite__mapDeps(indexes) {
|
||||
if (!__vite__mapDeps.viteFileDeps) {
|
||||
__vite__mapDeps.viteFileDeps = ["workspace_web/App-eQybp91e.js","workspace_web/assets/App-JXePnJiV.css"]
|
||||
__vite__mapDeps.viteFileDeps = ["workspace_web/App-Pi18TWng.js","workspace_web/assets/App-JXePnJiV.css"]
|
||||
}
|
||||
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
send2trash
|
||||
@@ -9,7 +9,7 @@ from .model_manager.model_preview import get_thumbnail_for_image_file
|
||||
|
||||
image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp']
|
||||
video_extensions = ['.mp4', '.mov', '.avi', '.webm', '.mkv']
|
||||
API_URL = 'https://www.nodecafe.org/api/image/upload'
|
||||
API_URL = 'https://www.nodecafe.co/api/image/upload'
|
||||
def view_media(filename, isPreview = False, isInput = False):
|
||||
if not filename:
|
||||
return web.Response(status=404)
|
||||
|
||||
@@ -5,6 +5,10 @@ import os
|
||||
from pathlib import Path
|
||||
import server
|
||||
from .db_service import get_my_workflows_dir
|
||||
try:
|
||||
from send2trash import send2trash
|
||||
except ImportError:
|
||||
send2trash = None
|
||||
|
||||
@server.PromptServer.instance.routes.post('/workspace/folder/create')
|
||||
async def create_folder(request):
|
||||
@@ -31,7 +35,11 @@ def delete_folder_sync(reqJson):
|
||||
folder_path = reqJson.get('path')
|
||||
folder_path = os.path.join(get_my_workflows_dir(), folder_path)
|
||||
try:
|
||||
shutil.rmtree(folder_path)
|
||||
if send2trash:
|
||||
send2trash(folder_path)
|
||||
else:
|
||||
shutil.rmtree(folder_path)
|
||||
print("❌⛔️send2trash is not available. Deleting file permanently. Please `pip install send2trash`")
|
||||
return {"success": True}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
+39
-41
@@ -157,7 +157,7 @@ export default function App() {
|
||||
if (id) {
|
||||
const flow = await workflowsTable?.get(id);
|
||||
flow && setCurFlowIDAndName(flow);
|
||||
if (flow && flow.json != latestWf) {
|
||||
if (!flow?.saveLock && flow && flow.json != latestWf) {
|
||||
setIsDirty(true);
|
||||
}
|
||||
}
|
||||
@@ -167,47 +167,45 @@ export default function App() {
|
||||
}
|
||||
fetchApi("/workspace/deduplicate_workflow_ids");
|
||||
const twoway = await userSettingsTable?.getSetting("twoWaySync");
|
||||
!twoway &&
|
||||
indexdb.cache.get(UPGRADE_TO_2WAY_SYNC_KEY).then(async (value) => {
|
||||
if (value?.value !== "true") {
|
||||
const workflowsCount = await indexdb.workflows.count();
|
||||
if (workflowsCount == 0) {
|
||||
// empty workflows, enable 2way sync
|
||||
await userSettingsTable?.upsert({ twoWaySync: true });
|
||||
await indexdb.cache.put({
|
||||
id: UPGRADE_TO_2WAY_SYNC_KEY,
|
||||
value: "true",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const myWorkflowsDir =
|
||||
await userSettingsTable?.getSetting("myWorkflowsDir");
|
||||
showDialog(
|
||||
<EnableTwowaySyncConfirm
|
||||
myWorkflowsDir={myWorkflowsDir ?? "undefined"}
|
||||
/>,
|
||||
[
|
||||
{
|
||||
label: "I have downloaded all my workflows and ready to enable",
|
||||
onClick: async () => {
|
||||
await userSettingsTable?.upsert({ twoWaySync: true });
|
||||
if (await userSettingsTable?.getSetting("twoWaySync")) {
|
||||
indexdb.cache.put({
|
||||
id: UPGRADE_TO_2WAY_SYNC_KEY,
|
||||
value: "true",
|
||||
});
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
colorScheme: "red",
|
||||
},
|
||||
],
|
||||
{
|
||||
closeOnOverlayClick: false,
|
||||
|
||||
if (!twoway) {
|
||||
const workflowsCount = await indexdb.workflows.count();
|
||||
if (workflowsCount == 0) {
|
||||
// empty workflows, enable 2way sync
|
||||
await userSettingsTable?.upsert({ twoWaySync: true });
|
||||
await indexdb.cache.put({
|
||||
id: UPGRADE_TO_2WAY_SYNC_KEY,
|
||||
value: "true",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const myWorkflowsDir =
|
||||
await userSettingsTable?.getSetting("myWorkflowsDir");
|
||||
showDialog(
|
||||
<EnableTwowaySyncConfirm
|
||||
myWorkflowsDir={myWorkflowsDir ?? "undefined"}
|
||||
/>,
|
||||
[
|
||||
{
|
||||
label: "I have downloaded all my workflows and ready to enable",
|
||||
onClick: async () => {
|
||||
await userSettingsTable?.upsert({ twoWaySync: true });
|
||||
if (await userSettingsTable?.getSetting("twoWaySync")) {
|
||||
indexdb.cache.put({
|
||||
id: UPGRADE_TO_2WAY_SYNC_KEY,
|
||||
value: "true",
|
||||
});
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
colorScheme: "red",
|
||||
},
|
||||
],
|
||||
{
|
||||
closeOnOverlayClick: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const loadWorkflowIDImpl = async (id: string, versionID?: string | null) => {
|
||||
|
||||
@@ -44,7 +44,6 @@ import CreateVersionDialog from "./CreateVersionDialog";
|
||||
import HoverMenu from "./HoverMenu";
|
||||
const ShareDialog = lazy(() => import("../share/ShareDialog"));
|
||||
import { TOPBAR_BUTTON_HEIGHT } from "../const";
|
||||
import DownloadSpaceJsonDialog from "../spacejson/DownloadSpaceJsonDialog";
|
||||
import { downloadJsonFile } from "../utils/downloadJsonFile";
|
||||
import { SharedTopbarButton } from "../share/SharedTopbarButton";
|
||||
import { app } from "../utils/comfyapp";
|
||||
@@ -186,13 +185,6 @@ export default function DropdownTitle() {
|
||||
>
|
||||
Versions History
|
||||
</MenuItem>
|
||||
{/* <MenuItem
|
||||
onClick={() => setRoute("downloadSpaceJson")}
|
||||
icon={<IconDownload size={20} />}
|
||||
iconSpacing={1}
|
||||
>
|
||||
Save runnable workflow🧪
|
||||
</MenuItem> */}
|
||||
<MenuItem
|
||||
onClick={() => setRoute("share")}
|
||||
icon={<IconShare2 size={20} />}
|
||||
@@ -212,7 +204,6 @@ export default function DropdownTitle() {
|
||||
{route == "versionHistory" && (
|
||||
<VersionHistoryDrawer onClose={() => setRoute("root")} />
|
||||
)}
|
||||
{route == "downloadSpaceJson" && <DownloadSpaceJsonDialog />}
|
||||
{route === "saveAsModal" && (
|
||||
<Modal isOpen={true} onClose={handleOnCloseModal}>
|
||||
<ModalOverlay />
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function EditFlowName({
|
||||
as="div"
|
||||
color="white"
|
||||
onClick={startEdit}
|
||||
maxW={200}
|
||||
maxW={160}
|
||||
fontWeight={500}
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
|
||||
@@ -44,9 +44,8 @@ export class ChangelogsTable extends TableBase<Changelog> {
|
||||
createTime: Date.now(),
|
||||
isAutoSave: input.isAutoSave,
|
||||
};
|
||||
await this.deleteLogsExceedLimit(input.workflowID);
|
||||
await indexdb.changelogs.add(change);
|
||||
// this.saveDiskDB(); // its not that important to save to disk for changelog table
|
||||
await this.deleteLogsExceedLimit(input.workflowID);
|
||||
return change;
|
||||
}
|
||||
async deleteLogsExceedLimit(workflowID: string) {
|
||||
|
||||
@@ -1,59 +1,6 @@
|
||||
import { deleteFile, updateFile } from "../Api";
|
||||
import { foldersTable, workflowsTable, userSettingsTable } from "./WorkspaceDB";
|
||||
import {
|
||||
COMFYSPACE_TRACKING_FIELD_NAME,
|
||||
LEGACY_COMFYSPACE_TRACKING_FIELD_NAME,
|
||||
} from "../const";
|
||||
import { toFileNameFriendly } from "../utils";
|
||||
import { Folder, Workflow } from "../types/dbTypes";
|
||||
import { TwowayFolderSyncAPI } from "../apis/TwowaySyncFolderApi";
|
||||
|
||||
export async function saveJsonFileMyWorkflows(workflow: Workflow) {
|
||||
const file_path = await generateFilePath(workflow);
|
||||
if (file_path == null) {
|
||||
return;
|
||||
}
|
||||
const fullPath = await generateFilePathAbsolute(workflow);
|
||||
await workflowsTable?.updateMetaInfo(workflow.id, {
|
||||
filePath: fullPath ?? undefined,
|
||||
});
|
||||
const json = workflow.json;
|
||||
const flow = JSON.parse(json);
|
||||
flow.extra[COMFYSPACE_TRACKING_FIELD_NAME] = {
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
};
|
||||
delete flow.extra[LEGACY_COMFYSPACE_TRACKING_FIELD_NAME];
|
||||
|
||||
await updateFile(file_path, JSON.stringify(flow));
|
||||
}
|
||||
|
||||
export async function deleteJsonFileMyWorkflows(workflow: Workflow) {
|
||||
if (workflow.name == null) {
|
||||
return;
|
||||
}
|
||||
const file_path = await generateFilePath(workflow);
|
||||
file_path != null && deleteFile(file_path);
|
||||
}
|
||||
|
||||
export async function generateFilePath(
|
||||
workflow: Workflow,
|
||||
): Promise<string | null> {
|
||||
let filePath = toFileNameFriendly(workflow.name) + ".json";
|
||||
let curFolderID = workflow.parentFolderID;
|
||||
while (curFolderID != null) {
|
||||
const folder = await foldersTable?.get(curFolderID);
|
||||
if (folder == null) {
|
||||
break;
|
||||
}
|
||||
const folderName = folder.name;
|
||||
filePath = `${folderName}/${filePath}`;
|
||||
curFolderID = folder.parentFolderID ?? undefined;
|
||||
}
|
||||
|
||||
return filePath ?? null;
|
||||
}
|
||||
|
||||
export async function genFolderRelPath(
|
||||
folderId: string | null,
|
||||
): Promise<string> {
|
||||
@@ -71,46 +18,6 @@ export async function genFolderRelPath(
|
||||
|
||||
return filePath;
|
||||
}
|
||||
export async function generateFilePathAbsolute(
|
||||
workflow: Workflow,
|
||||
): Promise<string | null> {
|
||||
const subPath = await generateFilePath(workflow);
|
||||
let myWorkflowsDir = await userSettingsTable?.getSetting("myWorkflowsDir");
|
||||
if (!myWorkflowsDir) {
|
||||
console.error("myWorkflowsDir is not set");
|
||||
return null;
|
||||
}
|
||||
if (!myWorkflowsDir.endsWith("/")) {
|
||||
myWorkflowsDir = myWorkflowsDir + "/";
|
||||
}
|
||||
return myWorkflowsDir + subPath;
|
||||
}
|
||||
|
||||
export async function generateFolderPath(id: string): Promise<string | null> {
|
||||
const folder = await foldersTable?.get(id);
|
||||
let parentFolderID = folder?.parentFolderID;
|
||||
let folderPath = folder?.name;
|
||||
while (parentFolderID) {
|
||||
const folder = await foldersTable?.get(parentFolderID);
|
||||
if (folder == null) {
|
||||
break;
|
||||
}
|
||||
folderPath = `${folder.name}/${folderPath}`;
|
||||
parentFolderID = folder.parentFolderID ?? undefined;
|
||||
}
|
||||
|
||||
let myWorkflowsDir = await userSettingsTable?.getSetting("myWorkflowsDir");
|
||||
|
||||
if (!myWorkflowsDir) {
|
||||
console.error("myWorkflowsDir is not set");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!myWorkflowsDir.endsWith("/")) {
|
||||
myWorkflowsDir = myWorkflowsDir + "/";
|
||||
}
|
||||
return myWorkflowsDir + folderPath;
|
||||
}
|
||||
|
||||
export async function getFileCountInFolder(folderId: string): Promise<number> {
|
||||
const twoWaySyncEnabled = await userSettingsTable?.getSetting("twoWaySync");
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Table, workflowsTable } from "./WorkspaceDB";
|
||||
import { TableBase } from "./TableBase";
|
||||
import { Media } from "../types/dbTypes";
|
||||
import { indexdb } from "./indexdb";
|
||||
import { getMetadataFromUrl } from "../gallery/utils.ts";
|
||||
|
||||
export class MediaTable extends TableBase<Media> {
|
||||
static readonly TABLE_NAME: Table = "media";
|
||||
@@ -25,20 +24,11 @@ export class MediaTable extends TableBase<Media> {
|
||||
const format = input.localPath.split(".").pop();
|
||||
if (format == null) return null;
|
||||
|
||||
const res = await getMetadataFromUrl(
|
||||
`/workspace/view_media?filename=${input.localPath}`,
|
||||
).catch((e) => {
|
||||
console.error("get meta error:", e);
|
||||
return { prompt: "" };
|
||||
});
|
||||
|
||||
const md: Media = {
|
||||
id: input.localPath,
|
||||
localPath: input.localPath,
|
||||
workflowID: input.workflowID,
|
||||
createTime: Date.now(),
|
||||
workflowJSON: JSON.stringify(res?.prompt),
|
||||
format: format,
|
||||
};
|
||||
await workflowsTable?.updateMetaInfo(input.workflowID, {
|
||||
latestImage: md.localPath,
|
||||
|
||||
@@ -43,7 +43,7 @@ export class UserSettingsTable extends TableBase<UserSettings> {
|
||||
autoSaveDuration: 3,
|
||||
twoWaySync: false,
|
||||
foldersOnTop: false,
|
||||
cloudHost: "https://www.nodecafe.org",
|
||||
cloudHost: "https://www.nodecafe.co",
|
||||
overwriteCurWorkflowWhenDroppingFileToCanvas: false,
|
||||
maximumChangelogNumber: 80,
|
||||
hideCoverImage: false,
|
||||
|
||||
@@ -8,10 +8,6 @@ import { sortFileItem } from "../utils";
|
||||
import { nanoid } from "nanoid";
|
||||
import { TableBase } from "./TableBase";
|
||||
import { indexdb } from "./indexdb";
|
||||
import {
|
||||
deleteJsonFileMyWorkflows,
|
||||
saveJsonFileMyWorkflows,
|
||||
} from "./DiskFileUtils";
|
||||
import { ESortTypes, ImportWorkflow } from "../RecentFilesDrawer/types";
|
||||
import { defaultGraph } from "../defaultGraph";
|
||||
import { scanMyWorkflowsDir } from "../utils/twowaySyncUtils";
|
||||
@@ -51,11 +47,7 @@ export class WorkflowsTable extends TableBase<Workflow> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the currently opened workflow is the latest version and is consistent with the DB.
|
||||
* Through updateTime comparison, if it is inconsistent, it means that a newer version already exists in the DB.
|
||||
* If the automatic save function is turned on at this time, the latest version in the DB will be overwritten.
|
||||
* Therefore, the user needs to confirm whether to continue to enable automatic saving.
|
||||
* @returns boolean
|
||||
* Check whether the currently opened workflow is the latest version and is consistent with the DB
|
||||
*/
|
||||
public async latestVersionCheck() {
|
||||
if (this._curWorkflow) {
|
||||
@@ -146,8 +138,6 @@ export class WorkflowsTable extends TableBase<Workflow> {
|
||||
if (twoWaySyncEnabled) {
|
||||
await TwowaySyncAPI.createWorkflow(newWorkflow);
|
||||
return await this.get(newWorkflow.id);
|
||||
} else {
|
||||
saveJsonFileMyWorkflows(newWorkflow);
|
||||
}
|
||||
return newWorkflow;
|
||||
}
|
||||
@@ -197,15 +187,6 @@ export class WorkflowsTable extends TableBase<Workflow> {
|
||||
}
|
||||
if (twoWaySyncEnabled) {
|
||||
await TwowaySyncAPI.moveWorkflow(oldWorkflow, change.parentFolderID!);
|
||||
} else {
|
||||
await saveJsonFileMyWorkflows(newWorkflow);
|
||||
await deleteJsonFileMyWorkflows(oldWorkflow);
|
||||
// update parent folder updateTime
|
||||
if (newWorkflow?.parentFolderID != null) {
|
||||
await indexdb.folders?.update(newWorkflow.parentFolderID, {
|
||||
updateTime: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
public async genLastManualSavedJson(id: string) {
|
||||
@@ -240,8 +221,6 @@ export class WorkflowsTable extends TableBase<Workflow> {
|
||||
const twoWaySyncEnabled = await userSettingsTable?.getSetting("twoWaySync");
|
||||
if (twoWaySyncEnabled) {
|
||||
after && (await TwowaySyncAPI.saveWorkflow(after));
|
||||
} else {
|
||||
after && (await saveJsonFileMyWorkflows(after));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,8 +273,6 @@ export class WorkflowsTable extends TableBase<Workflow> {
|
||||
} catch (e) {
|
||||
console.error("batchCreateFlows error", e);
|
||||
}
|
||||
} else {
|
||||
await saveJsonFileMyWorkflows(newWorkflow);
|
||||
}
|
||||
}
|
||||
try {
|
||||
@@ -314,8 +291,6 @@ export class WorkflowsTable extends TableBase<Workflow> {
|
||||
await userSettingsTable?.getSetting("twoWaySync");
|
||||
if (twoWaySyncEnabled) {
|
||||
await TwowaySyncAPI.deleteWorkflow(workflow);
|
||||
} else {
|
||||
deleteJsonFileMyWorkflows({ ...workflow });
|
||||
}
|
||||
}
|
||||
//add to IndexDB
|
||||
@@ -331,8 +306,6 @@ export class WorkflowsTable extends TableBase<Workflow> {
|
||||
await userSettingsTable?.getSetting("twoWaySync");
|
||||
if (twoWaySyncEnabled) {
|
||||
await TwowaySyncAPI.deleteWorkflow(flow);
|
||||
} else {
|
||||
deleteJsonFileMyWorkflows({ ...flow });
|
||||
}
|
||||
}
|
||||
await indexdb.workflows.bulkDelete(ids);
|
||||
|
||||
@@ -35,6 +35,10 @@ const zIndices = {
|
||||
};
|
||||
|
||||
const theme = extendTheme({
|
||||
config: {
|
||||
initialColorMode: "dark",
|
||||
useSystemColorMode: false,
|
||||
},
|
||||
zIndices,
|
||||
components: {
|
||||
Card: {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useEffect, useState } from "react";
|
||||
import { COMFYSPACE_TRACKING_FIELD_NAME } from "../../const";
|
||||
import type {
|
||||
DepsResult,
|
||||
ModelFile,
|
||||
ModelDep,
|
||||
} from "../../spacejson/handleDownloadSpaceJson";
|
||||
import { indexdb } from "../../db-tables/indexdb";
|
||||
import { app } from "../../utils/comfyapp";
|
||||
@@ -32,8 +32,8 @@ export interface MissingModel {
|
||||
|
||||
interface Props {
|
||||
model: MissingModel;
|
||||
setSelectedModelDeps: (deps: ModelFile[]) => void;
|
||||
selectedModelDeps: ModelFile[];
|
||||
setSelectedModelDeps: (deps: ModelDep[]) => void;
|
||||
selectedModelDeps: ModelDep[];
|
||||
foldersList: Record<string, string[]>;
|
||||
setFoldersList: (folders: Record<string, string[]>) => void;
|
||||
}
|
||||
@@ -46,7 +46,7 @@ export default function MissingModelItem({
|
||||
setFoldersList,
|
||||
}: Props) {
|
||||
const [suggestedModel, setSuggestedModel] = useState<
|
||||
ModelFile | { existingModel: string }
|
||||
ModelDep | { existingModel: string }
|
||||
>();
|
||||
const modelDeps: DepsResult["models"] =
|
||||
app.graph.extra?.[COMFYSPACE_TRACKING_FIELD_NAME]?.deps?.models;
|
||||
@@ -76,7 +76,7 @@ export default function MissingModelItem({
|
||||
|
||||
const selectedModelNames = selectedModelDeps.map((dep) => dep.filename);
|
||||
function isExistingModel(
|
||||
model: ModelFile | { existingModel: string },
|
||||
model: ModelDep | { existingModel: string },
|
||||
): model is { existingModel: string } {
|
||||
return (model as { existingModel: string }).existingModel != null;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { useCallback, useContext, useEffect, useState } from "react";
|
||||
import MissingModelItem from "./MissingModelItem";
|
||||
import { DRAWER_Z_INDEX } from "../../const";
|
||||
import type { ModelFile } from "../../spacejson/handleDownloadSpaceJson";
|
||||
import type { ModelDep } from "../../spacejson/handleDownloadSpaceJson";
|
||||
import { getAllFoldersList } from "../../Api";
|
||||
import { installModelsApi } from "../api/modelsApi";
|
||||
import { getCivitApiKey } from "../../utils/civitUtils";
|
||||
@@ -40,7 +40,7 @@ export default function MissingModelsListDrawer({
|
||||
app.canvasEl.removeEventListener("click", onClose);
|
||||
};
|
||||
}, []);
|
||||
const [selectedModelDeps, setSelectedModelDeps] = useState<ModelFile[]>([]);
|
||||
const [selectedModelDeps, setSelectedModelDeps] = useState<ModelDep[]>([]);
|
||||
const [foldersList, setFoldersList] = useState<Record<string, string[]>>({});
|
||||
const { setRoute } = useContext(WorkspaceContext);
|
||||
const loadData = useCallback(async () => {
|
||||
@@ -77,7 +77,7 @@ export default function MissingModelsListDrawer({
|
||||
}
|
||||
installModelsApi({
|
||||
file_hash: model.fileHash ?? undefined,
|
||||
filename: model.filename,
|
||||
filename: model.name,
|
||||
save_path: foldersList[model.fileFolder][0],
|
||||
url,
|
||||
force_filename: true,
|
||||
|
||||
@@ -13,10 +13,6 @@ import type { ModelsListRespItemFromApi } from "../types";
|
||||
import InatallModelsModal from "../install-models/InstallModelsModal";
|
||||
import { TOPBAR_BUTTON_HEIGHT } from "../../const";
|
||||
|
||||
const AddMissingModelsButton = lazy(
|
||||
() => import("./InstallMissingModelsButton"),
|
||||
);
|
||||
|
||||
export default function ModelManagerTopbar() {
|
||||
const { setRoute, route } = useContext(WorkspaceContext);
|
||||
|
||||
@@ -75,11 +71,6 @@ export default function ModelManagerTopbar() {
|
||||
>
|
||||
Models
|
||||
</Button>
|
||||
<div style={{ position: "absolute", top: "38px", left: "0px" }}>
|
||||
<Suspense>
|
||||
<AddMissingModelsButton />
|
||||
</Suspense>
|
||||
</div>
|
||||
{route === "modelList" && (
|
||||
<ModelsListDrawer onClose={() => setRoute("root")} />
|
||||
)}
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
import {
|
||||
Input,
|
||||
Stack,
|
||||
useToast,
|
||||
Text,
|
||||
HStack,
|
||||
Flex,
|
||||
Button,
|
||||
} from "@chakra-ui/react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Input, Stack, useToast, Text, Flex, Button } from "@chakra-ui/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { userSettingsTable } from "../db-tables/WorkspaceDB";
|
||||
|
||||
export default function CloudHostSetting() {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const toast = useToast();
|
||||
const [text, setText] = useState("");
|
||||
|
||||
const getSettings = () => {
|
||||
userSettingsTable?.getSetting("cloudHost").then((res) => {
|
||||
inputRef.current?.setAttribute("value", res ?? "");
|
||||
setText(res ?? "");
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getSettings();
|
||||
}, []);
|
||||
const submitChange = async (text: string) => {
|
||||
await userSettingsTable?.upsert({ cloudHost: text });
|
||||
getSettings();
|
||||
toast({
|
||||
title: "Setting saved. Please refresh to see the changes.",
|
||||
status: "success",
|
||||
duration: 4000,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
@@ -30,26 +32,16 @@ export default function CloudHostSetting() {
|
||||
</Text>
|
||||
<Flex gap={2} alignItems={"center"}>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
onBlur={() => {
|
||||
console.log("onBlur", inputRef.current?.value);
|
||||
userSettingsTable?.upsert({ cloudHost: inputRef.current?.value });
|
||||
getSettings();
|
||||
toast({
|
||||
title: "Setting saved. Please refresh to see the changes.",
|
||||
status: "success",
|
||||
duration: 4000,
|
||||
});
|
||||
}}
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onBlur={() => submitChange(text)}
|
||||
/>
|
||||
<Button
|
||||
size={"sm"}
|
||||
onClick={() => {
|
||||
inputRef.current?.setAttribute(
|
||||
"value",
|
||||
userSettingsTable?.defaultSettings.cloudHost!,
|
||||
);
|
||||
inputRef.current?.blur();
|
||||
const cloudHost = userSettingsTable?.defaultSettings.cloudHost!;
|
||||
setText(cloudHost);
|
||||
submitChange(cloudHost);
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
|
||||
+97
-100
@@ -10,7 +10,6 @@ import {
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Stack,
|
||||
Switch,
|
||||
@@ -19,14 +18,10 @@ import {
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { WorkflowPrivacy, WorkflowVersion } from "../types/dbTypes";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import CustomSelector from "../components/CustomSelector";
|
||||
import { IconCopy, IconExternalLink } from "@tabler/icons-react";
|
||||
import {
|
||||
userSettingsTable,
|
||||
workflowVersionsTable,
|
||||
workflowsTable,
|
||||
} from "../db-tables/WorkspaceDB";
|
||||
import { userSettingsTable, workflowsTable } from "../db-tables/WorkspaceDB";
|
||||
// @ts-ignore
|
||||
import { api } from "/scripts/api.js";
|
||||
import {
|
||||
@@ -37,18 +32,30 @@ import {
|
||||
privacyOptions,
|
||||
} from "./shareUtils";
|
||||
|
||||
import ShareDialogWorkflowVersionRadio from "./ShareDialogWorkflowVersionRadio";
|
||||
import ResourceDepsForm from "../spacejson/ResourceDepsForm";
|
||||
import { app } from "../utils/comfyapp";
|
||||
import { useStateRef } from "../customHooks/useStateRef";
|
||||
import { nanoid } from "nanoid";
|
||||
import { WorkspaceContext } from "../WorkspaceContext";
|
||||
import {
|
||||
DepsResult,
|
||||
WorkspaceInfoDeps,
|
||||
extractAndFetchFileNames,
|
||||
} from "../spacejson/handleDownloadSpaceJson";
|
||||
import { fetchApi } from "../Api";
|
||||
import { COMFYSPACE_TRACKING_FIELD_NAME } from "../const";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function ShareDialog({ onClose }: Props) {
|
||||
const [versionName, setVersionName] = useState("v" + getCurDateString());
|
||||
const [localVersions, setLocalVersions] = useState<WorkflowVersion[]>([]);
|
||||
const [versionName, setVersionName, versionNameRef] = useStateRef(
|
||||
"v" + getCurDateString(),
|
||||
);
|
||||
const { saveCurWorkflow } = useContext(WorkspaceContext);
|
||||
const [deps, setDeps] = useState<DepsResult>();
|
||||
const [uploadingImage, setUploadingImage] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const cloudHost = userSettingsTable?.settings?.cloudHost;
|
||||
const [privacy, setPrivacy, privacyRef] =
|
||||
@@ -56,10 +63,8 @@ export default function ShareDialog({ onClose }: Props) {
|
||||
const [selectedVersion, setSelectedVersion] = useState<
|
||||
string | "new_version"
|
||||
>("new_version");
|
||||
const cloudHostRef = useRef("");
|
||||
const workflow = workflowsTable?.curWorkflow;
|
||||
const toast = useToast();
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [enableDeps, setEnableDeps] = useState(true);
|
||||
const handleShareWorkflowSuccess = async (event: MessageEvent) => {
|
||||
const detail = event.data;
|
||||
@@ -71,7 +76,6 @@ export default function ShareDialog({ onClose }: Props) {
|
||||
}
|
||||
|
||||
const localID = detail.localWorkflowID;
|
||||
const localVerID = detail.localVersionID;
|
||||
const cloudVersionID = detail.version.id;
|
||||
const cloudID = detail.version.workflowID;
|
||||
|
||||
@@ -79,56 +83,34 @@ export default function ShareDialog({ onClose }: Props) {
|
||||
localID &&
|
||||
(await workflowsTable?.updateMetaInfo(localID, {
|
||||
cloudID: cloudID,
|
||||
cloudOrigin: event.origin,
|
||||
privacy: privacyRef.current,
|
||||
}));
|
||||
localVerID &&
|
||||
cloudVersionID &&
|
||||
(await workflowVersionsTable?.update(localVerID, {
|
||||
cloudID: cloudVersionID,
|
||||
cloudOrigin: event.origin,
|
||||
}));
|
||||
|
||||
loadData();
|
||||
window.open(
|
||||
cloudHostRef.current + "/workflow/" + cloudID + "/" + cloudVersionID,
|
||||
cloudHost + "/workflow/" + cloudID + "/" + cloudVersionID,
|
||||
"_blank",
|
||||
);
|
||||
setLoading(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
const graph = app.graph.serialize();
|
||||
extractAndFetchFileNames(graph.nodes ?? []).then((depsRes) => {
|
||||
setDeps(depsRes);
|
||||
});
|
||||
window.addEventListener("message", handleShareWorkflowSuccess);
|
||||
const shareDepsToCloud = (event: any) => {
|
||||
if (event.detail) {
|
||||
pushToCloud(event.detail ? JSON.stringify(event.detail) : undefined);
|
||||
}
|
||||
};
|
||||
window.addEventListener("workspace_info_deps_updated", shareDepsToCloud);
|
||||
return () => {
|
||||
window.removeEventListener("message", handleShareWorkflowSuccess);
|
||||
window.removeEventListener(
|
||||
"workspace_info_deps_updated",
|
||||
shareDepsToCloud,
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const loadData = async () => {
|
||||
const host =
|
||||
import.meta.env.MODE === "production"
|
||||
? await userSettingsTable?.getSetting("cloudHost")
|
||||
: "http://localhost:3000";
|
||||
if (host) {
|
||||
cloudHostRef.current = host;
|
||||
}
|
||||
if (workflow?.cloudID && workflow.cloudOrigin) {
|
||||
fetchCloudWorkflowPrivacy(workflow).then((privacy) => {
|
||||
setPrivacy(privacy);
|
||||
});
|
||||
}
|
||||
workflowVersionsTable?.listByWorkflowID(workflow!.id).then((vers) => {
|
||||
setLocalVersions(vers);
|
||||
});
|
||||
};
|
||||
const copyTextToClipboard = (text: string) => {
|
||||
navigator.clipboard
|
||||
@@ -145,7 +127,24 @@ export default function ShareDialog({ onClose }: Props) {
|
||||
console.error("Failed to copy text: ", err);
|
||||
});
|
||||
};
|
||||
const pushToCloud = async (jsonToShare?: string) => {
|
||||
const pushToCloud = async (imageDeps?: DepsResult["images"]) => {
|
||||
const graph = app.graph.serialize();
|
||||
(graph.extra ||= {})[COMFYSPACE_TRACKING_FIELD_NAME] ||= {};
|
||||
graph.extra[COMFYSPACE_TRACKING_FIELD_NAME].deps = {
|
||||
models: deps?.models ?? {},
|
||||
images: imageDeps ?? {},
|
||||
nodeRepos: deps?.nodeRepos ?? [],
|
||||
} as WorkspaceInfoDeps;
|
||||
|
||||
await saveCurWorkflow();
|
||||
|
||||
const apiPrompt = await app
|
||||
.graphToPrompt(app.graph)
|
||||
.then((data: { output: any; workflow: any }) => {
|
||||
graph.extra[COMFYSPACE_TRACKING_FIELD_NAME].apiPrompt = data.output;
|
||||
return data.output;
|
||||
});
|
||||
const jsonToShare = JSON.stringify(graph);
|
||||
setLoading(true);
|
||||
const secretKey = generateRandomKey(32);
|
||||
const host =
|
||||
@@ -153,30 +152,15 @@ export default function ShareDialog({ onClose }: Props) {
|
||||
? ((await userSettingsTable?.getSetting("cloudHost")) as string)
|
||||
: "http://localhost:3000";
|
||||
let nodeDefs: Object;
|
||||
let version: WorkflowVersion | undefined;
|
||||
try {
|
||||
nodeDefs = getNodeDefs();
|
||||
if (selectedVersion === "new_version") {
|
||||
version = await workflowVersionsTable?.add({
|
||||
workflowID: workflow!.id,
|
||||
name: versionName,
|
||||
createTime: Date.now(),
|
||||
json: jsonToShare ?? JSON.stringify(app.graph.serialize()),
|
||||
});
|
||||
} else {
|
||||
version = await workflowVersionsTable?.get(selectedVersion);
|
||||
}
|
||||
if (!version) {
|
||||
alert(`Failed to find version: ${selectedVersion}, please try again.`);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setLoading(false);
|
||||
alert("Failed to share workflow, please try again.");
|
||||
return;
|
||||
}
|
||||
nodeDefs = getNodeDefs();
|
||||
const version: WorkflowVersion = {
|
||||
id: nanoid(),
|
||||
workflowID: workflow!.id,
|
||||
name: versionNameRef.current,
|
||||
createTime: Date.now(),
|
||||
api_prompt: JSON.stringify(apiPrompt),
|
||||
json: jsonToShare ?? JSON.stringify(app.graph.serialize()),
|
||||
};
|
||||
const protocol = window.location.protocol;
|
||||
const baseHost = window.location.host;
|
||||
const baseUrl = `${protocol}//${baseHost}`;
|
||||
@@ -205,12 +189,33 @@ export default function ShareDialog({ onClose }: Props) {
|
||||
};
|
||||
window.addEventListener("message", handleChildReady);
|
||||
};
|
||||
|
||||
const onShare = async () => {
|
||||
const imageDeps = deps?.images ?? {};
|
||||
if (enableDeps) {
|
||||
formRef.current?.requestSubmit();
|
||||
} else {
|
||||
pushToCloud();
|
||||
// upload images
|
||||
setUploadingImage(true);
|
||||
const imageDepsToUpload = Object.values(deps?.images ?? {})
|
||||
// .filter((i) => !i.url)
|
||||
.map((i) => i.filename);
|
||||
if (imageDepsToUpload.length) {
|
||||
const uploadResp = await fetchApi("/workspace/upload_image", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
images: imageDepsToUpload,
|
||||
}),
|
||||
});
|
||||
const json = (await uploadResp.json()) as Record<string, string>;
|
||||
Object.keys(json).forEach((key) => {
|
||||
if (imageDeps?.[key]) {
|
||||
imageDeps[key].url = json[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
setUploadingImage(false);
|
||||
}
|
||||
|
||||
pushToCloud(imageDeps);
|
||||
};
|
||||
|
||||
const cloudWorkflowID = workflowsTable?.curWorkflow?.cloudID;
|
||||
@@ -284,7 +289,6 @@ export default function ShareDialog({ onClose }: Props) {
|
||||
</Button>
|
||||
</HStack>
|
||||
)}
|
||||
{/* <Text>Choose a version to share:</Text> */}
|
||||
<RadioGroup
|
||||
gap={4}
|
||||
onChange={(val) => {
|
||||
@@ -294,7 +298,6 @@ export default function ShareDialog({ onClose }: Props) {
|
||||
>
|
||||
<Stack>
|
||||
<HStack mb={5} alignItems={"center"}>
|
||||
{/* <Radio key={"new_version"} value="new_version" /> */}
|
||||
<Input
|
||||
value={versionName}
|
||||
width={"60%"}
|
||||
@@ -309,37 +312,31 @@ export default function ShareDialog({ onClose }: Props) {
|
||||
<Badge colorScheme="purple">Version Name</Badge>
|
||||
</Flex>
|
||||
</HStack>
|
||||
|
||||
{/* {localVersions.slice(0, 4).map((ver) => {
|
||||
return (
|
||||
<ShareDialogWorkflowVersionRadio
|
||||
key={ver.id}
|
||||
version={ver}
|
||||
cloudHost={cloudHost}
|
||||
cloudWorkflowID={cloudWorkflowID ?? null}
|
||||
/>
|
||||
);
|
||||
})} */}
|
||||
</Stack>
|
||||
</RadioGroup>
|
||||
<Stack borderRadius={6} borderWidth={"1px"} p={2}>
|
||||
<Switch
|
||||
isChecked={enableDeps}
|
||||
onChange={() => setEnableDeps(!enableDeps)}
|
||||
fontWeight={"600"}
|
||||
>
|
||||
Share Resource Links
|
||||
</Switch>
|
||||
{enableDeps && (
|
||||
<>
|
||||
<span style={{ color: "GrayText" }}>
|
||||
You can disable reource sharing if you don't want to resolve
|
||||
submit errors
|
||||
</span>
|
||||
<ResourceDepsForm ref={formRef} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
{Object.keys(deps?.images ?? {}).length > 0 && (
|
||||
<Stack borderRadius={6} borderWidth={"1px"} p={2}>
|
||||
<Switch
|
||||
isChecked={enableDeps}
|
||||
onChange={() => setEnableDeps(!enableDeps)}
|
||||
fontWeight={"600"}
|
||||
>
|
||||
Share input images
|
||||
</Switch>
|
||||
|
||||
<span style={{ color: "GrayText" }}>
|
||||
If enabled your input images will be uploaded and shared to{" "}
|
||||
{cloudHost}
|
||||
</span>
|
||||
{enableDeps && (
|
||||
<ResourceDepsForm
|
||||
deps={deps ?? null}
|
||||
setDeps={setDeps}
|
||||
uploadingImage={uploadingImage}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { useContext, useRef } from "react";
|
||||
import { WorkspaceContext } from "../WorkspaceContext";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
HStack,
|
||||
Heading,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@chakra-ui/react";
|
||||
import { IconInfoCircle } from "@tabler/icons-react";
|
||||
import ResourceDepsForm from "./ResourceDepsForm";
|
||||
|
||||
export default function DownloadSpaceJsonDialog() {
|
||||
const { setRoute } = useContext(WorkspaceContext);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
return (
|
||||
<Modal isOpen={true} onClose={() => setRoute("root")} size={"xl"}>
|
||||
<ModalContent>
|
||||
<ModalBody p={5} gap={5}>
|
||||
<HStack>
|
||||
<Heading size={"md"}>Add Resouce Dependencies</Heading>
|
||||
<Tooltip
|
||||
label="runnable workflows include all resource dependecy download urls
|
||||
(models, cursom nodes, input images) and can be one-click
|
||||
installed and run in any ComfyUI with workpace-manager installed."
|
||||
>
|
||||
<Badge borderRadius={10}>
|
||||
<IconInfoCircle size={20} />
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
<Text color={"GrayText"}>Export runnable workflow</Text>
|
||||
<ResourceDepsForm ref={formRef} />
|
||||
<HStack justifyContent={"flex-end"}>
|
||||
<Button
|
||||
colorScheme="teal"
|
||||
size="sm"
|
||||
onClick={() => formRef.current?.requestSubmit()}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => setRoute("share")}>
|
||||
Skip
|
||||
</Button>
|
||||
</HStack>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -1,335 +1,44 @@
|
||||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { WorkspaceContext } from "../WorkspaceContext";
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
HStack,
|
||||
Heading,
|
||||
IconButton,
|
||||
Image,
|
||||
Input,
|
||||
Spinner,
|
||||
Stack,
|
||||
Tag,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@chakra-ui/react";
|
||||
import {
|
||||
DepsResult,
|
||||
ModelFile,
|
||||
WorkspaceInfoDeps,
|
||||
extractAndFetchFileNames,
|
||||
} from "./handleDownloadSpaceJson";
|
||||
import { IconExternalLink, IconPencil, IconRefresh } from "@tabler/icons-react";
|
||||
import { getHgModelInfoUrlFromDownloadUrl } from "../utils/civitUtils";
|
||||
import { fetchApi, getAllModelsList } from "../Api";
|
||||
import { COMFYSPACE_TRACKING_FIELD_NAME } from "../const";
|
||||
import { indexdb } from "../db-tables/indexdb";
|
||||
import { app } from "../utils/comfyapp";
|
||||
import { HStack, Heading, Image, Spinner, Stack } from "@chakra-ui/react";
|
||||
import { DepsResult } from "./handleDownloadSpaceJson";
|
||||
|
||||
export default forwardRef<HTMLFormElement>(
|
||||
function ResourceDepsForm(_props, ref) {
|
||||
const { saveCurWorkflow, setRoute } = useContext(WorkspaceContext);
|
||||
const [deps, setDeps] = useState<DepsResult>();
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
const [uploadingImage, setUploadingImage] = useState(false);
|
||||
|
||||
const init = useCallback(async () => {
|
||||
const graph = app.graph.serialize();
|
||||
extractAndFetchFileNames(graph.nodes ?? []).then((depsRes) => {
|
||||
setDeps(depsRes);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onRefreshModelsList = useCallback(async () => {
|
||||
setDeps(undefined);
|
||||
await getAllModelsList();
|
||||
window.addEventListener("model_list_updated", () => {
|
||||
init();
|
||||
window.removeEventListener("model_list_updated", () => {});
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
}, []);
|
||||
|
||||
const validateURL = (url: string) => {
|
||||
const pattern = new RegExp(
|
||||
"^(https?:\\/\\/)?" +
|
||||
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" +
|
||||
"((\\d{1,3}\\.){3}\\d{1,3}))" +
|
||||
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" +
|
||||
"(\\?[;&a-z\\d%_.~+=-]*)?" +
|
||||
"(\\#[-a-z\\d_]*)?$",
|
||||
"i",
|
||||
);
|
||||
return !!pattern.test(url);
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: any) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const formData = new FormData(event.target);
|
||||
// validate model deps and save model urls to db
|
||||
const saveModelUrlDBPromises: Promise<ModelFile>[] =
|
||||
Object.values(deps?.models ?? {}).map(async (model) => {
|
||||
const inputDownloadUrl = formData.get(model.filename)?.toString();
|
||||
if (inputDownloadUrl?.length) {
|
||||
// because the models table is storing filename without extension as id, which should be migrated
|
||||
const baseName = getBaseFileName(model.filename);
|
||||
await indexdb.models.update(baseName + "@" + model.fileFolder, {
|
||||
downloadUrl: inputDownloadUrl,
|
||||
});
|
||||
}
|
||||
const infoUrl = model.infoUrl?.length
|
||||
? model.infoUrl
|
||||
: inputDownloadUrl
|
||||
? getHgModelInfoUrlFromDownloadUrl(inputDownloadUrl)
|
||||
: null;
|
||||
return {
|
||||
filename: model.filename,
|
||||
nodeType: model.nodeType,
|
||||
fileHash: model.fileHash,
|
||||
fileFolder: model.fileFolder,
|
||||
downloadUrl: inputDownloadUrl ?? model.downloadUrl ?? null,
|
||||
infoUrl: infoUrl ?? null,
|
||||
inputName: model.inputName,
|
||||
};
|
||||
}) ?? [];
|
||||
const modelDepsArr = await Promise.all(saveModelUrlDBPromises);
|
||||
if (!validateModelDeps(modelDepsArr)) {
|
||||
return;
|
||||
}
|
||||
setErrors({});
|
||||
|
||||
// upload images
|
||||
setUploadingImage(true);
|
||||
const imageDepsToUpload = Object.values(deps?.images ?? {})
|
||||
.filter((i) => !i.url)
|
||||
.map((i) => i.filename);
|
||||
const imageDeps = deps?.images ?? {};
|
||||
|
||||
if (imageDepsToUpload.length) {
|
||||
const uploadResp = await fetchApi("/workspace/upload_image", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
images: imageDepsToUpload,
|
||||
}),
|
||||
});
|
||||
const json = (await uploadResp.json()) as Record<string, string>;
|
||||
Object.keys(json).forEach((key) => {
|
||||
if (imageDeps?.[key]) {
|
||||
imageDeps[key].url = json[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
setUploadingImage(false);
|
||||
|
||||
// save deps to graph
|
||||
const modelDeps: WorkspaceInfoDeps["models"] = {};
|
||||
modelDepsArr.forEach((model) => {
|
||||
modelDeps[model.filename] = model;
|
||||
});
|
||||
const graph = app.graph.serialize();
|
||||
(graph.extra ||= {})[COMFYSPACE_TRACKING_FIELD_NAME] ||= {};
|
||||
graph.extra[COMFYSPACE_TRACKING_FIELD_NAME].deps = {
|
||||
models: modelDeps,
|
||||
images: imageDeps ?? {},
|
||||
nodeRepos: deps?.nodeRepos ?? [],
|
||||
} as WorkspaceInfoDeps;
|
||||
await app
|
||||
.graphToPrompt(app.graph)
|
||||
.then((data: { output: any; workflow: any }) => {
|
||||
graph.extra[COMFYSPACE_TRACKING_FIELD_NAME].apiPrompt = data.output;
|
||||
});
|
||||
|
||||
await saveCurWorkflow();
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("workspace_info_deps_updated", {
|
||||
detail: graph,
|
||||
}),
|
||||
);
|
||||
console.log("graph", graph);
|
||||
};
|
||||
|
||||
const validateModelDeps = useCallback((modelDeps: ModelFile[]) => {
|
||||
let isValid = true;
|
||||
modelDeps.forEach((model) => {
|
||||
const key = model.filename;
|
||||
if (!model.downloadUrl) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[key]: "Invalid URL",
|
||||
}));
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
if (!validateURL(model.downloadUrl)) {
|
||||
setErrors((prev) => ({ ...prev, [key]: "Invalid URL" }));
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
if (!model.fileFolder) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[key]: "Model file not found, please re-scan models folder",
|
||||
}));
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
});
|
||||
return isValid;
|
||||
}, []);
|
||||
|
||||
const imageDepsArr = Object.values(deps?.images ?? {});
|
||||
const modelDepsArr = Object.values(deps?.models ?? []);
|
||||
if (!deps) {
|
||||
return <Spinner size="md" color="teal.400" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} ref={ref}>
|
||||
<Stack gap={5}>
|
||||
<Stack>
|
||||
<Heading size={"sm"}>Models ({modelDepsArr.length})</Heading>
|
||||
{modelDepsArr.map((modelFile, index) => (
|
||||
<ModelDepsItem
|
||||
modelFile={modelFile}
|
||||
key={index}
|
||||
errors={errors}
|
||||
onClickRefetchModelList={onRefreshModelsList}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
{imageDepsArr.length > 0 && (
|
||||
<Stack>
|
||||
<HStack>
|
||||
<Heading size={"sm"}>Images ({imageDepsArr.length})</Heading>
|
||||
{/* <Tag colorScheme="yellow">Will be uploaded as url</Tag> */}
|
||||
<p style={{ color: "GrayText" }}>Will be uploaded as url</p>
|
||||
</HStack>
|
||||
{uploadingImage && (
|
||||
<span>
|
||||
<Spinner size="md" color="teal.400" /> Uploading
|
||||
</span>
|
||||
)}
|
||||
{imageDepsArr.map((image) => (
|
||||
<Stack key={image.filename}>
|
||||
<p>{image.filename}</p>
|
||||
<Image
|
||||
width={250}
|
||||
src={`/workspace/view_media?filename=${image.filename}&isPreview=true&isInput=true`}
|
||||
/>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
function getBaseFileName(filename: string) {
|
||||
const parts = filename.split(".");
|
||||
if (parts.length > 1) {
|
||||
parts.pop();
|
||||
return parts.join(".");
|
||||
} else {
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
|
||||
function ModelDepsItem({
|
||||
modelFile,
|
||||
errors,
|
||||
onClickRefetchModelList,
|
||||
export default function ResourceDepsForm({
|
||||
deps,
|
||||
uploadingImage,
|
||||
}: {
|
||||
modelFile: DepsResult["models"][0];
|
||||
errors: Record<string, string>;
|
||||
onClickRefetchModelList: () => void;
|
||||
deps: DepsResult | null;
|
||||
setDeps: (deps: DepsResult) => void;
|
||||
uploadingImage: boolean;
|
||||
}) {
|
||||
const inputKey = modelFile.filename;
|
||||
const [isEditing, setIsEditing] = useState(!modelFile.downloadUrl);
|
||||
|
||||
if (!modelFile.fileFolder) {
|
||||
return (
|
||||
<Stack>
|
||||
<Flex gap={2}>
|
||||
<p>{modelFile.filename}</p>
|
||||
<Tag size={"sm"} colorScheme={"red"}>
|
||||
Model file not found
|
||||
</Tag>
|
||||
</Flex>
|
||||
<Button
|
||||
size={"sm"}
|
||||
leftIcon={<IconRefresh />}
|
||||
width={"fit-content"}
|
||||
borderColor={"red"}
|
||||
borderWidth={errors[inputKey] != null ? 2 : 0}
|
||||
onClick={onClickRefetchModelList}
|
||||
>
|
||||
Fetch model file
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
const imageDepsArr = Object.values(deps?.images ?? {});
|
||||
if (!deps) {
|
||||
return <Spinner size="md" color="teal.400" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack ml={2}>
|
||||
<Flex key={modelFile.filename + modelFile.nodeType} gap={2}>
|
||||
<Tag height={"fit-content"} colorScheme={"teal"}>
|
||||
{modelFile.fileFolder}
|
||||
</Tag>
|
||||
|
||||
{modelFile.downloadUrl ? (
|
||||
<Stack gap={5}>
|
||||
{imageDepsArr.length > 0 && (
|
||||
<Stack>
|
||||
<HStack>
|
||||
<a
|
||||
href={modelFile.infoUrl ?? modelFile.downloadUrl}
|
||||
target="_blank"
|
||||
>
|
||||
{modelFile.filename}
|
||||
</a>
|
||||
<IconExternalLink size={18} />
|
||||
<IconButton
|
||||
ml={3}
|
||||
aria-label="edit"
|
||||
icon={<IconPencil size={18} />}
|
||||
size={"xs"}
|
||||
onClick={() => setIsEditing(!isEditing)}
|
||||
/>
|
||||
<Heading size={"sm"}>Images ({imageDepsArr.length})</Heading>
|
||||
{/* <Tag colorScheme="yellow">Will be uploaded as url</Tag> */}
|
||||
<p style={{ color: "GrayText" }}>Will be uploaded as url</p>
|
||||
</HStack>
|
||||
) : (
|
||||
<Stack>
|
||||
{uploadingImage && (
|
||||
<span>
|
||||
{modelFile.filename} <span>❓</span>
|
||||
<Spinner size="md" color="teal.400" /> Uploading
|
||||
</span>
|
||||
</Stack>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Stack style={{ display: isEditing ? "block" : "none" }}>
|
||||
<Tooltip
|
||||
label={"Model download link, supports Huggingface and Civitai"}
|
||||
>
|
||||
<Input
|
||||
name={inputKey}
|
||||
defaultValue={modelFile.downloadUrl ?? ""}
|
||||
size={"sm"}
|
||||
isInvalid={errors[inputKey] != null}
|
||||
placeholder={"https://civitai.com/api/download/models/324524"}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<Text color={"red.400"}>{errors[inputKey]}</Text>
|
||||
)}
|
||||
{imageDepsArr.map((image) => (
|
||||
<Stack key={image.filename}>
|
||||
<p>{image.filename}</p>
|
||||
<Image
|
||||
width={250}
|
||||
src={`/workspace/view_media?filename=${image.filename}&isPreview=true&isInput=true`}
|
||||
/>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,13 +13,18 @@ type LiteNode = {
|
||||
widgets_values?: Array<string | number>;
|
||||
};
|
||||
|
||||
export type ModelFile = {
|
||||
filename: string;
|
||||
fileFolder: string | null;
|
||||
fileHash: string | null;
|
||||
downloadUrl: string | null;
|
||||
export type ModelDep = {
|
||||
name: string;
|
||||
folder: string | null;
|
||||
hash: string | null;
|
||||
url: string | null;
|
||||
// TODO: will be deperecated
|
||||
filename?: string;
|
||||
fileFolder?: string | null;
|
||||
fileHash?: string | null;
|
||||
downloadUrl?: string | null;
|
||||
// optional info
|
||||
infoUrl: string | null;
|
||||
infoUrl?: string | null;
|
||||
nodeType?: string;
|
||||
inputName?: string;
|
||||
};
|
||||
@@ -40,12 +45,12 @@ export type DepsResult = {
|
||||
nodeRepos: NodeRepo[];
|
||||
};
|
||||
|
||||
export type DepsResultModel = ModelFile & {
|
||||
export type DepsResultModel = ModelDep & {
|
||||
length: number;
|
||||
};
|
||||
|
||||
export type WorkspaceInfoDeps = {
|
||||
models: Record<string, ModelFile>;
|
||||
models: Record<string, ModelDep>;
|
||||
images: DepsResult["images"];
|
||||
nodeRepos: DepsResult["nodeRepos"];
|
||||
};
|
||||
@@ -57,20 +62,18 @@ async function fetchModelData(
|
||||
): Promise<DepsResultModel> {
|
||||
const res =
|
||||
(await indexdb.models.where("fileName").equals(filename).toArray()) ?? [];
|
||||
console.log("fetch modeldata", res);
|
||||
const first = res.at(0);
|
||||
const modelFile: DepsResultModel = {
|
||||
filename,
|
||||
name: filename,
|
||||
nodeType,
|
||||
fileHash: first?.fileHash ?? null,
|
||||
fileFolder: first?.fileFolder ?? null,
|
||||
downloadUrl: first?.civitModelVersionID
|
||||
hash: first?.fileHash ?? null,
|
||||
folder: first?.fileFolder ?? null,
|
||||
url: first?.civitModelVersionID
|
||||
? getCivitModelDownloadUrl(first.civitModelVersionID)
|
||||
: first?.downloadUrl ?? null,
|
||||
inputName: inputName,
|
||||
length: res.length,
|
||||
infoUrl: first?.civitModelID
|
||||
? getCivitModelPageUrl(first.civitModelID, first.civitModelVersionID)
|
||||
: getHgModelInfoUrlFromDownloadUrl(first?.downloadUrl ?? "") ?? null,
|
||||
};
|
||||
return modelFile;
|
||||
}
|
||||
@@ -94,13 +97,9 @@ export async function extractAndFetchFileNames(
|
||||
if (typeof value != "string") return;
|
||||
// Check if it's a model file
|
||||
if (modelFileExtensions.some((ext) => value.endsWith(ext))) {
|
||||
if (curDeps?.models?.[value]) {
|
||||
modelPromises.push(Promise.resolve(curDeps.models[value]));
|
||||
} else {
|
||||
modelPromises.push(
|
||||
fetchModelData(value, node.class_type, inputName),
|
||||
);
|
||||
}
|
||||
modelPromises.push(
|
||||
fetchModelData(value, node.class_type, inputName),
|
||||
);
|
||||
}
|
||||
// Check if it's an image file
|
||||
if (imageFileExtensions.some((ext) => value.endsWith(ext))) {
|
||||
@@ -134,10 +133,7 @@ export async function extractAndFetchFileNames(
|
||||
const models = await Promise.all(modelPromises);
|
||||
const modelsMap: Record<string, DepsResultModel> = {};
|
||||
models.forEach((model) => {
|
||||
// if (model.nodeType) {
|
||||
// model.gitRepo = reposMapping[model.nodeType];
|
||||
// }
|
||||
modelsMap[model.filename] = model;
|
||||
modelsMap[model.name] = model;
|
||||
});
|
||||
const nodeRepos: Record<string, NodeRepo> = {};
|
||||
Object.values(reposMapping).forEach((repo) => {
|
||||
|
||||
@@ -10,10 +10,8 @@ export interface SortableItem {
|
||||
export interface Workflow extends SortableItem {
|
||||
id: string;
|
||||
json: string;
|
||||
lastSavedJson?: string; // TODO will be deprecated
|
||||
name: string;
|
||||
createTime: number;
|
||||
filePath?: string; // TODO will be deprecated
|
||||
privacy?: WorkflowPrivacy;
|
||||
tags?: string[];
|
||||
parentFolderID?: string | null; //TODO remove undefined, use null only
|
||||
@@ -67,6 +65,7 @@ export type WorkflowVersion = {
|
||||
workflowID: string;
|
||||
json: string;
|
||||
createTime: number;
|
||||
api_prompt: string;
|
||||
cloudID?: string;
|
||||
cloudOrigin?: string;
|
||||
authorID?: string;
|
||||
@@ -131,7 +130,6 @@ export type Media = {
|
||||
workflowJSON?: string;
|
||||
createTime: number;
|
||||
localPath: string;
|
||||
format: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
ScanLocalFolder,
|
||||
scanLocalFiles,
|
||||
} from "../apis/TwowaySyncApi";
|
||||
import { isFolder, userSettingsTable } from "../db-tables/WorkspaceDB";
|
||||
import { isFolder } from "../db-tables/WorkspaceDB";
|
||||
import { indexdb } from "../db-tables/indexdb";
|
||||
import { Folder, Workflow } from "../types/dbTypes";
|
||||
import { sortFileItem } from "../utils";
|
||||
@@ -34,7 +34,13 @@ export async function scanMyWorkflowsDir(
|
||||
return folder;
|
||||
} else {
|
||||
// is workflow
|
||||
const existingWorkflow = (await indexdb.workflows?.get(file.id)) ?? {
|
||||
|
||||
const existingWorkflow = (await indexdb.workflows
|
||||
?.get(file.id)
|
||||
.catch((err) => {
|
||||
console.error("Error getting workflow from indexdb", err);
|
||||
return null;
|
||||
})) ?? {
|
||||
createTime: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user