Spaces:
Sleeping
Sleeping
| <html lang="ko"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Persona Issue Survey(Beta version) - NBS Avatar System</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;700&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --secondary: #ec4899; | |
| --bg: #f8fafc; | |
| --sidebar-bg: #0f172a; | |
| --card-bg: #ffffff; | |
| --text-main: #1e293b; | |
| --text-muted: #64748b; | |
| --glass: rgba(255, 255, 255, 0.7); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: 'Pretendard', sans-serif; | |
| } | |
| body { | |
| background-color: var(--bg); | |
| color: var(--text-main); | |
| display: flex; | |
| min-height: 100vh; | |
| } | |
| /* Sidebar Styling */ | |
| .sidebar { | |
| width: 320px; | |
| background-color: var(--sidebar-bg); | |
| color: white; | |
| padding: 2.5rem 1.5rem; | |
| display: flex; | |
| flex-direction: column; | |
| position: fixed; | |
| height: 100vh; | |
| left: 0; | |
| top: 0; | |
| box-shadow: 4px 0 20px rgba(0, 0, 0, 0.1); | |
| z-index: 100; | |
| } | |
| .logo { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| margin-bottom: 3rem; | |
| background: linear-gradient(to right, #818cf8, #f472b6); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .menu-item { | |
| padding: 1rem 1.25rem; | |
| border-radius: 12px; | |
| margin-bottom: 0.5rem; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| color: #94a3b8; | |
| font-weight: 500; | |
| } | |
| .menu-item:hover { | |
| background: rgba(255, 255, 255, 0.05); | |
| color: white; | |
| } | |
| .menu-item.active { | |
| background: var(--primary); | |
| color: white; | |
| } | |
| .api-key-section { | |
| margin-top: auto; | |
| padding: 1.5rem; | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 16px; | |
| font-size: 0.875rem; | |
| } | |
| .api-key-label { | |
| display: block; | |
| margin-bottom: 0.75rem; | |
| font-weight: 500; | |
| color: #e2e8f0; | |
| } | |
| .api-key-container { | |
| position: relative; | |
| width: 100%; | |
| margin-bottom: 1rem; | |
| } | |
| .api-key-input { | |
| width: 100%; | |
| padding: 0.75rem; | |
| padding-right: 2.5rem; | |
| /* Space for toggle icon */ | |
| border-radius: 8px; | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| background: rgba(255, 255, 255, 0.05); | |
| color: #f8fafc; | |
| font-size: 0.85rem; | |
| transition: all 0.2s; | |
| } | |
| .api-key-input:focus { | |
| outline: none; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3); | |
| } | |
| .toggle-password { | |
| position: absolute; | |
| right: 0.75rem; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| cursor: pointer; | |
| color: #94a3b8; | |
| font-size: 0.9rem; | |
| user-select: none; | |
| } | |
| .toggle-password:hover { | |
| color: white; | |
| } | |
| .key-guide { | |
| color: #94a3b8; | |
| font-size: 0.75rem; | |
| line-height: 1.5; | |
| } | |
| .key-guide a { | |
| color: #818cf8; | |
| text-decoration: none; | |
| } | |
| /* Main Content Styling */ | |
| .main-content { | |
| flex: 1; | |
| margin-left: 320px; | |
| padding: 3rem; | |
| max-width: 1200px; | |
| } | |
| .header-box { | |
| margin-bottom: 3rem; | |
| } | |
| .title { | |
| font-size: 2.25rem; | |
| font-weight: 700; | |
| margin-bottom: 1rem; | |
| color: #0f172a; | |
| } | |
| .description { | |
| color: var(--text-muted); | |
| line-height: 1.6; | |
| font-size: 1.1rem; | |
| } | |
| .system-info { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 1.5rem; | |
| margin-top: 2rem; | |
| } | |
| .info-card { | |
| background: white; | |
| padding: 1.5rem; | |
| border-radius: 20px; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.03); | |
| border: 1px solid #f1f5f9; | |
| } | |
| .info-card h4 { | |
| margin-bottom: 0.5rem; | |
| color: var(--primary); | |
| } | |
| .info-card p { | |
| font-size: 0.9rem; | |
| color: var(--text-muted); | |
| line-height: 1.5; | |
| } | |
| /* Form Styling */ | |
| .survey-form { | |
| background: white; | |
| padding: 2.5rem; | |
| border-radius: 24px; | |
| box-shadow: 0 10px 40px rgba(0, 0, 0, 0.04); | |
| margin-bottom: 3rem; | |
| border: 1px solid #f1f5f9; | |
| } | |
| .form-grid { | |
| display: grid; | |
| grid-template-columns: repeat(2, 1fr); | |
| gap: 1.5rem; | |
| margin-bottom: 2rem; | |
| } | |
| .input-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| } | |
| .input-group.full { | |
| grid-column: span 2; | |
| } | |
| label { | |
| font-weight: 600; | |
| font-size: 0.9rem; | |
| color: #475569; | |
| } | |
| select, | |
| input[type="text"], | |
| input[type="number"], | |
| textarea { | |
| padding: 0.875rem; | |
| border-radius: 12px; | |
| border: 1px solid #e2e8f0; | |
| background: #f8fafc; | |
| font-size: 1rem; | |
| transition: all 0.2s; | |
| } | |
| select:focus, | |
| input:focus, | |
| textarea:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1); | |
| background: white; | |
| } | |
| textarea { | |
| resize: vertical; | |
| min-height: 100px; | |
| } | |
| .btn-submit { | |
| background: linear-gradient(135deg, var(--primary), var(--primary-dark)); | |
| color: white; | |
| padding: 1rem 2rem; | |
| border-radius: 14px; | |
| border: none; | |
| font-weight: 600; | |
| font-size: 1.1rem; | |
| cursor: pointer; | |
| width: 100%; | |
| transition: all 0.3s; | |
| box-shadow: 0 4px 15px rgba(79, 70, 229, 0.3); | |
| } | |
| .btn-submit:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px rgba(79, 70, 229, 0.4); | |
| } | |
| .btn-submit:active { | |
| transform: translateY(0); | |
| } | |
| .btn-submit:disabled { | |
| background: #cbd5e1; | |
| box-shadow: none; | |
| cursor: not-allowed; | |
| } | |
| .btn-reset { | |
| background: white; | |
| color: #64748b; | |
| padding: 1rem 1.5rem; | |
| border-radius: 14px; | |
| border: 1px solid #e2e8f0; | |
| font-weight: 600; | |
| font-size: 1.1rem; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .btn-reset:hover { | |
| background: #f8fafc; | |
| border-color: #cbd5e1; | |
| color: #1e293b; | |
| } | |
| .button-row { | |
| display: flex; | |
| gap: 1rem; | |
| } | |
| /* Results Styling */ | |
| .results-container { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1.5rem; | |
| } | |
| .result-card { | |
| background: white; | |
| padding: 2rem; | |
| border-radius: 20px; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03); | |
| border: 1px solid #f1f5f9; | |
| animation: slideIn 0.5s ease forwards; | |
| opacity: 0; | |
| } | |
| @keyframes slideIn { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .avatar-info { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| margin-bottom: 1.5rem; | |
| padding-bottom: 1rem; | |
| border-bottom: 1px solid #f1f5f9; | |
| } | |
| .avatar-icon { | |
| width: 48px; | |
| height: 48px; | |
| background: #e0e7ff; | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: var(--primary); | |
| font-weight: 700; | |
| } | |
| .avatar-meta { | |
| font-size: 0.85rem; | |
| color: var(--text-muted); | |
| } | |
| .response-text { | |
| line-height: 1.8; | |
| font-size: 1.05rem; | |
| color: #334155; | |
| white-space: pre-wrap; | |
| } | |
| .context-chips { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 0.5rem; | |
| margin-top: 1.5rem; | |
| } | |
| .chip { | |
| padding: 0.25rem 0.75rem; | |
| background: #f1f5f9; | |
| border-radius: 20px; | |
| font-size: 0.75rem; | |
| color: var(--text-muted); | |
| } | |
| .loader { | |
| display: none; | |
| text-align: center; | |
| padding: 2rem; | |
| } | |
| .spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 4px solid #f3f3f3; | |
| border-top: 4px solid var(--primary); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto 1rem; | |
| } | |
| @keyframes spin { | |
| 0% { | |
| transform: rotate(0deg); | |
| } | |
| 100% { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| .notice-label { | |
| font-size: 0.8rem; | |
| color: var(--secondary); | |
| font-weight: 500; | |
| margin-top: 0.5rem; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <aside class="sidebar"> | |
| <div class="logo"> | |
| <span>โจ</span> Issue Survey (Beta) | |
| </div> | |
| <div class="menu-item active" id="menu-actual" onclick="switchMode('actual')"> | |
| <span>๐ค</span> Actual Respondent | |
| </div> | |
| <div class="menu-item" id="menu-synthetic" onclick="switchMode('synthetic')"> | |
| <span>๐</span> Statistical Synthetic | |
| </div> | |
| <div class="api-key-section"> | |
| <label class="api-key-label">Gemini API Key</label> | |
| <div class="api-key-container"> | |
| <input type="password" id="api-key" class="api-key-input" placeholder="์ฌ๊ธฐ์ API ํค ์ ๋ ฅ"> | |
| <span class="toggle-password" id="toggle-key" onclick="toggleKeyVisibility()">๐๏ธ</span> | |
| </div> | |
| <div class="key-guide"> | |
| <p>Gemini API ํค๊ฐ ์์ผ์ ๊ฐ์?</p> | |
| <p><a href="https://aistudio.google.com/app/apikey" target="_blank">Google AI Studio</a>์์ ๋ฌด๋ฃ๋ก ๋ฐ๊ธ๋ฐ์ผ์ค ์ | |
| ์์ต๋๋ค.</p> | |
| </div> | |
| </div> | |
| </aside> | |
| <main class="main-content"> | |
| <div class="header-box"> | |
| <h1 class="title">Persona Issue Survey (Beta version)</h1> | |
| <p class="description">2020๋ 7์(1์ฐจ)๋ถํฐ 2025๋ 9์(164์ฐจ)๊น์ง ์ํ๋ <b>์ ๊ตญ์งํ์กฐ์ฌ(NBS)</b> ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก ํน์ ๊ณ์ธต์ ๋ชฉ์๋ฆฌ๋ฅผ | |
| ์๋ฎฌ๋ ์ด์ ํฉ๋๋ค.</p> | |
| <p style="margin-top: 0.5rem; font-size: 0.85rem; color: #f43f5e; font-weight: 500;">โ ๏ธ ๋ณธ ์์คํ ์ ์ฌํ์ ์ด์ ๋ถ์์ฉ์ด๋ฉฐ | |
| ์ธ๋ฌผ ํ๊ฐ์๋ ๋ถ์ ํฉํฉ๋๋ค. ์๋ฎฌ๋ ์ด์ ๊ฒฐ๊ณผ๋ ์ฐธ๊ณ ์ฉ์ผ๋ก๋ง ์ฌ์ฉํ์ญ์์ค.</p> | |
| <div class="system-info"> | |
| <div class="info-card"> | |
| <h4>๐ค Actual vs ๐ Synthetic</h4> | |
| <p><b>Actual</b>: ์ค์ ๊ฐ๋ณ ์๋ต์์ ์ด๋ ฅ๊ณผ ๊ฐ์น๊ด์ ์ถ์ ํ์ฌ ์์ํ ๊ฐ์ธ์ ๋ชฉ์๋ฆฌ๋ฅผ ์๋๋ฆฌ์คํ ํฉ๋๋ค. (์ฌ์ธต ์ธํฐ๋ทฐ ๋์ฉ)<br> | |
| <b>Synthetic</b>: ๊ทธ๋ฃน ์ ์ฒด์ ํต๊ณ์ ๋ต๋ณ ๋ถํฌ๋ฅผ ์์ฝํ์ฌ ์ง๋จ์ ํ๊ท ์ ์ด๊ณ ์ ํ์ ์ธ ๊ฒฝํฅ์ฑ์ ๋์ถํฉ๋๋ค. (์ฌ๋ก ์งํ ๋ถ์์ฉ) | |
| </p> | |
| </div> | |
| <div class="info-card"> | |
| <h4>๐ ๊ตฌํ ๊ณผ์ </h4> | |
| <p>163ํ์ NBS ์ค๋ฌธ(16๋ง๋ช )์ ํตํฉ ์ ์ฒ๋ฆฌํ๊ณ , SBERT ์๋ฏธ๋ก ์ ๊ฒ์์ ํตํด ์๋ฐํ์ '๊ธฐ์ต'์ AI์๊ฒ ์ฃผ์ ํฉ๋๋ค.</p> | |
| </div> | |
| <div class="info-card"> | |
| <h4>๐ก ์ฌ์ฉ๋ฒ</h4> | |
| <p>1. <b>API ํค๋ฅผ ๋จผ์ ์ ๋ ฅ</b>ํ์ธ์. 2. ์ธ๊ตฌํต๊ณ ์กฐ๊ฑด์ ์ค์ ํ๊ณ ์ด์ ์ง๋ฌธ์ ์ ๋ ฅ(์ต์ ์๊ธ ์ฌ๋ก ๋ฑ ๊ตฌ์ฒด์ ๋ฐฐ๊ฒฝ ๊ถ์ฅ) ํ ์์ํ์ธ์.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="survey-form"> | |
| <div class="form-grid"> | |
| <div class="input-group"> | |
| <label>์ง์ญ</label> | |
| <select id="region"> | |
| <option value="">์ ์ฒด (์ ๊ตญ)</option> | |
| <option value="์์ธ">์์ธ</option> | |
| <option value="๊ฒฝ๊ธฐ">๊ฒฝ๊ธฐ</option> | |
| <option value="์ธ์ฒ">์ธ์ฒ</option> | |
| <option value="๋ถ์ฐ">๋ถ์ฐ</option> | |
| <option value="๋๊ตฌ">๋๊ตฌ</option> | |
| <option value="๊ด์ฃผ">๊ด์ฃผ</option> | |
| <option value="๋์ ">๋์ </option> | |
| <option value="์ธ์ฐ">์ธ์ฐ</option> | |
| <option value="์ธ์ข ">์ธ์ข </option> | |
| <option value="์ถฉ๋ถ">์ถฉ๋ถ</option> | |
| <option value="์ถฉ๋จ">์ถฉ๋จ</option> | |
| <option value="์ ๋ถ">์ ๋ถ</option> | |
| <option value="์ ๋จ">์ ๋จ</option> | |
| <option value="๊ฒฝ๋ถ">๊ฒฝ๋ถ</option> | |
| <option value="๊ฒฝ๋จ">๊ฒฝ๋จ</option> | |
| <option value="์ ์ฃผ">์ ์ฃผ</option> | |
| <option value="๊ฐ์">๊ฐ์</option> | |
| </select> | |
| </div> | |
| <div class="input-group"> | |
| <label>์ฑ๋ณ</label> | |
| <select id="gender"> | |
| <option value="">์ ์ฒด (์ฑ๋ณ๋ฌด๊ด)</option> | |
| <option value="๋จ์">๋จ์</option> | |
| <option value="์ฌ์">์ฌ์</option> | |
| </select> | |
| </div> | |
| <div class="input-group"> | |
| <label>์ฐ๋ น๋</label> | |
| <select id="age"> | |
| <option value="">์ ์ฒด (์ฐ๋ น๋ฌด๊ด)</option> | |
| <option value="18~29">20๋ (18~29)</option> | |
| <option value="30~39">30๋ (30~39)</option> | |
| <option value="40~49">40๋ (40~49)</option> | |
| <option value="50~59">50๋ (50~59)</option> | |
| <option value="60~69">60๋ (60~69)</option> | |
| <option value="70~99">70๋ ์ด์</option> | |
| </select> | |
| </div> | |
| <div class="input-group"> | |
| <label>๊ตฌ์ฒด์ ์ฐ๋ น (์ ํ)</label> | |
| <input type="number" id="age-specific" placeholder="์: 32"> | |
| </div> | |
| <div class="input-group"> | |
| <label>์ง์ </label> | |
| <select id="job"> | |
| <option value="">์ ์ฒด (์ง์ ๋ฌด๊ด)</option> | |
| <option value="ํ์">ํ์</option> | |
| <option value="์ฌ๋ฌด/๊ธฐ์ ์ง">์ฌ๋ฌด/๊ธฐ์ ์ง</option> | |
| <option value="์์์ ">์์์ </option> | |
| <option value="์ฃผ๋ถ">์ฃผ๋ถ</option> | |
| <option value="๊ฒฝ์/๊ด๋ฆฌ/์ ๋ฌธ์ง">๊ฒฝ์/๊ด๋ฆฌ/์ ๋ฌธ์ง</option> | |
| <option value="์์ฐ/๊ธฐ๋ฅ/๋ ธ๋ฌด์ง">์์ฐ/๊ธฐ๋ฅ/๋ ธ๋ฌด์ง</option> | |
| <option value="๋/๋ฆผ/์์ฐ์ ">๋/๋ฆผ/์์ฐ์ </option> | |
| <option value="๋ฌด์ง/ํด์ง/๊ธฐํ">๋ฌด์ง/ํด์ง/๊ธฐํ</option> | |
| </select> | |
| </div> | |
| <div class="input-group full"> | |
| <label>๋ถ์ ์ด์ (์ง๋ฌธ)</label> | |
| <textarea id="question" | |
| placeholder="์: ์ต๊ทผ ๊ณ ๋ฌผ๊ฐ ์ํฉ์์ ์๋ฏผ ๊ฒฝ์ ์์ ์ ์ํด ์ต์ ์๊ธ์ ์ธ์ํด์ผ ํ๋ค๋ ์ฃผ์ฅ์ ์ผ๋ง๋ ๋์ํ์ญ๋๊น? (1: ์ ํ ๋์ ์ํจ ~ 5: ๋งค์ฐ ๋์)"></textarea> | |
| </div> | |
| <div class="input-group" id="sample-group"> | |
| <label>์์ฑํ ์๋ฐํ(์๋ต) ์</label> | |
| <input type="number" id="sample" value="3" min="1" max="10"> | |
| <div id="sample-notice" class="notice-label">Actual ๋ชจ๋๋ ์ต๋ 10๋ช ์ผ๋ก ์ ํ๋ฉ๋๋ค.</div> | |
| </div> | |
| </div> | |
| <div class="button-row"> | |
| <button class="btn-submit" id="btn-submit" onclick="runSimulation()">์๋ฎฌ๋ ์ด์ ์์</button> | |
| <button class="btn-reset" id="btn-reset" onclick="resetForm()">์ด๊ธฐํ</button> | |
| </div> | |
| </div> | |
| <div class="loader" id="loader"> | |
| <div class="spinner"></div> | |
| <p>๋ฐ์ดํฐ์์ ํ๋ฅด์๋๋ฅผ ์ถ์ถํ๊ณ AI ๋ต๋ณ์ ์์ฑํ๊ณ ์์ต๋๋ค...</p> | |
| </div> | |
| <div class="results-container" id="results"> | |
| <!-- Results will be injected here --> | |
| </div> | |
| </main> | |
| <script> | |
| let currentMode = 'actual'; | |
| function toggleKeyVisibility() { | |
| const keyInput = document.getElementById('api-key'); | |
| const toggleIcon = document.getElementById('toggle-key'); | |
| if (keyInput.type === 'password') { | |
| keyInput.type = 'text'; | |
| toggleIcon.innerText = '๐'; | |
| } else { | |
| keyInput.type = 'password'; | |
| toggleIcon.innerText = '๐๏ธ'; | |
| } | |
| } | |
| function switchMode(mode) { | |
| currentMode = mode; | |
| document.querySelectorAll('.menu-item').forEach(el => el.classList.remove('active')); | |
| document.getElementById(`menu-${mode}`).classList.add('active'); | |
| const sampleGroup = document.getElementById('sample-group'); | |
| const sampleInput = document.getElementById('sample'); | |
| const notice = document.getElementById('sample-notice'); | |
| if (mode === 'actual') { | |
| sampleGroup.style.display = "flex"; | |
| sampleInput.max = 10; | |
| if (sampleInput.value > 10) sampleInput.value = 10; | |
| notice.innerText = "Actual ๋ชจ๋๋ ์ต๋ 10๋ช ์ผ๋ก ์ ํ๋ฉ๋๋ค. (๋๊ท๋ชจ ๋ถ์์ Synthetic ์ถ์ฒ)"; | |
| notice.style.color = "#ec4899"; | |
| } else { | |
| // Synthetic is always 1 summarized avatar per request in current implementation | |
| sampleGroup.style.display = "none"; | |
| sampleInput.value = 1; | |
| } | |
| } | |
| function resetForm() { | |
| document.getElementById('question').value = ""; | |
| document.getElementById('region').value = ""; | |
| document.getElementById('gender').value = ""; | |
| document.getElementById('age').value = ""; | |
| document.getElementById('age-specific').value = ""; | |
| document.getElementById('job').value = ""; | |
| document.getElementById('sample').value = (currentMode === 'actual' ? "3" : "1"); | |
| document.getElementById('results').innerHTML = ""; | |
| } | |
| async function runSimulation() { | |
| const apiKey = document.getElementById('api-key').value.trim(); | |
| const question = document.getElementById('question').value.trim(); | |
| const region = document.getElementById('region').value; | |
| const gender = document.getElementById('gender').value; | |
| // Priority: specific age > age range | |
| const ageSpecific = document.getElementById('age-specific').value.trim(); | |
| const ageRange = document.getElementById('age').value; | |
| const age = ageSpecific || ageRange; | |
| const job = document.getElementById('job').value; | |
| const sample = parseInt(document.getElementById('sample').value); | |
| if (!apiKey) { | |
| alert("์์ํ๋ ค๋ฉด Gemini API ํค๋ฅผ ์ ๋ ฅํด ์ฃผ์ธ์ (์ฌ์ด๋๋ฐ ํ๋จ)."); | |
| return; | |
| } | |
| if (!question) { | |
| alert("์ง๋ฌธ์ ์ ๋ ฅํด ์ฃผ์ธ์."); | |
| return; | |
| } | |
| const resultsDiv = document.getElementById('results'); | |
| const loader = document.getElementById('loader'); | |
| const btn = document.getElementById('btn-submit'); | |
| resultsDiv.innerHTML = ""; | |
| loader.style.display = "block"; | |
| btn.disabled = true; | |
| const payload = { | |
| question, | |
| region: region || null, | |
| gender: gender || null, | |
| age: age || null, | |
| job: job || null, | |
| sample | |
| }; | |
| const endpoint = `/simulate/${currentMode}`; | |
| try { | |
| const response = await fetch(endpoint, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'X-API-Key': apiKey || "" | |
| }, | |
| body: JSON.stringify(payload) | |
| }); | |
| if (!response.ok) { | |
| const err = await response.json(); | |
| throw new Error(err.detail || "์๋ฎฌ๋ ์ด์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."); | |
| } | |
| const data = await response.json(); | |
| renderResults(data); | |
| } catch (error) { | |
| alert(error.message); | |
| } finally { | |
| loader.style.display = "none"; | |
| btn.disabled = false; | |
| } | |
| } | |
| function renderResults(data) { | |
| const resultsDiv = document.getElementById('results'); | |
| if (currentMode === 'synthetic') { | |
| resultsDiv.innerHTML = `<h2 style="margin-bottom: 1.5rem;">์ง๋จ ๋ํ(Representative) ๋ถ์ ๊ฒฐ๊ณผ</h2>`; | |
| } else { | |
| resultsDiv.innerHTML = `<h2 style="margin-bottom: 1.5rem;">์ด ${data.length}๋ช ์ ๋ต๋ณ ๊ฒฐ๊ณผ</h2>`; | |
| } | |
| data.forEach((item, index) => { | |
| const card = document.createElement('div'); | |
| card.className = "result-card"; | |
| card.style.animationDelay = `${index * 0.1}s`; | |
| const isSynthetic = !item.respondent_id; | |
| const idLabel = item.respondent_id ? `ID: ${item.respondent_id}` : `Group Representative (Representative)`; | |
| const roundLabel = item.survey_round ? ` | Survey Round: ${item.survey_round}` : ""; | |
| let demographicsStr = ""; | |
| if (item.demographics) { | |
| const d = item.demographics; | |
| demographicsStr = `${d.region || '์ ๊ตญ'} / ${d.age || '์ฐ๋ น๋'} / ${d.gender || '์ฑ๋ณ'} / ${d.job || '์ง์ '}`; | |
| } | |
| const contextHtml = (item.referenced_context || item.referenced_stat_context || []) | |
| .map(c => `<span class="chip">${c.split('\n')[0].replace('Q: ', '')}</span>`) | |
| .join(''); | |
| card.innerHTML = ` | |
| <div class="avatar-info"> | |
| <div class="avatar-icon">${item.respondent_id ? 'P' : 'S'}</div> | |
| <div> | |
| <div style="font-weight: 700; color: #0f172a;">${idLabel}${roundLabel}</div> | |
| <div class="avatar-meta">${demographicsStr}</div> | |
| </div> | |
| </div> | |
| <div class="response-text">${item.response}</div> | |
| <div class="api-key-label" style="margin-top: 1.5rem; font-size: 0.75rem; color: #94a3b8;">์ฐธ์กฐ๋ ๊ณผ๊ฑฐ ๋ฐ์ดํฐ ํ๋:</div> | |
| <div class="context-chips"> | |
| ${contextHtml} | |
| </div> | |
| `; | |
| resultsDiv.appendChild(card); | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> |