知乎林业 Logo
首页
软件
教程
规程
林业标准检索导航
关于网站
登录 →
知乎林业 Logo
首页 软件 教程 规程 林业标准检索导航 关于网站
登录
  1. 首页
  2. 软件
  3. 一堆AI开发历程

一堆AI开发历程

0
  • 软件
  • 发布于 2026-05-30
  • 10 次阅读
明亮先生
明亮先生
目录
当前文章没有目录

一、项目开发背景与初衷

自2024年起,各类AI工具已然成为我日常工作与生活中不可或缺的辅助工具。随着人工智能技术快速迭代,豆包、Kimi、腾讯元宝、文心一言、通义千问等多款AI平台相继成熟,不同工具拥有各自独特的优势,适配文本创作、代码编写、图片生成、视频制作、智能搜索等差异化工作场景。

在长期使用过程中我发现,不同AI工具的功能侧重点各不相同:有的擅长长文本解析,有的专注视频生成,有的深耕AI绘画与智能搜索。日常工作中,往往需要频繁切换多个平台,操作繁琐、效率低下。2025年底,我萌生了一个想法:能否打造一个一站式聚合工具,整合市面上主流的各类AI平台,实现多AI工具对比使用、自由切换,同时根据不同工具的功能特性搭建专属工作流,将文本撰写、图片生成、视频合成、代码开发等全流程功能整合在一个页面中,真正实现一站式AI办公。

我本身没有编程基础,仅能简单阅读代码、理解基础逻辑。为了落地这个想法,我确定了全新的开发模式:由AI自主编写代码,我全程负责需求梳理、功能测试与项目调试。经过对豆包、DeepSeek、Kimi等多款AI编程工具的实测对比,最终选择Kimi作为核心开发辅助工具,开启了「一堆AI」项目的开发之路

二、项目开发过程与迭代历程

项目初期,我一直在思考工具的整体形态与开发思路。一次偶然的机会,我接触到「电子书全网搜」浏览器插件,该插件拥有右键唤起AI搜索的实用功能。我尝试解压插件后台代码,将其导入Kimi中分析复用,以此为基础,开启了项目的初步开发。

最初,我让AI将插件逻辑改写为独立网站程序,初代版本界面简陋、功能单一、实用性较差。但在一次次调试和修改需求的过程中,我逐步理清了前端、后端的核心开发逻辑。结合自身使用需求,我最终确定项目采用纯前端架构,舍弃传统后端服务。

这样的设计核心优势十分明显:全程采用本地存储模式,用户所有账号数据、使用记录仅保存在个人浏览器与用户自有AI账号中,无需上传至服务器。既规避了服务器存储压力不足的问题,也最大程度保护了用户隐私数据,真正做到轻量化、安全化、无门槛使用。

后续我持续迭代优化,不断新增功能、优化界面、修复BUG,对UI样式、交互逻辑、功能模块、适配效果进行全方位升级。从简陋的初代网页,逐步打磨为功能完善、界面美观、操作流畅的一站式AI聚合平台,最终正式命名项目为「一堆AI」。

三、项目成品与核心功能

「一堆AI」是一款轻量化、纯前端、本地化的一站式AI聚合工具,整合国内主流AI平台,支持多工具并行使用、自定义工作流、批量管理、主题切换等实用功能,无需部署、无需注册,打开即可使用,数据全程本地保存。

核心功能亮点:

  • 全品类AI工具聚合:整合豆包、Kimi、腾讯元宝、文心一言、通义千问、讯飞星火、可灵AI、秒画、纳米AI搜索等主流平台,覆盖AI对话、AI视频、AI绘画、AI编程、智能搜索五大核心场景。

  • 自定义工作流:支持自由组合多个AI工具,自定义保存专属工作流,一键批量打开组合工具,适配批量办公、内容创作、程序开发等高频场景。

  • 批量管理操作:内置批量选择、批量添加、批量加入工作流功能,支持工具卡片拖拽排序、单独刷新、全屏、关闭等精细化操作。

  • 个性化设置:支持浅色/深色主题一键切换、自定义添加私有AI工具、快捷键快捷操作、配置导入导出、数据重置等功能。

  • 极致安全轻量化:纯前端运行,无后端服务器,用户数据本地存储,无信息泄露风险,适配电脑端所有浏览器,运行流畅无卡顿。

项目完整网页源代码已开发完成,线上可直接访问使用,全程免费、无广告、无付费功能。

四、二次开发:从网页端到桌面端

2026年5月,为了进一步提升工具的实用性和便捷性,摆脱浏览器标签限制,我接触到aardio桌面端开发工具,决定对「一堆AI」进行二次开发,将网页版程序打包升级为独立桌面端软件。

依托前期网页版成熟的功能架构,结合AI辅助编写桌面端代码,我快速完成了桌面端适配开发。通过WebView2内核嵌套线上网页,完美复刻网页版全部功能,同时拥有独立桌面客户端形态,启动更快、使用更稳定、不受浏览器限制。

桌面端核心开发代码简洁轻量化,依托aardio实现窗口自适应、全屏展示、稳定加载,完整保留网页版所有AI聚合、工作流、个性化设置功能,实现了项目的跨端升级。

目前,「一堆AI」网页端+桌面端已全部开发完成,用户可自由选择网页在线使用或桌面客户端使用,全方位满足日常办公、内容创作、学习研究、程序开发等各类AI使用需求。

五、开发总结

本项目是我零编程基础下,依托AI全流程辅助完成的独立开发作品。从最初的创意构思、参考插件逻辑,到网页版反复迭代优化,再到桌面端跨端升级,全程践行「AI赋能创作、人为核心把控」的模式。

最终打造的「一堆AI」工具,解决了多AI工具频繁切换的痛点,通过工作流组合、批量管理、本地化安全存储等特色功能,大幅提升AI使用效率,同时轻量化、无门槛、高安全的特性,也让普通用户可以零成本体验一站式AI办公创作服务。后续我将持续收录更多优质AI平台、优化交互体验、新增实用工作流模板,持续迭代完善项目功能。

六、源代码

以下就是网站的源代码,网站链接为“一堆AI”(点击就可以进去)。


<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>一堆AI </title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }

        :root {
            --primary: #4cd561; --bg-primary: #f6f6f6; --bg-secondary: #ffffff;
            --text-primary: #303133; --text-secondary: #606266; --border: #e5e5e5;
            --shadow: rgba(0, 0, 0, 0.1); --hover-bg: #f5f5f5;
            --danger: #f56c6c; --warning: #f59e0b; --info: #1677ff;
            --chat-color: #4b70e2;
            --video-color: #ec4899;
            --code-color: #ff7a00;
            --search-color: #10b981;
            --image-color: #8b5cf6;
        }

        [data-theme="dark"] {
            --bg-primary: #1a1a1a; --bg-secondary: #2d2d2d;
            --text-primary: #ffffff; --text-secondary: #b0b0b0;
            --border: #404040; --hover-bg: #3d3d3d; --shadow: rgba(0, 0, 0, 0.3);
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: var(--bg-primary); color: var(--text-primary);
            height: 100vh; overflow: hidden;
        }

        #app { height: 100vh; display: flex; flex-direction: column; }

        /* 工具栏 */
        .toolbar {
            height: 50px; background: var(--bg-secondary); border-bottom: 1px solid var(--border);
            display: flex; align-items: center; justify-content: space-between;
            padding: 0 16px; box-shadow: 0 2px 8px var(--shadow); z-index: 100;
        }

        .logo { display: flex; align-items: center; gap: 10px; font-size: 18px; font-weight: 600; color: var(--primary); cursor: pointer; }
        .logo:hover { opacity: 0.8; }

        .logo-icon { width: 32px; height: 32px; position: relative; }
        .square { position: absolute; width: 14px; height: 14px; background: var(--primary); border-radius: 3px; box-shadow: 0 1px 3px rgba(0,0,0,0.2); }
        .square:nth-child(1) { top: 0; left: 0; opacity: 0.9; }
        .square:nth-child(2) { top: 4px; left: 9px; opacity: 0.7; background: #3cb853; }
        .square:nth-child(3) { top: 9px; left: 4px; opacity: 0.5; background: #2aa845; }

        .toolbar-actions { display: flex; gap: 4px; align-items: center; }

        .btn-icon {
            width: 32px; height: 32px; border: none; background: transparent;
            color: var(--text-secondary); border-radius: 6px; cursor: pointer;
            display: flex; align-items: center; justify-content: center;
            transition: all 0.2s; font-size: 14px;
        }

        .btn-icon:hover { background: var(--hover-bg); color: var(--primary); }
        .btn-icon.active { color: var(--primary); background: rgba(76, 213, 97, 0.1); }
        .btn-icon.workflow { color: var(--warning); }
        .btn-icon.workflow:hover { background: rgba(245, 158, 11, 0.1); color: var(--warning); }
        .btn-icon.workflow.active { color: var(--warning); background: rgba(245, 158, 11, 0.15); }

        /* 主内容区 */
        .main-container { flex: 1; display: flex; overflow: hidden; position: relative; }

        .engines-container {
            flex: 1; overflow-x: auto; overflow-y: hidden;
            display: flex; align-items: stretch; gap: 12px; padding: 12px;
            scroll-behavior: smooth;
        }

        .engines-container::-webkit-scrollbar { height: 6px; }
        .engines-container::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }

        /* 引擎卡片 */
        .engine-card {
            min-width: 400px; max-width: 50%; flex: 1;
            background: var(--bg-secondary); border-radius: 12px;
            border: 1px solid var(--border); display: flex; flex-direction: column;
            overflow: hidden; box-shadow: 0 2px 8px var(--shadow);
        }

        .engine-card.fullscreen {
            position: fixed; top: 0; left: 0; right: 0; bottom: 0;
            z-index: 1000; max-width: 100%; border-radius: 0; min-width: auto;
        }

        .engine-card.dragging { opacity: 0.5; transform: scale(0.95); }

        .card-header {
            height: 44px; padding: 0 12px; display: flex;
            align-items: center; justify-content: space-between;
            border-bottom: 1px solid var(--border); background: rgba(76, 213, 97, 0.05);
            flex-shrink: 0;
        }

        .engine-info { display: flex; align-items: center; gap: 8px; }

        .engine-avatar {
            width: 28px; height: 28px; border-radius: 6px;
            display: flex; align-items: center; justify-content: center;
            font-size: 14px; color: white; font-weight: 600;
        }

        .engine-name { font-weight: 600; font-size: 14px; }

        .tag {
            font-size: 10px; padding: 2px 6px; border-radius: 4px; margin-left: 6px; color: white;
        }

        .tag-custom { background: var(--primary); }
        .tag-workflow { background: var(--warning); }
        .tag-hot { background: linear-gradient(135deg, #f56c6c, #f59e0b); animation: pulse 2s infinite; }
        .tag-category { font-size: 10px; padding: 2px 6px; border-radius: 4px; color: white; margin-left: 4px; }

        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.7; }
        }

        .card-actions { display: flex; gap: 4px; }

        .card-btn {
            width: 28px; height: 28px; border: none; background: transparent;
            color: var(--text-secondary); border-radius: 6px; cursor: pointer;
            display: flex; align-items: center; justify-content: center;
            font-size: 12px; transition: all 0.2s;
        }

        .card-btn:hover { background: var(--hover-bg); color: var(--primary); }
        .card-btn.delete:hover { color: var(--danger); }

        .iframe-container { flex: 1; position: relative; background: var(--bg-primary); }

        .engine-iframe { width: 100%; height: 100%; border: none; display: block; }

        .loading-mask {
            position: absolute; inset: 0; background: var(--bg-secondary);
            display: flex; flex-direction: column; align-items: center; justify-content: center;
            gap: 16px; z-index: 10;
        }

        .loading-spinner {
            width: 40px; height: 40px; border: 3px solid var(--border);
            border-top-color: var(--primary); border-radius: 50%;
            animation: spin 1s linear infinite;
        }

        @keyframes spin { to { transform: rotate(360deg); } }

        .loading-text { color: var(--text-secondary); font-size: 14px; }

        /* 空状态 */
        .empty-state {
            flex: 1; display: flex; flex-direction: column;
            align-items: center; justify-content: center;
            color: var(--text-secondary); text-align: center; padding: 40px;
        }

        .empty-state i { font-size: 64px; margin-bottom: 20px; opacity: 0.3; }
        .empty-state h3 { font-size: 20px; margin-bottom: 8px; color: var(--text-primary); }

        /* 底部备案信息 */
        .footer {
            height: 32px; background: var(--bg-secondary); border-top: 1px solid var(--border);
            display: flex; align-items: center; justify-content: center;
            gap: 16px; padding: 0 16px; font-size: 12px; color: var(--text-secondary);
        }

        .footer a { color: var(--text-secondary); text-decoration: none; }
        .footer a:hover { color: var(--primary); }
        .footer-divider { color: var(--border); }

        /* 备案图标样式 */
        .police-icon {
            color: #0052d9;
            margin-right: 4px;
            font-size: 13px;
        }

        [data-theme="dark"] .police-icon {
            color: #4c9aff;
        }

        /* 侧边栏 */
        .sidebar {
            position: fixed; top: 50px; right: -400px; width: 380px;
            height: calc(100vh - 50px); background: var(--bg-secondary);
            border-left: 1px solid var(--border); box-shadow: -4px 0 20px var(--shadow);
            transition: right 0.3s ease; z-index: 99; display: flex; flex-direction: column;
        }

        .sidebar.open { right: 0; }
        .sidebar-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; }
        .sidebar-title { font-size: 16px; font-weight: 600; }
        .sidebar-close { width: 28px; height: 28px; border: none; background: transparent; color: var(--text-secondary); border-radius: 6px; cursor: pointer; }
        .sidebar-content { flex: 1; overflow-y: auto; padding: 16px; }

        .menu-section { margin-bottom: 24px; }

        .menu-title {
            font-size: 12px; color: var(--text-secondary); margin-bottom: 12px;
            font-weight: 600; display: flex; align-items: center; justify-content: space-between;
        }

        .menu-title button {
            font-size: 11px; padding: 4px 10px; background: var(--primary);
            color: white; border: none; border-radius: 4px; cursor: pointer;
        }

        .menu-title button.warning { background: var(--warning); }

        .engine-list { display: flex; flex-direction: column; gap: 8px; }

        .engine-item {
            display: flex; align-items: center; justify-content: space-between;
            padding: 10px; border-radius: 8px; cursor: pointer;
            transition: all 0.2s; border: 2px solid transparent;
        }

        .engine-item:hover { background: var(--hover-bg); }
        .engine-item.active { border-color: var(--primary); background: rgba(76, 213, 97, 0.05); }
        .engine-item.in-workflow { border-color: var(--warning); background: rgba(245, 158, 11, 0.05); }

        .engine-item-info { display: flex; align-items: center; gap: 10px; }

        .engine-item-avatar {
            width: 32px; height: 32px; border-radius: 6px;
            display: flex; align-items: center; justify-content: center;
            color: white; font-size: 14px; font-weight: 600;
        }

        .engine-item-details h4 { font-size: 14px; margin-bottom: 2px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
        .engine-item-details p { font-size: 12px; color: var(--text-secondary); }

        .add-btn {
            width: 24px; height: 24px; border: none; background: var(--primary);
            color: white; border-radius: 6px; cursor: pointer;
            display: flex; align-items: center; justify-content: center; font-size: 12px;
        }

        .add-btn.added { background: #67c23a; }
        .add-btn.in-workflow { background: var(--warning); }

        .delete-btn {
            width: 24px; height: 24px; border: none; background: transparent;
            color: var(--text-secondary); border-radius: 6px; cursor: pointer;
            display: flex; align-items: center; justify-content: center; font-size: 12px;
        }

        .delete-btn:hover { color: var(--danger); background: #fef0f0; }

        /* 分类筛选器 */
        .category-filter {
            display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px;
        }

        .category-btn {
            padding: 6px 12px; border-radius: 6px; border: none;
            cursor: pointer; font-size: 12px; display: flex; align-items: center; gap: 6px;
            transition: all 0.2s;
        }

        .category-btn:hover { transform: translateY(-1px); }

        .category-btn.active { 
            background: var(--primary); color: white; 
            box-shadow: 0 2px 8px rgba(76, 213, 97, 0.3);
        }

        .category-btn:not(.active) { 
            background: var(--hover-bg); color: var(--text-secondary); 
        }

        /* 工作流 */
        .workflow-section {
            background: linear-gradient(135deg, rgba(245, 158, 11, 0.05) 0%, rgba(245, 158, 11, 0.1) 100%);
            border-radius: 12px; padding: 16px; margin-bottom: 24px;
            border: 1px solid rgba(245, 158, 11, 0.2);
        }

        .workflow-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }

        .workflow-title { font-size: 14px; font-weight: 600; color: var(--warning); display: flex; align-items: center; gap: 8px; }

        .workflow-btn {
            padding: 4px 10px; border: none; border-radius: 4px;
            font-size: 11px; cursor: pointer;
        }

        .workflow-btn-primary { background: var(--warning); color: white; }
        .workflow-btn-default { background: var(--hover-bg); color: var(--text-secondary); }

        .workflow-list { display: flex; flex-direction: column; gap: 8px; }

        .workflow-item {
            background: var(--bg-secondary); border-radius: 8px; padding: 12px;
            cursor: pointer; transition: all 0.2s; border: 2px solid transparent;
        }

        .workflow-item:hover { border-color: var(--warning); transform: translateX(4px); }
        .workflow-item.active { border-color: var(--warning); background: rgba(245, 158, 11, 0.1); }

        .workflow-item-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }

        .workflow-item-name { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 6px; }

        .workflow-item-count {
            font-size: 11px; color: var(--text-secondary); background: var(--bg-primary);
            padding: 2px 8px; border-radius: 10px;
        }

        .workflow-item-engines { display: flex; gap: 6px; flex-wrap: wrap; }

        .workflow-engine-tag { font-size: 11px; padding: 3px 8px; border-radius: 4px; color: white; font-weight: 500; }

        .workflow-empty { text-align: center; padding: 20px; color: var(--text-secondary); font-size: 13px; }

        /* 表单 */
        .add-custom-form { background: var(--bg-primary); padding: 16px; border-radius: 8px; margin-bottom: 16px; }

        .form-group { margin-bottom: 12px; }

        .form-group label { display: block; font-size: 12px; color: var(--text-secondary); margin-bottom: 4px; }

        .form-group input, .form-group select {
            width: 100%; padding: 8px 12px; border: 1px solid var(--border);
            border-radius: 6px; background: var(--bg-secondary);
            color: var(--text-primary); font-size: 14px;
        }

        .form-group input:focus, .form-group select:focus { outline: none; border-color: var(--primary); }

        .form-actions { display: flex; gap: 8px; margin-top: 16px; }

        .form-actions button { flex: 1; padding: 10px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; }

        .btn-primary { background: var(--primary); color: white; }
        .btn-default { background: var(--hover-bg); color: var(--text-secondary); }

        .workflow-edit-mode {
            background: rgba(245, 158, 11, 0.1); border: 2px dashed var(--warning);
            border-radius: 8px; padding: 12px; margin-bottom: 16px; text-align: center;
        }

        .workflow-edit-mode h4 { color: var(--warning); font-size: 14px; margin-bottom: 8px; }
        .workflow-edit-mode p { font-size: 12px; color: var(--text-secondary); margin-bottom: 12px; }

        .workflow-selected-count { font-size: 13px; color: var(--warning); font-weight: 600; }

        /* 遮罩层 */
        .overlay {
            position: fixed; top: 50px; left: 0; right: 0; bottom: 0;
            background: rgba(0, 0, 0, 0.3); z-index: 98;
            opacity: 0; visibility: hidden; transition: all 0.3s;
        }

        .overlay.show { opacity: 1; visibility: visible; }

        /* 颜色选择器 */
        .color-picker { display: flex; gap: 8px; flex-wrap: wrap; }

        .color-option {
            width: 28px; height: 28px; border-radius: 6px;
            cursor: pointer; border: 3px solid transparent; transition: all 0.2s;
        }

        .color-option:hover { transform: scale(1.1); }
        .color-option.selected { border-color: var(--text-primary); }

        /* Toast */
        .toast {
            position: fixed; top: 12px; left: 50%; transform: translateX(-50%) translateY(-100px);
            background: var(--bg-secondary); padding: 10px 20px; border-radius: 8px;
            box-shadow: 0 4px 20px var(--shadow); border: 1px solid var(--border);
            z-index: 1000; display: flex; align-items: center; gap: 10px;
            font-size: 14px; transition: transform 0.3s ease;
        }

        .toast.show { transform: translateX(-50%) translateY(0); }
        .toast.success { border-color: var(--primary); color: var(--primary); }
        .toast.warning { border-color: var(--warning); color: var(--warning); }

        /* 设置 */
        .settings-section { background: var(--bg-primary); border-radius: 8px; padding: 16px; margin-bottom: 16px; }

        .settings-item { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid var(--border); }
        .settings-item:last-child { border-bottom: none; }

        .settings-label { font-size: 13px; color: var(--text-primary); }
        .settings-desc { font-size: 11px; color: var(--text-secondary); margin-top: 2px; }

        .toggle-switch {
            width: 44px; height: 24px; background: var(--border); border-radius: 12px;
            position: relative; cursor: pointer; transition: all 0.3s;
        }

        .toggle-switch.active { background: var(--primary); }

        .toggle-switch::after {
            content: ''; position: absolute; width: 20px; height: 20px;
            background: white; border-radius: 50%; top: 2px; left: 2px;
            transition: all 0.3s; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
        }

        .toggle-switch.active::after { left: 22px; }

        .shortcut-list { display: flex; flex-direction: column; gap: 8px; }

        .shortcut-item {
            display: flex; align-items: center; justify-content: space-between;
            padding: 8px 12px; background: var(--bg-secondary); border-radius: 6px; font-size: 12px;
        }

        .shortcut-key {
            background: var(--bg-primary); padding: 2px 8px; border-radius: 4px;
            border: 1px solid var(--border); font-family: monospace; color: var(--text-secondary);
        }

        .data-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 12px; }

        .data-btn {
            padding: 8px; border: 1px solid var(--border); background: var(--bg-secondary);
            border-radius: 6px; cursor: pointer; font-size: 12px;
            display: flex; align-items: center; justify-content: center; gap: 6px;
            color: var(--text-secondary);
        }

        .data-btn:hover { border-color: var(--primary); color: var(--primary); }
        .data-btn.danger:hover { border-color: var(--danger); color: var(--danger); }

        /* 弹窗 */
        .modal-content {
            position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
            background: var(--bg-secondary); padding: 24px; border-radius: 12px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3); max-width: 400px; width: 90%;
        }

        /* 分类标签颜色 */
        .cat-chat { background: var(--chat-color); }
        .cat-video { background: var(--video-color); }
        .cat-code { background: var(--code-color); }
        .cat-search { background: var(--search-color); }
        .cat-image { background: var(--image-color); }

        /* 批量操作栏 */
        .batch-actions {
            position: fixed; bottom: 40px; left: 50%; transform: translateX(-50%) translateY(100px);
            background: var(--bg-secondary); padding: 12px 24px; border-radius: 24px;
            box-shadow: 0 4px 20px var(--shadow); border: 1px solid var(--border);
            z-index: 200; display: flex; gap: 12px; transition: transform 0.3s ease;
        }

        .batch-actions.show { transform: translateX(-50%) translateY(0); }

        .batch-btn {
            padding: 6px 16px; border: none; border-radius: 4px; cursor: pointer;
            font-size: 13px; display: flex; align-items: center; gap: 6px;
        }

        .batch-btn-primary { background: var(--primary); color: white; }
        .batch-btn-default { background: var(--hover-bg); color: var(--text-secondary); }
        .batch-btn-danger { background: var(--danger); color: white; }

        @media (max-width: 768px) {
            .engine-card { min-width: 320px; }
            .sidebar { width: 100%; right: -100%; }
            .toast { left: 50%; transform: translateX(-50%) translateY(-100px); top: 60px; }
            .toast.show { transform: translateX(-50%) translateY(0); }
            .footer { flex-direction: column; height: auto; padding: 8px; gap: 4px; font-size: 11px; }
            .footer-divider { display: none; }
            .batch-actions { width: 90%; justify-content: center; }
        }
    </style>
<base target="_blank">
</head>
<body>
    <div id="app">
        <!-- Toast -->
        <div class="toast" :class="[toastType, { show: showToast }]">
            <i :class="toastIcon"></i>
            <span>{{ toastMessage }}</span>
        </div>

        <!-- 批量操作栏 -->
        <div class="batch-actions" :class="{ show: selectedEngines.length > 0 }">
            <span style="font-size: 13px; color: var(--text-secondary);">已选择 {{ selectedEngines.length }} 个</span>
            <button class="batch-btn batch-btn-primary" @click="addSelectedToWorkflow">
                <i class="fas fa-project-diagram"></i> 加入工作流
            </button>
            <button class="batch-btn batch-btn-default" @click="addSelectedToActive">
                <i class="fas fa-plus"></i> 打开
            </button>
            <button class="batch-btn batch-btn-danger" @click="clearSelection">
                <i class="fas fa-times"></i> 取消
            </button>
        </div>

        <!-- 工具栏 -->
        <header class="toolbar">
            <div class="logo" @click="showShortcutModal = true">
                <div class="logo-icon">
                    <div class="square"></div>
                    <div class="square"></div>
                    <div class="square"></div>
                </div>
                <span>一堆 AI</span>
            </div>
            
            <div class="toolbar-actions">
                <button class="btn-icon" :class="{ active: isBatchMode }" @click="toggleBatchMode" title="批量选择 (Ctrl+B)">
                    <i class="fas fa-check-square"></i>
                </button>
                <button class="btn-icon" :class="{ active: isDarkMode }" @click="toggleTheme" title="切换主题 (Ctrl+T)">
                    <i :class="isDarkMode ? 'fas fa-sun' : 'fas fa-moon'"></i>
                </button>
                <button class="btn-icon workflow" :class="{ active: showWorkflowMenu }" @click="toggleWorkflowMenu" title="工作流 (Ctrl+W)">
                    <i class="fas fa-project-diagram"></i>
                </button>
                <button class="btn-icon" :class="{ active: showAddMenu }" @click="toggleAddMenu" title="添加引擎 (Ctrl+A)">
                    <i class="fas fa-plus"></i>
                </button>
                <button class="btn-icon" :class="{ active: showSettings }" @click="toggleSettings" title="设置 (Ctrl+,)">
                    <i class="fas fa-cog"></i>
                </button>
            </div>
        </header>

        <!-- 主内容区 -->
        <div class="main-container">
            <div class="engines-container" ref="enginesContainer">
                <div v-if="!activeEngines.length" class="empty-state">
                    <i class="fas fa-robot"></i>
                    <h3>欢迎使用 一堆AI</h3>
                    <p>点击右上角 <i class="fas fa-plus"></i> 添加 AI 引擎<br>或使用 <i class="fas fa-project-diagram"></i> 工作流快速启动</p>
                    <p style="margin-top: 12px; font-size: 13px; opacity: 0.7;">
                        已收录国内主流 AI 工具,本地存储模式,数据只保存在浏览器及个人ai引擎账号内,你的数据安全至关重要
                    </p>
                </div>

                <div v-for="(engine, index) in activeEngines" :key="engine.uniqueId"
                     class="engine-card"
                     :class="{ fullscreen: engine.fullscreen, dragging: engine.dragging }"
                     draggable="true"
                     @dragstart="dragStart($event, index)"
                     @dragover.prevent
                     @drop="drop($event, index)"
                     @dragend="dragEnd">
                    
                    <div class="card-header">
                        <div class="engine-info">
                            <div class="engine-avatar" :style="{ background: engine.color }">
                                {{ engine.avatar }}
                            </div>
                            <span class="engine-name">{{ engine.name }}</span>
                            <span v-if="engine.isHot" class="tag tag-hot" title="热门工具">🔥</span>
                            <span v-if="engine.isCustom" class="tag tag-custom">自定义</span>
                            <span v-if="engine.fromWorkflow" class="tag tag-workflow">{{ engine.workflowName }}</span>
                            <span v-if="engine.category" class="tag tag-category" :class="'cat-' + engine.category">
                                {{ getCategoryLabel(engine.category) }}
                            </span>
                        </div>
                        <div class="card-actions">
                            <button v-for="action in engineActions" :key="action.key"
                                    class="card-btn" 
                                    :class="action.class"
                                    @click="action.handler(engine, index)"
                                    :title="action.title">
                                <i :class="engine[action.iconKey] || action.icon"></i>
                            </button>
                        </div>
                    </div>

                    <div class="iframe-container">
                        <div v-if="engine.loading" class="loading-mask">
                            <div class="loading-spinner"></div>
                            <div class="loading-text">正在加载 {{ engine.name }}...</div>
                        </div>
                        <iframe 
                            v-show="!engine.loading"
                            :src="engine.url" 
                            class="engine-iframe"
                            :id="'iframe-' + engine.id"
                            sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
                            @load="engine.loading = false"
                        ></iframe>
                    </div>
                </div>
            </div>
        </div>

        <!-- 底部备案信息 -->
        <footer class="footer">
            <span>数据更新:2026年4月</span>
            <span class="footer-divider">|</span>
            <span><i class="fas fa-shield-alt police-icon"></i>鄂ICP备2025096233号-2 闽公网安备35010402351837号</span>
            <span class="footer-divider">|</span>
            <a href="https://www.yiduiai.com" target="_blank">一堆AI</a>
        </footer>

        <!-- 遮罩层 -->
        <div class="overlay" :class="{ show: sidebarOpen }" @click="closeAllSidebars"></div>

        <!-- 设置面板 -->
        <div class="sidebar" :class="{ open: showSettings }">
            <div class="sidebar-header">
                <h3 class="sidebar-title">
                    <i class="fas fa-cog" style="color: var(--primary); margin-right: 8px;"></i>
                    设置
                </h3>
                <button class="sidebar-close" @click="showSettings = false">
                    <i class="fas fa-times"></i>
                </button>
            </div>
            <div class="sidebar-content">
                <div class="menu-section">
                    <div class="menu-title">外观设置</div>
                    <div class="settings-section">
                        <div class="settings-item">
                            <div>
                                <div class="settings-label">深色模式</div>
                                <div class="settings-desc">切换浅色/深色主题</div>
                            </div>
                            <div class="toggle-switch" :class="{ active: isDarkMode }" @click="toggleTheme"></div>
                        </div>
                    </div>
                </div>

                <div class="menu-section">
                    <div class="menu-title">快捷键</div>
                    <div class="settings-section">
                        <div class="shortcut-list">
                            <div v-for="sc in shortcuts" :key="sc.key" class="shortcut-item">
                                <span>{{ sc.name }}</span>
                                <span class="shortcut-key">{{ sc.key }}</span>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="menu-section">
                    <div class="menu-title">数据管理</div>
                    <div class="settings-section">
                        <div class="data-actions">
                            <button v-for="btn in dataBtns" :key="btn.key" 
                                    class="data-btn" 
                                    :class="btn.class"
                                    @click="btn.action">
                                <i :class="btn.icon"></i>
                                {{ btn.text }}
                            </button>
                        </div>
                    </div>
                </div>

                <div class="menu-section">
                    <div class="menu-title">关于</div>
                    <div class="settings-section" style="text-align: center; padding: 20px;">
                        <div style="font-size: 24px; margin-bottom: 8px;">🤖</div>
                        <div style="font-weight: 600; margin-bottom: 4px;">一堆 AI v3.1</div>
                        <div style="font-size: 12px; color: var(--text-secondary);">
                            聚合国内主流 AI 平台,提升工作效率<br>
                            <span style="color: var(--warning);">本地存储模式 - 数据保存在浏览器中</span><br>
                            <span style="color: var(--primary);">2026年最新数据 | 支持批量操作</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- 工作流侧边栏 -->
        <div class="sidebar" :class="{ open: showWorkflowMenu }">
            <div class="sidebar-header">
                <h3 class="sidebar-title">
                    <i class="fas fa-project-diagram" style="color: var(--warning); margin-right: 8px;"></i>
                    工作流管理
                </h3>
                <button class="sidebar-close" @click="showWorkflowMenu = false">
                    <i class="fas fa-times"></i>
                </button>
            </div>
            <div class="sidebar-content">
                <div v-if="isEditingWorkflow" class="workflow-edit-mode">
                    <h4><i class="fas fa-edit"></i> 正在编辑工作流</h4>
                    <div class="form-group" style="text-align: left; margin-top: 12px;">
                        <label>工作流名称</label>
                        <input v-model="editingWorkflowName" placeholder="输入工作流名称" maxlength="20">
                    </div>
                    <p style="font-size: 12px; color: var(--text-secondary); margin: 8px 0;">
                        在下方选择要添加的 AI 引擎
                    </p>
                    <div class="workflow-selected-count">已选择 {{ editingWorkflowEngines.length }} 个引擎</div>
                    <div style="margin-top: 12px; display: flex; gap: 8px; justify-content: center;">
                        <button class="workflow-btn workflow-btn-default" @click="cancelEditWorkflow">
                            <i class="fas fa-times"></i> 取消
                        </button>
                        <button class="workflow-btn workflow-btn-primary" @click="saveWorkflow">
                            <i class="fas fa-save"></i> 保存工作流
                        </button>
                    </div>
                </div>

                <div class="workflow-section">
                    <div class="workflow-header">
                        <div class="workflow-title">
                            <i class="fas fa-list"></i>
                            我的工作流
                        </div>
                        <button v-if="!isEditingWorkflow" class="workflow-btn workflow-btn-primary" @click="startCreateWorkflow">
                            <i class="fas fa-plus"></i> 新建
                        </button>
                    </div>
                    
                    <div v-if="!workflows.length" class="workflow-empty">
                        <i class="fas fa-folder-open" style="font-size: 32px; margin-bottom: 8px; opacity: 0.3;"></i>
                        <div>暂无工作流</div>
                        <div style="font-size: 11px; margin-top: 4px;">点击"新建"创建工作流</div>
                    </div>

                    <div class="workflow-list">
                        <div v-for="workflow in workflows" :key="workflow.id"
                             class="workflow-item"
                             :class="{ active: currentWorkflow?.id === workflow.id }"
                             @click="applyWorkflow(workflow)">
                            <div class="workflow-item-header">
                                <div class="workflow-item-name">
                                    <i class="fas fa-sitemap" style="color: var(--warning);"></i>
                                    {{ workflow.name }}
                                </div>
                                <div style="display: flex; gap: 4px;">
                                    <span class="workflow-item-count">{{ workflow.engines.length }} 个引擎</span>
                                    <button class="delete-btn" @click.stop="deleteWorkflow(workflow.id)" title="删除">
                                        <i class="fas fa-trash"></i>
                                    </button>
                                </div>
                            </div>
                            <div class="workflow-item-engines">
                                <span v-for="engine in getWorkflowEnginesInfo(workflow)" :key="engine.id"
                                      class="workflow-engine-tag"
                                      :style="{ background: engine.color }">
                                    {{ engine.avatar }}
                                </span>
                            </div>
                        </div>
                    </div>
                </div>

                <div v-if="isEditingWorkflow" class="menu-section">
                    <div class="menu-title">
                        <span>选择 AI 引擎</span>
                        <span style="font-size: 11px; color: var(--warning);">点击添加/移除</span>
                    </div>
                    <div class="engine-list">
                        <div v-for="engine in allEngines" :key="engine.id"
                             class="engine-item"
                             :class="{ 'in-workflow': isInEditingWorkflow(engine.id) }"
                             @click="toggleEngineInWorkflow(engine)">
                            <div class="engine-item-info">
                                <div class="engine-item-avatar" :style="{ background: engine.color }">
                                    {{ engine.avatar }}
                                </div>
                                <div class="engine-item-details">
                                    <h4>
                                        {{ engine.name }}
                                        <span v-if="engine.isHot" class="tag tag-hot">🔥</span>
                                        <span v-if="engine.isCustom" class="tag tag-custom">自定义</span>
                                        <span v-if="engine.category" class="tag tag-category" :class="'cat-' + engine.category">
                                            {{ getCategoryLabel(engine.category) }}
                                        </span>
                                    </h4>
                                    <p>{{ engine.description }}</p>
                                </div>
                            </div>
                            <button class="add-btn" :class="{ 'in-workflow': isInEditingWorkflow(engine.id) }">
                                <i :class="isInEditingWorkflow(engine.id) ? 'fas fa-check' : 'fas fa-plus'"></i>
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- 添加引擎侧边栏 -->
        <div class="sidebar" :class="{ open: showAddMenu }">
            <div class="sidebar-header">
                <h3 class="sidebar-title">
                    <i class="fas fa-plus-circle" style="color: var(--primary); margin-right: 8px;"></i>
                    管理 AI 引擎
                </h3>
                <button class="sidebar-close" @click="showAddMenu = false">
                    <i class="fas fa-times"></i>
                </button>
            </div>
            <div class="sidebar-content">
                <!-- 分类筛选 -->
                <div class="menu-section">
                    <div class="menu-title">
                        <span>分类筛选</span>
                        <span style="font-size: 11px; color: var(--text-secondary);">{{ filteredEngines.length }} 个工具</span>
                    </div>
                    <div class="category-filter">
                        <button v-for="cat in categories" :key="cat.id"
                                class="category-btn"
                                :class="{ active: currentCategory === cat.id }"
                                @click="currentCategory = cat.id">
                            <i :class="cat.icon"></i>
                            {{ cat.name }}
                        </button>
                    </div>
                </div>

                <div class="menu-section">
                    <div class="menu-title">
                        <span>添加自定义 AI</span>
                        <button @click="showCustomForm = !showCustomForm">
                            {{ showCustomForm ? '收起' : '展开' }}
                        </button>
                    </div>
                    
                    <div v-if="showCustomForm" class="add-custom-form">
                        <div v-for="field in customFormFields" :key="field.key" class="form-group">
                            <label>{{ field.label }}</label>
                            <input v-if="field.type !== 'select'" 
                                   v-model="customForm[field.key]" 
                                   :placeholder="field.placeholder" 
                                   :maxlength="field.maxlength"
                                   :type="field.type || 'text'">
                            <select v-else v-model="customForm[field.key]">
                                <option v-for="opt in field.options" :key="opt.value" :value="opt.value">
                                    {{ opt.label }}
                                </option>
                            </select>
                        </div>
                        <div class="form-group">
                            <label>选择颜色</label>
                            <div class="color-picker">
                                <div v-for="color in presetColors" :key="color"
                                     class="color-option"
                                     :style="{ background: color }"
                                     :class="{ selected: customForm.color === color }"
                                     @click="customForm.color = color">
                                </div>
                            </div>
                        </div>
                        <div class="form-actions">
                            <button class="btn-default" @click="resetCustomForm">重置</button>
                            <button class="btn-primary" @click="addCustomEngine">添加</button>
                        </div>
                    </div>
                </div>

                <div class="menu-section">
                    <div class="menu-title">
                        <span>{{ currentCategory === 'all' ? '全部 AI 引擎' : categories.find(c => c.id === currentCategory)?.name }}</span>
                        <button v-if="currentCategory !== 'all'" @click="currentCategory = 'all'" style="background: transparent; color: var(--text-secondary);">
                            显示全部
                        </button>
                    </div>
                    <div class="engine-list">
                        <div v-for="engine in filteredEngines" :key="engine.id"
                             class="engine-item"
                             :class="{ 
                                 active: isActive(engine.id), 
                                 'in-workflow': isBatchMode && selectedEngines.includes(engine.id) 
                             }"
                             @click="handleEngineClick(engine)">
                            <div class="engine-item-info">
                                <div class="engine-item-avatar" :style="{ background: engine.color }">
                                    {{ engine.avatar }}
                                </div>
                                <div class="engine-item-details">
                                    <h4>
                                        {{ engine.name }}
                                        <span v-if="engine.isHot" class="tag tag-hot">🔥</span>
                                        <span v-if="engine.isCustom" class="tag tag-custom">自定义</span>
                                        <span v-if="engine.category && getCategoryLabel(engine.category)" class="tag tag-category" :class="'cat-' + engine.category">
                                            {{ getCategoryLabel(engine.category) }}
                                        </span>
                                    </h4>
                                    <p>{{ engine.description }}</p>
                                </div>
                            </div>
                            <button class="add-btn" :class="{ added: isActive(engine.id), 'in-workflow': isBatchMode && selectedEngines.includes(engine.id) }">
                                <i v-if="isBatchMode" :class="selectedEngines.includes(engine.id) ? 'fas fa-check-square' : 'far fa-square'"></i>
                                <i v-else :class="isActive(engine.id) ? 'fas fa-check' : 'fas fa-plus'"></i>
                            </button>
                        </div>
                    </div>
                </div>

                <div v-if="customEngines.length" class="menu-section">
                    <div class="menu-title">
                        <span>自定义 AI</span>
                        <button class="warning" @click="clearCustomEngines">清空</button>
                    </div>
                    <div class="engine-list">
                        <div v-for="engine in customEngines" :key="engine.id"
                             class="engine-item"
                             :class="{ active: isActive(engine.id) }"
                             @click="toggleEngine(engine)">
                            <div class="engine-item-info">
                                <div class="engine-item-avatar" :style="{ background: engine.color }">
                                    {{ engine.avatar }}
                                </div>
                                <div class="engine-item-details">
                                    <h4>
                                        {{ engine.name }}
                                        <span class="tag tag-custom">自定义</span>
                                        <span v-if="engine.category" class="tag tag-category" :class="'cat-' + engine.category">
                                            {{ getCategoryLabel(engine.category) }}
                                        </span>
                                    </h4>
                                    <p>{{ engine.description }}</p>
                                </div>
                            </div>
                            <div style="display: flex; gap: 4px;">
                                <button class="add-btn" :class="{ added: isActive(engine.id) }">
                                    <i :class="isActive(engine.id) ? 'fas fa-check' : 'fas fa-plus'"></i>
                                </button>
                                <button class="delete-btn" @click.stop="deleteCustomEngine(engine.id)">
                                    <i class="fas fa-trash"></i>
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- 快捷键帮助弹窗 -->
        <div v-if="showShortcutModal" class="overlay show" @click="showShortcutModal = false" style="z-index: 2000; top: 0;">
            <div class="modal-content" @click.stop>
                <h3 style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
                    <i class="fas fa-keyboard" style="color: var(--primary);"></i>
                    快捷键指南
                </h3>
                <div class="shortcut-list" style="margin-bottom: 16px;">
                    <div v-for="sc in shortcuts" :key="sc.key" class="shortcut-item">
                        <span>{{ sc.name }}</span>
                        <span class="shortcut-key">{{ sc.key }}</span>
                    </div>
                </div>
                <div style="background: var(--bg-primary); padding: 12px; border-radius: 8px; margin-bottom: 16px; font-size: 12px; color: var(--text-secondary);">
                    <div style="font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">💡 使用提示</div>
                    <div>• 点击 🔥 标签可快速识别热门工具</div>
                    <div>• 使用分类筛选快速找到所需类型</div>
                    <div>• 拖拽卡片可调整布局顺序</div>
                    <div>• 使用批量模式可同时添加多个工具</div>
                </div>
                <button class="btn-primary" style="width: 100%; padding: 10px;" @click="showShortcutModal = false">知道了</button>
            </div>
        </div>

    </div>

    <script>
        const { createApp, ref, computed, onMounted, onUnmounted } = Vue;

        createApp({
            setup() {
                // ========== 状态定义 ==========
                const showAddMenu = ref(false);
                const showWorkflowMenu = ref(false);
                const showSettings = ref(false);
                const showCustomForm = ref(false);
                const showToast = ref(false);
                const showShortcutModal = ref(false);
                const isEditingWorkflow = ref(false);
                const isBatchMode = ref(false);
                const editingWorkflowEngines = ref([]);
                const editingWorkflowName = ref('');
                const dragIndex = ref(-1);
                const currentCategory = ref('all');
                const selectedEngines = ref([]);
                
                const toastMessage = ref('');
                const toastType = ref('success');
                const isDarkMode = ref(false);
                const currentWorkflow = ref(null);

                const presetColors = ['#4cd561', '#1677ff', '#f59e0b', '#f56c6c', '#8b5cf6', '#ec4899', '#06b6d4', '#84cc16', '#4b70e2', '#ff0050'];

                const customForm = ref({ name: '', url: '', avatar: '', color: presetColors[0], category: 'chat' });

                // ========== 分类定义 ==========
                const categories = [
                    { id: 'all', name: '全部', icon: 'fas fa-th-large' },
                    { id: 'chat', name: 'AI对话', icon: 'fas fa-comments' },
                    { id: 'video', name: 'AI视频', icon: 'fas fa-video' },
                    { id: 'code', name: 'AI编程', icon: 'fas fa-code' },
                    { id: 'search', name: 'AI搜索', icon: 'fas fa-search' },
                    { id: 'image', name: 'AI绘画', icon: 'fas fa-image' }
                ];

                 const presetEngines = ref([
                    // ========== AI对话/大模型类 ==========
                    { 
                        id: 'doubao', 
                        name: '豆包', 
                        avatar: '豆', 
                        color: '#ff6b6b', 
                        description: '字节跳动 · 月活1.16亿 · 多模态AI助手', 
                        url: 'https://www.doubao.com',
                        category: 'chat',
                        isHot: true 
                    },
                    { 
                        id: 'yuanbao', 
                        name: '腾讯元宝', 
                        avatar: '元', 
                        color: '#00a1ff', 
                        description: '腾讯 · 月活4164万 · 接入DeepSeek-R1', 
                        url: 'https://yuanbao.tencent.com',
                        category: 'chat',
                        isHot: true 
                    },
                    { 
                        id: 'kimi', 
                        name: 'Kimi', 
                        avatar: 'K', 
                        color: '#4cd561', 
                        description: '月之暗面 · 月活2649万 · 200万字长文本', 
                        url: 'https://kimi.moonshot.cn',
                        category: 'chat',
                        isHot: true 
                    },
                    { 
                        id: 'wenxin', 
                        name: '文心一言', 
                        avatar: '文', 
                        color: '#1677ff', 
                        description: '百度 · 月活1393万 · 文心大模型4.0', 
                        url: 'https://yiyan.baidu.com',
                        category: 'chat' 
                    },
                    { 
                        id: 'qianwen', 
                        name: '通义千问', 
                        avatar: '问', 
                        color: '#ff7a00', 
                        description: '阿里巴巴 · 月活552万 · 开源大模型', 
                        url: 'https://tongyi.aliyun.com/qianwen',
                        category: 'chat' 
                    },
                    { 
                        id: 'xinghuo', 
                        name: '讯飞星火', 
                        avatar: '星', 
                        color: '#f59e0b', 
                        description: '科大讯飞 · 语音交互优势', 
                        url: 'https://xinghuo.xfyun.cn',
                        category: 'chat' 
                    },
                    { 
                        id: 'hunyuan', 
                        name: '腾讯混元', 
                        avatar: '混', 
                        color: '#0052d9', 
                        description: '腾讯大模型 · 企业级应用', 
                        url: 'https://hunyuan.tencent.com',
                        category: 'chat' 
                    },
                    
                    // ========== AI搜索类 ==========
                    { 
                        id: 'nano', 
                        name: '纳米AI搜索', 
                        avatar: '纳', 
                        color: '#10b981', 
                        description: '360 · 月活1688万 · 集成16家大模型', 
                        url: 'https://www.n.cn',
                        category: 'search',
                        isHot: true 
                    },
                    { 
                        id: 'tiangong', 
                        name: '天工AI', 
                        avatar: '天', 
                        color: '#f97316', 
                        description: '昆仑万维 · 多模态综合搜索', 
                        url: 'https://www.tiangong.cn',
                        category: 'search' 
                    },

                    // ========== AI视频生成类 ==========
                    { 
                        id: 'kling', 
                        name: '可灵AI', 
                        avatar: '灵', 
                        color: '#ff0050', 
                        description: '快手 · 月活1264万 · 商业化领先', 
                        url: 'https://app.klingai.com/cn/',
                        category: 'video',
                        isHot: true 
                    },
                    { 
                        id: 'vidu', 
                        name: 'Vidu', 
                        avatar: 'V', 
                        color: '#84cc16', 
                        description: '清华系 · 生数科技 · 1080p高清', 
                        url: 'https://www.vidu.com',
                        category: 'video' 
                    },

                    // ========== AI绘画/图像类 ==========
                    // 通义万相、文心一格 已移除
                    { 
                        id: 'miaohua', 
                        name: '秒画', 
                        avatar: '秒', 
                        color: '#ec4899', 
                        description: '商汤科技 · AI绘画 · 产教融合', 
                        url: 'https://miaohua.sensetime.com',
                        category: 'image' 
                    }
                ]);

                const customEngines = ref([]);
                const activeEngines = ref([]);
                const workflows = ref([]);

                // ========== 计算属性 ==========
                const allEngines = computed(() => [...presetEngines.value, ...customEngines.value]);
                
                const filteredEngines = computed(() => {
                    if (currentCategory.value === 'all') return allEngines.value;
                    return allEngines.value.filter(e => e.category === currentCategory.value);
                });

                const sidebarOpen = computed(() => showAddMenu.value || showWorkflowMenu.value || showSettings.value);
                const toastIcon = computed(() => toastType.value === 'success' ? 'fas fa-check-circle' : 'fas fa-exclamation-circle');

                // ========== 配置数据 ==========
                const engineActions = [
                    { key: 'refresh', icon: 'fas fa-sync-alt', handler: refreshEngine, title: '刷新' },
                    { key: 'fullscreen', icon: 'fas fa-expand', iconKey: 'fullscreenIcon', handler: toggleFullscreen, title: '全屏' },
                    { key: 'close', icon: 'fas fa-times', class: 'delete', handler: (e, idx) => removeEngine(idx), title: '关闭' }
                ];

                const shortcuts = [
                    { name: '切换主题', key: 'Ctrl + T' },
                    { name: '添加引擎', key: 'Ctrl + A' },
                    { name: '工作流', key: 'Ctrl + W' },
                    { name: '批量模式', key: 'Ctrl + B' },
                    { name: '关闭全部', key: 'Ctrl + Shift + C' },
                    { name: '刷新全部', key: 'Ctrl + R' }
                ];

                const dataBtns = [
                    { key: 'export', icon: 'fas fa-download', text: '导出配置', action: exportData },
                    { key: 'import', icon: 'fas fa-upload', text: '导入配置', action: importData },
                    { key: 'clear', icon: 'fas fa-trash', text: '清空数据', class: 'danger', action: clearAllData },
                    { key: 'reset', icon: 'fas fa-undo', text: '恢复默认', action: resetToDefault }
                ];

                const customFormFields = [
                    { key: 'name', label: 'AI 名称', placeholder: '例如:ChatGPT', maxlength: 10 },
                    { key: 'url', label: '网站地址', placeholder: 'https://chat.openai.com', type: 'url' },
                    { key: 'avatar', label: '缩写(1-2个字符)', placeholder: '例如:GPT', maxlength: 2 },
                    { key: 'category', label: '分类', type: 'select', options: [
                        { value: 'chat', label: 'AI对话' },
                        { value: 'video', label: 'AI视频' },
                        { value: 'code', label: 'AI编程' },
                        { value: 'search', label: 'AI搜索' },
                        { value: 'image', label: 'AI绘画' }
                    ]}
                ];

                // ========== 工具方法 ==========
                function showToastMessage(message, type = 'success') {
                    toastMessage.value = message;
                    toastType.value = type;
                    showToast.value = true;
                    setTimeout(() => showToast.value = false, 2000);
                }

                function generateId() {
                    return Date.now().toString(36) + Math.random().toString(36).substr(2);
                }

                function getCategoryLabel(category) {
                    const map = {
                        'chat': '对话',
                        'video': '视频',
                        'code': '编程',
                        'search': '搜索',
                        'image': '绘画'
                    };
                    return map[category] || category;
                }

                // ========== LocalStorage 数据管理 ==========
                
                function saveToStorage() {
                    const data = {
                        customEngines: customEngines.value,
                        workflows: workflows.value,
                        activeEngines: activeEngines.value.map(e => e.id),
                        currentWorkflow: currentWorkflow.value,
                        settings: { 
                            isDarkMode: isDarkMode.value,
                            theme: isDarkMode.value ? 'dark' : 'light'
                        },
                        saveTime: new Date().toISOString()
                    };
                    localStorage.setItem('ai_engines_data_v3', JSON.stringify(data));
                }

                function loadFromStorage() {
                    const data = localStorage.getItem('ai_engines_data_v3');
                    if (!data) return;
                    
                    try {
                        const parsed = JSON.parse(data);
                        
                        if (parsed.customEngines) {
                            customEngines.value = parsed.customEngines;
                        }
                        
                        if (parsed.workflows) {
                            workflows.value = parsed.workflows;
                        }
                        
                        if (parsed.settings?.isDarkMode !== undefined) {
                            isDarkMode.value = parsed.settings.isDarkMode;
                            document.documentElement.setAttribute('data-theme', isDarkMode.value ? 'dark' : 'light');
                        }
                        
                        if (parsed.currentWorkflow) {
                            currentWorkflow.value = parsed.currentWorkflow;
                        }
                        
                        if (parsed.activeEngines && Array.isArray(parsed.activeEngines)) {
                            parsed.activeEngines.forEach(id => {
                                const engine = allEngines.value.find(e => e.id === id);
                                if (engine) {
                                    activeEngines.value.push({
                                        ...engine, 
                                        loading: true, 
                                        fullscreen: false, 
                                        dragging: false,
                                        uniqueId: Date.now() + Math.random(),
                                        get fullscreenIcon() { 
                                            return this.fullscreen ? 'fas fa-compress' : 'fas fa-expand'; 
                                        }
                                    });
                                }
                            });
                        }
                        
                    } catch (e) {
                        console.error('加载本地数据失败:', e);
                    }
                }

                // ========== 批量操作 ==========
                function toggleBatchMode() {
                    isBatchMode.value = !isBatchMode.value;
                    if (!isBatchMode.value) {
                        selectedEngines.value = [];
                    } else {
                        showToastMessage('已进入批量选择模式,点击工具添加选择', 'warning');
                    }
                }

                function handleEngineClick(engine) {
                    if (isBatchMode.value) {
                        const idx = selectedEngines.value.indexOf(engine.id);
                        if (idx > -1) {
                            selectedEngines.value.splice(idx, 1);
                        } else {
                            selectedEngines.value.push(engine.id);
                        }
                    } else {
                        toggleEngine(engine);
                    }
                }

                function clearSelection() {
                    selectedEngines.value = [];
                    isBatchMode.value = false;
                }

                function addSelectedToActive() {
                    selectedEngines.value.forEach(id => {
                        const engine = allEngines.value.find(e => e.id === id);
                        if (engine && !isActive(engine.id)) {
                            activeEngines.value.push({
                                ...engine, 
                                loading: true, 
                                fullscreen: false, 
                                dragging: false,
                                uniqueId: Date.now() + Math.random(),
                                get fullscreenIcon() { 
                                    return this.fullscreen ? 'fas fa-compress' : 'fas fa-expand'; 
                                }
                            });
                        }
                    });
                    showToastMessage(`已添加 ${selectedEngines.value.length} 个工具`);
                    clearSelection();
                    saveToStorage();
                }

                function addSelectedToWorkflow() {
                    if (selectedEngines.value.length === 0) return;
                    isEditingWorkflow.value = true;
                    editingWorkflowEngines.value = [...selectedEngines.value];
                    editingWorkflowName.value = '新工作流 ' + (workflows.value.length + 1);
                    showWorkflowMenu.value = true;
                    showAddMenu.value = false;
                    isBatchMode.value = false;
                    selectedEngines.value = [];
                }

                // ========== 引擎管理 ==========
                function toggleEngine(engine) {
                    const idx = activeEngines.value.findIndex(e => e.id === engine.id);
                    if (idx > -1) {
                        activeEngines.value.splice(idx, 1);
                        showToastMessage(`已移除 ${engine.name}`);
                    } else {
                        activeEngines.value.push({
                            ...engine, 
                            loading: true, 
                            fullscreen: false, 
                            dragging: false,
                            uniqueId: Date.now() + Math.random(),
                            get fullscreenIcon() { 
                                return this.fullscreen ? 'fas fa-compress' : 'fas fa-expand'; 
                            }
                        });
                        showToastMessage(`已添加 ${engine.name}`);
                    }
                    saveToStorage();
                }

                function isActive(id) {
                    return activeEngines.value.some(e => e.id === id);
                }

                function removeEngine(index) {
                    const engine = activeEngines.value[index];
                    activeEngines.value.splice(index, 1);
                    showToastMessage(`已关闭 ${engine.name}`);
                    saveToStorage();
                }

                function refreshEngine(engine) {
                    engine.loading = true;
                    const iframe = document.getElementById(`iframe-${engine.id}`);
                    if (iframe) iframe.src = iframe.src;
                }

                function toggleFullscreen(engine) {
                    engine.fullscreen = !engine.fullscreen;
                }

                function dragStart(e, index) {
                    dragIndex.value = index;
                    activeEngines.value[index].dragging = true;
                    e.dataTransfer.effectAllowed = 'move';
                }

                function dragEnd() {
                    if (dragIndex.value > -1) {
                        activeEngines.value[dragIndex.value].dragging = false;
                        dragIndex.value = -1;
                    }
                }

                function drop(e, index) {
                    e.preventDefault();
                    if (dragIndex.value > -1 && dragIndex.value !== index) {
                        const item = activeEngines.value[dragIndex.value];
                        activeEngines.value.splice(dragIndex.value, 1);
                        activeEngines.value.splice(index, 0, item);
                        saveToStorage();
                    }
                }

                function addCustomEngine() {
                    if (!customForm.value.name || !customForm.value.url) {
                        showToastMessage('请填写完整信息', 'warning');
                        return;
                    }
                    
                    const newEngine = {
                        id: 'custom_' + Date.now(),
                        name: customForm.value.name,
                        url: customForm.value.url,
                        avatar: customForm.value.avatar || customForm.value.name.slice(0, 2),
                        color: customForm.value.color,
                        isCustom: true,
                        description: customForm.value.url,
                        category: customForm.value.category || 'chat'
                    };
                    
                    customEngines.value.push(newEngine);
                    resetCustomForm();
                    showToastMessage('自定义 AI 添加成功');
                    saveToStorage();
                }

                function deleteCustomEngine(id) {
                    const idx = customEngines.value.findIndex(e => e.id === id);
                    if (idx > -1) {
                        customEngines.value.splice(idx, 1);
                        const activeIdx = activeEngines.value.findIndex(e => e.id === id);
                        if (activeIdx > -1) activeEngines.value.splice(activeIdx, 1);
                        showToastMessage('已删除自定义 AI');
                        saveToStorage();
                    }
                }

                function clearCustomEngines() {
                    if (!confirm('确定要清空所有自定义 AI 吗?')) return;
                    customEngines.value = [];
                    activeEngines.value = activeEngines.value.filter(e => !e.isCustom);
                    showToastMessage('已清空自定义 AI');
                    saveToStorage();
                }

                function resetCustomForm() {
                    customForm.value = { name: '', url: '', avatar: '', color: presetColors[0], category: 'chat' };
                }

                // ========== 工作流管理 ==========
                function startCreateWorkflow() {
                    isEditingWorkflow.value = true;
                    editingWorkflowEngines.value = [];
                    editingWorkflowName.value = '新工作流 ' + (workflows.value.length + 1);
                    showToastMessage('请选择要添加的 AI 引擎', 'warning');
                }

                function cancelEditWorkflow() {
                    isEditingWorkflow.value = false;
                    editingWorkflowEngines.value = [];
                    editingWorkflowName.value = '';
                }

                function toggleEngineInWorkflow(engine) {
                    const idx = editingWorkflowEngines.value.indexOf(engine.id);
                    if (idx > -1) {
                        editingWorkflowEngines.value.splice(idx, 1);
                    } else {
                        editingWorkflowEngines.value.push(engine.id);
                    }
                }

                function isInEditingWorkflow(id) {
                    return editingWorkflowEngines.value.includes(id);
                }

                function saveWorkflow() {
                    if (!editingWorkflowEngines.value.length) {
                        showToastMessage('请至少选择一个 AI 引擎', 'warning');
                        return;
                    }
                    
                    if (!editingWorkflowName.value.trim()) {
                        showToastMessage('请输入工作流名称', 'warning');
                        return;
                    }
                    
                    workflows.value.push({
                        id: 'workflow_' + Date.now(),
                        name: editingWorkflowName.value.trim(),
                        engines: [...editingWorkflowEngines.value]
                    });
                    
                    isEditingWorkflow.value = false;
                    editingWorkflowEngines.value = [];
                    showToastMessage('工作流创建成功');
                    saveToStorage();
                }

                function applyWorkflow(workflow) {
                    currentWorkflow.value = workflow;
                    activeEngines.value = workflow.engines.map(id => {
                        const engine = allEngines.value.find(e => e.id === id);
                        return engine ? {
                            ...engine, 
                            loading: true, 
                            fullscreen: false, 
                            dragging: false,
                            fromWorkflow: true, 
                            workflowName: workflow.name,
                            uniqueId: Date.now() + Math.random()
                        } : null;
                    }).filter(Boolean);
                    
                    showToastMessage(`已应用工作流:${workflow.name}`);
                    showWorkflowMenu.value = false;
                    saveToStorage();
                }

                function deleteWorkflow(id) {
                    if (!confirm('确定要删除这个工作流吗?')) return;
                    const idx = workflows.value.findIndex(w => w.id === id);
                    if (idx > -1) {
                        workflows.value.splice(idx, 1);
                        if (currentWorkflow.value?.id === id) currentWorkflow.value = null;
                        showToastMessage('工作流已删除');
                        saveToStorage();
                    }
                }

                function getWorkflowEnginesInfo(workflow) {
                    return workflow.engines.map(id => 
                        allEngines.value.find(e => e.id === id) || { id, avatar: '?', color: '#ccc' }
                    ).slice(0, 5);
                }

                // ========== 设置与数据 ==========
                function toggleTheme() {
                    isDarkMode.value = !isDarkMode.value;
                    document.documentElement.setAttribute('data-theme', isDarkMode.value ? 'dark' : 'light');
                    saveToStorage();
                }

                function toggleAddMenu() {
                    showAddMenu.value = !showAddMenu.value;
                    if (showAddMenu.value) {
                        showWorkflowMenu.value = false;
                        showSettings.value = false;
                    }
                }

                function toggleWorkflowMenu() {
                    showWorkflowMenu.value = !showWorkflowMenu.value;
                    if (showWorkflowMenu.value) {
                        showAddMenu.value = false;
                        showSettings.value = false;
                    }
                }

                function toggleSettings() {
                    showSettings.value = !showSettings.value;
                    if (showSettings.value) {
                        showAddMenu.value = false;
                        showWorkflowMenu.value = false;
                    }
                }

                function closeAllSidebars() {
                    showAddMenu.value = false;
                    showWorkflowMenu.value = false;
                    showSettings.value = false;
                }

                function exportData() {
                    const data = { 
                        customEngines: customEngines.value, 
                        workflows: workflows.value, 
                        settings: { isDarkMode: isDarkMode.value },
                        exportTime: new Date().toISOString(),
                        version: '3.1'
                    };
                    const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
                    const url = URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = `ai-engines-config-${new Date().toISOString().slice(0,10)}.json`;
                    a.click();
                    URL.revokeObjectURL(url);
                    showToastMessage('配置已导出');
                }

                function importData() {
                    const input = document.createElement('input');
                    input.type = 'file';
                    input.accept = '.json';
                    input.onchange = e => {
                        const file = e.target.files[0];
                        if (!file) return;
                        const reader = new FileReader();
                        reader.onload = event => {
                            try {
                                const data = JSON.parse(event.target.result);
                                if (data.customEngines) customEngines.value = data.customEngines;
                                if (data.workflows) workflows.value = data.workflows;
                                if (data.settings?.isDarkMode !== undefined) {
                                    isDarkMode.value = data.settings.isDarkMode;
                                    document.documentElement.setAttribute('data-theme', isDarkMode.value ? 'dark' : 'light');
                                }
                                saveToStorage();
                                showToastMessage('配置已导入');
                            } catch {
                                showToastMessage('配置文件格式错误', 'warning');
                            }
                        };
                        reader.readAsText(file);
                    };
                    input.click();
                }

                function clearAllData() {
                    if (!confirm('确定要清空所有数据吗?此操作不可恢复!')) return;
                    customEngines.value = [];
                    workflows.value = [];
                    activeEngines.value = [];
                    localStorage.removeItem('ai_engines_data_v3');
                    showToastMessage('所有数据已清空');
                }

                function resetToDefault() {
                    if (!confirm('确定要恢复默认设置吗?')) return;
                    customEngines.value = [];
                    workflows.value = [];
                    activeEngines.value = [];
                    isDarkMode.value = false;
                    document.documentElement.setAttribute('data-theme', 'light');
                    localStorage.removeItem('ai_engines_data_v3');
                    showToastMessage('已恢复默认设置');
                }

                // ========== 生命周期 ==========
                const keyHandlers = {
                    'a': toggleAddMenu, 
                    'w': toggleWorkflowMenu, 
                    't': toggleTheme,
                    'b': toggleBatchMode,
                    'r': () => { 
                        activeEngines.value.forEach(refreshEngine); 
                        showToastMessage('已刷新所有引擎'); 
                    },
                    ',': toggleSettings
                };

                function handleKeydown(e) {
                    if (!e.ctrlKey) {
                        if (e.key === 'Escape') {
                            closeAllSidebars();
                            showShortcutModal.value = false;
                            if (isBatchMode.value) clearSelection();
                        }
                        return;
                    }
                    
                    const key = e.key.toLowerCase();
                    if (keyHandlers[key]) {
                        e.preventDefault();
                        keyHandlers[key]();
                    }
                    
                    if (e.shiftKey && key === 'c') {
                        e.preventDefault();
                        if (activeEngines.value.length) {
                            activeEngines.value = [];
                            showToastMessage('已关闭所有引擎');
                            saveToStorage();
                        }
                    }
                }

                onMounted(() => {
                    loadFromStorage();
                    document.addEventListener('keydown', handleKeydown);
                    console.log('🚀 一堆AI v3.1 已启动');
                    console.log('📊 已收录 12 个国内热门AI工具');
                    console.log('💬 AI对话: 豆包, 元宝, Kimi, 文心一言, 通义千问, 讯飞星火, 腾讯混元');
                    console.log('🔍 AI搜索: 纳米AI搜索, 天工AI');
                    console.log('🎬 AI视频: 可灵AI, Vidu');
                    console.log('🎨 AI绘画: 秒画');
                    console.log('⚠️ 精简版:仅保留核心工具');
                });

                onUnmounted(() => {
                    document.removeEventListener('keydown', handleKeydown);
                });

                return {
                    // 状态
                    showAddMenu, showWorkflowMenu, showSettings, showCustomForm, showToast, 
                    showShortcutModal, isEditingWorkflow, isBatchMode, editingWorkflowEngines,
                    toastMessage, toastType, toastIcon, isDarkMode, currentWorkflow, 
                    presetColors, customForm, currentCategory, selectedEngines,
                    presetEngines, customEngines, activeEngines, workflows, allEngines, 
                    filteredEngines, sidebarOpen, categories,
                    
                    // 配置
                    engineActions, shortcuts, dataBtns, customFormFields,
                    
                    // 方法
                    toggleEngine, isActive, removeEngine, refreshEngine, toggleFullscreen,
                    dragStart, dragEnd, drop, addCustomEngine, deleteCustomEngine,
                    clearCustomEngines, resetCustomForm, toggleTheme, toggleAddMenu,
                    toggleWorkflowMenu, toggleSettings, closeAllSidebars, startCreateWorkflow,
                    cancelEditWorkflow, toggleEngineInWorkflow, isInEditingWorkflow,
                    saveWorkflow, applyWorkflow, deleteWorkflow, getWorkflowEnginesInfo,
                    exportData, importData, clearAllData, resetToDefault, showToastMessage,
                    getCategoryLabel, toggleBatchMode, handleEngineClick, clearSelection,
                    addSelectedToActive, addSelectedToWorkflow
                };
            }
        }).mount('#app');
    </script>
</body>
</html>

七、桌面端

今年5月份,发现了一个桌面端软件开发工具,叫aardio,于是我就想能不能将网站做成一个桌面端工具,于是在一堆ai的帮助下,形成了一堆AI的桌面端,以下是源代码和软件下载路径。

import win.ui;
import web.view; // WebView2 浏览器控件
/*DSG{{*/
var winform = win.form(text="一堆AI";right=1173;bottom=752)
winform.add()
/*}}*/

// 创建WebView窗口,铺满整个窗体
var wb = web.view(winform);

// 加载线上网址(也可改为 wb.html = "..." 加载本地HTML)
wb.go("https://yiduiai.com/");

winform.show();
win.loopMessage();

一堆AI.exe

本项目仅供个人学习与交流使用,版权所有,未经许可禁止篡改、二次分发及商用。

相关文章
电脑初步设置

电脑初步设置

系统的安装 一、准备工作 1、U盘:容量至少8GB(Windows 11建议16GB以上),备份U盘数据(制作过程会清空)。

一堆AI开发历程

一、项目开发背景与初衷 自2024年起,各类AI工具已然成为我日常工作与生活中不可或缺的辅助工具。随着人工智能技术快速迭代,豆包、Kimi、腾讯元宝、文心一言、通义千问等多款AI平台相继成熟,不同工具拥有各自独特的优势,适配文本创作、代码编写、图片生成、视频制作、智能搜索等差异化工作场景。 在长期使

目录
当前文章没有目录
  • 微信
Copyright © 2026 知乎林业 All Rights Reserved. Powered by Halo.
鄂ICP备2025096233号-2
gongan beian 闽公网安备35010402351720号