├── .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 | 
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: ``,
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 |
126 |
127 |
128 |
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 |
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 |
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 |
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 |
131 |
132 |
133 |
134 |
135 |
{{$t('home.searchValue')}} : {{this.note_key}}
136 |
137 |
138 |
139 |
140 |
141 |
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 |
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 |
162 |
163 |
164 |
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: ``,
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 | `,
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: ``,
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 |
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: `
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 |
--------------------------------------------------------------------------------