File size: 12,786 Bytes
f900ee0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
import marimo

__generated_with = "0.10.13"
app = marimo.App(width="medium")


@app.cell
def _():
    import marimo as mo
    import subprocess
    import os
    import logging
    from datetime import datetime
    return datetime, logging, mo, os, subprocess


@app.cell
def _(mo):
    """UI 组件:工具选择"""
    ai_app = "ksa虚拟网络"
    aitool_name = mo.ui.dropdown(
        [f"{ai_app}"], 
        value=f"{ai_app}",
        label="选择AI工具"
    )
    return ai_app, aitool_name

@app.cell
def _(aitool_name):
    """虚拟环境所在文件夹、git REPO等常规变量设置"""
    uv_venv_dir = f"ai_{aitool_name.value}"
    git_repo_url = "https://openi.pcl.ac.cn/niubi/ComfyUI.git"
    repo_name = "ComfyUI"
    app_log = f"{aitool_name.value}.log"
    python_ver = "3.12.8"

    custom_node_1 = "comfyui-manager"
    custom_node_1_repo = "https://openi.pcl.ac.cn/niubi/comfyui-manager.git"
    return (
        app_log,
        custom_node_1,
        custom_node_1_repo,
        git_repo_url,
        python_ver,
        repo_name,
        uv_venv_dir,
    )


@app.cell
def _(custom_node_1, custom_node_1_repo, python_ver):
    """配置类"""
    class Config:
        SYSTEM_DEPS = [
            {
                "name": "系统依赖",
                "cmd": """
                apt-get update
                apt install build-essential libgl1 ffmpeg python3-pip aria2 git git-lfs htop -y
                pip install uv -i https://mirrors.cloud.tencent.com/pypi/simple
                """
            }
        ]

        @staticmethod
        def get_venv_commands(venv_dir):
            return [
                {
                    "name": "虚拟环境设置",
                    "cmd": f"""
                    echo "无需设置,直接跳过"
                    """
                }
            ]

        @staticmethod
        def setup_repo_commands(venv_dir, git_url, repo_name):
            return [
                {
                    "name": f"处理{repo_name}项目仓库",
                    "cmd": f"""
                    echo "无需设置,直接跳过"
                    """
                }
            ]

        @staticmethod
        def get_start_command(venv_dir, repo_name, app_log):
            """获取启动命令"""
            return {
                "name": "启动服务",
                "cmd": f"""
                echo "无需设置,直接跳过"
                """
            }

        @staticmethod
        def get_stop_commands():
            """获取停止命令"""
            return [
                {
                    "name": "停止服务",
                    "cmd": "echo '无需设置,直接跳过'"
                }
            ]

        @staticmethod
        def download_commands(venv_dir, repo_name):
            return [
                {
                    "name": "模型下载",
                    "cmd": f"""
                    echo "无需设置,直接跳过"
                    """
                }
            ]

        @staticmethod
        def get_end_show_info(venv_dir, repo_name):
            """获取安装完成后的展示信息"""
            return f"""
            ## 安装完成后的操作说明

            Q&A及资讯:
            - 最新资讯及相关命令请查看 https://fuliai-ai2u.hf.space/
            - 加群交流:https://qr61.cn/oohivs/qRp62U6

            ## 启动程序

            ```bash
            /workspace/apps/ksa/ksa_x64
            export HF_ENDPOINT=https://hf-mirror.com
            ```
            之后使用ksa连接,比如在终端中运行了ollama,在浏览器中使用 10.0.0.1:11434 来访问ollama。
            不会用的请查看视频教程:https://www.bilibili.com/video/BV13UBRYVEmX/

            ## 退出程序
            ```bash
            (使用 Ctrl + C)
            ```
            """
    return (Config,)


@app.cell
def _(datetime, logging, os):
    """日志处理类"""
    class Logger:
        def __init__(self, name):
            self.logger = self._setup_logger(name)

        def _setup_logger(self, name):
            os.makedirs("logs", exist_ok=True)
            log_file = f'logs/{name}_{datetime.now().strftime("%Y%m%d")}.log'

            logger = logging.getLogger(name)
            logging.basicConfig(
                level=logging.INFO,
                format="%(asctime)s - %(levelname)s - %(message)s",
                handlers=[
                    logging.FileHandler(log_file, encoding="utf-8"),
                    logging.StreamHandler(),
                ],
            )
            return logger

        def info(self, msg): self.logger.info(msg)
        def error(self, msg): self.logger.error(msg)
        def warning(self, msg): self.logger.warning(msg)
    return (Logger,)


@app.cell
def _(mo):
    """UI 组件:功能开关"""
    switches = {
        "system": mo.ui.switch(label="系统依赖安装", value=True),
        "venv": mo.ui.switch(label="虚拟环境设置", value=True),
        "repo": mo.ui.switch(label="程序文件设置", value=True),
        "app": mo.ui.switch(label="启动应用程序", value=True),
        "download": mo.ui.switch(label="文件或模型下载", value=True),
    }
    return (switches,)


@app.cell
def _(Logger, aitool_name):
    """初始化日志记录器"""
    logger = Logger(aitool_name.value)
    return (logger,)


@app.cell
def _(subprocess):
    """命令执行工具类"""
    class CommandRunner:
        @staticmethod
        def run(cmd, logger, check=True, step_name=""):
            try:
                prefix = f"[{step_name}] " if step_name else ""
                logger.info(f"{prefix}正在执行命令...")
                result = subprocess.run(
                    cmd,
                    shell=True,
                    check=check,
                    text=True,
                    capture_output=True
                )
                if result.stdout:
                    logger.info(f"{prefix}输出: {result.stdout.strip()}")
                logger.info(f"{prefix}命令执行成功")
                return True, None
            except subprocess.CalledProcessError as e:
                error_msg = f"{prefix}命令执行失败: {e.stderr if e.stderr else '未知错误'}"
                logger.error(error_msg)
                return False, error_msg
            except KeyboardInterrupt:
                logger.info(f"{prefix}用户中断了命令执行")
                return False, "用户中断了命令执行"
            except Exception as e:
                error_msg = f"{prefix}执行出错: {str(e)}"
                logger.error(error_msg)
                return False, error_msg
    return (CommandRunner,)


@app.cell
def _(aitool_name):
    aitool_name
    return


@app.cell
def _(switches):
    switches
    return


@app.cell
def _(CommandRunner, Config, logger, mo, switches):
    """系统依赖安装"""
    def install_system():
        if not switches["system"].value:
            return mo.md("⚠️ 请先启用系统依赖安装").callout(kind="warn")

        for cmd_info in Config.SYSTEM_DEPS:
            success, error = CommandRunner.run(cmd_info["cmd"], logger, step_name=cmd_info["name"])
            if not success:
                return mo.md(f"❌ {error}").callout(kind="danger")

        return mo.md("✅ 系统依赖安装完成").callout(kind="success")

    install_system()
    return (install_system,)


@app.cell
def _(CommandRunner, Config, logger, mo, os, switches, uv_venv_dir):
    """虚拟环境设置"""
    def setup_venv():
        if not switches["venv"].value:
            return mo.md("⚠️ 请先启用虚拟环境设置").callout(kind="warn")

        os.makedirs(uv_venv_dir, exist_ok=True)

        for cmd_info in Config.get_venv_commands(uv_venv_dir):
            success, error = CommandRunner.run(cmd_info["cmd"], logger, step_name=cmd_info["name"])
            if not success:
                return mo.md(f"❌ {error}").callout(kind="danger")

        return mo.md("✅ 虚拟环境设置完成").callout(kind="success")

    setup_venv()
    return (setup_venv,)


@app.cell
def _(
    CommandRunner,
    Config,
    git_repo_url,
    logger,
    mo,
    repo_name,
    switches,
    uv_venv_dir,
):
    """程序文件安装设置"""
    def setup_repo():
        if not switches["repo"].value:
            return mo.md("⚠️ 请先启用文件安装设置").callout(kind="warn")

        for cmd_info in Config.setup_repo_commands(uv_venv_dir, git_repo_url, repo_name):
            success, error = CommandRunner.run(cmd_info["cmd"], logger, step_name=cmd_info["name"])
            if not success:
                return mo.md(f"❌ {error}").callout(kind="danger")

        return mo.md("✅ 程序文件设置完成").callout(kind="success")

    setup_repo()
    return (setup_repo,)


@app.cell
def _(
    CommandRunner,
    Config,
    end_show,
    logger,
    mo,
    post_end_show,
    repo_name,
    switches,
    uv_venv_dir,
):
    """下载:文件或模型"""
    def download_files():
        if not switches["download"].value:
            return mo.md("⚠️ 请先启用下载开关").callout(kind="warn")

        try:
            for cmd_info in Config.download_commands(uv_venv_dir, repo_name):
                success, error = CommandRunner.run(cmd_info["cmd"], logger, step_name=cmd_info["name"])
                if not success:
                    return mo.md(f"❌ {error}").callout(kind="danger")

            # 显示下载完成信息
            download_success = mo.md("✅ 相关下载已经完成").callout(kind="success")
            # 显示安装完成信息
            install_info = end_show()
            post_info = post_end_show()

            return mo.hstack([download_success, install_info, post_info])
        except KeyboardInterrupt:
            logger.info("用户中断了下载过程")
            return mo.md("⚠️ 下载已被用户中断").callout(kind="warn")
        except Exception as e:
            logger.error(f"下载过程出错: {str(e)}")
            return mo.md(f"❌ 下载出错: {str(e)}").callout(kind="danger")

    download_files()
    return (download_files,)


@app.cell
def _(Config, mo, repo_name, uv_venv_dir):
    """主体完成后展示信息内容"""
    def end_show():
        return mo.md(Config.get_end_show_info(uv_venv_dir, repo_name)).callout(kind="success")
    return (end_show,)


@app.cell
def _(Config, mo, ai_app, uv_venv_dir):
    """将主体完成后展示信息内容写入说明文件"""
    def post_end_show():
        # 获取说明信息内容
        info_content = Config.get_end_show_info(uv_venv_dir, ai_app)

        # 写入说明文件
        try:
            with open(f"{ai_app}说明文件.md", "w", encoding="utf-8") as f:
                f.write(info_content)
            return mo.md(f"✅ 安装成功,使用说明已写入 {ai_app}说明文件.md").callout(kind="success")
        except Exception as e:
            return mo.md(f"❌ 写入说明文件失败: {str(e)}").callout(kind="danger")
    return (post_end_show,)


@app.cell
def _(mo):
    """创建启动和停止按钮"""
    btn = mo.ui.run_button(label="启动服务", kind="info")
    btn1 = mo.ui.run_button(label="停止服务", kind="info")
    return btn, btn1


@app.cell
def _(btn):
    btn
    return


@app.cell
def _(btn1):
    btn1
    return


@app.cell
def _(
    Config,
    app_log,
    btn,
    btn1,
    logger,
    repo_name,
    subprocess,
    switches,
    uv_venv_dir,
):
    """启动/停止应用程序"""
    def handle_app():
        if not switches["app"].value:
            print("⚠️ 请先启用应用程序开关")
            return

        if btn.value:
            # 获取启动配置
            start_config = Config.get_start_command(uv_venv_dir, repo_name, app_log)

            # 检查是否已有进程在运行
            check_result = subprocess.run(start_config["check_cmd"], shell=True)
            if check_result.returncode == 0:
                print("⚠️ 程序已在运行中")
                return

            # 启动服务
            logger.info("[启动服务] 正在启动应用程序...")
            subprocess.run(start_config["cmd"], shell=True)
            print("✅ 程序启动命令已执行")

        elif btn1.value:
            # 获取停止配置
            stop_config = Config.get_stop_commands()[0]

            # 停止服务
            logger.info("[停止服务] 正在停止应用程序...")
            subprocess.run(stop_config["cmd"], shell=True)
            print("✅ 程序停止命令已执行")

    handle_app()
    return (handle_app,)


if __name__ == "__main__":
    app.run()