(() => { 'use strict'; // --- U9: Unified state management --- const STORAGE_KEY = 'eternal_ai_state'; const defaultState = { isLoggedIn: false, isCreator: false, account: null, boundCreator: null, libraryName: '我的 [XXX]', creatorName: '', roles: [], income: { balance: 0, records: [] }, }; function loadState() { try { const saved = JSON.parse(localStorage.getItem(STORAGE_KEY)); return { ...defaultState, ...saved }; } catch { return { ...defaultState }; } } function saveState() { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } const state = loadState(); // --- Mock data for role library (U2) --- const mockRoles = [ { id: 'role_001', name: '云朵', avatar: 'https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=anime%20girl%20soft%20pastel%20portrait%20gentle%20smile&image_size=square', desc: '温柔如云的女孩,总是轻声细语地陪伴你。', price: 29.9, status: 'running', }, { id: 'role_002', name: '星河', avatar: 'https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=anime%20boy%20starry%20eyes%20cool%20portrait&image_size=square', desc: '嘴硬心软的少年,嘴上不饶人却总在关键时刻出现。', price: 39.9, status: 'running', }, { id: 'role_003', name: '月见', avatar: 'https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=anime%20girl%20moonlight%20mysterious%20elegant%20portrait&image_size=square', desc: '神秘而优雅,像月光一样忽远忽近的存在。', price: 19.9, status: 'stopped', }, ]; const mockIncome = { balance: 1280.50, records: [ { time: '2026-06-18 14:30', amount: 23.92, role: '云朵' }, { time: '2026-06-15 09:12', amount: 31.92, role: '星河' }, { time: '2026-06-10 20:05', amount: 15.92, role: '月见' }, ], }; // --- DOM references --- const views = { landing: document.getElementById('landing'), auth: document.getElementById('auth'), 'role-library': document.getElementById('role-library'), 'role-detail': document.getElementById('role-detail'), distill: document.getElementById('distill'), about: document.getElementById('about'), onboarding: document.getElementById('onboarding'), 'creator-center': document.getElementById('creator-center'), creator: document.getElementById('creator'), }; const form = document.getElementById('character-form'); const resultPanel = document.getElementById('result-panel'); const previewCode = document.querySelector('#preview-code code'); const systemPromptInput = document.getElementById('system-prompt'); const steps = Array.from(document.querySelectorAll('.form-step')); const dots = Array.from(document.querySelectorAll('#creator .stepper__dot')); let currentStep = 0; let generatedSoul = ''; let generatedConfig = ''; let activePreview = 'soul'; let activeAuthTab = 'login'; let activeCenterTab = 'roles'; let currentRole = null; let viewHistory = ['landing']; // --- U9: Unified showView with history and tab-bar sync --- function showView(name, trackHistory = true) { Object.entries(views).forEach(([key, el]) => { if (el) el.classList.toggle('active', key === name); }); if (trackHistory && viewHistory[viewHistory.length - 1] !== name) { viewHistory.push(name); } window.scrollTo({ top: 0, behavior: 'smooth' }); updateTabBar(name); } function goBack() { if (viewHistory.length > 1) { viewHistory.pop(); const prev = viewHistory[viewHistory.length - 1]; showView(prev, false); } else { showView('landing', false); } } // --- U8: Tab bar --- function updateTabBar(viewName) { const tabMap = { landing: 'tab-home', distill: 'tab-distill', 'role-library': 'tab-mine', 'creator-center': 'tab-mine', }; const activeTab = tabMap[viewName] || 'tab-home'; document.querySelectorAll('.tab-bar__item').forEach((item) => { item.classList.toggle('active', item.dataset.tabAction === activeTab); }); } function handleTabAction(action) { if (action === 'tab-home') { showView('landing'); } else if (action === 'tab-distill') { showView('distill'); } else if (action === 'tab-mine') { if (!state.isLoggedIn) { switchAuthTab('login'); showView('auth'); } else if (state.isCreator) { showView('creator-center'); renderCreatorCenter(); } else { renderRoleLibrary(); showView('role-library'); } } } // --- U1: Landing card state --- function updateLandingCard() { const nameEl = document.getElementById('library-name'); const descEl = document.getElementById('characters-desc'); const btnEl = document.getElementById('characters-btn'); const tabMineLabel = document.getElementById('tab-mine-label'); if (state.isLoggedIn) { nameEl.textContent = state.libraryName || '我的角色库'; if (state.isCreator) { descEl.textContent = '管理你的角色和收入'; btnEl.textContent = '进入管理中心'; tabMineLabel.textContent = '管理'; } else { descEl.textContent = state.boundCreator ? '查看你的专属角色' : '寻找你的专属创作者'; btnEl.textContent = '进入角色库'; tabMineLabel.textContent = '我的'; } } else { nameEl.textContent = '我的 [XXX]'; descEl.textContent = '登录后管理你的角色'; btnEl.textContent = '登录 / 注册'; tabMineLabel.textContent = '我的'; } } // --- Auth --- function switchAuthTab(tab) { activeAuthTab = tab; document.querySelectorAll('.auth-tab').forEach((t) => { t.classList.toggle('active', t.dataset.tab === tab); }); document.querySelectorAll('.auth-form').forEach((f) => { f.classList.toggle('active', f.dataset.form === tab); }); } function validatePasswordMatch(formEl) { const pwd = formEl.querySelector('[name="password"]'); const confirm = formEl.querySelector('[name="confirmPassword"]'); if (!pwd || !confirm) return true; if (pwd.value !== confirm.value) { confirm.setCustomValidity('两次输入的密码不一致'); confirm.reportValidity(); return false; } confirm.setCustomValidity(''); return true; } function login(account) { state.isLoggedIn = true; state.account = account; state.boundCreator = state.boundCreator || { name: '云朵', roles: mockRoles }; saveState(); updateLandingCard(); } function logout() { state.isLoggedIn = false; state.isCreator = false; state.account = null; saveState(); updateLandingCard(); showView('landing'); } // --- U2: Role Library --- function renderRoleLibrary() { const listEl = document.getElementById('role-list'); const emptyEl = document.getElementById('library-empty'); const titleEl = document.getElementById('library-title'); titleEl.textContent = state.libraryName || '我的角色库'; if (!state.boundCreator) { listEl.innerHTML = ''; emptyEl.hidden = false; return; } emptyEl.hidden = true; listEl.innerHTML = mockRoles .map( (role) => `
${role.name}

${role.name}

${role.desc}

¥${role.price}
` ) .join(''); } // --- U3: Role Detail --- function renderRoleDetail(roleId) { const role = mockRoles.find((r) => r.id === roleId); if (!role) return; currentRole = role; document.getElementById('detail-name').textContent = role.name; document.getElementById('detail-hero').style.backgroundImage = `url(${role.avatar})`; document.getElementById('detail-role-name').textContent = role.name; document.getElementById('detail-role-desc').textContent = role.desc; document.getElementById('detail-price').innerHTML = `¥${role.price}/ 月`; document.getElementById('detail-actions-pre').hidden = false; document.getElementById('detail-paid').hidden = true; } function payRole() { document.getElementById('detail-actions-pre').hidden = true; const paidEl = document.getElementById('detail-paid'); paidEl.hidden = false; document.getElementById('detail-qr').innerHTML = '
扫码连接
AI 角色
'; document.getElementById('detail-avatar').style.backgroundImage = `url(${currentRole.avatar})`; } // --- U4: About FAQ --- function toggleFaq(button) { const item = button.closest('.faq-item'); const answer = item.querySelector('.faq-a'); const icon = button.querySelector('.faq-icon'); const isOpen = answer.style.display === 'block'; answer.style.display = isOpen ? 'none' : 'block'; icon.textContent = isOpen ? '+' : '−'; } // --- U7: Creator Center --- function renderCreatorCenter() { renderCreatorRoles(); renderIncome(); renderSettings(); } function renderCreatorRoles() { const listEl = document.getElementById('creator-role-list'); const roles = state.roles.length > 0 ? state.roles : mockRoles; listEl.innerHTML = roles .map( (role) => `
${role.name}

${role.name}

${role.status === 'running' ? '运行中' : '已停止'}
` ) .join(''); } function renderIncome() { const income = state.income.balance > 0 ? state.income : mockIncome; document.getElementById('income-balance').textContent = `¥ ${income.balance.toFixed(2)}`; const listEl = document.getElementById('income-list'); if (income.records.length === 0) { listEl.innerHTML = '

暂无流水记录

'; return; } listEl.innerHTML = income.records .map( (r) => `
${r.role} ${r.time}
+¥${r.amount.toFixed(2)}
` ) .join(''); } function renderSettings() { document.getElementById('settings-name').value = state.creatorName || ''; document.getElementById('settings-library').value = state.libraryName === '我的 [XXX]' ? '' : state.libraryName; } function switchCenterTab(tab) { activeCenterTab = tab; document.querySelectorAll('.center-tab').forEach((t) => { t.classList.toggle('active', t.dataset.centerTab === tab); }); document.querySelectorAll('.center-panel').forEach((p) => { p.classList.toggle('active', p.id === `center-${tab}`); }); const labels = { roles: '我的角色', income: '收入', settings: '我的' }; document.getElementById('center-tab-label').textContent = labels[tab] || '我的角色'; } // --- Creator form (U6: preserved from original) --- function updateStep(index) { steps.forEach((step, i) => { step.classList.toggle('active', i === index); }); dots.forEach((dot, i) => { dot.classList.toggle('active', i === index); }); currentStep = index; } function validateStep(index) { const step = steps[index]; const inputs = step.querySelectorAll('input, textarea, select'); let valid = true; inputs.forEach((input) => { if (!input.checkValidity()) { valid = false; input.reportValidity(); } }); return valid; } function getFormData() { const fd = new FormData(form); const data = Object.fromEntries(fd.entries()); data.enableMemory = form.elements.enableMemory.checked; data.enableTools = form.elements.enableTools.checked; return data; } function escapeYaml(value) { if (typeof value !== 'string') return value; if (value.includes(':') || value.includes('#') || value.includes('\n') || value.includes('"')) { const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); return `"${escaped}"`; } return value; } function generateSoulMd(data) { const personalityTags = data.personality .split(/[,,]/) .map((t) => t.trim()) .filter(Boolean) .join(' | '); return `# Soul of ${data.displayName} > Generated by Eternal AI — Hermes agent character soul. ## Identity - **Name**: ${data.displayName} - **Gender**: ${data.gender === 'unknown' ? '未指定' : data.gender} - **Age**: ${data.age || '未指定'} - **Role in your life**: ${data.relationship || '未指定'} ## Background ${data.background} ## Personality ${personalityTags} ## Speech Style ${data.speechStyle} ## Likes ${data.likes || 'None specified.'} ## Dislikes ${data.dislikes || 'None specified.'} ## Shared Memories ${data.memories || 'None specified.'} ## Secrets & Inner Voice ${data.secrets || 'None specified.'} ## First Greeting ${data.greeting} --- *Keep the memory alive.* `; } function generateConfigYaml(data) { const system = (data.systemPrompt || buildSystemPrompt(data)).trim(); const toolsBlock = data.enableTools ? ` tools: - name: search_memory description: Search long-term memory for context about the user. - name: save_memory description: Persist a new memory about the user or conversation.` : ''; return `# Hermes Agent Config # Generated by Eternal AI for character: ${data.displayName} agent: id: ${data.agentId} name: ${escapeYaml(data.displayName)} version: "1.0.0" model: name: ${escapeYaml(data.model)} temperature: ${data.temperature} max_tokens: ${data.maxTokens} system_prompt: | ${system.split('\n').map((line) => ` ${line}`).join('\n')} memory: enabled: ${data.enableMemory} storage: local recall_depth: 10${toolsBlock} character: soul_file: Soul.md greeting: ${escapeYaml(data.greeting)} `; } function buildSystemPrompt(data) { const tags = data.personality .split(/[,,]/) .map((t) => t.trim()) .filter(Boolean) .join('、'); return `你是「${data.displayName}」,一个鲜活的角色。 基本设定: - 性别:${data.gender === 'unknown' ? '未指定' : data.gender} - 年龄:${data.age || '未指定'} - 你与用户的关系:${data.relationship || '亲密关系'} 性格:${tags} 背景: ${data.background} 说话风格: ${data.speechStyle} 喜好: ${data.likes || '无特别说明'} 厌恶/底线: ${data.dislikes || '无特别说明'} 共同记忆: ${data.memories || '无特别说明'} 内心秘密(只在语气与选择中流露,不直接说出): ${data.secrets || '无'} 请始终保持角色一致性,用符合设定的语气与用户交流。你的目标不是完美回答问题,而是像一个真实、有记忆、有情绪的人一样陪伴对方。`; } function updateSystemPromptPreview() { const data = getFormData(); if (!systemPromptInput.value.trim()) { systemPromptInput.dataset.auto = buildSystemPrompt(data); } else { delete systemPromptInput.dataset.auto; } } function publish() { if (!validateStep(currentStep)) return; const data = getFormData(); if (!data.systemPrompt.trim()) { data.systemPrompt = buildSystemPrompt(data); } generatedSoul = generateSoulMd(data); generatedConfig = generateConfigYaml(data); form.hidden = true; resultPanel.hidden = false; renderPreview(); } function renderPreview() { previewCode.textContent = activePreview === 'soul' ? generatedSoul : generatedConfig; } function download(filename, content) { const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function resetCreator() { form.reset(); form.hidden = false; resultPanel.hidden = true; generatedSoul = ''; generatedConfig = ''; activePreview = 'soul'; updateStep(0); updateTabs(); } function updateTabs() { document.querySelectorAll('.preview-tab').forEach((tab) => { tab.classList.toggle('active', tab.dataset.tab === activePreview); }); } // --- Event delegation --- document.addEventListener('click', (e) => { // FAQ toggle (U4) const faqBtn = e.target.closest('.faq-q'); if (faqBtn) { e.preventDefault(); toggleFaq(faqBtn); return; } // Role card click (U2/U3) const roleCard = e.target.closest('.role-card'); if (roleCard && !e.target.closest('[data-action]')) { e.preventDefault(); const roleId = roleCard.dataset.roleId; renderRoleDetail(roleId); showView('role-detail'); return; } // Tab bar (U8) const tabItem = e.target.closest('[data-tab-action]'); if (tabItem) { e.preventDefault(); handleTabAction(tabItem.dataset.tabAction); return; } const target = e.target.closest('[data-action], [data-tab], [data-download], [data-center-tab]'); if (!target) return; const action = target.dataset.action; // Center tab switching (U7) if (target.dataset.centerTab) { e.preventDefault(); switchCenterTab(target.dataset.centerTab); return; } // Landing card: open characters (U1) if (action === 'open-characters') { e.preventDefault(); if (!state.isLoggedIn) { switchAuthTab('login'); showView('auth'); } else if (state.isCreator) { showView('creator-center'); renderCreatorCenter(); } else { renderRoleLibrary(); showView('role-library'); } return; } if (action === 'open-distill') { e.preventDefault(); showView('distill'); return; } if (action === 'open-about') { e.preventDefault(); showView('about'); return; } if (action === 'open-onboarding') { e.preventDefault(); showView('onboarding'); return; } if (action === 'back') { e.preventDefault(); goBack(); return; } if (action === 'back-to-library') { e.preventDefault(); renderRoleLibrary(); showView('role-library'); return; } if (action === 'back-to-center') { e.preventDefault(); showView('creator-center'); renderCreatorCenter(); return; } if (action === 'pay') { e.preventDefault(); payRole(); return; } if (action === 'pay-distill') { e.preventDefault(); alert('下单成功!请添加客服微信完成后续流程。'); return; } if (action === 'contact-wechat') { e.preventDefault(); alert('客服微信号:EternalAI_Service'); return; } if (action === 'new-role') { e.preventDefault(); resetCreator(); showView('creator'); return; } if (action === 'edit-role') { e.preventDefault(); resetCreator(); showView('creator'); return; } if (action === 'logout') { e.preventDefault(); logout(); return; } if (action === 'download-avatar') { e.preventDefault(); if (currentRole) { download(currentRole.name + '_avatar.png', ''); window.open(currentRole.avatar, '_blank'); } return; } // Creator form navigation if (action === 'next') { e.preventDefault(); if (validateStep(currentStep) && currentStep < steps.length - 1) { updateStep(currentStep + 1); } return; } if (action === 'prev') { e.preventDefault(); if (currentStep > 0) { updateStep(currentStep - 1); } return; } if (action === 'publish') { e.preventDefault(); publish(); return; } if (action === 'reset') { e.preventDefault(); resetCreator(); return; } // Tab switching (auth tabs and preview tabs) if (target.dataset.tab) { e.preventDefault(); if (target.closest('.auth-tabs')) { switchAuthTab(target.dataset.tab); } else { activePreview = target.dataset.tab; updateTabs(); renderPreview(); } return; } // Download buttons if (target.dataset.download) { e.preventDefault(); if (target.dataset.download === 'soul') { download('Soul.md', generatedSoul); } else { download('config.yaml', generatedConfig); } return; } }); // --- Auth form submissions --- document.querySelectorAll('.auth-form').forEach((authForm) => { authForm.addEventListener('submit', (e) => { e.preventDefault(); if (!validatePasswordMatch(authForm)) return; const formData = new FormData(authForm); const data = Object.fromEntries(formData.entries()); login(data.account); // After login, go to role library or creator center if (state.isCreator) { showView('creator-center'); renderCreatorCenter(); } else { renderRoleLibrary(); showView('role-library'); } }); }); // --- Settings form (U7) --- document.getElementById('settings-form').addEventListener('submit', (e) => { e.preventDefault(); const formData = new FormData(e.target); const data = Object.fromEntries(formData.entries()); state.creatorName = data.creatorName || ''; state.libraryName = data.libraryName || '我的 [XXX]'; saveState(); updateLandingCard(); alert('设置已保存'); }); // --- Withdraw form (U7) --- document.getElementById('withdraw-form').addEventListener('submit', (e) => { e.preventDefault(); const formData = new FormData(e.target); const data = Object.fromEntries(formData.entries()); const amount = parseFloat(data.amount); if (amount > state.income.balance && amount > mockIncome.balance) { alert('提现金额超过可提现余额'); return; } alert(`提现申请已提交:${data.method === 'wechat' ? '微信' : '支付宝'} ¥${amount.toFixed(2)}\n平台负责人将手动审核转账。`); e.target.reset(); }); // --- Creator form input --- form.addEventListener('input', () => { updateSystemPromptPreview(); }); // --- Initialize --- updateStep(0); updateSystemPromptPreview(); updateLandingCard(); updateTabBar('landing'); })();