(() => { 'use strict'; const landing = document.getElementById('landing'); const 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('.stepper__dot')); let currentStep = 0; let generatedSoul = ''; let generatedConfig = ''; let activePreview = 'soul'; function showView(name) { if (name === 'landing') { landing.classList.add('active'); creator.classList.remove('active'); window.scrollTo({ top: 0, behavior: 'smooth' }); } else { landing.classList.remove('active'); creator.classList.add('active'); 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); }); } // 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 === '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(); 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; } }); // Update auto-generated system prompt as user types form.addEventListener('input', () => { updateSystemPromptPreview(); }); // Initial state updateStep(0); updateSystemPromptPreview(); })();