(() => { 'use strict'; const landing = document.getElementById('landing'); const creator = document.getElementById('creator'); const auth = document.getElementById('auth'); const distill = document.getElementById('distill'); 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 views = { landing, creator, auth, distill }; const steps = Array.from(document.querySelectorAll('.form-step')); const dots = Array.from(document.querySelectorAll('.stepper__dot')); let currentStep = 0; let generatedSoul = ''; let generatedConfig = ''; let activePreview = 'soul'; let activeAuthTab = 'login'; function showView(name) { Object.entries(views).forEach(([key, el]) => { if (el) el.classList.toggle('active', key === name); }); window.scrollTo({ top: 0, behavior: 'smooth' }); } 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); }); } 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; } // Event delegation document.addEventListener('click', (e) => { const target = e.target.closest('[data-action], [data-tab], [data-download]'); if (!target) return; const action = target.dataset.action; if (action === 'open-creator') { e.preventDefault(); resetCreator(); showView('creator'); return; } if (action === 'open-auth') { e.preventDefault(); switchAuthTab('login'); showView('auth'); return; } if (action === 'open-distill') { e.preventDefault(); showView('distill'); return; } if (action === 'back') { e.preventDefault(); showView('landing'); return; } 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; } if (target.dataset.tab) { e.preventDefault(); if (target.closest('.auth-tabs')) { switchAuthTab(target.dataset.tab); } else { activePreview = target.dataset.tab; updateTabs(); renderPreview(); } return; } 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()); const action = authForm.dataset.form === 'login' ? '登录' : '注册'; alert(`${action}成功:${data.account}`); showView('landing'); }); }); // Update auto-generated system prompt as user types form.addEventListener('input', () => { updateSystemPromptPreview(); }); // Initial state updateStep(0); updateSystemPromptPreview(); })();