v2.0
This commit is contained in:
+176
-150
@@ -2154,9 +2154,9 @@
|
||||
</div>
|
||||
|
||||
<div class="mode-toggle">
|
||||
<button class="mode-btn active" id="btn-chat" onclick="setMode('chat')">Chat</button>
|
||||
<button class="mode-btn" id="btn-bgtasks" onclick="setMode('bgtasks')">Tasks</button>
|
||||
<button class="mode-btn" id="btn-schedule" onclick="setMode('schedule')">Schedule</button>
|
||||
<button class="mode-btn active" id="btn-chat" onclick="setMode('chat')">채팅</button>
|
||||
<button class="mode-btn" id="btn-bgtasks" onclick="setMode('bgtasks')">작업</button>
|
||||
<button class="mode-btn" id="btn-schedule" onclick="setMode('schedule')">일정</button>
|
||||
</div>
|
||||
|
||||
<div class="status-bar">
|
||||
@@ -2165,8 +2165,8 @@
|
||||
class="icon-btn"
|
||||
data-theme-state="light"
|
||||
onclick="toggleTheme()"
|
||||
title="Switch to dark mode"
|
||||
aria-label="Toggle dark mode"
|
||||
title="다크 모드로 전환"
|
||||
aria-label="다크 모드 전환"
|
||||
>
|
||||
<svg class="theme-icon sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||||
<circle cx="12" cy="12" r="4"></circle>
|
||||
@@ -2180,9 +2180,18 @@
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
||||
설정
|
||||
</button>
|
||||
<!-- User info + logout (hidden until auth check confirms logged-in user) -->
|
||||
<div id="user-info-pill" style="display:none;align-items:center;gap:6px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-sm);padding:4px 10px 4px 8px;font-size:12px;color:var(--muted)">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/></svg>
|
||||
<span id="user-info-name" style="font-weight:600;color:var(--text);max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"></span>
|
||||
<button onclick="logout()" title="로그아웃" style="display:flex;align-items:center;gap:4px;background:none;border:none;cursor:pointer;padding:2px 4px;border-radius:5px;font-size:11px;color:var(--muted)" onmouseover="this.style.background='var(--line)';this.style.color='var(--text)'" onmouseout="this.style.background='none';this.style.color='var(--muted)'">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
||||
로그아웃
|
||||
</button>
|
||||
</div>
|
||||
<div class="status-pill">
|
||||
<div class="dot" id="ollama-dot"></div>
|
||||
<span id="ollama-status">Checking...</span>
|
||||
<span id="ollama-status">확인 중...</span>
|
||||
</div>
|
||||
<div class="status-pill" id="model-pill" onclick="openSettings('models')" title="모델 변경하려면 클릭" style="cursor:pointer">
|
||||
<span id="current-provider-badge" style="font-size:10px;font-weight:700;color:var(--muted);margin-right:4px"></span><span id="current-model">-</span>
|
||||
@@ -2196,8 +2205,8 @@
|
||||
<!-- SIDEBAR -->
|
||||
<aside>
|
||||
<div id="sidebar-tabs">
|
||||
<button class="sidebar-tab active" id="tab-jobs" onclick="setSidebarTab('jobs')">Sessions</button>
|
||||
<button class="sidebar-tab" id="tab-skills" onclick="setSidebarTab('skills')">🧩 Skills</button>
|
||||
<button class="sidebar-tab active" id="tab-jobs" onclick="setSidebarTab('jobs')">세션</button>
|
||||
<button class="sidebar-tab" id="tab-skills" onclick="setSidebarTab('skills')">🧩 스킬</button>
|
||||
</div>
|
||||
|
||||
<!-- SESSIONS TAB -->
|
||||
@@ -2205,12 +2214,12 @@
|
||||
<div class="sidebar-section" style="border-top:none">
|
||||
<span></span>
|
||||
<div style="display:flex;align-items:center;gap:8px">
|
||||
<button class="sidebar-action" onclick="newChatSession()">+ New Chat</button>
|
||||
<button class="sidebar-action" id="sessions-edit-btn" onclick="toggleSessionsEditMode()">Edit</button>
|
||||
<button class="sidebar-action" onclick="newChatSession()">+ 새 채팅</button>
|
||||
<button class="sidebar-action" id="sessions-edit-btn" onclick="toggleSessionsEditMode()">편집</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="jobs-list">
|
||||
<div class="empty-state">No chats yet.<br>Start a new conversation.</div>
|
||||
<div class="empty-state">아직 대화가 없습니다.<br>새 대화를 시작해 보세요.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2221,7 +2230,7 @@
|
||||
<button class="sidebar-action" onclick="openCreateSkillModal()">+ Create</button>
|
||||
</div>
|
||||
<div id="skills-list" style="flex:1;min-height:0;overflow-y:auto;padding:10px">
|
||||
<div class="empty-state" style="padding:16px">Loading skills...</div>
|
||||
<div class="empty-state" style="padding:16px">스킬 로딩 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -2234,31 +2243,31 @@
|
||||
<div class="chat-welcome" id="chat-welcome">
|
||||
<div class="chat-welcome-icon"><img src="cherry_logo.png" alt="🍒"></div>
|
||||
<h2>CherryClaw</h2>
|
||||
<p>Chat directly with your local model. No API keys, no cloud — just you and the model.</p>
|
||||
<div class="hint">Switch to Agent mode for multi-step agentic tasks with tools.</div>
|
||||
<p>로컬 모델과 직접 대화하세요. API 키도, 클라우드도 없이 — 오직 나와 모델만.</p>
|
||||
<div class="hint">멀티스텝 에이전트 작업은 에이전트 모드로 전환하세요.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-input-area">
|
||||
<div class="agent-toggle">
|
||||
<span class="agent-toggle-label">Mode:</span>
|
||||
<label class="toggle-switch" title="Toggle agent mode (gives AI access to tools)">
|
||||
<span class="agent-toggle-label">모드:</span>
|
||||
<label class="toggle-switch" title="에이전트 모드 전환 (AI에 도구 접근 허용)">
|
||||
<input type="checkbox" id="agent-mode-toggle" onchange="updateAgentMode()">
|
||||
<span class="toggle-track"></span>
|
||||
</label>
|
||||
<span class="agent-toggle-label" id="agent-mode-label">Chat</span>
|
||||
<span class="agent-toggle-label" id="agent-mode-label">채팅</span>
|
||||
<span class="agent-mode-badge" id="agent-mode-badge">AGENT</span>
|
||||
<div class="agent-toggle-right" style="display:flex;align-items:center;gap:6px">
|
||||
<button class="quick-mode-btn" id="context-pin-btn" onclick="toggleContextPinMode()" title="Pin messages to context">
|
||||
<button class="quick-mode-btn" id="context-pin-btn" onclick="toggleContextPinMode()" title="메시지를 컨텍스트에 고정">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></svg>
|
||||
<span id="context-pin-label">Context</span>
|
||||
<span id="context-pin-label">컨텍스트</span>
|
||||
</button>
|
||||
<div class="quick-mode-popover" id="context-pin-popover" style="width:280px">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
|
||||
<div class="quick-mode-title" style="margin:0">Pin Messages to Context</div>
|
||||
<div class="quick-mode-title" style="margin:0">컨텍스트에 메시지 고정</div>
|
||||
<span id="context-pin-count" style="font-size:11px;color:var(--muted);font-weight:700">0/3 selected</span>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--muted);margin-bottom:8px;line-height:1.5">Click messages in chat to pin them. Pinned messages are re-injected into context with each request.</div>
|
||||
<div style="font-size:11px;color:var(--muted);margin-bottom:8px;line-height:1.5">채팅에서 메시지를 클릭하여 고정하세요. 고정된 메시지는 매 요청마다 컨텍스트에 재주입됩니다.</div>
|
||||
<div id="context-pin-list" style="max-height:200px;overflow-y:auto;margin-bottom:8px"></div>
|
||||
<div style="display:flex;justify-content:flex-end;gap:6px">
|
||||
<button class="skill-mini-btn" onclick="cancelContextPin()" style="display:flex;align-items:center;gap:4px">
|
||||
@@ -2271,7 +2280,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="quick-mode-btn" id="quick-mode-btn" onclick="toggleQuickModePopover()" title="Web Search / Thinking settings">
|
||||
<button class="quick-mode-btn" id="quick-mode-btn" onclick="toggleQuickModePopover()" title="웹 검색 / 사고 설정">
|
||||
<span id="quick-mode-label">Verified</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
@@ -2280,25 +2289,25 @@
|
||||
<button class="quick-mode-opt" id="rigor-fast" onclick="setQuickSearchRigor('fast')">빠름</button>
|
||||
<button class="quick-mode-opt" id="rigor-verified" onclick="setQuickSearchRigor('verified')">검증됨</button>
|
||||
<button class="quick-mode-opt" id="rigor-strict" onclick="setQuickSearchRigor('strict')">엄격</button>
|
||||
<div class="quick-mode-title" style="margin-top:8px">Thinking Effort</div>
|
||||
<button class="quick-mode-opt" id="think-standard" onclick="setQuickThinkingEffort('standard')">Standard</button>
|
||||
<button class="quick-mode-opt" id="think-extended" onclick="setQuickThinkingEffort('extended')">Extended</button>
|
||||
<div class="quick-mode-title" style="margin-top:8px">사고 노력</div>
|
||||
<button class="quick-mode-opt" id="think-standard" onclick="setQuickThinkingEffort('standard')">기본</button>
|
||||
<button class="quick-mode-opt" id="think-extended" onclick="setQuickThinkingEffort('extended')">확장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="queued-prompts-panel" id="queued-prompts-panel">
|
||||
<div class="queued-prompts-head">
|
||||
<span id="queued-prompts-title">Queued prompts</span>
|
||||
<button class="queued-clear-btn" onclick="clearQueuedPrompts()">Clear</button>
|
||||
<span id="queued-prompts-title">대기 중인 프롬프트</span>
|
||||
<button class="queued-clear-btn" onclick="clearQueuedPrompts()">지우기</button>
|
||||
</div>
|
||||
<div class="queued-prompts-list" id="queued-prompts-list"></div>
|
||||
</div>
|
||||
<div class="chat-input-row">
|
||||
<input type="file" id="image-upload" accept="image/*" style="display:none" onchange="handleImageUpload(this)" />
|
||||
<button class="send-btn" onclick="document.getElementById('image-upload').click()" title="Attach image" style="font-size:18px;width:44px;height:44px">📎</button>
|
||||
<button class="send-btn" onclick="document.getElementById('image-upload').click()" title="이미지 첨부" style="font-size:18px;width:44px;height:44px">📎</button>
|
||||
<textarea
|
||||
id="chat-input"
|
||||
placeholder="Type a message... (Enter to send, Shift+Enter for newline)"
|
||||
placeholder="메시지를 입력하세요... (Enter로 전송, Shift+Enter로 줄바꿈)"
|
||||
rows="1"
|
||||
autocomplete="off"
|
||||
></textarea>
|
||||
@@ -2308,7 +2317,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="chat-hint">
|
||||
Chatting with <span id="chat-model-name">your model</span> via <span id="chat-provider-name">CherryClaw</span>.
|
||||
<span id="chat-model-name">모델</span>과 대화 중 — <span id="chat-provider-name">CherryClaw</span> 경유.
|
||||
<span id="queued-prompts-indicator" style="display:none;margin-left:8px;color:var(--brand);font-weight:700"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2322,7 +2331,7 @@
|
||||
<div id="bgtasks-view" style="display:none;flex-direction:column;gap:0;position:absolute;top:90px;bottom:12px;left:12px;right:12px;background:var(--bg);padding:16px;overflow:hidden;border:1px solid var(--line);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);z-index:5;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;flex-shrink:0">
|
||||
<div style="display:flex;align-items:center;gap:12px">
|
||||
<span style="font-size:16px;font-weight:800;letter-spacing:-0.01em">Background Tasks</span>
|
||||
<span style="font-size:16px;font-weight:800;letter-spacing:-0.01em">백그라운드 작업</span>
|
||||
<span id="bgt-count" style="font-size:11px;background:#eaf2ff;color:#0d4faf;border-radius:999px;padding:2px 10px;font-weight:700">0 tasks</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
@@ -2330,9 +2339,9 @@
|
||||
id="bgt-heartbeat-label"
|
||||
onclick="openBgtHeartbeatModal()"
|
||||
style="border:1px solid var(--line);background:var(--panel);color:var(--muted);border-radius:8px;padding:5px 10px;font-size:11px;font-weight:600;cursor:pointer"
|
||||
title="Task heartbeat settings"
|
||||
title="작업 하트비트 설정"
|
||||
>? heartbeat: 10min</button>
|
||||
<button onclick="refreshBgTasks()" style="border:1px solid var(--line);background:var(--panel);color:var(--muted);border-radius:8px;padding:5px 12px;font-size:12px;font-weight:600;cursor:pointer">Refresh</button>
|
||||
<button onclick="refreshBgTasks()" style="border:1px solid var(--line);background:var(--panel);color:var(--muted);border-radius:8px;padding:5px 12px;font-size:12px;font-weight:600;cursor:pointer">새로고침</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2344,9 +2353,9 @@
|
||||
<!-- Task detail side panel -->
|
||||
<div id="bgt-panel" style="display:none;position:absolute;top:0;right:0;width:480px;bottom:0;background:var(--panel);border-left:1px solid var(--line);flex-direction:column;overflow:hidden;z-index:10">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line);flex-shrink:0">
|
||||
<span id="bgt-panel-title" style="font-size:14px;font-weight:800">Task Details</span>
|
||||
<span id="bgt-panel-title" style="font-size:14px;font-weight:800">작업 상세</span>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button id="bgt-panel-pause" onclick="bgtPauseResume()" style="border:1px solid var(--line);background:var(--panel-2);border-radius:8px;padding:4px 12px;font-size:12px;font-weight:600;cursor:pointer;color:var(--muted)">Pause</button>
|
||||
<button id="bgt-panel-pause" onclick="bgtPauseResume()" style="border:1px solid var(--line);background:var(--panel-2);border-radius:8px;padding:4px 12px;font-size:12px;font-weight:600;cursor:pointer;color:var(--muted)">일시정지</button>
|
||||
<button onclick="closeBgtPanel()" style="border:0;background:none;font-size:18px;cursor:pointer;color:var(--muted);line-height:1">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2355,10 +2364,10 @@
|
||||
</div>
|
||||
<!-- Persistent task chat input — always visible regardless of task status -->
|
||||
<div id="bgt-chat-bar" style="flex-shrink:0;border-top:1px solid var(--line);padding:10px 12px;background:var(--panel);display:flex;flex-direction:column;gap:6px">
|
||||
<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;color:var(--muted)">Chat with agent</div>
|
||||
<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;color:var(--muted)">에이전트와 채팅</div>
|
||||
<div style="display:flex;gap:8px;align-items:flex-end">
|
||||
<textarea id="bgt-chat-input" rows="2" placeholder="Send a message to the task agent... (Shift+Enter for newline)" style="flex:1;resize:none;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:12px;font-family:inherit;background:var(--panel-2);color:var(--text);outline:none" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();bgtChatSend();}"></textarea>
|
||||
<button onclick="bgtChatSend()" style="background:var(--brand);color:#fff;border:none;border-radius:8px;padding:8px 14px;font-size:12px;font-weight:700;cursor:pointer;flex-shrink:0;height:36px">Send</button>
|
||||
<textarea id="bgt-chat-input" rows="2" placeholder="에이전트에게 메시지 전송... (Shift+Enter로 줄바꿈)" style="flex:1;resize:none;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:12px;font-family:inherit;background:var(--panel-2);color:var(--text);outline:none" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();bgtChatSend();}"></textarea>
|
||||
<button onclick="bgtChatSend()" style="background:var(--brand);color:#fff;border:none;border-radius:8px;padding:8px 14px;font-size:12px;font-weight:700;cursor:pointer;flex-shrink:0;height:36px">전송</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2368,18 +2377,18 @@
|
||||
<div id="schedule-view" style="display:none;flex-direction:column;gap:0;position:absolute;top:90px;bottom:12px;left:12px;right:12px;background:var(--bg);padding:16px;overflow:hidden;border:1px solid var(--line);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);z-index:5;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;flex-shrink:0">
|
||||
<div style="display:flex;align-items:center;gap:12px">
|
||||
<span style="font-size:16px;font-weight:800;letter-spacing:-0.01em">Schedules</span>
|
||||
<span style="font-size:16px;font-weight:800;letter-spacing:-0.01em">일정</span>
|
||||
<span id="schedule-count" style="font-size:11px;background:#eaf2ff;color:#0d4faf;border-radius:999px;padding:2px 10px;font-weight:700">0 schedules</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<button onclick="openScheduleCreateModal()" style="border:1px solid var(--brand);background:var(--brand);color:white;border-radius:8px;padding:6px 14px;font-size:12px;font-weight:600;cursor:pointer">+ New Schedule</button>
|
||||
<button onclick="refreshSchedules()" style="border:1px solid var(--line);background:var(--panel);color:var(--muted);border-radius:8px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer">Refresh</button>
|
||||
<button onclick="openScheduleCreateModal()" style="border:1px solid var(--brand);background:var(--brand);color:white;border-radius:8px;padding:6px 14px;font-size:12px;font-weight:600;cursor:pointer">+ 새 일정</button>
|
||||
<button onclick="refreshSchedules()" style="border:1px solid var(--line);background:var(--panel);color:var(--muted);border-radius:8px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer">새로고침</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schedule list -->
|
||||
<div id="schedule-list" style="flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:10px;padding-right:8px">
|
||||
<div class="empty-state" style="text-align:center;color:var(--muted);padding:40px 20px">No schedules yet. <strong>+ New Schedule</strong> to get started.</div>
|
||||
<div class="empty-state" style="text-align:center;color:var(--muted);padding:40px 20px">No schedules yet. <strong>+ 새 일정</strong> to get started.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2387,24 +2396,24 @@
|
||||
<div id="schedule-modal" style="display:none;position:fixed;inset:0;background:rgba(10,20,40,0.35);z-index:9999;align-items:center;justify-content:center;padding:18px">
|
||||
<div style="width:min(480px,96vw);max-height:90vh;overflow:auto;background:var(--panel);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow-md)">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line);flex-shrink:0">
|
||||
<div id="schedule-modal-title" style="font-weight:800">Create Schedule</div>
|
||||
<div id="schedule-modal-title" style="font-weight:800">일정 생성</div>
|
||||
<button onclick="closeScheduleModal()" style="border:0;background:none;font-size:18px;cursor:pointer;color:var(--muted);line-height:1">✕</button>
|
||||
</div>
|
||||
<div style="padding:16px;display:flex;flex-direction:column;gap:14px">
|
||||
<div>
|
||||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">Name</label>
|
||||
<input id="schedule-name" type="text" placeholder="e.g., Daily standup" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2)" />
|
||||
<input id="schedule-name" type="text" placeholder="예: 매일 스탠드업" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2)" />
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">Schedule Pattern</label>
|
||||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">일정 패턴</label>
|
||||
<div style="display:flex;gap:8px;margin-bottom:8px">
|
||||
<input id="schedule-pattern" type="text" placeholder="e.g., daily at 09:00 or '0 9 * * *' cron" style="flex:1;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2)" />
|
||||
<button onclick="parseSchedulePattern()" style="border:1px solid var(--brand);background:var(--brand);color:white;border-radius:8px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap">Parse</button>
|
||||
<input id="schedule-pattern" type="text" placeholder="예: daily at 09:00 또는 '0 9 * * *' cron" style="flex:1;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2)" />
|
||||
<button onclick="parseSchedulePattern()" style="border:1px solid var(--brand);background:var(--brand);color:white;border-radius:8px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap">파싱</button>
|
||||
</div>
|
||||
<div id="schedule-pattern-preview" style="font-size:11px;color:var(--muted);padding:6px 8px;background:var(--panel-2);border-radius:6px;display:none"></div>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">Timezone</label>
|
||||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">시간대</label>
|
||||
<select id="schedule-timezone" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2)">
|
||||
<option value="UTC">UTC</option>
|
||||
<option value="America/New_York">America/New_York</option>
|
||||
@@ -2417,21 +2426,21 @@
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">Prompt / Action</label>
|
||||
<textarea id="schedule-prompt" placeholder="What should happen when this schedule runs?" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2);resize:vertical;min-height:80px;font-family:'IBM Plex Mono',monospace"></textarea>
|
||||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">프롬프트 / 작업</label>
|
||||
<textarea id="schedule-prompt" placeholder="이 일정이 실행될 때 수행할 작업은 무엇인가요?" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2);resize:vertical;min-height:80px;font-family:'IBM Plex Mono',monospace"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">Delivery Channel</label>
|
||||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">전송 채널</label>
|
||||
<select id="schedule-channel" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2)">
|
||||
<option value="web">Web (notification)</option>
|
||||
<option value="telegram">Telegram (coming soon)</option>
|
||||
<option value="discord">Discord (coming soon)</option>
|
||||
<option value="web">웹 (알림)</option>
|
||||
<option value="telegram">텔레그램 (준비 중)</option>
|
||||
<option value="discord">디스코드 (준비 중)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--line);flex-shrink:0">
|
||||
<button class="btn btn-sm" style="background:var(--panel-2);color:var(--muted);border:1px solid var(--line)" onclick="closeScheduleModal()">취소</button>
|
||||
<button class="btn btn-sm" id="schedule-save-btn" onclick="saveSchedule()">Create Schedule</button>
|
||||
<button class="btn btn-sm" id="schedule-save-btn" onclick="saveSchedule()">일정 생성</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2440,7 +2449,7 @@
|
||||
<div id="bgt-heartbeat-modal" style="display:none;position:fixed;inset:0;background:rgba(10,20,40,0.35);z-index:9999;align-items:center;justify-content:center;padding:18px">
|
||||
<div style="width:min(420px,96vw);max-height:90vh;overflow:auto;background:var(--panel);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow-md)">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line)">
|
||||
<div style="font-weight:800">Task Heartbeat</div>
|
||||
<div style="font-weight:800">작업 하트비트</div>
|
||||
<button class="btn btn-sm" style="background:var(--panel-2);color:var(--muted);border:1px solid var(--line)" onclick="closeBgtHeartbeatModal()">닫기</button>
|
||||
</div>
|
||||
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:12px">
|
||||
@@ -2465,11 +2474,11 @@
|
||||
<!-- RIGHT PANEL -->
|
||||
<div class="right-panel" id="right-panel" style="position:relative">
|
||||
<!-- Resize handle on left edge -->
|
||||
<div id="right-panel-resize-handle" style="position:absolute;left:0;top:0;bottom:0;width:5px;cursor:col-resize;z-index:200;background:transparent;transition:background 0.15s ease" title="Drag to resize"></div>
|
||||
<div id="right-panel-resize-handle" style="position:absolute;left:0;top:0;bottom:0;width:5px;cursor:col-resize;z-index:200;background:transparent;transition:background 0.15s ease" title="드래그하여 크기 조절"></div>
|
||||
<!-- Right panel topbar with canvas toggle -->
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--line);flex-shrink:0;background:var(--panel);min-height:38px">
|
||||
<span style="font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:0.06em">Context</span>
|
||||
<button id="canvas-toggle-btn" onclick="toggleCanvas()" title="Toggle Canvas" style="display:flex;align-items:center;gap:6px;border:1px solid var(--line);background:var(--panel-2);color:var(--muted);border-radius:8px;padding:5px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:'Manrope',sans-serif;transition:all 0.15s ease">
|
||||
<span style="font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:0.06em">컨텍스트</span>
|
||||
<button id="canvas-toggle-btn" onclick="toggleCanvas()" title="캔버스 전환" style="display:flex;align-items:center;gap:6px;border:1px solid var(--line);background:var(--panel-2);color:var(--muted);border-radius:8px;padding:5px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:'Manrope',sans-serif;transition:all 0.15s ease">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="18" rx="2"/><path d="M8 9l-4 3 4 3"/><path d="M16 9l4 3-4 3"/><path d="M12 6v12"/></svg>
|
||||
Canvas
|
||||
</button>
|
||||
@@ -2482,18 +2491,18 @@
|
||||
<div id="canvas-tabs" style="display:flex;align-items:stretch;flex:1;min-width:0;overflow-x:auto"></div>
|
||||
<div style="display:flex;align-items:center;gap:4px;padding:4px 8px;flex-shrink:0;border-left:1px solid var(--line)">
|
||||
<div style="display:inline-flex;align-items:center;border:1px solid var(--line);border-radius:8px;overflow:hidden;background:#fff">
|
||||
<button id="canvas-code-btn" onclick="setCanvasMode('code')" class="canvas-mode-btn active" style="border:0;background:transparent;padding:4px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:'Manrope',sans-serif;color:var(--muted)">Code</button>
|
||||
<button id="canvas-preview-btn" onclick="setCanvasMode('preview')" class="canvas-mode-btn" style="border:0;background:transparent;padding:4px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:'Manrope',sans-serif;color:var(--muted)">Preview</button>
|
||||
<button id="canvas-code-btn" onclick="setCanvasMode('code')" class="canvas-mode-btn active" style="border:0;background:transparent;padding:4px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:'Manrope',sans-serif;color:var(--muted)">코드</button>
|
||||
<button id="canvas-preview-btn" onclick="setCanvasMode('preview')" class="canvas-mode-btn" style="border:0;background:transparent;padding:4px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:'Manrope',sans-serif;color:var(--muted)">미리보기</button>
|
||||
</div>
|
||||
<button onclick="canvasSave()" title="Save (Ctrl+S)" style="display:flex;align-items:center;gap:5px;border:1px solid var(--line);background:#fff;color:var(--text);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:'Manrope',sans-serif">
|
||||
<button onclick="canvasSave()" title="저장 (Ctrl+S)" style="display:flex;align-items:center;gap:5px;border:1px solid var(--line);background:#fff;color:var(--text);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:'Manrope',sans-serif">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
||||
저장
|
||||
</button>
|
||||
<button onclick="canvasAddToContext()" title="Add to chat input" style="display:flex;align-items:center;gap:5px;border:1px solid var(--line);background:#fff;color:var(--text);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:'Manrope',sans-serif">
|
||||
<button onclick="canvasAddToContext()" title="채팅 입력에 추가" style="display:flex;align-items:center;gap:5px;border:1px solid var(--line);background:#fff;color:var(--text);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:'Manrope',sans-serif">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
Add to Chat
|
||||
</button>
|
||||
<button onclick="toggleCanvas()" title="Close canvas" style="display:flex;align-items:center;justify-content:center;border:1px solid var(--line);background:#fff;color:var(--muted);border-radius:8px;width:28px;height:28px;cursor:pointer">
|
||||
<button onclick="toggleCanvas()" title="캔버스 닫기" style="display:flex;align-items:center;justify-content:center;border:1px solid var(--line);background:#fff;color:var(--muted);border-radius:8px;width:28px;height:28px;cursor:pointer">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -2502,7 +2511,7 @@
|
||||
<div style="flex:1;min-height:0;display:flex;flex-direction:column;position:relative">
|
||||
<div id="canvas-empty-state" style="position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;color:var(--muted);text-align:center;padding:24px;pointer-events:none">
|
||||
<svg width="42" height="42" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" style="opacity:0.25"><rect x="2" y="3" width="20" height="18" rx="2"/><path d="M8 9l-4 3 4 3"/><path d="M16 9l4 3-4 3"/><path d="M12 6v12"/></svg>
|
||||
<div style="font-size:13px;font-weight:700">No files open</div>
|
||||
<div style="font-size:13px;font-weight:700">열린 파일 없음</div>
|
||||
<div style="font-size:11px;line-height:1.7">Drop a file here or click <strong>+</strong> to create one</div>
|
||||
</div>
|
||||
<div id="canvas-editor-wrap" style="display:none;flex:1;min-height:0;overflow:hidden"></div>
|
||||
@@ -2512,7 +2521,7 @@
|
||||
<!-- End Canvas Overlay -->
|
||||
|
||||
<div class="right-section" id="system-stats-section">
|
||||
<div class="right-section-title">System Stats</div>
|
||||
<div class="right-section-title">시스템 통계</div>
|
||||
<div class="sys-grid">
|
||||
<div class="sys-card">
|
||||
<div class="sys-k">CPU</div>
|
||||
@@ -2535,28 +2544,28 @@
|
||||
<div class="sys-meter"><i id="sys-vram-meter"></i></div>
|
||||
</div>
|
||||
<div class="sys-card full" onclick="openSettings('models')" title="제공자/모델 변경하려면 클릭" style="cursor:pointer">
|
||||
<div class="sys-k">Active LLM</div>
|
||||
<div class="sys-v" id="sys-ollama">Checking...</div>
|
||||
<div class="sys-k">활성 LLM</div>
|
||||
<div class="sys-v" id="sys-ollama">확인 중...</div>
|
||||
<div class="sys-sub" id="sys-runtime-sub">Gateway RSS -- MB • Updated --</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-section" id="agent-context-section">
|
||||
<div class="right-section-title">Agent Context</div>
|
||||
<div class="right-section-title">에이전트 컨텍스트</div>
|
||||
<div class="agent-card">
|
||||
<div class="agent-metric-grid">
|
||||
<div class="agent-metric">
|
||||
<div class="k">Session Mode</div>
|
||||
<div class="k">세션 모드</div>
|
||||
<div class="v" id="r-session-mode">-</div>
|
||||
</div>
|
||||
<div class="agent-metric">
|
||||
<div class="k">Agent State</div>
|
||||
<div class="k">에이전트 상태</div>
|
||||
<div class="v" id="r-agent-state">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent-metric-grid">
|
||||
<div class="agent-metric">
|
||||
<div class="k">Overview</div>
|
||||
<div class="k">개요</div>
|
||||
<div class="v" id="r-overview-objective">-</div>
|
||||
</div>
|
||||
<div class="agent-metric">
|
||||
@@ -2566,7 +2575,7 @@
|
||||
</div>
|
||||
<div class="agent-metric-grid">
|
||||
<div class="agent-metric">
|
||||
<div class="k">Tasks</div>
|
||||
<div class="k">작업</div>
|
||||
<div class="v" id="r-task-counts">0</div>
|
||||
</div>
|
||||
<div class="agent-metric">
|
||||
@@ -2576,24 +2585,24 @@
|
||||
</div>
|
||||
<div class="agent-metric-grid">
|
||||
<div class="agent-metric">
|
||||
<div class="k">Heartbeat</div>
|
||||
<div class="k">하트비트</div>
|
||||
<div class="v" id="r-heartbeat-state">idle</div>
|
||||
</div>
|
||||
<div class="agent-metric">
|
||||
<div class="k">Watchdog</div>
|
||||
<div class="k">감시</div>
|
||||
<div class="v" id="r-heartbeat-meta">step:-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-progress-wrap"><div class="task-progress-fill" id="r-task-progress-bar"></div></div>
|
||||
<div class="agent-context-scroll" id="r-agent-context-scroll">
|
||||
<div class="task-list" id="r-turn-execution">
|
||||
<div style="font-size:11px;color:var(--muted)">No active turn execution yet.</div>
|
||||
<div style="font-size:11px;color:var(--muted)">활성 턴 실행 없음.</div>
|
||||
</div>
|
||||
<div class="task-list" id="r-task-list">
|
||||
<div style="font-size:11px;color:var(--muted)">No tasks yet.</div>
|
||||
<div style="font-size:11px;color:var(--muted)">작업 없음.</div>
|
||||
</div>
|
||||
<div class="task-list" id="r-live-steps">
|
||||
<div style="font-size:11px;color:var(--muted)">No live tool steps.</div>
|
||||
<div style="font-size:11px;color:var(--muted)">활성 도구 단계 없음.</div>
|
||||
</div>
|
||||
<div id="r-recent-turns" style="padding:8px;border:1px solid var(--line);border-radius:10px;background:var(--panel-2);font-size:11px;line-height:1.6;color:var(--muted)">
|
||||
Waiting for turn history...
|
||||
@@ -2605,12 +2614,12 @@
|
||||
<div class="right-section">
|
||||
<div class="right-section-title">⚠️ Pending Approvals</div>
|
||||
<div id="approvals-list">
|
||||
<div style="color:var(--muted);font-size:11px">No pending approvals</div>
|
||||
<div style="color:var(--muted);font-size:11px">대기 중인 승인 없음</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding:10px 16px 8px; border-bottom:1px solid var(--border); display:flex; align-items:center; justify-content:space-between">
|
||||
<div class="right-section-title" style="margin:0">Quick Actions</div>
|
||||
<div class="right-section-title" style="margin:0">빠른 작업</div>
|
||||
<div style="display:flex;gap:6px">
|
||||
<button class="btn btn-sm" style="background:var(--bg3);color:var(--muted);border:1px solid var(--border);padding:3px 8px" onclick="clearChat()">✕ Chat</button>
|
||||
<button class="btn btn-sm" style="background:var(--bg3);color:var(--muted);border:1px solid var(--border);padding:3px 8px" onclick="newChatSession()">+</button>
|
||||
@@ -2622,21 +2631,21 @@
|
||||
<!-- AGENT EXECUTION PANEL -->
|
||||
<div style="display:flex;flex-direction:column;max-height:200px;overflow:hidden;border:1px solid var(--border);border-radius:10px;background:var(--panel-2);margin-bottom:10px">
|
||||
<div style="padding:8px 12px;border-bottom:1px solid var(--border);flex-shrink:0">
|
||||
<div style="font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted);margin-bottom:8px">Active Agents</div>
|
||||
<div style="font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted);margin-bottom:8px">활성 에이전트</div>
|
||||
</div>
|
||||
<div id="agent-execution-panel" style="flex:1;overflow-y:auto;padding:0">
|
||||
<div style="padding:8px 12px;font-size:11px;color:var(--muted)">Waiting for task execution...</div>
|
||||
<div style="padding:8px 12px;font-size:11px;color:var(--muted)">작업 실행 대기 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PROCESS LOG -->
|
||||
<div style="display:flex;flex-direction:column;flex:1 1 320px;overflow:hidden;min-height:280px">
|
||||
<div style="padding:8px 16px 6px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);flex-shrink:0">
|
||||
<a href="javascript:void(0)" onclick="openProcessLogFile()" style="font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted);text-decoration:none;cursor:pointer;hover:color:var(--brand)">Process Log</a>
|
||||
<a href="javascript:void(0)" onclick="openProcessLogFile()" style="font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted);text-decoration:none;cursor:pointer;hover:color:var(--brand)">프로세스 로그</a>
|
||||
<button onclick="clearProcessLog()" style="background:none;border:none;color:var(--muted);cursor:pointer;font-size:10px;font-family:inherit">✕ clear</button>
|
||||
</div>
|
||||
<div id="process-log" style="flex:1;overflow-y:auto;padding:8px 12px;font-size:11px;line-height:1.7;font-family:'JetBrains Mono',monospace">
|
||||
<div style="color:var(--muted);text-align:center;padding:20px 0;opacity:0.5">Waiting for activity...</div>
|
||||
<div style="color:var(--muted);text-align:center;padding:20px 0;opacity:0.5">활동 대기 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2664,7 +2673,7 @@
|
||||
<div id="error-response-panel">
|
||||
<div class="error-response-header">
|
||||
<div class="error-response-title" id="error-response-title">⚠️ Authentication Required</div>
|
||||
<div class="error-response-desc" id="error-response-desc">The task needs your help to continue. Please provide the required information.</div>
|
||||
<div class="error-response-desc" id="error-response-desc">작업을 계속하려면 도움이 필요합니다. 필요한 정보를 입력해 주세요.</div>
|
||||
</div>
|
||||
|
||||
<div class="error-response-content">
|
||||
@@ -2678,7 +2687,7 @@
|
||||
|
||||
<div class="error-response-footer">
|
||||
<button class="btn-error-cancel" onclick="closeErrorResponse()">작업 취소</button>
|
||||
<button class="btn-error-submit" id="error-response-submit-btn" onclick="submitErrorResponse()">Continue</button>
|
||||
<button class="btn-error-submit" id="error-response-submit-btn" onclick="submitErrorResponse()">계속</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2686,13 +2695,13 @@
|
||||
<div id="skill-modal" style="display:none;position:fixed;inset:0;background:rgba(10,20,40,0.35);z-index:9999;align-items:center;justify-content:center;padding:18px">
|
||||
<div style="width:min(520px,96vw);max-height:90vh;overflow:auto;background:#fff;border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow-md)">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line)">
|
||||
<div style="font-weight:800" id="skill-modal-title">Create Skill</div>
|
||||
<div style="font-weight:800" id="skill-modal-title">스킬 생성</div>
|
||||
<button class="btn btn-sm" style="background:var(--panel-2);color:var(--muted);border:1px solid var(--line)" onclick="closeSkillModal()">닫기</button>
|
||||
</div>
|
||||
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:12px">
|
||||
<div>
|
||||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px;font-weight:700">Name</label>
|
||||
<input id="skill-modal-name" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px 10px;font-size:13px;font-family:'Manrope',sans-serif" placeholder="e.g. Modern Styling" />
|
||||
<input id="skill-modal-name" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px 10px;font-size:13px;font-family:'Manrope',sans-serif" placeholder="예: 모던 스타일링" />
|
||||
</div>
|
||||
<div style="display:flex;gap:10px">
|
||||
<div style="flex:1">
|
||||
@@ -2700,18 +2709,18 @@
|
||||
<input id="skill-modal-emoji" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px 10px;font-size:13px;font-family:'Manrope',sans-serif" placeholder="??" maxlength="4" />
|
||||
</div>
|
||||
<div style="flex:3">
|
||||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px;font-weight:700">Short Description</label>
|
||||
<input id="skill-modal-desc" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px 10px;font-size:13px;font-family:'Manrope',sans-serif" placeholder="What does this skill do?" />
|
||||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px;font-weight:700">짧은 설명</label>
|
||||
<input id="skill-modal-desc" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px 10px;font-size:13px;font-family:'Manrope',sans-serif" placeholder="이 스킬은 무엇을 하나요?" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px;font-weight:700">Instructions <span style="font-weight:400">(what the model should do when this skill is active)</span></label>
|
||||
<textarea id="skill-modal-instructions" style="width:100%;min-height:180px;border:1px solid var(--line);border-radius:10px;padding:10px;font-size:12px;font-family:'IBM Plex Mono',monospace;line-height:1.6;resize:vertical" placeholder="When creating HTML files, always use modern CSS patterns..."></textarea>
|
||||
<textarea id="skill-modal-instructions" style="width:100%;min-height:180px;border:1px solid var(--line);border-radius:10px;padding:10px;font-size:12px;font-family:'IBM Plex Mono',monospace;line-height:1.6;resize:vertical" placeholder="HTML 파일 생성 시, 항상 모던 CSS 패턴을 사용하세요..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--line)">
|
||||
<button class="btn btn-sm" style="background:var(--panel-2);color:var(--muted);border:1px solid var(--line)" onclick="closeSkillModal()">취소</button>
|
||||
<button class="btn btn-sm" id="skill-modal-save-btn" onclick="saveSkillFromModal()">Create Skill</button>
|
||||
<button class="btn btn-sm" id="skill-modal-save-btn" onclick="saveSkillFromModal()">스킬 생성</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3352,14 +3361,14 @@
|
||||
<div style="display:grid;grid-template-columns:0.9fr 1.1fr;gap:14px">
|
||||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
|
||||
<div class="right-section-title">Configured Agents</div>
|
||||
<div class="right-section-title">구성된 에이전트</div>
|
||||
<button class="btn btn-sm" style="padding:4px 8px" onclick="agentFormNew()">+ New</button>
|
||||
</div>
|
||||
<div id="settings-agents-list" style="display:flex;flex-direction:column;gap:8px;max-height:520px;overflow:auto;font-size:12px;color:var(--muted)">Loading agents...</div>
|
||||
<div id="settings-agents-list" style="display:flex;flex-direction:column;gap:8px;max-height:520px;overflow:auto;font-size:12px;color:var(--muted)">에이전트 로딩 중...</div>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:12px">
|
||||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||||
<div class="right-section-title" style="margin-bottom:8px">Agent Details</div>
|
||||
<div class="right-section-title" style="margin-bottom:8px">에이전트 상세</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
|
||||
<div>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">ID</label>
|
||||
@@ -3374,7 +3383,7 @@
|
||||
<input id="agent-edit-emoji" type="text" placeholder="🔍" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;box-sizing:border-box" />
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">Tool Profile</label>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">도구 프로필</label>
|
||||
<select id="agent-edit-profile" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;box-sizing:border-box">
|
||||
<option value="">(inherit)</option>
|
||||
<option value="minimal">minimal</option>
|
||||
@@ -3384,11 +3393,11 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin:8px 0 4px">Description</label>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin:8px 0 4px">설명</label>
|
||||
<textarea id="agent-edit-description" rows="3" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;box-sizing:border-box;resize:vertical"></textarea>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px">
|
||||
<div>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">Workspace</label>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">워크스페이스</label>
|
||||
<input id="agent-edit-workspace" type="text" placeholder="D:/CherryClaw/agents/researcher/workspace" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||||
</div>
|
||||
<div>
|
||||
@@ -3396,11 +3405,11 @@
|
||||
<input id="agent-edit-model" type="text" placeholder="ollama/qwen3:4b" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">Cron Schedule</label>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">크론 일정</label>
|
||||
<input id="agent-edit-cron" type="text" placeholder="0 8 * * *" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">Max Steps</label>
|
||||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">최대 단계</label>
|
||||
<input id="agent-edit-max-steps" type="number" min="1" step="1" placeholder="8" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;box-sizing:border-box" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -3413,7 +3422,7 @@
|
||||
<input id="agent-edit-spawn-allowlist" type="text" placeholder="researcher,writer" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||||
<div style="display:flex;gap:8px;margin-top:10px">
|
||||
<button class="btn btn-sm" onclick="saveAgentFromForm()">에이전트 저장</button>
|
||||
<button class="btn btn-sm" style="background:#fff;border:1px solid var(--line);color:var(--text)" onclick="deleteSelectedAgent()">Delete</button>
|
||||
<button class="btn btn-sm" style="background:#fff;border:1px solid var(--line);color:var(--text)" onclick="deleteSelectedAgent()">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||||
@@ -3854,7 +3863,7 @@ function newChatSession() {
|
||||
function toggleSessionsEditMode() {
|
||||
sessionsEditMode = !sessionsEditMode;
|
||||
const btn = document.getElementById('sessions-edit-btn');
|
||||
if (btn) btn.textContent = sessionsEditMode ? 'Done' : 'Edit';
|
||||
if (btn) btn.textContent = sessionsEditMode ? '완료' : '편집';
|
||||
renderSessionsList();
|
||||
}
|
||||
|
||||
@@ -3941,7 +3950,7 @@ function renderHeartbeatPage() {
|
||||
const meta = document.getElementById('hb-meta');
|
||||
const btn = document.getElementById('hb-toggle-btn');
|
||||
if (dot) dot.className = 'hb-status-dot ' + (hbConfig.enabled ? 'active' : '');
|
||||
if (label) label.textContent = hbConfig.enabled ? 'Heartbeat ON' : 'Heartbeat OFF';
|
||||
if (label) label.textContent = hbConfig.enabled ? '하트비트 ON' : '하트비트 OFF';
|
||||
if (meta) meta.textContent = `Every ${hbConfig.intervalMinutes}m \u00b7 ${hbConfig.activeHoursStart}:00\u2013${hbConfig.activeHoursEnd}:00`;
|
||||
if (btn) { btn.textContent = hbConfig.enabled ? 'Pause' : 'Enable'; btn.className = 'hb-btn' + (hbConfig.enabled ? ' danger' : ' primary'); }
|
||||
|
||||
@@ -4120,7 +4129,7 @@ function setSidebarTab(tab) {
|
||||
// ---- Agent mode toggle ----
|
||||
function updateAgentMode() {
|
||||
useAgentMode = document.getElementById('agent-mode-toggle').checked;
|
||||
document.getElementById('agent-mode-label').textContent = useAgentMode ? 'Agent' : 'Chat';
|
||||
document.getElementById('agent-mode-label').textContent = useAgentMode ? '에이전트' : '채팅';
|
||||
document.getElementById('agent-mode-badge').classList.toggle('visible', useAgentMode);
|
||||
document.getElementById('chat-input').placeholder = useAgentMode
|
||||
? 'Ask agent to do something... (has access to tools)'
|
||||
@@ -4555,7 +4564,7 @@ function updateHeartbeatUI() {
|
||||
function clearProcessLog() {
|
||||
processLogEntries.length = 0;
|
||||
const el = document.getElementById('process-log');
|
||||
if (el) el.innerHTML = '<div style="color:var(--muted);text-align:center;padding:20px 0;opacity:0.5">Waiting for activity...</div>';
|
||||
if (el) el.innerHTML = '<div style="color:var(--muted);text-align:center;padding:20px 0;opacity:0.5">활동 대기 중...</div>';
|
||||
processLogAutoFollow = true;
|
||||
rightColumnAutoFollow = true;
|
||||
maybeAutoScrollRightColumn(true);
|
||||
@@ -4583,7 +4592,7 @@ function renderProcessLog() {
|
||||
const shouldFollow = processLogAutoFollow || isThinking;
|
||||
|
||||
if (processLogEntries.length === 0) {
|
||||
el.innerHTML = '<div style="color:var(--muted);text-align:center;padding:20px 0;opacity:0.5">Waiting for activity...</div>';
|
||||
el.innerHTML = '<div style="color:var(--muted);text-align:center;padding:20px 0;opacity:0.5">활동 대기 중...</div>';
|
||||
if (shouldFollow) el.scrollTop = el.scrollHeight;
|
||||
return;
|
||||
}
|
||||
@@ -5254,7 +5263,7 @@ function renderAgentExecutionPanel() {
|
||||
if (!panel) return;
|
||||
|
||||
if (agentExecutionMap.size === 0) {
|
||||
panel.innerHTML = '<div style="padding:8px 12px;font-size:11px;color:var(--muted)">Waiting for task execution...</div>';
|
||||
panel.innerHTML = '<div style="padding:8px 12px;font-size:11px;color:var(--muted)">작업 실행 대기 중...</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5521,15 +5530,32 @@ async function checkAuth() {
|
||||
const data = await res.json();
|
||||
if (data.enabled && !data.authenticated && !data.hasPassword) {
|
||||
window.location.href = '/login.html';
|
||||
return;
|
||||
}
|
||||
if (data.enabled && !data.authenticated) {
|
||||
window.location.href = '/login.html';
|
||||
return;
|
||||
}
|
||||
if (data.authenticated && data.username) {
|
||||
currentUser = { username: data.username, role: data.role };
|
||||
// Show user pill with username
|
||||
const pill = document.getElementById('user-info-pill');
|
||||
const nameEl = document.getElementById('user-info-name');
|
||||
if (pill) pill.style.display = 'flex';
|
||||
if (nameEl) nameEl.textContent = data.username;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Auth check failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' });
|
||||
} catch (e) { /* ignore network errors */ }
|
||||
window.location.href = '/login.html';
|
||||
}
|
||||
|
||||
async function loadSearchSettingsSummary() {
|
||||
try {
|
||||
const s = await api('/api/settings/search');
|
||||
@@ -5695,14 +5721,14 @@ async function loadCredVaultLog() {
|
||||
try {
|
||||
const data = await api('/api/credentials/audit');
|
||||
const lines = (data.lines || []).slice(-18).reverse();
|
||||
if (!lines.length) { el.textContent = 'No audit entries yet.'; return; }
|
||||
if (!lines.length) { el.textContent = '감사 항목 없음.'; return; }
|
||||
el.innerHTML = lines.map(l => {
|
||||
const safe = l.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
const color = l.includes('SET') ? 'var(--ok)' : l.includes('GET') ? 'var(--brand)' : l.includes('DEL') ? 'var(--err)' : 'var(--muted)';
|
||||
return `<div style="color:${color};white-space:nowrap">${safe}</div>`;
|
||||
}).join('');
|
||||
} catch(e) {
|
||||
el.textContent = 'Could not load audit log.';
|
||||
el.textContent = '감사 로그를 불러올 수 없습니다.';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5892,7 +5918,7 @@ async function refreshCodexStatus() {
|
||||
if (data?.connected) {
|
||||
if (disc) disc.style.display = 'none';
|
||||
if (conn) conn.style.display = 'block';
|
||||
if (acct) acct.textContent = data.account_id ? `Account: ${data.account_id}` : 'Account connected';
|
||||
if (acct) acct.textContent = data.account_id ? `계정: ${data.account_id}` : '계정 연결됨';
|
||||
} else {
|
||||
if (disc) disc.style.display = 'block';
|
||||
if (conn) conn.style.display = 'none';
|
||||
@@ -5902,7 +5928,7 @@ async function refreshCodexStatus() {
|
||||
|
||||
async function startCodexOAuth() {
|
||||
const statusEl = document.getElementById('codex-oauth-status');
|
||||
if (statusEl) statusEl.textContent = 'Opening browser…';
|
||||
if (statusEl) statusEl.textContent = '브라우저 열기 중…';
|
||||
try {
|
||||
const data = await api('/api/auth/openai/start', { method: 'POST', body: '{}' });
|
||||
if (data?.needsManualPaste) {
|
||||
@@ -5922,7 +5948,7 @@ async function submitManualCodexUrl() {
|
||||
const url = document.getElementById('codex-manual-url')?.value?.trim();
|
||||
if (!url) return;
|
||||
const statusEl = document.getElementById('codex-oauth-status');
|
||||
if (statusEl) statusEl.textContent = 'Exchanging token…';
|
||||
if (statusEl) statusEl.textContent = '토큰 교환 중…';
|
||||
try {
|
||||
const data = await api('/api/auth/openai/manual', { method: 'POST', body: JSON.stringify({ url }) });
|
||||
if (data?.success) {
|
||||
@@ -5979,7 +6005,7 @@ async function loadAgentSessionContext() {
|
||||
if (execPanel) {
|
||||
const cur = data.current_turn_execution;
|
||||
if (!cur || typeof cur !== 'object') {
|
||||
execPanel.innerHTML = '<div style="font-size:11px;color:var(--muted)">No active turn execution yet.</div>';
|
||||
execPanel.innerHTML = '<div style="font-size:11px;color:var(--muted)">활성 턴 실행 없음.</div>';
|
||||
} else {
|
||||
const objective = escHtml(String(cur.objective_raw || '(no objective)').slice(0, 220));
|
||||
const status = escHtml(String(cur.status || 'planned'));
|
||||
@@ -6043,7 +6069,7 @@ async function loadAgentSessionContext() {
|
||||
if (taskList) {
|
||||
const tasks = Array.isArray(data.tasks) ? data.tasks : [];
|
||||
if (!tasks.length) {
|
||||
taskList.innerHTML = '<div style="font-size:11px;color:var(--muted)">No tasks yet.</div>';
|
||||
taskList.innerHTML = '<div style="font-size:11px;color:var(--muted)">작업 없음.</div>';
|
||||
} else {
|
||||
taskList.innerHTML = tasks.slice(0, 20).map(t => {
|
||||
const title = escHtml(String(t.title || '').slice(0, 120));
|
||||
@@ -6103,7 +6129,7 @@ async function loadAgentSessionContext() {
|
||||
}
|
||||
}
|
||||
if (label) {
|
||||
label.textContent = useAgentMode ? 'Agent' : 'Chat';
|
||||
label.textContent = useAgentMode ? '에이전트' : '채팅';
|
||||
}
|
||||
maybeAutoScrollRightColumn(rightColumnAutoFollow && isThinking);
|
||||
} catch {
|
||||
@@ -6116,7 +6142,7 @@ function renderLiveStepsPanel() {
|
||||
const el = document.getElementById('r-live-steps');
|
||||
if (!el) return;
|
||||
if (!liveTaskSteps.length) {
|
||||
el.innerHTML = '<div style="font-size:11px;color:var(--muted)">No live tool steps.</div>';
|
||||
el.innerHTML = '<div style="font-size:11px;color:var(--muted)">활성 도구 단계 없음.</div>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = liveTaskSteps.slice(-20).map((s) => {
|
||||
@@ -6433,7 +6459,7 @@ function agentFormNew() {
|
||||
});
|
||||
if (agentMdEditor) agentMdEditor.setValue('');
|
||||
const mdPath = document.getElementById('agent-md-path');
|
||||
if (mdPath) mdPath.textContent = 'Select or save an agent to load AGENTS.md';
|
||||
if (mdPath) mdPath.textContent = '에이전트를 선택하거나 저장하면 AGENTS.md를 불러옵니다';
|
||||
const resultEl = document.getElementById('agent-spawn-result');
|
||||
if (resultEl) resultEl.textContent = '';
|
||||
renderAgentsList();
|
||||
@@ -6547,7 +6573,7 @@ async function runSelectedAgentOnce() {
|
||||
return;
|
||||
}
|
||||
const out = document.getElementById('agent-spawn-result');
|
||||
if (out) out.textContent = 'Running...';
|
||||
if (out) out.textContent = '실행 중...';
|
||||
try {
|
||||
const data = await api(`/api/agents/${encodeURIComponent(selectedAgentId)}/spawn`, {
|
||||
method: 'POST',
|
||||
@@ -6929,7 +6955,7 @@ function renderSessionsList() {
|
||||
<div class="job-item ${s.id === activeChatSessionId ? 'active' : ''}${s.unread ? ' unread' : ''}" onclick="openSession('${s.id}')">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px">
|
||||
<div class="job-item-title" style="flex:1">${s.unread ? '🔵 ' : ''}${escHtml(s.title || 'New chat')}</div>
|
||||
${sessionsEditMode ? `<button class="sidebar-action" style="padding:3px 8px;font-size:10px" onclick="deleteChatSession('${s.id}', event)">Delete</button>` : ''}
|
||||
${sessionsEditMode ? `<button class="sidebar-action" style="padding:3px 8px;font-size:10px" onclick="deleteChatSession('${s.id}', event)">삭제</button>` : ''}
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:6px;margin-top:4px">
|
||||
${s.automated ? '<span class="session-auto-badge">Auto</span>' : ''}
|
||||
@@ -6998,7 +7024,7 @@ function renderJobDetail(data) {
|
||||
</div>` : ''}
|
||||
</div>
|
||||
`).join('')}` : `
|
||||
<div class="section-title">Tasks</div>
|
||||
<div class="section-title">작업</div>
|
||||
<div style="color:var(--muted);font-size:12px;padding:20px 0">
|
||||
${isRunning ? '<span class="spinner pulsing"></span> Planning tasks...' : 'No tasks'}
|
||||
</div>`}
|
||||
@@ -7038,7 +7064,7 @@ async function loadApprovals() {
|
||||
const approvals = await api('/api/approvals');
|
||||
const el = document.getElementById('approvals-list');
|
||||
if (!approvals || approvals.length === 0) {
|
||||
el.innerHTML = '<div style="color:var(--muted);font-size:11px">No pending approvals</div>';
|
||||
el.innerHTML = '<div style="color:var(--muted);font-size:11px">대기 중인 승인 없음</div>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = approvals.map(a => `
|
||||
@@ -7864,8 +7890,8 @@ async function deleteSkill(id) {
|
||||
|
||||
function openCreateSkillModal() {
|
||||
editingSkillId = null;
|
||||
document.getElementById('skill-modal-title').textContent = 'Create Skill';
|
||||
document.getElementById('skill-modal-save-btn').textContent = 'Create Skill';
|
||||
document.getElementById('skill-modal-title').textContent = '스킬 생성';
|
||||
document.getElementById('skill-modal-save-btn').textContent = '스킬 생성';
|
||||
document.getElementById('skill-modal-name').value = '';
|
||||
document.getElementById('skill-modal-emoji').value = '';
|
||||
document.getElementById('skill-modal-desc').value = '';
|
||||
@@ -7880,8 +7906,8 @@ async function editSkill(id) {
|
||||
if (!res.success) { alert('Skill not found'); return; }
|
||||
const s = res.skill;
|
||||
editingSkillId = id;
|
||||
document.getElementById('skill-modal-title').textContent = 'Edit Skill';
|
||||
document.getElementById('skill-modal-save-btn').textContent = 'Save Changes';
|
||||
document.getElementById('skill-modal-title').textContent = '스킬 편집';
|
||||
document.getElementById('skill-modal-save-btn').textContent = '변경 저장';
|
||||
document.getElementById('skill-modal-name').value = s.name || '';
|
||||
document.getElementById('skill-modal-emoji').value = s.emoji || '';
|
||||
document.getElementById('skill-modal-desc').value = s.description || '';
|
||||
@@ -7910,7 +7936,7 @@ async function saveSkillFromModal() {
|
||||
|
||||
const btn = document.getElementById('skill-modal-save-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Saving...';
|
||||
btn.textContent = '저장 중...';
|
||||
|
||||
try {
|
||||
let res;
|
||||
@@ -7938,7 +7964,7 @@ async function saveSkillFromModal() {
|
||||
alert(`Error: ${err.message}`);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = editingSkillId ? 'Save Changes' : 'Create Skill';
|
||||
btn.textContent = editingSkillId ? '변경 저장' : '스킬 생성';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8310,7 +8336,7 @@ function updatePinUI() {
|
||||
function updatePinBadge() {
|
||||
const label = document.getElementById('context-pin-label');
|
||||
if (label) {
|
||||
label.textContent = confirmedPins.length > 0 ? `Context (${confirmedPins.length})` : 'Context';
|
||||
label.textContent = confirmedPins.length > 0 ? `컨텍스트 (${confirmedPins.length})` : '컨텍스트';
|
||||
}
|
||||
const btn = document.getElementById('context-pin-btn');
|
||||
if (btn) {
|
||||
@@ -8384,7 +8410,7 @@ function renderScheduleList() {
|
||||
if (!list) return;
|
||||
|
||||
if (schedules.length === 0) {
|
||||
list.innerHTML = '<div class="empty-state" style="text-align:center;color:var(--muted);padding:40px 20px">No schedules yet. <strong>+ New Schedule</strong> to get started.</div>';
|
||||
list.innerHTML = '<div class="empty-state" style="text-align:center;color:var(--muted);padding:40px 20px">No schedules yet. <strong>+ 새 일정</strong> to get started.</div>';
|
||||
if (count) count.textContent = '0 schedules';
|
||||
return;
|
||||
}
|
||||
@@ -8412,7 +8438,7 @@ function renderScheduleList() {
|
||||
<div style="display:flex;flex-direction:column;gap:6px;flex-shrink:0">
|
||||
<button onclick="event.stopPropagation(); runScheduleNow('${job.id}')" style="border:1px solid var(--line);background:var(--panel-2);color:var(--muted);border-radius:6px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap" title="Run now">Run Now</button>
|
||||
<button onclick="event.stopPropagation(); toggleSchedulePause('${job.id}')" style="border:1px solid var(--line);background:var(--panel-2);color:var(--muted);border-radius:6px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap" title="Toggle pause">${job.status === 'active' ? 'Pause' : 'Resume'}</button>
|
||||
<button onclick="event.stopPropagation(); deleteSchedule('${job.id}')" style="border:1px solid #ff6b6b;background:#ffe0e0;color:#8b0000;border-radius:6px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap" title="Delete">Delete</button>
|
||||
<button onclick="event.stopPropagation(); deleteSchedule('${job.id}')" style="border:1px solid #ff6b6b;background:#ffe0e0;color:#8b0000;border-radius:6px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap" title="Delete">삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -8421,8 +8447,8 @@ function renderScheduleList() {
|
||||
|
||||
function openScheduleCreateModal() {
|
||||
editingScheduleId = null;
|
||||
document.getElementById('schedule-modal-title').textContent = 'Create Schedule';
|
||||
document.getElementById('schedule-save-btn').textContent = 'Create Schedule';
|
||||
document.getElementById('schedule-modal-title').textContent = '일정 생성';
|
||||
document.getElementById('schedule-save-btn').textContent = '일정 생성';
|
||||
document.getElementById('schedule-name').value = '';
|
||||
document.getElementById('schedule-pattern').value = '';
|
||||
document.getElementById('schedule-prompt').value = '';
|
||||
@@ -8437,8 +8463,8 @@ function editSchedule(jobId) {
|
||||
if (!job) return;
|
||||
|
||||
editingScheduleId = jobId;
|
||||
document.getElementById('schedule-modal-title').textContent = 'Edit Schedule';
|
||||
document.getElementById('schedule-save-btn').textContent = 'Save Changes';
|
||||
document.getElementById('schedule-modal-title').textContent = '일정 편집';
|
||||
document.getElementById('schedule-save-btn').textContent = '변경 저장';
|
||||
document.getElementById('schedule-name').value = job.name || '';
|
||||
document.getElementById('schedule-pattern').value = job.cron || job.run_at || '';
|
||||
document.getElementById('schedule-prompt').value = job.prompt || '';
|
||||
@@ -8741,7 +8767,7 @@ function renderBgTasks() {
|
||||
// If no tasks at all
|
||||
if (bgtTasks.length === 0) {
|
||||
board.innerHTML = `<div style="text-align:center;padding:60px 24px;color:var(--muted);font-size:13px;flex:1">
|
||||
<div style="font-size:36px;margin-bottom:12px">Tasks</div>
|
||||
<div style="font-size:36px;margin-bottom:12px">작업</div>
|
||||
<div style="font-weight:700;margin-bottom:4px">No background tasks yet</div>
|
||||
<div style="font-size:12px">Ask CherryClaw to do something in the background and it'll appear here.</div>
|
||||
</div>`;
|
||||
@@ -8818,15 +8844,15 @@ async function bgtRefreshOpenPanel() {
|
||||
if (!task) return;
|
||||
|
||||
const titleEl = document.getElementById('bgt-panel-title');
|
||||
if (titleEl) titleEl.textContent = task.title || 'Task Details';
|
||||
if (titleEl) titleEl.textContent = task.title || '작업 상세';
|
||||
|
||||
const pauseBtn = document.getElementById('bgt-panel-pause');
|
||||
if (pauseBtn) {
|
||||
if (task.status === 'running') {
|
||||
pauseBtn.textContent = 'Pause';
|
||||
pauseBtn.textContent = '일시정지';
|
||||
pauseBtn.style.display = '';
|
||||
} else if (task.status === 'paused' || task.status === 'queued') {
|
||||
pauseBtn.textContent = 'Resume';
|
||||
pauseBtn.textContent = '재개';
|
||||
pauseBtn.style.display = '';
|
||||
} else {
|
||||
pauseBtn.style.display = 'none';
|
||||
@@ -8892,7 +8918,7 @@ async function bgtRefreshOpenPanel() {
|
||||
+ '<div style="padding:8px 12px;font-size:11px;font-weight:700;color:#6d2d9e;border-bottom:1px solid #e9d8ff">💬 Reply to agent</div>'
|
||||
+ '<div style="display:flex;gap:8px;padding:8px">'
|
||||
+ '<textarea id="task-reply-input" rows="2" placeholder="Type your reply — the agent will see this and continue..." style="flex:1;resize:none;border:1px solid #cba8f5;border-radius:8px;padding:8px;font-size:12px;font-family:inherit;background:#fff;color:var(--text)"></textarea>'
|
||||
+ '<button id="task-reply-send" data-taskid="' + tid + '" style="background:#7c3aed;color:#fff;border:none;border-radius:8px;padding:8px 14px;font-size:12px;font-weight:700;cursor:pointer;align-self:flex-end">Send</button>'
|
||||
+ '<button id="task-reply-send" data-taskid="' + tid + '" style="background:#7c3aed;color:#fff;border:none;border-radius:8px;padding:8px 14px;font-size:12px;font-weight:700;cursor:pointer;align-self:flex-end">전송</button>'
|
||||
+ '</div></div>';
|
||||
}
|
||||
|
||||
@@ -9105,15 +9131,15 @@ function updateWebhookStatus(enabled, tokenSet) {
|
||||
if (!dot || !txt) return;
|
||||
if (enabled && tokenSet) {
|
||||
dot.style.background = '#22c55e';
|
||||
txt.textContent = 'Active — endpoint is running';
|
||||
txt.textContent = '활성 — 엔드포인트 실행 중';
|
||||
txt.style.color = '#166534';
|
||||
} else if (!enabled) {
|
||||
dot.style.background = '#ccc';
|
||||
txt.textContent = 'Disabled';
|
||||
txt.textContent = '비활성';
|
||||
txt.style.color = 'var(--muted)';
|
||||
} else {
|
||||
dot.style.background = '#f59e0b';
|
||||
txt.textContent = 'Enabled but no token set';
|
||||
txt.textContent = '활성화됨 — 토큰 미설정';
|
||||
txt.style.color = '#92400e';
|
||||
}
|
||||
}
|
||||
@@ -9239,7 +9265,7 @@ async function loadMCPServers() {
|
||||
function showMCPAddForm(editData) {
|
||||
_mcpEditingId = editData?.id || null;
|
||||
document.getElementById('mcp-add-form').style.display = 'block';
|
||||
document.getElementById('mcp-form-title').textContent = _mcpEditingId ? 'Edit MCP Server' : 'Add MCP Server';
|
||||
document.getElementById('mcp-form-title').textContent = _mcpEditingId ? 'MCP 서버 편집' : 'MCP 서버 추가';
|
||||
document.getElementById('mcp-form-msg').textContent = '';
|
||||
|
||||
if (editData) {
|
||||
@@ -9299,7 +9325,7 @@ async function saveMCPServer() {
|
||||
const enabled = document.getElementById('mcp-f-enabled')?.checked ?? true;
|
||||
const msgEl = document.getElementById('mcp-form-msg');
|
||||
|
||||
if (!id || !name) { if (msgEl) msgEl.textContent = 'ID and Name are required.'; return; }
|
||||
if (!id || !name) { if (msgEl) msgEl.textContent = 'ID와 이름은 필수입니다.'; return; }
|
||||
|
||||
const cfg = { id, name, transport, enabled };
|
||||
|
||||
@@ -9330,10 +9356,10 @@ async function saveMCPServer() {
|
||||
// Auto-connect if enabled
|
||||
if (enabled) connectMCPServer(id);
|
||||
} else {
|
||||
if (msgEl) msgEl.textContent = 'Error: ' + (r.error || 'Unknown');
|
||||
if (msgEl) msgEl.textContent = '오류: ' + (r.error || '알 수 없음');
|
||||
}
|
||||
} catch(e) {
|
||||
if (msgEl) msgEl.textContent = 'Save failed: ' + e.message;
|
||||
if (msgEl) msgEl.textContent = '저장 실패: ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9523,7 +9549,7 @@ function selectErrorOption(opt) {
|
||||
const submitBtn = document.getElementById('error-response-submit-btn');
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = opt.danger ? 'Cancel Task' : 'Continue';
|
||||
submitBtn.textContent = opt.danger ? '작업 취소' : '계속';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9575,7 +9601,7 @@ async function submitErrorResponse() {
|
||||
|
||||
// Disable submit to prevent double-send
|
||||
const submitBtn = document.getElementById('error-response-submit-btn');
|
||||
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Sending…'; }
|
||||
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = '전송 중…'; }
|
||||
|
||||
try {
|
||||
const resp = await fetch(`/api/bg-tasks/${_errTaskId}/error-response`, {
|
||||
@@ -9597,12 +9623,12 @@ async function submitErrorResponse() {
|
||||
}
|
||||
} else {
|
||||
bgtToast('\u274c Error', data.error || 'Failed to submit response');
|
||||
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Continue'; }
|
||||
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '계속'; }
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[ErrorResponse] Submit failed:', err);
|
||||
bgtToast('\u274c Network error', 'Could not submit response');
|
||||
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Continue'; }
|
||||
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '계속'; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+40
-40
@@ -241,30 +241,30 @@
|
||||
<div class="logo-icon">SC</div>
|
||||
<div class="logo-text">SmallClaw</div>
|
||||
</div>
|
||||
<p class="subtitle" id="subtitle">Checking authentication...</p>
|
||||
<p class="subtitle" id="subtitle">인증 확인 중...</p>
|
||||
|
||||
<div class="migrate-notice" id="migrateNotice">
|
||||
Your account needs a username. Choose one to upgrade.
|
||||
계정에 사용자 이름이 필요합니다. 업그레이드할 이름을 선택하세요.
|
||||
</div>
|
||||
|
||||
<form id="authForm" style="display:none; width:100%; display:flex; flex-direction:column; gap:16px;">
|
||||
<div class="form-group" id="usernameGroup" style="display:none;">
|
||||
<label class="form-label" id="usernameLabel">Username</label>
|
||||
<input type="text" class="form-input" id="username" autocomplete="username" placeholder="Enter username">
|
||||
<label class="form-label" id="usernameLabel">사용자 이름</label>
|
||||
<input type="text" class="form-input" id="username" autocomplete="username" placeholder="사용자 이름 입력">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" id="pwLabel">Password</label>
|
||||
<label class="form-label" id="pwLabel">비밀번호</label>
|
||||
<div class="password-wrapper">
|
||||
<input type="password" class="form-input" id="password" autocomplete="current-password" placeholder="Enter password" style="padding-right:40px;">
|
||||
<input type="password" class="form-input" id="password" autocomplete="current-password" placeholder="비밀번호 입력" style="padding-right:40px;">
|
||||
<button type="button" class="toggle-pw" id="togglePw" title="Show password">👁</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="confirmGroup" style="display:none;">
|
||||
<label class="form-label">Confirm password</label>
|
||||
<input type="password" class="form-input" id="confirmPassword" autocomplete="new-password" placeholder="Confirm password">
|
||||
<label class="form-label">비밀번호 확인</label>
|
||||
<input type="password" class="form-input" id="confirmPassword" autocomplete="new-password" placeholder="비밀번호 확인 입력">
|
||||
</div>
|
||||
<div class="error-msg" id="errorMsg"></div>
|
||||
<button type="submit" class="btn btn-primary" id="submitBtn">Login</button>
|
||||
<button type="submit" class="btn btn-primary" id="submitBtn">로그인</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -332,48 +332,48 @@
|
||||
if (data.hasLegacyPassword && !data.hasUsers) {
|
||||
// Legacy mode: old password-only install needs migration
|
||||
mode = 'legacy-migrate';
|
||||
subtitle.textContent = 'Set a username to upgrade your account';
|
||||
subtitle.textContent = '업그레이드할 사용자 이름을 설정하세요';
|
||||
migrateNotice.classList.add('visible');
|
||||
usernameGroup.style.display = 'flex';
|
||||
usernameLabel.textContent = 'Username';
|
||||
usernameInput.placeholder = 'Choose a username';
|
||||
pwLabel.textContent = 'Current password';
|
||||
submitBtn.textContent = 'Upgrade & Login';
|
||||
usernameLabel.textContent = '사용자 이름';
|
||||
usernameInput.placeholder = '사용자 이름 선택';
|
||||
pwLabel.textContent = '현재 비밀번호';
|
||||
submitBtn.textContent = '업그레이드 & 로그인';
|
||||
confirmGroup.style.display = 'none';
|
||||
passwordInput.autocomplete = 'current-password';
|
||||
passwordInput.placeholder = 'Enter your current password';
|
||||
passwordInput.placeholder = '현재 비밀번호를 입력하세요';
|
||||
} else if (!data.hasUsers) {
|
||||
// Fresh install: first user setup
|
||||
mode = 'setup';
|
||||
subtitle.textContent = 'Create an admin account';
|
||||
subtitle.textContent = '관리자 계정 생성';
|
||||
migrateNotice.classList.remove('visible');
|
||||
usernameGroup.style.display = 'flex';
|
||||
usernameLabel.textContent = 'Username';
|
||||
usernameInput.placeholder = 'Choose a username';
|
||||
pwLabel.textContent = 'Password';
|
||||
submitBtn.textContent = 'Create account';
|
||||
usernameLabel.textContent = '사용자 이름';
|
||||
usernameInput.placeholder = '사용자 이름 선택';
|
||||
pwLabel.textContent = '비밀번호';
|
||||
submitBtn.textContent = '계정 생성';
|
||||
confirmGroup.style.display = 'flex';
|
||||
passwordInput.autocomplete = 'new-password';
|
||||
passwordInput.placeholder = 'Choose a password';
|
||||
passwordInput.placeholder = '비밀번호 선택';
|
||||
} else {
|
||||
// Normal login
|
||||
mode = 'login';
|
||||
subtitle.textContent = 'Sign in to SmallClaw';
|
||||
subtitle.textContent = 'SmallClaw에 로그인';
|
||||
migrateNotice.classList.remove('visible');
|
||||
usernameGroup.style.display = 'flex';
|
||||
usernameLabel.textContent = 'Username';
|
||||
usernameInput.placeholder = 'Enter username';
|
||||
pwLabel.textContent = 'Password';
|
||||
submitBtn.textContent = 'Login';
|
||||
usernameLabel.textContent = '사용자 이름';
|
||||
usernameInput.placeholder = '사용자 이름 입력';
|
||||
pwLabel.textContent = '비밀번호';
|
||||
submitBtn.textContent = '로그인';
|
||||
confirmGroup.style.display = 'none';
|
||||
passwordInput.autocomplete = 'current-password';
|
||||
passwordInput.placeholder = 'Enter password';
|
||||
passwordInput.placeholder = '비밀번호 입력';
|
||||
}
|
||||
|
||||
form.style.display = 'flex';
|
||||
usernameInput.focus();
|
||||
} catch (e) {
|
||||
subtitle.textContent = 'Unable to reach server';
|
||||
subtitle.textContent = '서버에 연결할 수 없습니다';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,22 +385,22 @@
|
||||
const password = passwordInput.value;
|
||||
|
||||
if (mode === 'login') {
|
||||
if (!username) { showError('Please enter your username'); return; }
|
||||
if (!password) { showError('Please enter your password'); return; }
|
||||
if (!username) { showError('사용자 이름을 입력하세요'); return; }
|
||||
if (!password) { showError('비밀번호를 입력하세요'); return; }
|
||||
} else if (mode === 'setup') {
|
||||
if (!username || username.length < 2) { showError('Username must be at least 2 characters'); return; }
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(username)) { showError('Username: letters, numbers, dash, underscore only'); return; }
|
||||
if (!password || password.length < 4) { showError('Password must be at least 4 characters'); return; }
|
||||
if (password !== confirmInput.value) { showError('Passwords do not match'); return; }
|
||||
if (!username || username.length < 2) { showError('사용자 이름은 최소 2자 이상이어야 합니다'); return; }
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(username)) { showError('사용자 이름: 영문자, 숫자, 하이픈, 밑줄만 사용 가능합니다'); return; }
|
||||
if (!password || password.length < 4) { showError('비밀번호는 최소 4자 이상이어야 합니다'); return; }
|
||||
if (password !== confirmInput.value) { showError('비밀번호가 일치하지 않습니다'); return; }
|
||||
} else if (mode === 'legacy-migrate') {
|
||||
if (!username || username.length < 2) { showError('Username must be at least 2 characters'); return; }
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(username)) { showError('Username: letters, numbers, dash, underscore only'); return; }
|
||||
if (!password) { showError('Please enter your current password'); return; }
|
||||
if (!username || username.length < 2) { showError('사용자 이름은 최소 2자 이상이어야 합니다'); return; }
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(username)) { showError('사용자 이름: 영문자, 숫자, 하이픈, 밑줄만 사용 가능합니다'); return; }
|
||||
if (!password) { showError('현재 비밀번호를 입력하세요'); return; }
|
||||
}
|
||||
|
||||
submitBtn.disabled = true;
|
||||
const btnText = submitBtn.textContent;
|
||||
submitBtn.textContent = mode === 'setup' ? 'Creating account...' : mode === 'legacy-migrate' ? 'Upgrading...' : 'Logging in...';
|
||||
submitBtn.textContent = mode === 'setup' ? '계정 생성 중...' : mode === 'legacy-migrate' ? '업그레이드 중...' : '로그인 중...';
|
||||
|
||||
try {
|
||||
let endpoint, body;
|
||||
@@ -421,12 +421,12 @@
|
||||
if (res.ok && data.success) {
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
showError(data.error || 'Authentication failed');
|
||||
showError(data.error || '인증 실패');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = btnText;
|
||||
}
|
||||
} catch (e) {
|
||||
showError('Connection error — please try again');
|
||||
showError('연결 오류 — 다시 시도해 주세요');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = btnText;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user