Download index.html from aiqtech/face: direct link, hf CLI and curl.
- Browser
- Download file 33.6 kB
-
https://huggingface.co/spaces/aiqtech/face/resolve/main/index.html
- Command line
-
hf download hf://spaces/aiqtech/face/index.html
-
curl -L -o index.html https://huggingface.co/spaces/aiqtech/face/resolve/main/index.html
33.6 kB
| <html lang="ko"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Proto-AGI FACE — AI 성형 시뮬레이터</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&family=Noto+Sans+KR:wght@300;400;500;700;900&display=swap" rel="stylesheet"> | |
| <style> | |
| *{margin:0;padding:0;box-sizing:border-box} | |
| :root{--bg:#f8f9fc;--surface:#fff;--surface-alt:#f5f6fa;--border:#e2e5f0;--border-hover:#c7cce0; | |
| --shadow-sm:0 1px 3px rgba(15,23,42,.04),0 1px 2px rgba(15,23,42,.06);--shadow:0 4px 16px rgba(15,23,42,.06),0 1px 3px rgba(15,23,42,.08); | |
| --text:#0f172a;--text-sec:#475569;--text-muted:#94a3b8;--accent:#6366f1;--teal:#0d9488;--rose:#e11d48;--pink:#ec4899;--amber:#d97706; | |
| --radius:16px;--radius-sm:10px;--radius-xs:6px; | |
| --fd:'Sora','Noto Sans KR',sans-serif;--fb:'Noto Sans KR','Sora',sans-serif;--fm:'JetBrains Mono',monospace;--tr:.25s cubic-bezier(.4,0,.2,1)} | |
| html{scroll-behavior:smooth}body{font-family:var(--fb);background:var(--bg);color:var(--text);min-height:100vh;overflow-x:hidden;-webkit-font-smoothing:antialiased} | |
| ::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:rgba(99,102,241,.2);border-radius:10px}::selection{background:rgba(99,102,241,.15)} | |
| .bg-p{position:fixed;inset:0;z-index:0;pointer-events:none;background:radial-gradient(ellipse 80% 50% at 20% 10%,rgba(99,102,241,.04),transparent 50%),radial-gradient(ellipse 60% 40% at 80% 90%,rgba(13,148,136,.03),transparent 50%)} | |
| .wrap{position:relative;z-index:1;max-width:960px;margin:0 auto;padding:24px 24px 48px} | |
| .hdr{text-align:center;padding:28px 0 16px;animation:fi .8s ease-out} | |
| @keyframes fi{from{opacity:0;transform:translateY(-16px)}to{opacity:1;transform:translateY(0)}} | |
| .hdr-eye{font-family:var(--fm);font-size:10px;font-weight:600;letter-spacing:5px;text-transform:uppercase;color:var(--accent);margin-bottom:6px} | |
| .hdr-t{font-family:var(--fd);font-size:40px;font-weight:800;letter-spacing:-1.5px;margin-bottom:8px} | |
| .gt{background:linear-gradient(135deg,#6366f1,#e11d48 40%,#0d9488 70%,#6366f1);background-size:200% 200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:sh 6s ease-in-out infinite} | |
| @keyframes sh{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}} | |
| .pills{display:flex;gap:5px;justify-content:center;flex-wrap:wrap;animation:fu .8s .2s ease-out both} | |
| @keyframes fu{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}} | |
| .pill{padding:3px 10px;background:var(--surface);border:1px solid var(--border);border-radius:20px;font-family:var(--fm);font-size:9px;font-weight:500;color:var(--text-muted);box-shadow:var(--shadow-sm)} | |
| .nav{display:flex;gap:3px;background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:4px;margin-bottom:18px;position:sticky;top:12px;z-index:100;box-shadow:var(--shadow);animation:fu .8s .3s ease-out both} | |
| .ni{flex:1;display:flex;align-items:center;justify-content:center;gap:4px;padding:10px 4px;border-radius:var(--radius-sm);cursor:pointer;font-size:11.5px;font-weight:600;color:var(--text-muted);transition:var(--tr);user-select:none} | |
| .ni:hover{color:var(--text-sec);background:var(--surface-alt)} | |
| .ni.a{color:#fff;box-shadow:0 4px 16px rgba(99,102,241,.3)} | |
| .ni.a[data-t="face"]{background:linear-gradient(135deg,#3b82f6,#2563eb)}.ni.a[data-t="physio"]{background:linear-gradient(135deg,#8b5cf6,#7c3aed)}.ni.a[data-t="compat"]{background:linear-gradient(135deg,#ec4899,#db2777)}.ni.a[data-t="sim"]{background:linear-gradient(135deg,#e11d48,#be123c)} | |
| .pnl{display:none;animation:pi .4s ease-out}.pnl.a{display:block}@keyframes pi{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}} | |
| .card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px;margin-bottom:14px;box-shadow:var(--shadow-sm);transition:all .3s} | |
| .card:hover{box-shadow:var(--shadow);border-color:var(--border-hover)} | |
| .ch{display:flex;align-items:center;gap:10px;margin-bottom:12px}.ci{width:34px;height:34px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0} | |
| .ci.bl{background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.15)}.ci.pu{background:rgba(99,102,241,.06);border:1px solid rgba(99,102,241,.15)}.ci.pk{background:rgba(236,72,153,.06);border:1px solid rgba(236,72,153,.15)}.ci.ro{background:rgba(225,29,72,.06);border:1px solid rgba(225,29,72,.15)} | |
| .ct{font-family:var(--fd);font-size:15px;font-weight:700} | |
| .fl{display:block;font-size:10.5px;font-weight:600;color:var(--text-sec);margin-bottom:4px}.fr{display:flex;gap:10px;margin-bottom:10px;flex-wrap:wrap}.fg{flex:1;min-width:140px} | |
| select,input[type=password]{width:100%;padding:8px 11px;background:var(--surface-alt);border:1.5px solid var(--border);border-radius:var(--radius-xs);color:var(--text);font-family:var(--fb);font-size:12px;outline:none;transition:border-color .3s} | |
| select:focus,input:focus{border-color:var(--accent)} | |
| textarea{width:100%;min-height:60px;padding:8px 11px;background:var(--surface-alt);border:1.5px solid var(--border);border-radius:var(--radius-xs);color:var(--text);font-family:var(--fb);font-size:12px;line-height:1.6;resize:vertical;outline:none}textarea:focus{border-color:var(--accent)}textarea::placeholder{color:var(--text-muted);font-size:11px} | |
| .uz{position:relative;border:2px dashed var(--border);border-radius:var(--radius);padding:28px 14px;text-align:center;cursor:pointer;transition:all .3s;background:var(--surface-alt)} | |
| .uz:hover{border-color:var(--accent);background:rgba(99,102,241,.03)}.uz.hi{border-style:solid;border-color:var(--teal);background:#fff;padding:6px} | |
| .uz input[type=file]{position:absolute;inset:0;opacity:0;cursor:pointer}.uz img{width:100%;max-height:280px;object-fit:contain;border-radius:var(--radius-sm)} | |
| .uzi{font-size:26px;margin-bottom:4px;opacity:.6}.uzt{font-size:10.5px;color:var(--text-muted)}.uzt b{color:var(--text-sec)} | |
| .uzs{padding:18px 10px}.uzs .uzi{font-size:20px;margin-bottom:2px}.uzs img{max-height:180px} | |
| .btn{padding:11px 20px;border:none;border-radius:var(--radius-sm);cursor:pointer;font-family:var(--fb);font-weight:600;font-size:12.5px;transition:all .25s;width:100%;margin-top:10px} | |
| .btn:disabled{opacity:.4;cursor:not-allowed;transform:none!important}.btn:hover{transform:translateY(-2px)} | |
| .bb{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;box-shadow:0 4px 16px rgba(59,130,246,.25)} | |
| .bp{background:linear-gradient(135deg,#8b5cf6,#7c3aed);color:#fff;box-shadow:0 4px 16px rgba(139,92,246,.25)} | |
| .bk{background:linear-gradient(135deg,#ec4899,#db2777);color:#fff;box-shadow:0 4px 16px rgba(236,72,153,.25)} | |
| .br{background:linear-gradient(135deg,#e11d48,#be123c);color:#fff;box-shadow:0 4px 16px rgba(225,29,72,.25)} | |
| .rg{display:flex;gap:4px;flex-wrap:wrap}.rg label{display:flex;align-items:center;gap:3px;padding:5px 10px;background:var(--surface-alt);border:1.5px solid var(--border);border-radius:20px;cursor:pointer;font-size:10.5px;font-weight:500;color:var(--text-sec);transition:all .2s} | |
| .rg input{display:none}.rg input:checked+label{background:rgba(99,102,241,.06);border-color:var(--accent);color:var(--accent);font-weight:700} | |
| .kt{font-size:10px;color:var(--accent);cursor:pointer;font-weight:600;margin-bottom:8px;display:inline-flex;align-items:center;gap:3px}.kt:hover{text-decoration:underline} | |
| .ks{display:none;margin-bottom:10px;padding:12px;background:var(--surface-alt);border-radius:var(--radius-sm);border:1px solid var(--border)}.ks.o{display:block} | |
| /* Loader */ | |
| .ld{display:none;text-align:center;padding:36px 16px}.ld.a{display:block} | |
| .lr{width:38px;height:38px;border:3px solid rgba(99,102,241,.12);border-top-color:var(--accent);border-radius:50%;animation:sp .9s linear infinite;margin:0 auto 10px;position:relative} | |
| .lr::after{content:'';position:absolute;inset:4px;border:2px solid transparent;border-top-color:var(--teal);border-radius:50%;animation:sp 1.4s linear infinite reverse} | |
| @keyframes sp{to{transform:rotate(360deg)}}.lt{font-size:12px;color:var(--text-sec);animation:br 2s ease-in-out infinite}@keyframes br{0%,100%{opacity:1}50%{opacity:.5}} | |
| /* Scan */ | |
| .sco{display:none;position:relative;padding:44px 14px;text-align:center;background:linear-gradient(180deg,#0f172a,#1e1b4b 50%,#0f172a);border-radius:var(--radius);overflow:hidden;margin-bottom:14px}.sco.a{display:block;animation:sci .5s ease-out} | |
| @keyframes sci{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}} | |
| .sp{position:absolute;inset:0;overflow:hidden}.sp span{position:absolute;width:2px;height:2px;background:#818cf8;border-radius:50%;opacity:0;animation:pd 4s linear infinite} | |
| @keyframes pd{0%{opacity:0;transform:translateY(100%) scale(0)}20%{opacity:.5}80%{opacity:.3}100%{opacity:0;transform:translateY(-100%) scale(1.5)}} | |
| .fc{position:relative;width:160px;height:210px;margin:0 auto 16px} | |
| .fm{width:100%;height:100%;filter:drop-shadow(0 0 14px rgba(99,102,241,.3))} | |
| .fo{fill:none;stroke:#818cf8;stroke-width:1.5;stroke-linecap:round;opacity:0;animation:df 3s ease-out forwards} | |
| @keyframes df{0%{stroke-dasharray:1200;stroke-dashoffset:1200;opacity:0}10%{opacity:1}100%{stroke-dashoffset:0;opacity:1}} | |
| .ll{position:absolute;left:10%;right:10%;height:2px;background:linear-gradient(90deg,transparent,#e11d48,#f97316,#e11d48,transparent);box-shadow:0 0 15px 3px rgba(225,29,72,.4);border-radius:2px;animation:ls 2.8s ease-in-out infinite;z-index:5} | |
| @keyframes ls{0%{top:8%;opacity:0}5%{opacity:1}50%{top:88%;opacity:1}55%{opacity:0}100%{top:8%;opacity:0}} | |
| .sg{position:absolute;inset:0;background:repeating-linear-gradient(0deg,transparent,transparent 19px,rgba(99,102,241,.05) 19px,rgba(99,102,241,.05) 20px),repeating-linear-gradient(90deg,transparent,transparent 19px,rgba(99,102,241,.05) 19px,rgba(99,102,241,.05) 20px);animation:gp 3s ease-in-out infinite}@keyframes gp{0%,100%{opacity:.3}50%{opacity:.7}} | |
| .sc{position:absolute;width:18px;height:18px;z-index:6}.sc::before,.sc::after{content:'';position:absolute;background:#0d9488;border-radius:1px} | |
| .sc.tl{top:0;left:0}.sc.tr{top:0;right:0}.sc.bl{bottom:0;left:0}.sc.br{bottom:0;right:0} | |
| .sc.tl::before,.sc.tr::before{top:0;height:2px;width:12px}.sc.tl::after,.sc.bl::after{left:0;width:2px;height:12px} | |
| .sc.tr::before{right:0;left:auto}.sc.tr::after{right:0;width:2px;height:12px}.sc.bl::before{bottom:0;top:auto;height:2px;width:12px}.sc.bl::after{bottom:0;top:auto} | |
| .sc.br::before{bottom:0;right:0;top:auto;left:auto;height:2px;width:12px}.sc.br::after{right:0;bottom:0;top:auto;width:2px;height:12px} | |
| .pr{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);border:1px solid rgba(99,102,241,.2);border-radius:50%;animation:pe 3s ease-out infinite} | |
| @keyframes pe{0%{width:70px;height:95px;opacity:.5}100%{width:220px;height:290px;opacity:0}} | |
| .sd{position:absolute;font-family:var(--fm);font-size:7.5px;color:#818cf8;opacity:0;animation:dfl 4s ease-in-out infinite;white-space:nowrap} | |
| @keyframes dfl{0%,100%{opacity:0}20%{opacity:.7}60%{opacity:.8}80%{opacity:0}} | |
| .ssm{font-family:var(--fd);font-size:13px;font-weight:700;color:#e2e8f0;margin-bottom:3px;animation:br 2s ease-in-out infinite} | |
| .sss{font-family:var(--fm);font-size:9px;color:#818cf8;letter-spacing:1px} | |
| .spb{width:50%;margin:8px auto 0;height:3px;background:rgba(99,102,241,.15);border-radius:3px;overflow:hidden} | |
| .spf{height:100%;background:linear-gradient(90deg,#6366f1,#e11d48,#0d9488);background-size:200% 100%;border-radius:3px;animation:ps 2s linear infinite;transition:width .5s ease} | |
| @keyframes ps{0%{background-position:100% 0}100%{background-position:-100% 0}} | |
| /* Results */ | |
| .ra{margin-top:14px;animation:ri .5s ease-out}@keyframes ri{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}} | |
| .rh{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px;box-shadow:var(--shadow-sm);font-size:12.5px;line-height:1.9;color:var(--text-sec);margin-top:10px} | |
| .rh:empty{display:none}.rh h1,.rh h2,.rh h3{font-family:var(--fd);color:var(--text);margin:12px 0 5px}.rh h1{font-size:16px}.rh h2{font-size:13.5px}.rh h3{font-size:12px} | |
| .rh blockquote{border-left:3px solid var(--accent);padding:5px 10px;background:rgba(99,102,241,.06);border-radius:0 var(--radius-xs) var(--radius-xs) 0;margin:8px 0;font-size:11px;color:var(--accent)} | |
| .rh hr{border:none;border-top:1px solid var(--border);margin:12px 0}.rh li{margin-left:14px} | |
| .riw{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);margin-bottom:12px}.riw img{width:100%;display:block} | |
| .ril{padding:8px 12px;font-size:10.5px;font-weight:600;color:var(--text-sec);border-top:1px solid var(--border)} | |
| .rgg{display:grid;grid-template-columns:1fr 1fr;gap:10px}.em{padding:12px;text-align:center;color:var(--rose);font-size:11.5px;background:var(--surface);border:1px solid rgba(225,29,72,.2);border-radius:var(--radius);margin-bottom:10px}.em:empty{display:none} | |
| .rv:empty{display:none} | |
| .ft{text-align:center;margin-top:36px;padding:18px 0;border-top:1px solid var(--border)}.ftb{font-family:var(--fd);font-size:11px;font-weight:600;color:var(--text-muted);margin-bottom:3px} | |
| .ftd{font-size:10px;color:var(--text-muted)}.ftd a{color:var(--accent);text-decoration:none;font-weight:600} | |
| .ftl{width:40px;height:2px;background:linear-gradient(90deg,transparent,var(--accent),var(--rose),transparent);margin:6px auto;border-radius:2px;opacity:.4} | |
| .ftt{font-family:var(--fm);font-size:8px;color:var(--text-muted);opacity:.5;letter-spacing:1px} | |
| @media(max-width:640px){.wrap{padding:12px 12px 32px}.hdr{padding:16px 0 10px}.hdr-t{font-size:28px}.nav{flex-wrap:wrap;position:static}.ni{font-size:10px;padding:8px 3px}.card{padding:16px 12px}.fr{flex-direction:column}.rgg{grid-template-columns:1fr}.fc{width:120px;height:160px}} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="bg-p"></div> | |
| <div class="wrap"> | |
| <header class="hdr"> | |
| <div class="hdr-eye">얼굴 성형 시뮬레이션</div> | |
| <h1 class="hdr-t"><span class="gt">내가 왕이 될 상인가?</span></h1> | |
| <div class="pills"> | |
| <span class="pill" style="background:linear-gradient(135deg,rgba(225,29,72,.08),rgba(249,115,22,.08));border-color:rgba(225,29,72,.25);color:#e11d48;font-weight:700">🏥 VIDRAFT</span> | |
| <span class="pill">🩺 의사</span><span class="pill">🔮 관상</span><span class="pill">💡 창발</span><span class="pill">⚖️ 비평</span><span class="pill">🎯 감독</span> | |
| </div> | |
| </header> | |
| <nav class="nav"> | |
| <div class="ni a" data-t="face" onclick="st('face')">🔍 얼굴분석</div> | |
| <div class="ni" data-t="physio" onclick="st('physio')">🔮 관상</div> | |
| <div class="ni" data-t="compat" onclick="st('compat')">💑 궁합</div> | |
| <div class="ni" data-t="sim" onclick="st('sim')">✨ 시뮬레이션</div> | |
| </nav> | |
| <div style="margin-bottom:14px"><span class="kt" onclick="document.getElementById('ks').classList.toggle('o')">🔑 API Keys ▾</span> | |
| <div class="ks" id="ks"><div class="fr"><div class="fg"><div class="fl">FAL Key</div><input type="password" id="fk" placeholder="fal_..."></div><div class="fg"><div class="fl">Fireworks Key</div><input type="password" id="wk" placeholder="fw_..."></div></div></div></div> | |
| <!-- TAB 1: 얼굴 분석 --> | |
| <div class="pnl a" id="p-face"><div class="card"><div class="ch"><div class="ci bl">🔍</div><div class="ct">의학적 얼굴 분석</div></div> | |
| <div class="uz" id="uz-face" ><div class="ph"><div class="uzi">🖼️</div><div class="uzt"><b>얼굴 사진 업로드</b></div></div><img class="ui" style="display:none"><input type="file" accept="image/*" onchange="hu(this,'face')"></div> | |
| <button class="btn bb" id="bf" onclick="runFace()">🔍 의학적 얼굴 분석 시작</button></div> | |
| <div class="ld" id="ld-face"><div class="lr"></div><div class="lt">🩺 얼굴 구조 분석 중...</div></div> | |
| <div class="ra"><div class="rh" id="rf"></div></div></div> | |
| <!-- TAB 2: 관상 --> | |
| <div class="pnl" id="p-physio"><div class="card"><div class="ch"><div class="ci pu">🔮</div><div class="ct">관상 분석</div></div> | |
| <div class="uz" id="uz-physio" ><div class="ph"><div class="uzi">🖼️</div><div class="uzt"><b>얼굴 사진 업로드</b></div></div><img class="ui" style="display:none"><input type="file" accept="image/*" onchange="hu(this,'physio')"></div> | |
| <button class="btn bp" id="bp" onclick="runPhysio()">🔮 관상 분석 시작</button></div> | |
| <div class="ld" id="ld-physio"><div class="lr"></div><div class="lt">🔮 관상 분석 중...</div></div> | |
| <div class="ra"><div class="rv" id="rpv"></div><div class="rh" id="rp"></div></div></div> | |
| <!-- TAB 3: 궁합 --> | |
| <div class="pnl" id="p-compat"><div class="card"><div class="ch"><div class="ci pk">💑</div><div class="ct">관상 궁합 분석</div></div> | |
| <div class="fr"><div class="fg"><div class="fl">📸 나의 얼굴</div><div class="uz uzs" id="uz-c1" ><div class="ph"><div class="uzi">🧑</div><div class="uzt"><b>사진 1</b></div></div><img class="ui" style="display:none"><input type="file" accept="image/*" onchange="hu(this,'c1')"></div></div> | |
| <div class="fg"><div class="fl">📸 상대방 얼굴</div><div class="uz uzs" id="uz-c2" ><div class="ph"><div class="uzi">🧑</div><div class="uzt"><b>사진 2</b></div></div><img class="ui" style="display:none"><input type="file" accept="image/*" onchange="hu(this,'c2')"></div></div></div> | |
| <div class="fg"><div class="fl">🔗 관계 유형</div><div class="rg"> | |
| <input type="radio" name="rel" id="r1" value="💼 비즈니스 파트너"><label for="r1">💼 비즈니스</label> | |
| <input type="radio" name="rel" id="r2" value="❤️ 연인 / 배우자" checked><label for="r2">❤️ 연인</label> | |
| <input type="radio" name="rel" id="r3" value="👨👩👧 가족"><label for="r3">👨👩👧 가족</label> | |
| <input type="radio" name="rel" id="r4" value="🤝 친구 / 동료"><label for="r4">🤝 친구</label> | |
| </div></div> | |
| <button class="btn bk" id="bc" onclick="runCompat()">💑 궁합 분석 시작</button></div> | |
| <div class="ld" id="ld-compat"><div class="lr"></div><div class="lt">💑 궁합 분석 중...</div></div> | |
| <div class="ra"><div class="rv" id="rcv"></div><div class="rh" id="rc"></div></div></div> | |
| <!-- TAB 4: 시뮬레이션 --> | |
| <div class="pnl" id="p-sim"><div class="card"><div class="ch"><div class="ci ro">✨</div><div class="ct">성형 시뮬레이션</div></div> | |
| <div class="uz" id="uz-sim" ><div class="ph"><div class="uzi">🖼️</div><div class="uzt"><b>얼굴 사진 업로드</b></div></div><img class="ui" style="display:none"><input type="file" accept="image/*" onchange="hu(this,'sim')"></div> | |
| <div class="fr" style="margin-top:10px"><div class="fg" style="flex:2"><div class="fl">🏥 시술 프리셋</div><select id="ps"> | |
| <option>없음 (직접 입력)</option><option>👁️ 자연유착 쌍꺼풀</option><option>👁️ 절개 쌍꺼풀</option><option>👃 코끝 성형</option><option>👃 콧대 성형</option> | |
| <option>🦷 사각턱 축소</option><option>💉 팔자주름 필러</option><option>💉 이마 보톡스</option><option>💋 입술 필러</option><option>🏥 눈+코 동시</option><option>🏥 풀페이스 리프팅</option><option>✨ 동안 스타일</option> | |
| </select></div><div class="fg" style="flex:1"><div class="fl">📐 비율</div><select id="ar"><option>auto</option><option>1:1</option><option>3:4</option><option>4:3</option></select></div></div> | |
| <div class="fg"><div class="fl">✏️ 추가 지시</div><textarea id="cp" placeholder="프리셋 외 추가 요청"></textarea></div> | |
| <div class="fg" style="margin-top:4px"><div class="fl">💪 강도</div><div class="rg"> | |
| <input type="radio" name="int" id="i1" value="자연스럽게 (Subtle)"><label for="i1">🌿 자연</label> | |
| <input type="radio" name="int" id="i2" value="보통 (Moderate)" checked><label for="i2">⚡ 보통</label> | |
| <input type="radio" name="int" id="i3" value="확실하게 (Strong)"><label for="i3">🔥 확실</label> | |
| </div></div> | |
| <button class="btn br" id="bs" onclick="runSim()">🚀 시뮬레이션 시작</button></div> | |
| <!-- Scan Animation --> | |
| <div class="sco" id="sco"><div class="sp" id="spp"></div><div class="sg"></div> | |
| <div class="fc"><div class="pr"></div><div class="pr" style="animation-delay:1s"></div><div class="pr" style="animation-delay:2s"></div> | |
| <div class="sc tl"></div><div class="sc tr"></div><div class="sc bl"></div><div class="sc br"></div><div class="ll"></div> | |
| <svg class="fm" viewBox="0 0 220 280"><ellipse class="fo" cx="110" cy="145" rx="78" ry="105" style="stroke-dasharray:600"/> | |
| <ellipse class="fo" cx="78" cy="118" rx="22" ry="10" style="animation-delay:.4s;stroke-dasharray:120;stroke:#e11d48;stroke-width:1.2"/> | |
| <ellipse class="fo" cx="142" cy="118" rx="22" ry="10" style="animation-delay:.5s;stroke-dasharray:120;stroke:#e11d48;stroke-width:1.2"/> | |
| <path class="fo" d="M52,98 Q78,84 102,98" style="animation-delay:.7s;stroke-dasharray:80;stroke-width:1"/> | |
| <path class="fo" d="M118,98 Q142,84 168,98" style="animation-delay:.8s;stroke-dasharray:80;stroke-width:1"/> | |
| <path class="fo" d="M110,128 L110,160 Q102,172 94,166 M110,160 Q118,172 126,166" style="animation-delay:1s;stroke-dasharray:100;stroke:#0d9488;stroke-width:1.2"/> | |
| <path class="fo" d="M82,195 Q96,206 110,207 Q124,206 138,195" style="animation-delay:1.3s;stroke-dasharray:80;stroke:#f59e0b;stroke-width:1.2"/> | |
| </svg> | |
| <div class="sd" style="top:10%;left:-22%;animation-delay:.5s">SYMMETRY: ANALYZING</div> | |
| <div class="sd" style="top:38%;right:-25%;animation-delay:1.2s">RATIO: 1.618</div> | |
| <div class="sd" style="top:62%;left:-20%;animation-delay:2s">BONE: MAPPING</div> | |
| <div class="sd" style="top:82%;right:-22%;animation-delay:2.8s">TISSUE: READY</div> | |
| </div> | |
| <div style="position:relative;z-index:10"><div class="ssm" id="stx">🩺 분석 중...</div><div class="sss" id="ssu">SOMA Processing</div><div class="spb"><div class="spf" id="spg" style="width:5%"></div></div></div></div> | |
| <!-- Results --> | |
| <div id="sr" style="display:none"><div class="em" id="se"></div> | |
| <div class="riw" id="sg" style="display:none"><img id="si"><div class="ril">🎨 시뮬레이션 결과</div></div> | |
| <div id="sb" style="display:none;margin-top:10px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px"></div> | |
| <div class="rgg" id="sa" style="display:none;margin-top:10px"><div class="riw"><img id="s45"><div class="ril">📐 45도</div></div><div class="riw"><img id="ssd"><div class="ril">📐 측면</div></div></div> | |
| <div class="rh" id="srp" style="margin-top:10px"></div></div></div> | |
| <footer class="ft"><div class="ftb">Proto-AGI FACE</div><div class="ftd"><a href="https://vidraft.net" target="_blank">VIDRAFT</a> · <a href="/gradio" style="color:var(--teal);font-weight:600">Gradio UI →</a></div><div class="ftl"></div><div class="ftt">팀오케스트레이션 × 형상 분석 × 메타인지 · 5-Agent</div></footer> | |
| </div> | |
| <script> | |
| const B=window.location.origin,U={face:null,physio:null,c1:null,c2:null,sim:null}; | |
| function st(t){document.querySelectorAll('.ni').forEach(e=>e.classList.remove('a'));document.querySelectorAll('.pnl').forEach(e=>e.classList.remove('a'));document.querySelector(`[data-t="${t}"]`).classList.add('a');document.getElementById(`p-${t}`).classList.add('a')} | |
| function hu(inp,k){const f=inp.files&&inp.files[0];if(!f)return;const z=inp.closest('.uz'),ph=z.querySelector('.ph'),im=z.querySelector('.ui');const r=new FileReader();r.onload=e=>{im.src=e.target.result;im.style.display='block';if(ph)ph.style.display='none';z.classList.add('hi')};r.readAsDataURL(f);ul(f,k)} | |
| async function ul(f,k){const fd=new FormData();fd.append('files',f);for(const u of[`${B}/gradio/gradio_api/upload`,`${B}/gradio/upload`,`${B}/gradio_api/upload`,`${B}/upload`]){try{const r=await fetch(u,{method:'POST',body:fd});if(r.ok){const j=await r.json();U[k]=Array.isArray(j)?j[0]:j;return}}catch{}}} | |
| function mf(p){return{path:p,meta:{_type:'gradio.FileData'},orig_name:'face.png',mime_type:'image/png'}} | |
| async function gc(api,data){ | |
| /* ── Phase 1: POST → event_id 획득 (URL 순서대로 시도) ── */ | |
| let sseUrl=null; | |
| for(const u of[`${B}/gradio/gradio_api/call${api}`,`${B}/gradio/call${api}`,`${B}/gradio/api${api}`,`${B}/api${api}`,`${B}/gradio_api/call${api}`]){ | |
| try{ | |
| const r=await fetch(u,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({data})}); | |
| if(!r.ok)continue; | |
| const j=await r.json(); | |
| if(!j.event_id)continue; | |
| sseUrl=`${u}/${j.event_id}`; | |
| break; /* POST 성공 → SSE 단계로 이동 */ | |
| }catch{} | |
| } | |
| if(!sseUrl)throw new Error('API 연결 실패 (서버에 연결할 수 없습니다)'); | |
| /* ── Phase 2: SSE 수신 + 자동 재연결 (새 POST 절대 안 함) ── */ | |
| return new Promise((ok,no)=>{ | |
| let done=false,retries=0,es=null; | |
| const MAX_RETRY=150; /* 150×2s=5분 재연결 허용 */ | |
| const TOTAL_TO=setTimeout(()=>{if(!done){done=true;if(es)es.close();no(new Error('시간 초과 (10분)'));}},600000); | |
| function resolve(val){if(done)return;done=true;clearTimeout(TOTAL_TO);if(es)es.close();ok(val);} | |
| function reject(err){if(done)return;done=true;clearTimeout(TOTAL_TO);if(es)es.close();no(err);} | |
| function onData(e){ | |
| try{ | |
| const raw=JSON.parse(e.data); | |
| if(!raw)return; | |
| /* process_completed 포맷 */ | |
| if(raw.msg==='process_completed'&&raw.output&&raw.output.data){resolve(raw.output.data);return;} | |
| /* 직접 배열 포맷 */ | |
| const p=Array.isArray(raw)?raw:(raw.data&&Array.isArray(raw.data)?raw.data:null); | |
| if(p)resolve(p); | |
| }catch{} | |
| } | |
| function connect(){ | |
| if(done)return; | |
| es=new EventSource(sseUrl); | |
| es.onmessage=onData; | |
| es.addEventListener('complete',onData); | |
| es.addEventListener('process_completed',onData); | |
| es.onerror=()=>{ | |
| if(done)return; | |
| es.close(); | |
| retries++; | |
| if(retries>MAX_RETRY){reject(new Error('연결이 반복 끊김 (재시도 초과)'));return;} | |
| setTimeout(connect,2000); /* 2초 후 같은 event_id로 재연결 */ | |
| }; | |
| } | |
| connect(); | |
| }); | |
| } | |
| function iu(d){if(!d)return null;if(typeof d==='string'){if(d.startsWith('/file='))return B+'/gradio'+d;return d}const u=d.url||d.path||(d.value&&(d.value.url||d.value.path));if(!u)return null;if(u.startsWith('/file='))return B+'/gradio'+u;if(!u.startsWith('http'))return B+'/gradio/file='+u;return u} | |
| function md(s){return s.replace(/^### (.*$)/gm,'<h3>$1</h3>').replace(/^## (.*$)/gm,'<h2>$1</h2>').replace(/^# (.*$)/gm,'<h1>$1</h1>').replace(/\*\*(.*?)\*\*/g,'<b>$1</b>').replace(/\*(.*?)\*/g,'<em>$1</em>').replace(/^> (.*$)/gm,'<blockquote>$1</blockquote>').replace(/^---$/gm,'<hr>').replace(/^- (.*$)/gm,'<li>$1</li>').replace(/\n\n/g,'<br><br>').replace(/\n/g,'<br>')} | |
| function sl(id){document.getElementById(id).classList.add('a')}function hl(id){document.getElementById(id).classList.remove('a')} | |
| /* 1. Face */ | |
| async function runFace(){if(!U.face){alert('사진을 업로드해주세요.');return}const b=document.getElementById('bf');b.disabled=true;document.getElementById('rf').innerHTML='';sl('ld-face');try{const r=await gc('/face_analysis',[mf(U.face),document.getElementById('wk').value]);hl('ld-face');document.getElementById('rf').innerHTML=md(r[0]||'')}catch(e){hl('ld-face');document.getElementById('rf').innerHTML='<div class="em">⚠️ '+e.message+'</div>'}b.disabled=false} | |
| /* 2. Physio */ | |
| async function runPhysio(){if(!U.physio){alert('사진을 업로드해주세요.');return}const b=document.getElementById('bp');b.disabled=true;document.getElementById('rpv').innerHTML='';document.getElementById('rp').innerHTML='';sl('ld-physio');try{const r=await gc('/physiognomy_analysis',[mf(U.physio),document.getElementById('wk').value]);hl('ld-physio');document.getElementById('rpv').innerHTML=r[0]||'';document.getElementById('rp').innerHTML=md(r[1]||'')}catch(e){hl('ld-physio');document.getElementById('rp').innerHTML='<div class="em">⚠️ '+e.message+'</div>'}b.disabled=false} | |
| /* 3. Compat */ | |
| async function runCompat(){if(!U.c1||!U.c2){alert('두 사람의 사진을 모두 업로드해주세요.');return}const b=document.getElementById('bc');b.disabled=true;document.getElementById('rcv').innerHTML='';document.getElementById('rc').innerHTML='';sl('ld-compat');const rel=document.querySelector('input[name="rel"]:checked').value;try{const r=await gc('/compatibility_analysis',[mf(U.c1),mf(U.c2),rel,document.getElementById('wk').value]);hl('ld-compat');document.getElementById('rcv').innerHTML=r[0]||'';document.getElementById('rc').innerHTML=md(r[1]||'')}catch(e){hl('ld-compat');document.getElementById('rc').innerHTML='<div class="em">⚠️ '+e.message+'</div>'}b.disabled=false} | |
| /* 4. Sim — scan animation */ | |
| const SS=[{t:'🩺 얼굴 분석 중...',s:'Doctor Agent',p:12},{t:'💡 프롬프트 최적화...',s:'Creator Agent',p:25},{t:'🎨 이미지 생성 중...',s:'Nano Banana 2',p:45},{t:'⚖️ 품질 검증...',s:'Critic Agent',p:65},{t:'📐 다각도 생성...',s:'Angle Gen',p:78},{t:'🎯 리포트 작성...',s:'Director Agent',p:90}]; | |
| let si=0,stm=null; | |
| function startSc(){const o=document.getElementById('sco');o.style.display='block';o.classList.add('a');const c=document.getElementById('spp');c.innerHTML='';for(let i=0;i<20;i++){const s=document.createElement('span');s.style.left=Math.random()*100+'%';s.style.animationDelay=Math.random()*4+'s';s.style.animationDuration=(3+Math.random()*3)+'s';c.appendChild(s)}si=0;uSc();stm=setInterval(()=>{si++;if(si<SS.length)uSc()},5000)} | |
| function uSc(){const s=SS[si];document.getElementById('stx').textContent=s.t;document.getElementById('ssu').textContent=s.s;document.getElementById('spg').style.width=s.p+'%'} | |
| function stopSc(){if(stm)clearInterval(stm);const o=document.getElementById('sco');o.classList.remove('a');o.style.display='none'} | |
| async function runSim(){if(!U.sim){alert('사진을 업로드해주세요.');return}const b=document.getElementById('bs');b.disabled=true;document.getElementById('sr').style.display='none';['sg','sb','sa'].forEach(id=>{document.getElementById(id).style.display='none'});document.getElementById('se').textContent='';document.getElementById('srp').innerHTML='';startSc();try{const r=await gc('/run_simulation',[mf(U.sim),document.getElementById('cp').value,document.getElementById('ps').value,document.querySelector('input[name="int"]:checked').value,document.getElementById('ar').value,document.getElementById('fk').value,document.getElementById('wk').value]);stopSc();document.getElementById('sr').style.display='block';const gi=iu(r[0]),ba=r[1]||'',rp=r[2]||'',a45=iu(r[3]),as=iu(r[4]),er=r[5]||'';if(er)document.getElementById('se').textContent='⚠️ '+er;if(gi){document.getElementById('si').src=gi;document.getElementById('sg').style.display='block'}if(ba){document.getElementById('sb').innerHTML=ba;document.getElementById('sb').style.display='block'}if(a45||as){if(a45)document.getElementById('s45').src=a45;if(as)document.getElementById('ssd').src=as;document.getElementById('sa').style.display='grid'}if(rp)document.getElementById('srp').innerHTML=md(rp);document.getElementById('sr').scrollIntoView({behavior:'smooth'})}catch(e){stopSc();document.getElementById('sr').style.display='block';document.getElementById('se').textContent='⚠️ '+e.message}b.disabled=false} | |
| document.querySelectorAll('.uz').forEach(z=>{z.addEventListener('dragover',e=>{e.preventDefault();z.style.borderColor='var(--accent)'});z.addEventListener('dragleave',()=>{z.style.borderColor=''});z.addEventListener('drop',e=>{e.preventDefault();z.style.borderColor='';const f=e.dataTransfer.files&&e.dataTransfer.files[0];if(f&&f.type.startsWith('image/')){const inp=z.querySelector('input[type=file]');const dt=new DataTransfer();dt.items.add(f);inp.files=dt.files;inp.dispatchEvent(new Event('change'))}})}); | |
| /* BA Slider init — 동적으로 삽입된 슬라이더 자동 감지 */ | |
| (function(){ | |
| function initBA(uid){ | |
| var box=document.getElementById(uid+'_box'); | |
| var clip=document.getElementById(uid+'_clip'); | |
| var line=document.getElementById(uid+'_line'); | |
| var range=document.getElementById(uid+'_range'); | |
| if(!box||!clip||!line||!range)return; | |
| if(box.dataset.baInit==='1')return; | |
| box.dataset.baInit='1'; | |
| var dragging=false; | |
| function setPos(pct){pct=Math.max(0,Math.min(100,pct));clip.style.width=pct+'%';line.style.left=pct+'%';range.value=pct;} | |
| function getPct(e){var rect=box.getBoundingClientRect();var x=(e.touches?e.touches[0].clientX:e.clientX)-rect.left;return(x/rect.width)*100;} | |
| box.addEventListener('mousedown',function(e){dragging=true;setPos(getPct(e));e.preventDefault();}); | |
| document.addEventListener('mousemove',function(e){if(dragging)setPos(getPct(e));}); | |
| document.addEventListener('mouseup',function(){dragging=false;}); | |
| box.addEventListener('touchstart',function(e){dragging=true;setPos(getPct(e));e.preventDefault();},{passive:false}); | |
| box.addEventListener('touchmove',function(e){if(dragging){setPos(getPct(e));e.preventDefault();}},{passive:false}); | |
| box.addEventListener('touchend',function(){dragging=false;}); | |
| range.addEventListener('input',function(){setPos(parseFloat(this.value));}); | |
| } | |
| setInterval(function(){ | |
| document.querySelectorAll('[data-ba-uid]').forEach(function(el){ | |
| if(el.dataset.baInit!=='1') initBA(el.dataset.baUid); | |
| }); | |
| },500); | |
| })(); | |
| </script> | |
| </body> | |
| </html> |