[bsgit user="1sdv"]lunwentocode[/bsgit]
将论文Markdown自动转换为可运行的Python代码,可以在这里快速体验:https://modelscope.cn/studios/lcclxy/lunwentocode

✨ 功能特性

  • ? 文稿解析: 直接读取Markdown论文文件
  • ? 智能分析: 自动识别论文类型、研究方法和代码需求
  • ? 代码生成: 根据论文内容生成完整的Python代码
  • 自动验证: 语法检查、导入验证和自动修复
  • ? 数据支持: 支持额外Excel/CSV数据文件

?️ 系统架构

┌─────────────────────────────────────────────────────────────┐
│                    LunwenToCode 系统架构                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐               │
│  │ Analyzer │ →  │ Coder    │ →  │Validator │               │
│  │ Agent    │    │ Agent    │    │ Agent    │               │
│  └──────────┘    └──────────┘    └──────────┘               │
│       ↓               ↓               ↓                     │
│  提取需求          生成代码        验证修复                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

? 安装

1. 克隆项目

cd biyetocode

2. 安装依赖

pip install -r requirements.txt

3. 配置环境变量

cp .env.example .env
# 编辑 .env 文件,填入API密钥

? 使用方法

基本使用

pdf转为Maekdown文档使用Mineru一键转化下载即可

# 使用Markdown或PDF文件
python main.py --md thesis.md

# 使用Markdown和数据文件
python main.py --md thesis.md --data ./data

# 指定输出目录
python main.py --md thesis.md --output ./my_output

作为Python模块使用

import asyncio
from app.core.workflow import ThesisToCodeWorkflow
from app.core.llm import LLM

async def main():
    # 创建LLM实例
    llm = LLM(api_key="your-api-key")
    
    # 创建工作流
    workflow = ThesisToCodeWorkflow(llm)
    
    # 执行
    result = await workflow.run(
        md_path="thesis.md",
        data_dir="./data",  # 可选
        output_dir="./output"
    )
    
    print(f"生成文件: {list(result.files.keys())}")

asyncio.run(main())

? 项目结构

biyetocode/
├── app/
│   ├── agents/           # Agent实现
│   │   ├── analyzer_agent.py    # 内容分析
│   │   ├── coder_agent.py       # 代码生成
│   │   └── validator_agent.py   # 代码验证
│   ├── core/             # 核心模块
│   │   ├── llm.py              # LLM封装
│   │   ├── base_agent.py       # Agent基类
│   │   └── workflow.py         # 工作流
│   ├── schemas/          # 数据模型
│   │   └── models.py
│   ├── config/           # 配置
│   │   └── settings.py
│   └── utils/            # 工具函数
│       ├── logger.py
│       └── file_utils.py
├── output/               # 输出目录
├── main.py               # 主程序入口
├── requirements.txt      # 依赖
└── README.md

? 配置说明

双LLM架构

系统使用两个独立的LLM,每次调用独立无历史依赖:

  1. 分析LLM - 用于论文内容分析(AnalyzerAgent)
  2. 代码LLM - 用于代码生成和修复(CoderAgent、ValidatorAgent)

环境变量配置

# 分析LLM配置
ANALYZER_LLM_API_KEY=your-api-key
ANALYZER_LLM_MODEL=模型名称
ANALYZER_LLM_BASE_URL=https://example/v1

# 代码LLM配置
CODER_LLM_API_KEY=your-api-key
CODER_LLM_MODEL=模型名称
CODER_LLM_BASE_URL=https://example/api/v1

支持OpenAI兼容的API

? 支持的论文类型

  • 实证研究 (Empirical)
  • 仿真研究 (Simulation)
  • 算法设计 (Algorithm)
  • 系统设计 (System Design)
  • 数据分析 (Data Analysis)
  • 机器学习 (Machine Learning)

? 输出说明

生成的项目包含:

output/{task_id}/
├── main.py                 # 主程序入口
├── data_preprocessing.py   # 数据预处理
├── data_analysis.py        # 数据分析
├── visualization.py        # 可视化
├── model_training.py       # 模型训练(如有)
├── requirements.txt        # 依赖列表
├── README.md               # 项目说明
├── analysis_result.json    # 分析结果
└── thesis.md               # 论文Markdown

⚠️ 注意事项

  1. API费用: 使用LLM API会产生费用,请注意用量
  2. 代码质量: AI生成的代码可能需要人工调整
  3. 数据隐私: 论文内容会发送到API,请注意隐私
  4. 依赖安装: 生成的代码可能需要额外的Python库

基本上可以激活任意版本的finalshell 高级版、专业版
任意版本finalshell 离线激活码生成工具(高级版、专业版)

开源项目仅供学习使用,请大家支持正版。
源代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <meta name="description" content="FinalShell 离线激活码生成器 安全便捷地生成FinalShell各个版本的离线激活码,无需联网验证。本工具仅用于学习和测试目的,请支持正版软件" />

    <title>FinalShell 离线激活码生成器</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            color: #00ffff;
            font-family: 'Courier New', monospace;
            background: linear-gradient(135deg, #0c0c0c 0%, #1a1a2e 25%, #16213e 50%, #0f3460 75%, #533483 100%);
            min-height: 100vh;
            overflow-x: hidden;
            position: relative;
        }

        /* 动态背景粒子 */
        .particles {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 1;
        }

        .particle {
            position: absolute;
            width: 2px;
            height: 2px;
            background: #00ffff;
            border-radius: 50%;
            animation: float 8s infinite linear;
            box-shadow: 0 0 10px #00ffff;
        }

        @keyframes float {
            0% {
                transform: translateY(100vh) rotate(0deg);
                opacity: 0;
            }

            10% {
                opacity: 1;
            }

            90% {
                opacity: 1;
            }

            100% {
                transform: translateY(-100vh) rotate(360deg);
                opacity: 0;
            }
        }

        /* 网格背景 */
        .grid-bg {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            /*background-image: linear-gradient(rgba(0, 255, 255, 0.1) 1px, transparent 1px),*/
            /*linear-gradient(90deg, rgba(0, 255, 255, 0.1) 1px, transparent 1px);*/
            background: repeating-linear-gradient(
                    0deg,
                    rgba(0, 255, 255, 0.05) 0px,
                    rgba(0, 255, 255, 0.05) 1px,
                    transparent 1px,
                    transparent 20px
            ),
            repeating-linear-gradient(
                    90deg,
                    rgba(0, 255, 255, 0.05) 0px,
                    rgba(0, 255, 255, 0.05) 1px,
                    transparent 1px,
                    transparent 20px
            );
            animation: grid-move 2s linear infinite;
            background-size: 100px 100px;
            pointer-events: none;
            z-index: 0;
        }

        @keyframes grid-move {
            0% {
                background-position: 0 0,
                0 0;
            }
            100% {
                background-position: 40px 40px,
                40px 40px;
            }
        }

        /* 主体框架 */
        .ai-hud-component {
            background-color: transparent;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            min-width: 100%;
            position: relative;
            overflow: hidden;
        }

        /* 框架 */
        .ai-hud-frame {
            border-radius: 10px;
            display: flex;
            flex-direction: column;
            align-items: stretch;
            position: relative;
            padding: 1.5rem;
            width: 30em;
            background: rgba(0, 255, 255, 0.05);
            border: 1px solid rgba(0, 255, 255, 0.2);
            box-shadow: 0 0 30px rgba(0, 255, 255, 0.1);
            z-index: 2;
            animation: frame-pulse 5s infinite ease-in-out;
        }

        /* 输入框容器 */
        .ai-hud-input-wrapper {
            margin-bottom: 10px;
            position: relative;
            display: flex;
            align-items: center;
            z-index: 3;
        }

        /* 输入框 */
        .ai-hud-input {
            border-radius: 5px;
            flex: 1;
            background: transparent;
            border: 1px solid rgba(0, 255, 255, 0.2);
            outline: none;
            color: #00ffff;
            font-family: "Orbitron", sans-serif;
            font-size: 1rem;
            letter-spacing: 0.1em;
            padding: 0.5rem 0.75rem;
            caret-color: #00ffff;
            transition: border 0.3s,
            box-shadow 0.3s;
        }

        /* 输入框: 聚焦 */
        .ai-hud-input:hover {
            border: 1px solid rgba(0, 255, 255, 0.5);
            box-shadow: 0 0 12px rgba(0, 255, 255, 0.3);
        }

        /* 输入框: 占位符 */
        .ai-hud-input::placeholder {
            color: rgba(0, 255, 255, 0.4);
        }

        /* 优化后的 autofill 样式 */
        input:-webkit-autofill,
        input:-webkit-autofill:hover,
        input:-webkit-autofill:focus,
        input:-webkit-autofill:active {
            -webkit-background-clip: text;
            -webkit-text-fill-color: #00ffff;
            background-color: transparent !important;
        }

        input:-internal-autofill-selected {
            background-color: transparent;
            color: #00ffff;
        }

        /* 清除按钮 */
        .ai-hud-btn-clear {
            position: absolute;
            right: 110px; /* 调整位置以避开计算按钮 */
            background: transparent;
            border: none;
            color: rgba(0, 255, 255, 0.4);
            font-size: 1.2rem;
            cursor: pointer;
            width: 20px;
            height: 20px;
            display: none; /* 默认隐藏 */
            z-index: 4;
        }

        .ai-hud-btn-clear:hover {
            color: #00ffff;
        }

        /* 按钮 */
        .ai-hud-btn {
            border-radius: 5px;
            color: rgba(0, 255, 255, 0.4);
            background: rgba(0, 255, 255, 0.05);
            border: 1px solid rgba(0, 255, 255, 0.4);
            padding: 0.6rem 1rem;
            cursor: pointer;
            margin-left: 0.75rem;
            transition: all 0.4s ease;
            box-shadow: 0 0 8px rgba(0, 255, 255, 0.3);
        }

        /* 按钮: 聚焦 */
        .ai-hud-btn:hover {
            box-shadow: 0 0 10px rgba(0, 255, 255, 0.5), 0 0 20px rgba(0, 255, 255, 0.3);
            background: rgba(0, 255, 255, 0.08);
        }

        /* 按钮: 点击 */
        .ai-hud-btn:active {
            box-shadow: 0 0 10px rgba(0, 255, 255, 0.8), 0 0 20px rgba(0, 255, 255, 0.5);
            background: rgba(0, 255, 255, 0.08);
        }

        /* 复制按钮 */
        .ai-hud-btn-copy {
            color: rgb(93 178 26);
            background: transparent;
            border: none;
            padding: 0 5px;
            cursor: pointer;
        }

        /* 标题 */
        .ai-hud-title {
            text-align: center;
            font-size: 1.5rem;
            padding-bottom: 20px;
        }

        /* 内容 */
        .ai-hud-content {
            border: 1px solid rgba(0, 255, 255, 0.4);
            margin-top: 10px;
            padding: 10px 20px;
            border-radius: 5px;
        }

        /* 加载动画 */
        .loading {
            text-align: center;
            padding: 20px;
        }

        .spinner {
            border: 2px solid rgba(0, 255, 255, 0.3);
            border-top: 2px solid #00ffff;
            border-radius: 50%;
            width: 20px;
            height: 20px;
            animation: spin 1s linear infinite;
            margin: 0 auto;
        }

        @keyframes spin {
            0% {
                transform: rotate(0deg);
            }
            100% {
                transform: rotate(360deg);
            }
        }
    </style>
</head>

<body>
<!-- 网格背景 -->
<div class="grid-bg"></div>
<!-- 粒子背景 -->
<div class="particles" id="particles"></div>
<div class="ai-hud-component">
    <div class="ai-hud-frame">
        <div class="ai-hud-title">
            FinalShell 离线激活码生成器
            <div style="font-size: 12px;margin-top: 10px">
                注意:本工具仅用于学习和测试目的,请支持正版软件
            </div>
        </div>
        <div class="ai-hud-input-wrapper">
            <input type="text" id="machineIdInput" class="ai-hud-input" placeholder="请输入机器码..."/>
            <button class="ai-hud-btn-clear" id="clearBtn" onclick="clearInput()" title="清空">×</button>
            <button class="ai-hud-btn" id="generateBtn" onclick="generateActivationCode()">开始计算</button>
        </div>
        <div id="result">
        </div>
    </div>
    <div style="position: absolute;bottom: 10px">
        © 2025 FinalShell激活码生成器 | 安全可靠 | 离线使用
    </div>
</div>
<script>
    // 清空输入框函数
    function clearInput() {
        const input = document.getElementById('machineIdInput');
        input.value = '';
        // 隐藏清除按钮
        document.getElementById('clearBtn').style.display = 'none';
        // 聚焦到输入框
        input.focus();
    }

    // 监听输入框变化,控制清除按钮显示/隐藏
    document.getElementById('machineIdInput').addEventListener('input', function () {
        const clearBtn = document.getElementById('clearBtn');
        if (this.value.trim() !== '') {
            clearBtn.style.display = 'block';
        } else {
            clearBtn.style.display = 'none';
        }
    });

    // 生成激活码主函数
    function generateActivationCode() {
        const machineId = document.getElementById('machineIdInput').value.trim();

        // 输入验证
        if (!machineId) {
            showNotification('请输入机器码');
            return;
        }

        // 显示加载状态
        showLoading();

        // 发送请求到API
        fetch(`https://t.543.ink/v1/api/activat?machineId=${encodeURIComponent(machineId)}`)
            .then(response => {
                // 检查响应状态
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                return response.json();
            })
            .then(data => {
                // 检查API返回是否成功
                if (data && data.code === 200) {
                    // 处理返回的数据并在页面上显示结果
                    displayResult(data.data);
                } else {
                    // 处理API错误
                    throw new Error(data.message || 'API返回错误');
                }
            })
            .catch(error => {
                console.error('Error:', error);
                showError('请求失败: ' + error.message);
            });
    }

    // 展示结果函数
    function displayResult(data) {
        const resultDiv = document.getElementById('result');

        // 清空之前的结果
        resultDiv.innerHTML = '';

        // 检查数据是否存在且为数组
        if (!data || !Array.isArray(data) || data.length === 0) {
            resultDiv.innerHTML = '<div class="ai-hud-content">未找到相关数据</div>';
            return;
        }

        // 遍历数据并生成结果HTML
        data.forEach(item => {
            const version = item.version || 'N/A';
            const advanced = item.advanced || 'N/A';
            const professional = item.professional || 'N/A';

            const contentDiv = document.createElement('div');
            contentDiv.className = 'ai-hud-content';
            contentDiv.innerHTML = `
                <div>版本号: ${version}</div>
                <div>
                    <span>高级版: ${advanced}</span>
                    <button class="ai-hud-btn-copy" onclick="copyToClipboard('${advanced}')">复制</button>
                </div>
                <div>
                    <span>专业版: ${professional}</span>
                    <button class="ai-hud-btn-copy" onclick="copyToClipboard('${professional}')">复制</button>
                </div>
            `;

            resultDiv.appendChild(contentDiv);
        });
    }

    // 复制到剪贴板功能
    function copyToClipboard(text) {
        navigator.clipboard.writeText(text).then(() => {
            // 使用更友好的提示方式
            showNotification('已复制到剪贴板');
        }).catch(err => {
            console.error('复制失败:', err);
            showNotification('复制失败,请手动复制');
        });
    }

    // 显示加载状态
    function showLoading() {
        const resultDiv = document.getElementById('result');
        resultDiv.innerHTML = `
            <div class="loading">
                <div class="spinner"></div>
                <p>正在计算中...</p>
            </div>
        `;
    }

    // 显示错误信息
    function showError(message) {
        const resultDiv = document.getElementById('result');
        resultDiv.innerHTML = `<div class="ai-hud-content" style="color: #ff4d4d;">错误: ${message}</div>`;

        // 重新启用按钮
        const btn = document.getElementById('generateBtn');
        btn.disabled = false;
        btn.textContent = '开始计算';
    }

    // 显示通知消息
    function showNotification(message) {
        // 检查是否已存在通知容器,如果没有则创建
        let notificationContainer = document.getElementById('notification-container');
        if (!notificationContainer) {
            notificationContainer = document.createElement('div');
            notificationContainer.id = 'notification-container';
            notificationContainer.style.position = 'fixed';
            notificationContainer.style.top = '5%';
            notificationContainer.style.left = '50%';
            notificationContainer.style.transform = 'translateX(-50%)';
            notificationContainer.style.zIndex = '1000';
            notificationContainer.style.display = 'flex';
            notificationContainer.style.flexDirection = 'column';
            notificationContainer.style.alignItems = 'center';
            notificationContainer.style.gap = '10px';
            document.body.appendChild(notificationContainer);
        }

        // 创建通知元素(保留原始样式)
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.border = '1px solid rgba(0, 255, 255, 0.4)';
        notification.style.color = '#00ffff';
        notification.style.padding = '5px 30px';
        notification.style.borderRadius = '5px';
        notification.style.fontFamily = 'sans-serif';
        notification.style.fontSize = '16px';
        notification.style.textAlign = 'center';
        notification.style.minWidth = '200px';
        notification.style.backgroundColor = 'rgba(0, 0, 0, 0.2)';
        notification.style.opacity = '0';
        notification.style.transition = 'opacity 0.3s ease-in-out';

        // 添加到容器中
        notificationContainer.appendChild(notification);

        // 触发动画显示
        setTimeout(() => {
            notification.style.opacity = '1';
        }, 10);

        // 3秒后自动移除
        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => {
                if (notification.parentNode) {
                    notification.parentNode.removeChild(notification);
                }
                // 如果容器为空,则移除容器
                if (notificationContainer.children.length === 0) {
                    if (notificationContainer.parentNode) {
                        notificationContainer.parentNode.removeChild(notificationContainer);
                    }
                }
            }, 300);
        }, 3000);
    }

    // 创建粒子效果
    function createParticles() {
        const particlesContainer = document.getElementById('particles');
        const particleCount = 50;
        for (let i = 0; i < particleCount; i++) {
            const particle = document.createElement('div');
            particle.className = 'particle';
            particle.style.left = Math.random() * 100 + '%';
            particle.style.animationDelay = Math.random() * 8 + 's';
            particle.style.animationDuration = (Math.random() * 3 + 5) + 's';

            // 随机颜色
            const colors = ['#00ffff', '#ff00ff', '#ffff00', '#00ff00'];
            const randomColor = colors[Math.floor(Math.random() * colors.length)];
            particle.style.background = randomColor;
            particle.style.boxShadow = `0 0 10px ${randomColor}`;
            particlesContainer.appendChild(particle);
        }
    }

    // 初始化粒子
    createParticles();

    // 定期添加新粒子
    setInterval(() => {
        if (document.querySelectorAll('.particle').length < 100) {
            const particle = document.createElement('div');
            particle.className = 'particle';
            particle.style.left = Math.random() * 100 + '%';
            particle.style.animationDuration = (Math.random() * 3 + 5) + 's';

            const colors = ['#00ffff', '#ff00ff', '#ffff00', '#00ff00'];
            const randomColor = colors[Math.floor(Math.random() * colors.length)];
            particle.style.background = randomColor;
            particle.style.boxShadow = `0 0 10px ${randomColor}`;
            document.getElementById('particles').appendChild(particle);

            // 粒子动画结束后移除
            setTimeout(() => {
                if (particle.parentNode) {
                    particle.parentNode.removeChild(particle);
                }
            }, 8000);
        }
    }, 200);
</script>
<script>
!function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.51.la/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&&o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id:"3OGKOla4LiTErC1d",ck:"3OGKOla4LiTErC1d"});
</script>
<script id="LA-DATA-WIDGET" crossorigin="anonymous" charset="UTF-8" src="https://v6-widget.51.la/v6/3OGKOla4LiTErC1d/quote.js?theme=#00FFFF,#333333,#00FFFF,#00FFFF,#FFFFFF,#00FFFF,12&f=12&display=0,0,0,1,0,1,1,1"></script>
</body>
</html>

在线地址:https://finalshell.543.ink/

账号密码需要自己改下,搜索xxx改为你想设置的,首次加载网页的时候会有点慢,我没写成两个容器

注意:部署完别大量分享

# =============================================================================
# Z-Image-Turbo Gradio Web UI (文生图) - Gradio + ComfyUI 后端
# =============================================================================
# 部署命令: modal deploy z_image_turbo_gradio_deploy.py
# =============================================================================

import modal
import json
import os
import subprocess
from pathlib import Path

# =============================================================================
# 镜像配置 - 强制重建: 2025-12-02-v15 (参考wan2简洁风格)
# =============================================================================
comfy_image = (
    modal.Image.debian_slim(python_version="3.11")
    .apt_install("git", "wget", "curl")
    .pip_install(
        "fastapi[standard]==0.115.4",
        "comfy-cli==1.5.3",
        "requests==2.32.3",
        "huggingface_hub[hf_transfer]==0.34.4",
        "pillow",
        "websocket-client",
    )
    .env({"HF_HUB_ENABLE_HF_TRANSFER": "1"})
    .run_commands(
        "comfy --skip-prompt install --fast-deps --nvidia",
        # 更新到最新 master 代码
        "cd /root/comfy/ComfyUI && git fetch origin && git reset --hard origin/master",
        # 添加 z_image 到 DualCLIPLoader 类型列表 (nodes.py)
        "sed -i 's/\"hunyuan_video_15\"\\]/\"hunyuan_video_15\", \"z_image\"]/g' /root/comfy/ComfyUI/nodes.py",
        # 添加 Z_IMAGE 到 CLIPType 枚举 (sd.py)
        "sed -i 's/CHROMA = 15/CHROMA = 15\\n    Z_IMAGE = 16/g' /root/comfy/ComfyUI/comfy/sd.py",
        # 添加 z_image 处理逻辑到 load_dual_clip
        "sed -i 's/elif clip_type == CLIPType.HUNYUAN_IMAGE:/elif clip_type == CLIPType.Z_IMAGE:\\n            clip_target.clip = comfy.text_encoders.z_image.te(**llama_detect(clip_data))\\n            clip_target.tokenizer = comfy.text_encoders.z_image.ZImageTokenizer\\n        elif clip_type == CLIPType.HUNYUAN_IMAGE:/g' /root/comfy/ComfyUI/comfy/sd.py",
    )
    .pip_install("gradio==3.41.0")
)

app = modal.App(name="z-image-turbo-gradio", image=comfy_image)
vol = modal.Volume.from_name("z-image-turbo-gradio-cache", create_if_missing=True)


# =============================================================================
# 模型下载函数
# =============================================================================
def download_models():
    """下载 Z-Image-Turbo 模型"""
    from huggingface_hub import hf_hub_download

    hf_token = os.getenv("HF_TOKEN")
    repo_id = "Comfy-Org/z_image_turbo"

    print(f"? 从 {repo_id} 下载模型...")

    models = [
        {
            "filename": "split_files/diffusion_models/z_image_turbo_bf16.safetensors",
            "target_dir": "/root/comfy/ComfyUI/models/diffusion_models",
            "target_name": "z_image_turbo_bf16.safetensors",
            "desc": "主扩散模型"
        },
        {
            "filename": "split_files/text_encoders/qwen_3_4b.safetensors",
            "target_dir": "/root/comfy/ComfyUI/models/text_encoders",
            "target_name": "qwen_3_4b.safetensors",
            "desc": "Qwen3 文本编码器"
        },
        {
            "filename": "split_files/vae/ae.safetensors",
            "target_dir": "/root/comfy/ComfyUI/models/vae",
            "target_name": "ae.safetensors",
            "desc": "VAE 解码器"
        }
    ]

    for model in models:
        target_path = f"{model['target_dir']}/{model['target_name']}"

        if os.path.exists(target_path) or os.path.islink(target_path):
            print(f"   ✅ {model['desc']} 已存在,跳过")
            continue

        print(f"? 下载 {model['desc']}: {model['target_name']}...")

        cached_path = hf_hub_download(
            repo_id=repo_id,
            filename=model["filename"],
            cache_dir="/cache",
            token=hf_token
        )

        Path(model["target_dir"]).mkdir(parents=True, exist_ok=True)
        subprocess.run(f"ln -sf {cached_path} {target_path}", shell=True, check=True)
        print(f"   ✅ {model['desc']} 完成")

    print("? 所有模型准备就绪!")


# =============================================================================
# Gradio 应用
# =============================================================================
@app.function(
    max_containers=1,
    gpu="L40S",
    volumes={"/cache": vol},
    timeout=86400,
    scaledown_window=600,
)
@modal.web_server(7860, startup_timeout=600)
def serve():
    """Z-Image-Turbo Gradio Web UI"""

    # 下载模型
    download_models()

    # 启动 ComfyUI 后端 (端口 8188)
    print("? 启动 ComfyUI 后端...")
    subprocess.Popen(
        "comfy launch -- --listen 127.0.0.1 --port 8188",
        shell=True
    )

    # 等待 ComfyUI 启动
    import time
    time.sleep(30)

    # 写入 Gradio 脚本
    gradio_script = '''
import gradio as gr
import requests
import json
import uuid
import time
import os
import io
import threading
from PIL import Image
import websocket

COMFYUI_URL = "http://127.0.0.1:8188"

# 队列管理 - 使用文件持久化统计
STATS_FILE = "/cache/stats.json"
queue_lock = threading.Lock()
queue_count = 0

# 内存缓存,避免频繁读取文件
_stats_cache = {'total': 0, 'date': ''}

def get_today():
    """获取今天日期 (UTC+8)"""
    import datetime
    # 使用 UTC+8 时区
    return (datetime.datetime.utcnow() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d')

def load_stats():
    """从文件加载统计"""
    global _stats_cache
    try:
        if os.path.exists(STATS_FILE):
            with open(STATS_FILE, 'r') as f:
                data = json.load(f)
                _stats_cache['total'] = data.get('total_generated', 0)
                _stats_cache['date'] = data.get('date', '')
                print(f"[STATS] 加载统计: {_stats_cache}", flush=True)
                return _stats_cache['total'], _stats_cache['date']
    except Exception as e:
        print(f"[STATS] 加载失败: {e}", flush=True)
    return 0, ''

def save_stats(total):
    """保存统计到文件"""
    global _stats_cache
    try:
        today = get_today()
        _stats_cache = {'total': total, 'date': today}
        with open(STATS_FILE, 'w') as f:
            json.dump({'total_generated': total, 'date': today}, f)
            f.flush()
            os.fsync(f.fileno())  # 强制刷新到磁盘
        print(f"[STATS] 保存统计: total={total}, date={today}", flush=True)
    except Exception as e:
        print(f"[STATS] 保存失败: {e}", flush=True)

def get_total_generated():
    """获取今日生成总数"""
    global _stats_cache
    today = get_today()
    # 优先使用内存缓存
    if _stats_cache['date'] == today and _stats_cache['total'] > 0:
        return _stats_cache['total']
    # 否则从文件加载
    total, date = load_stats()
    if date != today:
        return 0  # 新的一天重置
    return total

def increment_total():
    """增加生成计数"""
    global _stats_cache
    today = get_today()
    # 使用内存缓存
    if _stats_cache['date'] == today:
        total = _stats_cache['total']
    else:
        total, date = load_stats()
        if date != today:
            total = 0
    total += 1
    save_stats(total)
    print(f"[STATS] 生成计数+1, 今日总计: {total}", flush=True)
    return total

def get_queue_status():
    """获取当前队列状态"""
    with queue_lock:
        if queue_count == 0:
            return "✅ 当前无排队,可立即生成"
        else:
            return f"⏳ 当前排队: {queue_count} 个任务等待中"

def get_stats():
    """获取统计信息"""
    with queue_lock:
        total = get_total_generated()
        return f"? 今日已生成: {total} 张 | 当前队列: {queue_count} 个"

# 启动时初始化加载统计
print("[STATS] 初始化加载统计...", flush=True)
load_stats()
print(f"[STATS] 初始化完成, 缓存: {_stats_cache}", flush=True)

# 分辨率选项
RESOLUTIONS = {
    "1:1 (1024x1024)": (1024, 1024),
    "16:9 (1024x576)": (1024, 576),
    "9:16 (576x1024)": (576, 1024),
    "4:3 (1024x768)": (1024, 768),
}

def create_workflow(prompt, width, height, steps, seed):
    """创建 ComfyUI 工作流"""
    return {
        "1": {
            "class_type": "UNETLoader",
            "inputs": {
                "unet_name": "z_image_turbo_bf16.safetensors",
                "weight_dtype": "default"
            }
        },
        "2": {
            "class_type": "DualCLIPLoader",
            "inputs": {
                "clip_name1": "qwen_3_4b.safetensors",
                "clip_name2": "qwen_3_4b.safetensors",
                "type": "z_image"
            }
        },
        "3": {
            "class_type": "VAELoader",
            "inputs": {
                "vae_name": "ae.safetensors"
            }
        },
        "4": {
            "class_type": "CLIPTextEncode",
            "inputs": {
                "text": prompt,
                "clip": ["2", 0]
            }
        },
        "6": {
            "class_type": "EmptyLatentImage",
            "inputs": {
                "width": width,
                "height": height,
                "batch_size": 1
            }
        },
        "7": {
            "class_type": "KSampler",
            "inputs": {
                "model": ["1", 0],
                "positive": ["4", 0],
                "negative": ["4", 0],
                "latent_image": ["6", 0],
                "seed": seed if seed != -1 else int(time.time() * 1000) % (2**32),
                "steps": steps,
                "cfg": 1.0,
                "sampler_name": "euler",
                "scheduler": "simple",
                "denoise": 1.0
            }
        },
        "8": {
            "class_type": "VAEDecode",
            "inputs": {
                "samples": ["7", 0],
                "vae": ["3", 0]
            }
        },
        "9": {
            "class_type": "SaveImage",
            "inputs": {
                "filename_prefix": "z_image_turbo",
                "images": ["8", 0]
            }
        }
    }

def generate_image(prompt, resolution, steps, seed):
    """生成图像"""
    global queue_count, total_generated

    if not prompt.strip():
        raise gr.Error("请输入提示词")

    # 加入队列
    with queue_lock:
        queue_count += 1
        my_position = queue_count

    print(f"[{time.strftime('%H:%M:%S')}] 任务加入队列,当前位置: {my_position}", flush=True)

    start_time = time.time()
    width, height = RESOLUTIONS[resolution]

    print(f"[{time.strftime('%H:%M:%S')}] 开始生成: {width}x{height}, {steps}步", flush=True)

    try:
        # 创建工作流
        workflow = create_workflow(prompt, width, height, int(steps), int(seed))

        # 生成客户端 ID
        client_id = str(uuid.uuid4())

        # 提交任务
        response = requests.post(
            f"{COMFYUI_URL}/prompt",
            json={"prompt": workflow, "client_id": client_id}
        )

        if response.status_code != 200:
            raise gr.Error(f"提交任务失败: {response.text}")

        prompt_id = response.json()["prompt_id"]
        print(f"[{time.strftime('%H:%M:%S')}] 任务已提交: {prompt_id}", flush=True)

        # 等待完成
        while True:
            time.sleep(0.5)
            history_response = requests.get(f"{COMFYUI_URL}/history/{prompt_id}")

            if history_response.status_code == 200:
                history = history_response.json()
                if prompt_id in history:
                    outputs = history[prompt_id].get("outputs", {})
                    if "9" in outputs and "images" in outputs["9"]:
                        image_info = outputs["9"]["images"][0]
                        filename = image_info["filename"]
                        subfolder = image_info.get("subfolder", "")

                        # 获取图像
                        params = {"filename": filename, "subfolder": subfolder, "type": "output"}
                        img_response = requests.get(f"{COMFYUI_URL}/view", params=params)

                        if img_response.status_code == 200:
                            # 保存图像
                            image_dir = "/tmp/gradio_images"
                            os.makedirs(image_dir, exist_ok=True)
                            image_path = f"{image_dir}/{uuid.uuid4()}.png"

                            image = Image.open(io.BytesIO(img_response.content))
                            image.save(image_path)

                            elapsed = time.time() - start_time
                            print(f"[{time.strftime('%H:%M:%S')}] 生成完成! 耗时: {elapsed:.1f}秒", flush=True)

                            # 完成,减少队列并更新统计
                            with queue_lock:
                                queue_count -= 1
                            increment_total()

                            return image_path

            # 超时检查 (5分钟)
            if time.time() - start_time > 300:
                with queue_lock:
                    queue_count -= 1
                raise gr.Error("生成超时,请重试")

    except gr.Error:
        raise
    except Exception as e:
        # 出错,减少队列
        with queue_lock:
            queue_count -= 1
        elapsed = time.time() - start_time
        print(f"[{time.strftime('%H:%M:%S')}] 错误: {e}")
        raise gr.Error(f"生成失败 ({elapsed:.0f}秒): {str(e)[:200]}")

# 示例提示词
example_prompts = [
    ["一只可爱的橘猫在阳光下打盹"],
    ["赛博朋克风格的未来城市夜景"],
    ["中国水墨画风格的山水"],
    ["宇航员在月球上骑自行车"],
]

# CSS - 参考 wan2 简洁风格
custom_css = """
html, body {
    background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%) !important;
    min-height: 100vh !important;
}
.gradio-container {
    background: transparent !important;
}
h1, h2, h3 {
    background: linear-gradient(90deg, #10b981, #3b82f6);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}
/* 圆角样式 */
.gr-button, .gr-input, .gr-textbox textarea, .gr-box {
    border-radius: 8px !important;
}
.gr-image {
    border-radius: 12px !important;
    background: #fff !important;
}
"""

with gr.Blocks(css=custom_css, title="Z-Image-Turbo") as demo:
    gr.Markdown("# ? Z-Image-Turbo AI 图像生成")
    gr.Markdown("**文生图 (T2I) | Turbo 快速生成 | Powered by Z-Image**")

    # 队列状态显示
    with gr.Row():
        queue_status = gr.Markdown(value=get_queue_status())
        refresh_btn = gr.Button("? 刷新状态", scale=0)

    with gr.Row():
        stats_display = gr.Markdown(value=get_stats())

    with gr.Row():
        with gr.Column():
            prompt = gr.Textbox(
                label="提示词",
                lines=5,
                value="",
                placeholder="请输入您想要生成的图像描述..."
            )

            gr.Markdown("### ? 提示词示例 (点击使用)")
            gr.Examples(
                examples=example_prompts,
                inputs=prompt,
                label=""
            )

            with gr.Accordion("⚙️ 高级设置", open=False):
                resolution = gr.Dropdown(
                    choices=list(RESOLUTIONS.keys()),
                    value="1:1 (1024x1024)",
                    label="分辨率"
                )
                steps = gr.Slider(
                    minimum=4,
                    maximum=20,
                    value=4,
                    step=1,
                    label="采样步数"
                )
                seed = gr.Number(value=-1, label="种子 (-1 为随机)")

            gr.Markdown("? **推荐设置**: 4-8步即可获得高质量图像")
            btn = gr.Button("✨ 生成图像", variant="primary")

        with gr.Column():
            output = gr.Image(label="生成结果", type="filepath")

    # 刷新状态按钮 - 不走队列,立即执行
    def refresh_status():
        return get_queue_status(), get_stats()

    refresh_btn.click(
        refresh_status,
        outputs=[queue_status, stats_display],
        queue=False,  # 不走队列,避免等待
        api_name=False  # 不创建 API 端点
    )

    # 生成按钮 - 生成后自动刷新状态
    def generate_and_refresh(prompt, resolution, steps, seed):
        result = generate_image(prompt, resolution, steps, seed)
        return result, get_queue_status(), get_stats()

    btn.click(generate_and_refresh, [prompt, resolution, steps, seed], [output, queue_status, stats_display])

print("? 启动 Gradio 界面...")
demo.launch(
    server_name="0.0.0.0",
    server_port=7860,
    share=False,
    auth=("xxx", "xxx"), // 账号密码自己改下
    allowed_paths=["/tmp/gradio_images", "/tmp/gradio", "/tmp"]
)

import time
while True:
    time.sleep(1)
'''

    script_path = "/tmp/gradio_app.py"
    with open(script_path, "w") as f:
        f.write(gradio_script)

    subprocess.Popen(["python", script_path])


# =============================================================================
# 本地入口
# =============================================================================
@app.local_entrypoint()
def main():
    print("=" * 60)
    print("Z-Image-Turbo Gradio Web UI")
    print("=" * 60)
    print("\n? 模型: Comfy-Org/z_image_turbo")
    print("\n? GPU: L40S")
    print("\n? 特点:")
    print("   - Gradio 前端界面")
    print("   - ComfyUI 后端推理")
    print("   - 支持多种分辨率")
    print("   - 4-20步快速生成")
    print("\n? 部署命令: modal deploy z_image_turbo_gradio_deploy.py")
    print("=" * 60)

【MODAL】发个简洁版的modal z-image-turbo 部署脚本

AI图像生成网页交互平台 - 基于Streamlit构建的Web应用,提供简洁的用户界面和实用的图像生成功能

应用预览

AI图像生成网页交互平台 - 基于Streamlit构建的Web应用,提供简洁的用户界面和实用的图像生成功能

项目结构

showimageweb/
├── app.py                    # 主应用文件(Streamlit界面)
├── Dockerfile               # Docker构建配置
├── requirements.txt         # Python依赖包
├── docker-compose.yml       # Docker Compose配置
├── LICENSE                  # MIT许可证
├── README.md                # 项目文档
└── assets/
    └── showimage-web-demo.png # 应用预览图

技术栈

  • 前端框架: Streamlit 1.29.0+
  • 后端语言: Python 3.9+
  • 容器化: Docker & Docker Compose
  • 核心依赖: requests, streamlit, base64

特性

  • 高性能: 基于Streamlit的快速响应界面
  • 美观UI: 现代化的卡片式设计,支持自定义画廊列数
  • 响应式: 自适应不同屏幕尺寸,适配移动端
  • 历史记录: 自动保存生成记录,支持无限数量存储
  • 配置选项: 支持随机/固定种子,自定义API配置
  • 实时状态: 生成进度实时显示,带有时间统计
  • 一键下载: PNG图片直接下载,自动命名
  • 通用API: 兼容多种AI图像生成服务
  • 内存管理: 智能存储管理,自动base64优化

快速开始

部署方式

方式一:Docker 部署(推荐)

# 克隆项目
git clone https://github.com/kaima2022/showimageweb.git
cd showimageweb

# 使用 Docker Compose 启动
docker compose up -d

方式二:本地部署

# 克隆项目
git clone https://github.com/kaima2022/showimageweb.git
cd showimageweb

# 安装依赖
pip install -r requirements.txt
# 启动
streamlit run app.py --server.address=0.0.0.0 --server.port=8501

访问应用

http://localhost:8501

演示

公开KEY:sk-zKTGcw8llBFZLpXAAsxTmMSmCfY8DNfe

API配置

应用支持任意兼容的AI图像生成API:

支持的API格式

  • 请求方式: POST
  • 认证方式: Bearer Token
  • 请求格式: {"prompt": "...", "seed": ...}
  • 响应格式: {"base64": "..."}

配置说明

  1. API URL: 完整的API接口地址(如:https://api.example.com/v1/generate
  2. API Key: 您的API密钥
  3. 种子设置: 支持随机种子或固定种子复现结果

兼容的服务

  • OpenAI DALL-E API
  • Stable Diffusion API
  • 自建AI图像服务
  • 任何支持标准格式的图像生成API

配置选项

环境变量(可选)

# Streamlit配置
STREAMLIT_SERVER_ADDRESS=0.0.0.0
STREAMLIT_SERVER_PORT=8501
STREAMLIT_SERVER_HEADLESS=true

# 时区设置
TZ=Asia/Shanghai

自定义配置

  • 画廊列数: 1-4列可调
  • API超时: 默认60秒
  • 图片格式: PNG格式输出
  • 文件命名: 时间戳自动命名

贡献指南

  1. Fork 本项目
  2. 创建特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交更改 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 开启 Pull Request

根据今日GitHub监控数据,整理出以下13个与AI视频制作、小说创作及有声书生成高度相关的开源项目。今日重点发现包括功能强大的电子书转有声书工具ebook2audiobook,以及多款视频生成与语音克隆的新兴工具。

1. 有声书制作与语音克隆

  • ebook2audiobook

    • 项目介绍:一款功能强大的电子书转有声书转换器,支持CPU和GPU加速。
    • 核心功能

      • 多引擎支持:集成XTTSv2、Bark、Vits等多种TTS引擎,支持超过1110种语言。
      • 智能处理:支持按章节分割电子书,保留元数据,支持自定义语音克隆。
      • 广泛兼容:支持.epub、.pdf、.mobi等多种输入格式及.m4b、.mp3等输出格式,提供Gradio Web界面和Docker部署。
    • 项目地址https://github.com/DrewThomasson/ebook2audiobook
  • Dia-TTS-Server

    • 项目介绍:Dia TTS模型的自托管服务器实现。
    • 核心功能

      • API兼容:提供兼容OpenAI格式的API端点,易于集成。
      • 高级特性:支持SafeTensors/BF16加速、语音克隆及多角色对话生成,配备用户友好的Web UI。
    • 项目地址https://github.com/Gmzxdotzz/Dia-TTS-Server
  • ComfyUI-VoxCPM

    • 项目介绍:专为ComfyUI设计的插件,用于生成高表现力的语音。
    • 核心功能

      • 零样本克隆:支持在ComfyUI工作流中实现逼真的零样本语音克隆。
      • 情感表达:能够将文本转换为具有丰富情感色彩的音频。
    • 项目地址https://github.com/krishnasaivamsi/ComfyUI-VoxCPM
  • OpenVoice (VoltsyGM Fork)

    • 项目介绍:基于MIT和MyShell技术的即时语音克隆应用。
    • 核心功能

      • 风格控制:支持在克隆语音时精确控制说话的风格和语调。
    • 项目地址https://github.com/VoltsyGM/OpenVoice
  • local-voice-cloning-app

    • 项目介绍:一个轻量级的Python应用程序,用于本地语音克隆。
    • 核心功能

      • 简易工作流:提供简单的界面和流程来合成和克隆语音。
    • 项目地址https://github.com/Mohamedfat7i/local-voice-cloning-app

2. 视频创作与生成

  • MOBIUS

    • 项目介绍:一个专门用于生成桌游教程视频的AI工具。
    • 核心功能

      • 垂直领域生成:专注于将规则文本转化为直观的教学视频内容。
    • 项目地址https://github.com/w9bikze8u4cbupc/MOBIUS
  • AI-course-generator

    • 项目介绍:利用AI将长视频讲座转化为结构化在线课程的工具。
    • 核心功能

      • 课程结构化:自动生成成绩单、模块划分、课程内容及测验题。集成OpenAI Whisper和GPT-4 Vision技术。
    • 项目地址https://github.com/DavidFW27/AI-course-generator
  • VibeArt

    • 项目介绍:一体化的图像与视频生成工具。
    • 核心功能

      • 模型集成:结合开源与闭源模型,利用社区训练的LoRA优化特定风格的生成效果,降低提示词门槛。
    • 项目地址https://github.com/vibeart-in/VibeArt
  • mulmocast-cli

    • 项目介绍:AI驱动的播客与视频生成器。
    • 核心功能

      • 脚本驱动:使用"MulmoScript"脚本语言生成多模态演示内容,集成OpenAI、Google、Anthropic等多家模型。
    • 项目地址https://github.com/receptron/mulmocast-cli
  • Hollywood-Quality-UGC-Ad-Generator

    • 项目介绍:利用单张产品照片生成好莱坞级视频广告的工具。
    • 核心功能

      • 多模型协作:通过n8n编排,结合Sora 2、GPT-4o和Gemini 2.5 Pro实现高质量广告生成。
    • 项目地址https://github.com/Saurabh22111998/Hollywood-Quality-UGC-Ad-Generator
  • AIQuoteClipGenerator

    • 项目介绍:基于MCP的自动化名言视频生成器,面向Instagram/TikTok。
    • 核心功能

      • 自动剪辑:自动生成包含名言的短视频片段,适合社交媒体快速传播。
    • 项目地址https://github.com/mercyg/AIQuoteClipGenerator

3. 小说与故事创作

  • Ghost-Writer

    • 项目介绍:一个AI驱动的故事创作引擎。
    • 核心功能

      • 引导式写作:逐步引导用户完成小说创作过程,充当智能写作助手。
    • 项目地址https://github.com/MAS-D-KING/Ghost-Writer

https://track.linso.ai/zh/execution/cmihfy83n07utl6945ke9i2yh

QQNT 42744 存在广泛扩散且严重威胁安全的XSS安全问题 所有在此版本的用户看到特定代码会自动在本机执行 请各位管理以及群友为了安全问题立刻降级QQNT 42744 版本查看左下角-关于 复现方法简单到令人发指 不提供复现 极易利用

2015.11.28 QQ内部灰度测试安装:QQNT42744

红墨 RedInk 小红书图文生成器开源

昨天就已经开源了,但是测试的时候智能使用官方接口,今天下午更新了,而且提供了docker版,部署更简单了,刚刚试了一下,已经成功生成图片了,就是使用起来账号积分如流水。

红墨 - 小红书AI图文生成器

让传播不再需要门槛,让创作从未如此简单

红墨首页

使用红墨生成的各类小红书封面

使用红墨生成的各类小红书封面 - AI驱动,风格统一,文字准确

写在前面

前段时间默子在 Linux.do 发了一个用 Nano banana Pro 做 PPT 的帖子,收获了 600 多个赞。很多人用?Nano banana Pro 去做产品宣传图、直接生成漫画等等。我就在想:为什么不拿?2来做点更功利、更刺激的事情?

于是就有了这个项目。一句话一张图片生成小红书图文


✨ 效果展示

输入一句话,就能生成完整的小红书图文

提示词:秋季显白美甲(暗广一个:默子牌美甲),图片 是我的小红书主页。符合我的风格生成

同时我还截图了我的小红书主页,包括我的头像,签名,背景,姓名什么的

示例1

然后等待10-20秒后,就会有每一页的大纲,大家可以根据的自己的需求去调整页面顺序(不建议),自定义每一个页面的内容(这个很建议)

示例2

首先生成的是封面页

示例3

然后稍等一会儿后,会生成后面的所有页面(这里是并发生成的所有页面(默认是15个),如果大家的API供应商无法支持高并发的话,记得要去改一下设置)

示例4


?️ 技术架构

后端

  • 语言: Python 3.11+
  • 框架: Flask
  • AI 模型:

    • Gemini 3 (文案生成)
    • ?Nano banana Pro (图片生成)
  • 包管理: uv

前端

  • 框架: Vue 3 + TypeScript
  • 构建: Vite
  • 状态管理: Pinia

? 如何自己部署

方式一:Docker 部署(推荐)

最简单的部署方式,一行命令即可启动:

docker run -d -p 12398:12398 -v ./output:/app/output histonemax/redink:latest

访问 http://localhost:12398,在 Web 界面的设置页面配置你的 API Key 即可使用。

使用 docker-compose(可选):

下载 docker-compose.yml 后:

docker-compose up -d

Docker 部署说明:

  • 容器内不包含任何 API Key,需要在 Web 界面配置
  • 使用 -v ./output:/app/output 持久化生成的图片
  • 可选:挂载自定义配置文件 -v ./text_providers.yaml:/app/text_providers.yaml

方式二:本地开发部署

前置要求:

  • Python 3.11+
  • Node.js 18+
  • pnpm
  • uv

1. 克隆项目

git clone https://github.com/HisMax/RedInk.git
cd RedInk

2. 配置 API 服务

复制配置模板文件:

cp text_providers.yaml.example text_providers.yaml
cp image_providers.yaml.example image_providers.yaml

编辑配置文件,填入你的 API Key 和服务配置。也可以启动后在 Web 界面的设置页面进行配置。

3. 安装后端依赖

uv sync

4. 安装前端依赖

cd frontend
pnpm install

5. 启动服务

启动后端:

uv run python -m backend.app

访问: http://localhost:12398

启动前端:

cd frontend
pnpm dev

访问: http://localhost:5173


? 使用指南

基础使用

  1. 输入主题: 在首页输入想要创作的主题,如"如何在家做拿铁"
  2. 生成大纲: AI 自动生成 6-9 页的内容大纲
  3. 编辑确认: 可以编辑和调整每一页的描述
  4. 生成图片: 点击生成,实时查看进度
  5. 下载使用: 一键下载所有图片

进阶使用

  • 上传参考图片: 适合品牌方,保持品牌视觉风格
  • 修改描述词: 精确控制每一页的内容和构图
  • 重新生成: 对不满意的页面单独重新生成

? 配置说明

配置方式

项目支持两种配置方式:

  1. Web 界面配置(推荐):启动服务后,在设置页面可视化配置
  2. YAML 文件配置:直接编辑配置文件

文本生成配置

配置文件: text_providers.yaml

# 当前激活的服务商
active_provider: openai

providers:
  # OpenAI 官方或兼容接口
  openai:
    type: openai_compatible
    api_key: sk-xxxxxxxxxxxxxxxxxxxx
    base_url: https://api.openai.com/v1
    model: gpt-4o

  # Google Gemini(原生接口)
  gemini:
    type: google_gemini
    api_key: AIzaxxxxxxxxxxxxxxxxxxxxxxxxx
    model: gemini-2.0-flash

图片生成配置

配置文件: image_providers.yaml

# 当前激活的服务商
active_provider: gemini

providers:
  # Google Gemini 图片生成
  gemini:
    type: google_genai
    api_key: AIzaxxxxxxxxxxxxxxxxxxxxxxxxx
    model: gemini-3-pro-image-preview
    high_concurrency: false  # 高并发模式

  # OpenAI 兼容接口
  openai_image:
    type: image_api
    api_key: sk-xxxxxxxxxxxxxxxxxxxx
    base_url: https://your-api-endpoint.com
    model: dall-e-3
    high_concurrency: false

高并发模式说明

  • 关闭(默认):图片逐张生成,适合 GCP 300$ 试用账号或有速率限制的 API
  • 开启:图片并行生成(最多15张同时),速度更快,但需要 API 支持高并发

⚠️ GCP 300$ 试用账号不建议启用高并发,可能会触发速率限制导致生成失败。


⚠️ 注意事项

  1. API 配额限制:

    • 注意 Gemini 和图片生成 API 的调用配额
    • GCP 试用账号建议关闭高并发模式
  2. 生成时间:

    • 图片生成需要时间,请耐心等待(不要离开页面)

? 参与贡献

欢迎提交 Issue 和 Pull Request!

如果这个项目对你有帮助,欢迎给个 Star ⭐

未来计划

  • [ ] 支持更多图片格式,例如一句话生成一套PPT什么的
  • [ ] 历史记录管理优化
  • [ ] 导出为各种格式(PDF、长图等)

更新日志

v1.3.0 (2025-11-26)

  • ✨ 新增 Docker 支持,一键部署
  • ✨ 发布官方 Docker 镜像到 Docker Hub: histonemax/redink
  • ? Flask 自动检测前端构建产物,支持单容器部署
  • ? Docker 镜像内置空白配置模板,保护 API Key 安全
  • ? 更新 README,添加 Docker 部署说明

v1.2.0 (2025-11-26)

  • ✨ 新增版权信息展示,所有页面显示开源协议和项目链接
  • ✨ 优化图片重新生成功能,支持单张图片重绘
  • ✨ 重新生成图片时保持风格一致,传递完整上下文(封面图、大纲、用户输入)
  • ✨ 修复图片缓存问题,重新生成的图片立即刷新显示
  • ✨ 统一文本生成客户端接口,支持 Google Gemini 和 OpenAI 兼容接口自动切换
  • ✨ 新增 Web 界面配置功能,可视化管理 API 服务商
  • ✨ 新增高并发模式开关,适配不同 API 配额
  • ✨ API Key 脱敏显示,保护密钥安全
  • ✨ 配置自动保存,修改即时生效
  • ? 调整默认 max_output_tokens 为 8000,兼容更多模型限制
  • ? 优化前端路由和页面布局,提升用户体验
  • ? 简化配置文件结构,移除冗余参数
  • ? 优化历史记录图片显示,使用缩略图节省带宽
  • ? 历史记录重新生成时自动从文件系统加载封面图作为参考
  • ? 修复 store.updateImage 方法缺失导致的重新生成失败问题
  • ? 修复历史记录加载时图片 URL 拼接错误
  • ? 修复下载功能中原图参数处理问题
  • ? 修复图片加载 500 错误问题

交流讨论与赞助

联系作者

用爱发电,如果可以,请默子喝一杯☕️咖啡吧

赞赏码

Star History

Star History Chart


? 开源协议

个人使用 - CC BY-NC-SA 4.0

本项目采用 CC BY-NC-SA 4.0 协议进行开源

你可以自由地:

  • 个人使用 - 用于学习、研究、个人项目
  • 分享 - 在任何媒介以任何形式复制、发行本作品
  • 修改 - 修改、转换或以本作品为基础进行创作

但需要遵守以下条款:

  • ? 署名 - 必须给出适当的署名,提供指向本协议的链接,同时标明是否对原始作品作了修改
  • ? 非商业性使用 - 不得将本作品用于商业目的
  • ? 相同方式共享 - 如果你修改、转换或以本作品为基础进行创作,你必须以相同的协议分发你的作品

商业授权

如果你希望将本项目用于商业目的(包括但不限于):

  • 提供付费服务
  • 集成到商业产品
  • 作为 SaaS 服务运营
  • 其他盈利性用途

请联系作者获取商业授权:

默子会根据你的具体使用场景提供灵活的商业授权方案。


免责声明

本软件按"原样"提供,不提供任何形式的明示或暗示担保,包括但不限于适销性、特定用途的适用性和非侵权性的担保。在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责。


? 致谢

  • Google Gemini - 强大的文案生成能力
  • 图片生成服务提供商 - 惊艳的图片生成效果
  • Linux.do - 优秀的开发者社区

?‍? 作者

默子 (Histone) - AI 创业者 | Python & 深度学习

  • ? 位置: 中国杭州
  • ? 状态: 创业中
  • ? 专注: Transformers、GANs、多模态AI
  • ? Email: histonemax@gmail.com
  • ? 微信: Histone2024
  • ? GitHub: @HisMax

"让 AI 帮我们做更有创造力的事"

昨天用banana生成了好几张照片,效果确实碉堡,看到这个项目挺不错的,分享一下,前提是要有你自己的key哦,并不是白嫖。

Gemini 3 Pro 是一个基于 Web 的轻量级、高性能 AI 绘图客户端。它专为 Google Gemini 多模态模型(如 gemini-3-pro-image-preview)设计,提供了一个无需后端、纯前端运行的专业工作台。

除了基础的对话和绘图功能外,它还内置了本地图片切片工厂表情包制作模式以及并发任务管理,所有数据均存储在本地浏览器中。

? 在线演示:点击这里查看 Demo (建议替换为您部署在 GitHub Pages 的链接)

✨ 核心特性

? 专业的绘图体验

  • 并发生成:支持多会话同时进行,后台处理生成任务,无需等待。
  • 多模态输入:支持上传多张参考图(Reference Images),完美适配 Gemini 的多模态理解能力。
  • 精细控制

    • 支持 1K / 2K / 4K 分辨率预设。
    • 内置 10+ 种常用长宽比(21:9, 16:9, 1:1, 9:16 等)。
  • 即时预览:生成的图片支持灯箱预览、一键下载原图。

✂️ 独家功能:图片切片工厂 (Slicer Tool)

不再需要 Photoshop,直接在浏览器中完成素材处理:

  • 九宫格/自定义切片:内置横向/纵向辅助线,拖拽即可调整切割位置。
  • 智能补全:支持 1:1 强制补全(不论原图比例,自动填充背景色)。
  • 一键打包:自动将切割后的图片打包为 ZIP 下载。
  • 表情包制作流:配合“制作表情包”快捷指令,生成后直接切片,工作流一气呵成。

?️ 隐私与安全

  • 纯前端运行:没有中间服务器,API 请求直接从您的浏览器发送至 Google。
  • 本地存储:所有对话记录、API Key 配置均通过 IndexedDB 和 LocalStorage 存储在您的设备上。
  • API 管理:支持自定义 API Host(便于反代用户)和多渠道轮询。

? 响应式设计

  • 完美适配桌面端与移动端。
  • 移动端支持侧边栏手势、触摸优化。

? 界面概览

?️ 桌面端工作台

image
image

image

? 移动端与切片工具

image
image
image

image


? 快速开始

本项目是一个单文件(或纯静态)应用,无需复杂的构建工具(如 Webpack/Vite),开箱即用。

方法 1:直接运行

  1. 克隆本项目或下载 ZIP 包。
  2. 直接双击打开 index.html 文件。
  3. 点击右上角 设置 图标,输入您的 Google API Key。

方法 2:部署到 GitHub Pages (推荐)

  1. Fork 本仓库。
  2. 进入仓库 Settings -> Pages
  3. Branch 设置为 main,点击保存。
  4. 一分钟后,您即可通过 https://您的用户名.github.io/仓库名 访问。

方法 3:本地开发

如果您想二次开发:

# 克隆仓库
git clone https://github.com/your-username/gemini-3-pro.git

# 进入目录
cd gemini-3-pro

# 使用 VS Code Live Server 或 Python 启动简易服务器
python -m http.server 8000

⚙️ 配置说明

点击界面右上角的 设置 (⚙️) 图标进入配置面板:

配置项说明默认值
渠道名称用于区分不同的 Key官方 API
API Base URL接口地址https://generativelanguage.googleapis.com
API KeyGoogle AI Studio 获取的 Key(空)
Model使用的模型名称gemini-3-pro-image-preview
提示: 如果您处于无法直接访问 Google API 的网络环境,请将 API Base URL 修改为您的反向代理地址(例如 Cloudflare Worker 地址)。

?️ 技术栈

  • Core: HTML5, CSS3 (Variables, Flex/Grid), Vanilla JavaScript (ES6+)
  • Storage: IndexedDB (对话历史), LocalStorage (配置)
  • Libraries:

    • JSZip (CDN 引入,用于图片打包下载)
    • 无其他第三方 UI 框架依赖

?️以此为基础的后续计划 (Roadmap)

  • [ ] PWA 支持:支持安装到桌面/手机主屏幕。
  • [ ] 提示词优化器:内置 prompt 润色功能。
  • [ ] 参数预设库:保存常用的绘图参数组合。
  • [ ] 更多模型支持:适配 Claude 或 OpenAI 绘图接口。

? 贡献指南

非常欢迎通过 Pull Requests 或 Issues 提交您的建议!

  1. Fork 本仓库
  2. 新建 Feat_xxx 分支
  3. 提交代码
  4. 新建 Pull Request

? 开源协议

本项目基于 MIT License 开源。


⚠️ 免责声明

本项目仅作为 API 调用客户端,不提供任何 AI 模型服务。

  • 请确保您使用的 API Key 符合 Google Generative AI 的使用条款。
  • 请勿利用本项目生成违反法律法规的内容。

[bsgit user="wusimpl"]AntigravityQuotaWatcher[/bsgit]

功能展示

![Antigravity IDE 模型配额监控器插件 [开源](支持 Win/Mac 系统) 1](https://xiaohack.oss-cn-zhangjiakou.aliyuncs.com/typecho/2025/11/2947478344.png!mark)
![Antigravity IDE 模型配额监控器插件 [开源](支持 Win/Mac 系统) 2](https://xiaohack.oss-cn-zhangjiakou.aliyuncs.com/typecho/2025/11/3305152143.png!mark)

使用方法

下载插件,安装,重启,Over(github README 有详细方法)

可配置项

可配置项

插件原理

Antigravity 通过内建的本地语言服务器通信获取数据,插件就是拿到这些端点,然后请求获取数据然后解析。开发过程的难点是获取 crsf token,最后在 AI 的帮助下还是很轻松地拿到了。

几点说明

Gemini Low 和 High 共用一个配额
Claude 和 Claude Thinking 共用一个配额
GPT-OSS 单独一个配额
所以你们在插件里面看到的公用一个配额的模型剩余用量是一样的,Google 也是偷懒…
配额更新默认每 30s 刷新一次,可以在配置里面修改。

Antigravity 目前还只能算半成品,复杂一点的项目出错概率不小,需要等 Google 慢慢迭代修复,或者训练专门针对 code agent 的模型。不过胜在免费,拿来小修小改,做点 Code Review 之类的问题还是不大。

Jetbrains家的KeyGen网上有很多,直接百度一下就能搜出来。
主要是ReSharper有联网验证,而且是.NET平台的软件,不能像Java一样用--javaagent直接进行注入。
这里给一个用Visual Studio拓展来解决的思路。

using HarmonyLib;
using System;

namespace ReSharperCrack
{
    [HarmonyPatch]
    internal static class Patches
    {
        static Patches()
        {
            try
            {
                var harmonyVersion = typeof(Harmony).Assembly.GetName().Version?.ToString() ?? "unknown";
                Logger.Log($"Patch loaded. Start. Harmony={harmonyVersion}, AppDomain={AppDomain.CurrentDomain.FriendlyName}");
            }
            catch (Exception ex)
            {
                Logger.Log($"Patch static ctor exception: {ex}");
            }
        }

        [HarmonyPatch("JetBrains.Application.License2.UserLicenses.UserLicenseViewSubmodel", "AddLicense")]
        [HarmonyPrefix]
        internal static bool AddLicense_Prefix(ref bool validateLicenseKey)
        {
            try
            {
                Logger.Log($"AddLicense_Prefix called. validateLicenseKey(before)={validateLicenseKey}");
                validateLicenseKey = false;
                Logger.Log($"AddLicense_Prefix finished. validateLicenseKey(after)={validateLicenseKey}");
                return true;
            }
            catch (Exception ex)
            {
                Logger.Log($"AddLicense_Prefix exception: {ex}");
                // be conservative: let original run if our prefix fails
                return true;
            }
        }

        [HarmonyPatch("JetBrains.Application.License2.NewLicenses.UserLicenseService", "VerifyCertificate")]
        [HarmonyPostfix]
        internal static void VerifyCertificate_Postfix(ref object __result)
        {
            try
            {
                Logger.Log($"VerifyCertificate_Postfix called. __result(before)={(__result == null ? "<null>" : __result + " (" + __result.GetType().FullName + ")")}");

                if ((int)__result != 0)
                {
                    __result = 0;
                }

                Logger.Log($"VerifyCertificate_Postfix finished. __result(after)={(__result == null ? "<null>" : __result + " (" + __result.GetType().FullName + ")")}");
            }
            catch (Exception ex)
            {
                Logger.Log($"VerifyCertificate_Postfix exception: {ex}");
            }
        }
    }
}

以及入口类:

/*...*/
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[Guid(ReSharperCrackPackage.PackageGuidString)]
[ProvideAutoLoad(UIContextGuids80.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
public sealed class ReSharperCrackPackage : AsyncPackage
{
    protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        // 在这里加载LibHarmony的Hook
    }
}
/*...*/

编译成vsix拓展,装载在Visual Studio上。

接着,使用License Key激活就可以了。

ReSharper稳定破解

报告摘要

2025年11月24日,人工智能领域在开源社区和企业应用层面均展现出强劲活力。小米发布了业界首个开源的自动驾驶与具身智能融合大模型MiMo-Embodied,Sber也开源了包括视频生成在内的一系列新模型。学术界持续探索AI Agent、多模态及模型可信赖性等前沿方向。企业动态方面,三星与英伟达宣布合作共建AI巨型工厂,预示着AI将深度赋能智能制造。同时,AI在网络安全、音乐授权等领域的应用也取得了新进展。

一、模型发布与产品更新 (Model Releases & Product Updates)

  1. 小米发布MiMo-Embodied开源模型
    小米公司发布了业界首个开源的视觉-语言基础模型 MiMo-Embodied。该模型旨在无缝集成自动驾驶和具身智能(Embodied AI)任务,在任务规划、可供性预测和空间理解方面表现出色,为机器人和智能汽车的协同发展提供了新的技术路径。(来自newsbytesapp.com)
  2. Sber开源一系列生成式AI模型
    俄罗斯联邦储蓄银行(Sber)发布并开源了多个AI模型,包括:

    • Kandinsky 5.0系列:包含Video Pro、Video Lite和Image Lite,原生支持俄语提示,并能稳健生成含西里尔字母的图像和视频。
    • K-VAE 1.0:一个高性能的开源图像/视频编解码器模型,对训练视觉生成模型至关重要。
    • GigaChat Ultra Preview / Lightning:基于混合专家(MoE)架构的新模型,专为俄语任务优化。(来自newsbytesapp.com)

二、精选AI论文 (New Papers)

arXiv在过去24小时内更新了多篇值得关注的论文,主要集中在多智能体系统、联邦学习和AI可信赖性等领域:

  • arXiv:2511.16205 - ChemLabs on ChemO: A Multi-Agent System for Multimodal Reasoning on IChO 2025: 介绍了一个用于化学奥林匹克竞赛(IChO)多模态推理的多智能体系统,展示了AI在复杂科学推理任务中的潜力。(来自arxiv.org)
  • arXiv:2511.16423 - TOFA: Training-Free One-Shot Federated Adaptation for Vision-Language Models: 提出了一种名为TOFA的免训练、一次性联邦自适应框架,用于视觉-语言模型,旨在解决联邦学习中的数据异构性和通信效率问题。(来自arxiv.org)
  • arXiv:2511.16402 - Trustworthy AI in the Agentic Lakehouse: from Concurrency to Governance: 探讨了在Agentic Lakehouse架构中实现可信赖AI的挑战,从并发性到治理提出了一个框架,对构建可靠的企业级AI系统具有指导意义。(来自arxiv.org)

三、热门开源项目 (Open-Source Projects)

  1. google / adk-go
    谷歌为Go语言开发者推出的AI Agent开发工具包(ADK)继续在GitHub上保持高热度。它提供了一个代码优先的开源工具集,用于构建、评估和部署复杂的AI智能体,持续吸引着社区的关注。(来自github.com)
  2. microsoft / call-center-ai
    微软开源的AI呼叫中心项目热度不减,该项目允许开发者通过API调用或直接拨打电话号码与AI Agent进行通话,为构建自动化客服、语音助手等应用提供了基础框架。(来自github.com)
  3. yeongpin / cursor-free-vip
    一个旨在免费使用Cursor AI编辑器Pro功能的工具登上趋势榜。该项目通过重置机器ID来绕过付费限制,虽然这反映了社区对强大AI编程工具的渴望,但也引发了关于软件许可和道德使用的讨论。(来自github.com)

四、重大科技新闻与公告 (Major Tech News)

  1. 三星与NVIDIA合作共建AI巨型工厂
    三星电子宣布与NVIDIA深化合作,将通过建设新的“AI巨型工厂”(AI Megafactory)来引领全球智能制造的转型。此举旨在将AI技术深度整合到生产流程中,提升效率和创新能力。(来自samsung.com)
  2. Anthropic挫败首例AI驱动的大规模网络间谍活动
    AI安全公司Anthropic宣布,其协助识别并挫败了首个主要由AI智能体大规模策划的网络间谍攻击。该攻击在很大程度上无需人类干预,凸显了AI在网络攻防两端日益增长的重要性。(来自apnews.com)
  3. OpenAI招募Intel AI高管
    OpenAI招募了Intel公司的首席技术与人工智能官Sachin Katti。他将负责领导设计和构建实现通用人工智能(AGI)所需的庞大计算基础设施,显示出OpenAI在硬件和基础设施层面的战略布局正在加速。(来自technologymagazine.com)
  4. 索尼、华纳等与AI音乐初创公司签署授权协议
    索尼、华纳和环球三大唱片公司与AI音乐初创公司Klay签署了授权协议。这一里程碑事件为AI生成音乐的合法化和商业化铺平了道路,可能将重塑音乐产业的创作和分发模式。(来自technologymagazine.com)

https://track.linso.ai/zh/execution/cmicgehwr03cwl694l1nkc212

即梦视频去水印下载 1
即梦视频去水印下载 2
图片&视频无水印下载

// ==UserScript==
// @name         即梦AI去水印
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  通过重写XMLHttpRequest实现即梦AI 图片&视频下载去水印!
// @author       mihuc
// @match        https://jimeng.jianying.com/ai-tool/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=jimeng.jianying.com
// @grant        GM_xmlhttpRequest
// @connect      *
// ==/UserScript==

(function () {
    'use strict';
    // 新增:有限大小的映射表与工具函数,用于保存 history id -> 对应 data
    const jimengDataMap = new Map(); // key: history id (uuid string), value: data object
    const JIMENG_DATA_MAP_MAX = 500;

    // 将数据写入 map(带容量控制)
    function setJimengDataMap(key, value) {
        if (!key || typeof key !== 'string' || value === undefined || value === null) return;
        try {
            jimengDataMap.set(key, value);
            // 简单淘汰最旧条目
            if (jimengDataMap.size > JIMENG_DATA_MAP_MAX) {
                const oldestKey = jimengDataMap.keys().next().value;
                if (oldestKey !== undefined) jimengDataMap.delete(oldestKey);
            }
        } catch (e) {
            console.warn('jimeng: setJimengDataMap error', e);
        }
    }

    // 从 map 读取数据
    function getJimengData(key) {
        if (!key) return null;
        return jimengDataMap.has(key) ? jimengDataMap.get(key) : null;
    }

    // 清空缓存(可选)
    function clearJimengDataMap() {
        try { jimengDataMap.clear(); } catch (e) { console.warn('jimeng: clearJimengDataMap error', e); }
    }

    /**
     * 尝试从响应中记录 get_history_by_ids 或 get_asset_list 类型的数据
     * @param {object} json - 已解析的 JSON 响应
     * @param {string} url - 请求 URL(可选,用于基于 URL 的判断)
     */
    function tryRecordHistoryFromResponse(json, url) {
        try {
            if (!json || typeof json !== 'object') return;
            const data = json.data;
            if (!data || typeof data !== 'object') return;

            // 通过 URL 判断接口类型
            const isHistoryEndpoint = typeof url === 'string' && url.indexOf('get_history_by_ids') !== -1;
            const isAssetEndpoint = typeof url === 'string' && url.indexOf('get_asset_list') !== -1;

            // 简单 UUID-like 正则匹配
            const uuidLike = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;

            // 1) 处理 get_history_by_ids 风格的响应(已有逻辑)
            if (isHistoryEndpoint) {
                for (const k in data) {
                    if (!Object.prototype.hasOwnProperty.call(data, k)) continue;
                    setJimengDataMap(k, data[k]);
                    // console.info('jimeng: recorded history key (by url)=', k);

                    if (data[k].item_list) {
                        for (const item of data[k].item_list) {
                            if (item.image && item.image.large_images) {
                                const image_item = item.image.large_images[0];
                                image_item["_type"] = "img"; // 标记类型,便于后续识别
                                setJimengDataMap(image_item.image_uri, image_item);

                            }
                        }
                    }
                }
            }

            // 2) 处理 get_asset_list 风格的响应:查找 data.asset_list 中的 submit_id(与 item_list 同级)
            if (isAssetEndpoint || Array.isArray(data.asset_list)) {
                const list = Array.isArray(data.asset_list) ? data.asset_list : [];
                for (const entry of list) {
                    // entry 可能是 { video: { submit_id, item_list: [...] , ... } } 或其他嵌套形式
                    if (!entry || typeof entry !== 'object') continue;

                    // 尝试常见路径:entry.video.submit_id 或 entry.submit_id
                    const candidates = [];
                    if (entry.video && typeof entry.video === 'object') candidates.push({ obj: entry.video, type: "video" });
                    if (entry.submit_id) candidates.push({ obj: entry, type: "video" });

                    if (entry.agent_conversation_session && entry.agent_conversation_session.submit_id_data_map) {
                        for (const key in entry.agent_conversation_session.submit_id_data_map) {
                            if (!Object.prototype.hasOwnProperty.call(entry.agent_conversation_session.submit_id_data_map, key)) continue;
                            const v = entry.agent_conversation_session.submit_id_data_map[key];
                            if (v.item_list) {
                                const image_item = v.item_list[0].image.large_images[0];
                                candidates.push({ obj: image_item, type: "img" });
                            }
                        }
                    }
                    // 也尝试向下查找一层含 submit_id 的对象(防止不同嵌套)
                    for (const key in entry) {
                        if (!Object.prototype.hasOwnProperty.call(entry, key)) continue;
                        const v = entry[key];
                        if (v && typeof v === 'object' && v.submit_id) {
                            candidates.push({ obj: v, type: "video" });
                        }
                    }

                    for (const c of candidates) {
                        const obj = c.obj;
                        obj["_type"] = c.type; // 标记类型,便于后续识别
                        const submitId = obj && obj.submit_id;
                        if (submitId && typeof submitId === 'string') {
                            // 将 submit_id 对应的整个对象记录(通常包含 item_list)
                            setJimengDataMap(submitId, obj);
                            console.info('jimeng: recorded asset submit_id=', submitId);
                        }

                        const imageUri = obj && obj.image_uri;
                        if (imageUri && typeof imageUri === 'string') {
                            setJimengDataMap(imageUri, obj);
                            console.info('jimeng: recorded asset image_uri=', imageUri);
                        }
                    }
                }
            }

            // 3) 兜底:若不是明确的接口,也检测顶级 uuid-like key(旧逻辑)
            for (const k in data) {
                if (!Object.prototype.hasOwnProperty.call(data, k)) continue;
                const val = data[k];
                if (uuidLike.test(k) && val && (val.item_list || val.video || Array.isArray(val.item_list))) {
                    setJimengDataMap(k, val);
                    console.info('jimeng: recorded history key (by content)=', k);
                }
            }
        } catch (e) {
            console.warn('jimeng: tryRecordHistoryFromResponse error', e);
        }
    }

    // 暴露查询接口到 window,便于控制台快速查看(只读查询)
    try {
        Object.defineProperty(window, 'getJimengData', {
            value: function (id) { return getJimengData(id); },
            writable: false,
            configurable: true
        });
        // 仅供调试:暴露底层 Map(请勿在生产逻辑中修改)
        Object.defineProperty(window, '_jimengDataMap', {
            value: jimengDataMap,
            writable: false,
            configurable: true
        });
    } catch (e) {
        // 忽略在严格 CSP/沙箱环境下的定义失败
    }

    function decodeBase64Safe(b64) {
        if (!b64 || typeof b64 !== 'string') return null;
        // 过滤掉明显不是 base64 的短串
        if (b64.length < 16) return null;
        try {
            return atob(b64);
        } catch (e) {
            // 尝试 URL-safe 变体
            try {
                const normalized = b64.replace(/-/g, '+').replace(/_/g, '/');
                return atob(normalized);
            } catch (e2) {
                return null;
            }
        }
    }
    // 从已缓存的响应数据(jimengDataMap)或响应对象中提取 main_url(优先返回第一个可解码的 URL)
    function extractMainUrlFromData(obj) {
        if (!obj || typeof obj !== 'object') return null;
        const seen = new Set();
        const type = obj._type || null;
        function walk(o) {
            if (!o || typeof o !== 'object') return null;
            if (seen.has(o)) return null;
            seen.add(o);

            for (const k in o) {
                if (!Object.prototype.hasOwnProperty.call(o, k)) continue;
                const v = o[k];

                try {
                    // 发现 main_url 字段(通常为 base64),尝试解码并返回第一个有效 URL
                    if ((k === 'main_url' || k === 'mainUrl') && typeof v === 'string') {
                        const decoded = decodeBase64Safe(v);
                        if (decoded && decoded.startsWith('http')) return decoded;
                    }

                    // 有些视频信息以 video_list.video_1.main_url 存在
                    if (k === 'video_list' && typeof v === 'object') {
                        // 遍历 video_list 下的各质量项
                        for (const q in v) {
                            if (!Object.prototype.hasOwnProperty.call(v, q)) continue;
                            const item = v[q];
                            if (item && typeof item === 'object' && item.main_url) {
                                const dec = decodeBase64Safe(item.main_url);
                                if (dec && dec.startsWith('http')) return dec;
                            }
                        }
                    }
                    if (k === 'image_url' && typeof v === 'string' && type == "img") {
                        if (v) return v;
                    }

                    // 若字段为字符串化 JSON,尝试解析并继续查找
                    if (typeof v === 'string') {
                        try {
                            const parsed = JSON.parse(v);
                            const r = walk(parsed);
                            if (r) return r;
                        } catch (e) {
                            // ignore
                        }
                    }

                    // 递归对象或数组
                    if (typeof v === 'object') {
                        const r = walk(v);
                        if (r) return r;
                    }
                } catch (e) {
                    // ignore individual errors
                }
            }
            return null;
        }

        return walk(obj);
    }

    // 重写 XMLHttpRequest.prototype 以拦截响应文本

    function interceptXHR() {
        try {
            if (!window || !window.XMLHttpRequest) return;
            const XProto = window.XMLHttpRequest && window.XMLHttpRequest.prototype;
            if (!XProto) return;
            // 防止重复打补丁
            if (XProto.__jimeng_patched) return;

            const _open = XProto.open;
            const _send = XProto.send;

            XProto.open = function (method, url, ...rest) {
                try {
                    this.__jimeng_url = url;
                } catch (e) { /* ignore */ }
                return _open.apply(this, [method, url, ...rest]);
            };

            XProto.send = function (body) {
                try {
                    // attach listener safely
                    this.addEventListener && this.addEventListener('readystatechange', function () {
                        try {
                            if (this.readyState === 4) {
                                // 仅在特定接口上处理,避免影响其它请求
                                const reqUrl = (this.__jimeng_url || '').toString();
                                if (!(reqUrl.indexOf('get_asset_list') !== -1 || reqUrl.indexOf('get_history_by_ids') !== -1)) {
                                    return; // 非目标接口,直接忽略
                                }

                                const txt = this.responseText;
                                if (typeof txt === 'string' && txt.length > 100) {
                                    // 尝试直接解析为 JSON
                                    try {
                                        const parsed = JSON.parse(txt);
                                        // 新增:记录 get_history_by_ids 返回的顶级 key -> data
                                        tryRecordHistoryFromResponse(parsed, this.__jimeng_url);
                                    } catch (e) {
                                        // 回退:查找第一个 '{' 子串并尝试解析
                                        try {
                                            const idx = txt.indexOf('{');
                                            if (idx >= 0) {
                                                const sub = txt.slice(idx);
                                                const parsed2 = JSON.parse(sub);
                                                // 新增回退解析时也尝试记录
                                                tryRecordHistoryFromResponse(parsed2, this.__jimeng_url);
                                            }
                                        } catch (e2) {
                                            // 忽略不可解析的文本
                                        }
                                    }
                                }
                            }
                        } catch (e) {
                            // 忽略单次处理错误,避免影响页面
                        }
                    });
                } catch (e) {
                    // 忽略 attach 错误
                }
                return _send.apply(this, arguments);
            };

            // 标记已打补丁
            try { XProto.__jimeng_patched = true; } catch (e) { }
            console.info('jimeng: XHR interceptor installed');
        } catch (e) {
            console.warn('jimeng: interceptXHR failed', e);
        }
    }

    // 替换原先注入/监听调用:直接安装 XHR 拦截器(同时保留 fetch 拦截作为回退)
    try {
        interceptXHR();
    } catch (e) {
        console.warn('jimeng: failed to install XHR interceptor', e);
    }

    // 优先使用 GM_xmlhttpRequest 绕过页面 CSP,回退到 fetch
    function fetchBypassCSP(url, options = {}) {
        // options: { method, headers, responseType, onProgress } - responseType支持 'arraybuffer' 或 'blob' 等
        const method = options.method || 'GET';
        const headers = options.headers || {};
        const responseType = options.responseType || 'arraybuffer';
        const onProgress = options.onProgress; // 进度回调函数

        // 如果 Tampermonkey 提供 GM_xmlhttpRequest,则使用它(可绕过页面 CSP)
        if (typeof GM_xmlhttpRequest === 'function') {
            return new Promise((resolve, reject) => {
                try {
                    GM_xmlhttpRequest({
                        method: method,
                        url: url,
                        headers: headers,
                        responseType: responseType,
                        onprogress(progressEvent) {
                            // 调用进度回调
                            if (typeof onProgress === 'function') {
                                onProgress(progressEvent);
                            }
                        },
                        onload(res) {
                            // res.response 在 responseType=arraybuffer 时是 ArrayBuffer
                            resolve({
                                ok: (res.status >= 200 && res.status < 300),
                                status: res.status,
                                statusText: res.statusText,
                                response: res.response,
                                responseHeaders: res.responseHeaders
                            });
                        },
                        onerror(err) {
                            reject(err);
                        },
                        ontimeout() {
                            reject(new Error('timeout'));
                        }
                    });
                } catch (e) {
                    reject(e);
                }
            });
        }

        // 回退:普通 fetch(受 CSP 限制,但无法支持进度监控)
        return (async () => {
            const resp = await fetch(url, { method, headers, mode: options.mode || 'cors' });
            const blob = await resp.blob();
            const arrayBuffer = await blob.arrayBuffer();
            return {
                ok: resp.ok,
                status: resp.status,
                statusText: resp.statusText,
                response: arrayBuffer
            };
        })();
    }

    /**
     * 进度弹窗管理器 - 管理多个下载弹窗的位置和堆叠
     */
    const ToastManager = {
        toasts: [], // 存储所有活跃的toast
        baseTop: 20, // 基础顶部距离
        spacing: 10, // toast之间的间距

        /**
         * 添加新的toast到管理器
         * @param {Object} toast - toast对象
         */
        add(toast) {
            this.toasts.push(toast);
            this.updatePositions();
        },

        /**
         * 从管理器中移除toast
         * @param {Object} toast - toast对象
         */
        remove(toast) {
            const index = this.toasts.indexOf(toast);
            if (index > -1) {
                this.toasts.splice(index, 1);
                this.updatePositions();
            }
        },

        /**
         * 更新所有toast的位置
         */
        updatePositions() {
            let currentTop = this.baseTop;
            this.toasts.forEach(toast => {
                if (toast.container && toast.container.parentNode) {
                    toast.container.style.top = currentTop + 'px';
                    // 获取toast的实际高度
                    const height = toast.container.offsetHeight;
                    currentTop += height + this.spacing;
                }
            });
        }
    };

    /**
     * 创建右上角进度弹窗
     * @returns {Object} 包含容器元素和更新方法的对象
     */
    function createProgressToast(msg = "正在下载视频...") {
        const container = document.createElement('div');
        container.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            min-width: 300px;
            max-width: 400px;
            background: rgba(0, 0, 0, 0.9);
            color: white;
            padding: 16px 20px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
            z-index: 99999;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
            font-size: 14px;
            line-height: 1.5;
            transition: top 0.3s ease, opacity 0.3s ease;
        `;

        const title = document.createElement('div');
        title.style.cssText = `
            font-weight: 600;
            margin-bottom: 8px;
            font-size: 15px;
        `;
        title.textContent = msg;

        const progressBarBg = document.createElement('div');
        progressBarBg.style.cssText = `
            width: 100%;
            height: 4px;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 2px;
            margin: 8px 0;
            overflow: hidden;
        `;

        const progressBar = document.createElement('div');
        progressBar.style.cssText = `
            width: 0%;
            height: 100%;
            background: linear-gradient(90deg, #4CAF50, #8BC34A);
            border-radius: 2px;
            transition: width 0.3s ease;
        `;
        progressBarBg.appendChild(progressBar);

        const statusText = document.createElement('div');
        statusText.style.cssText = `
            font-size: 12px;
            color: rgba(255, 255, 255, 0.8);
            margin-top: 8px;
        `;
        statusText.textContent = '准备下载...';

        container.appendChild(title);
        container.appendChild(progressBarBg);
        container.appendChild(statusText);
        document.body.appendChild(container);

        const toast = {
            container,
            title,
            progressBar,
            statusText,
            /**
             * 更新进度
             * @param {number} percent - 进度百分比 (0-100)
             * @param {string} status - 状态文本
             */
            update(percent, status) {
                progressBar.style.width = percent + '%';
                if (status) statusText.textContent = status;
            },
            /**
             * 设置为成功状态
             * @param {string} message - 成功消息
             */
            success(message) {
                title.textContent = '✓ 下载完成';
                progressBar.style.background = 'linear-gradient(90deg, #4CAF50, #66BB6A)';
                statusText.textContent = message || '视频已保存';
            },
            /**
             * 设置为错误状态
             * @param {string} message - 错误消息
             */
            error(message) {
                title.textContent = '✗ 下载失败';
                progressBar.style.background = 'linear-gradient(90deg, #f44336, #e57373)';
                statusText.textContent = message || '请重试';
            },
            /**
             * 移除弹窗
             * @param {number} delay - 延迟时间(毫秒)
             */
            remove(delay = 0) {
                setTimeout(() => {
                    container.style.opacity = '0';
                    setTimeout(() => {
                        if (container.parentNode) {
                            container.parentNode.removeChild(container);
                        }
                        // 从管理器中移除
                        ToastManager.remove(toast);
                    }, 300);
                }, delay);
            }
        };

        // 添加到管理器
        ToastManager.add(toast);

        return toast;
    }

    /**
     * 下载视频通用函数(带进度显示)
     * @param {string} url - 视频URL
     * @param {string} filename - 可选的文件名
     */
    async function downloadVideo(url, filename) {
        const toast = createProgressToast();

        try {
            const res = await fetchBypassCSP(url, {
                responseType: 'arraybuffer',
                method: 'GET',
                onProgress: (event) => {
                    if (event.lengthComputable) {
                        const percent = Math.round((event.loaded / event.total) * 100);
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        const totalMB = (event.total / 1024 / 1024).toFixed(2);
                        toast.update(percent, `${loadedMB} MB / ${totalMB} MB (${percent}%)`);
                    } else {
                        // 无法获取总大小时显示已下载量
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        toast.update(50, `已下载 ${loadedMB} MB...`);
                    }
                }
            });

            if (!res || !res.ok) throw new Error('请求失败,status=' + (res && res.status));

            toast.update(100, '处理文件中...');

            const arrayBuffer = res.response;
            const blob = new Blob([arrayBuffer], { type: 'video/mp4' });
            const objectUrl = URL.createObjectURL(blob);

            const link = document.createElement('a');
            const now = new Date();
            const dateStr = now.toISOString()
                .slice(0, 19)
                .replace('T', '_')
                .replace(/:/g, '-');
            link.download = filename || `RWater_${dateStr}.mp4`;
            link.href = objectUrl;
            link.target = '_blank';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);

            URL.revokeObjectURL(objectUrl);

            toast.success('视频已开始下载');
            toast.remove(1000);
        } catch (e) {
            console.error('下载失败:', e);
            toast.error(e.message || '下载失败,请重试');
            toast.remove(2000);
        }
    }

    async function downloadImage(url, filename) {
        const toast = createProgressToast("正在下载图片...");

        try {
            const res = await fetchBypassCSP(url, {
                responseType: 'arraybuffer',
                method: 'GET',
                onProgress: (event) => {
                    if (event.lengthComputable) {
                        const percent = Math.round((event.loaded / event.total) * 100);
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        const totalMB = (event.total / 1024 / 1024).toFixed(2);
                        toast.update(percent, `${loadedMB} MB / ${totalMB} MB (${percent}%)`);
                    } else {
                        // 无法获取总大小时显示已下载量
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        toast.update(50, `已下载 ${loadedMB} MB...`);
                    }
                }
            });

            if (!res || !res.ok) throw new Error('请求失败,status=' + (res && res.status));

            toast.update(100, '处理文件中...');

            const arrayBuffer = res.response;
            const blob = new Blob([arrayBuffer], { type: 'image/png' });
            const objectUrl = URL.createObjectURL(blob);

            const link = document.createElement('a');
            const now = new Date();
            const dateStr = now.toISOString()
                .slice(0, 19)
                .replace('T', '_')
                .replace(/:/g, '-');
            link.download = filename || `RWater_${dateStr}.png`;
            link.href = objectUrl;
            link.target = '_blank';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);

            URL.revokeObjectURL(objectUrl);

            toast.success('图片已开始下载');
            toast.remove(1000);
        } catch (e) {
            console.error('下载失败:', e);
            toast.error(e.message || '下载失败,请重试');
            toast.remove(2000);
        }
    }
    /**
     * 初始化即梦网站的功能
     */
    function initJimengSite() {
        const observer = new MutationObserver(() => {
            jimeng_addVideoDownloadButton();
            jimeng_addImageDownloadButton();
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['style', 'class']
        });

        // Also add event listeners for mouse interactions that might trigger UI changes
        document.addEventListener('mouseover', () => {
            setTimeout(() => {
                jimeng_addVideoDownloadButton();
                jimeng_addImageDownloadButton();
            }, 100);
        }, true);

        console.log('即梦: 初始化无水印下载工具');
        jimeng_addVideoDownloadButton();
        jimeng_addImageDownloadButton();
    }

    /**
     * 创建即梦视频下载按钮
     * @returns {HTMLElement} 下载按钮元素
     */
    function jimeng_createVideoDownloadButton() {
        const button = document.createElement('div');
        button.className = 'lv-btn lv-btn-secondary lv-btn-size-default lv-btn-shape-square baseButton-LQGhrC download-btn';
        const div = document.createElement('div');
        div.textContent = '无水印下载';
        button.appendChild(div);

        button.title = '无水印下载';
        button.style = '--right-padding: 14px; align-items: center;background-color: var(--bg-block-primary-default); border-radius: 8px; color: var(--text-primary, #f5fbff); display: flex; font-family: PingFang SC;font-size: 12px; font-weight: 400; gap: 4px; justify-content: center; line-height: 20px; padding: 8px var(--right-padding) 8px 12px; position: relative;'
        return button;
    }
    function jimeng_createImageDownloadButton() {
        const button = document.createElement('span');
        button.className = 'download-btn';
        button.textContent = "无水印下载";
        button.style = 'display: block;font-size: 12px;position: absolute;top: 5px;left: 5px;background: rgba(255, 255, 255, 0.87);border-radius: 5px;padding: 3px 10px;z-index: 100;'
        return button;
    }

    function jimeng_getVideoDataId(downloadBtn) {
        console.log('即梦: 开始查找视频元素...');
        const parentItem = downloadBtn.closest('[class*="item-"]');
        return parentItem ? parentItem.getAttribute('data-id') : null;
    }
    /**
     * 获取即梦视频URL
     * @param {Element} downloadBtn - 下载按钮元素
     * @returns {string|null} 视频URL或null
     */
    function jimeng_getVideoUrl(downloadBtn) {
        console.log('即梦: 开始查找视频元素...');
        const parentItem = downloadBtn.closest('[class*="item-"]');

        const videoWrapper = parentItem.querySelector('div[class^="video-node-wrapper-"]');
        if (!videoWrapper) {
            alert('未找到视频');
            console.log('即梦: 未找到videoWrapper');
            return null;
        }

        let videoElement = videoWrapper.querySelector('video');
        if (!videoElement) {
            alert('未找到视频');
            console.log('即梦: 未找到video元素');
            return null;
        }

        if (videoElement && videoElement.src) {
            console.log('即梦: 找到视频元素:', {
                src: videoElement.src,
                className: videoElement.className,
                width: videoElement.width,
                height: videoElement.height,
                naturalWidth: videoElement.naturalWidth,
                naturalHeight: videoElement.naturalHeight,
                attributes: Array.from(videoElement.attributes).map(attr => `${attr.name}="${attr.value}"`).join(', ')
            });
            return videoElement.src;
        }

        alert('未找到视频');
        console.log('即梦: 未找到合适的视频');
        return null;
    }
    function jimeng_getImageUrl(downloadBtn) {
        console.log('即梦: 开始查找图片元素...');
        const parentItem = downloadBtn.parentNode;

        const imageElement = parentItem.querySelector('img[class^="image-"]');
        if (!imageElement) {
            alert('未找到图片');
            console.log('即梦: 未找到imageElement');
            return null;
        }
        if (imageElement && imageElement.src) {
            console.log('即梦: 找到图片元素:', {
                src: imageElement.src,
            });
            return imageElement.src;
        }

        alert('未找到图片');
        console.log('即梦: 未找到合适的图片');
        return null;
    }
    /**
     * 添加视频下载按钮到即梦页面
     */
    function jimeng_addVideoDownloadButton() {
        const videoContents = document.querySelectorAll('div[class^="video-record-"]');
        if (!videoContents || videoContents.length === 0) {
            return;
        }
        for (let i = 0; i < videoContents.length; i++) {
            const topActionBar = videoContents[i].querySelector('div[class^="record-bottom-slots-"]');
            if (!topActionBar) {
                continue;
            }
            if (topActionBar.querySelector('div[class^="image-record-content-"]')) {
                continue;
            }
            if (topActionBar.querySelector('.download-btn')) {
                continue;
            }
            jimeng_processSingleVideoButton(topActionBar);
        }
    }

    /**
     * 添加图片下载按钮到即梦页面
     */
    function jimeng_addImageDownloadButton() {
        const imgContents = document.querySelectorAll('div[class^="agentic-image-record-item-"]');
        if (!imgContents || imgContents.length === 0) {
            return;
        }
        for (let i = 0; i < imgContents.length; i++) {
            const topActionBar = imgContents[i].querySelector('div[class^="image-card-container-"]');
            if (!topActionBar) {
                continue;
            }

            if (topActionBar.querySelector('.download-btn')) {
                continue;
            }
            jimeng_processSingleImageButton(topActionBar);
        }
    }

    /**
     * 处理单个视频按钮(已简化:不再依赖 jimengVideoMap)
     * @param {Element} topActionBar - 操作栏元素
     */
    function jimeng_processSingleVideoButton(topActionBar) {
        const downloadBtn = jimeng_createVideoDownloadButton();
        topActionBar.insertBefore(downloadBtn, topActionBar.firstChild);
        console.info('即梦: 添加视频下载按钮');

        downloadBtn.addEventListener('click', async () => {
            console.log('即梦: 点击视频下载按钮');
            const videoDataId = jimeng_getVideoDataId(downloadBtn);
            const videoUrl = jimeng_getVideoUrl(downloadBtn);
            console.log('即梦: 获取到的视频URL:', videoUrl);
            let finalUrl = videoUrl;

            try {
                // 2) 若仍然没有 finalUrl,尝试用 videoDataId 从 jimengDataMap 中查找 main_url
                if (videoDataId) {
                    const cached = getJimengData(videoDataId);
                    if (cached) {
                        const extracted = extractMainUrlFromData(cached);
                        if (extracted) {
                            finalUrl = extracted;
                            console.log('即梦: 从 jimengDataMap 提取到 main_url,使用该 URL 下载', finalUrl);
                        }
                    }
                }

                if (!finalUrl) {
                    alert('未找到可下载的视频链接');
                    console.error('即梦: 未能解析出有效的下载 URL');
                    return;
                }

                downloadBtn.style.opacity = '0.5';
                downloadBtn.style.pointerEvents = 'none';
                await downloadVideo(finalUrl);
            } catch (e) {
                console.error('即梦: 下载过程中出错', e);
                alert('下载失败,请重试(查看控制台以获得更多信息)');
            } finally {
                downloadBtn.style.opacity = '1';
                downloadBtn.style.pointerEvents = 'auto';
            }
        });
    }
    function extractTosPath(url) {
        // 方法1: 使用正则表达式
        const match = url.match(/tos-[^/]+\/[a-f0-9]+/);
        return match ? match[0] : null;
    }
    function jimeng_processSingleImageButton(topActionBar) {
        const downloadBtn = jimeng_createImageDownloadButton();
        topActionBar.insertBefore(downloadBtn, topActionBar.firstChild);
        console.info('即梦: 添加图片下载按钮');

        downloadBtn.addEventListener('click', async () => {
            console.log('即梦: 点击图片下载按钮');
            const imageUrl = jimeng_getImageUrl(downloadBtn);
            console.log('即梦: 获取到的图片URL:', imageUrl);
            if (!imageUrl) {
                alert('未找到可下载的图片链接');
                return;
            }
            let finalUrl = imageUrl;

            try {
                // 使用正则和分段处理规范化 imageUri:去除域名、query、尺寸、变体(~...)与扩展名,优先返回 "xxx/yyy" 或单段 id
                let imageUri = extractTosPath(imageUrl);
                // 2) 若仍然没有 finalUrl,尝试用 videoDataId 从 jimengDataMap 中查找 main_url
                if (imageUri) {
                    const cached = getJimengData(imageUri);
                    if (cached) {
                        const extracted = extractMainUrlFromData(cached);
                        if (extracted) {
                            finalUrl = extracted;
                            console.log('即梦: 从 jimengDataMap 提取到image_url,使用该 URL 下载', finalUrl);
                        }
                    }
                }

                if (!finalUrl) {
                    alert('未找到可下载的图片链接');
                    console.error('即梦: 未能解析出有效的下载 URL');
                    return;
                }

                downloadBtn.style.opacity = '0.5';
                downloadBtn.style.pointerEvents = 'none';
                await downloadImage(finalUrl);
            } catch (e) {
                console.error('即梦: 下载过程中出错', e);
                alert('下载失败,请重试(查看控制台以获得更多信息)');
            } finally {
                downloadBtn.style.opacity = '1';
                downloadBtn.style.pointerEvents = 'auto';
            }
        });
    }
    initJimengSite();

})();

仅视频无水印下载

// ==UserScript==
// @name         即梦AI去水印
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  通过重写XMLHttpRequest实现即梦AI视频下载去水印!
// @author       mihuc
// @match        https://jimeng.jianying.com/ai-tool/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=jimeng.jianying.com
// @grant        GM_xmlhttpRequest
// @connect      *
// ==/UserScript==

(function () {
    'use strict';
    // 新增:有限大小的映射表与工具函数,用于保存 history id -> 对应 data
    const jimengDataMap = new Map(); // key: history id (uuid string), value: data object
    const JIMENG_DATA_MAP_MAX = 500;

    // 将数据写入 map(带容量控制)
    function setJimengDataMap(key, value) {
        if (!key || typeof key !== 'string' || value === undefined || value === null) return;
        try {
            jimengDataMap.set(key, value);
            // 简单淘汰最旧条目
            if (jimengDataMap.size > JIMENG_DATA_MAP_MAX) {
                const oldestKey = jimengDataMap.keys().next().value;
                if (oldestKey !== undefined) jimengDataMap.delete(oldestKey);
            }
        } catch (e) {
            console.warn('jimeng: setJimengDataMap error', e);
        }
    }

    // 从 map 读取数据
    function getJimengData(key) {
        if (!key) return null;
        return jimengDataMap.has(key) ? jimengDataMap.get(key) : null;
    }

    // 清空缓存(可选)
    function clearJimengDataMap() {
        try { jimengDataMap.clear(); } catch (e) { console.warn('jimeng: clearJimengDataMap error', e); }
    }

    /**
     * 尝试从响应中记录 get_history_by_ids 或 get_asset_list 类型的数据
     * @param {object} json - 已解析的 JSON 响应
     * @param {string} url - 请求 URL(可选,用于基于 URL 的判断)
     */
    function tryRecordHistoryFromResponse(json, url) {
        try {
            if (!json || typeof json !== 'object') return;
            const data = json.data;
            if (!data || typeof data !== 'object') return;

            // 通过 URL 判断接口类型
            const isHistoryEndpoint = typeof url === 'string' && url.indexOf('get_history_by_ids') !== -1;
            const isAssetEndpoint = typeof url === 'string' && url.indexOf('get_asset_list') !== -1;

            // 简单 UUID-like 正则匹配
            const uuidLike = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;

            // 1) 处理 get_history_by_ids 风格的响应(已有逻辑)
            if (isHistoryEndpoint) {
                for (const k in data) {
                    if (!Object.prototype.hasOwnProperty.call(data, k)) continue;
                    setJimengDataMap(k, data[k]);
                    console.info('jimeng: recorded history key (by url)=', k);
                }
            }

            // 2) 处理 get_asset_list 风格的响应:查找 data.asset_list 中的 submit_id(与 item_list 同级)
            if (isAssetEndpoint || Array.isArray(data.asset_list)) {
                const list = Array.isArray(data.asset_list) ? data.asset_list : [];
                for (const entry of list) {
                    // entry 可能是 { video: { submit_id, item_list: [...] , ... } } 或其他嵌套形式
                    if (!entry || typeof entry !== 'object') continue;

                    // 尝试常见路径:entry.video.submit_id 或 entry.submit_id
                    const candidates = [];

                    if (entry.video && typeof entry.video === 'object') candidates.push({ obj: entry.video, parent: entry });
                    if (entry.submit_id) candidates.push({ obj: entry, parent: entry });

                    // 也尝试向下查找一层含 submit_id 的对象(防止不同嵌套)
                    for (const key in entry) {
                        if (!Object.prototype.hasOwnProperty.call(entry, key)) continue;
                        const v = entry[key];
                        if (v && typeof v === 'object' && v.submit_id) {
                            candidates.push({ obj: v, parent: entry });
                        }
                    }

                    for (const c of candidates) {
                        const obj = c.obj;
                        const submitId = obj && obj.submit_id;
                        if (submitId && typeof submitId === 'string') {
                            // 将 submit_id 对应的整个对象记录(通常包含 item_list)
                            setJimengDataMap(submitId, obj);
                            console.info('jimeng: recorded asset submit_id=', submitId);
                        }
                    }
                }
            }

            // 3) 兜底:若不是明确的接口,也检测顶级 uuid-like key(旧逻辑)
            for (const k in data) {
                if (!Object.prototype.hasOwnProperty.call(data, k)) continue;
                const val = data[k];
                if (uuidLike.test(k) && val && (val.item_list || val.video || Array.isArray(val.item_list))) {
                    setJimengDataMap(k, val);
                    console.info('jimeng: recorded history key (by content)=', k);
                }
            }
        } catch (e) {
            console.warn('jimeng: tryRecordHistoryFromResponse error', e);
        }
    }

    // 暴露查询接口到 window,便于控制台快速查看(只读查询)
    try {
        Object.defineProperty(window, 'getJimengData', {
            value: function (id) { return getJimengData(id); },
            writable: false,
            configurable: true
        });
        // 仅供调试:暴露底层 Map(请勿在生产逻辑中修改)
        Object.defineProperty(window, '_jimengDataMap', {
            value: jimengDataMap,
            writable: false,
            configurable: true
        });
    } catch (e) {
        // 忽略在严格 CSP/沙箱环境下的定义失败
    }

    function getVideoUrlPrams(url) {
        const urlObj = new URL(url);
        const params = {};
        urlObj.searchParams.forEach((value, key) => {
            params[key] = value;
        });
        return params;
    }



    function decodeBase64Safe(b64) {
        if (!b64 || typeof b64 !== 'string') return null;
        // 过滤掉明显不是 base64 的短串
        if (b64.length < 16) return null;
        try {
            return atob(b64);
        } catch (e) {
            // 尝试 URL-safe 变体
            try {
                const normalized = b64.replace(/-/g, '+').replace(/_/g, '/');
                return atob(normalized);
            } catch (e2) {
                return null;
            }
        }
    }
    // 从已缓存的响应数据(jimengDataMap)或响应对象中提取 main_url(优先返回第一个可解码的 URL)
    function extractMainUrlFromData(obj) {
        if (!obj || typeof obj !== 'object') return null;
        const seen = new Set();

        function walk(o) {
            if (!o || typeof o !== 'object') return null;
            if (seen.has(o)) return null;
            seen.add(o);

            for (const k in o) {
                if (!Object.prototype.hasOwnProperty.call(o, k)) continue;
                const v = o[k];

                try {
                    // 发现 main_url 字段(通常为 base64),尝试解码并返回第一个有效 URL
                    if ((k === 'main_url' || k === 'mainUrl') && typeof v === 'string') {
                        const decoded = decodeBase64Safe(v);
                        if (decoded && decoded.startsWith('http')) return decoded;
                    }

                    // 有些视频信息以 video_list.video_1.main_url 存在
                    if (k === 'video_list' && typeof v === 'object') {
                        // 遍历 video_list 下的各质量项
                        for (const q in v) {
                            if (!Object.prototype.hasOwnProperty.call(v, q)) continue;
                            const item = v[q];
                            if (item && typeof item === 'object' && item.main_url) {
                                const dec = decodeBase64Safe(item.main_url);
                                if (dec && dec.startsWith('http')) return dec;
                            }
                        }
                    }

                    // 若字段为字符串化 JSON,尝试解析并继续查找
                    if (typeof v === 'string') {
                        try {
                            const parsed = JSON.parse(v);
                            const r = walk(parsed);
                            if (r) return r;
                        } catch (e) {
                            // ignore
                        }
                    }

                    // 递归对象或数组
                    if (typeof v === 'object') {
                        const r = walk(v);
                        if (r) return r;
                    }
                } catch (e) {
                    // ignore individual errors
                }
            }
            return null;
        }

        return walk(obj);
    }

    // 重写 XMLHttpRequest.prototype 以拦截响应文本

    function interceptXHR() {
        try {
            if (!window || !window.XMLHttpRequest) return;
            const XProto = window.XMLHttpRequest && window.XMLHttpRequest.prototype;
            if (!XProto) return;
            // 防止重复打补丁
            if (XProto.__jimeng_patched) return;

            const _open = XProto.open;
            const _send = XProto.send;

            XProto.open = function (method, url, ...rest) {
                try {
                    this.__jimeng_url = url;
                } catch (e) { /* ignore */ }
                return _open.apply(this, [method, url, ...rest]);
            };

            XProto.send = function (body) {
                try {
                    // attach listener safely
                    this.addEventListener && this.addEventListener('readystatechange', function () {
                        try {
                            if (this.readyState === 4) {
                                // 仅在特定接口上处理,避免影响其它请求
                                const reqUrl = (this.__jimeng_url || '').toString();
                                if (!(reqUrl.indexOf('get_asset_list') !== -1 || reqUrl.indexOf('get_history_by_ids') !== -1)) {
                                    return; // 非目标接口,直接忽略
                                }

                                const txt = this.responseText;
                                if (typeof txt === 'string' && txt.length > 100) {
                                    // 尝试直接解析为 JSON
                                    try {
                                        const parsed = JSON.parse(txt);
                                        // 新增:记录 get_history_by_ids 返回的顶级 key -> data
                                        tryRecordHistoryFromResponse(parsed, this.__jimeng_url);
                                    } catch (e) {
                                        // 回退:查找第一个 '{' 子串并尝试解析
                                        try {
                                            const idx = txt.indexOf('{');
                                            if (idx >= 0) {
                                                const sub = txt.slice(idx);
                                                const parsed2 = JSON.parse(sub);
                                                // 新增回退解析时也尝试记录
                                                tryRecordHistoryFromResponse(parsed2, this.__jimeng_url);
                                            }
                                        } catch (e2) {
                                            // 忽略不可解析的文本
                                        }
                                    }
                                }
                            }
                        } catch (e) {
                            // 忽略单次处理错误,避免影响页面
                        }
                    });
                } catch (e) {
                    // 忽略 attach 错误
                }
                return _send.apply(this, arguments);
            };

            // 标记已打补丁
            try { XProto.__jimeng_patched = true; } catch (e) { }
            console.info('jimeng: XHR interceptor installed');
        } catch (e) {
            console.warn('jimeng: interceptXHR failed', e);
        }
    }

    // 替换原先注入/监听调用:直接安装 XHR 拦截器(同时保留 fetch 拦截作为回退)
    try {
        interceptXHR();
    } catch (e) {
        console.warn('jimeng: failed to install XHR interceptor', e);
    }

    // 优先使用 GM_xmlhttpRequest 绕过页面 CSP,回退到 fetch
    function fetchBypassCSP(url, options = {}) {
        // options: { method, headers, responseType, onProgress } - responseType支持 'arraybuffer' 或 'blob' 等
        const method = options.method || 'GET';
        const headers = options.headers || {};
        const responseType = options.responseType || 'arraybuffer';
        const onProgress = options.onProgress; // 进度回调函数

        // 如果 Tampermonkey 提供 GM_xmlhttpRequest,则使用它(可绕过页面 CSP)
        if (typeof GM_xmlhttpRequest === 'function') {
            return new Promise((resolve, reject) => {
                try {
                    GM_xmlhttpRequest({
                        method: method,
                        url: url,
                        headers: headers,
                        responseType: responseType,
                        onprogress(progressEvent) {
                            // 调用进度回调
                            if (typeof onProgress === 'function') {
                                onProgress(progressEvent);
                            }
                        },
                        onload(res) {
                            // res.response 在 responseType=arraybuffer 时是 ArrayBuffer
                            resolve({
                                ok: (res.status >= 200 && res.status < 300),
                                status: res.status,
                                statusText: res.statusText,
                                response: res.response,
                                responseHeaders: res.responseHeaders
                            });
                        },
                        onerror(err) {
                            reject(err);
                        },
                        ontimeout() {
                            reject(new Error('timeout'));
                        }
                    });
                } catch (e) {
                    reject(e);
                }
            });
        }

        // 回退:普通 fetch(受 CSP 限制,但无法支持进度监控)
        return (async () => {
            const resp = await fetch(url, { method, headers, mode: options.mode || 'cors' });
            const blob = await resp.blob();
            const arrayBuffer = await blob.arrayBuffer();
            return {
                ok: resp.ok,
                status: resp.status,
                statusText: resp.statusText,
                response: arrayBuffer
            };
        })();
    }

    /**
     * 进度弹窗管理器 - 管理多个下载弹窗的位置和堆叠
     */
    const ToastManager = {
        toasts: [], // 存储所有活跃的toast
        baseTop: 20, // 基础顶部距离
        spacing: 10, // toast之间的间距
        
        /**
         * 添加新的toast到管理器
         * @param {Object} toast - toast对象
         */
        add(toast) {
            this.toasts.push(toast);
            this.updatePositions();
        },
        
        /**
         * 从管理器中移除toast
         * @param {Object} toast - toast对象
         */
        remove(toast) {
            const index = this.toasts.indexOf(toast);
            if (index > -1) {
                this.toasts.splice(index, 1);
                this.updatePositions();
            }
        },
        
        /**
         * 更新所有toast的位置
         */
        updatePositions() {
            let currentTop = this.baseTop;
            this.toasts.forEach(toast => {
                if (toast.container && toast.container.parentNode) {
                    toast.container.style.top = currentTop + 'px';
                    // 获取toast的实际高度
                    const height = toast.container.offsetHeight;
                    currentTop += height + this.spacing;
                }
            });
        }
    };

    /**
     * 创建右上角进度弹窗
     * @returns {Object} 包含容器元素和更新方法的对象
     */
    function createProgressToast() {
        const container = document.createElement('div');
        container.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            min-width: 300px;
            max-width: 400px;
            background: rgba(0, 0, 0, 0.9);
            color: white;
            padding: 16px 20px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
            z-index: 99999;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
            font-size: 14px;
            line-height: 1.5;
            transition: top 0.3s ease, opacity 0.3s ease;
        `;

        const title = document.createElement('div');
        title.style.cssText = `
            font-weight: 600;
            margin-bottom: 8px;
            font-size: 15px;
        `;
        title.textContent = '正在下载视频...';

        const progressBarBg = document.createElement('div');
        progressBarBg.style.cssText = `
            width: 100%;
            height: 4px;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 2px;
            margin: 8px 0;
            overflow: hidden;
        `;

        const progressBar = document.createElement('div');
        progressBar.style.cssText = `
            width: 0%;
            height: 100%;
            background: linear-gradient(90deg, #4CAF50, #8BC34A);
            border-radius: 2px;
            transition: width 0.3s ease;
        `;
        progressBarBg.appendChild(progressBar);

        const statusText = document.createElement('div');
        statusText.style.cssText = `
            font-size: 12px;
            color: rgba(255, 255, 255, 0.8);
            margin-top: 8px;
        `;
        statusText.textContent = '准备下载...';

        container.appendChild(title);
        container.appendChild(progressBarBg);
        container.appendChild(statusText);
        document.body.appendChild(container);

        const toast = {
            container,
            title,
            progressBar,
            statusText,
            /**
             * 更新进度
             * @param {number} percent - 进度百分比 (0-100)
             * @param {string} status - 状态文本
             */
            update(percent, status) {
                progressBar.style.width = percent + '%';
                if (status) statusText.textContent = status;
            },
            /**
             * 设置为成功状态
             * @param {string} message - 成功消息
             */
            success(message) {
                title.textContent = '✓ 下载完成';
                progressBar.style.background = 'linear-gradient(90deg, #4CAF50, #66BB6A)';
                statusText.textContent = message || '视频已保存';
            },
            /**
             * 设置为错误状态
             * @param {string} message - 错误消息
             */
            error(message) {
                title.textContent = '✗ 下载失败';
                progressBar.style.background = 'linear-gradient(90deg, #f44336, #e57373)';
                statusText.textContent = message || '请重试';
            },
            /**
             * 移除弹窗
             * @param {number} delay - 延迟时间(毫秒)
             */
            remove(delay = 0) {
                setTimeout(() => {
                    container.style.opacity = '0';
                    setTimeout(() => {
                        if (container.parentNode) {
                            container.parentNode.removeChild(container);
                        }
                        // 从管理器中移除
                        ToastManager.remove(toast);
                    }, 300);
                }, delay);
            }
        };

        // 添加到管理器
        ToastManager.add(toast);

        return toast;
    }

    /**
     * 下载视频通用函数(带进度显示)
     * @param {string} url - 视频URL
     * @param {string} filename - 可选的文件名
     */
    async function downloadVideo(url, filename) {
        const toast = createProgressToast();
        
        try {
            const res = await fetchBypassCSP(url, { 
                responseType: 'arraybuffer', 
                method: 'GET',
                onProgress: (event) => {
                    if (event.lengthComputable) {
                        const percent = Math.round((event.loaded / event.total) * 100);
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        const totalMB = (event.total / 1024 / 1024).toFixed(2);
                        toast.update(percent, `${loadedMB} MB / ${totalMB} MB (${percent}%)`);
                    } else {
                        // 无法获取总大小时显示已下载量
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        toast.update(50, `已下载 ${loadedMB} MB...`);
                    }
                }
            });
            
            if (!res || !res.ok) throw new Error('请求失败,status=' + (res && res.status));
            
            toast.update(100, '处理文件中...');
            
            const arrayBuffer = res.response;
            const blob = new Blob([arrayBuffer], { type: 'video/mp4' });
            const objectUrl = URL.createObjectURL(blob);

            const link = document.createElement('a');
            const now = new Date();
            const dateStr = now.toISOString()
                .slice(0, 19)
                .replace('T', '_')
                .replace(/:/g, '-');
            link.download = filename || `RWater_${dateStr}.mp4`;
            link.href = objectUrl;
            link.target = '_blank';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);

            URL.revokeObjectURL(objectUrl);
            
            toast.success('视频已开始下载');
            toast.remove(1000);
        } catch (e) {
            console.error('下载失败:', e);
            toast.error(e.message || '下载失败,请重试');
            toast.remove(2000);
        }
    }
    /**
     * 初始化即梦网站的功能
     */
    function initJimengSite() {
        const observer = new MutationObserver(() => {
            jimeng_addVideoDownloadButton();
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['style', 'class']
        });

        // Also add event listeners for mouse interactions that might trigger UI changes
        document.addEventListener('mouseover', () => {
            setTimeout(() => {
                jimeng_addVideoDownloadButton();
            }, 100);
        }, true);

        console.log('即梦: 初始化无水印下载工具');
        jimeng_addVideoDownloadButton();
    }

    /**
     * 创建即梦视频下载按钮
     * @returns {HTMLElement} 下载按钮元素
     */
    function jimeng_createVideoDownloadButton() {
        const button = document.createElement('div');
        button.className = 'lv-btn lv-btn-secondary lv-btn-size-default lv-btn-shape-square baseButton-LQGhrC download-btn';
        const div = document.createElement('div');
        div.textContent = '下载无水印视频';
        button.appendChild(div);

        button.title = '无水印下载';
        button.style = '--right-padding: 14px; align-items: center;background-color: var(--bg-block-primary-default); border-radius: 8px; color: var(--text-primary, #f5fbff); display: flex; font-family: PingFang SC;font-size: 12px; font-weight: 400; gap: 4px; justify-content: center; line-height: 20px; padding: 8px var(--right-padding) 8px 12px; position: relative;'
        return button;
    }

    function jimeng_getVideoDataId(downloadBtn) {
        console.log('即梦: 开始查找视频元素...');
        const parentItem = downloadBtn.closest('[class*="item-"]');
        return parentItem ? parentItem.getAttribute('data-id') : null;
    }
    /**
     * 获取即梦视频URL
     * @param {Element} downloadBtn - 下载按钮元素
     * @returns {string|null} 视频URL或null
     */
    function jimeng_getVideoUrl(downloadBtn) {
        console.log('即梦: 开始查找视频元素...');
        const parentItem = downloadBtn.closest('[class*="item-"]');

        const videoWrapper = parentItem.querySelector('div[class^="video-node-wrapper-"]');
        if (!videoWrapper) {
            alert('未找到视频');
            console.log('即梦: 未找到videoWrapper');
            return null;
        }

        let videoElement = videoWrapper.querySelector('video');
        if (!videoElement) {
            alert('未找到视频');
            console.log('即梦: 未找到video元素');
            return null;
        }

        if (videoElement && videoElement.src) {
            console.log('即梦: 找到视频元素:', {
                src: videoElement.src,
                className: videoElement.className,
                width: videoElement.width,
                height: videoElement.height,
                naturalWidth: videoElement.naturalWidth,
                naturalHeight: videoElement.naturalHeight,
                attributes: Array.from(videoElement.attributes).map(attr => `${attr.name}="${attr.value}"`).join(', ')
            });
            return videoElement.src;
        }

        alert('未找到视频');
        console.log('即梦: 未找到合适的视频');
        return null;
    }

    /**
     * 添加视频下载按钮到即梦页面
     */
    function jimeng_addVideoDownloadButton() {
        const videoContents = document.querySelectorAll('div[class^="video-record-"]');
        if (!videoContents || videoContents.length === 0) {
            return;
        }
        for (let i = 0; i < videoContents.length; i++) {
            const topActionBar = videoContents[i].querySelector('div[class^="record-bottom-slots-"]');
            if (!topActionBar) {
                continue;
            }
            if (topActionBar.querySelector('div[class^="image-record-content-"]')) {
                continue;
            }
            if (topActionBar.querySelector('.download-btn')) {
                continue;
            }
            jimeng_processSingleVideoButton(topActionBar);
        }
    }

    /**
     * 处理单个视频按钮(已简化:不再依赖 jimengVideoMap)
     * @param {Element} topActionBar - 操作栏元素
     */
    function jimeng_processSingleVideoButton(topActionBar) {
        const downloadBtn = jimeng_createVideoDownloadButton();
        topActionBar.insertBefore(downloadBtn, topActionBar.firstChild);
        console.info('即梦: 添加视频下载按钮');

        downloadBtn.addEventListener('click', async () => {
            console.log('即梦: 点击视频下载按钮');
            const videoDataId = jimeng_getVideoDataId(downloadBtn);
            const videoUrl = jimeng_getVideoUrl(downloadBtn);
            console.log('即梦: 获取到的视频URL:', videoUrl);
            let finalUrl = videoUrl;

            try {
                // 2) 若仍然没有 finalUrl,尝试用 videoDataId 从 jimengDataMap 中查找 main_url
                if (videoDataId) {
                    const cached = getJimengData(videoDataId);
                    if (cached) {
                        const extracted = extractMainUrlFromData(cached);
                        if (extracted) {
                            finalUrl = extracted;
                            console.log('即梦: 从 jimengDataMap 提取到 main_url,使用该 URL 下载', finalUrl);
                        }
                    }
                }

                if (!finalUrl) {
                    alert('未找到可下载的视频链接');
                    console.error('即梦: 未能解析出有效的下载 URL');
                    return;
                }

                downloadBtn.style.opacity = '0.5';
                downloadBtn.style.pointerEvents = 'none';
                await downloadVideo(finalUrl);
            } catch (e) {
                console.error('即梦: 下载过程中出错', e);
                alert('下载失败,请重试(查看控制台以获得更多信息)');
            } finally {
                downloadBtn.style.opacity = '1';
                downloadBtn.style.pointerEvents = 'auto';
            }
        });
    }

    initJimengSite();
})();

[bsgit user="2erTwo6"]Smooth-Gateway[/bsgit]
上游 API 提供商提供的流式传输可能是粗糙的、一大块一大块出现的,体感上不 “丝滑”

Gemini Balance 的流式传输优化功能就解决了这个痛点,在玩酒馆等需要流式传输的场景下,能够极大的提高体验,但是就如项目名那样,只能给 Gemini 用。

于是就有了这个项目,参考了 Gemini Balance 的思路,可以插入到任何一个 OpenAI 格式的 API 服务中间,对流式传输进行后处理,把粗糙的流切成细腻的流,再推送给最终的 AI 应用。

目前仅支持接入 OpenAI 格式的 API,推荐的使用方法是先接入 New API,再套一层这个

快速开始

前提: 您已安装 Docker。

  1. 克隆本仓库:

    git clone https://github.com/2erTwo6/Smooth-Gateway.git
    cd Smooth-Gateway
  2. 创建并编辑配置文件:
    将模板文件复制为您的本地配置文件。

    cp .env.example .env

    然后使用您喜欢的编辑器(如 nano 或 vim)打开 .env 文件,并至少填入必需的 UPSTREAM_API_URL。

  3. 构建 Docker 镜像:

    docker build -t smooth-gateway .
  4. 运行容器:
    使用 --env-file 参数,Docker 会自动加载您的 .env 文件。

    docker run -d \
      --name my-smooth-gateway \
      -p 3001:3001 \
      --env-file .env \
      --restart unless-stopped \
      smooth-gateway

    现在,您的流式优化网关已根据您的 .env 文件配置,在 http://localhost:3001 上运行!

接下来,只需要在你的 AI 应用的 API URL 那里输入 http://localhost:3001 即可(假设你的 AI 应用和此 API 网关部署在同一机器?)

早上好!已为您整理截至目前的 GitHub 每日热点项目。以下列表包含了项目名称、简介及访问地址,信息清晰易读。

今日 GitHub 热点项目列表:

  1. Zie619/n8n-workflows

  2. TapXWorld/ChinaTextbook

  3. traefik/traefik

  4. google/adk-go

  5. iptv-org/iptv

  6. HKUDS/LightRAG

  7. bobeff/open-source-games

  8. playcanvas/engine

  9. yangshun/tech-interview-handbook

  10. volcengine/verl

  11. nvm-sh/nvm

  12. yeongpin/cursor-free-vip

  13. wolfpld/tracy

  14. GibsonAI/Memori

  15. milvus-io/milvus

  16. sansan0/TrendRadar

  17. MustardChef/WSABuilds

  18. microsoft/call-center-ai

https://track.linso.ai/zh/execution/cmi6qhyqy00kppfqtpp8fjfco

[bsgit user="fish2018"]YPrompt[/bsgit]

功能

  • 支持OpenAI、Anthropic、Google Gemini和自定义AI服务商
  • 支持内置提供商,将 builtin-providers.example.json 复制为 builtin-providers.json 并修改您需要的配置即可。
  • 自动生成和手动步进两种执行模式
  • 支持Markdown/XML格式切换和中英文互译
  • 支持对话引导挖掘用户需求后自动生成系统提示词
  • 支持系统提示词优化
  • 支持基于系统提示词和对话上下文的用户提示词优化
  • 支持效果对比,同时也是一个调试台,可以自定义模型,系统提示词
  • 支持提示词版本管理
  • 支持sqlite和mysql数据库
  • docker一键部署
  • 支持用户密码/linux.do认证

在线体验

使用Linux.do登录 或 使用账号:demo/demo

生成提示词
生成提示词
系统提示词优化和对比
系统提示词优化和对比
用户提示词优化和对比
用户提示词优化和对比
提示词版本管理
提示词版本管理

早上好,这是Linso为您整理的过去24小时内最重要的人工智能与科技动态报告。

一、模型发布与重大公告

过去24小时最受瞩目的事件是2025百度世界大会,发布了多项重要更新。同时,全球AI竞赛仍在继续,印度发布了本土AI聊天机器人。

  • 百度发布新一代AI硬件与智能体:在2025百度世界大会上,百度发布了新一代昆仑芯AI芯片 M100(针对推理)和 M300(针对训练与推理),分别预计于2026年和2027年上市。同时,推出了可自我演化的智能体 “伐谋” 和实时互动数字人 “秒哒 2.0”。来自aninews.in网站。
  • 印度发布首个本土AI聊天机器人Kyvex:印度企业家宣布推出名为 Kyvex 的AI聊天机器人,定位为ChatGPT和Perplexity的竞争者,由印度本土团队基于自研大语言模型构建,专注于提供高准确度的研究导向型回复。来自aninews.in网站。
  • 阿布扎比推出AI环境监测门户:阿布扎比环境署(EAD)推出了一个由AI驱动的环境状况门户(ADSOEP),利用大模型自动分析环境数据,以提供实时的环境洞察和预测。来自arabnews.com网站。
  • 深圳高交会开幕:第27届中国国际高新技术成果交易会在深圳开幕,重点展示人工智能、半导体等前沿技术。来自xhby.net网站。

二、最新研究论文精选 (来自arXiv)

计算机视觉和语言模型效率是近期研究的热点。

  1. 《Depth Anything 3: Recovering the Visual Space from Any Views》 (arXiv:2511.10647): 深度估计模型Depth Anything的第三代,旨在从任意视角恢复视觉空间,是3D视觉领域的重要进展。来自arxiv.org网站。
  2. 《ParoQuant: Pairwise Rotation Quantization for Efficient Reasoning LLM Inference》 (arXiv:2511.10645): 提出一种名为ParoQuant的成对旋转量化方法,旨在提升大语言模型(LLM)在推理时的效率。来自arxiv.org网站。
  3. 《Robot Crash Course: Learning Soft and Stylized Falling》 (arXiv:2511.10635): 一项有趣的研究,让机器人学习如何以柔和且有风格的方式跌倒,对提升机器人安全性和物理交互能力有重要意义。来自arxiv.org网站。
  4. 《Towards Emotionally Intelligent and Responsible Reinforcement Learning》 (arXiv:2511.10573): 探讨如何构建具备情感智能和责任感的强化学习系统,是AI对齐和伦理领域的前沿探索。来自arxiv.org网站。

三、热门开源项目 (来自GitHub)

AI Agent(智能体)的开发工具和基础设施成为近期开源社区的焦点。

  • google/adk-go & google/adk-python: Google开源的代码优先工具包,分别提供Go和Python版本,用于构建、评估和部署复杂的AI代理(Agent)。来自github.com网站。
  • microsoft/call-center-ai: 微软开源的项目,允许开发者通过API调用或直接拨打电话号码的方式与AI电话代理进行交互。来自github.com网站。
  • RLinf/RLinf: 一个为基础模型(如LLM、VLM)进行强化学习后训练而设计的开源基础设施,具有高度灵活性和可扩展性。来自github.com网站。

四、新工具与产品更新

AI在各行业的应用工具持续涌现,同时AI相关的政策和基础设施建设也在推进。

  • 1ForAll.ai:一个新推出的统一AI内容平台,整合了OpenAI、Google、AWS等多个主流AI模型的能力,提供文本、图像、视频和语音生成服务,旨在用一个平台取代多个订阅。来自entrepreneur.com网站。
  • NXTFACE:印度首个面向Z世代的AI驱动护肤品牌,其应用可通过摄像头实时扫描和分析用户皮肤状况,并提供定制化护肤方案。来自medianews4u.com网站。
  • Google投资400亿美元建数据中心:Google宣布将在美国德克萨斯州投资400亿美元建设三个新的数据中心,以满足其先进AI模型对基础设施的需求。来自texastribune.org网站。

https://track.linso.ai/zh/execution/cmi1182ii021kkp1jfcqdj1cu

早上好!这是为您整理的过去24小时内最重要的人工智能与科技动态报告。

一、模型发布与产品更新

过去一天,人工智能领域迎来了两大巨头的重磅模型更新,标志着模型能力在推理、多模态和智能化方面迈出新步伐。

  1. OpenAI 正式发布 GPT-5.1 系列模型
  2. 核心亮点:新系列模型旨在深度融合“智商与情商”,重点强化了推理能力和个性化交互体验。
  3. 主要模型:

    • GPT-5.1 Instant:引入“自适应推理”功能,在处理数学、编程等复杂任务时会主动延长思考时间以保证准确率。
    • GPT-5.1 Thinking:默认语气增强同理心,能更通俗地解释技术概念。
    • GPT-5.1-Auto:可自动为用户匹配最合适的模型,简化使用流程。
  4. 推送计划:已开始向全球付费用户推送,随后将覆盖免费用户。
    来源:来自openai.com, 21jingji.com
  5. 百度发布文心大模型5.0及系列智能体
  6. 文心大模型5.0:参数量达到2.4万亿,采用原生全模态统一建模技术,具备强大的全模态理解与生成能力。
  7. GenFlow 3.0 (通用智能体):作为全球规模最大的通用智能体,活跃用户已突破2000万,采用中心化原生Multi-Agent架构。
  8. “百度伐谋” (自我演化超级智能体): 全球首个可商用的自我演化超级智能体,能在交通、能源等领域发现人类未曾发现的全局最优解。
    来源:来自sina.com.cn

二、最新AI论文精选 (arXiv)

arXiv平台在11月14日新增了大量AI相关研究,涵盖了AI伦理、大模型应用、机器学习理论等多个方向。

arXiv ID标题精炼核心内容领域
arXiv:2511.10497大模型在医疗领域的应用综述系统性地回顾和总结了大型语言模型(LLM)在医疗保健中的应用、挑战与前景。cs.AI, cs.CL
arXiv:2511.10524AI时代的科学研究反思探讨在人工智能时代,如何重新思考和定义科学研究的范式与方法。cs.AI
arXiv:2511.10573迈向情感智能与负责任的强化学习研究如何让强化学习智能体具备情感理解能力,并以更负责任的方式进行决策。cs.LG, cs.AI
arXiv:2511.10571分布偏移学习的通用框架提出了一个通用的学习框架,旨在解决模型在训练数据和测试数据分布不一致时性能下降的问题。cs.LG

来源:来自arxiv.org

三、热门开源项目 (GitHub)

近期GitHub社区涌现出多个备受关注的AI项目,尤其在AI智能体和模型训练领域。

Column 1Column 2Column 3
项目名称描述看点
nanochat由AI专家Andrej Karpathy开源,一个用约8000行代码实现类似ChatGPT对话功能的项目。以极低成本(约100美元)和高效流程训练小型语言模型,包含从数据准备到强化学习的完整流程,Star数已达29.1K。
google/adk-go谷歌开源的Go语言工具包,用于构建、评估和部署复杂灵活的AI智能体。代码优先,专为构建AI Agents设计,显示出大厂在智能体开发工具上的布局。
microsoft/call-center-ai微软开源的AI呼叫中心解决方案。可通过API或直接拨打电话号码与AI机器人通话,展示了AI在企业服务自动化领域的应用潜力。

来源:来自github.com, aixploria.com

四、其他重大科技动态

  1. 国务院常务会议强调AI赋能:11月14日的会议指出,要加快新技术新模式创新应用,强化人工智能融合赋能,聚焦重点行业开发新产品,以消费升级引领产业升级。 (来源:来自people.com.cn)
  2. 加密货币市场大幅下挫:过去24小时,比特币跌破10万美元,以太坊跌破3100美元,导致全网大量用户被清算。 (来源:来自wallstreetcn.com)
  3. 特斯拉动态:公司Cybertruck和Model Y项目负责人相继离职,同时在华月销量创近三年新低。另一方面,公司计划扩建得州工厂,目标年产1000万台Optimus人形机器人。 (来源:来自nbd.com.cn)

一、前提条件与准备工作

适用型号

  • 小米网关 3(MGL03)
  • Aqara Hub M1S CN / EU
  • Aqara Hub E1(ZHWG16LM)
  • Mi Smart Hub 2 Pro(ZHWG16LM)
  • Aqara Hub G3 / 空调伴侣 P3 / USB 墙插网关

必要条件

  1. 网关 已连接米家(Mi Home)App;
  2. 获取设备:

    • IP 地址
    • Token(令牌)

获取方式(任选其一):

  • 使用提取工具:Miio Token Extractor
  • 或者使用 Home Assistant + XiaomiGateway3 插件(自动显示 token)

二、开启 Telnet 的三种方法

方法一(推荐)—— 通过 XiaomiGateway3 组件(Home Assistant)

  1. 在 Home Assistant 中安装 XiaomiGateway3 组件;
  2. 打开集成页面 → 找到对应网关;
  3. 点击 “Open Telnet Command”;
  4. 输入以下命令(保持原样):

【网关 3(MGL03)】

{"method":"set_ip_info","params":{"ssid":"\"\"","pswd":"123123 ; passwd -d admin ; echo enable > /sys/class/tty/tty/enable; telnetd"}}

【Aqara Hub E1】

{"method":"set_ip_info","params":{"ssid":"\"\"","pswd":"123123 ; /bin/riu_w 101e 53 3012; telnetd"}}

执行后,等待几秒即可开启 Telnet。

方法二(推荐给非 Home Assistant 用户)—— 使用 php-miio 工具

  1. 下载并安装 php-miio
  2. 在命令行中运行:

    php miio-cli.php --ip 网关IP --token 网关Token --sendcmd '{"id":123,"method":"set_ip_info","params":{"ssid":"\"\"","pswd":"123123 ; passwd -d admin ; echo enable > /sys/class/tty/tty/enable; telnetd"}}'

    对于不同型号的设备,请替换命令中的 pswd 内容,例如:

    • Hub E1:/bin/riu_w 101e 53 3012
    • Hub G3 或 M1S:passwd -d root; /bin/riu_w 101e 53 3012; telnetd

方法三 —— 使用 python-miio 工具

  1. 安装依赖:

    pip install python-miio
  2. 执行命令:

    miiocli device --ip 网关IP --token 网关Token raw_command set_ip_info '{"ssid":"\"\"","pswd":"123123 ; passwd -d admin ; echo enable > /sys/class/tty/tty/enable; telnetd"}'

[bsmessage type="common" color="red" title="提示"]如果遇到 SyntaxError 或 user ack timeout,检查命令引号(推荐整句外双引号、内部单引号)。[/bsmessage]

三、Telnet 登录操作

Telnet 启动后,通过命令行连接:

telnet 网关IP

设备型号用户名密码
小米网关3admin(空)
Aqara Hub / M1S / E1 / G3 / Hub 2 Proroot(空)

连接成功后,你将看到设备命令行提示符。

四、安装自定义固件(可选)

获取固件
参考官方固件仓库:

https://github.com/zvldz/mgl03_fw/tree/main/firmware#the-easy-way

安装命令(示例)

cd /tmp && wget -O /tmp/curl "http://master.dl.sourceforge.net/project/mgl03/bin/curl?viasf=1" && chmod a+x /tmp/curl
/tmp/curl -s -k -L -o /tmp/m1s_update.sh https://raw.githubusercontent.com/niceboygithub/AqaraM1SM2fw/main/modified/M1S/m1s_update.sh
chmod a+x /tmp/m1s_update.sh && /tmp/m1s_update.sh

[bsmessage type="common" color="yellow" title="注意"]不同型号设备 CPU 架构不同(如 mgl03 ≠ m1s),固件不通用;
若提示 unexpected "(" 代表文件格式或架构不兼容;
刷机前务必备份原系统。[/bsmessage]

五、安全建议

  1. 默认 Telnet 无密码,非常危险!
  2. 建议登录后立即设置密码:
    passwd
  3. 关闭远程访问端口
  4. 若不再使用 Telnet,可执行:
    killall telnetd
  5. 断电重启后确认服务状态
  6. 某些设备需重新发送命令以重新启用 telnet。

六、常见错误与解决方法

错误信息原因与解决办法
SyntaxError: invalid syntax命令格式错误;引号嵌套需正确。
user ack timeout设备未响应;确认 IP、Token、网络是否通畅。
/tmp/curl: unexpected "("执行了错误架构的二进制文件。
登录失败用户名或密码错误,或未成功启动 telnet。

七、总结与建议

用户类型推荐方法理由
Home Assistant方法一(XiaomiGateway3)简单直观、兼容好。
技术用户(Linux/PHP)方法二(php-miio)稳定性最佳、命令清晰。
Python 使用者方法三(python-miio)可扩展性强,适合自动化控制。

安利一个的 Python 工具 —— WeSpy

它可以帮助我们自动获取微信公众号文章,并转换为标准 Markdown,还支持图片防盗链处理、多格式导出等实用功能,对写作、归档、知识管理特别友好。

如果你平时会保存公众号文章、整理成笔记、同步到博客或 Obsidian,那这个工具绝对值得一试。

主要功能

  • 支持直接抓取公众号文章 URL 无需登录,无需 APP,复制链接即可处理。
  • 自动生成 Markdown 包括标题、正文、引用、分隔线、加粗、列表等格式全都能还原。
  • 图片防盗链处理 内置公众号图片重定向与源地址修复,解决 “无法加载图片” 的老大难问题。
  • 多种输出格式

    • Markdown
    • HTML
    • JSON
    • 结构化文本
  • 可作为命令行工具,也可作为 Python 库使用

安装使用方式

pip install wespy
wespy https://mp.weixin.qq.com/s/xxxxxxxxxxxx -o article.md
from wespy import WeChatSpider

url = "https://mp.weixin.qq.com/s/xxxxxxxxxxxx"
spider = WeChatSpider()

result = spider.fetch(url)

# Markdown 输出
markdown_text = result.markdown

with open("article.md", "w", encoding="utf-8") as f:
    f.write(markdown_text)

更详细的使用方式大家可以参考项目文档
[bsgit user="tianchangNorth"]WeSpy[/bsgit]

过去24小时重要人工智能与科技动态总结

您好!这是为您整理的过去24小时(截至2025年11月14日)最重要的AI与科技动态,重点梳理了模型发布、前沿论文、开源项目及其他重大公告。

一、模型与产品发布 (Model & Product Releases)

  1. 百度世界大会发布多款AI成果

    • 文心大模型5.0 (ERNIE 5.0):发布新一代原生全模态基础大模型,参数量达2.4万亿,支持文本、图像、音视频的统一处理,已通过文心一言向公众开放。(来自新华网)
    • 智能体“伐谋”:推出全球首个商用自进化代理(Agent),能模拟顶尖算法专家,处理交通、能源等复杂场景的动态优化问题。(来自21世纪经济报道)
    • 其他更新:发布了下一代“实时互动型数字人”慧播星、升级了通用AI代理GenFlow 3.0,并推出了无代码开发工具秒哒2.0。(来自新华网)
  2. OpenAI 发布 GPT-5.1 系列模型

    • OpenAI于11月12日正式发布了GPT-5.1,包含两个版本:GPT-5.1 Instant 引入“自适应推理”功能,更可靠地遵循指令;GPT-5.1 Thinking 则注重效率与更富同理心的交互风格。(来自investing.com)
  3. 谷歌与芯原联合推出 Coral NPU IP

    • 双方联合推出专为端侧大语言模型应用设计的神经处理单元(NPU)IP,基于RISC-V架构,旨在赋能始终在线、超低功耗的边缘AI设备。(来自chnfund.com)

二、最新论文与研究成果 (New Papers & Research)

  1. Google DeepMind 发布 AlphaProof 数学证明系统

    • 该研究成果发表于《自然》杂志。AlphaProof 是一个能证明复杂数学理论的AI系统,它在2024年国际数学奥林匹克竞赛中,取得了相当于银牌的成绩,展示了AI在高级数学推理领域的重大突破。(来自中国新闻网)
  2. Google 发布 AI 驱动的全球天然林地图

    • Google DeepMind 与 Google Research 合作,利用AI和卫星数据创建了“2020年世界天然林地图”,能有效区分天然林与人工林,为全球环境保护和供应链合规提供关键数据支持。(来自research.google)
  3. arXiv 平台最新研究

    • 多智能体学习 (arXiv:2511.09535):提出一种通过理性策略梯度实现鲁棒且多样化的多智能体学习方法。
    • 生成式AI安全 (arXiv:2511.09493):提出“共识采样”方法,旨在提升生成式AI的安全性。
    • VLM能力评估 (arXiv:2511.09483):发布CrochetBench基准,评估视觉-语言模型在钩针编织领域的程序生成能力,发现模型在长距离推理上仍有局限。(以上均来自arxiv.org)

三、热门开源项目 (Open-Source Projects)

  1. TrendRadar (sansan0/TrendRadar)

    • 一个AI舆情监控与热点分析工具,迅速登上GitHub趋势榜首。它能监控35个主流平台的热点,并利用AI进行趋势追踪、情感分析等,支持多渠道推送。(来自github.com)
  2. strix (usestrix/strix)

    • 一个开源的AI黑客代理工具,专为自动化渗透测试和漏洞验证设计,能模拟攻击路径并生成修复建议,受到安全社区的广泛关注。(来自github.com)
  3. adk-go (google/adk-go)

    • 谷歌开源的Go语言AI Agent开发工具包,用于构建、评估和部署复杂的AI代理,支持多智能体协作和Gemini等大模型。(来自github.com)
  4. LightRAG (HKUDS/LightRAG)

    • 一个旨在提供简单、快速的检索增强生成(RAG)解决方案的轻量级框架,相关工作已被EMNLP 2025会议收录。(来自github.com)

四、其他重要科技动态

  1. 李飞飞提出“空间智能”定义AI下一时代

    • 斯坦福大学教授李飞飞认为,AI的下一个十年将由“空间智能”定义,即让机器理解和互动物理世界。她强调,实现这一目标需要构建超越当前大模型的“世界模型”。(来自澎湃新闻)
  2. 第二十七届高交会在深圳开幕

    • 本届高交会于11月14日开幕,重点聚焦人工智能、机器人及半导体等前沿领域,预计将有多款全球首发的AI产品亮相。(来自21世纪经济报道)
  3. 科技股市场波动

    • 美股科技股普遍下跌,纳斯达克指数连跌三日,特斯拉、英伟达等巨头股价承压。比特币价格跌破10万美元关口。(来自21世纪经济报道)

https://track.linso.ai/zh/execution/cmhy5v31m00xi4hvizybq8hqg

这是一款免费开源的漫画阅读器,以插件的形式提供了来自全球的上千个漫画图源,可筛选包含中文、英文等多种语言的超一千个图源,不管是包子某漫还是蛙蛙一耽,统统可以一键搜索,甚至可以直接通过国外图源看生肉.
软件需要导入源仓库地址,这有三个有效的地址,里面的插件都差不多,随便复制一个就好

仓库的导入方式也很简单,全程开启魔法 ,在软件下方 “浏览”——“插件”——“插件仓库”,点击右下方添加,粘贴上方的仓库地址就 ok

IOS 无限制漫画神器,自带 999 + 漫画源 1
仓库添加好后刷新插件界面,右上角可以筛选插件语言,选择简体中文,下载你心仪的漫画网插件,就可以去书架搜索想要的漫画
IOS 无限制漫画神器,自带 999 + 漫画源 2
软件叫 Tachimanga,用苹果美区账户直接搜索下载即可,网上有很多相关的教程,这个软件不仅仅可以看漫画,通过不同的插件可以实现很多功能,各位佬友可自行探索。安卓和 ios 的图源是通用的,安卓端叫 Mihon 需要注意的是,使用软件全程需要魔法
IOS 无限制漫画神器,自带 999 + 漫画源