├── .gitattributes ├── .github └── workflows │ └── publish_action.yml ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── __init__.py ├── loader ├── blueprints.js ├── components │ ├── home │ │ ├── classification │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── foot │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── head │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── index.less │ │ └── model │ │ │ ├── detail │ │ │ ├── basicInf │ │ │ │ ├── index.css │ │ │ │ ├── index.js │ │ │ │ └── index.less │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ ├── note │ │ │ │ ├── form │ │ │ │ │ ├── index.css │ │ │ │ │ ├── index.js │ │ │ │ │ └── index.less │ │ │ │ ├── index.css │ │ │ │ ├── index.js │ │ │ │ └── index.less │ │ │ └── workflow │ │ │ │ ├── form │ │ │ │ ├── index.css │ │ │ │ ├── index.js │ │ │ │ └── index.less │ │ │ │ ├── index.css │ │ │ │ ├── index.js │ │ │ │ └── index.less │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ └── list │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── index.less │ ├── left │ │ ├── index.css │ │ ├── index.js │ │ └── index.less │ ├── public │ │ ├── confirmBox.js │ │ ├── hoverMenu.js │ │ ├── iconRenderer.js │ │ └── message.js │ └── settings │ │ ├── language │ │ ├── index.css │ │ ├── index.js │ │ └── index.less │ │ ├── modelShielding │ │ ├── index.css │ │ ├── index.js │ │ └── index.less │ │ ├── shortcut │ │ ├── index.css │ │ ├── index.js │ │ └── index.less │ │ └── windowing │ │ ├── index.css │ │ ├── index.js │ │ └── index.less ├── enterLoader.js ├── index.html ├── pages │ ├── home │ │ ├── home.css │ │ ├── home.js │ │ └── home.less │ └── settings │ │ ├── index.css │ │ ├── index.js │ │ └── index.less ├── router │ └── index.js ├── static │ ├── css │ │ ├── iconfont.css │ │ ├── index.css │ │ └── index.less │ ├── font │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ ├── image │ │ ├── default.jpg │ │ └── logo.webp │ └── js │ │ ├── confirm.js │ │ ├── i18n.js │ │ ├── message.js │ │ ├── public.js │ │ ├── vue-i18n.js │ │ ├── vue-router.js │ │ ├── vue.js │ │ └── vuex.js └── store │ ├── config.js │ ├── index.js │ └── prop.js ├── pyproject.toml └── utils.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=crlf -------------------------------------------------------------------------------- /.github/workflows/publish_action.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Comfy registry 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "pyproject.toml" 9 | 10 | jobs: 11 | publish-node: 12 | name: Publish Custom Node to registry 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Check out code 16 | uses: actions/checkout@v4 17 | - name: Publish Custom Node 18 | uses: Comfy-Org/publish-node-action@main 19 | with: 20 | personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | model-config.json 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.tabWidth": 2, 3 | "prettier.printWidth": 200 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | C# AIGODLIKE-ComfyUI-Studio 2 | Improve the interactive experience of using ComfyUI, such as making the loading of ComfyUI models more intuitive and making it easier to create model thumbnails 3 | 4 | ![image](https://github.com/user-attachments/assets/44ec8f54-bd34-420b-9b3c-b1dedffa8b81) 5 | 6 | ## Function Introduction 7 | |Function|Details| 8 | |:----|:----| 9 | |Loader Model Manager|More intuitive model management (model sorting, labeling, searching, rating, etc.)| 10 | |Model thumbnail|One click generation of model thumbnails or use local images as thumbnails| 11 | |Model shielding|Exclude certain models from appearing in the loader| 12 | |Automatic model labels|Automatically label the outer folder of the model, such as \ ComfyUI \ models \ checkpoints \ SD1.5 \ real \ A.ckpt, and add "SD1.5" and "real" as A.ckpt labels| 13 | |Model matching workflow|Match the matching workflow for the model and support search, add, load, delete, and copy to the clipboard| 14 | |Multilingual|Supports English, Simplified and Traditional Chinese| 15 | ## How to install(Only on WINDOWS 10\11) 16 | AIGODLIKE-ComfyUI-STUDIO is equivalent to a custom node, you can use any method you like, just put it in folder custom_nodes 17 | 18 | Then run: 19 | ```sh 20 | cd ComfyUI/custom_nodes 21 | git clone https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Studio.git 22 | ``` 23 | ## How to use it 24 | Find a loader, **left click** on model switch to pop up ComfyUI Studio Manager. If you still need to use the original model list, use **Shift+left** click on model switch to pop up the original model list 25 | ## List of supported loaders 26 | 1 **Standard Node**: Automatically supports ComfyUI official nodes and standard named * custom node types, which will be automatically taken over when they need to access the models folder. 27 | 28 | Standard names: ckpt_name,vae_name,clip_name,gligen_name,control_net_name,lora_name,style_model_name,hypernetwork_name,unet_name. 29 | 30 | 2 **Non-standard nodes**: Some developers have redefined the model loader for some purpose, which means that non-standard naming prevents the program from automatically adapting and requires manual adaptation, which requires some time and feedback to be added to the support list 31 | 32 | |Node name|节点名称| 33 | |:----|:----| 34 | |ImageOnlyCheckpointLoader|Checkpoint加载器(仅图像)| 35 | |CheckpointLoaderSimple|Checkpoint加载器(简易)| 36 | |unCLIPCheckpointLoader|unCLIPCheckpoint加载器| 37 | |CheckpointLoader|Checkpoint加载器| 38 | |VAELoader|VAE加载器| 39 | |CLIPVisionLoader|CLIP视觉加载器| 40 | |GLIGENLoader|GLIGEN加载器| 41 | |ControlNetLoader|ControlNet加载器| 42 | |DiffControlNetLoader|DiffControlNet加载器| 43 | |LoraLoaderModelOnly|LoRA加载器(仅模型)| 44 | |LoraLoader|LoRA加载器| 45 | |StyleModelLoader|风格模型加载器| 46 | |UpscaleModelLoader|放大模型加载器| 47 | |HypernetworkLoader|超网络加载器| 48 | |CLIPLoader|CLIP视觉加载器| 49 | |DualCLIPLoader|双CLIP加载器| 50 | |UNETLoader|UNET加载器| 51 | |DiffusersLoader|扩散加载器| 52 | 53 | ## TODO 54 | Add more support for functions. 55 | Improve TAG filtering interaction. 56 | -------------------------------------------------------------------------------- /loader/components/home/classification/index.css: -------------------------------------------------------------------------------- 1 | .classification_expand { 2 | max-height: 999px !important; 3 | } 4 | .classification { 5 | position: relative; 6 | max-width: 90%; 7 | max-height: 2vw; 8 | overflow: hidden; 9 | display: flex; 10 | flex-direction: row; 11 | flex-wrap: wrap; 12 | align-items: center; 13 | margin: 1.2vw 0; 14 | padding-right: 1.5vw; 15 | transform: max-height 0.3s; 16 | } 17 | .classification .all { 18 | margin: 0.2vw 0.3vw 0.2vw 0; 19 | } 20 | .classification .block { 21 | width: 0.2vw; 22 | height: 1vw; 23 | background: #383838; 24 | margin: 0 0.55vw; 25 | border-radius: 0.5vw; 26 | } 27 | .classification .selected_classify { 28 | background: #00baad !important; 29 | } 30 | .classification .item { 31 | cursor: pointer; 32 | margin: 0.2vw 0.3vw; 33 | padding: 0 0.5vw; 34 | height: 1.6vw; 35 | display: flex; 36 | justify-content: center; 37 | align-items: center; 38 | background: #383838; 39 | transition: background 0.3s; 40 | border-radius: 0.3vw; 41 | color: #ffffff; 42 | white-space: nowrap; 43 | } 44 | .classification .item:hover { 45 | background: #00baad; 46 | } 47 | .classification .rotate_em { 48 | transform: rotate(180deg); 49 | } 50 | .classification .expand_icon { 51 | cursor: pointer; 52 | position: absolute; 53 | display: flex; 54 | justify-content: center; 55 | align-items: center; 56 | top: 0.3vw; 57 | right: 0; 58 | padding: 0.4vw; 59 | background: #383838; 60 | border-radius: 0.3vw; 61 | transition: background 0.3s, transform 0.3s; 62 | } 63 | .classification .expand_icon em { 64 | color: #ffffff; 65 | font-size: 0.7rem; 66 | } 67 | .classification .expand_icon:hover { 68 | background: #00baad; 69 | } 70 | -------------------------------------------------------------------------------- /loader/components/home/classification/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | allList: { 4 | default: () => { 5 | return []; 6 | }, 7 | type: Array, 8 | }, 9 | }, 10 | data() { 11 | return { 12 | list: [], 13 | isExpand: false, 14 | tags: [], 15 | selectedList: [], 16 | }; 17 | }, 18 | watch: { 19 | allList: { 20 | handler(list) { 21 | this.updateTag(list); 22 | }, 23 | deep: true, 24 | immediate: true, 25 | }, 26 | }, 27 | methods: { 28 | // update tag 29 | updateTag(allList) { 30 | if (!allList) return; 31 | 32 | let tags = new Set(); 33 | 34 | for (let i = 0; i < allList.length; i++) { 35 | let model = allList[i]; 36 | for (let j = 0; j < model.tags.length; j++) { 37 | tags.add(model.tags[j]); 38 | } 39 | } 40 | 41 | this.list = Array.from(tags).map((x) => { 42 | return { name: x }; 43 | }); 44 | }, 45 | // Select all tags 46 | selectAll() { 47 | this.selectedList = []; 48 | this.tags = []; 49 | this.$emit("changeSearchParameter", { key: "", tags: this.tags }); 50 | }, 51 | // Select a single tag 52 | selectClassify(event, index) { 53 | const existIndex = this.selectedList.findIndex((x) => x === index); 54 | if (event.ctrlKey) { 55 | if (existIndex != -1) { 56 | this.selectedList.splice(existIndex, 1); 57 | const tagIndex = this.tags.findIndex((x) => x === this.list[index].name); 58 | this.tags.splice(tagIndex, 1); 59 | } else { 60 | this.selectedList.push(index); 61 | this.tags.push(this.list[index].name); 62 | } 63 | this.$emit("changeSearchParameter", { key: "", tags: this.tags }); 64 | return; 65 | } 66 | // 无ctrl: 单选, 当已存在时取消所有, 若当前已选多项时单击先取消所有再选择当前 67 | if (this.tags.length > 1 || existIndex == -1) { 68 | this.selectedList = [index]; 69 | this.tags = [this.list[index].name]; 70 | } else { 71 | this.selectedList = []; 72 | this.tags = []; 73 | } 74 | 75 | this.$emit("changeSearchParameter", { key: "", tags: this.tags }); 76 | }, 77 | handleContextMenu(event, index) { 78 | const existIndex = this.selectedList.findIndex((x) => x === index); 79 | // 按ctrl: 加选, 当已存在时仅取消当前 80 | if (event.ctrlKey) { 81 | if (existIndex != -1) { 82 | this.selectedList.splice(existIndex, 1); 83 | const tagIndex = this.tags.findIndex((x) => x === this.list[index].name); 84 | this.tags.splice(tagIndex, 1); 85 | } else { 86 | this.selectedList.push(index); 87 | this.tags.push(this.list[index].name); 88 | } 89 | this.$emit("changeSearchParameter", { key: "", tags: this.tags }); 90 | } 91 | return false; 92 | }, 93 | handleSelectStart() { 94 | return false; 95 | }, 96 | // Expand tag list 97 | expandClassify() { 98 | this.isExpand = !this.isExpand; 99 | }, 100 | }, 101 | template: `
102 |
ALL
103 |
104 |
105 | {{item.name}} 106 |
107 |
108 |
109 | `, 110 | }; 111 | -------------------------------------------------------------------------------- /loader/components/home/classification/index.less: -------------------------------------------------------------------------------- 1 | .classification_expand { 2 | max-height: 999px !important; 3 | } 4 | .classification { 5 | position: relative; 6 | max-width: 90%; 7 | max-height: 2vw; 8 | overflow: hidden; 9 | display: flex; 10 | flex-direction: row; 11 | flex-wrap: wrap; 12 | align-items: center; 13 | margin: 1.2vw 0; 14 | padding-right: 1.5vw; 15 | transform: max-height 0.3s; 16 | .all { 17 | margin: 0.2vw 0.3vw 0.2vw 0; 18 | } 19 | .block { 20 | width: 0.2vw; 21 | height: 1vw; 22 | background: #383838; 23 | margin: 0 0.55vw; 24 | border-radius: 0.5vw; 25 | } 26 | .selected_classify { 27 | background: #00baad !important; 28 | } 29 | .item { 30 | cursor: pointer; 31 | margin: 0.2vw 0.3vw; 32 | padding: 0 0.5vw; 33 | height: 1.6vw; 34 | display: flex; 35 | justify-content: center; 36 | align-items: center; 37 | background: #383838; 38 | transition: background 0.3s; 39 | border-radius: 0.3vw; 40 | color: #ffffff; 41 | white-space: nowrap; 42 | &:hover { 43 | background: #00baad; 44 | } 45 | } 46 | .rotate_em { 47 | transform: rotate(180deg); 48 | } 49 | .expand_icon { 50 | cursor: pointer; 51 | position: absolute; 52 | display: flex; 53 | justify-content: center; 54 | align-items: center; 55 | top: 0.3vw; 56 | right: 0; 57 | padding: 0.4vw; 58 | background: #383838; 59 | border-radius: 0.3vw; 60 | transition: background 0.3s, transform 0.3s; 61 | 62 | em { 63 | color: #ffffff; 64 | font-size: 0.7rem; 65 | } 66 | &:hover { 67 | background: #00baad; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /loader/components/home/foot/index.css: -------------------------------------------------------------------------------- 1 | .foot { 2 | width: 100%; 3 | height: 2.3vw; 4 | border-top: 2px solid #383838; 5 | display: flex; 6 | align-items: center; 7 | padding: 0.8vw; 8 | font-size: 0.8rem; 9 | } 10 | .foot .progress { 11 | width: 5.8vw; 12 | height: 0.8vw; 13 | border-radius: 0.3vw; 14 | background: #383838; 15 | overflow: hidden; 16 | margin-left: 0.6vw; 17 | } 18 | .foot .progress .value { 19 | height: 100%; 20 | width: 0; 21 | background: #43cf7c; 22 | } 23 | -------------------------------------------------------------------------------- /loader/components/home/foot/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | allList: { 4 | default: () => { 5 | return []; 6 | }, 7 | type: Array, 8 | }, 9 | }, 10 | watch: { 11 | allList: { 12 | handler(newValue) { 13 | this.updateCount(newValue); 14 | }, 15 | immediate: true, 16 | deep: true, 17 | }, 18 | }, 19 | data() { 20 | return { 21 | modelCount: 9999999, 22 | noThumbnailCount: 0, 23 | }; 24 | }, 25 | computed: { 26 | progress() { 27 | return this.renderer?.progress_value; 28 | }, 29 | }, 30 | methods: { 31 | // Update the model count / preview count 32 | updateCount(allList) { 33 | this.modelCount = allList.length; 34 | this.noThumbnailCount = allList.filter((item) => !item.cover).length; 35 | }, 36 | // Make span text 37 | makeSpanText() { 38 | let fmt = this.$t("home.foot.text", { modelCount: this.modelCount, noThumbnailCount: this.noThumbnailCount, taskIndex: this.renderer?.task_index, taskCount: this.renderer?.task_count}); 39 | return fmt; 40 | }, 41 | // Get the percentage of models without thumbnails 42 | getWithThumbnailPercent() { 43 | return (1 - this.noThumbnailCount / this.modelCount) * 100; 44 | }, 45 | }, 46 | template: `
47 | {{makeSpanText()}} 48 |
49 |
50 |
51 |
`, 52 | }; 53 | -------------------------------------------------------------------------------- /loader/components/home/foot/index.less: -------------------------------------------------------------------------------- 1 | .foot { 2 | width: 100%; 3 | height: 2.3vw; 4 | border-top: 2px solid #383838; 5 | display: flex; 6 | align-items: center; 7 | padding: 0.8vw; 8 | font-size: 0.8rem; 9 | .progress { 10 | width: 5.8vw; 11 | height: 0.8vw; 12 | border-radius: 0.3vw; 13 | background: #383838; 14 | overflow: hidden; 15 | margin-left: 0.6vw; 16 | .value { 17 | height: 100%; 18 | width: 0; 19 | background: #43cf7c; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /loader/components/home/head/index.css: -------------------------------------------------------------------------------- 1 | .head { 2 | display: flex; 3 | width: 100%; 4 | justify-content: space-between; 5 | align-items: center; 6 | z-index: 2; 7 | } 8 | .head .left { 9 | display: flex; 10 | align-items: center; 11 | } 12 | .head .left .title { 13 | font-weight: bold; 14 | } 15 | .head .left .search { 16 | display: flex; 17 | align-items: center; 18 | background: #383838; 19 | height: 1.6vw; 20 | min-height: 32px; 21 | margin: 0 0.5vw 0 1vw; 22 | border-radius: 5px; 23 | } 24 | .head .left .search input { 25 | width: 12.3vw; 26 | height: 100%; 27 | background: transparent; 28 | outline: none; 29 | border: none; 30 | color: #c4c4c4; 31 | padding: 0 1vw; 32 | } 33 | .head .left .search span { 34 | position: relative; 35 | display: flex; 36 | justify-content: center; 37 | align-items: center; 38 | width: 2.3vw; 39 | height: 100%; 40 | cursor: pointer; 41 | } 42 | .head .left .search span em { 43 | color: #c4c4c4; 44 | } 45 | .head .left .search span::before { 46 | content: ""; 47 | position: absolute; 48 | left: 0; 49 | top: 50%; 50 | transform: translate(-100%, -50%); 51 | width: 0.2vw; 52 | height: 70%; 53 | background: #1f1f1f; 54 | border-radius: 1vw; 55 | } 56 | .head .left .search span:hover em { 57 | color: #2a82e4; 58 | } 59 | .head .left .space { 60 | margin: 0 0.5vw; 61 | } 62 | .head .left .block { 63 | width: 0.2vw; 64 | height: 1.5vw; 65 | background: #383838; 66 | margin: 0 0.55vw; 67 | border-radius: 0.5vw; 68 | } 69 | .head .left .render_button { 70 | padding: 0.55vw 0.9vw; 71 | background: #383838; 72 | border-radius: 0.4vw; 73 | color: #ffffff; 74 | transition: background 0.3s; 75 | font-size: 1rem; 76 | font-weight: bold; 77 | } 78 | .head .left .render_button:hover { 79 | background: #2a82e4; 80 | } 81 | .head .right .close_button { 82 | cursor: pointer; 83 | background: #383838; 84 | width: 4.6vw; 85 | height: 2vw; 86 | color: #ffffff; 87 | transition: background 0.3s; 88 | border-radius: 2vw; 89 | } 90 | .head .right .close_button em { 91 | font-size: 1.3rem; 92 | } 93 | .head .right .close_button:hover { 94 | background: #d43030; 95 | } 96 | -------------------------------------------------------------------------------- /loader/components/home/head/index.js: -------------------------------------------------------------------------------- 1 | import hoverMenu from "../../public/hoverMenu.js"; 2 | import IconRenderer from "../../public/iconRenderer.js"; 3 | // import { app } from "/scripts/app.js"; 4 | const ext = { 5 | is_rendering: false, 6 | name: "ComfyUI-Studio.head", 7 | async register() { 8 | try { 9 | const { app } = await import("/scripts/app.js"); 10 | app.registerExtension(ext); 11 | } catch (error) { 12 | // console.error(error); 13 | } 14 | }, 15 | }; 16 | ext.register(); 17 | 18 | export default { 19 | props: { 20 | columnIndex: { 21 | default: 0, 22 | type: Number, 23 | }, 24 | allList: { 25 | default: () => { 26 | return []; 27 | }, 28 | type: Array, 29 | }, 30 | }, 31 | components: { HoverMenu: hoverMenu }, 32 | data() { 33 | return { 34 | value: "", 35 | search: { 36 | key: "", 37 | sort: "", 38 | level: "", 39 | }, 40 | }; 41 | }, 42 | computed: { 43 | language() { 44 | return this.$store.state.config.language; 45 | }, 46 | }, 47 | methods: { 48 | // Change sorting method 49 | changeSort(value) { 50 | const data = this.$t("home.head.categoryList")[value].value; 51 | this.search.sort = data; 52 | this.search.key = ""; 53 | this.$emit("changeSearchParameter", this.search); 54 | }, 55 | // Filter list based on level 56 | changeLevel(value) { 57 | const data = this.$t("home.head.rateList")[value].value; 58 | this.search.key = ""; 59 | this.search.level = data; 60 | this.$emit("changeSearchParameter", this.search); 61 | }, 62 | // Change the quantity that can be displayed in a row 63 | changeColumn(value) { 64 | const data = this.$t("home.head.sizeList")[value].value; 65 | localStorage.setItem("columnIndex", value); 66 | this.$emit("changeColumn", data); 67 | }, 68 | // Enter key for search 69 | handleKeyDown(e) { 70 | if (e.key === "Enter") { 71 | this.handleSearch(); 72 | } 73 | }, 74 | // Click to search 75 | handleSearch() { 76 | this.search.key = this.value; 77 | this.$emit("changeSearchParameter", this.search); 78 | this.value = ""; 79 | }, 80 | // One click rendering 81 | rendering() { 82 | this.$confirmBox({ 83 | describe: this.$t("home.head.renderAllThumbnails"), 84 | refuseText: this.$t("confirmBox.refuseText"), 85 | acceptText: this.$t("confirmBox.acceptText"), 86 | accept: () => { 87 | let renderer = this.renderer; 88 | if (this.renderer?.rendering) { 89 | alert(this.$t("home.head.renderingAlert")); 90 | return; 91 | } 92 | const curList = this.$store.state.prop.curModelList; 93 | 94 | renderer?.render(this.node, curList); 95 | }, 96 | refuse: () => {}, 97 | }); 98 | }, 99 | // Close the entire page 100 | closePage() { 101 | let renderer = this.renderer; 102 | if (!renderer?.rendering) { 103 | window.parent.postMessage({ type: "close_loader_page" }, "*"); 104 | return; 105 | } 106 | this.$confirmBox({ 107 | describe: this.$t("home.head.closePageConfirm"), 108 | refuseText: this.$t("confirmBox.refuseText"), 109 | acceptText: this.$t("confirmBox.acceptText"), 110 | accept: () => { 111 | // 点击确认调用 112 | let renderer = this.renderer; 113 | renderer?.stop(); 114 | window.parent.postMessage({ type: "close_loader_page" }, "*"); 115 | }, 116 | refuse: () => { 117 | // 点击取消调用 118 | }, 119 | }); 120 | }, 121 | }, 122 | template: `
123 |
124 | {{$t("home.head.title")}} 125 | 129 | 130 | 131 | 132 |
133 | 134 |
135 |
136 | 137 |
138 |
`, 139 | }; 140 | -------------------------------------------------------------------------------- /loader/components/home/head/index.less: -------------------------------------------------------------------------------- 1 | .head { 2 | display: flex; 3 | width: 100%; 4 | justify-content: space-between; 5 | align-items: center; 6 | z-index: 2; 7 | .left { 8 | display: flex; 9 | align-items: center; 10 | .title { 11 | font-weight: bold; 12 | } 13 | .search { 14 | display: flex; 15 | align-items: center; 16 | background: #383838; 17 | height: 1.6vw; 18 | min-height: 32px; 19 | margin: 0 0.5vw 0 1vw; 20 | border-radius: 5px; 21 | input { 22 | width: 12.3vw; 23 | height: 100%; 24 | background: transparent; 25 | outline: none; 26 | border: none; 27 | color: #c4c4c4; 28 | padding: 0 1vw; 29 | } 30 | 31 | span { 32 | position: relative; 33 | display: flex; 34 | justify-content: center; 35 | align-items: center; 36 | width: 2.3vw; 37 | height: 100%; 38 | cursor: pointer; 39 | em { 40 | color: #c4c4c4; 41 | } 42 | 43 | &::before { 44 | content: ""; 45 | position: absolute; 46 | left: 0; 47 | top: 50%; 48 | transform: translate(-100%, -50%); 49 | width: 0.2vw; 50 | height: 70%; 51 | background: #1f1f1f; 52 | border-radius: 1vw; 53 | } 54 | &:hover em { 55 | color: #2a82e4; 56 | } 57 | } 58 | } 59 | .space { 60 | margin: 0 0.5vw; 61 | } 62 | .block { 63 | width: 0.2vw; 64 | height: 1.5vw; 65 | background: #383838; 66 | margin: 0 0.55vw; 67 | border-radius: 0.5vw; 68 | } 69 | .render_button { 70 | padding: 0.55vw 0.9vw; 71 | background: #383838; 72 | border-radius: 0.4vw; 73 | color: #ffffff; 74 | transition: background 0.3s; 75 | font-size: 1rem; 76 | font-weight: bold; 77 | &:hover { 78 | background: #2a82e4; 79 | } 80 | } 81 | } 82 | .right { 83 | .close_button { 84 | cursor: pointer; 85 | background: #383838; 86 | width: 4.6vw; 87 | height: 2vw; 88 | color: #ffffff; 89 | transition: background 0.3s; 90 | border-radius: 2vw; 91 | em { 92 | font-size: 1.3rem; 93 | } 94 | &:hover { 95 | background: #d43030; 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/basicInf/index.css: -------------------------------------------------------------------------------- 1 | .basic_inf { 2 | flex: 1; 3 | overflow: hidden; 4 | display: flex; 5 | flex-direction: column; 6 | } 7 | .basic_inf .model_inf { 8 | display: grid; 9 | grid-gap: 0.5vw; 10 | width: 100%; 11 | padding: 0 0.8vw; 12 | } 13 | .basic_inf .model_inf .inf_item { 14 | display: flex; 15 | align-items: center; 16 | width: 100%; 17 | justify-content: space-between; 18 | font-size: 1rem; 19 | } 20 | .basic_inf .model_inf .inf_item span { 21 | font-weight: bold; 22 | color: #bfbfbf; 23 | } 24 | .basic_inf .classify_input_container { 25 | flex: 1; 26 | overflow: hidden; 27 | padding: 0.5vw 0.3vw 0.5vw 0.5vw; 28 | background: #1f1f1f; 29 | border-radius: 0.3vw; 30 | margin: 0.5vw 0; 31 | } 32 | .basic_inf .classify_input_container .classify_input { 33 | width: 100%; 34 | height: 100%; 35 | overflow-y: auto; 36 | display: flex; 37 | flex-wrap: wrap; 38 | gap: 0.5vw; 39 | align-content: flex-start; 40 | padding-right: 0.3vw; 41 | } 42 | .basic_inf .classify_input_container .classify_input .tag { 43 | cursor: default; 44 | display: flex; 45 | color: #ffffff; 46 | background: #383838; 47 | border-radius: 0.3vw; 48 | padding: 0.26vw 0.67vw; 49 | flex: 0; 50 | white-space: nowrap; 51 | font-weight: bold; 52 | align-items: center; 53 | } 54 | .basic_inf .classify_input_container .classify_input .tag em { 55 | color: #808080; 56 | cursor: pointer; 57 | margin-left: 0.4vw; 58 | transition: color 0.25s; 59 | font-size: 0.7rem; 60 | } 61 | .basic_inf .classify_input_container .classify_input .tag em:hover { 62 | color: red; 63 | } 64 | .basic_inf .classify_input_container .classify_input .expand { 65 | background: #383838 !important; 66 | border: 1px solid transparent !important; 67 | } 68 | .basic_inf .classify_input_container .classify_input .expand em { 69 | color: #ffffff !important; 70 | } 71 | .basic_inf .classify_input_container .classify_input .tag_input { 72 | padding: 0.36vw 0.62vw; 73 | border: 1px solid #3d3d3d; 74 | border-radius: 0.36vw; 75 | background: #1f1f1f; 76 | cursor: pointer; 77 | } 78 | .basic_inf .classify_input_container .classify_input .tag_input input { 79 | width: 0; 80 | overflow: hidden; 81 | color: #ffffff; 82 | background: transparent; 83 | padding: 0; 84 | animation: tag_input_enter 0.3s forwards; 85 | } 86 | .basic_inf .classify_input_container .classify_input .tag_input em { 87 | color: #3d3d3d; 88 | } 89 | .basic_inf .classify_input_container .classify_input .tag_input:hover { 90 | background: #262626; 91 | border: 1px solid #262626; 92 | } 93 | .basic_inf .classify_input_container .classify_input .tag_input:hover em { 94 | color: #ffffff; 95 | } 96 | .basic_inf .classify_input_container .classify_input::-webkit-scrollbar { 97 | width: 8px; 98 | } 99 | .basic_inf .classify_input_container .classify_input::-webkit-scrollbar-thumb:vertical { 100 | border-radius: 20px; 101 | background: #808080; 102 | } 103 | .basic_inf .classify_input_container .classify_input::-webkit-scrollbar-track { 104 | background-color: transparent; 105 | } 106 | .basic_inf .classify_input_container .classify_input::-webkit-scrollbar-corner { 107 | background-color: transparent; 108 | } 109 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/basicInf/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "BasicInf", 3 | props: { 4 | model: { 5 | default: () => { 6 | return {}; 7 | }, 8 | type: Object, 9 | }, 10 | }, 11 | 12 | data() { 13 | return { 14 | tagValue: "", 15 | isEditTagInput: false, 16 | }; 17 | }, 18 | 19 | methods: { 20 | //Timestamp conversion 21 | timestampConversion(timeStamp) { 22 | const date = new Date(Number(timeStamp)); 23 | return date.toLocaleString([], { 24 | year: "numeric", 25 | month: "2-digit", 26 | day: "2-digit", 27 | hour: "2-digit", 28 | minute: "2-digit", 29 | }); 30 | return date.getFullYear() + "/" + (date.getMonth() + 1) + "/" + date.getDate() + " " + date.getHours() + ": " + date.getMinutes(); 31 | }, 32 | // Delete tag 33 | deleteTag(index) { 34 | this.$emit("deleteTag", index); 35 | }, 36 | // Focus event of tag input box 37 | editTagInput() { 38 | if (!this.isEditTagInput) { 39 | this.isEditTagInput = true; 40 | this.$nextTick(() => { 41 | const tagInput = this.$refs.tagInput; 42 | tagInput.focus(); 43 | }); 44 | } 45 | }, 46 | // Blur event of tag input box 47 | tagInputBlur() { 48 | if (this.checkTagLegality()) { 49 | this.$emit("addTag", this.tagValue); 50 | } 51 | this.tagValue = ""; 52 | this.isEditTagInput = false; 53 | }, 54 | // Enter event for tag input box 55 | handleKeyDown(e) { 56 | if (e.key === "Enter") { 57 | this.createTag(); 58 | } 59 | }, 60 | // Create a tag 61 | createTag() { 62 | if (this.checkTagLegality()) { 63 | this.$emit("addTag", this.tagValue); 64 | this.tagValue = ""; 65 | this.isEditTagInput = false; 66 | } 67 | }, 68 | // Legality of detecting tags 69 | checkTagLegality() { 70 | if (this.tagValue === "") { 71 | return false; 72 | } 73 | if (this.model.tags.find((x) => x === this.tagValue)) { 74 | this.$message({ 75 | type: "error", 76 | message: this.$t("messages.tagExists"), 77 | }); 78 | return false; 79 | } 80 | return true; 81 | }, 82 | }, 83 | filters: { 84 | // 保留小数位数->str 85 | numberRound(num, decimal = 2) { 86 | return Math.round(num * Math.pow(10, decimal)) / Math.pow(10, decimal); 87 | }, 88 | }, 89 | template: `
90 |
91 |
92 | {{$t("home.modelDetail.basicInf.size")}} 93 | {{(model?.size || 0) | numberRound}} MB 94 |
95 |
96 | {{$t("home.modelDetail.basicInf.type")}} 97 | {{model?.type || '未知'}} 98 |
99 |
100 | {{$t("home.modelDetail.basicInf.creationTime")}} 101 | {{ timestampConversion(model?.creationTime) || null}} 102 |
103 |
104 | {{$t("home.modelDetail.basicInf.modificationTime")}} 105 | {{ timestampConversion(model?.modifyTime) || null}} 106 |
107 |
108 |
109 |
110 |
111 | {{ item }} 112 | 113 |
114 |
115 | 125 | 126 |
127 |
128 |
129 |
`, 130 | }; 131 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/basicInf/index.less: -------------------------------------------------------------------------------- 1 | .basic_inf { 2 | flex: 1; 3 | overflow: hidden; 4 | display: flex; 5 | flex-direction: column; 6 | .model_inf { 7 | display: grid; 8 | grid-gap: 0.5vw; 9 | width: 100%; 10 | padding: 0 0.8vw; 11 | .inf_item { 12 | display: flex; 13 | align-items: center; 14 | width: 100%; 15 | justify-content: space-between; 16 | font-size: 1rem; 17 | span { 18 | font-weight: bold; 19 | color: #bfbfbf; 20 | } 21 | } 22 | } 23 | .classify_input_container { 24 | flex: 1; 25 | overflow: hidden; 26 | padding: 0.5vw 0.3vw 0.5vw 0.5vw; 27 | background: #1f1f1f; 28 | border-radius: 0.3vw; 29 | margin: 0.5vw 0; 30 | .classify_input { 31 | width: 100%; 32 | height: 100%; 33 | overflow-y: auto; 34 | display: flex; 35 | flex-wrap: wrap; 36 | gap: 0.5vw; 37 | align-content: flex-start; 38 | padding-right: 0.3vw; 39 | .tag { 40 | cursor: default; 41 | display: flex; 42 | color: #ffffff; 43 | background: #383838; 44 | border-radius: 0.3vw; 45 | padding: 0.26vw 0.67vw; 46 | flex: 0; 47 | white-space: nowrap; 48 | font-weight: bold; 49 | align-items: center; 50 | em { 51 | color: #808080; 52 | cursor: pointer; 53 | margin-left: 0.4vw; 54 | transition: color 0.25s; 55 | font-size: 0.7rem; 56 | &:hover { 57 | color: red; 58 | } 59 | } 60 | } 61 | .expand { 62 | background: #383838 !important; 63 | border: 1px solid transparent !important; 64 | em { 65 | color: #ffffff !important; 66 | } 67 | } 68 | .tag_input { 69 | padding: 0.36vw 0.62vw; 70 | border: 1px solid #3d3d3d; 71 | border-radius: 0.36vw; 72 | background: #1f1f1f; 73 | cursor: pointer; 74 | input { 75 | width: 0; 76 | overflow: hidden; 77 | color: #ffffff; 78 | background: transparent; 79 | padding: 0; 80 | animation: tag_input_enter 0.3s forwards; 81 | } 82 | em { 83 | color: #3d3d3d; 84 | } 85 | &:hover { 86 | background: #262626; 87 | border: 1px solid #262626; 88 | em { 89 | color: #ffffff; 90 | } 91 | } 92 | } 93 | 94 | &::-webkit-scrollbar { 95 | width: 8px; 96 | } 97 | &::-webkit-scrollbar-thumb:vertical { 98 | border-radius: 20px; 99 | background: #808080; 100 | } 101 | &::-webkit-scrollbar-track { 102 | background-color: transparent; 103 | } 104 | &::-webkit-scrollbar-corner { 105 | background-color: transparent; 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/index.js: -------------------------------------------------------------------------------- 1 | import { getLevelInf } from "../../../../static/js/public.js"; 2 | // import { api } from "/scripts/api.js"; 3 | import BasicInf from "./basicInf/index.js"; 4 | import Workflow from "./workflow/index.js"; 5 | import Note from "./note/index.js"; 6 | import IconRenderer from "../../../public/iconRenderer.js"; 7 | 8 | function getApi() { 9 | const api = window.comfyAPI?.api?.api || window.parent.comfyAPI?.api?.api; 10 | api.api_base = ""; 11 | return api; 12 | } 13 | 14 | const ext = { 15 | is_rendering: false, 16 | name: "ComfyUI-Studio.model.detail", 17 | async register() { 18 | try { 19 | const { app } = await import("/scripts/app.js"); 20 | app.registerExtension(ext); 21 | } catch (error) { 22 | // console.error(error); 23 | } 24 | }, 25 | }; 26 | ext.register(); 27 | export default { 28 | props: ["model"], 29 | components: { 30 | BasicInf, 31 | Workflow, 32 | Note, 33 | }, 34 | data() { 35 | return { 36 | list: [], 37 | levelList: [], 38 | menuIndex: 0, 39 | isReadonly: true, 40 | selectedLevel: "D", 41 | defaultCover: "./static/image/default.jpg", 42 | }; 43 | }, 44 | created() { 45 | this.getLevelColor(); 46 | }, 47 | watch: { 48 | model: { 49 | handler(newValue) { 50 | this.selectedLevel = newValue.level; 51 | }, 52 | deep: true, 53 | }, 54 | }, 55 | methods: { 56 | // Click to trigger name modification 57 | editName() { 58 | this.isReadonly = false; 59 | this.$nextTick(() => { 60 | this.$refs.nameInput.focus(); 61 | }); 62 | }, 63 | nameInputKeyDown(e) { 64 | if (e.key === "Enter") { 65 | this.changeName(e); 66 | } else if (e.key === "Escape") { 67 | this.isReadonly = true; 68 | e.preventDefault(); 69 | e.stopPropagation(); 70 | } 71 | }, 72 | // Submit a new name 73 | changeName(e) { 74 | e.preventDefault(); 75 | this.isReadonly = true; 76 | const value = this.$refs.nameInput.value; 77 | if (value) { 78 | this.$emit("modifyName", this.model, value); 79 | } 80 | }, 81 | // Trigger when the name input box blur 82 | blurInput() { 83 | this.isReadonly = true; 84 | }, 85 | // Change level 86 | changeLevel(levelInf) { 87 | this.selectedLevel = levelInf.value; 88 | this.$emit("changeLevel", levelInf.value); 89 | }, 90 | // Obtain the corresponding color based on the current level 91 | getLevelColor() { 92 | const list = ["S", "A", "B", "C", "D"]; 93 | list.forEach((item) => { 94 | this.levelList.push(getLevelInf(item)); 95 | }); 96 | this.selectedLevel = this.model.level; 97 | }, 98 | // Add a tag 99 | addTag(tagValue) { 100 | this.$emit("addTag", tagValue); 101 | }, 102 | // Delete tag 103 | deleteTag(index) { 104 | this.$emit("deleteTag", index); 105 | }, 106 | // Click to use 107 | useModel() { 108 | this.$emit("useModel", this.model); 109 | }, 110 | // Rendering an image 111 | renderPic() { 112 | if (this.model) { 113 | if (this.renderer?.rendering) { 114 | alert(this.$t("home.head.renderingAlert")); 115 | return; 116 | } 117 | this.renderer.render(this.node, [this.model]); 118 | } 119 | }, 120 | // Click to trigger the image acquisition event 121 | modifyCover() { 122 | document.getElementById("file_input").click(); 123 | }, 124 | // Change cover image path 125 | inputCover(e) { 126 | const file = e.target.files[0]; 127 | const isImage = file.type.startsWith("image"); 128 | if (!isImage) { 129 | this.$message({ type: "error", message: "请选择图片格式文件" }); 130 | return; 131 | } 132 | if (file) { 133 | try { 134 | const body = new FormData(); 135 | body.append("image", file); 136 | body.append("type", this.model.type); 137 | body.append("mtype", this.model.mtype); 138 | body.append("name", this.model.name); 139 | const api = getApi(); 140 | api.fetchApi("/cs/upload_thumbnail", { method: "POST", body }); 141 | } catch (error) { 142 | alert(error); 143 | } 144 | const filepath = URL.createObjectURL(file); 145 | this.$emit("modifyCover", filepath); 146 | } 147 | }, 148 | changeMenu(index) { 149 | this.menuIndex = index; 150 | }, 151 | }, 152 | template: ` 153 |
154 |
155 | 156 |
157 | 158 | 159 | 160 | 161 |
162 |
163 |

{{model.name}}

164 |
165 | 166 | 167 |
168 |
169 | {{item.value}} 170 |
171 | 174 | 175 | 176 | 177 | 178 |
179 | `, 180 | }; 181 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/index.less: -------------------------------------------------------------------------------- 1 | @keyframes tag_input_enter { 2 | 0% { 3 | width: 0; 4 | } 5 | 100% { 6 | width: 7vw; 7 | } 8 | } 9 | .model_detail { 10 | width: 17vw; 11 | flex-shrink: 0; 12 | height: 100%; 13 | border: 1px solid #3d3d3d; 14 | background: #262626; 15 | border-radius: 0.5vw; 16 | padding: 0.52vw 0.52vw 1vw 0.52vw; 17 | display: flex; 18 | flex-direction: column; 19 | margin-left: 0.55vw; 20 | .img_container { 21 | position: relative; 22 | width: 100%; 23 | height: 8.7vw; 24 | border-radius: 0.5vw; 25 | overflow: hidden; 26 | img { 27 | width: 100%; 28 | height: 100%; 29 | object-fit: cover; 30 | } 31 | .option_group { 32 | position: absolute; 33 | display: flex; 34 | align-items: center; 35 | right: 1vw; 36 | top: 1vw; 37 | background: #383838; 38 | border-radius: 0.5vw; 39 | .icon_container { 40 | padding: 0.6vw; 41 | display: flex; 42 | justify-content: center; 43 | align-items: center; 44 | transition: color 0.3s; 45 | cursor: pointer; 46 | em { 47 | font-size: 0.8rem; 48 | } 49 | &:hover { 50 | color: aqua; 51 | } 52 | } 53 | .block { 54 | background: #808080; 55 | border-radius: 1vw; 56 | width: 0.15vw; 57 | height: 0.7vw; 58 | } 59 | #file_input { 60 | position: absolute; 61 | display: none; 62 | } 63 | } 64 | } 65 | .focus { 66 | background: #1f1f1f; 67 | input { 68 | color: #bfbfbf; 69 | } 70 | } 71 | .model_name { 72 | width: 100%; 73 | color: #ffffff; 74 | margin: 0.5vw 0; 75 | transition: 0.2s; 76 | min-height: 2.1vw; 77 | min-width: 0; 78 | display: flex; 79 | align-items: center; 80 | padding-left: 0.5vw; 81 | cursor: pointer; 82 | font-weight: bold; 83 | &:hover { 84 | color: #bfbfbf; 85 | } 86 | p { 87 | overflow: hidden; 88 | text-overflow: ellipsis; 89 | white-space: nowrap; 90 | } 91 | } 92 | .name_input { 93 | background: #000000; 94 | width: 100%; 95 | display: flex; 96 | height: 2.1vw; 97 | border-radius: 0.3vw; 98 | align-items: center; 99 | margin: 0.5vw 0; 100 | input { 101 | flex: 1; 102 | background: transparent; 103 | height: 100%; 104 | color: #bfbfbf; 105 | padding-left: 0.5vw; 106 | } 107 | span { 108 | width: 2.1vw; 109 | height: 2.1vw; 110 | display: flex; 111 | justify-content: center; 112 | align-items: center; 113 | cursor: pointer; 114 | em { 115 | font-size: 0.7rem; 116 | transition: color 0.2s; 117 | } 118 | &:hover { 119 | em { 120 | color: #2a82e4; 121 | } 122 | } 123 | } 124 | } 125 | .level_group { 126 | display: flex; 127 | width: 100%; 128 | background: #1f1f1f; 129 | border-radius: 0.3vw; 130 | padding: 0.2vw 0.1vw; 131 | .selected { 132 | background: var(--color) !important; 133 | } 134 | .level_item { 135 | cursor: pointer; 136 | width: 2.8vw; 137 | height: 1.6vw; 138 | background: #383838; 139 | transition: background 0.3s; 140 | border-radius: 0.3vw; 141 | margin: 0 0.15vw; 142 | display: flex; 143 | justify-content: center; 144 | align-items: center; 145 | font-weight: bold; 146 | &:hover { 147 | background: var(--color); 148 | } 149 | } 150 | } 151 | .menu_tab { 152 | display: flex; 153 | margin: 0.5vw 0; 154 | padding: 0 0.8vw; 155 | .active_menu { 156 | color: #ffffff !important; 157 | } 158 | .menu_item { 159 | color: #808080; 160 | font-size: 1rem; 161 | font-weight: bold; 162 | cursor: pointer; 163 | &:nth-of-type(2) { 164 | margin: 0 1vw; 165 | } 166 | } 167 | } 168 | @import url(./basicInf/index.less); 169 | @import url(./workflow/index.less); 170 | @import url(./note/index.less); 171 | .use_button { 172 | width: 100%; 173 | height: 2.1vw; 174 | background: #2a82e4; 175 | border-radius: 0.3vw; 176 | color: #ffffff; 177 | font-weight: bold; 178 | font-size: 1rem; 179 | &:hover { 180 | box-shadow: 0 0 5px 2px #2a82e4; 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/note/form/index.css: -------------------------------------------------------------------------------- 1 | .form { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100vw; 6 | height: 100vh; 7 | z-index: 9999; 8 | background: #181818a2; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | } 13 | .form .form_content { 14 | width: 100%; 15 | height: 13.9vw; 16 | background: #1a1b1c; 17 | border-radius: 0.5vw; 18 | box-shadow: 0 0 5px 1px #60606084; 19 | color: #ffffff; 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | justify-content: center; 24 | } 25 | .form .form_content p { 26 | font-size: 1.3rem; 27 | font-weight: bold; 28 | } 29 | .form .form_content .input_area { 30 | margin: 1vw 0 2.2vw 0; 31 | display: flex; 32 | align-items: center; 33 | position: relative; 34 | } 35 | .form .form_content .input_area input { 36 | width: 18vw; 37 | height: 1.6vw; 38 | min-height: 32px; 39 | color: #ffffff; 40 | font-weight: bold; 41 | background: #383838; 42 | font-size: 1.2rem; 43 | border-radius: 2vw; 44 | padding: 0 0.7vw; 45 | } 46 | .form .form_content .input_area .show_clear { 47 | opacity: 1 !important; 48 | visibility: visible !important; 49 | } 50 | .form .form_content .input_area .clear { 51 | position: absolute; 52 | right: -0.3vw; 53 | transform: translateX(100%); 54 | width: 1.6vw; 55 | height: 1.6vw; 56 | border-radius: 50%; 57 | display: flex; 58 | justify-content: center; 59 | align-items: center; 60 | cursor: pointer; 61 | transition: 0.3s; 62 | background: #383838; 63 | opacity: 0; 64 | visibility: hidden; 65 | } 66 | .form .form_content .input_area .clear em { 67 | font-size: 0.8rem; 68 | } 69 | .form .form_content .input_area .clear:hover { 70 | background: #ff5733; 71 | } 72 | .form .form_content .button_group { 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | } 77 | .form .form_content .button_group .button_item { 78 | height: 2.2vw; 79 | min-height: 40px; 80 | background: #383838; 81 | transition: 0.2s; 82 | display: flex; 83 | justify-content: space-between; 84 | align-items: center; 85 | white-space: nowrap; 86 | cursor: pointer; 87 | } 88 | .form .form_content .button_group .button_item .icon { 89 | width: 1.5vw; 90 | height: 1.5vw; 91 | min-height: 23px; 92 | min-width: 23px; 93 | display: flex; 94 | justify-content: center; 95 | align-items: center; 96 | transition: 0.2s; 97 | border-radius: 50%; 98 | } 99 | .form .form_content .button_group .button_item .icon em { 100 | font-size: 1rem; 101 | } 102 | .form .form_content .button_group .button_item .text { 103 | font-size: 1rem; 104 | } 105 | .form .form_content .button_group .accept { 106 | border-radius: 2vw 0 0 2vw; 107 | padding: 0 1vw 0 0.5vw; 108 | } 109 | .form .form_content .button_group .accept .icon { 110 | margin-right: 1.5vw; 111 | background: #43cf7c; 112 | } 113 | .form .form_content .button_group .accept:hover { 114 | background: #43cf7c; 115 | color: #ffffff; 116 | } 117 | .form .form_content .button_group .accept:hover .icon { 118 | background: #383838; 119 | } 120 | .form .form_content .button_group .refuse { 121 | border-radius: 0 2vw 2vw 0; 122 | padding: 0 0.5vw 0 1vw; 123 | } 124 | .form .form_content .button_group .refuse .icon { 125 | margin-left: 1.5vw; 126 | background: #ff5733; 127 | } 128 | .form .form_content .button_group .refuse:hover { 129 | background: #ff5733; 130 | color: #ffffff; 131 | } 132 | .form .form_content .button_group .refuse:hover .icon { 133 | background: #383838; 134 | } 135 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/note/form/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "NoteForm", 3 | data() { 4 | return { 5 | value: "", 6 | }; 7 | }, 8 | mounted() {}, 9 | methods: { 10 | cancel() { 11 | this.$emit("displayForm", false); 12 | }, 13 | determine() { 14 | this.$emit("displayForm", false); 15 | this.$emit("saveNote", this.value); 16 | }, 17 | clearInput() { 18 | this.value = ""; 19 | }, 20 | }, 21 | template: ` 22 |
23 |
24 |

{{$t("home.modelDetail.note.form.title")}}

25 |
26 | 27 | 28 |
29 |
30 |
31 | 32 | {{$t("home.modelDetail.note.form.confirmText")}} 33 |
34 |
35 | {{$t("home.modelDetail.note.form.cancelText")}} 36 | 37 |
38 |
39 |
40 |
41 | `, 42 | }; 43 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/note/form/index.less: -------------------------------------------------------------------------------- 1 | .form { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100vw; 6 | height: 100vh; 7 | z-index: 9999; 8 | background: #181818a2; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | .form_content { 13 | width: 100%; 14 | height: 13.9vw; 15 | background: #1a1b1c; 16 | border-radius: 0.5vw; 17 | box-shadow: 0 0 5px 1px #60606084; 18 | color: #ffffff; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | p { 24 | font-size: 1.3rem; 25 | font-weight: bold; 26 | } 27 | .input_area { 28 | margin: 1vw 0 2.2vw 0; 29 | display: flex; 30 | align-items: center; 31 | position: relative; 32 | input { 33 | width: 18vw; 34 | height: 1.6vw; 35 | min-height: 32px; 36 | color: #ffffff; 37 | font-weight: bold; 38 | background: #383838; 39 | font-size: 1.2rem; 40 | border-radius: 2vw; 41 | padding: 0 0.7vw; 42 | } 43 | .show_clear { 44 | opacity: 1 !important; 45 | visibility: visible !important; 46 | } 47 | .clear { 48 | position: absolute; 49 | right: -0.3vw; 50 | transform: translateX(100%); 51 | width: 1.6vw; 52 | height: 1.6vw; 53 | border-radius: 50%; 54 | display: flex; 55 | justify-content: center; 56 | align-items: center; 57 | cursor: pointer; 58 | transition: 0.3s; 59 | background: #383838; 60 | opacity: 0; 61 | visibility: hidden; 62 | em { 63 | font-size: 0.8rem; 64 | } 65 | &:hover { 66 | background: #ff5733; 67 | } 68 | } 69 | } 70 | 71 | .button_group { 72 | display: flex; 73 | align-items: center; 74 | justify-content: center; 75 | .button_item { 76 | height: 2.2vw; 77 | min-height: 40px; 78 | background: #383838; 79 | transition: 0.2s; 80 | display: flex; 81 | justify-content: space-between; 82 | align-items: center; 83 | white-space: nowrap; 84 | cursor: pointer; 85 | .icon { 86 | width: 1.5vw; 87 | height: 1.5vw; 88 | min-height: 23px; 89 | min-width: 23px; 90 | display: flex; 91 | justify-content: center; 92 | align-items: center; 93 | transition: 0.2s; 94 | border-radius: 50%; 95 | em { 96 | font-size: 1rem; 97 | } 98 | } 99 | .text { 100 | font-size: 1rem; 101 | } 102 | } 103 | .accept { 104 | border-radius: 2vw 0 0 2vw; 105 | padding: 0 1vw 0 0.5vw; 106 | .icon { 107 | margin-right: 1.5vw; 108 | background: #43cf7c; 109 | } 110 | &:hover { 111 | background: #43cf7c; 112 | color: #ffffff; 113 | .icon { 114 | background: #383838; 115 | } 116 | } 117 | } 118 | .refuse { 119 | border-radius: 0 2vw 2vw 0; 120 | padding: 0 0.5vw 0 1vw; 121 | .icon { 122 | margin-left: 1.5vw; 123 | background: #ff5733; 124 | } 125 | &:hover { 126 | background: #ff5733; 127 | color: #ffffff; 128 | .icon { 129 | background: #383838; 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/note/index.css: -------------------------------------------------------------------------------- 1 | .note { 2 | padding: 0.5vw; 3 | flex: 1; 4 | display: flex; 5 | flex-direction: column; 6 | overflow: hidden; 7 | background: #1f1f1f; 8 | border-radius: 0.5vw; 9 | margin-bottom: 0.3vw; 10 | } 11 | .note .notes { 12 | display: flex; 13 | height: 50%; 14 | flex-direction: column; 15 | flex: 1; 16 | } 17 | .note .notes .search_area { 18 | position: relative; 19 | } 20 | .note .notes .search_area .search { 21 | width: 100%; 22 | display: flex; 23 | align-items: center; 24 | background: #383838; 25 | height: 1.6vw; 26 | min-height: 32px; 27 | border-radius: 5px; 28 | } 29 | .note .notes .search_area .search input { 30 | flex: 1; 31 | height: 100%; 32 | background: transparent; 33 | outline: none; 34 | border: none; 35 | color: #c4c4c4; 36 | padding: 0 0.5vw; 37 | } 38 | .note .notes .search_area .search span { 39 | position: relative; 40 | display: flex; 41 | justify-content: center; 42 | align-items: center; 43 | width: 2.3vw; 44 | height: 100%; 45 | cursor: pointer; 46 | } 47 | .note .notes .search_area .search span em { 48 | color: #c4c4c4; 49 | } 50 | .note .notes .search_area .search span::before { 51 | content: ""; 52 | position: absolute; 53 | left: 0; 54 | top: 50%; 55 | transform: translate(-100%, -50%); 56 | width: 0.2vw; 57 | height: 70%; 58 | background: #1f1f1f; 59 | border-radius: 1vw; 60 | } 61 | .note .notes .search_area .search span:hover em { 62 | color: #2a82e4; 63 | } 64 | .note .notes .search_area .search_key { 65 | width: 100%; 66 | position: absolute; 67 | display: flex; 68 | align-items: center; 69 | left: 0; 70 | top: 0; 71 | height: 1.6vw; 72 | min-height: 32px; 73 | background: #808080; 74 | border-radius: 5px; 75 | padding: 0 0.5vw; 76 | transition: 0.3s; 77 | } 78 | .note .notes .search_area .search_key p { 79 | width: 100%; 80 | white-space: nowrap; 81 | overflow: hidden; 82 | text-overflow: ellipsis; 83 | font-size: 0.8rem; 84 | } 85 | .note .notes .search_area:hover .search_key { 86 | visibility: hidden; 87 | opacity: 0; 88 | } 89 | .note .notes .note_list { 90 | margin-top: 0.5vw; 91 | flex: 1; 92 | grid-auto-rows: auto; 93 | /* 让行高自动 */ 94 | overflow-y: auto; 95 | padding: 0 4px 5px 0; 96 | } 97 | .note .notes .note_list .note_item { 98 | width: 100%; 99 | height: 2.4vw; 100 | margin-bottom: 0.3vw; 101 | border: 1px solid #3d3d3d; 102 | background: #262626; 103 | border-radius: 0.3vw; 104 | display: grid; 105 | align-items: center; 106 | color: #ffffff; 107 | padding: 0 1vw; 108 | transition: 0.3s; 109 | } 110 | .note .notes .note_list .note_item .note_header { 111 | display: flex; 112 | justify-content: space-between; 113 | margin: 0.3vw 0.1vw; 114 | align-items: center; 115 | } 116 | .note .notes .note_list .note_item .note_header .name { 117 | font-size: 1.1rem; 118 | font-weight: bold; 119 | } 120 | .note .notes .note_list .note_item .note_header .option { 121 | display: none; 122 | } 123 | .note .notes .note_list .note_item .note_header .option em { 124 | font-size: 1.1rem; 125 | cursor: pointer; 126 | margin-left: 0.1vw; 127 | } 128 | .note .notes .note_list .note_item:hover { 129 | background: #383838; 130 | } 131 | .note .notes .note_list .note_item:hover .option { 132 | display: block; 133 | } 134 | .note .notes .note_list .note_item.selected { 135 | background: #4a4a4a; 136 | /* 高亮颜色 */ 137 | border-color: #ffffff; 138 | /* 高亮边框颜色 */ 139 | height: auto; 140 | /* 让高度自动适应内容,包括文本编辑框 */ 141 | } 142 | .note .notes .note_list .note_item .note-content-editor { 143 | width: 100%; 144 | height: auto; 145 | padding: 0.5vw; 146 | margin-top: 0.2vw; 147 | margin-bottom: 0.5vw; 148 | background: #3d3d3d; 149 | color: #ffffff; 150 | border-radius: 0.3vw; 151 | resize: none; 152 | /* 禁止调整文本框大小 */ 153 | box-sizing: border-box; 154 | /* 确保内边距和边框包含在宽度和高度内 */ 155 | overflow: hidden; 156 | /* 隐藏滚动条 */ 157 | } 158 | .note .notes .note_list::-webkit-scrollbar { 159 | width: 8px; 160 | background: transparent; 161 | } 162 | .note .notes .note_list::-webkit-scrollbar-thumb:vertical { 163 | border-radius: 20px; 164 | background: #808080; 165 | } 166 | .note .notes .empty_note { 167 | margin-top: 0.5vw; 168 | flex: 1; 169 | } 170 | .note .notes button { 171 | margin-top: 5px; 172 | width: 100%; 173 | height: 2.1vw; 174 | border-radius: 0.3vw; 175 | font-weight: bold; 176 | font-size: 1.1rem; 177 | background: #383838; 178 | transition: 0.3s; 179 | color: #ffffff; 180 | } 181 | .note .notes button:hover { 182 | background: #2a82e4; 183 | } 184 | .note .form { 185 | position: fixed; 186 | top: 0; 187 | left: 0; 188 | width: 100vw; 189 | height: 100vh; 190 | z-index: 9999; 191 | background: #181818a2; 192 | display: flex; 193 | justify-content: center; 194 | align-items: center; 195 | } 196 | .note .form .form_content { 197 | width: 100%; 198 | height: 13.9vw; 199 | background: #1a1b1c; 200 | border-radius: 0.5vw; 201 | box-shadow: 0 0 5px 1px #60606084; 202 | color: #ffffff; 203 | display: flex; 204 | flex-direction: column; 205 | align-items: center; 206 | justify-content: center; 207 | } 208 | .note .form .form_content p { 209 | font-size: 1.3rem; 210 | font-weight: bold; 211 | } 212 | .note .form .form_content .input_area { 213 | margin: 1vw 0 2.2vw 0; 214 | display: flex; 215 | align-items: center; 216 | position: relative; 217 | } 218 | .note .form .form_content .input_area input { 219 | width: 18vw; 220 | height: 1.6vw; 221 | min-height: 32px; 222 | color: #ffffff; 223 | font-weight: bold; 224 | background: #383838; 225 | font-size: 1.2rem; 226 | border-radius: 2vw; 227 | padding: 0 0.7vw; 228 | } 229 | .note .form .form_content .input_area .show_clear { 230 | opacity: 1 !important; 231 | visibility: visible !important; 232 | } 233 | .note .form .form_content .input_area .clear { 234 | position: absolute; 235 | right: -0.3vw; 236 | transform: translateX(100%); 237 | width: 1.6vw; 238 | height: 1.6vw; 239 | border-radius: 50%; 240 | display: flex; 241 | justify-content: center; 242 | align-items: center; 243 | cursor: pointer; 244 | transition: 0.3s; 245 | background: #383838; 246 | opacity: 0; 247 | visibility: hidden; 248 | } 249 | .note .form .form_content .input_area .clear em { 250 | font-size: 0.8rem; 251 | } 252 | .note .form .form_content .input_area .clear:hover { 253 | background: #ff5733; 254 | } 255 | .note .form .form_content .button_group { 256 | display: flex; 257 | align-items: center; 258 | justify-content: center; 259 | } 260 | .note .form .form_content .button_group .button_item { 261 | height: 2.2vw; 262 | min-height: 40px; 263 | background: #383838; 264 | transition: 0.2s; 265 | display: flex; 266 | justify-content: space-between; 267 | align-items: center; 268 | white-space: nowrap; 269 | cursor: pointer; 270 | } 271 | .note .form .form_content .button_group .button_item .icon { 272 | width: 1.5vw; 273 | height: 1.5vw; 274 | min-height: 23px; 275 | min-width: 23px; 276 | display: flex; 277 | justify-content: center; 278 | align-items: center; 279 | transition: 0.2s; 280 | border-radius: 50%; 281 | } 282 | .note .form .form_content .button_group .button_item .icon em { 283 | font-size: 1rem; 284 | } 285 | .note .form .form_content .button_group .button_item .text { 286 | font-size: 1rem; 287 | } 288 | .note .form .form_content .button_group .accept { 289 | border-radius: 2vw 0 0 2vw; 290 | padding: 0 1vw 0 0.5vw; 291 | } 292 | .note .form .form_content .button_group .accept .icon { 293 | margin-right: 1.5vw; 294 | background: #43cf7c; 295 | } 296 | .note .form .form_content .button_group .accept:hover { 297 | background: #43cf7c; 298 | color: #ffffff; 299 | } 300 | .note .form .form_content .button_group .accept:hover .icon { 301 | background: #383838; 302 | } 303 | .note .form .form_content .button_group .refuse { 304 | border-radius: 0 2vw 2vw 0; 305 | padding: 0 0.5vw 0 1vw; 306 | } 307 | .note .form .form_content .button_group .refuse .icon { 308 | margin-left: 1.5vw; 309 | background: #ff5733; 310 | } 311 | .note .form .form_content .button_group .refuse:hover { 312 | background: #ff5733; 313 | color: #ffffff; 314 | } 315 | .note .form .form_content .button_group .refuse:hover .icon { 316 | background: #383838; 317 | } 318 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/note/index.js: -------------------------------------------------------------------------------- 1 | import NoteForm from "../note/form/index.js"; 2 | export default { 3 | name: "Note", 4 | props: ["model"], 5 | components: { NoteForm }, 6 | data() { 7 | return { 8 | value: "", 9 | key: "", 10 | list: [], 11 | note_selected: "", 12 | note_key: "", 13 | note_value: "", 14 | notes: [], 15 | isShowForm: false, 16 | }; 17 | }, 18 | watch: { 19 | model: { 20 | handler(model) { 21 | // note 22 | let notes = []; 23 | for (let key in model?.notes || {}) { 24 | notes.push({ name: key, content: model.notes[key].content || ""}); 25 | } 26 | // notes 按 ascii 排序 27 | notes.sort((a, b) => a.name.localeCompare(b.name)); 28 | this.notes = notes; 29 | }, 30 | deep: true, 31 | immediate: true, 32 | }, 33 | }, 34 | computed: { 35 | filterNoteList() { 36 | return this.notes.filter((x) => x.name?.includes(this.note_value)); 37 | } 38 | }, 39 | mounted() { 40 | this.handleNoteSearch(); 41 | }, 42 | methods: { 43 | // Enter key for search 44 | handleKeyDown(e) { 45 | if (e.key === "Enter") { 46 | this.handleNoteSearch(); 47 | } 48 | }, 49 | // Click to search 50 | handleNoteSearch() { 51 | this.note_key = this.note_value; 52 | }, 53 | // Click add note 54 | displayForm(flag) { 55 | this.isShowForm = flag; 56 | }, 57 | selectNote(item) { 58 | this.note_selected = item.name; 59 | this.$nextTick(() => { 60 | (this.$refs.textarea || []).forEach((textarea) => { 61 | this.autoContentResize({ target: textarea }); 62 | }); 63 | }); 64 | }, 65 | saveContent(item) { 66 | this.saveNote(item.name); 67 | }, 68 | autoContentResize(event) { 69 | const textarea = event.target; 70 | if(textarea?.type !== "textarea") 71 | return 72 | textarea.style.height = 'auto'; // 先重置高度 73 | textarea.style.height = `${textarea.scrollHeight}px`; // 然后设置成内容的滚动高度 74 | }, 75 | cancelSelect(){ 76 | this.note_selected = null; 77 | }, 78 | saveNote(name) { 79 | // 随机生成 80 | if (!name) name = `note-${Math.random().toString(36).substring(2, 10)}`; 81 | var node = this.node; 82 | var data = this.notes.filter((item) => item.name === name)?.[0] || { name: name, content: "" }; 83 | var request = new XMLHttpRequest(); 84 | // request.timeout = 500; // 超时 85 | request.open("post", "/cs/save_note", true); 86 | request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 87 | request.onload = () => { 88 | if (request.status != 200) return; 89 | var resp = JSON.parse(request.responseText); 90 | if (resp?.saved) { 91 | node.CSupdateModelConfig(this.model.name); 92 | this.$message(name + " " + this.$t("home.modelDetail.note.saveNoteSuccess")); 93 | } else { 94 | this.$message(name + " " + this.$t("home.modelDetail.note.saveNoteFail")); 95 | } 96 | }; 97 | let mtype = node.CSgetModelWidgetType(); 98 | let body = { mtype: mtype, mname: this.model?.name, data, name }; 99 | request.send(JSON.stringify(body)); 100 | }, 101 | // Copy note 102 | copyNote(item) { 103 | navigator.clipboard.writeText(item.content); 104 | }, 105 | // Delete note 106 | deleteNote(index, item) { 107 | // 异步, 且取消超时等待 108 | var request = new XMLHttpRequest(); 109 | // request.timeout = 500; // 超时 110 | request.open("post", "/cs/remove_note", true); 111 | request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 112 | request.onload = () => { 113 | if (request.status != 200) return; 114 | var resp = JSON.parse(request.responseText); 115 | if (resp?.removed) { 116 | this.node.CSupdateModelConfig(this.model.name); 117 | this.$message(item.name + " " + this.$t("home.modelDetail.note.deleteNoteSuccess")); 118 | } else { 119 | this.$message(item.name + " " + this.$t("home.modelDetail.note.deleteNoteFail")); 120 | } 121 | }; 122 | let mtype = this.node.CSgetModelWidgetType(); 123 | let body = { mtype: mtype, mname: this.model?.name, note: item, name: item.name }; 124 | request.send(JSON.stringify(body)); 125 | }, 126 | }, 127 | template: `
128 |
129 |
130 | 134 |
135 |

{{$t('home.searchValue')}} : {{this.note_key}}

136 |
137 |
138 | 139 |
140 |
141 |
142 | {{item.name}} 143 |
144 | 145 | 146 |
147 |
148 | 157 |
158 |
159 |
160 | {{$t('noResult')}} 161 |
162 |
163 | {{ $t('home.modelDetail.note.noNoteTip')}} 164 |
165 | 166 |
167 | 168 |
`, 169 | }; 170 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/note/index.less: -------------------------------------------------------------------------------- 1 | .note { 2 | padding: 0.5vw; 3 | flex: 1; 4 | display: flex; 5 | flex-direction: column; 6 | overflow: hidden; 7 | background: #1f1f1f; 8 | border-radius: 0.5vw; 9 | margin-bottom: 0.3vw; 10 | .notes { 11 | display: flex; 12 | height: 50%; 13 | flex-direction: column; 14 | flex: 1; 15 | .search_area { 16 | position: relative; 17 | .search { 18 | width: 100%; 19 | display: flex; 20 | align-items: center; 21 | background: #383838; 22 | height: 1.6vw; 23 | min-height: 32px; 24 | border-radius: 5px; 25 | input { 26 | flex: 1; 27 | height: 100%; 28 | background: transparent; 29 | outline: none; 30 | border: none; 31 | color: #c4c4c4; 32 | padding: 0 0.5vw; 33 | } 34 | span { 35 | position: relative; 36 | display: flex; 37 | justify-content: center; 38 | align-items: center; 39 | width: 2.3vw; 40 | height: 100%; 41 | cursor: pointer; 42 | em { 43 | color: #c4c4c4; 44 | } 45 | 46 | &::before { 47 | content: ""; 48 | position: absolute; 49 | left: 0; 50 | top: 50%; 51 | transform: translate(-100%, -50%); 52 | width: 0.2vw; 53 | height: 70%; 54 | background: #1f1f1f; 55 | border-radius: 1vw; 56 | } 57 | &:hover em { 58 | color: #2a82e4; 59 | } 60 | } 61 | } 62 | .search_key { 63 | width: 100%; 64 | position: absolute; 65 | display: flex; 66 | align-items: center; 67 | left: 0; 68 | top: 0; 69 | height: 1.6vw; 70 | min-height: 32px; 71 | background: #808080; 72 | border-radius: 5px; 73 | padding: 0 0.5vw; 74 | transition: 0.3s; 75 | p { 76 | width: 100%; 77 | white-space: nowrap; 78 | overflow: hidden; 79 | text-overflow: ellipsis; 80 | font-size: 0.8rem; 81 | } 82 | } 83 | &:hover .search_key { 84 | visibility: hidden; 85 | opacity: 0; 86 | } 87 | } 88 | .note_list { 89 | margin-top: 0.5vw; 90 | flex: 1; 91 | // display: grid; 92 | // grid-gap: 0.3vw; 93 | // grid-auto-rows: 2.4vw; /* 固定每行的高度 */ 94 | grid-auto-rows: auto; /* 让行高自动 */ 95 | overflow-y: auto; 96 | padding: 0 4px 5px 0; 97 | .note_item { 98 | width: 100%; 99 | height: 2.4vw; 100 | margin-bottom: 0.3vw; 101 | border: 1px solid #3d3d3d; 102 | background: #262626; 103 | border-radius: 0.3vw; 104 | display: grid; 105 | // grid-template-rows: auto auto; /* 确保行高可以自适应 */ 106 | align-items: center; 107 | color: #ffffff; 108 | padding: 0 1vw; 109 | transition: 0.3s; 110 | .note_header { 111 | display: flex; 112 | justify-content: space-between; 113 | margin: 0.3vw 0.1vw; 114 | align-items: center; 115 | .name { 116 | font-size: 1.1rem; 117 | font-weight: bold; 118 | } 119 | .option { 120 | display: none; 121 | em { 122 | font-size: 1.1rem; 123 | cursor: pointer; 124 | margin-left: 0.1vw; 125 | } 126 | } 127 | } 128 | &:hover { 129 | background: #383838; 130 | .option { 131 | display: block; 132 | } 133 | } 134 | &.selected { 135 | background: #4a4a4a; /* 高亮颜色 */ 136 | border-color: #ffffff; /* 高亮边框颜色 */ 137 | height: auto; /* 让高度自动适应内容,包括文本编辑框 */ 138 | } 139 | .note-content-editor { 140 | width: 100%; 141 | height: auto; 142 | padding: 0.5vw; 143 | margin-top: 0.2vw; 144 | margin-bottom: 0.5vw; 145 | background: #3d3d3d; 146 | color: #ffffff; 147 | // border: 1px solid #ffffff; 148 | border-radius: 0.3vw; 149 | resize: none; /* 禁止调整文本框大小 */ 150 | box-sizing: border-box; /* 确保内边距和边框包含在宽度和高度内 */ 151 | overflow: hidden; /* 隐藏滚动条 */ 152 | } 153 | } 154 | &::-webkit-scrollbar { 155 | width: 8px; 156 | background: transparent; 157 | } 158 | &::-webkit-scrollbar-thumb:vertical { 159 | border-radius: 20px; 160 | background: #808080; 161 | } 162 | } 163 | .empty_note { 164 | margin-top: 0.5vw; 165 | flex: 1; 166 | } 167 | button { 168 | margin-top: 5px; 169 | width: 100%; 170 | height: 2.1vw; 171 | border-radius: 0.3vw; 172 | font-weight: bold; 173 | font-size: 1.1rem; 174 | background: #383838; 175 | transition: 0.3s; 176 | color: #ffffff; 177 | &:hover { 178 | background: #2a82e4; 179 | } 180 | } 181 | } 182 | @import url(../note/form/index.less); 183 | } 184 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/workflow/form/index.css: -------------------------------------------------------------------------------- 1 | .form { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100vw; 6 | height: 100vh; 7 | z-index: 9999; 8 | background: #181818a2; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | } 13 | .form .form_content { 14 | width: 100%; 15 | height: 13.9vw; 16 | background: #1a1b1c; 17 | border-radius: 0.5vw; 18 | box-shadow: 0 0 5px 1px #60606084; 19 | color: #ffffff; 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | justify-content: center; 24 | } 25 | .form .form_content p { 26 | font-size: 1.3rem; 27 | font-weight: bold; 28 | } 29 | .form .form_content .input_area { 30 | margin: 1vw 0 2.2vw 0; 31 | display: flex; 32 | align-items: center; 33 | position: relative; 34 | } 35 | .form .form_content .input_area input { 36 | width: 18vw; 37 | height: 1.6vw; 38 | min-height: 32px; 39 | color: #ffffff; 40 | font-weight: bold; 41 | background: #383838; 42 | font-size: 1.2rem; 43 | border-radius: 2vw; 44 | padding: 0 0.7vw; 45 | } 46 | .form .form_content .input_area .show_clear { 47 | opacity: 1 !important; 48 | visibility: visible !important; 49 | } 50 | .form .form_content .input_area .clear { 51 | position: absolute; 52 | right: -0.3vw; 53 | transform: translateX(100%); 54 | width: 1.6vw; 55 | height: 1.6vw; 56 | border-radius: 50%; 57 | display: flex; 58 | justify-content: center; 59 | align-items: center; 60 | cursor: pointer; 61 | transition: 0.3s; 62 | background: #383838; 63 | opacity: 0; 64 | visibility: hidden; 65 | } 66 | .form .form_content .input_area .clear em { 67 | font-size: 0.8rem; 68 | } 69 | .form .form_content .input_area .clear:hover { 70 | background: #ff5733; 71 | } 72 | .form .form_content .button_group { 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | } 77 | .form .form_content .button_group .button_item { 78 | height: 2.2vw; 79 | min-height: 40px; 80 | background: #383838; 81 | transition: 0.2s; 82 | display: flex; 83 | justify-content: space-between; 84 | align-items: center; 85 | white-space: nowrap; 86 | cursor: pointer; 87 | } 88 | .form .form_content .button_group .button_item .icon { 89 | width: 1.5vw; 90 | height: 1.5vw; 91 | min-height: 23px; 92 | min-width: 23px; 93 | display: flex; 94 | justify-content: center; 95 | align-items: center; 96 | transition: 0.2s; 97 | border-radius: 50%; 98 | } 99 | .form .form_content .button_group .button_item .icon em { 100 | font-size: 1rem; 101 | } 102 | .form .form_content .button_group .button_item .text { 103 | font-size: 1rem; 104 | } 105 | .form .form_content .button_group .accept { 106 | border-radius: 2vw 0 0 2vw; 107 | padding: 0 1vw 0 0.5vw; 108 | } 109 | .form .form_content .button_group .accept .icon { 110 | margin-right: 1.5vw; 111 | background: #43cf7c; 112 | } 113 | .form .form_content .button_group .accept:hover { 114 | background: #43cf7c; 115 | color: #ffffff; 116 | } 117 | .form .form_content .button_group .accept:hover .icon { 118 | background: #383838; 119 | } 120 | .form .form_content .button_group .refuse { 121 | border-radius: 0 2vw 2vw 0; 122 | padding: 0 0.5vw 0 1vw; 123 | } 124 | .form .form_content .button_group .refuse .icon { 125 | margin-left: 1.5vw; 126 | background: #ff5733; 127 | } 128 | .form .form_content .button_group .refuse:hover { 129 | background: #ff5733; 130 | color: #ffffff; 131 | } 132 | .form .form_content .button_group .refuse:hover .icon { 133 | background: #383838; 134 | } 135 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/workflow/form/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "WorkflowForm", 3 | data() { 4 | return { 5 | value: "", 6 | }; 7 | }, 8 | mounted() {}, 9 | methods: { 10 | cancel() { 11 | this.$emit("displayForm", false); 12 | }, 13 | determine() { 14 | this.$emit("displayForm", false); 15 | this.$emit("saveWorkflow", this.value); 16 | }, 17 | clearInput() { 18 | this.value = ""; 19 | }, 20 | }, 21 | template: ` 22 |
23 |
24 |

{{$t("home.modelDetail.workflow.form.title")}}

25 |
26 | 27 | 28 |
29 |
30 |
31 | 32 | {{$t("home.modelDetail.workflow.form.confirmText")}} 33 |
34 |
35 | {{$t("home.modelDetail.workflow.form.cancelText")}} 36 | 37 |
38 |
39 |
40 |
41 | `, 42 | }; 43 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/workflow/form/index.less: -------------------------------------------------------------------------------- 1 | .form { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100vw; 6 | height: 100vh; 7 | z-index: 9999; 8 | background: #181818a2; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | .form_content { 13 | width: 100%; 14 | height: 13.9vw; 15 | background: #1a1b1c; 16 | border-radius: 0.5vw; 17 | box-shadow: 0 0 5px 1px #60606084; 18 | color: #ffffff; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | p { 24 | font-size: 1.3rem; 25 | font-weight: bold; 26 | } 27 | .input_area { 28 | margin: 1vw 0 2.2vw 0; 29 | display: flex; 30 | align-items: center; 31 | position: relative; 32 | input { 33 | width: 18vw; 34 | height: 1.6vw; 35 | min-height: 32px; 36 | color: #ffffff; 37 | font-weight: bold; 38 | background: #383838; 39 | font-size: 1.2rem; 40 | border-radius: 2vw; 41 | padding: 0 0.7vw; 42 | } 43 | .show_clear { 44 | opacity: 1 !important; 45 | visibility: visible !important; 46 | } 47 | .clear { 48 | position: absolute; 49 | right: -0.3vw; 50 | transform: translateX(100%); 51 | width: 1.6vw; 52 | height: 1.6vw; 53 | border-radius: 50%; 54 | display: flex; 55 | justify-content: center; 56 | align-items: center; 57 | cursor: pointer; 58 | transition: 0.3s; 59 | background: #383838; 60 | opacity: 0; 61 | visibility: hidden; 62 | em { 63 | font-size: 0.8rem; 64 | } 65 | &:hover { 66 | background: #ff5733; 67 | } 68 | } 69 | } 70 | 71 | .button_group { 72 | display: flex; 73 | align-items: center; 74 | justify-content: center; 75 | .button_item { 76 | height: 2.2vw; 77 | min-height: 40px; 78 | background: #383838; 79 | transition: 0.2s; 80 | display: flex; 81 | justify-content: space-between; 82 | align-items: center; 83 | white-space: nowrap; 84 | cursor: pointer; 85 | .icon { 86 | width: 1.5vw; 87 | height: 1.5vw; 88 | min-height: 23px; 89 | min-width: 23px; 90 | display: flex; 91 | justify-content: center; 92 | align-items: center; 93 | transition: 0.2s; 94 | border-radius: 50%; 95 | em { 96 | font-size: 1rem; 97 | } 98 | } 99 | .text { 100 | font-size: 1rem; 101 | } 102 | } 103 | .accept { 104 | border-radius: 2vw 0 0 2vw; 105 | padding: 0 1vw 0 0.5vw; 106 | .icon { 107 | margin-right: 1.5vw; 108 | background: #43cf7c; 109 | } 110 | &:hover { 111 | background: #43cf7c; 112 | color: #ffffff; 113 | .icon { 114 | background: #383838; 115 | } 116 | } 117 | } 118 | .refuse { 119 | border-radius: 0 2vw 2vw 0; 120 | padding: 0 0.5vw 0 1vw; 121 | .icon { 122 | margin-left: 1.5vw; 123 | background: #ff5733; 124 | } 125 | &:hover { 126 | background: #ff5733; 127 | color: #ffffff; 128 | .icon { 129 | background: #383838; 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/workflow/index.css: -------------------------------------------------------------------------------- 1 | .workflow { 2 | padding: 0.5vw; 3 | flex: 1; 4 | display: flex; 5 | flex-direction: column; 6 | overflow: hidden; 7 | background: #1f1f1f; 8 | border-radius: 0.5vw; 9 | margin-bottom: 0.3vw; 10 | } 11 | .workflow .workflow_content { 12 | display: flex; 13 | height: 50%; 14 | flex-direction: column; 15 | flex: 1; 16 | } 17 | .workflow .workflow_content .search_area { 18 | position: relative; 19 | } 20 | .workflow .workflow_content .search_area .search { 21 | width: 100%; 22 | display: flex; 23 | align-items: center; 24 | background: #383838; 25 | height: 1.6vw; 26 | min-height: 32px; 27 | border-radius: 5px; 28 | } 29 | .workflow .workflow_content .search_area .search input { 30 | flex: 1; 31 | height: 100%; 32 | background: transparent; 33 | outline: none; 34 | border: none; 35 | color: #c4c4c4; 36 | padding: 0 0.5vw; 37 | } 38 | .workflow .workflow_content .search_area .search span { 39 | position: relative; 40 | display: flex; 41 | justify-content: center; 42 | align-items: center; 43 | width: 2.3vw; 44 | height: 100%; 45 | cursor: pointer; 46 | } 47 | .workflow .workflow_content .search_area .search span em { 48 | color: #c4c4c4; 49 | } 50 | .workflow .workflow_content .search_area .search span::before { 51 | content: ""; 52 | position: absolute; 53 | left: 0; 54 | top: 50%; 55 | transform: translate(-100%, -50%); 56 | width: 0.2vw; 57 | height: 70%; 58 | background: #1f1f1f; 59 | border-radius: 1vw; 60 | } 61 | .workflow .workflow_content .search_area .search span:hover em { 62 | color: #2a82e4; 63 | } 64 | .workflow .workflow_content .search_area .search_key { 65 | width: 100%; 66 | position: absolute; 67 | display: flex; 68 | align-items: center; 69 | left: 0; 70 | top: 0; 71 | height: 1.6vw; 72 | min-height: 32px; 73 | background: #808080; 74 | border-radius: 5px; 75 | padding: 0 0.5vw; 76 | transition: 0.3s; 77 | } 78 | .workflow .workflow_content .search_area .search_key p { 79 | width: 100%; 80 | white-space: nowrap; 81 | overflow: hidden; 82 | text-overflow: ellipsis; 83 | font-size: 0.8rem; 84 | } 85 | .workflow .workflow_content .search_area:hover .search_key { 86 | visibility: hidden; 87 | opacity: 0; 88 | } 89 | .workflow .workflow_content .workflow_list { 90 | margin-top: 0.5vw; 91 | flex: 1; 92 | display: grid; 93 | grid-gap: 0.3vw; 94 | grid-auto-rows: 2.4vw; 95 | overflow-y: auto; 96 | padding: 0 4px 5px 0; 97 | } 98 | .workflow .workflow_content .workflow_list .workflow_item { 99 | width: 100%; 100 | height: 100%; 101 | border: 1px solid #3d3d3d; 102 | background: #262626; 103 | border-radius: 0.3vw; 104 | display: flex; 105 | justify-content: space-between; 106 | align-items: center; 107 | color: #ffffff; 108 | padding: 0 1vw; 109 | transition: 0.3s; 110 | } 111 | .workflow .workflow_content .workflow_list .workflow_item .name { 112 | font-size: 1.1rem; 113 | font-weight: bold; 114 | } 115 | .workflow .workflow_content .workflow_list .workflow_item .option { 116 | display: none; 117 | } 118 | .workflow .workflow_content .workflow_list .workflow_item .option em { 119 | font-size: 1.1rem; 120 | cursor: pointer; 121 | margin-left: 0.1vw; 122 | } 123 | .workflow .workflow_content .workflow_list .workflow_item:hover { 124 | background: #383838; 125 | } 126 | .workflow .workflow_content .workflow_list .workflow_item:hover .option { 127 | display: block; 128 | } 129 | .workflow .workflow_content .workflow_list::-webkit-scrollbar { 130 | width: 8px; 131 | background: transparent; 132 | } 133 | .workflow .workflow_content .workflow_list::-webkit-scrollbar-thumb:vertical { 134 | border-radius: 20px; 135 | background: #808080; 136 | } 137 | .workflow .workflow_content .empty_list { 138 | margin-top: 0.5vw; 139 | flex: 1; 140 | } 141 | .workflow .workflow_content button { 142 | margin-top: 5px; 143 | width: 100%; 144 | height: 2.1vw; 145 | border-radius: 0.3vw; 146 | font-weight: bold; 147 | font-size: 1.1rem; 148 | background: #383838; 149 | transition: 0.3s; 150 | color: #ffffff; 151 | } 152 | .workflow .workflow_content button:hover { 153 | background: #2a82e4; 154 | } 155 | .workflow .form { 156 | position: fixed; 157 | top: 0; 158 | left: 0; 159 | width: 100vw; 160 | height: 100vh; 161 | z-index: 9999; 162 | background: #181818a2; 163 | display: flex; 164 | justify-content: center; 165 | align-items: center; 166 | } 167 | .workflow .form .form_content { 168 | width: 100%; 169 | height: 13.9vw; 170 | background: #1a1b1c; 171 | border-radius: 0.5vw; 172 | box-shadow: 0 0 5px 1px #60606084; 173 | color: #ffffff; 174 | display: flex; 175 | flex-direction: column; 176 | align-items: center; 177 | justify-content: center; 178 | } 179 | .workflow .form .form_content p { 180 | font-size: 1.3rem; 181 | font-weight: bold; 182 | } 183 | .workflow .form .form_content .input_area { 184 | margin: 1vw 0 2.2vw 0; 185 | display: flex; 186 | align-items: center; 187 | position: relative; 188 | } 189 | .workflow .form .form_content .input_area input { 190 | width: 18vw; 191 | height: 1.6vw; 192 | min-height: 32px; 193 | color: #ffffff; 194 | font-weight: bold; 195 | background: #383838; 196 | font-size: 1.2rem; 197 | border-radius: 2vw; 198 | padding: 0 0.7vw; 199 | } 200 | .workflow .form .form_content .input_area .show_clear { 201 | opacity: 1 !important; 202 | visibility: visible !important; 203 | } 204 | .workflow .form .form_content .input_area .clear { 205 | position: absolute; 206 | right: -0.3vw; 207 | transform: translateX(100%); 208 | width: 1.6vw; 209 | height: 1.6vw; 210 | border-radius: 50%; 211 | display: flex; 212 | justify-content: center; 213 | align-items: center; 214 | cursor: pointer; 215 | transition: 0.3s; 216 | background: #383838; 217 | opacity: 0; 218 | visibility: hidden; 219 | } 220 | .workflow .form .form_content .input_area .clear em { 221 | font-size: 0.8rem; 222 | } 223 | .workflow .form .form_content .input_area .clear:hover { 224 | background: #ff5733; 225 | } 226 | .workflow .form .form_content .button_group { 227 | display: flex; 228 | align-items: center; 229 | justify-content: center; 230 | } 231 | .workflow .form .form_content .button_group .button_item { 232 | height: 2.2vw; 233 | min-height: 40px; 234 | background: #383838; 235 | transition: 0.2s; 236 | display: flex; 237 | justify-content: space-between; 238 | align-items: center; 239 | white-space: nowrap; 240 | cursor: pointer; 241 | } 242 | .workflow .form .form_content .button_group .button_item .icon { 243 | width: 1.5vw; 244 | height: 1.5vw; 245 | min-height: 23px; 246 | min-width: 23px; 247 | display: flex; 248 | justify-content: center; 249 | align-items: center; 250 | transition: 0.2s; 251 | border-radius: 50%; 252 | } 253 | .workflow .form .form_content .button_group .button_item .icon em { 254 | font-size: 1rem; 255 | } 256 | .workflow .form .form_content .button_group .button_item .text { 257 | font-size: 1rem; 258 | } 259 | .workflow .form .form_content .button_group .accept { 260 | border-radius: 2vw 0 0 2vw; 261 | padding: 0 1vw 0 0.5vw; 262 | } 263 | .workflow .form .form_content .button_group .accept .icon { 264 | margin-right: 1.5vw; 265 | background: #43cf7c; 266 | } 267 | .workflow .form .form_content .button_group .accept:hover { 268 | background: #43cf7c; 269 | color: #ffffff; 270 | } 271 | .workflow .form .form_content .button_group .accept:hover .icon { 272 | background: #383838; 273 | } 274 | .workflow .form .form_content .button_group .refuse { 275 | border-radius: 0 2vw 2vw 0; 276 | padding: 0 0.5vw 0 1vw; 277 | } 278 | .workflow .form .form_content .button_group .refuse .icon { 279 | margin-left: 1.5vw; 280 | background: #ff5733; 281 | } 282 | .workflow .form .form_content .button_group .refuse:hover { 283 | background: #ff5733; 284 | color: #ffffff; 285 | } 286 | .workflow .form .form_content .button_group .refuse:hover .icon { 287 | background: #383838; 288 | } 289 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/workflow/index.js: -------------------------------------------------------------------------------- 1 | import WorkflowForm from "../workflow/form/index.js"; 2 | export default { 3 | name: "Workflow", 4 | props: ["model"], 5 | components: { WorkflowForm }, 6 | data() { 7 | return { 8 | value: "", 9 | key: "", 10 | list: [], 11 | isShowForm: false, 12 | }; 13 | }, 14 | watch: { 15 | model: { 16 | handler(model) { 17 | let list = []; 18 | if (model?.workflows?.length > 0) { 19 | // workflows 是一个 array 20 | for (let i = 0; i < model.workflows.length; i++) { 21 | let wk = model.workflows[i]; // 以.json结尾 22 | list.push({ name: wk.replace(/\.json$/, ""), workflow: wk }); 23 | } 24 | } 25 | // list 按 ascii 排序 26 | list.sort((a, b) => a.name.localeCompare(b.name)); 27 | this.list = list; 28 | }, 29 | deep: true, 30 | immediate: true, 31 | }, 32 | }, 33 | computed: { 34 | filterList() { 35 | return this.list.filter((x) => x.name.includes(this.value)); 36 | }, 37 | }, 38 | mounted() { 39 | this.handleSearch(); 40 | }, 41 | methods: { 42 | // Enter key for search 43 | handleKeyDown(e) { 44 | if (e.key === "Enter") { 45 | this.handleSearch(); 46 | } 47 | }, 48 | // Click to search 49 | handleSearch() { 50 | this.key = this.value; 51 | }, 52 | // Click add workflow 53 | displayForm(flag) { 54 | this.isShowForm = flag; 55 | }, 56 | saveWorkflow(name) { 57 | // 随机生成 58 | if (!name) name = `wk-${Math.random().toString(36).substring(2, 10)}`; 59 | var node = this.node; 60 | var data = window.parent.app.graph.serialize(); 61 | var request = new XMLHttpRequest(); 62 | // request.timeout = 500; // 超时 63 | request.open("post", "/cs/save_workflow", true); 64 | request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 65 | request.onload = () => { 66 | if (request.status != 200) return; 67 | var resp = JSON.parse(request.responseText); 68 | if (resp?.saved) { 69 | node.CSupdateModelConfig(this.model.name); 70 | this.$message(name + " " + this.$t("home.modelDetail.workflow.saveSuccess")); 71 | } else { 72 | this.$message(name + " " + this.$t("home.modelDetail.workflow.saveFail")); 73 | } 74 | }; 75 | // request.ontimeout = () => { 76 | // this.$message(name + " " + this.$t("home.modelDetail.workflow.saveTimeout")); 77 | // }; 78 | let mtype = node.CSgetModelWidgetType(); 79 | let body = { mtype: mtype, mname: this.model?.name, data, name }; 80 | request.send(JSON.stringify(body)); 81 | }, 82 | // Copy workflow 83 | copyWorkflow(item) { 84 | var request = new XMLHttpRequest(); 85 | request.timeout = 500; // 超时 86 | request.open("post", "/cs/fetch_workflow", true); 87 | request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 88 | request.onload = async () => { 89 | if (request.status != 200) return; 90 | var resp = JSON.parse(request.responseText); 91 | if (resp) { 92 | await navigator.clipboard.writeText(request.responseText); 93 | this.$message(item.name + " " + this.$t("home.modelDetail.workflow.copySuccess")); 94 | } else { 95 | this.$message(item.name + " " + this.$t("home.modelDetail.workflow.copyFail")); 96 | } 97 | }; 98 | request.ontimeout = () => { 99 | this.$message(item.name + " " + this.$t("home.modelDetail.workflow.copyTimeout")); 100 | }; 101 | let mtype = this.node.CSgetModelWidgetType(); 102 | let body = { mtype: mtype, mname: this.model?.name, workflow: item.workflow, name: item.name }; 103 | request.send(JSON.stringify(body)); 104 | }, 105 | // Delete workflow 106 | deleteWorkflow(index, item) { 107 | // 异步, 且取消超时等待 108 | var request = new XMLHttpRequest(); 109 | // request.timeout = 500; // 超时 110 | request.open("post", "/cs/remove_workflow", true); 111 | request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 112 | request.onload = () => { 113 | if (request.status != 200) return; 114 | var resp = JSON.parse(request.responseText); 115 | if (resp?.removed) { 116 | this.node.CSupdateModelConfig(this.model.name); 117 | this.$message(item.name + " " + this.$t("home.modelDetail.workflow.deleteSuccess")); 118 | } else { 119 | this.$message(item.name + " " + this.$t("home.modelDetail.workflow.deleteFail")); 120 | } 121 | }; 122 | // request.ontimeout = () => { 123 | // this.$message(item.name + " " + this.$t("home.modelDetail.workflow.deleteTimeout")); 124 | // }; 125 | let mtype = this.node.CSgetModelWidgetType(); 126 | let body = { mtype: mtype, mname: this.model?.name, workflow: item.workflow, name: item.name }; 127 | request.send(JSON.stringify(body)); 128 | }, 129 | // Import 130 | importWorkflow(item) { 131 | var request = new XMLHttpRequest(); 132 | request.timeout = 500; // 超时 133 | request.open("post", "/cs/fetch_workflow", true); 134 | request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 135 | request.onload = async () => { 136 | if (request.status != 200) return; 137 | var resp = JSON.parse(request.responseText); 138 | if (resp) { 139 | try { 140 | if (resp.hasOwnProperty("workflow")) resp = resp.workflow; 141 | await window.parent.app.loadGraphData(resp); 142 | this.$message(item.name + " " + this.$t("home.modelDetail.workflow.importSuccess")); 143 | } catch (e) { 144 | this.$message(item.name + " " + this.$t("home.modelDetail.workflow.importFail") + `: ${e}`); 145 | } 146 | } else { 147 | this.$message(item.name + " " + this.$t("home.modelDetail.workflow.importFail")); 148 | } 149 | }; 150 | request.ontimeout = () => { 151 | this.$message(item.name + " " + this.$t("home.modelDetail.workflow.importTimeout")); 152 | }; 153 | let mtype = this.node.CSgetModelWidgetType(); 154 | let body = { mtype: mtype, mname: this.model?.name, workflow: item.workflow, name: item.name }; 155 | request.send(JSON.stringify(body)); 156 | }, 157 | }, 158 | template: `
159 |
160 |
161 | 165 |
166 |

{{$t('home.searchValue')}} : {{this.key}}

167 |
168 |
169 | 170 |
171 |
172 | {{item.name}} 173 |
174 | 175 | 176 | 177 |
178 |
179 |
180 |
181 | {{$t('noResult')}} 182 |
183 |
184 | {{ $t('home.modelDetail.workflow.noWorkflowTip')}} 185 |
186 | 187 |
188 | 189 |
`, 190 | }; 191 | -------------------------------------------------------------------------------- /loader/components/home/model/detail/workflow/index.less: -------------------------------------------------------------------------------- 1 | .workflow { 2 | padding: 0.5vw; 3 | flex: 1; 4 | display: flex; 5 | flex-direction: column; 6 | overflow: hidden; 7 | background: #1f1f1f; 8 | border-radius: 0.5vw; 9 | margin-bottom: 0.3vw; 10 | .workflow_content { 11 | display: flex; 12 | height: 50%; 13 | flex-direction: column; 14 | flex: 1; 15 | .search_area { 16 | position: relative; 17 | .search { 18 | width: 100%; 19 | display: flex; 20 | align-items: center; 21 | background: #383838; 22 | height: 1.6vw; 23 | min-height: 32px; 24 | border-radius: 5px; 25 | input { 26 | flex: 1; 27 | height: 100%; 28 | background: transparent; 29 | outline: none; 30 | border: none; 31 | color: #c4c4c4; 32 | padding: 0 0.5vw; 33 | } 34 | span { 35 | position: relative; 36 | display: flex; 37 | justify-content: center; 38 | align-items: center; 39 | width: 2.3vw; 40 | height: 100%; 41 | cursor: pointer; 42 | em { 43 | color: #c4c4c4; 44 | } 45 | 46 | &::before { 47 | content: ""; 48 | position: absolute; 49 | left: 0; 50 | top: 50%; 51 | transform: translate(-100%, -50%); 52 | width: 0.2vw; 53 | height: 70%; 54 | background: #1f1f1f; 55 | border-radius: 1vw; 56 | } 57 | &:hover em { 58 | color: #2a82e4; 59 | } 60 | } 61 | } 62 | .search_key { 63 | width: 100%; 64 | position: absolute; 65 | display: flex; 66 | align-items: center; 67 | left: 0; 68 | top: 0; 69 | height: 1.6vw; 70 | min-height: 32px; 71 | background: #808080; 72 | border-radius: 5px; 73 | padding: 0 0.5vw; 74 | transition: 0.3s; 75 | p { 76 | width: 100%; 77 | white-space: nowrap; 78 | overflow: hidden; 79 | text-overflow: ellipsis; 80 | font-size: 0.8rem; 81 | } 82 | } 83 | &:hover .search_key { 84 | visibility: hidden; 85 | opacity: 0; 86 | } 87 | } 88 | .workflow_list { 89 | margin-top: 0.5vw; 90 | flex: 1; 91 | display: grid; 92 | grid-gap: 0.3vw; 93 | grid-auto-rows: 2.4vw; 94 | overflow-y: auto; 95 | padding: 0 4px 5px 0; 96 | .workflow_item { 97 | width: 100%; 98 | height: 100%; 99 | border: 1px solid #3d3d3d; 100 | background: #262626; 101 | border-radius: 0.3vw; 102 | display: flex; 103 | justify-content: space-between; 104 | align-items: center; 105 | color: #ffffff; 106 | padding: 0 1vw; 107 | transition: 0.3s; 108 | .name { 109 | font-size: 1.1rem; 110 | font-weight: bold; 111 | } 112 | .option { 113 | display: none; 114 | em { 115 | font-size: 1.1rem; 116 | cursor: pointer; 117 | margin-left: 0.1vw; 118 | } 119 | } 120 | &:hover { 121 | background: #383838; 122 | .option { 123 | display: block; 124 | } 125 | } 126 | } 127 | &::-webkit-scrollbar { 128 | width: 8px; 129 | background: transparent; 130 | } 131 | &::-webkit-scrollbar-thumb:vertical { 132 | border-radius: 20px; 133 | background: #808080; 134 | } 135 | } 136 | .empty_list { 137 | margin-top: 0.5vw; 138 | flex: 1; 139 | } 140 | button { 141 | margin-top: 5px; 142 | width: 100%; 143 | height: 2.1vw; 144 | border-radius: 0.3vw; 145 | font-weight: bold; 146 | font-size: 1.1rem; 147 | background: #383838; 148 | transition: 0.3s; 149 | color: #ffffff; 150 | &:hover { 151 | background: #2a82e4; 152 | } 153 | } 154 | } 155 | @import url(../workflow/form/index.less); 156 | } 157 | -------------------------------------------------------------------------------- /loader/components/home/model/index.js: -------------------------------------------------------------------------------- 1 | import ModelList from "./list/index.js"; 2 | import ModelDetail from "./detail/index.js"; 3 | // import { api } from "/scripts/api.js"; 4 | 5 | function getApi() { 6 | const api = window.comfyAPI?.api?.api || window.parent.comfyAPI?.api?.api; 7 | api.api_base = ""; 8 | return api; 9 | } 10 | 11 | async function update_config(model, key, old_data, cb = (resp) => {}) { 12 | try { 13 | const body = new FormData(); 14 | body.append("data", JSON.stringify(model)); 15 | // 如果 old_data为基本类型 则直接赋值 16 | if (typeof old_data !== "object") { 17 | body.append("old_data", old_data); 18 | } else { 19 | body.append("old_data", JSON.stringify(old_data)); 20 | } 21 | body.append("key", key); 22 | const api = getApi(); 23 | let resp = await api.fetchApi("/cs/update_config", { 24 | method: "POST", 25 | body, 26 | }); 27 | cb(resp); 28 | } catch (error) { 29 | alert(error); 30 | } 31 | } 32 | 33 | export default { 34 | components: { ModelList, ModelDetail }, 35 | props: { 36 | allList: { 37 | default: () => { 38 | return []; 39 | }, 40 | type: Array, 41 | }, 42 | selectedWidget: { 43 | default: null, 44 | type: String, 45 | }, 46 | column: { 47 | default: 0, 48 | type: Number, 49 | }, 50 | searchParameter: { 51 | default: () => { 52 | return {}; 53 | }, 54 | type: Object, 55 | }, 56 | }, 57 | watch: { 58 | allList: { 59 | handler(newValue) { 60 | this.filterList(this.searchParameter); 61 | }, 62 | immediate: true, 63 | }, 64 | curList: { 65 | handler(newValue) { 66 | this.$store.commit("prop/setCurModelList", newValue); 67 | }, 68 | }, 69 | searchParameter: { 70 | handler(newValue) { 71 | this.filterList(newValue); 72 | }, 73 | immediate: true, 74 | deep: true, 75 | }, 76 | }, 77 | data() { 78 | return { 79 | curList: [], 80 | selectedModel: null, 81 | }; 82 | }, 83 | methods: { 84 | // Use model 85 | useModel(model) { 86 | let renderer = this.renderer; 87 | let node = this.node; 88 | if (!node) return; 89 | if (!renderer?.rendering) { 90 | node.CSsetModelWidget(model.data || model.name); 91 | window.parent.postMessage({ type: "close_loader_page" }, "*"); 92 | return; 93 | } 94 | this.$confirmBox({ 95 | describe: this.$t("home.model.useModelConfirm"), 96 | refuseText: this.$t("confirmBox.refuseText"), 97 | acceptText: this.$t("confirmBox.acceptText"), 98 | accept: () => { 99 | // 点击确认调用 100 | renderer?.stop(); 101 | node.CSsetModelWidget(model.data || model.name); 102 | window.parent.postMessage({ type: "close_loader_page" }, "*"); 103 | }, 104 | refuse: () => {}, 105 | }); 106 | }, 107 | // Change name 108 | modifyName(model, value) { 109 | let old_data = model.name; 110 | model.name = value; 111 | async function cb(resp) { 112 | if (resp.status === 200) { 113 | let json = await resp.json(); 114 | if (!json.hasOwnProperty("status") || !json.status) { 115 | model.name = old_data; 116 | alert("修改失败"); 117 | } 118 | } 119 | } 120 | update_config(model, "name", old_data, cb); 121 | }, 122 | // Change level 123 | changeLevel(level) { 124 | let old_data = this.selectedModel.level; 125 | this.selectedModel.level = level; 126 | update_config(this.selectedModel, "level", old_data); 127 | }, 128 | // Add tag 129 | addTag(value) { 130 | let old_data = this.selectedModel.tags; 131 | this.selectedModel.tags.push(value); 132 | update_config(this.selectedModel, "tags", old_data); 133 | }, 134 | // Delete tag 135 | deleteTag(index) { 136 | // 如果 当前删除的tag在 dir_tags中 则不允许删除 137 | let tag = this.selectedModel.tags[index]; 138 | if (this.selectedModel.dir_tags?.includes(tag)) { 139 | alert(this.$t("home.model.dirTagCantDelete")); 140 | return; 141 | } 142 | let old_data = this.selectedModel.tags; 143 | this.selectedModel.tags.splice(index, 1); 144 | update_config(this.selectedModel, "tags", old_data); 145 | }, 146 | // Modify cover 147 | modifyCover(coverSrc) { 148 | this.selectedModel.cover = coverSrc; 149 | }, 150 | // Change selected items 151 | changeSelectedModel(model) { 152 | this.selectedModel = model; 153 | }, 154 | // Filter and filter all data based on criteria 155 | filterList(searchParameter) { 156 | const newList = this.allList.filter((x) => { 157 | const key = x.name.includes(searchParameter.key); 158 | const level = searchParameter.level === 0 || x.level === searchParameter.level; 159 | const tag = searchParameter.tags.length === 0 || x.tags.find((tagItem) => searchParameter.tags.includes(tagItem)); 160 | if (this.node) { 161 | let filters = this.node.CSgetModelFilters(true); 162 | if (filters?.includes(x.name)) { 163 | return false; 164 | } 165 | } 166 | return key && level && tag; 167 | }); 168 | this.sortList(searchParameter.sort, newList); 169 | this.curList = newList; 170 | this.selectedModel = false; 171 | if (this.selectedWidget === null) { 172 | this.selectedModel = this.curList[0] || false; 173 | return; 174 | } 175 | for (let i = 0; i < this.curList.length; i++) { 176 | if (this.curList[i].name === this.selectedWidget) { 177 | this.selectedModel = this.curList[i] || false; 178 | return; 179 | } 180 | } 181 | // selectedWidget 可能不在 curList 中(被屏蔽了) 182 | this.selectedModel = this.curList[0] || false; 183 | }, 184 | // Sort data based on conditions 185 | sortList(orderValue, newList) { 186 | switch (orderValue) { 187 | case 0: 188 | newList.sort((a, b) => b.creationTime - a.creationTime); 189 | break; 190 | case 1: 191 | newList.sort((a, b) => a.creationTime - b.creationTime); 192 | break; 193 | case 2: 194 | newList.sort((a, b) => a.name.localeCompare(b.name, "zh-CN")); 195 | break; 196 | case 3: 197 | newList.sort((a, b) => b.name.localeCompare(a.name, "zh-CN")); 198 | break; 199 | case 4: 200 | newList.sort((a, b) => { 201 | if (a.level === "S" && b.level !== "S") { 202 | return 1; 203 | } else if (a.level !== "S" && b.level === "S") { 204 | return -1; 205 | } 206 | return b.level.localeCompare(a.level, "zh-CN"); 207 | }); 208 | break; 209 | case 5: 210 | newList.sort((a, b) => { 211 | if (a.level === "S" && b.level !== "S") { 212 | return -1; 213 | } else if (a.level !== "S" && b.level === "S") { 214 | return 1; 215 | } 216 | return a.level.localeCompare(b.level, "zh-CN"); 217 | }); 218 | break; 219 | case 6: 220 | newList.sort((a, b) => a.size - b.size); 221 | break; 222 | case 7: 223 | newList.sort((a, b) => b.size - a.size); 224 | break; 225 | } 226 | }, 227 | }, 228 | template: ` 229 |
230 | 231 | 232 |
233 |

{{$t('home.searchValue')}}: {{searchParameter.key}}

234 | {{$t('noResult')}} 235 |
236 |
237 | `, 238 | }; 239 | -------------------------------------------------------------------------------- /loader/components/home/model/index.less: -------------------------------------------------------------------------------- 1 | .model_display { 2 | flex: 1; 3 | width: 100%; 4 | height: 100%; 5 | display: flex; 6 | overflow: hidden; 7 | @import url("./list/index.less"); 8 | @import url("./detail/index.less"); 9 | .empty { 10 | flex: 1; 11 | height: 100%; 12 | font-size: 2rem; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /loader/components/home/model/list/index.css: -------------------------------------------------------------------------------- 1 | .model_list { 2 | width: 100%; 3 | height: 100%; 4 | display: grid; 5 | grid-template-columns: repeat(var(--column), 1fr); 6 | grid-gap: 0.55vw; 7 | grid-auto-rows: max-content; 8 | overflow-y: auto; 9 | padding-right: 0.55vw; 10 | align-content: flex-start; 11 | } 12 | .model_list .selected { 13 | transition: 0.3s; 14 | border: 2px solid #ffffff !important; 15 | filter: contrast(120%); 16 | } 17 | .model_list .model_item { 18 | position: relative; 19 | cursor: pointer; 20 | width: 100%; 21 | border-radius: 0.3vw; 22 | border: 2px solid #3d3d3d; 23 | display: flex; 24 | flex-direction: column; 25 | overflow: hidden; 26 | } 27 | .model_list .model_item .img_container { 28 | width: 100%; 29 | height: calc(var(--height) - 4px); 30 | font-size: 0; 31 | transition: 0.3s; 32 | } 33 | .model_list .model_item .img_container img { 34 | width: 100%; 35 | height: 100%; 36 | object-fit: cover; 37 | } 38 | .model_list .model_item .model_des { 39 | height: var(--height); 40 | width: 100%; 41 | transition: 0.3s; 42 | display: flex; 43 | align-items: center; 44 | justify-content: flex-start; 45 | background: #262626; 46 | padding: 0 0.5vw; 47 | } 48 | .model_list .model_item .model_des .level { 49 | width: 1.56vw; 50 | height: 1.56vw; 51 | display: flex; 52 | justify-content: center; 53 | align-items: center; 54 | color: #ffffff; 55 | font-size: 1.3rem; 56 | background-color: red; 57 | border-radius: 0.3vw; 58 | } 59 | .model_list .model_item .model_des .text_des { 60 | flex: 1; 61 | margin-left: 0.3vw; 62 | width: 100%; 63 | overflow: hidden; 64 | } 65 | .model_list .model_item .model_des .text_des p { 66 | width: 100%; 67 | overflow: hidden; 68 | text-overflow: ellipsis; 69 | white-space: nowrap; 70 | } 71 | .model_list .model_item .model_des .text_des p:nth-of-type(1) { 72 | font-size: 1rem; 73 | font-weight: bold; 74 | } 75 | .model_list .model_item .model_des .text_des p:nth-of-type(2) { 76 | font-size: 0.7rem; 77 | color: #808080; 78 | } 79 | .model_list::-webkit-scrollbar { 80 | width: 15px; 81 | } 82 | .model_list::-webkit-scrollbar-thumb:vertical { 83 | border-radius: 20px; 84 | background: #ff8d1a; 85 | } 86 | .model_list::-webkit-scrollbar-track { 87 | background-color: transparent; 88 | } 89 | -------------------------------------------------------------------------------- /loader/components/home/model/list/index.js: -------------------------------------------------------------------------------- 1 | import { getLevelInf } from "../../../../static/js/public.js"; 2 | 3 | export default { 4 | props: ["curList", "selectedModel", "column"], 5 | data() { 6 | return { 7 | defaultCover: "./static/image/default.jpg", 8 | }; 9 | }, 10 | mounted() {}, 11 | methods: { 12 | // Use selected items 13 | useModel(model) { 14 | this.$emit("useModel", model); 15 | }, 16 | // Change selected items 17 | changeModel(model) { 18 | this.$emit("changeSelectedModel", model); 19 | }, 20 | // Obtain color values based on level 21 | levelInf(level) { 22 | return getLevelInf(level).color || "#808080"; 23 | }, 24 | }, 25 | template: ` 26 |
27 |
28 |
29 | cover 30 |
31 |
32 |
{{item.level}}
33 |
34 |

{{item.name}}

35 |

{{item.type}}

36 |
37 |
38 |
39 |
40 | `, 41 | }; 42 | -------------------------------------------------------------------------------- /loader/components/home/model/list/index.less: -------------------------------------------------------------------------------- 1 | .model_list { 2 | width: 100%; 3 | height: 100%; 4 | display: grid; 5 | grid-template-columns: repeat(var(--column), 1fr); 6 | grid-gap: 0.55vw; 7 | grid-auto-rows: max-content; 8 | overflow-y: auto; 9 | padding-right: 0.55vw; 10 | align-content: flex-start; 11 | .selected { 12 | transition: 0.3s; 13 | border: 2px solid #ffffff !important; 14 | filter: contrast(120%); 15 | } 16 | .model_item { 17 | position: relative; 18 | cursor: pointer; 19 | width: 100%; 20 | border-radius: 0.3vw; 21 | border: 2px solid #3d3d3d; 22 | display: flex; 23 | flex-direction: column; 24 | overflow: hidden; 25 | .img_container { 26 | width: 100%; 27 | height: calc(var(--height) - 4px); 28 | font-size: 0; 29 | transition: 0.3s; 30 | img { 31 | width: 100%; 32 | height: 100%; 33 | object-fit: cover; 34 | } 35 | } 36 | .model_des { 37 | height: var(--height); 38 | width: 100%; 39 | transition: 0.3s; 40 | display: flex; 41 | align-items: center; 42 | justify-content: flex-start; 43 | background: #262626; 44 | padding: 0 0.5vw; 45 | .level { 46 | width: 1.56vw; 47 | height: 1.56vw; 48 | display: flex; 49 | justify-content: center; 50 | align-items: center; 51 | color: #ffffff; 52 | font-size: 1.3rem; 53 | background-color: red; 54 | border-radius: 0.3vw; 55 | } 56 | .text_des { 57 | flex: 1; 58 | margin-left: 0.3vw; 59 | width: 100%; 60 | overflow: hidden; 61 | p { 62 | width: 100%; 63 | overflow: hidden; 64 | text-overflow: ellipsis; 65 | white-space: nowrap; 66 | &:nth-of-type(1) { 67 | font-size: 1rem; 68 | font-weight: bold; 69 | } 70 | &:nth-of-type(2) { 71 | font-size: 0.7rem; 72 | color: #808080; 73 | } 74 | } 75 | } 76 | } 77 | } 78 | &::-webkit-scrollbar { 79 | width: 15px; 80 | } 81 | &::-webkit-scrollbar-thumb:vertical { 82 | border-radius: 20px; 83 | background: #ff8d1a; 84 | } 85 | &::-webkit-scrollbar-track { 86 | background-color: transparent; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /loader/components/left/index.css: -------------------------------------------------------------------------------- 1 | .left_menu { 2 | position: relative; 3 | width: 4.2vw; 4 | height: 100%; 5 | border-right: 2px solid #383838; 6 | } 7 | .left_menu .item { 8 | width: 4.2vw; 9 | height: 4.2vw; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | .left_menu img { 15 | width: 3vw; 16 | height: 3vw; 17 | } 18 | .left_menu ul { 19 | width: 100%; 20 | height: calc(100% - 10vw); 21 | overflow-y: auto; 22 | } 23 | .left_menu ul .selected { 24 | background: #ffc300; 25 | transition: 0.2s; 26 | } 27 | .left_menu ul li { 28 | cursor: pointer; 29 | display: flex; 30 | flex-direction: column; 31 | } 32 | .left_menu ul li em { 33 | font-size: 2rem; 34 | } 35 | .left_menu ul li span { 36 | text-align: center; 37 | width: 90%; 38 | overflow: hidden; 39 | text-overflow: ellipsis; 40 | white-space: nowrap; 41 | margin: 0.2vw 0 -0.5vw 0; 42 | font-weight: bold; 43 | font-size: 0.6rem; 44 | } 45 | .left_menu ul li:hover { 46 | background: #ffc300; 47 | transition: 0.2s; 48 | } 49 | .left_menu ul::-webkit-scrollbar { 50 | width: 0; 51 | } 52 | .left_menu ul::-webkit-scrollbar-track { 53 | background-color: transparent; 54 | } 55 | .left_menu .language { 56 | position: absolute; 57 | left: 50%; 58 | transform: translateX(-50%); 59 | bottom: 2vw; 60 | width: 3vw; 61 | height: 3vw; 62 | overflow: hidden; 63 | } 64 | .left_menu .language span { 65 | cursor: pointer; 66 | width: 100%; 67 | height: 100%; 68 | display: flex; 69 | justify-content: center; 70 | align-items: center; 71 | position: absolute; 72 | } 73 | .left_menu .language span:nth-of-type(1) { 74 | left: 0; 75 | } 76 | .left_menu .language span:nth-of-type(2) { 77 | left: 100%; 78 | } 79 | .left_menu .language span:nth-of-type(3) { 80 | left: 200%; 81 | } 82 | -------------------------------------------------------------------------------- /loader/components/left/index.js: -------------------------------------------------------------------------------- 1 | // fake vue: avoid pre load error 2 | if (typeof Vue === "undefined") { 3 | var Vue = { 4 | component: function () {}, 5 | prototype: { 6 | $message: function () {}, 7 | }, 8 | }; 9 | } 10 | 11 | Vue.component("Left", { 12 | props: ["title"], 13 | data() { 14 | return { 15 | selectedIndex: 0, 16 | rotationList: [ 17 | { 18 | value: 0, 19 | icon: "icon-home", 20 | name: "All", 21 | type: "All", 22 | }, 23 | { 24 | value: 1, 25 | icon: "icon-checkpoint", 26 | name: "Checkpoint", 27 | type: "checkpoints", 28 | }, 29 | { 30 | value: 2, 31 | icon: "icon-vae", 32 | name: "VAE", 33 | type: "vae", 34 | }, 35 | { 36 | value: 3, 37 | icon: "icon-clip-version", 38 | name: "CLIP Vision", 39 | type: "clip_vision", 40 | }, 41 | { 42 | value: 4, 43 | icon: "icon-gligen", 44 | name: "GLIGEN", 45 | type: "gligen", 46 | }, 47 | { 48 | value: 5, 49 | icon: "icon-control", 50 | name: "ControlNET", 51 | type: "controlnet", 52 | }, 53 | { 54 | value: 6, 55 | icon: "icon-lora", 56 | name: "LoRA", 57 | type: "loras", 58 | }, 59 | { 60 | value: 7, 61 | icon: "icon-style-model", 62 | name: "StyleModel", 63 | type: "style_models", 64 | }, 65 | { 66 | value: 8, 67 | icon: "icon-upscale", 68 | name: "Upscale", 69 | type: "upscale_models", 70 | }, 71 | { 72 | value: 9, 73 | icon: "icon-hyper", 74 | name: "HyperNetwork", 75 | type: "hypernetworks", 76 | }, 77 | { 78 | value: 10, 79 | icon: "icon-clip", 80 | name: "CLIP", 81 | type: "clip", 82 | }, 83 | { 84 | value: 11, 85 | icon: "icon-unet", 86 | name: "UNET", 87 | type: "unet", 88 | }, 89 | { 90 | value: 12, 91 | icon: "icon-diffuser", 92 | name: "Diffuser", 93 | type: "diffusers", 94 | }, 95 | ], 96 | }; 97 | }, 98 | watch: { 99 | $route: { 100 | handler(newRoute) { 101 | const pathList = [ 102 | { 103 | path: "/home", 104 | value: 0, 105 | }, 106 | { 107 | path: "/settings", 108 | value: 1, 109 | }, 110 | ]; 111 | this.selectedIndex = -1; 112 | for (const item of pathList) { 113 | if (newRoute.path.includes(item.path)) { 114 | this.selectedIndex = item.value; 115 | } 116 | } 117 | }, 118 | immediate: true, 119 | }, 120 | }, 121 | computed: { 122 | language() { 123 | return this.$store.state.config.language; 124 | }, 125 | rotationIcon() { 126 | let icon = this.rotationList.find((x) => x.type === this.node?.CSgetModelWidgetType()); 127 | if (!icon) { 128 | icon = this.rotationList[0]; 129 | } 130 | return icon; 131 | }, 132 | }, 133 | methods: { 134 | jumpPage(page) { 135 | this.$router.push(page); 136 | }, 137 | // Change language 138 | changeLanguage(language) { 139 | this.$i18n.locale = language; 140 | this.$store.commit("config/updateLanguage", language); 141 | }, 142 | }, 143 | template: `
144 | 145 |
    146 |
  • 148 | 149 | {{rotationIcon.name}} 150 |
  • 151 |
  • 152 | 153 |
  • 154 |
155 |
156 | 157 | 158 | EN 159 |
160 |
`, 161 | }); 162 | -------------------------------------------------------------------------------- /loader/components/left/index.less: -------------------------------------------------------------------------------- 1 | .left_menu { 2 | position: relative; 3 | width: 4.2vw; 4 | height: 100%; 5 | border-right: 2px solid #383838; 6 | .item { 7 | width: 4.2vw; 8 | height: 4.2vw; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | } 13 | img { 14 | width: 3vw; 15 | height: 3vw; 16 | } 17 | ul { 18 | width: 100%; 19 | height: calc(100% - 10vw); 20 | overflow-y: auto; 21 | .selected { 22 | background: #ffc300; 23 | transition: 0.2s; 24 | } 25 | li { 26 | cursor: pointer; 27 | display: flex; 28 | flex-direction: column; 29 | em { 30 | font-size: 2rem; 31 | } 32 | span { 33 | text-align: center; 34 | width: 90%; 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | white-space: nowrap; 38 | margin: 0.2vw 0 -0.5vw 0; 39 | font-weight: bold; 40 | font-size: 0.6rem; 41 | } 42 | &:hover { 43 | background: #ffc300; 44 | transition: 0.2s; 45 | } 46 | } 47 | &::-webkit-scrollbar { 48 | width: 0; 49 | } 50 | &::-webkit-scrollbar-track { 51 | background-color: transparent; 52 | } 53 | } 54 | .language { 55 | position: absolute; 56 | left: 50%; 57 | transform: translateX(-50%); 58 | bottom: 2vw; 59 | width: 3vw; 60 | height: 3vw; 61 | overflow: hidden; 62 | span { 63 | cursor: pointer; 64 | width: 100%; 65 | height: 100%; 66 | display: flex; 67 | justify-content: center; 68 | align-items: center; 69 | position: absolute; 70 | &:nth-of-type(1) { 71 | left: 0; 72 | } 73 | &:nth-of-type(2) { 74 | left: 100%; 75 | } 76 | &:nth-of-type(3) { 77 | left: 200%; 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /loader/components/public/confirmBox.js: -------------------------------------------------------------------------------- 1 | const styleInnerHtml = ` 2 | @keyframes confirmEnter { 3 | 0% { 4 | opacity: 0; 5 | height: 0; 6 | } 7 | 8 | 100% { 9 | opacity: 1; 10 | height: 10.8vw; 11 | } 12 | } 13 | 14 | .confirm_mask_box { 15 | position: fixed; 16 | top: 0; 17 | left: 0; 18 | width: 100vw; 19 | height: 100vh; 20 | z-index: 9999; 21 | background: #1818186b; 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | } 26 | 27 | .confirm_mask_box .confirm_area { 28 | width: 100%; 29 | background: #1a1b1c; 30 | padding: 2vw; 31 | height: 10.8vw; 32 | min-height:165px; 33 | display: flex; 34 | flex-direction: column; 35 | align-items: center; 36 | justify-content: center; 37 | box-shadow: 0 0 3px 1px #ffffff50; 38 | overflow: hidden; 39 | animation: confirmEnter 0.2s forwards; 40 | } 41 | 42 | .confirm_mask_box .confirm_area .describe { 43 | font-size: 1.2em; 44 | font-weight: bold; 45 | color: #ffffff; 46 | } 47 | 48 | .confirm_mask_box .confirm_area .button_group { 49 | display: flex; 50 | margin-top: 2vw; 51 | } 52 | 53 | .confirm_mask_box .confirm_area .button_group .button_item { 54 | height: 2.2vw; 55 | min-height: 40px; 56 | background: #383838; 57 | transition: 0.2s; 58 | display: flex; 59 | justify-content: space-between; 60 | align-items: center; 61 | white-space: nowrap; 62 | cursor: pointer; 63 | } 64 | 65 | .confirm_mask_box .confirm_area .button_group .button_item .icon { 66 | width: 1.5vw; 67 | height: 1.5vw; 68 | min-height:23px; 69 | min-width:23px; 70 | display: flex; 71 | justify-content: center; 72 | align-items: center; 73 | transition: 0.2s; 74 | border-radius: 50%; 75 | } 76 | .confirm_mask_box .confirm_area .button_group .button_item .icon em { 77 | font-size:1rem; 78 | } 79 | .confirm_mask_box .confirm_area .button_group .button_item .text { 80 | font-size: 1rem; 81 | } 82 | 83 | .confirm_mask_box .confirm_area .button_group .accept { 84 | border-radius: 2vw 0 0 2vw; 85 | padding: 0 1vw 0 0.5vw; 86 | } 87 | 88 | .confirm_mask_box .confirm_area .button_group .accept .icon { 89 | margin-right: 1.5vw; 90 | background: #43cf7c; 91 | } 92 | 93 | .confirm_mask_box .confirm_area .button_group .accept:hover { 94 | background: #43cf7c; 95 | color: #ffffff; 96 | } 97 | 98 | .confirm_mask_box .confirm_area .button_group .accept:hover .icon { 99 | background: #383838; 100 | } 101 | 102 | .confirm_mask_box .confirm_area .button_group .refuse { 103 | border-radius: 0 2vw 2vw 0; 104 | padding: 0 0.5vw 0 1vw; 105 | } 106 | 107 | .confirm_mask_box .confirm_area .button_group .refuse .icon { 108 | margin-left: 1.5vw; 109 | background: #ff5733; 110 | } 111 | 112 | .confirm_mask_box .confirm_area .button_group .refuse:hover { 113 | background: #ff5733; 114 | color: #ffffff; 115 | } 116 | 117 | .confirm_mask_box .confirm_area .button_group .refuse:hover .icon { 118 | background: #383838; 119 | } 120 | 121 | 122 | `; 123 | 124 | export default { 125 | name: "ConfirmBox", 126 | data() { 127 | return { 128 | accept: null, 129 | refuse: null, 130 | acceptText: "確定", 131 | refuseText: "取消", 132 | describe: "你确定要退出当前操作吗", 133 | }; 134 | }, 135 | mounted() { 136 | const style = document.createElement("style"); 137 | style.innerHTML = styleInnerHtml; 138 | this.$refs.confimrBox.appendChild(style); 139 | }, 140 | methods: { 141 | determine() { 142 | this.accept && this.accept(); 143 | this.$destroy(); 144 | this.$el.parentNode.removeChild(this.$el); 145 | }, 146 | cancel() { 147 | this.refuse && this.refuse(); 148 | this.$destroy(); 149 | this.$el.parentNode.removeChild(this.$el); 150 | }, 151 | }, 152 | template: ` 153 |
154 |
155 |
156 |
157 |
158 |
159 | 160 | {{acceptText}} 161 |
162 |
163 | {{refuseText}} 164 | 165 |
166 |
167 |
168 |
`, 169 | }; 170 | -------------------------------------------------------------------------------- /loader/components/public/hoverMenu.js: -------------------------------------------------------------------------------- 1 | const styleInnerHtml = ` 2 | .hover_menu { 3 | cursor: pointer; 4 | position: relative; 5 | padding: 0.5vw 0.9vw; 6 | background: #383838; 7 | border-radius: 0.4vw; 8 | transition: 0.3s; 9 | font-weight:bold; 10 | } 11 | .hover_menu .default_value { 12 | cursor: pointer; 13 | width: 100%; 14 | height: 100%; 15 | } 16 | .hover_menu:hover { 17 | background: #2a82e4; 18 | } 19 | .hover_menu:hover ul { 20 | max-height: 500px; 21 | background: #383838; 22 | box-shadow:0 0 3px 1px #383838; 23 | } 24 | .hover_menu .default_value em { 25 | margin-right: 0.3vw; 26 | font-size: 1rem; 27 | } 28 | .hover_menu ul { 29 | max-height: 0; 30 | overflow: hidden; 31 | position: absolute; 32 | right: 0; 33 | bottom: 0; 34 | transform: translateY(102%); 35 | padding: 0.3vw 0.3vw; 36 | display: grid; 37 | grid-gap: 0.2vw; 38 | background: #38383800; 39 | border-radius: 0.36vw; 40 | transition: max-height 0.3s, background 0.3s; 41 | } 42 | .hover_menu ul li { 43 | cursor: pointer; 44 | color: #ffffff; 45 | padding: 0.2vw 0.8vw; 46 | transition: background 0.3s; 47 | border-radius: 0.3vw; 48 | white-space:nowrap; 49 | display:flex; 50 | justify-content: flex-end; 51 | } 52 | .hover_menu ul li:hover { 53 | background: #1f1f1f; 54 | } 55 | `; 56 | export default { 57 | props: { 58 | icon: { 59 | default: "", 60 | type: String, 61 | }, 62 | value: { 63 | default: 0, 64 | type: Number, 65 | }, 66 | list: { 67 | default: () => { 68 | return []; 69 | }, 70 | type: Array, 71 | }, 72 | fontSize: { 73 | default: "1rem", 74 | type: String, 75 | }, 76 | }, 77 | data() { 78 | return { 79 | index: 0, 80 | isExpand: false, 81 | }; 82 | }, 83 | watch: { 84 | value(newValue) { 85 | this.selectItem(newValue); 86 | }, 87 | }, 88 | mounted() { 89 | const style = document.createElement("style"); 90 | style.innerHTML = styleInnerHtml; 91 | this.$refs.hoverMenu.appendChild(style); 92 | this.selectItem(this.value); 93 | }, 94 | methods: { 95 | selectItem(index) { 96 | this.index = index; 97 | this.$emit("changeValue", index); 98 | }, 99 | }, 100 | template: `
101 |
102 | 103 | {{list[index]?.name || ''}} 104 |
105 |
    106 |
  • 107 | {{item.name}} 108 |
  • 109 |
110 |
`, 111 | }; 112 | -------------------------------------------------------------------------------- /loader/components/public/iconRenderer.js: -------------------------------------------------------------------------------- 1 | // import { api } from "/scripts/api.js"; 2 | 3 | function getApi() { 4 | const api = window.comfyAPI?.api?.api || window.parent.comfyAPI?.api?.api; 5 | api.api_base = ""; 6 | return api; 7 | } 8 | 9 | class IconRenderer { 10 | get rendering() { 11 | return this._rendering; 12 | } 13 | set rendering(v) { 14 | this._rendering = v; 15 | this.rendering_setter?.(v); 16 | } 17 | get progress_value() { 18 | return this._progress_value; 19 | } 20 | set progress_value(v) { 21 | this._progress_value = v; 22 | this.progress_value_setter?.(v); 23 | } 24 | 25 | constructor(app) { 26 | this.task_index = 0; 27 | this.task_count = 0; 28 | this.loop_finished = true; 29 | 30 | this._rendering = false; 31 | this.rendering_setter = null; 32 | this._progress_value = 0; 33 | this.progress_value_setter = null; 34 | 35 | this.stopped = false; 36 | 37 | this.executed_cb_user = null; 38 | const api = getApi(); 39 | api.addEventListener("progress", this.progress.bind(this)); 40 | api.addEventListener("executed", this.executed.bind(this)); 41 | api.addEventListener("execution_start", this.execution_start.bind(this)); 42 | api.addEventListener("executing", this.executing.bind(this)); 43 | api.addEventListener("execution_error", this.execution_error.bind(this)); 44 | api.addEventListener("execution_cached", this.execution_cached.bind(this)); 45 | api.addEventListener( 46 | "execution_interrupted", 47 | this.execution_interrupted.bind(this) 48 | ); 49 | api.addEventListener("open", this.open.bind(this)); 50 | api.addEventListener("close", this.close.bind(this)); 51 | api.addEventListener("error", this.error.bind(this)); 52 | } 53 | progress({ detail }) { 54 | // console.log("progress", detail.value / detail.max); 55 | this.progress_value = (detail.value / detail.max) * 100; 56 | } 57 | executed({ detail }) { 58 | // console.log("executed", detail); 59 | this.executed_cb(detail); 60 | } 61 | execution_start({ detail }) { 62 | // console.log("execution_start", detail); 63 | } 64 | executing({ detail }) { 65 | // console.log("executing", detail); 66 | } 67 | execution_error({ detail }) { 68 | // console.log("execution_error", detail); 69 | this.markEndLoop(); 70 | } 71 | execution_cached({ detail }) { 72 | // console.log("execution_cached", detail); 73 | } 74 | execution_interrupted({ detail }) { 75 | // console.log("execution_interrupted", detail); 76 | this.markEndLoop(); 77 | } 78 | open({ detail }) {} 79 | close({ detail }) {} 80 | error({ detail }) { 81 | this.markEndLoop(); 82 | } 83 | async executed_cb(detail) { 84 | try { 85 | const images = detail?.output?.images; 86 | if (!images || !images.length) return; 87 | let name = encodeURIComponent(images[0].filename); 88 | let type = images[0].type; 89 | let subfolder = encodeURIComponent(images[0].subfolder); 90 | // let fmt = app.getPreviewFormatParam(); 91 | // const src1 = api.apiURL(`/view?filename=${name}&type=${type}&subfolder=${subfolder}${fmt}`); 92 | 93 | const src = [`/view?filename=${name}`, `type=${type}`, `subfolder=${subfolder}`, `&t=${+new Date()}`].join("&"); 94 | await this.executed_cb_user(src, name); 95 | } catch (e) {} 96 | this.markEndLoop(); 97 | } 98 | async waitForOneLoop() { 99 | while (this.loop_finished !== true) { 100 | await new Promise((resolve) => setTimeout(resolve, 100)); 101 | } 102 | } 103 | markNewLoop() { 104 | this.progress_value = 0; 105 | this.loop_finished = false; 106 | } 107 | markEndLoop() { 108 | this.loop_finished = true; 109 | this.progress_value = 0; 110 | } 111 | async fetch_image(src, name) { 112 | return new Promise((resolve, reject) => { 113 | var blob = null; 114 | var xhr = new XMLHttpRequest(); 115 | let url = new URL(src, window.location.origin); 116 | xhr.open("GET", url); 117 | xhr.setRequestHeader("Accept", "image/jpeg"); 118 | xhr.responseType = "blob"; 119 | xhr.onload = () => { 120 | blob = xhr.response; 121 | let file = new File([blob], name, { type: "image/png" }); 122 | resolve(file); 123 | }; 124 | xhr.onerror = (e) => { 125 | reject(e); 126 | }; 127 | xhr.send(); 128 | }); 129 | } 130 | 131 | async executed_cb_user_factory(model, src, name) { 132 | // console.log(model.name, src); 133 | // 从src 读取image 为File 对象 134 | const file = await this.fetch_image(src, name); 135 | // 如果 读图报错 则不上传缩略图 136 | if (!file) { 137 | return; 138 | } 139 | try { 140 | const body = new FormData(); 141 | body.append("image", file); 142 | body.append("type", model.type); 143 | body.append("mtype", model.mtype); 144 | body.append("name", model.name); 145 | const api = getApi(); 146 | await api.fetchApi("/cs/upload_thumbnail", { 147 | method: "POST", 148 | body, 149 | }); 150 | model.cover = src; 151 | } catch (error) { 152 | alert(error); 153 | } 154 | } 155 | 156 | async render(inputNode, modelList) { 157 | if (this.rendering || !this.loop_finished) return; 158 | this.startRender(); 159 | try { 160 | await this.render_ex(inputNode, modelList); 161 | } catch (e) { 162 | // console.log(e); 163 | } 164 | this.endRender(); 165 | } 166 | 167 | startRender() { 168 | this.stopped = false; 169 | this.rendering = true; 170 | this.progress_value = 0; 171 | this.task_count = 0; 172 | this.task_index = 0; 173 | } 174 | 175 | endRender() { 176 | this.stopped = false; 177 | this.rendering = false; 178 | this.progress_value = 0; 179 | this.task_count = 0; 180 | this.task_index = 0; 181 | } 182 | 183 | async render_ex(inputNode, modelList) { 184 | if (!modelList || !modelList.length) { 185 | return; 186 | } 187 | let comfyWindow = window.parent; 188 | let app = comfyWindow.app; 189 | this.task_count = modelList.length; 190 | for await (let model of modelList) { 191 | inputNode.CSsetModelWidget(model.data || model.name); 192 | await this.waitForOneLoop(); 193 | if (this.stopped) { 194 | break; 195 | } 196 | this.markNewLoop(); 197 | this.task_index++; 198 | this.executed_cb_user = this.executed_cb_user_factory.bind(this, model); 199 | await app.queuePrompt(1); 200 | // app.lastNodeErrors 可能为 null 或 {} 201 | if (app.lastNodeErrors && Object.keys(app.lastNodeErrors).length > 0) { 202 | this.markEndLoop(); 203 | let lastNodeErrors = app.lastNodeErrors; 204 | let msg = []; 205 | for (let k of Object.keys(lastNodeErrors)) { 206 | let nodeError = lastNodeErrors[k]; 207 | msg.push(nodeError.class_type); 208 | for (let e of nodeError.errors) { 209 | if (!e.message) continue; 210 | msg.push(" " + e.message); 211 | } 212 | } 213 | alert(msg.join("\n")); 214 | break; 215 | } 216 | } 217 | await this.waitForOneLoop(); // 等待最后一个执行完成 218 | } 219 | 220 | stop() { 221 | this.stopped = true; 222 | const api = getApi(); 223 | api.clearItems("queue"); 224 | api.interrupt(); 225 | } 226 | } 227 | export default IconRenderer; 228 | -------------------------------------------------------------------------------- /loader/components/public/message.js: -------------------------------------------------------------------------------- 1 | const styleInnerHtml = ` 2 | @keyframes enter { 3 | 0% { 4 | opacity: 0; 5 | } 6 | 100% { 7 | opacity: 1; 8 | } 9 | } 10 | @keyframes leave { 11 | 0% { 12 | opacity: 1; 13 | left: 0; 14 | } 15 | 100% { 16 | opacity: 0; 17 | left: -50px; 18 | } 19 | } 20 | .message-enter-active { 21 | opacity: 0; 22 | } 23 | .message-enter-to { 24 | animation: enter 0.5s forwards; 25 | } 26 | .message-leave-to { 27 | animation: leave 1s forwards; 28 | } 29 | .warn { 30 | background: #fff1e3; 31 | color: #ff8d1a; 32 | } 33 | .warn em { 34 | color: #ff8d1a; 35 | } 36 | .error { 37 | background: #fae1e1; 38 | color: #ff5733; 39 | } 40 | .error em { 41 | color: #ff5733; 42 | } 43 | .success { 44 | background: #e5f0fb; 45 | color: #2a81e4; 46 | } 47 | .success em { 48 | color: #2a82e4; 49 | } 50 | .messageBox { 51 | display: flex; 52 | flex-direction: column; 53 | align-items: center; 54 | position: fixed; 55 | left: 50%; 56 | top: 2vw; 57 | transform: translateX(-50%); 58 | z-index: 9999; 59 | gap: 10px; 60 | } 61 | .messageBox .message-item { 62 | display: flex; 63 | align-items: center; 64 | position: absolute; 65 | box-sizing: border-box; 66 | padding: 0 20px; 67 | border-radius: 20px; 68 | box-shadow: 0 2px 6px 2px #00000010; 69 | transform: translate(-50%, var(--transformY)); 70 | height: 4vw; 71 | transition: 0.2s; 72 | } 73 | .messageBox .message-item em { 74 | font-size: 25px; 75 | } 76 | .messageBox .message-item .message { 77 | white-space: nowrap; 78 | font-weight: bold; 79 | font-size: 16px; 80 | margin-left: 15px; 81 | } 82 | `; 83 | export default { 84 | name: "MessageTips", 85 | data() { 86 | return { 87 | timer: null, 88 | isFirst: true, 89 | id: 0, 90 | pt: 0, 91 | messageList: [], 92 | destroyedTimer: false, 93 | }; 94 | }, 95 | mounted() { 96 | const style = document.createElement("style"); 97 | style.innerHTML = styleInnerHtml; 98 | this.$refs.messageBox.appendChild(style); 99 | }, 100 | methods: { 101 | add(option, callback) { 102 | clearTimeout(this.destroyedTimer); 103 | this.destroyedTimer = false; 104 | if (this.messageList.length >= 4) { 105 | this.messageList.shift(); 106 | } 107 | this.messageList.push({ 108 | id: this.id, 109 | isLeave: false, 110 | ...option, 111 | }); 112 | this.id++; 113 | if (this.isFirst) { 114 | this.addDelete(callback); 115 | this.isFirst = false; 116 | } 117 | }, 118 | exposed() { 119 | return { 120 | add: this.add, 121 | }; 122 | }, 123 | addDelete(callback) { 124 | this.timer = setInterval(() => { 125 | this.messageList.shift(); 126 | if (this.messageList.length === 0) { 127 | clearInterval(this.timer); 128 | this.isFirst = true; 129 | this.destroyedTimer = setTimeout(() => { 130 | callback(); 131 | }, 500); 132 | } 133 | }, 2000); 134 | }, 135 | }, 136 | template: ` 137 |
138 | 139 |
151 | 152 | 153 | 154 | {{ item.message }} 155 |
156 |
157 |
158 | `, 159 | }; 160 | -------------------------------------------------------------------------------- /loader/components/settings/language/index.css: -------------------------------------------------------------------------------- 1 | .language_list { 2 | display: grid; 3 | grid-gap: 0.5vw; 4 | padding: 0.3vw; 5 | grid-template-columns: repeat(3, 1fr); 6 | background: #262626; 7 | border-radius: 0.5vw; 8 | margin-top: 0.7vw; 9 | border: 1px solid #3d3d3d; 10 | } 11 | .language_list .selected { 12 | background: #ffc300 !important; 13 | } 14 | .language_list .language_item { 15 | cursor: pointer; 16 | transition: background 0.2s; 17 | padding: 0.5vw 0.8vw; 18 | border-radius: 0.4vw; 19 | white-space: nowrap; 20 | font-weight: bold; 21 | text-align: center; 22 | } 23 | .language_list .language_item:hover { 24 | background: #383838; 25 | } 26 | -------------------------------------------------------------------------------- /loader/components/settings/language/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | languageList: [ 5 | { 6 | name: "简体中文", 7 | value: "cn", 8 | }, 9 | { 10 | name: "ENGLISH", 11 | value: "en", 12 | }, 13 | { 14 | name: "繁体中文", 15 | value: "tc", 16 | }, 17 | ], 18 | selectedLanguage: "cn", 19 | }; 20 | }, 21 | computed: { 22 | language() { 23 | return this.$store.state.config.language; 24 | }, 25 | }, 26 | methods: { 27 | // Change language 28 | changeLanguage(language) { 29 | this.$i18n.locale = language; 30 | this.$store.commit("config/updateLanguage", language); 31 | }, 32 | }, 33 | template: `
34 |

{{$t("settings.language.title")}}

35 |

{{$t("settings.language.describe")}}

36 |

37 |
38 | {{item.name}}
39 |
40 |
`, 41 | }; 42 | -------------------------------------------------------------------------------- /loader/components/settings/language/index.less: -------------------------------------------------------------------------------- 1 | .language_list { 2 | display: grid; 3 | grid-gap: 0.5vw; 4 | padding: 0.3vw; 5 | grid-template-columns: repeat(3, 1fr); 6 | background: #262626; 7 | border-radius: 0.5vw; 8 | margin-top: 0.7vw; 9 | border: 1px solid #3d3d3d; 10 | .selected { 11 | background: #ffc300 !important; 12 | } 13 | .language_item { 14 | cursor: pointer; 15 | transition: background 0.2s; 16 | padding: 0.5vw 0.8vw; 17 | border-radius: 0.4vw; 18 | white-space: nowrap; 19 | font-weight: bold; 20 | text-align: center; 21 | &:hover { 22 | background: #383838; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /loader/components/settings/modelShielding/index.css: -------------------------------------------------------------------------------- 1 | @keyframes tag_input_enter { 2 | 0% { 3 | width: 0; 4 | } 5 | 100% { 6 | width: 7vw; 7 | } 8 | } 9 | .shield_table { 10 | margin-top: 0.7vw; 11 | width: 75vw; 12 | border: 1px solid #3d3d3d; 13 | background-color: #262626; 14 | } 15 | .shield_table .title { 16 | height: 2vw; 17 | font-size: 1rem; 18 | line-height: 2vw; 19 | display: grid; 20 | grid-template-columns: 40% 60%; 21 | border-bottom: 1px solid #3d3d3d; 22 | font-weight: bold; 23 | } 24 | .shield_table .title .loader_name { 25 | padding-left: 1.6vw; 26 | } 27 | .shield_table .list { 28 | display: grid; 29 | grid-gap: 0.5vw; 30 | padding: 0.5vw 0 1.6vw 0; 31 | } 32 | .shield_table .list .list_item { 33 | width: 100%; 34 | display: grid; 35 | grid-template-columns: 40% 60%; 36 | align-items: flex-start; 37 | } 38 | .shield_table .list .list_item .item_name { 39 | margin: 0 0.5vw 0 1.6vw; 40 | display: flex; 41 | height: 2.6vw; 42 | padding: 0 0.5vw; 43 | text-align: left; 44 | align-items: center; 45 | background: #1f1f1f; 46 | border-radius: 0.3vw; 47 | overflow: hidden; 48 | text-overflow: ellipsis; 49 | white-space: nowrap; 50 | } 51 | .shield_table .list .list_item .shield_model_list { 52 | width: 100%; 53 | padding-right: 1.8vw; 54 | } 55 | .shield_table .list .list_item .shield_model_list .list_area { 56 | width: 100%; 57 | min-height: 2.6vw; 58 | border-radius: 0.3vw; 59 | background: #1f1f1f; 60 | display: flex; 61 | flex-wrap: wrap; 62 | align-items: center; 63 | padding: 0 0.5vw; 64 | } 65 | .shield_table .list .list_item .shield_model_list .list_area .model { 66 | cursor: default; 67 | display: flex; 68 | color: #ffffff; 69 | background: #383838; 70 | border-radius: 0.3vw; 71 | padding: 0.2vw 0.5vw; 72 | flex: 0; 73 | white-space: nowrap; 74 | align-items: center; 75 | margin: 0.2vw 0.5vw 0.2vw 0; 76 | font-size: 1rem; 77 | } 78 | .shield_table .list .list_item .shield_model_list .list_area .model em { 79 | color: #808080; 80 | cursor: pointer; 81 | margin-left: 0.5vw; 82 | transition: color 0.25s; 83 | font-size: 0.7rem; 84 | } 85 | .shield_table .list .list_item .shield_model_list .list_area .model em:hover { 86 | color: red; 87 | } 88 | .shield_table .list .list_item .shield_model_list .list_area .expand { 89 | background: #383838 !important; 90 | border: 1px solid transparent !important; 91 | } 92 | .shield_table .list .list_item .shield_model_list .list_area .expand em { 93 | color: #ffffff !important; 94 | } 95 | .shield_table .list .list_item .shield_model_list .list_area .add_icon { 96 | padding: 0.36vw 0.62vw; 97 | border: 1px solid #3d3d3d; 98 | border-radius: 0.36vw; 99 | background: #1f1f1f; 100 | cursor: pointer; 101 | } 102 | .shield_table .list .list_item .shield_model_list .list_area .add_icon em { 103 | color: #3d3d3d; 104 | } 105 | .shield_table .list .list_item .shield_model_list .list_area .add_icon:hover { 106 | background: #262626; 107 | border: 1px solid #262626; 108 | } 109 | .shield_table .list .list_item .shield_model_list .list_area .add_icon:hover em { 110 | color: #ffffff; 111 | } 112 | .shield_table .list .list_item .shield_model_list .list_area::-webkit-scrollbar { 113 | width: 8px; 114 | } 115 | .shield_table .list .list_item .shield_model_list .list_area::-webkit-scrollbar-thumb:vertical { 116 | border-radius: 20px; 117 | background: #808080; 118 | } 119 | .shield_table .list .list_item .shield_model_list .list_area::-webkit-scrollbar-track { 120 | background-color: transparent; 121 | } 122 | .shield_table .list .list_item .shield_model_list .list_area::-webkit-scrollbar-corner { 123 | background-color: transparent; 124 | } 125 | .model_input { 126 | width: 100%; 127 | height: 100%; 128 | position: fixed; 129 | left: 0; 130 | top: 0; 131 | background: #1f1f1fcd; 132 | display: flex; 133 | justify-content: center; 134 | align-items: center; 135 | } 136 | .model_input .input_area { 137 | width: 40vw; 138 | border-radius: 1vw; 139 | } 140 | .model_input .input_area .loader_name { 141 | font-size: 2rem; 142 | color: #ffffff; 143 | } 144 | .model_input .input_area .input_box { 145 | display: flex; 146 | height: 4vw; 147 | border: 2px solid #3d3d3d; 148 | border-radius: 0.3vw; 149 | background: #1f1f1f; 150 | margin-top: 1vw; 151 | overflow: hidden; 152 | } 153 | .model_input .input_area .input_box input { 154 | flex: 1; 155 | height: 100%; 156 | color: #ffffff; 157 | padding: 0 1vw; 158 | font-size: 1.5rem; 159 | background: transparent; 160 | border-right: 1px solid #3d3d3d; 161 | } 162 | .model_input .input_area .input_box input::-webkit-input-placeholder { 163 | font-size: 1rem; 164 | } 165 | .model_input .input_area .input_box .button_group { 166 | display: flex; 167 | justify-content: center; 168 | } 169 | .model_input .input_area .input_box .button_group button { 170 | background: transparent; 171 | font-size: 1.2rem; 172 | padding: 0 1vw; 173 | color: #ffffff; 174 | transition: background 0.2s; 175 | } 176 | .model_input .input_area .input_box .button_group button:nth-of-type(1):hover { 177 | background: #43cf7c; 178 | } 179 | .model_input .input_area .input_box .button_group button:nth-of-type(2):hover { 180 | background: #ff5733; 181 | } 182 | -------------------------------------------------------------------------------- /loader/components/settings/modelShielding/index.js: -------------------------------------------------------------------------------- 1 | // import { api } from "/scripts/api.js"; 2 | 3 | function getApi() { 4 | const api = window.comfyAPI?.api?.api || window.parent.comfyAPI?.api?.api; 5 | api.api_base = ""; 6 | return api; 7 | } 8 | 9 | async function update_filter(filter, loader, old_data) { 10 | try { 11 | const body = new FormData(); 12 | body.append("data", JSON.stringify(filter)); 13 | body.append("old_data", JSON.stringify(old_data)); 14 | body.append("loader", loader); 15 | const api = getApi(); 16 | api.fetchApi("/cs/update_filter", { method: "POST", body }); 17 | } catch (error) { 18 | alert(error); 19 | } 20 | } 21 | export default { 22 | data() { 23 | return { 24 | list: [], 25 | value: "", 26 | isAddModel: false, 27 | selectedData: {}, 28 | }; 29 | }, 30 | watch: { 31 | nodeId: { 32 | handler() { 33 | this.list = this.node?.CSgetModelFilters() || []; 34 | }, 35 | immediate: true, 36 | }, 37 | }, 38 | methods: { 39 | // Delete blocked model names 40 | deleteModel(data, index) { 41 | let old_data = [...data.modelList]; 42 | data.modelList.splice(index, 1); 43 | update_filter(data.modelList, data.name, old_data); 44 | }, 45 | // Enter to trigger the add masking model event 46 | handleKeyDown(e) { 47 | if (e.key === "Enter") { 48 | this.addName(); 49 | } 50 | }, 51 | // Open the masked model input box 52 | editInput(index) { 53 | this.selectedData = this.list[index]; 54 | this.isAddModel = true; 55 | this.$nextTick(() => { 56 | this.$refs.modelInput.focus(); 57 | }); 58 | }, 59 | // Add shielding model name 60 | addName() { 61 | if (!this.value) { 62 | this.$message({ 63 | type: "error", 64 | message: this.$t("messages.fileNameError"), 65 | }); 66 | return; 67 | } 68 | if (this.selectedData.modelList.find((x) => x.name === this.value)) { 69 | this.$message({ 70 | type: "error", 71 | message: this.$t("messages.fileNameExists"), 72 | }); 73 | return; 74 | } 75 | let old_data = [...this.selectedData.modelList]; 76 | this.selectedData.modelList.push(this.value); 77 | update_filter(this.selectedData.modelList, this.selectedData.name, old_data); 78 | this.value = ""; 79 | this.selectedData = {}; 80 | this.isAddModel = false; 81 | }, 82 | // Turn off blocking model input boxes 83 | cancelEdit() { 84 | this.value = ""; 85 | this.selectedData = {}; 86 | this.isAddModel = false; 87 | }, 88 | }, 89 | template: `
90 |

{{$t("settings.modelShield.title")}}

91 |

{{$t("settings.modelShield.describe")}}

92 |

93 |
94 |
{{$t("settings.modelShield.loaderName")}}
95 |
{{$t("settings.modelShield.shieldingModel")}}
96 |
97 |
98 |
99 |
{{loader.name}}
100 |
101 |
102 |
103 | {{ model }} 104 | 105 |
106 |
107 | 108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |

{{selectedData.name || loader名称 }}

117 |
118 | 119 |
120 | 121 | 122 |
123 |
124 | 125 |
126 |
127 |
`, 128 | }; 129 | -------------------------------------------------------------------------------- /loader/components/settings/modelShielding/index.less: -------------------------------------------------------------------------------- 1 | @keyframes tag_input_enter { 2 | 0% { 3 | width: 0; 4 | } 5 | 100% { 6 | width: 7vw; 7 | } 8 | } 9 | .shield_table { 10 | margin-top: 0.7vw; 11 | width: 75vw; 12 | border: 1px solid #3d3d3d; 13 | background-color: #262626; 14 | .title { 15 | height: 2vw; 16 | font-size: 1rem; 17 | line-height: 2vw; 18 | display: grid; 19 | grid-template-columns: 40% 60%; 20 | border-bottom: 1px solid #3d3d3d; 21 | font-weight: bold; 22 | .loader_name { 23 | padding-left: 1.6vw; 24 | } 25 | .shield_Model { 26 | } 27 | } 28 | .list { 29 | display: grid; 30 | grid-gap: 0.5vw; 31 | padding: 0.5vw 0 1.6vw 0; 32 | .list_item { 33 | width: 100%; 34 | display: grid; 35 | grid-template-columns: 40% 60%; 36 | align-items: flex-start; 37 | .item_name { 38 | margin: 0 0.5vw 0 1.6vw; 39 | display: flex; 40 | height: 2.6vw; 41 | padding: 0 0.5vw; 42 | text-align: left; 43 | align-items: center; 44 | background: #1f1f1f; 45 | border-radius: 0.3vw; 46 | overflow: hidden; 47 | text-overflow: ellipsis; 48 | white-space: nowrap; 49 | } 50 | .shield_model_list { 51 | width: 100%; 52 | padding-right: 1.8vw; 53 | .list_area { 54 | width: 100%; 55 | min-height: 2.6vw; 56 | border-radius: 0.3vw; 57 | background: #1f1f1f; 58 | display: flex; 59 | flex-wrap: wrap; 60 | align-items: center; 61 | padding: 0 0.5vw; 62 | .model { 63 | cursor: default; 64 | display: flex; 65 | color: #ffffff; 66 | background: #383838; 67 | border-radius: 0.3vw; 68 | padding: 0.2vw 0.5vw; 69 | flex: 0; 70 | white-space: nowrap; 71 | align-items: center; 72 | margin: 0.2vw 0.5vw 0.2vw 0; 73 | font-size: 1rem; 74 | em { 75 | color: #808080; 76 | cursor: pointer; 77 | margin-left: 0.5vw; 78 | transition: color 0.25s; 79 | font-size: 0.7rem; 80 | &:hover { 81 | color: red; 82 | } 83 | } 84 | } 85 | .expand { 86 | background: #383838 !important; 87 | border: 1px solid transparent !important; 88 | em { 89 | color: #ffffff !important; 90 | } 91 | } 92 | .add_icon { 93 | padding: 0.36vw 0.62vw; 94 | border: 1px solid #3d3d3d; 95 | border-radius: 0.36vw; 96 | background: #1f1f1f; 97 | cursor: pointer; 98 | em { 99 | color: #3d3d3d; 100 | } 101 | &:hover { 102 | background: #262626; 103 | border: 1px solid #262626; 104 | em { 105 | color: #ffffff; 106 | } 107 | } 108 | } 109 | &::-webkit-scrollbar { 110 | width: 8px; 111 | } 112 | &::-webkit-scrollbar-thumb:vertical { 113 | border-radius: 20px; 114 | background: #808080; 115 | } 116 | &::-webkit-scrollbar-track { 117 | background-color: transparent; 118 | } 119 | &::-webkit-scrollbar-corner { 120 | background-color: transparent; 121 | } 122 | } 123 | } 124 | } 125 | } 126 | } 127 | .model_input { 128 | width: 100%; 129 | height: 100%; 130 | position: fixed; 131 | left: 0; 132 | top: 0; 133 | background: #1f1f1fcd; 134 | display: flex; 135 | justify-content: center; 136 | align-items: center; 137 | .input_area { 138 | width: 40vw; 139 | border-radius: 1vw; 140 | .loader_name { 141 | font-size: 2rem; 142 | color: #ffffff; 143 | } 144 | .input_box { 145 | display: flex; 146 | height: 4vw; 147 | border: 2px solid #3d3d3d; 148 | border-radius: 0.3vw; 149 | background: #1f1f1f; 150 | margin-top: 1vw; 151 | overflow: hidden; 152 | input { 153 | flex: 1; 154 | height: 100%; 155 | color: #ffffff; 156 | padding: 0 1vw; 157 | font-size: 1.5rem; 158 | background: transparent; 159 | border-right: 1px solid #3d3d3d; 160 | &::-webkit-input-placeholder { 161 | font-size: 1rem; 162 | } 163 | } 164 | .button_group { 165 | display: flex; 166 | justify-content: center; 167 | button { 168 | background: transparent; 169 | font-size: 1.2rem; 170 | padding: 0 1vw; 171 | color: #ffffff; 172 | transition: background 0.2s; 173 | &:nth-of-type(1) { 174 | &:hover { 175 | background: #43cf7c; 176 | } 177 | } 178 | &:nth-of-type(2) { 179 | &:hover { 180 | background: #ff5733; 181 | } 182 | } 183 | } 184 | } 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /loader/components/settings/shortcut/index.css: -------------------------------------------------------------------------------- 1 | .shortcut_list { 2 | display: grid; 3 | grid-gap: 0.5vw; 4 | padding: 0.3vw; 5 | grid-template-columns: repeat(2, 1fr); 6 | background: #262626; 7 | border-radius: 0.5vw; 8 | margin-top: 0.7vw; 9 | border: 1px solid #3d3d3d; 10 | } 11 | .shortcut_list .selected { 12 | background: #ffc300 !important; 13 | } 14 | .shortcut_list .shortcut_item { 15 | cursor: pointer; 16 | transition: background 0.2s; 17 | padding: 0.5vw 0.8vw; 18 | border-radius: 0.4vw; 19 | white-space: nowrap; 20 | font-weight: bold; 21 | text-align: center; 22 | } 23 | .shortcut_list .shortcut_item:hover { 24 | background: #383838; 25 | } 26 | -------------------------------------------------------------------------------- /loader/components/settings/shortcut/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | shortcutList: [ 5 | { 6 | name: "LeftClick", 7 | value: "click", 8 | }, 9 | { 10 | name: "Shift + LeftClick", 11 | value: "shift_click", 12 | }, 13 | ], 14 | selectedShortcut: "click" 15 | }; 16 | }, 17 | computed: { 18 | shortcut() { 19 | return this.$store.state.config.shortcut; 20 | }, 21 | }, 22 | methods: { 23 | // Change shortcut 24 | changeShortcut(shortcut) { 25 | this.$store.commit("config/updateShortcut", shortcut); 26 | }, 27 | }, 28 | template: `
29 |

{{$t("settings.shortcut.title")}}

30 |

{{$t("settings.shortcut.describe")}}

31 |

32 |
33 | {{item.name}}
34 |
35 |
`, 36 | }; 37 | -------------------------------------------------------------------------------- /loader/components/settings/shortcut/index.less: -------------------------------------------------------------------------------- 1 | .shortcut_list { 2 | display: grid; 3 | grid-gap: 0.5vw; 4 | padding: 0.3vw; 5 | grid-template-columns: repeat(2, 1fr); 6 | background: #262626; 7 | border-radius: 0.5vw; 8 | margin-top: 0.7vw; 9 | border: 1px solid #3d3d3d; 10 | .selected { 11 | background: #ffc300 !important; 12 | } 13 | .shortcut_item { 14 | cursor: pointer; 15 | transition: background 0.2s; 16 | padding: 0.5vw 0.8vw; 17 | border-radius: 0.4vw; 18 | white-space: nowrap; 19 | font-weight: bold; 20 | text-align: center; 21 | &:hover { 22 | background: #383838; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /loader/components/settings/windowing/index.css: -------------------------------------------------------------------------------- 1 | .window_list { 2 | display: grid; 3 | grid-gap: 0.5vw; 4 | padding: 0.3vw; 5 | grid-template-columns: repeat(2, 1fr); 6 | background: #262626; 7 | border-radius: 0.5vw; 8 | margin-top: 0.7vw; 9 | border: 1px solid #3d3d3d; 10 | } 11 | .window_list .selected { 12 | background: #ffc300 !important; 13 | } 14 | .window_list .window_item { 15 | cursor: pointer; 16 | transition: background 0.2s; 17 | padding: 0.5vw 0.8vw; 18 | border-radius: 0.4vw; 19 | white-space: nowrap; 20 | font-weight: bold; 21 | text-align: center; 22 | } 23 | .window_list .window_item:hover { 24 | background: #383838; 25 | } 26 | -------------------------------------------------------------------------------- /loader/components/settings/windowing/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | windowList: [ 5 | { 6 | name: "全屏", 7 | value: "full", 8 | }, 9 | { 10 | name: "窗口化", 11 | value: "noFull", 12 | }, 13 | ], 14 | }; 15 | }, 16 | computed: { 17 | windowing() { 18 | return this.$store.state.config.windowing; 19 | }, 20 | }, 21 | methods: { 22 | // Change window 23 | changeWindow(value) { 24 | this.$store.commit("config/updateWindowing", value); 25 | }, 26 | }, 27 | template: `
28 |

{{$t("settings.window.title") || '标题'}}

29 |

{{$t("settings.window.describe") || '描述'}}

30 |

31 |
32 | {{item.name}}
33 |
34 |
`, 35 | }; 36 | -------------------------------------------------------------------------------- /loader/components/settings/windowing/index.less: -------------------------------------------------------------------------------- 1 | .window_list { 2 | display: grid; 3 | grid-gap: 0.5vw; 4 | padding: 0.3vw; 5 | grid-template-columns: repeat(2, 1fr); 6 | background: #262626; 7 | border-radius: 0.5vw; 8 | margin-top: 0.7vw; 9 | border: 1px solid #3d3d3d; 10 | .selected { 11 | background: #ffc300 !important; 12 | } 13 | .window_item { 14 | cursor: pointer; 15 | transition: background 0.2s; 16 | padding: 0.5vw 0.8vw; 17 | border-radius: 0.4vw; 18 | white-space: nowrap; 19 | font-weight: bold; 20 | text-align: center; 21 | &:hover { 22 | background: #383838; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /loader/enterLoader.js: -------------------------------------------------------------------------------- 1 | import { app } from "../../../../scripts/app.js"; 2 | import BluePrints from "./blueprints.js"; 3 | import IconRenderer from "./components/public/iconRenderer.js"; 4 | function getPage() { 5 | return document.getElementById("loader_iframe"); 6 | } 7 | 8 | function loadPage() { 9 | let page = getPage(); 10 | if (page) return page; 11 | window.removeEventListener("message", message); 12 | var realpath = "/cs/loader/index.html"; 13 | const html = ``; 14 | document.body.insertAdjacentHTML("beforeend", html); 15 | page = getPage(); 16 | page.style.display = "none"; 17 | let w = page.contentWindow; 18 | window.addEventListener("message", message); 19 | // w.focus(); 20 | w.addEventListener("keydown", (e) => { 21 | if (e.key === "Escape") { 22 | w.parent.postMessage({ type: "close_loader_page" }, "*"); 23 | } 24 | }); 25 | } 26 | 27 | function callBack() { 28 | let page = loadPage(); 29 | page.style.display = "block"; 30 | window.CSvm.node = this._node; 31 | window.CSvm.entryWidget = "default"; 32 | if (!window.CSvm.renderer) { 33 | window.CSvm.renderer = new IconRenderer(); 34 | } 35 | page.focus(); 36 | } 37 | 38 | function message(event) { 39 | if (event.data.type === "close_loader_page") { 40 | let page = getPage(); 41 | page.style.display = "none"; 42 | window.CSvm.node = null; // reset node 43 | // document.body.removeChild(document.getElementById("loader_iframe")); 44 | } 45 | } 46 | 47 | function registerCallBack(node) { 48 | BluePrints.prototype.CSregister(node, callBack); 49 | } 50 | 51 | function styleInit() { 52 | const style = document.createElement("style"); 53 | style.type = "text/css"; // 已启用 需要更改 54 | style.innerHTML = ` 55 | iframe { 56 | position: absolute; 57 | left: 0; 58 | top: 0; 59 | width: 100%; 60 | height: 100%; 61 | z-index: 9999; 62 | background: #000000; 63 | } 64 | `; 65 | document.head.appendChild(style); 66 | } 67 | 68 | function shouldPopUp(event, shortcut) { 69 | var clicked = event.type === LiteGraph.pointerevents_method + "down"; 70 | switch (shortcut) { 71 | case "click": 72 | return !event.shiftKey && clicked; 73 | case "shift_click": 74 | return event.shiftKey && clicked; 75 | } 76 | return false; 77 | } 78 | 79 | function popUpReg() { 80 | let f = LGraphCanvas.prototype.processNodeWidgets; 81 | function processNodeWidgets(node, pos, event, active_widget) { 82 | var shortcut = window.CSvm?.$store.state.config.shortcut || "click"; 83 | if (shouldPopUp(event, shortcut)) { 84 | if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) { 85 | return null; 86 | } 87 | 88 | var x = pos[0] - node.pos[0]; 89 | var y = pos[1] - node.pos[1]; 90 | var width = node.size[0]; 91 | 92 | for (var i = 0; i < node.widgets.length; ++i) { 93 | var w = node.widgets[i]; 94 | // 仅枚举 95 | if (!w || w.disabled || w.type !== "combo") continue; 96 | var w_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT; 97 | var w_width = w.width || width; 98 | // outside 99 | if (w != active_widget && (x < 6 || x > w_width - 12 || y < w.last_y || y > w.last_y + w_height || w.last_y === undefined)) continue; 100 | 101 | var delta = x < 40 ? -1 : x > w_width - 40 ? 1 : 0; 102 | if (!delta) { 103 | // combo clicked 104 | // console.error(node.title, node.constructor.type, w.name, event.type); 105 | const default_name2type = { 106 | ckpt_name: "checkpoints", 107 | vae_name: "vae", 108 | clip_name: "clip", 109 | gligen_name: "gligen", 110 | control_net_name: "controlnet", 111 | lora_name: "loras", 112 | style_model_name: "style_models", 113 | hypernetwork_name: "hypernetworks", 114 | unet_name: "unets", 115 | }; 116 | if (node.CSnative || default_name2type[w.name]) { 117 | let page = loadPage(); 118 | page.style.display = "block"; 119 | window.CSvm.node = node; 120 | window.CSvm.entryWidget = w.name; 121 | if (!window.CSvm.renderer) { 122 | window.CSvm.renderer = new IconRenderer(); 123 | } 124 | page.focus(); 125 | return w; 126 | } 127 | continue; 128 | } 129 | } 130 | } 131 | return f.call(this, node, pos, event, active_widget); 132 | } 133 | LGraphCanvas.prototype.processNodeWidgets = processNodeWidgets; 134 | } 135 | 136 | const ext = { 137 | name: "AIGODLIKE.MMM", 138 | async init(app) { 139 | loadPage(); 140 | styleInit(); 141 | popUpReg(); 142 | }, 143 | async setup(app) {}, 144 | async addCustomNodeDefs(defs, app) { 145 | // Add custom node definitions 146 | // These definitions will be configured and registered automatically 147 | // defs is a lookup core nodes, add yours into this 148 | // console.log("[logging]", "add custom node definitions", "current nodes:", Object.keys(defs)); 149 | }, 150 | async getCustomWidgets(app) { 151 | // Return custom widget types 152 | // See ComfyWidgets for widget examples 153 | // console.log("[logging]", "provide custom widgets"); 154 | }, 155 | async beforeRegisterNodeDef(nodeType, nodeData, app) { 156 | // Run custom logic before a node definition is registered with the graph 157 | // console.log("[logging]", "before register node: ", nodeType.comfyClass); 158 | // This fires for every node definition so only log once 159 | // applyNodeTranslationDef(nodeType, nodeData); 160 | // delete ext.beforeRegisterNodeDef; 161 | }, 162 | async registerCustomNodes(app) { 163 | // Register any custom node implementations here allowing for more flexability than a custom node def 164 | // console.log("[logging]", "register custom nodes"); 165 | }, 166 | loadedGraphNode(node, app) { 167 | // registerCallBack(node); 168 | }, 169 | nodeCreated(node, app) { 170 | registerCallBack(node); 171 | }, 172 | }; 173 | 174 | app.registerExtension(ext); 175 | -------------------------------------------------------------------------------- /loader/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Loader 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 |
19 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /loader/pages/home/home.js: -------------------------------------------------------------------------------- 1 | import Head from "../../components/home/head/index.js"; 2 | import Classification from "../../components/home/classification/index.js"; 3 | import Model from "../../components/home/model/index.js"; 4 | import Foot from "../../components/home/foot/index.js"; 5 | 6 | export default { 7 | name: "Home", 8 | components: { 9 | Head, 10 | Classification, 11 | Model, 12 | Foot, 13 | }, 14 | data() { 15 | return { 16 | allList: [], 17 | searchParameter: { 18 | key: "", 19 | sort: "", 20 | level: "", 21 | tags: [], 22 | }, 23 | isLoading: true, 24 | column: 0, 25 | columnIndex: 0, 26 | }; 27 | }, 28 | computed: { 29 | selectedWidget() { 30 | return this.node?.CSgetSelModelWidget(); 31 | }, 32 | rendering() { 33 | return this.renderer?.rendering; 34 | }, 35 | }, 36 | watch: { 37 | nodeId: { 38 | handler() { 39 | this.allList = this.node?.CSgetModelLists() || []; 40 | }, 41 | immediate: true, 42 | }, 43 | }, 44 | mounted() { 45 | const language = localStorage.getItem("language") || "cn"; 46 | this.columnIndex = JSON.parse(localStorage.getItem("columnIndex")); 47 | if (typeof this.columnIndex !== "number") { 48 | this.columnIndex = 3; 49 | } 50 | this.column = this.$t("home.head.sizeList")[this.columnIndex].value; 51 | this.$i18n.locale = language; 52 | this.$store.commit("config/updateLanguage", language); 53 | this.isLoading = false; 54 | }, 55 | methods: { 56 | // Change the quantity displayed in a row 57 | changeColumn(value) { 58 | this.column = value; 59 | }, 60 | // Change filtering criteria 61 | changeSearchParameter(value) { 62 | this.searchParameter = { 63 | ...this.searchParameter, 64 | ...value, 65 | }; 66 | }, 67 | }, 68 | template: `
69 |
70 | 71 | 72 | 73 |
74 | 75 |
`, 76 | }; 77 | -------------------------------------------------------------------------------- /loader/pages/home/home.less: -------------------------------------------------------------------------------- 1 | .home_page { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | @import url(../../components/home/foot/index.less); 7 | .content { 8 | flex: 1; 9 | padding: 1.6vw 2.6vw 1.8vw 2.6vw; 10 | display: flex; 11 | flex-direction: column; 12 | overflow: hidden; 13 | @import url(../../components/home/head/index.less); 14 | @import url(../../components/home/classification/index.less); 15 | @import url(../../components/home/model/index.less); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /loader/pages/settings/index.css: -------------------------------------------------------------------------------- 1 | .settings_page { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | } 7 | .settings_page .content_wrapper { 8 | flex: 1; 9 | overflow: hidden; 10 | padding: 1.6vw 2.6vw 1.8vw 2.6vw; 11 | } 12 | .settings_page .content_wrapper .content { 13 | height: 100%; 14 | overflow-y: auto; 15 | padding-right: 5px; 16 | display: flex; 17 | flex-direction: column; 18 | align-items: flex-start; 19 | } 20 | .settings_page .content_wrapper .content .setting_items { 21 | margin-bottom: 1.3vw; 22 | } 23 | .settings_page .content_wrapper .content .setting_items h1 { 24 | font-size: 1.4rem; 25 | } 26 | .settings_page .content_wrapper .content .setting_items p { 27 | color: #bfbfbf; 28 | } 29 | .settings_page .content_wrapper .content .setting_items .language_list { 30 | display: grid; 31 | grid-gap: 0.5vw; 32 | padding: 0.3vw; 33 | grid-template-columns: repeat(3, 1fr); 34 | background: #262626; 35 | border-radius: 0.5vw; 36 | margin-top: 0.7vw; 37 | border: 1px solid #3d3d3d; 38 | } 39 | .settings_page .content_wrapper .content .setting_items .language_list .selected { 40 | background: #ffc300 !important; 41 | } 42 | .settings_page .content_wrapper .content .setting_items .language_list .language_item { 43 | cursor: pointer; 44 | transition: background 0.2s; 45 | padding: 0.5vw 0.8vw; 46 | border-radius: 0.4vw; 47 | white-space: nowrap; 48 | font-weight: bold; 49 | text-align: center; 50 | } 51 | .settings_page .content_wrapper .content .setting_items .language_list .language_item:hover { 52 | background: #383838; 53 | } 54 | .settings_page .content_wrapper .content .setting_items .shortcut_list { 55 | display: grid; 56 | grid-gap: 0.5vw; 57 | padding: 0.3vw; 58 | grid-template-columns: repeat(2, 1fr); 59 | background: #262626; 60 | border-radius: 0.5vw; 61 | margin-top: 0.7vw; 62 | border: 1px solid #3d3d3d; 63 | } 64 | .settings_page .content_wrapper .content .setting_items .shortcut_list .selected { 65 | background: #ffc300 !important; 66 | } 67 | .settings_page .content_wrapper .content .setting_items .shortcut_list .shortcut_item { 68 | cursor: pointer; 69 | transition: background 0.2s; 70 | padding: 0.5vw 0.8vw; 71 | border-radius: 0.4vw; 72 | white-space: nowrap; 73 | font-weight: bold; 74 | text-align: center; 75 | } 76 | .settings_page .content_wrapper .content .setting_items .shortcut_list .shortcut_item:hover { 77 | background: #383838; 78 | } 79 | @keyframes tag_input_enter { 80 | 0% { 81 | width: 0; 82 | } 83 | 100% { 84 | width: 7vw; 85 | } 86 | } 87 | .settings_page .content_wrapper .content .setting_items .shield_table { 88 | margin-top: 0.7vw; 89 | width: 75vw; 90 | border: 1px solid #3d3d3d; 91 | background-color: #262626; 92 | } 93 | .settings_page .content_wrapper .content .setting_items .shield_table .title { 94 | height: 2vw; 95 | font-size: 1rem; 96 | line-height: 2vw; 97 | display: grid; 98 | grid-template-columns: 40% 60%; 99 | border-bottom: 1px solid #3d3d3d; 100 | font-weight: bold; 101 | } 102 | .settings_page .content_wrapper .content .setting_items .shield_table .title .loader_name { 103 | padding-left: 1.6vw; 104 | } 105 | .settings_page .content_wrapper .content .setting_items .shield_table .list { 106 | display: grid; 107 | grid-gap: 0.5vw; 108 | padding: 0.5vw 0 1.6vw 0; 109 | } 110 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item { 111 | width: 100%; 112 | display: grid; 113 | grid-template-columns: 40% 60%; 114 | align-items: flex-start; 115 | } 116 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .item_name { 117 | margin: 0 0.5vw 0 1.6vw; 118 | display: flex; 119 | height: 2.6vw; 120 | padding: 0 0.5vw; 121 | text-align: left; 122 | align-items: center; 123 | background: #1f1f1f; 124 | border-radius: 0.3vw; 125 | overflow: hidden; 126 | text-overflow: ellipsis; 127 | white-space: nowrap; 128 | } 129 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list { 130 | width: 100%; 131 | padding-right: 1.8vw; 132 | } 133 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area { 134 | width: 100%; 135 | min-height: 2.6vw; 136 | border-radius: 0.3vw; 137 | background: #1f1f1f; 138 | display: flex; 139 | flex-wrap: wrap; 140 | align-items: center; 141 | padding: 0 0.5vw; 142 | } 143 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area .model { 144 | cursor: default; 145 | display: flex; 146 | color: #ffffff; 147 | background: #383838; 148 | border-radius: 0.3vw; 149 | padding: 0.2vw 0.5vw; 150 | flex: 0; 151 | white-space: nowrap; 152 | align-items: center; 153 | margin: 0.2vw 0.5vw 0.2vw 0; 154 | font-size: 1rem; 155 | } 156 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area .model em { 157 | color: #808080; 158 | cursor: pointer; 159 | margin-left: 0.5vw; 160 | transition: color 0.25s; 161 | font-size: 0.7rem; 162 | } 163 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area .model em:hover { 164 | color: red; 165 | } 166 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area .expand { 167 | background: #383838 !important; 168 | border: 1px solid transparent !important; 169 | } 170 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area .expand em { 171 | color: #ffffff !important; 172 | } 173 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area .add_icon { 174 | padding: 0.36vw 0.62vw; 175 | border: 1px solid #3d3d3d; 176 | border-radius: 0.36vw; 177 | background: #1f1f1f; 178 | cursor: pointer; 179 | } 180 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area .add_icon em { 181 | color: #3d3d3d; 182 | } 183 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area .add_icon:hover { 184 | background: #262626; 185 | border: 1px solid #262626; 186 | } 187 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area .add_icon:hover em { 188 | color: #ffffff; 189 | } 190 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area::-webkit-scrollbar { 191 | width: 8px; 192 | } 193 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area::-webkit-scrollbar-thumb:vertical { 194 | border-radius: 20px; 195 | background: #808080; 196 | } 197 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area::-webkit-scrollbar-track { 198 | background-color: transparent; 199 | } 200 | .settings_page .content_wrapper .content .setting_items .shield_table .list .list_item .shield_model_list .list_area::-webkit-scrollbar-corner { 201 | background-color: transparent; 202 | } 203 | .settings_page .content_wrapper .content .setting_items .model_input { 204 | width: 100%; 205 | height: 100%; 206 | position: fixed; 207 | left: 0; 208 | top: 0; 209 | background: #1f1f1fcd; 210 | display: flex; 211 | justify-content: center; 212 | align-items: center; 213 | } 214 | .settings_page .content_wrapper .content .setting_items .model_input .input_area { 215 | width: 40vw; 216 | border-radius: 1vw; 217 | } 218 | .settings_page .content_wrapper .content .setting_items .model_input .input_area .loader_name { 219 | font-size: 2rem; 220 | color: #ffffff; 221 | } 222 | .settings_page .content_wrapper .content .setting_items .model_input .input_area .input_box { 223 | display: flex; 224 | height: 4vw; 225 | border: 2px solid #3d3d3d; 226 | border-radius: 0.3vw; 227 | background: #1f1f1f; 228 | margin-top: 1vw; 229 | overflow: hidden; 230 | } 231 | .settings_page .content_wrapper .content .setting_items .model_input .input_area .input_box input { 232 | flex: 1; 233 | height: 100%; 234 | color: #ffffff; 235 | padding: 0 1vw; 236 | font-size: 1.5rem; 237 | background: transparent; 238 | border-right: 1px solid #3d3d3d; 239 | } 240 | .settings_page .content_wrapper .content .setting_items .model_input .input_area .input_box input::-webkit-input-placeholder { 241 | font-size: 1rem; 242 | } 243 | .settings_page .content_wrapper .content .setting_items .model_input .input_area .input_box .button_group { 244 | display: flex; 245 | justify-content: center; 246 | } 247 | .settings_page .content_wrapper .content .setting_items .model_input .input_area .input_box .button_group button { 248 | background: transparent; 249 | font-size: 1.2rem; 250 | padding: 0 1vw; 251 | color: #ffffff; 252 | transition: background 0.2s; 253 | } 254 | .settings_page .content_wrapper .content .setting_items .model_input .input_area .input_box .button_group button:nth-of-type(1):hover { 255 | background: #43cf7c; 256 | } 257 | .settings_page .content_wrapper .content .setting_items .model_input .input_area .input_box .button_group button:nth-of-type(2):hover { 258 | background: #ff5733; 259 | } 260 | .settings_page .content_wrapper .content .setting_items .window_list { 261 | display: grid; 262 | grid-gap: 0.5vw; 263 | padding: 0.3vw; 264 | grid-template-columns: repeat(2, 1fr); 265 | background: #262626; 266 | border-radius: 0.5vw; 267 | margin-top: 0.7vw; 268 | border: 1px solid #3d3d3d; 269 | } 270 | .settings_page .content_wrapper .content .setting_items .window_list .selected { 271 | background: #ffc300 !important; 272 | } 273 | .settings_page .content_wrapper .content .setting_items .window_list .window_item { 274 | cursor: pointer; 275 | transition: background 0.2s; 276 | padding: 0.5vw 0.8vw; 277 | border-radius: 0.4vw; 278 | white-space: nowrap; 279 | font-weight: bold; 280 | text-align: center; 281 | } 282 | .settings_page .content_wrapper .content .setting_items .window_list .window_item:hover { 283 | background: #383838; 284 | } 285 | .settings_page .content_wrapper .content::-webkit-scrollbar { 286 | width: 15px; 287 | } 288 | .settings_page .content_wrapper .content::-webkit-scrollbar-thumb:vertical { 289 | border-radius: 20px; 290 | background: #ff8d1a; 291 | } 292 | .settings_page .content_wrapper .content::-webkit-scrollbar-track { 293 | background-color: transparent; 294 | } 295 | .settings_page .foot { 296 | width: 100%; 297 | height: 2.2vw; 298 | border-top: 1px solid #383838; 299 | } 300 | -------------------------------------------------------------------------------- /loader/pages/settings/index.js: -------------------------------------------------------------------------------- 1 | import Language from "../../components/settings/language/index.js"; 2 | import Shortcut from "../../components/settings/shortcut/index.js"; 3 | import ModelShield from "../../components/settings/modelShielding/index.js"; 4 | import windowing from "../../components/settings/windowing/index.js"; 5 | 6 | export default { 7 | name: "Settings", 8 | components: { 9 | Language, 10 | Shortcut, 11 | ModelShield, 12 | windowing, 13 | }, 14 | data() { 15 | return {}; 16 | }, 17 | methods: {}, 18 | template: `
19 |
20 |
21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 |
`, 29 | }; 30 | -------------------------------------------------------------------------------- /loader/pages/settings/index.less: -------------------------------------------------------------------------------- 1 | .settings_page { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | .content_wrapper { 7 | flex: 1; 8 | overflow: hidden; 9 | padding: 1.6vw 2.6vw 1.8vw 2.6vw; 10 | .content { 11 | height: 100%; 12 | overflow-y: auto; 13 | padding-right: 5px; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: flex-start; 17 | .setting_items { 18 | margin-bottom: 1.3vw; 19 | h1 { 20 | font-size: 1.4rem; 21 | } 22 | p { 23 | color: #bfbfbf; 24 | } 25 | @import url(../../components/settings/language/index.less); 26 | @import url(../../components/settings/shortcut/index.less); 27 | @import url(../../components/settings/modelShielding/index.less); 28 | @import url(../../components/settings/windowing/index.less); 29 | } 30 | &::-webkit-scrollbar { 31 | width: 15px; 32 | } 33 | &::-webkit-scrollbar-thumb:vertical { 34 | border-radius: 20px; 35 | background: #ff8d1a; 36 | } 37 | &::-webkit-scrollbar-track { 38 | background-color: transparent; 39 | } 40 | } 41 | } 42 | 43 | .foot { 44 | width: 100%; 45 | height: 2.2vw; 46 | border-top: 1px solid #383838; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /loader/router/index.js: -------------------------------------------------------------------------------- 1 | import homePage from "../pages/home/home.js"; 2 | import SettingsPage from "../pages/settings/index.js"; 3 | 4 | const routes = [ 5 | { path: "", redirect: "/home" }, 6 | { path: "/home", component: homePage }, 7 | { path: "/settings", component: SettingsPage }, 8 | ]; 9 | const router = window.VueRouter === undefined ? null : new VueRouter({ 10 | mode: "hash", 11 | routes, 12 | scrollBehavior: () => ({ y: 0 }), //路由跳转后页面回到顶部 13 | }); 14 | 15 | export default router; 16 | -------------------------------------------------------------------------------- /loader/static/css/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; /* Project id 4382725 */ 3 | src: url("../font/iconfont.woff2") format("woff2"), url("../font/iconfont.woff") format("woff"), url("../font/iconfont.ttf") format("truetype"); 4 | } 5 | 6 | .iconfont { 7 | font-family: "iconfont" !important; 8 | font-size: 16px; 9 | font-style: normal; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | .icon-checkpoint:before { 15 | content: "\e6bb"; 16 | } 17 | 18 | .icon-upscale:before { 19 | content: "\e6ba"; 20 | } 21 | 22 | .icon-gligen:before { 23 | content: "\e6bc"; 24 | } 25 | 26 | .icon-unet:before { 27 | content: "\e6bd"; 28 | } 29 | 30 | .icon-hyper:before { 31 | content: "\e6be"; 32 | } 33 | 34 | .icon-vae:before { 35 | content: "\e6bf"; 36 | } 37 | 38 | .icon-lora:before { 39 | content: "\e6c0"; 40 | } 41 | 42 | .icon-clip:before { 43 | content: "\e6c1"; 44 | } 45 | 46 | .icon-control:before { 47 | content: "\e6c2"; 48 | } 49 | 50 | .icon-clip-version:before { 51 | content: "\e6c3"; 52 | } 53 | 54 | .icon-diffuser:before { 55 | content: "\e6c4"; 56 | } 57 | 58 | .icon-style-model:before { 59 | content: "\e6c5"; 60 | } 61 | 62 | .icon-import:before { 63 | content: "\e6b9"; 64 | } 65 | 66 | .icon-copy:before { 67 | content: "\e6b7"; 68 | } 69 | 70 | .icon-delete:before { 71 | content: "\e6b8"; 72 | } 73 | 74 | .icon-yes:before { 75 | content: "\e64b"; 76 | } 77 | 78 | .icon-close2:before { 79 | content: "\e6b1"; 80 | } 81 | 82 | .icon-medal:before { 83 | content: "\e6ae"; 84 | } 85 | 86 | .icon-exchange:before { 87 | content: "\e6af"; 88 | } 89 | 90 | .icon-home:before { 91 | content: "\e6b0"; 92 | } 93 | 94 | .icon-no:before { 95 | content: "\e685"; 96 | } 97 | 98 | .icon-add:before { 99 | content: "\e6ad"; 100 | } 101 | 102 | .icon-close:before { 103 | content: "\e6ac"; 104 | } 105 | 106 | .icon-cube:before { 107 | content: "\e6a5"; 108 | } 109 | 110 | .icon-edit:before { 111 | content: "\e6a6"; 112 | } 113 | 114 | .icon-setting:before { 115 | content: "\e6a7"; 116 | } 117 | 118 | .icon-search:before { 119 | content: "\e6a8"; 120 | } 121 | 122 | .icon-upload:before { 123 | content: "\e6a9"; 124 | } 125 | 126 | .icon-double-arrow-bottom:before { 127 | content: "\e6aa"; 128 | } 129 | 130 | .icon-camera:before { 131 | content: "\e6ab"; 132 | } 133 | -------------------------------------------------------------------------------- /loader/static/css/index.less: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 2500px) { 2 | html { 3 | font-size: 18px; 4 | } 5 | } 6 | @media screen and (max-width: 2200px) { 7 | html { 8 | font-size: 14px; 9 | } 10 | } 11 | html { 12 | width: 100%; 13 | height: 100%; 14 | * { 15 | box-sizing: border-box; 16 | } 17 | } 18 | 19 | body { 20 | color: #ffffff; 21 | margin: 0; 22 | width: 100%; 23 | height: 100%; 24 | background-color: #1f1f1f; 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | .loader_page { 29 | display: flex; 30 | width: 100%; 31 | height: 100%; 32 | @import url(../../components/left/index.less); 33 | @import url(../../pages/home/home.less); 34 | @import url(../../pages/settings/index.less); 35 | } 36 | .windowing { 37 | width: 90%; 38 | height: 90%; 39 | border-radius: 0.7vw; 40 | border: 2px solid #383838; 41 | } 42 | ul { 43 | list-style: none; 44 | margin: 0; 45 | padding: 0; 46 | li { 47 | display: flex; 48 | justify-content: center; 49 | align-items: center; 50 | color: #ffffff; 51 | } 52 | } 53 | button, 54 | input { 55 | outline: none; 56 | border: none; 57 | } 58 | button { 59 | cursor: pointer; 60 | } 61 | p { 62 | margin: 0; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /loader/static/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGODLIKE/AIGODLIKE-ComfyUI-Studio/8ad96db58d631441ffb97e1bbf4a3fa74a9af953/loader/static/font/iconfont.ttf -------------------------------------------------------------------------------- /loader/static/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGODLIKE/AIGODLIKE-ComfyUI-Studio/8ad96db58d631441ffb97e1bbf4a3fa74a9af953/loader/static/font/iconfont.woff -------------------------------------------------------------------------------- /loader/static/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGODLIKE/AIGODLIKE-ComfyUI-Studio/8ad96db58d631441ffb97e1bbf4a3fa74a9af953/loader/static/font/iconfont.woff2 -------------------------------------------------------------------------------- /loader/static/image/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGODLIKE/AIGODLIKE-ComfyUI-Studio/8ad96db58d631441ffb97e1bbf4a3fa74a9af953/loader/static/image/default.jpg -------------------------------------------------------------------------------- /loader/static/image/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIGODLIKE/AIGODLIKE-ComfyUI-Studio/8ad96db58d631441ffb97e1bbf4a3fa74a9af953/loader/static/image/logo.webp -------------------------------------------------------------------------------- /loader/static/js/confirm.js: -------------------------------------------------------------------------------- 1 | if (typeof Vue === "undefined") { 2 | await import("./vue.js"); 3 | } 4 | import confirmBox from "../../components/public/confirmBox.js"; 5 | 6 | /** 7 | * 8 | option = { 9 | accept: Function; 10 | refuse: Function; 11 | describe?: string; 12 | }; 13 | */ 14 | Vue.prototype.$confirmBox = function (option) { 15 | const ConfirmBox = Vue.extend(confirmBox); 16 | const instance = new ConfirmBox({ 17 | data: { 18 | ...option, 19 | }, 20 | }); 21 | const modal = instance.$mount(); 22 | document.body.appendChild(modal.$el); 23 | }; 24 | -------------------------------------------------------------------------------- /loader/static/js/message.js: -------------------------------------------------------------------------------- 1 | if (typeof Vue === "undefined") { 2 | await import("./vue.js"); 3 | } 4 | import Message from "../../components/public/message.js"; 5 | let modal = null; 6 | function getMessageInstance() { 7 | if (modal) return; 8 | const MessageBox = Vue.extend(Message); 9 | const instance = new MessageBox(); 10 | modal = instance.$mount(); 11 | document.body.appendChild(modal.$el); 12 | } 13 | 14 | /** 15 | * @param option String || { type : 'success | warn | error', message : String } 16 | */ 17 | function message(option) { 18 | if (typeof option === "string") { 19 | option = { 20 | type: "success", 21 | message: option, 22 | }; 23 | } 24 | getMessageInstance(); 25 | modal.add(option, () => { 26 | modal.$destroy(); 27 | modal.$el.parentNode.removeChild(modal.$el); 28 | modal = null; 29 | }); 30 | } 31 | Vue.prototype.$message = message; 32 | -------------------------------------------------------------------------------- /loader/static/js/public.js: -------------------------------------------------------------------------------- 1 | function getLevelInf(level) { 2 | const levelList = [ 3 | { 4 | value: "S", 5 | color: "#FF5733", 6 | }, 7 | { 8 | value: "A", 9 | color: "#7948EA", 10 | }, 11 | { 12 | value: "B", 13 | color: "#2A82E4", 14 | }, 15 | { 16 | value: "C", 17 | color: "#43CF7C", 18 | }, 19 | { 20 | value: "D", 21 | color: "#808080", 22 | }, 23 | ]; 24 | return levelList.find((x) => x.value === level) || false; 25 | } 26 | 27 | export { getLevelInf }; 28 | -------------------------------------------------------------------------------- /loader/store/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | namespaced: true, 3 | state: () => ({ 4 | language: localStorage.getItem("CS.language") || "cn", 5 | shortcut: localStorage.getItem("CS.shortcut") || "click", 6 | windowing: localStorage.getItem("CS.windowing") || "full", 7 | }), 8 | mutations: { 9 | updateLanguage(state, newLanguage) { 10 | localStorage.setItem("CS.language", newLanguage); 11 | state.language = newLanguage; 12 | }, 13 | updateShortcut(state, newShortcut) { 14 | localStorage.setItem("CS.shortcut", newShortcut); 15 | state.shortcut = newShortcut; 16 | }, 17 | updateWindowing(state, value) { 18 | localStorage.setItem("CS.windowing", value); 19 | if (value === "full") { 20 | document.getElementById("loader_page").classList.remove("windowing"); 21 | } else { 22 | document.getElementById("loader_page").classList.add("windowing"); 23 | } 24 | state.windowing = value; 25 | }, 26 | }, 27 | }; 28 | export default config; 29 | -------------------------------------------------------------------------------- /loader/store/index.js: -------------------------------------------------------------------------------- 1 | import config from "./config.js"; 2 | import prop from "./prop.js"; 3 | let store = null; 4 | if (typeof Vuex === "undefined") { 5 | } else { 6 | store = new Vuex.Store({ 7 | modules: { 8 | config, 9 | prop, 10 | }, 11 | }); 12 | } 13 | export default store; 14 | -------------------------------------------------------------------------------- /loader/store/prop.js: -------------------------------------------------------------------------------- 1 | const prop = { 2 | namespaced: true, 3 | state: () => ({ 4 | nodeId: null, 5 | renderer: null, 6 | curModelList: null, 7 | }), 8 | mutations: { 9 | setNodeId(state, nodeId) { 10 | state.nodeId = nodeId; 11 | }, 12 | setRenderer(state, renderer) { 13 | state.renderer = renderer; 14 | }, 15 | setCurModelList(state, curModelList) { 16 | state.curModelList = curModelList; 17 | }, 18 | }, 19 | }; 20 | export default prop; 21 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "aigodlike-comfyui-studio" 3 | description = "Improve the interactive experience of using ComfyUI, such as making the loading of ComfyUI models more intuitive and making it easier to create model thumbnails" 4 | version = "1.0.1" 5 | license = "LICENSE" 6 | 7 | [project.urls] 8 | Repository = "https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Studio" 9 | 10 | [tool.comfy] 11 | PublisherId = "aigodlike" 12 | DisplayName = "AIGODLIKE-ComfyUI-Studio" 13 | Icon = "" -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | 5 | def read_json(path: Path | str) -> dict: 6 | if not path.exists() or not path.is_file(): 7 | return {} 8 | encodings = ["utf8", "gbk"] 9 | for encoding in encodings: 10 | try: 11 | return json.loads(Path(path).read_text(encoding=encoding)) 12 | except UnicodeDecodeError: 13 | continue 14 | return {} 15 | --------------------------------------------------------------------------------