AI对话导出word/json/md - DeepSeek/纳米/腾讯元宝/Kimi/通义千问/讯飞星火/豆包

支持DeepSeek,纳米AI,腾讯元宝,kimi,通义千问和讯飞星火的对话导出功能,支持JSON、Markdown和word格式,豆包中的图片也能保存

// ==UserScript==
// @name         AI对话导出word/json/md - DeepSeek/纳米/腾讯元宝/Kimi/通义千问/讯飞星火/豆包
// @namespace    http://tampermonkey.net/
// @version      2025.7.13-2
// @description  支持DeepSeek,纳米AI,腾讯元宝,kimi,通义千问和讯飞星火的对话导出功能,支持JSON、Markdown和word格式,豆包中的图片也能保存
// @author       各位大神 + deepseek + 春秋
// @match        *://chat.deepseek.com/*
// @match        *://bot.n.cn/*
// @match        *://yuanbao.tencent.com/*
// @match        *://*.kimi.com/*
// @match        *://*.tongyi.com/*
// @match        *://*.xfyun.cn/*
// @match        *://*.doubao.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 配置常量
    const CONFIG = {
        API_ENDPOINT_WORD: 'https://api.any2card.com/api/md-to-word',
        DEFAULT_TITLE: '未命名对话',
        BUTTON_OPACITY: 0.3,
        BUTTON_HOVER_OPACITY: 1,
        SPINNER_ANIMATION_DURATION: '1s',
        REL_AND_KNOWLEDGE: false // 默认值为false,表示默认不导出参考链接和知识库
    };

    // 平台URL模式
    const PLATFORM_PATTERNS = {
        deepseek: /chat_session_id=/,
        ncn: /conversation\/info\?conversation_id=/,
        yuanbao: /\/api\/user\/agent\/conversation\/v1\/detail/,
        kimi: /\/api\/chat\/.+\/segment\/scroll/,
        tongyi: /\/dialog\/chat\/list/,
        iflytek: /\/iflygpt\/u\/chat_history\/all\//,
        doubao: /\/alice\/message\/list/,
        doubao_chat: /\/api\/chat\/list/ // 备用豆包聊天列表API
    };

    // 状态管理
    const state = {
        targetResponse: null,
        lastUpdateTime: null,
        convertedMd: null,
        platformType: null,
        messageStats: {
            totalTokens: 0,
            totalChars: 0,
            fileCount: 0,
            questions: 0,
            convTurns: 0
        },
        currentTitle: CONFIG.DEFAULT_TITLE,
        kimiSessionId: null,
        kimiTitleCache: null,
        authToken: null
    };

    // 日志工具
    const logger = {
        info: (msg, data) => console.log(`[AI对话导出] INFO: ${msg}`, data || ''),
        warn: (msg, data) => console.warn(`[AI对话导出] WARN: ${msg}`, data || ''),
        error: (msg, error) => console.error(`[AI对话导出] ERROR: ${msg}`, error || '')
    };

    // 工具函数
    const utils = {
        formatTimestamp: (timestamp) => {
            if (!timestamp) return 'N/A';
            let dt;
            try {
                // 1. 检查是否是数字(Unix 时间戳)
                if (typeof timestamp === 'number') {
                    // 判断是秒级还是毫秒级(通常毫秒级时间戳 >= 1e12)
                    dt = new Date(timestamp < 1e12 ? timestamp * 1000 : timestamp);
                }
                // 2. 检查是否是字符串(ISO 8601 或类似格式)
                else if (typeof timestamp === 'string') {
                    dt = new Date(timestamp);
                }
                // 3. 其他情况(如已经是 Date 对象)
                else {
                    dt = new Date(timestamp);
                }

                // 检查日期是否有效
                if (isNaN(dt.getTime())) {
                    return 'Invalid Date';
                }

                // 使用瑞典格式 (YYYY-MM-DD HH:MM:SS) 并替换特殊字符
                return dt.toLocaleString('sv').replace(/[T \/]/g, '_');
            } catch (e) {
                logger.error('时间戳格式化错误', e);
                return 'Invalid Date';
            }
        },

        adjustHeaderLevels: (text, increaseBy = 1) => {
            if (!text) return '';
            return text.replace(/^(#+)(\s*)(.*?)\s*$/gm, (match, hashes, space, content) => {
                return '#'.repeat(hashes.length + increaseBy) + ' ' + content.trim();
            });
        },

        getLocalTimestamp: () => {
            const d = new Date();
            const offset = d.getTimezoneOffset() * 60 * 1000;
            return new Date(d.getTime() - offset).toISOString()
                .slice(0, 19)
                .replace(/T/g, '_')
                .replace(/:/g, '-');
        },

        sanitizeFilename: (name) => {
            return name.replace(/[\/\\?%*:|"<>]/g, '-');
        },

        createBlobDownload: (content, type, filename) => {
            try {
                const blob = new Blob([content], { type });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.style.display = 'none';
                a.href = url;
                a.download = filename;
                document.body.appendChild(a);
                a.click();
                setTimeout(() => {
                    document.body.removeChild(a);
                    URL.revokeObjectURL(url);
                }, 100);
                return true;
            } catch (e) {
                logger.error('创建Blob下载失败', e);
                return false;
            }
        }
    };

    // 平台特定的转换器
    const platformConverters = {
        deepseek: (data) => {
            let mdContent = [];
            const title = data.data?.biz_data?.chat_session?.title || CONFIG.DEFAULT_TITLE;
            const totalTokens = data.data?.biz_data?.chat_messages?.reduce((acc, msg) =>
                acc + (msg.accumulated_token_usage || 0), 0) || 0;

            mdContent.push(`# DeepSeek对话 - ${title}`);
            mdContent.push(`\n> 统计信息:累计Token用量 ${totalTokens}`);

            data.data?.biz_data?.chat_messages?.forEach(msg => {
                if (msg.role === 'USER') return;

                const role = msg.role === 'USER' ? 'Human' : 'Assistant';
                mdContent.push(`### ${role}`);
                mdContent.push(`*${utils.formatTimestamp(msg.inserted_at)}*\n`);

                if (msg.files?.length > 0) {
                    msg.files.forEach(file => {
                        const insertTime = new Date(file.inserted_at * 1000).toISOString();
                        const updateTime = new Date(file.updated_at * 1000).toISOString();
                        mdContent.push(`### File Information`);
                        mdContent.push(`- Name: ${file.file_name}`);
                        mdContent.push(`- Size: ${file.file_size} bytes`);
                        mdContent.push(`- Token Usage: ${file.token_usage}`);
                        mdContent.push(`- Upload Time: ${insertTime}`);
                        mdContent.push(`- Last Update: ${updateTime}\n`);
                    });
                }

                let content = msg.content || '';

                if (msg.search_results?.length > 0) {
                    const citations = {};
                    msg.search_results.forEach((result) => {
                        if (result.cite_index !== null) {
                            citations[result.cite_index] = result.url;
                        }
                    });
                    content = content.replace(/\[citation:(\d+)\]/g, (match, p1) => {
                        const url = citations[parseInt(p1)];
                        return url ? ` [${p1}](${url})` : match;
                    });
                    content = content.replace(/\s+,/g, ',').replace(/\s+\./g, '.');
                }

                if (msg.thinking_content) {
                    const thinkingTime = msg.thinking_elapsed_secs ? `(${msg.thinking_elapsed_secs}s)` : '';
                    content += `\n\n**Thinking Process ${thinkingTime}:**\n${msg.thinking_content}`;
                }

                content = content.replace(/\$\$(.*?)\$\$/gs, (match, formula) => {
                    return formula.includes('\n') ? `\n$$\n${formula}\n$$\n` : `$$${formula}$$`;
                });

                mdContent.push(content + '\n');
            });
            return mdContent.join('\n');
        },

        ncn: (data) => {
            let mdContent = [];
            const meta = data.data || {};
            mdContent.push(`# 纳米AI对话记录 - ${meta.title || CONFIG.DEFAULT_TITLE}`);
            mdContent.push(`**生成时间**: ${utils.formatTimestamp(data.timestamp) || '未知'}`);


            const totalChars = meta.messages?.reduce((acc, msg) =>
                acc + (msg.result?.length || 0), 0) || 0;
            const questions = meta.messages?.reduce((acc, msg) =>
                acc + (msg.ask_further?.length || 0), 0) || 0;

            mdContent.push(`\n> 统计信息:总字数 ${totalChars} | 后续问题 ${questions} 个`);

            meta.messages?.forEach(msg => {
                if (msg.file?.length) {
                    mdContent.push('### 附件信息');
                    msg.file.forEach(file => {
                        mdContent.push(`- ${file.title} (${(file.size / 1024).toFixed(1)}KB)`);
                    });
                }

                // 用户提问和AI回复
                mdContent.push(`\n## 用户提问\n${msg.prompt || '无内容'}`);
                // 处理AI回复内容,移除引用标记
                let cleanResult = msg.result || '无内容';
                // 移除类似[[3]()][[7]()]的引用标记
                cleanResult = cleanResult.replace(/\[\[\d+\]\(\)\]/g, '');
                // 移除多余的换行和空格
                cleanResult = cleanResult.replace(/\n{3,}/g, '\n').trim();
                mdContent.push(`\n## AI回复\n${cleanResult}`);
                //mdContent.push(`## AI回复\n${msg.result || '无内容'}`);

                // 推荐追问
                if (msg.ask_further?.length) {
                    mdContent.push('### 推荐追问');
                    msg.ask_further.forEach(q => {
                        mdContent.push(`- ${q.content}`);
                    });
                }

                //是否需要导出,通过全局配置进行选择
                if(CONFIG.REL_AND_KNOWLEDGE){
                    // 参考链接
                    if (msg.refer_search?.length) {
                        mdContent.push('\n### 参考来源');
                        msg.refer_search.forEach(ref => {
                            mdContent.push(`- [${ref.title || '无标题'}](${ref.url}) - ${ref.summary || '无摘要'}`);
                            if (ref.date) mdContent.push(`  - 发布日期: ${ref.date}`);
                            if (ref.site) mdContent.push(`  - 来源网站: ${ref.site}`);
                        });
                    }

                    // 用户知识库引用
                    if (msg.user_knowledge?.length) {
                        mdContent.push('\n### 知识库引用');
                        const uniqueFiles = new Map();
                        msg.user_knowledge.forEach(knowledge => {
                            if (knowledge.file_name && !uniqueFiles.has(knowledge.file_id)) {
                                uniqueFiles.set(knowledge.file_id, knowledge);
                                mdContent.push(`- ${knowledge.file_name} (来自文件夹: ${knowledge.folder_id || '未知'})`);
                            }
                        });
                    }
                }

                mdContent.push('\n---\n');
            });
            return mdContent.join('\n');
        },

        yuanbao: (data) => {
            if (!data?.convs || !Array.isArray(data.convs)) {
                logger.error('无效的元宝数据', data);
                return '# 错误:无效的JSON数据\n\n无法解析对话内容。';
            }

            let markdownContent = [];
            const title = data.sessionTitle || data.title || '元宝对话记录';
            markdownContent.push(`# ${title}\n`);

            if (data.multiMediaInfo?.length > 0) {
                markdownContent.push('**包含的多媒体文件:**\n');
                data.multiMediaInfo.forEach(media => {
                    markdownContent.push(`* [${media.fileName || '未知文件'}](${media.url || '#'}) (${media.type || '未知类型'})\n`);
                });
                markdownContent.push('---\n');
            }

            const sortedConvs = [...data.convs].sort((a, b) => (a.index || 0) - (b.index || 0));

            sortedConvs.forEach(turn => {
                if (turn.speaker === 'human') return;

                const timestamp = utils.formatTimestamp(turn.createTime);
                const index = turn.index !== undefined ? turn.index : 'N/A';

                if (turn.speaker === 'ai') {
                    markdownContent.push(`\n## AI (轮次 ${index})\n`);

                    let modelDisplay = '未知模型';
                    let primaryPluginId = '无插件';

                    if (turn.speechesV2?.length > 0) {
                        const firstSpeech = turn.speechesV2[0];
                        const modelIdRaw = firstSpeech.chatModelId;
                        primaryPluginId = firstSpeech.pluginId || primaryPluginId;

                        if (modelIdRaw && String(modelIdRaw).trim() !== '') {
                            modelDisplay = `\`${modelIdRaw}\``;
                        }
                    }

                    markdownContent.push(`*时间: ${timestamp} | 模型: ${modelDisplay} | 插件: \`${primaryPluginId}\`*\n\n`);

                    turn.speechesV2?.forEach(speech => {
                        speech.content?.forEach(block => {
                            switch (block.type) {
                                case 'text':
                                    markdownContent.push(`${utils.adjustHeaderLevels(block.msg || '', 1)}\n\n`);
                                    break;
                                case 'think':
                                    markdownContent.push(`> **[思考过程]** ${block.title || ''}\n>\n`);
                                    (block.content || '无思考内容').split('\n').forEach(line => {
                                        markdownContent.push(`> ${line}\n`);
                                    });
                                    markdownContent.push('\n');
                                    break;
                                case 'searchGuid':
                                    markdownContent.push(`**${block.title || '搜索结果'}** (查询: \`${block.botPrompt || 'N/A'}\` | 主题: ${block.topic || 'N/A'})\n`);
                                    block.docs?.forEach((doc, docIndex) => {
                                        markdownContent.push(`* [${docIndex + 1}] [${doc.title || '无标题'}](${doc.url || '#'}) (${doc.sourceName || '未知来源'})\n    * > ${doc.quote || '无引用'}\n`);
                                    });
                                    markdownContent.push('\n');
                                    break;
                                case 'image':
                                case 'code':
                                case 'pdf':
                                    markdownContent.push(`*文件:* [${block.fileName || '未知文件'}](${block.url || '#'}) (类型: ${block.type})\n\n`);
                                    break;
                            }
                        });
                    });
                }
                markdownContent.push('\n---\n');
            });

            return markdownContent.join('\n').replace(/\n---\n$/, '').trim();
        },

        kimi: async (data) => {
            let mdContent = [];
            const title = await fetchKimiChatTitle() || 'Kimi对话记录';
            mdContent.push(`# ${title}\n`);

            data.items?.forEach(item => {
                const role = item.role === 'user' ? 'Human' : 'Assistant';
                const timestamp = utils.formatTimestamp(item.created_at);
                mdContent.push(`### ${role}`);
                mdContent.push(`*${timestamp}*\n`);

                item.contents?.zones?.forEach(zone => {
                    zone.sections?.forEach(section => {
                        if (section.view === 'k1' && section.k1?.text) {
                            mdContent.push("**推理过程**\n> ");
                            mdContent.push(section.k1.text
                                .replace(/^\n+|\n+$/g, '')
                                .replace(/\n{2,}/g, '\n') + '\n');
                        }

                        if (section.view === 'cmpl' && section.cmpl) {
                            let content = section.cmpl
                                .replace(/\[citation:\d+\]/g, '')
                                .replace(/\n{3,}/g, '\n\n');
                            content = content.replace(/```([\s\S]*?)```/g, '\n```$1```\n');
                            mdContent.push(content);
                        }
                    });
                });
                mdContent.push('\n---\n');
            });

            return mdContent.join('\n');
        },

        tongyi: (data) => {
            let mdContent = [];
            mdContent.push(`# 通义千问对话记录`);

            const convTurns = data.data?.length || 0;
            const totalChars = data.data?.reduce((acc, msg) => {
                return acc + (msg.contents?.reduce((sum, content) =>
                    sum + (content.content?.length || 0), 0) || 0);
            }, 0) || 0;

            mdContent.push(`\n> 统计信息:对话轮次 ${convTurns} | 总字数 ${totalChars}`);

            if (data.data?.length) {
                const sortedMessages = [...data.data].sort((a, b) => a.createTime - b.createTime);

                sortedMessages.forEach(msg => {
                    const time = utils.formatTimestamp(msg.createTime);
                    const role = msg.senderType === 'USER' ? '用户' : '助手';
                    mdContent.push(`## ${role} (${time})\n`);

                    msg.contents?.forEach(content => {
                        if (content.content) {
                            let text = content.content.replace(/\n/g, '\n\n');
                            text = text.replace(/```([\s\S]*?)```/g, '\n```$1```\n');
                            mdContent.push(text);
                        }
                    });
                    mdContent.push('\n---\n');
                });
            }
            return mdContent.join('\n');
        },

        iflytek: (data) => {
            let mdContent = [];
            mdContent.push(`# 讯飞星火对话记录`);

            const convTurns = data.data?.[0]?.historyList?.length || 0;
            const totalChars = data.data?.[0]?.historyList?.reduce((acc, msg) => {
                return acc + (msg.message?.length || 0) + (msg.answer?.length || 0);
            }, 0) || 0;

            mdContent.push(`\n> 统计信息:对话轮次 ${convTurns} | 总字数 ${totalChars}`);

            data.data?.[0]?.historyList?.forEach(msg => {
                if (msg.type === 0) {
                    const time = utils.formatTimestamp(msg.createTime);
                    mdContent.push(`## 用户 (${time})\n\n${msg.message}\n`);
                } else if (msg.message) {
                    const time = utils.formatTimestamp(msg.createTime);
                    let sourceInfo = '';

                    if (msg.traceSource) {
                        try {
                            const sources = JSON.parse(msg.traceSource);
                            if (Array.isArray(sources)) {
                                sources.forEach(source => {
                                    if (source.type === 'searchSource' && source.data) {
                                        sourceInfo += '\n\n**参考来源**:\n';
                                        source.data.forEach(item => {
                                            sourceInfo += `- [${item.docid}](${item.source})\n`;
                                        });
                                    }
                                });
                            }
                        } catch (e) {
                            logger.error('解析来源信息失败', e);
                        }
                    }

                    mdContent.push(`## AI助手 (${time})\n\n${msg.message}${sourceInfo}\n`);
                }
                mdContent.push('---\n');
            });

            return mdContent.join('\n');
        },
        //豆包消息转换
        doubao:(data) => {
            let mdContent = [];
            const title = data.data?.title || '豆包对话记录';
            mdContent.push(`# ${title}\n`);

            // 统计信息
            const totalMessages = data.data?.message_list?.length || 0;
            const totalChars = data.data?.message_list?.reduce((acc, msg) => {
                try {
                    if (msg.content_type === 1) {
                        const contentObj = typeof msg.content === 'string' ? JSON.parse(msg.content || '{}') : msg.content;
                        return acc + (contentObj.text?.length || 0);
                    } else if (msg.content_type === 9999) {
                        let length = 0;
                        msg.content_block?.forEach(block => {
                            try {
                                const blockContent = typeof block.content === 'string' ? JSON.parse(block.content || '{}') : block.content;
                                if (block.block_type === 10000) {
                                    length += (blockContent.text?.length || 0);
                                } else if (block.block_type === 2074) {
                                    blockContent.creations?.forEach(creation => {
                                        if (creation.type === 1 && creation.image?.gen_params?.prompt) {
                                            length += creation.image.gen_params.prompt.length;
                                        }
                                    });
                                }
                            } catch (e) {
                                console.error('解析内容块失败', e);
                            }
                        });
                        return acc + length;
                    }
                    return acc + (msg.content?.length || 0);
                } catch {
                    return acc + (msg.content?.length || 0);
                }
            }, 0) || 0;

            mdContent.push(`> 统计信息:消息数 ${totalMessages} | 总字数 ${totalChars}\n`);

            // 处理消息内容 - 按时间倒序排列
            const sortedMessages = [...(data.data?.message_list || [])].sort((a, b) => b.create_time - a.create_time);

            sortedMessages.forEach(msg => {
                const time = utils.formatTimestamp(msg.create_time);
                const role = msg.user_type === 1 ? '用户' : '豆包AI';

                mdContent.push(`## ${role} (${time})`);

                // 处理消息内容
                let content = '';
                try {
                    if (msg.content_type === 1) { // 文本消息
                        const contentObj = typeof msg.content === 'string' ? JSON.parse(msg.content || '{}') : msg.content;
                        content = contentObj.text || msg.content || '';
                    } else if (msg.content_type === 9999) { // 复合消息
                        if (msg.content_block?.length) {
                            msg.content_block.forEach(block => {
                                try {
                                    const blockContent = typeof block.content === 'string' ? JSON.parse(block.content || '{}') : block.content;
                                    if (block.block_type === 10000) { // 文本块
                                        content += (blockContent.text || '') + '\n\n';
                                    } else if (block.block_type === 2074) { // 图片块
                                        content += platformConverters.processDoubaoMediaContent(block) + '\n\n';
                                    }
                                } catch (e) {
                                    console.error('解析内容块失败', e);
                                }
                            });
                        }
                    } else if (msg.content_type === 6) { // 图片消息
                        content = platformConverters.processDoubaoMediaContent(msg);
                    }
                } catch (e) {
                    console.error('解析消息内容失败', e);
                    content = msg.content || '';
                }

                // 处理代码块和换行
                content = content.replace(/```([\s\S]*?)```/g, '\n```$1```\n')
                    .replace(/\n{3,}/g, '\n\n');

                mdContent.push(content);

                // 处理语音转文字内容
                if (msg.tts_content && msg.tts_content !== content) {
                    mdContent.push('\n**语音转文字:**\n' + msg.tts_content);
                }

                mdContent.push('\n---\n');
            });

            return mdContent.join('\n');
        },

        // 处理豆包的媒体内容(图片等)
        processDoubaoMediaContent:(blockOrMsg) => {
            let content = [];

            try {
                // 获取内容对象
                const contentObj = typeof blockOrMsg.content === 'string'
                ? JSON.parse(blockOrMsg.content || '{}')
                : blockOrMsg.content;

                // 如果是复合消息中的图片块(block_type=2074)
                if (blockOrMsg.block_type === 2074) {
                    const creations = contentObj.creations || [];
                    creations.forEach(creation => {
                        if (creation.type === 1 && creation.image) {
                            const img = creation.image;
                            const originalUrl = img.image_raw?.url || img.image_ori?.url;

                            if (originalUrl) {
                                content.push(`![${img.description || '图片'}](${originalUrl})`);
                                if (img.gen_params?.prompt) {
                                    content.push(`**提示词:** ${img.gen_params.prompt}`);
                                }
                            }
                        }
                    });
                }
                // 如果是单独的图片消息(content_type=6)
                else if (blockOrMsg.content_type === 6) {
                    if (contentObj.image_list) {
                        contentObj.image_list.forEach(image => {
                            const url = image.image_raw?.url || image.image_ori?.url;
                            if (url) {
                                content.push(`![${image.description || '图片'}](${url})`);
                            }
                        });
                    }
                }
            } catch (e) {
      console.error('处理媒体内容失败', e);
            }

            return content.join('\n\n');
        } ,
        // 豆包聊天列表转换器
        doubao_chat: (data) => {
            let mdContent = [];
            mdContent.push(`# 豆包对话列表\n`);

            // 统计信息
            const totalChats = data.data?.length || 0;
            mdContent.push(`> 统计信息:共 ${totalChats} 个对话\n`);

            // 处理对话列表
            data.data?.forEach(chat => {
                const time = utils.formatTimestamp(chat.create_time);
                mdContent.push(`## ${chat.title || '未命名对话'} (${time})`);
                mdContent.push(`- 对话ID: ${chat.conversation_id}`);
                mdContent.push(`- 最后更新时间: ${new Date(chat.update_time * 1000).toLocaleString()}`);
                mdContent.push(`- 消息数: ${chat.message_count || 0}`);
                mdContent.push('\n---\n');
            });

            return mdContent.join('\n');
        }

    };

    // 核心处理函数
    async function processTargetResponse(text, url) {
        try {
            let detectedPlatform = null;
            for (const [platform, pattern] of Object.entries(PLATFORM_PATTERNS)) {
                if (pattern.test(url)) {
                    detectedPlatform = platform;
                    break;
                }
            }

            if (!detectedPlatform) return;

            state.targetResponse = text;
            state.platformType = detectedPlatform;
            state.lastUpdateTime = new Date().toLocaleTimeString();

            // 重置统计信息
            state.messageStats = {
                totalTokens: 0,
                totalChars: 0,
                fileCount: 0,
                questions: 0,
                convTurns: 0
            };

            const jsonData = JSON.parse(text);
            const match = url.match(/\/api\/chat\/(.+?)\/segment\/scroll/);//Kimi

            // 平台特定处理
            switch(detectedPlatform) {
                case 'deepseek':
                    state.convertedMd = platformConverters.deepseek(jsonData);
                    state.messageStats.totalTokens = jsonData.data?.biz_data?.chat_messages?.reduce(
                        (acc, msg) => acc + (msg.accumulated_token_usage || 0), 0) || 0;
                    state.currentTitle = jsonData.data?.biz_data?.chat_session?.title || 'deepseek-Chat';
                    break;

                case 'ncn':
                    state.convertedMd = platformConverters.ncn(jsonData);
                    state.messageStats.totalChars = jsonData.data?.messages?.reduce(
                        (acc, msg) => acc + (msg.result?.length || 0), 0) || 0;
                    state.messageStats.questions = jsonData.data?.messages?.reduce(
                        (acc, msg) => acc + (msg.ask_further?.length || 0), 0) || 0;
                    state.currentTitle = jsonData.data?.title || 'AI-Chat';
                    break;

                case 'yuanbao':
                    state.convertedMd = platformConverters.yuanbao(jsonData);
                    state.messageStats.convTurns = jsonData.convs?.length || 0;
                    state.currentTitle = jsonData.sessionTitle || jsonData.title || 'Yuanbao-Chat';
                    state.messageStats.fileCount = jsonData.multiMediaInfo?.length || 0;
                    break;

                case 'kimi':
                    //const match = url.match(/\/api\/chat\/(.+?)\/segment\/scroll/);
                    if (match?.[1]) {
                        state.kimiSessionId = match[1];
                        state.currentTitle = await fetchKimiChatTitle();
                    }
                    state.convertedMd = await platformConverters.kimi(jsonData);
                    state.messageStats.totalChars = jsonData.items?.reduce(
                        (acc, item) => acc + (JSON.stringify(item.contents).length || 0), 0) || 0;
                    break;

                case 'tongyi':
                    state.convertedMd = platformConverters.tongyi(jsonData);
                    state.messageStats.convTurns = jsonData.data?.length || 0;
                    state.messageStats.totalChars = jsonData.data?.reduce((acc, msg) => {
                        return acc + (msg.contents?.reduce(
                            (sum, content) => sum + (content.content?.length || 0), 0) || 0);
                    }, 0) || 0;
                    state.currentTitle = '通义千问对话';
                    break;

                case 'iflytek':
                    state.convertedMd = platformConverters.iflytek(jsonData);
                    state.messageStats.convTurns = jsonData.data?.[0]?.historyList?.length || 0;
                    state.messageStats.totalChars = jsonData.data?.[0]?.historyList?.reduce((acc, msg) => {
                        return acc + (msg.message?.length || 0) + (msg.answer?.length || 0);
                    }, 0) || 0;
                    state.currentTitle = '讯飞星火对话';
                    break;
                case 'doubao':
                case 'doubao_chat':
                    state.convertedMd = platformConverters.doubao(jsonData);
                    state.messageStats.totalChars = jsonData.data?.message_list?.reduce((acc, msg) => {
                        try {
                            const contentObj = JSON.parse(msg.content || '{}');
                            return acc + (contentObj.text?.length || 0);
                        } catch {
                            return acc + (msg.content?.length || 0);
                        }
                    }, 0) || 0;
                    state.messageStats.convTurns = jsonData.data?.message_list?.length || 0;
                    state.currentTitle = '豆包对话';
                    break;
            }

            ui.updateButtonStatus();
            logger.info(`成功处理${detectedPlatform.toUpperCase()}响应`);
        } catch (e) {
            logger.error('响应处理错误', e);
        }
    }

    // Kimi相关功能
    // sessionId提取
    function getKimiSessionId() {
        const currentUrl = window.location.href;
        // 匹配多种可能的URL格式
        const match = currentUrl.match(/(?:chat|chat_session)\/([a-zA-Z0-9-]+)(?:\/|$)/);
        return match ? match[1] : null;
    }

    // 拦截XHR请求获取授权令牌
    function setupAuthInterceptor() {
        const originalOpen = XMLHttpRequest.prototype.open;
        const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;

        XMLHttpRequest.prototype.open = function(method, url) {
            this._requestURL = url;
            if (url.includes('/api/chat/') && !url.includes('/api/chat/list')) {
                logger.info(`拦截到Kimi API请求: ${method} ${url}`);
                this._isChatAPI = true;
            }
            return originalOpen.apply(this, arguments);
        };

        XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
            if (this._isChatAPI && header.toLowerCase() === 'authorization') {
                state.authToken = value;
                logger.info('获取到Authorization头');
            }
            return originalSetRequestHeader.apply(this, arguments);
        };
    }

    // 获取Kimi对话标题
    async function fetchKimiChatTitle() {
        if (state.kimiTitleCache) return state.kimiTitleCache;

        state.kimiSessionId = getKimiSessionId();
        if (!state.kimiSessionId) {
            logger.warn('无法获取sessionId');
            return "Kimi对话";
        }

        // 等待获取授权令牌
        let retry = 0;
        while (!state.authToken && retry < 5) {
            logger.info('等待获取授权令牌...');
            await new Promise(resolve => setTimeout(resolve, 500));
            retry++;
        }

        if (!state.authToken) {
            logger.warn('未能获取授权令牌');
            return "Kimi对话";
        }

        try {
            const url = `https://www.kimi.com/api/chat/${state.kimiSessionId}`;
            logger.info(`请求标题API: ${url}`);

            const response = await fetch(url, {
                headers: {
                    'Authorization': state.authToken,
                    'Content-Type': 'application/json',
                },
                credentials: 'include'
            });

            if (!response.ok) {
                logger.warn(`API错误: ${response.status}`);
                return "Kimi对话";
            }

            const data = await response.json();
            logger.info('API响应:', data);

            state.kimiTitleCache = data.name || "Kimi对话";
            return state.kimiTitleCache;
        } catch (error) {
            logger.error('获取标题失败:', error);
            return "Kimi对话";
        }
    }

    // 导出功能
    // 提取获取文件名的公共逻辑
    function getExportFilename(extension = '') {
        const jsonData = JSON.parse(state.targetResponse || '{}');
        let platformPrefix = '';
        let defaultSuffix = 'Chat';

        switch(state.platformType) {
            case 'deepseek':
                platformPrefix = 'DeepSeek';
                defaultSuffix = 'deepseek-Chat';
                break;
            case 'ncn':
                platformPrefix = 'AI-N';
                defaultSuffix = 'AI-Chat';
                break;
            case 'yuanbao':
                platformPrefix = 'Yuanbao';
                defaultSuffix = 'Yuanbao-Chat';
                break;
            case 'kimi':
                platformPrefix = 'Kimi';
                defaultSuffix = 'Kimi-Chat';
                break;
            case 'tongyi':
                platformPrefix = 'Tongyi';
                defaultSuffix = 'Tongyi-Chat';
                break;
            case 'iflytek':
                platformPrefix = 'Iflytek';
                defaultSuffix = 'Iflytek-Chat';
                break;
            case 'doubao':
            case 'doubao_chat':
                platformPrefix = 'Doubao';
                defaultSuffix = state.platformType === 'doubao_chat' ? 'ChatList' : 'Chat';
                break;
            default:
                platformPrefix = 'AI';
                defaultSuffix = 'Chat';
        }

        const title = state.currentTitle ||
              jsonData.data?.biz_data?.chat_session?.title ||
              jsonData.data?.title ||
              jsonData.sessionTitle ||(state.platformType === 'doubao_chat' ? '豆包列表' : '豆包对话')||
              defaultSuffix;

        const sanitizedTitle = utils.sanitizeFilename(`${platformPrefix}_${title}`);
        return `${sanitizedTitle}_${utils.getLocalTimestamp()}${extension ? '.' + extension : ''}`;
    }

    const exportHandlers = {
        json: () => {
            if (!state.targetResponse) {
                alert('还没有发现有效的对话记录。\n请等待目标响应或进行一些对话。');
                return false;
            }

            try {
                const fileName = getExportFilename('json');
                return utils.createBlobDownload(
                    state.targetResponse,
                    'application/json',
                    fileName
                );
            } catch (e) {
                logger.error('JSON导出失败', e);
                alert('导出过程中发生错误,请查看控制台了解详情。');
                return false;
            }
        },

        markdown: () => {
            if (!state.convertedMd) {
                alert('还没有发现有效的对话记录。\n请等待目标响应或进行一些对话。');
                return false;
            }

            try {
                const fileName = getExportFilename('md');
                return utils.createBlobDownload(
                    state.convertedMd,
                    'text/markdown',
                    fileName
                );
            } catch (e) {
                logger.error('Markdown导出失败', e);
                alert(`导出失败: ${e.message}`);
                return false;
            }
        },

        word: () => {
            if (!state.convertedMd) {
                alert('还没有发现有效的对话记录。\n请等待目标响应或进行一些对话。');
                return;
            }

            try {
                const fileName = getExportFilename('docx');
                const chatName = fileName.replace(/_[\d_-]+\.docx$/, ''); // 移除时间戳部分作为标题

                const wordButton = document.getElementById('downloadWordButton');
                const originalText = wordButton.innerHTML;
                wordButton.innerHTML = '<span class="gm-spinner"></span>生成中...';
                wordButton.disabled = true;

                GM_xmlhttpRequest({
                    method: "POST",
                    url: CONFIG.API_ENDPOINT_WORD,
                    headers: { "Content-Type": "application/json" },
                    data: JSON.stringify({
                        markdown: state.convertedMd,
                        title: chatName
                    }),
                    responseType: 'blob',
                    onload: function(response) {
                        wordButton.innerHTML = originalText;
                        wordButton.disabled = false;

                        if (response.status >= 200 && response.status < 300) {
                            try {
                                const blob = response.response;
                                const url = URL.createObjectURL(blob);
                                const a = document.createElement('a');
                                a.style.display = 'none';
                                a.href = url;
                                a.download = fileName;
                                document.body.appendChild(a);
                                a.click();
                                setTimeout(() => {
                                    document.body.removeChild(a);
                                    URL.revokeObjectURL(url);
                                }, 100);
                                logger.info(`成功下载Word文件: ${fileName}`);
                            } catch (e) {
                                alert('下载文件时出错: ' + e.message);
                                logger.error("处理Word下载时出错", e);
                            }
                        } else {
                            const reader = new FileReader();
                            reader.onload = function() {
                                try {
                                    const errorResult = JSON.parse(this.result);
                                    alert(`导出失败: ${errorResult.error || '未知错误'}`);
                                } catch (e) {
                                    alert(`导出失败,状态码: ${response.status}。无法解析错误信息。`);
                                }
                            };
                            reader.readAsText(response.response);
                        }
                    },
                    onerror: function(response) {
                        wordButton.innerHTML = originalText;
                        wordButton.disabled = false;
                        alert(`请求错误: ${response.statusText || '无法连接到服务器'}`);
                    }
                });
            } catch (e) {
                logger.error('Word导出初始化失败', e);
                alert('导出初始化失败: ' + e.message);
            }
        }
    };

    // UI相关功能
    const ui = {
        updateButtonStatus: () => {
            const jsonButton = document.getElementById('downloadJsonButton');
            const mdButton = document.getElementById('downloadMdButton');
            const wordButton = document.getElementById('downloadWordButton');

            if (!jsonButton || !mdButton || !wordButton) return;

            const hasResponse = state.targetResponse !== null;
            let platformName = 'AI对话';
            let statsText = '';

            try {
                const jsonData = JSON.parse(state.targetResponse || '{}');

                switch(state.platformType) {
                    case 'deepseek':
                        platformName = `DeepSeek_${jsonData.data?.biz_data?.chat_session?.title || 'deepseek-Chat'}`;
                        statsText = `Token用量: ${state.messageStats.totalTokens}`;
                        break;
                    case 'ncn':
                        platformName = `AI-N_${jsonData.data?.title || 'AI-Chat'}`;
                        statsText = `字数: ${state.messageStats.totalChars} | 附件: ${state.messageStats.fileCount}`;
                        break;
                    case 'yuanbao':
                        platformName = `Yuanbao_${jsonData.sessionTitle || jsonData.title || 'Yuanbao-Chat'}`;
                        statsText = `对话轮次: ${state.messageStats.convTurns} | 文件: ${state.messageStats.fileCount}`;
                        break;
                    case 'kimi':
                        platformName = `Kimi_${state.currentTitle}`;
                        statsText = `消息数: ${state.messageStats.convTurns} | 字数: ${state.messageStats.totalChars}`;
                        break;
                    case 'tongyi':
                        platformName = `Tongyi_${state.currentTitle}`;
                        statsText = `对话轮次: ${state.messageStats.convTurns} | 字数: ${state.messageStats.totalChars}`;
                        break;
                    case 'iflytek':
                        platformName = `xinghuo_${state.currentTitle}`;
                        statsText = `对话轮次: ${state.messageStats.convTurns} | 字数: ${state.messageStats.totalChars}`;
                        break;
                    case 'doubao':
                    case 'doubao_chat':
                        platformName = `豆包${state.platformType === 'doubao_chat' ? '对话列表' : '对话'}`;
                        statsText = state.platformType === 'doubao_chat'
                            ? `对话数: ${state.messageStats.convTurns}`
                        : `消息数: ${state.messageStats.convTurns} | 字数: ${state.messageStats.totalChars} | 附件: ${state.messageStats.fileCount}`;
                        break;
                }
            } catch (e) {
                logger.error('更新按钮状态时解析JSON失败', e);
            }

            // 更新按钮状态和样式
            [jsonButton, mdButton, wordButton].forEach(button => {
                button.style.backgroundColor = hasResponse ? '#28a745' : '#007bff';
                button.dataset.tooltip = `${platformName}数据已就绪\n${statsText}\n最后更新: ${state.lastUpdateTime}`;

                // 悬停效果
                button.onmouseenter = () => {
                    button.style.transform = 'translateY(-2px)';
                    button.style.boxShadow = '0 6px 8px rgba(0,0,0,0.15)';
                };
                button.onmouseleave = () => {
                    button.style.transform = '';
                    button.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
                };
            });

            // 特殊处理MD和Word按钮
            mdButton.style.backgroundColor = state.convertedMd ? '#28a745' : '#007bff';
            wordButton.style.backgroundColor = state.convertedMd ? '#28a745' : '#007bff';

            // 禁用状态
            jsonButton.disabled = !hasResponse;
            mdButton.disabled = !state.convertedMd;
            wordButton.disabled = !state.convertedMd;
        },

        createDownloadButtons: () => {
            // 如果按钮已存在,则不再创建
            if (document.getElementById('downloadJsonButton')) return;

            const buttonContainer = document.createElement('div');
            buttonContainer.id = 'exportButtonsContainer';
            const jsonButton = document.createElement('button');
            const mdButton = document.createElement('button');
            const wordButton = document.createElement('button');

            // 容器样式
            Object.assign(buttonContainer.style, {
                position: 'fixed',
                top: '45%',
                right: '10px',
                zIndex: '9999',
                display: 'flex',
                flexDirection: 'column',
                gap: '10px',
                opacity: CONFIG.BUTTON_OPACITY,
                transition: 'opacity 0.3s ease',
                cursor: 'move'
            });

            // 按钮通用样式
            const buttonStyles = {
                padding: '8px 12px',
                backgroundColor: '#007bff',
                color: '#ffffff',
                border: 'none',
                borderRadius: '5px',
                cursor: 'pointer',
                transition: 'all 0.3s ease',
                fontFamily: 'Arial, sans-serif',
                boxShadow: '0 2px 5px rgba(0,0,0,0.2)',
                whiteSpace: 'nowrap',
                fontSize: '14px'
            };

            // 设置按钮属性和样式
            jsonButton.id = 'downloadJsonButton';
            jsonButton.innerText = 'JSON';
            mdButton.id = 'downloadMdButton';
            mdButton.innerText = 'MD';
            wordButton.id = 'downloadWordButton';
            wordButton.innerHTML = `
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle; margin-right: 5px;">
                    <path d="M4 4.5V19C4 20.1046 4.89543 21 6 21H18C19.1046 21 20 20.1046 20 19V8.2468C20 7.61538 19.7893 7.00372 19.4029 6.5L16.5 3H6C4.89543 3 4 3.89543 4 5V5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
                    <path d="M16 3V7C16 7.55228 16.4477 8 17 8H20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
                    <path d="M8 13L10 17L12 13L14 17L16 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
                </svg>Word`;
            // 添加按钮的 classname
            [jsonButton, mdButton, wordButton].forEach(btn => {
                btn.className = 'export-button';
            });

            Object.assign(jsonButton.style, buttonStyles);
            Object.assign(mdButton.style, buttonStyles);
            Object.assign(wordButton.style, buttonStyles);

            // 鼠标悬停效果
            buttonContainer.onmouseenter = () =>{
                buttonContainer.style.opacity = CONFIG.BUTTON_HOVER_OPACITY;
                buttonContainer.style.boxShadow = '0 8px 16px rgba(0,0,0,0.15)';
            };
            buttonContainer.onmouseleave = () =>{
                buttonContainer.style.opacity = CONFIG.BUTTON_OPACITY;
                buttonContainer.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
            }


            // 拖动功能
            let isDragging = false;
            let initialX, initialY, xOffset = 0, yOffset = 0;

            buttonContainer.addEventListener('mousedown', (e) => {
                initialX = e.clientX - xOffset;
                initialY = e.clientY - yOffset;
                if (e.target === buttonContainer) {
                    isDragging = true;
                }
            });

            document.addEventListener('mousemove', (e) => {
                if (isDragging) {
                    e.preventDefault();
                    xOffset = e.clientX - initialX;
                    yOffset = e.clientY - initialY;
                    buttonContainer.style.transform = `translate(${xOffset}px, ${yOffset}px)`;
                }
            });

            document.addEventListener('mouseup', () => {
                isDragging = false;
            });

            // 阻止按钮的 mousedown 冒泡影响拖动
            [jsonButton, mdButton, wordButton].forEach(btn => {
                btn.addEventListener('mousedown', (e) => e.stopPropagation());
            });

            // 按钮点击事件
            jsonButton.onclick = exportHandlers.json;
            mdButton.onclick = exportHandlers.markdown;
            wordButton.onclick = exportHandlers.word;

            // 组装按钮
            buttonContainer.appendChild(jsonButton);
            buttonContainer.appendChild(mdButton);
            buttonContainer.appendChild(wordButton);
            document.body.appendChild(buttonContainer);

            // 自动折叠/展开功能
            let isCollapsed = false;
            let collapseTimeout;
            const collapseDelay = 2000; // 2秒后自动折叠

            // 自动折叠函数
            const autoCollapse = () => {
                collapseTimeout = setTimeout(() => {
                    buttonContainer.classList.add('collapsed');
                    isCollapsed = true;
                }, collapseDelay);
            };

            // 初始设置自动折叠
            autoCollapse();

            // 鼠标进入展开
            buttonContainer.addEventListener('mouseenter', () => {
                clearTimeout(collapseTimeout);
                if (isCollapsed) {
                    buttonContainer.classList.remove('collapsed');
                    isCollapsed = false;
                }
            });

            // 鼠标离开后延迟折叠
            buttonContainer.addEventListener('mouseleave', () => {
                if (!isCollapsed) {
                    autoCollapse();
                }
            });

            // 点击折叠按钮也可展开
            buttonContainer.addEventListener('click', (e) => {
                if (isCollapsed && e.target === buttonContainer) {
                    buttonContainer.classList.remove('collapsed');
                    isCollapsed = false;
                    clearTimeout(collapseTimeout);
                }
            });

            // 确保拖动时不折叠
            buttonContainer.addEventListener('mousedown', () => {
                clearTimeout(collapseTimeout);
            });

            // 添加加载动画样式
            GM_addStyle(`
               .export-button {
                transition: all 0.2s ease;
                }
                .export-button:hover {
                background-color: #0056b3 !important;
               }
                .export-button:disabled {
                    opacity: 0.6;
                    cursor: not-allowed;
                    background-color: #6c757d !important;
                }
                .gm-spinner {
                    border: 2px solid rgba(255,255,255,0.3);
                    border-radius: 50%;
                    border-top-color: #fff;
                    width: 12px;
                    height: 12px;
                    animation: spin ${CONFIG.SPINNER_ANIMATION_DURATION} linear infinite;
                    display: inline-block;
                    margin-right: 8px;
                    vertical-align: middle;
                }
                @keyframes spin {
                    0% { transform: rotate(0deg); }
                    100% { transform: rotate(360deg); }
                }
                    /* 添加折叠/展开相关样式 */
                #exportButtonsContainer.collapsed {
                    width: 40px;
                    height: 40px;
                    overflow: hidden;
                    padding: 5px;
                }

                #exportButtonsContainer.collapsed .export-button {
                    display: none;
                }

                #exportButtonsContainer.collapsed::before {
                    content: "↓↓↓";
                    position: absolute;
                    width: 30px;
                    height: 30px;
                    background: #007bff;
                    color: white;
                    border-radius: 50%;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    font-size: 16px;

                    cursor: pointer;
                    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
                }

                #exportButtonsContainer:not(.collapsed)::before {
                    display: none;
                }

                #exportButtonsContainer {
                    transition: all 0.3s ease;
                }
                /* 标题相关样式 */
                [data-tooltip] {
                    position: relative;
                }

                [data-tooltip]:hover::after {
                    content: attr(data-tooltip);
                    position: absolute;
                    right: 100%;
                    top: 50%;
                    transform: translateY(-50%);
                    background:rgba(228,17,136,0.7);
                    color: #fff;
                    padding: 8px 12px;
                    border-radius: 4px;
                    font-size: 16px;
                    font-family: "方正小标宋简体","黑体", Arial, sans-serif;
                    white-space: pre;
                    margin-right: 10px;
                    pointer-events: none;
                    opacity: 0.1;
                    transition: opacity 0.3s;
                }

                [data-tooltip]:hover::after {
                    opacity: 1;
                }
            `);

            ui.updateButtonStatus();
        }
    };

    // 网络拦截
    function setupNetworkInterception() {
        // 拦截XHR请求
        const originalOpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(...args) {
            // 处理缓存参数
            if (args[1]?.includes('history_messages?chat_session_id')) {
                args[1] = args[1].split('&cache_version=')[0];
            } else if (args[1]?.includes('conversation/info?conversation_id')) {
                args[1] = args[1].split('&cache_version=')[0];
            } else if (args[1]?.includes('/api/user/agent/conversation/v1/detail')) {
                args[1] = args[1].split('&cacheBust=')[0];
            }

            this._requestURL = args[1];

            this.addEventListener('load', () => {
                if (this.responseURL) {
                    for (const [platform, pattern] of Object.entries(PLATFORM_PATTERNS)) {
                        if (pattern.test(this.responseURL)) {
                            processTargetResponse(this.responseText, this.responseURL);
                            break;
                        }
                    }
                }
            });

            originalOpen.apply(this, args);
        };

        // 拦截Fetch请求
        const originalFetch = window.fetch;
        window.fetch = async function(...args) {
            const url = args[0] instanceof Request ? args[0].url : args[0];
            let response;

            try {
                response = await originalFetch.apply(this, args);

                if (typeof url === 'string') {
                    for (const [platform, pattern] of Object.entries(PLATFORM_PATTERNS)) {
                        if (pattern.test(url)) {
                            const contentType = response.headers.get('content-type');
                            if (contentType?.includes('application/json')) {
                                const clonedResponse = response.clone();
                                clonedResponse.text().then(text => {
                                    processTargetResponse(text, url);
                                }).catch(e => {
                                    logger.error(`解析fetch响应文本时出错`, { url, error: e });
                                });
                            }
                            break;
                        }
                    }
                }
            } catch (error) {
                logger.error('Fetch请求失败', error);
                throw error;
            }

            return response;
        };
    }

    setupNetworkInterception();
    setupAuthInterceptor(); // Kimi标题授权拦截器

    // 初始化
    function initialize() {
       // setupNetworkInterception();
        ui.createDownloadButtons();
        logger.info('增强版导出脚本已启动');

        // 使用MutationObserver确保按钮存在
        const observer = new MutationObserver((mutations) => {
            if (!document.getElementById('downloadJsonButton') ||
                !document.getElementById('downloadMdButton')) {
                logger.info('检测到按钮丢失,正在重新创建...');
                ui.createDownloadButtons();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // 页面加载完成后初始化
    if (document.readyState === 'complete') {
        initialize();
    } else {
        window.addEventListener('load', initialize);
    }
})();