class StoryRenderer { constructor(dataSourceId) { this.dataSourceId = dataSourceId; this.yamlData = null; this.rootNode = null; // 根节点名称 this.initElements(); } // 初始化DOM元素引用 initElements() { this.elements = { timeDisplay: document.getElementById('time-display'), locationDisplay: document.getElementById('location-display'), charactersContainer: document.getElementById('characters-container'), actionOwner: document.getElementById('action-owner'), optionsList: document.getElementById('options-list'), }; } // 初始化方法 init() { try { // 从script标签中加载YAML数据 this.loadYamlFromScriptTag(); // 如果没有有效数据(显示加载状态的情况),直接返回 if (!this.yamlData) { this.setupEventListeners(); return; } // 找到根节点 this.findRootNode(); this.renderAll(); this.setupEventListeners(); } catch (error) { this.handleError(error); } } // 从script标签加载并解析YAML数据 loadYamlFromScriptTag() { const scriptElement = document.getElementById(this.dataSourceId); if (!scriptElement) { throw new Error('未找到id为"yaml-data-source"的script标签'); } let yamlContent = scriptElement.textContent.trim(); // 检查是否为真正的空内容 if (!yamlContent) { // 当YAML内容为空时,设置默认的加载状态但不抛出错误 this.showLoadingState(); return; // 直接返回,不抛出错误 } // 如果是"加载中..."文本,也显示加载状态 if (yamlContent === '加载中...') { this.showLoadingState(); return; } // 有内容,尝试解析YAML try { this.yamlData = jsyaml.load(yamlContent); } catch (e) { // YAML格式错误,应该弹出错误对话框 throw new Error(`YAML格式错误: ${e.message}`); } if (!this.yamlData || Object.keys(this.yamlData).length === 0) { // 解析成功但数据为空,这是格式问题 throw new Error('YAML解析成功但数据为空,请检查YAML格式是否正确'); } } // 显示加载状态的独立方法 showLoadingState() { this.elements.timeDisplay.textContent = '⏰ 加载中...'; this.elements.locationDisplay.textContent = '📍 加载中...'; this.elements.actionOwner.textContent = '加载中...'; this.elements.charactersContainer.innerHTML = this.createEmptyState('数据加载中...'); this.elements.optionsList.innerHTML = '
  • 加载选项中...
  • '; } // 查找根节点 findRootNode() { const rootNodeNames = Object.keys(this.yamlData); if (rootNodeNames.length === 0) { throw new Error('YAML数据中未找到任何根节点'); } this.rootNode = rootNodeNames[0]; } // 格式化节点名称,使其更易读 formatNodeName(name) { // 提取emoji后面的文本(如果有emoji) const emojiMatch = name.match(/^(\p{Emoji}\s*)(.*)$/u); if (emojiMatch && emojiMatch[2]) { return emojiMatch[2]; } return name; } // 渲染所有内容 renderAll() { if (!this.rootNode || !this.yamlData[this.rootNode]) { throw new Error('未找到有效的根节点数据'); } const rootData = this.yamlData[this.rootNode]; this.renderHeaderInfo(rootData); this.renderCharacters(rootData); this.renderActionOptions(rootData); } // 渲染头部信息(日期和时间和地点) renderHeaderInfo(rootData) { // 查找日期时间字段 const dateTimeField = this.findFieldByKeywords(rootData, ['日期', '时间', 'datetime', 'time']); // 查找地点字段 const locationField = this.findFieldByKeywords(rootData, ['地点', '位置', 'location', 'place']); // 直接使用包含emoji的值 this.elements.timeDisplay.textContent = dateTimeField ? rootData[dateTimeField] : '⏰ 时间未知'; this.elements.locationDisplay.textContent = locationField ? rootData[locationField] : '📍 地点未知'; } // 根据关键词查找字段名 findFieldByKeywords(data, keywords) { if (!data || typeof data !== 'object') return null; const fields = Object.keys(data); for (const field of fields) { for (const keyword of keywords) { if (field.toLowerCase().includes(keyword.toLowerCase())) { return field; } } } return null; } // 渲染角色列表 renderCharacters(rootData) { // 查找用户列表字段 const userListField = this.findFieldByKeywords(rootData, ['用户', '角色', '列表', 'user', 'role', 'list']); const userList = userListField && Array.isArray(rootData[userListField]) ? rootData[userListField] : []; this.elements.charactersContainer.innerHTML = ''; if (userList.length === 0) { this.elements.charactersContainer.innerHTML = this.createEmptyState('没有角色数据'); return; } // 处理每个用户项 userList.forEach(userItem => { // 检查是否有外层包装 let userData = userItem; if (typeof userItem === 'object' && userItem !== null) { const userField = this.findFieldByKeywords(userItem, ['用户', 'user', '角色', 'role']); if (userField) { userData = userItem[userField]; } } const characterCard = this.createCharacterCard(userData); if (characterCard) { this.elements.charactersContainer.appendChild(characterCard); } }); } // 创建单个角色卡片 createCharacterCard(userData) { if (!userData || typeof userData !== 'object') return null; const card = document.createElement('div'); card.className = 'bg-dark rounded-xl border border-gray-700/30 p-3.5 shadow-sm card-hover character-card theme-transition'; // 查找名字字段 const nameField = this.findFieldByKeywords(userData, ['名字', '姓名', '名称', 'name']); const userName = nameField ? userData[nameField] : '👤 未知角色'; // 创建标题 const title = document.createElement('h3'); title.className = 'font-bold text-lg mb-2 pb-1 border-b border-gray-700/30 theme-transition'; title.textContent = `${this.formatNodeName(userName)}的状态`; card.appendChild(title); // 创建属性列表 const attributesList = document.createElement('ul'); attributesList.className = 'space-y-2 text-sm'; card.appendChild(attributesList); // 处理所有属性 Object.keys(userData).forEach(key => { // 跳过已经作为标题使用的名字节点 if (key === nameField) return; // 创建属性项,直接使用包含emoji的值 const attributeItem = this.createAttributeItem(key, userData[key]); if (attributeItem) { attributesList.appendChild(attributeItem); } }); return card; } // 创建属性项 createAttributeItem(key, value) { const item = document.createElement('li'); // 处理数组类型 if (Array.isArray(value)) { item.innerHTML = `${this.formatNodeName(key)}:`; const subList = document.createElement('ul'); subList.className = 'list-disc list-inside ml-4 mt-1 space-y-1 text-gray-400 theme-transition'; value.forEach(itemData => { if (typeof itemData === 'object' && itemData !== null) { const subKey = Object.keys(itemData)[0]; const subValue = itemData[subKey]; const subItem = document.createElement('li'); subItem.textContent = subValue; subList.appendChild(subItem); } else { const subItem = document.createElement('li'); subItem.textContent = itemData; subList.appendChild(subItem); } }); item.appendChild(subList); } // 处理对象类型 else if (typeof value === 'object' && value !== null) { item.innerHTML = `${this.formatNodeName(key)}:`; const subList = document.createElement('ul'); subList.className = 'list-disc list-inside ml-4 mt-1 space-y-1 text-gray-400 theme-transition'; Object.keys(value).forEach(subKey => { const subItem = document.createElement('li'); subItem.textContent = value[subKey]; subList.appendChild(subItem); }); item.appendChild(subList); } // 处理普通文本值 else if (value !== null && value !== undefined && value.toString().trim() !== '') { item.innerHTML = `${this.formatNodeName(key)}: ${value}`; } return item; } // 渲染行动选项 renderActionOptions(rootData) { // 查找行动选项字段 const actionOptionsField = this.findFieldByKeywords(rootData, ['行动', '选项', 'action', 'option']); const actionOptions = actionOptionsField && typeof rootData[actionOptionsField] === 'object' ? rootData[actionOptionsField] : {}; // 设置行动所有者 const ownerField = this.findFieldByKeywords(actionOptions, ['名字', '姓名', '所有者', 'owner', 'name']); this.elements.actionOwner.textContent = ownerField ? this.formatNodeName(actionOptions[ownerField]) : '未知角色'; // 渲染选项列表 const optionsField = this.findFieldByKeywords(actionOptions, ['选项', '选择', 'option', 'choice']); const options = optionsField && Array.isArray(actionOptions[optionsField]) ? actionOptions[optionsField] : []; this.elements.optionsList.innerHTML = ''; if (options.length === 0) { this.elements.optionsList.innerHTML = this.createEmptyState('没有可用选项'); return; } options.forEach(optionText => { const optionItem = document.createElement('li'); optionItem.className = 'pl-2 py-1 border-l-2 border-primary/30 ml-1 hover:border-primary/70 transition-colors text-gray-300 theme-transition'; optionItem.textContent = optionText; this.elements.optionsList.appendChild(optionItem); }); } // 创建空状态提示 createEmptyState(message) { return `
    ${message}
    `; } // 设置事件监听器 setupEventListeners() { const detailsElement = document.querySelector('details'); const contentElement = this.elements.charactersContainer; // 初始化高度为0以实现动画效果 contentElement.style.maxHeight = '0'; // 监听详情展开/折叠事件 detailsElement.addEventListener('toggle', () => { if (detailsElement.open) { // 展开时设置实际高度 setTimeout(() => { contentElement.style.maxHeight = contentElement.scrollHeight + 'px'; }, 10); } else { // 折叠时设置高度为0 contentElement.style.maxHeight = '0'; } }); // 根据自动折叠设置决定默认状态 const autoCollapseToggle = document.getElementById('auto-collapse-toggle'); if (autoCollapseToggle) { // 从本地存储读取设置,默认为true(折叠) const savedAutoCollapse = localStorage.getItem('autoCollapse'); const shouldCollapse = savedAutoCollapse === null ? true : savedAutoCollapse === 'true'; detailsElement.open = !shouldCollapse; // 如果默认展开,需要设置正确的高度 if (!shouldCollapse) { setTimeout(() => { contentElement.style.maxHeight = contentElement.scrollHeight + 'px'; }, 100); } } else { // 如果没有设置切换开关,默认折叠 detailsElement.open = false; } } // 错误处理 handleError(error) { console.error('渲染错误:', error); // 使用美化的错误弹窗 showErrorModal(error.message); // 在角色状态区域显示错误信息 this.elements.charactersContainer.innerHTML = ` `; // 在行动选项区域也显示错误信息 this.elements.optionsList.innerHTML = `
  • 行动选项加载失败
    请检查YAML格式是否正确
  • `; } } $(document).ready(function () { /** * 获取本楼消息 * @returns {Object|null} 包含本楼消息信息的对象,失败时返回null */ function getCurrentMessage() { try { if (typeof getCurrentMessageId !== 'function' || typeof getChatMessages !== 'function') { return null; } const currentMessageId = getCurrentMessageId(); if (!currentMessageId && currentMessageId !== 0) return null; const messageData = getChatMessages(currentMessageId); if (!messageData) return null; return Array.isArray(messageData) && messageData.length > 0 ? messageData[0] : messageData; } catch (error) { console.error('获取消息失败:', error); return null; } } function extractMaintext(message) { if (!message || typeof message !== 'string') return ''; const match = message.match(/([\s\S]*?)<\/maintext>/i); return match ? match[1].trim() : ''; } /** * 从消息中提取Status_block内容 * @param {string} message 消息文本 * @returns {string} 提取的YAML状态内容 */ function extractStatusBlock(message) { if (!message || typeof message !== 'string') return ''; const match = message.match(/\s*([\s\S]*?)\s*<\/Status_block>/i); return match ? cleanYamlContent(match[1].trim()) : ''; } /** * 清理YAML内容,修复常见的格式问题 * @param {string} yamlContent 原始YAML内容 * @returns {string} 清理后的YAML内容 */ function cleanYamlContent(yamlContent) { if (!yamlContent) return ''; return yamlContent .split('\n') .map(line => { if (line.trim() === '' || !line.trim().match(/^-\s*"/)) return line; const match = line.match(/^(\s*-\s*)"(.*)"\s*$/); if (!match) return line; const [, indent, content] = match; return content.includes('"') || content.includes("'") ? indent + "'" + content.replace(/'/g, "''") + "'" : indent + '"' + content + '"'; }) .join('\n'); } /** * 更新YAML数据源 * @param {string} yamlContent YAML格式的状态内容 */ function updateYamlDataSource(yamlContent) { const yamlScript = document.getElementById('yaml-data-source'); if (!yamlScript) return; // 如果内容为空或无效,设置为加载中状态 if (!yamlContent || typeof yamlContent !== 'string' || !yamlContent.trim()) { yamlScript.textContent = ''; // 设置为空,让后续处理显示加载状态 return; } // 先设置内容,让StoryRenderer能处理格式错误 yamlScript.textContent = yamlContent; // 验证YAML格式,如果有错误会被StoryRenderer捕获并处理 try { jsyaml.load(yamlContent); } catch (error) { // 尝试修复常见的YAML错误 const fixedYaml = attemptYamlFix(yamlContent, error); if (fixedYaml) { try { jsyaml.load(fixedYaml); yamlScript.textContent = fixedYaml; } catch (e) { console.error('YAML修复失败:', e.message); // 修复失败时保留原内容,让StoryRenderer显示具体错误 } } // 如果无法修复,保留原内容,让StoryRenderer显示具体错误 } } /** * 尝试修复常见的YAML错误 * @param {string} yamlContent 有问题的YAML内容 * @param {Error} error YAML解析错误 * @returns {string|null} 修复后的YAML或null */ function attemptYamlFix(yamlContent, error) { if (!(error.message.includes('bad indentation') || error.message.includes('quote'))) { return null; } return yamlContent .split('\n') .map(line => { const match = line.match(/^(\s*-\s*)"(.*)"\s*$/); if (!match) return line; const [, indent, content] = match; return content.includes('"') ? indent + "'" + content.replace(/'/g, "''") + "'" : indent + '"' + content + '"'; }) .join('\n'); } /** * 更新maintext内容 * @param {string} maintextContent maintext内容 */ function updateMaintext(maintextContent) { try { const maintextElement = document.getElementById('maintext'); if (!maintextElement) return; // 如果内容为空或无效,设置为加载中状态 if (!maintextContent || typeof maintextContent !== 'string' || !maintextContent.trim()) { maintextElement.textContent = ''; } else { maintextElement.textContent = maintextContent; } formatMainText(); } catch (error) { console.error('更新maintext失败:', error); // 如果更新失败,直接调用formatMainText,它会处理错误 formatMainText(); } } /** * 重新渲染状态栏 */ function reRenderStatusBar() { try { const yamlScript = document.getElementById('yaml-data-source'); if (!yamlScript || !yamlScript.textContent) return; const storyRenderer = new StoryRenderer('yaml-data-source'); storyRenderer.init(); } catch (error) { console.error('重新渲染状态栏失败:', error); // 状态栏渲染失败时,错误处理由StoryRenderer.handleError处理 // 这里不需要额外处理,因为StoryRenderer的init方法已经有handleError调用 } } /** * 根据消息数据渲染整个页面 * @param {Object} messageData 消息数据对象(格式参考test.json) */ function renderPageFromMessage(messageData) { let actualMessageData = Array.isArray(messageData) && messageData.length > 0 ? messageData[0] : messageData; if (!actualMessageData || !actualMessageData.message || typeof actualMessageData.message !== 'string') { return; } const messageContent = actualMessageData.message; // 提取并更新maintext内容 const maintextContent = extractMaintext(messageContent); if (maintextContent) { updateMaintext(maintextContent); } // 提取并更新Status_block内容 const statusContent = extractStatusBlock(messageContent); if (statusContent) { updateYamlDataSource(statusContent); setTimeout(() => reRenderStatusBar(), 100); } } // 执行获取操作并处理结果 try { const currentMessage = getCurrentMessage(); if (currentMessage && typeof currentMessage === 'object') { renderPageFromMessage(currentMessage); } } catch (error) { console.error('获取或渲染消息时出错:', error); } window.statusBlockInitialized = true; }); pm