commit 7db0dab973e8bf435bb4e6597b5de2dff23b1bab Author: Eternal AI Builder Date: Sat Jun 20 16:30:12 2026 +0800 Initial commit: Eternal AI landing page and character creator diff --git a/app.js b/app.js new file mode 100644 index 0000000..e4a2ea2 --- /dev/null +++ b/app.js @@ -0,0 +1,334 @@ +(() => { + '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(); +})(); diff --git a/img/bg.png b/img/bg.png new file mode 100644 index 0000000..c870fa9 Binary files /dev/null and b/img/bg.png differ diff --git a/img/card1.png b/img/card1.png new file mode 100644 index 0000000..35cd146 Binary files /dev/null and b/img/card1.png differ diff --git a/img/card2.png b/img/card2.png new file mode 100644 index 0000000..ff96b34 Binary files /dev/null and b/img/card2.png differ diff --git a/img/ref.png b/img/ref.png new file mode 100644 index 0000000..4e1e70b Binary files /dev/null and b/img/ref.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..f5616f2 --- /dev/null +++ b/index.html @@ -0,0 +1,230 @@ + + + + + + Eternal AI - 在记忆与陪伴中,遇见更懂你的 AI + + + + + + +
+ +
+
+
+ +
+

我的 [XXX ]

+

登录后管理你的角色

+ +
+
+ +
+ +
+

蒸馏前任

+

留住你心里的 ta

+ +
+
+
+ + +
+ + +
+
+ +
+ Eternal AI + 人设蒸馏器 +
+ +
+ +
+ +
+ 基础身份 +
+ + + + +
+
+ +
+
+ + +
+ 灵魂设定(Soul.md) +
+ + + + + +
+
+ + +
+
+ + +
+ 关系与记忆 +
+ + + + +
+
+ + +
+
+ + +
+ 运行配置(config.yaml) +
+ + + + + + +
+
+ + +
+
+
+ + + +
+
+ + + + diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..cb824b3 --- /dev/null +++ b/styles.css @@ -0,0 +1,635 @@ +/* Reset & base */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + --bg: #050508; + --bg-elevated: rgba(15, 18, 32, 0.72); + --card-bg: rgba(20, 24, 45, 0.55); + --card-border: rgba(147, 155, 211, 0.22); + --card-glow: rgba(120, 140, 255, 0.18); + --text: #f1f3fb; + --text-muted: #9aa3c2; + --accent: #7c8cff; + --accent-soft: #a5b4fc; + --accent-dark: #4b55a8; + --distill-pink: #ffb6d5; + --distill-purple: #c4b5fd; + --radius: 24px; + --radius-sm: 14px; + --shadow: 0 20px 60px rgba(0, 0, 0, 0.45); + --transition: 0.35s cubic-bezier(0.22, 1, 0.36, 1); +} + +html { + scroll-behavior: smooth; +} + +body { + min-height: 100vh; + font-family: "Inter", "Noto Serif SC", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + background: + linear-gradient(90deg, #06040d 0%, transparent 18%, transparent 82%, #06040d 100%), + linear-gradient(180deg, transparent 0%, transparent 55%, #06040d 92%), + url(img/bg.png); + background-color: #06040d; + background-size: 100% 100%, 100% 100%, auto 100vh; + background-position: center, center, center top; + background-repeat: no-repeat, no-repeat, no-repeat; + background-attachment: scroll, scroll, scroll; + color: var(--text); + overflow-x: hidden; + -webkit-font-smoothing: antialiased; +} + +.universe, +.stars, +.glow-orb { + display: none; +} + +/* App shell */ +.app { + position: relative; + z-index: 1; + min-height: 100vh; + max-width: 520px; + margin: 0 auto; + padding: 48px 22px 32px; + display: flex; + flex-direction: column; +} + +.view { + display: none; + flex: 1; + flex-direction: column; + animation: fadeIn 0.5s ease forwards; +} + +.view.active { + display: flex; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(12px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Landing */ +.view--landing { + justify-content: flex-start; + padding-top: 46vh; + gap: 0; +} + +/* Cards */ +.cards { + display: flex; + flex-direction: column; + gap: 22px; + margin: 0; +} + +.card { + position: relative; + border-radius: 30px; + overflow: hidden; + transition: transform var(--transition), box-shadow var(--transition); + cursor: pointer; + isolation: isolate; +} + +.card__frame { + position: absolute; + inset: 0; + border-radius: inherit; + pointer-events: none; + z-index: 3; +} + +/* Characters card: deep space crystal */ +.card--characters { + min-height: 188px; + display: flex; + align-items: center; + padding: 26px 24px; + background: + linear-gradient(100deg, rgba(8, 10, 26, 0.82) 0%, rgba(15, 20, 55, 0.45) 45%, transparent 75%), + url(img/card1.png); + background-size: cover, cover; + background-position: center, center; + box-shadow: + inset 0 0 0 1px rgba(255, 255, 255, 0.18), + 0 0 0 1px rgba(140, 160, 255, 0.28), + 0 12px 40px rgba(0, 0, 0, 0.55), + 0 0 48px rgba(100, 120, 255, 0.2); +} + +.card--characters .card__frame { + box-shadow: + inset 0 0 0 1px rgba(255, 255, 255, 0.12), + inset 0 0 24px rgba(120, 140, 255, 0.12); +} + +.card--characters:hover { + transform: translateY(-3px); + box-shadow: + inset 0 0 0 1px rgba(255, 255, 255, 0.25), + 0 0 0 1px rgba(160, 180, 255, 0.45), + 0 18px 50px rgba(0, 0, 0, 0.6), + 0 0 60px rgba(110, 135, 255, 0.3); +} + +/* Distill card: warm sunset lovers */ +.card--distill { + min-height: 210px; + display: flex; + align-items: flex-end; + padding: 26px 24px; + background: + linear-gradient(100deg, rgba(45, 25, 60, 0.78) 0%, rgba(80, 40, 80, 0.35) 40%, transparent 72%), + linear-gradient(0deg, rgba(20, 15, 35, 0.55) 0%, transparent 45%), + url(img/card2.png); + background-size: cover, cover, cover; + background-position: center, center, center; + box-shadow: + inset 0 0 0 1px rgba(255, 255, 255, 0.2), + 0 0 0 1px rgba(255, 160, 205, 0.35), + 0 12px 40px rgba(0, 0, 0, 0.5), + 0 0 52px rgba(255, 130, 180, 0.22); +} + +.card--distill .card__frame { + box-shadow: + inset 0 0 0 1px rgba(255, 255, 255, 0.1), + inset 0 0 28px rgba(255, 160, 200, 0.12); +} + +.card--distill:hover { + transform: translateY(-3px); + box-shadow: + inset 0 0 0 1px rgba(255, 255, 255, 0.28), + 0 0 0 1px rgba(255, 170, 210, 0.5), + 0 18px 50px rgba(0, 0, 0, 0.55), + 0 0 68px rgba(255, 140, 190, 0.32); +} + +.card__content { + position: relative; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + z-index: 2; +} + +.card__title { + font-family: "Noto Serif SC", serif; + font-size: 27px; + font-weight: 700; + letter-spacing: 1px; + color: #fff; + text-shadow: 0 2px 14px rgba(0, 0, 0, 0.55); +} + +.card__desc { + font-size: 14px; + color: rgba(235, 238, 255, 0.82); + margin-bottom: 6px; + text-shadow: 0 1px 10px rgba(0, 0, 0, 0.5); +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 10px 20px; + border-radius: 999px; + border: 1px solid transparent; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all var(--transition); + font-family: inherit; + white-space: nowrap; +} + +.btn--primary { + background: linear-gradient(135deg, #8b9dff 0%, #6c7bf7 100%); + color: #fff; + box-shadow: 0 6px 24px rgba(108, 123, 247, 0.35); +} + +.btn--primary:hover { + transform: translateY(-1px); + box-shadow: 0 10px 32px rgba(108, 123, 247, 0.5); +} + +.btn--outline { + background: rgba(0, 0, 0, 0.22); + border-color: rgba(255,255,255,0.35); + color: var(--text); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); +} + +.btn--outline:hover { + background: rgba(0, 0, 0, 0.35); + border-color: rgba(255,255,255,0.55); +} + +.btn--ghost { + background: transparent; + color: var(--text-muted); +} + +.btn--ghost:hover { + color: var(--text); +} + +.btn--small { + padding: 7px 14px; + font-size: 13px; +} + +.btn__arrow { + transition: transform var(--transition); +} + +.btn:hover .btn__arrow { + transform: translateX(3px); +} + +.icon-btn { + width: 38px; + height: 38px; + border-radius: 50%; + border: 1px solid rgba(255,255,255,0.12); + background: rgba(255,255,255,0.05); + color: var(--text); + font-size: 18px; + cursor: pointer; + transition: background var(--transition); +} + +.icon-btn:hover { + background: rgba(255,255,255,0.12); +} + +/* Footer */ +.landing-footer { + display: flex; + justify-content: space-between; + margin-top: 28px; + padding: 0 6px 8px; +} + +.footer-link { + font-size: 13px; + color: var(--text-muted); + text-decoration: none; + transition: color var(--transition); +} + +.footer-link:hover { + color: var(--text); +} + +/* Creator view */ +.view--creator { + padding-top: 44vh; +} + +.creator-header { + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 24px; +} + +.creator-brand { + display: flex; + flex-direction: column; + flex: 1; +} + +.creator-brand__name { + font-family: "Noto Serif SC", serif; + font-size: 18px; + font-weight: 600; + letter-spacing: 1px; + text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6); +} + +.creator-brand__step { + font-size: 12px; + color: var(--text-muted); + text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5); +} + +.stepper { + display: flex; + gap: 6px; +} + +.stepper__dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: rgba(255,255,255,0.15); + transition: background var(--transition); +} + +.stepper__dot.active { + background: var(--accent); + box-shadow: 0 0 8px var(--accent); +} + +/* Form */ +.creator-form { + display: flex; + flex-direction: column; + flex: 1; +} + +.form-step { + display: none; + flex-direction: column; + flex: 1; + border: none; + animation: fadeIn 0.35s ease; +} + +.form-step.active { + display: flex; +} + +.form-step__legend { + font-family: "Noto Serif SC", serif; + font-size: 22px; + font-weight: 600; + margin-bottom: 18px; + padding: 0; + text-shadow: 0 2px 10px rgba(0, 0, 0, 0.55); +} + +.field-group { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 14px; +} + +.field { + display: flex; + flex-direction: column; + gap: 6px; + grid-column: 1 / -1; +} + +.field--half { + grid-column: span 1; +} + +.field--checkbox { + flex-direction: row; + align-items: center; + gap: 10px; + cursor: pointer; +} + +.field__label { + font-size: 13px; + color: var(--text-muted); +} + +.field__label small { + color: rgba(154, 163, 194, 0.7); + font-size: 11px; +} + +.field__input { + width: 100%; + padding: 12px 14px; + border-radius: var(--radius-sm); + border: 1px solid rgba(255,255,255,0.12); + background: rgba(8, 10, 26, 0.78); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + color: var(--text); + font-size: 14px; + font-family: inherit; + outline: none; + transition: border-color var(--transition), box-shadow var(--transition), background var(--transition); +} + +.field__input::placeholder { + color: rgba(154, 163, 194, 0.45); +} + +.field__input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(124, 140, 255, 0.12); +} + +textarea.field__input { + resize: vertical; + min-height: 86px; + line-height: 1.6; +} + +textarea.field__input--tall { + min-height: 120px; +} + +.field--checkbox input { + position: absolute; + opacity: 0; + width: 0; + height: 0; +} + +.checkmark { + width: 18px; + height: 18px; + border-radius: 5px; + border: 1px solid rgba(255,255,255,0.2); + background: rgba(8, 10, 26, 0.78); + display: grid; + place-items: center; + transition: all var(--transition); + flex-shrink: 0; +} + +.field--checkbox input:checked + .checkmark { + background: var(--accent); + border-color: var(--accent); +} + +.field--checkbox input:checked + .checkmark::after { + content: "✓"; + color: #fff; + font-size: 11px; + font-weight: 700; +} + +.form-actions { + margin-top: auto; + padding-top: 22px; + display: flex; + justify-content: flex-end; +} + +.form-actions--split { + justify-content: space-between; +} + +/* Result panel */ +.result-panel { + display: flex; + flex-direction: column; + gap: 18px; + animation: fadeIn 0.5s ease; +} + +.result-panel__header { + text-align: center; +} + +.result-panel__title { + font-family: "Noto Serif SC", serif; + font-size: 24px; + font-weight: 600; +} + +.result-panel__desc { + font-size: 13px; + color: var(--text-muted); + margin-top: 4px; +} + +.result-panel__files { + display: flex; + gap: 12px; +} + +.file-card { + flex: 1; + display: flex; + align-items: center; + gap: 10px; + padding: 14px; + border-radius: var(--radius-sm); + border: 1px solid var(--card-border); + background: var(--card-bg); + backdrop-filter: blur(16px); +} + +.file-card__icon { + font-size: 24px; +} + +.file-card__info { + display: flex; + flex-direction: column; + flex: 1; + gap: 2px; +} + +.file-card__info strong { + font-size: 14px; +} + +.file-card__info small { + font-size: 11px; + color: var(--text-muted); +} + +.result-panel__preview { + border-radius: var(--radius); + border: 1px solid var(--card-border); + background: rgba(0, 0, 0, 0.35); + overflow: hidden; +} + +.preview-tabs { + display: flex; + border-bottom: 1px solid rgba(255,255,255,0.08); +} + +.preview-tab { + flex: 1; + padding: 12px; + background: transparent; + border: none; + color: var(--text-muted); + font-size: 13px; + cursor: pointer; + transition: color var(--transition), background var(--transition); +} + +.preview-tab.active { + color: var(--text); + background: rgba(255,255,255,0.05); +} + +.preview-code { + padding: 16px; + margin: 0; + overflow-x: auto; + max-height: 320px; + overflow-y: auto; + font-family: "SF Mono", Monaco, "Cascadia Code", monospace; + font-size: 12px; + line-height: 1.6; + color: #d4d8f0; + white-space: pre-wrap; + word-break: break-word; +} + +.preview-code code { + font-family: inherit; +} + +/* Utility */ +[hidden] { + display: none !important; +} + +/* Responsive */ +@media (max-width: 420px) { + .app { + padding: 36px 18px 24px; + } + + .card--characters { + padding: 22px 20px; + } + + .card__title { + font-size: 22px; + } + + .field-group { + grid-template-columns: 1fr; + } + + .field--half { + grid-column: 1 / -1; + } + + .result-panel__files { + flex-direction: column; + } +} + +@media (min-width: 640px) { + .app { + max-width: 560px; + } +}