├── LICENSE
├── README.md
├── js_lib
└── functions.js
├── projects
├── .DS_Store
├── project_2_1
│ ├── wechat-example
│ │ └── txt2img
│ │ │ ├── app.js
│ │ │ ├── app.json
│ │ │ ├── app.wxss
│ │ │ ├── data
│ │ │ └── api_json.js
│ │ │ ├── pages
│ │ │ ├── index
│ │ │ │ ├── index.js
│ │ │ │ ├── index.json
│ │ │ │ ├── index.wxml
│ │ │ │ └── index.wxss
│ │ │ └── logs
│ │ │ │ ├── logs.js
│ │ │ │ ├── logs.json
│ │ │ │ ├── logs.wxml
│ │ │ │ └── logs.wxss
│ │ │ ├── project.config.json
│ │ │ ├── project.private.config.json
│ │ │ └── sitemap.json
│ └── workflow_api.json
├── project_2_3
│ ├── wechat-example
│ │ └── img2img
│ │ │ ├── app.js
│ │ │ ├── app.json
│ │ │ ├── app.wxss
│ │ │ ├── data
│ │ │ └── api_json.js
│ │ │ ├── pages
│ │ │ ├── index
│ │ │ │ ├── index.js
│ │ │ │ ├── index.json
│ │ │ │ ├── index.wxml
│ │ │ │ └── index.wxss
│ │ │ └── logs
│ │ │ │ ├── logs.js
│ │ │ │ ├── logs.json
│ │ │ │ ├── logs.wxml
│ │ │ │ └── logs.wxss
│ │ │ ├── project.config.json
│ │ │ ├── project.private.config.json
│ │ │ ├── sitemap.json
│ │ │ └── utils
│ │ │ └── util.js
│ └── workflow_api.json
├── project_3_5
│ ├── wechat-example
│ │ └── video2video
│ │ │ ├── app.js
│ │ │ ├── app.json
│ │ │ ├── app.wxss
│ │ │ ├── pages
│ │ │ ├── index
│ │ │ │ ├── index.js
│ │ │ │ ├── index.json
│ │ │ │ ├── index.wxml
│ │ │ │ └── index.wxss
│ │ │ └── logs
│ │ │ │ ├── logs.js
│ │ │ │ ├── logs.json
│ │ │ │ ├── logs.wxml
│ │ │ │ └── logs.wxss
│ │ │ ├── project.config.json
│ │ │ ├── project.private.config.json
│ │ │ └── sitemap.json
│ ├── workflow_api.json
│ └── workflow_api.py
├── project_4_1
│ ├── wechat-example
│ │ └── crazy_sticker
│ │ │ ├── app.js
│ │ │ ├── app.json
│ │ │ ├── app.wxss
│ │ │ ├── pages
│ │ │ ├── images
│ │ │ │ ├── bg.png
│ │ │ │ ├── btn_download.png
│ │ │ │ ├── btn_upload.png
│ │ │ │ ├── completed.png
│ │ │ │ ├── ele.png
│ │ │ │ ├── ele_1.png
│ │ │ │ ├── ele_2.png
│ │ │ │ ├── ele_3.png
│ │ │ │ └── ele_4.png
│ │ │ ├── index
│ │ │ │ ├── index.js
│ │ │ │ ├── index.json
│ │ │ │ ├── index.wxml
│ │ │ │ └── index.wxss
│ │ │ └── logs
│ │ │ │ ├── logs.js
│ │ │ │ ├── logs.json
│ │ │ │ ├── logs.wxml
│ │ │ │ └── logs.wxss
│ │ │ ├── project.config.json
│ │ │ ├── project.private.config.json
│ │ │ └── sitemap.json
│ ├── workflow_api.json
│ └── workflow_api.py
├── project_4_2
│ ├── enamel_adapter
│ │ ├── enamel34.png
│ │ ├── enamel37.png
│ │ ├── enamel39.png
│ │ ├── enamel74.png
│ │ └── enamel75.png
│ ├── wechat-example
│ │ └── pet_enamel
│ │ │ ├── app.js
│ │ │ ├── app.json
│ │ │ ├── app.wxss
│ │ │ ├── pages
│ │ │ ├── images
│ │ │ │ ├── bg.png
│ │ │ │ ├── btn_download.png
│ │ │ │ ├── btn_upload.png
│ │ │ │ ├── dog_1.png
│ │ │ │ └── dog_text.png
│ │ │ ├── index
│ │ │ │ ├── index.js
│ │ │ │ ├── index.json
│ │ │ │ ├── index.wxml
│ │ │ │ └── index.wxss
│ │ │ └── logs
│ │ │ │ ├── logs.js
│ │ │ │ ├── logs.json
│ │ │ │ ├── logs.wxml
│ │ │ │ └── logs.wxss
│ │ │ ├── project.config.json
│ │ │ ├── project.private.config.json
│ │ │ └── sitemap.json
│ ├── workflow_api.json
│ └── workflow_api.py
├── project_5_1
│ ├── .DS_Store
│ ├── web_takephoto
│ │ ├── .DS_Store
│ │ ├── assets
│ │ │ ├── .DS_Store
│ │ │ ├── css
│ │ │ │ └── style.css
│ │ │ ├── images
│ │ │ │ ├── .DS_Store
│ │ │ │ ├── bg_1.png
│ │ │ │ ├── bg_2.png
│ │ │ │ ├── bg_3.png
│ │ │ │ ├── button_2.png
│ │ │ │ ├── button_3.png
│ │ │ │ ├── loading.png
│ │ │ │ └── start.png
│ │ │ ├── js
│ │ │ │ ├── app.js
│ │ │ │ └── utils.js
│ │ │ └── services
│ │ │ │ └── api.js
│ │ ├── index.html
│ │ └── libs
│ │ │ └── jquery-2.1.3.min.js
│ ├── workflow_api.json
│ └── workflow_api.py
├── requirement.txt
└── test.py
├── sdk
└── comfy_sdk.py
└── workflows
├── 2-1默认工作流.png
├── 2-2黏土小猫.png
├── 2-3图片转黏土.png
├── 3-5视频liveportrait.png
├── 4-1贴纸小程序.png
├── 4-2宠物珐琅生成器.png
├── 5-1圣诞节照相馆.png
└── README.md
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 余悠
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ComfyUI_workflow2app
2 |
5 |
6 | ## 课程地址
7 | Bilibili课堂 https://www.bilibili.com/cheese/play/ss33322
8 |
9 | ## 步骤说明
10 |
11 | - 1.在ComfyUI中搭建并运行成功任意工作流。
12 | - 2.在ComfyUI中,配置开发模式,允许通过Save(API Format)的形式下载可用于API搭建的工作流版本。
13 | - 3.将编辑完成的workflow工作流下载到本地。
14 | - 4.部署workflow的后端模块,开发对应的API接口。
15 | - 5.开发「app / 小程序」的前端业务逻辑。
16 | - 6.调试 / 部署 / 上线
17 |
18 | ## 文件结构
19 |
20 | ### projects
21 | 工程文件目录
22 |
23 | ### workflows
24 | 工作流目录
25 |
--------------------------------------------------------------------------------
/js_lib/functions.js:
--------------------------------------------------------------------------------
1 | /*
2 | 调用微信小程序的相册 / 后置摄像头,获取图片,上传到ComfyUI服务器端的input文件夹
3 | */
4 | uploadImage() {
5 | let that = this;
6 | wx.chooseMedia({
7 | count: 9,
8 | mediaType: ['image','video'],
9 | sourceType: ['album', 'camera'],
10 | maxDuration: 30,
11 | camera: 'back',
12 | success (res) {
13 | const tempFilePath = res.tempFiles[0].tempFilePath
14 | wx.uploadFile({
15 | url: `${that.data.baseUrl}/api/upload/image`,
16 | filePath: tempFilePath,
17 | name: 'image',
18 | success: function(res) {
19 | if (res.statusCode === 200 && res.data.name) {
20 | let image_name = res.data.name
21 | console.log(image_name)
22 | prompt_json['11']['inputs']['image'] = image_name
23 | } else {
24 | console.error('Upload failed:', res.statusCode, res.data);
25 | }
26 | },
27 | fail: function(error) {
28 | console.error('Upload error:', error);
29 | }
30 | })
31 | }
32 | })
33 | },
34 |
35 | // 通用请求方法
36 | makeRequest(endpoint, method, data = {}) {
37 | return new Promise((resolve, reject) => {
38 | wx.request({
39 | url: `${this.data.baseUrl}${endpoint}`,
40 | method,
41 | data,
42 | timeout: 20000,
43 | header: {
44 | 'content-type': 'application/json'
45 | },
46 | success: resolve,
47 | fail: reject
48 | });
49 | });
50 | },
51 |
52 | // 列队
53 | queue(prompt) {
54 | return this.makeRequest('/api/prompt','POST',{prompt:prompt})
55 | .then(res => {
56 | if (res.data && res.data.prompt_id) {
57 | return res.data.prompt_id;
58 | } else {
59 | throw new Error('Failed to get prompt_id');
60 | }
61 | });
62 | },
63 |
--------------------------------------------------------------------------------
/projects/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/.DS_Store
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/app.js:
--------------------------------------------------------------------------------
1 | // app.js
2 | App({
3 | onLaunch() {
4 | // 展示本地存储能力
5 | const logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 |
9 | // 登录
10 | wx.login({
11 | success: res => {
12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
13 | }
14 | })
15 | },
16 | globalData: {
17 | userInfo: null
18 | }
19 | })
20 |
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index",
4 | "pages/logs/logs"
5 | ],
6 | "window": {
7 | "navigationBarTextStyle": "black",
8 | "navigationBarTitleText": "文生图",
9 | "navigationBarBackgroundColor": "#ffffff"
10 | },
11 | "style": "v2",
12 | "componentFramework": "glass-easel",
13 | "sitemapLocation": "sitemap.json",
14 | "lazyCodeLoading": "requiredComponents"
15 | }
16 |
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | box-sizing: border-box;
8 | padding: 20px;
9 | }
10 |
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/data/api_json.js:
--------------------------------------------------------------------------------
1 | let prompt_json = "" // 替换为从 Save(API Format)下载下来的JSON对象
2 | module.exports = prompt_json;
3 |
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/pages/index/index.js:
--------------------------------------------------------------------------------
1 | const prompt_json = require('../../data/api_json')
2 |
3 | Page({
4 | data: {
5 | filename: '',
6 | baseUrl: "http://192.168.1.11:6008" //替换成ComfyUI的运行url
7 | },
8 |
9 | // 处理文本输入
10 | handleTextInput(e) {
11 | this.setData({
12 | textVal: e.detail.value
13 | });
14 | },
15 |
16 | // 上传文本并获取图片
17 | uploadTextAndFetchImage() {
18 | const { textVal } = this.data;
19 |
20 | wx.showLoading({ title: '请等待...' });
21 |
22 | this.uploadPrompt(textVal)
23 | .then(this.fetchImageByPromptId)
24 | .then(imgUrls => {
25 | this.setData({ imgReturns: imgUrls });
26 | })
27 | .catch(error => {
28 | console.error(error);
29 | })
30 | .finally(() => {
31 | wx.hideLoading();
32 | });
33 | },
34 |
35 | // 上传文本,返回 prompt_id
36 | uploadPrompt(text) {
37 | prompt_json['6']['inputs']['text_positive'] = text; // 在API配置文件中查找对应的属性
38 | return this.makeRequest('/prompt', 'POST', { prompt: prompt_json })
39 | .then(res => {
40 | if (res.data && res.data.prompt_id) {
41 | return res.data.prompt_id;
42 | } else {
43 | throw new Error('Failed to get prompt_id');
44 | }
45 | });
46 | },
47 |
48 | // 根据 prompt_id 获取图片
49 | fetchImageByPromptId(prompt_id) {
50 | return new Promise((resolve, reject) => {
51 | const fetchImage = () => {
52 | this.makeRequest(`/history/${prompt_id}`, 'GET')
53 | .then(res => {
54 | if (res.data && res.data[prompt_id]) {
55 | this.setData({
56 | filename: res.data[prompt_id]["outputs"][9]['images'][0]['filename']
57 | });
58 | resolve(res.data[prompt_id]["outputs"][9]['images']);
59 | } else {
60 | setTimeout(fetchImage, 2000); // 轮询直到获取到图片
61 | }
62 | })
63 | .catch(reject);
64 | };
65 |
66 | fetchImage();
67 | });
68 | },
69 |
70 | // 通用请求方法
71 | makeRequest(endpoint, method, data = {}) {
72 | return new Promise((resolve, reject) => {
73 | wx.request({
74 | url: `${this.data.baseUrl}${endpoint}`,
75 | method,
76 | data,
77 | timeout: 20000,
78 | header: {
79 | 'content-type': 'application/json'
80 | },
81 | success: resolve,
82 | fail: reject
83 | });
84 | });
85 | },
86 |
87 | // 重置文件名
88 | resetFilename() {
89 | this.setData({
90 | filename: ''
91 | });
92 | }
93 | });
94 |
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | }
4 | }
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | .page_txt2img {
2 | position: relative;
3 | width: 100vw;
4 | display: flex;
5 | margin-bottom: 32rpx;
6 | flex-direction: column;
7 | align-items: center;
8 | }
9 |
10 | .img_box{
11 | display: flex;
12 | flex-wrap: wrap;
13 | justify-content: space-between;
14 | align-items: center;
15 | flex-direction: column;
16 | }
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | // logs.js
2 | const util = require('../../utils/util.js')
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad() {
9 | this.setData({
10 | logs: (wx.getStorageSync('logs') || []).map(log => {
11 | return {
12 | date: util.formatTime(new Date(log)),
13 | timeStamp: log
14 | }
15 | })
16 | })
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | }
4 | }
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/pages/logs/logs.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{index + 1}}. {{log.date}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/pages/logs/logs.wxss:
--------------------------------------------------------------------------------
1 | page {
2 | height: 100vh;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 | .scrollarea {
7 | flex: 1;
8 | overflow-y: hidden;
9 | }
10 | .log-item {
11 | margin-top: 20rpx;
12 | text-align: center;
13 | }
14 | .log-item:last-child {
15 | padding-bottom: env(safe-area-inset-bottom);
16 | }
17 |
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileType": "miniprogram",
3 | "libVersion": "trial",
4 | "packOptions": {
5 | "ignore": [],
6 | "include": []
7 | },
8 | "setting": {
9 | "coverView": true,
10 | "es6": true,
11 | "postcss": true,
12 | "minified": true,
13 | "enhance": true,
14 | "showShadowRootInWxmlPanel": true,
15 | "packNpmRelationList": [],
16 | "babelSetting": {
17 | "ignore": [],
18 | "disablePlugins": [],
19 | "outputPath": ""
20 | }
21 | },
22 | "condition": {},
23 | "editorSetting": {
24 | "tabIndent": "auto",
25 | "tabSize": 2
26 | },
27 | "appid": "wx0e0781e2f366ca61"
28 | }
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/project.private.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
3 | "projectname": "txt2img",
4 | "setting": {
5 | "compileHotReLoad": true,
6 | "urlCheck": false
7 | }
8 | }
--------------------------------------------------------------------------------
/projects/project_2_1/wechat-example/txt2img/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/projects/project_2_1/workflow_api.json:
--------------------------------------------------------------------------------
1 | {
2 | "3": {
3 | "inputs": {
4 | "seed": 358242010803396,
5 | "steps": 20,
6 | "cfg": 8,
7 | "sampler_name": "euler",
8 | "scheduler": "normal",
9 | "denoise": 1,
10 | "model": [
11 | "4",
12 | 0
13 | ],
14 | "positive": [
15 | "6",
16 | 0
17 | ],
18 | "negative": [
19 | "7",
20 | 0
21 | ],
22 | "latent_image": [
23 | "5",
24 | 0
25 | ]
26 | },
27 | "class_type": "KSampler",
28 | "_meta": {
29 | "title": "KSampler"
30 | }
31 | },
32 | "4": {
33 | "inputs": {
34 | "ckpt_name": "revAnimated_v122.safetensors"
35 | },
36 | "class_type": "CheckpointLoaderSimple",
37 | "_meta": {
38 | "title": "Load Checkpoint"
39 | }
40 | },
41 | "5": {
42 | "inputs": {
43 | "width": 512,
44 | "height": 512,
45 | "batch_size": 1
46 | },
47 | "class_type": "EmptyLatentImage",
48 | "_meta": {
49 | "title": "Empty Latent Image"
50 | }
51 | },
52 | "6": {
53 | "inputs": {
54 | "text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
55 | "clip": [
56 | "4",
57 | 1
58 | ]
59 | },
60 | "class_type": "CLIPTextEncode",
61 | "_meta": {
62 | "title": "CLIP Text Encode (Prompt)"
63 | }
64 | },
65 | "7": {
66 | "inputs": {
67 | "text": "text, watermark",
68 | "clip": [
69 | "4",
70 | 1
71 | ]
72 | },
73 | "class_type": "CLIPTextEncode",
74 | "_meta": {
75 | "title": "CLIP Text Encode (Prompt)"
76 | }
77 | },
78 | "8": {
79 | "inputs": {
80 | "samples": [
81 | "3",
82 | 0
83 | ],
84 | "vae": [
85 | "4",
86 | 2
87 | ]
88 | },
89 | "class_type": "VAEDecode",
90 | "_meta": {
91 | "title": "VAE Decode"
92 | }
93 | },
94 | "9": {
95 | "inputs": {
96 | "filename_prefix": "ComfyUI",
97 | "images": [
98 | "8",
99 | 0
100 | ]
101 | },
102 | "class_type": "SaveImage",
103 | "_meta": {
104 | "title": "Save Image"
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/app.js:
--------------------------------------------------------------------------------
1 | // app.js
2 | App({
3 | onLaunch() {
4 | // 展示本地存储能力
5 | const logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 |
9 | // 登录
10 | wx.login({
11 | success: res => {
12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
13 | }
14 | })
15 | },
16 | globalData: {
17 | userInfo: null
18 | }
19 | })
20 |
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index",
4 | "pages/logs/logs"
5 | ],
6 | "window": {
7 | "navigationBarTextStyle": "black",
8 | "navigationBarTitleText": "课2-3:粘土风滤镜",
9 | "navigationBarBackgroundColor": "#ffffff"
10 | },
11 | "style": "v2",
12 | "componentFramework": "glass-easel",
13 | "sitemapLocation": "sitemap.json",
14 | "lazyCodeLoading": "requiredComponents"
15 | }
16 |
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | box-sizing: border-box;
8 | }
9 |
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/data/api_json.js:
--------------------------------------------------------------------------------
1 | let prompt_json = "" // 替换为从 Save(API Format)下载下来的JSON对象
2 | module.exports = prompt_json;
3 |
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/pages/index/index.js:
--------------------------------------------------------------------------------
1 | const prompt_json = require('../../data/api_json')
2 |
3 | Page({
4 | data: {
5 | filename: '',
6 | baseUrl: "http://192.168.10.85:6008" // 替换成ComfyUI的实际运行地址
7 | },
8 |
9 | // 根据 prompt_id 获取图片
10 | fetchImageByPromptId(prompt_id) {
11 | return new Promise((resolve, reject) => {
12 | const fetchImage = () => {
13 | this.makeRequest(`/api/history/${prompt_id}`, 'GET')
14 | .then(res => {
15 | if (res.data && res.data[prompt_id]) {
16 | this.setData({
17 | filename: res.data[prompt_id]["outputs"][9]['images'][0]['filename']
18 | });
19 | resolve(res.data[prompt_id]["outputs"][9]['images']);
20 | } else {
21 | setTimeout(fetchImage, 2000); // 轮询直到获取到图片
22 | }
23 | })
24 | .catch(reject);
25 | };
26 | fetchImage();
27 | });
28 | },
29 |
30 | uploadImage() {
31 | let that = this;
32 | wx.showLoading({ title: '请等待...' });
33 | wx.chooseMedia({
34 | count: 9,
35 | mediaType: ['image','video'],
36 | sourceType: ['album', 'camera'],
37 | maxDuration: 30,
38 | camera: 'back',
39 | success (res) {
40 | const tempFilePath = res.tempFiles[0].tempFilePath
41 | wx.uploadFile({
42 | url: `${that.data.baseUrl}/api/upload/image`,
43 | filePath: tempFilePath,
44 | name: 'image',
45 | success: function(res) {
46 | if (res.statusCode === 200) {
47 | let res_data = JSON.parse(res.data)
48 | prompt_json['11']['inputs']['image'] = res_data['name']
49 |
50 | that.queue(prompt_json)
51 | .then(that.fetchImageByPromptId)
52 | .then(imgUrls => {
53 | that.setData({ imgReturns: imgUrls });
54 | })
55 | .catch(error => {
56 | console.error(error);
57 | })
58 | .finally(() => {
59 | wx.hideLoading();
60 | });
61 | } else {
62 | console.error('Upload failed:', res.statusCode, res.data);
63 | }
64 | },
65 | fail: function(error) {
66 | console.error('Upload error:', error);
67 | }
68 | })
69 | }
70 | })
71 | },
72 |
73 | queue(prompt) {
74 | return this.makeRequest('/api/prompt','POST',{prompt:prompt})
75 | .then(res => {
76 | if (res.data && res.data.prompt_id) {
77 | return res.data.prompt_id;
78 | } else {
79 | throw new Error('Failed to get prompt_id');
80 | }
81 | });
82 | },
83 |
84 | // 通用请求方法
85 | makeRequest(endpoint, method, data = {}) {
86 | return new Promise((resolve, reject) => {
87 | wx.request({
88 | url: `${this.data.baseUrl}${endpoint}`,
89 | method,
90 | data,
91 | timeout: 20000,
92 | header: {
93 | 'content-type': 'application/json'
94 | },
95 | success: resolve,
96 | fail: reject
97 | });
98 | });
99 | },
100 |
101 | // 重置文件名
102 | resetFilename() {
103 | this.setData({
104 | filename: ''
105 | });
106 | }
107 | });
108 |
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | }
4 | }
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 |
2 |
3 | .page_img2img{
4 | position: relative;
5 | width: 100vw;
6 | display: flex;
7 | margin-top: 32rpx;
8 | margin-bottom: 32px;
9 | flex-direction: column;
10 | align-items: center;
11 | }
12 | .upload-button{
13 | box-sizing: border-box;
14 | width: 500rpx;
15 | height: 500rpx;
16 | line-height: 100px;
17 | margin-bottom: 10px;
18 | display: flex;
19 | background-color: #F6F6F6;
20 | justify-content: center;
21 | align-items: center;
22 | text-align: center;
23 | font-size: 60px;
24 | color: #C4C4C4;
25 | font-weight: 300;
26 | border-radius: 10px;
27 | overflow: hidden;
28 |
29 | }
30 | .upload-button>view{
31 | margin-top: -20px;
32 | }
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | // logs.js
2 | const util = require('../../utils/util.js')
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad() {
9 | this.setData({
10 | logs: (wx.getStorageSync('logs') || []).map(log => {
11 | return {
12 | date: util.formatTime(new Date(log)),
13 | timeStamp: log
14 | }
15 | })
16 | })
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | }
4 | }
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/pages/logs/logs.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{index + 1}}. {{log.date}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/pages/logs/logs.wxss:
--------------------------------------------------------------------------------
1 | page {
2 | height: 100vh;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 | .scrollarea {
7 | flex: 1;
8 | overflow-y: hidden;
9 | }
10 | .log-item {
11 | margin-top: 20rpx;
12 | text-align: center;
13 | }
14 | .log-item:last-child {
15 | padding-bottom: env(safe-area-inset-bottom);
16 | }
17 |
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileType": "miniprogram",
3 | "libVersion": "trial",
4 | "packOptions": {
5 | "ignore": [],
6 | "include": []
7 | },
8 | "setting": {
9 | "coverView": true,
10 | "es6": true,
11 | "postcss": true,
12 | "minified": true,
13 | "enhance": true,
14 | "showShadowRootInWxmlPanel": true,
15 | "packNpmRelationList": [],
16 | "babelSetting": {
17 | "ignore": [],
18 | "disablePlugins": [],
19 | "outputPath": ""
20 | }
21 | },
22 | "condition": {},
23 | "editorSetting": {
24 | "tabIndent": "auto",
25 | "tabSize": 2
26 | },
27 | "appid": "wx0e0781e2f366ca61"
28 | }
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/project.private.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
3 | "projectname": "img2img",
4 | "setting": {
5 | "compileHotReLoad": true,
6 | "urlCheck": false
7 | }
8 | }
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/projects/project_2_3/wechat-example/img2img/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const year = date.getFullYear()
3 | const month = date.getMonth() + 1
4 | const day = date.getDate()
5 | const hour = date.getHours()
6 | const minute = date.getMinutes()
7 | const second = date.getSeconds()
8 |
9 | return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
10 | }
11 |
12 | const formatNumber = n => {
13 | n = n.toString()
14 | return n[1] ? n : `0${n}`
15 | }
16 |
17 | module.exports = {
18 | formatTime
19 | }
20 |
--------------------------------------------------------------------------------
/projects/project_2_3/workflow_api.json:
--------------------------------------------------------------------------------
1 | {
2 | "3": {
3 | "inputs": {
4 | "seed": 750425633602940,
5 | "steps": 11,
6 | "cfg": 6,
7 | "sampler_name": "euler_ancestral",
8 | "scheduler": "normal",
9 | "denoise": 0.8,
10 | "model": [
11 | "4",
12 | 0
13 | ],
14 | "positive": [
15 | "33",
16 | 0
17 | ],
18 | "negative": [
19 | "33",
20 | 1
21 | ],
22 | "latent_image": [
23 | "12",
24 | 0
25 | ]
26 | },
27 | "class_type": "KSampler",
28 | "_meta": {
29 | "title": "KSampler"
30 | }
31 | },
32 | "4": {
33 | "inputs": {
34 | "ckpt_name": "dreamshaperXL_turboDpmppSDE.safetensors"
35 | },
36 | "class_type": "CheckpointLoaderSimple",
37 | "_meta": {
38 | "title": "Load Checkpoint"
39 | }
40 | },
41 | "6": {
42 | "inputs": {
43 | "text": [
44 | "10",
45 | 0
46 | ],
47 | "speak_and_recognation": true,
48 | "clip": [
49 | "4",
50 | 1
51 | ]
52 | },
53 | "class_type": "CLIPTextEncode",
54 | "_meta": {
55 | "title": "CLIP Text Encode (Prompt)"
56 | }
57 | },
58 | "7": {
59 | "inputs": {
60 | "text": [
61 | "10",
62 | 1
63 | ],
64 | "speak_and_recognation": true,
65 | "clip": [
66 | "4",
67 | 1
68 | ]
69 | },
70 | "class_type": "CLIPTextEncode",
71 | "_meta": {
72 | "title": "CLIP Text Encode (Prompt)"
73 | }
74 | },
75 | "8": {
76 | "inputs": {
77 | "samples": [
78 | "3",
79 | 0
80 | ],
81 | "vae": [
82 | "4",
83 | 2
84 | ]
85 | },
86 | "class_type": "VAEDecode",
87 | "_meta": {
88 | "title": "VAE Decode"
89 | }
90 | },
91 | "9": {
92 | "inputs": {
93 | "filename_prefix": "ComfyUI",
94 | "images": [
95 | "8",
96 | 0
97 | ]
98 | },
99 | "class_type": "SaveImage",
100 | "_meta": {
101 | "title": "Save Image"
102 | }
103 | },
104 | "10": {
105 | "inputs": {
106 | "text_positive": [
107 | "24",
108 | 0
109 | ],
110 | "text_negative": "",
111 | "style": "sai-craft clay",
112 | "log_prompt": "Yes",
113 | "style_positive": "true",
114 | "style_negative": true,
115 | "speak_and_recognation": true
116 | },
117 | "class_type": "SDXLPromptStyler",
118 | "_meta": {
119 | "title": "SDXL Prompt Styler"
120 | }
121 | },
122 | "11": {
123 | "inputs": {
124 | "image": "DALL·E 2024-09-11 14.58.01 - A child-like illustration of a crow flying in the air, with simple and playful shapes. The crow has black feathers, a round body, large eyes, and flap.webp",
125 | "upload": "image"
126 | },
127 | "class_type": "LoadImage",
128 | "_meta": {
129 | "title": "Load Image"
130 | }
131 | },
132 | "12": {
133 | "inputs": {
134 | "pixels": [
135 | "25",
136 | 0
137 | ],
138 | "vae": [
139 | "4",
140 | 2
141 | ]
142 | },
143 | "class_type": "VAEEncode",
144 | "_meta": {
145 | "title": "VAE Encode"
146 | }
147 | },
148 | "20": {
149 | "inputs": {
150 | "control_net_name": "CN-anytest_v4-marged.safetensors"
151 | },
152 | "class_type": "ControlNetLoader",
153 | "_meta": {
154 | "title": "Load ControlNet Model"
155 | }
156 | },
157 | "24": {
158 | "inputs": {
159 | "model": "wd-v1-4-convnext-tagger-v2",
160 | "threshold": 0.35,
161 | "character_threshold": 0.85,
162 | "replace_underscore": false,
163 | "trailing_comma": false,
164 | "exclude_tags": "",
165 | "tags": "solo, simple_background, closed_mouth, full_body, artist_name, black_eyes, no_humans, bird, animal, pink_background, flying, animal_focus, beak",
166 | "image": [
167 | "11",
168 | 0
169 | ]
170 | },
171 | "class_type": "WD14Tagger|pysssss",
172 | "_meta": {
173 | "title": "WD14 Tagger 🐍"
174 | }
175 | },
176 | "25": {
177 | "inputs": {
178 | "upscale_method": "nearest-exact",
179 | "width": 1024,
180 | "height": 1024,
181 | "crop": "center",
182 | "image": [
183 | "11",
184 | 0
185 | ]
186 | },
187 | "class_type": "ImageScale",
188 | "_meta": {
189 | "title": "Upscale Image"
190 | }
191 | },
192 | "27": {
193 | "inputs": {
194 | "images": [
195 | "25",
196 | 0
197 | ]
198 | },
199 | "class_type": "PreviewImage",
200 | "_meta": {
201 | "title": "Preview Image"
202 | }
203 | },
204 | "33": {
205 | "inputs": {
206 | "strength": 1,
207 | "start_percent": 0,
208 | "end_percent": 1,
209 | "positive": [
210 | "6",
211 | 0
212 | ],
213 | "negative": [
214 | "7",
215 | 0
216 | ],
217 | "control_net": [
218 | "20",
219 | 0
220 | ],
221 | "image": [
222 | "25",
223 | 0
224 | ]
225 | },
226 | "class_type": "ACN_AdvancedControlNetApply",
227 | "_meta": {
228 | "title": "Apply Advanced ControlNet 🛂🅐🅒🅝"
229 | }
230 | }
231 | }
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/app.js:
--------------------------------------------------------------------------------
1 | // app.js
2 | App({
3 | onLaunch() {
4 | // 展示本地存储能力
5 | const logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 |
9 | // 登录
10 | wx.login({
11 | success: res => {
12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
13 | }
14 | })
15 | },
16 | globalData: {
17 | userInfo: null
18 | }
19 | })
20 |
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index",
4 | "pages/logs/logs"
5 | ],
6 | "window": {
7 | "navigationBarTextStyle": "black",
8 | "navigationBarTitleText": "视频live-portrait",
9 | "navigationBarBackgroundColor": "#ffffff"
10 | },
11 | "style": "v2",
12 | "componentFramework": "glass-easel",
13 | "sitemapLocation": "sitemap.json",
14 | "lazyCodeLoading": "requiredComponents"
15 | }
16 |
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | /* justify-content: space-between; */
8 | /* padding: 200rpx 0; */
9 | box-sizing: border-box;
10 | padding: 20px;
11 | }
12 |
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/pages/index/index.js:
--------------------------------------------------------------------------------
1 | Page({
2 | data: {
3 | videoUrl: '',
4 | videoUrlOutput: '',
5 | genderArray: ['男', '女'],
6 | genderIndex: 0,
7 | canUpload: false,
8 | baseUrl: "http://192.168.1.65:6020",
9 | selected_index: null,
10 | file_response_data: null
11 | },
12 |
13 | onLoad() {
14 | this.initServer();
15 | },
16 |
17 | // 初始化服务器
18 | initServer() {
19 | this.request('/init', 'GET', null)
20 | .then(res => {
21 | this.setData({
22 | selected_index: res.data.selected_index
23 | });
24 | console.log('Selected index:', res.data.selected_index);
25 | })
26 | .catch(err => console.error('Init request failed', err));
27 | },
28 |
29 | // 性别选择器的变更
30 | onGenderChange(e) {
31 | this.setData({
32 | genderIndex: e.detail.value
33 | });
34 | },
35 |
36 | // 选择视频
37 | chooseMedia() {
38 | wx.chooseMedia({
39 | count: 1,
40 | mediaType: ['video'],
41 | success: res => {
42 | this.setData({
43 | videoUrl: res.tempFiles[0].tempFilePath
44 | });
45 | },
46 | fail: err => console.error('Video selection failed', err)
47 | });
48 | },
49 |
50 | // 上传视频
51 | uploadVideo() {
52 | if (!this.data.videoUrl) {
53 | return this.showError('Please select a video first');
54 | }
55 |
56 | this.uploadFile(`${this.data.baseUrl}/upload`, this.data.videoUrl, {
57 | 'selected_index': this.data.selected_index
58 | }).then(res => {
59 | this.setData({
60 | canUpload: true,
61 | file_response_data: res.data
62 | });
63 | this.showToast('Upload successful', 'success');
64 | }).catch(err => {
65 | console.error('Upload failed', err);
66 | this.showError('Upload failed');
67 | });
68 | },
69 |
70 | // 队列提示
71 | queuePrompt() {
72 | if (!this.data.file_response_data) {
73 | return this.showError('No file to process');
74 | }
75 | wx.showLoading({
76 | title: '正在生成',
77 | })
78 | this.request('/queue', 'POST', {
79 | gender_index: this.data.genderIndex,
80 | selected_index: this.data.selected_index,
81 | file_response_data: this.data.file_response_data
82 | }).then(res => {
83 | this.getOutput(res.data.prompt_id);
84 | }).catch(err => {
85 | this.showError('Queue prompt failed');
86 | console.error('Queue prompt failed', err);
87 | });
88 | },
89 |
90 | // 获取输出
91 | getOutput(prompt_id) {
92 |
93 | this.request('/get_output', 'POST', {
94 | prompt_id: prompt_id,
95 | selected_index: this.data.selected_index
96 | }).then(res => {
97 | console.log(res)
98 | if (res.code === 10000) {
99 | const filename = res.data.outputs_video.gifs[0].filename;
100 | this.setData({
101 | videoUrlOutput: `${this.data.baseUrl}/view?filename=${filename}&selected_index=${this.data.selected_index}`
102 | });
103 | wx.hideLoading();
104 | } else if (res.code === 10002) {
105 | // 如果返回 10002,递归调用 getOutput,等待 2 秒后再次请求
106 | setTimeout(() => {
107 | this.getOutput(prompt_id);
108 | }, 2000);
109 | } else {
110 | wx.hideLoading();
111 | this.showError(res.message);
112 | }
113 | }).catch(err => {
114 | wx.hideLoading();
115 | console.error('Get output failed', err);
116 | this.showError('Get output failed');
117 | });
118 | },
119 |
120 |
121 | // 保存视频
122 | saveVideo() {
123 | if (!this.data.videoUrlOutput) {
124 | return this.showError('No video to save');
125 | }
126 |
127 | this.downloadFile(this.data.videoUrlOutput)
128 | .then(filePath => {
129 | wx.saveVideoToPhotosAlbum({
130 | filePath,
131 | success: () => this.showToast('保存成功', 'success'),
132 | fail: () => this.showError('保存失败')
133 | });
134 | })
135 | .catch(err => console.error('Error downloading video:', err));
136 | },
137 |
138 | // 重置按钮
139 | returnBtn() {
140 | this.setData({
141 | videoUrlOutput: ''
142 | });
143 | },
144 |
145 | // 通用request请求函数
146 | request(url, method, data) {
147 | return new Promise((resolve, reject) => {
148 | wx.request({
149 | url: `${this.data.baseUrl}${url}`,
150 | method: method,
151 | data: data,
152 | header: {
153 | 'content-type': 'application/json'
154 | },
155 | success: res => {
156 | if (res.data.code.toString().startsWith('1000')) {
157 | resolve(res.data);
158 | } else {
159 | this.showError(res.data.message);
160 | reject(new Error(res.data.message));
161 | }
162 | },
163 | fail: err => {
164 | this.showError('Request failed');
165 | reject(err);
166 | }
167 | });
168 | });
169 | },
170 |
171 | // 通用文件上传函数
172 | uploadFile(url, filePath, formData) {
173 | wx.showLoading({
174 | title: '正在上传...',
175 | });
176 | return new Promise((resolve, reject) => {
177 | wx.uploadFile({
178 | url: url,
179 | filePath: filePath,
180 | name: 'file',
181 | formData: formData,
182 | success: res => {
183 | const data = JSON.parse(res.data);
184 | if (data.code === 10000) {
185 | resolve(data);
186 | } else {
187 | this.showError(data.message);
188 | reject(new Error(data.message));
189 | }
190 | wx.hideLoading();
191 | },
192 | fail: reject
193 | });
194 | });
195 | },
196 |
197 | // 通用文件下载函数
198 | downloadFile(url) {
199 | return new Promise((resolve, reject) => {
200 | wx.downloadFile({
201 | url: url,
202 | success: res => {
203 | if (res.statusCode === 200) {
204 | resolve(res.tempFilePath);
205 | } else {
206 | reject(new Error(`Download failed with status code: ${res.statusCode}`));
207 | }
208 | },
209 | fail: reject
210 | });
211 | });
212 | },
213 |
214 | // 通用showToast函数
215 | showToast(title, icon = 'none', duration = 2000) {
216 | wx.showToast({
217 | title: title,
218 | icon: icon,
219 | duration: duration
220 | });
221 | },
222 |
223 | // 错误处理函数
224 | showError(message) {
225 | wx.hideLoading();
226 | this.showToast(`Error: ${message}`);
227 | }
228 | });
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | }
4 | }
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 点击视频框可重新选择视频
8 |
9 |
10 |
11 | 性别选择:{{genderArray[genderIndex]}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | .page_img2video {
2 | position: relative;
3 | width: 100vw;
4 | display: flex;
5 | margin-top: 80px;
6 | margin-bottom: 32rpx;
7 | flex-direction: column;
8 | align-items: center;
9 | }
10 | .video_box {
11 | display: flex;
12 | flex-direction: column;
13 | justify-content: center;
14 | align-items: center;
15 | width: 100%;
16 | height: 60vh;
17 | /* padding: 30px; */
18 | }
19 | .button_box button {
20 | width: 280rpx;
21 | }
22 |
23 | .button_box {
24 | width: 100%;
25 | padding: 40px;
26 | display: flex;
27 | justify-content: space-around;
28 | box-sizing: border-box;
29 | }
30 |
31 | .picker {
32 | padding: 10px;
33 | background-color: #f6f6f6;
34 | border-radius: 5px;
35 | border-radius: 10px;
36 | margin-top: 40px;
37 | }
38 | .output_box {
39 | width: 100%;
40 | display: flex;
41 | flex-direction: column;
42 | align-items: center;
43 | justify-content: center;
44 | }
45 |
46 |
47 |
48 | .output_video {
49 | width: 590rpx;
50 | height: 787rpx;
51 | }
52 |
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | // logs.js
2 | const util = require('../../utils/util.js')
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad() {
9 | this.setData({
10 | logs: (wx.getStorageSync('logs') || []).map(log => {
11 | return {
12 | date: util.formatTime(new Date(log)),
13 | timeStamp: log
14 | }
15 | })
16 | })
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | }
4 | }
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/pages/logs/logs.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{index + 1}}. {{log.date}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/pages/logs/logs.wxss:
--------------------------------------------------------------------------------
1 | page {
2 | height: 100vh;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 | .scrollarea {
7 | flex: 1;
8 | overflow-y: hidden;
9 | }
10 | .log-item {
11 | margin-top: 20rpx;
12 | text-align: center;
13 | }
14 | .log-item:last-child {
15 | padding-bottom: env(safe-area-inset-bottom);
16 | }
17 |
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileType": "miniprogram",
3 | "libVersion": "trial",
4 | "packOptions": {
5 | "ignore": [],
6 | "include": []
7 | },
8 | "setting": {
9 | "coverView": true,
10 | "es6": true,
11 | "postcss": true,
12 | "minified": true,
13 | "enhance": true,
14 | "showShadowRootInWxmlPanel": true,
15 | "packNpmRelationList": [],
16 | "babelSetting": {
17 | "ignore": [],
18 | "disablePlugins": [],
19 | "outputPath": ""
20 | }
21 | },
22 | "condition": {},
23 | "editorSetting": {
24 | "tabIndent": "auto",
25 | "tabSize": 2
26 | },
27 | "appid": "wx0e0781e2f366ca61"
28 | }
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/project.private.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
3 | "projectname": "video2video",
4 | "setting": {
5 | "compileHotReLoad": true,
6 | "urlCheck": false
7 | }
8 | }
--------------------------------------------------------------------------------
/projects/project_3_5/wechat-example/video2video/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/projects/project_3_5/workflow_api.json:
--------------------------------------------------------------------------------
1 | {
2 | "4": {
3 | "inputs": {
4 | "ckpt_name": "adammixSDXLDoll_v11.safetensors"
5 | },
6 | "class_type": "CheckpointLoaderSimple",
7 | "_meta": {
8 | "title": "Load Checkpoint"
9 | }
10 | },
11 | "5": {
12 | "inputs": {
13 | "width": 768,
14 | "height": 1024,
15 | "batch_size": 1
16 | },
17 | "class_type": "EmptyLatentImage",
18 | "_meta": {
19 | "title": "Empty Latent Image"
20 | }
21 | },
22 | "6": {
23 | "inputs": {
24 | "text": [
25 | "11",
26 | 0
27 | ],
28 | "speak_and_recognation": true,
29 | "clip": [
30 | "14",
31 | 1
32 | ]
33 | },
34 | "class_type": "CLIPTextEncode",
35 | "_meta": {
36 | "title": "CLIP Text Encode (Prompt)"
37 | }
38 | },
39 | "7": {
40 | "inputs": {
41 | "text": [
42 | "11",
43 | 1
44 | ],
45 | "speak_and_recognation": true,
46 | "clip": [
47 | "14",
48 | 1
49 | ]
50 | },
51 | "class_type": "CLIPTextEncode",
52 | "_meta": {
53 | "title": "CLIP Text Encode (Prompt)"
54 | }
55 | },
56 | "11": {
57 | "inputs": {
58 | "text_positive": "chibi:2,cute,Kawaii,(full body:2),Standing:2,shoe:2,(blue clean background),(Highly saturated background),PVC material, silicone material,standing character, soft smooth lighting, soft pastel colors, skottie young, 3d blender render, polycount, modular constructivism, pop surrealism, physically based rendering, square image, a man wear a suit and jeans",
59 | "text_negative": "Glow, Wrinkles, Aging, Cock-eye,realist photo, Real Material",
60 | "style": "3d-model",
61 | "log_prompt": "No",
62 | "style_name": true,
63 | "speak_and_recognation": true
64 | },
65 | "class_type": "SDXLPromptStyler",
66 | "_meta": {
67 | "title": "SDXL Prompt Styler"
68 | }
69 | },
70 | "13": {
71 | "inputs": {
72 | "toggle": true,
73 | "mode": "simple",
74 | "num_loras": 5,
75 | "lora_1_name": "smiling.pt",
76 | "lora_1_strength": 2,
77 | "lora_1_model_strength": 1,
78 | "lora_1_clip_strength": 1,
79 | "lora_2_name": "age.pt",
80 | "lora_2_strength": -1,
81 | "lora_2_model_strength": 1,
82 | "lora_2_clip_strength": 1,
83 | "lora_3_name": "fix_hands.pt",
84 | "lora_3_strength": 1,
85 | "lora_3_model_strength": 1,
86 | "lora_3_clip_strength": 1,
87 | "lora_4_name": "looking_at_viewer.safetensors",
88 | "lora_4_strength": 1,
89 | "lora_4_model_strength": 1,
90 | "lora_4_clip_strength": 1,
91 | "lora_5_name": "3D模型丨可爱化SDXL版_v2.0.safetensors",
92 | "lora_5_strength": 1,
93 | "lora_5_model_strength": 1,
94 | "lora_5_clip_strength": 1,
95 | "lora_6_name": "cartoon_style.pt",
96 | "lora_6_strength": 1,
97 | "lora_6_model_strength": 1,
98 | "lora_6_clip_strength": 1,
99 | "lora_7_name": "None",
100 | "lora_7_strength": 1,
101 | "lora_7_model_strength": 1,
102 | "lora_7_clip_strength": 1,
103 | "lora_8_name": "None",
104 | "lora_8_strength": 1,
105 | "lora_8_model_strength": 1,
106 | "lora_8_clip_strength": 1,
107 | "lora_9_name": "None",
108 | "lora_9_strength": 1,
109 | "lora_9_model_strength": 1,
110 | "lora_9_clip_strength": 1,
111 | "lora_10_name": "None",
112 | "lora_10_strength": 1,
113 | "lora_10_model_strength": 1,
114 | "lora_10_clip_strength": 1
115 | },
116 | "class_type": "easy loraStack",
117 | "_meta": {
118 | "title": "EasyLoraStack"
119 | }
120 | },
121 | "14": {
122 | "inputs": {
123 | "model": [
124 | "4",
125 | 0
126 | ],
127 | "clip": [
128 | "4",
129 | 1
130 | ],
131 | "lora_stack": [
132 | "13",
133 | 0
134 | ]
135 | },
136 | "class_type": "CR Apply LoRA Stack",
137 | "_meta": {
138 | "title": "💊 CR Apply LoRA Stack"
139 | }
140 | },
141 | "15": {
142 | "inputs": {
143 | "method": "fidelity",
144 | "weight": 1,
145 | "start_at": 0,
146 | "end_at": 0.9,
147 | "model": [
148 | "34",
149 | 0
150 | ],
151 | "pulid": [
152 | "19",
153 | 0
154 | ],
155 | "eva_clip": [
156 | "20",
157 | 0
158 | ],
159 | "face_analysis": [
160 | "21",
161 | 0
162 | ],
163 | "image": [
164 | "42",
165 | 0
166 | ]
167 | },
168 | "class_type": "ApplyPulid",
169 | "_meta": {
170 | "title": "Apply PuLID"
171 | }
172 | },
173 | "17": {
174 | "inputs": {
175 | "preset": "PLUS FACE (portraits)",
176 | "model": [
177 | "14",
178 | 0
179 | ]
180 | },
181 | "class_type": "IPAdapterUnifiedLoader",
182 | "_meta": {
183 | "title": "IPAdapter Unified Loader"
184 | }
185 | },
186 | "19": {
187 | "inputs": {
188 | "pulid_file": "ip-adapter_pulid_sdxl_fp16.safetensors"
189 | },
190 | "class_type": "PulidModelLoader",
191 | "_meta": {
192 | "title": "Load PuLID Model"
193 | }
194 | },
195 | "20": {
196 | "inputs": {},
197 | "class_type": "PulidEvaClipLoader",
198 | "_meta": {
199 | "title": "Load Eva Clip (PuLID)"
200 | }
201 | },
202 | "21": {
203 | "inputs": {
204 | "provider": "CPU"
205 | },
206 | "class_type": "InstantIDFaceAnalysis",
207 | "_meta": {
208 | "title": "InstantID Face Analysis"
209 | }
210 | },
211 | "31": {
212 | "inputs": {
213 | "seed": 939642571730518,
214 | "steps": 20,
215 | "cfg": 4,
216 | "sampler_name": "euler_ancestral",
217 | "scheduler": "karras",
218 | "denoise": 1,
219 | "preview_method": "auto",
220 | "vae_decode": "true",
221 | "model": [
222 | "15",
223 | 0
224 | ],
225 | "positive": [
226 | "6",
227 | 0
228 | ],
229 | "negative": [
230 | "7",
231 | 0
232 | ],
233 | "latent_image": [
234 | "5",
235 | 0
236 | ],
237 | "optional_vae": [
238 | "4",
239 | 2
240 | ],
241 | "script": [
242 | "36",
243 | 0
244 | ]
245 | },
246 | "class_type": "KSampler (Efficient)",
247 | "_meta": {
248 | "title": "KSampler (Efficient)"
249 | }
250 | },
251 | "34": {
252 | "inputs": {
253 | "weight": 1,
254 | "weight_faceidv2": 1,
255 | "weight_type": "ease in",
256 | "combine_embeds": "concat",
257 | "start_at": 0.3,
258 | "end_at": 0.6,
259 | "embeds_scaling": "V only",
260 | "model": [
261 | "17",
262 | 0
263 | ],
264 | "ipadapter": [
265 | "17",
266 | 1
267 | ],
268 | "image": [
269 | "42",
270 | 0
271 | ]
272 | },
273 | "class_type": "IPAdapterFaceID",
274 | "_meta": {
275 | "title": "IPAdapter FaceID"
276 | }
277 | },
278 | "36": {
279 | "inputs": {
280 | "upscale_type": "latent",
281 | "hires_ckpt_name": "(use same)",
282 | "latent_upscaler": "nearest-exact",
283 | "pixel_upscaler": "4x-UltraSharp.pth",
284 | "upscale_by": 1.5,
285 | "use_same_seed": true,
286 | "seed": 142878327312673,
287 | "hires_steps": 12,
288 | "denoise": 0.56,
289 | "iterations": 1,
290 | "use_controlnet": false,
291 | "control_net_name": "CN-anytest_v4-marged.safetensors",
292 | "strength": 1,
293 | "preprocessor": "none",
294 | "preprocessor_imgs": false
295 | },
296 | "class_type": "HighRes-Fix Script",
297 | "_meta": {
298 | "title": "HighRes-Fix Script"
299 | }
300 | },
301 | "37": {
302 | "inputs": {
303 | "filename_prefix": "cute_you",
304 | "images": [
305 | "31",
306 | 5
307 | ]
308 | },
309 | "class_type": "SaveImage",
310 | "_meta": {
311 | "title": "Save Image"
312 | }
313 | },
314 | "39": {
315 | "inputs": {
316 | "video": "video/d0.mp4",
317 | "video_segment_frames": -1,
318 | "transition_frames": 0,
319 | "upload-preview": null,
320 | "upload file": "video"
321 | },
322 | "class_type": "LoadVideoAndSegment_",
323 | "_meta": {
324 | "title": "Load Video And Segment"
325 | }
326 | },
327 | "41": {
328 | "inputs": {
329 | "index": 0,
330 | "scenes_video": [
331 | "39",
332 | 0
333 | ]
334 | },
335 | "class_type": "ScenesNode_",
336 | "_meta": {
337 | "title": "Select Scene"
338 | }
339 | },
340 | "42": {
341 | "inputs": {
342 | "start": 0,
343 | "length": 1,
344 | "image": [
345 | "41",
346 | 0
347 | ]
348 | },
349 | "class_type": "ImageFromBatch+",
350 | "_meta": {
351 | "title": "🔧 Image From Batch"
352 | }
353 | },
354 | "43": {
355 | "inputs": {
356 | "driving_video_reverse_align": true,
357 | "source_image": [
358 | "31",
359 | 5
360 | ],
361 | "driving_video": [
362 | "39",
363 | 0
364 | ]
365 | },
366 | "class_type": "LivePortraitNode",
367 | "_meta": {
368 | "title": "Live Portrait"
369 | }
370 | },
371 | "45": {
372 | "inputs": {
373 | "index": 0,
374 | "scenes_video": [
375 | "43",
376 | 0
377 | ]
378 | },
379 | "class_type": "ScenesNode_",
380 | "_meta": {
381 | "title": "Select Scene"
382 | }
383 | },
384 | "46": {
385 | "inputs": {
386 | "frame_rate": 24,
387 | "loop_count": 0,
388 | "filename_prefix": "AnimateDiff",
389 | "format": "video/h264-mp4",
390 | "pix_fmt": "yuv420p",
391 | "crf": 19,
392 | "save_metadata": true,
393 | "pingpong": false,
394 | "save_output": true,
395 | "images": [
396 | "45",
397 | 0
398 | ]
399 | },
400 | "class_type": "VHS_VideoCombine",
401 | "_meta": {
402 | "title": "Video Combine 🎥🅥🅗🅢"
403 | }
404 | }
405 | }
--------------------------------------------------------------------------------
/projects/project_3_5/workflow_api.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request as flask_request, Response, jsonify
2 | from comfy_sdk import select_random_server, run_workflow, upload_file, history, show_result
3 | import json
4 |
5 | app = Flask(__name__)
6 |
7 | # 配置目标服务器的URL数组
8 | target_urls = [
9 | 'https://xxxxx-xxxx-xxxxxx',
10 | None,
11 | None,
12 | None,
13 | None
14 | ]
15 |
16 |
17 | # 统一的响应函数
18 | def create_response(data=None, code=10000, message="Success"):
19 | response = {
20 | "code": code,
21 | "message": message
22 | }
23 | if data:
24 | response["data"] = data
25 | return jsonify(response), 200 if str(code).startswith('1000') else 500
26 |
27 | # init 分配到哪台服务器 返回index
28 | @app.route('/init', methods=['GET'])
29 | def init():
30 | try:
31 |
32 | target_url, selected_index = select_random_server(target_urls)
33 | print(target_url)
34 | print(selected_index)
35 | return create_response({"selected_index": selected_index})
36 | except Exception as e:
37 | return create_response(code=40001, message=f"Error: {str(e)}")
38 |
39 | # 上传视频
40 | @app.route('/upload', methods=['POST'])
41 | def upload():
42 | try:
43 | # 获取上传的文件
44 | uploaded_file = flask_request.files['file']
45 | original_filename = uploaded_file.filename # 获取原始文件名
46 | selected_index = flask_request.form.get('selected_index')
47 | # 转发上传请求
48 | files = {'image': (original_filename, uploaded_file.stream, uploaded_file.mimetype)}
49 | data = {'subfolder': 'video'}
50 | print(target_urls[int(selected_index)],'target_urls[selected_index]')
51 | #上传文件并返回参数
52 | file_response = upload_file(target_urls[int(selected_index)],files,data)
53 | file_response_data = file_response.json()
54 | print(file_response_data)
55 | # 调用 queue 函数处理后续工作流
56 | return create_response(file_response_data)
57 | except Exception as e:
58 | return create_response(code=40002, message=f"Error: {str(e)}")
59 |
60 | @app.route('/queue', methods=['POST'])
61 | def queue():
62 | try:
63 | data = flask_request.get_json()
64 | gender_index = data.get('gender_index')
65 | selected_index = data.get('selected_index')
66 | file_response_data = data.get('file_response_data')
67 |
68 | print(data)
69 | print(gender_index)
70 | print(selected_index)
71 | print(file_response_data)
72 |
73 | # 加载并修改工作流参数
74 | prompt_workflow = json.load(open('./workflow_api.json'))
75 | prompt_workflow['39']['inputs']['video'] = f"{file_response_data['subfolder']}/{file_response_data['name']}"
76 |
77 | if int(gender_index) == 0:
78 | prompt_workflow['11']['inputs']['text_positive'] = 'chibi:2, a man with black clothes, jeans, cute, Kawaii, (full body:2), Standing:2, shoe:2, (blue clean background), (Highly saturated background), PVC material, silicone material, standing character, soft smooth lighting, soft pastel colors, skottie young, 3d blender render, polycount, modular constructivism, pop surrealism, physically based rendering, square image'
79 | else:
80 | prompt_workflow['11']['inputs']['text_positive'] = 'chibi:2, a girl with skirt, cute, Kawaii, (full body:2), Standing:2, shoe:2, (blue clean background), (Highly saturated background), PVC material, silicone material, standing character, soft smooth lighting, soft pastel colors, skottie young, 3d blender render, polycount, modular constructivism, pop surrealism, physically based rendering, square image'
81 |
82 | queue_response = run_workflow(target_urls[int(selected_index)], prompt_workflow)
83 | queue_response_data = queue_response.json()
84 |
85 | return create_response(queue_response_data)
86 | except Exception as e:
87 | return create_response(code=40002, message=f"Error: {str(e)}")
88 |
89 | # 轮询history
90 | @app.route('/get_output', methods=['POST'])
91 | def get_output():
92 | try:
93 | data = flask_request.get_json()
94 | prompt_id = data.get('prompt_id')
95 | selected_index = data.get('selected_index')
96 | response_content = history(target_urls[int(selected_index)], prompt_id).json()
97 | if not response_content.get(prompt_id, {}):
98 | return create_response({"outputs_video": None}, code=10002, message="The value is an empty dictionary.")
99 | else:
100 | outputs_video = response_content[prompt_id]['outputs']['46']
101 | return create_response({"outputs_video": outputs_video})
102 | except Exception as e:
103 | return create_response(code=40002, message=f"Error: {str(e)}")
104 |
105 | # 显示
106 | @app.route('/view')
107 | def view():
108 | try:
109 | filename = flask_request.args.get('filename')
110 | selected_index = flask_request.args.get('selected_index')
111 | if not filename:
112 | return create_response(code=40003, message="Filename is required")
113 |
114 | response = show_result(target_urls[int(selected_index)], filename)
115 | return Response(response.content, content_type=response.headers['Content-Type'])
116 | except Exception as e:
117 | return create_response(code=40002, message=f"Error: {str(e)}")
118 |
119 | if __name__ == '__main__':
120 | app.run(port=6020)
121 |
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/app.js:
--------------------------------------------------------------------------------
1 | // app.js
2 | App({
3 | onLaunch() {
4 | // 展示本地存储能力
5 | const logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 |
9 | // 登录
10 | wx.login({
11 | success: res => {
12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
13 | }
14 | })
15 | },
16 | globalData: {
17 | userInfo: null
18 | }
19 | })
20 |
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index",
4 | "pages/logs/logs"
5 | ],
6 | "window": {
7 | "navigationBarTextStyle": "black",
8 | "navigationBarTitleText": "疯狂贴纸",
9 | "navigationBarBackgroundColor": "#ffffff"
10 | },
11 | "style": "v2",
12 | "componentFramework": "glass-easel",
13 | "sitemapLocation": "sitemap.json",
14 | "lazyCodeLoading": "requiredComponents"
15 | }
16 |
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: space-between;
8 | box-sizing: border-box;
9 | }
10 |
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/images/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_1/wechat-example/crazy_sticker/pages/images/bg.png
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/images/btn_download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_1/wechat-example/crazy_sticker/pages/images/btn_download.png
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/images/btn_upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_1/wechat-example/crazy_sticker/pages/images/btn_upload.png
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/images/completed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_1/wechat-example/crazy_sticker/pages/images/completed.png
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/images/ele.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_1/wechat-example/crazy_sticker/pages/images/ele.png
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/images/ele_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_1/wechat-example/crazy_sticker/pages/images/ele_1.png
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/images/ele_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_1/wechat-example/crazy_sticker/pages/images/ele_2.png
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/images/ele_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_1/wechat-example/crazy_sticker/pages/images/ele_3.png
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/images/ele_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_1/wechat-example/crazy_sticker/pages/images/ele_4.png
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/index/index.js:
--------------------------------------------------------------------------------
1 | Page({
2 | data: {
3 | baseUrl: "http://192.168.31.101:6020",
4 | imglist: ['', '', '', ''],
5 | selected_index: ''
6 | },
7 | onLoad() {
8 | this.initServer();
9 | },
10 | // 初始化服务器
11 | initServer() {
12 | this.request('/init', 'GET', null)
13 | .then(data => {
14 | this.setData({
15 | selected_index: data.data.selected_index
16 | });
17 | console.log('Selected index:', data.data.selected_index);
18 | });
19 | },
20 | uploadImage() {
21 | wx.chooseMedia({
22 | count: 1,
23 | mediaType: ['image'],
24 | sourceType: ['album', 'camera'],
25 | maxDuration: 30,
26 | camera: 'back',
27 | success: res => {
28 | const tempFilePath = res.tempFiles[0].tempFilePath;
29 | wx.showLoading({
30 | title: '正在上传...'
31 | });
32 |
33 | wx.uploadFile({
34 | url: `${this.data.baseUrl}/upload`,
35 | filePath: tempFilePath,
36 | formData: {
37 | 'selected_index': this.data.selected_index
38 | },
39 | name: 'image',
40 | success: res => {
41 | const data = JSON.parse(res.data);
42 | if (data.code === 10000) {
43 | this.showToast('Upload successful', 'success');
44 | this.queue(data.data);
45 | } else {
46 | this.showError(data.message);
47 | }
48 | },
49 | fail: err => {
50 | console.error('Upload error:', err);
51 | this.showError('Upload failed');
52 | }
53 | });
54 | }
55 | });
56 | },
57 | queue(file_response_data) {
58 | wx.showLoading({
59 | title: '正在生成图片...'
60 | });
61 | this.request('/queue', 'POST', {
62 | selected_index: this.data.selected_index,
63 | file_response_data: file_response_data
64 | })
65 | .then(data => this.fetchImageByPromptId(data.data.prompt_id))
66 | .catch(err => console.error('Queue error:', err));
67 | },
68 | fetchImageByPromptId(prompt_id) {
69 | let that = this
70 | this.request('/get_output', 'POST', {
71 | prompt_id: prompt_id,
72 | selected_index: this.data.selected_index
73 | }).then(data => {
74 | if (data.data.outputs_img && data.data.outputs_img.images.length > 0) {
75 | wx.hideLoading();
76 | let outputs_imgs = data.data.outputs_img.images;
77 | let img_list = this.data.imglist;
78 | outputs_imgs.forEach((img, index) => {
79 | img_list[index] = `${this.data.baseUrl}/view?filename=${img.filename}&selected_index=${this.data.selected_index}`;
80 | });
81 | this.setData({
82 | imglist: img_list
83 | });
84 | } else if (data.code === 10002) {
85 | setTimeout(() => {
86 | that.fetchImageByPromptId(prompt_id);
87 | }, 2000);
88 | } else {
89 | this.showError(data.message);
90 | }
91 | }).catch(err => {
92 | console.error('Get output failed', err);
93 | this.showError('Get output failed');
94 | });
95 | },
96 | saveFile() {
97 | Promise.all(this.data.imglist.map(url => this.downloadImage(url)))
98 | .then(() => {
99 | this.showToast('保存成功', 'success');
100 | })
101 | .catch(error => {
102 | console.error('Error downloading images:', error);
103 | });
104 | },
105 | downloadImage(url) {
106 | return new Promise((resolve, reject) => {
107 | wx.downloadFile({
108 | url: url,
109 | success: res => {
110 | if (res.statusCode === 200) {
111 | wx.saveImageToPhotosAlbum({
112 | filePath: res.tempFilePath,
113 | success: resolve,
114 | fail: () => {
115 | this.showError('保存失败');
116 | reject(new Error('Save failed'));
117 | }
118 | });
119 | } else {
120 | reject(new Error('Download failed with status code: ' + res.statusCode));
121 | }
122 | },
123 | fail: reject
124 | });
125 | });
126 | },
127 | // 通用request请求函数
128 | request(url, method, data) {
129 | return new Promise((resolve, reject) => {
130 | wx.request({
131 | url: `${this.data.baseUrl}${url}`,
132 | method: method,
133 | data: data,
134 | header: {
135 | 'content-type': 'application/json'
136 | },
137 | success: res => {
138 | if (res.data.code.toString().startsWith('1000')) {
139 | resolve(res.data);
140 | } else {
141 | this.showError(res.data.message);
142 | reject(new Error(res.data.message));
143 | }
144 | },
145 | fail: err => {
146 | console.error('Request failed', err);
147 | this.showError('Request failed');
148 | reject(err);
149 | }
150 | });
151 | });
152 | },
153 | // 通用showToast函数
154 | showToast(title, icon = 'none', duration = 2000) {
155 | wx.showToast({
156 | title: title,
157 | icon: icon,
158 | duration: duration
159 | });
160 | },
161 | // 错误处理函数
162 | showError(message) {
163 | wx.hideLoading();
164 | this.showToast(`Error: ${message}`);
165 | },
166 | });
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | }
4 | }
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | .view_box{
2 | display: flex;
3 | flex-direction: row;
4 | top: 40%;
5 | width: 640rpx;
6 | left: 55rpx;
7 | justify-content: space-evenly;
8 | flex-wrap: wrap;
9 | }
10 | .result{
11 | height: 286rpx;
12 | width: 286rpx;
13 | margin-bottom: 18rpx;
14 | /* background-image: url('../images/ele_1.png'); */
15 | background-size: contain;
16 | }
17 | .completed{
18 | position: absolute;
19 | left: 50%;
20 | top: 33%;
21 | height: 61rpx;
22 | width: 380rpx;
23 | transform: translate(-50%);
24 | }
25 | .title{
26 | position: absolute;
27 | left: 50%;
28 | top: 8.5%;
29 | height: 109rpx;
30 | width: 405rpx;
31 | transform: translate(-50%);
32 | }
33 | .button_upload{
34 | position: absolute;
35 | left: 50%;
36 | top: 18%;
37 | height: 117rpx;
38 | width: 425rpx;
39 | transform: translate(-50%);
40 | }
41 | .btn_download{
42 | position: absolute;
43 | left: 50%;
44 | top: 85.3%;
45 | height: 149rpx;
46 | width: 335rpx;
47 | transform: translate(-50%);
48 | }
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | // logs.js
2 | const util = require('../../utils/util.js')
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad() {
9 | this.setData({
10 | logs: (wx.getStorageSync('logs') || []).map(log => {
11 | return {
12 | date: util.formatTime(new Date(log)),
13 | timeStamp: log
14 | }
15 | })
16 | })
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | }
4 | }
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/logs/logs.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{index + 1}}. {{log.date}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/pages/logs/logs.wxss:
--------------------------------------------------------------------------------
1 | page {
2 | height: 100vh;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 | .scrollarea {
7 | flex: 1;
8 | overflow-y: hidden;
9 | }
10 | .log-item {
11 | margin-top: 20rpx;
12 | text-align: center;
13 | }
14 | .log-item:last-child {
15 | padding-bottom: env(safe-area-inset-bottom);
16 | }
17 |
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileType": "miniprogram",
3 | "libVersion": "trial",
4 | "packOptions": {
5 | "ignore": [],
6 | "include": []
7 | },
8 | "setting": {
9 | "coverView": true,
10 | "es6": true,
11 | "postcss": true,
12 | "minified": true,
13 | "enhance": true,
14 | "showShadowRootInWxmlPanel": true,
15 | "packNpmRelationList": [],
16 | "babelSetting": {
17 | "ignore": [],
18 | "disablePlugins": [],
19 | "outputPath": ""
20 | }
21 | },
22 | "condition": {},
23 | "editorSetting": {
24 | "tabIndent": "auto",
25 | "tabSize": 2
26 | },
27 | "appid": "wx0e0781e2f366ca61"
28 | }
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/project.private.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
3 | "projectname": "crazy_sticker",
4 | "setting": {
5 | "compileHotReLoad": true,
6 | "urlCheck": false
7 | }
8 | }
--------------------------------------------------------------------------------
/projects/project_4_1/wechat-example/crazy_sticker/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/projects/project_4_1/workflow_api.json:
--------------------------------------------------------------------------------
1 | {
2 | "4": {
3 | "inputs": {
4 | "ckpt_name": "dreamshaperXL_turboDpmppSDE.safetensors"
5 | },
6 | "class_type": "CheckpointLoaderSimple",
7 | "_meta": {
8 | "title": "Load Checkpoint"
9 | }
10 | },
11 | "5": {
12 | "inputs": {
13 | "width": 768,
14 | "height": 768,
15 | "batch_size": 4
16 | },
17 | "class_type": "EmptyLatentImage",
18 | "_meta": {
19 | "title": "Empty Latent Image"
20 | }
21 | },
22 | "6": {
23 | "inputs": {
24 | "text": [
25 | "16",
26 | 0
27 | ],
28 | "speak_and_recognation": true,
29 | "clip": [
30 | "13",
31 | 1
32 | ]
33 | },
34 | "class_type": "CLIPTextEncode",
35 | "_meta": {
36 | "title": "CLIP Text Encode (Prompt)"
37 | }
38 | },
39 | "12": {
40 | "inputs": {
41 | "toggle": true,
42 | "mode": "simple",
43 | "num_loras": 3,
44 | "lora_1_name": "StickersRedmond.safetensors",
45 | "lora_1_strength": 1,
46 | "lora_1_model_strength": 1,
47 | "lora_1_clip_strength": 1,
48 | "lora_2_name": "cartoon_style.pt",
49 | "lora_2_strength": 2,
50 | "lora_2_model_strength": 1,
51 | "lora_2_clip_strength": 1,
52 | "lora_3_name": "smiling.pt",
53 | "lora_3_strength": 2,
54 | "lora_3_model_strength": 1,
55 | "lora_3_clip_strength": 1,
56 | "lora_4_name": "None",
57 | "lora_4_strength": 1,
58 | "lora_4_model_strength": 1,
59 | "lora_4_clip_strength": 1,
60 | "lora_5_name": "None",
61 | "lora_5_strength": 1,
62 | "lora_5_model_strength": 1,
63 | "lora_5_clip_strength": 1,
64 | "lora_6_name": "None",
65 | "lora_6_strength": 1,
66 | "lora_6_model_strength": 1,
67 | "lora_6_clip_strength": 1,
68 | "lora_7_name": "None",
69 | "lora_7_strength": 1,
70 | "lora_7_model_strength": 1,
71 | "lora_7_clip_strength": 1,
72 | "lora_8_name": "None",
73 | "lora_8_strength": 1,
74 | "lora_8_model_strength": 1,
75 | "lora_8_clip_strength": 1,
76 | "lora_9_name": "None",
77 | "lora_9_strength": 1,
78 | "lora_9_model_strength": 1,
79 | "lora_9_clip_strength": 1,
80 | "lora_10_name": "None",
81 | "lora_10_strength": 1,
82 | "lora_10_model_strength": 1,
83 | "lora_10_clip_strength": 1
84 | },
85 | "class_type": "easy loraStack",
86 | "_meta": {
87 | "title": "EasyLoraStack"
88 | }
89 | },
90 | "13": {
91 | "inputs": {
92 | "model": [
93 | "4",
94 | 0
95 | ],
96 | "clip": [
97 | "4",
98 | 1
99 | ],
100 | "lora_stack": [
101 | "12",
102 | 0
103 | ]
104 | },
105 | "class_type": "CR Apply LoRA Stack",
106 | "_meta": {
107 | "title": "💊 CR Apply LoRA Stack"
108 | }
109 | },
110 | "16": {
111 | "inputs": {
112 | "text_positive": "Sticker, 1 girl,chibi:2,Flat:1.5,(full body:2),Simple details,(Empty Background:2), Boxers, with red boxing gloves, swinging their fists",
113 | "text_negative": "Realism, photo-realism, real materials,(full body:2)",
114 | "style": "3d-model",
115 | "log_prompt": "No",
116 | "style_name": true,
117 | "speak_and_recognation": true
118 | },
119 | "class_type": "SDXLPromptStyler",
120 | "_meta": {
121 | "title": "SDXL Prompt Styler"
122 | }
123 | },
124 | "17": {
125 | "inputs": {
126 | "text": [
127 | "16",
128 | 1
129 | ],
130 | "speak_and_recognation": true,
131 | "clip": [
132 | "13",
133 | 1
134 | ]
135 | },
136 | "class_type": "CLIPTextEncode",
137 | "_meta": {
138 | "title": "CLIP Text Encode (Prompt)"
139 | }
140 | },
141 | "19": {
142 | "inputs": {
143 | "method": "fidelity",
144 | "weight": 1,
145 | "start_at": 0,
146 | "end_at": 1,
147 | "model": [
148 | "13",
149 | 0
150 | ],
151 | "pulid": [
152 | "24",
153 | 0
154 | ],
155 | "eva_clip": [
156 | "23",
157 | 0
158 | ],
159 | "face_analysis": [
160 | "21",
161 | 0
162 | ],
163 | "image": [
164 | "22",
165 | 0
166 | ]
167 | },
168 | "class_type": "ApplyPulid",
169 | "_meta": {
170 | "title": "Apply PuLID"
171 | }
172 | },
173 | "21": {
174 | "inputs": {
175 | "provider": "CUDA"
176 | },
177 | "class_type": "PulidInsightFaceLoader",
178 | "_meta": {
179 | "title": "Load InsightFace (PuLID)"
180 | }
181 | },
182 | "22": {
183 | "inputs": {
184 | "image": "80455-351241547-r645RxPDeXHEm6eAzsc4.png",
185 | "upload": "image"
186 | },
187 | "class_type": "LoadImage",
188 | "_meta": {
189 | "title": "Load Image"
190 | }
191 | },
192 | "23": {
193 | "inputs": {},
194 | "class_type": "PulidEvaClipLoader",
195 | "_meta": {
196 | "title": "Load Eva Clip (PuLID)"
197 | }
198 | },
199 | "24": {
200 | "inputs": {
201 | "pulid_file": "ip-adapter_pulid_sdxl_fp16.safetensors"
202 | },
203 | "class_type": "PulidModelLoader",
204 | "_meta": {
205 | "title": "Load PuLID Model"
206 | }
207 | },
208 | "35": {
209 | "inputs": {
210 | "model": "u2net",
211 | "alpha_matting": "true",
212 | "alpha_matting_foreground_threshold": 240,
213 | "alpha_matting_background_threshold": 20,
214 | "alpha_matting_erode_size": 10,
215 | "post_process_mask": "false",
216 | "images": [
217 | "37",
218 | 5
219 | ]
220 | },
221 | "class_type": "ImageSegmentation",
222 | "_meta": {
223 | "title": "ImageSegmentation"
224 | }
225 | },
226 | "37": {
227 | "inputs": {
228 | "seed": 722086658157523,
229 | "steps": 8,
230 | "cfg": 2,
231 | "sampler_name": "dpmpp_2m",
232 | "scheduler": "karras",
233 | "denoise": 1,
234 | "preview_method": "auto",
235 | "vae_decode": "true",
236 | "model": [
237 | "19",
238 | 0
239 | ],
240 | "positive": [
241 | "6",
242 | 0
243 | ],
244 | "negative": [
245 | "17",
246 | 0
247 | ],
248 | "latent_image": [
249 | "5",
250 | 0
251 | ],
252 | "optional_vae": [
253 | "4",
254 | 2
255 | ]
256 | },
257 | "class_type": "KSampler (Efficient)",
258 | "_meta": {
259 | "title": "KSampler (Efficient)"
260 | }
261 | },
262 | "44": {
263 | "inputs": {
264 | "filename_prefix": "ComfyUI",
265 | "images": [
266 | "35",
267 | 0
268 | ]
269 | },
270 | "class_type": "SaveImage",
271 | "_meta": {
272 | "title": "Save Image"
273 | }
274 | }
275 | }
--------------------------------------------------------------------------------
/projects/project_4_1/workflow_api.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request as flask_request, Response, jsonify
2 | from comfy_sdk import select_random_server, run_workflow, upload_file, history, show_result
3 | import json
4 |
5 | app = Flask(__name__)
6 |
7 | # 配置目标服务器的URL数组
8 | target_urls = [
9 | 'https://xxx.xxx.xxx',
10 | None,
11 | None,
12 | None,
13 | None
14 | ]
15 |
16 |
17 | # 统一的响应函数
18 | def create_response(data=None, code=10000, message="Success"):
19 | response = {
20 | "code": code,
21 | "message": message
22 | }
23 | if data:
24 | response["data"] = data
25 | return jsonify(response), 200 if str(code).startswith('1000') else 500
26 |
27 | # init 分配到哪台服务器 返回index
28 | @app.route('/init', methods=['GET'])
29 | def init():
30 | try:
31 | target_url, selected_index = select_random_server(target_urls)
32 | return create_response({"selected_index": selected_index})
33 | except Exception as e:
34 | return create_response(code=40001, message=f"Error: {str(e)}")
35 |
36 | # 上传视频
37 | @app.route('/upload', methods=['POST'])
38 | def upload():
39 | try:
40 | # 获取上传的文件
41 | uploaded_file = flask_request.files['image']
42 | print(uploaded_file)
43 | original_filename = uploaded_file.filename # 获取原始文件名
44 | selected_index = flask_request.form.get('selected_index')
45 | print(selected_index,'selected_index')
46 |
47 | # 转发上传请求
48 | files = {'image': (original_filename, uploaded_file.stream, uploaded_file.mimetype)}
49 | #上传文件并返回参数
50 | file_response = upload_file(target_urls[int(selected_index)],files)
51 | file_response_data = file_response.json()
52 | # 调用 queue 函数处理后续工作流
53 | return create_response(file_response_data)
54 | except Exception as e:
55 | return create_response(code=40002, message=f"Error: {str(e)}")
56 |
57 | @app.route('/queue', methods=['POST'])
58 | def queue():
59 | try:
60 | data = flask_request.get_json()
61 | selected_index = data.get('selected_index')
62 | file_response_data = data.get('file_response_data')
63 |
64 | # 加载并修改工作流参数
65 | prompt_workflow = json.load(open('./workflow_api.json'))
66 | prompt_workflow['22']['inputs']['image'] = f"{file_response_data['name']}"
67 |
68 | queue_response = run_workflow(target_urls[int(selected_index)], prompt_workflow)
69 | queue_response_data = queue_response.json()
70 |
71 | return create_response(queue_response_data)
72 | except Exception as e:
73 | return create_response(code=40002, message=f"Error: {str(e)}")
74 |
75 | # 轮询history
76 | @app.route('/get_output', methods=['POST'])
77 | def get_output():
78 | try:
79 | data = flask_request.get_json()
80 | prompt_id = data.get('prompt_id')
81 | selected_index = data.get('selected_index')
82 | response_content = history(target_urls[int(selected_index)], prompt_id).json()
83 | if not response_content.get(prompt_id, {}):
84 | return create_response({"outputs_img": None}, code=10002, message="The value is an empty dictionary.")
85 | else:
86 | outputs_img = response_content[prompt_id]['outputs']['44']
87 | return create_response({"outputs_img": outputs_img})
88 | except Exception as e:
89 | return create_response(code=40002, message=f"Error: {str(e)}")
90 |
91 | # 显示
92 | @app.route('/view')
93 | def view():
94 | try:
95 | filename = flask_request.args.get('filename')
96 | selected_index = flask_request.args.get('selected_index')
97 | if not filename:
98 | return create_response(code=40003, message="Filename is required")
99 |
100 | response = show_result(target_urls[int(selected_index)], filename)
101 | return Response(response.content, content_type=response.headers['Content-Type'])
102 | except Exception as e:
103 | return create_response(code=40002, message=f"Error: {str(e)}")
104 |
105 | if __name__ == '__main__':
106 | app.run(port=6020)
107 |
--------------------------------------------------------------------------------
/projects/project_4_2/enamel_adapter/enamel34.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_2/enamel_adapter/enamel34.png
--------------------------------------------------------------------------------
/projects/project_4_2/enamel_adapter/enamel37.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_2/enamel_adapter/enamel37.png
--------------------------------------------------------------------------------
/projects/project_4_2/enamel_adapter/enamel39.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_2/enamel_adapter/enamel39.png
--------------------------------------------------------------------------------
/projects/project_4_2/enamel_adapter/enamel74.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_2/enamel_adapter/enamel74.png
--------------------------------------------------------------------------------
/projects/project_4_2/enamel_adapter/enamel75.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_2/enamel_adapter/enamel75.png
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/app.js:
--------------------------------------------------------------------------------
1 | // app.js
2 | App({
3 | onLaunch() {
4 | // 展示本地存储能力
5 | const logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 |
9 | // 登录
10 | wx.login({
11 | success: res => {
12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
13 | }
14 | })
15 | },
16 | globalData: {
17 | userInfo: null
18 | }
19 | })
20 |
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index",
4 | "pages/logs/logs"
5 | ],
6 | "window": {
7 | "navigationBarTextStyle": "black",
8 | "navigationBarTitleText": "狗狗项链生成器",
9 | "navigationBarBackgroundColor": "#ffffff"
10 | },
11 | "style": "v2",
12 | "componentFramework": "glass-easel",
13 | "sitemapLocation": "sitemap.json",
14 | "lazyCodeLoading": "requiredComponents"
15 | }
16 |
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: space-between;
8 | box-sizing: border-box;
9 | }
10 |
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/images/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_2/wechat-example/pet_enamel/pages/images/bg.png
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/images/btn_download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_2/wechat-example/pet_enamel/pages/images/btn_download.png
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/images/btn_upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_2/wechat-example/pet_enamel/pages/images/btn_upload.png
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/images/dog_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_2/wechat-example/pet_enamel/pages/images/dog_1.png
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/images/dog_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_4_2/wechat-example/pet_enamel/pages/images/dog_text.png
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/index/index.js:
--------------------------------------------------------------------------------
1 | Page({
2 | data: {
3 | baseUrl: "http://192.168.31.101:6020",
4 | result_img: '',
5 | selected_index: '',
6 | upload_src: '../images/dog_1.png'
7 | },
8 | onLoad() {
9 | this.initServer();
10 | },
11 | // 初始化服务器
12 | initServer() {
13 | this.request('/init', 'GET', null)
14 | .then(data => {
15 | this.setData({
16 | selected_index: data.data.selected_index
17 | });
18 | console.log('Selected index:', data.data.selected_index);
19 | });
20 | },
21 | uploadImage() {
22 | wx.chooseMedia({
23 | count: 1,
24 | mediaType: ['image'],
25 | sourceType: ['album', 'camera'],
26 | maxDuration: 30,
27 | camera: 'back',
28 | success: res => {
29 | const tempFilePath = res.tempFiles[0].tempFilePath;
30 | this.setData({
31 | upload_src: tempFilePath
32 | });
33 | wx.showLoading({
34 | title: '请等待...'
35 | });
36 | wx.uploadFile({
37 | url: `${this.data.baseUrl}/upload`,
38 | filePath: tempFilePath,
39 | formData: {
40 | 'selected_index': this.data.selected_index
41 | },
42 | name: 'image',
43 | success: res => {
44 | const data = JSON.parse(res.data);
45 | if (data.code === 10000) {
46 | this.queue(data.data);
47 | } else {
48 | this.showError(data.message);
49 | }
50 | },
51 | fail: err => {
52 | console.error('Upload error:', err);
53 | this.showError('Upload failed');
54 | }
55 | });
56 | }
57 | });
58 | },
59 | queue(file_response_data) {
60 | this.request('/queue', 'POST', {
61 | selected_index: this.data.selected_index,
62 | file_response_data: file_response_data
63 | })
64 | .then(data => this.fetchImageByPromptId(data.data.prompt_id))
65 | .catch(err => console.error('Queue error:', err));
66 | },
67 | // 获取输出
68 | fetchImageByPromptId(prompt_id) {
69 | let that = this;
70 | this.request('/get_output', 'POST', {
71 | prompt_id: prompt_id,
72 | selected_index: this.data.selected_index
73 | }).then(data => {
74 | if (data.data.outputs_img && data.data.outputs_img.images.length > 0) {
75 | wx.hideLoading();
76 | let outputs_img = data.data.outputs_img.images[0]["filename"];
77 | this.setData({
78 | result_img: `${this.data.baseUrl}/view?filename=${outputs_img}&selected_index=${this.data.selected_index}`
79 | });
80 | } else if (data.code === 10002) {
81 | setTimeout(() => {
82 | that.fetchImageByPromptId(prompt_id);
83 | }, 2000);
84 | } else {
85 | this.showError(data.message);
86 | }
87 | }).catch(err => {
88 | console.error('Get output failed', err);
89 | this.showError('Get output failed');
90 | });
91 | },
92 | downloadImage() {
93 | wx.downloadFile({
94 | url: this.data.result_img,
95 | success: res => {
96 | if (res.statusCode === 200) {
97 | wx.saveImageToPhotosAlbum({
98 | filePath: res.tempFilePath,
99 | success: () => {
100 | this.showToast('保存成功', 'success');
101 | },
102 | fail: () => {
103 | this.showError('保存失败');
104 | }
105 | });
106 | } else {
107 | console.error('Download failed', res);
108 | }
109 | },
110 | fail: err => {
111 | console.error('Download error', err);
112 | }
113 | });
114 | },
115 | // 通用request请求函数
116 | request(url, method, data) {
117 | return new Promise((resolve, reject) => {
118 | wx.request({
119 | url: `${this.data.baseUrl}${url}`,
120 | method: method,
121 | data: data,
122 | header: {
123 | 'content-type': 'application/json'
124 | },
125 | success: res => {
126 | if (res.data.code.toString().startsWith('1000')) {
127 | resolve(res.data);
128 | } else {
129 | this.showError(res.data.message);
130 | reject(new Error(res.data.message));
131 | }
132 | },
133 | fail: err => {
134 | console.error('Request failed', err);
135 | this.showError('Request failed');
136 | reject(err);
137 | }
138 | });
139 | });
140 | },
141 | // 通用showToast函数
142 | showToast(title, icon = 'none', duration = 2000) {
143 | wx.showToast({
144 | title: title,
145 | icon: icon,
146 | duration: duration
147 | });
148 | },
149 | // 通用错误处理函数
150 | showError(message) {
151 | wx.hideLoading();
152 | this.showToast(`Error: ${message}`);
153 | },
154 | });
155 |
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | }
4 | }
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | .view_box{
2 | display: flex;
3 | flex-direction: row;
4 | top: 40%;
5 | width: 640rpx;
6 | left: 55rpx;
7 | justify-content: space-evenly;
8 | flex-wrap: wrap;
9 | }
10 | .dog_text{
11 | position: absolute;
12 | left: 50%;
13 | top: 44%;
14 | width: 300rpx;
15 | height: 43rpx;
16 | transform: translate(-50%);
17 | }
18 | .dog_1{
19 | position: absolute;
20 | left: 50%;
21 | top: 24%;
22 | height: 15.3%;
23 | transform: translate(-50%);
24 | }
25 | .button_upload{
26 | position: absolute;
27 | left: 50%;
28 | top: 11%;
29 | height: 117rpx;
30 | width: 425rpx;
31 | transform: translate(-50%);
32 | }
33 | .btn_download{
34 | position: absolute;
35 | left: 50%;
36 | top: 85.3%;
37 | height: 149rpx;
38 | width: 335rpx;
39 | transform: translate(-50%);
40 | }
41 | .view_box{
42 | /* width: 405rpx; */
43 | /* width: 430rpx; */
44 | height: 30.8%;
45 | left: 50%;
46 | transform: translate(-50%);
47 | top: 52.5%;
48 | /* background-color: #fff; */
49 |
50 | }
51 | .result_img{
52 | /* width: 430rpx;
53 | height: 430rpx; */
54 | height: 100%;
55 | /* background-color: #f00; */
56 | }
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | // logs.js
2 | const util = require('../../utils/util.js')
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad() {
9 | this.setData({
10 | logs: (wx.getStorageSync('logs') || []).map(log => {
11 | return {
12 | date: util.formatTime(new Date(log)),
13 | timeStamp: log
14 | }
15 | })
16 | })
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | }
4 | }
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/logs/logs.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{index + 1}}. {{log.date}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/pages/logs/logs.wxss:
--------------------------------------------------------------------------------
1 | page {
2 | height: 100vh;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 | .scrollarea {
7 | flex: 1;
8 | overflow-y: hidden;
9 | }
10 | .log-item {
11 | margin-top: 20rpx;
12 | text-align: center;
13 | }
14 | .log-item:last-child {
15 | padding-bottom: env(safe-area-inset-bottom);
16 | }
17 |
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileType": "miniprogram",
3 | "libVersion": "trial",
4 | "packOptions": {
5 | "ignore": [],
6 | "include": []
7 | },
8 | "setting": {
9 | "coverView": true,
10 | "es6": true,
11 | "postcss": true,
12 | "minified": true,
13 | "enhance": true,
14 | "showShadowRootInWxmlPanel": true,
15 | "packNpmRelationList": [],
16 | "babelSetting": {
17 | "ignore": [],
18 | "disablePlugins": [],
19 | "outputPath": ""
20 | }
21 | },
22 | "condition": {},
23 | "editorSetting": {
24 | "tabIndent": "auto",
25 | "tabSize": 2
26 | },
27 | "appid": "wx0e0781e2f366ca61"
28 | }
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/project.private.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
3 | "projectname": "pet_enamel",
4 | "setting": {
5 | "compileHotReLoad": true,
6 | "urlCheck": false
7 | }
8 | }
--------------------------------------------------------------------------------
/projects/project_4_2/wechat-example/pet_enamel/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/projects/project_4_2/workflow_api.json:
--------------------------------------------------------------------------------
1 | {
2 | "701": {
3 | "inputs": {
4 | "width": 1024,
5 | "height": 1024,
6 | "batch_size": 1
7 | },
8 | "class_type": "EmptyLatentImage",
9 | "_meta": {
10 | "title": "Empty Latent Image"
11 | }
12 | },
13 | "704": {
14 | "inputs": {
15 | "samples": [
16 | "708",
17 | 0
18 | ],
19 | "vae": [
20 | "705",
21 | 2
22 | ]
23 | },
24 | "class_type": "VAEDecode",
25 | "_meta": {
26 | "title": "VAE Decode"
27 | }
28 | },
29 | "705": {
30 | "inputs": {
31 | "ckpt_name": "sd_xl_base_1.0.safetensors"
32 | },
33 | "class_type": "CheckpointLoaderSimple",
34 | "_meta": {
35 | "title": "Load Checkpoint"
36 | }
37 | },
38 | "706": {
39 | "inputs": {
40 | "filename_prefix": "ComfyUI",
41 | "images": [
42 | "704",
43 | 0
44 | ]
45 | },
46 | "class_type": "SaveImage",
47 | "_meta": {
48 | "title": "Save Image"
49 | }
50 | },
51 | "708": {
52 | "inputs": {
53 | "seed": 831651772274428,
54 | "steps": 30,
55 | "cfg": 2.52,
56 | "sampler_name": "dpmpp_3m_sde",
57 | "scheduler": "exponential",
58 | "denoise": 1,
59 | "model": [
60 | "754",
61 | 0
62 | ],
63 | "positive": [
64 | "717",
65 | 0
66 | ],
67 | "negative": [
68 | "717",
69 | 1
70 | ],
71 | "latent_image": [
72 | "701",
73 | 0
74 | ]
75 | },
76 | "class_type": "KSampler",
77 | "_meta": {
78 | "title": "KSampler"
79 | }
80 | },
81 | "717": {
82 | "inputs": {
83 | "strength": 1,
84 | "start_percent": 0,
85 | "end_percent": 1,
86 | "positive": [
87 | "748",
88 | 0
89 | ],
90 | "negative": [
91 | "749",
92 | 0
93 | ],
94 | "control_net": [
95 | "728",
96 | 0
97 | ],
98 | "image": [
99 | "727",
100 | 0
101 | ]
102 | },
103 | "class_type": "ControlNetApplyAdvanced",
104 | "_meta": {
105 | "title": "Apply ControlNet (Advanced)"
106 | }
107 | },
108 | "718": {
109 | "inputs": {
110 | "image": "WechatIMG2588.jpg",
111 | "upload": "image"
112 | },
113 | "class_type": "LoadImage",
114 | "_meta": {
115 | "title": "Load Image"
116 | }
117 | },
118 | "720": {
119 | "inputs": {
120 | "side_length": 1024,
121 | "side": "Longest",
122 | "upscale_method": "nearest-exact",
123 | "crop": "disabled",
124 | "image": [
125 | "718",
126 | 0
127 | ]
128 | },
129 | "class_type": "DF_Image_scale_to_side",
130 | "_meta": {
131 | "title": "Image scale to side"
132 | }
133 | },
134 | "721": {
135 | "inputs": {
136 | "model": "u2net",
137 | "alpha_matting": "true",
138 | "alpha_matting_foreground_threshold": 240,
139 | "alpha_matting_background_threshold": 20,
140 | "alpha_matting_erode_size": 10,
141 | "post_process_mask": "false",
142 | "images": [
143 | "720",
144 | 0
145 | ]
146 | },
147 | "class_type": "ImageSegmentation",
148 | "_meta": {
149 | "title": "ImageSegmentation"
150 | }
151 | },
152 | "722": {
153 | "inputs": {
154 | "width": 1024,
155 | "height": 1024,
156 | "batch_size": 1,
157 | "color": 0
158 | },
159 | "class_type": "EmptyImage",
160 | "_meta": {
161 | "title": "EmptyImage"
162 | }
163 | },
164 | "723": {
165 | "inputs": {
166 | "images_a_x": 100,
167 | "images_a_y": 0,
168 | "images_b_x": 0,
169 | "images_b_y": 0,
170 | "container_width": 0,
171 | "container_height": 0,
172 | "background": "images_b",
173 | "method": "matrix",
174 | "images_a": [
175 | "721",
176 | 0
177 | ],
178 | "images_b": [
179 | "722",
180 | 0
181 | ]
182 | },
183 | "class_type": "ImageCompositeAbsolute",
184 | "_meta": {
185 | "title": "ImageCompositeAbsolute"
186 | }
187 | },
188 | "725": {
189 | "inputs": {
190 | "images": [
191 | "723",
192 | 0
193 | ]
194 | },
195 | "class_type": "PreviewImage",
196 | "_meta": {
197 | "title": "Preview Image"
198 | }
199 | },
200 | "726": {
201 | "inputs": {
202 | "pixels": [
203 | "723",
204 | 0
205 | ],
206 | "vae": [
207 | "705",
208 | 2
209 | ]
210 | },
211 | "class_type": "VAEEncode",
212 | "_meta": {
213 | "title": "VAE Encode"
214 | }
215 | },
216 | "727": {
217 | "inputs": {
218 | "samples": [
219 | "726",
220 | 0
221 | ],
222 | "vae": [
223 | "705",
224 | 2
225 | ]
226 | },
227 | "class_type": "VAEDecode",
228 | "_meta": {
229 | "title": "VAE Decode"
230 | }
231 | },
232 | "728": {
233 | "inputs": {
234 | "control_net_name": "CN-anytest_v4-marged.safetensors"
235 | },
236 | "class_type": "ControlNetLoader",
237 | "_meta": {
238 | "title": "Load ControlNet Model"
239 | }
240 | },
241 | "737": {
242 | "inputs": {
243 | "model": [
244 | "705",
245 | 0
246 | ],
247 | "clip": [
248 | "705",
249 | 1
250 | ],
251 | "lora_stack": [
252 | "738",
253 | 0
254 | ]
255 | },
256 | "class_type": "CR Apply LoRA Stack",
257 | "_meta": {
258 | "title": "💊 CR Apply LoRA Stack"
259 | }
260 | },
261 | "738": {
262 | "inputs": {
263 | "toggle": true,
264 | "mode": "simple",
265 | "num_loras": 2,
266 | "lora_1_name": "sdxl-ename_v1.safetensors",
267 | "lora_1_strength": 1,
268 | "lora_1_model_strength": 1,
269 | "lora_1_clip_strength": 1,
270 | "lora_2_name": "cartoon_style.pt",
271 | "lora_2_strength": 1,
272 | "lora_2_model_strength": 1,
273 | "lora_2_clip_strength": 1,
274 | "lora_3_name": "smiling.pt",
275 | "lora_3_strength": 1,
276 | "lora_3_model_strength": 1,
277 | "lora_3_clip_strength": 1,
278 | "lora_4_name": "None",
279 | "lora_4_strength": 1,
280 | "lora_4_model_strength": 1,
281 | "lora_4_clip_strength": 1,
282 | "lora_5_name": "None",
283 | "lora_5_strength": 1,
284 | "lora_5_model_strength": 1,
285 | "lora_5_clip_strength": 1,
286 | "lora_6_name": "None",
287 | "lora_6_strength": 1,
288 | "lora_6_model_strength": 1,
289 | "lora_6_clip_strength": 1,
290 | "lora_7_name": "None",
291 | "lora_7_strength": 1,
292 | "lora_7_model_strength": 1,
293 | "lora_7_clip_strength": 1,
294 | "lora_8_name": "None",
295 | "lora_8_strength": 1,
296 | "lora_8_model_strength": 1,
297 | "lora_8_clip_strength": 1,
298 | "lora_9_name": "None",
299 | "lora_9_strength": 1,
300 | "lora_9_model_strength": 1,
301 | "lora_9_clip_strength": 1,
302 | "lora_10_name": "None",
303 | "lora_10_strength": 1,
304 | "lora_10_model_strength": 1,
305 | "lora_10_clip_strength": 1
306 | },
307 | "class_type": "easy loraStack",
308 | "_meta": {
309 | "title": "EasyLoraStack"
310 | }
311 | },
312 | "744": {
313 | "inputs": {
314 | "images": [
315 | "727",
316 | 0
317 | ]
318 | },
319 | "class_type": "PreviewImage",
320 | "_meta": {
321 | "title": "Preview Image"
322 | }
323 | },
324 | "748": {
325 | "inputs": {
326 | "text": [
327 | "750",
328 | 0
329 | ],
330 | "speak_and_recognation": true,
331 | "clip": [
332 | "737",
333 | 1
334 | ]
335 | },
336 | "class_type": "CLIPTextEncode",
337 | "_meta": {
338 | "title": "CLIP Text Encode (Prompt)"
339 | }
340 | },
341 | "749": {
342 | "inputs": {
343 | "text": [
344 | "750",
345 | 1
346 | ],
347 | "speak_and_recognation": true,
348 | "clip": [
349 | "737",
350 | 1
351 | ]
352 | },
353 | "class_type": "CLIPTextEncode",
354 | "_meta": {
355 | "title": "CLIP Text Encode (Prompt)"
356 | }
357 | },
358 | "750": {
359 | "inputs": {
360 | "text_positive": "enamel material,cartoon,no smile,poodle,white, chain, Simple lines,animal,sitting, hanging,solo, simple_background, full_body, no_humans,",
361 | "text_negative": "disproportional, Octane render, smudge, blurred, Low resolution, worst quality,Virtualization,smile",
362 | "style": "3d-model",
363 | "log_prompt": "No",
364 | "style_name": true,
365 | "speak_and_recognation": true
366 | },
367 | "class_type": "SDXLPromptStyler",
368 | "_meta": {
369 | "title": "SDXL Prompt Styler"
370 | }
371 | },
372 | "754": {
373 | "inputs": {
374 | "weight": 1,
375 | "weight_type": "strong style transfer",
376 | "start_at": 0,
377 | "end_at": 1,
378 | "embeds_scaling": "V only",
379 | "model": [
380 | "757",
381 | 0
382 | ],
383 | "ipadapter": [
384 | "757",
385 | 1
386 | ],
387 | "pos_embed": [
388 | "756",
389 | 0
390 | ],
391 | "neg_embed": [
392 | "756",
393 | 1
394 | ]
395 | },
396 | "class_type": "IPAdapterEmbeds",
397 | "_meta": {
398 | "title": "IPAdapter Embeds"
399 | }
400 | },
401 | "755": {
402 | "inputs": {
403 | "directory": "enamel_adapter",
404 | "image_load_cap": 0,
405 | "skip_first_images": 0,
406 | "select_every_nth": 1
407 | },
408 | "class_type": "VHS_LoadImages",
409 | "_meta": {
410 | "title": "Load Images (Upload) 🎥🅥🅗🅢"
411 | }
412 | },
413 | "756": {
414 | "inputs": {
415 | "weight": 1,
416 | "ipadapter": [
417 | "757",
418 | 1
419 | ],
420 | "image": [
421 | "755",
422 | 0
423 | ]
424 | },
425 | "class_type": "IPAdapterEncoder",
426 | "_meta": {
427 | "title": "IPAdapter Encoder"
428 | }
429 | },
430 | "757": {
431 | "inputs": {
432 | "preset": "PLUS (high strength)",
433 | "model": [
434 | "737",
435 | 0
436 | ]
437 | },
438 | "class_type": "IPAdapterUnifiedLoader",
439 | "_meta": {
440 | "title": "IPAdapter Unified Loader"
441 | }
442 | },
443 | "758": {
444 | "inputs": {
445 | "images": [
446 | "755",
447 | 0
448 | ]
449 | },
450 | "class_type": "PreviewImage",
451 | "_meta": {
452 | "title": "Preview Image"
453 | }
454 | }
455 | }
--------------------------------------------------------------------------------
/projects/project_4_2/workflow_api.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request as flask_request, Response, jsonify
2 | from comfy_sdk import select_random_server, run_workflow, upload_file, history, show_result
3 | import json
4 |
5 | app = Flask(__name__)
6 |
7 | # 配置目标服务器的URL数组
8 | target_urls = [
9 | 'https://xxx.xxx.xxx',
10 | None,
11 | None,
12 | None,
13 | None
14 | ]
15 |
16 |
17 | # 统一的响应函数
18 | def create_response(data=None, code=10000, message="Success"):
19 | response = {
20 | "code": code,
21 | "message": message
22 | }
23 | if data:
24 | response["data"] = data
25 | return jsonify(response), 200 if str(code).startswith('1000') else 500
26 |
27 | # init 分配到哪台服务器 返回index
28 | @app.route('/init', methods=['GET'])
29 | def init():
30 | try:
31 | target_url, selected_index = select_random_server(target_urls)
32 | return create_response({"selected_index": selected_index})
33 | except Exception as e:
34 | return create_response(code=40001, message=f"Error: {str(e)}")
35 |
36 | # 上传视频
37 | @app.route('/upload', methods=['POST'])
38 | def upload():
39 | try:
40 | # 获取上传的文件
41 | uploaded_file = flask_request.files['image']
42 | print(uploaded_file)
43 | original_filename = uploaded_file.filename # 获取原始文件名
44 | selected_index = flask_request.form.get('selected_index')
45 | print(selected_index,'selected_index')
46 |
47 | # 转发上传请求
48 | files = {'image': (original_filename, uploaded_file.stream, uploaded_file.mimetype)}
49 | #上传文件并返回参数
50 | file_response = upload_file(target_urls[int(selected_index)],files)
51 | file_response_data = file_response.json()
52 | # 调用 queue 函数处理后续工作流
53 | return create_response(file_response_data)
54 | except Exception as e:
55 | return create_response(code=40002, message=f"Error: {str(e)}")
56 |
57 | @app.route('/queue', methods=['POST'])
58 | def queue():
59 | try:
60 | data = flask_request.get_json()
61 | selected_index = data.get('selected_index')
62 | file_response_data = data.get('file_response_data')
63 |
64 | print(data)
65 | print(selected_index)
66 | print(file_response_data)
67 |
68 | # 加载并修改工作流参数
69 | prompt_workflow = json.load(open('./workflow_api.json'))
70 | prompt_workflow['718']['inputs']['image'] = f"{file_response_data['name']}"
71 |
72 | queue_response = run_workflow(target_urls[int(selected_index)], prompt_workflow)
73 | queue_response_data = queue_response.json()
74 |
75 | return create_response(queue_response_data)
76 | except Exception as e:
77 | return create_response(code=40002, message=f"Error: {str(e)}")
78 |
79 | # 轮询history
80 | @app.route('/get_output', methods=['POST'])
81 | def get_output():
82 | try:
83 | data = flask_request.get_json()
84 | prompt_id = data.get('prompt_id')
85 | selected_index = data.get('selected_index')
86 | response_content = history(target_urls[int(selected_index)], prompt_id).json()
87 | if not response_content.get(prompt_id, {}):
88 | return create_response({"outputs_img": None}, code=10002, message="The value is an empty dictionary.")
89 | else:
90 | outputs_img = response_content[prompt_id]['outputs']['706']
91 | return create_response({"outputs_img": outputs_img})
92 | except Exception as e:
93 | return create_response(code=40002, message=f"Error: {str(e)}")
94 |
95 | # 显示
96 | @app.route('/view')
97 | def view():
98 | try:
99 | filename = flask_request.args.get('filename')
100 | selected_index = flask_request.args.get('selected_index')
101 | if not filename:
102 | return create_response(code=40003, message="Filename is required")
103 |
104 | response = show_result(target_urls[int(selected_index)], filename)
105 | return Response(response.content, content_type=response.headers['Content-Type'])
106 | except Exception as e:
107 | return create_response(code=40002, message=f"Error: {str(e)}")
108 |
109 | if __name__ == '__main__':
110 | app.run(port=6020)
111 |
--------------------------------------------------------------------------------
/projects/project_5_1/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_5_1/.DS_Store
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_5_1/web_takephoto/.DS_Store
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_5_1/web_takephoto/assets/.DS_Store
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | overflow: hidden;
4 | width: 1080px;
5 | height: 1920px;
6 | }
7 | *{
8 | margin: 0;
9 | padding: 0;
10 | }
11 |
12 | .container {
13 | max-width: 800px;
14 | margin: 0 auto;
15 | padding: 20px;
16 | }
17 |
18 | .upload-area {
19 | border: 2px dashed #007bff;
20 | border-radius: 5px;
21 | width: 100%;
22 | max-width: 300px;
23 | height: 200px;
24 | margin: 20px auto;
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | cursor: pointer;
29 | transition: all 0.3s ease;
30 | }
31 |
32 | .upload-area:hover {
33 | background-color: #e6f2ff;
34 | }
35 |
36 | button {
37 | background-color: #007bff;
38 | color: white;
39 | padding: 10px 20px;
40 | border: none;
41 | border-radius: 5px;
42 | cursor: pointer;
43 | transition: background-color 0.3s ease;
44 | }
45 |
46 | button:hover:not(:disabled) {
47 | background-color: #0056b3;
48 | }
49 |
50 | button:disabled {
51 | background-color: #cccccc;
52 | cursor: not-allowed;
53 | }
54 |
55 |
56 | @media (max-width: 600px) {
57 | .upload-area {
58 | max-width: 100%;
59 | }
60 | }
61 |
62 | .page{
63 | width: 1080px;
64 | height: 1920px;
65 | position: absolute;
66 | top: 0;
67 | left: 0;
68 | display: none;
69 | }
70 | .bg_2{
71 | width: 1080px;
72 | height: 1920px;
73 | position: absolute;
74 | z-index: 4;
75 | }
76 | .active {
77 | display: block;
78 | }
79 |
80 |
81 | .start {
82 | position: absolute;
83 | left: 386px;
84 | top: 1739px;
85 | width: 308px;
86 | height: 115px;
87 | z-index: 4;
88 | }
89 |
90 |
91 | .text_1 {
92 | position: absolute;
93 | left: 258px;
94 | top: 205px;
95 | width: 565px;
96 | height: 325px;
97 | z-index: 11;
98 | }
99 |
100 |
101 | .button_2 {
102 | position: absolute;
103 | left: 341px;
104 | top: 1490px;
105 | width: 379px;
106 | height: 310px;
107 | z-index: 8;
108 | }
109 |
110 |
111 |
112 | .button_3 {
113 | position: absolute;
114 | left: 250px;
115 | top: 1625px;
116 | width: 608px;
117 | height: 189px;
118 | z-index: 25;
119 | }
120 |
121 |
122 | #page_video{
123 |
124 | width: 1080px;
125 | height: 1920px;
126 | top: 0;
127 | left: 0;
128 | position: absolute;
129 | z-index: 2;
130 | }
131 |
132 | #video{
133 |
134 | object-fit: cover;
135 | }
136 | .per_img{
137 | position: absolute;
138 | width: 1080px;
139 | height: 1920px;
140 | top: 0px;
141 | left: 0px;
142 | }
143 | #resultImage{
144 | display: block;
145 | height: 1412px;
146 | position: absolute;
147 | top: 223px;
148 | left: 142px;
149 | border-radius: 20px;
150 | pointer-events: none;
151 | }
152 | .loading{
153 | display: none;
154 | position: absolute;
155 | z-index: 9;
156 | opacity: 0.8;
157 | }
158 | .bg_3{
159 | position: absolute;
160 | }
161 |
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/images/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_5_1/web_takephoto/assets/images/.DS_Store
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/images/bg_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_5_1/web_takephoto/assets/images/bg_1.png
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/images/bg_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_5_1/web_takephoto/assets/images/bg_2.png
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/images/bg_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_5_1/web_takephoto/assets/images/bg_3.png
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/images/button_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_5_1/web_takephoto/assets/images/button_2.png
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/images/button_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_5_1/web_takephoto/assets/images/button_3.png
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/images/loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_5_1/web_takephoto/assets/images/loading.png
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/images/start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/projects/project_5_1/web_takephoto/assets/images/start.png
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/js/app.js:
--------------------------------------------------------------------------------
1 | // 负责应用的前端逻辑和与 API 服务的交互
2 |
3 | import { ApiService } from '../services/api.js';
4 |
5 | // 摄像头控制类
6 | class CameraController {
7 | constructor(videoElement) {
8 | this.videoElement = videoElement;
9 | this.stream = null;
10 | }
11 |
12 | // 启动摄像头
13 | async startCamera() {
14 | try {
15 | const mediaStream = await navigator.mediaDevices.getUserMedia({ video: true });
16 | this.stream = mediaStream;
17 | this.videoElement.srcObject = mediaStream;
18 | } catch (error) {
19 | console.error('无法访问摄像头:', error);
20 | }
21 | }
22 |
23 | // 停止摄像头
24 | stopCamera() {
25 | if (this.stream) {
26 | const tracks = this.stream.getTracks();
27 | tracks.forEach(track => track.stop());
28 | this.videoElement.srcObject = null;
29 | }
30 | }
31 |
32 | // 获取当前视频帧
33 | getCurrentFrame() {
34 | let width = 1080;
35 | let height = 1920;
36 |
37 | console.log('getcurrentframe')
38 | const canvas = document.createElement('canvas');
39 | const context = canvas.getContext('2d');
40 | const video = this.videoElement;
41 | const scaleFactor = height / video.videoHeight;
42 | const scaledWidth = video.videoWidth * scaleFactor;
43 | const cropOffsetX = (scaledWidth - width) / 2 / scaleFactor;
44 |
45 | // 设置 canvas 大小
46 | canvas.width = width;
47 | canvas.height = height;
48 |
49 | // 绘制缩放并裁剪的视频帧
50 | context.drawImage(video, cropOffsetX, 0, video.videoWidth - 2 * cropOffsetX, video.videoHeight, 0, 0, canvas.width, canvas.height);
51 | return canvas.toDataURL('image/png');
52 | }
53 | }
54 |
55 | // 图像处理类
56 | class ImageProcessor {
57 | constructor(apiService, selectedIndex) {
58 | this.apiService = apiService;
59 | this.selectedIndex = selectedIndex;
60 | }
61 |
62 | // 上传图像
63 | async uploadImage(file) {
64 | try {
65 |
66 | const response = await this.apiService.uploadImage(file, this.selectedIndex);
67 | return response;
68 | } catch (error) {
69 | console.error('图像上传失败:', error);
70 | throw error;
71 | }
72 | }
73 |
74 | // 加入队列并请求生成图像
75 | async queueImageAndGenerate(fileResponseData) {
76 | try {
77 | const queueResponse = await this.apiService.queueImage(this.selectedIndex, fileResponseData);
78 | const promptId = queueResponse.data.prompt_id;
79 | return promptId;
80 | } catch (error) {
81 | console.error('图像排队失败:', error);
82 | throw error;
83 | }
84 | }
85 | }
86 |
87 | // 图像获取类
88 | class ImageFetcher {
89 | constructor(apiService, selectedIndex) {
90 | this.apiService = apiService;
91 | this.selectedIndex = selectedIndex;
92 | }
93 |
94 | // 获取生成的图像
95 | async getGeneratedImage(promptId) {
96 | try {
97 | const response = await this.apiService.getGeneratedImage(promptId, this.selectedIndex);
98 | return response.data;
99 | } catch (error) {
100 | console.error('获取生成的图像失败:', error);
101 | throw error;
102 | }
103 | }
104 | }
105 |
106 | // 主应用控制类
107 | class AppController {
108 | constructor(apiService, cameraController, imageProcessor, imageFetcher) {
109 | this.apiService = apiService;
110 | this.cameraController = cameraController;
111 | this.imageProcessor = imageProcessor;
112 | this.imageFetcher = imageFetcher;
113 | this.selectedIndex = null;
114 | this.resultImageUrl = '';
115 | this.resultImage = document.getElementById('resultImage');
116 | this.downloadButton = document.getElementById('downloadButton');
117 | this.loadingLayer = document.getElementById('loading');
118 | this.page1 = document.getElementById('page1');
119 | this.generateImageButton = document.getElementById('generateImageButton');
120 | this.init();
121 |
122 | }
123 |
124 | // 初始化应用
125 | async init() {
126 | try {
127 | const serverData = await this.apiService.initServer();
128 | this.selectedIndex = serverData.data.selected_index;
129 | // 在初始化完成后更新 ImageProcessor 和 ImageFetcher 的 selectedIndex
130 | this.imageProcessor.selectedIndex = this.selectedIndex;
131 | this.imageFetcher.selectedIndex = this.selectedIndex;
132 | this.page1.addEventListener('click', ()=>{
133 | if (document.documentElement.requestFullscreen) {
134 | document.documentElement.requestFullscreen();
135 | } else if (document.documentElement.mozRequestFullScreen) { // Firefox
136 | document.documentElement.mozRequestFullScreen();
137 | } else if (document.documentElement.webkitRequestFullscreen) { // Chrome, Safari, and Opera
138 | document.documentElement.webkitRequestFullscreen();
139 | } else if (document.documentElement.msRequestFullscreen) { // IE/Edge
140 | document.documentElement.msRequestFullscreen();
141 | }
142 | })
143 | console.log('Selected index:', this.selectedIndex);
144 | this.addEventListeners();
145 | } catch (error) {
146 | console.error('应用初始化失败:', error);
147 | }
148 | }
149 |
150 | // 添加事件监听器
151 | addEventListeners() {
152 | document.getElementById('startButton').addEventListener('click', this.startCamera.bind(this));
153 | this.generateImageButton.addEventListener('click', this.handleGenerateImage.bind(this));
154 | this.downloadButton.addEventListener('click', this.handleDownloadImage.bind(this));
155 | }
156 |
157 | // 启动摄像头并进入下一页
158 | async startCamera() {
159 | try {
160 |
161 | await this.cameraController.startCamera();
162 |
163 | this.showPage(2); // 显示第二页(拍照页面)
164 | } catch (error) {
165 | console.error('启动摄像头失败:', error);
166 | }
167 | }
168 |
169 | // 生成图像
170 | async handleGenerateImage() {
171 | try {
172 | const photoDataBase64 = this.cameraController.getCurrentFrame();
173 | const photoElement = document.createElement('div');
174 | photoElement.classList.add('img_box');
175 | const imgElement = document.createElement('img');
176 | imgElement.classList.add('per_img');
177 | imgElement.src = photoDataBase64;
178 | photoElement.appendChild(imgElement);
179 | document.getElementById('page2').appendChild(photoElement);
180 | this.cameraController.stopCamera()
181 | const file = this.dataURItoFile(photoDataBase64);
182 | this.loadingLayer.style.display = 'block';
183 | const uploadResponse = await this.imageProcessor.uploadImage(file);
184 |
185 | if (uploadResponse.code === 10000) {
186 | const promptId = await this.imageProcessor.queueImageAndGenerate(uploadResponse.data);
187 | await this.fetchGeneratedImage(promptId);
188 | } else {
189 | throw new Error(uploadResponse.message);
190 | }
191 | } catch (error) {
192 | console.error('生成图像失败:', error);
193 | alert('生成图像失败,请重试');
194 | }
195 | }
196 |
197 | // 从 dataURI 创建文件对象
198 | dataURItoFile(dataURI) {
199 | const byteString = atob(dataURI.split(',')[1]);
200 | const arrayBuffer = new ArrayBuffer(byteString.length);
201 | const uint8Array = new Uint8Array(arrayBuffer);
202 |
203 | for (let i = 0; i < byteString.length; i++) {
204 | uint8Array[i] = byteString.charCodeAt(i);
205 | }
206 |
207 | const file = new File([uint8Array], `captured_image_${Date.now()}.png`, { type: 'image/png' });
208 | return file;
209 | }
210 |
211 | // 获取生成的图像并显示
212 | async fetchGeneratedImage(promptId) {
213 | try {
214 | const generatedData = await this.imageFetcher.getGeneratedImage(promptId,this.selectedIndex);
215 | if (generatedData.outputs_img && generatedData.outputs_img.images.length > 0) {
216 | this.showPage(3);
217 | this.loadingLayer.style.display = 'none';
218 |
219 | const imageFileName = generatedData.outputs_img.images[0].filename;
220 | this.resultImageUrl = `${this.apiService.baseUrl}/view?filename=${imageFileName}&selected_index=${this.selectedIndex}`;
221 | this.resultImage.src = this.resultImageUrl;
222 | this.resultImage.style.display = 'block';
223 | this.downloadButton.style.display = 'block';
224 | } else {
225 | setTimeout(() => this.fetchGeneratedImage(promptId), 2000); // 如果图像还没有准备好,继续请求
226 | }
227 | } catch (error) {
228 | console.error('获取生成图像失败:', error);
229 | }
230 | }
231 |
232 | // 下载生成的图像
233 | handleDownloadImage() {
234 | const link = document.createElement('a');
235 | link.href = this.resultImageUrl;
236 | link.download = 'generated_image.png';
237 | document.body.appendChild(link);
238 | link.click();
239 | document.body.removeChild(link);
240 | }
241 |
242 | // 显示页面
243 | showPage(pageNumber) {
244 | document.querySelectorAll('.page').forEach(page => page.classList.remove('active'));
245 | document.getElementById(`page${pageNumber}`).classList.add('active');
246 | }
247 | }
248 |
249 | // 实例化并启动应用
250 | const apiService = new ApiService();
251 | const cameraController = new CameraController(document.getElementById('video'));
252 | const imageProcessor = new ImageProcessor(apiService, null); // selectedIndex 会在初始化时设置
253 | const imageFetcher = new ImageFetcher(apiService, null);
254 |
255 | const appController = new AppController(apiService, cameraController, imageProcessor, imageFetcher);
256 |
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/js/utils.js:
--------------------------------------------------------------------------------
1 | // utils.js - 工具函数类,封装了与图像处理、视频流、延迟相关的功能
2 |
3 | class Utils {
4 | // 1. 创建图像文件并触发下载
5 | static downloadImage(imageSrc, filename = 'image.png') {
6 | const link = document.createElement('a');
7 | link.href = imageSrc;
8 | link.download = filename;
9 | document.body.appendChild(link);
10 | link.click();
11 | document.body.removeChild(link);
12 | }
13 |
14 | // 2. 延迟功能 - 返回一个 Promise,延迟指定的毫秒数
15 | static delay(ms) {
16 | return new Promise(resolve => setTimeout(resolve, ms));
17 | }
18 |
19 | // 3. 从视频流获取当前帧并生成图像
20 | static captureVideoFrame(videoElement, width, height) {
21 | // 创建一个 canvas 元素
22 | const canvas = document.createElement('canvas');
23 | const context = canvas.getContext('2d');
24 |
25 | // 设置 canvas 尺寸
26 | canvas.width = width;
27 | canvas.height = height;
28 |
29 | // 从视频中绘制当前帧
30 | context.drawImage(videoElement, 0, 0, videoElement.videoWidth, videoElement.videoHeight, 0, 0, width, height);
31 |
32 | // 返回图像的 base64 编码
33 | return canvas.toDataURL('image/png');
34 | }
35 |
36 | // 4. 获取视频流并播放
37 | static async startVideoStream(videoElement) {
38 | try {
39 | const stream = await navigator.mediaDevices.getUserMedia({ video: true });
40 | videoElement.srcObject = stream;
41 | return stream; // 返回获取到的视频流
42 | } catch (error) {
43 | console.error('无法获取视频流:', error);
44 | throw new Error('无法访问摄像头');
45 | }
46 | }
47 | }
48 |
49 | export default Utils;
50 |
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/assets/services/api.js:
--------------------------------------------------------------------------------
1 | // api.js - 负责与后端 API 的通信
2 |
3 | const API_TIMEOUT = 300000; // 300 seconds
4 |
5 | export class ApiService {
6 | constructor(baseUrl) {
7 | this.baseUrl = baseUrl || "http://192.168.31.101:6020"; // 默认 API 地址,可以通过传参自定义
8 | }
9 |
10 | // 带有超时控制的 fetch 请求
11 | async fetchWithTimeout(url, options, timeout = API_TIMEOUT) {
12 | const controller = new AbortController();
13 | const signal = controller.signal;
14 | const id = setTimeout(() => {
15 | controller.abort(); // 超时后中止请求
16 | }, timeout);
17 |
18 | try {
19 | const response = await fetch(url, { ...options, signal });
20 | clearTimeout(id); // 请求完成后清除超时定时器
21 |
22 |
23 | if (!response.ok) {
24 | throw new Error(`HTTP error! status: ${response.status}`);
25 | }
26 |
27 | return await response.json();
28 | } catch (error) {
29 | // clearTimeout(id); // 捕获错误时也要清除定时器
30 | if (error.name === "AbortError") {
31 | console.error("Fetch request timed out");
32 | throw new Error("Request timed out");
33 | } else {
34 | console.error("Fetch failed:", error);
35 | throw error;
36 | }
37 | }
38 | }
39 |
40 | // 初始化服务器(获取 selectedIndex 等信息)
41 | async initServer() {
42 | const url = `${this.baseUrl}/init`;
43 | return this.fetchWithTimeout(url, { method: 'GET' });
44 | }
45 |
46 | // 上传图像
47 | async uploadImage(file, selectedIndex, rotatePitch) {
48 | const url = `${this.baseUrl}/upload`;
49 | const formData = new FormData();
50 | formData.append('image', file);
51 | formData.append('selected_index', selectedIndex);
52 |
53 | return this.fetchWithTimeout(url, {
54 | method: 'POST',
55 | body: formData,
56 | });
57 | }
58 |
59 | // 将图像加入队列
60 | async queueImage(selectedIndex, fileResponseData, rotatePitch) {
61 | const url = `${this.baseUrl}/queue`;
62 | const body = JSON.stringify({
63 | selected_index: selectedIndex,
64 | file_response_data: fileResponseData,
65 | rotate_pitch: rotatePitch
66 | });
67 |
68 | return this.fetchWithTimeout(url, {
69 | method: 'POST',
70 | headers: { 'Content-Type': 'application/json' },
71 | body,
72 | });
73 | }
74 |
75 | // 获取生成的图像
76 | async getGeneratedImage(promptId, selectedIndex) {
77 | const url = `${this.baseUrl}/get_output`;
78 |
79 | const body = JSON.stringify({
80 | prompt_id: promptId,
81 | selected_index: selectedIndex
82 | });
83 |
84 | return this.fetchWithTimeout(url, {
85 | method: 'POST',
86 | headers: { 'Content-Type': 'application/json' },
87 | body,
88 | });
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/projects/project_5_1/web_takephoto/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 圣诞节照相馆
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |

18 |
19 |
20 |
21 |
22 |
23 |
24 |

25 |

26 |

27 |
28 |
29 |
30 |

31 |
32 |
![Generated Image]()
33 |
34 |

35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/projects/project_5_1/workflow_api.json:
--------------------------------------------------------------------------------
1 | {
2 | "10": {
3 | "inputs": {
4 | "crop": "center",
5 | "clip_vision": [
6 | "11",
7 | 0
8 | ],
9 | "image": [
10 | "32",
11 | 0
12 | ]
13 | },
14 | "class_type": "CLIPVisionEncode",
15 | "_meta": {
16 | "title": "CLIP Vision Encode"
17 | }
18 | },
19 | "11": {
20 | "inputs": {
21 | "clip_name": "sigclip_vision_patch14_384.safetensors"
22 | },
23 | "class_type": "CLIPVisionLoader",
24 | "_meta": {
25 | "title": "Load CLIP Vision"
26 | }
27 | },
28 | "12": {
29 | "inputs": {
30 | "image": "IMG_5169.jpg",
31 | "upload": "image"
32 | },
33 | "class_type": "LoadImage",
34 | "_meta": {
35 | "title": "Load Image"
36 | }
37 | },
38 | "13": {
39 | "inputs": {
40 | "style_model_name": "flux1-redux-dev.safetensors"
41 | },
42 | "class_type": "StyleModelLoader",
43 | "_meta": {
44 | "title": "Load Style Model"
45 | }
46 | },
47 | "14": {
48 | "inputs": {
49 | "strength": 1,
50 | "strength_type": "multiply",
51 | "conditioning": [
52 | "35",
53 | 0
54 | ],
55 | "style_model": [
56 | "13",
57 | 0
58 | ],
59 | "clip_vision_output": [
60 | "10",
61 | 0
62 | ]
63 | },
64 | "class_type": "StyleModelApply",
65 | "_meta": {
66 | "title": "Apply Style Model"
67 | }
68 | },
69 | "15": {
70 | "inputs": {
71 | "model": [
72 | "73",
73 | 0
74 | ],
75 | "conditioning": [
76 | "79",
77 | 0
78 | ]
79 | },
80 | "class_type": "BasicGuider",
81 | "_meta": {
82 | "title": "BasicGuider"
83 | }
84 | },
85 | "16": {
86 | "inputs": {
87 | "unet_name": "FLUX.1-dev/flux1-dev-fp8.safetensors",
88 | "weight_dtype": "fp8_e4m3fn"
89 | },
90 | "class_type": "UNETLoader",
91 | "_meta": {
92 | "title": "Load Diffusion Model"
93 | }
94 | },
95 | "18": {
96 | "inputs": {
97 | "text": "Christmas poster scene, background, incorporating Christmas elements such as Christmas trees, snowflakes, and gift boxes. The overall design combines Pixar's creativity with the warmth of Christmas, creating a festive atmosphere. Character, there is a Christmas tree behind the character",
98 | "speak_and_recognation": true,
99 | "clip": [
100 | "19",
101 | 0
102 | ]
103 | },
104 | "class_type": "CLIPTextEncode",
105 | "_meta": {
106 | "title": "CLIP Text Encode (Prompt)"
107 | }
108 | },
109 | "19": {
110 | "inputs": {
111 | "clip_name1": "Flux/t5xxl_fp16.safetensors",
112 | "clip_name2": "clip_l.safetensors",
113 | "type": "flux"
114 | },
115 | "class_type": "DualCLIPLoader",
116 | "_meta": {
117 | "title": "DualCLIPLoader"
118 | }
119 | },
120 | "20": {
121 | "inputs": {
122 | "noise": [
123 | "22",
124 | 0
125 | ],
126 | "guider": [
127 | "15",
128 | 0
129 | ],
130 | "sampler": [
131 | "27",
132 | 0
133 | ],
134 | "sigmas": [
135 | "26",
136 | 0
137 | ],
138 | "latent_image": [
139 | "28",
140 | 0
141 | ]
142 | },
143 | "class_type": "SamplerCustomAdvanced",
144 | "_meta": {
145 | "title": "SamplerCustomAdvanced"
146 | }
147 | },
148 | "21": {
149 | "inputs": {
150 | "guidance": 3.5,
151 | "conditioning": [
152 | "18",
153 | 0
154 | ]
155 | },
156 | "class_type": "FluxGuidance",
157 | "_meta": {
158 | "title": "FluxGuidance"
159 | }
160 | },
161 | "22": {
162 | "inputs": {
163 | "noise_seed": 483495504021542
164 | },
165 | "class_type": "RandomNoise",
166 | "_meta": {
167 | "title": "RandomNoise"
168 | }
169 | },
170 | "23": {
171 | "inputs": {
172 | "samples": [
173 | "20",
174 | 0
175 | ],
176 | "vae": [
177 | "24",
178 | 0
179 | ]
180 | },
181 | "class_type": "VAEDecode",
182 | "_meta": {
183 | "title": "VAE Decode"
184 | }
185 | },
186 | "24": {
187 | "inputs": {
188 | "vae_name": "Flux/ae.safetensors"
189 | },
190 | "class_type": "VAELoader",
191 | "_meta": {
192 | "title": "Load VAE"
193 | }
194 | },
195 | "26": {
196 | "inputs": {
197 | "scheduler": "normal",
198 | "steps": 20,
199 | "denoise": 1,
200 | "model": [
201 | "16",
202 | 0
203 | ]
204 | },
205 | "class_type": "BasicScheduler",
206 | "_meta": {
207 | "title": "BasicScheduler"
208 | }
209 | },
210 | "27": {
211 | "inputs": {
212 | "sampler_name": "euler"
213 | },
214 | "class_type": "KSamplerSelect",
215 | "_meta": {
216 | "title": "KSamplerSelect"
217 | }
218 | },
219 | "28": {
220 | "inputs": {
221 | "width": [
222 | "76",
223 | 0
224 | ],
225 | "height": [
226 | "76",
227 | 1
228 | ],
229 | "batch_size": 1
230 | },
231 | "class_type": "EmptyLatentImage",
232 | "_meta": {
233 | "title": "Empty Latent Image"
234 | }
235 | },
236 | "32": {
237 | "inputs": {
238 | "fill_background": true,
239 | "background_color": "#FFFFFF",
240 | "RGBA_image": [
241 | "38",
242 | 0
243 | ]
244 | },
245 | "class_type": "LayerUtility: ImageRemoveAlpha",
246 | "_meta": {
247 | "title": "LayerUtility: ImageRemoveAlpha"
248 | }
249 | },
250 | "33": {
251 | "inputs": {
252 | "crop": "center",
253 | "clip_vision": [
254 | "11",
255 | 0
256 | ],
257 | "image": [
258 | "34",
259 | 0
260 | ]
261 | },
262 | "class_type": "CLIPVisionEncode",
263 | "_meta": {
264 | "title": "CLIP Vision Encode"
265 | }
266 | },
267 | "34": {
268 | "inputs": {
269 | "image": "高质量的圣诞海报场景,背景,融入圣诞元素如圣诞树、雪花和礼物盒。整体设计结合了皮克斯的创意与圣诞节的温馨,充满节日气氛。没有人物,真实场景.png",
270 | "upload": "image"
271 | },
272 | "class_type": "LoadImage",
273 | "_meta": {
274 | "title": "Load Image"
275 | }
276 | },
277 | "35": {
278 | "inputs": {
279 | "strength": 1,
280 | "strength_type": "multiply",
281 | "conditioning": [
282 | "21",
283 | 0
284 | ],
285 | "style_model": [
286 | "13",
287 | 0
288 | ],
289 | "clip_vision_output": [
290 | "33",
291 | 0
292 | ]
293 | },
294 | "class_type": "StyleModelApply",
295 | "_meta": {
296 | "title": "Apply Style Model"
297 | }
298 | },
299 | "37": {
300 | "inputs": {
301 | "filename_prefix": "ComfyUI",
302 | "images": [
303 | "23",
304 | 0
305 | ]
306 | },
307 | "class_type": "SaveImage",
308 | "_meta": {
309 | "title": "Save Image"
310 | }
311 | },
312 | "38": {
313 | "inputs": {
314 | "rem_mode": "RMBG-1.4",
315 | "image_output": "Preview",
316 | "save_prefix": "ComfyUI",
317 | "torchscript_jit": false,
318 | "images": [
319 | "12",
320 | 0
321 | ]
322 | },
323 | "class_type": "easy imageRemBg",
324 | "_meta": {
325 | "title": "Image Remove Bg"
326 | }
327 | },
328 | "71": {
329 | "inputs": {
330 | "pulid_file": "pulid_flux_v0.9.1.safetensors"
331 | },
332 | "class_type": "PulidFluxModelLoader",
333 | "_meta": {
334 | "title": "Load PuLID Flux Model"
335 | }
336 | },
337 | "73": {
338 | "inputs": {
339 | "weight": 1,
340 | "start_at": 0,
341 | "end_at": 1,
342 | "source_face_selection": "center_face",
343 | "model": [
344 | "16",
345 | 0
346 | ],
347 | "pulid_flux": [
348 | "71",
349 | 0
350 | ],
351 | "eva_clip": [
352 | "77",
353 | 0
354 | ],
355 | "face_analysis": [
356 | "74",
357 | 0
358 | ],
359 | "image": [
360 | "12",
361 | 0
362 | ]
363 | },
364 | "class_type": "ApplyPulidFlux",
365 | "_meta": {
366 | "title": "Apply PuLID Flux"
367 | }
368 | },
369 | "74": {
370 | "inputs": {
371 | "provider": "CPU"
372 | },
373 | "class_type": "PulidInsightFaceLoader",
374 | "_meta": {
375 | "title": "Load InsightFace (PuLID)"
376 | }
377 | },
378 | "76": {
379 | "inputs": {
380 | "image": [
381 | "34",
382 | 0
383 | ]
384 | },
385 | "class_type": "GetImageSize+",
386 | "_meta": {
387 | "title": "🔧 Get Image Size"
388 | }
389 | },
390 | "77": {
391 | "inputs": {},
392 | "class_type": "PulidEvaClipLoader",
393 | "_meta": {
394 | "title": "Load Eva Clip (PuLID)"
395 | }
396 | },
397 | "78": {
398 | "inputs": {
399 | "crop": "center",
400 | "clip_vision": [
401 | "11",
402 | 0
403 | ],
404 | "image": [
405 | "80",
406 | 0
407 | ]
408 | },
409 | "class_type": "CLIPVisionEncode",
410 | "_meta": {
411 | "title": "CLIP Vision Encode"
412 | }
413 | },
414 | "79": {
415 | "inputs": {
416 | "strength": 1,
417 | "strength_type": "multiply",
418 | "conditioning": [
419 | "14",
420 | 0
421 | ],
422 | "style_model": [
423 | "13",
424 | 0
425 | ],
426 | "clip_vision_output": [
427 | "78",
428 | 0
429 | ]
430 | },
431 | "class_type": "StyleModelApply",
432 | "_meta": {
433 | "title": "Apply Style Model"
434 | }
435 | },
436 | "80": {
437 | "inputs": {
438 | "image": "高质量的圣诞海报场景,背景,融入圣诞元素如圣诞树、雪花和礼物盒。整体设计结合了皮克斯的创意与圣诞节的温馨,充满节日气氛。有个穿红色衣服的男人站在圣诞树前面,真实.png",
439 | "upload": "image"
440 | },
441 | "class_type": "LoadImage",
442 | "_meta": {
443 | "title": "Load Image"
444 | }
445 | }
446 | }
--------------------------------------------------------------------------------
/projects/project_5_1/workflow_api.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request as flask_request, Response, jsonify
2 | from comfy_sdk import select_random_server, run_workflow, upload_file, history, show_result
3 | import json
4 | from flask_cors import CORS
5 |
6 | app = Flask(__name__)
7 | CORS(app)
8 | # 配置目标服务器的URL数组
9 | target_urls = [
10 | 'http://127.0.0.1:6006',
11 | None,
12 | None,
13 | None,
14 | None
15 | ]
16 |
17 |
18 | # 统一的响应函数
19 | def create_response(data=None, code=10000, message="Success"):
20 | response = {
21 | "code": code,
22 | "message": message
23 | }
24 | if data:
25 | response["data"] = data
26 | return jsonify(response), 200 if str(code).startswith('1000') else 500
27 |
28 | # init 分配到哪台服务器 返回index
29 | @app.route('/init', methods=['GET'])
30 | def init():
31 | try:
32 | target_url, selected_index = select_random_server(target_urls)
33 | return create_response({"selected_index": selected_index})
34 | except Exception as e:
35 | return create_response(code=40001, message=f"Error: {str(e)}")
36 |
37 | # 上传视频
38 | @app.route('/upload', methods=['POST'])
39 | def upload():
40 | try:
41 | # 获取上传的文件
42 | uploaded_file = flask_request.files['image']
43 | print(uploaded_file)
44 | original_filename = uploaded_file.filename # 获取原始文件名
45 | selected_index = flask_request.form.get('selected_index')
46 | print(selected_index,'selected_index')
47 |
48 | # 转发上传请求
49 | files = {'image': (original_filename, uploaded_file.stream, uploaded_file.mimetype)}
50 | #上传文件并返回参数
51 | file_response = upload_file(target_urls[int(selected_index)],files)
52 | file_response_data = file_response.json()
53 | # 调用 queue 函数处理后续工作流
54 | return create_response(file_response_data)
55 | except Exception as e:
56 | return create_response(code=40002, message=f"Error: {str(e)}")
57 |
58 | @app.route('/queue', methods=['POST'])
59 | def queue():
60 | try:
61 | data = flask_request.get_json()
62 | selected_index = data.get('selected_index')
63 | file_response_data = data.get('file_response_data')
64 |
65 | print(data)
66 | print(selected_index)
67 | print(file_response_data)
68 |
69 | # 加载并修改工作流参数
70 | prompt_workflow = json.load(open('./workflow_api.json'))
71 | prompt_workflow['12']['inputs']['image'] = f"{file_response_data['name']}"
72 |
73 | queue_response = run_workflow(target_urls[int(selected_index)], prompt_workflow)
74 | queue_response_data = queue_response.json()
75 |
76 | return create_response(queue_response_data)
77 | except Exception as e:
78 | return create_response(code=40002, message=f"Error: {str(e)}")
79 |
80 | # 轮询history
81 | @app.route('/get_output', methods=['POST'])
82 | def get_output():
83 | try:
84 | data = flask_request.get_json()
85 | prompt_id = data.get('prompt_id')
86 | selected_index = data.get('selected_index')
87 | response_content = history(target_urls[int(selected_index)], prompt_id).json()
88 | if not response_content.get(prompt_id, {}):
89 | return create_response({"outputs_img": None}, code=10002, message="The value is an empty dictionary.")
90 | else:
91 | outputs_img = response_content[prompt_id]['outputs']['37']
92 | return create_response({"outputs_img": outputs_img})
93 | except Exception as e:
94 | return create_response(code=40002, message=f"Error: {str(e)}")
95 |
96 | # 显示
97 | @app.route('/view')
98 | def view():
99 | try:
100 | filename = flask_request.args.get('filename')
101 | selected_index = flask_request.args.get('selected_index')
102 | if not filename:
103 | return create_response(code=40003, message="Filename is required")
104 |
105 | response = show_result(target_urls[int(selected_index)], filename)
106 | return Response(response.content, content_type=response.headers['Content-Type'])
107 | except Exception as e:
108 | return create_response(code=40002, message=f"Error: {str(e)}")
109 |
110 | if __name__ == '__main__':
111 | app.run(port=6020)
112 |
--------------------------------------------------------------------------------
/projects/requirement.txt:
--------------------------------------------------------------------------------
1 | Flask==2.3.2
2 | flask-cors==3.0.10
3 | requests==2.31.0
4 | Pillow==10.0.0
5 |
--------------------------------------------------------------------------------
/projects/test.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request as flask_request, Response, jsonify
2 | from comfy_sdk import select_random_server, run_workflow, upload_file, history, show_result
3 | import json
4 |
5 | app = Flask(__name__)
6 |
7 | @app.route('/test', methods=['GET'])
8 | def upload():
9 | key = flask_request.args.get('key')
10 | return key
11 |
12 |
13 | @app.route('/queue', methods=['POST'])
14 | def queue():
15 | # 加载工作流
16 | prompt_workflow = json.load(open('./workflow_api.json'))
17 |
18 | queue_response = run_workflow("server base", prompt_workflow)
19 | queue_response_data = queue_response.json()
20 |
21 | return queue_response_data
22 |
23 |
24 | if __name__ == '__main__':
25 | app.run(port=6006)
26 |
--------------------------------------------------------------------------------
/sdk/comfy_sdk.py:
--------------------------------------------------------------------------------
1 | import random
2 | import json
3 | import requests
4 | # url检查服务器是否在线
5 | def is_server_online(url, timeout=5):
6 | try:
7 | response = requests.get(url, timeout=timeout)
8 | return response.status_code == 200
9 | except requests.exceptions.RequestException:
10 | return False
11 |
12 | def select_random_server(target_urls):
13 | # 只返回不是 None 的随机 URL
14 | online_servers = [(index, url) for index, url in enumerate(target_urls) if url is not None]
15 | if not online_servers:
16 | raise Exception("No servers are online.")
17 | selected_index, selected_url = random.choice(online_servers)
18 | return selected_url, selected_index
19 |
20 | # 运行工作流,返回prompt_id
21 | def run_workflow(selected_url,prompt_workflow):
22 | p = {"prompt": prompt_workflow}
23 | data = json.dumps(p).encode('utf-8')
24 | headers = {
25 | 'Content-Type': 'application/json', # 确保请求头正确
26 | 'Accept': 'application/json' # 如果服务器期望返回 JSON
27 | }
28 | response = proxy_request(f"{selected_url}",'/prompt','POST', data=data,headers=headers)
29 | return response
30 |
31 |
32 | # 上传文件
33 | def upload_file(target_url,files,data=None):
34 | try:
35 | print(target_url)
36 | response = proxy_request(target_url,'/upload/image','POST', files=files,data=data)
37 | return response
38 | except Exception as e:
39 | print(f"Error fetching upload_img: {e}")
40 | return None
41 |
42 | # 查看运行的结果
43 | def history(target_url,prompt_id):
44 | try:
45 | data={prompt_id:prompt_id}
46 | response = proxy_request(target_url,'/history','GET', data=data)
47 | print(response)
48 | return response
49 | except Exception as e:
50 | print(f"Error fetching history: {e}")
51 | return None
52 | # 查看图片
53 | def show_result(target_url, filename):
54 |
55 | try:
56 | params = {
57 | 'filename': filename
58 | }
59 | response = proxy_request(target_url, '/api/view', 'GET', params=params)
60 | if isinstance(response, dict): # 处理代理请求失败的情况
61 | return jsonify({'error': response['content']}), response['status_code']
62 | # 返回视频内容给客户端
63 | return response
64 |
65 | except Exception as e:
66 | print(f"Error fetching image: {e}")
67 | return None
68 |
69 | # 通用代理转发
70 | def proxy_request(target_url, path, method, params=None, data=None, headers=None, cookies=None,files=None):
71 | url = f"{target_url}{path}"
72 | try:
73 | response = requests.request(
74 | method=method,
75 | url=url,
76 | headers=headers,
77 | params=params,
78 | data=data,
79 | files=files,
80 | cookies=cookies,
81 | allow_redirects=False
82 | )
83 |
84 | return response
85 | except requests.exceptions.RequestException as e:
86 | print(f"Request failed: {e}")
87 | return {
88 | 'status_code': 502,
89 | 'content': f"Request failed: {e}"
90 | }
91 |
92 |
--------------------------------------------------------------------------------
/workflows/2-1默认工作流.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/workflows/2-1默认工作流.png
--------------------------------------------------------------------------------
/workflows/2-2黏土小猫.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/workflows/2-2黏土小猫.png
--------------------------------------------------------------------------------
/workflows/2-3图片转黏土.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/workflows/2-3图片转黏土.png
--------------------------------------------------------------------------------
/workflows/3-5视频liveportrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/workflows/3-5视频liveportrait.png
--------------------------------------------------------------------------------
/workflows/4-1贴纸小程序.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/workflows/4-1贴纸小程序.png
--------------------------------------------------------------------------------
/workflows/4-2宠物珐琅生成器.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/workflows/4-2宠物珐琅生成器.png
--------------------------------------------------------------------------------
/workflows/5-1圣诞节照相馆.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuyou-dev/ComfyUI_workflow2app/3e6087453fc1889c9451714108ecc0ae130c0d28/workflows/5-1圣诞节照相馆.png
--------------------------------------------------------------------------------
/workflows/README.md:
--------------------------------------------------------------------------------
1 | ## Workflow工作流清单
2 |
--------------------------------------------------------------------------------